From 7b60140b41d254e80b12bac3c1d124a386552832 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Wed, 22 Dec 2021 11:35:54 +1300 Subject: [PATCH 01/14] Add http middleware and websocket upgrade on existing listener --- modules/network/http/http.js | 29 ++- modules/network/http/middleware/hotspot.js | 75 ++++++++ modules/network/http/middleware/manifest.json | 23 +++ modules/network/http/middleware/rewritespa.js | 40 ++++ modules/network/http/middleware/server.js | 75 ++++++++ modules/network/http/middleware/staticzip.js | 101 ++++++++++ modules/network/http/middleware/status.js | 33 ++++ modules/network/http/middleware/websocket.js | 181 ++++++++++++++++++ 8 files changed, 547 insertions(+), 10 deletions(-) create mode 100644 modules/network/http/middleware/hotspot.js create mode 100644 modules/network/http/middleware/manifest.json create mode 100644 modules/network/http/middleware/rewritespa.js create mode 100644 modules/network/http/middleware/server.js create mode 100644 modules/network/http/middleware/staticzip.js create mode 100644 modules/network/http/middleware/status.js create mode 100644 modules/network/http/middleware/websocket.js diff --git a/modules/network/http/http.js b/modules/network/http/http.js index deddd47dfb..1cfb07a23e 100644 --- a/modules/network/http/http.js +++ b/modules/network/http/http.js @@ -115,7 +115,7 @@ export class Request { } close() { - this.socket?.close(); + if( 16 & this.flags == 0) this.socket?.close(); delete this.socket; delete this.buffers; delete this.callback; @@ -452,7 +452,7 @@ function done(error = false, data) { state: 0 - connecting - 1 - receieving request status + 1 - receiving request status 2 - receiving request headers @@ -661,8 +661,10 @@ function server(message, value, etc) { const status = response?.status ?? 200; const message = response?.reason?.toString() ?? reason(status); const parts = ["HTTP/1.1 ", status.toString(), " ", message, "\r\n", - "connection: ", "close\r\n"]; - + "connection: ", "close\r\n"]; + if ( status === 101 ) { + this.flags = 16; + } if (response) { let byteLength; @@ -682,11 +684,13 @@ function server(message, value, etc) { this.flags = 4; } else { - this.flags = 1; - let count = 0; - if (this.body) - count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell - parts.push("content-length: ", count.toString(), "\r\n"); + if ( this.flags !== 16 ) { + this.flags = 1; + let count = 0; + if (this.body) + count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell + parts.push("content-length: ", count.toString(), "\r\n"); + } } } else @@ -702,6 +706,11 @@ function server(message, value, etc) { if (count > (socket.write() - ((2 & this.flags) ? 8 : 0))) return; } + + if ( this.flags === 16 ) { + this.state = 10; + return; + } } if (8 === this.state) { let body = this.body; @@ -741,7 +750,7 @@ function server(message, value, etc) { } finally { this.server.connections.splice(this.server.connections.indexOf(this), 1); - this.close(); + if ( this.flags !== 16 ) this.close(); } } } diff --git a/modules/network/http/middleware/hotspot.js b/modules/network/http/middleware/hotspot.js new file mode 100644 index 0000000000..0efaf4cdbf --- /dev/null +++ b/modules/network/http/middleware/hotspot.js @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import { Middleware, Server } from "middleware/server"; +import Net from "net" + +const hotspot = new Map; + +// iOS 8/9 +hotspot.set("/library/test/success.html",{status: 302,body: "Success"}); +hotspot.set("/hotspot-detect.html",{status: 302,body: "Success"}); + +// Windows +hotspot.set("/ncsi.txt",{status: 302,body: "Microsoft NCSI"}); +hotspot.set("/connecttest.txt",{status: 302,body: "Microsoft Connect Test"}); +hotspot.set("/redirect",{status: 302,body: ""}); // Win 10 + +// Android +hotspot.set("/mobile/status.php", {status:302}); // Android 8.0 (Samsung s9+) +hotspot.set("/generate_204", {status:302}); // Android actual redirect +hotspot.set("/gen_204", {status:204}); // Android 9.0 + +export class MiddlewareHotspot extends Middleware { + constructor() { + super(); + } + handler(req, message, value, etc) { + switch (message) { + case Server.status: + + req.redirect=hotspot.get(value); // value is path + if ( req.redirect) return; // Hotspot url match + delete req.redirect; + return this.next?.handler(req, message, value, etc); + case Server.header: { + if ( "host" === value ) { + req.host=etc; + trace(`MiddlewareHotspot: http://${req.host}${req.path}\n`); + } + return this.next?.handler(req, message, value, etc); + } + case Server.prepareResponse: + + if( req.redirect) { + let apIP=Net.get("IP", "ap"); + let redirect={ + headers: [ "Content-type", "text/plain", "Location",`http://${apIP}`], + ...req.redirect + }; + trace(`Hotspot match: http://${req.host}${req.path}\n`); + trace(JSON.stringify(redirect),'\n'); + + return redirect; + } + } + return this.next?.handler(req, message, value, etc); + } +} +Object.freeze(hotspot); + +/* TO DO +add dns constructor flag. then becomes self contained. +*/ diff --git a/modules/network/http/middleware/manifest.json b/modules/network/http/middleware/manifest.json new file mode 100644 index 0000000000..eb9699ec3b --- /dev/null +++ b/modules/network/http/middleware/manifest.json @@ -0,0 +1,23 @@ +{ + "modules": { + "*": [ + "$(MODULES)/files/zip/*", + "$(MODULES)/network/http/*", + "$(MODULES)/data/base64/*", + "$(MODULES)/data/hex/*", + "$(MODULES)/data/logical/*" + + ], + "dns/server": "$(MODULES)/network/dns/dnsserver", + "websocket/websocket": "$(MODULES)/network/websocket/websocket" + }, + "preload": [ + "http", + "dns/server", + "websocket/websocket", + "base64", + "hex", + "logical", + "websocket" + ] +} diff --git a/modules/network/http/middleware/rewritespa.js b/modules/network/http/middleware/rewritespa.js new file mode 100644 index 0000000000..a29962dcc8 --- /dev/null +++ b/modules/network/http/middleware/rewritespa.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import { Middleware, Server } from "middleware/server"; + +// Single Page application. Map page requests back to / + +export class MiddlewareRewriteSPA extends Middleware { + #routes + constructor(routes) { + super(); + this.#routes=routes + } + + handler(req, message, value, etc) { + + switch (message) { + case Server.status: + if ( this.#routes.includes(value) ) { // To do: possibly use Set? + trace(`Rewrite: ${value}\n`); + value='/'; + trace(`Rewrite now: ${value}\n`); + } + break; + } + return this.next?.handler(req, message, value, etc); + } +} diff --git a/modules/network/http/middleware/server.js b/modules/network/http/middleware/server.js new file mode 100644 index 0000000000..d0e2b595d9 --- /dev/null +++ b/modules/network/http/middleware/server.js @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import {Server} from "http" + +class Middleware { + #next; + constructor() { + this.#next = null + } + get next() { + return this.#next; + } + set next(n) { + this.#next = n; + } +} + +class WebServer extends Server { + #last; + #first; + + constructor(options) { + super(options); + + this.#first = null; + this.#last = null; + } + + use( handler ) { + // chaining here, join new to previous + if( this.#first === null ) { + this.#first=this.#last=handler; + } else { + this.#last.next=handler; + this.#last=handler; + } + // Advise middleware of http parent - needed by websocket for connection management + if ( 'parent' in handler ) + handler.parent=this; + } + + callback(message, value, etc) { + switch (message) { + case Server.status: + this.path=value; + this.method = etc; + break; + case Server.header: + value=value.toLowerCase(); // Header field names are case-insensitive - force lower for easy compare + } + return this.server.#first?.handler(this, message, value, etc); + } + + close() { + if ( this.connections ) + super.close(); // Close any http connections + } +} + +Object.freeze(WebServer.prototype); + +export { WebServer as Server, Middleware }; diff --git a/modules/network/http/middleware/staticzip.js b/modules/network/http/middleware/staticzip.js new file mode 100644 index 0000000000..b370b87d48 --- /dev/null +++ b/modules/network/http/middleware/staticzip.js @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import { Middleware, Server } from "middleware/server"; +import {ZIP} from "zip" + +const mime = new Map; +mime.set("js", "application/javascript"); +mime.set("css", "text/css"); +mime.set("ico", "image/vnd.microsoft.icon"); +mime.set("txt", "text/plain"); +mime.set("htm", "text/html"); +mime.set("html", "text/html"); +mime.set("svg", "image/svg+xml"); +mime.set("png", "image/png"); +mime.set("gif", "image/gif"); +mime.set("webp", "image/webp"); +mime.set("jpg", "image/jpeg"); +mime.set("jpeg", "image/jpeg"); + +export class MiddlewareStaticZip extends Middleware { + constructor(archive) { + super(); + + this.archive = new ZIP(archive); + } + + handler(req, message, value, etc) { + switch (message) { + case Server.status: + // redirect home page + if (value === '/') value='/index.html'; + req.path = value; + try { + req.data = this.archive.file(req.path.slice(1)); // drop leading / to match zip content + req.etag = "mod-" + req.data.crc.toString(16); + } + catch { + delete req.data; + delete req.etag; + return this.next?.handler(req, message, value, etc); + } + break; + + case Server.header: + req.match ||= ("if-none-match" === value) && (req.etag === etc); + return this.next?.handler(req, message, value, etc); + + case Server.prepareResponse: + if (req.match) { + return { + status: 304, + headers: [ + "ETag", req.etag, + ] + }; + } + if (!req.data) { + trace(`prepareResponse: missing file ${req.path}\n`); + + return this.next?.handler(req, message, value, etc); + } + + req.data.current = 0; + const result = { + headers: [ + "Content-type", mime.get(req.path.split('.').pop()) ?? "text/plain", + "Content-length", req.data.length, + "ETag", req.etag, + "Cache-Control", "max-age=60" + ], + body: true + } + if (8 === req.data.method) // Compression Method + result.headers.push("Content-Encoding", "deflate"); + return result; + + case Server.responseFragment: + if (req.data.current >= req.data.length) + return; + + const chunk = req.data.read(ArrayBuffer, (value > 1536) ? 1536 : value); + req.data.current += chunk.byteLength; + return chunk; + } + } +} + +Object.freeze(mime); \ No newline at end of file diff --git a/modules/network/http/middleware/status.js b/modules/network/http/middleware/status.js new file mode 100644 index 0000000000..0fb57584c6 --- /dev/null +++ b/modules/network/http/middleware/status.js @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import { Middleware, Server } from "middleware/server"; + +export class MiddlewareStatus extends Middleware { + #status; + constructor(status=404) { + super(); + this.#status=status + } + handler(req, message, value, etc) { + switch (message) { + + case Server.prepareResponse: + return { + status: this.#status + }; + } + } +} diff --git a/modules/network/http/middleware/websocket.js b/modules/network/http/middleware/websocket.js new file mode 100644 index 0000000000..924b467a53 --- /dev/null +++ b/modules/network/http/middleware/websocket.js @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import { Middleware, Server as HTTPServer } from "middleware/server"; + +// https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers +// https://github.com/Moddable-OpenSource/moddable/blob/public/modules/network/websocket/websocket.js#L375-L398 + +import Base64 from "base64"; +import {Digest} from "crypt"; +import {Client as WebSocketClient} from "websocket/websocket" + +class WebSocketUpgrade extends WebSocketClient { + constructor(opts) { + super(opts); + + this.doMask = false; + this.state = WebSocketClient.receive; + opts.socket=null; // Nuke so http server does not close + } + + send(packet) { + packet=JSON.stringify(packet); + let encoded = this.callback.call( this, MiddlewareWebsocket.encode, packet); + if (encoded) + packet = encoded; + this.write(packet); + } +} + +class MiddlewareWebsocket extends Middleware { + #path; + #webserver; + constructor( path='/') { + super(); + this.#path=path + } + + get parent() { + return this.#webserver; + } + set parent(p) { + + this.#webserver = p; + } + + callbackHandler( message, value ) { + if ( message === WebSocketClient.receive ) { + let decoded = this.callback.call( this, MiddlewareWebsocket.decode, value); + if (decoded) { + value = decoded; + } + } + return this.callback.call( this, message, value ); // call app callback; + } + + broadcast(message, except) { + for (let i = 0, connections = this.#webserver.connections; i < connections.length; i++) { + const connection = connections[i]; + + if (except === connection) // Don't broadcast to self + continue; + if (connection instanceof WebSocketUpgrade) { + try { + connection.send(message); + } catch { + connections.splice(i, 1); + } + } + } + } + + close() { + this.#webserver.close(); + } + + handler(req, message, value, etc) { + switch (message) { + case HTTPServer.status: + if ( value === this.#path ) { + req.ws = { flags: 0 }; + } + break; + case HTTPServer.header: { + if ( req.ws ) { + if ( value==='connection' && etc === 'Upgrade') { + req.ws.flags |= 1 + } + if ( value==='sec-websocket-version') { + req.ws.flags |= (etc === '13') ? 2 : 0; + } + if ( ( value==='upgrade' && etc === 'websocket' ) ) { + req.ws.flags |= 4 + } + if ( value==='sec-websocket-key') { + req.ws.flags |= 8; + req.ws.key = etc; + this.callback( WebSocketClient.connect ); // tell app we have a new connection + } + if ( value==='sec-websocket-protocol') { + let data = etc.split(","); + for (let i = 0; i < data.length; ++i) + data[i] = data[i].trim().toLowerCase(); + let protocol = this.callback(MiddlewareWebsocket.subprotocol, data); + if (protocol) + req.ws.protocol = protocol; + } + } + break; // continue chaining headers + } + case HTTPServer.prepareResponse: + + if ( req.ws && req.ws.flags === 15 ) { + let sha1 = new Digest("SHA1"); + + sha1.write(req.ws.key); + delete req.ws.key; + delete req.ws.flags; + sha1.write("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + let accept = Base64.encode(sha1.close()); + let response = { + headers: [ + "Sec-WebSocket-Accept", accept, + "Upgrade","websocket" + ], + status: 101 + }; + if (req.ws.protocol) { + response.headers.push("Sec-WebSocket-Protocol",req.ws.protocol); + } + return response; + } + // ws handshake failure - continue chain + delete req.ws; + break; + case HTTPServer.responseComplete: + if ( req.ws ) { + + const websocket = new WebSocketUpgrade({socket:req.socket}); + websocket.callback=this.callbackHandler.bind(this); + + req.server.connections.push(websocket); + this.callback.call( websocket, WebSocketClient.handshake ); // tell app we have handshake complete + return; + } + break; + } + return this.next?.handler(req, message, value, etc); + } +} + +MiddlewareWebsocket.connect = 1; +MiddlewareWebsocket.handshake = 2; +MiddlewareWebsocket.receive = 3; +MiddlewareWebsocket.disconnect = 4; +MiddlewareWebsocket.subprotocol = 5; +MiddlewareWebsocket.encode = 6; // Websocket encode packet +MiddlewareWebsocket.decode = 7; // Websocket decode packet +Object.freeze(MiddlewareWebsocket.prototype); + +export { MiddlewareWebsocket }; + +/* TO DO + +flags -> use constants ? also http module + +method === 'GET' + +*/ From a5b1e38c5feed983cacef2b5337ed46ec50cb97b Mon Sep 17 00:00:00 2001 From: wilberforce Date: Fri, 4 Feb 2022 11:20:25 +1300 Subject: [PATCH 02/14] add example, rename middleware to bridge --- examples/network/http/httpbridge/.gitignore | 1 + examples/network/http/httpbridge/main.js | 70 +++++++ .../network/http/httpbridge/manifest.json | 22 +++ .../network/http/httpbridge/site/README.md | 33 ++++ .../http/httpbridge/site/dist/site.zip | Bin 0 -> 3479 bytes .../network/http/httpbridge/site/package.json | 23 +++ .../http/httpbridge/site/public/app.ts | 136 +++++++++++++ .../http/httpbridge/site/public/index.html | 39 ++++ .../http/httpbridge/site/public/moddable.svg | 2 + .../http/httpbridge/site/public/model.js | 6 + .../httpbridge/site/public/stylesheet.css | 75 ++++++++ .../http/httpbridge/site/wmr.config.mjs | 13 ++ .../http/{middleware => bridge}/hotspot.js | 12 +- .../staticzip.js => bridge/httpzip.js} | 17 +- .../http/{middleware => bridge}/manifest.json | 6 +- .../http/{middleware => bridge}/rewritespa.js | 6 +- .../http/{middleware => bridge}/status.js | 6 +- .../server.js => bridge/webserver.js} | 14 +- modules/network/http/bridge/websocket.js | 134 +++++++++++++ modules/network/http/http.js | 43 +++-- modules/network/http/middleware/websocket.js | 181 ------------------ modules/network/websocket/websocket.js | 31 ++- 22 files changed, 632 insertions(+), 238 deletions(-) create mode 100644 examples/network/http/httpbridge/.gitignore create mode 100644 examples/network/http/httpbridge/main.js create mode 100644 examples/network/http/httpbridge/manifest.json create mode 100644 examples/network/http/httpbridge/site/README.md create mode 100644 examples/network/http/httpbridge/site/dist/site.zip create mode 100644 examples/network/http/httpbridge/site/package.json create mode 100644 examples/network/http/httpbridge/site/public/app.ts create mode 100644 examples/network/http/httpbridge/site/public/index.html create mode 100644 examples/network/http/httpbridge/site/public/moddable.svg create mode 100644 examples/network/http/httpbridge/site/public/model.js create mode 100644 examples/network/http/httpbridge/site/public/stylesheet.css create mode 100644 examples/network/http/httpbridge/site/wmr.config.mjs rename modules/network/http/{middleware => bridge}/hotspot.js (88%) rename modules/network/http/{middleware/staticzip.js => bridge/httpzip.js} (87%) rename modules/network/http/{middleware => bridge}/manifest.json (85%) rename modules/network/http/{middleware => bridge}/rewritespa.js (86%) rename modules/network/http/{middleware => bridge}/status.js (81%) rename modules/network/http/{middleware/server.js => bridge/webserver.js} (82%) create mode 100644 modules/network/http/bridge/websocket.js delete mode 100644 modules/network/http/middleware/websocket.js diff --git a/examples/network/http/httpbridge/.gitignore b/examples/network/http/httpbridge/.gitignore new file mode 100644 index 0000000000..b512c09d47 --- /dev/null +++ b/examples/network/http/httpbridge/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/examples/network/http/httpbridge/main.js b/examples/network/http/httpbridge/main.js new file mode 100644 index 0000000000..40f62a7b29 --- /dev/null +++ b/examples/network/http/httpbridge/main.js @@ -0,0 +1,70 @@ +import { WebServer as HTTPServer } from "bridge/webserver"; +import { BridgeWebsocket } from "bridge/websocket"; +import { BridgeHttpZip } from "bridge/httpzip"; + +const http = new HTTPServer({ + port: 80 +}); + +let ws = new BridgeWebsocket('/api'); +http.use( ws ); +http.use( new BridgeHttpZip('site.zip')); + +import { _model } from "model"; + +let observer = { + set: function (obj, prop, value) { + trace(`set prop: ${prop} ${ JSON.stringify(value)}\n`); + obj[prop] = value; + return true; + } + }; + + /* + class thingy { + minus() { + trace(`minus\n`); + } + } + */ + +const model = new Proxy(_model, observer); + +ws.callback =function cb(websock,message, value) { + switch (message) { + case BridgeWebsocket.connect: + break; + + case BridgeWebsocket.handshake: + websock.broadcast( model ) + break; + + case BridgeWebsocket.receive: + try { + trace(`Main WebSocket receive: ${value}\n`); + value = JSON.parse(value); + + if ( value?.action === 'shutdown'){ + ws.close(); + http.close(); + } + if ( value?.action === 'minus'){ + model.satisfaction=Math.max(0,model.satisfaction-1) + value=model; + model.minus(); + } + if ( value?.action === 'plus'){ + model.satisfaction=Math.min(10,model.satisfaction+1) + value=model; + } + if ( value.hasOwnProperty('language') ) { + model.language = value.language; + } + + websock.broadcast(value); + } + catch (e) { + trace(`WebSocket parse received data error: ${e}\n`); + } + } +} diff --git a/examples/network/http/httpbridge/manifest.json b/examples/network/http/httpbridge/manifest.json new file mode 100644 index 0000000000..869e33005a --- /dev/null +++ b/examples/network/http/httpbridge/manifest.json @@ -0,0 +1,22 @@ +{ + "include": [ + "$(MODDABLE)/examples/manifest_base.json", + "$(MODDABLE)/examples/manifest_net.json", + "$(MODULES)/network/http/manifest.json", + "$(MODULES)/network/http/bridge/manifest.json", + "$(MODULES)/network/websocket/manifest.json", + "$(MODULES)/files/zip/manifest.json" + ], + "modules": { + "*": [ + "./main" + ], + "bridge/*": "$(MODULES)/network/http/bridge/*", + "model":"site/public/model" + }, + "preload": [ + ], + "data": { + "*": "./site/dist/site" + } +} diff --git a/examples/network/http/httpbridge/site/README.md b/examples/network/http/httpbridge/site/README.md new file mode 100644 index 0000000000..580e238d4b --- /dev/null +++ b/examples/network/http/httpbridge/site/README.md @@ -0,0 +1,33 @@ +# Moddable http bridge example + +This example shows how to use the http bridge components. + +It starts a bi-directional websocket between the Moddable server and a browser. If you start another browser instance, changes on one browser reflect in the other. + +### build the zip file +``` +cd site +npm install +npm run build +``` + +### build moddable +From the site folder: +``` +npm run mcconfig +``` + +This will build the `site.zip` and launch the simulator + +open browser `http://localhost` + +### front end development +``` +`npm run dev` +or +`wmr` + +open browser `http://localhost:8080` + +Edit any ts or css file, and on save the browser will auto-update with changes, and the `websocket` is connected to the simulator Modable server + diff --git a/examples/network/http/httpbridge/site/dist/site.zip b/examples/network/http/httpbridge/site/dist/site.zip new file mode 100644 index 0000000000000000000000000000000000000000..9f184249ea5941c45a4c1e43defc4556980f4cf2 GIT binary patch literal 3479 zcmaJ^c{CJk+n)#7qU=+)$=C_mhRQCAAv^IP+YBON7)#l+GUt z_pBosYp5Y!&w0PT=Y5}ZzVEv4bMEV$`~2=duJgNp*Y7fhQqyn%0QB?#Ka{pPL0y#efL@3doJNQF62jW%dekg_V>0@7^O<~FgCn-mZ>p^ zU}YNPB~Su4uV7GD!nsT$X!`SzoyMJlP*!TWx8QM9TCYZ`=>YF^Tz0yK(Ku(Bz}wvL z=Amwr$Ur*A;r+-)iIu@;yoGHa!CDNWFB3cmgo+4~M4M|-J;+POlxmzm9YZZuYv1Oe zgD~A=AZhGwkrO<%DbeUt7bT|BI}>^@@a1?bgQ&n`V$PAA9`BQ5LH@UQklCQ^@4=2V z1{oVRBrN!2=dqYwr?2RsQek;Y{1@@AyPV5O@WZw{i;sFp?EZSRx^FJL8jTGAdBF7x z3UhVMo2)OFh<^|<`UG;AqfV#$DI*% z$|Z(uvEihm?6F+#^(;b}o<&Ffpdar$UYS^ks7x@N5A%_g`Cwjl670*>+ssQoH6{qk zJm}Y&JaBvb&^UVIoNOdV(ID+%+;zwJYuec%`9X+i`@Epnmgh$oAv&&RCmxx-0h|pg zlIle_lO1+nNbk5jR;m2j@=1Y+2vR4>y*JKU2uN3Lt&qaMk@g9)1X4Im4~NuLYP&TC z?K4ZP6E?5Tf`a-$k!YJ9|E*{~w7k<&gNUQU8=}X1_!g~9;*$Qt|7iM3D z>V|P#59wL%)5Nl^bYfRDi-6YykPWN-K9!p@(KX|pRt3g8h4sLlkVv~ky6;(;8pC4xEq6r= zvchkgT!79#H*CR8(a7)+DkbA|A#Uw08we|MSMAU4`~NO1{r`N~vs(UdTs+GhA3c(W zUz1fG9#-U(Xxv^n#@{*kQV*}M%niB;<>&7D4?I$8X16&Ps&g@o)(6-qjng@)N;UoD z3b|Jgnn?CfW#GcT$4p^BwXj_k8rkuhU9~8t&?_&Au3>Ha#PzLkyS~JVAveC;LqFkk zJ1I-GyF~6rYO|s}&fFD$m_9$qAcbc;Z~d|fDCQf(>k4$fLrWKdfkoMMmfoh5z$(Th zRKFGsxbZduGv=BqLbzP@qQ@*9H?3tiec1zD2IX?{0|O;hd#ehWE_{FJDc);Zz*v@3 zlRz0uW=A#m++0zKJ^;vWEqQw~DAKWZYt#9b9b0&~{TR`kK_FGaLwodYQiz#snVwpWY5qPsUmJQy)g^oNVtcHNQ1VwnFhHCx`kfg7bz-4Y#YWuqAl)ABq+N+XY+N0q<{d17 zpAh}Mf1jo2{&CLfchekJ!2yX{A?z zbuacpR`AQ8x?au^d#MQS;7mJalTTtM*Vi34*?xgf2gTjA>rtA{<4)7<#Az8T>NEAcd&TbZf2fla8H+~kix)5xWV#n;~$PU{718k z-PXC>9SqVBn#!NF6A$#n-`KLXM@Sq(i|m*sQ`Mj7zbFQA1ns-;>}+__>~po=es+22 z+^(L+CpL$PyadadeN6m$NE={lEkOoNd5EZxu;74eVOQpIu|K3ZKU^d$ex&f1XxamWFMzeZeto+s4{cJ=`K-FXeJdV( z=!^_N{(`HkH7s@1{Td$5Jc2UqES~(pg-mYAjFU)}EiBKLK}F zEoL${=J2U{qfEhunq$;a;6j7bn}%nVjys%kY--&pw%91?C6RG1A}jResd&qUpUiS{ z-s_ou9&Prc!4KY_mclOEWPq^CJ-DZ#{Q_Rf)|QlT;O#77Kc$Xy6*whLt*9i_mMq7S zCbUh)gh#Rd)^rdpM5r!-Gv$k#5T1~5XwbM_mV_jIXg^3?EI8b^%i%(`&!z{geEg=> z2-aYEh{9w7)L&hB5+gVNG_<`!di!c<^eQVqnxk6KS*54};cS@3)SssCsd_v>duCy~CR0ez|gVJq^Hus6Tm|>&6;Jogr8JQutl=DT31qgyV zyXys5kcC&IrCgeU^_!RzG3Tg~q1Q!J1)5U<@F(1E#XM7naqxgm9hgQB5V|Iy{j6^f zpPZ6q9O3=?mJ$>74Ep5aVHo`(QM@*$y|=nSvRZ{e*O9;Kd(e;uS(WD^u2i)HaE4G;F#sMBW9XaHPQhEx?bSVO2CA z6tt2MZN;BDtA}E&xk^hL}W?`FRXLDq%;nB{und|dStIXk!Y^5vmxLPw528j)gp%V&Vz16f{#+wU#S{c`bkX2lOb zp80xJ@5P3n;U7W_w4Un7|5?+F#U(yaElaHb?wmS&kfYOK=(9$m@L(_!Q+6XR^5e1{ zH&z*;9hW~_)}-ON<9*?u2JM+3fm-8ZINimga07 Mot-g%^#s7b04;V}_W%F@ literal 0 HcmV?d00001 diff --git a/examples/network/http/httpbridge/site/package.json b/examples/network/http/httpbridge/site/package.json new file mode 100644 index 0000000000..50c9ce3868 --- /dev/null +++ b/examples/network/http/httpbridge/site/package.json @@ -0,0 +1,23 @@ +{ + "name": "moddable", + "version": "1.0.0", + "description": "", + "main": "app.ts", + "scripts": { + "dev": "wmr", + "build": "wmr build", + "serve": "wmr serve", + "mcconfig": "wmr build && cd .. && mcconfig -d -m" + }, + "author": "", + "license": "MIT", + "dependencies": { + "alpinejs": "^3.8.1", + "rollup-plugin-zip": "^1.0.3", + "vite-plugin-singlefile": "^0.6.3", + "wmr": "^3.7.2" + }, + "devDependencies": { + "rollup-plugin-embed-css": "^1.0.24" + } +} diff --git a/examples/network/http/httpbridge/site/public/app.ts b/examples/network/http/httpbridge/site/public/app.ts new file mode 100644 index 0000000000..cc576ab47e --- /dev/null +++ b/examples/network/http/httpbridge/site/public/app.ts @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021-2022 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import { _model } from "./model.js"; + +let observer = { + set: function (obj, prop, value) { + console.log(`set prop: ${prop} ${ JSON.stringify(value)}\n`) + obj[prop] = value + return true; + } + }; + +const model = new Proxy(_model, observer); + +class WebConnect extends WebSocket { + constructor(url) { + super(url); + this.onopen = this.onOpen.bind(this) + this.onmessage = this.onMessage.bind(this) + this.onclose = this.onClose.bind(this) + this.onerror = this.onError.bind(this) + } + + onOpen() { + this.trace("ws open", "e") + this.send({ hello: "world" }) + } + + onMessage(event) { + this.trace(`ws recv: ${event.data}`, "w") + const data = JSON.parse(event.data) + this.model( data ) + } + + model(data) { + if ( data.hasOwnProperty('satisfaction') ) { + model.satisfaction = data.satisfaction + document.getElementById('satisfaction').value = data.satisfaction + } + if ( data.hasOwnProperty('language') ) { + model.language = data.language; + document.getElementById(`language-${data.language}`).checked=true + } + } + + onClose(event: CloseEvent) { + this.trace(`ws close: ${event.code}`, "e") + } + + onError(event: Event) { + this.trace(`ws error: ${event.code}`, "e") + } + + send(msg) { + if (typeof msg === "object") msg = JSON.stringify(msg) + this.trace(`ws sent: ${msg}`); + try { + super.send(msg); + } + catch (e) { + this.trace(`ws error: ${e}`, "e"); + } + } + + trace(msg, cls = "i") { + if (typeof msg === "object") msg = JSON.stringify(msg); + console.log(msg); + msg = new Date().toString().substring(16, 24) + ": " + msg; + document.getElementById("log").innerHTML = `${msg}\n${log.innerHTML}`; + } + + action(act) { + this.send({ action: act }) + } +} + +class App { + ws: WebConnect = null + + constructor() { + const watch = document.querySelectorAll('.e-watch') + watch.forEach(el => { + el.addEventListener("change", (e) => { + let t=e.target + let packet = {} + packet[t.name] = t.value + this.ws.send(packet) + }) + }) + + const actionElements = document.querySelectorAll('.e-action'); + actionElements.forEach(el => { + el.addEventListener('click', (e) => { + + if ( e.target.id === "connect" ) { + this.connect(); + } else { + this.ws.action(e.target.id) + } + }) + }) + this.connect(); + } + + connect() { + if ( this.ws ) { + try { + this.ws.trace(`disconnecting... ${this.ws.readyState}`) + if ( this.ws.readyState === WebSocket.OPEN ) + this.ws.close() + } + catch (e) { + } + delete this.ws; + } + const url = "ws://" + window.location.hostname + "/api" + this.ws = new WebConnect(url) + this.ws.trace("connecting...") + this.ws.model(model) + } +} + +let app = new App(); diff --git a/examples/network/http/httpbridge/site/public/index.html b/examples/network/http/httpbridge/site/public/index.html new file mode 100644 index 0000000000..36f7292993 --- /dev/null +++ b/examples/network/http/httpbridge/site/public/index.html @@ -0,0 +1,39 @@ + + + + + + + +Moddable + + + +
+Moddable +
+ +
+ +
+ +Javascript +Typescript +
+
+ + + +
+
+

Debug Log

+
+

+
+ + +
+ + + + diff --git a/examples/network/http/httpbridge/site/public/moddable.svg b/examples/network/http/httpbridge/site/public/moddable.svg new file mode 100644 index 0000000000..bec1eced59 --- /dev/null +++ b/examples/network/http/httpbridge/site/public/moddable.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/network/http/httpbridge/site/public/model.js b/examples/network/http/httpbridge/site/public/model.js new file mode 100644 index 0000000000..d47baa4a47 --- /dev/null +++ b/examples/network/http/httpbridge/site/public/model.js @@ -0,0 +1,6 @@ +let _model= { + satisfaction: 5, + language: 'javascript' +} + +export { _model }; \ No newline at end of file diff --git a/examples/network/http/httpbridge/site/public/stylesheet.css b/examples/network/http/httpbridge/site/public/stylesheet.css new file mode 100644 index 0000000000..5901aabfb2 --- /dev/null +++ b/examples/network/http/httpbridge/site/public/stylesheet.css @@ -0,0 +1,75 @@ +* { + box-sizing: inherit; +} + +div, +input { + font-size: 1em; + vertical-align:middle; + padding: 4px +} + +body { + font-family: sans-serif; +} + +h1 { + text-align: center; +} + +#log { + padding:0.5em; + text-align: left; + color: wheat; + background-color: #1c1c1c; + overflow-x: scroll; +} + +#log pre { + overflow-x: scroll +} + +#log .v { + color: #888888; +} + +#log .d { + color: #00DDDD; +} + +#log .c { + color: magenta; +} + +#log .i { + color: limegreen; +} + +#log .w { + color: yellow; +} + +#log .e { + color: red; + font-weight: bold; +} + +.flow-x { + overflow-x:auto +} + +.right { + float: right; +} + +button { + border-radius: 1rem; + border-width: 1px; + min-width: 0.8rem; + font-size: 1.2rem; +} + +label { + min-width: 6rem; + float:left; +} \ No newline at end of file diff --git a/examples/network/http/httpbridge/site/wmr.config.mjs b/examples/network/http/httpbridge/site/wmr.config.mjs new file mode 100644 index 0000000000..fc345c34be --- /dev/null +++ b/examples/network/http/httpbridge/site/wmr.config.mjs @@ -0,0 +1,13 @@ +//import htmlMinifier from 'rollup-plugin-html-minifier'; +import zip from 'rollup-plugin-zip'; +//import embedCSS from 'rollup-plugin-embed-css'; + +export function build({ plugins }) { + plugins.push( + //embedCSS({/* Options */}), + //htmlMinifier({ + // any options here + //}), + zip({file: 'site.zip'}) + ); +} diff --git a/modules/network/http/middleware/hotspot.js b/modules/network/http/bridge/hotspot.js similarity index 88% rename from modules/network/http/middleware/hotspot.js rename to modules/network/http/bridge/hotspot.js index 0efaf4cdbf..be9a33b5b9 100644 --- a/modules/network/http/middleware/hotspot.js +++ b/modules/network/http/bridge/hotspot.js @@ -13,7 +13,7 @@ * */ -import { Middleware, Server } from "middleware/server"; +import { Bridge, HTTPServer } from "bridge/webserver"; import Net from "net" const hotspot = new Map; @@ -32,26 +32,26 @@ hotspot.set("/mobile/status.php", {status:302}); // Android 8.0 (Samsung s9+) hotspot.set("/generate_204", {status:302}); // Android actual redirect hotspot.set("/gen_204", {status:204}); // Android 9.0 -export class MiddlewareHotspot extends Middleware { +export class BridgeHotspot extends Bridge { constructor() { super(); } handler(req, message, value, etc) { switch (message) { - case Server.status: + case HTTPServer.status: req.redirect=hotspot.get(value); // value is path if ( req.redirect) return; // Hotspot url match delete req.redirect; return this.next?.handler(req, message, value, etc); - case Server.header: { + case HTTPServer.header: { if ( "host" === value ) { req.host=etc; - trace(`MiddlewareHotspot: http://${req.host}${req.path}\n`); + trace(`BridgeHotspot: http://${req.host}${req.path}\n`); } return this.next?.handler(req, message, value, etc); } - case Server.prepareResponse: + case HTTPServer.prepareResponse: if( req.redirect) { let apIP=Net.get("IP", "ap"); diff --git a/modules/network/http/middleware/staticzip.js b/modules/network/http/bridge/httpzip.js similarity index 87% rename from modules/network/http/middleware/staticzip.js rename to modules/network/http/bridge/httpzip.js index b370b87d48..c3dd2ca7f8 100644 --- a/modules/network/http/middleware/staticzip.js +++ b/modules/network/http/bridge/httpzip.js @@ -13,7 +13,8 @@ * */ -import { Middleware, Server } from "middleware/server"; +import { Bridge, HTTPServer } from "bridge/webserver"; +import Resource from "Resource"; import {ZIP} from "zip" const mime = new Map; @@ -30,16 +31,16 @@ mime.set("webp", "image/webp"); mime.set("jpg", "image/jpeg"); mime.set("jpeg", "image/jpeg"); -export class MiddlewareStaticZip extends Middleware { - constructor(archive) { +export class BridgeHttpZip extends Bridge { + constructor(resource) { super(); - this.archive = new ZIP(archive); + this.archive = new ZIP(new Resource(resource)); } handler(req, message, value, etc) { switch (message) { - case Server.status: + case HTTPServer.status: // redirect home page if (value === '/') value='/index.html'; req.path = value; @@ -54,11 +55,11 @@ export class MiddlewareStaticZip extends Middleware { } break; - case Server.header: + case HTTPServer.header: req.match ||= ("if-none-match" === value) && (req.etag === etc); return this.next?.handler(req, message, value, etc); - case Server.prepareResponse: + case HTTPServer.prepareResponse: if (req.match) { return { status: 304, @@ -87,7 +88,7 @@ export class MiddlewareStaticZip extends Middleware { result.headers.push("Content-Encoding", "deflate"); return result; - case Server.responseFragment: + case HTTPServer.responseFragment: if (req.data.current >= req.data.length) return; diff --git a/modules/network/http/middleware/manifest.json b/modules/network/http/bridge/manifest.json similarity index 85% rename from modules/network/http/middleware/manifest.json rename to modules/network/http/bridge/manifest.json index eb9699ec3b..3f821cd6f3 100644 --- a/modules/network/http/middleware/manifest.json +++ b/modules/network/http/bridge/manifest.json @@ -1,12 +1,12 @@ { "modules": { "*": [ + "$(MODULES)/files/resource/*", "$(MODULES)/files/zip/*", "$(MODULES)/network/http/*", "$(MODULES)/data/base64/*", "$(MODULES)/data/hex/*", "$(MODULES)/data/logical/*" - ], "dns/server": "$(MODULES)/network/dns/dnsserver", "websocket/websocket": "$(MODULES)/network/websocket/websocket" @@ -18,6 +18,8 @@ "base64", "hex", "logical", - "websocket" + "websocket", + "resource", + "zip" ] } diff --git a/modules/network/http/middleware/rewritespa.js b/modules/network/http/bridge/rewritespa.js similarity index 86% rename from modules/network/http/middleware/rewritespa.js rename to modules/network/http/bridge/rewritespa.js index a29962dcc8..4fdda7c152 100644 --- a/modules/network/http/middleware/rewritespa.js +++ b/modules/network/http/bridge/rewritespa.js @@ -13,11 +13,11 @@ * */ -import { Middleware, Server } from "middleware/server"; +import { Bridge, HTTPServer } from "bridge/webserver"; // Single Page application. Map page requests back to / -export class MiddlewareRewriteSPA extends Middleware { +export class BridgeRewriteSPA extends Bridge { #routes constructor(routes) { super(); @@ -27,7 +27,7 @@ export class MiddlewareRewriteSPA extends Middleware { handler(req, message, value, etc) { switch (message) { - case Server.status: + case HTTPServer.status: if ( this.#routes.includes(value) ) { // To do: possibly use Set? trace(`Rewrite: ${value}\n`); value='/'; diff --git a/modules/network/http/middleware/status.js b/modules/network/http/bridge/status.js similarity index 81% rename from modules/network/http/middleware/status.js rename to modules/network/http/bridge/status.js index 0fb57584c6..f119b87436 100644 --- a/modules/network/http/middleware/status.js +++ b/modules/network/http/bridge/status.js @@ -13,9 +13,9 @@ * */ -import { Middleware, Server } from "middleware/server"; +import { Bridge, HTTPServer } from "bridge/webserver"; -export class MiddlewareStatus extends Middleware { +export class BridgeStatus extends Bridge { #status; constructor(status=404) { super(); @@ -24,7 +24,7 @@ export class MiddlewareStatus extends Middleware { handler(req, message, value, etc) { switch (message) { - case Server.prepareResponse: + case HTTPServer.prepareResponse: return { status: this.#status }; diff --git a/modules/network/http/middleware/server.js b/modules/network/http/bridge/webserver.js similarity index 82% rename from modules/network/http/middleware/server.js rename to modules/network/http/bridge/webserver.js index d0e2b595d9..9e950ee9cd 100644 --- a/modules/network/http/middleware/server.js +++ b/modules/network/http/bridge/webserver.js @@ -13,9 +13,9 @@ * */ -import {Server} from "http" +import {Server as HTTPServer} from "http" -class Middleware { +class Bridge { #next; constructor() { this.#next = null @@ -28,7 +28,7 @@ class Middleware { } } -class WebServer extends Server { +class WebServer extends HTTPServer { #last; #first; @@ -47,18 +47,18 @@ class WebServer extends Server { this.#last.next=handler; this.#last=handler; } - // Advise middleware of http parent - needed by websocket for connection management + // Advise Bridge of http parent - needed by websocket for connection management if ( 'parent' in handler ) handler.parent=this; } callback(message, value, etc) { switch (message) { - case Server.status: + case HTTPServer.status: this.path=value; this.method = etc; break; - case Server.header: + case HTTPServer.header: value=value.toLowerCase(); // Header field names are case-insensitive - force lower for easy compare } return this.server.#first?.handler(this, message, value, etc); @@ -72,4 +72,4 @@ class WebServer extends Server { Object.freeze(WebServer.prototype); -export { WebServer as Server, Middleware }; +export { WebServer, HTTPServer, Bridge }; diff --git a/modules/network/http/bridge/websocket.js b/modules/network/http/bridge/websocket.js new file mode 100644 index 0000000000..bf06dff433 --- /dev/null +++ b/modules/network/http/bridge/websocket.js @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ + +import { Bridge, HTTPServer } from "bridge/webserver"; +import {Server as WebSocketServer} from "websocket" + +class WebSocketUpgrade extends WebSocketServer { + static connections = []; + + constructor(opts) { + super(opts); + } + + /* + send(packet) { + packet=JSON.stringify(packet); + let encoded = WebSocketUpgrade.bridge?.callback.call( this, BridgeWebsocket.encode, packet); + if (encoded) + packet = encoded; + this.write(packet); + } + */ + + callback(message, value) { + const ws = this.ws; + + switch (message) { + case WebSocketServer.connect: + this.ws = value; + break; + + case WebSocketServer.handshake: + WebSocketUpgrade.connections.push(this); + trace(`WebSocket connected: ${WebSocketUpgrade.connections.length}\n`); + break; + + case WebSocketServer.disconnect: + case WebSocketServer.error: + value = WebSocketUpgrade.connections.findIndex(value => value === this); + if (-1 === value) + return; + + WebSocketUpgrade.connections.splice(value, 1); + trace(`WebSocket disconnected: ${WebSocketUpgrade.connections.length}\n`); + break; + + case WebSocketServer.receive: + // App handles.. + break; + } + WebSocketUpgrade.bridge?.callback(ws,message, value ); // call app callback; + } + + broadcast(message, except) { + message = JSON.stringify(message); + trace(`WebSocket send: ${message}\n`); + for (let i = 0, connections = WebSocketUpgrade.connections; i < connections.length; i++) { + const connection = connections[i]; + + if (except === connection) // Don't broadcast to self + continue; + try { + //connection.send(message); + connection.write(message); + } catch { + connections.splice(i, 1); + } + } + } +} + +class BridgeWebsocket extends Bridge { + #path; + #webserver; + constructor( path='/') { + super(); + this.#path=path; + } + + get parent() { + return this.#webserver; + } + set parent(p) { + + this.#webserver = p; + } + + close() { + for (let i = 0, connections = WebSocketUpgrade.connections; i < connections.length; i++) { + try { + connections[i].close() + } catch { + // Silently ignore + } + } + } + + handler(req, message, value, etc) { + switch (message) { + case HTTPServer.status: + if ( value === this.#path ) { + WebSocketUpgrade.bridge=this; + const socket = this.parent.detach(req); + const websocket = new WebSocketUpgrade({socket:socket}); + return; + } + break; + } + return this.next?.handler(req, message, value, etc); + } +} + +BridgeWebsocket.connect = 1; +BridgeWebsocket.handshake = 2; +BridgeWebsocket.receive = 3; +BridgeWebsocket.disconnect = 4; +BridgeWebsocket.subprotocol = 5; +BridgeWebsocket.encode = 6; // Websocket encode packet +BridgeWebsocket.decode = 7; // Websocket decode packet +Object.freeze(BridgeWebsocket.prototype); + +export { BridgeWebsocket }; diff --git a/modules/network/http/http.js b/modules/network/http/http.js index 1cfb07a23e..1f8411fd97 100644 --- a/modules/network/http/http.js +++ b/modules/network/http/http.js @@ -115,7 +115,7 @@ export class Request { } close() { - if( 16 & this.flags == 0) this.socket?.close(); + this.socket?.close(); delete this.socket; delete this.buffers; delete this.callback; @@ -512,6 +512,20 @@ export class Server { this.#listener.close(); this.#listener = undefined; } + detach(connection) { + const i = this.connections.indexOf(connection); + if (i < 0) throw new Error; + + this.connections.splice(i, 1); + + const socket = connection.socket; + delete socket.callback; + connection.state = 10; + delete connection.socket; + connection.close(); + + return socket; + } } Server.connection = 1; Server.status = 2; @@ -589,6 +603,8 @@ function server(message, value, etc) { let method = line.shift(); let path = line.join(" "); // re-aassemble path this.callback(Server.status, path, method); + if (!this.socket) + return; this.total = undefined; this.state = 2; @@ -661,10 +677,8 @@ function server(message, value, etc) { const status = response?.status ?? 200; const message = response?.reason?.toString() ?? reason(status); const parts = ["HTTP/1.1 ", status.toString(), " ", message, "\r\n", - "connection: ", "close\r\n"]; - if ( status === 101 ) { - this.flags = 16; - } + "connection: ", "close\r\n"]; + if (response) { let byteLength; @@ -684,13 +698,11 @@ function server(message, value, etc) { this.flags = 4; } else { - if ( this.flags !== 16 ) { - this.flags = 1; - let count = 0; - if (this.body) - count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell - parts.push("content-length: ", count.toString(), "\r\n"); - } + this.flags = 1; + let count = 0; + if (this.body) + count = ("string" === typeof this.body) ? this.body.length : this.body.byteLength; //@@ utf-8 hell + parts.push("content-length: ", count.toString(), "\r\n"); } } else @@ -706,11 +718,6 @@ function server(message, value, etc) { if (count > (socket.write() - ((2 & this.flags) ? 8 : 0))) return; } - - if ( this.flags === 16 ) { - this.state = 10; - return; - } } if (8 === this.state) { let body = this.body; @@ -750,7 +757,7 @@ function server(message, value, etc) { } finally { this.server.connections.splice(this.server.connections.indexOf(this), 1); - if ( this.flags !== 16 ) this.close(); + this.close(); } } } diff --git a/modules/network/http/middleware/websocket.js b/modules/network/http/middleware/websocket.js deleted file mode 100644 index 924b467a53..0000000000 --- a/modules/network/http/middleware/websocket.js +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2016-2021 Moddable Tech, Inc. - * Copyright (c) Wilberforce - * - * This file is part of the Moddable SDK. - * - * This work is licensed under the - * Creative Commons Attribution 4.0 International License. - * To view a copy of this license, visit - * . - * or send a letter to Creative Commons, PO Box 1866, - * Mountain View, CA 94042, USA. - * - */ - -import { Middleware, Server as HTTPServer } from "middleware/server"; - -// https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers -// https://github.com/Moddable-OpenSource/moddable/blob/public/modules/network/websocket/websocket.js#L375-L398 - -import Base64 from "base64"; -import {Digest} from "crypt"; -import {Client as WebSocketClient} from "websocket/websocket" - -class WebSocketUpgrade extends WebSocketClient { - constructor(opts) { - super(opts); - - this.doMask = false; - this.state = WebSocketClient.receive; - opts.socket=null; // Nuke so http server does not close - } - - send(packet) { - packet=JSON.stringify(packet); - let encoded = this.callback.call( this, MiddlewareWebsocket.encode, packet); - if (encoded) - packet = encoded; - this.write(packet); - } -} - -class MiddlewareWebsocket extends Middleware { - #path; - #webserver; - constructor( path='/') { - super(); - this.#path=path - } - - get parent() { - return this.#webserver; - } - set parent(p) { - - this.#webserver = p; - } - - callbackHandler( message, value ) { - if ( message === WebSocketClient.receive ) { - let decoded = this.callback.call( this, MiddlewareWebsocket.decode, value); - if (decoded) { - value = decoded; - } - } - return this.callback.call( this, message, value ); // call app callback; - } - - broadcast(message, except) { - for (let i = 0, connections = this.#webserver.connections; i < connections.length; i++) { - const connection = connections[i]; - - if (except === connection) // Don't broadcast to self - continue; - if (connection instanceof WebSocketUpgrade) { - try { - connection.send(message); - } catch { - connections.splice(i, 1); - } - } - } - } - - close() { - this.#webserver.close(); - } - - handler(req, message, value, etc) { - switch (message) { - case HTTPServer.status: - if ( value === this.#path ) { - req.ws = { flags: 0 }; - } - break; - case HTTPServer.header: { - if ( req.ws ) { - if ( value==='connection' && etc === 'Upgrade') { - req.ws.flags |= 1 - } - if ( value==='sec-websocket-version') { - req.ws.flags |= (etc === '13') ? 2 : 0; - } - if ( ( value==='upgrade' && etc === 'websocket' ) ) { - req.ws.flags |= 4 - } - if ( value==='sec-websocket-key') { - req.ws.flags |= 8; - req.ws.key = etc; - this.callback( WebSocketClient.connect ); // tell app we have a new connection - } - if ( value==='sec-websocket-protocol') { - let data = etc.split(","); - for (let i = 0; i < data.length; ++i) - data[i] = data[i].trim().toLowerCase(); - let protocol = this.callback(MiddlewareWebsocket.subprotocol, data); - if (protocol) - req.ws.protocol = protocol; - } - } - break; // continue chaining headers - } - case HTTPServer.prepareResponse: - - if ( req.ws && req.ws.flags === 15 ) { - let sha1 = new Digest("SHA1"); - - sha1.write(req.ws.key); - delete req.ws.key; - delete req.ws.flags; - sha1.write("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); - let accept = Base64.encode(sha1.close()); - let response = { - headers: [ - "Sec-WebSocket-Accept", accept, - "Upgrade","websocket" - ], - status: 101 - }; - if (req.ws.protocol) { - response.headers.push("Sec-WebSocket-Protocol",req.ws.protocol); - } - return response; - } - // ws handshake failure - continue chain - delete req.ws; - break; - case HTTPServer.responseComplete: - if ( req.ws ) { - - const websocket = new WebSocketUpgrade({socket:req.socket}); - websocket.callback=this.callbackHandler.bind(this); - - req.server.connections.push(websocket); - this.callback.call( websocket, WebSocketClient.handshake ); // tell app we have handshake complete - return; - } - break; - } - return this.next?.handler(req, message, value, etc); - } -} - -MiddlewareWebsocket.connect = 1; -MiddlewareWebsocket.handshake = 2; -MiddlewareWebsocket.receive = 3; -MiddlewareWebsocket.disconnect = 4; -MiddlewareWebsocket.subprotocol = 5; -MiddlewareWebsocket.encode = 6; // Websocket encode packet -MiddlewareWebsocket.decode = 7; // Websocket decode packet -Object.freeze(MiddlewareWebsocket.prototype); - -export { MiddlewareWebsocket }; - -/* TO DO - -flags -> use constants ? also http module - -method === 'GET' - -*/ diff --git a/modules/network/websocket/websocket.js b/modules/network/websocket/websocket.js index 0b553eeaf5..aa2c354b2a 100644 --- a/modules/network/websocket/websocket.js +++ b/modules/network/websocket/websocket.js @@ -287,21 +287,32 @@ trace("partial header!!\n"); //@@ untested export class Server { #listener; constructor(dictionary = {}) { - this.#listener = new Listener({port: dictionary.port ?? 80}); - this.#listener.callback = () => { - const socket = new Socket({listener: this.#listener}); - const request = new Client({socket}); - request.doMask = false; - socket.callback = server.bind(request); - request.state = 1; // already connected socket - request.callback = this.callback; // transfer server.callback to request.callback - request.callback(Server.connect, this); // tell app we have a new connection - }; + if (dictionary.socket) { + let socket=dictionary.socket; + this.attach(socket,2); + socket.callback(2, socket.read()); + } + else { + this.#listener = new Listener({port: dictionary.port ?? 80}); + this.#listener.callback = () => { + this.attach(new Socket({listener: this.#listener}),1); + } + } } + close() { this.#listener.close(); this.#listener = undefined; } + + attach(socket,state) { + const request = new Client({socket}); + request.doMask = false; + socket.callback = server.bind(request); + request.state = state; // already connected or handshake + request.callback = this.callback; // transfer server.callback to request.callback + request.callback(Server.connect, this); // tell app we have a new connection; + } }; /* From 283b228d6ddc18b9d13de028155a1215b12d1a1c Mon Sep 17 00:00:00 2001 From: wilberforce Date: Tue, 8 Feb 2022 16:25:52 +1300 Subject: [PATCH 03/14] fix Server websocket error - however can't preload bridge/websocket --- examples/network/http/httpbridge/main.js | 3 ++- modules/network/http/bridge/manifest.json | 18 +++++++++--------- modules/network/http/bridge/websocket.js | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/network/http/httpbridge/main.js b/examples/network/http/httpbridge/main.js index 40f62a7b29..34fb45a40b 100644 --- a/examples/network/http/httpbridge/main.js +++ b/examples/network/http/httpbridge/main.js @@ -7,6 +7,7 @@ const http = new HTTPServer({ }); let ws = new BridgeWebsocket('/api'); +//let ws = new WebSocketServer('/api'); http.use( ws ); http.use( new BridgeHttpZip('site.zip')); @@ -30,7 +31,7 @@ let observer = { const model = new Proxy(_model, observer); -ws.callback =function cb(websock,message, value) { +ws.callback = function cb(websock,message, value) { switch (message) { case BridgeWebsocket.connect: break; diff --git a/modules/network/http/bridge/manifest.json b/modules/network/http/bridge/manifest.json index 3f821cd6f3..a57aa7423c 100644 --- a/modules/network/http/bridge/manifest.json +++ b/modules/network/http/bridge/manifest.json @@ -1,15 +1,16 @@ { + "include": [ + "$(MODDABLE)/examples/manifest_base.json", + "$(MODDABLE)/examples/manifest_net.json", + "$(MODULES)/network/http/manifest.json", + "$(MODULES)/network/websocket/manifest.json", + "$(MODULES)/files/zip/manifest.json" + ], "modules": { "*": [ - "$(MODULES)/files/resource/*", - "$(MODULES)/files/zip/*", - "$(MODULES)/network/http/*", - "$(MODULES)/data/base64/*", - "$(MODULES)/data/hex/*", - "$(MODULES)/data/logical/*" + "$(MODULES)/files/resource/*" ], - "dns/server": "$(MODULES)/network/dns/dnsserver", - "websocket/websocket": "$(MODULES)/network/websocket/websocket" + "dns/server": "$(MODULES)/network/dns/dnsserver" }, "preload": [ "http", @@ -18,7 +19,6 @@ "base64", "hex", "logical", - "websocket", "resource", "zip" ] diff --git a/modules/network/http/bridge/websocket.js b/modules/network/http/bridge/websocket.js index bf06dff433..672c9d1628 100644 --- a/modules/network/http/bridge/websocket.js +++ b/modules/network/http/bridge/websocket.js @@ -13,8 +13,8 @@ * */ -import { Bridge, HTTPServer } from "bridge/webserver"; -import {Server as WebSocketServer} from "websocket" +import {Bridge, HTTPServer} from "bridge/webserver"; +import {Server as WebSocketServer} from "websocket"; class WebSocketUpgrade extends WebSocketServer { static connections = []; From e0e909db13f4aec3c7c291ceee42f292a286afd3 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Wed, 9 Feb 2022 13:37:25 +1300 Subject: [PATCH 04/14] Add App with methods mapping ws actions --- examples/network/http/httpbridge/main.js | 110 +++++++++--------- .../network/http/httpbridge/manifest.json | 3 +- 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/examples/network/http/httpbridge/main.js b/examples/network/http/httpbridge/main.js index 34fb45a40b..cef6aeda51 100644 --- a/examples/network/http/httpbridge/main.js +++ b/examples/network/http/httpbridge/main.js @@ -1,71 +1,71 @@ import { WebServer as HTTPServer } from "bridge/webserver"; import { BridgeWebsocket } from "bridge/websocket"; -import { BridgeHttpZip } from "bridge/httpzip"; +import { BridgeHttpZip } from "bridge/httpzip"; const http = new HTTPServer({ - port: 80 + port: 80, }); -let ws = new BridgeWebsocket('/api'); -//let ws = new WebSocketServer('/api'); -http.use( ws ); -http.use( new BridgeHttpZip('site.zip')); +let ws = new BridgeWebsocket("/api"); +http.use(ws); +http.use(new BridgeHttpZip("site.zip")); import { _model } from "model"; let observer = { - set: function (obj, prop, value) { - trace(`set prop: ${prop} ${ JSON.stringify(value)}\n`); - obj[prop] = value; - return true; - } - }; + set: function (obj, prop, value) { + trace(`set prop: ${prop} ${JSON.stringify(value)}\n`); + obj[prop] = value; + return true; + }, +}; - /* - class thingy { - minus() { - trace(`minus\n`); - } +class App { + minus(value) { + model.satisfaction = Math.max(0, model.satisfaction - 1); + return model; } - */ - -const model = new Proxy(_model, observer); + plus(value) { + model.satisfaction = Math.min(10, model.satisfaction + 1); + return model; + } + shutdown() { + ws.close(); + http.close(); + } + language() { + model.language = value.language; + return model; + } +} -ws.callback = function cb(websock,message, value) { - switch (message) { - case BridgeWebsocket.connect: - break; +const app = new App(); - case BridgeWebsocket.handshake: - websock.broadcast( model ) - break; +let model = { ..._model }; - case BridgeWebsocket.receive: - try { - trace(`Main WebSocket receive: ${value}\n`); - value = JSON.parse(value); - - if ( value?.action === 'shutdown'){ - ws.close(); - http.close(); - } - if ( value?.action === 'minus'){ - model.satisfaction=Math.max(0,model.satisfaction-1) - value=model; - model.minus(); - } - if ( value?.action === 'plus'){ - model.satisfaction=Math.min(10,model.satisfaction+1) - value=model; - } - if ( value.hasOwnProperty('language') ) { - model.language = value.language; - } +ws.callback = function cb(websock, message, value) { + switch (message) { + case BridgeWebsocket.connect: + break; - websock.broadcast(value); - } - catch (e) { - trace(`WebSocket parse received data error: ${e}\n`); - } - } -} + case BridgeWebsocket.handshake: + websock.broadcast(model); + break; + + case BridgeWebsocket.receive: + try { + trace(`Main WebSocket receive: ${value}\n`); + value = JSON.parse(value); + + let action = value?.action; + + if (typeof app[action] === "function") { + value = app[action](value); + } + + websock.broadcast(value); + } catch (e) { + trace(`WebSocket parse received data error: ${e}\n`); + } + } +}; diff --git a/examples/network/http/httpbridge/manifest.json b/examples/network/http/httpbridge/manifest.json index 869e33005a..418a346d0c 100644 --- a/examples/network/http/httpbridge/manifest.json +++ b/examples/network/http/httpbridge/manifest.json @@ -3,7 +3,6 @@ "$(MODDABLE)/examples/manifest_base.json", "$(MODDABLE)/examples/manifest_net.json", "$(MODULES)/network/http/manifest.json", - "$(MODULES)/network/http/bridge/manifest.json", "$(MODULES)/network/websocket/manifest.json", "$(MODULES)/files/zip/manifest.json" ], @@ -15,6 +14,8 @@ "model":"site/public/model" }, "preload": [ + "model", + "bridge/httpzip" ], "data": { "*": "./site/dist/site" From 308172599cae315a338924a18a732531500d94ba Mon Sep 17 00:00:00 2001 From: wilberforce Date: Thu, 10 Feb 2022 14:47:34 +1300 Subject: [PATCH 05/14] Add save/restore model to preferences --- examples/network/http/httpbridge/main.js | 33 ++++++++++++------ .../network/http/httpbridge/manifest.json | 3 +- .../http/httpbridge/site/dist/site.zip | Bin 3479 -> 3478 bytes .../http/httpbridge/site/public/index.html | 3 ++ 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/examples/network/http/httpbridge/main.js b/examples/network/http/httpbridge/main.js index cef6aeda51..8bc58f849c 100644 --- a/examples/network/http/httpbridge/main.js +++ b/examples/network/http/httpbridge/main.js @@ -1,6 +1,7 @@ import { WebServer as HTTPServer } from "bridge/webserver"; import { BridgeWebsocket } from "bridge/websocket"; import { BridgeHttpZip } from "bridge/httpzip"; +import Preference from "preference"; const http = new HTTPServer({ port: 80, @@ -12,13 +13,7 @@ http.use(new BridgeHttpZip("site.zip")); import { _model } from "model"; -let observer = { - set: function (obj, prop, value) { - trace(`set prop: ${prop} ${JSON.stringify(value)}\n`); - obj[prop] = value; - return true; - }, -}; +const preference_domain = "bridge"; class App { minus(value) { @@ -37,11 +32,28 @@ class App { model.language = value.language; return model; } -} + restore() { + let keys = Preference.keys(preference_domain); + trace(`${keys}`); + for (let key of keys) { + trace(`${key}: ${Preference.get(preference_domain, key)}\n`); -const app = new App(); + let pref_settings = Preference.get(preference_domain, key); + if (pref_settings) { + Object.assign(model, JSON.parse(pref_settings)); + } + trace(`restore ${key}: `, pref_settings, "\n"); + } + return model; + } + save() { + Preference.set(preference_domain, "settings", JSON.stringify(model)); + } +} let model = { ..._model }; +const app = new App(); +app.restore() ws.callback = function cb(websock, message, value) { switch (message) { @@ -62,8 +74,7 @@ ws.callback = function cb(websock, message, value) { if (typeof app[action] === "function") { value = app[action](value); } - - websock.broadcast(value); + if (value) websock.broadcast(value); } catch (e) { trace(`WebSocket parse received data error: ${e}\n`); } diff --git a/examples/network/http/httpbridge/manifest.json b/examples/network/http/httpbridge/manifest.json index 418a346d0c..4783444644 100644 --- a/examples/network/http/httpbridge/manifest.json +++ b/examples/network/http/httpbridge/manifest.json @@ -4,7 +4,8 @@ "$(MODDABLE)/examples/manifest_net.json", "$(MODULES)/network/http/manifest.json", "$(MODULES)/network/websocket/manifest.json", - "$(MODULES)/files/zip/manifest.json" + "$(MODULES)/files/zip/manifest.json", + "$(MODULES)/files/preference/manifest.json" ], "modules": { "*": [ diff --git a/examples/network/http/httpbridge/site/dist/site.zip b/examples/network/http/httpbridge/site/dist/site.zip index 9f184249ea5941c45a4c1e43defc4556980f4cf2..b621025eaf4f2cb28023b6502a528fc4dd966d12 100644 GIT binary patch delta 1738 zcmY+Fc{tPy7sr3hFf?PyHns?nrKUp4GNdforXfp`FqRpE$k;NI-yLhx2xEz|CTSrC zm5IrfV$y336Q#0ma~a)ht=zfq^Stldd(L^D^T#>QdCni-b3O%1`AQ(x0Roi)01*)& zEx_J&nYn^IEeHT-g#cg|006<40s`Yugs8C48doRM;9*JhE5wc^AyvA`$#IP8yFJiO z)qxl!Lpo`#^=<)d4K`+L+0zp<5nxaLisRO{ay;N${aFp9Cx44_L#pQcX;gS&%T$CZX(Wj)x#FMP8&P-6NFSBp^Kc za6K^U7)Gg6Vbjz#_mZCO$ds&jMF+tZbqm`rnw5Rf632S(+iGn{m+!bg&dn{Y&M1pYjM7+0t-Gc>`~E@u0B=G|#8V1KG4CG^wKC+@IX)Su z5PyGv(mKTHB3J(?Co;&X@Z0g={h{@Hh&?ByK=SkNK&~%M(o0hJbe{a02yHyGq+{RP ziA-T|e6TFvQwpI%Ux<$lCBSP#*B&cYC(jHXo|aJ_{TR}ZM)*2tm@I(62m0(gR|>jo zr#ye%?9ovJ*+)rB&1!PjZM#HV>u5$%H`_#Ch#E}2?kzOJ&+!Ujgc>aRQ`KC++cPl_ zC?4hA^i_Z6S-58JW+H~uznN(PtD>uPkAcSdXgE#bW$@*UMOIKOv#Oh3!fVKmSK_ua zqJ<&rOsyU%7ySIsbbpq}(|PZE&q_FUMFQk1>7?P{Qw0>MnS*FqykFVg9kiO?dD?*s znZYuR7tma1P7{=g!i;##cdkZCfA;!JS`qi9&+F_RZijCnf6<&^fPDjA&|Lt+S0B9% zA`9x{j_Mox8wR0>q&&B<@G&FFpL)8^Q1uG^Pj%cR??6rlwT5t8P^C_Si#_uGgL`+D z%9ZOxk?+f<_PI+fONojpRa!ND@qPp+cMA&2ux2y zbDUD^D_2Vdvi9#*{^la6JRr5M$$X_v%3c-LbmmU1lvEfPh)y~%@NC63P`((9KR){C z-Q@KRZ}kXj4BD53qL9&Vra%91rR+c~@EJFXCkG#vQTC<2$-4Y=W08BC5kI}2<8EU} zVXH^SD8Rw!-K9PWhP~un#=^>bUEf?j!b~}lS?{t zPLsjp{&*bG_B_-b0f_aNYhz7_n6KUD2Y$dLZN~eAll!%;E6m(&Zzp%Y3|}d1T?;$O zboP-_wGlgVS3AhP;sK~4Um0uc`$HfQlVM+e0rJ8bl3m8JE7p#igraS;5c;nyJ&d~p27o>JNT z(S8vTs9z2n>mVR#0r?+wg4k{B4JZKABLILO|FuS{KYypN@PGh6TxcMQ6obb)2nqjF PhVp%zpHukld|dqvS?~z7 delta 1732 zcmZ8iX*Ao39{o2|5{-_1Yub!dt+lpQZDXI>jR<9U7(9(7I+h9&)mW;ORIIg(HMT0j zG`7}WOGr>MZDU9_1*LP@H_Y1b3fgC?!9BV$4F=!0YOOsfIxuo z6f-BiBkz24K>)xN1OQD~Kr}Tu<~YCX_lKdHzQ4vY+D~ zE|(c8^%$jYRB?&o#mm;*@Qp?_yzd@5%f3!9!f|zL(E0O`IR-uT8pGw9p#*0cz9eR6 z!VuLmXguzjd*7A3c6tUu7(v9-+!*)|iN|SYd=?%nd3%+k+SWDJBBu89&W%#rJZER> zgHs0RTPV;)bTWf`r|4G0%i!@TwZbo;Trsm(F1PF42jbG3XV#_W9~RNuUALPOjQ3|X8Vs!|^}4?Yc5*#$X<<)= z?ZQYhh9n+JP-n84XP5xH`C}g2kJP)xUj7j4F><$l!vDC%#AhscBYmNT$ChswaIDyr zDte2*YJCD>OG-bsUc4;lfVlk^w6va9;?+ICf{N<<%|P7xSz;Q=&vylor`te$jDic#CTwB;o=5r_#DP4?9Z&8Wc zfX;`7;2`KZu-Kp(IIMQ}3eNx2GpkqEBTyuzu`!ZL-$prT&3%)-{7wkQ6nRa(9kq+! zw;&i%Iy6@=;JB9R)5R!?$EqKJTK%;r zsVZ>UK1gi>5>T%U;?Enwma47%u?^jE=FXCBb8#MivNmdKP~fs#yqTwUY>e9*LyJ}f ziQ`_?VWR$QTEBy_WNZ6mJ#*2uG36`5oFSjaQ;TX!ZWT7MY+{{YC4L;;5@r0lTe$xb zQ%H9m9NXBSmELHANUeT4_<0B2bcYTT@B*P$(+R{vJ z1s}Ys*ln6=C;O@|^4D(XswWOHG%Z__3hXZ5SMEk1KN8ric|w=Nh>opQ*0a9XA{do$+k@Z!)w-YlC5 zVg(>`JwzOaR2(iWd#|Nxj!=?7$ej`sdAwN{G@*m%v@|ZkLx}Ca+aJA&@u!klU!ImM zy14M8sf^@QIp+YRYAr9f*o4R!2vR&dwi0W*$ZJ33eG&h>pxS>~I>5^|XS=iR#uN6o zm1?=W_>(xb9h(Xd5%mnC8`jy?2uZ?Lz{bW(kl>bdkHsCiiNicA;~{acsbaEo^AMgZeQ84F!?nc{y zA!`^D-*5o{r~w1O_XQ3CD;z%h{Z3?+z|;f|sH_p#F_0T5iwycP3n~J0|EBt1qi;3; w4_e~r2loG0dbG_UkdeTDrwBXTGyLgbhW>y7z`^~Era0fMP!WXC?SGU10$p<_ZU6uP diff --git a/examples/network/http/httpbridge/site/public/index.html b/examples/network/http/httpbridge/site/public/index.html index 36f7292993..30f22346cb 100644 --- a/examples/network/http/httpbridge/site/public/index.html +++ b/examples/network/http/httpbridge/site/public/index.html @@ -32,6 +32,9 @@

Debug Log

+ + + From 7fec23a2f4e69faec7350493ae756903855e7cd0 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Thu, 10 Feb 2022 14:52:50 +1300 Subject: [PATCH 06/14] remove unused npm modules --- examples/network/http/httpbridge/site/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/network/http/httpbridge/site/package.json b/examples/network/http/httpbridge/site/package.json index 50c9ce3868..05a9af63c7 100644 --- a/examples/network/http/httpbridge/site/package.json +++ b/examples/network/http/httpbridge/site/package.json @@ -12,9 +12,7 @@ "author": "", "license": "MIT", "dependencies": { - "alpinejs": "^3.8.1", "rollup-plugin-zip": "^1.0.3", - "vite-plugin-singlefile": "^0.6.3", "wmr": "^3.7.2" }, "devDependencies": { From 4c4cd85ceecd0ef502e345067ddf96bb4418d964 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Thu, 10 Feb 2022 15:16:54 +1300 Subject: [PATCH 07/14] move #model to app --- examples/network/http/httpbridge/main.js | 74 +++++++++++++------ .../http/httpbridge/site/wmr.config.mjs | 6 -- modules/network/http/bridge/webserver.js | 1 + 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/examples/network/http/httpbridge/main.js b/examples/network/http/httpbridge/main.js index 8bc58f849c..2403dbb76b 100644 --- a/examples/network/http/httpbridge/main.js +++ b/examples/network/http/httpbridge/main.js @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) Wilberforce + * + * This file is part of the Moddable SDK. + * + * This work is licensed under the + * Creative Commons Attribution 4.0 International License. + * To view a copy of this license, visit + * . + * or send a letter to Creative Commons, PO Box 1866, + * Mountain View, CA 94042, USA. + * + */ import { WebServer as HTTPServer } from "bridge/webserver"; import { BridgeWebsocket } from "bridge/websocket"; import { BridgeHttpZip } from "bridge/httpzip"; @@ -7,53 +21,63 @@ const http = new HTTPServer({ port: 80, }); -let ws = new BridgeWebsocket("/api"); -http.use(ws); +let ws = http.use(new BridgeWebsocket("/api")); http.use(new BridgeHttpZip("site.zip")); -import { _model } from "model"; +class App { + #preference_domain = "bridge"; + #model; -const preference_domain = "bridge"; + constructor(m) { + this.#model = m; + } + + get model() { + return this.#model; + } + + set model(m) { + this.#model = m; + } -class App { minus(value) { - model.satisfaction = Math.max(0, model.satisfaction - 1); - return model; + this.model.satisfaction = Math.max(0, this.model.satisfaction - 1); + return this.model; } plus(value) { - model.satisfaction = Math.min(10, model.satisfaction + 1); - return model; + this.model.satisfaction = Math.min(10, this.model.satisfaction + 1); + return this.model; } shutdown() { ws.close(); http.close(); } language() { - model.language = value.language; - return model; + this.model.language = value.language; + return this.model; } restore() { - let keys = Preference.keys(preference_domain); - trace(`${keys}`); + let keys = Preference.keys(this.#preference_domain); for (let key of keys) { - trace(`${key}: ${Preference.get(preference_domain, key)}\n`); - - let pref_settings = Preference.get(preference_domain, key); + let pref_settings = Preference.get(this.#preference_domain, key); if (pref_settings) { - Object.assign(model, JSON.parse(pref_settings)); + Object.assign(this.model, JSON.parse(pref_settings)); } - trace(`restore ${key}: `, pref_settings, "\n"); } - return model; + return this.model; } save() { - Preference.set(preference_domain, "settings", JSON.stringify(model)); + Preference.set( + this.#preference_domain, + "settings", + JSON.stringify(this.model) + ); } } -let model = { ..._model }; -const app = new App(); -app.restore() +import { _model } from "model"; +const app = new App({ ..._model }); +app.restore(); ws.callback = function cb(websock, message, value) { switch (message) { @@ -61,7 +85,7 @@ ws.callback = function cb(websock, message, value) { break; case BridgeWebsocket.handshake: - websock.broadcast(model); + websock.broadcast(app.model); break; case BridgeWebsocket.receive: @@ -73,6 +97,8 @@ ws.callback = function cb(websock, message, value) { if (typeof app[action] === "function") { value = app[action](value); + } else { + trace("No matching action found\n"); } if (value) websock.broadcast(value); } catch (e) { diff --git a/examples/network/http/httpbridge/site/wmr.config.mjs b/examples/network/http/httpbridge/site/wmr.config.mjs index fc345c34be..8776c89f1e 100644 --- a/examples/network/http/httpbridge/site/wmr.config.mjs +++ b/examples/network/http/httpbridge/site/wmr.config.mjs @@ -1,13 +1,7 @@ -//import htmlMinifier from 'rollup-plugin-html-minifier'; import zip from 'rollup-plugin-zip'; -//import embedCSS from 'rollup-plugin-embed-css'; export function build({ plugins }) { plugins.push( - //embedCSS({/* Options */}), - //htmlMinifier({ - // any options here - //}), zip({file: 'site.zip'}) ); } diff --git a/modules/network/http/bridge/webserver.js b/modules/network/http/bridge/webserver.js index 9e950ee9cd..0945e80c75 100644 --- a/modules/network/http/bridge/webserver.js +++ b/modules/network/http/bridge/webserver.js @@ -50,6 +50,7 @@ class WebServer extends HTTPServer { // Advise Bridge of http parent - needed by websocket for connection management if ( 'parent' in handler ) handler.parent=this; + return handler; } callback(message, value, etc) { From 9ed878ccc531c1709e2ed4f944a97ab24db42c07 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Fri, 11 Feb 2022 11:26:51 +1300 Subject: [PATCH 08/14] fix npm dependancies --- examples/network/http/httpbridge/site/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/network/http/httpbridge/site/package.json b/examples/network/http/httpbridge/site/package.json index 05a9af63c7..561cc84710 100644 --- a/examples/network/http/httpbridge/site/package.json +++ b/examples/network/http/httpbridge/site/package.json @@ -14,8 +14,5 @@ "dependencies": { "rollup-plugin-zip": "^1.0.3", "wmr": "^3.7.2" - }, - "devDependencies": { - "rollup-plugin-embed-css": "^1.0.24" } } From bd7e5880f76f36872afd0d91c289de20678a3195 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Wed, 16 Feb 2022 10:37:18 +1300 Subject: [PATCH 09/14] take attach out of constuctor --- modules/network/http/bridge/websocket.js | 4 +++- modules/network/websocket/websocket.js | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/network/http/bridge/websocket.js b/modules/network/http/bridge/websocket.js index 672c9d1628..3875bd86e6 100644 --- a/modules/network/http/bridge/websocket.js +++ b/modules/network/http/bridge/websocket.js @@ -113,7 +113,9 @@ class BridgeWebsocket extends Bridge { if ( value === this.#path ) { WebSocketUpgrade.bridge=this; const socket = this.parent.detach(req); - const websocket = new WebSocketUpgrade({socket:socket}); + //const websocket = new WebSocketUpgrade({socket:socket}); + const websocket = new WebSocketUpgrade({port:null}); + websocket.attach(socket,2); return; } break; diff --git a/modules/network/websocket/websocket.js b/modules/network/websocket/websocket.js index aa2c354b2a..f3e174ea45 100644 --- a/modules/network/websocket/websocket.js +++ b/modules/network/websocket/websocket.js @@ -29,6 +29,7 @@ import {Socket, Listener} from "socket"; import Base64 from "base64"; import Logical from "logical"; import {Digest} from "crypt"; +import Timer from "timer"; /* state: @@ -287,10 +288,12 @@ trace("partial header!!\n"); //@@ untested export class Server { #listener; constructor(dictionary = {}) { + if (null === dictionary.port) + return; + if (dictionary.socket) { let socket=dictionary.socket; this.attach(socket,2); - socket.callback(2, socket.read()); } else { this.#listener = new Listener({port: dictionary.port ?? 80}); @@ -305,13 +308,17 @@ export class Server { this.#listener = undefined; } - attach(socket,state) { + attach(socket,state = 2) { const request = new Client({socket}); request.doMask = false; socket.callback = server.bind(request); request.state = state; // already connected or handshake request.callback = this.callback; // transfer server.callback to request.callback request.callback(Server.connect, this); // tell app we have a new connection; + + if ( state === 2) { // continue handshake + socket.callback(2, socket.read()); + } } }; From 41fca69919916c3aafe404623b009a9ac7297f90 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Sat, 19 Feb 2022 11:48:08 +1300 Subject: [PATCH 10/14] remove callback out of attach --- modules/network/http/bridge/websocket.js | 16 +++------------- modules/network/websocket/websocket.js | 13 ++++++------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/modules/network/http/bridge/websocket.js b/modules/network/http/bridge/websocket.js index 3875bd86e6..ac07f2df70 100644 --- a/modules/network/http/bridge/websocket.js +++ b/modules/network/http/bridge/websocket.js @@ -23,16 +23,6 @@ class WebSocketUpgrade extends WebSocketServer { super(opts); } - /* - send(packet) { - packet=JSON.stringify(packet); - let encoded = WebSocketUpgrade.bridge?.callback.call( this, BridgeWebsocket.encode, packet); - if (encoded) - packet = encoded; - this.write(packet); - } - */ - callback(message, value) { const ws = this.ws; @@ -113,9 +103,9 @@ class BridgeWebsocket extends Bridge { if ( value === this.#path ) { WebSocketUpgrade.bridge=this; const socket = this.parent.detach(req); - //const websocket = new WebSocketUpgrade({socket:socket}); - const websocket = new WebSocketUpgrade({port:null}); - websocket.attach(socket,2); + const websocket = new WebSocketUpgrade({socket:socket}); + //const websocket = new WebSocketUpgrade({port:null}); + //websocket.attach(socket,2); return; } break; diff --git a/modules/network/websocket/websocket.js b/modules/network/websocket/websocket.js index f3e174ea45..395e7c685d 100644 --- a/modules/network/websocket/websocket.js +++ b/modules/network/websocket/websocket.js @@ -293,12 +293,15 @@ export class Server { if (dictionary.socket) { let socket=dictionary.socket; - this.attach(socket,2); + let request = this.attach(socket,2); + request.callback(Server.connect, this); // tell app we have a new connection; + socket.callback(2, socket.read()); // continue handshake } else { this.#listener = new Listener({port: dictionary.port ?? 80}); this.#listener.callback = () => { this.attach(new Socket({listener: this.#listener}),1); + request.callback(Server.connect, this); // tell app we have a new connection; } } } @@ -308,17 +311,13 @@ export class Server { this.#listener = undefined; } - attach(socket,state = 2) { + attach(socket,state) { const request = new Client({socket}); request.doMask = false; socket.callback = server.bind(request); request.state = state; // already connected or handshake request.callback = this.callback; // transfer server.callback to request.callback - request.callback(Server.connect, this); // tell app we have a new connection; - - if ( state === 2) { // continue handshake - socket.callback(2, socket.read()); - } + return request; } }; From a5b2a360eff90b04f752f6a6649a4b2e7587373a Mon Sep 17 00:00:00 2001 From: wilberforce Date: Sat, 19 Feb 2022 11:54:15 +1300 Subject: [PATCH 11/14] no timer needed --- modules/network/websocket/websocket.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/network/websocket/websocket.js b/modules/network/websocket/websocket.js index 395e7c685d..5dcfef1f6e 100644 --- a/modules/network/websocket/websocket.js +++ b/modules/network/websocket/websocket.js @@ -29,7 +29,6 @@ import {Socket, Listener} from "socket"; import Base64 from "base64"; import Logical from "logical"; import {Digest} from "crypt"; -import Timer from "timer"; /* state: From 74edbb1e00215c0b66ef06c78fda846c8102b32c Mon Sep 17 00:00:00 2001 From: wilberforce Date: Wed, 2 Mar 2022 10:44:12 +1300 Subject: [PATCH 12/14] Move http bridge to contributed --- .../http => contributed}/httpbridge/.gitignore | 0 .../site => contributed/httpbridge}/README.md | 0 .../network/http => contributed}/httpbridge/main.js | 6 +++++- .../http => contributed}/httpbridge/manifest.json | 2 +- .../httpbridge/modules}/hotspot.js | 0 .../httpbridge/modules}/httpzip.js | 0 .../httpbridge/modules}/manifest.json | 0 .../httpbridge/modules}/rewritespa.js | 0 .../httpbridge/modules}/status.js | 0 .../httpbridge/modules}/webserver.js | 0 .../httpbridge/modules}/websocket.js | 0 .../httpbridge/site/dist/site.zip | Bin .../httpbridge/site/package.json | 0 .../httpbridge/site/public/app.ts | 0 .../httpbridge/site/public/index.html | 0 .../httpbridge/site/public/moddable.svg | 0 .../httpbridge/site/public/model.js | 0 .../httpbridge/site/public/stylesheet.css | 0 .../httpbridge/site/wmr.config.mjs | 0 19 files changed, 6 insertions(+), 2 deletions(-) rename {examples/network/http => contributed}/httpbridge/.gitignore (100%) rename {examples/network/http/httpbridge/site => contributed/httpbridge}/README.md (100%) rename {examples/network/http => contributed}/httpbridge/main.js (93%) rename {examples/network/http => contributed}/httpbridge/manifest.json (87%) rename {modules/network/http/bridge => contributed/httpbridge/modules}/hotspot.js (100%) rename {modules/network/http/bridge => contributed/httpbridge/modules}/httpzip.js (100%) rename {modules/network/http/bridge => contributed/httpbridge/modules}/manifest.json (100%) rename {modules/network/http/bridge => contributed/httpbridge/modules}/rewritespa.js (100%) rename {modules/network/http/bridge => contributed/httpbridge/modules}/status.js (100%) rename {modules/network/http/bridge => contributed/httpbridge/modules}/webserver.js (100%) rename {modules/network/http/bridge => contributed/httpbridge/modules}/websocket.js (100%) rename {examples/network/http => contributed}/httpbridge/site/dist/site.zip (100%) rename {examples/network/http => contributed}/httpbridge/site/package.json (100%) rename {examples/network/http => contributed}/httpbridge/site/public/app.ts (100%) rename {examples/network/http => contributed}/httpbridge/site/public/index.html (100%) rename {examples/network/http => contributed}/httpbridge/site/public/moddable.svg (100%) rename {examples/network/http => contributed}/httpbridge/site/public/model.js (100%) rename {examples/network/http => contributed}/httpbridge/site/public/stylesheet.css (100%) rename {examples/network/http => contributed}/httpbridge/site/wmr.config.mjs (100%) diff --git a/examples/network/http/httpbridge/.gitignore b/contributed/httpbridge/.gitignore similarity index 100% rename from examples/network/http/httpbridge/.gitignore rename to contributed/httpbridge/.gitignore diff --git a/examples/network/http/httpbridge/site/README.md b/contributed/httpbridge/README.md similarity index 100% rename from examples/network/http/httpbridge/site/README.md rename to contributed/httpbridge/README.md diff --git a/examples/network/http/httpbridge/main.js b/contributed/httpbridge/main.js similarity index 93% rename from examples/network/http/httpbridge/main.js rename to contributed/httpbridge/main.js index 2403dbb76b..fff1bf7a2e 100644 --- a/examples/network/http/httpbridge/main.js +++ b/contributed/httpbridge/main.js @@ -98,7 +98,11 @@ ws.callback = function cb(websock, message, value) { if (typeof app[action] === "function") { value = app[action](value); } else { - trace("No matching action found\n"); + if (value.hasOwnProperty('language')) { + Object.assign(app.model,value) + } else { + trace("No matching action found\n"); + } } if (value) websock.broadcast(value); } catch (e) { diff --git a/examples/network/http/httpbridge/manifest.json b/contributed/httpbridge/manifest.json similarity index 87% rename from examples/network/http/httpbridge/manifest.json rename to contributed/httpbridge/manifest.json index 4783444644..2b2d93c7b8 100644 --- a/examples/network/http/httpbridge/manifest.json +++ b/contributed/httpbridge/manifest.json @@ -11,7 +11,7 @@ "*": [ "./main" ], - "bridge/*": "$(MODULES)/network/http/bridge/*", + "bridge/*": "$(MODDABLE)/contributed/httpbridge/modules/*", "model":"site/public/model" }, "preload": [ diff --git a/modules/network/http/bridge/hotspot.js b/contributed/httpbridge/modules/hotspot.js similarity index 100% rename from modules/network/http/bridge/hotspot.js rename to contributed/httpbridge/modules/hotspot.js diff --git a/modules/network/http/bridge/httpzip.js b/contributed/httpbridge/modules/httpzip.js similarity index 100% rename from modules/network/http/bridge/httpzip.js rename to contributed/httpbridge/modules/httpzip.js diff --git a/modules/network/http/bridge/manifest.json b/contributed/httpbridge/modules/manifest.json similarity index 100% rename from modules/network/http/bridge/manifest.json rename to contributed/httpbridge/modules/manifest.json diff --git a/modules/network/http/bridge/rewritespa.js b/contributed/httpbridge/modules/rewritespa.js similarity index 100% rename from modules/network/http/bridge/rewritespa.js rename to contributed/httpbridge/modules/rewritespa.js diff --git a/modules/network/http/bridge/status.js b/contributed/httpbridge/modules/status.js similarity index 100% rename from modules/network/http/bridge/status.js rename to contributed/httpbridge/modules/status.js diff --git a/modules/network/http/bridge/webserver.js b/contributed/httpbridge/modules/webserver.js similarity index 100% rename from modules/network/http/bridge/webserver.js rename to contributed/httpbridge/modules/webserver.js diff --git a/modules/network/http/bridge/websocket.js b/contributed/httpbridge/modules/websocket.js similarity index 100% rename from modules/network/http/bridge/websocket.js rename to contributed/httpbridge/modules/websocket.js diff --git a/examples/network/http/httpbridge/site/dist/site.zip b/contributed/httpbridge/site/dist/site.zip similarity index 100% rename from examples/network/http/httpbridge/site/dist/site.zip rename to contributed/httpbridge/site/dist/site.zip diff --git a/examples/network/http/httpbridge/site/package.json b/contributed/httpbridge/site/package.json similarity index 100% rename from examples/network/http/httpbridge/site/package.json rename to contributed/httpbridge/site/package.json diff --git a/examples/network/http/httpbridge/site/public/app.ts b/contributed/httpbridge/site/public/app.ts similarity index 100% rename from examples/network/http/httpbridge/site/public/app.ts rename to contributed/httpbridge/site/public/app.ts diff --git a/examples/network/http/httpbridge/site/public/index.html b/contributed/httpbridge/site/public/index.html similarity index 100% rename from examples/network/http/httpbridge/site/public/index.html rename to contributed/httpbridge/site/public/index.html diff --git a/examples/network/http/httpbridge/site/public/moddable.svg b/contributed/httpbridge/site/public/moddable.svg similarity index 100% rename from examples/network/http/httpbridge/site/public/moddable.svg rename to contributed/httpbridge/site/public/moddable.svg diff --git a/examples/network/http/httpbridge/site/public/model.js b/contributed/httpbridge/site/public/model.js similarity index 100% rename from examples/network/http/httpbridge/site/public/model.js rename to contributed/httpbridge/site/public/model.js diff --git a/examples/network/http/httpbridge/site/public/stylesheet.css b/contributed/httpbridge/site/public/stylesheet.css similarity index 100% rename from examples/network/http/httpbridge/site/public/stylesheet.css rename to contributed/httpbridge/site/public/stylesheet.css diff --git a/examples/network/http/httpbridge/site/wmr.config.mjs b/contributed/httpbridge/site/wmr.config.mjs similarity index 100% rename from examples/network/http/httpbridge/site/wmr.config.mjs rename to contributed/httpbridge/site/wmr.config.mjs From 6bec2ad8a986dd7334f87a6397173a21a8de54af Mon Sep 17 00:00:00 2001 From: wilberforce Date: Wed, 9 Mar 2022 14:20:41 +1300 Subject: [PATCH 13/14] change to @phoddies websocket --- contributed/httpbridge/modules/websocket.js | 13 +++-- modules/network/websocket/websocket.js | 56 +++++++++++---------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/contributed/httpbridge/modules/websocket.js b/contributed/httpbridge/modules/websocket.js index ac07f2df70..1f5594e8e0 100644 --- a/contributed/httpbridge/modules/websocket.js +++ b/contributed/httpbridge/modules/websocket.js @@ -18,10 +18,18 @@ import {Server as WebSocketServer} from "websocket"; class WebSocketUpgrade extends WebSocketServer { static connections = []; + #bridge; constructor(opts) { super(opts); } + set bridge(value) { + this.#bridge = value; + } + + get bridge() { + return this.#bridge; + } callback(message, value) { const ws = this.ws; @@ -103,9 +111,8 @@ class BridgeWebsocket extends Bridge { if ( value === this.#path ) { WebSocketUpgrade.bridge=this; const socket = this.parent.detach(req); - const websocket = new WebSocketUpgrade({socket:socket}); - //const websocket = new WebSocketUpgrade({port:null}); - //websocket.attach(socket,2); + const websocket = new WebSocketUpgrade({port:null}); + websocket.attach(socket); return; } break; diff --git a/modules/network/websocket/websocket.js b/modules/network/websocket/websocket.js index 5dcfef1f6e..8e903ae357 100644 --- a/modules/network/websocket/websocket.js +++ b/modules/network/websocket/websocket.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2021 Moddable Tech, Inc. + * Copyright (c) 2016-2022 Moddable Tech, Inc. * * This file is part of the Moddable SDK Runtime. * @@ -22,13 +22,13 @@ websocket client and server - validate Sec-WebSocket-Accept in client - - messages with characters with ascii values above 255 will fail */ import {Socket, Listener} from "socket"; import Base64 from "base64"; import Logical from "logical"; import {Digest} from "crypt"; +import Timer from "timer"; /* state: @@ -56,6 +56,7 @@ export class Client { this.headers = dictionary.headers ?? []; this.protocol = dictionary.protocol; this.state = 0; + this.flags = 0; if (dictionary.socket) this.socket = dictionary.socket; @@ -104,6 +105,10 @@ export class Client { close() { this.socket?.close(); delete this.socket; + + if (this.timer) + Timer.clear(this.timer); + delete this.timer; } }; @@ -288,38 +293,37 @@ export class Server { #listener; constructor(dictionary = {}) { if (null === dictionary.port) - return; + return; - if (dictionary.socket) { - let socket=dictionary.socket; - let request = this.attach(socket,2); - request.callback(Server.connect, this); // tell app we have a new connection; - socket.callback(2, socket.read()); // continue handshake - } - else { - this.#listener = new Listener({port: dictionary.port ?? 80}); - this.#listener.callback = () => { - this.attach(new Socket({listener: this.#listener}),1); - request.callback(Server.connect, this); // tell app we have a new connection; - } - } + this.#listener = new Listener({port: dictionary.port ?? 80}); + this.#listener.callback = () => { + const request = addClient(new Socket({listener: this.#listener}), 1, this.callback); + request.callback(Server.connect, this); // tell app we have a new connection + }; } - close() { - this.#listener.close(); + this.#listener?.close(); this.#listener = undefined; } - - attach(socket,state) { - const request = new Client({socket}); - request.doMask = false; - socket.callback = server.bind(request); - request.state = state; // already connected or handshake - request.callback = this.callback; // transfer server.callback to request.callback - return request; + attach(socket) { + const request = addClient(socket, 2, this.callback); + request.timer = Timer.set(() => { + delete request.timer; + request.callback(Server.connect, this); // tell app we have a new connection + socket.callback(2, socket.read()); + }); } }; +function addClient(socket, state, callback) { + const request = new Client({socket}); + delete request.doMask; + socket.callback = server.bind(request); + request.state = state; + request.callback = callback; // transfer server.callback to request.callback + return request; +} + /* callback for server handshake. after that, switches to client callback */ From 939ba3d9ece30c0f08b79493591322e0dc7a8c16 Mon Sep 17 00:00:00 2001 From: wilberforce Date: Thu, 10 Mar 2022 14:02:01 +1300 Subject: [PATCH 14/14] update ws --- modules/network/websocket/websocket.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/network/websocket/websocket.js b/modules/network/websocket/websocket.js index 8e903ae357..808f2e64c9 100644 --- a/modules/network/websocket/websocket.js +++ b/modules/network/websocket/websocket.js @@ -22,6 +22,7 @@ websocket client and server - validate Sec-WebSocket-Accept in client + - messages with characters with ascii values above 255 will fail */ import {Socket, Listener} from "socket";