mirror of
https://github.com/defold/extension-websocket.git
synced 2025-09-30 09:12:18 +02:00
Compare commits
87 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
65b9e38850 | ||
|
a75c9f6c8f | ||
|
0ac6e3f9a3 | ||
|
6ec47ddc17 | ||
|
24bb291c72 | ||
|
5c5736d8bc | ||
|
24dd20512b | ||
|
63cb5f8de6 | ||
|
ace3242158 | ||
|
4cf11c3048 | ||
|
ecaac30238 | ||
|
8fd283385f | ||
|
dfc94aa94a | ||
|
d5237bbee1 | ||
|
daffff4c06 | ||
|
0f841f16af | ||
|
97cca427d7 | ||
|
bc56b02e5b | ||
|
cba11de5f1 | ||
|
4e67c4dfaa | ||
|
efe9115413 | ||
|
b3afb9a276 | ||
|
7346f142fa | ||
|
165b7333dc | ||
|
bd4e5c0b35 | ||
|
ac230a6278 | ||
|
39abd7cdea | ||
|
f988413a74 | ||
|
73b236b249 | ||
|
636a11daa3 | ||
|
0f147ef180 | ||
|
3cd0328c2a | ||
|
f5aa57452f | ||
|
7e89b8a685 | ||
|
ba7454a431 | ||
|
b93a91c9b8 | ||
|
0bf63cbdf8 | ||
|
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']
|
76
.github/workflows/bob.yml
vendored
Normal file
76
.github/workflows/bob.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
name: Build with bob
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request_target:
|
||||||
|
schedule:
|
||||||
|
# nightly at 05:00 on the 1st and 15th
|
||||||
|
- cron: 0 5 1,15 * *
|
||||||
|
|
||||||
|
env:
|
||||||
|
VERSION_FILENAME: 'info.json'
|
||||||
|
BUILD_SERVER: 'https://build.defold.com'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_with_bob:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [armv7-android, x86_64-linux, x86_64-win32, x86-win32, js-web]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: Build
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '11.0.2'
|
||||||
|
|
||||||
|
- name: Get Defold version
|
||||||
|
run: |
|
||||||
|
TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'`
|
||||||
|
echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV
|
||||||
|
echo "Found version ${TMPVAR}"
|
||||||
|
|
||||||
|
- name: Download bob.jar
|
||||||
|
run: |
|
||||||
|
wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar
|
||||||
|
java -jar bob.jar --version
|
||||||
|
|
||||||
|
- name: Resolve libraries
|
||||||
|
run: java -jar bob.jar resolve --email a@b.com --auth 123456
|
||||||
|
- name: Build
|
||||||
|
run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}}
|
||||||
|
- name: Bundle
|
||||||
|
run: java -jar bob.jar --platform=${{ matrix.platform }} bundle
|
||||||
|
|
||||||
|
# macOS is not technically needed for building, but we want to test bundling as well, since we're also testing the manifest merging
|
||||||
|
build_with_bob_macos:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [armv7-darwin, x86_64-darwin]
|
||||||
|
runs-on: macOS-latest
|
||||||
|
|
||||||
|
name: Build
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '11.0.2'
|
||||||
|
|
||||||
|
- name: Get Defold version
|
||||||
|
run: |
|
||||||
|
TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'`
|
||||||
|
echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV
|
||||||
|
echo "Found version ${TMPVAR}"
|
||||||
|
|
||||||
|
- name: Download bob.jar
|
||||||
|
run: |
|
||||||
|
wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar
|
||||||
|
java -jar bob.jar --version
|
||||||
|
|
||||||
|
- name: Resolve libraries
|
||||||
|
run: java -jar bob.jar resolve --email a@b.com --auth 123456
|
||||||
|
- name: Build
|
||||||
|
run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}}
|
||||||
|
- name: Bundle
|
||||||
|
run: java -jar bob.jar --platform=${{ matrix.platform }} bundle
|
22
.github/workflows/trigger-site-rebuild.yml
vendored
Normal file
22
.github/workflows/trigger-site-rebuild.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Trigger site rebuild
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
}]
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ builtins
|
|||||||
lws_source
|
lws_source
|
||||||
lws_build
|
lws_build
|
||||||
*.profraw
|
*.profraw
|
||||||
|
*.der
|
61
README.md
61
README.md
@@ -1,4 +1,59 @@
|
|||||||
# 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/).
|
[](https://github.com/defold/extension-websocket/actions)
|
||||||
|
|
||||||
|
## 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` (edit `game.project` as text and add):
|
||||||
|
|
||||||
|
```
|
||||||
|
[websocket]
|
||||||
|
debug = level
|
||||||
|
```
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
This extension makes use of the C library WSlay by @tatsuhiro-t:
|
||||||
|
|
||||||
|
* https://github.com/tatsuhiro-t/wslay
|
||||||
|
|
||||||
|
The test server used by the example:
|
||||||
|
|
||||||
|
* https://www.lob.com/blog/websocket-org-is-down-here-is-an-alternative
|
||||||
|
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,13 +14,21 @@ 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)
|
print("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
|
print("Connected: " .. tostring(conn))
|
||||||
elseif data.event == websocket.EVENT_ERROR then
|
elseif data.event == websocket.EVENT_ERROR then
|
||||||
print("Error:", data.error)
|
print("Error: '" .. tostring(data.message) .. "'")
|
||||||
|
if data.handshake_response then
|
||||||
|
print("Handshake response status: '" .. tostring(data.handshake_response.status) .. "'")
|
||||||
|
for key, value in pairs(data.handshake_response.headers) do
|
||||||
|
log("Handshake response header: '" .. key .. ": " .. value .. "'")
|
||||||
|
end
|
||||||
|
print("Handshake response body: '" .. tostring(data.handshake_response.response) .. "'")
|
||||||
|
end
|
||||||
elseif data.event == websocket.EVENT_MESSAGE then
|
elseif data.event == websocket.EVENT_MESSAGE then
|
||||||
print("Receiving: '" .. tostring(data.message) .. "'")
|
print("Receiving: '" .. tostring(data.message) .. "'")
|
||||||
end
|
end
|
||||||
@@ -48,6 +56,19 @@ 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).
|
We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-websocket/releases).
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
The following configuration options can be set in the game.project file:
|
||||||
|
|
||||||
|
```
|
||||||
|
[websocket]
|
||||||
|
debug = 1
|
||||||
|
socket_timeout = 10000000
|
||||||
|
```
|
||||||
|
|
||||||
|
* `debug` - Log level where 0 means no logs and higher values shows more logs (currently 1 or 2).
|
||||||
|
* `socket_timeout` - Timeout for the underlying socket connection. In microseconds.
|
||||||
|
|
||||||
|
|
||||||
## Source code
|
## Source code
|
||||||
|
|
||||||
The source code is available on [GitHub](https://github.com/defold/extension-websocket)
|
The source code is available on [GitHub](https://github.com/defold/extension-websocket)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
local URL="://echo.websocket.org"
|
local URL = "echo.websocket.events"
|
||||||
|
|
||||||
local function click_button(node, action)
|
local function click_button(node, action)
|
||||||
return gui.is_enabled(node) and action.pressed and gui.pick_node(node, action.x, action.y)
|
return gui.is_enabled(node) and action.pressed and gui.pick_node(node, action.x, action.y)
|
||||||
@@ -57,14 +57,21 @@ end
|
|||||||
|
|
||||||
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
|
||||||
log("Disconnected: " .. tostring(conn))
|
log("Disconnected: " .. tostring(conn) .. " Code: " .. data.code .. " Message: " .. tostring(data.message))
|
||||||
self.connection = nil
|
self.connection = nil
|
||||||
update_gui(self)
|
update_gui(self)
|
||||||
elseif data.event == websocket.EVENT_CONNECTED then
|
elseif data.event == websocket.EVENT_CONNECTED then
|
||||||
update_gui(self)
|
update_gui(self)
|
||||||
log("Connected: " .. tostring(conn))
|
log("Connected: " .. tostring(conn))
|
||||||
elseif data.event == websocket.EVENT_ERROR then
|
elseif data.event == websocket.EVENT_ERROR then
|
||||||
log("Error: '" .. data.error .. "'")
|
log("Error: '" .. tostring(data.message) .. "'")
|
||||||
|
if data.handshake_response then
|
||||||
|
log("Handshake response status: '" .. tostring(data.handshake_response.status) .. "'")
|
||||||
|
for key, value in pairs(data.handshake_response.headers) do
|
||||||
|
log("Handshake response header: '" .. key .. ": " .. value .. "'")
|
||||||
|
end
|
||||||
|
log("Handshake response body: '" .. tostring(data.handshake_response.response) .. "'")
|
||||||
|
end
|
||||||
elseif data.event == websocket.EVENT_MESSAGE then
|
elseif data.event == websocket.EVENT_MESSAGE then
|
||||||
log("Receiving: '" .. tostring(data.message) .. "'")
|
log("Receiving: '" .. tostring(data.message) .. "'")
|
||||||
end
|
end
|
||||||
@@ -73,7 +80,7 @@ end
|
|||||||
local function connect(self, scheme)
|
local function connect(self, scheme)
|
||||||
local params = {}
|
local params = {}
|
||||||
|
|
||||||
self.url = scheme .. URL
|
self.url = scheme .. "://" .. URL
|
||||||
log("Connecting to " .. self.url)
|
log("Connecting to " .. self.url)
|
||||||
self.connection = websocket.connect(self.url, params, websocket_callback)
|
self.connection = websocket.connect(self.url, params, websocket_callback)
|
||||||
end
|
end
|
||||||
|
@@ -14,4 +14,3 @@ _dependencies = https://github.com/GameAnalytics/defold-openssl/archive/1.0.0.zi
|
|||||||
|
|
||||||
[library]
|
[library]
|
||||||
include_dirs = websocket
|
include_dirs = websocket
|
||||||
|
|
||||||
|
@@ -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'). If not set, no `Sec-WebSocket-Protocol` header is sent.
|
||||||
|
|
||||||
|
- 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,15 @@
|
|||||||
|
|
||||||
- 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
|
- name: handshake_response
|
||||||
type: string
|
type: table
|
||||||
desc: The error string. Only valid if event is `websocket.EVENT_ERROR`
|
desc: Handshake response information (status, headers etc)
|
||||||
|
|
||||||
|
- name: code
|
||||||
|
type: number
|
||||||
|
desc: Status code received from the server if the server closed the connection. Only present if event is `EVENT_DISCONNECTED`.
|
||||||
|
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
@@ -65,21 +80,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.events"
|
||||||
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 +119,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
|
||||||
|
@@ -6,3 +6,10 @@ platforms:
|
|||||||
context:
|
context:
|
||||||
includes: ["upload/websocket/include/wslay"]
|
includes: ["upload/websocket/include/wslay"]
|
||||||
defines: ["HAVE_CONFIG_H"]
|
defines: ["HAVE_CONFIG_H"]
|
||||||
|
|
||||||
|
wasm-web:
|
||||||
|
context:
|
||||||
|
linkFlags: ["-lwebsocket.js"]
|
||||||
|
js-web:
|
||||||
|
context:
|
||||||
|
linkFlags: ["-lwebsocket.js"]
|
@@ -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
|
||||||
|
42
websocket/src/emscripten_callbacks.cpp
Normal file
42
websocket/src/emscripten_callbacks.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include "websocket.h"
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
|
||||||
|
namespace dmWebsocket
|
||||||
|
{
|
||||||
|
EM_BOOL Emscripten_WebSocketOnOpen(int eventType, const EmscriptenWebSocketOpenEvent *websocketEvent, void *userData) {
|
||||||
|
DebugLog(1, "WebSocket OnOpen");
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)userData;
|
||||||
|
SetState(conn, STATE_CONNECTED);
|
||||||
|
HandleCallback(conn, EVENT_CONNECTED, 0, 0);
|
||||||
|
return EM_TRUE;
|
||||||
|
}
|
||||||
|
EM_BOOL Emscripten_WebSocketOnError(int eventType, const EmscriptenWebSocketErrorEvent *websocketEvent, void *userData) {
|
||||||
|
DebugLog(1, "WebSocket OnError");
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)userData;
|
||||||
|
conn->m_Status = RESULT_ERROR;
|
||||||
|
SetState(conn, STATE_DISCONNECTED);
|
||||||
|
return EM_TRUE;
|
||||||
|
}
|
||||||
|
EM_BOOL Emscripten_WebSocketOnClose(int eventType, const EmscriptenWebSocketCloseEvent *websocketEvent, void *userData) {
|
||||||
|
DebugLog(1, "WebSocket OnClose");
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)userData;
|
||||||
|
int length = strlen(websocketEvent->reason);
|
||||||
|
PushMessage(conn, MESSAGE_TYPE_CLOSE, length, (uint8_t*)websocketEvent->reason, websocketEvent->code);
|
||||||
|
return EM_TRUE;
|
||||||
|
}
|
||||||
|
EM_BOOL Emscripten_WebSocketOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *websocketEvent, void *userData) {
|
||||||
|
DebugLog(1, "WebSocket OnMessage");
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)userData;
|
||||||
|
int length = websocketEvent->numBytes;
|
||||||
|
if (websocketEvent->isText)
|
||||||
|
{
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
PushMessage(conn, MESSAGE_TYPE_NORMAL, length, websocketEvent->data, 0);
|
||||||
|
return EM_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif // __EMSCRIPTEN__
|
@@ -1,5 +1,7 @@
|
|||||||
#include "websocket.h"
|
#include "websocket.h"
|
||||||
#include <dmsdk/dlib/socket.h>
|
#include <dmsdk/dlib/socket.h>
|
||||||
|
#include <dmsdk/dlib/http_client.h>
|
||||||
|
#include <ctype.h> // tolower
|
||||||
|
|
||||||
namespace dmWebsocket
|
namespace dmWebsocket
|
||||||
{
|
{
|
||||||
@@ -39,8 +41,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 ");
|
||||||
WS_SENDALL(conn->m_Url.m_Path);
|
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(" 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 +59,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 +120,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 +161,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 +171,65 @@ Result ReceiveHeaders(WebsocketConnection* conn)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void HandleVersion(void* user_data, int major, int minor, int status, const char* status_str)
|
||||||
|
{
|
||||||
|
HandshakeResponse* response = (HandshakeResponse*)user_data;
|
||||||
|
response->m_HttpMajor = major;
|
||||||
|
response->m_HttpMinor = minor;
|
||||||
|
response->m_ResponseStatusCode = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void HandleHeader(void* user_data, const char* key, const char* value)
|
||||||
|
{
|
||||||
|
HandshakeResponse* response = (HandshakeResponse*)user_data;
|
||||||
|
if (response->m_Headers.Remaining() == 0)
|
||||||
|
{
|
||||||
|
response->m_Headers.OffsetCapacity(4);
|
||||||
|
}
|
||||||
|
HttpHeader* new_header = new HttpHeader(key, value);
|
||||||
|
|
||||||
|
response->m_Headers.Push(new_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void HandleContent(void* user_data, int offset)
|
||||||
|
{
|
||||||
|
HandshakeResponse* response = (HandshakeResponse*)user_data;
|
||||||
|
response->m_BodyOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ValidateSecretKey(WebsocketConnection* conn, const char* server_key)
|
||||||
|
{
|
||||||
|
uint8_t client_key[32 + 40];
|
||||||
|
uint32_t client_key_len = sizeof(client_key);
|
||||||
|
dmCrypt::Base64Encode(conn->m_Key, sizeof(conn->m_Key), client_key, &client_key_len);
|
||||||
|
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));
|
||||||
|
client_key_len += strlen(RFC_MAGIC);
|
||||||
|
client_key[client_key_len] = 0;
|
||||||
|
|
||||||
|
DebugLog(2, "Secret key + RFC_MAGIC: %s", client_key);
|
||||||
|
|
||||||
|
uint8_t client_key_sha1[20];
|
||||||
|
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);
|
||||||
|
dmCrypt::Base64Encode(client_key_sha1, sizeof(client_key_sha1), client_key, &client_key_len);
|
||||||
|
client_key[client_key_len] = 0;
|
||||||
|
DebugLog(2, "Client key (base64): %s", client_key);
|
||||||
|
DebugLog(2, "Server key (base64): %s", server_key);
|
||||||
|
|
||||||
|
return strcmp(server_key, (const char*)client_key) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#if defined(__EMSCRIPTEN__)
|
#if defined(__EMSCRIPTEN__)
|
||||||
Result VerifyHeaders(WebsocketConnection* conn)
|
Result VerifyHeaders(WebsocketConnection* conn)
|
||||||
{
|
{
|
||||||
@@ -165,74 +240,54 @@ Result VerifyHeaders(WebsocketConnection* conn)
|
|||||||
{
|
{
|
||||||
char* r = conn->m_Buffer;
|
char* r = conn->m_Buffer;
|
||||||
|
|
||||||
// According to protocol, the response should start with "HTTP/1.1 <statuscode> <message>"
|
// Find start of payload now because dmHttpClient::ParseHeader is destructive
|
||||||
const char* http_version_and_status_protocol = "HTTP/1.1 101";
|
const char* start_of_payload = strstr(conn->m_Buffer, "\r\n\r\n");
|
||||||
if (strstr(r, http_version_and_status_protocol) != r) {
|
start_of_payload += 4;
|
||||||
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Missing: '%s' in header", http_version_and_status_protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
r = strstr(r, "\r\n") + 2;
|
HandshakeResponse* response = new HandshakeResponse();
|
||||||
|
conn->m_HandshakeResponse = response;
|
||||||
bool upgraded = false;
|
dmHttpClient::ParseResult parse_result = dmHttpClient::ParseHeader(r, response, true, &HandleVersion, &HandleHeader, &HandleContent);
|
||||||
bool valid_key = false;
|
if (parse_result != dmHttpClient::ParseResult::PARSE_RESULT_OK)
|
||||||
const char* protocol = "";
|
|
||||||
|
|
||||||
// TODO: Perhaps also support the Sec-WebSocket-Protocol
|
|
||||||
|
|
||||||
// parse the headers in place
|
|
||||||
while (r)
|
|
||||||
{
|
{
|
||||||
// Tokenize the each header line: "Key: Value\r\n"
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Failed to parse handshake response. 'dmHttpClient::ParseResult=%i'", parse_result);
|
||||||
const char* key = r;
|
|
||||||
r = strchr(r, ':');
|
|
||||||
*r = 0;
|
|
||||||
++r;
|
|
||||||
const char* value = r;
|
|
||||||
while(*value == ' ')
|
|
||||||
++value;
|
|
||||||
r = strstr(r, "\r\n");
|
|
||||||
*r = 0;
|
|
||||||
r += 2;
|
|
||||||
|
|
||||||
if (strcmp(key, "Connection") == 0 && strcmp(value, "Upgrade") == 0)
|
|
||||||
upgraded = true;
|
|
||||||
else if (strcmp(key, "Sec-WebSocket-Accept") == 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
uint8_t client_key[32 + 40];
|
|
||||||
uint32_t client_key_len = sizeof(client_key);
|
|
||||||
dmCrypt::Base64Encode(conn->m_Key, sizeof(conn->m_Key), client_key, &client_key_len);
|
|
||||||
client_key[client_key_len] = 0;
|
|
||||||
|
|
||||||
memcpy(client_key + client_key_len, RFC_MAGIC, strlen(RFC_MAGIC));
|
|
||||||
client_key_len += strlen(RFC_MAGIC);
|
|
||||||
client_key[client_key_len] = 0;
|
|
||||||
|
|
||||||
uint8_t client_key_sha1[20];
|
|
||||||
dmCrypt::HashSha1(client_key, client_key_len, client_key_sha1);
|
|
||||||
|
|
||||||
client_key_len = sizeof(client_key);
|
|
||||||
dmCrypt::Base64Encode(client_key_sha1, sizeof(client_key_sha1), client_key, &client_key_len);
|
|
||||||
client_key[client_key_len] = 0;
|
|
||||||
|
|
||||||
if (strcmp(value, (const char*)client_key) == 0)
|
|
||||||
valid_key = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(r, "\r\n") == 0)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!upgraded)
|
if (response->m_ResponseStatusCode != 101) {
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Wrong response status: %i", response->m_ResponseStatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeader *connection_header, *upgrade_header, *websocket_secret_header;
|
||||||
|
connection_header = response->GetHeader("Connection");
|
||||||
|
upgrade_header = response->GetHeader("Upgrade");
|
||||||
|
websocket_secret_header = response->GetHeader("Sec-WebSocket-Accept");
|
||||||
|
bool connection = connection_header && dmStrCaseCmp(connection_header->m_Value, "Upgrade") == 0;
|
||||||
|
bool upgrade = upgrade_header && dmStrCaseCmp(upgrade_header->m_Value, "websocket") == 0;
|
||||||
|
bool valid_key = websocket_secret_header && ValidateSecretKey(conn, websocket_secret_header->m_Value);
|
||||||
|
|
||||||
|
// Send error to lua?
|
||||||
|
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;
|
||||||
dmLogError("Response:\n\"%s\"\n", conn->m_Buffer);
|
if(!ok)
|
||||||
|
{
|
||||||
|
return RESULT_HANDSHAKE_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (upgraded && valid_key) ? RESULT_OK : RESULT_HANDSHAKE_FAILED;
|
delete conn->m_HandshakeResponse;
|
||||||
|
conn->m_HandshakeResponse = 0;
|
||||||
|
|
||||||
|
// The response might contain both the headers, but also (if successful) the first batch of data
|
||||||
|
uint32_t size = conn->m_BufferSize - (start_of_payload - conn->m_Buffer);
|
||||||
|
conn->m_BufferSize = size;
|
||||||
|
memmove(conn->m_Buffer, start_of_payload, size);
|
||||||
|
conn->m_Buffer[size] = 0;
|
||||||
|
conn->m_HasHandshakeData = conn->m_BufferSize != 0 ? 1 : 0;
|
||||||
|
return RESULT_OK;
|
||||||
}
|
}
|
||||||
#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
|
@@ -7,11 +7,23 @@
|
|||||||
#include "websocket.h"
|
#include "websocket.h"
|
||||||
#include "script_util.h"
|
#include "script_util.h"
|
||||||
#include <dmsdk/dlib/connection_pool.h>
|
#include <dmsdk/dlib/connection_pool.h>
|
||||||
#include <dmsdk/dlib/dns.h>
|
#include <dmsdk/dlib/thread.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
|
||||||
|
#include <emscripten/websocket.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WIN32)
|
||||||
|
#include <malloc.h>
|
||||||
|
#define alloca _alloca
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace dmWebsocket {
|
namespace dmWebsocket {
|
||||||
|
|
||||||
|
int g_DebugWebSocket = 0;
|
||||||
|
|
||||||
struct WebsocketContext
|
struct WebsocketContext
|
||||||
{
|
{
|
||||||
@@ -19,14 +31,10 @@ struct WebsocketContext
|
|||||||
int m_Timeout;
|
int m_Timeout;
|
||||||
dmArray<WebsocketConnection*> m_Connections;
|
dmArray<WebsocketConnection*> m_Connections;
|
||||||
dmConnectionPool::HPool m_Pool;
|
dmConnectionPool::HPool m_Pool;
|
||||||
dmDNS::HChannel m_Channel;
|
|
||||||
uint32_t m_Initialized:1;
|
uint32_t m_Initialized:1;
|
||||||
} g_Websocket;
|
} g_Websocket;
|
||||||
|
|
||||||
|
|
||||||
static void HandleCallback(WebsocketConnection* conn, int event);
|
|
||||||
|
|
||||||
|
|
||||||
#define STRING_CASE(_X) case _X: return #_X;
|
#define STRING_CASE(_X) case _X: return #_X;
|
||||||
|
|
||||||
const char* ResultToString(Result err)
|
const char* ResultToString(Result err)
|
||||||
@@ -45,10 +53,12 @@ const char* ResultToString(Result err)
|
|||||||
const char* StateToString(State err)
|
const char* StateToString(State err)
|
||||||
{
|
{
|
||||||
switch(err) {
|
switch(err) {
|
||||||
|
STRING_CASE(STATE_CREATE);
|
||||||
STRING_CASE(STATE_CONNECTING);
|
STRING_CASE(STATE_CONNECTING);
|
||||||
STRING_CASE(STATE_HANDSHAKE_WRITE);
|
STRING_CASE(STATE_HANDSHAKE_WRITE);
|
||||||
STRING_CASE(STATE_HANDSHAKE_READ);
|
STRING_CASE(STATE_HANDSHAKE_READ);
|
||||||
STRING_CASE(STATE_CONNECTED);
|
STRING_CASE(STATE_CONNECTED);
|
||||||
|
STRING_CASE(STATE_DISCONNECTING);
|
||||||
STRING_CASE(STATE_DISCONNECTED);
|
STRING_CASE(STATE_DISCONNECTED);
|
||||||
default: return "Unknown error";
|
default: return "Unknown error";
|
||||||
};
|
};
|
||||||
@@ -56,21 +66,85 @@ 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__); \
|
||||||
CloseConnection(conn);
|
CloseConnection(conn);
|
||||||
|
|
||||||
|
|
||||||
static void SetState(WebsocketConnection* conn, State state)
|
void SetState(WebsocketConnection* conn, State state)
|
||||||
{
|
{
|
||||||
State prev_state = conn->m_State;
|
State prev_state = conn->m_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 +159,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;
|
||||||
}
|
}
|
||||||
@@ -92,23 +168,38 @@ Result SetStatus(WebsocketConnection* conn, Result status, const char* format, .
|
|||||||
// ***************************************************************************************************
|
// ***************************************************************************************************
|
||||||
// LUA functions
|
// LUA functions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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::Parse(url, &conn->m_Url);
|
dmURI::Parse(url, &conn->m_Url);
|
||||||
|
|
||||||
if (strcmp(conn->m_Url.m_Scheme, "https") == 0)
|
if (strcmp(conn->m_Url.m_Scheme, "https") == 0)
|
||||||
strcpy(conn->m_Url.m_Scheme, "wss");
|
strcpy(conn->m_Url.m_Scheme, "wss");
|
||||||
|
|
||||||
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_CREATE;
|
||||||
|
|
||||||
|
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_HandshakeResponse = 0;
|
||||||
|
conn->m_ConnectionThread = 0;
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
conn->m_Ctx = 0;
|
||||||
|
#endif
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
conn->m_WS = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
@@ -120,39 +211,72 @@ 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_WS)
|
||||||
|
{
|
||||||
|
emscripten_websocket_delete(conn->m_WS);
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (conn->m_Connection)
|
if (conn->m_Connection)
|
||||||
dmConnectionPool::Return(g_Websocket.m_Pool, conn->m_Connection);
|
dmConnectionPool::Close(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (conn->m_HandshakeResponse)
|
||||||
|
delete conn->m_HandshakeResponse;
|
||||||
|
|
||||||
|
|
||||||
free((void*)conn->m_Buffer);
|
free((void*)conn->m_Buffer);
|
||||||
free((void*)conn);
|
|
||||||
|
if (conn->m_ConnectionThread)
|
||||||
|
{
|
||||||
|
dmThread::Join(conn->m_ConnectionThread);
|
||||||
|
conn->m_ConnectionThread = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete conn;
|
||||||
|
DebugLog(2, "DestroyConnection: %p", conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void CloseConnection(WebsocketConnection* conn)
|
static void CloseConnection(WebsocketConnection* conn)
|
||||||
{
|
{
|
||||||
State prev_state = conn->m_State;
|
|
||||||
|
|
||||||
// we want it to send this message in the polling
|
// we want it to send this message in the polling
|
||||||
if (conn->m_State == STATE_CONNECTED) {
|
if (conn->m_State == STATE_CONNECTED) {
|
||||||
#if defined(HAVE_WSLAY)
|
#if defined(HAVE_WSLAY)
|
||||||
WSL_Close(conn->m_Ctx);
|
WSL_Close(conn->m_Ctx);
|
||||||
|
#else
|
||||||
|
// start disconnecting by closing the WebSocket through the JS API
|
||||||
|
// we transition to the DISCONNECTED state when we receive the
|
||||||
|
// Emscripten callback that the connection has closed
|
||||||
|
emscripten_websocket_close(conn->m_WS, 1000, "CloseConnection");
|
||||||
|
SetState(conn, STATE_DISCONNECTING);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
// close the connection and immediately transition to the DISCONNECTED
|
||||||
|
// state
|
||||||
SetState(conn, STATE_DISCONNECTED);
|
SetState(conn, STATE_DISCONNECTED);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int FindConnection(WebsocketConnection* conn)
|
static bool IsConnectionValid(WebsocketConnection* conn)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < g_Websocket.m_Connections.Size(); ++i )
|
if (conn)
|
||||||
{
|
{
|
||||||
if (g_Websocket.m_Connections[i] == conn)
|
for (int i = 0; i < g_Websocket.m_Connections.Size(); ++i )
|
||||||
return i;
|
{
|
||||||
|
if (g_Websocket.m_Connections[i] == conn)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#
|
/*#
|
||||||
@@ -166,13 +290,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);
|
||||||
|
|
||||||
@@ -196,8 +329,7 @@ static int LuaDisconnect(lua_State* L)
|
|||||||
|
|
||||||
WebsocketConnection* conn = (WebsocketConnection*)lua_touserdata(L, 1);
|
WebsocketConnection* conn = (WebsocketConnection*)lua_touserdata(L, 1);
|
||||||
|
|
||||||
int i = FindConnection(conn);
|
if (IsConnectionValid(conn))
|
||||||
if (i != -1)
|
|
||||||
{
|
{
|
||||||
CloseConnection(conn);
|
CloseConnection(conn);
|
||||||
}
|
}
|
||||||
@@ -216,8 +348,7 @@ static int LuaSend(lua_State* L)
|
|||||||
|
|
||||||
WebsocketConnection* conn = (WebsocketConnection*)lua_touserdata(L, 1);
|
WebsocketConnection* conn = (WebsocketConnection*)lua_touserdata(L, 1);
|
||||||
|
|
||||||
int i = FindConnection(conn);
|
if (!IsConnectionValid(conn))
|
||||||
if (i == -1)
|
|
||||||
return DM_LUA_ERROR("Invalid connection");
|
return DM_LUA_ERROR("Invalid connection");
|
||||||
|
|
||||||
if (conn->m_State != STATE_CONNECTED)
|
if (conn->m_State != STATE_CONNECTED)
|
||||||
@@ -227,7 +358,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;
|
||||||
@@ -236,9 +367,17 @@ static int LuaSend(lua_State* L)
|
|||||||
|
|
||||||
wslay_event_queue_msg(conn->m_Ctx, &msg); // it makes a copy of the data
|
wslay_event_queue_msg(conn->m_Ctx, &msg); // it makes a copy of the data
|
||||||
#else
|
#else
|
||||||
|
EMSCRIPTEN_RESULT result;
|
||||||
dmSocket::Result sr = Send(conn, string, string_length, 0);
|
int write_mode = dmScript::CheckTableNumber(L, 3, "type", DATA_TYPE_BINARY);
|
||||||
if (dmSocket::RESULT_OK != sr)
|
if (write_mode == DATA_TYPE_BINARY)
|
||||||
|
{
|
||||||
|
result = emscripten_websocket_send_binary(conn->m_WS, (void*)string, string_length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = emscripten_websocket_send_utf8_text(conn->m_WS, string);
|
||||||
|
}
|
||||||
|
if (result)
|
||||||
{
|
{
|
||||||
CLOSE_CONN("Failed to send on websocket");
|
CLOSE_CONN("Failed to send on websocket");
|
||||||
}
|
}
|
||||||
@@ -247,7 +386,7 @@ static int LuaSend(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void HandleCallback(WebsocketConnection* conn, int event)
|
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,13 +407,39 @@ static void HandleCallback(WebsocketConnection* conn, int event)
|
|||||||
lua_pushinteger(L, event);
|
lua_pushinteger(L, event);
|
||||||
lua_setfield(L, -2, "event");
|
lua_setfield(L, -2, "event");
|
||||||
|
|
||||||
if (EVENT_ERROR == event) {
|
lua_pushlstring(L, conn->m_Buffer + msg_offset, msg_length);
|
||||||
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
|
lua_setfield(L, -2, "message");
|
||||||
lua_setfield(L, -2, "error");
|
|
||||||
|
if(conn->m_HandshakeResponse)
|
||||||
|
{
|
||||||
|
HandshakeResponse* response = conn->m_HandshakeResponse;
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
lua_pushnumber(L, response->m_ResponseStatusCode);
|
||||||
|
lua_setfield(L, -2, "status");
|
||||||
|
|
||||||
|
lua_pushstring(L, &conn->m_Buffer[response->m_BodyOffset]);
|
||||||
|
lua_setfield(L, -2, "response");
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
for (uint32_t i = 0; i < response->m_Headers.Size(); ++i)
|
||||||
|
{
|
||||||
|
lua_pushstring(L, response->m_Headers[i]->m_Value);
|
||||||
|
lua_setfield(L, -2, response->m_Headers[i]->m_Key);
|
||||||
|
}
|
||||||
|
lua_setfield(L, -2, "headers");
|
||||||
|
|
||||||
|
lua_setfield(L, -2, "handshake_response");
|
||||||
|
|
||||||
|
delete conn->m_HandshakeResponse;
|
||||||
|
conn->m_HandshakeResponse = 0;
|
||||||
}
|
}
|
||||||
else if (EVENT_MESSAGE == event) {
|
|
||||||
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
|
if (event == EVENT_DISCONNECTED)
|
||||||
lua_setfield(L, -2, "message");
|
{
|
||||||
|
lua_pushinteger(L, conn->m_CloseCode);
|
||||||
|
lua_setfield(L, -2, "code");
|
||||||
}
|
}
|
||||||
|
|
||||||
dmScript::PCall(L, 3, 0);
|
dmScript::PCall(L, 3, 0);
|
||||||
@@ -283,6 +448,42 @@ static void HandleCallback(WebsocketConnection* conn, int event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HttpHeader::HttpHeader(const char* key, const char* value)
|
||||||
|
{
|
||||||
|
m_Key = strdup(key);
|
||||||
|
m_Value = strdup(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeader::~HttpHeader()
|
||||||
|
{
|
||||||
|
free((void*)m_Key);
|
||||||
|
free((void*)m_Value);
|
||||||
|
m_Key = 0;
|
||||||
|
m_Value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeader* HandshakeResponse::GetHeader(const char* header_key)
|
||||||
|
{
|
||||||
|
for(uint32_t i = 0; i < m_Headers.Size(); ++i)
|
||||||
|
{
|
||||||
|
if (dmStrCaseCmp(m_Headers[i]->m_Key, header_key) == 0)
|
||||||
|
{
|
||||||
|
return m_Headers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandshakeResponse::~HandshakeResponse()
|
||||||
|
{
|
||||||
|
for(uint32_t i = 0; i < m_Headers.Size(); ++i)
|
||||||
|
{
|
||||||
|
delete m_Headers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ***************************************************************************************************
|
// ***************************************************************************************************
|
||||||
// Life cycle functions
|
// Life cycle functions
|
||||||
|
|
||||||
@@ -303,13 +504,23 @@ static void LuaInit(lua_State* L)
|
|||||||
luaL_register(L, MODULE_NAME, Websocket_module_methods);
|
luaL_register(L, MODULE_NAME, Websocket_module_methods);
|
||||||
|
|
||||||
#define SETCONSTANT(_X) \
|
#define SETCONSTANT(_X) \
|
||||||
lua_pushnumber(L, (lua_Number) _X); \
|
lua_pushnumber(L, (lua_Number) _X); \
|
||||||
lua_setfield(L, -2, #_X);
|
lua_setfield(L, -2, #_X);
|
||||||
|
|
||||||
SETCONSTANT(EVENT_CONNECTED);
|
SETCONSTANT(EVENT_CONNECTED);
|
||||||
SETCONSTANT(EVENT_DISCONNECTED);
|
SETCONSTANT(EVENT_DISCONNECTED);
|
||||||
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
|
||||||
|
|
||||||
@@ -317,50 +528,33 @@ static void LuaInit(lua_State* L)
|
|||||||
assert(top == lua_gettop(L));
|
assert(top == lua_gettop(L));
|
||||||
}
|
}
|
||||||
|
|
||||||
static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params)
|
static dmExtension::Result AppInitialize(dmExtension::AppParams* params)
|
||||||
{
|
{
|
||||||
g_Websocket.m_BufferSize = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.buffer_size", 64 * 1024);
|
g_Websocket.m_BufferSize = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.buffer_size", 64 * 1024);
|
||||||
g_Websocket.m_Timeout = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.socket_timeout", 500 * 1000);
|
g_Websocket.m_Timeout = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.socket_timeout", 500 * 1000);
|
||||||
g_Websocket.m_Connections.SetCapacity(4);
|
g_Websocket.m_Connections.SetCapacity(4);
|
||||||
g_Websocket.m_Channel = 0;
|
|
||||||
g_Websocket.m_Pool = 0;
|
g_Websocket.m_Pool = 0;
|
||||||
|
g_Websocket.m_Initialized = 0;
|
||||||
|
|
||||||
dmConnectionPool::Params pool_params;
|
dmConnectionPool::Params pool_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);
|
||||||
|
return dmExtension::RESULT_INIT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can do without the channel, it will then fallback to the dmSocket::GetHostname (as opposed to dmDNS::GetHostname)
|
|
||||||
#if defined(HAVE_WSLAY)
|
|
||||||
dmDNS::Result dns_result = dmDNS::NewChannel(&g_Websocket.m_Channel);
|
|
||||||
|
|
||||||
if (dmDNS::RESULT_OK != dns_result)
|
|
||||||
{
|
|
||||||
dmLogError("Failed to create connection pool: %d", dns_result);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
g_Websocket.m_Initialized = 1;
|
g_Websocket.m_Initialized = 1;
|
||||||
if (!g_Websocket.m_Pool)
|
|
||||||
{
|
|
||||||
if (!g_Websocket.m_Pool)
|
|
||||||
{
|
|
||||||
dmLogInfo("pool is null!");
|
|
||||||
dmConnectionPool::Delete(g_Websocket.m_Pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
dmLogInfo("%s extension not initialized", MODULE_NAME);
|
|
||||||
g_Websocket.m_Initialized = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 +565,72 @@ 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)
|
||||||
{
|
{
|
||||||
|
while (!g_Websocket.m_Connections.Empty())
|
||||||
|
{
|
||||||
|
WebsocketConnection* conn = g_Websocket.m_Connections.Back();
|
||||||
|
g_Websocket.m_Connections.Pop();
|
||||||
|
DestroyConnection(conn);
|
||||||
|
}
|
||||||
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, uint16_t code)
|
||||||
|
{
|
||||||
|
if (conn->m_Messages.Full())
|
||||||
|
conn->m_Messages.OffsetCapacity(4);
|
||||||
|
|
||||||
|
Message msg;
|
||||||
|
msg.m_Type = (uint32_t)type;
|
||||||
|
msg.m_Length = length;
|
||||||
|
msg.m_Code = code;
|
||||||
|
conn->m_Messages.Push(msg);
|
||||||
|
|
||||||
|
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 void ConnectionWorker(void* _conn)
|
||||||
|
{
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)_conn;
|
||||||
|
dmSocket::Result sr;
|
||||||
|
dmConnectionPool::Result pool_result = dmConnectionPool::Dial(g_Websocket.m_Pool, conn->m_Url.m_Hostname, conn->m_Url.m_Port, conn->m_SSL, g_Websocket.m_Timeout, &conn->m_Connection, &sr);
|
||||||
|
if (dmConnectionPool::RESULT_OK != pool_result)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed to open connection: %s", dmSocket::ResultToString(sr));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SetState(conn, STATE_HANDSHAKE_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result OnUpdate(dmExtension::Params* params)
|
||||||
{
|
{
|
||||||
uint32_t size = g_Websocket.m_Connections.Size();
|
uint32_t size = g_Websocket.m_Connections.Size();
|
||||||
|
|
||||||
@@ -395,17 +642,18 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
{
|
{
|
||||||
if (RESULT_OK != conn->m_Status)
|
if (RESULT_OK != conn->m_Status)
|
||||||
{
|
{
|
||||||
HandleCallback(conn, EVENT_ERROR);
|
HandleCallback(conn, EVENT_ERROR, 0, conn->m_BufferSize);
|
||||||
|
conn->m_BufferSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleCallback(conn, EVENT_DISCONNECTED);
|
HandleCallback(conn, EVENT_DISCONNECTED, 0, conn->m_BufferSize);
|
||||||
|
|
||||||
g_Websocket.m_Connections.EraseSwap(i);
|
g_Websocket.m_Connections.EraseSwap(i);
|
||||||
--i;
|
--i;
|
||||||
--size;
|
--size;
|
||||||
DestroyConnection(conn);
|
DestroyConnection(conn);
|
||||||
}
|
}
|
||||||
else if (STATE_CONNECTED == conn->m_State)
|
else if ((STATE_CONNECTED == conn->m_State) || (STATE_DISCONNECTING == conn->m_State))
|
||||||
{
|
{
|
||||||
#if defined(HAVE_WSLAY)
|
#if defined(HAVE_WSLAY)
|
||||||
int r = WSL_Poll(conn->m_Ctx);
|
int r = WSL_Poll(conn->m_Ctx);
|
||||||
@@ -414,42 +662,37 @@ 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
|
|
||||||
int recv_bytes = 0;
|
|
||||||
dmSocket::Result sr = Receive(conn, conn->m_Buffer, conn->m_BufferCapacity-1, &recv_bytes);
|
|
||||||
if( sr == dmSocket::RESULT_WOULDBLOCK )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dmSocket::RESULT_OK == sr)
|
|
||||||
{
|
|
||||||
conn->m_BufferSize += recv_bytes;
|
|
||||||
conn->m_Buffer[conn->m_BufferCapacity-1] = 0;
|
|
||||||
conn->m_HasMessage = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CLOSE_CONN("Websocket failed to receive data %s", dmSocket::ResultToString(sr));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (conn->m_HasMessage)
|
uint32_t offset = 0;
|
||||||
|
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;
|
|
||||||
conn->m_BufferSize = 0;
|
if (EVENT_DISCONNECTED == msg.m_Type)
|
||||||
|
{
|
||||||
|
conn->m_Status = RESULT_OK;
|
||||||
|
// close the connection and immediately transition to the DISCONNECTED
|
||||||
|
// state
|
||||||
|
SetState(conn, STATE_DISCONNECTED);
|
||||||
|
conn->m_CloseCode = msg.m_Code;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleCallback(conn, EVENT_MESSAGE, offset, msg.m_Length);
|
||||||
|
offset += msg.m_Length;
|
||||||
}
|
}
|
||||||
|
conn->m_Messages.SetSize(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 +705,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)
|
||||||
{
|
{
|
||||||
@@ -482,17 +726,27 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
dmSocket::SetReceiveTimeout(conn->m_Socket, 1000);
|
dmSocket::SetReceiveTimeout(conn->m_Socket, 1000);
|
||||||
if (conn->m_SSLSocket)
|
if (conn->m_SSLSocket)
|
||||||
dmSSLSocket::SetReceiveTimeout(conn->m_SSLSocket, 1000);
|
dmSSLSocket::SetReceiveTimeout(conn->m_SSLSocket, 1000);
|
||||||
#endif
|
|
||||||
dmSocket::SetBlocking(conn->m_Socket, false);
|
dmSocket::SetBlocking(conn->m_Socket, false);
|
||||||
|
#endif
|
||||||
conn->m_Buffer[0] = 0;
|
|
||||||
conn->m_BufferSize = 0;
|
|
||||||
|
|
||||||
SetState(conn, STATE_CONNECTED);
|
SetState(conn, STATE_CONNECTED);
|
||||||
HandleCallback(conn, EVENT_CONNECTED);
|
HandleCallback(conn, EVENT_CONNECTED, 0, 0);
|
||||||
}
|
}
|
||||||
else if (STATE_HANDSHAKE_WRITE == conn->m_State)
|
else if (STATE_HANDSHAKE_WRITE == conn->m_State)
|
||||||
{
|
{
|
||||||
|
if (CheckConnectTimeout(conn))
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Connect sequence timed out");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->m_ConnectionThread)
|
||||||
|
{
|
||||||
|
dmThread::Join(conn->m_ConnectionThread);
|
||||||
|
conn->m_ConnectionThread = 0;
|
||||||
|
}
|
||||||
|
conn->m_Socket = dmConnectionPool::GetSocket(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
|
conn->m_SSLSocket = dmConnectionPool::GetSSLSocket(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
Result result = SendClientHandshake(conn);
|
Result result = SendClientHandshake(conn);
|
||||||
if (RESULT_WOULDBLOCK == result)
|
if (RESULT_WOULDBLOCK == result)
|
||||||
{
|
{
|
||||||
@@ -506,23 +760,67 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
|||||||
|
|
||||||
SetState(conn, STATE_HANDSHAKE_READ);
|
SetState(conn, STATE_HANDSHAKE_READ);
|
||||||
}
|
}
|
||||||
else if (STATE_CONNECTING == conn->m_State)
|
else if (STATE_CREATE == 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn->m_Socket = dmConnectionPool::GetSocket(g_Websocket.m_Pool, conn->m_Connection);
|
#if defined(__EMSCRIPTEN__)
|
||||||
conn->m_SSLSocket = dmConnectionPool::GetSSLSocket(g_Websocket.m_Pool, conn->m_Connection);
|
char uri_buffer[dmURI::MAX_URI_LEN];
|
||||||
SetState(conn, STATE_HANDSHAKE_WRITE);
|
const char* uri;
|
||||||
|
bool no_path = conn->m_Url.m_Path[0] == '\0';
|
||||||
|
bool no_port = conn->m_Url.m_Port == -1;
|
||||||
|
if (no_path && no_port)
|
||||||
|
{
|
||||||
|
dmSnPrintf(uri_buffer, sizeof(uri_buffer), "%s://%s", conn->m_Url.m_Scheme, conn->m_Url.m_Hostname);
|
||||||
|
}
|
||||||
|
else if (no_port)
|
||||||
|
{
|
||||||
|
dmSnPrintf(uri_buffer, sizeof(uri_buffer), "%s://%s%s", conn->m_Url.m_Scheme, conn->m_Url.m_Hostname, conn->m_Url.m_Path);
|
||||||
|
}
|
||||||
|
else if (no_path)
|
||||||
|
{
|
||||||
|
dmSnPrintf(uri_buffer, sizeof(uri_buffer), "%s://%s:%d", conn->m_Url.m_Scheme, conn->m_Url.m_Hostname, conn->m_Url.m_Port);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dmSnPrintf(uri_buffer, sizeof(uri_buffer), "%s://%s:%d%s", conn->m_Url.m_Scheme, conn->m_Url.m_Hostname, conn->m_Url.m_Port, conn->m_Url.m_Path);
|
||||||
|
}
|
||||||
|
uri = uri_buffer;
|
||||||
|
|
||||||
|
EmscriptenWebSocketCreateAttributes ws_attrs = {
|
||||||
|
uri,
|
||||||
|
conn->m_Protocol,
|
||||||
|
EM_TRUE
|
||||||
|
};
|
||||||
|
EMSCRIPTEN_WEBSOCKET_T ws = emscripten_websocket_new(&ws_attrs);
|
||||||
|
if (ws < 0)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed to connect to '%s:%d': %d", conn->m_Url.m_Hostname, (int)conn->m_Url.m_Port, ws);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
conn->m_WS = ws;
|
||||||
|
|
||||||
|
emscripten_websocket_set_onopen_callback(ws, conn, Emscripten_WebSocketOnOpen);
|
||||||
|
emscripten_websocket_set_onerror_callback(ws, conn, Emscripten_WebSocketOnError);
|
||||||
|
emscripten_websocket_set_onclose_callback(ws, conn, Emscripten_WebSocketOnClose);
|
||||||
|
emscripten_websocket_set_onmessage_callback(ws, conn, Emscripten_WebSocketOnMessage);
|
||||||
|
#else
|
||||||
|
conn->m_ConnectionThread = dmThread::New((dmThread::ThreadStart)ConnectionWorker, 0x80000, conn, "WSConnect");
|
||||||
|
#endif
|
||||||
|
SetState(conn, STATE_CONNECTING);
|
||||||
|
}
|
||||||
|
else if (STATE_CONNECTING == conn->m_State)
|
||||||
|
{
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
if (CheckConnectTimeout(conn))
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Connect sequence timed out");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,6 +829,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>
|
||||||
|
|
||||||
@@ -9,12 +13,17 @@
|
|||||||
|
|
||||||
#if defined(HAVE_WSLAY)
|
#if defined(HAVE_WSLAY)
|
||||||
#include <wslay/wslay.h>
|
#include <wslay/wslay.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
#include <emscripten/websocket.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <dmsdk/dlib/connection_pool.h>
|
#include <dmsdk/dlib/connection_pool.h>
|
||||||
#include <dmsdk/dlib/socket.h>
|
#include <dmsdk/dlib/socket.h>
|
||||||
#include <dmsdk/dlib/dns.h>
|
|
||||||
#include <dmsdk/dlib/uri.h>
|
#include <dmsdk/dlib/uri.h>
|
||||||
|
#include <dmsdk/dlib/array.h>
|
||||||
|
#include <dmsdk/dlib/thread.h>
|
||||||
|
|
||||||
namespace dmCrypt
|
namespace dmCrypt
|
||||||
{
|
{
|
||||||
@@ -30,10 +39,12 @@ namespace dmWebsocket
|
|||||||
|
|
||||||
enum State
|
enum State
|
||||||
{
|
{
|
||||||
|
STATE_CREATE,
|
||||||
STATE_CONNECTING,
|
STATE_CONNECTING,
|
||||||
STATE_HANDSHAKE_WRITE,
|
STATE_HANDSHAKE_WRITE,
|
||||||
STATE_HANDSHAKE_READ,
|
STATE_HANDSHAKE_READ,
|
||||||
STATE_CONNECTED,
|
STATE_CONNECTED,
|
||||||
|
STATE_DISCONNECTING,
|
||||||
STATE_DISCONNECTED,
|
STATE_DISCONNECTED,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,24 +66,75 @@ 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
|
||||||
|
{
|
||||||
|
uint16_t m_Code;
|
||||||
|
uint32_t m_Length:30;
|
||||||
|
uint32_t m_Type:2;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HttpHeader
|
||||||
|
{
|
||||||
|
const char* m_Key;
|
||||||
|
const char* m_Value;
|
||||||
|
HttpHeader(const char* key, const char* value);
|
||||||
|
~HttpHeader();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HandshakeResponse
|
||||||
|
{
|
||||||
|
int m_HttpMajor;
|
||||||
|
int m_HttpMinor;
|
||||||
|
int m_ResponseStatusCode;
|
||||||
|
int m_BodyOffset;
|
||||||
|
dmArray<HttpHeader*> m_Headers;
|
||||||
|
|
||||||
|
~HandshakeResponse();
|
||||||
|
HttpHeader* GetHeader(const char* header);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct WebsocketConnection
|
struct WebsocketConnection
|
||||||
{
|
{
|
||||||
dmScript::LuaCallbackInfo* m_Callback;
|
dmScript::LuaCallbackInfo* m_Callback;
|
||||||
#if defined(HAVE_WSLAY)
|
#if defined(HAVE_WSLAY)
|
||||||
wslay_event_context_ptr m_Ctx;
|
wslay_event_context_ptr m_Ctx;
|
||||||
|
#endif
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
EMSCRIPTEN_WEBSOCKET_T m_WS;
|
||||||
#endif
|
#endif
|
||||||
dmURI::Parts m_Url;
|
dmURI::Parts m_Url;
|
||||||
dmConnectionPool::HConnection m_Connection;
|
dmConnectionPool::HConnection m_Connection;
|
||||||
dmSocket::Socket m_Socket;
|
dmSocket::Socket m_Socket;
|
||||||
dmSSLSocket::Socket m_SSLSocket;
|
dmSSLSocket::Socket m_SSLSocket;
|
||||||
|
dmThread::Thread m_ConnectionThread;
|
||||||
|
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;
|
||||||
|
uint16_t m_CloseCode;
|
||||||
|
uint8_t m_SSL:1;
|
||||||
|
uint8_t m_HasHandshakeData:1;
|
||||||
|
uint8_t :7;
|
||||||
|
HandshakeResponse* m_HandshakeResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set error message
|
// Set error message
|
||||||
@@ -82,6 +144,9 @@ namespace dmWebsocket
|
|||||||
Result SetStatus(WebsocketConnection* conn, Result status, const char* fmt, ...);
|
Result SetStatus(WebsocketConnection* conn, Result status, const char* fmt, ...);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Set socket state
|
||||||
|
void SetState(WebsocketConnection* conn, State state);
|
||||||
|
|
||||||
// Communication
|
// Communication
|
||||||
dmSocket::Result Send(WebsocketConnection* conn, const char* buffer, int length, int* out_sent_bytes);
|
dmSocket::Result Send(WebsocketConnection* conn, const char* buffer, int length, int* out_sent_bytes);
|
||||||
dmSocket::Result Receive(WebsocketConnection* conn, void* buffer, int length, int* received_bytes);
|
dmSocket::Result Receive(WebsocketConnection* conn, void* buffer, int length, int* received_bytes);
|
||||||
@@ -92,33 +157,41 @@ namespace dmWebsocket
|
|||||||
Result ReceiveHeaders(WebsocketConnection* conn);
|
Result ReceiveHeaders(WebsocketConnection* conn);
|
||||||
Result VerifyHeaders(WebsocketConnection* conn);
|
Result VerifyHeaders(WebsocketConnection* conn);
|
||||||
|
|
||||||
|
// Callback to Lua
|
||||||
|
void HandleCallback(WebsocketConnection* conn, int event, int msg_offset, int msg_length);
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
Result PushMessage(WebsocketConnection* conn, MessageType type, int length, const uint8_t* msg, uint16_t code);
|
||||||
|
|
||||||
#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);
|
||||||
int WSL_GenmaskCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data);
|
int WSL_GenmaskCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data);
|
||||||
const char* WSL_ResultToString(int err);
|
const char* WSL_ResultToString(int err);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
EM_BOOL Emscripten_WebSocketOnOpen(int eventType, const EmscriptenWebSocketOpenEvent *websocketEvent, void *userData);
|
||||||
|
EM_BOOL Emscripten_WebSocketOnError(int eventType, const EmscriptenWebSocketErrorEvent *websocketEvent, void *userData);
|
||||||
|
EM_BOOL Emscripten_WebSocketOnClose(int eventType, const EmscriptenWebSocketCloseEvent *websocketEvent, void *userData);
|
||||||
|
EM_BOOL Emscripten_WebSocketOnMessage(int eventType, const EmscriptenWebSocketMessageEvent *websocketEvent, void *userData);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Random numbers (PCG)
|
// Random numbers (PCG)
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -39,11 +39,7 @@ const char* WSL_ResultToString(int err)
|
|||||||
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)
|
||||||
{
|
{
|
||||||
// Currently only supports client implementation
|
// Currently only supports client implementation
|
||||||
int ret = -1;
|
return wslay_event_context_client_init(ctx, &g_WslCallbacks, userctx);
|
||||||
ret = wslay_event_context_client_init(ctx, &g_WslCallbacks, userctx);
|
|
||||||
if (ret == 0)
|
|
||||||
wslay_event_config_set_max_recv_msg_length(*ctx, buffer_size);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -54,8 +50,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 +64,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 +114,36 @@ 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, 0);
|
||||||
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];
|
||||||
|
uint16_t status_code = wslay_event_get_status_code_received(ctx);
|
||||||
|
len = dmSnPrintf(buffer, sizeof(buffer), "Server closing (%u). Reason: '%s'", status_code, reason);
|
||||||
|
PushMessage(conn, MESSAGE_TYPE_CLOSE, len, (const uint8_t*)buffer, status_code);
|
||||||
|
|
||||||
|
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