Compare commits

...

29 Commits

Author SHA1 Message Date
Damien Arrachequesne
439a8f669c chore(release): engine.io@6.6.7
Diff: https://github.com/socketio/socket.io/compare/engine.io@6.6.6...engine.io@6.6.7
2026-04-27 11:20:14 +02:00
Damien Arrachequesne
fc11285e14 fix(eio): close HTTP requests with invalid content type
Before this commit, after the initial handshake, an HTTP request with a "application/octet-stream"
content type would trigger a "invalid content" error at the Engine.IO level, but would not be
properly closed, which could possibly lead to resource exhaustion.

This behavior was introduced in [1] (engine.io@4.1.0, January 2021).

[1]: 663d326d18
2026-04-27 11:08:50 +02:00
Damien Arrachequesne
b059af6b12 refactor(eio): use plain IncomingMessage in the public API
The EngineRequest type was introduced in [1] (engine.io@6.6.0).

[1]: c310b7b6b6

Related: https://github.com/socketio/socket.io/issues/5468
2026-04-17 09:55:51 +02:00
Abhijeet Abhi
4e85378a46 docs: fix various typos in test and documentation (#5481) 2026-04-17 07:48:08 +02:00
Damien Arrachequesne
d1f5aa9372 fix(eio): prevent WebTransport connections when a middleware is registered 2026-04-16 10:19:34 +02:00
Damien Arrachequesne
b7853771af docs: fix typos in the release notes
[skip ci]
2026-04-16 09:39:33 +02:00
Damien Arrachequesne
25d877cd9f docs: fix links in the release notes
For some reason, the links generated by `conventional-changelog` were wrong.
2026-04-16 09:23:56 +02:00
Damien Arrachequesne
d4bc787731 refactor(eio): use hasOwn() method everywhere 2026-04-16 09:22:10 +02:00
Damien Arrachequesne
1fa1f46cd4 fix(eio): handle invalid packets when upgrading to WebTransport 2026-04-16 09:08:40 +02:00
Damien Arrachequesne
4f7edb46ec ci: enable caching of npm modules
Note: `npm ci` is always required because it's the npm cache that is cached, not the node_modules/ directory.

Reference: https://github.com/actions/setup-node
2026-04-02 17:40:35 +02:00
Damien Arrachequesne
85b26e5c99 ci: re-enable tests with fetch and uws
Tests were skipped since [1].

[1]: b837949479
2026-04-01 10:20:57 +02:00
Damien Arrachequesne
e4d016bd5b docs(security): add CVE-2026-33151
[skip ci]
2026-03-18 09:09:12 +01:00
Varun Chawla
8b93a18681 fix(sio): allow emitWithAck() for events with void callbacks (#5453)
EventNamesWithAck previously excluded events whose callback had no
non-error arguments (e.g. `(cb: () => void) => void` or
`(cb: (err: Error) => void) => void`). This made it impossible to use
emitWithAck as a simple acknowledgement mechanism without data.

The fix removes the FirstNonErrorArg void check while keeping the guard
against events with no parameters at all, so events like `() => void`
(no callback) are still correctly excluded.

Related: https://github.com/socketio/socket.io/issues/5257
2026-03-17 18:56:40 +01:00
Damien Arrachequesne
e6c722edbe docs: add changelog for socket.io-parser 3.3.5 and 3.4.4
[skip ci]
2026-03-17 18:39:54 +01:00
Damien Arrachequesne
8b0ab0a9d9 chore(release): socket.io-parser@4.2.6
Diff: https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.5...socket.io-parser@4.2.6
2026-03-17 10:57:14 +01:00
Damien Arrachequesne
b25738c416 fix(parser): add a limit to the number of binary attachments
When a packet contains binary elements, the built-in parser does not modify them and simply sends them in their own WebSocket frame.

Example: `socket.emit("some event", Buffer.of(1,2,3))`

is encoded and transferred as:

- 1st frame: 51-["some event",{"_placeholder":true,"num":0}]
- 2nd frame: <buffer 01 02 03>

where:

- `5` is the type of the packet (binary message)
- `1` is the number of binary attachments
- `-` is the separator
- `["some event",{"_placeholder":true,"num":0}]` is the payload (including the placeholder)

On the receiving end, the parser reads the number of attachments and buffers them until they are all received.

Before this change, the built-in parser accepted any number of binary attachments, which could be exploited to make the server run out of memory.

The number of attachments is now limited to 10, which should be sufficient for most use cases.

The limit can be increased with a custom `parser`:

```js
import { Encoder, Decoder } from "socket.io-parser";

const io = new Server({
  parser: {
    Encoder,
    Decoder: class extends Decoder {
      constructor() {
        super({
          maxAttachments: 20
        });
      }
    }
  }
});
```
2026-03-17 10:57:13 +01:00
Sarthak Shah
f6301588ca fix(adapter): do not skip local broadcast when publishAndReturnOffset throws (#5457)
Remove the `return` in the catch block of ClusterAdapter.broadcast() so
that super.broadcast() is still called when remote publishing fails.

This ensures local sockets receive the event even if the cluster publish
errors out (e.g. due to a serialization error in the adapter layer).

Related: https://github.com/socketio/socket.io/issues/5456
2026-03-12 11:38:29 +01:00
Damien Arrachequesne
37aad11417 fix: cleanup pending acks on timeout to prevent memory leak
Related: https://github.com/socketio/socket.io/issues/4984
2026-03-11 18:26:52 +01:00
Damien Arrachequesne
ba9cd6900d revert: fix: cleanup pending acks on timeout to prevent memory leak
This reverts commit da04267ffc.

The reverted fix was incorrect because the rooms might have changed between the emit() and the timeout.
2026-03-11 18:26:30 +01:00
Damien Arrachequesne
84c2fb7821 chore(release): engine.io@6.6.6
Diff: https://github.com/socketio/socket.io/compare/engine.io@6.6.5...engine.io@6.6.6
2026-03-10 10:52:10 +01:00
Not-Sarthak
07cbe1510d fix(eio): add @types/ws as dependency (#5458)
Since engine.io@6.6.5, the generated .d.ts files import types from "ws"
(WebSocket, PerMessageDeflateOptions), but @types/ws was not declared as
a dependency. This causes TypeScript compilation errors for consumers
who do not have @types/ws installed.

This follows the existing pattern where @types/cors and @types/node are
already listed as dependencies.

Related: https://github.com/socketio/socket.io/issues/5437
2026-03-04 09:54:58 +01:00
Erdinç Cürebal
44ed73f539 fix(eio): emit initial_headers and headers events in uServer (#5460)
The uServer (uWebSockets.js) implementation did not emit
"initial_headers" and "headers" events during WebSocket upgrades,
unlike the regular Server which does this via the ws "headers" event.

Related: https://github.com/socketio/socket.io/issues/5300
2026-03-04 09:07:02 +01:00
seungeonchoi
da04267ffc fix: cleanup pending acks on timeout to prevent memory leak (#5442)
When using `emitWithAck` with a timeout, if clients didn't respond
and the timeout triggers, the ack callbacks remained in `socket.acks`
Map indefinitely, causing a memory leak.

Related: https://github.com/socketio/socket.io/issues/4984
2026-03-02 16:32:13 +01:00
Damien Arrachequesne
74599a6b9e fix(types): properly import http module
This commit fixes `Module '"http"' has no default export.` errors (ts-node + esm).
2026-01-23 14:55:39 +01:00
Pádraic Slattery
d48718cb67 ci: use actions/checkout@v6 and actions/setup-node@v6 (#5449)
Release notes:

- https://github.com/actions/checkout/blob/main/CHANGELOG.md
- https://github.com/actions/setup-node/releases/tag/v6.0.0
2026-01-23 09:46:45 +01:00
Damien Arrachequesne
9978574e4f chore(release): socket.io@4.8.3
Diff: https://github.com/socketio/socket.io/compare/socket.io@4.8.2...socket.io@4.8.3
2025-12-23 17:39:51 +01:00
Damien Arrachequesne
e9e5bed4f2 chore(release): socket.io-client@4.8.3
Diff: https://github.com/socketio/socket.io/compare/socket.io-client@4.8.2...socket.io-client@4.8.3
2025-12-23 17:36:53 +01:00
Damien Arrachequesne
9581f9bcfd fix(sio): do not throw when calling io.close() on a stopped server
Following [1], calling both `io.close()` and `httpServer.close()` would throw an ERR_SERVER_NOT_RUNNING exception, which was not the case before.

Related: https://github.com/socketio/socket.io/issues/5431

[1]: bb0b480d2a
2025-12-23 17:30:15 +01:00
Damien Arrachequesne
579d43f33f refactor: remove unused files
[skip ci]
2025-12-23 13:34:44 +01:00
43 changed files with 670 additions and 280 deletions

View File

@@ -31,12 +31,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Use Node.js 20
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
cache: npm
- name: Build ${{ matrix.example }}
run: |

View File

@@ -20,12 +20,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Use Node.js 20
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci

View File

@@ -55,6 +55,7 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
@@ -67,12 +68,12 @@ jobs:
- name: Run tests with uws (engine.io)
run: npm run test:uws --workspace=engine.io
if: ${{ matrix.node-version == '18' }}
if: ${{ matrix.node-version == '24' }}
- name: Run tests with fetch instead of XHR (engine.io-client)
run: npm run test:node-fetch --workspace=engine.io-client
if: ${{ matrix.node-version == '18' }}
if: ${{ matrix.node-version == '24' }}
- name: Run tests with Node.js native WebSocket (engine.io-client)
run: npm run test:node-builtin-ws --workspace=engine.io-client
if: ${{ matrix.node-version == '22' }}
if: ${{ matrix.node-version == '24' }}

View File

@@ -17,13 +17,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Use Node.js 24
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
cache: npm
- name: Install dependencies
run: npm ci

View File

@@ -37,33 +37,35 @@ We will get back to you as soon as possible and publish a fix if necessary.
From the transitive dependencies:
| Date | Dependency | Description | CVE number |
|---------------|--------------------|---------------------------------------------------------------------------------------------------------------|------------------|
| January 2016 | `ws` | [Buffer vulnerability](https://github.com/advisories/GHSA-2mhh-w6q8-5hxw) | `CVE-2016-10518` |
| January 2016 | `ws` | [DoS due to excessively large websocket message](https://github.com/advisories/GHSA-6663-c963-2gqg) | `CVE-2016-10542` |
| November 2017 | `ws` | [DoS in the `Sec-Websocket-Extensions` header parser](https://github.com/advisories/GHSA-5v72-xg48-5rpm) | `-` |
| February 2020 | `engine.io` | [Resource exhaustion](https://github.com/advisories/GHSA-j4f2-536g-r55m) | `CVE-2020-36048` |
| January 2021 | `socket.io-parser` | [Resource exhaustion](https://github.com/advisories/GHSA-xfhh-g9f5-x4m4) | `CVE-2020-36049` |
| May 2021 | `ws` | [ReDoS in `Sec-Websocket-Protocol` header](https://github.com/advisories/GHSA-6fc8-4gx4-v693) | `CVE-2021-32640` |
| January 2022 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-273r-mgr4-v34f) | `CVE-2022-21676` |
| October 2022 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-qm95-pgcg-qqfq) | `CVE-2022-2421` |
| November 2022 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-r7qp-cfhv-p84w) | `CVE-2022-41940` |
| May 2023 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-q9mw-68c2-j6m5) | `CVE-2023-31125` |
| May 2023 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-cqmj-92xf-r6r9) | `CVE-2023-32695` |
| June 2024 | `ws` | [DoS when handling a request with many HTTP headers](https://github.com/advisories/GHSA-3h5v-q93c-6h6q) | `CVE-2024-37890` |
| Date | Dependency | Description | CVE number |
|---------------|--------------------|-------------------------------------------------------------------------------------------------------------------------|------------------|
| January 2016 | `ws` | [Buffer vulnerability](https://github.com/advisories/GHSA-2mhh-w6q8-5hxw) | `CVE-2016-10518` |
| January 2016 | `ws` | [DoS due to excessively large websocket message](https://github.com/advisories/GHSA-6663-c963-2gqg) | `CVE-2016-10542` |
| November 2017 | `ws` | [DoS in the `Sec-Websocket-Extensions` header parser](https://github.com/advisories/GHSA-5v72-xg48-5rpm) | `-` |
| February 2020 | `engine.io` | [Resource exhaustion](https://github.com/advisories/GHSA-j4f2-536g-r55m) | `CVE-2020-36048` |
| January 2021 | `socket.io-parser` | [Resource exhaustion](https://github.com/advisories/GHSA-xfhh-g9f5-x4m4) | `CVE-2020-36049` |
| May 2021 | `ws` | [ReDoS in `Sec-Websocket-Protocol` header](https://github.com/advisories/GHSA-6fc8-4gx4-v693) | `CVE-2021-32640` |
| January 2022 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-273r-mgr4-v34f) | `CVE-2022-21676` |
| October 2022 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-qm95-pgcg-qqfq) | `CVE-2022-2421` |
| November 2022 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-r7qp-cfhv-p84w) | `CVE-2022-41940` |
| May 2023 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-q9mw-68c2-j6m5) | `CVE-2023-31125` |
| May 2023 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-cqmj-92xf-r6r9) | `CVE-2023-32695` |
| June 2024 | `ws` | [DoS when handling a request with many HTTP headers](https://github.com/advisories/GHSA-3h5v-q93c-6h6q) | `CVE-2024-37890` |
| March 2026 | `socket.io-parser` | [Unbounded number of binary attachments](https://github.com/socketio/socket.io/security/advisories/GHSA-677m-j7p3-52f9) | `CVE-2026-33151` |
### For the `socket.io-client` package
From the transitive dependencies:
| Date | Dependency | Description | CVE number |
|---------------|--------------------|---------------------------------------------------------------------------------------------------------------|------------------|
| January 2016 | `ws` | [Buffer vulnerability](https://github.com/advisories/GHSA-2mhh-w6q8-5hxw) | `CVE-2016-10518` |
| January 2016 | `ws` | [DoS due to excessively large websocket message](https://github.com/advisories/GHSA-6663-c963-2gqg) | `CVE-2016-10542` |
| October 2016 | `engine.io-client` | [Insecure Defaults Allow MITM Over TLS](https://github.com/advisories/GHSA-4r4m-hjwj-43p8) | `CVE-2016-10536` |
| November 2017 | `ws` | [DoS in the `Sec-Websocket-Extensions` header parser](https://github.com/advisories/GHSA-5v72-xg48-5rpm) | `-` |
| January 2021 | `socket.io-parser` | [Resource exhaustion](https://github.com/advisories/GHSA-xfhh-g9f5-x4m4) | `CVE-2020-36049` |
| May 2021 | `ws` | [ReDoS in `Sec-Websocket-Protocol` header](https://github.com/advisories/GHSA-6fc8-4gx4-v693) | `CVE-2021-32640` |
| October 2022 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-qm95-pgcg-qqfq) | `CVE-2022-2421` |
| May 2023 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-cqmj-92xf-r6r9) | `CVE-2023-32695` |
| June 2024 | `ws` | [DoS when handling a request with many HTTP headers](https://github.com/advisories/GHSA-3h5v-q93c-6h6q) | `CVE-2024-37890` |
| Date | Dependency | Description | CVE number |
|---------------|--------------------|-------------------------------------------------------------------------------------------------------------------------|------------------|
| January 2016 | `ws` | [Buffer vulnerability](https://github.com/advisories/GHSA-2mhh-w6q8-5hxw) | `CVE-2016-10518` |
| January 2016 | `ws` | [DoS due to excessively large websocket message](https://github.com/advisories/GHSA-6663-c963-2gqg) | `CVE-2016-10542` |
| October 2016 | `engine.io-client` | [Insecure Defaults Allow MITM Over TLS](https://github.com/advisories/GHSA-4r4m-hjwj-43p8) | `CVE-2016-10536` |
| November 2017 | `ws` | [DoS in the `Sec-Websocket-Extensions` header parser](https://github.com/advisories/GHSA-5v72-xg48-5rpm) | `-` |
| January 2021 | `socket.io-parser` | [Resource exhaustion](https://github.com/advisories/GHSA-xfhh-g9f5-x4m4) | `CVE-2020-36049` |
| May 2021 | `ws` | [ReDoS in `Sec-Websocket-Protocol` header](https://github.com/advisories/GHSA-6fc8-4gx4-v693) | `CVE-2021-32640` |
| October 2022 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-qm95-pgcg-qqfq) | `CVE-2022-2421` |
| May 2023 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-cqmj-92xf-r6r9) | `CVE-2023-32695` |
| June 2024 | `ws` | [DoS when handling a request with many HTTP headers](https://github.com/advisories/GHSA-3h5v-q93c-6h6q) | `CVE-2024-37890` |
| March 2026 | `socket.io-parser` | [Unbounded number of binary attachments](https://github.com/socketio/socket.io/security/advisories/GHSA-677m-j7p3-52f9) | `CVE-2026-33151` |

View File

@@ -296,7 +296,7 @@ A payload is a series of encoded packets tied together. The payload encoding for
<length1>:<packet1>[<length2>:<packet2>[...]]
```
* length: length of the packet in __characters__
* packet: actual packets as descriped above
* packet: actual packets as described above
When XHR2 is not supported, the same encoding principle is used also when
binary data is sent, but it is sent as base64 encoded strings. For the purposes of decoding, an identifier `b` is

14
package-lock.json generated
View File

@@ -3499,7 +3499,6 @@
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -16038,11 +16037,12 @@
}
},
"packages/engine.io": {
"version": "6.6.4",
"version": "6.6.5",
"license": "MIT",
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"@types/ws": "^8.5.12",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
@@ -16056,7 +16056,7 @@
}
},
"packages/engine.io-client": {
"version": "6.6.3",
"version": "6.6.4",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
@@ -16082,7 +16082,7 @@
}
},
"packages/socket.io": {
"version": "4.8.1",
"version": "4.8.3",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
@@ -16098,7 +16098,7 @@
}
},
"packages/socket.io-adapter": {
"version": "2.5.5",
"version": "2.5.6",
"license": "MIT",
"dependencies": {
"debug": "~4.4.1",
@@ -16106,7 +16106,7 @@
}
},
"packages/socket.io-client": {
"version": "4.8.1",
"version": "4.8.3",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
@@ -16164,7 +16164,7 @@
"license": "MIT"
},
"packages/socket.io-parser": {
"version": "4.2.4",
"version": "4.2.5",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",

View File

@@ -1,12 +0,0 @@
const parser = require('.');
parser.encodePayload([
{
type: 'message',
data: '€',
},
{
type: 'message',
data: Buffer.from([1, 2, 3, 4]),
},
], true, console.log);

View File

@@ -1,44 +0,0 @@
"use strict";
const browsers = require("socket.io-browsers");
const zuulConfig = (module.exports = {
ui: "mocha-bdd",
// test on localhost by default
local: true,
open: true,
concurrency: 2, // ngrok only accepts two tunnels by default
// if browser does not sends output in 120s since last output:
// stop testing, something is wrong
browser_output_timeout: 120 * 1000,
browser_open_timeout: 60 * 4 * 1000,
// we want to be notified something is wrong asap, so no retry
browser_retries: 1,
browserify: [
{
plugin: ["tsify", {
target: "es5"
}],
transform: {
name: "babelify",
presets: ["@babel/preset-env"]
}
}
]
});
if (process.env.CI === "true") {
zuulConfig.local = false;
zuulConfig.tunnel = {
type: "ngrok",
bind_tls: true
};
}
const isPullRequest =
process.env.TRAVIS_PULL_REQUEST &&
process.env.TRAVIS_PULL_REQUEST !== "false";
zuulConfig.browsers = isPullRequest ? browsers.pullRequest : browsers.all;

View File

@@ -1,51 +1,83 @@
# Changelog
| Version | Release date |
|------------------------------------------------------------------------------------------------------|----------------|
| [6.6.5](#665-2025-12-22) | December 2025 |
| [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 |
| [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 |
| Version | Release date | `ws` version |
|------------------------------------------------------------------------------------------------------|----------------|--------------|
| [6.6.6](#666-2026-03-10) | March 2026 | `"` |
| [6.6.5](#665-2025-12-22) | December 2025 | `~8.18.3` |
| [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 | `"` |
| [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 | `~8.17.1` |
| [3.6.2](#362-2024-06-18) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | June 2024 | `~7.5.10` |
| [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 | `~8.11.0` |
| [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 | `~8.2.3` |
| [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 | `~7.4.2` |
| [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 | `^7.1.2` |
## [6.6.7](https://github.com/socketio/socket.io/compare/engine.io@6.6.6...engine.io@6.6.7) (2026-04-27)
### Bug Fixes
* close HTTP requests with invalid content type ([fc11285](https://github.com/socketio/socket.io/commit/fc11285e14964c2132d122164bf130c355f60671))
* handle invalid packets when upgrading to WebTransport ([1fa1f46](https://github.com/socketio/socket.io/commit/1fa1f46cd420ac5b57bb4c04c959b58f3c79158c))
* prevent WebTransport connections when a middleware is registered ([d1f5aa9](https://github.com/socketio/socket.io/commit/d1f5aa93722a7f1ed729b96f771daf92a3dfdaf7))
### Dependencies
- [`ws@~8.18.3`](https://github.com/websockets/ws/releases/tag/8.18.3) (no change)
## [6.6.6](https://github.com/socketio/socket.io/compare/engine.io@6.6.5...engine.io@6.6.6) (2026-03-10)
### Bug Fixes
* add `@types/ws` as dependency ([#5458](https://github.com/socketio/socket.io/issues/5458)) ([07cbe15](https://github.com/socketio/socket.io/commit/07cbe1510ded7e5460cb82e026e2533e50e30eaf))
* **uws** emit initial_headers and headers events in uServer ([#5460](https://github.com/socketio/socket.io/issues/5460)) ([44ed73f](https://github.com/socketio/socket.io/commit/44ed73f53995d35ef0c8d10df6806d5687238282))
### Dependencies
- [`ws@~8.18.3`](https://github.com/websockets/ws/releases/tag/8.18.3) (no change)
## [6.6.5](https://github.com/socketio/socket.io/compare/engine.io@6.6.4...engine.io@6.6.5) (2025-12-22)
@@ -123,7 +155,7 @@ See also: https://github.com/advisories/GHSA-pxg6-pf52-xh8x
### Performance Improvements
* do not reset the hearbeat timer on each packet ([5359bae](https://github.com/socketio/engine.io/commit/5359bae683e2a25742bd4989d0355a8fc10d294e))
* do not reset the heartbeat 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))
@@ -520,9 +552,9 @@ Please upgrade as soon as possible.
* decrease the default value of maxHttpBufferSize ([58e274c](https://github.com/socketio/engine.io/commit/58e274c437e9cbcf69fd913c813aad8fbd253703))
This change reduces the default value from 100 mb to a more sane 1 mb.
This change reduces the default value from 100 MB to a saner 1 MB.
This helps protect the server against denial of service attacks by malicious clients sending huge amounts of data.
This helps protect the server against denial-of-service attacks by malicious clients sending huge amounts of data.
See also: https://github.com/advisories/GHSA-j4f2-536g-r55m
@@ -540,7 +572,7 @@ See also: https://github.com/advisories/GHSA-j4f2-536g-r55m
So that clients in HTTP long-polling can decide how many packets they have to send to stay under the maxHttpBufferSize
value.
This is a backward compatible change which should not mandate a new major revision of the protocol (we stay in v4), as
This is a backward compatible change that should not mandate a new major revision of the protocol (we stay in v4), as
we only add a field in the JSON-encoded handshake data:
```
@@ -626,7 +658,7 @@ The codebase was migrated to TypeScript ([c0d6eaa](https://github.com/socketio/e
An ES module wrapper was also added ([401f4b6](https://github.com/socketio/engine.io/commit/401f4b60693fb6702c942692ce42e5bb701d81d7)).
Please note that the communication protocol was not updated, so a v5 client will be able to reach a v6 server (and vice-versa).
Please note that the communication protocol was not updated, so a v5 client will be able to reach a v6 server (and vice versa).
Reference: https://github.com/socketio/engine.io-protocol

View File

@@ -166,6 +166,11 @@ function parseSessionId(data: string): string | undefined {
} catch (e) {}
}
// Object.hasOwn() was introduced in Node.js 16.9
function hasOwn(obj: Record<string, any>, key: string): boolean {
return Object.prototype.hasOwnProperty.call(obj, key);
}
export abstract class BaseServer extends EventEmitter {
public opts: ServerOptions;
@@ -298,7 +303,7 @@ export abstract class BaseServer extends EventEmitter {
// sid check
const sid = req._query.sid;
if (sid) {
if (!this.clients.hasOwnProperty(sid)) {
if (!hasOwn(this.clients, sid)) {
debug('unknown sid "%s"', sid);
return fn(Server.errors.UNKNOWN_SID, {
sid,
@@ -398,9 +403,9 @@ export abstract class BaseServer extends EventEmitter {
*/
public close() {
debug("closing all open clients");
for (let i in this.clients) {
if (this.clients.hasOwnProperty(i)) {
this.clients[i].close(true);
for (const sid in this.clients) {
if (hasOwn(this.clients, sid)) {
this.clients[sid].close(true);
}
}
this.cleanup();
@@ -524,6 +529,15 @@ export abstract class BaseServer extends EventEmitter {
}
public async onWebTransportSession(session: any) {
if (this.middlewares.length > 0) {
// middlewares expect an IncomingMessage argument, which cannot be created from the WebTransport session object
// see also: https://github.com/fails-components/webtransport/issues/448
debug(
"closing session since WebTransport is not compatible with middlewares",
);
return session.close();
}
const timeout = setTimeout(() => {
debug(
"the client failed to establish a bidirectional stream in the given period",
@@ -583,7 +597,7 @@ export abstract class BaseServer extends EventEmitter {
const sid = parseSessionId(value.data);
if (!sid) {
if (!sid || !hasOwn(this.clients, sid)) {
debug("invalid WebTransport handshake");
return session.close();
}
@@ -750,18 +764,20 @@ export class Server extends BaseServer {
/**
* Handles an Engine.IO HTTP request.
*
* @param {EngineRequest} req
* @param {IncomingMessage} req
* @param {ServerResponse} res
*/
public handleRequest(req: EngineRequest, res: ServerResponse) {
public handleRequest(req: IncomingMessage, res: ServerResponse) {
debug('handling "%s" http request "%s"', req.method, req.url);
this.prepare(req);
req.res = res;
const engineRequest = req as EngineRequest;
this.prepare(engineRequest);
engineRequest.res = res;
const callback: ErrorCallback = (errorCode, errorContext) => {
if (errorCode !== undefined) {
this.emit("connection_error", {
req,
req: engineRequest,
code: errorCode,
message: Server.errorMessages[errorCode],
context: errorContext,
@@ -770,25 +786,27 @@ export class Server extends BaseServer {
return;
}
if (req._query.sid) {
if (engineRequest._query.sid) {
debug("setting new request for existing client");
this.clients[req._query.sid].transport.onRequest(req);
this.clients[engineRequest._query.sid].transport.onRequest(
engineRequest,
);
} else {
const closeConnection = (errorCode, errorContext) =>
abortRequest(res, errorCode, errorContext);
this.handshake(
req._query.transport as TransportName,
req,
engineRequest._query.transport as TransportName,
engineRequest,
closeConnection,
);
}
};
this._applyMiddlewares(req, res, (err) => {
this._applyMiddlewares(engineRequest, res, (err) => {
if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else {
this.verify(req, false, callback);
this.verify(engineRequest, false, callback);
}
});
}
@@ -797,17 +815,19 @@ export class Server extends BaseServer {
* Handles an Engine.IO HTTP Upgrade.
*/
public handleUpgrade(
req: EngineRequest,
req: IncomingMessage,
socket: Duplex,
upgradeHead: Buffer,
) {
this.prepare(req);
const engineRequest = req as EngineRequest;
const res = new WebSocketResponse(req, socket);
this.prepare(engineRequest);
const res = new WebSocketResponse(engineRequest, socket);
const callback: ErrorCallback = (errorCode, errorContext) => {
if (errorCode !== undefined) {
this.emit("connection_error", {
req,
req: engineRequest,
code: errorCode,
message: Server.errorMessages[errorCode],
context: errorContext,
@@ -824,18 +844,22 @@ export class Server extends BaseServer {
res.writeHead();
// delegate to ws
this.ws.handleUpgrade(req, socket, head, (websocket) => {
this.onWebSocket(req, socket, websocket);
this.ws.handleUpgrade(engineRequest, socket, head, (websocket) => {
this.onWebSocket(engineRequest, socket, websocket);
});
};
this._applyMiddlewares(req, res as unknown as ServerResponse, (err) => {
if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else {
this.verify(req, true, callback);
}
});
this._applyMiddlewares(
engineRequest,
res as unknown as ServerResponse,
(err) => {
if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else {
this.verify(engineRequest, true, callback);
}
},
);
}
/**
@@ -933,7 +957,7 @@ export class Server extends BaseServer {
server.on("request", (req, res) => {
if (check(req)) {
debug('intercepting request for path "%s"', path);
this.handleRequest(req as EngineRequest, res);
this.handleRequest(req, res);
} else {
let i = 0;
const l = listeners.length;
@@ -946,7 +970,7 @@ export class Server extends BaseServer {
if (~this.opts.transports.indexOf("websocket")) {
server.on("upgrade", (req, socket, head) => {
if (check(req)) {
this.handleUpgrade(req as EngineRequest, socket, head);
this.handleUpgrade(req, socket, head);
} else if (false !== options.destroyUpgrade) {
// default node behavior is to disconnect when no handlers
// but by adding a handler, we prevent that

View File

@@ -136,7 +136,8 @@ export class Polling extends Transport {
const isBinary = "application/octet-stream" === req.headers["content-type"];
if (isBinary && this.protocol === 4) {
return this.onError("invalid content");
this.onError("invalid content");
return res.writeStatus("400 Bad Request").end();
}
this.dataReq = req;

View File

@@ -122,7 +122,8 @@ export class Polling extends Transport {
const isBinary = "application/octet-stream" === req.headers["content-type"];
if (isBinary && this.protocol === 4) {
return this.onError("invalid content");
this.onError("invalid content");
return res.writeHead(400).end();
}
this.dataReq = req;

View File

@@ -226,9 +226,23 @@ export class uServer extends BaseServer {
}
}
// emit headers events for WebSocket upgrades
const additionalHeaders = {};
const isInitialRequest = !id;
if (isInitialRequest) {
this.emit("initial_headers", additionalHeaders, req);
}
this.emit("headers", additionalHeaders, req);
// calling writeStatus() triggers the flushing of any header added in a middleware
req.res.writeStatus("101 Switching Protocols");
Object.keys(additionalHeaders).forEach((key) => {
req.res.writeHeader(key, additionalHeaders[key]);
});
res.upgrade(
{
transport,

View File

@@ -1,6 +1,6 @@
{
"name": "engine.io",
"version": "6.6.5",
"version": "6.6.7",
"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",
@@ -33,6 +33,7 @@
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"@types/ws": "^8.5.12",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",

View File

@@ -60,6 +60,32 @@ exports.listen = (opts, fn) => {
return e;
};
exports.listenAsync = function listenAsync(opts = {}) {
return new Promise((resolve) => {
const engine = exports.listen(opts, (port) => {
resolve({
port,
close: () => {
engine.close();
if (engine.httpServer) {
engine.httpServer.close();
}
},
});
});
});
};
exports.runHandshake = async function runHandshake(port) {
const res = await fetch(
`http://localhost:${port}/engine.io/?EIO=4&transport=polling`,
);
const data = await res.text();
return {
sid: JSON.parse(data.substring(1)).sid,
};
};
exports.ClientSocket = Socket;
exports.createPartialDone = (done, count) => {

View File

@@ -7,7 +7,13 @@ const path = require("path");
const exec = require("child_process").exec;
const zlib = require("zlib");
const { Server, Socket, attach } = require("..");
const { ClientSocket, listen, createPartialDone } = require("./common");
const {
ClientSocket,
listen,
listenAsync,
runHandshake,
createPartialDone,
} = require("./common");
const expect = require("expect.js");
const request = require("superagent");
const cookieMod = require("cookie");
@@ -581,7 +587,7 @@ describe("server", () => {
});
});
it("should not suggest upgrades when none are availble", (done) => {
it("should not suggest upgrades when none are available", (done) => {
listen({ transports: ["polling"] }, (port) => {
const socket = new ClientSocket(`ws://localhost:${port}`, {});
socket.on("handshake", (obj) => {
@@ -1458,6 +1464,26 @@ describe("server", () => {
},
);
it("should abort the polling data request if the content type is invalid", async () => {
const { port, close } = await listenAsync();
const { sid } = await runHandshake(port);
const res = await fetch(
`http://localhost:${port}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "POST",
headers: {
"content-type": "application/octet-stream",
},
body: Buffer.of(1, 2, 3),
},
);
expect(res.status).to.eql(400);
close();
});
// tests https://github.com/LearnBoost/engine.io-client/issues/207
// websocket test, transport error
it("should trigger transport close before open for ws", (done) => {
@@ -3598,10 +3624,7 @@ describe("server", () => {
});
it("should emit a 'initial_headers' event (websocket)", function (done) {
if (
process.env.EIO_WS_ENGINE === "eiows" ||
process.env.EIO_WS_ENGINE === "uws"
) {
if (process.env.EIO_WS_ENGINE === "eiows") {
return this.skip();
}
const partialDone = createPartialDone(done, 2);
@@ -3644,10 +3667,7 @@ describe("server", () => {
});
it("should emit several 'headers' events per connection", function (done) {
if (
process.env.EIO_WS_ENGINE === "eiows" ||
process.env.EIO_WS_ENGINE === "uws"
) {
if (process.env.EIO_WS_ENGINE === "eiows") {
return this.skip();
}
const partialDone = createPartialDone(done, 4);

View File

@@ -302,6 +302,122 @@ describe("WebTransport", () => {
);
});
it("should close a connection that sends an invalid upgrade", (done) => {
setupServer(
{
transports: ["polling", "websocket", "webtransport"],
},
async ({ engine, h3Server, certificate }) => {
const httpServer = await createHttpServer(h3Server.port);
engine.attach(httpServer);
request(`http://localhost:${h3Server.port}/engine.io/`)
.query({ EIO: 4, transport: "polling" })
.end(async (_, res) => {
const payload = JSON.parse(res.text.substring(1));
expect(payload.upgrades).to.eql(["websocket", "webtransport"]);
const client = new WebTransport(
`https://127.0.0.1:${h3Server.port}/engine.io/`,
{
serverCertificateHashes: [
{
algorithm: "sha-256",
value: certificate.hash,
},
],
},
);
await client.ready;
const stream = await client.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(Uint8Array.of(31));
await writer.write(
TEXT_ENCODER.encode(`0{"sid":"11111111111111111111"}`),
);
client.closed.then(() => {
success(engine, h3Server, done);
});
});
},
);
});
it("should close a connection that sends an invalid upgrade (bis)", (done) => {
setupServer(
{
transports: ["polling", "websocket", "webtransport"],
},
async ({ engine, h3Server, certificate }) => {
const httpServer = await createHttpServer(h3Server.port);
engine.attach(httpServer);
request(`http://localhost:${h3Server.port}/engine.io/`)
.query({ EIO: 4, transport: "polling" })
.end(async (_, res) => {
const payload = JSON.parse(res.text.substring(1));
expect(payload.upgrades).to.eql(["websocket", "webtransport"]);
const client = new WebTransport(
`https://127.0.0.1:${h3Server.port}/engine.io/`,
{
serverCertificateHashes: [
{
algorithm: "sha-256",
value: certificate.hash,
},
],
},
);
await client.ready;
const stream = await client.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(Uint8Array.of(20));
await writer.write(TEXT_ENCODER.encode(`0{"sid":"__proto__"}`));
client.closed.then(() => {
success(engine, h3Server, done);
});
});
},
);
});
it("should refuse the connection when a middleware is registered", (done) => {
setupServer({}, async ({ engine, h3Server, certificate }) => {
engine.use((req, res, next) => next());
engine.on("connection", () => {
done(new Error("should not happen"));
});
const client = new WebTransport(
`https://127.0.0.1:${h3Server.port}/engine.io/`,
{
serverCertificateHashes: [
{
algorithm: "sha-256",
value: certificate.hash,
},
],
},
);
await client.closed;
success(engine, h3Server, done);
});
});
it("should send ping/pong packets", (done) => {
setup(
{

View File

@@ -438,11 +438,7 @@ export abstract class ClusterAdapter extends Adapter {
});
this.addOffsetIfNecessary(packet, opts, offset);
} catch (e) {
return debug(
"[%s] error while broadcasting message: %s",
this.uid,
e.message,
);
debug("[%s] error while broadcasting message: %s", this.uid, e.message);
}
}

View File

@@ -15,6 +15,7 @@ const NODES_COUNT = 3;
class EventEmitterAdapter extends ClusterAdapterWithHeartbeat {
private offset = 1;
public shouldFailPublish = false;
constructor(
nsp,
@@ -27,6 +28,9 @@ class EventEmitterAdapter extends ClusterAdapterWithHeartbeat {
}
protected doPublish(message: ClusterMessage): Promise<string> {
if (this.shouldFailPublish) {
return Promise.reject(new Error("publish failed"));
}
this.eventBus.emit("message", message);
return Promise.resolve(String(++this.offset));
}
@@ -152,6 +156,19 @@ describe("cluster adapter", () => {
servers[0].local.emit("test");
});
it("broadcasts to local clients even when publishAndReturnOffset throws", (done) => {
const adapter = servers[0].of("/").adapter as EventEmitterAdapter;
adapter.shouldFailPublish = true;
clientSockets[0].on("test", (arg1) => {
expect(arg1).to.eql(1);
adapter.shouldFailPublish = false;
done();
});
servers[0].emit("test", 1);
});
it("broadcasts with multiple acknowledgements", (done) => {
clientSockets[0].on("test", (cb) => cb(1));
clientSockets[1].on("test", (cb) => cb(2));

View File

@@ -2,7 +2,8 @@
| Version | Release date | Bundle size (UMD min+gzip) |
|-------------------------------------------------------------------------------------------------------------|----------------|----------------------------|
| [4.8.2](#482-2025-12-22) | December 2024 | `14.4 KB` |
| [4.8.3](#483-2025-12-23) | December 2025 | `14.4 KB` |
| [4.8.2](#482-2025-12-22) | December 2025 | `14.4 KB` |
| [4.8.1](#481-2024-10-25) | October 2024 | `14.4 KB` |
| [4.8.0](#480-2024-09-21) | September 2024 | `14.4 KB` |
| [4.7.5](#475-2024-03-14) | March 2024 | `14.6 KB` |
@@ -51,6 +52,18 @@
| [2.1.0](#210-2018-03-29) | March 2018 | `18.7 KB` |
## [4.8.3](https://github.com/socketio/socket.io/compare/socket.io-client@4.8.2...socket.io-client@4.8.3) (2025-12-23)
There were some minor bug fixes on the server side, which mandate a client bump.
### Dependencies
- [`engine.io-client@~6.6.1`](https://github.com/socketio/engine.io-client/releases/tag/6.5.2) (no change)
- [`ws@~8.18.3`](https://github.com/websockets/ws/releases/tag/8.18.3) ([diff](https://github.com/websockets/ws/compare/8.17.1...8.18.3))
## [4.8.2](https://github.com/socketio/socket.io/compare/socket.io-client@4.8.1...socket.io-client@4.8.2) (2025-12-22)

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io-client",
"version": "4.8.2",
"version": "4.8.3",
"description": "Realtime application framework client",
"keywords": [
"realtime",

View File

@@ -1,5 +1,5 @@
{
"name": "socket.io-client",
"version": "4.8.2",
"version": "4.8.3",
"type": "module"
}

View File

@@ -1,37 +1,66 @@
# Changelog
| Version | Release date |
|-------------------------------------------------------------------------------------------------------------|----------------|
| [4.2.5](#425-2025-12-23) | December 2025 |
| [3.3.4](#334-2024-07-22) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | July 2024 |
| [4.2.4](#424-2023-05-31) | May 2023 |
| [3.4.3](#343-2023-05-22) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch) | May 2023 |
| [4.2.3](#423-2023-05-22) | May 2023 |
| [4.2.2](#422-2023-01-19) | January 2023 |
| [3.3.3](#333-2022-11-09) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | November 2022 |
| [3.4.2](#342-2022-11-09) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch) | November 2022 |
| [4.0.5](#405-2022-06-27) (from the [4.0.x](https://github.com/socketio/socket.io-parser/tree/4.0.x) branch) | June 2022 |
| [4.2.1](#421-2022-06-27) | June 2022 |
| [4.2.0](#420-2022-04-17) | April 2022 |
| [4.1.2](#412-2022-02-17) | February 2022 |
| [3.3.3](#333-2022-11-09) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | November 2022 |
| [3.4.2](#342-2022-11-09) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch) | November 2022 |
| [4.0.5](#405-2022-06-27) (from the [4.0.x](https://github.com/socketio/socket.io-parser/tree/4.0.x) branch) | June 2022 |
| [4.2.1](#421-2022-06-27) | June 2022 |
| [4.2.0](#420-2022-04-17) | April 2022 |
| [4.1.2](#412-2022-02-17) | February 2022 |
| [4.1.1](#411-2021-10-14) | October 2021 |
| [4.1.0](#410-2021-10-11) | October 2021 |
| [4.0.4](#404-2021-01-15) | January 2021 |
| [3.3.2](#332-2021-01-09) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | January 2021 |
| [4.0.3](#403-2021-01-05) | January 2021 |
| [4.0.2](#402-2020-11-25) | November 2020 |
| [4.0.1](#401-2020-11-05) | November 2020 |
| [3.3.1](#331-2020-09-30) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | September 2020 |
| [**4.0.0**](#400-2020-09-28) | September 2020 |
| [3.4.1](#341-2020-05-13) | May 2020 |
| [3.4.0](#340-2019-09-20) | September 2019 |
| [3.3.0](#330-2018-11-07) | November 2018 |
| Version | Release date |
|-----------------------------------------------------------------------------------------------------------------------|----------------|
| [3.4.4](#344-2026-03-17) (from the [3.4.x](https://github.com/socketio/socket.io/tree/socket.io-parser/3.4.x) branch) | March 2026 |
| [3.3.5](#335-2026-03-17) (from the [3.3.x](https://github.com/socketio/socket.io/tree/socket.io-parser/3.3.x) branch) | March 2026 |
| [4.2.6](#426-2026-03-17) | March 2026 |
| [4.2.5](#425-2025-12-23) | December 2025 |
| [3.3.4](#334-2024-07-22) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | July 2024 |
| [4.2.4](#424-2023-05-31) | May 2023 |
| [3.4.3](#343-2023-05-22) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch) | May 2023 |
| [4.2.3](#423-2023-05-22) | May 2023 |
| [4.2.2](#422-2023-01-19) | January 2023 |
| [3.3.3](#333-2022-11-09) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | November 2022 |
| [3.4.2](#342-2022-11-09) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch) | November 2022 |
| [4.0.5](#405-2022-06-27) (from the [4.0.x](https://github.com/socketio/socket.io-parser/tree/4.0.x) branch) | June 2022 |
| [4.2.1](#421-2022-06-27) | June 2022 |
| [4.2.0](#420-2022-04-17) | April 2022 |
| [4.1.2](#412-2022-02-17) | February 2022 |
| [3.3.3](#333-2022-11-09) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | November 2022 |
| [3.4.2](#342-2022-11-09) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch) | November 2022 |
| [4.0.5](#405-2022-06-27) (from the [4.0.x](https://github.com/socketio/socket.io-parser/tree/4.0.x) branch) | June 2022 |
| [4.2.1](#421-2022-06-27) | June 2022 |
| [4.2.0](#420-2022-04-17) | April 2022 |
| [4.1.2](#412-2022-02-17) | February 2022 |
| [4.1.1](#411-2021-10-14) | October 2021 |
| [4.1.0](#410-2021-10-11) | October 2021 |
| [4.0.4](#404-2021-01-15) | January 2021 |
| [3.3.2](#332-2021-01-09) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | January 2021 |
| [4.0.3](#403-2021-01-05) | January 2021 |
| [4.0.2](#402-2020-11-25) | November 2020 |
| [4.0.1](#401-2020-11-05) | November 2020 |
| [3.3.1](#331-2020-09-30) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch) | September 2020 |
| [**4.0.0**](#400-2020-09-28) | September 2020 |
| [3.4.1](#341-2020-05-13) | May 2020 |
| [3.4.0](#340-2019-09-20) | September 2019 |
| [3.3.0](#330-2018-11-07) | November 2018 |
## [3.4.4](https://github.com/socketio/socket.io-parser/compare/3.4.3...3.4.4) (2026-03-17)
### Bug Fixes
* add a limit to the number of binary attachments ([719f9eb](https://github.com/socketio/socket.io/commit/719f9ebab0772ffb882bd614b387e585c1aa75d4))
## [3.3.5](https://github.com/socketio/socket.io-parser/compare/3.3.4...3.3.5) (2026-03-17)
### Bug Fixes
* add a limit to the number of binary attachments ([9d39f1f](https://github.com/socketio/socket.io/commit/9d39f1f080510f036782f2177fac701cc041faaf))
## [4.2.6](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.5...socket.io-parser@4.2.6) (2026-03-17)
### Bug Fixes
* **parser:** add a limit to the number of binary attachments ([3fff7ca](https://github.com/socketio/socket.io/commit/3fff7cafa98f1ba5840475b6917c651fe841a943))
## [4.2.5](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.4...socket.io-parser@4.2.5) (2025-12-23)

View File

@@ -135,6 +135,20 @@ interface DecoderReservedEvents {
decoded: (packet: Packet) => void;
}
type JSONReviver = (this: any, key: string, value: any) => any;
export interface DecoderOptions {
/**
* Custom reviver to pass down to JSON.parse()
*/
reviver?: JSONReviver;
/**
* Maximum number of binary attachments per packet
* @default 10
*/
maxAttachments?: number;
}
/**
* A socket.io Decoder instance
*
@@ -142,14 +156,20 @@ interface DecoderReservedEvents {
*/
export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
private reconstructor: BinaryReconstructor;
private opts: Required<DecoderOptions>;
/**
* Decoder constructor
*
* @param {function} reviver - custom reviver to pass down to JSON.stringify
*/
constructor(private reviver?: (this: any, key: string, value: any) => any) {
constructor(opts?: DecoderOptions | JSONReviver) {
super();
this.opts = Object.assign(
{
reviver: undefined,
maxAttachments: 10,
},
typeof opts === "function" ? { reviver: opts } : opts,
);
}
/**
@@ -224,7 +244,13 @@ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
if (buf != Number(buf) || str.charAt(i) !== "-") {
throw new Error("Illegal attachments");
}
p.attachments = Number(buf);
const n = Number(buf);
if (!isInteger(n) || n < 0) {
throw new Error("Illegal attachments");
} else if (n > this.opts.maxAttachments) {
throw new Error("too many attachments");
}
p.attachments = n;
}
// look up namespace (if any)
@@ -271,7 +297,7 @@ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
private tryParse(str) {
try {
return JSON.parse(str, this.reviver);
return JSON.parse(str, this.opts.reviver);
} catch (e) {
return false;
}

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io-parser",
"version": "4.2.5",
"version": "4.2.6",
"description": "socket.io protocol parser",
"homepage": "https://github.com/socketio/socket.io/tree/main/packages/socket.io-client#readme",
"repository": {

View File

@@ -107,6 +107,56 @@ describe("socket.io-parser", () => {
}
});
it("throws an error when receiving too many attachments", () => {
const decoder = new Decoder({ maxAttachments: 2 });
expect(() => {
decoder.add(
'53-["hello",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1},{"_placeholder":true,"num":2}]',
);
}).to.throwException(/^too many attachments$/);
});
it("decodes with a custom reviver", () => {
const decoder = new Decoder((key, value) => {
if (key === "a") {
return value.toUpperCase();
} else {
return value;
}
});
return new Promise((resolve) => {
decoder.on("decoded", (packet) => {
expect(packet.data).to.eql(["b", { a: "VAL" }]);
resolve();
});
decoder.add('2["b",{"a":"val"}]');
});
});
it("decodes with a custom reviver (options object)", () => {
const decoder = new Decoder({
reviver: (key, value) => {
if (key === "a") {
return value.toUpperCase();
} else {
return value;
}
},
});
return new Promise((resolve) => {
decoder.on("decoded", (packet) => {
expect(packet.data).to.eql(["b", { a: "VAL" }]);
resolve();
});
decoder.add('2["b",{"a":"val"}]');
});
});
it("throw an error upon parsing error", () => {
const isInvalidPayload = (str) =>
expect(() => new Decoder().add(str)).to.throwException(
@@ -125,6 +175,16 @@ describe("socket.io-parser", () => {
isInvalidPayload('2["connect"]');
isInvalidPayload('2["disconnect","123"]');
const isInvalidAttachmentCount = (str) =>
expect(() => new Decoder().add(str)).to.throwException(
/^Illegal attachments$/,
);
isInvalidAttachmentCount("5");
isInvalidAttachmentCount("51");
isInvalidAttachmentCount("5a-");
isInvalidAttachmentCount("51.23-");
expect(() => new Decoder().add("999")).to.throwException(
/^unknown packet type 9$/,
);

View File

@@ -2,6 +2,7 @@
| Version | Release date |
|--------------------------------------------------------------------------------------------------|----------------|
| [4.8.3](#483-2025-12-23) | December 2025 |
| [4.8.2](#482-2025-12-22) | December 2025 |
| [4.8.1](#481-2024-10-25) | October 2024 |
| [4.8.0](#480-2024-09-21) | September 2024 |
@@ -50,6 +51,21 @@
| [2.1.0](#210-2018-03-29) | March 2018 |
## [4.8.3](https://github.com/socketio/socket.io/compare/socket.io@4.8.2...socket.io@4.8.3) (2025-12-23)
### Bug Fixes
* do not throw when calling io.close() on a stopped server ([9581f9b](https://github.com/socketio/socket.io/commit/9581f9bcfd0c0fa8cb16eae1604c6a727af21efa))
### Dependencies
- [`engine.io@~6.6.0`](https://github.com/socketio/engine.io/releases/tag/6.6.0) (no change)
- [`ws@~8.18.3`](https://github.com/websockets/ws/releases/tag/8.18.3) (no change)
## [4.8.2](https://github.com/socketio/socket.io/compare/socket.io@4.8.1...socket.io@4.8.2) (2025-12-22)
The `url.parse()` function is now deprecated and has been replaced by `new URL()` (see [8af7019](https://github.com/socketio/socket.io/commit/8af70195bb8c5bc3efe9685997ab6373fb8b1ca9)).

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.8.2
* Socket.IO v4.8.3
* (c) 2014-2025 Guillermo Rauch
* Released under the MIT License.
*/

View File

@@ -13,6 +13,9 @@ import type {
FirstNonErrorArg,
EventNamesWithError,
} from "./typed-events";
import debugModule from "debug";
const debug = debugModule("socket.io:broadcast-operator");
export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
implements TypedEventBroadcaster<EmitEvents>
@@ -235,6 +238,17 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
const timer = setTimeout(() => {
timedOut = true;
debug("operation has timed out");
// @ts-expect-error
const packetId = packet.id;
if (packetId !== undefined) {
this.adapter.nsp.sockets.forEach((socket) => {
socket.acks.delete(packetId);
});
}
ack.apply(this, [
new Error("operation has timed out"),
this.flags.expectSingleResponse ? null : responses,
@@ -246,6 +260,13 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
let expectedClientCount = 0;
const checkCompleteness = () => {
debug(
"responses: servers: %d / %d ; clients: %d / %d",
actualServerCount,
expectedServerCount,
responses.length,
expectedClientCount,
);
if (
!timedOut &&
expectedServerCount === actualServerCount &&

View File

@@ -1,4 +1,9 @@
import http from "http";
import { createServer } from "http"; // 'node:' prefix was added in Node.js 16
import type {
Server as HTTPServer,
IncomingMessage,
ServerResponse,
} from "http";
import type { Server as HTTPSServer } from "https";
import type { Http2SecureServer, Http2Server } from "http2";
import { createReadStream } from "fs";
@@ -58,7 +63,7 @@ type ParentNspNameMatchFn = (
type AdapterConstructor = typeof Adapter | ((nsp: Namespace) => Adapter);
type TServerInstance =
| http.Server
| HTTPServer
| HTTPSServer
| Http2SecureServer
| Http2Server;
@@ -276,8 +281,8 @@ export class Server<
*/
_connectTimeout: number;
private _corsMiddleware: (
req: http.IncomingMessage,
res: http.ServerResponse,
req: IncomingMessage,
res: ServerResponse,
next: () => void,
) => void;
@@ -301,7 +306,7 @@ export class Server<
if (
"object" === typeof srv &&
srv instanceof Object &&
!(srv as Partial<http.Server>).listen
!(srv as Partial<HTTPServer>).listen
) {
opts = srv as Partial<ServerOptions>;
srv = undefined;
@@ -493,7 +498,7 @@ export class Server<
if ("number" == typeof srv) {
debug("creating http server and binding to %d", srv);
const port = srv;
srv = http.createServer((req, res) => {
srv = createServer((_req, res) => {
res.writeHead(404);
res.end();
});
@@ -591,7 +596,7 @@ export class Server<
): void {
// initialize engine
debug("creating engine.io instance with opts %j", opts);
this.eio = attach(srv as http.Server, opts);
this.eio = attach(srv as HTTPServer, opts);
// attach static file serving
if (this._serveClient) this.attachServe(srv);
@@ -638,7 +643,7 @@ export class Server<
* @param res
* @private
*/
private serve(req: http.IncomingMessage, res: http.ServerResponse): void {
private serve(req: IncomingMessage, res: ServerResponse): void {
const filename = req.url!.replace(this._path, "").replace(/\?.*$/, "");
const isMap = dotMapRegex.test(filename);
const type = isMap ? "map" : "source";
@@ -678,8 +683,8 @@ export class Server<
*/
private static sendFile(
filename: string,
req: http.IncomingMessage,
res: http.ServerResponse,
req: IncomingMessage,
res: ServerResponse,
): void {
const readStream = createReadStream(
path.join(__dirname, "../client-dist/", filename),
@@ -831,14 +836,13 @@ export class Server<
restoreAdapter();
if (this.httpServer) {
await new Promise<void>((resolve, reject) => {
return new Promise<void>((resolve) => {
this.httpServer.close((err) => {
fn && fn(err);
if (err) {
reject(err);
} else {
resolve();
debug("server was not running");
}
resolve();
});
});
} else {

View File

@@ -22,8 +22,6 @@ export type EventNames<Map extends EventsMap> = keyof Map & (string | symbol);
/**
* Returns a union type containing all the keys of an event map that have an acknowledgement callback.
*
* That also have *some* data coming in.
*/
export type EventNamesWithAck<
Map extends EventsMap,
@@ -32,11 +30,11 @@ export type EventNamesWithAck<
Last<Parameters<Map[K]>> | Map[K],
K,
K extends (
Last<Parameters<Map[K]>> extends (...args: any[]) => any
? FirstNonErrorArg<Last<Parameters<Map[K]>>> extends void
? never
: K
: never
Parameters<Map[K]> extends never[]
? never
: Last<Parameters<Map[K]>> extends (...args: any[]) => any
? K
: never
)
? K
: never

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "4.8.2",
"version": "4.8.3",
"description": "node.js realtime framework server",
"keywords": [
"realtime",

View File

@@ -70,6 +70,27 @@ describe("close", () => {
});
});
it("should not throw when the underlying HTTP server is not running (callback)", (done) => {
const httpServer = createServer();
const io = new Server(httpServer);
io.close((err) => {
expect((err as Error & { code: string }).code).to.eql(
"ERR_SERVER_NOT_RUNNING",
);
done();
});
});
it("should not throw when the underlying HTTP server is not running (Promise)", (done) => {
const httpServer = createServer();
const io = new Server(httpServer);
io.close()
.then(() => done())
.catch((e) => done(e));
});
describe("graceful close", () => {
function fixture(filename) {
return (

View File

@@ -534,6 +534,11 @@ describe("messaging many", () => {
// @ts-ignore
expect(err.responses).to.contain(1, 2);
for (const [, serverSocket] of io.of("/").sockets) {
// @ts-ignore accessing private acks map to verify cleanup
expect(serverSocket.acks.size).to.be(0);
}
success(done, io, socket1, socket2, socket3);
}
});

View File

@@ -265,15 +265,11 @@ describe("server", () => {
interface ServerToClientEventsWithMultipleWithAck {
ackFromServer: (a: boolean, b: string) => Promise<boolean[]>;
ackFromServerSingleArg: (a: boolean, b: string) => Promise<string[]>;
// This should technically be `undefined[]`, but this doesn't work currently *only* with emitWithAck
// you can use an empty callback with emit, but not emitWithAck
onlyCallback: () => Promise<undefined>;
}
interface ServerToClientEventsWithAck {
ackFromServer: (a: boolean, b: string) => Promise<boolean>;
ackFromServerSingleArg: (a: boolean, b: string) => Promise<string>;
// This doesn't work currently *only* with emitWithAck
// you can use an empty callback with emit, but not emitWithAck
onlyCallback: () => Promise<undefined>;
}
describe("Emitting Types", () => {
@@ -420,8 +416,9 @@ describe("server", () => {
sio.timeout(0).emitWithAck("noArgs");
// @ts-expect-error - "helloFromServer" doesn't have a callback and is thus excluded
sio.timeout(0).emitWithAck("helloFromServer");
// @ts-expect-error - "onlyCallback" doesn't have a callback and is thus excluded
sio.timeout(0).emitWithAck("onlyCallback");
expectType<
ToEmitWithAck<ServerToClientEventsWithMultipleWithAck, "onlyCallback">
>(sio.timeout(0).emitWithAck<"onlyCallback">);
expectType<
ToEmitWithAck<
ServerToClientEventsWithMultipleWithAck,
@@ -447,7 +444,7 @@ describe("server", () => {
nio.emit<"noArgs">,
);
expectType<ToEmit<ServerToClientEventsNoAck, "helloFromServer">>(
// These errors will dissapear once the TS version is updated from 4.7.4
// These errors will disappear once the TS version is updated from 4.7.4
// the TSD instance is using a newer version of TS than the workspace version
// to enable the ability to compare against `any`
sio.emit<"helloFromServer">,
@@ -496,10 +493,12 @@ describe("server", () => {
s.emitWithAck("noArgs");
// @ts-expect-error - "helloFromServer" doesn't have a callback and is thus excluded
s.emitWithAck("helloFromServer");
// @ts-expect-error - "onlyCallback" doesn't have a callback and is thus excluded
s.emitWithAck("onlyCallback");
// @ts-expect-error - "onlyCallback" doesn't have a callback and is thus excluded
s.timeout(0).emitWithAck("onlyCallback");
expectType<
ToEmitWithAck<ServerToClientEventsWithAck, "onlyCallback">
>(s.emitWithAck<"onlyCallback">);
expectType<
ToEmitWithAck<ServerToClientEventsWithAck, "onlyCallback">
>(s.timeout(0).emitWithAck<"onlyCallback">);
expectType<
ToEmitWithAck<ServerToClientEventsWithAck, "ackFromServerSingleArg">
>(s.emitWithAck<"ackFromServerSingleArg">);