mirror of
https://github.com/defold/extension-websocket.git
synced 2025-09-30 09:12:18 +02:00
Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8a9dc759a4 | ||
|
70afde4cfa | ||
|
ed6d131470 | ||
|
2c9c0b74f1 | ||
|
8450a88640 | ||
|
3636ea73db | ||
|
1cc1d802b3 | ||
|
94bcc82f25 | ||
|
d62a53cbcc | ||
|
ea2fe97943 | ||
|
268e9bf472 | ||
|
5ee358cbba | ||
|
78d527d3a8 | ||
|
b54e3e07ad | ||
|
e105702c79 | ||
|
9d1ace0a82 | ||
|
833477a134 | ||
|
15a589585a | ||
|
3fe8fbc0ff | ||
|
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'
|
||||||
|
}
|
||||||
|
}]
|
44
README.md
44
README.md
@@ -1,4 +1,42 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
## External resources
|
||||||
|
|
||||||
|
To verify that your websocket server works, you can test it with some tools.
|
||||||
|
|
||||||
|
* [websocat](https://github.com/vi/websocat)
|
||||||
|
|
||||||
|
Or, you can test your server on this web page:
|
||||||
|
|
||||||
|
* https://www.websocket.org/echo.html
|
||||||
|
|
||||||
|
To monitor all the packets sent to/from the client/server, you can use e.g.
|
||||||
|
|
||||||
|
* [Wireshark](https://www.wireshark.org)
|
||||||
|
|
||||||
|
For command line debugging, there's
|
||||||
|
|
||||||
|
* tcpdump: `sudo tcpdump -X -s0 -ilo0 port 8080 ` (example for local ws:// connection)
|
||||||
|
|
||||||
|
* tcpdump: `sudo tcpdump -X -s0 host echo.websocket.org` (Monitors packets to/from echo.websocket.org)
|
||||||
|
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)
|
||||||
|
@@ -17,6 +17,17 @@
|
|||||||
type: table
|
type: table
|
||||||
desc: optional parameters as properties. The following parameters can be set
|
desc: optional parameters as properties. The following parameters can be set
|
||||||
members:
|
members:
|
||||||
|
- name: timeout
|
||||||
|
type: number
|
||||||
|
desc: Timeout for the connection sequence (milliseconds). Not used on HTML5. (Default is 3000)
|
||||||
|
|
||||||
|
- name: protocol
|
||||||
|
type: string
|
||||||
|
desc: the protocol to use (e.g. 'chat'). (Default is 'binary')
|
||||||
|
|
||||||
|
- name: headers
|
||||||
|
type: string
|
||||||
|
desc: list of http headers. Each pair is separated with "\r\n". Not used on HTML5.
|
||||||
|
|
||||||
- name: callback
|
- name: callback
|
||||||
type: function
|
type: function
|
||||||
@@ -48,11 +59,7 @@
|
|||||||
|
|
||||||
- name: message
|
- name: message
|
||||||
type: string
|
type: string
|
||||||
desc: The received data. Only valid if event is `websocket.EVENT_MESSAGE`
|
desc: The received data if event is `websocket.EVENT_MESSAGE`. Error message otherwise
|
||||||
|
|
||||||
- name: error
|
|
||||||
type: string
|
|
||||||
desc: The error string. Only valid if event is `websocket.EVENT_ERROR`
|
|
||||||
|
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
@@ -65,21 +72,25 @@
|
|||||||
```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.message .. "'")
|
||||||
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
|
||||||
|
|
||||||
function init(self)
|
function init(self)
|
||||||
self.url = "ws://echo.websocket.org"
|
self.url = "ws://echo.websocket.org"
|
||||||
local params = {}
|
local params = {
|
||||||
|
timeout = 3000,
|
||||||
|
headers = "Sec-WebSocket-Protocol: chat\r\nOrigin: mydomain.com\r\n"
|
||||||
|
}
|
||||||
self.connection = websocket.connect(self.url, params, websocket_callback)
|
self.connection = websocket.connect(self.url, params, websocket_callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -100,6 +111,46 @@
|
|||||||
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
|
||||||
|
- name: options
|
||||||
|
type: table
|
||||||
|
desc: options for this particular message. May be `nil`
|
||||||
|
members:
|
||||||
|
- name: type
|
||||||
|
type: number
|
||||||
|
desc: The data type of the message
|
||||||
|
|
||||||
|
- `websocket.DATA_TYPE_BINARY` (default)
|
||||||
|
|
||||||
|
- `websocket.DATA_TYPE_TEXT`
|
||||||
|
|
||||||
|
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
|
||||||
{
|
{
|
||||||
@@ -39,8 +40,12 @@ static Result SendClientHandshakeHeaders(WebsocketConnection* conn)
|
|||||||
dmSnPrintf(port, sizeof(port), ":%d", conn->m_Url.m_Port);
|
dmSnPrintf(port, sizeof(port), ":%d", conn->m_Url.m_Port);
|
||||||
|
|
||||||
dmSocket::Result sr;
|
dmSocket::Result sr;
|
||||||
WS_SENDALL("GET /");
|
WS_SENDALL("GET ");
|
||||||
|
if (conn->m_Url.m_Path[0] == '\0') {
|
||||||
|
WS_SENDALL("/"); // Default to / for empty path
|
||||||
|
} else {
|
||||||
WS_SENDALL(conn->m_Url.m_Path);
|
WS_SENDALL(conn->m_Url.m_Path);
|
||||||
|
}
|
||||||
WS_SENDALL(" HTTP/1.1\r\n");
|
WS_SENDALL(" HTTP/1.1\r\n");
|
||||||
WS_SENDALL("Host: ");
|
WS_SENDALL("Host: ");
|
||||||
WS_SENDALL(conn->m_Url.m_Hostname);
|
WS_SENDALL(conn->m_Url.m_Hostname);
|
||||||
@@ -53,9 +58,23 @@ static Result SendClientHandshakeHeaders(WebsocketConnection* conn)
|
|||||||
WS_SENDALL("\r\n");
|
WS_SENDALL("\r\n");
|
||||||
WS_SENDALL("Sec-WebSocket-Version: 13\r\n");
|
WS_SENDALL("Sec-WebSocket-Version: 13\r\n");
|
||||||
|
|
||||||
// Add custom protocols
|
if (conn->m_CustomHeaders)
|
||||||
|
{
|
||||||
|
WS_SENDALL(conn->m_CustomHeaders);
|
||||||
|
// make sure we ended with '\r\n'
|
||||||
|
int len = strlen(conn->m_CustomHeaders);
|
||||||
|
bool ended_with_sentinel = len >= 2 && conn->m_CustomHeaders[len - 2] == '\r' && conn->m_CustomHeaders[len - 1] == '\n';
|
||||||
|
if (!ended_with_sentinel)
|
||||||
|
{
|
||||||
|
WS_SENDALL("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add custom headers
|
if (conn->m_Protocol) {
|
||||||
|
WS_SENDALL("Sec-WebSocket-Protocol: ");
|
||||||
|
WS_SENDALL(conn->m_Protocol);
|
||||||
|
WS_SENDALL("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
WS_SENDALL("\r\n");
|
WS_SENDALL("\r\n");
|
||||||
|
|
||||||
@@ -100,17 +119,12 @@ Result ReceiveHeaders(WebsocketConnection* conn)
|
|||||||
#else
|
#else
|
||||||
Result ReceiveHeaders(WebsocketConnection* conn)
|
Result ReceiveHeaders(WebsocketConnection* conn)
|
||||||
{
|
{
|
||||||
dmSocket::Selector selector;
|
dmSocket::Result sr = WaitForSocket(conn, dmSocket::SELECTOR_KIND_READ, SOCKET_WAIT_TIMEOUT);
|
||||||
dmSocket::SelectorZero(&selector);
|
|
||||||
dmSocket::SelectorSet(&selector, dmSocket::SELECTOR_KIND_READ, conn->m_Socket);
|
|
||||||
|
|
||||||
dmSocket::Result sr = dmSocket::Select(&selector, 200*1000);
|
|
||||||
|
|
||||||
if (dmSocket::RESULT_OK != sr)
|
if (dmSocket::RESULT_OK != sr)
|
||||||
{
|
{
|
||||||
if (dmSocket::RESULT_WOULDBLOCK)
|
if (dmSocket::RESULT_WOULDBLOCK)
|
||||||
{
|
{
|
||||||
dmLogWarning("Waiting for socket to be available for reading");
|
DebugLog(2, "Waiting for socket to be available for reading");
|
||||||
return RESULT_WOULDBLOCK;
|
return RESULT_WOULDBLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +160,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 +170,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 +200,16 @@ 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 = "";
|
|
||||||
|
|
||||||
// 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 +223,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
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
#include "script_util.h"
|
#include "script_util.h"
|
||||||
|
|
||||||
namespace dmWebsocket {
|
namespace dmScript {
|
||||||
|
|
||||||
bool luaL_checkbool(lua_State *L, int numArg)
|
bool CheckBool(lua_State *L, int numArg)
|
||||||
{
|
{
|
||||||
bool b = false;
|
bool b = false;
|
||||||
if (lua_isboolean(L, numArg))
|
if (lua_isboolean(L, numArg))
|
||||||
@@ -16,17 +16,17 @@ bool luaL_checkbool(lua_State *L, int numArg)
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool luaL_checkboold(lua_State *L, int numArg, int def)
|
bool CheckBoold(lua_State *L, int numArg, int def)
|
||||||
{
|
{
|
||||||
int type = lua_type(L, numArg);
|
int type = lua_type(L, numArg);
|
||||||
if (type != LUA_TNONE && type != LUA_TNIL)
|
if (type != LUA_TNONE && type != LUA_TNIL)
|
||||||
{
|
{
|
||||||
return luaL_checkbool(L, numArg);
|
return CheckBool(L, numArg);
|
||||||
}
|
}
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_Number luaL_checknumberd(lua_State *L, int numArg, lua_Number def)
|
lua_Number CheckNumberd(lua_State *L, int numArg, lua_Number def)
|
||||||
{
|
{
|
||||||
int type = lua_type(L, numArg);
|
int type = lua_type(L, numArg);
|
||||||
if (type != LUA_TNONE && type != LUA_TNIL)
|
if (type != LUA_TNONE && type != LUA_TNIL)
|
||||||
@@ -36,7 +36,7 @@ lua_Number luaL_checknumberd(lua_State *L, int numArg, lua_Number def)
|
|||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* luaL_checkstringd(lua_State *L, int numArg, const char* def)
|
char* CheckStringd(lua_State *L, int numArg, const char* def)
|
||||||
{
|
{
|
||||||
int type = lua_type(L, numArg);
|
int type = lua_type(L, numArg);
|
||||||
if (type != LUA_TNONE && type != LUA_TNIL)
|
if (type != LUA_TNONE && type != LUA_TNIL)
|
||||||
@@ -46,7 +46,7 @@ char* luaL_checkstringd(lua_State *L, int numArg, const char* def)
|
|||||||
return (char*)def;
|
return (char*)def;
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_Number luaL_checktable_number(lua_State *L, int numArg, const char* field, lua_Number def)
|
lua_Number CheckTableNumber(lua_State *L, int numArg, const char* field, lua_Number def)
|
||||||
{
|
{
|
||||||
lua_Number result = def;
|
lua_Number result = def;
|
||||||
if(lua_istable(L, numArg))
|
if(lua_istable(L, numArg))
|
||||||
@@ -61,7 +61,7 @@ lua_Number luaL_checktable_number(lua_State *L, int numArg, const char* field, l
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* luaL_checktable_string(lua_State *L, int numArg, const char* field, char* def)
|
char* CheckTableString(lua_State *L, int numArg, const char* field, char* def)
|
||||||
{
|
{
|
||||||
char* result = def;
|
char* result = def;
|
||||||
if(lua_istable(L, numArg))
|
if(lua_istable(L, numArg))
|
||||||
|
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
#include <dmsdk/sdk.h>
|
#include <dmsdk/sdk.h>
|
||||||
|
|
||||||
namespace dmWebsocket {
|
namespace dmScript {
|
||||||
bool luaL_checkbool(lua_State *L, int numArg);
|
bool CheckBool(lua_State *L, int numArg);
|
||||||
bool luaL_checkboold(lua_State *L, int numArg, int def);
|
bool CheckBoold(lua_State *L, int numArg, int def);
|
||||||
lua_Number luaL_checknumberd(lua_State *L, int numArg, lua_Number def);
|
lua_Number CheckNumberd(lua_State *L, int numArg, lua_Number def);
|
||||||
char* luaL_checkstringd(lua_State *L, int numArg, const char* def);
|
char* CheckStringd(lua_State *L, int numArg, const char* def);
|
||||||
lua_Number luaL_checktable_number(lua_State *L, int numArg, const char* field, lua_Number def);
|
lua_Number CheckTableNumber(lua_State *L, int numArg, const char* field, lua_Number def);
|
||||||
char* luaL_checktable_string(lua_State *L, int numArg, const char* field, char* def);
|
char* CheckTableString(lua_State *L, int numArg, const char* field, char* def);
|
||||||
} // namespace
|
} // namespace
|
@@ -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,12 @@ 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;
|
||||||
|
conn->m_ConnectTimeout = 0;
|
||||||
|
|
||||||
dmURI::Parts uri;
|
dmURI::Parts uri;
|
||||||
dmURI::Parse(url, &conn->m_Url);
|
dmURI::Parse(url, &conn->m_Url);
|
||||||
@@ -110,6 +189,18 @@ 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;
|
||||||
|
conn->m_WasConnected = 0;
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
conn->m_Ctx = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,14 +211,27 @@ static void DestroyConnection(WebsocketConnection* conn)
|
|||||||
WSL_Exit(conn->m_Ctx);
|
WSL_Exit(conn->m_Ctx);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
free((void*)conn->m_CustomHeaders);
|
||||||
|
free((void*)conn->m_Protocol);
|
||||||
|
|
||||||
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;
|
||||||
|
DebugLog(2, "DestroyConnection: %p", conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -166,13 +270,22 @@ static int LuaConnect(lua_State* L)
|
|||||||
return DM_LUA_ERROR("The web socket module isn't initialized");
|
return DM_LUA_ERROR("The web socket module isn't initialized");
|
||||||
|
|
||||||
const char* url = luaL_checkstring(L, 1);
|
const char* url = luaL_checkstring(L, 1);
|
||||||
|
lua_Number timeout = dmScript::CheckTableNumber(L, 2, "timeout", 3000);
|
||||||
|
const char* custom_headers = dmScript::CheckTableString(L, 2, "headers", 0);
|
||||||
|
const char* protocol = dmScript::CheckTableString(L, 2, "protocol", 0);
|
||||||
|
|
||||||
// long playedTime = luaL_checktable_number(L, 2, "playedTime", -1);
|
if (custom_headers != 0)
|
||||||
// long progressValue = luaL_checktable_number(L, 2, "progressValue", -1);
|
{
|
||||||
// char *description = luaL_checktable_string(L, 2, "description", NULL);
|
if (strstr(custom_headers, "\r\n\r\n") != 0)
|
||||||
// char *coverImage = luaL_checktable_string(L, 2, "coverImage", NULL);
|
{
|
||||||
|
return DM_LUA_ERROR("The header field must not contain double '\\r\\n\\r\\n': '%s'", custom_headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WebsocketConnection* conn = CreateConnection(url);
|
WebsocketConnection* conn = CreateConnection(url);
|
||||||
|
conn->m_ConnectTimeout = dmTime::GetTime() + timeout * 1000;
|
||||||
|
conn->m_CustomHeaders = custom_headers ? strdup(custom_headers) : 0;
|
||||||
|
conn->m_Protocol = protocol ? strdup(protocol) : 0;
|
||||||
|
|
||||||
conn->m_Callback = dmScript::CreateCallback(L, 3);
|
conn->m_Callback = dmScript::CreateCallback(L, 3);
|
||||||
|
|
||||||
@@ -227,7 +340,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 = dmScript::CheckTableNumber(L, 3, "type", WSLAY_BINARY_FRAME);
|
||||||
|
|
||||||
struct wslay_event_msg msg;
|
struct wslay_event_msg msg;
|
||||||
msg.opcode = write_mode;
|
msg.opcode = write_mode;
|
||||||
@@ -247,7 +360,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 +381,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, "error");
|
|
||||||
}
|
|
||||||
else if (EVENT_MESSAGE == event) {
|
|
||||||
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
|
|
||||||
lua_setfield(L, -2, "message");
|
lua_setfield(L, -2, "message");
|
||||||
}
|
|
||||||
|
|
||||||
dmScript::PCall(L, 3, 0);
|
dmScript::PCall(L, 3, 0);
|
||||||
|
|
||||||
@@ -311,13 +418,23 @@ static void LuaInit(lua_State* L)
|
|||||||
SETCONSTANT(EVENT_MESSAGE);
|
SETCONSTANT(EVENT_MESSAGE);
|
||||||
SETCONSTANT(EVENT_ERROR);
|
SETCONSTANT(EVENT_ERROR);
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
lua_pushnumber(L, (lua_Number) WSLAY_BINARY_FRAME);
|
||||||
|
lua_setfield(L, -2, "DATA_TYPE_BINARY");
|
||||||
|
lua_pushnumber(L, (lua_Number) WSLAY_TEXT_FRAME);
|
||||||
|
lua_setfield(L, -2, "DATA_TYPE_TEXT");
|
||||||
|
#else
|
||||||
|
SETCONSTANT(DATA_TYPE_BINARY);
|
||||||
|
SETCONSTANT(DATA_TYPE_TEXT);
|
||||||
|
#endif
|
||||||
|
|
||||||
#undef SETCONSTANT
|
#undef SETCONSTANT
|
||||||
|
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
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 +446,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 +465,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 +489,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 +500,57 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// has the connect procedure taken too long?
|
||||||
|
static bool CheckConnectTimeout(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
uint64_t t = dmTime::GetTime();
|
||||||
|
return t >= conn->m_ConnectTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +562,14 @@ 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);
|
if (conn->m_WasConnected)
|
||||||
|
{
|
||||||
|
HandleCallback(conn, EVENT_DISCONNECTED, 0, conn->m_BufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
g_Websocket.m_Connections.EraseSwap(i);
|
g_Websocket.m_Connections.EraseSwap(i);
|
||||||
--i;
|
--i;
|
||||||
@@ -414,12 +585,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 +595,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,15 +604,42 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (STATE_HANDSHAKE_READ == conn->m_State)
|
else if (STATE_HANDSHAKE_READ == conn->m_State)
|
||||||
{
|
{
|
||||||
|
if (CheckConnectTimeout(conn))
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Connect sequence timed out");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Result result = ReceiveHeaders(conn);
|
Result result = ReceiveHeaders(conn);
|
||||||
if (RESULT_WOULDBLOCK == result)
|
if (RESULT_WOULDBLOCK == result)
|
||||||
{
|
{
|
||||||
@@ -462,6 +652,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,14 +676,18 @@ 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_WasConnected = 1;
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
if (CheckConnectTimeout(conn))
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Connect sequence timed out");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Result result = SendClientHandshake(conn);
|
Result result = SendClientHandshake(conn);
|
||||||
if (RESULT_WOULDBLOCK == result)
|
if (RESULT_WOULDBLOCK == result)
|
||||||
{
|
{
|
||||||
@@ -508,20 +703,62 @@ 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;
|
if (CheckConnectTimeout(conn))
|
||||||
int timeout = g_Websocket.m_Timeout;
|
|
||||||
#if defined(__EMSCRIPTEN__)
|
|
||||||
timeout = 0;
|
|
||||||
#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);
|
|
||||||
if (dmConnectionPool::RESULT_OK != pool_result)
|
|
||||||
{
|
{
|
||||||
CLOSE_CONN("Failed to open connection: %s", dmSocket::ResultToString(socket_result));
|
CLOSE_CONN("Connect sequence timed out");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
conn->m_SSLSocket = dmSSLSocket::INVALID_SOCKET_HANDLE;
|
||||||
|
|
||||||
|
if (conn->m_Protocol) {
|
||||||
|
EM_ASM({
|
||||||
|
// https://emscripten.org/docs/porting/networking.html#emulated-posix-tcp-sockets-over-websockets
|
||||||
|
Module["websocket"]["subprotocol"] = UTF8ToString($0);
|
||||||
|
}, conn->m_Protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
char uri_buffer[dmURI::MAX_URI_LEN];
|
||||||
|
const char* uri;
|
||||||
|
if (conn->m_Url.m_Path[0] != '\0') {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +768,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,24 @@ namespace dmWebsocket
|
|||||||
EVENT_ERROR,
|
EVENT_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum MessageType
|
||||||
|
{
|
||||||
|
MESSAGE_TYPE_NORMAL = 0,
|
||||||
|
MESSAGE_TYPE_CLOSE = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DataType
|
||||||
|
{
|
||||||
|
DATA_TYPE_BINARY = 0,
|
||||||
|
DATA_TYPE_TEXT = 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 +88,20 @@ 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
|
||||||
|
uint64_t m_ConnectTimeout;
|
||||||
uint8_t m_Key[16];
|
uint8_t m_Key[16];
|
||||||
|
const char* m_Protocol;
|
||||||
|
const char* m_CustomHeaders;
|
||||||
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 m_WasConnected:1;
|
||||||
|
uint8_t :6;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set error message
|
// Set error message
|
||||||
@@ -92,13 +121,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 +141,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