From e62042326e4f85176fdf532f42b9f40ef6a8fbf3 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 30 Aug 2021 21:31:03 -0700 Subject: [PATCH] websocket cleanups --- sdk/bin/scrypted-package-json.js | 2 + sdk/webpack.nodejs.config.js | 4 - server/src/plugin/plugin-host.ts | 5 +- server/src/plugin/plugin-remote-websocket.ts | 159 +++++++++++++++++++ server/src/plugin/plugin-remote.ts | 69 ++++---- 5 files changed, 199 insertions(+), 40 deletions(-) create mode 100644 server/src/plugin/plugin-remote-websocket.ts diff --git a/sdk/bin/scrypted-package-json.js b/sdk/bin/scrypted-package-json.js index 14e3a11cd..f10d62c89 100755 --- a/sdk/bin/scrypted-package-json.js +++ b/sdk/bin/scrypted-package-json.js @@ -3,6 +3,8 @@ const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); pkg.scripts = { + // alias + "build": "scrypted-webpack", "prepublishOnly": "NODE_ENV=production scrypted-webpack", "prescrypted-vscode-launch": "scrypted-webpack", "scrypted-vscode-launch": "scrypted-deploy-debug", diff --git a/sdk/webpack.nodejs.config.js b/sdk/webpack.nodejs.config.js index 5ee97eb75..d36121063 100644 --- a/sdk/webpack.nodejs.config.js +++ b/sdk/webpack.nodejs.config.js @@ -79,7 +79,6 @@ module.exports = { Long: "long", // browser provide plugin polyfills - _websocket: path.resolve(__dirname, 'polyfill/websocket.js'), wrtc: path.resolve(__dirname, 'polyfill/nodejs/wrtc'), mdns: path.resolve(__dirname, 'polyfill/nodejs/mdns'), serialport: path.resolve(__dirname, 'polyfill/nodejs/serialport'), @@ -97,9 +96,6 @@ module.exports = { new webpack.DefinePlugin({ 'process.env.SSDP_COV': false, }), - new webpack.ProvidePlugin({ - WebSocket: '_websocket' - }), ], optimization: { diff --git a/server/src/plugin/plugin-host.ts b/server/src/plugin/plugin-host.ts index 79f682780..9f7961343 100644 --- a/server/src/plugin/plugin-host.ts +++ b/server/src/plugin/plugin-host.ts @@ -379,8 +379,11 @@ async function createREPLServer(events: EventEmitter): Promise { device }); delete ctx.console; + delete ctx.window; - const welcome = `JavaScript REPL variables:\n${Object.keys(ctx).map(key => ' ' + key).join('\n')}\n\n`; + const replVariables = Object.keys(ctx).filter(key => key !== 'require'); + + const welcome = `JavaScript REPL variables:\n${replVariables.map(key => ' ' + key).join('\n')}\n\n`; socket.write(welcome); const r = repl.start({ diff --git a/server/src/plugin/plugin-remote-websocket.ts b/server/src/plugin/plugin-remote-websocket.ts new file mode 100644 index 000000000..22e532497 --- /dev/null +++ b/server/src/plugin/plugin-remote-websocket.ts @@ -0,0 +1,159 @@ +interface WebSocketEvent { + type: string; + reason?: string; + message?: string; + data?: string|ArrayBufferLike; + source?: any; +} + +interface WebSocketEventListener { + (evt: WebSocketEvent): void; +} + +// @ts-ignore +class WebSocketEventTarget { + events: { [type: string]: WebSocketEventListener[]} = {}; + + dispatchEvent(event: WebSocketEvent) { + const list = this.events[event.type]; + if (!list) { + return; + } + for (const l of list) { + l(event); + } + } + addEventListener(type: string, f: WebSocketEventListener) { + let list = this.events[type]; + if (!list) { + list = this.events[type] = []; + } + list.push(f); + } + removeEventListener(type: string, f: WebSocketEventListener) { + const list = this.events[type]; + if (!list) { + return; + } + const index = list.indexOf(f); + if (index > -1) { + list.splice(index, 1); + } + } +} + +function defineEventAttribute(p: any, type: string) { + Object.defineProperty(p, 'on' + type, { + get: function () { + throw new Error(`${type} is write only`); + }, + set: function (f) { + this.events[type] = [f]; + } + }); +} + +interface WebSocketEndCallback { + (): void; +} + +interface WebSocketErrorCallback { + (e: Error): void; +} + +interface WebSocketDataCallback { + (data: string | ArrayBufferLike): void; +} + +interface WebSocketSend { + (message: string|ArrayBufferLike): void; +} + +interface WebSocketConnectCallback { + (e: Error, ws: any, send: WebSocketSend): void; +} + +interface WebSocketConnect { + (url: string, protocols: string[], + connect: WebSocketConnectCallback, + end: WebSocketEndCallback, + error: WebSocketErrorCallback, + data: WebSocketDataCallback): void; +} + +export function createWebSocketClass(__websocketConnect: WebSocketConnect) { + + // @ts-ignore + class WebSocket extends WebSocketEventTarget { + _url: string; + _protocols: string[]; + readyState: number; + send: (message: string|ArrayBufferLike) => void; + _ws: any; + + constructor(url: string, protocols?: string[]) { + super(); + this._url = url; + this._protocols = protocols; + this.readyState = 0; + + __websocketConnect(url, protocols, (e, ws, send) => { + // connect + if (e != null) { + this.dispatchEvent({ + type: 'error', + message: e.toString(), + }); + return; + } + + this._ws = ws; + this.send = send; + this.readyState = 1; + this.dispatchEvent({ + type: 'open', + }); + }, () => { + // end + this.readyState = 3; + this.dispatchEvent({ + type: 'close', + reason: 'closed', + }); + }, (e: Error) => { + // error + this.readyState = 3; + this.dispatchEvent({ + type: 'error', + message: e.toString(), + }); + }, (data: string | ArrayBufferLike) => { + // data + this.dispatchEvent({ + type: 'message', + data: data, + source: this, + }); + }); + } + + get url() { + return this._url; + } + + get extensions() { + return ""; + } + + close() { + this._ws.close(); + } + } + + defineEventAttribute(WebSocket.prototype, "close"); + defineEventAttribute(WebSocket.prototype, "error"); + defineEventAttribute(WebSocket.prototype, "message"); + defineEventAttribute(WebSocket.prototype, "open"); + + return WebSocket; +} diff --git a/server/src/plugin/plugin-remote.ts b/server/src/plugin/plugin-remote.ts index 19c4a3337..a78983319 100644 --- a/server/src/plugin/plugin-remote.ts +++ b/server/src/plugin/plugin-remote.ts @@ -10,6 +10,7 @@ import { BufferSerializer } from './buffer-serializer'; import { Console } from 'console'; import { EventEmitter, PassThrough } from 'stream'; import { Writable } from 'node:stream'; +import { createWebSocketClass } from './plugin-remote-websocket'; class DeviceLogger implements Logger { nativeId: string; @@ -405,42 +406,39 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp volume.writeFileSync(name, entry.getData()); } + function websocketConnect(url: string, protocols: any, connect: any, end: any, error: any, data: any) { + if (url.startsWith('io://')) { + const id = url.substring('io://'.length); + + ioSockets[id] = { + data, + error, + end + }; + + connect(undefined, { + close: () => api.ioClose(id), + }, (message: string) => api.ioSend(id, message)); + } + else if (url.startsWith('ws://')) { + const id = url.substring('ws://'.length); + + ioSockets[id] = { + data, + error, + end + }; + + connect(undefined, { + close: () => api.ioClose(id), + }, (message: string) => api.ioSend(id, message)); + } + else { + throw new Error('unsupported websocket'); + } + } + const params: any = { - // legacy - android: {}, - - __websocketConnect(url: string, protocols: any, connect: any, end: any, error: any, data: any) { - if (url.startsWith('io://')) { - const id = url.substring('io://'.length); - - ioSockets[id] = { - data, - error, - end - }; - - connect(undefined, { - close: () => api.ioClose(id), - }, (message: string) => api.ioSend(id, message)); - } - else if (url.startsWith('ws://')) { - const id = url.substring('ws://'.length); - - ioSockets[id] = { - data, - error, - end - }; - - connect(undefined, { - close: () => api.ioClose(id), - }, (message: string) => api.ioSend(id, message)); - } - else { - throw new Error('unsupported websocket'); - } - }, - window, require: (name: string) => { if (name === 'fs' && !packageJson.scrypted.realfs) { @@ -459,6 +457,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp log, localStorage, pluginHostAPI: api, + WebSocket: createWebSocketClass(websocketConnect), }; if (getDeviceConsole) {