From 7bd77759bd3e76696f1c636167c219524bef407b Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 23 May 2023 08:39:21 +0200 Subject: [PATCH 1/5] docs: update the list of CVE --- SECURITY.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 995eb0769..366bc63e1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,5 +19,7 @@ We will get back to you as soon as possible and publish a fix if necessary. ## History -- Jan 2022: [Uncaught exception in engine.io](https://github.com/socketio/engine.io/security/advisories/GHSA-273r-mgr4-v34f) (CVE-2022-21676) -- Nov 2022: [Uncaught exception in engine.io](https://github.com/socketio/engine.io/security/advisories/GHSA-r7qp-cfhv-p84w) (CVE-2022-41940) +- Feb 2020: [Resource exhaustion in engine.io](https://github.com/advisories/GHSA-j4f2-536g-r55m) (CVE-2020-36048) +- Jan 2022: [Uncaught exception in engine.io](https://github.com/advisories/GHSA-273r-mgr4-v34f) (CVE-2022-21676) +- Nov 2022: [Uncaught exception in engine.io](https://github.com/advisories/GHSA-r7qp-cfhv-p84w) (CVE-2022-41940) +- May 2023: [Uncaught exception in engine.io](https://github.com/advisories/GHSA-q9mw-68c2-j6m5) (CVE-2023-31125) From 3144d274584ae3b96cca4e609c66c56d534f1715 Mon Sep 17 00:00:00 2001 From: Sean Oxley <13091705+OxleyS@users.noreply.github.com> Date: Wed, 31 May 2023 21:27:40 +0900 Subject: [PATCH 2/5] fix(uws): discard any write to an aborted uWS response (#682) This bug only exists for polling transport connections running on top of uWS. If the remote client abruptly disconnects (thus aborting the request) while the server is waiting on an asynchronous operation such as compression, the server may attempt to write a response via the aborted response object. This causes an uncaught exception to be thrown. --- lib/userver.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/userver.ts b/lib/userver.ts index 951d3b966..654f037d7 100644 --- a/lib/userver.ts +++ b/lib/userver.ts @@ -258,6 +258,7 @@ export class uServer extends BaseServer { class ResponseWrapper { private statusWritten: boolean = false; private headers = []; + private isAborted = false; constructor(readonly res: HttpResponse) {} @@ -291,6 +292,8 @@ class ResponseWrapper { public getHeader() {} public writeStatus(status: string) { + if (this.isAborted) return; + this.res.writeStatus(status); this.statusWritten = true; this.writeBufferedHeaders(); @@ -298,6 +301,8 @@ class ResponseWrapper { } public writeHeader(key: string, value: string) { + if (this.isAborted) return; + if (key === "Content-Length") { // the content length is automatically added by uWebSockets.js return; @@ -316,6 +321,8 @@ class ResponseWrapper { } public end(data) { + if (this.isAborted) return; + if (!this.statusWritten) { // status will be inferred as "200 OK" this.writeBufferedHeaders(); @@ -324,10 +331,18 @@ class ResponseWrapper { } public onData(fn) { + if (this.isAborted) return; + this.res.onData(fn); } public onAborted(fn) { - this.res.onAborted(fn); + if (this.isAborted) return; + + this.res.onAborted(() => { + // Any attempt to use the UWS response object after abort will throw! + this.isAborted = true; + fn(); + }); } } From 123b68c04f9e971f59b526e0f967a488ee6b0116 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Sun, 11 Jun 2023 09:42:45 +0200 Subject: [PATCH 3/5] feat: add support for WebTransport Reference: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport --- .github/workflows/ci.yml | 1 - lib/server.ts | 108 ++++- lib/socket.ts | 11 +- lib/transport.ts | 18 + lib/transports/index.ts | 4 +- lib/transports/webtransport.ts | 88 ++++ package-lock.json | 809 ++++++++++++++++++++++++++++++++- package.json | 8 +- test/server.js | 1 + test/util.mjs | 404 ++++++++++++++++ test/webtransport.mjs | 436 ++++++++++++++++++ 11 files changed, 1853 insertions(+), 35 deletions(-) create mode 100644 lib/transports/webtransport.ts create mode 100644 test/util.mjs create mode 100644 test/webtransport.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62ebbbf17..f0ef66e17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,6 @@ jobs: strategy: matrix: node-version: - - 10 - 18 steps: diff --git a/lib/server.ts b/lib/server.ts index a4a4dcf81..2dba26dbe 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -15,10 +15,12 @@ import type { import type { CookieSerializeOptions } from "cookie"; import type { CorsOptions, CorsOptionsDelegate } from "cors"; import type { Duplex } from "stream"; +import { WebTransport } from "./transports/webtransport"; const debug = debugModule("engine"); const kResponseHeaders = Symbol("responseHeaders"); +const TEXT_DECODER = new TextDecoder(); type Transport = "polling" | "websocket"; @@ -78,7 +80,13 @@ export interface ServerOptions { fn: (err: string | null | undefined, success: boolean) => void ) => void; /** - * the low-level transports that are enabled + * The low-level transports that are enabled. WebTransport is disabled by default and must be manually enabled: + * + * @example + * new Server({ + * transports: ["polling", "websocket", "webtransport"] + * }); + * * @default ["polling", "websocket"] */ transports?: Transport[]; @@ -140,6 +148,17 @@ type Middleware = ( next: (err?: any) => void ) => void; +function parseSessionId(handshake: string) { + if (handshake.startsWith("0{")) { + try { + const parsed = JSON.parse(handshake.substring(1)); + if (typeof parsed.sid === "string") { + return parsed.sid; + } + } catch (e) {} + } +} + export abstract class BaseServer extends EventEmitter { public opts: ServerOptions; @@ -166,7 +185,7 @@ export abstract class BaseServer extends EventEmitter { pingInterval: 25000, upgradeTimeout: 10000, maxHttpBufferSize: 1e6, - transports: Object.keys(transports), + transports: ["polling", "websocket"], // WebTransport is disabled by default allowUpgrades: true, httpCompression: { threshold: 1024, @@ -245,7 +264,11 @@ export abstract class BaseServer extends EventEmitter { protected verify(req, upgrade, fn) { // transport check const transport = req._query.transport; - if (!~this.opts.transports.indexOf(transport)) { + // WebTransport does not go through the verify() method, see the onWebTransportSession() method + if ( + !~this.opts.transports.indexOf(transport) || + transport === "webtransport" + ) { debug('unknown transport "%s"', transport); return fn(Server.errors.UNKNOWN_TRANSPORT, { transport }); } @@ -495,6 +518,85 @@ export abstract class BaseServer extends EventEmitter { return transport; } + public async onWebTransportSession(session: any) { + const timeout = setTimeout(() => { + debug( + "the client failed to establish a bidirectional stream in the given period" + ); + session.close(); + }, this.opts.upgradeTimeout); + + const streamReader = session.incomingBidirectionalStreams.getReader(); + const result = await streamReader.read(); + + if (result.done) { + debug("session is closed"); + return; + } + + const stream = result.value; + const reader = stream.readable.getReader(); + + // reading the first packet of the stream + const { value, done } = await reader.read(); + if (done) { + debug("stream is closed"); + return; + } + + clearTimeout(timeout); + const handshake = TEXT_DECODER.decode(value); + + // handshake is either + // "0" => new session + // '0{"sid":"xxxx"}' => upgrade + if (handshake === "0") { + const transport = new WebTransport(session, stream, reader); + + // note: we cannot use "this.generateId()", because there is no "req" argument + const id = base64id.generateId(); + debug('handshaking client "%s" (WebTransport)', id); + + const socket = new Socket(id, this, transport, null, 4); + + this.clients[id] = socket; + this.clientsCount++; + + socket.once("close", () => { + delete this.clients[id]; + this.clientsCount--; + }); + + this.emit("connection", socket); + return; + } + + const sid = parseSessionId(handshake); + + if (!sid) { + debug("invalid WebTransport handshake"); + return session.close(); + } + + const client = this.clients[sid]; + + if (!client) { + debug("upgrade attempt for closed client"); + session.close(); + } else if (client.upgrading) { + debug("transport has already been trying to upgrade"); + session.close(); + } else if (client.upgraded) { + debug("transport had already been upgraded"); + session.close(); + } else { + debug("upgrading existing transport"); + + const transport = new WebTransport(session, stream, reader); + client.maybeUpgrade(transport); + } + } + protected abstract createTransport(transportName, req); /** diff --git a/lib/socket.ts b/lib/socket.ts index 461953831..7afb96222 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -70,10 +70,15 @@ export class Socket extends EventEmitter { this.protocol = protocol; // Cache IP since it might not be in the req later - if (req.websocket && req.websocket._socket) { - this.remoteAddress = req.websocket._socket.remoteAddress; + if (req) { + if (req.websocket && req.websocket._socket) { + this.remoteAddress = req.websocket._socket.remoteAddress; + } else { + this.remoteAddress = req.connection.remoteAddress; + } } else { - this.remoteAddress = req.connection.remoteAddress; + // TODO there is currently no way to get the IP address of the client when it connects with WebTransport + // see https://github.com/fails-components/webtransport/issues/114 } this.checkIntervalTimer = null; diff --git a/lib/transport.ts b/lib/transport.ts index 70bc9cfa3..4068a31ec 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -136,8 +136,26 @@ export abstract class Transport extends EventEmitter { this.emit("close"); } + /** + * Advertise framing support. + */ abstract get supportsFraming(); + + /** + * The name of the transport. + */ abstract get name(); + + /** + * Sends an array of packets. + * + * @param {Array} packets + * @package + */ abstract send(packets); + + /** + * Closes the transport. + */ abstract doClose(fn?); } diff --git a/lib/transports/index.ts b/lib/transports/index.ts index e70aec82c..5c8449d4b 100644 --- a/lib/transports/index.ts +++ b/lib/transports/index.ts @@ -1,10 +1,12 @@ import { Polling as XHR } from "./polling"; import { JSONP } from "./polling-jsonp"; import { WebSocket } from "./websocket"; +import { WebTransport } from "./webtransport"; export default { polling: polling, websocket: WebSocket, + webtransport: WebTransport, }; /** @@ -21,4 +23,4 @@ function polling(req) { } } -polling.upgradesTo = ["websocket"]; +polling.upgradesTo = ["websocket", "webtransport"]; diff --git a/lib/transports/webtransport.ts b/lib/transports/webtransport.ts new file mode 100644 index 000000000..b79b81645 --- /dev/null +++ b/lib/transports/webtransport.ts @@ -0,0 +1,88 @@ +import { Transport } from "../transport"; +import debugModule from "debug"; + +const debug = debugModule("engine:webtransport"); + +const BINARY_HEADER = Buffer.of(54); + +function shouldIncludeBinaryHeader(packet, encoded) { + // 48 === "0".charCodeAt(0) (OPEN packet type) + // 54 === "6".charCodeAt(0) (NOOP packet type) + return ( + packet.type === "message" && + typeof packet.data !== "string" && + encoded[0] >= 48 && + encoded[0] <= 54 + ); +} + +/** + * Reference: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API + */ +export class WebTransport extends Transport { + private readonly writer; + + constructor(private readonly session, stream, reader) { + super({ _query: { EIO: "4" } }); + this.writer = stream.writable.getWriter(); + (async () => { + let binaryFlag = false; + while (true) { + const { value, done } = await reader.read(); + if (done) { + debug("session is closed"); + break; + } + debug("received chunk: %o", value); + if (!binaryFlag && value.byteLength === 1 && value[0] === 54) { + binaryFlag = true; + continue; + } + this.onPacket( + this.parser.decodePacketFromBinary(value, binaryFlag, "nodebuffer") + ); + binaryFlag = false; + } + })(); + + session.closed.then(() => this.onClose()); + + this.writable = true; + } + + get name() { + return "webtransport"; + } + + get supportsFraming() { + return true; + } + + send(packets) { + this.writable = false; + + for (let i = 0; i < packets.length; i++) { + const packet = packets[i]; + const isLast = i + 1 === packets.length; + + this.parser.encodePacketToBinary(packet, (data) => { + if (shouldIncludeBinaryHeader(packet, data)) { + debug("writing binary header"); + this.writer.write(BINARY_HEADER); + } + debug("writing chunk: %o", data); + this.writer.write(data); + if (isLast) { + this.writable = true; + this.emit("drain"); + } + }); + } + } + + doClose(fn) { + debug("closing WebTransport session"); + this.session.close(); + fn && fn(); + } +} diff --git a/package-lock.json b/package-lock.json index 8f830d8fc..17db85ca7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "engine.io", - "version": "6.3.1", + "version": "6.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "engine.io", - "version": "6.3.1", + "version": "6.4.2", "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", @@ -17,10 +17,11 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.1.0", "ws": "~8.11.0" }, "devDependencies": { + "@fails-components/webtransport": "^0.1.7", "babel-eslint": "^8.0.2", "eiows": "^4.1.2", "engine.io-client": "6.4.0", @@ -29,6 +30,7 @@ "express-session": "^1.17.3", "helmet": "^6.0.1", "mocha": "^9.1.3", + "node-forge": "^1.3.1", "prettier": "^2.8.2", "rimraf": "^3.0.2", "superagent": "^3.8.1", @@ -152,12 +154,19 @@ "to-fast-properties": "^2.0.0" } }, - "node_modules/@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", + "node_modules/@fails-components/webtransport": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@fails-components/webtransport/-/webtransport-0.1.7.tgz", + "integrity": "sha512-RD8kGxFVSBElx7Y/ApskD1/t8kXF4GNtvveJnnMET8TAd6FfcEmtETvzJax5o7KyvGONsoVlCtLRY6s12ncn4w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.1.1" + }, "engines": { - "node": ">= 0.6.0" + "node": ">=16" } }, "node_modules/@socket.io/component-emitter": { @@ -311,6 +320,26 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -328,6 +357,40 @@ "node": ">=8" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -362,6 +425,30 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -428,6 +515,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -550,6 +643,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -568,6 +685,15 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -593,6 +719,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io-client": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", @@ -684,13 +819,19 @@ "node": ">=0.4.0" } }, + "node_modules/engine.io-client/node_modules/engine.io-parser": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.7.tgz", + "integrity": "sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "dependencies": { - "@socket.io/base64-arraybuffer": "~1.0.2" - }, + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", + "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==", "engines": { "node": ">=10.0.0" } @@ -774,6 +915,15 @@ "node": ">=0.10.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/expect.js": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz", @@ -840,6 +990,12 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -901,6 +1057,12 @@ "url": "https://ko-fi.com/tunnckoCore/commissions" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -950,6 +1112,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1072,6 +1240,26 @@ "node": ">=14.0.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", @@ -1094,6 +1282,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -1336,6 +1530,18 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -1376,6 +1582,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1388,6 +1606,21 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "node_modules/mocha": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", @@ -1502,6 +1735,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -1510,6 +1749,33 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.43.0.tgz", + "integrity": "sha512-QB0MMv+tn9Ur2DtJrc8y09n0n6sw88CyDniWSX2cHW10goQXYPK9ZpFJOktDS4ron501edPX6h9i7Pg+RnH5nQ==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1635,6 +1901,32 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prettier": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", @@ -1656,6 +1948,16 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -1689,6 +1991,30 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -1746,6 +2072,21 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -1769,6 +2110,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -1869,6 +2255,48 @@ "node": ">=4" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -1899,6 +2327,18 @@ "node": ">=0.10.0" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/typescript": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", @@ -2058,6 +2498,12 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -2234,10 +2680,16 @@ "to-fast-properties": "^2.0.0" } }, - "@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==" + "@fails-components/webtransport": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@fails-components/webtransport/-/webtransport-0.1.7.tgz", + "integrity": "sha512-RD8kGxFVSBElx7Y/ApskD1/t8kXF4GNtvveJnnMET8TAd6FfcEmtETvzJax5o7KyvGONsoVlCtLRY6s12ncn4w==", + "dev": true, + "requires": { + "bindings": "^1.5.0", + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.1.1" + } }, "@socket.io/component-emitter": { "version": "3.1.0", @@ -2362,6 +2814,12 @@ "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, "base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -2373,6 +2831,39 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -2404,6 +2895,16 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2447,6 +2948,12 @@ "readdirp": "~3.6.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -2546,6 +3053,21 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2558,6 +3080,12 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "dev": true + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -2576,6 +3104,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, "engine.io-client": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", @@ -2587,6 +3124,14 @@ "engine.io-parser": "~5.0.3", "ws": "~8.11.0", "xmlhttprequest-ssl": "~2.0.0" + }, + "dependencies": { + "engine.io-parser": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.7.tgz", + "integrity": "sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==", + "dev": true + } } }, "engine.io-client-v3": { @@ -2652,12 +3197,9 @@ } }, "engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "requires": { - "@socket.io/base64-arraybuffer": "~1.0.2" - } + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", + "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==" }, "escalade": { "version": "3.1.1", @@ -2716,6 +3258,12 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true + }, "expect.js": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz", @@ -2767,6 +3315,12 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2809,6 +3363,12 @@ "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", "dev": true }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2845,6 +3405,12 @@ "has-symbols": "^1.0.3" } }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -2936,6 +3502,12 @@ "integrity": "sha512-8wo+VdQhTMVBMCITYZaGTbE4lvlthelPYSvoyNvk4RECTmrVjMerp9RfUOQXZWLvCcAn1pKj7ZRxK4lI9Alrcw==", "dev": true }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", @@ -2958,6 +3530,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -3133,6 +3711,15 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3158,6 +3745,12 @@ "mime-db": "1.44.0" } }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3167,6 +3760,18 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, "mocha": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", @@ -3248,11 +3853,38 @@ "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-abi": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.43.0.tgz", + "integrity": "sha512-QB0MMv+tn9Ur2DtJrc8y09n0n6sw88CyDniWSX2cHW10goQXYPK9ZpFJOktDS4ron501edPX6h9i7Pg+RnH5nQ==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3339,6 +3971,26 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dev": true, + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, "prettier": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", @@ -3351,6 +4003,16 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -3375,6 +4037,26 @@ "safe-buffer": "^5.1.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + } + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -3420,6 +4102,15 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -3440,6 +4131,23 @@ "object-inspect": "^1.9.0" } }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3519,6 +4227,44 @@ "has-flag": "^3.0.0" } }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -3540,6 +4286,15 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "typescript": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", @@ -3647,6 +4402,12 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index 0a8e24599..5bfd64255 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,11 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.1.0", "ws": "~8.11.0" }, "devDependencies": { + "@fails-components/webtransport": "^0.1.7", "babel-eslint": "^8.0.2", "eiows": "^4.1.2", "engine.io-client": "6.4.0", @@ -51,6 +52,7 @@ "express-session": "^1.17.3", "helmet": "^6.0.1", "mocha": "^9.1.3", + "node-forge": "^1.3.1", "prettier": "^2.8.2", "rimraf": "^3.0.2", "superagent": "^3.8.1", @@ -64,8 +66,8 @@ "test:compat-v3": "EIO_CLIENT=3 mocha --exit", "test:eiows": "EIO_WS_ENGINE=eiows mocha --exit", "test:uws": "EIO_WS_ENGINE=uws mocha --exit", - "format:check": "prettier --check \"wrapper.mjs\" \"lib/**/*.ts\" \"test/**/*.js\"", - "format:fix": "prettier --write \"wrapper.mjs\" \"lib/**/*.ts\" \"test/**/*.js\"", + "format:check": "prettier --check \"wrapper.mjs\" \"lib/**/*.ts\" \"test/**/*.js\" \"test/webtransport.mjs\"", + "format:fix": "prettier --write \"wrapper.mjs\" \"lib/**/*.ts\" \"test/**/*.js\" \"test/webtransport.mjs\"", "prepack": "npm run compile" }, "repository": { diff --git a/test/server.js b/test/server.js index bfda7cd98..fa0ab9c0c 100644 --- a/test/server.js +++ b/test/server.js @@ -1618,6 +1618,7 @@ describe("server", () => { socket.on("open", () => { engine.close(); setTimeout(() => { + delete Object.prototype.foo; done(); }, 100); }); diff --git a/test/util.mjs b/test/util.mjs new file mode 100644 index 000000000..01f6e3ea3 --- /dev/null +++ b/test/util.mjs @@ -0,0 +1,404 @@ +// imported from https://github.com/fails-components/webtransport/blob/master/test/fixtures/certificate.js + +// @ts-expect-error node-forge has no types and @types/node-forge do not include oids +import forge from 'node-forge' +import { webcrypto as crypto, X509Certificate } from 'crypto' + +const { pki, asn1, oids } = forge +// taken from node-forge +/** + * Converts an X.509 subject or issuer to an ASN.1 RDNSequence. + * + * @param {any} obj the subject or issuer (distinguished name). + * + * @return the ASN.1 RDNSequence. + */ +function _dnToAsn1(obj) { + // create an empty RDNSequence + const rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []) + + // iterate over attributes + let attr, set + const attrs = obj.attributes + for (let i = 0; i < attrs.length; ++i) { + attr = attrs[i] + let value = attr.value + + // reuse tag class for attribute value if available + let valueTagClass = asn1.Type.PRINTABLESTRING + if ('valueTagClass' in attr) { + valueTagClass = attr.valueTagClass + + if (valueTagClass === asn1.Type.UTF8) { + value = forge.util.encodeUtf8(value) + } + // FIXME: handle more encodings + } + + // create a RelativeDistinguishedName set + // each value in the set is an AttributeTypeAndValue first + // containing the type (an OID) and second the value + set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + // AttributeType + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.OID, + false, + asn1.oidToDer(attr.type).getBytes() + ), + // AttributeValue + asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value) + ]) + ]) + rval.value.push(set) + } + + return rval +} + +const jan_1_1950 = new Date('1950-01-01T00:00:00Z') // eslint-disable-line camelcase +const jan_1_2050 = new Date('2050-01-01T00:00:00Z') // eslint-disable-line camelcase +// taken from node-forge almost not modified +/** + * Converts a Date object to ASN.1 + * Handles the different format before and after 1st January 2050 + * + * @param {Date} date date object. + * + * @return the ASN.1 object representing the date. + */ +function _dateToAsn1(date) { + // eslint-disable-next-line camelcase + if (date >= jan_1_1950 && date < jan_1_2050) { + return asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.UTCTIME, + false, + asn1.dateToUtcTime(date) + ) + } else { + return asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.GENERALIZEDTIME, + false, + asn1.dateToGeneralizedTime(date) + ) + } +} + +// taken from node-forge almost not modified +/** + * Convert signature parameters object to ASN.1 + * + * @param {string} oid Signature algorithm OID + * @param {any} params The signature parameters object + * @return ASN.1 object representing signature parameters + */ +function _signatureParametersToAsn1(oid, params) { + const parts = [] + + switch (oid) { + case oids['RSASSA-PSS']: + if (params.hash.algorithmOid !== undefined) { + parts.push( + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.OID, + false, + asn1.oidToDer(params.hash.algorithmOid).getBytes() + ), + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') + ]) + ]) + ) + } + + if (params.mgf.algorithmOid !== undefined) { + parts.push( + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [ + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.OID, + false, + asn1.oidToDer(params.mgf.algorithmOid).getBytes() + ), + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.OID, + false, + asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes() + ), + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') + ]) + ]) + ]) + ) + } + + if (params.saltLength !== undefined) { + parts.push( + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [ + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.INTEGER, + false, + asn1.integerToDer(params.saltLength).getBytes() + ) + ]) + ) + } + + return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts) + + default: + return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') + } +} + +// taken from node-forge and modified to work with ECDSA +/** + * Gets the ASN.1 TBSCertificate part of an X.509v3 certificate. + * + * @param {any} cert the certificate. + * + * @return the asn1 TBSCertificate. + */ +function getTBSCertificate(cert) { + // TBSCertificate + const notBefore = _dateToAsn1(cert.validity.notBefore) + const notAfter = _dateToAsn1(cert.validity.notAfter) + + const tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + // version + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ + // integer + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.INTEGER, + false, + asn1.integerToDer(cert.version).getBytes() + ) + ]), + // serialNumber + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.INTEGER, + false, + forge.util.hexToBytes(cert.serialNumber) + ), + // signature + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + // algorithm + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.OID, + false, + asn1.oidToDer(cert.siginfo.algorithmOid).getBytes() + ), + // parameters + _signatureParametersToAsn1( + cert.siginfo.algorithmOid, + cert.siginfo.parameters + ) + ]), + // issuer + _dnToAsn1(cert.issuer), + // validity + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ + notBefore, + notAfter + ]), + // subject + _dnToAsn1(cert.subject), + // SubjectPublicKeyInfo + // here comes our modification, we are other objects here + asn1.fromDer( + new forge.util.ByteBuffer( + cert.publicKey + ) /* is in already SPKI format but in DER encoding */ + ) + ]) + + if (cert.issuer.uniqueId) { + // issuerUniqueID (optional) + tbs.value.push( + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [ + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.BITSTRING, + false, + // TODO: support arbitrary bit length ids + String.fromCharCode(0x00) + cert.issuer.uniqueId + ) + ]) + ) + } + if (cert.subject.uniqueId) { + // subjectUniqueID (optional) + tbs.value.push( + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [ + asn1.create( + asn1.Class.UNIVERSAL, + asn1.Type.BITSTRING, + false, + // TODO: support arbitrary bit length ids + String.fromCharCode(0x00) + cert.subject.uniqueId + ) + ]) + ) + } + + if (cert.extensions.length > 0) { + // extensions (optional) + tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions)) + } + + return tbs +} + +// function taken form selfsigned +// a hexString is considered negative if it's most significant bit is 1 +// because serial numbers use ones' complement notation +// this RFC in section 4.1.2.2 requires serial numbers to be positive +// http://www.ietf.org/rfc/rfc5280.txt +/** + * @param {string} hexString + * @returns + */ +function toPositiveHex(hexString) { + let mostSiginficativeHexAsInt = parseInt(hexString[0], 16) + if (mostSiginficativeHexAsInt < 8) { + return hexString + } + + mostSiginficativeHexAsInt -= 8 + return mostSiginficativeHexAsInt.toString() + hexString.substring(1) +} + +// the next is an edit of the selfsigned function reduced to the function necessary for webtransport +/** + * @typedef {object} Certificate + * @property {string} public + * @property {string} private + * @property {string} cert + * @property {Uint8Array} hash + * @property {string} fingerprint + * + * @param {*} attrs + * @param {*} options + * @returns {Promise} + */ +export async function generateWebTransportCertificate(attrs, options) { + try { + const keyPair = await crypto.subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256' + }, + true, + ['sign', 'verify'] + ) + + const cert = pki.createCertificate() + + cert.serialNumber = toPositiveHex( + forge.util.bytesToHex(forge.random.getBytesSync(9)) + ) // the serial number can be decimal or hex (if preceded by 0x) + cert.validity.notBefore = new Date() + cert.validity.notAfter = new Date() + cert.validity.notAfter.setDate( + cert.validity.notBefore.getDate() + (options.days || 14) + ) // per spec only 14 days allowed + + cert.setSubject(attrs) + cert.setIssuer(attrs) + + const privateKey = crypto.subtle.exportKey('pkcs8', keyPair.privateKey) + const publicKey = (cert.publicKey = await crypto.subtle.exportKey( + 'spki', + keyPair.publicKey + )) + + cert.setExtensions( + options.extensions || [ + { + name: 'basicConstraints', + cA: true + }, + { + name: 'keyUsage', + keyCertSign: true, + digitalSignature: true, + nonRepudiation: true, + keyEncipherment: true, + dataEncipherment: true + }, + { + name: 'subjectAltName', + altNames: [ + { + type: 6, // URI + value: 'http://example.org/webid#me' + } + ] + } + ] + ) + + // to signing + // patch oids object + oids['1.2.840.10045.4.3.2'] = 'ecdsa-with-sha256' + oids['ecdsa-with-sha256'] = '1.2.840.10045.4.3.2' + + cert.siginfo.algorithmOid = cert.signatureOid = '1.2.840.10045.4.3.2' // 'ecdsa-with-sha256' + + cert.tbsCertificate = getTBSCertificate(cert) + const encoded = Buffer.from( + asn1.toDer(cert.tbsCertificate).getBytes(), + 'binary' + ) + cert.md = crypto.subtle.digest('SHA-256', encoded) + cert.signature = crypto.subtle.sign( + { + name: 'ECDSA', + hash: { name: 'SHA-256' } + }, + keyPair.privateKey, + encoded + ) + cert.md = await cert.md + cert.signature = await cert.signature + + const pemcert = pki.certificateToPem(cert) + + const x509cert = new X509Certificate(pemcert) + + const certhash = Buffer.from( + x509cert.fingerprint256.split(':').map((el) => parseInt(el, 16)) + ) + + const pem = { + private: forge.pem.encode({ + type: 'PRIVATE KEY', + body: new forge.util.ByteBuffer(await privateKey).getBytes() + }), + public: forge.pem.encode({ + type: 'PUBLIC KEY', + body: new forge.util.ByteBuffer(publicKey).getBytes() + }), + cert: pemcert, + hash: certhash, + fingerprint: x509cert.fingerprint256 + } + + return pem + } catch (error) { + console.log('error in generate certificate', error) + return null + } +} diff --git a/test/webtransport.mjs b/test/webtransport.mjs new file mode 100644 index 000000000..d26f9dfd0 --- /dev/null +++ b/test/webtransport.mjs @@ -0,0 +1,436 @@ +import * as eio from "../build/server.js"; +import { Http3Server, WebTransport } from "@fails-components/webtransport"; +import { Http3EventLoop } from "@fails-components/webtransport/lib/event-loop.js"; +import expect from "expect.js"; +import request from "superagent"; +import { createServer } from "http"; +import { generateWebTransportCertificate } from "./util.mjs"; + +const TEXT_ENCODER = new TextEncoder(); +const TEXT_DECODER = new TextDecoder(); + +function success(engine, h3server, done) { + engine.close(); + h3server.stopServer(); + done(); +} + +function createPartialDone(done, count) { + let i = 0; + return () => { + if (++i === count) { + done(); + } else if (i > count) { + done(new Error(`partialDone() called too many times: ${i} > ${count}`)); + } + }; +} + +async function setupServer(opts, cb) { + const certificate = await generateWebTransportCertificate( + [{ shortName: "CN", value: "localhost" }], + { + days: 14, // the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements) + } + ); + + const engine = new eio.Server(opts); + + const h3Server = new Http3Server({ + port: 0, // random port + host: "0.0.0.0", + secret: "changeit", + cert: certificate.cert, + privKey: certificate.private, + }); + + (async () => { + try { + const stream = await h3Server.sessionStream("/engine.io/"); + const sessionReader = stream.getReader(); + + while (true) { + const { done, value } = await sessionReader.read(); + if (done) { + break; + } + engine.onWebTransportSession(value); + } + } catch (ex) { + console.error("Server error", ex); + } + })(); + + h3Server.startServer(); + h3Server.onServerListening = () => cb({ engine, h3Server, certificate }); +} + +function setup(opts, cb) { + setupServer(opts, async ({ engine, h3Server, certificate }) => { + const client = new WebTransport( + `https://127.0.0.1:${h3Server.port}/engine.io/`, + { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: certificate.hash, + }, + ], + } + ); + + await client.ready; + + const stream = await client.createBidirectionalStream(); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + + engine.on("connection", (socket) => { + cb({ engine, h3Server, socket, client, stream, reader, writer }); + }); + + await writer.write(TEXT_ENCODER.encode("0")); + await reader.read(); // handshake + }); +} + +describe("WebTransport", () => { + after(() => { + Http3EventLoop.globalLoop.shutdownEventLoop(); // manually shutdown the event loop, instead of waiting 20s + }); + + it("should allow to connect with WebTransport directly", (done) => { + setupServer({}, async ({ engine, h3Server, certificate }) => { + const partialDone = createPartialDone( + () => success(engine, h3Server, done), + 2 + ); + + engine.on("connection", (socket) => { + expect(socket.transport.name).to.eql("webtransport"); + partialDone(); + }); + + const client = new WebTransport( + `https://127.0.0.1:${h3Server.port}/engine.io/`, + { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: certificate.hash, + }, + ], + } + ); + + await client.ready; + + const stream = await client.createBidirectionalStream(); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + + (async function read() { + const { done, value } = await reader.read(); + + if (done) { + return; + } + + const handshake = TEXT_DECODER.decode(value); + expect(handshake.startsWith("0{")).to.be(true); + + partialDone(); + })(); + + await writer.write(TEXT_ENCODER.encode("0")); + }); + }); + + it("should allow to upgrade to WebTransport", (done) => { + setupServer( + { + transports: ["polling", "websocket", "webtransport"], + }, + async ({ engine, h3Server, certificate }) => { + const httpServer = createServer(); + engine.attach(httpServer); + httpServer.listen(h3Server.port); + + const partialDone = createPartialDone(() => { + httpServer.close(); + success(engine, h3Server, done); + }, 2); + + engine.on("connection", (socket) => { + socket.on("upgrade", (transport) => { + expect(transport.name).to.eql("webtransport"); + partialDone(); + }); + }); + + request(`http://localhost:${h3Server.port}/engine.io/`) + .query({ EIO: 4, transport: "polling" }) + .end(async (_, res) => { + const payload = JSON.parse(res.text.substring(1)); + + expect(payload.upgrades).to.eql(["websocket", "webtransport"]); + + const client = new WebTransport( + `https://127.0.0.1:${h3Server.port}/engine.io/`, + { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: certificate.hash, + }, + ], + } + ); + + await client.ready; + + const stream = await client.createBidirectionalStream(); + const reader = stream.readable.getReader(); + const writer = stream.writable.getWriter(); + + (async function read() { + const { done, value } = await reader.read(); + + if (done) { + return; + } + + const probeValue = TEXT_DECODER.decode(value); + expect(probeValue).to.eql("3probe"); + + partialDone(); + })(); + + await writer.write( + TEXT_ENCODER.encode(`0{"sid":"${payload.sid}"}`) + ); + await writer.write(TEXT_ENCODER.encode(`2probe`)); + await writer.write(TEXT_ENCODER.encode(`5`)); + }); + } + ); + }); + + it("should close a connection that fails to open a bidirectional stream", (done) => { + setupServer( + { + upgradeTimeout: 50, + }, + async ({ engine, h3Server, certificate }) => { + const client = new WebTransport( + `https://127.0.0.1:${h3Server.port}/engine.io/`, + { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: certificate.hash, + }, + ], + } + ); + + await client.ready; + + client.closed.then(() => { + success(engine, h3Server, done); + }); + } + ); + }); + + it("should close a connection that sends an invalid handshake", (done) => { + setupServer( + { + upgradeTimeout: 50, + }, + async ({ engine, h3Server, certificate }) => { + const client = new WebTransport( + `https://127.0.0.1:${h3Server.port}/engine.io/`, + { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: certificate.hash, + }, + ], + } + ); + + await client.ready; + const stream = await client.createBidirectionalStream(); + const writer = stream.writable.getWriter(); + + await writer.write(Uint8Array.of(1, 2, 3)); + + client.closed.then(() => { + success(engine, h3Server, done); + }); + } + ); + }); + + it("should send ping/pong packets", (done) => { + setup( + { + pingInterval: 20, + }, + async ({ engine, h3Server, reader, writer }) => { + for (let i = 0; i < 5; i++) { + const packet = await reader.read(); + const value = TEXT_DECODER.decode(packet.value); + expect(value).to.eql("2"); + + writer.write(TEXT_ENCODER.encode("3")); + } + + success(engine, h3Server, done); + } + ); + }); + + it("should close on ping timeout", (done) => { + setup( + { + pingInterval: 20, + pingTimeout: 30, + }, + async ({ engine, h3Server, socket, client }) => { + const partialDone = createPartialDone(done, 2); + socket.on("close", (reason) => { + expect(reason).to.eql("ping timeout"); + partialDone(); + }); + + client.closed.then(() => success(engine, h3Server, partialDone)); + } + ); + }); + + it("should handle connections closed by the server", (done) => { + setup({}, async ({ engine, h3Server, socket, client }) => { + client.closed.then(() => success(engine, h3Server, done)); + + socket.close(); + }); + }); + + it("should handle connections closed by the client", (done) => { + setup({}, async ({ engine, h3Server, socket, client }) => { + socket.on("close", (reason) => { + expect(reason).to.eql("transport close"); + success(engine, h3Server, done); + }); + + client.close(); + }); + }); + + it("should send some plaintext data (client to server)", (done) => { + setup({}, async ({ engine, h3Server, socket, writer }) => { + socket.on("data", (data) => { + expect(data).to.eql("hello"); + + success(engine, h3Server, done); + }); + + writer.write(TEXT_ENCODER.encode("4hello")); + }); + }); + + it("should send some plaintext data (server to client)", (done) => { + setup({}, async ({ engine, h3Server, socket, reader }) => { + socket.send("hello"); + + const { value } = await reader.read(); + const decoded = TEXT_DECODER.decode(value); + expect(decoded).to.eql("4hello"); + + success(engine, h3Server, done); + }); + }); + + it("should send some binary data (client to server)", (done) => { + setup({}, async ({ engine, h3Server, socket, writer }) => { + socket.on("data", (data) => { + expect(Buffer.isBuffer(data)).to.be(true); + expect(data).to.eql(Buffer.of(1, 2, 3)); + + success(engine, h3Server, done); + }); + + writer.write(Uint8Array.of(1, 2, 3)); + }); + }); + + it("should send some binary data (server to client)", (done) => { + setup({}, async ({ engine, h3Server, socket, reader }) => { + socket.send(Buffer.of(1, 2, 3)); + + const { value } = await reader.read(); + expect(value).to.eql(Uint8Array.of(1, 2, 3)); + + success(engine, h3Server, done); + }); + }); + + it("should send some binary data (client to server) (with binary flag)", (done) => { + setup({}, async ({ engine, h3Server, socket, writer }) => { + socket.on("data", (data) => { + expect(Buffer.isBuffer(data)).to.be(true); + expect(data).to.eql(Buffer.of(48, 1, 2, 3)); + + success(engine, h3Server, done); + }); + + writer.write(Uint8Array.of(54)); + writer.write(Uint8Array.of(48, 1, 2, 3)); + }); + }); + + it("should send some binary data (server to client) (with binary flag)", (done) => { + setup({}, async ({ engine, h3Server, socket, reader }) => { + socket.send(Buffer.of(48, 1, 2, 3)); + + const header = await reader.read(); + expect(header.value).to.eql(Uint8Array.of(54)); + + const { value } = await reader.read(); + expect(value).to.eql(Uint8Array.of(48, 1, 2, 3)); + + success(engine, h3Server, done); + }); + }); + + it("should send some binary data (client to server) (binary flag)", (done) => { + setup({}, async ({ engine, h3Server, socket, writer }) => { + socket.on("data", (data) => { + expect(Buffer.isBuffer(data)).to.be(true); + expect(data).to.eql(Buffer.of(54)); + + success(engine, h3Server, done); + }); + + writer.write(Uint8Array.of(54)); + writer.write(Uint8Array.of(54)); + }); + }); + + it("should send some binary data (server to client) (binary flag)", (done) => { + setup({}, async ({ engine, h3Server, socket, reader }) => { + socket.send(Buffer.of(54)); + + const header = await reader.read(); + expect(header.value).to.eql(Uint8Array.of(54)); + + const { value } = await reader.read(); + expect(value).to.eql(Uint8Array.of(54)); + + success(engine, h3Server, done); + }); + }); +}); From 1bfa9cd0888481a1a5ff05869114b39c1c47df40 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 16 Jun 2023 10:10:51 +0200 Subject: [PATCH 4/5] refactor: adapt to latest uWebSockets.js changes Reference: https://github.com/uNetworking/uWebSockets.js/releases --- lib/transports-uws/polling.ts | 12 ++++++++---- lib/userver.ts | 31 ++++++++++++++++++++----------- package-lock.json | 10 +++++----- package.json | 2 +- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 5cab41ba7..ce503a8a6 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -162,7 +162,9 @@ export class Polling extends Transport { const onEnd = (buffer) => { this.onData(buffer.toString()); this.onDataRequestCleanup(); - res.end("ok"); + res.cork(() => { + res.end("ok"); + }); }; res.onAborted(() => { @@ -312,10 +314,12 @@ export class Polling extends Transport { const respond = (data) => { this.headers(this.req, headers); - Object.keys(headers).forEach((key) => { - this.res.writeHeader(key, String(headers[key])); + this.res.cork(() => { + Object.keys(headers).forEach((key) => { + this.res.writeHeader(key, String(headers[key])); + }); + this.res.end(data); }); - this.res.end(data); callback(); }; diff --git a/lib/userver.ts b/lib/userver.ts index 654f037d7..98380fbd9 100644 --- a/lib/userver.ts +++ b/lib/userver.ts @@ -70,24 +70,25 @@ export class uServer extends BaseServer { (app as TemplatedApp) .any(path, this.handleRequest.bind(this)) // - .ws(path, { + .ws<{ transport: any }>(path, { compression: options.compression, idleTimeout: options.idleTimeout, maxBackpressure: options.maxBackpressure, maxPayloadLength: this.opts.maxHttpBufferSize, upgrade: this.handleUpgrade.bind(this), open: (ws) => { - ws.transport.socket = ws; - ws.transport.writable = true; - ws.transport.emit("drain"); + const transport = ws.getUserData().transport; + transport.socket = ws; + transport.writable = true; + transport.emit("drain"); }, message: (ws, message, isBinary) => { - ws.transport.onData( + ws.getUserData().transport.onData( isBinary ? message : Buffer.from(message).toString() ); }, close: (ws, code, message) => { - ws.transport.onClose(code, message); + ws.getUserData().transport.onClose(code, message); }, }); } @@ -323,11 +324,13 @@ class ResponseWrapper { public end(data) { if (this.isAborted) return; - if (!this.statusWritten) { - // status will be inferred as "200 OK" - this.writeBufferedHeaders(); - } - this.res.end(data); + this.res.cork(() => { + if (!this.statusWritten) { + // status will be inferred as "200 OK" + this.writeBufferedHeaders(); + } + this.res.end(data); + }); } public onData(fn) { @@ -345,4 +348,10 @@ class ResponseWrapper { fn(); }); } + + public cork(fn) { + if (this.isAborted) return; + + this.res.cork(fn); + } } diff --git a/package-lock.json b/package-lock.json index 17db85ca7..ecb921816 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "rimraf": "^3.0.2", "superagent": "^3.8.1", "typescript": "^4.4.3", - "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.15.0" + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.30.0" }, "engines": { "node": ">=10.0.0" @@ -2371,8 +2371,8 @@ "dev": true }, "node_modules/uWebSockets.js": { - "version": "20.15.0", - "resolved": "git+https://git@github.com/uNetworking/uWebSockets.js.git#77bc1fd5577ae42b56675912eb8481a31f3fefd2", + "version": "20.30.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142", "dev": true }, "node_modules/vary": { @@ -4317,9 +4317,9 @@ "dev": true }, "uWebSockets.js": { - "version": "git+https://git@github.com/uNetworking/uWebSockets.js.git#77bc1fd5577ae42b56675912eb8481a31f3fefd2", + "version": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142", "dev": true, - "from": "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.15.0" + "from": "uWebSockets.js@uNetworking/uWebSockets.js#v20.30.0" }, "vary": { "version": "1.1.2", diff --git a/package.json b/package.json index 5bfd64255..db3ae718e 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "rimraf": "^3.0.2", "superagent": "^3.8.1", "typescript": "^4.4.3", - "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.15.0" + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.30.0" }, "scripts": { "compile": "rimraf ./build && tsc", From 1f640a2a0f7cd3beba2d1a0ecec6614e7ff6fe4c Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 16 Jun 2023 11:36:09 +0200 Subject: [PATCH 5/5] chore(release): 6.5.0 Diff: https://github.com/socketio/engine.io/compare/6.4.2...6.5.0 --- CHANGELOG.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 37 ++++++-------------- package.json | 4 +-- 3 files changed, 98 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ebdb9d9..527cd0808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2023 +- [6.5.0](#650-2023-06-16) (Jun 2023) - [6.4.2](#642-2023-05-02) (May 2023) - [6.4.1](#641-2023-02-20) (Feb 2023) - [6.4.0](#640-2023-02-06) (Feb 2023) @@ -47,6 +48,91 @@ # Release notes +## [6.5.0](https://github.com/socketio/engine.io/compare/6.4.2...6.5.0) (2023-06-16) + + +### Bug Fixes + +* **uws:** discard any write to an aborted uWS response ([#682](https://github.com/socketio/engine.io/issues/682)) ([3144d27](https://github.com/socketio/engine.io/commit/3144d274584ae3b96cca4e609c66c56d534f1715)) + + +### Features + +#### Support for WebTransport + +The Engine.IO server can now use WebTransport as the underlying transport. + +WebTransport is a web API that uses the HTTP/3 protocol as a bidirectional transport. It's intended for two-way communications between a web client and an HTTP/3 server. + +References: + +- https://w3c.github.io/webtransport/ +- https://developer.mozilla.org/en-US/docs/Web/API/WebTransport +- https://developer.chrome.com/articles/webtransport/ + +Until WebTransport support lands [in Node.js](https://github.com/nodejs/node/issues/38478), you can use the `@fails-components/webtransport` package: + +```js +import { readFileSync } from "fs"; +import { createServer } from "https"; +import { Server } from "engine.io"; +import { Http3Server } from "@fails-components/webtransport"; + +// WARNING: the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements) +const cert = readFileSync("/path/to/my/cert.pem"); +const key = readFileSync("/path/to/my/key.pem"); + +const httpsServer = createServer({ + key, + cert +}); + +httpsServer.listen(3000); + +const engine = new Server({ + transports: ["polling", "websocket", "webtransport"] // WebTransport is not enabled by default +}); + +engine.attach(httpsServer); + +const h3Server = new Http3Server({ + port: 3000, + host: "0.0.0.0", + secret: "changeit", + cert, + privKey: key, +}); + +(async () => { + const stream = await h3Server.sessionStream("/engine.io/"); + const sessionReader = stream.getReader(); + + while (true) { + const { done, value } = await sessionReader.read(); + if (done) { + break; + } + engine.onWebTransportSession(value); + } +})(); + +h3Server.startServer(); +``` + +Added in [123b68c](https://github.com/socketio/engine.io/commit/123b68c04f9e971f59b526e0f967a488ee6b0116). + + +### Credits + +Huge thanks to [@OxleyS](https://github.com/OxleyS) for helping! + + +### Dependencies + +- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change) + + + ## [6.4.2](https://github.com/socketio/engine.io/compare/6.4.1...6.4.2) (2023-05-02) :warning: This release contains an important security fix :warning: diff --git a/package-lock.json b/package-lock.json index ecb921816..95421dce9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@fails-components/webtransport": "^0.1.7", "babel-eslint": "^8.0.2", "eiows": "^4.1.2", - "engine.io-client": "6.4.0", + "engine.io-client": "6.5.0", "engine.io-client-v3": "npm:engine.io-client@3.5.2", "expect.js": "^0.3.1", "express-session": "^1.17.3", @@ -729,14 +729,14 @@ } }, "node_modules/engine.io-client": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", - "integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.0.tgz", + "integrity": "sha512-C7eN3OKggSfd5g8IDgUA9guC8TNS6CEganKT7dL6Fp3q+FobcQ/WBn2Qq2XTL1vNTiFZfDzXohvqLuR9dWejdg==", "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.1.0", "ws": "~8.11.0", "xmlhttprequest-ssl": "~2.0.0" } @@ -819,15 +819,6 @@ "node": ">=0.4.0" } }, - "node_modules/engine.io-client/node_modules/engine.io-parser": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.7.tgz", - "integrity": "sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/engine.io-parser": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", @@ -3114,24 +3105,16 @@ } }, "engine.io-client": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", - "integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.0.tgz", + "integrity": "sha512-C7eN3OKggSfd5g8IDgUA9guC8TNS6CEganKT7dL6Fp3q+FobcQ/WBn2Qq2XTL1vNTiFZfDzXohvqLuR9dWejdg==", "dev": true, "requires": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", + "engine.io-parser": "~5.1.0", "ws": "~8.11.0", "xmlhttprequest-ssl": "~2.0.0" - }, - "dependencies": { - "engine.io-parser": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.7.tgz", - "integrity": "sha512-P+jDFbvK6lE3n1OL+q9KuzdOFWkkZ/cMV9gol/SbVfpyqfvrfrFTOFJ6fQm2VC3PZHlU3QPhVwmbsCnauHF2MQ==", - "dev": true - } } }, "engine.io-client-v3": { @@ -4319,7 +4302,7 @@ "uWebSockets.js": { "version": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142", "dev": true, - "from": "uWebSockets.js@uNetworking/uWebSockets.js#v20.30.0" + "from": "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.30.0" }, "vary": { "version": "1.1.2", diff --git a/package.json b/package.json index db3ae718e..5eae71126 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "engine.io", - "version": "6.4.2", + "version": "6.5.0", "description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server", "type": "commonjs", "main": "./build/engine.io.js", @@ -46,7 +46,7 @@ "@fails-components/webtransport": "^0.1.7", "babel-eslint": "^8.0.2", "eiows": "^4.1.2", - "engine.io-client": "6.4.0", + "engine.io-client": "6.5.0", "engine.io-client-v3": "npm:engine.io-client@3.5.2", "expect.js": "^0.3.1", "express-session": "^1.17.3",