31 Commits

Author SHA1 Message Date
JCash
ba2b8e4a69 Updated hex string logging to use our internal logging functions
This will keep the output in-order
2020-10-13 09:33:38 +02:00
JCash
176f213060 updated documentation 2020-10-13 09:14:21 +02:00
Mathias Westerdahl
dc1d57d661 Merge pull request #10 from defold/issue-4-case-insensitive-header
Issue 4: Compare header keys without case sensitivity
2020-10-12 09:59:55 +02:00
JCash
5b0a9960a8 Check the response headers more thoroughly 2020-10-12 09:45:05 +02:00
JCash
40ba1b334c Issue 4: Compare header keys without case sensitivity 2020-10-12 09:33:47 +02:00
Mathias Westerdahl
23ba179e2a Merge pull request #9 from defold/issue-8-close-event
Issue 8: Handle the websocket close event
2020-09-28 09:30:12 +02:00
JCash
36bf5d1c03 code cleanup 2020-09-27 17:04:00 +02:00
JCash
274f29d7e4 moved error checking code outside of socket implementation 2020-09-27 17:02:14 +02:00
JCash
bd8569f49a Issue 8: Handle the websocket close event 2020-09-27 16:54:22 +02:00
Mathias Westerdahl
18a768774f Merge pull request #7 from defold/issue-6-multiple-messages
Issue 6: Handle multiple messages per frame
2020-09-27 10:14:08 +02:00
JCash
8e32fa3c76 compile fixes 2020-09-26 15:50:24 +02:00
JCash
832a156395 Issue 6: Handle multiple messages per frame 2020-09-26 12:51:43 +02:00
JCash
8df9eed682 Removed redundant / in concatenated uri's 2020-09-06 09:35:09 +02:00
JCash
071adac853 Added separate code path for html5 2020-09-05 11:02:59 +02:00
JCash
6ef040aee6 more testing 2020-09-05 10:52:37 +02:00
JCash
337a389cf8 more testing 2020-09-04 18:37:58 +02:00
JCash
b9f3563652 more testing 2020-09-04 18:11:40 +02:00
JCash
358e652946 more testing 2020-09-04 17:56:03 +02:00
JCash
44fc5037cb more testing 2020-09-04 17:38:09 +02:00
JCash
18ecf3857f more testing 2020-09-04 17:16:49 +02:00
JCash
b31e5bec7f more testing 2020-09-04 16:59:30 +02:00
JCash
61916f4c27 more testing 2020-09-04 16:41:19 +02:00
JCash
72f081bf23 test wss+html5 2020-09-04 15:53:27 +02:00
JCash
ab1b52c676 Merge branch 'master' of github.com:defold/extension-websocket 2020-09-04 12:04:16 +02:00
JCash
2bd4e228c6 added reference demo 2020-09-04 12:04:06 +02:00
Björn Ritzl
1202731ec8 Added api docs for websocket.send() 2020-09-04 11:22:54 +02:00
JCash
f0cf078019 debugging 2020-09-04 11:14:13 +02:00
JCash
bd9e777b99 Windows compile fixes 2020-09-03 17:28:10 +02:00
JCash
90a654e639 Added doc builder trigger + funding.yml 2020-09-03 15:24:19 +02:00
JCash
f3ea270c68 Updated readme 2020-09-03 15:18:31 +02:00
Mathias Westerdahl
2bddcac495 Merge pull request #1 from defold/initial
Add first version of websocket extension
2020-09-03 08:45:42 +02:00
35 changed files with 19739 additions and 12175 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
github: defold
patreon: Defold
custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NBNBHTUW4GS4C']

View File

@@ -0,0 +1,19 @@
name: Trigger site rebuild
on: [push]
jobs:
site-rebuild:
runs-on: ubuntu-latest
steps: [
{
name: 'Repository dispatch',
uses: defold/repository-dispatch@1.2.1,
with: {
repo: 'defold/defold.github.io',
token: '${{ secrets.SERVICES_GITHUB_TOKEN }}',
user: 'services@defold.se',
action: 'extension-websocket'
}
}]

View File

@@ -1,4 +1,22 @@
# Native extension template
This template contains the basic setup for creation of a Defold native extension.
# Defold websocket extension
You can learn more about native extensions in the [official manual](https://www.defold.com/manuals/extensions/).
## Installation
To use this library in your Defold project, add the following URL to your `game.project` dependencies:
https://github.com/defold/extension-websocket/archive/master.zip
We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-websocket/releases).
## API reference
https://defold.com/extension-websocket/api/
## Debugging
In order to make it easier to debug this extension, we provide a `game.project` setting `websocket.debug`.
Set it to:
* `0` to disable debugging (i.e. no debug output).
* `1` to display state changes.
* `2` to display the messages sent and received.

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

View 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}]}]}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

Binary file not shown.

View 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);
};
};

View 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>

View File

@@ -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}]}]}

View File

@@ -3,8 +3,6 @@ title = extension-websocket
version = 1.0
write_log = 0
compress_archive = 1
publisher = unnamed
developer = unnamed
_dependencies = https://github.com/GameAnalytics/defold-openssl/archive/1.0.0.zip
[display]
@@ -54,7 +52,6 @@ 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
@@ -153,8 +150,6 @@ auto_finish_transactions = 1
[network]
http_timeout = 0
http_thread_count = 4
http_cache_enabled = 1
[library]
include_dirs = websocket
@@ -171,7 +166,6 @@ track_cpu = 0
[liveupdate]
settings = /liveupdate.settings
enabled = 1
[tilemap]
max_count = 16

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -14,15 +14,16 @@ Here is how you connect to a websocket and listen to events:
```lua
local function websocket_callback(self, conn, data)
if data.event == websocket.EVENT_DISCONNECTED then
print("disconnected " .. conn)
log("Disconnected: " .. tostring(conn))
self.connection = nil
update_gui(self)
elseif data.event == websocket.EVENT_CONNECTED then
print("Connected " .. conn)
-- self.connection = conn
update_gui(self)
log("Connected: " .. tostring(conn))
elseif data.event == websocket.EVENT_ERROR then
print("Error:", data.error)
log("Error: '" .. data.error .. "'")
elseif data.event == websocket.EVENT_MESSAGE then
print("Receiving: '" .. tostring(data.message) .. "'")
log("Receiving: '" .. tostring(data.message) .. "'")
end
end

View File

@@ -33,6 +33,12 @@ local function log(...)
gui.set_text(node, gui.get_text(node) .. "\n" .. text)
end
local function http_result(self, _, response)
print(response.status)
--print(response.response)
pprint(response.headers)
end
function init(self)
msg.post(".", "acquire_input_focus")
msg.post("@render:", "clear_color", { color = vmath.vector4(0.2, 0.4, 0.8, 1.0) })
@@ -43,6 +49,9 @@ function init(self)
self.connection_text = gui.get_node("connection_text")
self.connection = nil
update_gui(self)
--http.request("https://defold.com", "GET", http_result)
end
function final(self)

View File

@@ -65,15 +65,16 @@
```lua
local function websocket_callback(self, conn, data)
if data.event == websocket.EVENT_DISCONNECTED then
print("disconnected " .. conn)
log("Disconnected: " .. tostring(conn))
self.connection = nil
update_gui(self)
elseif data.event == websocket.EVENT_CONNECTED then
print("Connected " .. conn)
-- self.connection = conn
update_gui(self)
log("Connected: " .. tostring(conn))
elseif data.event == websocket.EVENT_ERROR then
print("Error:", data.error)
log("Error: '" .. data.error .. "'")
elseif data.event == websocket.EVENT_MESSAGE then
print("Receiving: '" .. tostring(data.message) .. "'")
log("Receiving: '" .. tostring(data.message) .. "'")
end
end
@@ -100,6 +101,35 @@
type: object
desc: the websocket connection
#*****************************************************************************************************
- name: send
type: function
desc: Send data on a websocket
parameters:
- name: connection
type: object
desc: the websocket connection
- name: message
type: string
desc: the message to send
examples:
- desc: |-
```lua
local function websocket_callback(self, conn, data)
if data.event == websocket.EVENT_CONNECTED then
websocket.send(conn, "Hello from the other side")
end
end
function init(self)
self.url = "ws://echo.websocket.org"
local params = {}
self.connection = websocket.connect(self.url, params, websocket_callback)
end
```
#*****************************************************************************************************
- name: EVENT_CONNECTED

View File

@@ -2,8 +2,12 @@
#ifndef CONFIG_H
#define CONFIG_H
#define HAVE_ARPA_INET_H
#define HAVE_NETINET_IN_H
#if defined(_WIN32)
#define HAVE_WINSOCK2_H
#else
#define HAVE_ARPA_INET_H
#define HAVE_NETINET_IN_H
#endif
/* #undef HAVE_WINSOCK2_H */
/* #undef WORDS_BIGENDIAN */

View File

@@ -33,6 +33,14 @@ extern "C" {
#include <stdlib.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

View File

@@ -1,5 +1,6 @@
#include "websocket.h"
#include <dmsdk/dlib/socket.h>
#include <ctype.h> // tolower
namespace dmWebsocket
{
@@ -146,7 +147,8 @@ Result ReceiveHeaders(WebsocketConnection* conn)
conn->m_Buffer[conn->m_BufferSize] = '\0';
// 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;
}
@@ -155,6 +157,20 @@ Result ReceiveHeaders(WebsocketConnection* conn)
}
#endif
static int dmStriCmp(const char* s1, const char* s2)
{
for (;;)
{
if (!*s1 || !*s2 || tolower((unsigned char) *s1) != tolower((unsigned char) *s2))
{
return (unsigned char) *s1 - (unsigned char) *s2;
}
s1++;
s2++;
}
}
#if defined(__EMSCRIPTEN__)
Result VerifyHeaders(WebsocketConnection* conn)
{
@@ -171,16 +187,19 @@ Result VerifyHeaders(WebsocketConnection* conn)
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Missing: '%s' in header", http_version_and_status_protocol);
}
const char* endtag = strstr(conn->m_Buffer, "\r\n\r\n");
r = strstr(r, "\r\n") + 2;
bool upgraded = false;
bool connection = false;
bool upgrade = false;
bool valid_key = false;
const char* protocol = "";
// TODO: Perhaps also support the Sec-WebSocket-Protocol
// parse the headers in place
while (r)
while (r < endtag)
{
// Tokenize the each header line: "Key: Value\r\n"
const char* key = r;
@@ -194,45 +213,65 @@ Result VerifyHeaders(WebsocketConnection* conn)
*r = 0;
r += 2;
if (strcmp(key, "Connection") == 0 && strcmp(value, "Upgrade") == 0)
upgraded = true;
else if (strcmp(key, "Sec-WebSocket-Accept") == 0)
// Page 18 in https://tools.ietf.org/html/rfc6455#section-11.3.3
if (dmStriCmp(key, "Connection") == 0 && dmStriCmp(value, "Upgrade") == 0)
connection = true;
else if (dmStriCmp(key, "Upgrade") == 0 && dmStriCmp(value, "websocket") == 0)
upgrade = true;
else if (dmStriCmp(key, "Sec-WebSocket-Accept") == 0)
{
uint8_t client_key[32 + 40];
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", value);
if (strcmp(value, (const char*)client_key) == 0)
valid_key = true;
}
if (strcmp(r, "\r\n") == 0)
break;
}
if (!upgraded)
// The response might contain both the headers, but also (if successful) the first batch of data
endtag += 4;
uint32_t size = conn->m_BufferSize - (endtag - conn->m_Buffer);
conn->m_BufferSize = size;
memmove(conn->m_Buffer, endtag, size);
conn->m_Buffer[size] = 0;
conn->m_HasHandshakeData = conn->m_BufferSize != 0 ? 1 : 0;
if (!connection)
dmLogError("Failed to find the Connection keyword in the response headers");
if (!upgrade)
dmLogError("Failed to find the Upgrade keyword in the response headers");
if (!valid_key)
dmLogError("Failed to find valid key in the response headers");
if (!(upgraded && valid_key)) {
bool ok = connection && upgrade && valid_key;
if (!ok) {
dmLogError("Response:\n\"%s\"\n", conn->m_Buffer);
}
return (upgraded && valid_key) ? RESULT_OK : RESULT_HANDSHAKE_FAILED;
return ok ? RESULT_OK : RESULT_HANDSHAKE_FAILED;
}
#endif

View File

@@ -42,15 +42,24 @@ dmSocket::Result Send(WebsocketConnection* conn, const char* buffer, int length,
}
if (out_sent_bytes)
*out_sent_bytes = total_sent_bytes;
DebugPrint(2, "Sent buffer:", buffer, length);
return dmSocket::RESULT_OK;
}
dmSocket::Result Receive(WebsocketConnection* conn, void* buffer, int length, int* received_bytes)
{
dmSocket::Result sr;
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
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

View File

@@ -9,9 +9,20 @@
#include <dmsdk/dlib/connection_pool.h>
#include <dmsdk/dlib/dns.h>
#include <dmsdk/dlib/sslsocket.h>
#include <ctype.h> // isprint et al
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h> // for EM_ASM
#endif
#if defined(WIN32)
#include <malloc.h>
#define alloca _alloca
#endif
namespace dmWebsocket {
int g_DebugWebSocket = 0;
struct WebsocketContext
{
@@ -56,8 +67,72 @@ const char* StateToString(State err)
#undef STRING_CASE
#define WS_DEBUG(...)
//#define WS_DEBUG(...) dmLogWarning(__VA_ARGS__);
void DebugLog(int level, const char* fmt, ...)
{
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(...) \
SetStatus(conn, RESULT_ERROR, __VA_ARGS__); \
@@ -70,7 +145,7 @@ static void SetState(WebsocketConnection* conn, State state)
if (prev_state != state)
{
conn->m_State = state;
WS_DEBUG("%s -> %s", StateToString(prev_state), StateToString(conn->m_State));
DebugLog(1, "%s -> %s", StateToString(prev_state), StateToString(conn->m_State));
}
}
@@ -85,6 +160,8 @@ Result SetStatus(WebsocketConnection* conn, Result status, const char* format, .
conn->m_BufferSize = vsnprintf(conn->m_Buffer, conn->m_BufferCapacity, format, lst);
va_end(lst);
conn->m_Status = status;
DebugLog(1, "STATUS: '%s' len: %u", conn->m_Buffer, conn->m_BufferSize);
}
return status;
}
@@ -96,10 +173,11 @@ Result SetStatus(WebsocketConnection* conn, Result status, const char* format, .
static WebsocketConnection* CreateConnection(const char* url)
{
WebsocketConnection* conn = (WebsocketConnection*)malloc(sizeof(WebsocketConnection));
memset(conn, 0, sizeof(WebsocketConnection));
WebsocketConnection* conn = new WebsocketConnection;
conn->m_BufferCapacity = g_Websocket.m_BufferSize;
conn->m_Buffer = (char*)malloc(conn->m_BufferCapacity);
conn->m_Buffer[0] = 0;
conn->m_BufferSize = 0;
dmURI::Parts uri;
dmURI::Parse(url, &conn->m_Url);
@@ -110,6 +188,17 @@ static WebsocketConnection* CreateConnection(const char* url)
conn->m_SSL = strcmp(conn->m_Url.m_Scheme, "wss") == 0 ? 1 : 0;
conn->m_State = STATE_CONNECTING;
conn->m_Callback = 0;
conn->m_Connection = 0;
conn->m_Socket = 0;
conn->m_SSLSocket = 0;
conn->m_Status = RESULT_OK;
conn->m_HasHandshakeData = 0;
#if defined(HAVE_WSLAY)
conn->m_Ctx = 0;
#endif
return conn;
}
@@ -123,11 +212,20 @@ static void DestroyConnection(WebsocketConnection* conn)
if (conn->m_Callback)
dmScript::DestroyCallback(conn->m_Callback);
#if defined(__EMSCRIPTEN__)
if (conn->m_Socket != dmSocket::INVALID_SOCKET_HANDLE) {
// We would normally do a shutdown() first, but Emscripten returns ENOSYS
//dmSocket::Shutdown(conn->m_Socket, dmSocket::SHUTDOWNTYPE_READWRITE);
dmSocket::Delete(conn->m_Socket);
}
#else
if (conn->m_Connection)
dmConnectionPool::Return(g_Websocket.m_Pool, conn->m_Connection);
#endif
free((void*)conn->m_Buffer);
free((void*)conn);
delete conn;
}
@@ -227,7 +325,7 @@ static int LuaSend(lua_State* L)
const char* string = luaL_checklstring(L, 2, &string_length);
#if defined(HAVE_WSLAY)
int write_mode = WSLAY_BINARY_FRAME; // WSLAY_TEXT_FRAME
int write_mode = WSLAY_BINARY_FRAME; // or WSLAY_TEXT_FRAME
struct wslay_event_msg msg;
msg.opcode = write_mode;
@@ -247,7 +345,7 @@ static int LuaSend(lua_State* L)
return 0;
}
static void HandleCallback(WebsocketConnection* conn, int event)
static void HandleCallback(WebsocketConnection* conn, int event, int msg_offset, int msg_length)
{
if (!dmScript::IsCallbackValid(conn->m_Callback))
return;
@@ -268,14 +366,8 @@ static void HandleCallback(WebsocketConnection* conn, int event)
lua_pushinteger(L, event);
lua_setfield(L, -2, "event");
if (EVENT_ERROR == event) {
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
lua_setfield(L, -2, "error");
}
else if (EVENT_MESSAGE == event) {
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
lua_setfield(L, -2, "message");
}
lua_pushlstring(L, conn->m_Buffer + msg_offset, msg_length);
lua_setfield(L, -2, "message");
dmScript::PCall(L, 3, 0);
@@ -317,7 +409,7 @@ static void LuaInit(lua_State* 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_Timeout = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.socket_timeout", 500 * 1000);
@@ -329,6 +421,10 @@ static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params
pool_params.m_MaxConnections = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.max_connections", 2);
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)
{
dmLogError("Failed to create connection pool: %d", result);
@@ -344,6 +440,14 @@ static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params
}
#endif
#if defined(__EMSCRIPTEN__)
// avoid mixed content warning if trying to access wss resource from http page
// If not using this, we get EHOSTUNREACH
EM_ASM({
Module["websocket"].url = window["location"]["protocol"].replace("http", "ws") + "//";
});
#endif
g_Websocket.m_Initialized = 1;
if (!g_Websocket.m_Pool)
{
@@ -360,7 +464,7 @@ static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params
return dmExtension::RESULT_OK;
}
static dmExtension::Result WebsocketInitialize(dmExtension::Params* params)
static dmExtension::Result Initialize(dmExtension::Params* params)
{
if (!g_Websocket.m_Initialized)
return dmExtension::RESULT_OK;
@@ -371,19 +475,50 @@ static dmExtension::Result WebsocketInitialize(dmExtension::Params* params)
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);
return dmExtension::RESULT_OK;
}
static dmExtension::Result WebsocketFinalize(dmExtension::Params* params)
static dmExtension::Result Finalize(dmExtension::Params* params)
{
return dmExtension::RESULT_OK;
}
static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
Result PushMessage(WebsocketConnection* conn, MessageType type, int length, const uint8_t* buffer)
{
if (conn->m_Messages.Full())
conn->m_Messages.OffsetCapacity(4);
Message msg;
msg.m_Type = (uint32_t)type;
msg.m_Length = length;
conn->m_Messages.Push(msg);
// No need to copy itself (html5)
if (buffer != (const uint8_t*)conn->m_Buffer)
{
if ((conn->m_BufferSize + length) >= conn->m_BufferCapacity)
{
conn->m_BufferCapacity = conn->m_BufferSize + length + 1;
conn->m_Buffer = (char*)realloc(conn->m_Buffer, conn->m_BufferCapacity);
}
// append to the end of the buffer
memcpy(conn->m_Buffer + conn->m_BufferSize, buffer, length);
}
conn->m_BufferSize += length;
conn->m_Buffer[conn->m_BufferCapacity-1] = 0;
// Instead of printing from the incoming buffer, we print from our own, to make sure it looks ok
DebugPrint(2, __FUNCTION__, conn->m_Buffer+conn->m_BufferSize-length, length);
return dmWebsocket::RESULT_OK;
}
static dmExtension::Result OnUpdate(dmExtension::Params* params)
{
uint32_t size = g_Websocket.m_Connections.Size();
@@ -395,10 +530,11 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
{
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);
--i;
@@ -414,12 +550,6 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
CLOSE_CONN("Websocket closing for %s (%s)", conn->m_Url.m_Hostname, WSL_ResultToString(r));
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);
@@ -430,9 +560,7 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
if (dmSocket::RESULT_OK == sr)
{
conn->m_BufferSize += recv_bytes;
conn->m_Buffer[conn->m_BufferCapacity-1] = 0;
conn->m_HasMessage = 1;
PushMessage(conn, MESSAGE_TYPE_NORMAL, recv_bytes, (const uint8_t*)conn->m_Buffer);
}
else
{
@@ -441,10 +569,31 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
}
#endif
if (conn->m_HasMessage)
uint32_t offset = 0;
bool close_received = false;
for (uint32_t i = 0; i < conn->m_Messages.Size(); ++i)
{
HandleCallback(conn, EVENT_MESSAGE);
conn->m_HasMessage = 0;
const Message& msg = conn->m_Messages[i];
if (EVENT_DISCONNECTED == msg.m_Type)
{
conn->m_Status = RESULT_OK;
CloseConnection(conn);
// Put the message at the front of the buffer
conn->m_Messages.SetSize(0);
conn->m_BufferSize = 0;
PushMessage(conn, MESSAGE_TYPE_CLOSE, msg.m_Length, (const uint8_t*)conn->m_Buffer+offset);
close_received = true;
break;
}
HandleCallback(conn, EVENT_MESSAGE, offset, msg.m_Length);
offset += msg.m_Length;
}
if (!close_received) // saving the close message for next step
{
conn->m_Messages.SetSize(0);
conn->m_BufferSize = 0;
}
}
@@ -462,6 +611,7 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
continue;
}
// Verifies headers, and also stages any initial sent data
result = VerifyHeaders(conn);
if (RESULT_OK != result)
{
@@ -485,11 +635,8 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
#endif
dmSocket::SetBlocking(conn->m_Socket, false);
conn->m_Buffer[0] = 0;
conn->m_BufferSize = 0;
SetState(conn, STATE_CONNECTED);
HandleCallback(conn, EVENT_CONNECTED);
HandleCallback(conn, EVENT_CONNECTED, 0, 0);
}
else if (STATE_HANDSHAKE_WRITE == conn->m_State)
{
@@ -508,20 +655,49 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
}
else if (STATE_CONNECTING == conn->m_State)
{
dmSocket::Result socket_result;
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));
conn->m_SSLSocket = dmSSLSocket::INVALID_SOCKET_HANDLE;
char uri_buffer[dmURI::MAX_URI_LEN];
const char* uri;
if (conn->m_Url.m_Path[0] != '\0') {
dmSnPrintf(uri_buffer, sizeof(uri_buffer), "%s%s", conn->m_Url.m_Hostname, conn->m_Url.m_Path);
uri = uri_buffer;
} else {
uri = conn->m_Url.m_Hostname;
}
dmSocket::Address address;
dmSocket::Result sr = dmSocket::GetHostByName(uri, &address, true, false);
if (dmSocket::RESULT_OK != sr) {
CLOSE_CONN("Failed to get address from host name '%s': %s", uri, dmSocket::ResultToString(sr));
continue;
}
sr = dmSocket::New(address.m_family, dmSocket::TYPE_STREAM, dmSocket::PROTOCOL_TCP, &conn->m_Socket);
if (dmSocket::RESULT_OK != sr) {
CLOSE_CONN("Failed to create socket for '%s': %s", conn->m_Url.m_Hostname, dmSocket::ResultToString(sr));
continue;
}
sr = dmSocket::Connect(conn->m_Socket, address, conn->m_Url.m_Port);
if (dmSocket::RESULT_OK != sr) {
CLOSE_CONN("Failed to connect to '%s:%d': %s", conn->m_Url.m_Hostname, (int)conn->m_Url.m_Port, dmSocket::ResultToString(sr));
continue;
}
#else
dmSocket::Result sr;
int timeout = g_Websocket.m_Timeout;
dmConnectionPool::Result pool_result = dmConnectionPool::Dial(g_Websocket.m_Pool, conn->m_Url.m_Hostname, conn->m_Url.m_Port, g_Websocket.m_Channel, conn->m_SSL, timeout, &conn->m_Connection, &sr);
if (dmConnectionPool::RESULT_OK != pool_result)
{
CLOSE_CONN("Failed to open connection: %s", dmSocket::ResultToString(sr));
continue;
}
conn->m_Socket = dmConnectionPool::GetSocket(g_Websocket.m_Pool, conn->m_Connection);
conn->m_SSLSocket = dmConnectionPool::GetSSLSocket(g_Websocket.m_Pool, conn->m_Connection);
#endif
SetState(conn, STATE_HANDSHAKE_WRITE);
}
}
@@ -531,6 +707,6 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
} // 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

View File

@@ -1,5 +1,9 @@
#pragma once
#if defined(_WIN32)
#include <WinSock2.h>
#endif
// include the Defold SDK
#include <dmsdk/sdk.h>
@@ -15,6 +19,7 @@
#include <dmsdk/dlib/socket.h>
#include <dmsdk/dlib/dns.h>
#include <dmsdk/dlib/uri.h>
#include <dmsdk/dlib/array.h>
namespace dmCrypt
{
@@ -55,6 +60,18 @@ namespace dmWebsocket
EVENT_ERROR,
};
enum MessageType
{
MESSAGE_TYPE_NORMAL = 0,
MESSAGE_TYPE_CLOSE = 1,
};
struct Message
{
uint32_t m_Length:30;
uint32_t m_Type:2;
};
struct WebsocketConnection
{
dmScript::LuaCallbackInfo* m_Callback;
@@ -65,14 +82,16 @@ namespace dmWebsocket
dmConnectionPool::HConnection m_Connection;
dmSocket::Socket m_Socket;
dmSSLSocket::Socket m_SSLSocket;
dmArray<Message> m_Messages; // lengths of the messages in the data buffer
uint8_t m_Key[16];
State m_State;
uint32_t m_SSL:1;
uint32_t m_HasMessage:1;
char* m_Buffer;
int m_BufferSize;
uint32_t m_BufferCapacity;
Result m_Status;
uint8_t m_SSL:1;
uint8_t m_HasHandshakeData:1;
uint8_t :6;
};
// Set error message
@@ -92,13 +111,15 @@ namespace dmWebsocket
Result ReceiveHeaders(WebsocketConnection* conn);
Result VerifyHeaders(WebsocketConnection* conn);
// Messages
Result PushMessage(WebsocketConnection* conn, MessageType type, int length, const uint8_t* msg);
#if defined(HAVE_WSLAY)
// Wslay callbacks
int WSL_Init(wslay_event_context_ptr* ctx, ssize_t buffer_size, void* userctx);
void WSL_Exit(wslay_event_context_ptr ctx);
int WSL_Close(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_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);
@@ -110,6 +131,15 @@ namespace dmWebsocket
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);
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);
}

View File

@@ -54,8 +54,8 @@ void WSL_Exit(wslay_event_context_ptr ctx)
int WSL_Close(wslay_event_context_ptr ctx)
{
const char* reason = "Client wants to close";
wslay_event_queue_close(ctx, 0, (const uint8_t*)reason, strlen(reason));
const char* reason = "";
wslay_event_queue_close(ctx, WSLAY_CODE_NORMAL_CLOSURE, (const uint8_t*)reason, 0);
return 0;
}
@@ -68,24 +68,25 @@ int WSL_Poll(wslay_event_context_ptr ctx)
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)
{
WebsocketConnection* conn = (WebsocketConnection*)user_data;
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);
if (dmSocket::RESULT_OK == socket_result && r == 0)
socket_result = dmSocket::RESULT_WOULDBLOCK;
socket_result = dmSocket::RESULT_CONNABORTED;
if (dmSocket::RESULT_OK != socket_result)
{
@@ -117,20 +118,35 @@ ssize_t WSL_SendCallback(wslay_event_context_ptr ctx, const uint8_t *data, size_
return (ssize_t)sent_bytes;
}
// 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)
{
WebsocketConnection* conn = (WebsocketConnection*)user_data;
if (arg->opcode == WSLAY_TEXT_FRAME || arg->opcode == WSLAY_BINARY_FRAME)
{
if (arg->msg_length >= conn->m_BufferCapacity)
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;
PushMessage(conn, MESSAGE_TYPE_NORMAL, arg->msg_length, arg->msg);
} else if (arg->opcode == WSLAY_CONNECTION_CLOSE)
{
// TODO: Store the reason
// The first two bytes is the close code
const uint8_t* reason = (const uint8_t*)"";
size_t len = arg->msg_length;
if (arg->msg_length > 2)
{
reason = arg->msg + 2;
len -= 2;
}
char buffer[1024];
len = dmSnPrintf(buffer, sizeof(buffer), "Server closing (%u). Reason: '%s'", wslay_event_get_status_code_received(ctx), reason);
PushMessage(conn, MESSAGE_TYPE_CLOSE, len, (const uint8_t*)buffer);
if (!wslay_event_get_close_sent(ctx))
{
wslay_event_queue_close(ctx, arg->status_code, (const uint8_t*)buffer, len);
}
DebugLog(1, "%s", buffer);
}
}