diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6acb3241c4..553beee6b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: node-version: - 18 - 20 - - 22 + - 22.17.1 # experimental TS type striping in 22.18.0 breaks the build services: redis: diff --git a/docs/engine.io-protocol/v4-test-suite/test-suite.js b/docs/engine.io-protocol/v4-test-suite/test-suite.js index 70b28f5fe8..b010f50967 100644 --- a/docs/engine.io-protocol/v4-test-suite/test-suite.js +++ b/docs/engine.io-protocol/v4-test-suite/test-suite.js @@ -17,16 +17,35 @@ function sleep(delay) { return new Promise((resolve) => setTimeout(resolve, delay)); } +function createWebSocket(url) { + const socket = new WebSocket(url); + socket._eventBuffer = {}; + socket._pendingPromises = {}; + + for (const eventType of ["open", "close", "message"]) { + socket._eventBuffer[eventType] = []; + socket._pendingPromises[eventType] = []; + + socket.addEventListener(eventType, (event) => { + if (socket._pendingPromises[eventType].length) { + socket._pendingPromises[eventType].shift()(event); + } else { + socket._eventBuffer[eventType].push(event); + } + }); + } + + return socket; +} + function waitFor(socket, eventType) { - return new Promise((resolve) => { - socket.addEventListener( - eventType, - (event) => { - resolve(event); - }, - { once: true } - ); - }); + if (socket._eventBuffer[eventType].length) { + return Promise.resolve(socket._eventBuffer[eventType].shift()); + } else { + return new Promise((resolve) => { + socket._pendingPromises[eventType].push(resolve); + }); + } } async function initLongPollingSession() { @@ -110,7 +129,7 @@ describe("Engine.IO protocol", () => { describe("WebSocket", () => { it("successfully opens a session", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket` ); @@ -137,7 +156,7 @@ describe("Engine.IO protocol", () => { }); it("fails with an invalid 'EIO' query parameter", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?transport=websocket` ); @@ -145,9 +164,9 @@ describe("Engine.IO protocol", () => { socket.on("error", () => {}); } - waitFor(socket, "close"); + await waitFor(socket, "close"); - const socket2 = new WebSocket( + const socket2 = createWebSocket( `${WS_URL}/engine.io/?EIO=abc&transport=websocket` ); @@ -155,19 +174,19 @@ describe("Engine.IO protocol", () => { socket2.on("error", () => {}); } - waitFor(socket2, "close"); + await waitFor(socket2, "close"); }); it("fails with an invalid 'transport' query parameter", async () => { - const socket = new WebSocket(`${WS_URL}/engine.io/?EIO=4`); + const socket = createWebSocket(`${WS_URL}/engine.io/?EIO=4`); if (isNodejs) { socket.on("error", () => {}); } - waitFor(socket, "close"); + await waitFor(socket, "close"); - const socket2 = new WebSocket( + const socket2 = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=abc` ); @@ -175,7 +194,7 @@ describe("Engine.IO protocol", () => { socket2.on("error", () => {}); } - waitFor(socket2, "close"); + await waitFor(socket2, "close"); }); }); }); @@ -317,7 +336,7 @@ describe("Engine.IO protocol", () => { describe("WebSocket", () => { it("sends and receives a plain text packet", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket` ); @@ -335,7 +354,7 @@ describe("Engine.IO protocol", () => { }); it("sends and receives a binary packet", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket` ); socket.binaryType = "arraybuffer"; @@ -352,7 +371,7 @@ describe("Engine.IO protocol", () => { }); it("closes the session upon invalid packet format", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket` ); @@ -412,7 +431,7 @@ describe("Engine.IO protocol", () => { describe("WebSocket", () => { it("sends ping/pong packets", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket` ); @@ -430,7 +449,7 @@ describe("Engine.IO protocol", () => { }); it("closes the session upon ping timeout", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket` ); @@ -468,7 +487,7 @@ describe("Engine.IO protocol", () => { describe("WebSocket", () => { it("forcefully closes the session", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket` ); @@ -485,7 +504,7 @@ describe("Engine.IO protocol", () => { it("successfully upgrades from HTTP long-polling to WebSocket", async () => { const sid = await initLongPollingSession(); - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` ); @@ -521,12 +540,13 @@ describe("Engine.IO protocol", () => { it("ignores HTTP requests with same sid after upgrade", async () => { const sid = await initLongPollingSession(); - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` ); await waitFor(socket, "open"); socket.send("2probe"); + await waitFor(socket, "message"); // "3probe" socket.send("5"); const pollResponse = await fetch( @@ -545,15 +565,16 @@ describe("Engine.IO protocol", () => { it("ignores WebSocket connection with same sid after upgrade", async () => { const sid = await initLongPollingSession(); - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` ); await waitFor(socket, "open"); socket.send("2probe"); + await waitFor(socket, "message"); // "3probe" socket.send("5"); - const socket2 = new WebSocket( + const socket2 = createWebSocket( `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` ); diff --git a/docs/socket.io-protocol/v5-test-suite/test-suite.js b/docs/socket.io-protocol/v5-test-suite/test-suite.js index aaee882e0d..a00e433ddb 100644 --- a/docs/socket.io-protocol/v5-test-suite/test-suite.js +++ b/docs/socket.io-protocol/v5-test-suite/test-suite.js @@ -17,16 +17,35 @@ function sleep(delay) { return new Promise((resolve) => setTimeout(resolve, delay)); } +function createWebSocket(url) { + const socket = new WebSocket(url); + socket._eventBuffer = {}; + socket._pendingPromises = {}; + + for (const eventType of ["open", "close", "message"]) { + socket._eventBuffer[eventType] = []; + socket._pendingPromises[eventType] = []; + + socket.addEventListener(eventType, (event) => { + if (socket._pendingPromises[eventType].length) { + socket._pendingPromises[eventType].shift()(event); + } else { + socket._eventBuffer[eventType].push(event); + } + }); + } + + return socket; +} + function waitFor(socket, eventType) { - return new Promise((resolve) => { - socket.addEventListener( - eventType, - (event) => { - resolve(event); - }, - { once: true } - ); - }); + if (socket._eventBuffer[eventType].length) { + return Promise.resolve(socket._eventBuffer[eventType].shift()); + } else { + return new Promise((resolve) => { + socket._pendingPromises[eventType].push(resolve); + }); + } } function waitForPackets(socket, count) { @@ -55,7 +74,7 @@ async function initLongPollingSession() { } async function initSocketIOConnection() { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); socket.binaryType = "arraybuffer"; @@ -145,7 +164,7 @@ describe("Engine.IO protocol", () => { describe("WebSocket", () => { it("should successfully open a session", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -172,7 +191,7 @@ describe("Engine.IO protocol", () => { }); it("should fail with an invalid 'EIO' query parameter", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?transport=websocket` ); @@ -180,9 +199,9 @@ describe("Engine.IO protocol", () => { socket.on("error", () => {}); } - waitFor(socket, "close"); + await waitFor(socket, "close"); - const socket2 = new WebSocket( + const socket2 = createWebSocket( `${WS_URL}/socket.io/?EIO=abc&transport=websocket` ); @@ -190,19 +209,19 @@ describe("Engine.IO protocol", () => { socket2.on("error", () => {}); } - waitFor(socket2, "close"); + await waitFor(socket2, "close"); }); it("should fail with an invalid 'transport' query parameter", async () => { - const socket = new WebSocket(`${WS_URL}/socket.io/?EIO=4`); + const socket = createWebSocket(`${WS_URL}/socket.io/?EIO=4`); if (isNodejs) { socket.on("error", () => {}); } - waitFor(socket, "close"); + await waitFor(socket, "close"); - const socket2 = new WebSocket( + const socket2 = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=abc` ); @@ -210,7 +229,7 @@ describe("Engine.IO protocol", () => { socket2.on("error", () => {}); } - waitFor(socket2, "close"); + await waitFor(socket2, "close"); }); }); }); @@ -260,7 +279,7 @@ describe("Engine.IO protocol", () => { describe("WebSocket", () => { it("should send ping/pong packets", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -278,7 +297,7 @@ describe("Engine.IO protocol", () => { }); it("should close the session upon ping timeout", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -316,7 +335,7 @@ describe("Engine.IO protocol", () => { describe("WebSocket", () => { it("should forcefully close the session", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -333,7 +352,7 @@ describe("Engine.IO protocol", () => { it("should successfully upgrade from HTTP long-polling to WebSocket", async () => { const sid = await initLongPollingSession(); - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` ); @@ -353,12 +372,13 @@ describe("Engine.IO protocol", () => { it("should ignore HTTP requests with same sid after upgrade", async () => { const sid = await initLongPollingSession(); - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` ); await waitFor(socket, "open"); socket.send("2probe"); + await waitFor(socket, "message"); // "3probe" socket.send("5"); const pollResponse = await fetch( @@ -371,15 +391,16 @@ describe("Engine.IO protocol", () => { it("should ignore WebSocket connection with same sid after upgrade", async () => { const sid = await initLongPollingSession(); - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` ); await waitFor(socket, "open"); socket.send("2probe"); + await waitFor(socket, "message"); // "3probe" socket.send("5"); - const socket2 = new WebSocket( + const socket2 = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` ); @@ -391,7 +412,7 @@ describe("Engine.IO protocol", () => { describe("Socket.IO protocol", () => { describe("connect", () => { it("should allow connection to the main namespace", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -414,7 +435,7 @@ describe("Socket.IO protocol", () => { }); it("should allow connection to the main namespace with a payload", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -437,7 +458,7 @@ describe("Socket.IO protocol", () => { }); it("should allow connection to a custom namespace", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -460,7 +481,7 @@ describe("Socket.IO protocol", () => { }); it("should allow connection to a custom namespace with a payload", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -483,7 +504,7 @@ describe("Socket.IO protocol", () => { }); it("should disallow connection to an unknown namespace", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -497,7 +518,7 @@ describe("Socket.IO protocol", () => { }); it("should disallow connection with an invalid handshake", async () => { - const socket = new WebSocket( + const socket = createWebSocket( `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); @@ -508,10 +529,9 @@ describe("Socket.IO protocol", () => { await waitFor(socket, "close"); }); - it("should close the connection if no handshake is received", async () => { - const socket = new WebSocket( - `${WS_URL}/socket.io/?EIO=4&transport=websocket` + const socket = createWebSocket( + `${WS_URL}/socket.io/?EIO=4&transport=websocket` ); await waitFor(socket, "close"); diff --git a/package-lock.json b/package-lock.json index f0b8ca918d..33b83d3ba7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "base64-arraybuffer": "^1.0.2", "benchmark": "^2.1.4", "blob": "^0.1.0", + "cookie": "~0.7.2", "eiows": "^7.1.0", "engine.io-client-v3": "npm:engine.io-client@^3.5.2", "expect.js": "^0.3.1", @@ -2823,11 +2824,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", @@ -15511,10 +15507,9 @@ } }, "packages/engine.io": { - "version": "6.6.2", + "version": "6.6.3", "license": "MIT", "dependencies": { - "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", @@ -15530,7 +15525,7 @@ } }, "packages/engine.io-client": { - "version": "6.6.1", + "version": "6.6.3", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -15572,7 +15567,7 @@ } }, "packages/socket.io": { - "version": "4.8.0", + "version": "4.8.1", "license": "MIT", "dependencies": { "accepts": "~1.3.4", @@ -15596,7 +15591,7 @@ } }, "packages/socket.io-client": { - "version": "4.8.0", + "version": "4.8.1", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", diff --git a/package.json b/package.json index 1c249dbcf2..f6b3a04419 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "base64-arraybuffer": "^1.0.2", "benchmark": "^2.1.4", "blob": "^0.1.0", + "cookie": "~0.7.2", "eiows": "^7.1.0", "engine.io-client-v3": "npm:engine.io-client@^3.5.2", "expect.js": "^0.3.1", diff --git a/packages/engine.io-client/CHANGELOG.md b/packages/engine.io-client/CHANGELOG.md index 2a090dcb18..3553b84b62 100644 --- a/packages/engine.io-client/CHANGELOG.md +++ b/packages/engine.io-client/CHANGELOG.md @@ -2,6 +2,7 @@ | Version | Release date | Bundle size (UMD min+gzip) | |-------------------------------------------------------------------------------------------------------------|----------------|----------------------------| +| [6.6.3](#663-2025-01-23) | January 2025 | `8.7 KB` | | [6.6.2](#662-2024-10-23) | October 2024 | `8.7 KB` | | [6.6.1](#661-2024-09-21) | September 2024 | `8.7 KB` | | [6.6.0](#660-2024-06-21) | June 2024 | `8.6 KB` | @@ -40,6 +41,20 @@ # Release notes +## [6.6.3](https://github.com/socketio/socket.io/compare/engine.io-client@6.6.2...engine.io-client@6.6.3) (2025-01-23) + + +### Bug Fixes + +* correctly consume the `ws` package ([#5220](https://github.com/socketio/socket.io/issues/5220)) ([7fcddcb](https://github.com/socketio/socket.io/commit/7fcddcb3bbd236b46aa8fee6f4ce6c45afb7b03a)) + + +### Dependencies + +- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change) + + + ## [6.6.2](https://github.com/socketio/socket.io/compare/engine.io-client@6.6.1...engine.io-client@6.6.2) (2024-10-23) diff --git a/packages/engine.io-client/dist/engine.io.esm.min.js b/packages/engine.io-client/dist/engine.io.esm.min.js index a37741a87e..4dc071a834 100644 --- a/packages/engine.io-client/dist/engine.io.esm.min.js +++ b/packages/engine.io-client/dist/engine.io.esm.min.js @@ -1,6 +1,6 @@ /*! - * Engine.IO v6.6.2 - * (c) 2014-2024 Guillermo Rauch + * Engine.IO v6.6.3 + * (c) 2014-2025 Guillermo Rauch * Released under the MIT License. */ const t=Object.create(null);t.open="0",t.close="1",t.ping="2",t.pong="3",t.message="4",t.upgrade="5",t.noop="6";const s=Object.create(null);Object.keys(t).forEach((e=>{s[t[e]]=e}));const e={type:"error",data:"parser error"},i="function"==typeof Blob||"undefined"!=typeof Blob&&"[object BlobConstructor]"===Object.prototype.toString.call(Blob),n="function"==typeof ArrayBuffer,r=t=>"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer instanceof ArrayBuffer,o=({type:s,data:e},o,c)=>i&&e instanceof Blob?o?c(e):h(e,c):n&&(e instanceof ArrayBuffer||r(e))?o?c(e):h(new Blob([e]),c):c(t[s]+(e||"")),h=(t,s)=>{const e=new FileReader;return e.onload=function(){const t=e.result.split(",")[1];s("b"+(t||""))},e.readAsDataURL(t)};function c(t){return t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}let a;const u="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",f="undefined"==typeof Uint8Array?[]:new Uint8Array(256);for(let t=0;t<64;t++)f[u.charCodeAt(t)]=t;const l="function"==typeof ArrayBuffer,p=(t,i)=>{if("string"!=typeof t)return{type:"message",data:y(t,i)};const n=t.charAt(0);if("b"===n)return{type:"message",data:d(t.substring(1),i)};return s[n]?t.length>1?{type:s[n],data:t.substring(1)}:{type:s[n]}:e},d=(t,s)=>{if(l){const e=(t=>{let s,e,i,n,r,o=.75*t.length,h=t.length,c=0;"="===t[t.length-1]&&(o--,"="===t[t.length-2]&&o--);const a=new ArrayBuffer(o),u=new Uint8Array(a);for(s=0;s>4,u[c++]=(15&i)<<4|n>>2,u[c++]=(3&n)<<6|63&r;return a})(t);return y(e,s)}return{base64:!0,data:t}},y=(t,s)=>"blob"===s?t instanceof Blob?t:new Blob([t]):t instanceof ArrayBuffer?t:t.buffer,g=String.fromCharCode(30);function w(){return new TransformStream({transform(t,s){!function(t,s){i&&t.data instanceof Blob?t.data.arrayBuffer().then(c).then(s):n&&(t.data instanceof ArrayBuffer||r(t.data))?s(c(t.data)):o(t,!1,(t=>{a||(a=new TextEncoder),s(a.encode(t))}))}(t,(e=>{const i=e.length;let n;if(i<126)n=new Uint8Array(1),new DataView(n.buffer).setUint8(0,i);else if(i<65536){n=new Uint8Array(3);const t=new DataView(n.buffer);t.setUint8(0,126),t.setUint16(1,i)}else{n=new Uint8Array(9);const t=new DataView(n.buffer);t.setUint8(0,127),t.setBigUint64(1,BigInt(i))}t.data&&"string"!=typeof t.data&&(n[0]|=128),s.enqueue(n),s.enqueue(e)}))}})}let b;function m(t){return t.reduce(((t,s)=>t+s.length),0)}function v(t,s){if(t[0].length===s)return t.shift();const e=new Uint8Array(s);let i=0;for(let n=0;nPromise.resolve().then(t):(t,s)=>s(t,0),E="undefined"!=typeof self?self:"undefined"!=typeof window?window:Function("return this")();function A(t,...s){return s.reduce(((s,e)=>(t.hasOwnProperty(e)&&(s[e]=t[e]),s)),{})}const U=E.setTimeout,B=E.clearTimeout;function O(t,s){s.useNativeTimers?(t.setTimeoutFn=U.bind(E),t.clearTimeoutFn=B.bind(E)):(t.setTimeoutFn=E.setTimeout.bind(E),t.clearTimeoutFn=E.clearTimeout.bind(E))}function T(){return Date.now().toString(36).substring(3)+Math.random().toString(36).substring(2,5)}class _ extends Error{constructor(t,s,e){super(t),this.description=s,this.context=e,this.type="TransportError"}}class C extends k{constructor(t){super(),this.writable=!1,O(this,t),this.opts=t,this.query=t.query,this.socket=t.socket,this.supportsBinary=!t.forceBase64}onError(t,s,e){return super.emitReserved("error",new _(t,s,e)),this}open(){return this.readyState="opening",this.doOpen(),this}close(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this}send(t){"open"===this.readyState&&this.write(t)}onOpen(){this.readyState="open",this.writable=!0,super.emitReserved("open")}onData(t){const s=p(t,this.socket.binaryType);this.onPacket(s)}onPacket(t){super.emitReserved("packet",t)}onClose(t){this.readyState="closed",super.emitReserved("close",t)}pause(t){}createUri(t,s={}){return t+"://"+this.i()+this.o()+this.opts.path+this.h(s)}i(){const t=this.opts.hostname;return-1===t.indexOf(":")?t:"["+t+"]"}o(){return this.opts.port&&(this.opts.secure&&Number(443!==this.opts.port)||!this.opts.secure&&80!==Number(this.opts.port))?":"+this.opts.port:""}h(t){const s=function(t){let s="";for(let e in t)t.hasOwnProperty(e)&&(s.length&&(s+="&"),s+=encodeURIComponent(e)+"="+encodeURIComponent(t[e]));return s}(t);return s.length?"?"+s:""}}class P extends C{constructor(){super(...arguments),this.u=!1}get name(){return"polling"}doOpen(){this.l()}pause(t){this.readyState="pausing";const s=()=>{this.readyState="paused",t()};if(this.u||!this.writable){let t=0;this.u&&(t++,this.once("pollComplete",(function(){--t||s()}))),this.writable||(t++,this.once("drain",(function(){--t||s()})))}else s()}l(){this.u=!0,this.doPoll(),this.emitReserved("poll")}onData(t){((t,s)=>{const e=t.split(g),i=[];for(let t=0;t{if("opening"===this.readyState&&"open"===t.type&&this.onOpen(),"close"===t.type)return this.onClose({description:"transport closed by the server"}),!1;this.onPacket(t)})),"closed"!==this.readyState&&(this.u=!1,this.emitReserved("pollComplete"),"open"===this.readyState&&this.l())}doClose(){const t=()=>{this.write([{type:"close"}])};"open"===this.readyState?t():this.once("open",t)}write(t){this.writable=!1,((t,s)=>{const e=t.length,i=new Array(e);let n=0;t.forEach(((t,r)=>{o(t,!1,(t=>{i[r]=t,++n===e&&s(i.join(g))}))}))})(t,(t=>{this.doWrite(t,(()=>{this.writable=!0,this.emitReserved("drain")}))}))}uri(){const t=this.opts.secure?"https":"http",s=this.query||{};return!1!==this.opts.timestampRequests&&(s[this.opts.timestampParam]=T()),this.supportsBinary||s.sid||(s.b64=1),this.createUri(t,s)}}let j=!1;try{j="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(t){}const D=j;function L(){}class M extends P{constructor(t){if(super(t),"undefined"!=typeof location){const s="https:"===location.protocol;let e=location.port;e||(e=s?"443":"80"),this.xd="undefined"!=typeof location&&t.hostname!==location.hostname||e!==t.port}}doWrite(t,s){const e=this.request({method:"POST",data:t});e.on("success",s),e.on("error",((t,s)=>{this.onError("xhr post error",t,s)}))}doPoll(){const t=this.request();t.on("data",this.onData.bind(this)),t.on("error",((t,s)=>{this.onError("xhr poll error",t,s)})),this.pollXhr=t}}class S extends k{constructor(t,s,e){super(),this.createRequest=t,O(this,e),this.p=e,this.m=e.method||"GET",this.v=s,this.k=void 0!==e.data?e.data:null,this.A()}A(){var t;const s=A(this.p,"agent","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","autoUnref");s.xdomain=!!this.p.xd;const e=this.U=this.createRequest(s);try{e.open(this.m,this.v,!0);try{if(this.p.extraHeaders){e.setDisableHeaderCheck&&e.setDisableHeaderCheck(!0);for(let t in this.p.extraHeaders)this.p.extraHeaders.hasOwnProperty(t)&&e.setRequestHeader(t,this.p.extraHeaders[t])}}catch(t){}if("POST"===this.m)try{e.setRequestHeader("Content-type","text/plain;charset=UTF-8")}catch(t){}try{e.setRequestHeader("Accept","*/*")}catch(t){}null===(t=this.p.cookieJar)||void 0===t||t.addCookies(e),"withCredentials"in e&&(e.withCredentials=this.p.withCredentials),this.p.requestTimeout&&(e.timeout=this.p.requestTimeout),e.onreadystatechange=()=>{var t;3===e.readyState&&(null===(t=this.p.cookieJar)||void 0===t||t.parseCookies(e.getResponseHeader("set-cookie"))),4===e.readyState&&(200===e.status||1223===e.status?this.B():this.setTimeoutFn((()=>{this.O("number"==typeof e.status?e.status:0)}),0))},e.send(this.k)}catch(t){return void this.setTimeoutFn((()=>{this.O(t)}),0)}"undefined"!=typeof document&&(this.T=S.requestsCount++,S.requests[this.T]=this)}O(t){this.emitReserved("error",t,this.U),this._(!0)}_(t){if(void 0!==this.U&&null!==this.U){if(this.U.onreadystatechange=L,t)try{this.U.abort()}catch(t){}"undefined"!=typeof document&&delete S.requests[this.T],this.U=null}}B(){const t=this.U.responseText;null!==t&&(this.emitReserved("data",t),this.emitReserved("success"),this._())}abort(){this._()}}if(S.requestsCount=0,S.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",R);else if("function"==typeof addEventListener){addEventListener("onpagehide"in E?"pagehide":"unload",R,!1)}function R(){for(let t in S.requests)S.requests.hasOwnProperty(t)&&S.requests[t].abort()}const H=function(){const t=q({xdomain:!1});return t&&null!==t.responseType}();class $ extends M{constructor(t){super(t);const s=t&&t.forceBase64;this.supportsBinary=H&&!s}request(t={}){return Object.assign(t,{xd:this.xd},this.opts),new S(q,this.uri(),t)}}function q(t){const s=t.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!s||D))return new XMLHttpRequest}catch(t){}if(!s)try{return new(E[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(t){}}const I="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase();class W extends C{get name(){return"websocket"}doOpen(){const t=this.uri(),s=this.opts.protocols,e=I?{}:A(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(e.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(t,s,e)}catch(t){return this.emitReserved("error",t)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()}addEventListeners(){this.ws.onopen=()=>{this.opts.autoUnref&&this.ws.C.unref(),this.onOpen()},this.ws.onclose=t=>this.onClose({description:"websocket connection closed",context:t}),this.ws.onmessage=t=>this.onData(t.data),this.ws.onerror=t=>this.onError("websocket error",t)}write(t){this.writable=!1;for(let s=0;s{try{this.doWrite(e,t)}catch(t){}i&&x((()=>{this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){void 0!==this.ws&&(this.ws.onerror=()=>{},this.ws.close(),this.ws=null)}uri(){const t=this.opts.secure?"wss":"ws",s=this.query||{};return this.opts.timestampRequests&&(s[this.opts.timestampParam]=T()),this.supportsBinary||(s.b64=1),this.createUri(t,s)}}const N=E.WebSocket||E.MozWebSocket;class X extends W{createSocket(t,s,e){return I?new N(t,s,e):s?new N(t,s):new N(t)}doWrite(t,s){this.ws.send(s)}}class V extends C{get name(){return"webtransport"}doOpen(){try{this.P=new WebTransport(this.createUri("https"),this.opts.transportOptions[this.name])}catch(t){return this.emitReserved("error",t)}this.P.closed.then((()=>{this.onClose()})).catch((t=>{this.onError("webtransport error",t)})),this.P.ready.then((()=>{this.P.createBidirectionalStream().then((t=>{const s=function(t,s){b||(b=new TextDecoder);const i=[];let n=0,r=-1,o=!1;return new TransformStream({transform(h,c){for(i.push(h);;){if(0===n){if(m(i)<1)break;const t=v(i,1);o=!(128&~t[0]),r=127&t[0],n=r<126?3:126===r?1:2}else if(1===n){if(m(i)<2)break;const t=v(i,2);r=new DataView(t.buffer,t.byteOffset,t.length).getUint16(0),n=3}else if(2===n){if(m(i)<8)break;const t=v(i,8),s=new DataView(t.buffer,t.byteOffset,t.length),o=s.getUint32(0);if(o>Math.pow(2,21)-1){c.enqueue(e);break}r=o*Math.pow(2,32)+s.getUint32(4),n=3}else{if(m(i)t){c.enqueue(e);break}}}})}(Number.MAX_SAFE_INTEGER,this.socket.binaryType),i=t.readable.pipeThrough(s).getReader(),n=w();n.readable.pipeTo(t.writable),this.j=n.writable.getWriter();const r=()=>{i.read().then((({done:t,value:s})=>{t||(this.onPacket(s),r())})).catch((t=>{}))};r();const o={type:"open"};this.query.sid&&(o.data=`{"sid":"${this.query.sid}"}`),this.j.write(o).then((()=>this.onOpen()))}))}))}write(t){this.writable=!1;for(let s=0;s{i&&x((()=>{this.writable=!0,this.emitReserved("drain")}),this.setTimeoutFn)}))}}doClose(){var t;null===(t=this.P)||void 0===t||t.close()}}const F={websocket:X,webtransport:V,polling:$},z=/^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,G=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];function J(t){if(t.length>8e3)throw"URI too long";const s=t,e=t.indexOf("["),i=t.indexOf("]");-1!=e&&-1!=i&&(t=t.substring(0,e)+t.substring(e,i).replace(/:/g,";")+t.substring(i,t.length));let n=z.exec(t||""),r={},o=14;for(;o--;)r[G[o]]=n[o]||"";return-1!=e&&-1!=i&&(r.source=s,r.host=r.host.substring(1,r.host.length-1).replace(/;/g,":"),r.authority=r.authority.replace("[","").replace("]","").replace(/;/g,":"),r.ipv6uri=!0),r.pathNames=function(t,s){const e=/\/{2,9}/g,i=s.replace(e,"/").split("/");"/"!=s.slice(0,1)&&0!==s.length||i.splice(0,1);"/"==s.slice(-1)&&i.splice(i.length-1,1);return i}(0,r.path),r.queryKey=function(t,s){const e={};return s.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(t,s,i){s&&(e[s]=i)})),e}(0,r.query),r}const K="function"==typeof addEventListener&&"function"==typeof removeEventListener,Q=[];K&&addEventListener("offline",(()=>{Q.forEach((t=>t()))}),!1);class Y extends k{constructor(t,s){if(super(),this.binaryType="arraybuffer",this.writeBuffer=[],this.D=0,this.L=-1,this.M=-1,this.S=-1,this.R=1/0,t&&"object"==typeof t&&(s=t,t=null),t){const e=J(t);s.hostname=e.host,s.secure="https"===e.protocol||"wss"===e.protocol,s.port=e.port,e.query&&(s.query=e.query)}else s.host&&(s.hostname=J(s.host).host);O(this,s),this.secure=null!=s.secure?s.secure:"undefined"!=typeof location&&"https:"===location.protocol,s.hostname&&!s.port&&(s.port=this.secure?"443":"80"),this.hostname=s.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=s.port||("undefined"!=typeof location&&location.port?location.port:this.secure?"443":"80"),this.transports=[],this.H={},s.transports.forEach((t=>{const s=t.prototype.name;this.transports.push(s),this.H[s]=t})),this.opts=Object.assign({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},s),this.opts.path=this.opts.path.replace(/\/$/,"")+(this.opts.addTrailingSlash?"/":""),"string"==typeof this.opts.query&&(this.opts.query=function(t){let s={},e=t.split("&");for(let t=0,i=e.length;t{this.transport&&(this.transport.removeAllListeners(),this.transport.close())},addEventListener("beforeunload",this.$,!1)),"localhost"!==this.hostname&&(this.q=()=>{this.I("transport close",{description:"network connection lost"})},Q.push(this.q))),this.opts.withCredentials&&(this.W=void 0),this.N()}createTransport(t){const s=Object.assign({},this.opts.query);s.EIO=4,s.transport=t,this.id&&(s.sid=this.id);const e=Object.assign({},this.opts,{query:s,socket:this,hostname:this.hostname,secure:this.secure,port:this.port},this.opts.transportOptions[t]);return new this.H[t](e)}N(){if(0===this.transports.length)return void this.setTimeoutFn((()=>{this.emitReserved("error","No transports available")}),0);const t=this.opts.rememberUpgrade&&Y.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket")?"websocket":this.transports[0];this.readyState="opening";const s=this.createTransport(t);s.open(),this.setTransport(s)}setTransport(t){this.transport&&this.transport.removeAllListeners(),this.transport=t,t.on("drain",this.X.bind(this)).on("packet",this.V.bind(this)).on("error",this.O.bind(this)).on("close",(t=>this.I("transport close",t)))}onOpen(){this.readyState="open",Y.priorWebsocketSuccess="websocket"===this.transport.name,this.emitReserved("open"),this.flush()}V(t){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState)switch(this.emitReserved("packet",t),this.emitReserved("heartbeat"),t.type){case"open":this.onHandshake(JSON.parse(t.data));break;case"ping":this.F("pong"),this.emitReserved("ping"),this.emitReserved("pong"),this.G();break;case"error":const s=new Error("server error");s.code=t.data,this.O(s);break;case"message":this.emitReserved("data",t.data),this.emitReserved("message",t.data)}}onHandshake(t){this.emitReserved("handshake",t),this.id=t.sid,this.transport.query.sid=t.sid,this.L=t.pingInterval,this.M=t.pingTimeout,this.S=t.maxPayload,this.onOpen(),"closed"!==this.readyState&&this.G()}G(){this.clearTimeoutFn(this.J);const t=this.L+this.M;this.R=Date.now()+t,this.J=this.setTimeoutFn((()=>{this.I("ping timeout")}),t),this.opts.autoUnref&&this.J.unref()}X(){this.writeBuffer.splice(0,this.D),this.D=0,0===this.writeBuffer.length?this.emitReserved("drain"):this.flush()}flush(){if("closed"!==this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){const t=this.K();this.transport.send(t),this.D=t.length,this.emitReserved("flush")}}K(){if(!(this.S&&"polling"===this.transport.name&&this.writeBuffer.length>1))return this.writeBuffer;let t=1;for(let e=0;e=57344?e+=3:(i++,e+=4);return e}(s):Math.ceil(1.33*(s.byteLength||s.size))),e>0&&t>this.S)return this.writeBuffer.slice(0,e);t+=2}var s;return this.writeBuffer}Y(){if(!this.R)return!0;const t=Date.now()>this.R;return t&&(this.R=0,x((()=>{this.I("ping timeout")}),this.setTimeoutFn)),t}write(t,s,e){return this.F("message",t,s,e),this}send(t,s,e){return this.F("message",t,s,e),this}F(t,s,e,i){if("function"==typeof s&&(i=s,s=void 0),"function"==typeof e&&(i=e,e=null),"closing"===this.readyState||"closed"===this.readyState)return;(e=e||{}).compress=!1!==e.compress;const n={type:t,data:s,options:e};this.emitReserved("packetCreate",n),this.writeBuffer.push(n),i&&this.once("flush",i),this.flush()}close(){const t=()=>{this.I("forced close"),this.transport.close()},s=()=>{this.off("upgrade",s),this.off("upgradeError",s),t()},e=()=>{this.once("upgrade",s),this.once("upgradeError",s)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(()=>{this.upgrading?e():t()})):this.upgrading?e():t()),this}O(t){if(Y.priorWebsocketSuccess=!1,this.opts.tryAllTransports&&this.transports.length>1&&"opening"===this.readyState)return this.transports.shift(),this.N();this.emitReserved("error",t),this.I("transport error",t)}I(t,s){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState){if(this.clearTimeoutFn(this.J),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),K&&(this.$&&removeEventListener("beforeunload",this.$,!1),this.q)){const t=Q.indexOf(this.q);-1!==t&&Q.splice(t,1)}this.readyState="closed",this.id=null,this.emitReserved("close",t,s),this.writeBuffer=[],this.D=0}}}Y.protocol=4;class Z extends Y{constructor(){super(...arguments),this.Z=[]}onOpen(){if(super.onOpen(),"open"===this.readyState&&this.opts.upgrade)for(let t=0;t{e||(s.send([{type:"ping",data:"probe"}]),s.once("packet",(t=>{if(!e)if("pong"===t.type&&"probe"===t.data){if(this.upgrading=!0,this.emitReserved("upgrading",s),!s)return;Y.priorWebsocketSuccess="websocket"===s.name,this.transport.pause((()=>{e||"closed"!==this.readyState&&(a(),this.setTransport(s),s.send([{type:"upgrade"}]),this.emitReserved("upgrade",s),s=null,this.upgrading=!1,this.flush())}))}else{const t=new Error("probe error");t.transport=s.name,this.emitReserved("upgradeError",t)}})))};function n(){e||(e=!0,a(),s.close(),s=null)}const r=t=>{const e=new Error("probe error: "+t);e.transport=s.name,n(),this.emitReserved("upgradeError",e)};function o(){r("transport closed")}function h(){r("socket closed")}function c(t){s&&t.name!==s.name&&n()}const a=()=>{s.removeListener("open",i),s.removeListener("error",r),s.removeListener("close",o),this.off("close",h),this.off("upgrading",c)};s.once("open",i),s.once("error",r),s.once("close",o),this.once("close",h),this.once("upgrading",c),-1!==this.Z.indexOf("webtransport")&&"webtransport"!==t?this.setTimeoutFn((()=>{e||s.open()}),200):s.open()}onHandshake(t){this.Z=this.st(t.upgrades),super.onHandshake(t)}st(t){const s=[];for(let e=0;eF[t])).filter((t=>!!t))),super(t,e)}}class st extends P{doPoll(){this.et().then((t=>{if(!t.ok)return this.onError("fetch read error",t.status,t);t.text().then((t=>this.onData(t)))})).catch((t=>{this.onError("fetch read error",t)}))}doWrite(t,s){this.et(t).then((t=>{if(!t.ok)return this.onError("fetch write error",t.status,t);s()})).catch((t=>{this.onError("fetch write error",t)}))}et(t){var s;const e=void 0!==t,i=new Headers(this.opts.extraHeaders);return e&&i.set("content-type","text/plain;charset=UTF-8"),null===(s=this.socket.W)||void 0===s||s.appendCookies(i),fetch(this.uri(),{method:e?"POST":"GET",body:e?t:null,headers:i,credentials:this.opts.withCredentials?"include":"omit"}).then((t=>{var s;return null===(s=this.socket.W)||void 0===s||s.parseCookies(t.headers.getSetCookie()),t}))}}const et=tt.protocol;export{st as Fetch,X as NodeWebSocket,$ as NodeXHR,tt as Socket,Z as SocketWithUpgrade,Y as SocketWithoutUpgrade,C as Transport,_ as TransportError,X as WebSocket,V as WebTransport,$ as XHR,O as installTimerFunctions,x as nextTick,J as parse,et as protocol,F as transports}; diff --git a/packages/engine.io-client/dist/engine.io.js b/packages/engine.io-client/dist/engine.io.js index cb2100204f..83d420513f 100644 --- a/packages/engine.io-client/dist/engine.io.js +++ b/packages/engine.io-client/dist/engine.io.js @@ -1,6 +1,6 @@ /*! - * Engine.IO v6.6.2 - * (c) 2014-2024 Guillermo Rauch + * Engine.IO v6.6.3 + * (c) 2014-2025 Guillermo Rauch * Released under the MIT License. */ (function (global, factory) { diff --git a/packages/engine.io-client/dist/engine.io.min.js b/packages/engine.io-client/dist/engine.io.min.js index 76a48e1c67..782d498eee 100644 --- a/packages/engine.io-client/dist/engine.io.min.js +++ b/packages/engine.io-client/dist/engine.io.min.js @@ -1,6 +1,6 @@ /*! - * Engine.IO v6.6.2 - * (c) 2014-2024 Guillermo Rauch + * Engine.IO v6.6.3 + * (c) 2014-2025 Guillermo Rauch * Released under the MIT License. */ !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t="undefined"!=typeof globalThis?globalThis:t||self).eio=n()}(this,(function(){"use strict";function t(t,n){for(var i=0;i1?{type:a[i],data:t.substring(1)}:{type:a[i]}:l},O=function(t,n){if(B){var i=function(t){var n,i,r,e,o,s=.75*t.length,u=t.length,f=0;"="===t[t.length-1]&&(s--,"="===t[t.length-2]&&s--);var h=new ArrayBuffer(s),c=new Uint8Array(h);for(n=0;n>4,c[f++]=(15&r)<<4|e>>2,c[f++]=(3&e)<<6|63&o;return h}(t);return U(i,n)}return{base64:!0,data:t}},U=function(t,n){return"blob"===n?t instanceof Blob?t:new Blob([t]):t instanceof ArrayBuffer?t:t.buffer},T=String.fromCharCode(30);function S(){return new TransformStream({transform:function(t,n){!function(t,n){v&&t.data instanceof Blob?t.data.arrayBuffer().then(w).then(n):d&&(t.data instanceof ArrayBuffer||y(t.data))?n(w(t.data)):b(t,!1,(function(t){p||(p=new TextEncoder),n(p.encode(t))}))}(t,(function(i){var r,e=i.length;if(e<126)r=new Uint8Array(1),new DataView(r.buffer).setUint8(0,e);else if(e<65536){r=new Uint8Array(3);var o=new DataView(r.buffer);o.setUint8(0,126),o.setUint16(1,e)}else{r=new Uint8Array(9);var s=new DataView(r.buffer);s.setUint8(0,127),s.setBigUint64(1,BigInt(e))}t.data&&"string"!=typeof t.data&&(r[0]|=128),n.enqueue(r),n.enqueue(i)}))}})}function M(t){return t.reduce((function(t,n){return t+n.length}),0)}function x(t,n){if(t[0].length===n)return t.shift();for(var i=new Uint8Array(n),r=0,e=0;e1?n-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:{};return t+"://"+this.i()+this.o()+this.opts.path+this.u(n)},i.i=function(){var t=this.opts.hostname;return-1===t.indexOf(":")?t:"["+t+"]"},i.o=function(){return this.opts.port&&(this.opts.secure&&Number(443!==this.opts.port)||!this.opts.secure&&80!==Number(this.opts.port))?":"+this.opts.port:""},i.u=function(t){var n=function(t){var n="";for(var i in t)t.hasOwnProperty(i)&&(n.length&&(n+="&"),n+=encodeURIComponent(i)+"="+encodeURIComponent(t[i]));return n}(t);return n.length?"?"+n:""},n}(C),X=function(t){function i(){var n;return(n=t.apply(this,arguments)||this).h=!1,n}e(i,t);var r=i.prototype;return r.doOpen=function(){this.p()},r.pause=function(t){var n=this;this.readyState="pausing";var i=function(){n.readyState="paused",t()};if(this.h||!this.writable){var r=0;this.h&&(r++,this.once("pollComplete",(function(){--r||i()}))),this.writable||(r++,this.once("drain",(function(){--r||i()})))}else i()},r.p=function(){this.h=!0,this.doPoll(),this.emitReserved("poll")},r.onData=function(t){var n=this;(function(t,n){for(var i=t.split(T),r=[],e=0;e0&&void 0!==arguments[0]?arguments[0]:{};return i(t,{xd:this.xd},this.opts),new J(Z,this.uri(),t)},n}(G);function Z(t){var n=t.xdomain;try{if("undefined"!=typeof XMLHttpRequest&&(!n||F))return new XMLHttpRequest}catch(t){}if(!n)try{return new(R[["Active"].concat("Object").join("X")])("Microsoft.XMLHTTP")}catch(t){}}var _="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),tt=function(t){function i(){return t.apply(this,arguments)||this}e(i,t);var r=i.prototype;return r.doOpen=function(){var t=this.uri(),n=this.opts.protocols,i=_?{}:D(this.opts,"agent","perMessageDeflate","pfx","key","passphrase","cert","ca","ciphers","rejectUnauthorized","localAddress","protocolVersion","origin","maxPayload","family","checkServerIdentity");this.opts.extraHeaders&&(i.headers=this.opts.extraHeaders);try{this.ws=this.createSocket(t,n,i)}catch(t){return this.emitReserved("error",t)}this.ws.binaryType=this.socket.binaryType,this.addEventListeners()},r.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.opts.autoUnref&&t.ws.S.unref(),t.onOpen()},this.ws.onclose=function(n){return t.onClose({description:"websocket connection closed",context:n})},this.ws.onmessage=function(n){return t.onData(n.data)},this.ws.onerror=function(n){return t.onError("websocket error",n)}},r.write=function(t){var n=this;this.writable=!1;for(var i=function(){var i=t[r],e=r===t.length-1;b(i,n.supportsBinary,(function(t){try{n.doWrite(i,t)}catch(t){}e&&L((function(){n.writable=!0,n.emitReserved("drain")}),n.setTimeoutFn)}))},r=0;rMath.pow(2,21)-1){u.enqueue(l);break}e=p*Math.pow(2,32)+a.getUint32(4),r=3}else{if(M(i)t){u.enqueue(l);break}}}})}(Number.MAX_SAFE_INTEGER,t.socket.binaryType),r=n.readable.pipeThrough(i).getReader(),e=S();e.readable.pipeTo(n.writable),t.C=e.writable.getWriter();!function n(){r.read().then((function(i){var r=i.done,e=i.value;r||(t.onPacket(e),n())})).catch((function(t){}))}();var o={type:"open"};t.query.sid&&(o.data='{"sid":"'.concat(t.query.sid,'"}')),t.C.write(o).then((function(){return t.onOpen()}))}))}))},r.write=function(t){var n=this;this.writable=!1;for(var i=function(){var i=t[r],e=r===t.length-1;n.C.write(i).then((function(){e&&L((function(){n.writable=!0,n.emitReserved("drain")}),n.setTimeoutFn)}))},r=0;r8e3)throw"URI too long";var n=t,i=t.indexOf("["),r=t.indexOf("]");-1!=i&&-1!=r&&(t=t.substring(0,i)+t.substring(i,r).replace(/:/g,";")+t.substring(r,t.length));for(var e,o,s=ot.exec(t||""),u={},f=14;f--;)u[st[f]]=s[f]||"";return-1!=i&&-1!=r&&(u.source=n,u.host=u.host.substring(1,u.host.length-1).replace(/;/g,":"),u.authority=u.authority.replace("[","").replace("]","").replace(/;/g,":"),u.ipv6uri=!0),u.pathNames=function(t,n){var i=/\/{2,9}/g,r=n.replace(i,"/").split("/");"/"!=n.slice(0,1)&&0!==n.length||r.splice(0,1);"/"==n.slice(-1)&&r.splice(r.length-1,1);return r}(0,u.path),u.queryKey=(e=u.query,o={},e.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,(function(t,n,i){n&&(o[n]=i)})),o),u}var ft="function"==typeof addEventListener&&"function"==typeof removeEventListener,ht=[];ft&&addEventListener("offline",(function(){ht.forEach((function(t){return t()}))}),!1);var ct=function(t){function n(n,r){var e;if((e=t.call(this)||this).binaryType="arraybuffer",e.writeBuffer=[],e.L=0,e.R=-1,e.D=-1,e.P=-1,e.I=1/0,n&&"object"===f(n)&&(r=n,n=null),n){var o=ut(n);r.hostname=o.host,r.secure="https"===o.protocol||"wss"===o.protocol,r.port=o.port,o.query&&(r.query=o.query)}else r.host&&(r.hostname=ut(r.host).host);return $(e,r),e.secure=null!=r.secure?r.secure:"undefined"!=typeof location&&"https:"===location.protocol,r.hostname&&!r.port&&(r.port=e.secure?"443":"80"),e.hostname=r.hostname||("undefined"!=typeof location?location.hostname:"localhost"),e.port=r.port||("undefined"!=typeof location&&location.port?location.port:e.secure?"443":"80"),e.transports=[],e.$={},r.transports.forEach((function(t){var n=t.prototype.name;e.transports.push(n),e.$[n]=t})),e.opts=i({path:"/engine.io",agent:!1,withCredentials:!1,upgrade:!0,timestampParam:"t",rememberUpgrade:!1,addTrailingSlash:!0,rejectUnauthorized:!0,perMessageDeflate:{threshold:1024},transportOptions:{},closeOnBeforeunload:!1},r),e.opts.path=e.opts.path.replace(/\/$/,"")+(e.opts.addTrailingSlash?"/":""),"string"==typeof e.opts.query&&(e.opts.query=function(t){for(var n={},i=t.split("&"),r=0,e=i.length;r1))return this.writeBuffer;for(var t,n=1,i=0;i=57344?i+=3:(r++,i+=4);return i}(t):Math.ceil(1.33*(t.byteLength||t.size))),i>0&&n>this.P)return this.writeBuffer.slice(0,i);n+=2}return this.writeBuffer},r.Z=function(){var t=this;if(!this.I)return!0;var n=Date.now()>this.I;return n&&(this.I=0,L((function(){t.V("ping timeout")}),this.setTimeoutFn)),n},r.write=function(t,n,i){return this.J("message",t,n,i),this},r.send=function(t,n,i){return this.J("message",t,n,i),this},r.J=function(t,n,i,r){if("function"==typeof n&&(r=n,n=void 0),"function"==typeof i&&(r=i,i=null),"closing"!==this.readyState&&"closed"!==this.readyState){(i=i||{}).compress=!1!==i.compress;var e={type:t,data:n,options:i};this.emitReserved("packetCreate",e),this.writeBuffer.push(e),r&&this.once("flush",r),this.flush()}},r.close=function(){var t=this,n=function(){t.V("forced close"),t.transport.close()},i=function i(){t.off("upgrade",i),t.off("upgradeError",i),n()},r=function(){t.once("upgrade",i),t.once("upgradeError",i)};return"opening"!==this.readyState&&"open"!==this.readyState||(this.readyState="closing",this.writeBuffer.length?this.once("drain",(function(){t.upgrading?r():n()})):this.upgrading?r():n()),this},r.O=function(t){if(n.priorWebsocketSuccess=!1,this.opts.tryAllTransports&&this.transports.length>1&&"opening"===this.readyState)return this.transports.shift(),this.H();this.emitReserved("error",t),this.V("transport error",t)},r.V=function(t,n){if("opening"===this.readyState||"open"===this.readyState||"closing"===this.readyState){if(this.clearTimeoutFn(this.K),this.transport.removeAllListeners("close"),this.transport.close(),this.transport.removeAllListeners(),ft&&(this.q&&removeEventListener("beforeunload",this.q,!1),this.N)){var i=ht.indexOf(this.N);-1!==i&&ht.splice(i,1)}this.readyState="closed",this.id=null,this.emitReserved("close",t,n),this.writeBuffer=[],this.L=0}},n}(C);ct.protocol=4;var at=function(t){function n(){var n;return(n=t.apply(this,arguments)||this)._=[],n}e(n,t);var i=n.prototype;return i.onOpen=function(){if(t.prototype.onOpen.call(this),"open"===this.readyState&&this.opts.upgrade)for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},r="object"===f(n)?n:i;return(!r.transports||r.transports&&"string"==typeof r.transports[0])&&(r.transports=(r.transports||["polling","websocket","webtransport"]).map((function(t){return et[t]})).filter((function(t){return!!t}))),t.call(this,n,r)||this}return e(n,t),n}(at);return function(t,n){return new pt(t,n)}})); diff --git a/packages/engine.io-client/lib/transports/websocket.node.ts b/packages/engine.io-client/lib/transports/websocket.node.ts index 83777991c1..c587cceb73 100644 --- a/packages/engine.io-client/lib/transports/websocket.node.ts +++ b/packages/engine.io-client/lib/transports/websocket.node.ts @@ -1,4 +1,4 @@ -import { WebSocket } from "ws"; +import * as ws from "ws"; import type { Packet, RawData } from "engine.io-parser"; import { BaseWS } from "./websocket.js"; @@ -27,7 +27,7 @@ export class WS extends BaseWS { opts.headers.cookie.push(`${name}=${cookie.value}`); } } - return new WebSocket(uri, protocols, opts); + return new ws.WebSocket(uri, protocols, opts); } doWrite(packet: Packet, data: RawData) { diff --git a/packages/engine.io-client/package.json b/packages/engine.io-client/package.json index 95c5f2bba4..6cad940749 100644 --- a/packages/engine.io-client/package.json +++ b/packages/engine.io-client/package.json @@ -2,7 +2,7 @@ "name": "engine.io-client", "description": "Client for the realtime Engine", "license": "MIT", - "version": "6.6.2", + "version": "6.6.3", "main": "./build/cjs/index.js", "module": "./build/esm/index.js", "exports": { diff --git a/packages/engine.io-parser/lib/commons.ts b/packages/engine.io-parser/lib/commons.ts index 2562382f72..bbbd43c262 100644 --- a/packages/engine.io-parser/lib/commons.ts +++ b/packages/engine.io-parser/lib/commons.ts @@ -32,7 +32,11 @@ export type RawData = any; export interface Packet { type: PacketType; - options?: { compress: boolean }; + options?: { + compress: boolean; + wsPreEncoded?: string; // deprecated in favor of `wsPreEncodedFrame` + wsPreEncodedFrame?: any; // computed in the socket.io-adapter package (should be typed as Buffer) + }; data?: RawData; } diff --git a/packages/engine.io/CHANGELOG.md b/packages/engine.io/CHANGELOG.md index 7105615476..0ae2948832 100644 --- a/packages/engine.io/CHANGELOG.md +++ b/packages/engine.io/CHANGELOG.md @@ -2,6 +2,8 @@ | Version | Release date | |------------------------------------------------------------------------------------------------------|----------------| +| [6.6.4](#664-2025-01-28) | January 2025 | +| [6.6.3](#663-2025-01-23) | January 2025 | | [6.6.2](#662-2024-10-09) | October 2024 | | [6.6.1](#661-2024-09-21) | September 2024 | | [6.6.0](#660-2024-06-21) | June 2024 | @@ -47,6 +49,30 @@ # Release notes +## [6.6.4](https://github.com/socketio/socket.io/compare/engine.io@6.6.3...engine.io@6.6.4) (2025-01-28) + +The bump of the `cookie` dependency was reverted, as it drops support for older Node.js versions (< 14). + + +### Dependencies + +- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change) + + + +## [6.6.3](https://github.com/socketio/socket.io/compare/engine.io@6.6.2...engine.io@6.6.3) (2025-01-23) + +This release contains a bump of the `cookie` dependency. + +Release notes: https://github.com/jshttp/cookie/releases/tag/v1.0.0 + + +### Dependencies + +- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change) + + + ## [6.6.2](https://github.com/socketio/socket.io/compare/engine.io@6.6.1...engine.io@6.6.2) (2024-10-09) This release contains a bump of the `cookie` dependency. diff --git a/packages/engine.io/lib/contrib/types.cookie.ts b/packages/engine.io/lib/contrib/types.cookie.ts new file mode 100644 index 0000000000..7e546707e9 --- /dev/null +++ b/packages/engine.io/lib/contrib/types.cookie.ts @@ -0,0 +1,117 @@ +// imported from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b83cf9ef8b044e69f05b2a00aa7c6cb767a9acd2/types/cookie/index.d.ts (now deleted) +/** + * Basic HTTP cookie parser and serializer for HTTP servers. + */ + +/** + * Additional serialization options + */ +export interface CookieSerializeOptions { + /** + * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.3|Domain Set-Cookie attribute}. By default, no + * domain is set, and most clients will consider the cookie to apply to only + * the current domain. + */ + domain?: string | undefined; + + /** + * Specifies a function that will be used to encode a cookie's value. Since + * value of a cookie has a limited character set (and must be a simple + * string), this function can be used to encode a value into a string suited + * for a cookie's value. + * + * The default function is the global `encodeURIComponent`, which will + * encode a JavaScript string into UTF-8 byte sequences and then URL-encode + * any that fall outside of the cookie range. + */ + encode?(value: string): string; + + /** + * Specifies the `Date` object to be the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.1|`Expires` `Set-Cookie` attribute}. By default, + * no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete + * it on a condition like exiting a web browser application. + * + * *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification} + * states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is + * possible not all clients by obey this, so if both are set, they should + * point to the same date and time. + */ + expires?: Date | undefined; + /** + * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute}. + * When truthy, the `HttpOnly` attribute is set, otherwise it is not. By + * default, the `HttpOnly` attribute is not set. + * + * *Note* be careful when setting this to true, as compliant clients will + * not allow client-side JavaScript to see the cookie in `document.cookie`. + */ + httpOnly?: boolean | undefined; + /** + * Specifies the number (in seconds) to be the value for the `Max-Age` + * `Set-Cookie` attribute. The given number will be converted to an integer + * by rounding down. By default, no maximum age is set. + * + * *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification} + * states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is + * possible not all clients by obey this, so if both are set, they should + * point to the same date and time. + */ + maxAge?: number | undefined; + /** + * Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) + * attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the + * `Partitioned` attribute is not set. + * + * **note** This is an attribute that has not yet been fully standardized, and may change in the future. + * This also means many clients may ignore this attribute until they understand it. + * + * More information about can be found in [the proposal](https://github.com/privacycg/CHIPS) + */ + partitioned?: boolean | undefined; + /** + * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute}. + * By default, the path is considered the "default path". + */ + path?: string | undefined; + /** + * Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1]. + * + * - `'low'` will set the `Priority` attribute to `Low`. + * - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. + * - `'high'` will set the `Priority` attribute to `High`. + * + * More information about the different priority levels can be found in + * [the specification][rfc-west-cookie-priority-00-4.1]. + * + * **note** This is an attribute that has not yet been fully standardized, and may change in the future. + * This also means many clients may ignore this attribute until they understand it. + */ + priority?: "low" | "medium" | "high" | undefined; + /** + * Specifies the boolean or string to be the value for the {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute}. + * + * - `true` will set the `SameSite` attribute to `Strict` for strict same + * site enforcement. + * - `false` will not set the `SameSite` attribute. + * - `'lax'` will set the `SameSite` attribute to Lax for lax same site + * enforcement. + * - `'strict'` will set the `SameSite` attribute to Strict for strict same + * site enforcement. + * - `'none'` will set the SameSite attribute to None for an explicit + * cross-site cookie. + * + * More information about the different enforcement levels can be found in {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification}. + * + * *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. + */ + sameSite?: true | false | "lax" | "strict" | "none" | undefined; + /** + * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute}. When truthy, the + * `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. + * + * *Note* be careful when setting this to `true`, as compliant clients will + * not send the cookie back to the server in the future if the browser does + * not have an HTTPS connection. + */ + secure?: boolean | undefined; +} diff --git a/packages/engine.io/lib/engine.io.ts b/packages/engine.io/lib/engine.io.ts index 3ba9e67f8f..54ed1a4c9b 100644 --- a/packages/engine.io/lib/engine.io.ts +++ b/packages/engine.io/lib/engine.io.ts @@ -1,27 +1,36 @@ -import { createServer } from "http"; +import { createServer, Server as HttpServer } from "http"; import { Server, AttachOptions, ServerOptions } from "./server"; import transports from "./transports/index"; import * as parser from "engine.io-parser"; export { Server, transports, listen, attach, parser }; -export type { AttachOptions, ServerOptions, BaseServer } from "./server"; +export type { + AttachOptions, + ServerOptions, + BaseServer, + ErrorCallback, +} from "./server"; export { uServer } from "./userver"; export { Socket } from "./socket"; export { Transport } from "./transport"; export const protocol = parser.protocol; /** - * Creates an http.Server exclusively used for WS upgrades. + * Creates an http.Server exclusively used for WS upgrades, and starts listening. * - * @param {Number} port - * @param {Function} callback - * @param {Object} options - * @return {Server} websocket.io server + * @param port + * @param options + * @param listenCallback - callback for http.Server.listen() + * @return engine.io server */ -function listen(port, options: AttachOptions & ServerOptions, fn) { +function listen( + port: number, + options?: AttachOptions & ServerOptions, + listenCallback?: () => void, +): Server { if ("function" === typeof options) { - fn = options; + listenCallback = options; options = {}; } @@ -34,7 +43,7 @@ function listen(port, options: AttachOptions & ServerOptions, fn) { const engine = attach(server, options); engine.httpServer = server; - server.listen(port, fn); + server.listen(port, listenCallback); return engine; } @@ -42,12 +51,15 @@ function listen(port, options: AttachOptions & ServerOptions, fn) { /** * Captures upgrade requests for a http.Server. * - * @param {http.Server} server - * @param {Object} options - * @return {Server} engine server + * @param server + * @param options + * @return engine.io server */ -function attach(server, options: AttachOptions & ServerOptions) { +function attach( + server: HttpServer, + options: AttachOptions & ServerOptions, +): Server { const engine = new Server(options); engine.attach(server, options); return engine; diff --git a/packages/engine.io/lib/parser-v3/index.ts b/packages/engine.io/lib/parser-v3/index.ts index 367ae13767..8cf34dc2f2 100644 --- a/packages/engine.io/lib/parser-v3/index.ts +++ b/packages/engine.io/lib/parser-v3/index.ts @@ -59,8 +59,7 @@ const EMPTY_BUFFER = Buffer.concat([]); * * @api private */ - -export function encodePacket (packet, supportsBinary, utf8encode, callback) { +export function encodePacket (packet: any, supportsBinary?: any, utf8encode?: any, callback?: any) { if (typeof supportsBinary === 'function') { callback = supportsBinary; supportsBinary = null; @@ -86,7 +85,7 @@ export function encodePacket (packet, supportsBinary, utf8encode, callback) { } return callback('' + encoded); -}; +} /** * Encode Buffer data @@ -120,16 +119,16 @@ export function encodeBase64Packet (packet, callback){ /** * Decodes a packet. Data also available as an ArrayBuffer if requested. * - * @return {Object} with `type` and `data` (if any) + * @return {import('engine.io-parser').Packet} with `type` and `data` (if any) * @api private */ -export function decodePacket (data, binaryType, utf8decode) { +export function decodePacket (data: any, binaryType?: any, utf8decode?: any): any { if (data === undefined) { return err; } - var type; + let type: string | number; // String data if (typeof data === 'string') { @@ -147,6 +146,7 @@ export function decodePacket (data, binaryType, utf8decode) { } } + // @ts-expect-error if (Number(type) != type || !packetslist[type]) { return err; } @@ -274,7 +274,7 @@ function map(ary, each, done) { * @api public */ -export function decodePayload (data, binaryType, callback) { +export function decodePayload (data: any, binaryType?: any, callback?: any) { if (typeof data !== 'string') { return decodePayloadAsBinary(data, binaryType, callback); } diff --git a/packages/engine.io/lib/server.ts b/packages/engine.io/lib/server.ts index 0e3ae063ef..1491070017 100644 --- a/packages/engine.io/lib/server.ts +++ b/packages/engine.io/lib/server.ts @@ -6,24 +6,34 @@ import { EventEmitter } from "events"; import { Socket } from "./socket"; import debugModule from "debug"; import { serialize } from "cookie"; -import { Server as DEFAULT_WS_ENGINE } from "ws"; +import { + Server as DEFAULT_WS_ENGINE, + type Server as WsServer, + type PerMessageDeflateOptions, + type WebSocket as WsWebSocket, +} from "ws"; import type { IncomingMessage, Server as HttpServer, ServerResponse, } from "http"; -import type { CookieSerializeOptions } from "cookie"; 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"; +import type { EngineRequest, Transport } from "./transport"; +import type { CookieSerializeOptions } from "./contrib/types.cookie"; const debug = debugModule("engine"); const kResponseHeaders = Symbol("responseHeaders"); -type Transport = "polling" | "websocket" | "webtransport"; +type TransportName = "polling" | "websocket" | "webtransport"; + +export type ErrorCallback = ( + errorCode?: (typeof Server.errors)[keyof typeof Server.errors], + errorContext?: Record & { name?: string; message?: string }, +) => void; export interface AttachOptions { /** @@ -90,7 +100,7 @@ export interface ServerOptions { * * @default ["polling", "websocket"] */ - transports?: Transport[]; + transports?: TransportName[]; /** * whether to allow transport upgrades * @default true @@ -100,7 +110,7 @@ export interface ServerOptions { * parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable. * @default false */ - perMessageDeflate?: boolean | object; + perMessageDeflate?: boolean | PerMessageDeflateOptions; /** * parameters of the http compression for the polling transports (see zlib api docs). Set to false to disable. * @default true @@ -149,7 +159,7 @@ type Middleware = ( next: (err?: any) => void, ) => void; -function parseSessionId(data: string) { +function parseSessionId(data: string): string | undefined { try { const parsed = JSON.parse(data); if (typeof parsed.sid === "string") { @@ -224,7 +234,7 @@ export abstract class BaseServer extends EventEmitter { this.init(); } - protected abstract init(); + protected abstract init(): void; /** * Compute the pathname of the requests that are handled by the server @@ -244,10 +254,8 @@ export abstract class BaseServer extends EventEmitter { /** * Returns a list of available transports for upgrade given a certain transport. - * - * @return {Array} */ - public upgrades(transport: string) { + public upgrades(transport: TransportName): string[] { if (!this.opts.allowUpgrades) return []; return transports[transport].upgradesTo || []; } @@ -259,17 +267,18 @@ export abstract class BaseServer extends EventEmitter { * @param upgrade - whether it's an upgrade request * @param fn * @protected + * @return whether the request is valid */ protected verify( - req: any, + req: EngineRequest, upgrade: boolean, - fn: (errorCode?: number, errorContext?: any) => void, - ) { + fn: ErrorCallback, + ): void | boolean { // transport check const transport = req._query.transport; // WebTransport does not go through the verify() method, see the onWebTransportSession() method if ( - !~this.opts.transports.indexOf(transport) || + !~this.opts.transports.indexOf(transport as TransportName) || transport === "webtransport" ) { debug('unknown transport "%s"', transport); @@ -408,7 +417,7 @@ export abstract class BaseServer extends EventEmitter { * * @param {IncomingMessage} req - the request object */ - public generateId(req: IncomingMessage) { + public generateId(req: IncomingMessage): string | PromiseLike { return base64id.generateId(); } @@ -422,9 +431,9 @@ export abstract class BaseServer extends EventEmitter { * @protected */ protected async handshake( - transportName: string, - req: any, - closeConnection: (errorCode?: number, errorContext?: any) => void, + transportName: TransportName, + req: EngineRequest, + closeConnection: ErrorCallback, ) { const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default if (protocol === 3 && !this.opts.allowEIO3) { @@ -600,7 +609,10 @@ export abstract class BaseServer extends EventEmitter { } } - protected abstract createTransport(transportName, req); + protected abstract createTransport( + transportName: TransportName, + req: EngineRequest, + ); /** * Protocol errors mappings. @@ -613,7 +625,7 @@ export abstract class BaseServer extends EventEmitter { BAD_REQUEST: 3, FORBIDDEN: 4, UNSUPPORTED_PROTOCOL_VERSION: 5, - }; + } as const; static errorMessages = { 0: "Transport unknown", @@ -622,7 +634,7 @@ export abstract class BaseServer extends EventEmitter { 3: "Bad request", 4: "Forbidden", 5: "Unsupported protocol version", - }; + } as const; } /** @@ -667,7 +679,7 @@ class WebSocketResponse { */ export class Server extends BaseServer { public httpServer?: HttpServer; - private ws: any; + private ws: WsServer; /** * Initialize websocket server @@ -687,7 +699,7 @@ export class Server extends BaseServer { }); if (typeof this.ws.on === "function") { - this.ws.on("headers", (headersArray, req) => { + this.ws.on("headers", (headersArray, req: EngineRequest) => { // note: 'ws' uses an array of headers, while Engine.IO uses an object (response.writeHead() accepts both formats) // we could also try to parse the array and then sync the values, but that will be error-prone const additionalHeaders = req[kResponseHeaders] || {}; @@ -730,7 +742,11 @@ export class Server extends BaseServer { } } - protected createTransport(transportName: string, req: IncomingMessage) { + protected createTransport( + transportName: TransportName, + req: IncomingMessage, + ): Transport { + // @ts-expect-error 'polling' is a plain function used as constructor return new transports[transportName](req); } @@ -745,7 +761,7 @@ export class Server extends BaseServer { this.prepare(req); req.res = res; - const callback = (errorCode, errorContext) => { + const callback: ErrorCallback = (errorCode, errorContext) => { if (errorCode !== undefined) { this.emit("connection_error", { req, @@ -763,7 +779,11 @@ export class Server extends BaseServer { } else { const closeConnection = (errorCode, errorContext) => abortRequest(res, errorCode, errorContext); - this.handshake(req._query.transport, req, closeConnection); + this.handshake( + req._query.transport as TransportName, + req, + closeConnection, + ); } }; @@ -787,7 +807,7 @@ export class Server extends BaseServer { this.prepare(req); const res = new WebSocketResponse(req, socket); - const callback = (errorCode, errorContext) => { + const callback: ErrorCallback = (errorCode, errorContext) => { if (errorCode !== undefined) { this.emit("connection_error", { req, @@ -823,11 +843,16 @@ export class Server extends BaseServer { /** * Called upon a ws.io connection. - * - * @param {ws.Socket} websocket + * @param req + * @param socket + * @param websocket * @private */ - private onWebSocket(req, socket, websocket) { + private onWebSocket( + req: EngineRequest, + socket: Duplex, + websocket: WsWebSocket, + ) { websocket.on("error", onUpgradeError); if ( @@ -862,14 +887,22 @@ export class Server extends BaseServer { // transport error handling takes over websocket.removeListener("error", onUpgradeError); - const transport = this.createTransport(req._query.transport, req); + const transport = this.createTransport( + req._query.transport as TransportName, + req, + ); + // @ts-expect-error this option is only for WebSocket impl transport.perMessageDeflate = this.opts.perMessageDeflate; client._maybeUpgrade(transport); } } else { const closeConnection = (errorCode, errorContext) => abortUpgrade(socket, errorCode, errorContext); - this.handshake(req._query.transport, req, closeConnection); + this.handshake( + req._query.transport as TransportName, + req, + closeConnection, + ); } function onUpgradeError() { @@ -947,7 +980,11 @@ export class Server extends BaseServer { * @private */ -function abortRequest(res, errorCode, errorContext) { +function abortRequest( + res: ServerResponse, + errorCode: number, + errorContext?: { message?: string }, +) { const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400; const message = errorContext && errorContext.message @@ -1030,7 +1067,7 @@ const validHdrChars = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // ... 255 ] -function checkInvalidHeaderChar(val) { +function checkInvalidHeaderChar(val?: string) { val += ""; if (val.length < 1) return false; if (!validHdrChars[val.charCodeAt(0)]) { diff --git a/packages/engine.io/lib/socket.ts b/packages/engine.io/lib/socket.ts index bfe6d3ba1d..c2efe8ebbe 100644 --- a/packages/engine.io/lib/socket.ts +++ b/packages/engine.io/lib/socket.ts @@ -5,6 +5,7 @@ import type { EngineRequest, Transport } from "./transport"; import type { BaseServer } from "./server"; import { setTimeout, clearTimeout } from "timers"; import type { Packet, PacketType, RawData } from "engine.io-parser"; +import type transports from "./transports"; const debug = debugModule("engine:socket"); @@ -537,9 +538,11 @@ export class Socket extends EventEmitter { */ private getAvailableUpgrades() { const availableUpgrades = []; - const allUpgrades = this.server.upgrades(this.transport.name); + const allUpgrades = this.server.upgrades( + this.transport.name as keyof typeof transports, + ); for (let i = 0; i < allUpgrades.length; ++i) { - const upg = allUpgrades[i]; + const upg = allUpgrades[i] as keyof typeof transports; if (this.server.opts.transports.indexOf(upg) !== -1) { availableUpgrades.push(upg); } diff --git a/packages/engine.io/lib/transport.ts b/packages/engine.io/lib/transport.ts index 1121e4e348..6d3b9cbf62 100644 --- a/packages/engine.io/lib/transport.ts +++ b/packages/engine.io/lib/transport.ts @@ -4,6 +4,7 @@ import * as parser_v3 from "./parser-v3/index"; import debugModule from "debug"; import type { IncomingMessage, ServerResponse } from "http"; import { Packet, RawData } from "engine.io-parser"; +import type { WebSocket } from "ws"; const debug = debugModule("engine:transport"); @@ -15,7 +16,11 @@ export type EngineRequest = IncomingMessage & { _query: Record; res?: ServerResponse; cleanup?: Function; - websocket?: any; + websocket?: WebSocket & { + _socket?: { + remoteAddress: string; + }; + }; }; export abstract class Transport extends EventEmitter { @@ -37,7 +42,7 @@ export abstract class Transport extends EventEmitter { * * @see https://github.com/socketio/engine.io-protocol */ - public protocol: number; + public protocol: 3 | 4; /** * The current state of the transport. @@ -53,7 +58,7 @@ export abstract class Transport extends EventEmitter { * The parser to use (depends on the revision of the {@link Transport#protocol}. * @protected */ - protected parser: any; + protected parser: typeof parser_v4 | typeof parser_v3; /** * Whether the transport supports binary payloads (else it will be base64-encoded) * @protected @@ -74,6 +79,11 @@ export abstract class Transport extends EventEmitter { this._readyState = state; } + /** + * The list of transports this transport can be upgraded to. + */ + static upgradesTo: string[] = []; + /** * Transport constructor. * @@ -148,7 +158,7 @@ export abstract class Transport extends EventEmitter { /** * Called with the encoded packet data. * - * @param {String} data + * @param data * @protected */ protected onData(data: RawData) { diff --git a/packages/engine.io/lib/transports-uws/polling.ts b/packages/engine.io/lib/transports-uws/polling.ts index 090270ec2f..7463b3b3d3 100644 --- a/packages/engine.io/lib/transports-uws/polling.ts +++ b/packages/engine.io/lib/transports-uws/polling.ts @@ -3,6 +3,8 @@ import { createGzip, createDeflate } from "zlib"; import * as accepts from "accepts"; import debugModule from "debug"; import { HttpRequest, HttpResponse } from "uWebSockets.js"; +import type * as parser_v4 from "engine.io-parser"; +import type * as parser_v3 from "../parser-v3/index"; const debug = debugModule("engine:polling"); @@ -228,9 +230,9 @@ export class Polling extends Transport { }; if (this.protocol === 3) { - this.parser.decodePayload(data, callback); + (this.parser as typeof parser_v3).decodePayload(data, callback); } else { - this.parser.decodePayload(data).forEach(callback); + (this.parser as typeof parser_v4).decodePayload(data).forEach(callback); } } @@ -263,7 +265,7 @@ export class Polling extends Transport { this.shouldClose = null; } - const doWrite = (data) => { + const doWrite = (data: string) => { const compress = packets.some((packet) => { return packet.options && packet.options.compress; }); @@ -271,9 +273,13 @@ export class Polling extends Transport { }; if (this.protocol === 3) { - this.parser.encodePayload(packets, this.supportsBinary, doWrite); + (this.parser as typeof parser_v3).encodePayload( + packets, + this.supportsBinary, + doWrite, + ); } else { - this.parser.encodePayload(packets, doWrite); + (this.parser as typeof parser_v4).encodePayload(packets, doWrite); } } diff --git a/packages/engine.io/lib/transports/index.ts b/packages/engine.io/lib/transports/index.ts index e585112bb1..47b3f17be1 100644 --- a/packages/engine.io/lib/transports/index.ts +++ b/packages/engine.io/lib/transports/index.ts @@ -2,9 +2,10 @@ import { Polling as XHR } from "./polling"; import { JSONP } from "./polling-jsonp"; import { WebSocket } from "./websocket"; import { WebTransport } from "./webtransport"; +import type { EngineRequest } from "../transport"; export default { - polling: polling, + polling, websocket: WebSocket, webtransport: WebTransport, }; @@ -12,8 +13,7 @@ export default { /** * Polling polymorphic constructor. */ - -function polling(req) { +function polling(req: EngineRequest) { if ("string" === typeof req._query.j) { return new JSONP(req); } else { diff --git a/packages/engine.io/lib/transports/polling.ts b/packages/engine.io/lib/transports/polling.ts index 1f46335934..15e492a19c 100644 --- a/packages/engine.io/lib/transports/polling.ts +++ b/packages/engine.io/lib/transports/polling.ts @@ -4,6 +4,8 @@ import * as accepts from "accepts"; import debugModule from "debug"; import type { IncomingMessage, ServerResponse } from "http"; import type { Packet, RawData } from "engine.io-parser"; +import type * as parser_v4 from "engine.io-parser"; +import type * as parser_v3 from "../parser-v3/index"; const debug = debugModule("engine:polling"); @@ -196,9 +198,9 @@ export class Polling extends Transport { }; if (this.protocol === 3) { - this.parser.decodePayload(data, callback); + (this.parser as typeof parser_v3).decodePayload(data, callback); } else { - this.parser.decodePayload(data).forEach(callback); + (this.parser as typeof parser_v4).decodePayload(data).forEach(callback); } } @@ -225,7 +227,7 @@ export class Polling extends Transport { this.shouldClose = null; } - const doWrite = (data) => { + const doWrite = (data: string) => { const compress = packets.some((packet) => { return packet.options && packet.options.compress; }); @@ -233,9 +235,13 @@ export class Polling extends Transport { }; if (this.protocol === 3) { - this.parser.encodePayload(packets, this.supportsBinary, doWrite); + (this.parser as typeof parser_v3).encodePayload( + packets, + this.supportsBinary, + doWrite, + ); } else { - this.parser.encodePayload(packets, doWrite); + (this.parser as typeof parser_v4).encodePayload(packets, doWrite); } } diff --git a/packages/engine.io/lib/transports/websocket.ts b/packages/engine.io/lib/transports/websocket.ts index a95f4b0f19..ebc0553fbf 100644 --- a/packages/engine.io/lib/transports/websocket.ts +++ b/packages/engine.io/lib/transports/websocket.ts @@ -1,12 +1,13 @@ import { EngineRequest, Transport } from "../transport"; import debugModule from "debug"; import type { Packet, RawData } from "engine.io-parser"; +import type { PerMessageDeflateOptions, WebSocket as WsWebSocket } from "ws"; const debug = debugModule("engine:ws"); export class WebSocket extends Transport { - protected perMessageDeflate: any; - private socket: any; + perMessageDeflate?: boolean | PerMessageDeflateOptions; + private socket: WsWebSocket; /** * WebSocket transport @@ -51,8 +52,8 @@ export class WebSocket extends Transport { if (this._canSendPreEncodedFrame(packet)) { // the WebSocket frame was computed with WebSocket.Sender.frame() // see https://github.com/websockets/ws/issues/617#issuecomment-283002469 + // @ts-expect-error use of untyped member this.socket._sender.sendFrame( - // @ts-ignore packet.options.wsPreEncodedFrame, isLast ? this._onSentLast : this._onSent, ); @@ -74,8 +75,8 @@ export class WebSocket extends Transport { private _canSendPreEncodedFrame(packet: Packet) { return ( !this.perMessageDeflate && + // @ts-expect-error use of untyped member typeof this.socket?._sender?.sendFrame === "function" && - // @ts-ignore packet.options?.wsPreEncodedFrame !== undefined ); } diff --git a/packages/engine.io/lib/userver.ts b/packages/engine.io/lib/userver.ts index e8fb47f580..71ed616fbb 100644 --- a/packages/engine.io/lib/userver.ts +++ b/packages/engine.io/lib/userver.ts @@ -2,6 +2,7 @@ import debugModule from "debug"; import { AttachOptions, BaseServer, Server } from "./server"; import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import transports from "./transports-uws"; +import { EngineRequest } from "./transport"; const debug = debugModule("engine:uws"); @@ -36,7 +37,7 @@ export class uServer extends BaseServer { * * @private */ - private prepare(req, res: HttpResponse) { + private prepare(req: HttpRequest & EngineRequest, res: HttpResponse) { req.method = req.getMethod().toUpperCase(); req.url = req.getUrl(); @@ -48,6 +49,7 @@ export class uServer extends BaseServer { req.headers[key] = value; }); + // @ts-expect-error req.connection = { remoteAddress: Buffer.from(res.getRemoteAddressAsText()).toString(), }; @@ -57,7 +59,7 @@ export class uServer extends BaseServer { }); } - protected createTransport(transportName, req) { + protected createTransport(transportName: string, req: EngineRequest) { return new transports[transportName](req); } @@ -123,7 +125,7 @@ export class uServer extends BaseServer { req: HttpRequest & { res: any; _query: any }, ) { debug('handling "%s" http request "%s"', req.getMethod(), req.getUrl()); - this.prepare(req, res); + this.prepare(req as unknown as HttpRequest & EngineRequest, res); req.res = res; @@ -146,7 +148,11 @@ export class uServer extends BaseServer { } else { const closeConnection = (errorCode, errorContext) => this.abortRequest(res, errorCode, errorContext); - this.handshake(req._query.transport, req, closeConnection); + this.handshake( + req._query.transport, + req as unknown as EngineRequest, + closeConnection, + ); } }; @@ -154,7 +160,11 @@ export class uServer extends BaseServer { if (err) { callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" }); } else { - this.verify(req, false, callback); + this.verify( + req as unknown as HttpRequest & EngineRequest, + false, + callback, + ); } }); } @@ -166,7 +176,7 @@ export class uServer extends BaseServer { ) { debug("on upgrade"); - this.prepare(req, res); + this.prepare(req as unknown as HttpRequest & EngineRequest, res); req.res = res; @@ -198,13 +208,16 @@ export class uServer extends BaseServer { return res.close(); } else { debug("upgrading existing transport"); - transport = this.createTransport(req._query.transport, req); + transport = this.createTransport( + req._query.transport, + req as unknown as EngineRequest, + ); client._maybeUpgrade(transport); } } else { transport = await this.handshake( req._query.transport, - req, + req as unknown as EngineRequest, (errorCode, errorContext) => this.abortRequest(res, errorCode, errorContext), ); @@ -231,15 +244,15 @@ export class uServer extends BaseServer { if (err) { callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" }); } else { - this.verify(req, true, callback); + this.verify(req as unknown as EngineRequest, true, callback); } }); } private abortRequest( res: HttpResponse | ResponseWrapper, - errorCode, - errorContext, + errorCode: number, + errorContext?: { message?: string }, ) { const statusCode = errorCode === Server.errors.FORBIDDEN diff --git a/packages/engine.io/package.json b/packages/engine.io/package.json index 6c126c6b71..ed8386ea3b 100644 --- a/packages/engine.io/package.json +++ b/packages/engine.io/package.json @@ -1,6 +1,6 @@ { "name": "engine.io", - "version": "6.6.2", + "version": "6.6.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", @@ -31,7 +31,6 @@ ], "license": "MIT", "dependencies": { - "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", diff --git a/packages/socket.io-adapter/lib/in-memory-adapter.ts b/packages/socket.io-adapter/lib/in-memory-adapter.ts index 2b78e8285e..cf178170e0 100644 --- a/packages/socket.io-adapter/lib/in-memory-adapter.ts +++ b/packages/socket.io-adapter/lib/in-memory-adapter.ts @@ -1,6 +1,6 @@ import { EventEmitter } from "events"; import { yeast } from "./contrib/yeast"; -import WebSocket = require("ws"); +import * as WebSocket from "ws"; // @ts-expect-error const canPreComputeFrame = typeof WebSocket?.Sender?.frame === "function"; @@ -51,11 +51,11 @@ export class Adapter extends EventEmitter { /** * In-memory adapter constructor. * - * @param {Namespace} nsp + * @param nsp */ constructor(readonly nsp: any) { super(); - this.encoder = nsp.server.encoder; + this.encoder = nsp.server.encoder; // nsp is a Namespace object } /** diff --git a/packages/socket.io-cluster-engine/lib/engine.ts b/packages/socket.io-cluster-engine/lib/engine.ts index 4972f31881..e12f643cb7 100644 --- a/packages/socket.io-cluster-engine/lib/engine.ts +++ b/packages/socket.io-cluster-engine/lib/engine.ts @@ -1,4 +1,10 @@ -import { Server, type ServerOptions, Socket, type Transport } from "engine.io"; +import { + Server, + type ServerOptions, + type ErrorCallback, + Socket, + type Transport, +} from "engine.io"; import { randomBytes } from "node:crypto"; import { setTimeout, clearTimeout } from "node:timers"; import { type IncomingMessage } from "node:http"; @@ -392,9 +398,9 @@ export abstract class ClusterEngine extends Server { override verify( req: IncomingMessage & { _query: Record }, upgrade: boolean, - fn: (errorCode?: number, context?: any) => void, + fn: ErrorCallback, ): void { - super.verify(req, upgrade, (errorCode: number, errorContext: any) => { + super.verify(req, upgrade, (errorCode, errorContext) => { if (errorCode !== Server.errors.UNKNOWN_SID) { return fn(errorCode, errorContext); } @@ -412,7 +418,7 @@ export abstract class ClusterEngine extends Server { req[kSenderId] = senderId; fn(); } else { - const transport = this.createTransport(transportName, req); + const transport = this.createTransport(transportName as any, req); this._hookTransport(sid, transport, lockType, senderId); transport.onRequest(req); } diff --git a/packages/socket.io/lib/client.ts b/packages/socket.io/lib/client.ts index 6ca7b77818..99ff42cd12 100644 --- a/packages/socket.io/lib/client.ts +++ b/packages/socket.io/lib/client.ts @@ -1,6 +1,6 @@ import { Decoder, Encoder, Packet, PacketType } from "socket.io-parser"; -import debugModule = require("debug"); -import url = require("url"); +import debugModule from "debug"; +import url from "url"; import type { IncomingMessage } from "http"; import type { Server } from "./index"; import type { Namespace } from "./namespace"; @@ -61,12 +61,13 @@ export class Client< */ constructor( server: Server, - conn: any, + conn: RawSocket, ) { this.server = server; this.conn = conn; this.encoder = server.encoder; this.decoder = new server._parser.Decoder(); + // @ts-expect-error use of private this.id = conn.id; this.setup(); } @@ -216,13 +217,13 @@ export class Client< * @param {Object} opts * @private */ - _packet(packet: Packet | any[], opts: WriteOptions = {}): void { + _packet(packet: Packet | Packet[], opts: WriteOptions = {}): void { if (this.conn.readyState !== "open") { debug("ignoring packet write %j", packet); return; } const encodedPackets = opts.preEncoded - ? (packet as any[]) // previous versions of the adapter incorrectly used socket.packet() instead of writeToEngine() + ? (packet as Packet[]) // previous versions of the adapter incorrectly used socket.packet() instead of writeToEngine() : this.encoder.encode(packet as Packet); this.writeToEngine(encodedPackets, opts); } @@ -250,7 +251,7 @@ export class Client< * * @private */ - private ondata(data): void { + private ondata(data: unknown): void { // try/catch is needed for protocol violations (GH-1880) try { this.decoder.add(data); diff --git a/packages/socket.io/lib/index.ts b/packages/socket.io/lib/index.ts index b77b8fd3fd..5653911b33 100644 --- a/packages/socket.io/lib/index.ts +++ b/packages/socket.io/lib/index.ts @@ -1,4 +1,4 @@ -import http = require("http"); +import http from "http"; import type { Server as HTTPSServer } from "https"; import type { Http2SecureServer, Http2Server } from "http2"; import { createReadStream } from "fs"; @@ -7,7 +7,11 @@ import accepts = require("accepts"); import { pipeline } from "stream"; import path = require("path"); import { attach, Server as Engine, uServer } from "engine.io"; -import type { ServerOptions as EngineOptions, AttachOptions } from "engine.io"; +import type { + ServerOptions as EngineOptions, + AttachOptions, + Socket as RawSocket, +} from "engine.io"; import { Client } from "./client"; import { EventEmitter } from "events"; import { ExtendedError, Namespace, ServerReservedEventsMap } from "./namespace"; @@ -506,6 +510,11 @@ export class Server< return this; } + /** + * Attaches socket.io to a uWebSockets.js app. + * @param app + * @param opts + */ public attachApp(app /*: TemplatedApp */, opts: Partial = {}) { // merge the options passed to the Socket.IO server Object.assign(opts, this.opts); @@ -582,7 +591,7 @@ export class Server< ): void { // initialize engine debug("creating engine.io instance with opts %j", opts); - this.eio = attach(srv, opts); + this.eio = attach(srv as http.Server, opts); // attach static file serving if (this._serveClient) this.attachServe(srv); @@ -723,11 +732,12 @@ export class Server< * @return self * @private */ - private onconnection(conn): this { + private onconnection(conn: RawSocket): this { + // @ts-expect-error use of private debug("incoming connection with id %s", conn.id); const client = new Client(this, conn); if (conn.protocol === 3) { - // @ts-ignore + // @ts-expect-error use of private client.connect("/"); } return this; diff --git a/packages/socket.io/lib/socket.ts b/packages/socket.io/lib/socket.ts index 719b597e59..55131fcee7 100644 --- a/packages/socket.io/lib/socket.ts +++ b/packages/socket.io/lib/socket.ts @@ -163,7 +163,7 @@ export class Socket< ) { super(); this.server = nsp.server; - this.adapter = this.nsp.adapter; + this.adapter = nsp.adapter; if (previousSession) { this.id = previousSession.sid; this.pid = previousSession.pid;