From c6bf8c0f571aad7a5917f43860c8c3d74a9b429b Mon Sep 17 00:00:00 2001 From: Avi Vahl Date: Thu, 17 Aug 2023 08:59:32 +0300 Subject: [PATCH 01/30] fix: improve compatibility with node16 module resolution (#689) Related: https://github.com/microsoft/TypeScript/issues/46770#issuecomment-966612103 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5753cdc1..39f79955 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "main": "./build/engine.io.js", "types": "./build/engine.io.d.ts", "exports": { + "types": "./build/engine.io.d.ts", "import": "./wrapper.mjs", "require": "./build/engine.io.js" }, From ff1c8615483bab25acc9cf04fb40339b0bd78812 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 5 Oct 2023 16:56:11 +0200 Subject: [PATCH 02/30] fix(webtransport): properly handle abruptly closed connections Refreshing the page with a client connected with WebTransport would trigger the following exception: > node:internal/process/promises:288 > triggerUncaughtException(err, true /* fromPromise */); > ^ > > [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "0".] { > code: 'ERR_UNHANDLED_REJECTION' > } Related: https://github.com/socketio/engine.io/issues/688 --- lib/transports/webtransport.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/transports/webtransport.ts b/lib/transports/webtransport.ts index 4f6f6877..5922fab0 100644 --- a/lib/transports/webtransport.ts +++ b/lib/transports/webtransport.ts @@ -14,7 +14,9 @@ export class WebTransport extends Transport { super({ _query: { EIO: "4" } }); const transformStream = createPacketEncoderStream(); - transformStream.readable.pipeTo(stream.writable); + transformStream.readable.pipeTo(stream.writable).catch(() => { + debug("the stream was closed"); + }); this.writer = transformStream.writable.getWriter(); (async () => { From 9545b44b3cccc1e2ff51c126d0d759571e22b3a6 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 5 Oct 2023 17:14:09 +0200 Subject: [PATCH 03/30] refactor: add cache-control header in the polling response This header should not be needed since the client already includes a cache busting query parameter ("t"), but a misconfigured CDN could ignore the query parameters and cache the server response. Related: https://github.com/socketio/socket.io/issues/4842 --- lib/transports-uws/polling.ts | 2 ++ lib/transports/polling.ts | 2 ++ test/server.js | 22 +++++++++++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index ce503a8a..4ce6e52f 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -423,6 +423,8 @@ export class Polling extends Transport { headers["X-XSS-Protection"] = "0"; } + headers["cache-control"] = "no-store"; + this.emit("headers", headers, req); return headers; } diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index 70be3411..f0edcdc8 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -392,6 +392,8 @@ export class Polling extends Transport { headers["X-XSS-Protection"] = "0"; } + headers["cache-control"] = "no-store"; + this.emit("headers", headers, req); return headers; } diff --git a/test/server.js b/test/server.js index fa0ab9c0..a373837e 100644 --- a/test/server.js +++ b/test/server.js @@ -3443,13 +3443,12 @@ describe("server", () => { }); describe("response headers", () => { - function testForHeaders(headers, done) { + function testForHeaders(headers, callback) { const engine = listen((port) => { engine.on("connection", (conn) => { conn.transport.once("headers", (headers) => { - expect(headers["X-XSS-Protection"]).to.be("0"); + callback(headers); conn.close(); - done(); }); conn.send("hi"); }); @@ -3465,7 +3464,10 @@ describe("server", () => { "user-agent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; Tablet PC 2.0)", }; - testForHeaders(headers, done); + testForHeaders(headers, (headers) => { + expect(headers["X-XSS-Protection"]).to.be("0"); + done(); + }); }); it("should contain X-XSS-Protection: 0 for IE11", (done) => { @@ -3473,7 +3475,17 @@ describe("server", () => { "user-agent": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko", }; - testForHeaders(headers, done); + testForHeaders(headers, (headers) => { + expect(headers["X-XSS-Protection"]).to.be("0"); + done(); + }); + }); + + it("should include a 'cache-control' header", (done) => { + testForHeaders({}, (headers) => { + expect(headers["cache-control"]).to.be("no-store"); + done(); + }); }); it("should emit a 'initial_headers' event (polling)", (done) => { From 2da559a8fa8376a835bfaedfb13ef075414af306 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 6 Oct 2023 10:20:34 +0200 Subject: [PATCH 04/30] chore(release): 6.5.3 Diff: https://github.com/socketio/engine.io/compare/6.5.2...6.5.3 --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1df1a2ad..ac452545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2023 +- [6.5.3](#653-2023-10-06) (Oct 2023) - [6.5.2](#652-2023-08-01) (Aug 2023) - [6.5.1](#651-2023-06-27) (Jun 2023) - [6.5.0](#650-2023-06-16) (Jun 2023) @@ -50,6 +51,21 @@ # Release notes +## [6.5.3](https://github.com/socketio/engine.io/compare/6.5.2...6.5.3) (2023-10-06) + + +### Bug Fixes + +* improve compatibility with node16 module resolution ([#689](https://github.com/socketio/engine.io/issues/689)) ([c6bf8c0](https://github.com/socketio/engine.io/commit/c6bf8c0f571aad7a5917f43860c8c3d74a9b429b)), closes [/github.com/microsoft/TypeScript/issues/46770#issuecomment-966612103](https://github.com//github.com/microsoft/TypeScript/issues/46770/issues/issuecomment-966612103) +* **webtransport:** properly handle abruptly closed connections ([ff1c861](https://github.com/socketio/engine.io/commit/ff1c8615483bab25acc9cf04fb40339b0bd78812)) + + +### Dependencies + +- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change) + + + ## [6.5.2](https://github.com/socketio/engine.io/compare/6.5.1...6.5.2) (2023-08-01) diff --git a/package.json b/package.json index 39f79955..98ef51e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "engine.io", - "version": "6.5.2", + "version": "6.5.3", "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", From f27a6c35017e4eb37546949f754e09933102837a Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 9 Nov 2023 11:41:13 +0100 Subject: [PATCH 05/30] refactor: remove useless reference A reference to the initial IncomingMessage object (the first HTTP request of the session) is kept in memory by default (`socket.request`), so its attached ServerResponse object (`req.res`) would not be garbage-collected. This will now be the case. Note: the IncomingMessage object is needed in two cases: - when working with the `express-session` middleware (`request.session`) - when fetching the certificate of the client with `request.socket.getPeerCertificate()` That's why removing it would be a breaking change. --- lib/transports-uws/polling.ts | 2 ++ lib/transports/polling.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 4ce6e52f..16e72c00 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -55,6 +55,8 @@ export class Polling extends Transport { */ onRequest(req) { const res = req.res; + // remove the reference to the ServerResponse object (as the first request of the session is kept in memory by default) + req.res = null; if (req.getMethod() === "get") { this.onPollRequest(req, res); diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index f0edcdc8..e5ea24cf 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -54,6 +54,8 @@ export class Polling extends Transport { */ onRequest(req: IncomingMessage & { res: ServerResponse }) { const res = req.res; + // remove the reference to the ServerResponse object (as the first request of the session is kept in memory by default) + req.res = null; if ("GET" === req.method) { this.onPollRequest(req, res); From 3b5e79ef7942226d0c06b6e99872f138e402bf55 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 9 Nov 2023 12:01:28 +0100 Subject: [PATCH 06/30] refactor: remove useless references Those timers are only used during the upgrade, so there is no need to keep those references in memory. --- lib/socket.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index 7afb9622..e7143285 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -28,8 +28,6 @@ export class Socket extends EventEmitter { private packetsFn: Array<() => void>; private sentCallbackFn: any[]; private cleanupFn: any[]; - private checkIntervalTimer; - private upgradeTimeoutTimer; private pingTimeoutTimer; private pingIntervalTimer; @@ -81,8 +79,6 @@ export class Socket extends EventEmitter { // see https://github.com/fails-components/webtransport/issues/114 } - this.checkIntervalTimer = null; - this.upgradeTimeoutTimer = null; this.pingTimeoutTimer = null; this.pingIntervalTimer = null; @@ -265,7 +261,7 @@ export class Socket extends EventEmitter { this.upgrading = true; // set transport upgrade timer - this.upgradeTimeoutTimer = setTimeout(() => { + const upgradeTimeoutTimer = setTimeout(() => { debug("client did not complete upgrade - closing transport"); cleanup(); if ("open" === transport.readyState) { @@ -273,13 +269,15 @@ export class Socket extends EventEmitter { } }, this.server.opts.upgradeTimeout); + let checkIntervalTimer; + const onPacket = (packet) => { if ("ping" === packet.type && "probe" === packet.data) { debug("got probe ping packet, sending pong"); transport.send([{ type: "pong", data: "probe" }]); this.emit("upgrading", transport); - clearInterval(this.checkIntervalTimer); - this.checkIntervalTimer = setInterval(check, 100); + clearInterval(checkIntervalTimer); + checkIntervalTimer = setInterval(check, 100); } else if ("upgrade" === packet.type && this.readyState !== "closed") { debug("got upgrade packet - upgrading"); cleanup(); @@ -311,11 +309,8 @@ export class Socket extends EventEmitter { const cleanup = () => { this.upgrading = false; - clearInterval(this.checkIntervalTimer); - this.checkIntervalTimer = null; - - clearTimeout(this.upgradeTimeoutTimer); - this.upgradeTimeoutTimer = null; + clearInterval(checkIntervalTimer); + clearTimeout(upgradeTimeoutTimer); transport.removeListener("packet", onPacket); transport.removeListener("close", onTransportClose); @@ -384,9 +379,6 @@ export class Socket extends EventEmitter { clearTimeout(this.pingIntervalTimer); clearTimeout(this.pingTimeoutTimer); - clearInterval(this.checkIntervalTimer); - this.checkIntervalTimer = null; - clearTimeout(this.upgradeTimeoutTimer); // clean writeBuffer in next tick, so developers can still // grab the writeBuffer on 'close' event process.nextTick(() => { From 43c1c1c1e2a366317fe2684f6b31dd71ba7458a5 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 9 Nov 2023 12:13:15 +0100 Subject: [PATCH 07/30] refactor: simplify code --- lib/server.ts | 11 ----------- lib/transport.ts | 1 + 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/server.ts b/lib/server.ts index 5cec3d61..99b3f471 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -464,12 +464,6 @@ export abstract class BaseServer extends EventEmitter { } else if ("websocket" === transportName) { transport.perMessageDeflate = this.opts.perMessageDeflate; } - - if (req._query && req._query.b64) { - transport.supportsBinary = false; - } else { - transport.supportsBinary = true; - } } catch (e) { debug('error handshaking to transport "%s"', transportName); this.emit("connection_error", { @@ -862,11 +856,6 @@ export class Server extends BaseServer { websocket.removeListener("error", onUpgradeError); const transport = this.createTransport(req._query.transport, req); - if (req._query && req._query.b64) { - transport.supportsBinary = false; - } else { - transport.supportsBinary = true; - } transport.perMessageDeflate = this.opts.perMessageDeflate; client.maybeUpgrade(transport); } diff --git a/lib/transport.ts b/lib/transport.ts index 4068a31e..aa2a0bd3 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -52,6 +52,7 @@ export abstract class Transport extends EventEmitter { this.discarded = false; this.protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default this.parser = this.protocol === 4 ? parser_v4 : parser_v3; + this.supportsBinary = !(req._query && req._query.b64); } /** From 39937f8f4d8b5b3c48d118f66dbe59809cff2940 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 9 Nov 2023 12:18:55 +0100 Subject: [PATCH 08/30] refactor: minor cleanups --- lib/socket.ts | 27 +++++++++++---------------- lib/transport.ts | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index e7143285..6fc2f0d7 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -12,22 +12,24 @@ export interface SendOptions { compress?: boolean; } +type ReadyState = "opening" | "open" | "closing" | "closed"; + export class Socket extends EventEmitter { public readonly protocol: number; // TODO for the next major release: do not keep the reference to the first HTTP request, as it stays in memory public readonly request: IncomingMessage; public readonly remoteAddress: string; - public _readyState: string; + public _readyState: ReadyState = "opening"; public transport: Transport; private server: Server; - private upgrading: boolean; - private upgraded: boolean; - private writeBuffer: Packet[]; - private packetsFn: Array<() => void>; - private sentCallbackFn: any[]; - private cleanupFn: any[]; + private upgrading = false; + private upgraded = false; + private writeBuffer: Packet[] = []; + private packetsFn: Array<() => void> = []; + private sentCallbackFn: any[] = []; + private cleanupFn: any[] = []; private pingTimeoutTimer; private pingIntervalTimer; @@ -43,7 +45,7 @@ export class Socket extends EventEmitter { return this._readyState; } - set readyState(state) { + set readyState(state: ReadyState) { debug("readyState updated from %s to %s", this._readyState, state); this._readyState = state; } @@ -57,13 +59,6 @@ export class Socket extends EventEmitter { super(); this.id = id; this.server = server; - this.upgrading = false; - this.upgraded = false; - this.readyState = "opening"; - this.writeBuffer = []; - this.packetsFn = []; - this.sentCallbackFn = []; - this.cleanupFn = []; this.request = req; this.protocol = protocol; @@ -179,7 +174,7 @@ export class Socket extends EventEmitter { /** * Called upon transport error. * - * @param {Error} error object + * @param {Error} err - error object * @api private */ private onError(err) { diff --git a/lib/transport.ts b/lib/transport.ts index aa2a0bd3..b0777bca 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -15,13 +15,15 @@ const debug = debugModule("engine:transport"); function noop() {} +type ReadyState = "open" | "closing" | "closed"; + export abstract class Transport extends EventEmitter { public sid: string; - public writable: boolean; + public writable = false; public protocol: number; - protected _readyState: string; - protected discarded: boolean; + protected _readyState: ReadyState = "open"; + protected discarded = false; protected parser: any; protected req: IncomingMessage & { cleanup: Function }; protected supportsBinary: boolean; @@ -30,7 +32,7 @@ export abstract class Transport extends EventEmitter { return this._readyState; } - set readyState(state) { + set readyState(state: ReadyState) { debug( "readyState updated from %s to %s (%s)", this._readyState, @@ -43,13 +45,11 @@ export abstract class Transport extends EventEmitter { /** * Transport constructor. * - * @param {http.IncomingMessage} request + * @param {http.IncomingMessage} req * @api public */ constructor(req) { super(); - this.readyState = "open"; - this.discarded = false; this.protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default this.parser = this.protocol === 4 ? parser_v4 : parser_v3; this.supportsBinary = !(req._query && req._query.b64); @@ -67,7 +67,7 @@ export abstract class Transport extends EventEmitter { /** * Called with an incoming HTTP request. * - * @param {http.IncomingMessage} request + * @param {http.IncomingMessage} req * @api protected */ protected onRequest(req) { @@ -90,8 +90,8 @@ export abstract class Transport extends EventEmitter { /** * Called with a transport error. * - * @param {String} message error - * @param {Object} error description + * @param {String} msg - message error + * @param {Object} desc - error description * @api protected */ protected onError(msg: string, desc?) { From 09acb177a6e3bceea06643d403d9c99782a2a3d5 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 9 Nov 2023 18:01:33 +0100 Subject: [PATCH 09/30] ci: add Node.js 20 in the test matrix Reference: https://github.com/nodejs/Release --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0ef66e1..cc3f4ece 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: matrix: node-version: - 18 + - 20 steps: - name: Checkout repository From ff0fbfb61f2509ef302870cfd993344c1d035e7d Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 9 Nov 2023 18:07:45 +0100 Subject: [PATCH 10/30] chore(release): 6.5.4 Diff: https://github.com/socketio/engine.io/compare/6.5.3...6.5.4 --- CHANGELOG.md | 14 +++++++++++++- package.json | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac452545..8754a06d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2023 +- [6.5.4](#654-2023-11-09) (Nov 2023) - [6.5.3](#653-2023-10-06) (Oct 2023) - [6.5.2](#652-2023-08-01) (Aug 2023) - [6.5.1](#651-2023-06-27) (Jun 2023) @@ -51,12 +52,23 @@ # Release notes +## [6.5.4](https://github.com/socketio/engine.io/compare/6.5.3...6.5.4) (2023-11-09) + +This release contains some minor changes which should improve the memory usage of the server, notably [this](https://github.com/socketio/engine.io/commit/f27a6c35017e4eb37546949f754e09933102837a). + + +### Dependencies + +- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change) + + + ## [6.5.3](https://github.com/socketio/engine.io/compare/6.5.2...6.5.3) (2023-10-06) ### Bug Fixes -* improve compatibility with node16 module resolution ([#689](https://github.com/socketio/engine.io/issues/689)) ([c6bf8c0](https://github.com/socketio/engine.io/commit/c6bf8c0f571aad7a5917f43860c8c3d74a9b429b)), closes [/github.com/microsoft/TypeScript/issues/46770#issuecomment-966612103](https://github.com//github.com/microsoft/TypeScript/issues/46770/issues/issuecomment-966612103) +* improve compatibility with node16 module resolution ([#689](https://github.com/socketio/engine.io/issues/689)) ([c6bf8c0](https://github.com/socketio/engine.io/commit/c6bf8c0f571aad7a5917f43860c8c3d74a9b429b)) * **webtransport:** properly handle abruptly closed connections ([ff1c861](https://github.com/socketio/engine.io/commit/ff1c8615483bab25acc9cf04fb40339b0bd78812)) diff --git a/package.json b/package.json index 98ef51e0..aa53c4ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "engine.io", - "version": "6.5.3", + "version": "6.5.4", "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", From 0efa04b5841816d18b0c6ebf7c5f592f8382978a Mon Sep 17 00:00:00 2001 From: Jonathan Perret Date: Fri, 23 Feb 2024 10:21:43 +0100 Subject: [PATCH 11/30] fix(types): make socket.request writable (#697) Related: https://github.com/socketio/engine.io/issues/696 --- lib/socket.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/socket.ts b/lib/socket.ts index 6fc2f0d7..59814dee 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -17,7 +17,7 @@ type ReadyState = "opening" | "open" | "closing" | "closed"; export class Socket extends EventEmitter { public readonly protocol: number; // TODO for the next major release: do not keep the reference to the first HTTP request, as it stays in memory - public readonly request: IncomingMessage; + public request: IncomingMessage; public readonly remoteAddress: string; public _readyState: ReadyState = "opening"; From 79ea52dc0954c5929c34c3ed067cfe90692ea73e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:29:47 +0100 Subject: [PATCH 12/30] chore(deps): bump express from 4.18.2 to 4.19.2 in /examples/latency (#700) Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2) --- updated-dependencies: - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/latency/package-lock.json | 198 +++++++++++++++++------------ examples/latency/package.json | 2 +- 2 files changed, 118 insertions(+), 82 deletions(-) diff --git a/examples/latency/package-lock.json b/examples/latency/package-lock.json index 73658c3e..407cb591 100644 --- a/examples/latency/package-lock.json +++ b/examples/latency/package-lock.json @@ -127,12 +127,12 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -140,7 +140,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -287,12 +287,15 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsite": { @@ -361,9 +364,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "convert-source-map": { "version": "1.1.1", @@ -371,9 +374,9 @@ "integrity": "sha1-dOUYJHMFhBOwkN1zd3rLxKD/88w=" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -422,6 +425,16 @@ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", "integrity": "sha1-skbCuApXCkfBG+HZvRBw7IeLh84=" }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "defined": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", @@ -610,6 +623,19 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz", "integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg==" }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -689,16 +715,16 @@ "integrity": "sha1-dYSdz+k9EPsFfDAFWv29UdBqjiQ=" }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -726,15 +752,6 @@ "vary": "~1.1.2" }, "dependencies": { - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -743,28 +760,10 @@ "ms": "2.0.0" } }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" } } }, @@ -816,18 +815,20 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "glob": { @@ -839,12 +840,12 @@ "minimatch": "0.3" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "requires": { - "function-bind": "^1.1.1" + "get-intrinsic": "^1.1.3" } }, "has-cors": { @@ -852,11 +853,32 @@ "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "http-browserify": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.3.2.tgz", @@ -998,16 +1020,16 @@ "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" } }, "minimatch": { @@ -1081,9 +1103,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "object-keys": { "version": "0.4.0", @@ -1197,9 +1219,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -1339,6 +1361,19 @@ "send": "0.18.0" } }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1355,13 +1390,14 @@ "integrity": "sha1-GkEZbzwDM8SCMjWT1ohuzxU92YY=" }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "sigmund": { diff --git a/examples/latency/package.json b/examples/latency/package.json index d90c57c1..6737cb2b 100644 --- a/examples/latency/package.json +++ b/examples/latency/package.json @@ -5,7 +5,7 @@ "enchilada": "0.13.0", "engine.io": "^6.4.2", "engine.io-client": "^4.1.4", - "express": "^4.18.2", + "express": "^4.19.2", "smoothie": "1.19.0" } } From fc21c4a05f9d50d7efd62aa7a937fadce385e919 Mon Sep 17 00:00:00 2001 From: Jonathan Perret Date: Thu, 13 Jun 2024 23:02:22 +0200 Subject: [PATCH 13/30] fix: fix `websocket` and `webtransport` send callbacks (#699) With the `websocket` transport, the callbacks which indicate that the packets are actually written were not properly called. Example: ```js socket.send("hello", () => { // the message has been written to the underlying transport }); ``` The bug was caused by the `websocket` transport (and `webtransport` as well) having its `supportsFraming` property set to `true`, despite having been changed in [1] to emit a single `drain` event for each batch of messages written to the transport like the `polling` transport always did. Note that although [1] is partially reverted in [2], the new `drain` event behavior is preserved as called out in that commit's message. The `supportsFraming` attribute was introduced in [3] (amended by [4]) as a way to distinguish transports that emit one `drain` per message from those that emit one `drain` per batch. Since the delivery of `send` callbacks depends on matching `drain` events with `transport.send` calls, that distinction is vital to correct behavior. However, now that all transports have converged to "one `drain` per batch" behavior, this `supportsFraming` property can be retired (and the code for calling callbacks simplified). [1]: https://github.com/socketio/engine.io/pull/618 [2]: https://github.com/socketio/engine.io/commit/a65a047526401bebaa113a8c70d03f5d963eaa54 [3]: https://github.com/socketio/engine.io/pull/130 [4]: https://github.com/socketio/engine.io/pull/132 Related: https://github.com/socketio/engine.io/issues/698 --- lib/socket.ts | 38 ++++++++++++--------------------- lib/transport.ts | 5 ----- lib/transports-uws/polling.ts | 4 ---- lib/transports-uws/websocket.ts | 9 -------- lib/transports/polling.ts | 4 ---- lib/transports/websocket.ts | 9 -------- lib/transports/webtransport.ts | 4 ---- test/server.js | 14 ++++++++++-- test/webtransport.mjs | 15 +++++++++++++ 9 files changed, 41 insertions(+), 61 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index 59814dee..b8ff48aa 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -14,6 +14,8 @@ export interface SendOptions { type ReadyState = "opening" | "open" | "closing" | "closed"; +type SendCallback = (transport: Transport) => void; + export class Socket extends EventEmitter { public readonly protocol: number; // TODO for the next major release: do not keep the reference to the first HTTP request, as it stays in memory @@ -27,8 +29,8 @@ export class Socket extends EventEmitter { private upgrading = false; private upgraded = false; private writeBuffer: Packet[] = []; - private packetsFn: Array<() => void> = []; - private sentCallbackFn: any[] = []; + private packetsFn: SendCallback[] = []; + private sentCallbackFn: SendCallback[][] = []; private cleanupFn: any[] = []; private pingTimeoutTimer; private pingIntervalTimer; @@ -395,19 +397,11 @@ export class Socket extends EventEmitter { // the message was sent successfully, execute the callback const onDrain = () => { if (this.sentCallbackFn.length > 0) { - const seqFn = this.sentCallbackFn.splice(0, 1)[0]; - if ("function" === typeof seqFn) { - debug("executing send callback"); - seqFn(this.transport); - } else if (Array.isArray(seqFn)) { - debug("executing batch send callback"); - const l = seqFn.length; - let i = 0; - for (; i < l; i++) { - if ("function" === typeof seqFn[i]) { - seqFn[i](this.transport); - } - } + debug("executing batch send callback"); + const seqFn = this.sentCallbackFn.shift(); + const l = seqFn.length; + for (let i = 0; i < l; i++) { + seqFn[i](this.transport); } } }; @@ -428,7 +422,7 @@ export class Socket extends EventEmitter { * @return {Socket} for chaining * @api public */ - public send(data: RawData, options?: SendOptions, callback?: () => void) { + public send(data: RawData, options?: SendOptions, callback?: SendCallback) { this.sendPacket("message", data, options, callback); return this; } @@ -440,7 +434,7 @@ export class Socket extends EventEmitter { * @param options * @param callback */ - public write(data: RawData, options?: SendOptions, callback?: () => void) { + public write(data: RawData, options?: SendOptions, callback?: SendCallback) { this.sendPacket("message", data, options, callback); return this; } @@ -459,7 +453,7 @@ export class Socket extends EventEmitter { type: PacketType, data?: RawData, options: SendOptions = {}, - callback?: () => void + callback?: SendCallback ) { if ("function" === typeof options) { callback = options; @@ -485,7 +479,7 @@ export class Socket extends EventEmitter { this.writeBuffer.push(packet); // add send callback to object, if defined - if (callback) this.packetsFn.push(callback); + if ("function" === typeof callback) this.packetsFn.push(callback); this.flush(); } @@ -507,11 +501,7 @@ export class Socket extends EventEmitter { this.server.emit("flush", this, this.writeBuffer); const wbuf = this.writeBuffer; this.writeBuffer = []; - if (!this.transport.supportsFraming) { - this.sentCallbackFn.push(this.packetsFn); - } else { - this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn); - } + this.sentCallbackFn.push(this.packetsFn); this.packetsFn = []; this.transport.send(wbuf); this.emit("drain"); diff --git a/lib/transport.ts b/lib/transport.ts index b0777bca..6318fb01 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -137,11 +137,6 @@ export abstract class Transport extends EventEmitter { this.emit("close"); } - /** - * Advertise framing support. - */ - abstract get supportsFraming(); - /** * The name of the transport. */ diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 16e72c00..3fa30d90 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -42,10 +42,6 @@ export class Polling extends Transport { return "polling"; } - get supportsFraming() { - return false; - } - /** * Overrides onRequest. * diff --git a/lib/transports-uws/websocket.ts b/lib/transports-uws/websocket.ts index 9fc43ce3..4de3c23e 100644 --- a/lib/transports-uws/websocket.ts +++ b/lib/transports-uws/websocket.ts @@ -37,15 +37,6 @@ export class WebSocket extends Transport { return true; } - /** - * Advertise framing support. - * - * @api public - */ - get supportsFraming() { - return true; - } - /** * Writes a packet payload. * diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index e5ea24cf..598315b7 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -42,10 +42,6 @@ export class Polling extends Transport { return "polling"; } - get supportsFraming() { - return false; - } - /** * Overrides onRequest. * diff --git a/lib/transports/websocket.ts b/lib/transports/websocket.ts index 57c4a7b3..1f98fb7d 100644 --- a/lib/transports/websocket.ts +++ b/lib/transports/websocket.ts @@ -45,15 +45,6 @@ export class WebSocket extends Transport { return true; } - /** - * Advertise framing support. - * - * @api public - */ - get supportsFraming() { - return true; - } - /** * Writes a packet payload. * diff --git a/lib/transports/webtransport.ts b/lib/transports/webtransport.ts index 5922fab0..663a0f66 100644 --- a/lib/transports/webtransport.ts +++ b/lib/transports/webtransport.ts @@ -44,10 +44,6 @@ export class WebTransport extends Transport { return "webtransport"; } - get supportsFraming() { - return true; - } - async send(packets) { this.writable = false; diff --git a/test/server.js b/test/server.js index a373837e..d0301308 100644 --- a/test/server.js +++ b/test/server.js @@ -2759,13 +2759,23 @@ describe("server", () => { }); }); - it("should execute in multipart packet", (done) => { + it("should execute in multipart packet (websocket)", (done) => { const engine = listen((port) => { - const socket = new ClientSocket(`ws://localhost:${port}`); + const socket = new ClientSocket(`ws://localhost:${port}`, { + transports: ["websocket"], + }); let i = 0; let j = 0; engine.on("connection", (conn) => { + conn.send("d", (transport) => { + i++; + }); + + conn.send("c", (transport) => { + i++; + }); + conn.send("b", (transport) => { i++; }); diff --git a/test/webtransport.mjs b/test/webtransport.mjs index 3d54e554..10948ba2 100644 --- a/test/webtransport.mjs +++ b/test/webtransport.mjs @@ -373,6 +373,21 @@ describe("WebTransport", () => { }); }); + it("should invoke send callbacks (server to client)", (done) => { + setup({}, async ({ engine, h3Server, socket, reader }) => { + const messageCount = 4; + let receivedCallbacks = 0; + + for (let i = 0; i < messageCount; i++) { + socket.send("hello", () => { + if (++receivedCallbacks === messageCount) { + success(engine, h3Server, done); + } + }); + } + }); + }); + it("should send some binary data (client to server)", (done) => { setup({}, async ({ engine, h3Server, socket, writer }) => { socket.on("data", (data) => { From 8955eb7b75baa6bbc116f36a784a0feffc95b22d Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 13 Jun 2024 23:18:35 +0200 Subject: [PATCH 14/30] docs(examples): add some examples to track the memory usage Related: https://github.com/socketio/engine.io/commit/fc21c4a05f9d50d7efd62aa7a937fadce385e919 --- examples/memory-usage-webtransport/.gitignore | 2 + examples/memory-usage-webtransport/client.js | 35 ++ .../generate_cert.sh | 6 + .../package-lock.json | 530 ++++++++++++++++++ .../memory-usage-webtransport/package.json | 11 + examples/memory-usage-webtransport/server.js | 65 +++ examples/memory-usage/.gitignore | 1 + examples/memory-usage/client.js | 15 + examples/memory-usage/package-lock.json | 90 +++ examples/memory-usage/package.json | 10 + examples/memory-usage/server.js | 41 ++ 11 files changed, 806 insertions(+) create mode 100755 examples/memory-usage-webtransport/.gitignore create mode 100644 examples/memory-usage-webtransport/client.js create mode 100755 examples/memory-usage-webtransport/generate_cert.sh create mode 100644 examples/memory-usage-webtransport/package-lock.json create mode 100644 examples/memory-usage-webtransport/package.json create mode 100644 examples/memory-usage-webtransport/server.js create mode 100755 examples/memory-usage/.gitignore create mode 100644 examples/memory-usage/client.js create mode 100644 examples/memory-usage/package-lock.json create mode 100644 examples/memory-usage/package.json create mode 100644 examples/memory-usage/server.js diff --git a/examples/memory-usage-webtransport/.gitignore b/examples/memory-usage-webtransport/.gitignore new file mode 100755 index 00000000..cfb6da4f --- /dev/null +++ b/examples/memory-usage-webtransport/.gitignore @@ -0,0 +1,2 @@ +*.pem +*.log diff --git a/examples/memory-usage-webtransport/client.js b/examples/memory-usage-webtransport/client.js new file mode 100644 index 00000000..db81dc19 --- /dev/null +++ b/examples/memory-usage-webtransport/client.js @@ -0,0 +1,35 @@ +import { Socket } from "engine.io-client"; +import { X509Certificate } from "crypto"; +import { readFileSync } from "node:fs"; +import { WebTransport } from "@fails-components/webtransport"; + +const cert = readFileSync("./cert.pem"); +const CLIENTS_COUNT = 100; + +global.WebTransport = WebTransport; + +for (let i = 0; i < CLIENTS_COUNT; i++) { + const socket = new Socket("ws://localhost:3000", { + transports: ["webtransport"], + transportOptions: { + webtransport: { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: Buffer.from( + new X509Certificate(cert).fingerprint256 + .split(":") + .map((el) => parseInt(el, 16)) + ), + }, + ], + }, + }, + }); + + socket.on("open", () => {}); + + socket.on("message", () => {}); + + socket.on("close", (reason) => {}); +} diff --git a/examples/memory-usage-webtransport/generate_cert.sh b/examples/memory-usage-webtransport/generate_cert.sh new file mode 100755 index 00000000..bbc1e6ea --- /dev/null +++ b/examples/memory-usage-webtransport/generate_cert.sh @@ -0,0 +1,6 @@ +#!/bin/bash +openssl req -new -x509 -nodes \ + -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ + -days 14 \ + -out cert.pem -keyout key.pem \ + -subj '/CN=127.0.0.1' diff --git a/examples/memory-usage-webtransport/package-lock.json b/examples/memory-usage-webtransport/package-lock.json new file mode 100644 index 00000000..99f3318c --- /dev/null +++ b/examples/memory-usage-webtransport/package-lock.json @@ -0,0 +1,530 @@ +{ + "name": "memory-usage-webtransport", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "memory-usage-webtransport", + "version": "0.0.1", + "dependencies": { + "@fails-components/webtransport": "^0.3.1", + "engine.io-client": "^6.5.3" + } + }, + "node_modules/@fails-components/webtransport": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@fails-components/webtransport/-/webtransport-0.3.1.tgz", + "integrity": "sha512-pbFhPSDCg1vLXiUMq3Q9D9O/tI8g4KpuPFAnPREAYoOw/wU4f/U4e93g0wnxIKyrmnz0ZM7lzf3VMJuHP8SzYw==", + "hasInstallScript": true, + "dependencies": { + "@types/debug": "^4.1.7", + "bindings": "^1.5.0", + "debug": "^4.3.4", + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": ">=16.5" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "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==", + "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/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "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==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "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==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "license": "MIT", + "engines": { + "node": ">=10.0.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==", + "engines": { + "node": ">=6" + } + }, + "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==" + }, + "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==" + }, + "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==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "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==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "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==" + }, + "node_modules/node-abi": { + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", + "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "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==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "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/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "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/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "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==", + "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/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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==", + "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/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/examples/memory-usage-webtransport/package.json b/examples/memory-usage-webtransport/package.json new file mode 100644 index 00000000..5f0b7bdd --- /dev/null +++ b/examples/memory-usage-webtransport/package.json @@ -0,0 +1,11 @@ +{ + "name": "memory-usage-webtransport", + "version": "0.0.1", + "private": true, + "description": "", + "type": "module", + "dependencies": { + "@fails-components/webtransport": "^0.3.1", + "engine.io-client": "^6.5.3" + } +} diff --git a/examples/memory-usage-webtransport/server.js b/examples/memory-usage-webtransport/server.js new file mode 100644 index 00000000..2128ec38 --- /dev/null +++ b/examples/memory-usage-webtransport/server.js @@ -0,0 +1,65 @@ +import { readFileSync } from "node:fs"; +import { Http3Server } from "@fails-components/webtransport"; +import { Server } from "../../build/engine.io.js"; + +const key = readFileSync("./key.pem"); +const cert = readFileSync("./cert.pem"); + +const PACKETS_COUNT = 100; +const PACKET_SIZE = 100; + +const engine = new Server({ + transports: ["polling", "websocket", "webtransport"], +}); + +const h3Server = new Http3Server({ + port: 3000, + host: "0.0.0.0", + secret: "changeit", + cert, + privKey: key, +}); + +const packets = []; + +for (let i = 0; i < PACKETS_COUNT; i++) { + packets.push("a".repeat(PACKET_SIZE)); +} + +setInterval(() => { + Object.keys(engine.clients).forEach((id) => { + const client = engine.clients[id]; + packets.forEach((packet) => { + client.send(packet); + }); + }); +}, 10000); + +function formatSize(val) { + return Math.floor(val / 1024); +} + +setInterval(() => { + const mem = process.memoryUsage(); + console.log( + `${Math.floor(process.uptime())}; ${formatSize(mem.heapUsed)}; ${formatSize( + mem.heapTotal + )}` + ); +}, 1000); + +h3Server.startServer(); + +(async () => { + // const stream = await h3Server.sessionStream("/engine.io/"); + 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); + } +})(); diff --git a/examples/memory-usage/.gitignore b/examples/memory-usage/.gitignore new file mode 100755 index 00000000..397b4a76 --- /dev/null +++ b/examples/memory-usage/.gitignore @@ -0,0 +1 @@ +*.log diff --git a/examples/memory-usage/client.js b/examples/memory-usage/client.js new file mode 100644 index 00000000..90a1ebea --- /dev/null +++ b/examples/memory-usage/client.js @@ -0,0 +1,15 @@ +import { Socket } from "engine.io-client"; + +const CLIENTS_COUNT = 100; + +for (let i = 0; i < CLIENTS_COUNT; i++) { + const socket = new Socket("ws://localhost:3000", { + transports: ["websocket"], + }); + + socket.on("open", () => {}); + + socket.on("message", () => {}); + + socket.on("close", (reason) => {}); +} diff --git a/examples/memory-usage/package-lock.json b/examples/memory-usage/package-lock.json new file mode 100644 index 00000000..7bf05315 --- /dev/null +++ b/examples/memory-usage/package-lock.json @@ -0,0 +1,90 @@ +{ + "name": "memory-usage", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "memory-usage", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "engine.io-client": "^6.5.3" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/examples/memory-usage/package.json b/examples/memory-usage/package.json new file mode 100644 index 00000000..5cd84c27 --- /dev/null +++ b/examples/memory-usage/package.json @@ -0,0 +1,10 @@ +{ + "name": "memory-usage", + "version": "0.0.1", + "private": true, + "description": "", + "type": "module", + "dependencies": { + "engine.io-client": "^6.5.3" + } +} diff --git a/examples/memory-usage/server.js b/examples/memory-usage/server.js new file mode 100644 index 00000000..c051220b --- /dev/null +++ b/examples/memory-usage/server.js @@ -0,0 +1,41 @@ +import { createServer } from "node:http"; +import { Server } from "../../build/engine.io.js"; + +const EMIT_INTERVAL_MS = 10_000; +const PACKETS_COUNT = 100; +const PACKET_SIZE = 100; + +const httpServer = createServer(); +const engine = new Server(); + +engine.attach(httpServer); + +const packets = []; + +for (let i = 0; i < PACKETS_COUNT; i++) { + packets.push("a".repeat(PACKET_SIZE)); +} + +setInterval(() => { + Object.keys(engine.clients).forEach((id) => { + const client = engine.clients[id]; + packets.forEach((packet) => { + client.send(packet); + }); + }); +}, EMIT_INTERVAL_MS); + +function formatSize(val) { + return Math.floor(val / 1024); +} + +setInterval(() => { + const mem = process.memoryUsage(); + console.log( + `${Math.floor(process.uptime())}; ${formatSize(mem.heapUsed)}; ${formatSize( + mem.heapTotal + )}` + ); +}, 1000); + +httpServer.listen(3000); From ef1c4c8bb771f007f3db3131af59f2027ccb181c Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 13 Jun 2024 23:55:16 +0200 Subject: [PATCH 15/30] refactor: remove the wsPreEncoded option The wsPreEncoded option was added in the `socket.io-adapter` package when broadcasting a message to multiple clients. It was removed in [1] and is now superseded by the `wsPreEncodedFrame` option, which directly computes the WebSocket frame once for all clients (see [2]). [1]: https://github.com/socketio/socket.io-adapter/commit/88eee5948aba94f999405239025f29c754a002e2 [2]: https://github.com/socketio/socket.io-adapter/commit/5f7b47d40f9daabe4e3c321eda620bbadfe5ce96 --- lib/transports/websocket.ts | 4 +--- test/server.js | 19 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/lib/transports/websocket.ts b/lib/transports/websocket.ts index 1f98fb7d..83d4de57 100644 --- a/lib/transports/websocket.ts +++ b/lib/transports/websocket.ts @@ -85,9 +85,7 @@ export class WebSocket extends Transport { this.socket.send(data, opts, onSent); }; - if (packet.options && typeof packet.options.wsPreEncoded === "string") { - send(packet.options.wsPreEncoded); - } else if (this._canSendPreEncodedFrame(packet)) { + if (this._canSendPreEncodedFrame(packet)) { // the WebSocket frame was computed with WebSocket.Sender.frame() // see https://github.com/websockets/ws/issues/617#issuecomment-283002469 this.socket._sender.sendFrame(packet.options.wsPreEncodedFrame, onSent); diff --git a/test/server.js b/test/server.js index d0301308..cacb6774 100644 --- a/test/server.js +++ b/test/server.js @@ -2889,25 +2889,6 @@ describe("server", () => { }); describe("pre-encoded content", () => { - it("should use the pre-encoded content", (done) => { - engine = listen((port) => { - client = new ClientSocket(`ws://localhost:${port}`, { - transports: ["websocket"], - }); - - engine.on("connection", (conn) => { - conn.send("test", { - wsPreEncoded: "4test pre-encoded", - }); - }); - - client.on("message", (msg) => { - expect(msg).to.be("test pre-encoded"); - done(); - }); - }); - }); - it("should use the pre-encoded frame", function (done) { if (process.env.EIO_WS_ENGINE === "uws") { return this.skip(); From 407c3ad236eee294b34f929501c8b4c8c68d08ac Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 14 Jun 2024 00:26:10 +0200 Subject: [PATCH 16/30] refactor: simplify the handling of the "drain" event The two event handlers are merged into one. --- lib/socket.ts | 50 ++++++++++++++++++++------------------------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index b8ff48aa..bac970c2 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -223,25 +223,40 @@ export class Socket extends EventEmitter { private setTransport(transport) { const onError = this.onError.bind(this); const onPacket = this.onPacket.bind(this); - const flush = this.flush.bind(this); + const onDrain = this.onDrain.bind(this); const onClose = this.onClose.bind(this, "transport close"); this.transport = transport; this.transport.once("error", onError); this.transport.on("packet", onPacket); - this.transport.on("drain", flush); + this.transport.on("drain", onDrain); this.transport.once("close", onClose); - // this function will manage packet events (also message callbacks) - this.setupSendCallback(); this.cleanupFn.push(function () { transport.removeListener("error", onError); transport.removeListener("packet", onPacket); - transport.removeListener("drain", flush); + transport.removeListener("drain", onDrain); transport.removeListener("close", onClose); }); } + /** + * Upon transport "drain" event + * + * @private + */ + private onDrain() { + this.flush(); + + if (this.sentCallbackFn.length > 0) { + debug("executing batch send callback"); + const seqFn = this.sentCallbackFn.shift(); + for (let i = 0; i < seqFn.length; i++) { + seqFn[i](this.transport); + } + } + } + /** * Upgrades socket to the given transport * @@ -388,31 +403,6 @@ export class Socket extends EventEmitter { } } - /** - * Setup and manage send callback - * - * @api private - */ - private setupSendCallback() { - // the message was sent successfully, execute the callback - const onDrain = () => { - if (this.sentCallbackFn.length > 0) { - debug("executing batch send callback"); - const seqFn = this.sentCallbackFn.shift(); - const l = seqFn.length; - for (let i = 0; i < l; i++) { - seqFn[i](this.transport); - } - } - }; - - this.transport.on("drain", onDrain); - - this.cleanupFn.push(() => { - this.transport.removeListener("drain", onDrain); - }); - } - /** * Sends a message packet. * From 62f59b6cf3b2f073820d0a8cff70682bc1d4ba9e Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 14 Jun 2024 01:07:48 +0200 Subject: [PATCH 17/30] refactor: remove unnecessary array allocation If the `packetsFn` array is empty, there is no need to allocate one new array. --- lib/socket.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index bac970c2..300fcb19 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -251,8 +251,10 @@ export class Socket extends EventEmitter { if (this.sentCallbackFn.length > 0) { debug("executing batch send callback"); const seqFn = this.sentCallbackFn.shift(); - for (let i = 0; i < seqFn.length; i++) { - seqFn[i](this.transport); + if (seqFn) { + for (let i = 0; i < seqFn.length; i++) { + seqFn[i](this.transport); + } } } } @@ -491,8 +493,14 @@ export class Socket extends EventEmitter { this.server.emit("flush", this, this.writeBuffer); const wbuf = this.writeBuffer; this.writeBuffer = []; - this.sentCallbackFn.push(this.packetsFn); - this.packetsFn = []; + + if (this.packetsFn.length) { + this.sentCallbackFn.push(this.packetsFn); + this.packetsFn = []; + } else { + this.sentCallbackFn.push(null); + } + this.transport.send(wbuf); this.emit("drain"); this.server.emit("drain", this); From 9a68c8ce93cc1bc0bc1a30548558da49860f4acd Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 17 Jun 2024 17:41:41 +0200 Subject: [PATCH 18/30] perf(websocket): use bound callbacks Instead of allocating one temporary function for each WebSocket `send()` call. Regarding the test removal, the permessage-deflate threshold was implemented in the "ws" package in [1], so it's not needed anymore. [1]: https://github.com/websockets/ws/commit/6b3904b42dbd48aed2e0d8d599787cca04f05384 --- lib/transports/websocket.ts | 81 +++++++++++++++++-------------------- test/server.js | 62 ---------------------------- 2 files changed, 38 insertions(+), 105 deletions(-) diff --git a/lib/transports/websocket.ts b/lib/transports/websocket.ts index 83d4de57..e4cc2623 100644 --- a/lib/transports/websocket.ts +++ b/lib/transports/websocket.ts @@ -1,5 +1,6 @@ import { Transport } from "../transport"; import debugModule from "debug"; +import type { Packet, RawData } from "engine.io-parser"; const debug = debugModule("engine:ws"); @@ -45,52 +46,27 @@ export class WebSocket extends Transport { return true; } - /** - * Writes a packet payload. - * - * @param {Array} packets - * @api private - */ - send(packets) { + send(packets: Packet[]) { this.writable = false; for (let i = 0; i < packets.length; i++) { const packet = packets[i]; const isLast = i + 1 === packets.length; - // always creates a new object since ws modifies it - const opts: { compress?: boolean } = {}; - if (packet.options) { - opts.compress = packet.options.compress; - } - - const onSent = (err) => { - if (err) { - return this.onError("write error", err.stack); - } else if (isLast) { - this.writable = true; - this.emit("drain"); - } - }; - - const send = (data) => { - if (this.perMessageDeflate) { - const len = - "string" === typeof data ? Buffer.byteLength(data) : data.length; - if (len < this.perMessageDeflate.threshold) { - opts.compress = false; - } - } - debug('writing "%s"', data); - this.socket.send(data, opts, onSent); - }; - if (this._canSendPreEncodedFrame(packet)) { // the WebSocket frame was computed with WebSocket.Sender.frame() // see https://github.com/websockets/ws/issues/617#issuecomment-283002469 - this.socket._sender.sendFrame(packet.options.wsPreEncodedFrame, onSent); + this.socket._sender.sendFrame( + // @ts-ignore + packet.options.wsPreEncodedFrame, + isLast ? this._onSentLast : this._onSent + ); } else { - this.parser.encodePacket(packet, this.supportsBinary, send); + this.parser.encodePacket( + packet, + this.supportsBinary, + isLast ? this._doSendLast : this._doSend + ); } } } @@ -100,20 +76,39 @@ export class WebSocket extends Transport { * @param packet * @private */ - private _canSendPreEncodedFrame(packet) { + private _canSendPreEncodedFrame(packet: Packet) { return ( !this.perMessageDeflate && typeof this.socket?._sender?.sendFrame === "function" && + // @ts-ignore packet.options?.wsPreEncodedFrame !== undefined ); } - /** - * Closes the transport. - * - * @api private - */ - doClose(fn) { + private _doSend = (data: RawData) => { + this.socket.send(data, this._onSent); + }; + + private _doSendLast = (data: RawData) => { + this.socket.send(data, this._onSentLast); + }; + + private _onSent = (err?: Error) => { + if (err) { + this.onError("write error", err.stack); + } + }; + + private _onSentLast = (err?: Error) => { + if (err) { + this.onError("write error", err.stack); + } else { + this.writable = true; + this.emit("drain"); + } + }; + + doClose(fn?: () => void) { debug("closing"); this.socket.close(); fn && fn(); diff --git a/test/server.js b/test/server.js index cacb6774..c7c914bb 100644 --- a/test/server.js +++ b/test/server.js @@ -3337,68 +3337,6 @@ describe("server", () => { }); }); - describe("permessage-deflate", () => { - it("should set threshold", function (done) { - if (process.env.EIO_WS_ENGINE === "uws") { - return this.skip(); - } - const engine = listen( - { transports: ["websocket"], perMessageDeflate: { threshold: 0 } }, - (port) => { - engine.on("connection", (conn) => { - const socket = conn.transport.socket; - const send = socket.send; - socket.send = (data, opts, callback) => { - socket.send = send; - socket.send(data, opts, callback); - - expect(opts.compress).to.be(true); - conn.close(); - done(); - }; - - const buf = Buffer.allocUnsafe(100); - for (let i = 0; i < buf.length; i++) buf[i] = i % 0xff; - conn.send(buf, { compress: true }); - }); - new ClientSocket(`http://localhost:${port}`, { - transports: ["websocket"], - }); - } - ); - }); - - it("should not compress when the byte size is below threshold", function (done) { - if (process.env.EIO_WS_ENGINE === "uws") { - return this.skip(); - } - const engine = listen( - { transports: ["websocket"], perMessageDeflate: true }, - (port) => { - engine.on("connection", (conn) => { - const socket = conn.transport.socket; - const send = socket.send; - socket.send = (data, opts, callback) => { - socket.send = send; - socket.send(data, opts, callback); - - expect(opts.compress).to.be(false); - conn.close(); - done(); - }; - - const buf = Buffer.allocUnsafe(100); - for (let i = 0; i < buf.length; i++) buf[i] = i % 0xff; - conn.send(buf, { compress: true }); - }); - new ClientSocket(`http://localhost:${port}`, { - transports: ["websocket"], - }); - } - ); - }); - }); - describe("extraHeaders", function () { this.timeout(5000); From b0721e64070c9f164fa3ef774bc785c391d7eb69 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 17 Jun 2024 17:50:39 +0200 Subject: [PATCH 19/30] ci: upgrade to actions/checkout@4 and actions/setup-node@4 Reference: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/ --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc3f4ece..79c974a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} From 3c4d314998c0072e477e7614e6a1fc91c61a6dd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:57:32 +0200 Subject: [PATCH 20/30] chore(deps): bump ws from 8.11.0 to 8.17.1 (#702) Bumps [ws](https://github.com/websockets/ws) from 8.11.0 to 8.17.1. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.11.0...8.17.1) --- updated-dependencies: - dependency-name: ws dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 48 +++++++++++++++++++++++++++++++++++++---------- package.json | 2 +- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7508dec7..0b5d814a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "engine.io", - "version": "6.5.1", + "version": "6.5.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "engine.io", - "version": "6.5.1", + "version": "6.5.4", "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", @@ -18,7 +18,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "devDependencies": { "@fails-components/webtransport": "^0.1.7", @@ -828,6 +828,27 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/engine.io-parser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", @@ -2461,15 +2482,15 @@ "dev": true }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -3131,6 +3152,13 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==", "dev": true + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "requires": {} } } }, @@ -4385,9 +4413,9 @@ "dev": true }, "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "requires": {} }, "xmlhttprequest-ssl": { diff --git a/package.json b/package.json index aa53c4ac..a0723d17 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "devDependencies": { "@fails-components/webtransport": "^0.1.7", From d3f45dca38f84fed68ac710b6b510f55145b8731 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 18 Jun 2024 10:47:10 +0200 Subject: [PATCH 21/30] docs(changelog): add release notes for versions 3.6.2 and 6.5.5 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8754a06d..7d76710e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # History +## 2024 + +- [6.5.5](#655-2024-06-18) (Jun 2024) (from the [6.5.x](https://github.com/socketio/engine.io/tree/6.5.x) branch) +- [3.6.2](#362-2024-06-18) (Jun 2024) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) + ## 2023 - [6.5.4](#654-2023-11-09) (Nov 2023) @@ -52,6 +57,34 @@ # Release notes +## [6.5.5](https://github.com/socketio/engine.io/compare/6.5.4...6.5.5) (2024-06-18) + +This release contains a bump of the `ws` dependency, which includes an important [security fix](https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c). + +Advisory: https://github.com/advisories/GHSA-3h5v-q93c-6h6q + +### Bug Fixes + +* **types:** make socket.request writable ([#697](https://github.com/socketio/engine.io/issues/697)) ([0efa04b](https://github.com/socketio/engine.io/commit/0efa04b5841816d18b0c6ebf7c5f592f8382978a)) + +### Dependencies + +- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) ([diff](https://github.com/websockets/ws/compare/8.11.0...8.17.1)) + + + +## [3.6.2](https://github.com/socketio/engine.io/compare/3.6.1...3.6.2) (2024-06-18) + +This release contains a bump of the `ws` dependency, which includes an important [security fix](https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c). + +Advisory: https://github.com/advisories/GHSA-3h5v-q93c-6h6q + +### Dependencies + +- [`ws@~7.5.10`](https://github.com/websockets/ws/releases/tag/7.5.10) ([diff](https://github.com/websockets/ws/compare/7.4.2...7.5.10)) + + + ## [6.5.4](https://github.com/socketio/engine.io/compare/6.5.3...6.5.4) (2023-11-09) This release contains some minor changes which should improve the memory usage of the server, notably [this](https://github.com/socketio/engine.io/commit/f27a6c35017e4eb37546949f754e09933102837a). From 5359bae683e2a25742bd4989d0355a8fc10d294e Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 18 Jun 2024 17:14:41 +0200 Subject: [PATCH 22/30] perf: do not reset the hearbeat timer on each packet This behavior was added in [1]. However, there are two problems: - a new timer is allocated every time a packet is received, which is wasteful - the next heartbeat is not actually delayed, since it's the timeout timer which gets reset, and not the interval timer Note: delaying the next heartbeat would be a breaking change. [1]: https://github.com/socketio/engine.io/commit/be7b4e7478132a9409603327b27d1aa1970dd1d9 --- lib/socket.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index 300fcb19..8c35b7a3 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -135,12 +135,6 @@ export class Socket extends EventEmitter { debug(`received packet ${packet.type}`); this.emit("packet", packet); - // Reset ping timeout on any packet, incoming data is a good sign of - // other side's liveness - this.resetPingTimeout( - this.server.opts.pingInterval + this.server.opts.pingTimeout - ); - switch (packet.type) { case "ping": if (this.transport.protocol !== 3) { @@ -148,6 +142,7 @@ export class Socket extends EventEmitter { return; } debug("got ping"); + this.pingTimeoutTimer.refresh(); this.sendPacket("pong"); this.emit("heartbeat"); break; @@ -158,6 +153,7 @@ export class Socket extends EventEmitter { return; } debug("got pong"); + clearTimeout(this.pingTimeoutTimer); this.pingIntervalTimer.refresh(); this.emit("heartbeat"); break; From f521cbab6df2e6d92a61a40240ea23761b36ba5f Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 18 Jun 2024 17:32:06 +0200 Subject: [PATCH 23/30] refactor: simplify the heartbeat code --- lib/socket.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index 8c35b7a3..acdd0a61 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -112,9 +112,7 @@ export class Socket extends EventEmitter { if (this.protocol === 3) { // in protocol v3, the client sends a ping, and the server answers with a pong - this.resetPingTimeout( - this.server.opts.pingInterval + this.server.opts.pingTimeout - ); + this.resetPingTimeout(); } else { // in protocol v4, the server sends a ping, and the client answers with a pong this.schedulePing(); @@ -193,7 +191,7 @@ export class Socket extends EventEmitter { this.server.opts.pingTimeout ); this.sendPacket("ping"); - this.resetPingTimeout(this.server.opts.pingTimeout); + this.resetPingTimeout(); }, this.server.opts.pingInterval); } @@ -202,12 +200,17 @@ export class Socket extends EventEmitter { * * @api private */ - private resetPingTimeout(timeout) { + private resetPingTimeout() { clearTimeout(this.pingTimeoutTimer); - this.pingTimeoutTimer = setTimeout(() => { - if (this.readyState === "closed") return; - this.onClose("ping timeout"); - }, timeout); + this.pingTimeoutTimer = setTimeout( + () => { + if (this.readyState === "closed") return; + this.onClose("ping timeout"); + }, + this.protocol === 3 + ? this.server.opts.pingInterval + this.server.opts.pingTimeout + : this.server.opts.pingTimeout + ); } /** From 6b9e3e458e7158f291ae4d6e1eccd7deb674bb89 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 18 Jun 2024 17:37:12 +0200 Subject: [PATCH 24/30] refactor: improve types --- lib/server.ts | 7 ++++--- lib/socket.ts | 6 +++--- lib/userver.ts | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/server.ts b/lib/server.ts index 99b3f471..ba096f2e 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -160,7 +160,8 @@ function parseSessionId(data: string) { export abstract class BaseServer extends EventEmitter { public opts: ServerOptions; - protected clients: any; + // TODO for the next major release: use a Map instead + protected clients: Record; public clientsCount: number; protected middlewares: Middleware[] = []; @@ -590,7 +591,7 @@ export abstract class BaseServer extends EventEmitter { debug("upgrading existing transport"); const transport = new WebTransport(session, stream, reader); - client.maybeUpgrade(transport); + client._maybeUpgrade(transport); } } @@ -857,7 +858,7 @@ export class Server extends BaseServer { const transport = this.createTransport(req._query.transport, req); transport.perMessageDeflate = this.opts.perMessageDeflate; - client.maybeUpgrade(transport); + client._maybeUpgrade(transport); } } else { const closeConnection = (errorCode, errorContext) => diff --git a/lib/socket.ts b/lib/socket.ts index acdd0a61..0febe90e 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -26,8 +26,8 @@ export class Socket extends EventEmitter { public transport: Transport; private server: Server; - private upgrading = false; - private upgraded = false; + /* private */ upgrading = false; + /* private */ upgraded = false; private writeBuffer: Packet[] = []; private packetsFn: SendCallback[] = []; private sentCallbackFn: SendCallback[][] = []; @@ -264,7 +264,7 @@ export class Socket extends EventEmitter { * @param {Transport} transport * @api private */ - private maybeUpgrade(transport) { + /* private */ _maybeUpgrade(transport: Transport) { debug( 'might upgrade socket transport from "%s" to "%s"', this.transport.name, diff --git a/lib/userver.ts b/lib/userver.ts index 98380fbd..9d1eeb77 100644 --- a/lib/userver.ts +++ b/lib/userver.ts @@ -137,6 +137,7 @@ export class uServer extends BaseServer { if (req._query.sid) { debug("setting new request for existing client"); + // @ts-ignore this.clients[req._query.sid].transport.onRequest(req); } else { const closeConnection = (errorCode, errorContext) => @@ -194,7 +195,7 @@ export class uServer extends BaseServer { } else { debug("upgrading existing transport"); transport = this.createTransport(req._query.transport, req); - client.maybeUpgrade(transport); + client._maybeUpgrade(transport); } } else { transport = await this.handshake( From 56c4664bcf84e0afc884c98e26ad95f806e932c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:18:35 +0200 Subject: [PATCH 25/30] chore(deps-dev): bump braces from 3.0.2 to 3.0.3 (#701) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b5d814a..cad4c345 100644 --- a/package-lock.json +++ b/package-lock.json @@ -408,12 +408,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1018,9 +1018,9 @@ "dev": true }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2902,12 +2902,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -3350,9 +3350,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" From afd29341ee18415baa9d54e324488882e8e51741 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:18:57 +0200 Subject: [PATCH 26/30] chore(dev-deps): bump ws and engine.io-client in /examples/memory-usage (#703) Bumps [ws](https://github.com/websockets/ws) to 8.17.1 and updates ancestor dependency [engine.io-client](https://github.com/socketio/engine.io-client). These dependencies need to be updated together. Updates `ws` from 8.11.0 to 8.17.1 - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.11.0...8.17.1) Updates `engine.io-client` from 6.5.3 to 6.5.4 - [Release notes](https://github.com/socketio/engine.io-client/releases) - [Changelog](https://github.com/socketio/engine.io-client/blob/6.5.4/CHANGELOG.md) - [Commits](https://github.com/socketio/engine.io-client/compare/6.5.3...6.5.4) --- updated-dependencies: - dependency-name: ws dependency-type: indirect - dependency-name: engine.io-client dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/memory-usage/package-lock.json | 23 +++++++++++------------ examples/memory-usage/package.json | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/memory-usage/package-lock.json b/examples/memory-usage/package-lock.json index 7bf05315..1acf8dea 100644 --- a/examples/memory-usage/package-lock.json +++ b/examples/memory-usage/package-lock.json @@ -1,15 +1,14 @@ { "name": "memory-usage", - "version": "1.0.0", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "memory-usage", - "version": "1.0.0", - "license": "ISC", + "version": "0.0.1", "dependencies": { - "engine.io-client": "^6.5.3" + "engine.io-client": "^6.5.4" } }, "node_modules/@socket.io/component-emitter": { @@ -34,14 +33,14 @@ } }, "node_modules/engine.io-client": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", - "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0", + "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.0.0" } }, @@ -59,15 +58,15 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/examples/memory-usage/package.json b/examples/memory-usage/package.json index 5cd84c27..030c8769 100644 --- a/examples/memory-usage/package.json +++ b/examples/memory-usage/package.json @@ -5,6 +5,6 @@ "description": "", "type": "module", "dependencies": { - "engine.io-client": "^6.5.3" + "engine.io-client": "^6.5.4" } } From 362bc78191c607e6b7c7f2b2e7e7ddb2fe53101c Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 21 Jun 2024 11:47:41 +0200 Subject: [PATCH 27/30] fix: properly call the send callback during upgrade The "drain" event (added in [1]) had two different meanings: - the transport is ready to be written - the packets are sent over the wire For the WebSocket and the WebTransport transports, those two events happen at the same time, but this is not the case for the HTTP long-polling transport: - the transport is ready to be written when the client sends a GET request - the packets are sent over the wire when the server responds to the GET request Which caused an issue with send callbacks during an upgrade, since the packets were written but the client would not open a new GET request. There are now two distinct events: "ready" and "drain" Related: https://github.com/socketio/engine.io/issues/695 [1]: https://github.com/socketio/engine.io/commit/2a93f06e276f3adb76e494ae9198da38799d493c --- lib/socket.ts | 5 +++-- lib/transports-uws/polling.ts | 3 ++- lib/transports-uws/websocket.ts | 3 ++- lib/transports/polling.ts | 3 ++- lib/transports/websocket.ts | 3 ++- lib/transports/webtransport.ts | 3 ++- lib/userver.ts | 2 +- test/server.js | 23 +++++++++++++++++++++++ 8 files changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index 0febe90e..1dfa460b 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -221,18 +221,21 @@ export class Socket extends EventEmitter { */ private setTransport(transport) { const onError = this.onError.bind(this); + const onReady = () => this.flush(); const onPacket = this.onPacket.bind(this); const onDrain = this.onDrain.bind(this); const onClose = this.onClose.bind(this, "transport close"); this.transport = transport; this.transport.once("error", onError); + this.transport.on("ready", onReady); this.transport.on("packet", onPacket); this.transport.on("drain", onDrain); this.transport.once("close", onClose); this.cleanupFn.push(function () { transport.removeListener("error", onError); + transport.removeListener("ready", onReady); transport.removeListener("packet", onPacket); transport.removeListener("drain", onDrain); transport.removeListener("close", onClose); @@ -245,8 +248,6 @@ export class Socket extends EventEmitter { * @private */ private onDrain() { - this.flush(); - if (this.sentCallbackFn.length > 0) { debug("executing batch send callback"); const seqFn = this.sentCallbackFn.shift(); diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 3fa30d90..01a82f6e 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -97,7 +97,7 @@ export class Polling extends Transport { res.onAborted(onClose); this.writable = true; - this.emit("drain"); + this.emit("ready"); // if we're still writable but had a pending close, trigger an empty send if (this.writable && this.shouldClose) { @@ -291,6 +291,7 @@ export class Polling extends Transport { debug('writing "%s"', data); this.doWrite(data, options, () => { this.req.cleanup(); + this.emit("drain"); }); } diff --git a/lib/transports-uws/websocket.ts b/lib/transports-uws/websocket.ts index 4de3c23e..d9aaf2ae 100644 --- a/lib/transports-uws/websocket.ts +++ b/lib/transports-uws/websocket.ts @@ -60,8 +60,9 @@ export class WebSocket extends Transport { this.socket.send(data, isBinary, compress); if (isLast) { - this.writable = true; this.emit("drain"); + this.writable = true; + this.emit("ready"); } }; diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index 598315b7..09af20fb 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -96,7 +96,7 @@ export class Polling extends Transport { req.on("close", onClose); this.writable = true; - this.emit("drain"); + this.emit("ready"); // if we're still writable but had a pending close, trigger an empty send if (this.writable && this.shouldClose) { @@ -258,6 +258,7 @@ export class Polling extends Transport { debug('writing "%s"', data); this.doWrite(data, options, () => { this.req.cleanup(); + this.emit("drain"); }); } diff --git a/lib/transports/websocket.ts b/lib/transports/websocket.ts index e4cc2623..788094f4 100644 --- a/lib/transports/websocket.ts +++ b/lib/transports/websocket.ts @@ -103,8 +103,9 @@ export class WebSocket extends Transport { if (err) { this.onError("write error", err.stack); } else { - this.writable = true; this.emit("drain"); + this.writable = true; + this.emit("ready"); } }; diff --git a/lib/transports/webtransport.ts b/lib/transports/webtransport.ts index 663a0f66..07852282 100644 --- a/lib/transports/webtransport.ts +++ b/lib/transports/webtransport.ts @@ -56,8 +56,9 @@ export class WebTransport extends Transport { debug("error while writing: %s", e.message); } - this.writable = true; this.emit("drain"); + this.writable = true; + this.emit("ready"); } doClose(fn) { diff --git a/lib/userver.ts b/lib/userver.ts index 9d1eeb77..c7c91c76 100644 --- a/lib/userver.ts +++ b/lib/userver.ts @@ -80,7 +80,7 @@ export class uServer extends BaseServer { const transport = ws.getUserData().transport; transport.socket = ws; transport.writable = true; - transport.emit("drain"); + transport.emit("ready"); }, message: (ws, message, isBinary) => { ws.getUserData().transport.onData( diff --git a/test/server.js b/test/server.js index c7c914bb..129b07c3 100644 --- a/test/server.js +++ b/test/server.js @@ -2701,6 +2701,29 @@ describe("server", () => { }); }); + it("should execute when message sent during polling upgrade window", (done) => { + const engine = listen((port) => { + const socket = new ClientSocket(`ws://localhost:${port}`, { + transports: ["polling", "websocket"], + }); + + const partialDone = createPartialDone(() => { + engine.httpServer?.close(); + socket.close(); + done(); + }, 2); + + engine.on("connection", (conn) => { + conn.on("upgrading", () => { + conn.send("a", partialDone); + }); + }); + socket.on("open", () => { + socket.on("message", partialDone); + }); + }); + }); + it("should execute when message sent (websocket)", (done) => { const engine = listen({ allowUpgrades: false }, (port) => { const socket = new ClientSocket(`ws://localhost:${port}`, { From c310b7b6b66236f312f017583a67f81389fa5d72 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 21 Jun 2024 13:13:36 +0200 Subject: [PATCH 28/30] refactor: improve types --- lib/engine.io.ts | 2 - lib/server.ts | 75 ++++++++++++++-------------- lib/socket.ts | 88 ++++++++++++++++++++------------- lib/transport.ts | 82 ++++++++++++++++++++---------- lib/transports-uws/polling.ts | 28 +++++------ lib/transports-uws/websocket.ts | 9 +--- lib/transports/index.ts | 2 - lib/transports/polling-jsonp.ts | 20 ++------ lib/transports/polling.ts | 69 +++++++++++--------------- lib/transports/websocket.ts | 11 ++--- lib/userver.ts | 2 +- 11 files changed, 198 insertions(+), 190 deletions(-) diff --git a/lib/engine.io.ts b/lib/engine.io.ts index f6d4cb1d..3ba9e67f 100644 --- a/lib/engine.io.ts +++ b/lib/engine.io.ts @@ -17,7 +17,6 @@ export const protocol = parser.protocol; * @param {Function} callback * @param {Object} options * @return {Server} websocket.io server - * @api public */ function listen(port, options: AttachOptions & ServerOptions, fn) { @@ -46,7 +45,6 @@ function listen(port, options: AttachOptions & ServerOptions, fn) { * @param {http.Server} server * @param {Object} options * @return {Server} engine server - * @api public */ function attach(server, options: AttachOptions & ServerOptions) { diff --git a/lib/server.ts b/lib/server.ts index ba096f2e..a010092f 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -17,12 +17,13 @@ import type { CorsOptions, CorsOptionsDelegate } from "cors"; import type { Duplex } from "stream"; import { WebTransport } from "./transports/webtransport"; import { createPacketDecoderStream } from "engine.io-parser"; +import type { EngineRequest } from "./transport"; const debug = debugModule("engine"); const kResponseHeaders = Symbol("responseHeaders"); -type Transport = "polling" | "websocket"; +type Transport = "polling" | "websocket" | "webtransport"; export interface AttachOptions { /** @@ -169,7 +170,6 @@ export abstract class BaseServer extends EventEmitter { * Server constructor. * * @param {Object} opts - options - * @api public */ constructor(opts: ServerOptions = {}) { super(); @@ -246,9 +246,8 @@ export abstract class BaseServer extends EventEmitter { * Returns a list of available transports for upgrade given a certain transport. * * @return {Array} - * @api public */ - public upgrades(transport) { + public upgrades(transport: string) { if (!this.opts.allowUpgrades) return []; return transports[transport].upgradesTo || []; } @@ -256,11 +255,16 @@ export abstract class BaseServer extends EventEmitter { /** * Verifies a request. * - * @param {http.IncomingMessage} - * @return {Boolean} whether the request is valid - * @api private + * @param {EngineRequest} req + * @param upgrade - whether it's an upgrade request + * @param fn + * @protected */ - protected verify(req, upgrade, fn) { + protected verify( + req: any, + upgrade: boolean, + fn: (errorCode?: number, errorContext?: any) => void + ) { // transport check const transport = req._query.transport; // WebTransport does not go through the verify() method, see the onWebTransportSession() method @@ -384,8 +388,6 @@ export abstract class BaseServer extends EventEmitter { /** * Closes all clients. - * - * @api public */ public close() { debug("closing all open clients"); @@ -404,23 +406,26 @@ export abstract class BaseServer extends EventEmitter { * generate a socket id. * Overwrite this method to generate your custom socket id * - * @param {Object} request object - * @api public + * @param {IncomingMessage} req - the request object */ - public generateId(req) { + public generateId(req: IncomingMessage) { return base64id.generateId(); } /** * Handshakes a new client. * - * @param {String} transport name - * @param {Object} request object + * @param {String} transportName + * @param {Object} req - the request object * @param {Function} closeConnection * - * @api protected + * @protected */ - protected async handshake(transportName, req, closeConnection) { + protected async handshake( + transportName: string, + req: any, + closeConnection: (errorCode?: number, errorContext?: any) => void + ) { const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default if (protocol === 3 && !this.opts.allowEIO3) { debug("unsupported protocol version"); @@ -661,7 +666,7 @@ export class Server extends BaseServer { /** * Initialize websocket server * - * @api protected + * @protected */ protected init() { if (!~this.opts.transports.indexOf("websocket")) return; @@ -708,30 +713,30 @@ export class Server extends BaseServer { /** * Prepares a request by processing the query string. * - * @api private + * @private */ - private prepare(req) { + private prepare(req: EngineRequest) { // try to leverage pre-existing `req._query` (e.g: from connect) if (!req._query) { - req._query = ~req.url.indexOf("?") ? qs.parse(parse(req.url).query) : {}; + req._query = ( + ~req.url.indexOf("?") ? qs.parse(parse(req.url).query) : {} + ) as Record; } } - protected createTransport(transportName, req) { + protected createTransport(transportName: string, req: IncomingMessage) { return new transports[transportName](req); } /** * Handles an Engine.IO HTTP request. * - * @param {IncomingMessage} req + * @param {EngineRequest} req * @param {ServerResponse} res - * @api public */ - public handleRequest(req: IncomingMessage, res: ServerResponse) { + public handleRequest(req: EngineRequest, res: ServerResponse) { debug('handling "%s" http request "%s"', req.method, req.url); this.prepare(req); - // @ts-ignore req.res = res; const callback = (errorCode, errorContext) => { @@ -746,15 +751,12 @@ export class Server extends BaseServer { return; } - // @ts-ignore if (req._query.sid) { debug("setting new request for existing client"); - // @ts-ignore this.clients[req._query.sid].transport.onRequest(req); } else { const closeConnection = (errorCode, errorContext) => abortRequest(res, errorCode, errorContext); - // @ts-ignore this.handshake(req._query.transport, req, closeConnection); } }; @@ -770,11 +772,9 @@ export class Server extends BaseServer { /** * Handles an Engine.IO HTTP Upgrade. - * - * @api public */ public handleUpgrade( - req: IncomingMessage, + req: EngineRequest, socket: Duplex, upgradeHead: Buffer ) { @@ -819,7 +819,7 @@ export class Server extends BaseServer { * Called upon a ws.io connection. * * @param {ws.Socket} websocket - * @api private + * @private */ private onWebSocket(req, socket, websocket) { websocket.on("error", onUpgradeError); @@ -877,7 +877,6 @@ export class Server extends BaseServer { * * @param {http.Server} server * @param {Object} options - * @api public */ public attach(server: HttpServer, options: AttachOptions = {}) { const path = this._computePath(options); @@ -898,7 +897,7 @@ export class Server extends BaseServer { server.on("request", (req, res) => { if (check(req)) { debug('intercepting request for path "%s"', path); - this.handleRequest(req, res); + this.handleRequest(req as EngineRequest, res); } else { let i = 0; const l = listeners.length; @@ -911,7 +910,7 @@ export class Server extends BaseServer { if (~this.opts.transports.indexOf("websocket")) { server.on("upgrade", (req, socket, head) => { if (check(req)) { - this.handleUpgrade(req, socket, head); + this.handleUpgrade(req as EngineRequest, socket, head); } else if (false !== options.destroyUpgrade) { // default node behavior is to disconnect when no handlers // but by adding a handler, we prevent that @@ -939,7 +938,7 @@ export class Server extends BaseServer { * @param errorCode - the error code * @param errorContext - additional error context * - * @api private + * @private */ function abortRequest(res, errorCode, errorContext) { @@ -964,8 +963,6 @@ function abortRequest(res, errorCode, errorContext) { * @param {net.Socket} socket * @param {string} errorCode - the error code * @param {object} errorContext - additional error context - * - * @api private */ function abortUpgrade( diff --git a/lib/socket.ts b/lib/socket.ts index 1dfa460b..d9f58a68 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -1,10 +1,10 @@ import { EventEmitter } from "events"; import debugModule from "debug"; -import { IncomingMessage } from "http"; -import { Transport } from "./transport"; -import { Server } from "./server"; +import type { IncomingMessage } from "http"; +import type { EngineRequest, Transport } from "./transport"; +import type { BaseServer } from "./server"; import { setTimeout, clearTimeout } from "timers"; -import { Packet, PacketType, RawData } from "engine.io-parser"; +import type { Packet, PacketType, RawData } from "engine.io-parser"; const debug = debugModule("engine:socket"); @@ -17,15 +17,38 @@ type ReadyState = "opening" | "open" | "closing" | "closed"; type SendCallback = (transport: Transport) => void; export class Socket extends EventEmitter { + /** + * The revision of the protocol: + * + * - 3rd is used in Engine.IO v3 / Socket.IO v2 + * - 4th is used in Engine.IO v4 and above / Socket.IO v3 and above + * + * It is found in the `EIO` query parameters of the HTTP requests. + * + * @see https://github.com/socketio/engine.io-protocol + */ public readonly protocol: number; - // TODO for the next major release: do not keep the reference to the first HTTP request, as it stays in memory + /** + * A reference to the first HTTP request of the session + * + * TODO for the next major release: remove it + */ public request: IncomingMessage; + /** + * The IP address of the client. + */ public readonly remoteAddress: string; + /** + * The current state of the socket. + */ public _readyState: ReadyState = "opening"; + /** + * The current low-level transport. + */ public transport: Transport; - private server: Server; + private server: BaseServer; /* private */ upgrading = false; /* private */ upgraded = false; private writeBuffer: Packet[] = []; @@ -52,12 +75,13 @@ export class Socket extends EventEmitter { this._readyState = state; } - /** - * Client class (abstract). - * - * @api private - */ - constructor(id, server, transport, req, protocol) { + constructor( + id: string, + server: BaseServer, + transport: Transport, + req: EngineRequest, + protocol: number + ) { super(); this.id = id; this.server = server; @@ -86,7 +110,7 @@ export class Socket extends EventEmitter { /** * Called upon transport considered open. * - * @api private + * @private */ private onOpen() { this.readyState = "open"; @@ -123,7 +147,7 @@ export class Socket extends EventEmitter { * Called upon transport packet. * * @param {Object} packet - * @api private + * @private */ private onPacket(packet: Packet) { if ("open" !== this.readyState) { @@ -136,7 +160,7 @@ export class Socket extends EventEmitter { switch (packet.type) { case "ping": if (this.transport.protocol !== 3) { - this.onError("invalid heartbeat direction"); + this.onError(new Error("invalid heartbeat direction")); return; } debug("got ping"); @@ -147,7 +171,7 @@ export class Socket extends EventEmitter { case "pong": if (this.transport.protocol === 3) { - this.onError("invalid heartbeat direction"); + this.onError(new Error("invalid heartbeat direction")); return; } debug("got pong"); @@ -171,9 +195,9 @@ export class Socket extends EventEmitter { * Called upon transport error. * * @param {Error} err - error object - * @api private + * @private */ - private onError(err) { + private onError(err: Error) { debug("transport error"); this.onClose("transport error", err); } @@ -182,7 +206,7 @@ export class Socket extends EventEmitter { * Pings client every `this.pingInterval` and expects response * within `this.pingTimeout` or closes connection. * - * @api private + * @private */ private schedulePing() { this.pingIntervalTimer = setTimeout(() => { @@ -198,7 +222,7 @@ export class Socket extends EventEmitter { /** * Resets ping timeout. * - * @api private + * @private */ private resetPingTimeout() { clearTimeout(this.pingTimeoutTimer); @@ -217,9 +241,9 @@ export class Socket extends EventEmitter { * Attaches handlers for the given transport. * * @param {Transport} transport - * @api private + * @private */ - private setTransport(transport) { + private setTransport(transport: Transport) { const onError = this.onError.bind(this); const onReady = () => this.flush(); const onPacket = this.onPacket.bind(this); @@ -263,7 +287,7 @@ export class Socket extends EventEmitter { * Upgrades socket to the given transport * * @param {Transport} transport - * @api private + * @private */ /* private */ _maybeUpgrade(transport: Transport) { debug( @@ -357,7 +381,7 @@ export class Socket extends EventEmitter { /** * Clears listeners and timers associated with current transport. * - * @api private + * @private */ private clearTransport() { let cleanup; @@ -412,7 +436,6 @@ export class Socket extends EventEmitter { * @param {Object} options * @param {Function} callback * @return {Socket} for chaining - * @api public */ public send(data: RawData, options?: SendOptions, callback?: SendCallback) { this.sendPacket("message", data, options, callback); @@ -439,7 +462,7 @@ export class Socket extends EventEmitter { * @param {Object} options * @param {Function} callback * - * @api private + * @private */ private sendPacket( type: PacketType, @@ -480,7 +503,7 @@ export class Socket extends EventEmitter { /** * Attempts to flush the packets buffer. * - * @api private + * @private */ private flush() { if ( @@ -510,14 +533,12 @@ export class Socket extends EventEmitter { /** * Get available upgrades for this socket. * - * @api private + * @private */ private getAvailableUpgrades() { const availableUpgrades = []; const allUpgrades = this.server.upgrades(this.transport.name); - let i = 0; - const l = allUpgrades.length; - for (; i < l; ++i) { + for (let i = 0; i < allUpgrades.length; ++i) { const upg = allUpgrades[i]; if (this.server.opts.transports.indexOf(upg) !== -1) { availableUpgrades.push(upg); @@ -531,7 +552,6 @@ export class Socket extends EventEmitter { * * @param {Boolean} discard - optional, discard the transport * @return {Socket} for chaining - * @api public */ public close(discard?: boolean) { if ("open" !== this.readyState) return; @@ -558,9 +578,9 @@ export class Socket extends EventEmitter { * Closes the underlying transport. * * @param {Boolean} discard - * @api private + * @private */ - private closeTransport(discard) { + private closeTransport(discard: boolean) { debug("closing the transport (discard? %s)", discard); if (discard) this.transport.discard(); this.transport.close(this.onClose.bind(this, "forced close")); diff --git a/lib/transport.ts b/lib/transport.ts index 6318fb01..d44cb7b2 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -2,30 +2,63 @@ import { EventEmitter } from "events"; import * as parser_v4 from "engine.io-parser"; import * as parser_v3 from "./parser-v3/index"; import debugModule from "debug"; -import { IncomingMessage } from "http"; -import { Packet } from "engine.io-parser"; +import type { IncomingMessage, ServerResponse } from "http"; +import { Packet, RawData } from "engine.io-parser"; const debug = debugModule("engine:transport"); -/** - * Noop function. - * - * @api private - */ - function noop() {} type ReadyState = "open" | "closing" | "closed"; +export type EngineRequest = IncomingMessage & { + _query: Record; + res?: ServerResponse; + cleanup?: Function; + websocket?: any; +}; + export abstract class Transport extends EventEmitter { + /** + * The session ID. + */ public sid: string; + /** + * Whether the transport is currently ready to send packets. + */ public writable = false; + /** + * The revision of the protocol: + * + * - 3 is used in Engine.IO v3 / Socket.IO v2 + * - 4 is used in Engine.IO v4 and above / Socket.IO v3 and above + * + * It is found in the `EIO` query parameters of the HTTP requests. + * + * @see https://github.com/socketio/engine.io-protocol + */ public protocol: number; + /** + * The current state of the transport. + * @protected + */ protected _readyState: ReadyState = "open"; + /** + * Whether the transport is discarded and can be safely closed (used during upgrade). + * @protected + */ protected discarded = false; + /** + * The parser to use (depends on the revision of the {@link Transport#protocol}. + * @protected + */ protected parser: any; - protected req: IncomingMessage & { cleanup: Function }; + protected req: EngineRequest; + /** + * Whether the transport supports binary payloads (else it will be base64-encoded) + * @protected + */ protected supportsBinary: boolean; get readyState() { @@ -45,10 +78,9 @@ export abstract class Transport extends EventEmitter { /** * Transport constructor. * - * @param {http.IncomingMessage} req - * @api public + * @param {EngineRequest} req */ - constructor(req) { + constructor(req: { _query: Record }) { super(); this.protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default this.parser = this.protocol === 4 ? parser_v4 : parser_v3; @@ -58,7 +90,7 @@ export abstract class Transport extends EventEmitter { /** * Flags the transport as discarded. * - * @api private + * @package */ discard() { this.discarded = true; @@ -68,9 +100,9 @@ export abstract class Transport extends EventEmitter { * Called with an incoming HTTP request. * * @param {http.IncomingMessage} req - * @api protected + * @package */ - protected onRequest(req) { + onRequest(req) { debug("setting request"); this.req = req; } @@ -78,9 +110,9 @@ export abstract class Transport extends EventEmitter { /** * Closes the transport. * - * @api private + * @package */ - close(fn?) { + close(fn?: () => void) { if ("closed" === this.readyState || "closing" === this.readyState) return; this.readyState = "closing"; @@ -92,7 +124,7 @@ export abstract class Transport extends EventEmitter { * * @param {String} msg - message error * @param {Object} desc - error description - * @api protected + * @protected */ protected onError(msg: string, desc?) { if (this.listeners("error").length) { @@ -111,7 +143,7 @@ export abstract class Transport extends EventEmitter { * Called with parsed out a packets from the data stream. * * @param {Object} packet - * @api protected + * @protected */ protected onPacket(packet: Packet) { this.emit("packet", packet); @@ -121,16 +153,16 @@ export abstract class Transport extends EventEmitter { * Called with the encoded packet data. * * @param {String} data - * @api protected + * @protected */ - protected onData(data) { + protected onData(data: RawData) { this.onPacket(this.parser.decodePacket(data)); } /** * Called upon transport close. * - * @api protected + * @protected */ protected onClose() { this.readyState = "closed"; @@ -140,7 +172,7 @@ export abstract class Transport extends EventEmitter { /** * The name of the transport. */ - abstract get name(); + abstract get name(): string; /** * Sends an array of packets. @@ -148,10 +180,10 @@ export abstract class Transport extends EventEmitter { * @param {Array} packets * @package */ - abstract send(packets); + abstract send(packets: Packet[]): void; /** * Closes the transport. */ - abstract doClose(fn?); + abstract doClose(fn?: () => void): void; } diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 01a82f6e..1fc643f8 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -24,8 +24,6 @@ export class Polling extends Transport { /** * HTTP polling constructor. - * - * @api public. */ constructor(req) { super(req); @@ -35,8 +33,6 @@ export class Polling extends Transport { /** * Transport name - * - * @api public */ get name() { return "polling"; @@ -47,7 +43,7 @@ export class Polling extends Transport { * * @param req * - * @api private + * @private */ onRequest(req) { const res = req.res; @@ -67,7 +63,7 @@ export class Polling extends Transport { /** * The client sends a request awaiting for us to send data. * - * @api private + * @private */ onPollRequest(req, res) { if (this.req) { @@ -109,7 +105,7 @@ export class Polling extends Transport { /** * The client sends a request with data. * - * @api private + * @private */ onDataRequest(req, res) { if (this.dataReq) { @@ -206,7 +202,7 @@ export class Polling extends Transport { /** * Cleanup request. * - * @api private + * @private */ private onDataRequestCleanup() { this.dataReq = this.dataRes = null; @@ -216,7 +212,7 @@ export class Polling extends Transport { * Processes the incoming data payload. * * @param {String} encoded payload - * @api private + * @private */ onData(data) { debug('received "%s"', data); @@ -240,7 +236,7 @@ export class Polling extends Transport { /** * Overrides onClose. * - * @api private + * @private */ onClose() { if (this.writable) { @@ -254,7 +250,7 @@ export class Polling extends Transport { * Writes a packet payload. * * @param {Object} packet - * @api private + * @private */ send(packets) { this.writable = false; @@ -285,7 +281,7 @@ export class Polling extends Transport { * * @param {String} data * @param {Object} options - * @api private + * @private */ write(data, options) { debug('writing "%s"', data); @@ -298,7 +294,7 @@ export class Polling extends Transport { /** * Performs the write. * - * @api private + * @private */ doWrite(data, options, callback) { // explicit UTF-8 is required for pages not served under utf @@ -355,7 +351,7 @@ export class Polling extends Transport { /** * Compresses data. * - * @api private + * @private */ compress(data, encoding, callback) { debug("compressing"); @@ -378,7 +374,7 @@ export class Polling extends Transport { /** * Closes the transport. * - * @api private + * @private */ doClose(fn) { debug("closing"); @@ -410,7 +406,7 @@ export class Polling extends Transport { * * @param req - request * @param {Object} extra headers - * @api private + * @private */ headers(req, headers) { headers = headers || {}; diff --git a/lib/transports-uws/websocket.ts b/lib/transports-uws/websocket.ts index d9aaf2ae..aa5e1c93 100644 --- a/lib/transports-uws/websocket.ts +++ b/lib/transports-uws/websocket.ts @@ -11,7 +11,6 @@ export class WebSocket extends Transport { * WebSocket transport * * @param req - * @api public */ constructor(req) { super(req); @@ -21,8 +20,6 @@ export class WebSocket extends Transport { /** * Transport name - * - * @api public */ get name() { return "websocket"; @@ -30,8 +27,6 @@ export class WebSocket extends Transport { /** * Advertise upgrade support. - * - * @api public */ get handlesUpgrades() { return true; @@ -41,7 +36,7 @@ export class WebSocket extends Transport { * Writes a packet payload. * * @param {Array} packets - * @api private + * @private */ send(packets) { this.writable = false; @@ -77,7 +72,7 @@ export class WebSocket extends Transport { /** * Closes the transport. * - * @api private + * @private */ doClose(fn) { debug("closing"); diff --git a/lib/transports/index.ts b/lib/transports/index.ts index 5c8449d4..e585112b 100644 --- a/lib/transports/index.ts +++ b/lib/transports/index.ts @@ -11,8 +11,6 @@ export default { /** * Polling polymorphic constructor. - * - * @api private */ function polling(req) { diff --git a/lib/transports/polling-jsonp.ts b/lib/transports/polling-jsonp.ts index d94ba23a..05806008 100644 --- a/lib/transports/polling-jsonp.ts +++ b/lib/transports/polling-jsonp.ts @@ -1,5 +1,6 @@ import { Polling } from "./polling"; import * as qs from "querystring"; +import type { RawData } from "engine.io-parser"; const rDoubleSlashes = /\\\\n/g; const rSlashes = /(\\)?\\n/g; @@ -10,8 +11,6 @@ export class JSONP extends Polling { /** * JSON-P polling transport. - * - * @api public */ constructor(req) { super(req); @@ -20,16 +19,10 @@ export class JSONP extends Polling { this.foot = ");"; } - /** - * Handles incoming data. - * Due to a bug in \n handling by browsers, we expect a escaped string. - * - * @api private - */ - onData(data) { + override onData(data: RawData) { // we leverage the qs module so that we get built-in DoS protection // and the fast alternative to decodeURIComponent - data = qs.parse(data).d; + data = qs.parse(data).d as string; if ("string" === typeof data) { // client will send already escaped newlines as \\\\n and newlines as \\n // \\n must be replaced with \n and \\\\n with \\n @@ -40,12 +33,7 @@ export class JSONP extends Polling { } } - /** - * Performs the write. - * - * @api private - */ - doWrite(data, options, callback) { + override doWrite(data, options, callback) { // we must output valid javascript, not valid json // see: http://timelessrepo.com/json-isnt-a-javascript-subset const js = JSON.stringify(data) diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index 09af20fb..3b477a62 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -1,8 +1,9 @@ -import { Transport } from "../transport"; +import { EngineRequest, Transport } from "../transport"; import { createGzip, createDeflate } from "zlib"; import * as accepts from "accepts"; import debugModule from "debug"; -import { IncomingMessage, ServerResponse } from "http"; +import type { IncomingMessage, ServerResponse } from "http"; +import type { Packet, RawData } from "engine.io-parser"; const debug = debugModule("engine:polling"); @@ -18,14 +19,12 @@ export class Polling extends Transport { private res: ServerResponse; private dataReq: IncomingMessage; private dataRes: ServerResponse; - private shouldClose: Function; + private shouldClose: () => void; private readonly closeTimeout: number; /** * HTTP polling constructor. - * - * @api public. */ constructor(req) { super(req); @@ -35,8 +34,6 @@ export class Polling extends Transport { /** * Transport name - * - * @api public */ get name() { return "polling"; @@ -45,10 +42,10 @@ export class Polling extends Transport { /** * Overrides onRequest. * - * @param {http.IncomingMessage} - * @api private + * @param {EngineRequest} req + * @package */ - onRequest(req: IncomingMessage & { res: ServerResponse }) { + onRequest(req: EngineRequest) { const res = req.res; // remove the reference to the ServerResponse object (as the first request of the session is kept in memory by default) req.res = null; @@ -66,9 +63,9 @@ export class Polling extends Transport { /** * The client sends a request awaiting for us to send data. * - * @api private + * @private */ - onPollRequest(req, res) { + private onPollRequest(req: EngineRequest, res: ServerResponse) { if (this.req) { debug("request overlap"); // assert: this.res, '.req and .res should be (un)set together' @@ -108,9 +105,9 @@ export class Polling extends Transport { /** * The client sends a request with data. * - * @api private + * @private */ - onDataRequest(req: IncomingMessage, res: ServerResponse) { + private onDataRequest(req: IncomingMessage, res: ServerResponse) { if (this.dataReq) { // assert: this.dataRes, '.dataReq and .dataRes should be (un)set together' this.onError("data request overlap from client"); @@ -165,7 +162,7 @@ export class Polling extends Transport { // text/html is required instead of text/plain to avoid an // unwanted download dialog on certain user-agents (GH-43) "Content-Type": "text/html", - "Content-Length": 2, + "Content-Length": "2", }; res.writeHead(200, this.headers(req, headers)); @@ -182,10 +179,10 @@ export class Polling extends Transport { /** * Processes the incoming data payload. * - * @param {String} encoded payload - * @api private + * @param data - encoded payload + * @protected */ - onData(data) { + override onData(data: RawData) { debug('received "%s"', data); const callback = (packet) => { if ("close" === packet.type) { @@ -207,7 +204,7 @@ export class Polling extends Transport { /** * Overrides onClose. * - * @api private + * @private */ onClose() { if (this.writable) { @@ -217,13 +214,7 @@ export class Polling extends Transport { super.onClose(); } - /** - * Writes a packet payload. - * - * @param {Object} packet - * @api private - */ - send(packets) { + send(packets: Packet[]) { this.writable = false; if (this.shouldClose) { @@ -252,9 +243,9 @@ export class Polling extends Transport { * * @param {String} data * @param {Object} options - * @api private + * @private */ - write(data, options) { + private write(data, options) { debug('writing "%s"', data); this.doWrite(data, options, () => { this.req.cleanup(); @@ -265,9 +256,9 @@ export class Polling extends Transport { /** * Performs the write. * - * @api private + * @protected */ - doWrite(data, options, callback) { + protected doWrite(data, options, callback) { // explicit UTF-8 is required for pages not served under utf const isString = typeof data === "string"; const contentType = isString @@ -319,9 +310,9 @@ export class Polling extends Transport { /** * Compresses data. * - * @api private + * @private */ - compress(data, encoding, callback) { + private compress(data, encoding, callback) { debug("compressing"); const buffers = []; @@ -342,9 +333,9 @@ export class Polling extends Transport { /** * Closes the transport. * - * @api private + * @private */ - doClose(fn) { + override doClose(fn: () => void) { debug("closing"); let closeTimeoutTimer; @@ -377,13 +368,11 @@ export class Polling extends Transport { /** * Returns headers for a response. * - * @param {http.IncomingMessage} request - * @param {Object} extra headers - * @api private + * @param {http.IncomingMessage} req + * @param {Object} headers - extra headers + * @private */ - headers(req, headers) { - headers = headers || {}; - + private headers(req: IncomingMessage, headers: Record = {}) { // prevent XSS warnings on IE // https://github.com/LearnBoost/socket.io/pull/1333 const ua = req.headers["user-agent"]; diff --git a/lib/transports/websocket.ts b/lib/transports/websocket.ts index 788094f4..71ac94a7 100644 --- a/lib/transports/websocket.ts +++ b/lib/transports/websocket.ts @@ -1,4 +1,4 @@ -import { Transport } from "../transport"; +import { EngineRequest, Transport } from "../transport"; import debugModule from "debug"; import type { Packet, RawData } from "engine.io-parser"; @@ -11,10 +11,9 @@ export class WebSocket extends Transport { /** * WebSocket transport * - * @param {http.IncomingMessage} - * @api public + * @param {EngineRequest} req */ - constructor(req) { + constructor(req: EngineRequest) { super(req); this.socket = req.websocket; this.socket.on("message", (data, isBinary) => { @@ -30,8 +29,6 @@ export class WebSocket extends Transport { /** * Transport name - * - * @api public */ get name() { return "websocket"; @@ -39,8 +36,6 @@ export class WebSocket extends Transport { /** * Advertise upgrade support. - * - * @api public */ get handlesUpgrades() { return true; diff --git a/lib/userver.ts b/lib/userver.ts index c7c91c76..ce56575a 100644 --- a/lib/userver.ts +++ b/lib/userver.ts @@ -30,7 +30,7 @@ export class uServer extends BaseServer { /** * Prepares a request by processing the query string. * - * @api private + * @private */ private prepare(req, res: HttpResponse) { req.method = req.getMethod().toUpperCase(); From 6d8a0bea49a0bbc96875393fa2857aa6a0d63319 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 21 Jun 2024 13:43:01 +0200 Subject: [PATCH 29/30] refactor: move the `req` attribute to the polling class --- lib/transport.ts | 8 ++------ lib/transports-uws/polling.ts | 1 + lib/transports/polling.ts | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/transport.ts b/lib/transport.ts index d44cb7b2..5e74813c 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -54,7 +54,6 @@ export abstract class Transport extends EventEmitter { * @protected */ protected parser: any; - protected req: EngineRequest; /** * Whether the transport supports binary payloads (else it will be base64-encoded) * @protected @@ -99,13 +98,10 @@ export abstract class Transport extends EventEmitter { /** * Called with an incoming HTTP request. * - * @param {http.IncomingMessage} req + * @param req * @package */ - onRequest(req) { - debug("setting request"); - this.req = req; - } + onRequest(req: any) {} /** * Closes the transport. diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 1fc643f8..090270ec 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -15,6 +15,7 @@ export class Polling extends Transport { public maxHttpBufferSize: number; public httpCompression: any; + private req: HttpRequest & { cleanup: () => void }; private res: HttpResponse; private dataReq: HttpRequest; private dataRes: HttpResponse; diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index 3b477a62..1f463359 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -16,6 +16,7 @@ export class Polling extends Transport { public maxHttpBufferSize: number; public httpCompression: any; + private req: EngineRequest; private res: ServerResponse; private dataReq: IncomingMessage; private dataRes: ServerResponse; From 791aa58f4c0a003335a945fdd704cb910bd35970 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 21 Jun 2024 14:32:55 +0200 Subject: [PATCH 30/30] chore(release): 6.6.0 Diff: https://github.com/socketio/engine.io/compare/6.5.4...6.6.0 --- CHANGELOG.md | 116 ++++++++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d76710e..649a6014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,62 +1,72 @@ # History -## 2024 - -- [6.5.5](#655-2024-06-18) (Jun 2024) (from the [6.5.x](https://github.com/socketio/engine.io/tree/6.5.x) branch) -- [3.6.2](#362-2024-06-18) (Jun 2024) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) - -## 2023 - -- [6.5.4](#654-2023-11-09) (Nov 2023) -- [6.5.3](#653-2023-10-06) (Oct 2023) -- [6.5.2](#652-2023-08-01) (Aug 2023) -- [6.5.1](#651-2023-06-27) (Jun 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) -- [6.3.1](#631-2023-01-12) (Jan 2023) -- [6.3.0](#630-2023-01-10) (Jan 2023) - -## 2022 - -- [3.6.1](#361-2022-11-20) (Nov 2022) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) -- [6.2.1](#621-2022-11-20) (Nov 2022) -- [3.6.0](#360-2022-06-06) (Jun 2022) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) -- [6.2.0](#620-2022-04-17) (Apr 2022) -- [6.1.3](#613-2022-02-23) (Feb 2022) -- [6.1.2](#612-2022-01-18) (Jan 2022) -- [6.1.1](#611-2022-01-11) (Jan 2022) - -## 2021 - -- [6.1.0](#610-2021-11-08) (Nov 2021) -- [6.0.1](#601-2021-11-06) (Nov 2021) -- [**6.0.0**](#600-2021-10-08) (Oct 2021) -- [5.2.0](#520-2021-08-29) (Aug 2021) -- [5.1.1](#511-2021-05-16) (May 2021) -- [5.1.0](#510-2021-05-04) (May 2021) -- [**5.0.0**](#500-2021-03-10) (Mar 2021) -- [4.1.1](#411-2021-02-02) (Feb 2021) -- [4.1.0](#410-2021-01-14) (Jan 2021) -- [4.0.6](#406-2021-01-04) (Jan 2021) - -## 2020 - -- [3.5.0](#350-2020-12-30) (Dec 2020) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) -- [4.0.5](#405-2020-12-07) (Dec 2020) -- [4.0.4](#404-2020-11-17) (Nov 2020) -- [4.0.3](#403-2020-11-17) (Nov 2020) -- [4.0.2](#402-2020-11-09) (Nov 2020) -- [4.0.1](#401-2020-10-21) (Oct 2020) -- [**4.0.0**](#400-2020-09-10) (Sep 2020) -- [3.4.2](#342-2020-06-04) (Jun 2020) -- [3.4.1](#341-2020-04-17) (Apr 2020) - +| Version | Release date | +|------------------------------------------------------------------------------------------------------|----------------| +| [6.6.0](#660-2024-06-21) | June 2024 | +| [6.5.5](#655-2024-06-18) (from the [6.5.x](https://github.com/socketio/engine.io/tree/6.5.x) branch) | June 2024 | +| [3.6.2](#362-2024-06-18) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | June 2024 | +| [6.5.4](#654-2023-11-09) | November 2023 | +| [6.5.3](#653-2023-10-06) | October 2023 | +| [6.5.2](#652-2023-08-01) | August 2023 | +| [6.5.1](#651-2023-06-27) | June 2023 | +| [6.5.0](#650-2023-06-16) | June 2023 | +| [6.4.2](#642-2023-05-02) | May 2023 | +| [6.4.1](#641-2023-02-20) | February 2023 | +| [6.4.0](#640-2023-02-06) | February 2023 | +| [6.3.1](#631-2023-01-12) | January 2023 | +| [6.3.0](#630-2023-01-10) | January 2023 | +| [3.6.1](#361-2022-11-20) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | November 2022 | +| [6.2.1](#621-2022-11-20) | November 2022 | +| [3.6.0](#360-2022-06-06) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | June 2022 | +| [6.2.0](#620-2022-04-17) | April 2022 | +| [6.1.3](#613-2022-02-23) | February 2022 | +| [6.1.2](#612-2022-01-18) | January 2022 | +| [6.1.1](#611-2022-01-11) | January 2021 | +| [6.1.0](#610-2021-11-08) | November 2022 | +| [6.0.1](#601-2021-11-06) | November 2021 | +| [**6.0.0**](#600-2021-10-08) | October 2021 | +| [5.2.0](#520-2021-08-29) | August 2021 | +| [5.1.1](#511-2021-05-16) | May 2021 | +| [5.1.0](#510-2021-05-04) | May 2021 | +| [**5.0.0**](#500-2021-03-10) | March 2021 | +| [4.1.1](#411-2021-02-02) | February 2021 | +| [4.1.0](#410-2021-01-14) | January 2021 | +| [4.0.6](#406-2021-01-04) | January 2021 | +| [3.5.0](#350-2020-12-30) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | December 2020 | +| [4.0.5](#405-2020-12-07) | December 2020 | +| [4.0.4](#404-2020-11-17) | November 2020 | +| [4.0.3](#403-2020-11-17) | November 2020 | +| [4.0.2](#402-2020-11-09) | November 2020 | +| [4.0.1](#401-2020-10-21) | October 2020 | +| [**4.0.0**](#400-2020-09-10) | September 2020 | +| [3.4.2](#342-2020-06-04) | June 2020 | +| [3.4.1](#341-2020-04-17) | April 2020 | # Release notes +## [6.6.0](https://github.com/socketio/engine.io/compare/6.5.4...6.6.0) (2024-06-21) + + +### Bug Fixes + +* fix `websocket` and `webtransport` send callbacks ([#699](https://github.com/socketio/engine.io/issues/699)) ([fc21c4a](https://github.com/socketio/engine.io/commit/fc21c4a05f9d50d7efd62aa7a937fadce385e919)) +* properly call the send callback during upgrade ([362bc78](https://github.com/socketio/engine.io/commit/362bc78191c607e6b7c7f2b2e7e7ddb2fe53101c)) +* **types:** make socket.request writable ([#697](https://github.com/socketio/engine.io/issues/697)) ([0efa04b](https://github.com/socketio/engine.io/commit/0efa04b5841816d18b0c6ebf7c5f592f8382978a)) + + +### Performance Improvements + +* do not reset the hearbeat timer on each packet ([5359bae](https://github.com/socketio/engine.io/commit/5359bae683e2a25742bd4989d0355a8fc10d294e)) +* **websocket:** use bound callbacks ([9a68c8c](https://github.com/socketio/engine.io/commit/9a68c8ce93cc1bc0bc1a30548558da49860f4acd)) + + +### Dependencies + +- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change) + + + ## [6.5.5](https://github.com/socketio/engine.io/compare/6.5.4...6.5.5) (2024-06-18) This release contains a bump of the `ws` dependency, which includes an important [security fix](https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c). diff --git a/package.json b/package.json index a0723d17..b20c90f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "engine.io", - "version": "6.5.4", + "version": "6.6.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",