mirror of
https://github.com/defold/extension-websocket.git
synced 2025-09-30 09:12:18 +02:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ba2b8e4a69 | ||
|
176f213060 | ||
|
dc1d57d661 | ||
|
5b0a9960a8 | ||
|
40ba1b334c | ||
|
23ba179e2a | ||
|
36bf5d1c03 | ||
|
274f29d7e4 | ||
|
bd8569f49a | ||
|
18a768774f | ||
|
8e32fa3c76 | ||
|
832a156395 | ||
|
8df9eed682 | ||
|
071adac853 | ||
|
6ef040aee6 | ||
|
337a389cf8 | ||
|
b9f3563652 | ||
|
358e652946 | ||
|
44fc5037cb | ||
|
18ecf3857f | ||
|
b31e5bec7f | ||
|
61916f4c27 | ||
|
72f081bf23 | ||
|
ab1b52c676 | ||
|
2bd4e228c6 | ||
|
1202731ec8 | ||
|
f0cf078019 | ||
|
bd9e777b99 | ||
|
90a654e639 | ||
|
f3ea270c68 | ||
|
2bddcac495 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
github: defold
|
||||||
|
patreon: Defold
|
||||||
|
custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NBNBHTUW4GS4C']
|
19
.github/workflows/trigger-site-rebuild.yml
vendored
Normal file
19
.github/workflows/trigger-site-rebuild.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Trigger site rebuild
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
site-rebuild:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: 'Repository dispatch',
|
||||||
|
uses: defold/repository-dispatch@1.2.1,
|
||||||
|
with: {
|
||||||
|
repo: 'defold/defold.github.io',
|
||||||
|
token: '${{ secrets.SERVICES_GITHUB_TOKEN }}',
|
||||||
|
user: 'services@defold.se',
|
||||||
|
action: 'extension-websocket'
|
||||||
|
}
|
||||||
|
}]
|
24
README.md
24
README.md
@@ -1,4 +1,22 @@
|
|||||||
# Native extension template
|
# Defold websocket extension
|
||||||
This template contains the basic setup for creation of a Defold native extension.
|
|
||||||
|
|
||||||
You can learn more about native extensions in the [official manual](https://www.defold.com/manuals/extensions/).
|
## Installation
|
||||||
|
To use this library in your Defold project, add the following URL to your `game.project` dependencies:
|
||||||
|
|
||||||
|
https://github.com/defold/extension-websocket/archive/master.zip
|
||||||
|
|
||||||
|
We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-websocket/releases).
|
||||||
|
|
||||||
|
## API reference
|
||||||
|
|
||||||
|
https://defold.com/extension-websocket/api/
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
In order to make it easier to debug this extension, we provide a `game.project` setting `websocket.debug`.
|
||||||
|
|
||||||
|
Set it to:
|
||||||
|
|
||||||
|
* `0` to disable debugging (i.e. no debug output).
|
||||||
|
* `1` to display state changes.
|
||||||
|
* `2` to display the messages sent and received.
|
||||||
|
5523
docs/Defold-Websocket/DefoldWebsocket.symbols
Normal file
5523
docs/Defold-Websocket/DefoldWebsocket.symbols
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/Defold-Websocket/DefoldWebsocket.wasm
Normal file
BIN
docs/Defold-Websocket/DefoldWebsocket.wasm
Normal file
Binary file not shown.
3882
docs/Defold-Websocket/DefoldWebsocket_asmjs.js
Normal file
3882
docs/Defold-Websocket/DefoldWebsocket_asmjs.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/Defold-Websocket/DefoldWebsocket_wasm.js
Normal file
1
docs/Defold-Websocket/DefoldWebsocket_wasm.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/Defold-Websocket/archive/archive_files.json
Normal file
1
docs/Defold-Websocket/archive/archive_files.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"content":[{"name":"game.projectc","size":3154,"pieces":[{"name":"game.projectc0","offset":0}]},{"name":"game.arci","size":2208,"pieces":[{"name":"game.arci0","offset":0}]},{"name":"game.arcd","size":34370,"pieces":[{"name":"game.arcd0","offset":0}]},{"name":"game.dmanifest","size":4314,"pieces":[{"name":"game.dmanifest0","offset":0}]},{"name":"game.public.der","size":162,"pieces":[{"name":"game.public.der0","offset":0}]}]}
|
BIN
docs/Defold-Websocket/archive/game.arcd0
Normal file
BIN
docs/Defold-Websocket/archive/game.arcd0
Normal file
Binary file not shown.
BIN
docs/Defold-Websocket/archive/game.arci0
Normal file
BIN
docs/Defold-Websocket/archive/game.arci0
Normal file
Binary file not shown.
BIN
docs/Defold-Websocket/archive/game.dmanifest0
Normal file
BIN
docs/Defold-Websocket/archive/game.dmanifest0
Normal file
Binary file not shown.
184
docs/Defold-Websocket/archive/game.projectc0
Normal file
184
docs/Defold-Websocket/archive/game.projectc0
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
[project]
|
||||||
|
title = Defold-Websocket
|
||||||
|
version = 1.0.0
|
||||||
|
write_log = 0
|
||||||
|
compress_archive = 1
|
||||||
|
publisher = unnamed
|
||||||
|
developer = unnamed
|
||||||
|
|
||||||
|
[display]
|
||||||
|
width = 640
|
||||||
|
height = 1136
|
||||||
|
high_dpi = 0
|
||||||
|
samples = 0
|
||||||
|
fullscreen = 0
|
||||||
|
update_frequency = 0
|
||||||
|
vsync = 1
|
||||||
|
display_profiles = /builtins/render/default.display_profilesc
|
||||||
|
dynamic_orientation = 0
|
||||||
|
|
||||||
|
[render]
|
||||||
|
clear_color_red = 0
|
||||||
|
clear_color_green = 0
|
||||||
|
clear_color_blue = 0
|
||||||
|
clear_color_alpha = 0
|
||||||
|
|
||||||
|
[physics]
|
||||||
|
type = 2D
|
||||||
|
gravity_y = -10
|
||||||
|
debug = 0
|
||||||
|
debug_alpha = 0.9
|
||||||
|
world_count = 4
|
||||||
|
gravity_x = 0
|
||||||
|
gravity_z = 0
|
||||||
|
scale = 0.02
|
||||||
|
allow_dynamic_transforms = 0
|
||||||
|
debug_scale = 30
|
||||||
|
max_collisions = 64
|
||||||
|
max_contacts = 128
|
||||||
|
contact_impulse_limit = 0
|
||||||
|
ray_cast_limit_2d = 64
|
||||||
|
ray_cast_limit_3d = 128
|
||||||
|
trigger_overlap_capacity = 16
|
||||||
|
|
||||||
|
[bootstrap]
|
||||||
|
main_collection = /examples/websocket.collectionc
|
||||||
|
render = /builtins/render/default.renderc
|
||||||
|
|
||||||
|
[graphics]
|
||||||
|
default_texture_min_filter = linear
|
||||||
|
default_texture_mag_filter = linear
|
||||||
|
max_draw_calls = 1024
|
||||||
|
max_characters = 8192
|
||||||
|
max_debug_vertices = 10000
|
||||||
|
texture_profiles = /builtins/graphics/default.texture_profiles
|
||||||
|
verify_graphics_calls = 1
|
||||||
|
memory_size = 512
|
||||||
|
|
||||||
|
[shader]
|
||||||
|
output_spirv = 0
|
||||||
|
|
||||||
|
[sound]
|
||||||
|
gain = 1
|
||||||
|
max_sound_data = 128
|
||||||
|
max_sound_buffers = 32
|
||||||
|
max_sound_sources = 16
|
||||||
|
max_sound_instances = 256
|
||||||
|
max_component_count = 32
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
http_cache = 0
|
||||||
|
max_resources = 1024
|
||||||
|
|
||||||
|
[input]
|
||||||
|
repeat_delay = 0.5
|
||||||
|
repeat_interval = 0.2
|
||||||
|
gamepads = /builtins/input/default.gamepadsc
|
||||||
|
game_binding = /input/game.input_bindingc
|
||||||
|
use_accelerometer = 1
|
||||||
|
|
||||||
|
[sprite]
|
||||||
|
max_count = 128
|
||||||
|
subpixels = 1
|
||||||
|
|
||||||
|
[spine]
|
||||||
|
max_count = 128
|
||||||
|
|
||||||
|
[model]
|
||||||
|
max_count = 128
|
||||||
|
|
||||||
|
[mesh]
|
||||||
|
max_count = 128
|
||||||
|
|
||||||
|
[gui]
|
||||||
|
max_count = 64
|
||||||
|
max_particlefx_count = 64
|
||||||
|
max_particle_count = 1024
|
||||||
|
|
||||||
|
[collection]
|
||||||
|
max_instances = 1024
|
||||||
|
max_input_stack_entries = 16
|
||||||
|
|
||||||
|
[collection_proxy]
|
||||||
|
max_count = 8
|
||||||
|
|
||||||
|
[collectionfactory]
|
||||||
|
max_count = 128
|
||||||
|
|
||||||
|
[factory]
|
||||||
|
max_count = 128
|
||||||
|
|
||||||
|
[ios]
|
||||||
|
launch_screen = /builtins/manifests/ios/LaunchScreen.storyboardc
|
||||||
|
pre_renderered_icons = 0
|
||||||
|
bundle_identifier = com.defold.websocket
|
||||||
|
infoplist = /builtins/manifests/ios/Info.plist
|
||||||
|
default_language = en
|
||||||
|
localizations = en
|
||||||
|
|
||||||
|
[android]
|
||||||
|
version_code = 1
|
||||||
|
minimum_sdk_version = 16
|
||||||
|
target_sdk_version = 29
|
||||||
|
package = com.defold.websocket
|
||||||
|
manifest = /builtins/manifests/android/AndroidManifest.xml
|
||||||
|
iap_provider = GooglePlay
|
||||||
|
input_method = KeyEvent
|
||||||
|
immersive_mode = 0
|
||||||
|
display_cutout = 1
|
||||||
|
debuggable = 0
|
||||||
|
|
||||||
|
[osx]
|
||||||
|
infoplist = /builtins/manifests/osx/Info.plist
|
||||||
|
bundle_identifier = com.defold.websocket
|
||||||
|
default_language = en
|
||||||
|
localizations = en
|
||||||
|
|
||||||
|
[windows]
|
||||||
|
|
||||||
|
[html5]
|
||||||
|
custom_heap_size = 0
|
||||||
|
heap_size = 256
|
||||||
|
htmlfile = /builtins/manifests/web/engine_template.html
|
||||||
|
cssfile = /builtins/manifests/web/light_theme.css
|
||||||
|
archive_location_prefix = archive
|
||||||
|
show_fullscreen_button = 1
|
||||||
|
show_made_with_defold = 1
|
||||||
|
scale_mode = downscale_fit
|
||||||
|
|
||||||
|
[particle_fx]
|
||||||
|
max_count = 64
|
||||||
|
max_particle_count = 1024
|
||||||
|
|
||||||
|
[iap]
|
||||||
|
auto_finish_transactions = 1
|
||||||
|
|
||||||
|
[network]
|
||||||
|
http_timeout = 0
|
||||||
|
http_thread_count = 4
|
||||||
|
http_cache_enabled = 1
|
||||||
|
|
||||||
|
[library]
|
||||||
|
include_dirs = websocket
|
||||||
|
|
||||||
|
[script]
|
||||||
|
shared_state = 1
|
||||||
|
|
||||||
|
[label]
|
||||||
|
max_count = 64
|
||||||
|
subpixels = 1
|
||||||
|
|
||||||
|
[profiler]
|
||||||
|
track_cpu = 0
|
||||||
|
|
||||||
|
[liveupdate]
|
||||||
|
settings = /liveupdate.settings
|
||||||
|
enabled = 1
|
||||||
|
|
||||||
|
[tilemap]
|
||||||
|
max_count = 16
|
||||||
|
max_tile_count = 2048
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
run_while_iconified = 0
|
||||||
|
|
BIN
docs/Defold-Websocket/archive/game.public.der0
Normal file
BIN
docs/Defold-Websocket/archive/game.public.der0
Normal file
Binary file not shown.
702
docs/Defold-Websocket/dmloader.js
Normal file
702
docs/Defold-Websocket/dmloader.js
Normal file
@@ -0,0 +1,702 @@
|
|||||||
|
/* ********************************************************************* */
|
||||||
|
/* Load and combine data that is split into archives */
|
||||||
|
/* ********************************************************************* */
|
||||||
|
|
||||||
|
var Combine = {
|
||||||
|
_targets: [],
|
||||||
|
_targetIndex: 0,
|
||||||
|
// target: build target
|
||||||
|
// name: intended filepath of built object
|
||||||
|
// size: expected size of built object.
|
||||||
|
// data: combined data
|
||||||
|
// downloaded: total amount of data downloaded
|
||||||
|
// pieces: array of name, offset and data objects
|
||||||
|
// numExpectedFiles: total number of files expected in description
|
||||||
|
// lastRequestedPiece: index of last data file requested (strictly ascending)
|
||||||
|
// totalLoadedPieces: counts the number of data files received
|
||||||
|
|
||||||
|
//MAX_CONCURRENT_XHR: 6, // remove comment if throttling of XHR is desired.
|
||||||
|
|
||||||
|
isCompleted: false, // status of process
|
||||||
|
|
||||||
|
_onCombineCompleted: [], // signature: name, data.
|
||||||
|
_onAllTargetsBuilt:[], // signature: void
|
||||||
|
_onDownloadProgress: [], // signature: downloaded, total
|
||||||
|
|
||||||
|
_currentDownloadBytes: 0,
|
||||||
|
_totalDownloadBytes: 0,
|
||||||
|
|
||||||
|
_retry_time: 0, // pause before retry file loading after error
|
||||||
|
_max_retry_count: 0, // how many attempts we do when trying to download a file.
|
||||||
|
_can_not_download_file_callback: undefined, //Function that is called if you can't download file after 'retry_count' attempts.
|
||||||
|
|
||||||
|
_archiveLocationFilter: function(path) { return "split" + path; },
|
||||||
|
|
||||||
|
can_not_download_file: function(file) {
|
||||||
|
if (typeof Combine._can_not_download_file_callback === 'function') {
|
||||||
|
Combine._can_not_download_file_callback(file);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addProgressListener: function(callback) {
|
||||||
|
if (typeof callback !== 'function') {
|
||||||
|
throw "Invalid callback registration";
|
||||||
|
}
|
||||||
|
this._onDownloadProgress.push(callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
addCombineCompletedListener: function(callback) {
|
||||||
|
if (typeof callback !== 'function') {
|
||||||
|
throw "Invalid callback registration";
|
||||||
|
}
|
||||||
|
this._onCombineCompleted.push(callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
addAllTargetsBuiltListener: function(callback) {
|
||||||
|
if (typeof callback !== 'function') {
|
||||||
|
throw "Invalid callback registration";
|
||||||
|
}
|
||||||
|
this._onAllTargetsBuilt.push(callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
// descriptUrl: location of text file describing files to be preloaded
|
||||||
|
process: function(descriptUrl, attempt_count) {
|
||||||
|
if (!attempt_count) {
|
||||||
|
attempt_count = 0;
|
||||||
|
}
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', descriptUrl);
|
||||||
|
xhr.responseType = 'text';
|
||||||
|
xhr.onload = function(evt) {
|
||||||
|
Combine.onReceiveDescription(xhr);
|
||||||
|
};
|
||||||
|
xhr.onerror = function(evt) {
|
||||||
|
attempt_count += 1;
|
||||||
|
if (attempt_count < Combine._max_retry_count) {
|
||||||
|
console.warn("Can't download file '" + descriptUrl + "' . Next try in " + Combine._retry_time + " sec.");
|
||||||
|
setTimeout(function() {
|
||||||
|
Combine.process(descriptUrl, attempt_count);
|
||||||
|
}, Combine._retry_time * 1000);
|
||||||
|
} else {
|
||||||
|
Combine.can_not_download_file(descriptUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanUp: function() {
|
||||||
|
this._targets = [];
|
||||||
|
this._targetIndex = 0;
|
||||||
|
this.isCompleted = false;
|
||||||
|
this._onCombineCompleted = [];
|
||||||
|
this._onAllTargetsBuilt = [];
|
||||||
|
this._onDownloadProgress = [];
|
||||||
|
|
||||||
|
this._currentDownloadBytes = 0;
|
||||||
|
this._totalDownloadBytes = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
onReceiveDescription: function(xhr) {
|
||||||
|
var json = JSON.parse(xhr.responseText);
|
||||||
|
this._targets = json.content;
|
||||||
|
this._totalDownloadBytes = 0;
|
||||||
|
this._currentDownloadBytes = 0;
|
||||||
|
|
||||||
|
var targets = this._targets;
|
||||||
|
for(var i=0; i<targets.length; ++i) {
|
||||||
|
this._totalDownloadBytes += targets[i].size;
|
||||||
|
}
|
||||||
|
this.requestContent();
|
||||||
|
},
|
||||||
|
|
||||||
|
requestContent: function() {
|
||||||
|
var target = this._targets[this._targetIndex];
|
||||||
|
if (1 < target.pieces.length) {
|
||||||
|
target.data = new Uint8Array(target.size);
|
||||||
|
}
|
||||||
|
var limit = target.pieces.length;
|
||||||
|
if (typeof this.MAX_CONCURRENT_XHR !== 'undefined') {
|
||||||
|
limit = Math.min(limit, this.MAX_CONCURRENT_XHR);
|
||||||
|
}
|
||||||
|
for (var i=0; i<limit; ++i) {
|
||||||
|
this.requestPiece(target, i);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
requestPiece: function(target, index, attempt_count) {
|
||||||
|
if (!attempt_count) {
|
||||||
|
attempt_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < target.lastRequestedPiece) {
|
||||||
|
throw "Request out of order";
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = target.pieces[index];
|
||||||
|
target.lastRequestedPiece = index;
|
||||||
|
|
||||||
|
var total = 0;
|
||||||
|
var downloaded = 0;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
var url = this._archiveLocationFilter('/' + item.name);
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.responseType = 'arraybuffer';
|
||||||
|
// called periodically with information about the transaction
|
||||||
|
xhr.onprogress = function(evt) {
|
||||||
|
if (evt.total && evt.lengthComputable) {
|
||||||
|
total = evt.total;
|
||||||
|
}
|
||||||
|
if (evt.loaded && evt.lengthComputable) {
|
||||||
|
var delta = evt.loaded - downloaded;
|
||||||
|
downloaded = evt.loaded;
|
||||||
|
Combine._currentDownloadBytes += delta;
|
||||||
|
Combine.updateProgress(target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// called when the transaction completes successfully
|
||||||
|
xhr.onload = function(evt) {
|
||||||
|
item.data = new Uint8Array(xhr.response);
|
||||||
|
item.dataLength = item.data.length;
|
||||||
|
total = item.dataLength;
|
||||||
|
downloaded = item.dataLength;
|
||||||
|
Combine.copyData(target, item);
|
||||||
|
Combine.onPieceLoaded(target, item);
|
||||||
|
Combine.updateProgress(target);
|
||||||
|
item.data = undefined;
|
||||||
|
};
|
||||||
|
// called when the transaction fails
|
||||||
|
xhr.onerror = function(evt) {
|
||||||
|
downloaded = 0;
|
||||||
|
Combine.updateProgress(target);
|
||||||
|
attempt_count += 1;
|
||||||
|
if (attempt_count < Combine._max_retry_count) {
|
||||||
|
console.warn("Can't download file '" + item.name + "' . Next try in " + Combine._retry_time + " sec.");
|
||||||
|
setTimeout(function() {
|
||||||
|
Combine.requestPiece(target, index, attempt_count);
|
||||||
|
}, Combine._retry_time * 1000);
|
||||||
|
} else {
|
||||||
|
Combine.can_not_download_file(item.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateProgress: function(target) {
|
||||||
|
for(i = 0; i<this._onDownloadProgress.length; ++i) {
|
||||||
|
this._onDownloadProgress[i](this._currentDownloadBytes, this._totalDownloadBytes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
copyData: function(target, item) {
|
||||||
|
if (1 == target.pieces.length) {
|
||||||
|
target.data = item.data;
|
||||||
|
} else {
|
||||||
|
var start = item.offset;
|
||||||
|
var end = start + item.data.length;
|
||||||
|
if (0 > start) {
|
||||||
|
throw "Buffer underflow";
|
||||||
|
}
|
||||||
|
if (end > target.data.length) {
|
||||||
|
throw "Buffer overflow";
|
||||||
|
}
|
||||||
|
target.data.set(item.data, item.offset);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onPieceLoaded: function(target, item) {
|
||||||
|
if (typeof target.totalLoadedPieces === 'undefined') {
|
||||||
|
target.totalLoadedPieces = 0;
|
||||||
|
}
|
||||||
|
++target.totalLoadedPieces;
|
||||||
|
if (target.totalLoadedPieces == target.pieces.length) {
|
||||||
|
this.finalizeTarget(target);
|
||||||
|
++this._targetIndex;
|
||||||
|
for (var i=0; i<this._onCombineCompleted.length; ++i) {
|
||||||
|
this._onCombineCompleted[i](target.name, target.data);
|
||||||
|
}
|
||||||
|
if (this._targetIndex < this._targets.length) {
|
||||||
|
this.requestContent();
|
||||||
|
} else {
|
||||||
|
this.isCompleted = true;
|
||||||
|
for (i=0; i<this._onAllTargetsBuilt.length; ++i) {
|
||||||
|
this._onAllTargetsBuilt[i]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var next = target.lastRequestedPiece + 1;
|
||||||
|
if (next < target.pieces.length) {
|
||||||
|
this.requestPiece(target, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
finalizeTarget: function(target) {
|
||||||
|
var actualSize = 0;
|
||||||
|
for (var i=0;i<target.pieces.length; ++i) {
|
||||||
|
actualSize += target.pieces[i].dataLength;
|
||||||
|
}
|
||||||
|
if (actualSize != target.size) {
|
||||||
|
throw "Unexpected data size";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 < target.pieces.length) {
|
||||||
|
var output = target.data;
|
||||||
|
var pieces = target.pieces;
|
||||||
|
for (i=0; i<pieces.length; ++i) {
|
||||||
|
var item = pieces[i];
|
||||||
|
// Bounds check
|
||||||
|
var start = item.offset;
|
||||||
|
var end = start + item.dataLength;
|
||||||
|
if (0 < i) {
|
||||||
|
var previous = pieces[i - 1];
|
||||||
|
if (previous.offset + previous.dataLength > start) {
|
||||||
|
throw "Segment underflow";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pieces.length - 2 > i) {
|
||||||
|
var next = pieces[i + 1];
|
||||||
|
if (end > next.offset) {
|
||||||
|
throw "Segment overflow";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ********************************************************************* */
|
||||||
|
/* Default splash and progress visualisation */
|
||||||
|
/* ********************************************************************* */
|
||||||
|
|
||||||
|
var Progress = {
|
||||||
|
progress_id: "defold-progress",
|
||||||
|
bar_id: "defold-progress-bar",
|
||||||
|
|
||||||
|
addProgress : function (canvas) {
|
||||||
|
/* Insert default progress bar below canvas */
|
||||||
|
canvas.insertAdjacentHTML('afterend', '<div id="' + Progress.progress_id + '" class="canvas-app-progress"><div id="' + Progress.bar_id + '" class="canvas-app-progress-bar" style="width: 0%;"></div></div>');
|
||||||
|
Progress.bar = document.getElementById(Progress.bar_id);
|
||||||
|
Progress.progress = document.getElementById(Progress.progress_id);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateProgress: function (percentage, text) {
|
||||||
|
Progress.bar.style.width = percentage + "%";
|
||||||
|
},
|
||||||
|
|
||||||
|
removeProgress: function () {
|
||||||
|
if (Progress.progress.parentElement !== null) {
|
||||||
|
Progress.progress.parentElement.removeChild(Progress.progress);
|
||||||
|
|
||||||
|
// Remove any background/splash image that was set in runApp().
|
||||||
|
// Workaround for Safari bug DEF-3061.
|
||||||
|
Module.canvas.style.background = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ********************************************************************* */
|
||||||
|
/* Default input override */
|
||||||
|
/* ********************************************************************* */
|
||||||
|
|
||||||
|
var CanvasInput = {
|
||||||
|
arrowKeysHandler : function(e) {
|
||||||
|
switch(e.keyCode) {
|
||||||
|
case 37: case 38: case 39: case 40: // Arrow keys
|
||||||
|
case 32: e.preventDefault(); e.stopPropagation(); // Space
|
||||||
|
default: break; // do not block other keys
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFocusIn : function(e) {
|
||||||
|
window.addEventListener("keydown", CanvasInput.arrowKeysHandler, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
onFocusOut: function(e) {
|
||||||
|
window.removeEventListener("keydown", CanvasInput.arrowKeysHandler, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
addToCanvas : function(canvas) {
|
||||||
|
canvas.addEventListener("focus", CanvasInput.onFocusIn, false);
|
||||||
|
canvas.addEventListener("blur", CanvasInput.onFocusOut, false);
|
||||||
|
canvas.focus();
|
||||||
|
CanvasInput.onFocusIn();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ********************************************************************* */
|
||||||
|
/* Module is Emscripten namespace */
|
||||||
|
/* ********************************************************************* */
|
||||||
|
|
||||||
|
var Module = {
|
||||||
|
noInitialRun: true,
|
||||||
|
|
||||||
|
_filesToPreload: [],
|
||||||
|
_archiveLoaded: false,
|
||||||
|
_preLoadDone: false,
|
||||||
|
_waitingForArchive: false,
|
||||||
|
|
||||||
|
// Persistent storage
|
||||||
|
persistentStorage: true,
|
||||||
|
_syncInProgress: false,
|
||||||
|
_syncNeeded: false,
|
||||||
|
_syncInitial: false,
|
||||||
|
_syncMaxTries: 3,
|
||||||
|
_syncTries: 0,
|
||||||
|
|
||||||
|
print: function(text) { console.log(text); },
|
||||||
|
printErr: function(text) { console.error(text); },
|
||||||
|
|
||||||
|
setStatus: function(text) { console.log(text); },
|
||||||
|
|
||||||
|
isWASMSupported: (function() {
|
||||||
|
try {
|
||||||
|
if (typeof WebAssembly === "object"
|
||||||
|
&& typeof WebAssembly.instantiate === "function") {
|
||||||
|
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
|
||||||
|
if (module instanceof WebAssembly.Module)
|
||||||
|
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})(),
|
||||||
|
|
||||||
|
prepareErrorObject: function (err, url, line, column, errObj) {
|
||||||
|
line = typeof line == "undefined" ? 0 : line;
|
||||||
|
column = typeof column == "undefined" ? 0 : column;
|
||||||
|
url = typeof url == "undefined" ? "" : url;
|
||||||
|
var errorLine = url + ":" + line + ":" + column;
|
||||||
|
|
||||||
|
var error = errObj || (typeof window.event != "undefined" ? window.event.error : "" ) || err || "Undefined Error";
|
||||||
|
var message = "";
|
||||||
|
var stack = "";
|
||||||
|
var backtrace = "";
|
||||||
|
|
||||||
|
if (typeof error == "object" && typeof error.stack != "undefined" && typeof error.message != "undefined") {
|
||||||
|
stack = String(error.stack);
|
||||||
|
message = String(error.message);
|
||||||
|
} else {
|
||||||
|
stack = String(error).split("\n");
|
||||||
|
message = stack.shift();
|
||||||
|
stack = stack.join("\n");
|
||||||
|
}
|
||||||
|
stack = stack || errorLine;
|
||||||
|
|
||||||
|
var callLine = /at (\S+:\d*$)/.exec(message);
|
||||||
|
if (callLine) {
|
||||||
|
message = message.replace(/(at \S+:\d*$)/, "");
|
||||||
|
stack = callLine[1] + "\n" + stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = message.replace(/(abort\(.+\)) at .+/, "$1");
|
||||||
|
stack = stack.replace(/\?{1}\S+(:\d+:\d+)/g, "$1");
|
||||||
|
stack = stack.replace(/ *at (\S+)$/gm, "@$1");
|
||||||
|
stack = stack.replace(/ *at (\S+)(?: \[as \S+\])? +\((.+)\)/g, "$1@$2");
|
||||||
|
stack = stack.replace(/^((?:Object|Array)\.)/gm, "");
|
||||||
|
stack = stack.split("\n");
|
||||||
|
|
||||||
|
return { stack:stack, message:message };
|
||||||
|
},
|
||||||
|
|
||||||
|
hasWebGLSupport: function() {
|
||||||
|
var webgl_support = false;
|
||||||
|
try {
|
||||||
|
var canvas = document.createElement("canvas");
|
||||||
|
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||||
|
if (gl && gl instanceof WebGLRenderingContext) {
|
||||||
|
webgl_support = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("An error occurred while detecting WebGL support: " + error);
|
||||||
|
webgl_support = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return webgl_support;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleVisibilityChange: function () {
|
||||||
|
GLFW.onFocusChanged(document[Module.hiddenProperty] ? 0 : 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
getHiddenProperty: function () {
|
||||||
|
if ('hidden' in document) return 'hidden';
|
||||||
|
var prefixes = ['webkit','moz','ms','o'];
|
||||||
|
for (var i = 0; i < prefixes.length; i++) {
|
||||||
|
if ((prefixes[i] + 'Hidden') in document)
|
||||||
|
return prefixes[i] + 'Hidden';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
setupVisibilityChangeListener: function() {
|
||||||
|
Module.hiddenProperty = Module.getHiddenProperty();
|
||||||
|
if( Module.hiddenProperty ) {
|
||||||
|
var eventName = Module.hiddenProperty.replace(/[H|h]idden/,'') + 'visibilitychange';
|
||||||
|
document.addEventListener(eventName, Module.handleVisibilityChange, false);
|
||||||
|
} else {
|
||||||
|
console.log("No document.hidden property found. The focus events won't be enabled.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module.runApp - Starts the application given a canvas element id
|
||||||
|
*
|
||||||
|
* 'extra_params' is an optional object that can have the following fields:
|
||||||
|
*
|
||||||
|
* 'archive_location_filter':
|
||||||
|
* Filter function that will run for each archive path.
|
||||||
|
*
|
||||||
|
* 'unsupported_webgl_callback':
|
||||||
|
* Function that is called if WebGL is not supported.
|
||||||
|
*
|
||||||
|
* 'engine_arguments':
|
||||||
|
* List of arguments (strings) that will be passed to the engine.
|
||||||
|
*
|
||||||
|
* 'persistent_storage':
|
||||||
|
* Boolean toggling the usage of persistent storage.
|
||||||
|
*
|
||||||
|
* 'custom_heap_size':
|
||||||
|
* Number of bytes specifying the memory heap size.
|
||||||
|
*
|
||||||
|
* 'disable_context_menu':
|
||||||
|
* Disables the right-click context menu on the canvas element if true.
|
||||||
|
*
|
||||||
|
* 'retry_time':
|
||||||
|
* Pause before retry file loading after error.
|
||||||
|
*
|
||||||
|
* 'retry_count':
|
||||||
|
* How many attempts we do when trying to download a file.
|
||||||
|
*
|
||||||
|
* 'can_not_download_file_callback':
|
||||||
|
* Function that is called if you can't download file after 'retry_count' attempts.
|
||||||
|
**/
|
||||||
|
runApp: function(app_canvas_id, extra_params) {
|
||||||
|
app_canvas_id = (typeof app_canvas_id === 'undefined') ? 'canvas' : app_canvas_id;
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
archive_location_filter: function(path) { return 'split' + path; },
|
||||||
|
unsupported_webgl_callback: undefined,
|
||||||
|
engine_arguments: [],
|
||||||
|
persistent_storage: true,
|
||||||
|
custom_heap_size: undefined,
|
||||||
|
disable_context_menu: true,
|
||||||
|
retry_time: 1,
|
||||||
|
retry_count: 10,
|
||||||
|
can_not_download_file_callback: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var k in extra_params) {
|
||||||
|
if (extra_params.hasOwnProperty(k)) {
|
||||||
|
params[k] = extra_params[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Module.canvas = document.getElementById(app_canvas_id);
|
||||||
|
Module.arguments = params["engine_arguments"];
|
||||||
|
Module.persistentStorage = params["persistent_storage"];
|
||||||
|
Module["TOTAL_MEMORY"] = params["custom_heap_size"];
|
||||||
|
|
||||||
|
if (Module.hasWebGLSupport()) {
|
||||||
|
// Override game keys
|
||||||
|
CanvasInput.addToCanvas(Module.canvas);
|
||||||
|
|
||||||
|
Module.setupVisibilityChangeListener();
|
||||||
|
|
||||||
|
// Add progress visuals
|
||||||
|
Progress.addProgress(Module.canvas);
|
||||||
|
|
||||||
|
// Add context menu hide-handler if requested
|
||||||
|
if (params["disable_context_menu"])
|
||||||
|
{
|
||||||
|
Module.canvas.oncontextmenu = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Combine._retry_time = params["retry_time"];
|
||||||
|
Combine._max_retry_count = params["retry_count"];
|
||||||
|
if (typeof params["can_not_download_file_callback"] === "function") {
|
||||||
|
Combine._can_not_download_file_callback = params["can_not_download_file_callback"];
|
||||||
|
}
|
||||||
|
// Load and assemble archive
|
||||||
|
Combine.addCombineCompletedListener(Module.onArchiveFileLoaded);
|
||||||
|
Combine.addAllTargetsBuiltListener(Module.onArchiveLoaded);
|
||||||
|
Combine.addProgressListener(Module.onArchiveLoadProgress);
|
||||||
|
Combine._archiveLocationFilter = params["archive_location_filter"];
|
||||||
|
Combine.process(Combine._archiveLocationFilter('/archive_files.json'));
|
||||||
|
} else {
|
||||||
|
Progress.addProgress(Module.canvas);
|
||||||
|
Progress.updateProgress(100, "Unable to start game, WebGL not supported");
|
||||||
|
Module.setStatus = function(text) {
|
||||||
|
if (text) Module.printErr('[missing WebGL] ' + text);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof params["unsupported_webgl_callback"] === "function") {
|
||||||
|
params["unsupported_webgl_callback"]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onArchiveLoadProgress: function(downloaded, total) {
|
||||||
|
Progress.updateProgress(downloaded / total * 100);
|
||||||
|
},
|
||||||
|
|
||||||
|
onArchiveFileLoaded: function(name, data) {
|
||||||
|
Module._filesToPreload.push({path: name, data: data});
|
||||||
|
},
|
||||||
|
|
||||||
|
onArchiveLoaded: function() {
|
||||||
|
Combine.cleanUp();
|
||||||
|
Module._archiveLoaded = true;
|
||||||
|
Progress.updateProgress(100, "Starting...");
|
||||||
|
|
||||||
|
if (Module._waitingForArchive) {
|
||||||
|
Module._preloadAndCallMain();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleFullscreen: function() {
|
||||||
|
if (GLFW.isFullscreen) {
|
||||||
|
GLFW.cancelFullScreen();
|
||||||
|
} else {
|
||||||
|
GLFW.requestFullScreen();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
preSync: function(done) {
|
||||||
|
// Initial persistent sync before main is called
|
||||||
|
FS.syncfs(true, function(err) {
|
||||||
|
if(err) {
|
||||||
|
Module._syncTries += 1;
|
||||||
|
console.error("FS syncfs error: " + err);
|
||||||
|
if (Module._syncMaxTries > Module._syncTries) {
|
||||||
|
Module.preSync(done);
|
||||||
|
} else {
|
||||||
|
Module._syncInitial = true;
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Module._syncInitial = true;
|
||||||
|
if (done !== undefined) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
preloadAll: function() {
|
||||||
|
if (Module._preLoadDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Module._preLoadDone = true;
|
||||||
|
for (var i = 0; i < Module._filesToPreload.length; ++i) {
|
||||||
|
var item = Module._filesToPreload[i];
|
||||||
|
FS.createPreloadedFile("", item.path, item.data, true, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tries to do a MEM->IDB sync
|
||||||
|
// It will flag that another one is needed if there is already one sync running.
|
||||||
|
persistentSync: function() {
|
||||||
|
|
||||||
|
// Need to wait for the initial sync to finish since it
|
||||||
|
// will call close on all its file streams which will trigger
|
||||||
|
// new persistentSync for each.
|
||||||
|
if (Module._syncInitial) {
|
||||||
|
if (Module._syncInProgress) {
|
||||||
|
Module._syncNeeded = true;
|
||||||
|
} else {
|
||||||
|
Module._startSyncFS();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
preInit: [function() {
|
||||||
|
/* Mount filesystem on preinit */
|
||||||
|
var dir = DMSYS.GetUserPersistentDataRoot();
|
||||||
|
FS.mkdir(dir);
|
||||||
|
|
||||||
|
// If IndexedDB is supported we mount the persistent data root as IDBFS,
|
||||||
|
// then try to do a IDB->MEM sync before we start the engine to get
|
||||||
|
// previously saved data before boot.
|
||||||
|
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||||
|
if (Module.persistentStorage && window.indexedDB) {
|
||||||
|
FS.mount(IDBFS, {}, dir);
|
||||||
|
|
||||||
|
// Patch FS.close so it will try to sync MEM->IDB
|
||||||
|
var _close = FS.close; FS.close = function(stream) { var r = _close(stream); Module.persistentSync(); return r; }
|
||||||
|
|
||||||
|
// Sync IDB->MEM before calling main()
|
||||||
|
Module.preSync(function() {
|
||||||
|
Module._preloadAndCallMain();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Module._preloadAndCallMain();
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
preRun: [function() {
|
||||||
|
/* If archive is loaded, preload all its files */
|
||||||
|
if(Module._archiveLoaded) {
|
||||||
|
Module.preloadAll();
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
postRun: [function() {
|
||||||
|
if(Module._archiveLoaded) {
|
||||||
|
Progress.removeProgress();
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
_preloadAndCallMain: function() {
|
||||||
|
// If the archive isn't loaded,
|
||||||
|
// we will have to wait with calling main.
|
||||||
|
if (!Module._archiveLoaded) {
|
||||||
|
Module._waitingForArchive = true;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Need to set heap size before calling main
|
||||||
|
TOTAL_MEMORY = Module["TOTAL_MEMORY"] || TOTAL_MEMORY;
|
||||||
|
|
||||||
|
Module.preloadAll();
|
||||||
|
Progress.removeProgress();
|
||||||
|
if (Module.callMain === undefined) {
|
||||||
|
Module.noInitialRun = false;
|
||||||
|
} else {
|
||||||
|
Module.callMain(Module.arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Wrap IDBFS syncfs call with logic to avoid multiple syncs
|
||||||
|
// running at the same time.
|
||||||
|
_startSyncFS: function() {
|
||||||
|
Module._syncInProgress = true;
|
||||||
|
|
||||||
|
if (Module._syncMaxTries > Module._syncTries) {
|
||||||
|
FS.syncfs(false, function(err) {
|
||||||
|
Module._syncInProgress = false;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
console.error("Module._startSyncFS error: " + err);
|
||||||
|
Module._syncTries += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Module._syncNeeded) {
|
||||||
|
Module._syncNeeded = false;
|
||||||
|
Module._startSyncFS();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onerror = function(err, url, line, column, errObj) {
|
||||||
|
var errorObject = Module.prepareErrorObject(err, url, line, column, errObj);
|
||||||
|
Module.ccall('JSWriteDump', 'null', ['string'], [JSON.stringify(errorObject.stack)]);
|
||||||
|
Module.setStatus('Exception thrown, see JavaScript console');
|
||||||
|
Module.setStatus = function(text) {
|
||||||
|
if (text) Module.printErr('[post-exception status] ' + text);
|
||||||
|
};
|
||||||
|
};
|
245
docs/Defold-Websocket/index.html
Normal file
245
docs/Defold-Websocket/index.html
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, minimal-ui, shrink-to-fit=no">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<!-- The above 4 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||||
|
|
||||||
|
<title>Defold-Websocket 1.0.0</title>
|
||||||
|
<style type='text/css'>
|
||||||
|
/* Disable user selection to avoid strange bug in Chrome on Windows:
|
||||||
|
* Selecting a text outside the canvas, then clicking+draging would
|
||||||
|
* drag the selected text but block mouse down/up events to the engine.
|
||||||
|
*/
|
||||||
|
body {
|
||||||
|
|
||||||
|
position: fixed; /* Prevent overscroll */
|
||||||
|
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-app-container:-webkit-full-screen {
|
||||||
|
/* Auto width and height in Safari/Chrome fullscreen. */
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
outline: none;
|
||||||
|
border: 0;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas:focus, canvas:active {
|
||||||
|
outline: none;
|
||||||
|
border: 0;
|
||||||
|
ie-dummy: expression(this.hideFocus=true);
|
||||||
|
-moz-outline-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-app-progress {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #d1dbeb;
|
||||||
|
height: 6px;
|
||||||
|
margin-top: -6px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-app-progress-bar {
|
||||||
|
font-size: 12px;
|
||||||
|
height: 6px;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
background-color: #1a72eb;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-image: url("data:image/svg+xml,%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg xmlns='http://www.w3.org/2000/svg' baseProfile='full' width='16' height='16' viewBox='0 0 16 16' version='1.1' xml:space='preserve'%3E%3Ctitle%3Eic-16-fullscreen%3C/title%3E%3Cg id='ic-16-fullscreen' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cpath d='M3,11.5 C3,11.776 3.224,12 3.5,12 L12.5,12 C12.776,12 13,11.776 13,11.5 L13,4.5 C13,4.224 12.776,4 12.5,4 L3.5,4 C3.224,4 3,4.224 3,4.5 L3,11.5 Z M14,11 L14,13 L12,13 C11.724,13 11.5,13.224 11.5,13.5 C11.5,13.776 11.724,14 12,14 L14.5,14 C14.776,14 15,13.776 15,13.5 L15,11 C15,10.724 14.776,10.5 14.5,10.5 C14.224,10.5 14,10.724 14,11 Z M12,2 C11.724,2 11.5,2.224 11.5,2.5 C11.5,2.776 11.724,3 12,3 L14,3 L14,5 C14,5.276 14.224,5.5 14.5,5.5 C14.776,5.5 15,5.276 15,5 L15,2.5 C15,2.224 14.776,2 14.5,2 L12,2 Z M2,13 L2,11 C2,10.724 1.776,10.5 1.5,10.5 C1.224,10.5 1,10.724 1,11 L1,13.5 C1,13.776 1.224,14 1.5,14 L4,14 C4.276,14 4.5,13.776 4.5,13.5 C4.5,13.224 4.276,13 4,13 L2,13 Z M1,2.5 C1,2.224 1.224,2 1.5,2 L4,2 C4.276,2 4.5,2.224 4.5,2.5 C4.5,2.776 4.276,3 4,3 L2,3 L2,5 C2,5.276 1.776,5.5 1.5,5.5 C1.224,5.5 1,5.276 1,5 L1,2.5 Z ' id='fill_1' fill='%23006fff'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-color: transparent;
|
||||||
|
float: left;
|
||||||
|
color: #006fff;
|
||||||
|
padding-left: 50%;
|
||||||
|
padding: 0px 0px 0px 20px;
|
||||||
|
cursor:pointer;
|
||||||
|
background-position: left bottom;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
text-align: right;
|
||||||
|
color: #4e5258;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #006fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.link, .button {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-stretch: normal;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-background {
|
||||||
|
background-color: #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-app-container {
|
||||||
|
background: rgba(250,252,255,1);
|
||||||
|
background: -moz-linear-gradient(-45deg, rgba(250,252,255,1) 0%, rgba(250,252,255,1) 50%, rgba(245,249,255,1) 50%, rgba(245,249,255,1) 100%);
|
||||||
|
background: -webkit-gradient(left top, right bottom, color-stop(0%, rgba(250,252,255,1)), color-stop(50%, rgba(250,252,255,1)), color-stop(50%, rgba(245,249,255,1)), color-stop(100%, rgba(245,249,255,1)));
|
||||||
|
background: -webkit-linear-gradient(-45deg, rgba(250,252,255,1) 0%, rgba(250,252,255,1) 50%, rgba(245,249,255,1) 50%, rgba(245,249,255,1) 100%);
|
||||||
|
background: -o-linear-gradient(-45deg, rgba(250,252,255,1) 0%, rgba(250,252,255,1) 50%, rgba(245,249,255,1) 50%, rgba(245,249,255,1) 100%);
|
||||||
|
background: -ms-linear-gradient(-45deg, rgba(250,252,255,1) 0%, rgba(250,252,255,1) 50%, rgba(245,249,255,1) 50%, rgba(245,249,255,1) 100%);
|
||||||
|
background: linear-gradient(135deg, rgba(250,252,255,1) 0%, rgba(250,252,255,1) 50%, rgba(245,249,255,1) 50%, rgba(245,249,255,1) 100%);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fafcff', endColorstr='#f5f9ff', GradientType=1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-app-canvas {
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: 70%;
|
||||||
|
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='300px' height='64px' viewBox='-2467.5 2469 300 64' style='enable-background:new -2467.5 2469 300 64;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bfill:%2315244A;%7D .st1%7Bfill:url(%23SVGID_1_);%7D .st2%7Bfill:url(%23SVGID_2_);%7D%0A%3C/style%3E%3Ctitle%3Edefold-logo-html5-splash%3C/title%3E%3Cpolygon class='st0' points='-2177,2482.9 -2175.5,2482.9 -2175.5,2486.7 -2174.4,2486.7 -2174.4,2482.9 -2173.2,2482.9 -2173.2,2481.9 -2177,2481.9 '/%3E%3Cpolygon class='st0' points='-2169.8,2484.1 -2171,2482.1 -2172.1,2482.1 -2172.1,2486.7 -2171,2486.7 -2171,2483.5 -2169.7,2485.6 -2169.7,2485.6 -2168.5,2483.5 -2168.5,2486.7 -2167.5,2486.7 -2167.5,2482.1 -2168.6,2482.1 '/%3E%3Cpath class='st0' d='M-2376,2482h-13.8v38h13.6c6.6,0,12.2-1.9,16.1-5.5c3.8-3.5,5.8-8.5,5.7-13.7v-0.1 C-2354.5,2489.3-2362.9,2482-2376,2482z M-2364,2501.2c0,6.7-4.5,10.9-11.8,10.9h-4.7v-22h4.7c7.3,0,11.8,4.2,11.8,10.9 L-2364,2501.2z'/%3E%3Cpolygon class='st0' points='-2340.9,2505 -2325.1,2505 -2325.1,2497.4 -2340.9,2497.4 -2340.9,2489.6 -2322.4,2489.6 -2322.4,2481.9 -2350.1,2481.9 -2350.1,2520 -2322.2,2520 -2322.2,2512.4 -2340.9,2512.4 '/%3E%3Cpolygon class='st0' points='-2317.1,2481.9 -2317.1,2520 -2307.9,2520 -2307.9,2505.9 -2293,2505.9 -2293,2498.4 -2307.9,2498.4 -2307.9,2489.9 -2289.6,2489.9 -2289.6,2481.9 '/%3E%3Cpolygon class='st0' points='-2233,2482.1 -2242.2,2482.1 -2242.2,2520 -2216.3,2520 -2216.3,2512.2 -2233,2512.2 '/%3E%3Cpath class='st0' d='M-2197.1,2482h-13.7v38h13.5c6.7,0,12.2-1.9,16.1-5.5c3.8-3.5,5.8-8.5,5.7-13.7v-0.1 C-2175.5,2489.3-2184,2482-2197.1,2482z M-2185.1,2501.2c0,6.7-4.5,10.9-11.8,10.9h-4.7v-22h4.7c7.3,0,11.8,4.2,11.8,10.9V2501.2z' /%3E%3Cpath class='st0' d='M-2267.5,2481.7c-10.8,0-19.6,8.8-19.6,19.7c0,10.8,8.8,19.6,19.7,19.6c10.8,0,19.6-8.8,19.6-19.6l0,0 C-2247.8,2490.5-2256.6,2481.7-2267.5,2481.7C-2267.5,2481.7-2267.5,2481.7-2267.5,2481.7z M-2258,2507.9l-8.8,5.1 c-0.5,0.3-1.2,0.3-1.8,0l-8.8-5.1c-0.5-0.3-0.9-0.9-0.9-1.5v-10.2c0-0.6,0.3-1.2,0.9-1.5l8.8-5.1c0.5-0.3,1.2-0.3,1.8,0l8.8,5.1 c0.5,0.3,0.9,0.9,0.9,1.5v10.2C-2257.1,2507-2257.4,2507.6-2258,2507.9z'/%3E%3Cpath class='st0' d='M-2423.2,2494.6l-11.1,6.4l-11.1-6.4l11.1-6.4L-2423.2,2494.6z M-2412.1,2501v12.8l11.1-6.4L-2412.1,2501z M-2467.5,2507.4l11.1,6.4V2501L-2467.5,2507.4z M-2434.3,2526.6l11.1,6.4l11.1-6.4l-11.1-6.4l11.1-6.4l-11.1-6.4l-11.1,6.4 l-11.1-6.4l-11.1,6.4l11.1,6.4l-11.1,6.4l11.1,6.4L-2434.3,2526.6z'/%3E%3ClinearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='-2451.2178' y1='2525.4604' x2='-2406.2178' y2='2499.6304' gradientTransform='matrix(1 0 0 -1 0 5004)'%3E%3Cstop offset='0' style='stop-color:%231C68EC'/%3E%3Cstop offset='1' style='stop-color:%2300E9DF'/%3E%3C/linearGradient%3E%3Cpath class='st1' d='M-2412.1,2513.8v12.8l-11.1-6.4L-2412.1,2513.8z M-2434.3,2513.8V2501l-11.1-6.4v12.8L-2434.3,2513.8z M-2445.4,2469v12.8l11.1-6.4L-2445.4,2469z M-2412.1,2488.2L-2412.1,2488.2 M-2423.2,2507.4l11.1,6.4V2501l11.1,6.4v-12.8 l-11.1-6.4v-12.8l0,0l-11.1-6.4v12.8l-11.1-6.4v12.8l11.1,6.4V2507.4z'/%3E%3ClinearGradient id='SVGID_2_' gradientUnits='userSpaceOnUse' x1='-2465.9385' y1='2521.2493' x2='-2423.5085' y2='2496.7893' gradientTransform='matrix(1 0 0 -1 0 5004)'%3E%3Cstop offset='0' style='stop-color:%23FF3C2A'/%3E%3Cstop offset='1' style='stop-color:%23FFD215'/%3E%3C/linearGradient%3E%3Cpath class='st2' d='M-2434.3,2513.8V2501l11.1-6.4v12.8L-2434.3,2513.8z M-2434.3,2475.4l11.1,6.4V2469L-2434.3,2475.4z M-2456.5,2488.2L-2456.5,2488.2 M-2445.4,2494.6l11.1-6.4v-12.8l-11.1,6.4V2469l-11.1,6.4l0,0v12.8l-11.1,6.4v12.8l11.1-6.4v12.8 l11.1-6.4V2494.6z M-2456.5,2513.8v12.8l11.1-6.4L-2456.5,2513.8z'/%3E%3C/svg%3E%0A");
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app-container" class="canvas-app-container">
|
||||||
|
<canvas id="canvas" class="canvas-app-canvas" tabindex="1" width="640" height="1136"></canvas>
|
||||||
|
<div class="buttons-background">
|
||||||
|
<div class="button" onclick="Module.toggleFullscreen();">Fullscreen</div>
|
||||||
|
<div class="link">Made with <a href="https://defold.com/" target="_blank">Defold</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- -->
|
||||||
|
<script id='engine-loader' type='text/javascript' src="dmloader.js"></script>
|
||||||
|
<!-- -->
|
||||||
|
<script id='engine-setup' type='text/javascript'>
|
||||||
|
var extra_params = {
|
||||||
|
archive_location_filter: function( path ) {
|
||||||
|
return ("archive" + path + "");
|
||||||
|
},
|
||||||
|
engine_arguments: [],
|
||||||
|
custom_heap_size: 268435456,
|
||||||
|
disable_context_menu: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Module['onRuntimeInitialized'] = function() {
|
||||||
|
Module.runApp("canvas", extra_params);
|
||||||
|
};
|
||||||
|
|
||||||
|
Module["locateFile"] = function(path, scriptDirectory)
|
||||||
|
{
|
||||||
|
// dmengine*.wasm is hardcoded in the built JS loader for WASM,
|
||||||
|
// we need to replace it here with the correct project name.
|
||||||
|
if (path == "dmengine.wasm" || path == "dmengine_release.wasm" || path == "dmengine_headless.wasm") {
|
||||||
|
path = "DefoldWebsocket.wasm";
|
||||||
|
}
|
||||||
|
return scriptDirectory + path;
|
||||||
|
};
|
||||||
|
|
||||||
|
var is_iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||||
|
var buttonHeight = 0;
|
||||||
|
buttonHeight = 42;
|
||||||
|
buttonHeight = 42;
|
||||||
|
// Resize on init, screen resize and orientation change
|
||||||
|
function resize_game_canvas() {
|
||||||
|
// Hack for iOS when exit from Fullscreen mode
|
||||||
|
if (is_iOS) {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var app_container = document.getElementById('app-container');
|
||||||
|
var game_canvas = document.getElementById('canvas');
|
||||||
|
var innerWidth = window.innerWidth;
|
||||||
|
var innerHeight = window.innerHeight - buttonHeight;
|
||||||
|
var width = 640;
|
||||||
|
var height = 1136;
|
||||||
|
var targetRatio = width / height;
|
||||||
|
var actualRatio = innerWidth / innerHeight;
|
||||||
|
|
||||||
|
//Downscale fit
|
||||||
|
if (innerWidth < width || innerHeight < height) {
|
||||||
|
if (actualRatio > targetRatio) {
|
||||||
|
width = innerHeight * targetRatio;
|
||||||
|
height = innerHeight;
|
||||||
|
app_container.style.marginLeft = ((innerWidth - width) / 2) + "px";
|
||||||
|
app_container.style.marginTop = "0px";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
width = innerWidth;
|
||||||
|
height = innerWidth / targetRatio;
|
||||||
|
app_container.style.marginLeft = "0px";
|
||||||
|
app_container.style.marginTop = ((innerHeight - height) / 2) + "px";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app_container.style.marginLeft = ((innerWidth - width) / 2) + "px";
|
||||||
|
app_container.style.marginTop = ((innerHeight - height) / 2) + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app_container.style.width = width + "px";
|
||||||
|
app_container.style.height = height + buttonHeight + "px";
|
||||||
|
game_canvas.width = width;
|
||||||
|
game_canvas.height = height;
|
||||||
|
}
|
||||||
|
resize_game_canvas();
|
||||||
|
window.addEventListener('resize', resize_game_canvas, false);
|
||||||
|
window.addEventListener('orientationchange', resize_game_canvas, false);
|
||||||
|
|
||||||
|
function load_engine() {
|
||||||
|
var engineJS = document.createElement('script');
|
||||||
|
engineJS.type = 'text/javascript';
|
||||||
|
if (Module['isWASMSupported']) {
|
||||||
|
engineJS.src = 'DefoldWebsocket_wasm.js';
|
||||||
|
} else {
|
||||||
|
engineJS.src = 'DefoldWebsocket_asmjs.js';
|
||||||
|
}
|
||||||
|
document.head.appendChild(engineJS);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script id='engine-start' type='text/javascript'>
|
||||||
|
load_engine();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -1 +1 @@
|
|||||||
{"content":[{"name":"game.projectc","size":3195,"pieces":[{"name":"game.projectc0","offset":0}]},{"name":"game.arci","size":1488,"pieces":[{"name":"game.arci0","offset":0}]},{"name":"game.arcd","size":19614,"pieces":[{"name":"game.arcd0","offset":0}]},{"name":"game.dmanifest","size":2532,"pieces":[{"name":"game.dmanifest0","offset":0}]},{"name":"game.public.der","size":162,"pieces":[{"name":"game.public.der0","offset":0}]}]}
|
{"content":[{"name":"game.projectc","size":3080,"pieces":[{"name":"game.projectc0","offset":0}]},{"name":"game.arci","size":1488,"pieces":[{"name":"game.arci0","offset":0}]},{"name":"game.arcd","size":19498,"pieces":[{"name":"game.arcd0","offset":0}]},{"name":"game.dmanifest","size":2532,"pieces":[{"name":"game.dmanifest0","offset":0}]},{"name":"game.public.der","size":162,"pieces":[{"name":"game.public.der0","offset":0}]}]}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,8 +3,6 @@ title = extension-websocket
|
|||||||
version = 1.0
|
version = 1.0
|
||||||
write_log = 0
|
write_log = 0
|
||||||
compress_archive = 1
|
compress_archive = 1
|
||||||
publisher = unnamed
|
|
||||||
developer = unnamed
|
|
||||||
_dependencies = https://github.com/GameAnalytics/defold-openssl/archive/1.0.0.zip
|
_dependencies = https://github.com/GameAnalytics/defold-openssl/archive/1.0.0.zip
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
@@ -54,7 +52,6 @@ max_characters = 8192
|
|||||||
max_debug_vertices = 10000
|
max_debug_vertices = 10000
|
||||||
texture_profiles = /builtins/graphics/default.texture_profiles
|
texture_profiles = /builtins/graphics/default.texture_profiles
|
||||||
verify_graphics_calls = 1
|
verify_graphics_calls = 1
|
||||||
memory_size = 512
|
|
||||||
|
|
||||||
[shader]
|
[shader]
|
||||||
output_spirv = 0
|
output_spirv = 0
|
||||||
@@ -153,8 +150,6 @@ auto_finish_transactions = 1
|
|||||||
|
|
||||||
[network]
|
[network]
|
||||||
http_timeout = 0
|
http_timeout = 0
|
||||||
http_thread_count = 4
|
|
||||||
http_cache_enabled = 1
|
|
||||||
|
|
||||||
[library]
|
[library]
|
||||||
include_dirs = websocket
|
include_dirs = websocket
|
||||||
@@ -171,7 +166,6 @@ track_cpu = 0
|
|||||||
|
|
||||||
[liveupdate]
|
[liveupdate]
|
||||||
settings = /liveupdate.settings
|
settings = /liveupdate.settings
|
||||||
enabled = 1
|
|
||||||
|
|
||||||
[tilemap]
|
[tilemap]
|
||||||
max_count = 16
|
max_count = 16
|
||||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -14,15 +14,16 @@ Here is how you connect to a websocket and listen to events:
|
|||||||
```lua
|
```lua
|
||||||
local function websocket_callback(self, conn, data)
|
local function websocket_callback(self, conn, data)
|
||||||
if data.event == websocket.EVENT_DISCONNECTED then
|
if data.event == websocket.EVENT_DISCONNECTED then
|
||||||
print("disconnected " .. conn)
|
log("Disconnected: " .. tostring(conn))
|
||||||
self.connection = nil
|
self.connection = nil
|
||||||
|
update_gui(self)
|
||||||
elseif data.event == websocket.EVENT_CONNECTED then
|
elseif data.event == websocket.EVENT_CONNECTED then
|
||||||
print("Connected " .. conn)
|
update_gui(self)
|
||||||
-- self.connection = conn
|
log("Connected: " .. tostring(conn))
|
||||||
elseif data.event == websocket.EVENT_ERROR then
|
elseif data.event == websocket.EVENT_ERROR then
|
||||||
print("Error:", data.error)
|
log("Error: '" .. data.error .. "'")
|
||||||
elseif data.event == websocket.EVENT_MESSAGE then
|
elseif data.event == websocket.EVENT_MESSAGE then
|
||||||
print("Receiving: '" .. tostring(data.message) .. "'")
|
log("Receiving: '" .. tostring(data.message) .. "'")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -33,6 +33,12 @@ local function log(...)
|
|||||||
gui.set_text(node, gui.get_text(node) .. "\n" .. text)
|
gui.set_text(node, gui.get_text(node) .. "\n" .. text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function http_result(self, _, response)
|
||||||
|
print(response.status)
|
||||||
|
--print(response.response)
|
||||||
|
pprint(response.headers)
|
||||||
|
end
|
||||||
|
|
||||||
function init(self)
|
function init(self)
|
||||||
msg.post(".", "acquire_input_focus")
|
msg.post(".", "acquire_input_focus")
|
||||||
msg.post("@render:", "clear_color", { color = vmath.vector4(0.2, 0.4, 0.8, 1.0) })
|
msg.post("@render:", "clear_color", { color = vmath.vector4(0.2, 0.4, 0.8, 1.0) })
|
||||||
@@ -43,6 +49,9 @@ function init(self)
|
|||||||
self.connection_text = gui.get_node("connection_text")
|
self.connection_text = gui.get_node("connection_text")
|
||||||
self.connection = nil
|
self.connection = nil
|
||||||
update_gui(self)
|
update_gui(self)
|
||||||
|
|
||||||
|
--http.request("https://defold.com", "GET", http_result)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function final(self)
|
function final(self)
|
||||||
|
@@ -65,15 +65,16 @@
|
|||||||
```lua
|
```lua
|
||||||
local function websocket_callback(self, conn, data)
|
local function websocket_callback(self, conn, data)
|
||||||
if data.event == websocket.EVENT_DISCONNECTED then
|
if data.event == websocket.EVENT_DISCONNECTED then
|
||||||
print("disconnected " .. conn)
|
log("Disconnected: " .. tostring(conn))
|
||||||
self.connection = nil
|
self.connection = nil
|
||||||
|
update_gui(self)
|
||||||
elseif data.event == websocket.EVENT_CONNECTED then
|
elseif data.event == websocket.EVENT_CONNECTED then
|
||||||
print("Connected " .. conn)
|
update_gui(self)
|
||||||
-- self.connection = conn
|
log("Connected: " .. tostring(conn))
|
||||||
elseif data.event == websocket.EVENT_ERROR then
|
elseif data.event == websocket.EVENT_ERROR then
|
||||||
print("Error:", data.error)
|
log("Error: '" .. data.error .. "'")
|
||||||
elseif data.event == websocket.EVENT_MESSAGE then
|
elseif data.event == websocket.EVENT_MESSAGE then
|
||||||
print("Receiving: '" .. tostring(data.message) .. "'")
|
log("Receiving: '" .. tostring(data.message) .. "'")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -100,6 +101,35 @@
|
|||||||
type: object
|
type: object
|
||||||
desc: the websocket connection
|
desc: the websocket connection
|
||||||
|
|
||||||
|
#*****************************************************************************************************
|
||||||
|
|
||||||
|
- name: send
|
||||||
|
type: function
|
||||||
|
desc: Send data on a websocket
|
||||||
|
parameters:
|
||||||
|
- name: connection
|
||||||
|
type: object
|
||||||
|
desc: the websocket connection
|
||||||
|
- name: message
|
||||||
|
type: string
|
||||||
|
desc: the message to send
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- desc: |-
|
||||||
|
```lua
|
||||||
|
local function websocket_callback(self, conn, data)
|
||||||
|
if data.event == websocket.EVENT_CONNECTED then
|
||||||
|
websocket.send(conn, "Hello from the other side")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function init(self)
|
||||||
|
self.url = "ws://echo.websocket.org"
|
||||||
|
local params = {}
|
||||||
|
self.connection = websocket.connect(self.url, params, websocket_callback)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
#*****************************************************************************************************
|
#*****************************************************************************************************
|
||||||
|
|
||||||
- name: EVENT_CONNECTED
|
- name: EVENT_CONNECTED
|
||||||
|
@@ -2,8 +2,12 @@
|
|||||||
#ifndef CONFIG_H
|
#ifndef CONFIG_H
|
||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
#define HAVE_ARPA_INET_H
|
#if defined(_WIN32)
|
||||||
#define HAVE_NETINET_IN_H
|
#define HAVE_WINSOCK2_H
|
||||||
|
#else
|
||||||
|
#define HAVE_ARPA_INET_H
|
||||||
|
#define HAVE_NETINET_IN_H
|
||||||
|
#endif
|
||||||
/* #undef HAVE_WINSOCK2_H */
|
/* #undef HAVE_WINSOCK2_H */
|
||||||
/* #undef WORDS_BIGENDIAN */
|
/* #undef WORDS_BIGENDIAN */
|
||||||
|
|
||||||
|
@@ -33,6 +33,14 @@ extern "C" {
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
// Defold addition
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#if defined(_WIN64)
|
||||||
|
typedef int64_t ssize_t;
|
||||||
|
#else
|
||||||
|
typedef int32_t ssize_t;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* wslay/wslayver.h is generated from wslay/wslayver.h.in by
|
* wslay/wslayver.h is generated from wslay/wslayver.h.in by
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#include "websocket.h"
|
#include "websocket.h"
|
||||||
#include <dmsdk/dlib/socket.h>
|
#include <dmsdk/dlib/socket.h>
|
||||||
|
#include <ctype.h> // tolower
|
||||||
|
|
||||||
namespace dmWebsocket
|
namespace dmWebsocket
|
||||||
{
|
{
|
||||||
@@ -146,7 +147,8 @@ Result ReceiveHeaders(WebsocketConnection* conn)
|
|||||||
conn->m_Buffer[conn->m_BufferSize] = '\0';
|
conn->m_Buffer[conn->m_BufferSize] = '\0';
|
||||||
|
|
||||||
// Check if the end of the response has arrived
|
// Check if the end of the response has arrived
|
||||||
if (conn->m_BufferSize >= 4 && strcmp(conn->m_Buffer + conn->m_BufferSize - 4, "\r\n\r\n") == 0)
|
const char* endtag = strstr(conn->m_Buffer, "\r\n\r\n");
|
||||||
|
if (endtag != 0)
|
||||||
{
|
{
|
||||||
return RESULT_OK;
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
@@ -155,6 +157,20 @@ Result ReceiveHeaders(WebsocketConnection* conn)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static int dmStriCmp(const char* s1, const char* s2)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (!*s1 || !*s2 || tolower((unsigned char) *s1) != tolower((unsigned char) *s2))
|
||||||
|
{
|
||||||
|
return (unsigned char) *s1 - (unsigned char) *s2;
|
||||||
|
}
|
||||||
|
s1++;
|
||||||
|
s2++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#if defined(__EMSCRIPTEN__)
|
#if defined(__EMSCRIPTEN__)
|
||||||
Result VerifyHeaders(WebsocketConnection* conn)
|
Result VerifyHeaders(WebsocketConnection* conn)
|
||||||
{
|
{
|
||||||
@@ -171,16 +187,19 @@ Result VerifyHeaders(WebsocketConnection* conn)
|
|||||||
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Missing: '%s' in header", http_version_and_status_protocol);
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Missing: '%s' in header", http_version_and_status_protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* endtag = strstr(conn->m_Buffer, "\r\n\r\n");
|
||||||
|
|
||||||
r = strstr(r, "\r\n") + 2;
|
r = strstr(r, "\r\n") + 2;
|
||||||
|
|
||||||
bool upgraded = false;
|
bool connection = false;
|
||||||
|
bool upgrade = false;
|
||||||
bool valid_key = false;
|
bool valid_key = false;
|
||||||
const char* protocol = "";
|
const char* protocol = "";
|
||||||
|
|
||||||
// TODO: Perhaps also support the Sec-WebSocket-Protocol
|
// TODO: Perhaps also support the Sec-WebSocket-Protocol
|
||||||
|
|
||||||
// parse the headers in place
|
// parse the headers in place
|
||||||
while (r)
|
while (r < endtag)
|
||||||
{
|
{
|
||||||
// Tokenize the each header line: "Key: Value\r\n"
|
// Tokenize the each header line: "Key: Value\r\n"
|
||||||
const char* key = r;
|
const char* key = r;
|
||||||
@@ -194,45 +213,65 @@ Result VerifyHeaders(WebsocketConnection* conn)
|
|||||||
*r = 0;
|
*r = 0;
|
||||||
r += 2;
|
r += 2;
|
||||||
|
|
||||||
if (strcmp(key, "Connection") == 0 && strcmp(value, "Upgrade") == 0)
|
// Page 18 in https://tools.ietf.org/html/rfc6455#section-11.3.3
|
||||||
upgraded = true;
|
if (dmStriCmp(key, "Connection") == 0 && dmStriCmp(value, "Upgrade") == 0)
|
||||||
else if (strcmp(key, "Sec-WebSocket-Accept") == 0)
|
connection = true;
|
||||||
|
else if (dmStriCmp(key, "Upgrade") == 0 && dmStriCmp(value, "websocket") == 0)
|
||||||
|
upgrade = true;
|
||||||
|
else if (dmStriCmp(key, "Sec-WebSocket-Accept") == 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
uint8_t client_key[32 + 40];
|
uint8_t client_key[32 + 40];
|
||||||
uint32_t client_key_len = sizeof(client_key);
|
uint32_t client_key_len = sizeof(client_key);
|
||||||
dmCrypt::Base64Encode(conn->m_Key, sizeof(conn->m_Key), client_key, &client_key_len);
|
dmCrypt::Base64Encode(conn->m_Key, sizeof(conn->m_Key), client_key, &client_key_len);
|
||||||
client_key[client_key_len] = 0;
|
client_key[client_key_len] = 0;
|
||||||
|
|
||||||
|
DebugLog(2, "Secret key (base64): %s", client_key);
|
||||||
|
|
||||||
memcpy(client_key + client_key_len, RFC_MAGIC, strlen(RFC_MAGIC));
|
memcpy(client_key + client_key_len, RFC_MAGIC, strlen(RFC_MAGIC));
|
||||||
client_key_len += strlen(RFC_MAGIC);
|
client_key_len += strlen(RFC_MAGIC);
|
||||||
client_key[client_key_len] = 0;
|
client_key[client_key_len] = 0;
|
||||||
|
|
||||||
|
DebugLog(2, "Secret key + RFC_MAGIC: %s", client_key);
|
||||||
|
|
||||||
uint8_t client_key_sha1[20];
|
uint8_t client_key_sha1[20];
|
||||||
dmCrypt::HashSha1(client_key, client_key_len, client_key_sha1);
|
dmCrypt::HashSha1(client_key, client_key_len, client_key_sha1);
|
||||||
|
|
||||||
|
DebugPrint(2, "Hashed key (sha1):", client_key_sha1, sizeof(client_key_sha1));
|
||||||
|
|
||||||
client_key_len = sizeof(client_key);
|
client_key_len = sizeof(client_key);
|
||||||
dmCrypt::Base64Encode(client_key_sha1, sizeof(client_key_sha1), client_key, &client_key_len);
|
dmCrypt::Base64Encode(client_key_sha1, sizeof(client_key_sha1), client_key, &client_key_len);
|
||||||
client_key[client_key_len] = 0;
|
client_key[client_key_len] = 0;
|
||||||
|
|
||||||
|
DebugLog(2, "Client key (base64): %s", client_key);
|
||||||
|
|
||||||
|
DebugLog(2, "Server key (base64): %s", value);
|
||||||
|
|
||||||
if (strcmp(value, (const char*)client_key) == 0)
|
if (strcmp(value, (const char*)client_key) == 0)
|
||||||
valid_key = true;
|
valid_key = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(r, "\r\n") == 0)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!upgraded)
|
// The response might contain both the headers, but also (if successful) the first batch of data
|
||||||
|
endtag += 4;
|
||||||
|
uint32_t size = conn->m_BufferSize - (endtag - conn->m_Buffer);
|
||||||
|
conn->m_BufferSize = size;
|
||||||
|
memmove(conn->m_Buffer, endtag, size);
|
||||||
|
conn->m_Buffer[size] = 0;
|
||||||
|
conn->m_HasHandshakeData = conn->m_BufferSize != 0 ? 1 : 0;
|
||||||
|
|
||||||
|
if (!connection)
|
||||||
|
dmLogError("Failed to find the Connection keyword in the response headers");
|
||||||
|
if (!upgrade)
|
||||||
dmLogError("Failed to find the Upgrade keyword in the response headers");
|
dmLogError("Failed to find the Upgrade keyword in the response headers");
|
||||||
if (!valid_key)
|
if (!valid_key)
|
||||||
dmLogError("Failed to find valid key in the response headers");
|
dmLogError("Failed to find valid key in the response headers");
|
||||||
|
|
||||||
if (!(upgraded && valid_key)) {
|
bool ok = connection && upgrade && valid_key;
|
||||||
|
if (!ok) {
|
||||||
dmLogError("Response:\n\"%s\"\n", conn->m_Buffer);
|
dmLogError("Response:\n\"%s\"\n", conn->m_Buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (upgraded && valid_key) ? RESULT_OK : RESULT_HANDSHAKE_FAILED;
|
return ok ? RESULT_OK : RESULT_HANDSHAKE_FAILED;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -42,15 +42,24 @@ dmSocket::Result Send(WebsocketConnection* conn, const char* buffer, int length,
|
|||||||
}
|
}
|
||||||
if (out_sent_bytes)
|
if (out_sent_bytes)
|
||||||
*out_sent_bytes = total_sent_bytes;
|
*out_sent_bytes = total_sent_bytes;
|
||||||
|
|
||||||
|
DebugPrint(2, "Sent buffer:", buffer, length);
|
||||||
return dmSocket::RESULT_OK;
|
return dmSocket::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
dmSocket::Result Receive(WebsocketConnection* conn, void* buffer, int length, int* received_bytes)
|
dmSocket::Result Receive(WebsocketConnection* conn, void* buffer, int length, int* received_bytes)
|
||||||
{
|
{
|
||||||
|
dmSocket::Result sr;
|
||||||
if (conn->m_SSLSocket)
|
if (conn->m_SSLSocket)
|
||||||
return dmSSLSocket::Receive(conn->m_SSLSocket, buffer, length, received_bytes);
|
sr = dmSSLSocket::Receive(conn->m_SSLSocket, buffer, length, received_bytes);
|
||||||
else
|
else
|
||||||
return dmSocket::Receive(conn->m_Socket, buffer, length, received_bytes);
|
sr = dmSocket::Receive(conn->m_Socket, buffer, length, received_bytes);
|
||||||
|
|
||||||
|
int num_bytes = received_bytes ? (uint32_t)*received_bytes : 0;
|
||||||
|
if (sr == dmSocket::RESULT_OK && num_bytes > 0)
|
||||||
|
DebugPrint(2, "Received bytes:", buffer, num_bytes);
|
||||||
|
|
||||||
|
return sr;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
@@ -9,9 +9,20 @@
|
|||||||
#include <dmsdk/dlib/connection_pool.h>
|
#include <dmsdk/dlib/connection_pool.h>
|
||||||
#include <dmsdk/dlib/dns.h>
|
#include <dmsdk/dlib/dns.h>
|
||||||
#include <dmsdk/dlib/sslsocket.h>
|
#include <dmsdk/dlib/sslsocket.h>
|
||||||
|
#include <ctype.h> // isprint et al
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
#include <emscripten/emscripten.h> // for EM_ASM
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WIN32)
|
||||||
|
#include <malloc.h>
|
||||||
|
#define alloca _alloca
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace dmWebsocket {
|
namespace dmWebsocket {
|
||||||
|
|
||||||
|
int g_DebugWebSocket = 0;
|
||||||
|
|
||||||
struct WebsocketContext
|
struct WebsocketContext
|
||||||
{
|
{
|
||||||
@@ -56,8 +67,72 @@ const char* StateToString(State err)
|
|||||||
|
|
||||||
#undef STRING_CASE
|
#undef STRING_CASE
|
||||||
|
|
||||||
#define WS_DEBUG(...)
|
void DebugLog(int level, const char* fmt, ...)
|
||||||
//#define WS_DEBUG(...) dmLogWarning(__VA_ARGS__);
|
{
|
||||||
|
if (level > g_DebugWebSocket)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t buffer_size = 4096;
|
||||||
|
char* buffer = (char*)alloca(buffer_size);
|
||||||
|
va_list lst;
|
||||||
|
va_start(lst, fmt);
|
||||||
|
|
||||||
|
buffer_size = vsnprintf(buffer, buffer_size, fmt, lst);
|
||||||
|
dmLogWarning("%s", buffer);
|
||||||
|
va_end(lst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugPrint(int level, const char* msg, const void* _bytes, uint32_t num_bytes)
|
||||||
|
{
|
||||||
|
if (level > g_DebugWebSocket)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
char submessage[128];
|
||||||
|
uint32_t sublength = 0;
|
||||||
|
|
||||||
|
uint32_t len = dmSnPrintf(buffer, sizeof(buffer), "%s '", msg);
|
||||||
|
|
||||||
|
const uint8_t* bytes = (const uint8_t*)_bytes;
|
||||||
|
for (uint32_t i = 0; i < num_bytes; ++i)
|
||||||
|
{
|
||||||
|
if (len + 2 >= sizeof(buffer))
|
||||||
|
{
|
||||||
|
dmLogWarning("%s", buffer);
|
||||||
|
buffer[0] = 0;
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sublength = 0;
|
||||||
|
|
||||||
|
int c = bytes[i];
|
||||||
|
if (isprint(c))
|
||||||
|
sublength = dmSnPrintf(submessage, sizeof(submessage), "%c", c);
|
||||||
|
else if (c == '\r')
|
||||||
|
sublength = dmSnPrintf(submessage, sizeof(submessage), "\\r");
|
||||||
|
else if (c == '\n')
|
||||||
|
sublength = dmSnPrintf(submessage, sizeof(submessage), "\\n");
|
||||||
|
else if (c == '\t')
|
||||||
|
sublength = dmSnPrintf(submessage, sizeof(submessage), "\\t");
|
||||||
|
else
|
||||||
|
sublength = dmSnPrintf(submessage, sizeof(submessage), "\\%02x", c);
|
||||||
|
dmStrlCat(buffer, submessage, sizeof(buffer));
|
||||||
|
len += sublength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len + 2 >= sizeof(buffer))
|
||||||
|
{
|
||||||
|
dmLogWarning("%s", buffer);
|
||||||
|
buffer[0] = 0;
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sublength = dmSnPrintf(submessage, sizeof(submessage), "' %u bytes", num_bytes);
|
||||||
|
dmStrlCat(buffer, submessage, sizeof(buffer));
|
||||||
|
len += sublength;
|
||||||
|
dmLogWarning("%s", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#define CLOSE_CONN(...) \
|
#define CLOSE_CONN(...) \
|
||||||
SetStatus(conn, RESULT_ERROR, __VA_ARGS__); \
|
SetStatus(conn, RESULT_ERROR, __VA_ARGS__); \
|
||||||
@@ -70,7 +145,7 @@ static void SetState(WebsocketConnection* conn, State state)
|
|||||||
if (prev_state != state)
|
if (prev_state != state)
|
||||||
{
|
{
|
||||||
conn->m_State = state;
|
conn->m_State = state;
|
||||||
WS_DEBUG("%s -> %s", StateToString(prev_state), StateToString(conn->m_State));
|
DebugLog(1, "%s -> %s", StateToString(prev_state), StateToString(conn->m_State));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +160,8 @@ Result SetStatus(WebsocketConnection* conn, Result status, const char* format, .
|
|||||||
conn->m_BufferSize = vsnprintf(conn->m_Buffer, conn->m_BufferCapacity, format, lst);
|
conn->m_BufferSize = vsnprintf(conn->m_Buffer, conn->m_BufferCapacity, format, lst);
|
||||||
va_end(lst);
|
va_end(lst);
|
||||||
conn->m_Status = status;
|
conn->m_Status = status;
|
||||||
|
|
||||||
|
DebugLog(1, "STATUS: '%s' len: %u", conn->m_Buffer, conn->m_BufferSize);
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -96,10 +173,11 @@ Result SetStatus(WebsocketConnection* conn, Result status, const char* format, .
|
|||||||
|
|
||||||
static WebsocketConnection* CreateConnection(const char* url)
|
static WebsocketConnection* CreateConnection(const char* url)
|
||||||
{
|
{
|
||||||
WebsocketConnection* conn = (WebsocketConnection*)malloc(sizeof(WebsocketConnection));
|
WebsocketConnection* conn = new WebsocketConnection;
|
||||||
memset(conn, 0, sizeof(WebsocketConnection));
|
|
||||||
conn->m_BufferCapacity = g_Websocket.m_BufferSize;
|
conn->m_BufferCapacity = g_Websocket.m_BufferSize;
|
||||||
conn->m_Buffer = (char*)malloc(conn->m_BufferCapacity);
|
conn->m_Buffer = (char*)malloc(conn->m_BufferCapacity);
|
||||||
|
conn->m_Buffer[0] = 0;
|
||||||
|
conn->m_BufferSize = 0;
|
||||||
|
|
||||||
dmURI::Parts uri;
|
dmURI::Parts uri;
|
||||||
dmURI::Parse(url, &conn->m_Url);
|
dmURI::Parse(url, &conn->m_Url);
|
||||||
@@ -110,6 +188,17 @@ static WebsocketConnection* CreateConnection(const char* url)
|
|||||||
conn->m_SSL = strcmp(conn->m_Url.m_Scheme, "wss") == 0 ? 1 : 0;
|
conn->m_SSL = strcmp(conn->m_Url.m_Scheme, "wss") == 0 ? 1 : 0;
|
||||||
conn->m_State = STATE_CONNECTING;
|
conn->m_State = STATE_CONNECTING;
|
||||||
|
|
||||||
|
conn->m_Callback = 0;
|
||||||
|
conn->m_Connection = 0;
|
||||||
|
conn->m_Socket = 0;
|
||||||
|
conn->m_SSLSocket = 0;
|
||||||
|
conn->m_Status = RESULT_OK;
|
||||||
|
conn->m_HasHandshakeData = 0;
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
conn->m_Ctx = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +212,20 @@ static void DestroyConnection(WebsocketConnection* conn)
|
|||||||
if (conn->m_Callback)
|
if (conn->m_Callback)
|
||||||
dmScript::DestroyCallback(conn->m_Callback);
|
dmScript::DestroyCallback(conn->m_Callback);
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
if (conn->m_Socket != dmSocket::INVALID_SOCKET_HANDLE) {
|
||||||
|
// We would normally do a shutdown() first, but Emscripten returns ENOSYS
|
||||||
|
//dmSocket::Shutdown(conn->m_Socket, dmSocket::SHUTDOWNTYPE_READWRITE);
|
||||||
|
dmSocket::Delete(conn->m_Socket);
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (conn->m_Connection)
|
if (conn->m_Connection)
|
||||||
dmConnectionPool::Return(g_Websocket.m_Pool, conn->m_Connection);
|
dmConnectionPool::Return(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
free((void*)conn->m_Buffer);
|
free((void*)conn->m_Buffer);
|
||||||
free((void*)conn);
|
delete conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -227,7 +325,7 @@ static int LuaSend(lua_State* L)
|
|||||||
const char* string = luaL_checklstring(L, 2, &string_length);
|
const char* string = luaL_checklstring(L, 2, &string_length);
|
||||||
|
|
||||||
#if defined(HAVE_WSLAY)
|
#if defined(HAVE_WSLAY)
|
||||||
int write_mode = WSLAY_BINARY_FRAME; // WSLAY_TEXT_FRAME
|
int write_mode = WSLAY_BINARY_FRAME; // or WSLAY_TEXT_FRAME
|
||||||
|
|
||||||
struct wslay_event_msg msg;
|
struct wslay_event_msg msg;
|
||||||
msg.opcode = write_mode;
|
msg.opcode = write_mode;
|
||||||
@@ -247,7 +345,7 @@ static int LuaSend(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void HandleCallback(WebsocketConnection* conn, int event)
|
static void HandleCallback(WebsocketConnection* conn, int event, int msg_offset, int msg_length)
|
||||||
{
|
{
|
||||||
if (!dmScript::IsCallbackValid(conn->m_Callback))
|
if (!dmScript::IsCallbackValid(conn->m_Callback))
|
||||||
return;
|
return;
|
||||||
@@ -268,14 +366,8 @@ static void HandleCallback(WebsocketConnection* conn, int event)
|
|||||||
lua_pushinteger(L, event);
|
lua_pushinteger(L, event);
|
||||||
lua_setfield(L, -2, "event");
|
lua_setfield(L, -2, "event");
|
||||||
|
|
||||||
if (EVENT_ERROR == event) {
|
lua_pushlstring(L, conn->m_Buffer + msg_offset, msg_length);
|
||||||
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
|
lua_setfield(L, -2, "message");
|
||||||
lua_setfield(L, -2, "error");
|
|
||||||
}
|
|
||||||
else if (EVENT_MESSAGE == event) {
|
|
||||||
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
|
|
||||||
lua_setfield(L, -2, "message");
|
|
||||||
}
|
|
||||||
|
|
||||||
dmScript::PCall(L, 3, 0);
|
dmScript::PCall(L, 3, 0);
|
||||||
|
|
||||||
@@ -317,7 +409,7 @@ static void LuaInit(lua_State* L)
|
|||||||
assert(top == lua_gettop(L));
|
assert(top == lua_gettop(L));
|
||||||
}
|
}
|
||||||
|
|
||||||
static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params)
|
static dmExtension::Result AppInitialize(dmExtension::AppParams* params)
|
||||||
{
|
{
|
||||||
g_Websocket.m_BufferSize = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.buffer_size", 64 * 1024);
|
g_Websocket.m_BufferSize = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.buffer_size", 64 * 1024);
|
||||||
g_Websocket.m_Timeout = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.socket_timeout", 500 * 1000);
|
g_Websocket.m_Timeout = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.socket_timeout", 500 * 1000);
|
||||||
@@ -329,6 +421,10 @@ static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params
|
|||||||
pool_params.m_MaxConnections = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.max_connections", 2);
|
pool_params.m_MaxConnections = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.max_connections", 2);
|
||||||
dmConnectionPool::Result result = dmConnectionPool::New(&pool_params, &g_Websocket.m_Pool);
|
dmConnectionPool::Result result = dmConnectionPool::New(&pool_params, &g_Websocket.m_Pool);
|
||||||
|
|
||||||
|
g_DebugWebSocket = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.debug", 0);
|
||||||
|
if (g_DebugWebSocket)
|
||||||
|
dmLogInfo("dmWebSocket::g_DebugWebSocket == %d", g_DebugWebSocket);
|
||||||
|
|
||||||
if (dmConnectionPool::RESULT_OK != result)
|
if (dmConnectionPool::RESULT_OK != result)
|
||||||
{
|
{
|
||||||
dmLogError("Failed to create connection pool: %d", result);
|
dmLogError("Failed to create connection pool: %d", result);
|
||||||
@@ -344,6 +440,14 @@ static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
// avoid mixed content warning if trying to access wss resource from http page
|
||||||
|
// If not using this, we get EHOSTUNREACH
|
||||||
|
EM_ASM({
|
||||||
|
Module["websocket"].url = window["location"]["protocol"].replace("http", "ws") + "//";
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
g_Websocket.m_Initialized = 1;
|
g_Websocket.m_Initialized = 1;
|
||||||
if (!g_Websocket.m_Pool)
|
if (!g_Websocket.m_Pool)
|
||||||
{
|
{
|
||||||
@@ -360,7 +464,7 @@ static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params
|
|||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dmExtension::Result WebsocketInitialize(dmExtension::Params* params)
|
static dmExtension::Result Initialize(dmExtension::Params* params)
|
||||||
{
|
{
|
||||||
if (!g_Websocket.m_Initialized)
|
if (!g_Websocket.m_Initialized)
|
||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
@@ -371,19 +475,50 @@ static dmExtension::Result WebsocketInitialize(dmExtension::Params* params)
|
|||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dmExtension::Result WebsocketAppFinalize(dmExtension::AppParams* params)
|
static dmExtension::Result AppFinalize(dmExtension::AppParams* params)
|
||||||
{
|
{
|
||||||
|
|
||||||
dmConnectionPool::Shutdown(g_Websocket.m_Pool, dmSocket::SHUTDOWNTYPE_READWRITE);
|
dmConnectionPool::Shutdown(g_Websocket.m_Pool, dmSocket::SHUTDOWNTYPE_READWRITE);
|
||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dmExtension::Result WebsocketFinalize(dmExtension::Params* params)
|
static dmExtension::Result Finalize(dmExtension::Params* params)
|
||||||
{
|
{
|
||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
Result PushMessage(WebsocketConnection* conn, MessageType type, int length, const uint8_t* buffer)
|
||||||
|
{
|
||||||
|
if (conn->m_Messages.Full())
|
||||||
|
conn->m_Messages.OffsetCapacity(4);
|
||||||
|
|
||||||
|
Message msg;
|
||||||
|
msg.m_Type = (uint32_t)type;
|
||||||
|
msg.m_Length = length;
|
||||||
|
conn->m_Messages.Push(msg);
|
||||||
|
|
||||||
|
// No need to copy itself (html5)
|
||||||
|
if (buffer != (const uint8_t*)conn->m_Buffer)
|
||||||
|
{
|
||||||
|
if ((conn->m_BufferSize + length) >= conn->m_BufferCapacity)
|
||||||
|
{
|
||||||
|
conn->m_BufferCapacity = conn->m_BufferSize + length + 1;
|
||||||
|
conn->m_Buffer = (char*)realloc(conn->m_Buffer, conn->m_BufferCapacity);
|
||||||
|
}
|
||||||
|
// append to the end of the buffer
|
||||||
|
memcpy(conn->m_Buffer + conn->m_BufferSize, buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->m_BufferSize += length;
|
||||||
|
conn->m_Buffer[conn->m_BufferCapacity-1] = 0;
|
||||||
|
|
||||||
|
// Instead of printing from the incoming buffer, we print from our own, to make sure it looks ok
|
||||||
|
DebugPrint(2, __FUNCTION__, conn->m_Buffer+conn->m_BufferSize-length, length);
|
||||||
|
|
||||||
|
return dmWebsocket::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result OnUpdate(dmExtension::Params* params)
|
||||||
{
|
{
|
||||||
uint32_t size = g_Websocket.m_Connections.Size();
|
uint32_t size = g_Websocket.m_Connections.Size();
|
||||||
|
|
||||||
@@ -395,10 +530,11 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
{
|
{
|
||||||
if (RESULT_OK != conn->m_Status)
|
if (RESULT_OK != conn->m_Status)
|
||||||
{
|
{
|
||||||
HandleCallback(conn, EVENT_ERROR);
|
HandleCallback(conn, EVENT_ERROR, 0, conn->m_BufferSize);
|
||||||
|
conn->m_BufferSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleCallback(conn, EVENT_DISCONNECTED);
|
HandleCallback(conn, EVENT_DISCONNECTED, 0, conn->m_BufferSize);
|
||||||
|
|
||||||
g_Websocket.m_Connections.EraseSwap(i);
|
g_Websocket.m_Connections.EraseSwap(i);
|
||||||
--i;
|
--i;
|
||||||
@@ -414,12 +550,6 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
CLOSE_CONN("Websocket closing for %s (%s)", conn->m_Url.m_Hostname, WSL_ResultToString(r));
|
CLOSE_CONN("Websocket closing for %s (%s)", conn->m_Url.m_Hostname, WSL_ResultToString(r));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
r = WSL_WantsExit(conn->m_Ctx);
|
|
||||||
if (0 != r)
|
|
||||||
{
|
|
||||||
CLOSE_CONN("Websocket received close event for %s", conn->m_Url.m_Hostname);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
int recv_bytes = 0;
|
int recv_bytes = 0;
|
||||||
dmSocket::Result sr = Receive(conn, conn->m_Buffer, conn->m_BufferCapacity-1, &recv_bytes);
|
dmSocket::Result sr = Receive(conn, conn->m_Buffer, conn->m_BufferCapacity-1, &recv_bytes);
|
||||||
@@ -430,9 +560,7 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
|
|
||||||
if (dmSocket::RESULT_OK == sr)
|
if (dmSocket::RESULT_OK == sr)
|
||||||
{
|
{
|
||||||
conn->m_BufferSize += recv_bytes;
|
PushMessage(conn, MESSAGE_TYPE_NORMAL, recv_bytes, (const uint8_t*)conn->m_Buffer);
|
||||||
conn->m_Buffer[conn->m_BufferCapacity-1] = 0;
|
|
||||||
conn->m_HasMessage = 1;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -441,10 +569,31 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (conn->m_HasMessage)
|
uint32_t offset = 0;
|
||||||
|
bool close_received = false;
|
||||||
|
for (uint32_t i = 0; i < conn->m_Messages.Size(); ++i)
|
||||||
{
|
{
|
||||||
HandleCallback(conn, EVENT_MESSAGE);
|
const Message& msg = conn->m_Messages[i];
|
||||||
conn->m_HasMessage = 0;
|
|
||||||
|
if (EVENT_DISCONNECTED == msg.m_Type)
|
||||||
|
{
|
||||||
|
conn->m_Status = RESULT_OK;
|
||||||
|
CloseConnection(conn);
|
||||||
|
|
||||||
|
// Put the message at the front of the buffer
|
||||||
|
conn->m_Messages.SetSize(0);
|
||||||
|
conn->m_BufferSize = 0;
|
||||||
|
PushMessage(conn, MESSAGE_TYPE_CLOSE, msg.m_Length, (const uint8_t*)conn->m_Buffer+offset);
|
||||||
|
close_received = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleCallback(conn, EVENT_MESSAGE, offset, msg.m_Length);
|
||||||
|
offset += msg.m_Length;
|
||||||
|
}
|
||||||
|
if (!close_received) // saving the close message for next step
|
||||||
|
{
|
||||||
|
conn->m_Messages.SetSize(0);
|
||||||
conn->m_BufferSize = 0;
|
conn->m_BufferSize = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,6 +611,7 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verifies headers, and also stages any initial sent data
|
||||||
result = VerifyHeaders(conn);
|
result = VerifyHeaders(conn);
|
||||||
if (RESULT_OK != result)
|
if (RESULT_OK != result)
|
||||||
{
|
{
|
||||||
@@ -485,11 +635,8 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
#endif
|
#endif
|
||||||
dmSocket::SetBlocking(conn->m_Socket, false);
|
dmSocket::SetBlocking(conn->m_Socket, false);
|
||||||
|
|
||||||
conn->m_Buffer[0] = 0;
|
|
||||||
conn->m_BufferSize = 0;
|
|
||||||
|
|
||||||
SetState(conn, STATE_CONNECTED);
|
SetState(conn, STATE_CONNECTED);
|
||||||
HandleCallback(conn, EVENT_CONNECTED);
|
HandleCallback(conn, EVENT_CONNECTED, 0, 0);
|
||||||
}
|
}
|
||||||
else if (STATE_HANDSHAKE_WRITE == conn->m_State)
|
else if (STATE_HANDSHAKE_WRITE == conn->m_State)
|
||||||
{
|
{
|
||||||
@@ -508,20 +655,49 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
}
|
}
|
||||||
else if (STATE_CONNECTING == conn->m_State)
|
else if (STATE_CONNECTING == conn->m_State)
|
||||||
{
|
{
|
||||||
dmSocket::Result socket_result;
|
|
||||||
int timeout = g_Websocket.m_Timeout;
|
|
||||||
#if defined(__EMSCRIPTEN__)
|
#if defined(__EMSCRIPTEN__)
|
||||||
timeout = 0;
|
conn->m_SSLSocket = dmSSLSocket::INVALID_SOCKET_HANDLE;
|
||||||
#endif
|
|
||||||
dmConnectionPool::Result pool_result = dmConnectionPool::Dial(g_Websocket.m_Pool, conn->m_Url.m_Hostname, conn->m_Url.m_Port, g_Websocket.m_Channel, conn->m_SSL, timeout, &conn->m_Connection, &socket_result);
|
char uri_buffer[dmURI::MAX_URI_LEN];
|
||||||
if (dmConnectionPool::RESULT_OK != pool_result)
|
const char* uri;
|
||||||
{
|
if (conn->m_Url.m_Path[0] != '\0') {
|
||||||
CLOSE_CONN("Failed to open connection: %s", dmSocket::ResultToString(socket_result));
|
dmSnPrintf(uri_buffer, sizeof(uri_buffer), "%s%s", conn->m_Url.m_Hostname, conn->m_Url.m_Path);
|
||||||
|
uri = uri_buffer;
|
||||||
|
} else {
|
||||||
|
uri = conn->m_Url.m_Hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
dmSocket::Address address;
|
||||||
|
dmSocket::Result sr = dmSocket::GetHostByName(uri, &address, true, false);
|
||||||
|
if (dmSocket::RESULT_OK != sr) {
|
||||||
|
CLOSE_CONN("Failed to get address from host name '%s': %s", uri, dmSocket::ResultToString(sr));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sr = dmSocket::New(address.m_family, dmSocket::TYPE_STREAM, dmSocket::PROTOCOL_TCP, &conn->m_Socket);
|
||||||
|
if (dmSocket::RESULT_OK != sr) {
|
||||||
|
CLOSE_CONN("Failed to create socket for '%s': %s", conn->m_Url.m_Hostname, dmSocket::ResultToString(sr));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sr = dmSocket::Connect(conn->m_Socket, address, conn->m_Url.m_Port);
|
||||||
|
if (dmSocket::RESULT_OK != sr) {
|
||||||
|
CLOSE_CONN("Failed to connect to '%s:%d': %s", conn->m_Url.m_Hostname, (int)conn->m_Url.m_Port, dmSocket::ResultToString(sr));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
dmSocket::Result sr;
|
||||||
|
int timeout = g_Websocket.m_Timeout;
|
||||||
|
dmConnectionPool::Result pool_result = dmConnectionPool::Dial(g_Websocket.m_Pool, conn->m_Url.m_Hostname, conn->m_Url.m_Port, g_Websocket.m_Channel, conn->m_SSL, timeout, &conn->m_Connection, &sr);
|
||||||
|
if (dmConnectionPool::RESULT_OK != pool_result)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed to open connection: %s", dmSocket::ResultToString(sr));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
conn->m_Socket = dmConnectionPool::GetSocket(g_Websocket.m_Pool, conn->m_Connection);
|
conn->m_Socket = dmConnectionPool::GetSocket(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
conn->m_SSLSocket = dmConnectionPool::GetSSLSocket(g_Websocket.m_Pool, conn->m_Connection);
|
conn->m_SSLSocket = dmConnectionPool::GetSSLSocket(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
|
#endif
|
||||||
|
|
||||||
SetState(conn, STATE_HANDSHAKE_WRITE);
|
SetState(conn, STATE_HANDSHAKE_WRITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -531,6 +707,6 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
|
|
||||||
} // dmWebsocket
|
} // dmWebsocket
|
||||||
|
|
||||||
DM_DECLARE_EXTENSION(Websocket, LIB_NAME, dmWebsocket::WebsocketAppInitialize, dmWebsocket::WebsocketAppFinalize, dmWebsocket::WebsocketInitialize, dmWebsocket::WebsocketOnUpdate, 0, dmWebsocket::WebsocketFinalize)
|
DM_DECLARE_EXTENSION(Websocket, LIB_NAME, dmWebsocket::AppInitialize, dmWebsocket::AppFinalize, dmWebsocket::Initialize, dmWebsocket::OnUpdate, 0, dmWebsocket::Finalize)
|
||||||
|
|
||||||
#undef CLOSE_CONN
|
#undef CLOSE_CONN
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// include the Defold SDK
|
// include the Defold SDK
|
||||||
#include <dmsdk/sdk.h>
|
#include <dmsdk/sdk.h>
|
||||||
|
|
||||||
@@ -15,6 +19,7 @@
|
|||||||
#include <dmsdk/dlib/socket.h>
|
#include <dmsdk/dlib/socket.h>
|
||||||
#include <dmsdk/dlib/dns.h>
|
#include <dmsdk/dlib/dns.h>
|
||||||
#include <dmsdk/dlib/uri.h>
|
#include <dmsdk/dlib/uri.h>
|
||||||
|
#include <dmsdk/dlib/array.h>
|
||||||
|
|
||||||
namespace dmCrypt
|
namespace dmCrypt
|
||||||
{
|
{
|
||||||
@@ -55,6 +60,18 @@ namespace dmWebsocket
|
|||||||
EVENT_ERROR,
|
EVENT_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum MessageType
|
||||||
|
{
|
||||||
|
MESSAGE_TYPE_NORMAL = 0,
|
||||||
|
MESSAGE_TYPE_CLOSE = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
uint32_t m_Length:30;
|
||||||
|
uint32_t m_Type:2;
|
||||||
|
};
|
||||||
|
|
||||||
struct WebsocketConnection
|
struct WebsocketConnection
|
||||||
{
|
{
|
||||||
dmScript::LuaCallbackInfo* m_Callback;
|
dmScript::LuaCallbackInfo* m_Callback;
|
||||||
@@ -65,14 +82,16 @@ namespace dmWebsocket
|
|||||||
dmConnectionPool::HConnection m_Connection;
|
dmConnectionPool::HConnection m_Connection;
|
||||||
dmSocket::Socket m_Socket;
|
dmSocket::Socket m_Socket;
|
||||||
dmSSLSocket::Socket m_SSLSocket;
|
dmSSLSocket::Socket m_SSLSocket;
|
||||||
|
dmArray<Message> m_Messages; // lengths of the messages in the data buffer
|
||||||
uint8_t m_Key[16];
|
uint8_t m_Key[16];
|
||||||
State m_State;
|
State m_State;
|
||||||
uint32_t m_SSL:1;
|
|
||||||
uint32_t m_HasMessage:1;
|
|
||||||
char* m_Buffer;
|
char* m_Buffer;
|
||||||
int m_BufferSize;
|
int m_BufferSize;
|
||||||
uint32_t m_BufferCapacity;
|
uint32_t m_BufferCapacity;
|
||||||
Result m_Status;
|
Result m_Status;
|
||||||
|
uint8_t m_SSL:1;
|
||||||
|
uint8_t m_HasHandshakeData:1;
|
||||||
|
uint8_t :6;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set error message
|
// Set error message
|
||||||
@@ -92,13 +111,15 @@ namespace dmWebsocket
|
|||||||
Result ReceiveHeaders(WebsocketConnection* conn);
|
Result ReceiveHeaders(WebsocketConnection* conn);
|
||||||
Result VerifyHeaders(WebsocketConnection* conn);
|
Result VerifyHeaders(WebsocketConnection* conn);
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
Result PushMessage(WebsocketConnection* conn, MessageType type, int length, const uint8_t* msg);
|
||||||
|
|
||||||
#if defined(HAVE_WSLAY)
|
#if defined(HAVE_WSLAY)
|
||||||
// Wslay callbacks
|
// Wslay callbacks
|
||||||
int WSL_Init(wslay_event_context_ptr* ctx, ssize_t buffer_size, void* userctx);
|
int WSL_Init(wslay_event_context_ptr* ctx, ssize_t buffer_size, void* userctx);
|
||||||
void WSL_Exit(wslay_event_context_ptr ctx);
|
void WSL_Exit(wslay_event_context_ptr ctx);
|
||||||
int WSL_Close(wslay_event_context_ptr ctx);
|
int WSL_Close(wslay_event_context_ptr ctx);
|
||||||
int WSL_Poll(wslay_event_context_ptr ctx);
|
int WSL_Poll(wslay_event_context_ptr ctx);
|
||||||
int WSL_WantsExit(wslay_event_context_ptr ctx);
|
|
||||||
ssize_t WSL_RecvCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, int flags, void *user_data);
|
ssize_t WSL_RecvCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, int flags, void *user_data);
|
||||||
ssize_t WSL_SendCallback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data);
|
ssize_t WSL_SendCallback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data);
|
||||||
void WSL_OnMsgRecvCallback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data);
|
void WSL_OnMsgRecvCallback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data);
|
||||||
@@ -110,6 +131,15 @@ namespace dmWebsocket
|
|||||||
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t;
|
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t;
|
||||||
void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq);
|
void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq);
|
||||||
uint32_t pcg32_random_r(pcg32_random_t* rng);
|
uint32_t pcg32_random_r(pcg32_random_t* rng);
|
||||||
|
|
||||||
|
// If level <= dmWebSocket::g_DebugWebSocket, then it outputs the message
|
||||||
|
#ifdef __GNUC__
|
||||||
|
void DebugLog(int level, const char* fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
|
#else
|
||||||
|
void DebugLog(int level, const char* fmt, ...);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void DebugPrint(int level, const char* msg, const void* _bytes, uint32_t num_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -54,8 +54,8 @@ void WSL_Exit(wslay_event_context_ptr ctx)
|
|||||||
|
|
||||||
int WSL_Close(wslay_event_context_ptr ctx)
|
int WSL_Close(wslay_event_context_ptr ctx)
|
||||||
{
|
{
|
||||||
const char* reason = "Client wants to close";
|
const char* reason = "";
|
||||||
wslay_event_queue_close(ctx, 0, (const uint8_t*)reason, strlen(reason));
|
wslay_event_queue_close(ctx, WSLAY_CODE_NORMAL_CLOSURE, (const uint8_t*)reason, 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,24 +68,25 @@ int WSL_Poll(wslay_event_context_ptr ctx)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WSL_WantsExit(wslay_event_context_ptr ctx)
|
|
||||||
{
|
|
||||||
if ((wslay_event_get_close_sent(ctx) && wslay_event_get_close_received(ctx))) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t WSL_RecvCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, int flags, void *user_data)
|
ssize_t WSL_RecvCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, int flags, void *user_data)
|
||||||
{
|
{
|
||||||
WebsocketConnection* conn = (WebsocketConnection*)user_data;
|
WebsocketConnection* conn = (WebsocketConnection*)user_data;
|
||||||
|
|
||||||
int r = -1; // received bytes if >=0, error if < 0
|
int r = -1; // received bytes if >=0, error if < 0
|
||||||
|
|
||||||
|
if (conn->m_HasHandshakeData)
|
||||||
|
{
|
||||||
|
r = conn->m_BufferSize;
|
||||||
|
memcpy(buf, conn->m_Buffer, r);
|
||||||
|
conn->m_BufferSize = 0;
|
||||||
|
conn->m_HasHandshakeData = 0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
dmSocket::Result socket_result = Receive(conn, buf, len, &r);
|
dmSocket::Result socket_result = Receive(conn, buf, len, &r);
|
||||||
|
|
||||||
if (dmSocket::RESULT_OK == socket_result && r == 0)
|
if (dmSocket::RESULT_OK == socket_result && r == 0)
|
||||||
socket_result = dmSocket::RESULT_WOULDBLOCK;
|
socket_result = dmSocket::RESULT_CONNABORTED;
|
||||||
|
|
||||||
if (dmSocket::RESULT_OK != socket_result)
|
if (dmSocket::RESULT_OK != socket_result)
|
||||||
{
|
{
|
||||||
@@ -117,20 +118,35 @@ ssize_t WSL_SendCallback(wslay_event_context_ptr ctx, const uint8_t *data, size_
|
|||||||
return (ssize_t)sent_bytes;
|
return (ssize_t)sent_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Might be called multiple times for a connection receiving multiple events
|
||||||
void WSL_OnMsgRecvCallback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data)
|
void WSL_OnMsgRecvCallback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data)
|
||||||
{
|
{
|
||||||
WebsocketConnection* conn = (WebsocketConnection*)user_data;
|
WebsocketConnection* conn = (WebsocketConnection*)user_data;
|
||||||
if (arg->opcode == WSLAY_TEXT_FRAME || arg->opcode == WSLAY_BINARY_FRAME)
|
if (arg->opcode == WSLAY_TEXT_FRAME || arg->opcode == WSLAY_BINARY_FRAME)
|
||||||
{
|
{
|
||||||
if (arg->msg_length >= conn->m_BufferCapacity)
|
PushMessage(conn, MESSAGE_TYPE_NORMAL, arg->msg_length, arg->msg);
|
||||||
conn->m_Buffer = (char*)realloc(conn->m_Buffer, arg->msg_length + 1);
|
|
||||||
memcpy(conn->m_Buffer, arg->msg, arg->msg_length);
|
|
||||||
conn->m_BufferSize = arg->msg_length;
|
|
||||||
conn->m_HasMessage = 1;
|
|
||||||
|
|
||||||
} else if (arg->opcode == WSLAY_CONNECTION_CLOSE)
|
} else if (arg->opcode == WSLAY_CONNECTION_CLOSE)
|
||||||
{
|
{
|
||||||
// TODO: Store the reason
|
// The first two bytes is the close code
|
||||||
|
const uint8_t* reason = (const uint8_t*)"";
|
||||||
|
size_t len = arg->msg_length;
|
||||||
|
if (arg->msg_length > 2)
|
||||||
|
{
|
||||||
|
reason = arg->msg + 2;
|
||||||
|
len -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
len = dmSnPrintf(buffer, sizeof(buffer), "Server closing (%u). Reason: '%s'", wslay_event_get_status_code_received(ctx), reason);
|
||||||
|
PushMessage(conn, MESSAGE_TYPE_CLOSE, len, (const uint8_t*)buffer);
|
||||||
|
|
||||||
|
if (!wslay_event_get_close_sent(ctx))
|
||||||
|
{
|
||||||
|
wslay_event_queue_close(ctx, arg->status_code, (const uint8_t*)buffer, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugLog(1, "%s", buffer);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user