Compare commits

..

73 Commits

Author SHA1 Message Date
Damien Arrachequesne
ee9aac3134 chore(release): socket.io-parser@4.2.5
Diff: https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.4...socket.io-parser@4.2.5
2025-12-23 12:26:38 +01:00
Damien Arrachequesne
968277cef8 chore(release): socket.io-adapter@2.5.6
Diff: https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.5...socket.io-adapter@2.5.6
2025-12-23 12:18:53 +01:00
Damien Arrachequesne
2bf16bd214 chore(release): engine.io-client@6.6.4
Diff: https://github.com/socketio/socket.io/compare/engine.io-client@6.6.3...engine.io-client@6.6.4
2025-12-23 12:03:43 +01:00
Damien Arrachequesne
ad616070b8 docs(eio): fix link in the release notes
[skip ci]
2025-12-22 17:53:09 +01:00
Damien Arrachequesne
dd71792455 chore(release): socket.io@4.8.2
Diff: https://github.com/socketio/socket.io/compare/socket.io@4.8.1...socket.io@4.8.2
2025-12-22 17:42:41 +01:00
Ihor Machuzhak
bb0b480d2a fix(sio): improve io.close() function (#5344)
Before this change, `await io.close();` would resolve before the HTTP server was properly shut down.

Related: https://github.com/socketio/socket.io/pull/4971
2025-12-22 17:37:24 +01:00
Damien Arrachequesne
161be91975 test(sio): pin version of the client bundle in the tests 2025-12-22 17:35:35 +01:00
Damien Arrachequesne
fd9d4cab5e chore(release): socket.io-client@4.8.2
Diff: https://github.com/socketio/socket.io/compare/socket.io-client@4.8.1...socket.io-client@4.8.2
2025-12-22 16:52:21 +01:00
Damien Arrachequesne
0a99ac44a2 chore(release): engine.io@6.6.5
Diff: https://github.com/socketio/socket.io/compare/engine.io@6.6.4...engine.io@6.6.5
2025-12-22 16:27:31 +01:00
Damien Arrachequesne
4338f47336 ci(publish): use Node.js 24
Trusted publishing requires npm CLI version 11.5.1 or later.

Reference: https://docs.npmjs.com/trusted-publishers#for-github-actions
2025-12-22 16:27:30 +01:00
Damien Arrachequesne
9199156758 test(eio): fix flaky test 2025-12-22 15:27:48 +01:00
Damien Arrachequesne
594841617d test(redis-streams-emitter): migrate to Node.js test runner
We should eventually be able to replace:

- mocha and nyc with Node.js built-in test runner (`node:test`)
- expect.js with Node.js built-in assertion library (`node:assert`)
2025-12-22 14:48:29 +01:00
Denis Barbaron
84e7253e57 refactor(sio): add package.json entrypoint (#5239) 2025-12-22 14:33:12 +01:00
Damien Arrachequesne
30ec4a136a test(sio-client): reactivate all tests 2025-12-22 13:46:15 +01:00
Damien Arrachequesne
e08293bc37 refactor(eio): use URL constructor instead of url.parse() 2025-12-22 13:45:52 +01:00
Damien Arrachequesne
b837949479 ci: use Node.js 24
Reference: https://github.com/nodejs/Release
2025-12-22 13:45:36 +01:00
Damien Arrachequesne
118ef41b94 test: use tsx instead of ts-node 2025-12-22 10:39:57 +01:00
Suraj Rana
d19928e8d8 fix(sio-client): drain queue before emitting "connect" (#5259)
When the `retries` option was enabled, an event emitted in the "connect" handler would be sent twice.

Related: https://github.com/socketio/socket.io/issues/5258
2025-12-22 10:39:39 +01:00
Damien Arrachequesne
cdae01983a fix(sio-client): do not mangle the "_placeholder" attribute (bis)
The "_placeholder" attribute is used when sending binary data, and was
incorrectly mangled (converted to a random short property, like "it",
to reduce the bundle size).

Related:

- ca9e994815
- https://github.com/socketio/socket.io/issues/5215

[skip ci]
2025-12-19 15:48:28 +01:00
Damien Arrachequesne
39bb72039d docs: add release steps
[skip ci]
2025-12-19 15:43:14 +01:00
Valentin Rault
98741e15e9 refactor(sio-client): export DisconnectDescription type (#5392)
Related: https://github.com/socketio/socket.io/issues/4556
2025-12-19 14:51:39 +01:00
Damien Arrachequesne
8af70195bb refactor(sio): use URL constructor instead of url.parse()
Related: https://github.com/socketio/socket.io/issues/5377
2025-12-19 14:44:30 +01:00
Damien Arrachequesne
d88f3f4578 ci: use actions/checkout@v6 and actions/setup-node@v6
Release notes:

- https://github.com/actions/checkout/blob/main/CHANGELOG.md
- https://github.com/actions/setup-node/releases/tag/v6.0.0
2025-12-15 09:38:45 +01:00
Damien Arrachequesne
f5ee981ee8 ci(publish): use trusted publishing
Reference: https://docs.npmjs.com/trusted-publishers

[skip ci]
2025-12-15 08:57:17 +01:00
Damien Arrachequesne
76e3a72bba docs: add missing changelog links
[skip ci]
2025-12-15 08:55:26 +01:00
Damien Arrachequesne
a7b1938d06 test: regenerate SSL certs 2025-12-15 08:45:47 +01:00
Damien Arrachequesne
54743633ff chore(release): @socket.io/redis-streams-emitter@0.1.1
Diff: https://github.com/socketio/socket.io/compare/@socket.io/redis-streams-emitter@0.1.0...@socket.io/redis-streams-emitter@0.1.1
2025-11-07 10:33:07 +01:00
Damien Arrachequesne
7617707ed8 fix(redis-streams-emitter): remove dependency on socket.io-adapter
Related: https://github.com/socketio/socket.io/issues/5414
2025-11-07 10:28:27 +01:00
Damien Arrachequesne
599001d213 chore(release): @socket.io/redis-streams-emitter@0.1.0 2025-11-06 18:23:00 +01:00
Damien Arrachequesne
1c3e4711c1 feat: add emitter based on Redis streams
Related: https://github.com/socketio/socket.io-redis-streams-adapter/issues/8
2025-11-06 18:03:37 +01:00
Damien Arrachequesne
693080cac7 refactor(sio-adapter): add more debug logs 2025-10-20 15:11:21 +02:00
Damien Arrachequesne
5080c73e1e refactor: fix npm command 2025-10-17 09:41:39 +02:00
Damien Arrachequesne
47ff1cd04c chore(release): @socket.io/cluster-adapter@0.3.0
Diff: https://github.com/socketio/socket.io-cluster-adapter/compare/0.2.2...0.3.0
2025-10-16 19:55:56 +02:00
Damien Arrachequesne
0ae76360f9 Merge remote-tracking branch 'socket.io-cluster-adapter/monorepo' 2025-10-16 19:35:51 +02:00
Damien Arrachequesne
27fd420e75 refactor: prepare migration to monorepo 2025-10-16 19:31:30 +02:00
Damien Arrachequesne
0c431243e2 refactor: use the ClusterAdapter class from socket.io-adapter package
The ClusterAdapter class has been moved to [1], so that this adapter
only needs to implement to pub/sub mechanism.

Also, [2] should reduce the number of "timeout reached: only x
responses received out of y" errors, since the fetchSockets() requests
will now succeed even if a server leaves the cluster.

[1]: https://github.com/socketio/socket.io/tree/main/packages/socket.io-adapter
[2]: 0e23ff0cc6
2025-10-16 19:16:20 +02:00
Damien Arrachequesne
4fc25d80ec ci: add Node.js 24
Reference: https://github.com/nodejs/Release
2025-10-16 12:03:32 +02:00
Damien Arrachequesne
1dd729b1a1 refactor: upgrade to prettier 3 2025-10-16 11:51:11 +02:00
Damien Arrachequesne
6877512f57 refactor: upgrade to TypeScript 5 2025-10-16 11:49:12 +02:00
Damien Arrachequesne
cf6816afcf chore: npm audit fix 2025-10-10 09:29:29 +02:00
Damien Arrachequesne
625fd66d73 chore: dedupe debug dependency 2025-10-10 09:11:54 +02:00
Damien Arrachequesne
f3e1f5ebdf fix(sio): call adapter.init() when creating each namespace
The init() method of the adapter will now be called when creating a namespace with `io.of(<the-namespace>)`.

Note: any promise rejection is silently caught, as I don't see how we could properly expose the promise.

```js
const io = new Server({
  adapter: myAdapter
});
// under the hood, this:
// - implicitly creates the main namespace (/)
// - creates an instance of `myAdapter` for the main namespace
// - calls `myAdapter.init()` (with this change)
```

Related:

- https://github.com/socketio/socket.io/issues/3662
- https://github.com/socketio/socket.io-postgres-adapter/issues/16
2025-10-09 09:48:07 +02:00
Damien Arrachequesne
e97549259e ci(browser): use Windows 8 for IE tests 2025-09-30 11:44:50 +02:00
MiaoWoo
1da9cddeab fix(eio-client): properly handle port option (#5241)
Passing { port: "443" } would include the port in the URL (":443").
2025-09-30 10:57:43 +02:00
Avi Vahl
6f9b198bc8 chore(deps): ws@8.18.3, debug@4.4.1 (#5335)
Release notes:

- https://github.com/websockets/ws/releases/tag/8.18.3
- https://github.com/debug-js/debug/releases/tag/4.4.1
2025-09-11 07:51:07 +02:00
Damien Arrachequesne
ac3df9a747 chore(release): @socket.io/postgres-emitter@0.1.1 2025-09-05 07:27:48 +02:00
Damien Arrachequesne
21fd54ece6 refactor(postgres-emitter): update compose file 2025-09-05 07:20:29 +02:00
Damien Arrachequesne
96d907b9b5 docs(postgres-emitter): add dark version of the explanation diagram 2025-09-05 07:19:07 +02:00
Damien Arrachequesne
32257b6cb8 fix(postgres-emitter): use parameterized query to send the NOTIFY command
Related:

- https://github.com/socketio/socket.io-postgres-emitter/issues/1
- https://github.com/socketio/socket.io-postgres-adapter/pull/1
2025-09-05 07:18:36 +02:00
Damien Arrachequesne
c7144920e3 Merge remote-tracking branch 'socket.io-postgres-emitter/main' into monorepo 2025-09-04 09:30:26 +02:00
Damien Arrachequesne
42480e9a7f chore: prepare migration to monorepo 2025-09-04 09:23:32 +02:00
Lou Klepner
0a8f91047c docs: fix adapter link (#2) 2025-09-04 09:22:40 +02:00
Damien Arrachequesne
a66ed68506 docs(protocol): add test with cancelled request
Related: 8f1ea3d58f
2025-09-03 09:02:44 +02:00
Damien Arrachequesne
3be6481d9d ci: pin Node.js 22 version
Related: https://github.com/nodejs/node/issues/59364
2025-08-09 09:31:12 +02:00
Wang Guan
be13cca94c refactor: improve type annotations and comments (#5364) 2025-08-09 08:43:34 +02:00
Damien Arrachequesne
e95f6abf93 docs: fix message handler latency in test suites
Related: https://github.com/socketio/socket.io-protocol/issues/32
2025-03-28 21:29:20 +01:00
Damien Arrachequesne
56a53bceb9 ci: add Node.js 20 in the test matrix 2023-07-09 10:03:47 +02:00
Damien Arrachequesne
683720a67d test: fix flaky test 2023-07-09 10:03:46 +02:00
Damien Arrachequesne
a529eb08d6 chore: bump dev dependencies 2023-07-09 10:03:32 +02:00
Damien Arrachequesne
cddb78e5fa chore(release): 0.2.2
Diff: https://github.com/socketio/socket.io-cluster-adapter/compare/0.2.1...0.2.2
2023-03-24 17:32:35 +01:00
Damien Arrachequesne
15fd56e78d chore: add socket.io-parser to peerDependencies
This should (at least in theory) fix sync issues for the
`socket.io-adapter` package, which is imported by both the `socket.io`
and `@socket.io/cluster-adapter` packages:

- `socket.io@4.5.0` should resolve `socket.io-adapter@~2.4.0`
- `socket.io@4.6.0` should resolve `socket.io-adapter@~2.5.0`
2023-03-24 17:28:57 +01:00
Damien Arrachequesne
e86ef45f87 ci: upgrade to actions/checkout@3 and actions/setup-node@3
Reference: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/
2023-03-24 17:16:25 +01:00
Damien Arrachequesne
fe840e2eb3 chore(release): 0.2.1
Diff: https://github.com/socketio/socket.io-cluster-adapter/compare/0.2.0...0.2.1
2022-10-13 09:23:36 +02:00
Damien Arrachequesne
a5a1c29082 chore: update dev dependencies 2022-10-13 09:17:46 +02:00
Damien Arrachequesne
66b4079953 ci: add Node.js 18 in the test matrix
Reference: https://github.com/nodejs/Release
2022-10-13 09:15:34 +02:00
Rolando Andrade
be0a0e3217 fix: properly handle ERR_IPC_CHANNEL_CLOSED errors (#6)
Related: https://github.com/socketio/socket.io-cluster-adapter/issues/5
2022-10-13 09:11:37 +02:00
Damien Arrachequesne
43f9ee8d23 chore(release): 0.2.0
Diff: https://github.com/socketio/socket.io-cluster-adapter/compare/0.1.0...0.2.0
2022-04-28 16:16:13 +02:00
Damien Arrachequesne
055b7840d8 feat: broadcast and expect multiple acks
This feature was added in `socket.io@4.5.0`:

```js
io.timeout(1000).emit("some-event", (err, responses) => {
  // ...
});
```

Thanks to this change, it will now work with multiple Socket.IO
servers.

Related: https://github.com/socketio/socket.io/issues/4163
2022-04-28 16:11:12 +02:00
Damien Arrachequesne
6397c1bdfd chore(release): 0.1.0 2021-06-22 07:06:15 +02:00
Damien Arrachequesne
ff370cfc46 Initial commit 2021-06-22 07:02:16 +02:00
Damien Arrachequesne
1f8a6c4ecb docs: add link to related packages 2021-06-14 08:21:28 +02:00
Damien Arrachequesne
eb01ff5803 chore(release): 0.1.0 2021-06-14 08:02:20 +02:00
Damien Arrachequesne
f2e3d162ab Initial commit 2021-06-14 07:59:12 +02:00
129 changed files with 12814 additions and 2850 deletions

View File

@@ -20,9 +20,9 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
node-version: node-version:
- 18
- 20 - 20
- 22 - 22
- 24
services: services:
redis: redis:
@@ -35,12 +35,24 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: changeit
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}

View File

@@ -1,4 +1,4 @@
# reference: https://docs.npmjs.com/generating-provenance-statements # reference: https://docs.npmjs.com/trusted-publishers#for-github-actions
name: Publish name: Publish
@@ -19,10 +19,10 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Use Node.js 20 - name: Use Node.js 24
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 24
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- name: Install dependencies - name: Install dependencies
@@ -32,6 +32,4 @@ jobs:
run: npm run compile --workspaces --if-present run: npm run compile --workspaces --if-present
- name: Publish package - name: Publish package
run: npm publish --workspace=${GITHUB_REF_NAME%@*} --provenance --access public run: npm publish --workspace=${GITHUB_REF_NAME%@*} --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -2,14 +2,17 @@
Here are the detailed changelogs for each package in this monorepo: Here are the detailed changelogs for each package in this monorepo:
| Package | Changelog | | Package | Changelog |
|--------------------------------|---------------------------------------------------------| |------------------------------------|----------------------------------------------------------------|
| `engine.io` | [link](packages/engine.io/CHANGELOG.md) | | `engine.io` | [link](packages/engine.io/CHANGELOG.md) |
| `engine.io-client` | [link](packages/engine.io-client/CHANGELOG.md) | | `engine.io-client` | [link](packages/engine.io-client/CHANGELOG.md) |
| `engine.io-parser` | [link](packages/engine.io-parser/CHANGELOG.md) | | `engine.io-parser` | [link](packages/engine.io-parser/CHANGELOG.md) |
| `socket.io` | [link](packages/socket.io/CHANGELOG.md) | | `socket.io` | [link](packages/socket.io/CHANGELOG.md) |
| `socket.io-adapter` | [link](packages/socket.io-adapter/CHANGELOG.md) | | `socket.io-adapter` | [link](packages/socket.io-adapter/CHANGELOG.md) |
| `socket.io-client` | [link](packages/socket.io-client/CHANGELOG.md) | | `socket.io-client` | [link](packages/socket.io-client/CHANGELOG.md) |
| `@socket.io/cluster-engine` | [link](packages/socket.io-cluster-engine/CHANGELOG.md) | | `@socket.io/cluster-adapter` | [link](packages/socket.io-cluster-adapter/CHANGELOG.md) |
| `@socket.io/component-emitter` | [link](packages/socket.io-component-emitter/History.md) | | `@socket.io/cluster-engine` | [link](packages/socket.io-cluster-engine/CHANGELOG.md) |
| `socket.io-parser` | [link](packages/socket.io-parser/CHANGELOG.md) | | `@socket.io/component-emitter` | [link](packages/socket.io-component-emitter/History.md) |
| `socket.io-parser` | [link](packages/socket.io-parser/CHANGELOG.md) |
| `@socket.io/postgres-emitter` | [link](packages/socket.io-postgres-emitter/CHANGELOG.md) |
| `@socket.io/redis-streams-emitter` | [link](/packages/socket.io-redis-streams-emitter/CHANGELOG.md) |

View File

@@ -17,16 +17,35 @@ function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, 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) { function waitFor(socket, eventType) {
return new Promise((resolve) => { if (socket._eventBuffer[eventType].length) {
socket.addEventListener( return Promise.resolve(socket._eventBuffer[eventType].shift());
eventType, } else {
(event) => { return new Promise((resolve) => {
resolve(event); socket._pendingPromises[eventType].push(resolve);
}, });
{ once: true } }
);
});
} }
async function initLongPollingSession() { async function initLongPollingSession() {
@@ -110,7 +129,7 @@ describe("Engine.IO protocol", () => {
describe("WebSocket", () => { describe("WebSocket", () => {
it("successfully opens a session", async () => { it("successfully opens a session", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket` `${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 () => { it("fails with an invalid 'EIO' query parameter", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?transport=websocket` `${WS_URL}/engine.io/?transport=websocket`
); );
@@ -145,9 +164,9 @@ describe("Engine.IO protocol", () => {
socket.on("error", () => {}); 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` `${WS_URL}/engine.io/?EIO=abc&transport=websocket`
); );
@@ -155,19 +174,19 @@ describe("Engine.IO protocol", () => {
socket2.on("error", () => {}); socket2.on("error", () => {});
} }
waitFor(socket2, "close"); await waitFor(socket2, "close");
}); });
it("fails with an invalid 'transport' query parameter", async () => { 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) { if (isNodejs) {
socket.on("error", () => {}); 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` `${WS_URL}/engine.io/?EIO=4&transport=abc`
); );
@@ -175,7 +194,7 @@ describe("Engine.IO protocol", () => {
socket2.on("error", () => {}); socket2.on("error", () => {});
} }
waitFor(socket2, "close"); await waitFor(socket2, "close");
}); });
}); });
}); });
@@ -313,11 +332,30 @@ describe("Engine.IO protocol", () => {
expect(pollResponse.status).to.eql(400); expect(pollResponse.status).to.eql(400);
}); });
it("closes the session upon cancelled polling request", async () => {
const sid = await initLongPollingSession();
const controller = new AbortController();
fetch(`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`, {
signal: controller.signal,
}).catch(() => {});
await sleep(5);
controller.abort();
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
);
expect(pollResponse.status).to.eql(400);
});
}); });
describe("WebSocket", () => { describe("WebSocket", () => {
it("sends and receives a plain text packet", async () => { it("sends and receives a plain text packet", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket` `${WS_URL}/engine.io/?EIO=4&transport=websocket`
); );
@@ -335,7 +373,7 @@ describe("Engine.IO protocol", () => {
}); });
it("sends and receives a binary packet", async () => { it("sends and receives a binary packet", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket` `${WS_URL}/engine.io/?EIO=4&transport=websocket`
); );
socket.binaryType = "arraybuffer"; socket.binaryType = "arraybuffer";
@@ -352,7 +390,7 @@ describe("Engine.IO protocol", () => {
}); });
it("closes the session upon invalid packet format", async () => { it("closes the session upon invalid packet format", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket` `${WS_URL}/engine.io/?EIO=4&transport=websocket`
); );
@@ -412,7 +450,7 @@ describe("Engine.IO protocol", () => {
describe("WebSocket", () => { describe("WebSocket", () => {
it("sends ping/pong packets", async () => { it("sends ping/pong packets", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket` `${WS_URL}/engine.io/?EIO=4&transport=websocket`
); );
@@ -430,7 +468,7 @@ describe("Engine.IO protocol", () => {
}); });
it("closes the session upon ping timeout", async () => { it("closes the session upon ping timeout", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket` `${WS_URL}/engine.io/?EIO=4&transport=websocket`
); );
@@ -468,7 +506,7 @@ describe("Engine.IO protocol", () => {
describe("WebSocket", () => { describe("WebSocket", () => {
it("forcefully closes the session", async () => { it("forcefully closes the session", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket` `${WS_URL}/engine.io/?EIO=4&transport=websocket`
); );
@@ -485,7 +523,7 @@ describe("Engine.IO protocol", () => {
it("successfully upgrades from HTTP long-polling to WebSocket", async () => { it("successfully upgrades from HTTP long-polling to WebSocket", async () => {
const sid = await initLongPollingSession(); const sid = await initLongPollingSession();
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
); );
@@ -521,12 +559,13 @@ describe("Engine.IO protocol", () => {
it("ignores HTTP requests with same sid after upgrade", async () => { it("ignores HTTP requests with same sid after upgrade", async () => {
const sid = await initLongPollingSession(); const sid = await initLongPollingSession();
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
); );
await waitFor(socket, "open"); await waitFor(socket, "open");
socket.send("2probe"); socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5"); socket.send("5");
const pollResponse = await fetch( const pollResponse = await fetch(
@@ -545,15 +584,16 @@ describe("Engine.IO protocol", () => {
it("ignores WebSocket connection with same sid after upgrade", async () => { it("ignores WebSocket connection with same sid after upgrade", async () => {
const sid = await initLongPollingSession(); const sid = await initLongPollingSession();
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
); );
await waitFor(socket, "open"); await waitFor(socket, "open");
socket.send("2probe"); socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5"); socket.send("5");
const socket2 = new WebSocket( const socket2 = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}` `${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
); );

View File

@@ -17,16 +17,35 @@ function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, 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) { function waitFor(socket, eventType) {
return new Promise((resolve) => { if (socket._eventBuffer[eventType].length) {
socket.addEventListener( return Promise.resolve(socket._eventBuffer[eventType].shift());
eventType, } else {
(event) => { return new Promise((resolve) => {
resolve(event); socket._pendingPromises[eventType].push(resolve);
}, });
{ once: true } }
);
});
} }
function waitForPackets(socket, count) { function waitForPackets(socket, count) {
@@ -55,7 +74,7 @@ async function initLongPollingSession() {
} }
async function initSocketIOConnection() { async function initSocketIOConnection() {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${WS_URL}/socket.io/?EIO=4&transport=websocket`
); );
socket.binaryType = "arraybuffer"; socket.binaryType = "arraybuffer";
@@ -145,7 +164,7 @@ describe("Engine.IO protocol", () => {
describe("WebSocket", () => { describe("WebSocket", () => {
it("should successfully open a session", async () => { it("should successfully open a session", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${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 () => { it("should fail with an invalid 'EIO' query parameter", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?transport=websocket` `${WS_URL}/socket.io/?transport=websocket`
); );
@@ -180,9 +199,9 @@ describe("Engine.IO protocol", () => {
socket.on("error", () => {}); 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` `${WS_URL}/socket.io/?EIO=abc&transport=websocket`
); );
@@ -190,19 +209,19 @@ describe("Engine.IO protocol", () => {
socket2.on("error", () => {}); socket2.on("error", () => {});
} }
waitFor(socket2, "close"); await waitFor(socket2, "close");
}); });
it("should fail with an invalid 'transport' query parameter", async () => { 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) { if (isNodejs) {
socket.on("error", () => {}); 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` `${WS_URL}/socket.io/?EIO=4&transport=abc`
); );
@@ -210,7 +229,7 @@ describe("Engine.IO protocol", () => {
socket2.on("error", () => {}); socket2.on("error", () => {});
} }
waitFor(socket2, "close"); await waitFor(socket2, "close");
}); });
}); });
}); });
@@ -260,7 +279,7 @@ describe("Engine.IO protocol", () => {
describe("WebSocket", () => { describe("WebSocket", () => {
it("should send ping/pong packets", async () => { it("should send ping/pong packets", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${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 () => { it("should close the session upon ping timeout", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${WS_URL}/socket.io/?EIO=4&transport=websocket`
); );
@@ -316,7 +335,7 @@ describe("Engine.IO protocol", () => {
describe("WebSocket", () => { describe("WebSocket", () => {
it("should forcefully close the session", async () => { it("should forcefully close the session", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${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 () => { it("should successfully upgrade from HTTP long-polling to WebSocket", async () => {
const sid = await initLongPollingSession(); const sid = await initLongPollingSession();
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` `${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 () => { it("should ignore HTTP requests with same sid after upgrade", async () => {
const sid = await initLongPollingSession(); const sid = await initLongPollingSession();
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` `${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
); );
await waitFor(socket, "open"); await waitFor(socket, "open");
socket.send("2probe"); socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5"); socket.send("5");
const pollResponse = await fetch( const pollResponse = await fetch(
@@ -371,15 +391,16 @@ describe("Engine.IO protocol", () => {
it("should ignore WebSocket connection with same sid after upgrade", async () => { it("should ignore WebSocket connection with same sid after upgrade", async () => {
const sid = await initLongPollingSession(); const sid = await initLongPollingSession();
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` `${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
); );
await waitFor(socket, "open"); await waitFor(socket, "open");
socket.send("2probe"); socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5"); socket.send("5");
const socket2 = new WebSocket( const socket2 = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}` `${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
); );
@@ -391,7 +412,7 @@ describe("Engine.IO protocol", () => {
describe("Socket.IO protocol", () => { describe("Socket.IO protocol", () => {
describe("connect", () => { describe("connect", () => {
it("should allow connection to the main namespace", async () => { it("should allow connection to the main namespace", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${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 () => { 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` `${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 () => { it("should allow connection to a custom namespace", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${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 () => { 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` `${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 () => { it("should disallow connection to an unknown namespace", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${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 () => { it("should disallow connection with an invalid handshake", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${WS_URL}/socket.io/?EIO=4&transport=websocket`
); );
@@ -508,10 +529,9 @@ describe("Socket.IO protocol", () => {
await waitFor(socket, "close"); await waitFor(socket, "close");
}); });
it("should close the connection if no handshake is received", async () => { it("should close the connection if no handshake is received", async () => {
const socket = new WebSocket( const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket` `${WS_URL}/socket.io/?EIO=4&transport=websocket`
); );
await waitFor(socket, "close"); await waitFor(socket, "close");

View File

@@ -7,7 +7,7 @@
"prettier": "^2.8.4", "prettier": "^2.8.4",
"rollup": "^3.20.2", "rollup": "^3.20.2",
"socket.io": "^4.6.1", "socket.io": "^4.6.1",
"ws": "^8.13.0" "ws": "^8.18.3"
}, },
"scripts": { "scripts": {
"bundle": "rollup -c", "bundle": "rollup -c",

4787
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,14 +8,17 @@
"packages/socket.io-cluster-engine", "packages/socket.io-cluster-engine",
"packages/engine.io-client", "packages/engine.io-client",
"packages/socket.io-adapter", "packages/socket.io-adapter",
"packages/socket.io-cluster-adapter",
"packages/socket.io-parser", "packages/socket.io-parser",
"packages/socket.io-client", "packages/socket.io-client",
"packages/socket.io" "packages/socket.io",
"packages/socket.io-postgres-emitter",
"packages/socket.io-redis-streams-emitter"
], ],
"overrides": { "overrides": {
"@types/estree": "0.0.52", "@types/estree": "0.0.52",
"@types/lodash": "4.14.189", "@types/lodash": "4.14.189",
"ws": "8.17.1" "ws": "8.18.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.7", "@babel/core": "^7.24.7",
@@ -29,10 +32,13 @@
"@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@sinonjs/fake-timers": "^11.2.2", "@sinonjs/fake-timers": "^11.2.2",
"@socket.io/postgres-adapter": "^0.1.0",
"@socket.io/redis-streams-adapter": "~0.2.2",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/expect.js": "^0.3.32", "@types/expect.js": "^0.3.32",
"@types/mocha": "^10.0.7", "@types/mocha": "^10.0.7",
"@types/node": "18.15.3", "@types/node": "18.15.3",
"@types/pg": "^8.6.0",
"@types/sinonjs__fake-timers": "^8.1.5", "@types/sinonjs__fake-timers": "^8.1.5",
"@wdio/cli": "^8.39.1", "@wdio/cli": "^8.39.1",
"@wdio/local-runner": "^8.39.1", "@wdio/local-runner": "^8.39.1",
@@ -55,6 +61,7 @@
"mocha": "^10.6.0", "mocha": "^10.6.0",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nyc": "^17.0.0", "nyc": "^17.0.0",
"pg": "^8.6.0",
"prettier": "^3.3.2", "prettier": "^3.3.2",
"redis": "^4.6.15", "redis": "^4.6.15",
"rimraf": "^6.0.0", "rimraf": "^6.0.0",
@@ -69,8 +76,9 @@
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsd": "^0.31.1", "tsd": "^0.31.1",
"tsx": "~4.20.6",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0", "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.56.0",
"wdio-geckodriver-service": "^5.0.2" "wdio-geckodriver-service": "^5.0.2"
} }
} }

View File

@@ -1,7 +1,8 @@
# History # Changelog
| Version | Release date | Bundle size (UMD min+gzip) | | Version | Release date | Bundle size (UMD min+gzip) |
|-------------------------------------------------------------------------------------------------------------|----------------|----------------------------| |-------------------------------------------------------------------------------------------------------------|----------------|----------------------------|
| [6.6.4](#664-2025-12-23) | December 2025 | `8.7 KB` |
| [6.6.3](#663-2025-01-23) | January 2025 | `8.7 KB` | | [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.2](#662-2024-10-23) | October 2024 | `8.7 KB` |
| [6.6.1](#661-2024-09-21) | September 2024 | `8.7 KB` | | [6.6.1](#661-2024-09-21) | September 2024 | `8.7 KB` |
@@ -39,7 +40,24 @@
| [4.1.1](#411-2021-02-02) | February 2021 | `9.1 KB` | | [4.1.1](#411-2021-02-02) | February 2021 | `9.1 KB` |
| [4.1.0](#410-2021-01-14) | January 2021 | `9.1 KB` | | [4.1.0](#410-2021-01-14) | January 2021 | `9.1 KB` |
# Release notes
## [6.6.4](https://github.com/socketio/socket.io/compare/engine.io-client@6.6.3...engine.io-client@6.6.4) (2025-12-23)
This release contains a bump of:
- `ws` from `~8.17.1` to `~8.18.3`
- `debug` from `~4.3.1` to `~4.4.1`
### Bug Fixes
* properly handle port option ([#5241](https://github.com/socketio/socket.io/issues/5241)) ([1da9cdd](https://github.com/socketio/socket.io/commit/1da9cddeab0bf5ce41890d156d73af8194cef656))
### Dependencies
- [`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))
## [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) ## [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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/*! /*!
* Engine.IO v6.6.3 * Engine.IO v6.6.4
* (c) 2014-2025 Guillermo Rauch * (c) 2014-2025 Guillermo Rauch
* Released under the MIT License. * Released under the MIT License.
*/ */
@@ -35,6 +35,54 @@
writable: !1 writable: !1
}), e; }), e;
} }
function _createForOfIteratorHelper(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (!t) {
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
t && (r = t);
var n = 0,
F = function () {};
return {
s: F,
n: function () {
return n >= r.length ? {
done: !0
} : {
done: !1,
value: r[n++]
};
},
e: function (r) {
throw r;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var o,
a = !0,
u = !1;
return {
s: function () {
t = t.call(r);
},
n: function () {
var r = t.next();
return a = r.done, r;
},
e: function (r) {
u = !0, o = r;
},
f: function () {
try {
a || null == t.return || t.return();
} finally {
if (u) throw o;
}
}
};
}
function _extends() { function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) { return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) { for (var e = 1; e < arguments.length; e++) {
@@ -1049,21 +1097,65 @@
createDebug.namespaces = namespaces; createDebug.namespaces = namespaces;
createDebug.names = []; createDebug.names = [];
createDebug.skips = []; createDebug.skips = [];
var i; var split = (typeof namespaces === 'string' ? namespaces : '').trim().replace(/\s+/g, ',').split(',').filter(Boolean);
var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); var _iterator = _createForOfIteratorHelper(split),
var len = split.length; _step;
for (i = 0; i < len; i++) { try {
if (!split[i]) { for (_iterator.s(); !(_step = _iterator.n()).done;) {
// ignore empty strings var ns = _step.value;
continue; if (ns[0] === '-') {
createDebug.skips.push(ns.slice(1));
} else {
createDebug.names.push(ns);
}
} }
namespaces = split[i].replace(/\*/g, '.*?'); } catch (err) {
if (namespaces[0] === '-') { _iterator.e(err);
createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$')); } finally {
_iterator.f();
}
}
/**
* Checks if the given string matches a namespace template, honoring
* asterisks as wildcards.
*
* @param {String} search
* @param {String} template
* @return {Boolean}
*/
function matchesTemplate(search, template) {
var searchIndex = 0;
var templateIndex = 0;
var starIndex = -1;
var matchIndex = 0;
while (searchIndex < search.length) {
if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) {
// Match character or proceed with wildcard
if (template[templateIndex] === '*') {
starIndex = templateIndex;
matchIndex = searchIndex;
templateIndex++; // Skip the '*'
} else {
searchIndex++;
templateIndex++;
}
} else if (starIndex !== -1) {
// eslint-disable-line no-negated-condition
// Backtrack to the last '*' and try to match more characters
templateIndex = starIndex + 1;
matchIndex++;
searchIndex = matchIndex;
} else { } else {
createDebug.names.push(new RegExp('^' + namespaces + '$')); return false; // No match
} }
} }
// Handle trailing '*' in template
while (templateIndex < template.length && template[templateIndex] === '*') {
templateIndex++;
}
return templateIndex === template.length;
} }
/** /**
@@ -1073,7 +1165,7 @@
* @api public * @api public
*/ */
function disable() { function disable() {
var namespaces = [].concat(_toConsumableArray(createDebug.names.map(toNamespace)), _toConsumableArray(createDebug.skips.map(toNamespace).map(function (namespace) { var namespaces = [].concat(_toConsumableArray(createDebug.names), _toConsumableArray(createDebug.skips.map(function (namespace) {
return '-' + namespace; return '-' + namespace;
}))).join(','); }))).join(',');
createDebug.enable(''); createDebug.enable('');
@@ -1088,35 +1180,37 @@
* @api public * @api public
*/ */
function enabled(name) { function enabled(name) {
if (name[name.length - 1] === '*') { var _iterator2 = _createForOfIteratorHelper(createDebug.skips),
return true; _step2;
} try {
var i; for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var len; var skip = _step2.value;
for (i = 0, len = createDebug.skips.length; i < len; i++) { if (matchesTemplate(name, skip)) {
if (createDebug.skips[i].test(name)) { return false;
return false; }
} }
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
} }
for (i = 0, len = createDebug.names.length; i < len; i++) { var _iterator3 = _createForOfIteratorHelper(createDebug.names),
if (createDebug.names[i].test(name)) { _step3;
return true; try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var ns = _step3.value;
if (matchesTemplate(name, ns)) {
return true;
}
} }
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
} }
return false; return false;
} }
/**
* Convert regexp to namespace
*
* @param {RegExp} regxep
* @return {String} namespace
* @api private
*/
function toNamespace(regexp) {
return regexp.toString().substring(2, regexp.toString().length - 2).replace(/\.\*\?$/, '*');
}
/** /**
* Coerce `val`. * Coerce `val`.
* *
@@ -1192,15 +1286,17 @@
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
return false; return false;
} }
var m;
// Is webkit? http://stackoverflow.com/a/16459606/376773 // Is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
// eslint-disable-next-line no-return-assign
return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance ||
// Is firebug? http://stackoverflow.com/a/398120/376773 // Is firebug? http://stackoverflow.com/a/398120/376773
typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) || typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) ||
// Is firefox >= v31? // Is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 ||
// Double check webkit in userAgent just in case we are in a worker // Double check webkit in userAgent just in case we are in a worker
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/); typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
} }
@@ -1276,7 +1372,7 @@
function load() { function load() {
var r; var r;
try { try {
r = exports.storage.getItem('debug'); r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG');
} catch (error) { } catch (error) {
// Swallow // Swallow
// XXX (@Qix-) should we be logging these? // XXX (@Qix-) should we be logging these?
@@ -1457,7 +1553,7 @@
return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]"; return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]";
}; };
_proto._port = function _port() { _proto._port = function _port() {
if (this.opts.port && (this.opts.secure && Number(this.opts.port !== 443) || !this.opts.secure && Number(this.opts.port) !== 80)) { if (this.opts.port && (this.opts.secure && Number(this.opts.port) !== 443 || !this.opts.secure && Number(this.opts.port) !== 80)) {
return ":" + this.opts.port; return ":" + this.opts.port;
} else { } else {
return ""; return "";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -192,7 +192,7 @@ export abstract class Transport extends Emitter<
private _port() { private _port() {
if ( if (
this.opts.port && this.opts.port &&
((this.opts.secure && Number(this.opts.port !== 443)) || ((this.opts.secure && Number(this.opts.port) !== 443) ||
(!this.opts.secure && Number(this.opts.port) !== 80)) (!this.opts.secure && Number(this.opts.port) !== 80))
) { ) {
return ":" + this.opts.port; return ":" + this.opts.port;

View File

@@ -2,7 +2,7 @@
"name": "engine.io-client", "name": "engine.io-client",
"description": "Client for the realtime Engine", "description": "Client for the realtime Engine",
"license": "MIT", "license": "MIT",
"version": "6.6.3", "version": "6.6.4",
"main": "./build/cjs/index.js", "main": "./build/cjs/index.js",
"module": "./build/esm/index.js", "module": "./build/esm/index.js",
"exports": { "exports": {
@@ -53,9 +53,9 @@
], ],
"dependencies": { "dependencies": {
"@socket.io/component-emitter": "~3.1.0", "@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1", "debug": "~4.4.1",
"engine.io-parser": "~5.2.1", "engine.io-parser": "~5.2.1",
"ws": "~8.17.1", "ws": "~8.18.3",
"xmlhttprequest-ssl": "~2.1.1" "xmlhttprequest-ssl": "~2.1.1"
}, },
"scripts": { "scripts": {

View File

@@ -116,6 +116,32 @@ describe("Transport", () => {
expect(polling.uri()).to.contain("https://localhost/engine.io?sid=test"); expect(polling.uri()).to.contain("https://localhost/engine.io?sid=test");
}); });
it("should generate an https uri w/o a port (string)", () => {
const polling = new eio.transports.polling({
path: "/engine.io",
hostname: "localhost",
secure: true,
query: { sid: "test" },
port: "443",
timestampRequests: false,
});
expect(polling.uri()).to.contain("https://localhost/engine.io?sid=test");
});
it("should generate an https uri with a port", () => {
const polling = new eio.transports.polling({
path: "/engine.io",
hostname: "localhost",
secure: true,
query: { sid: "test" },
port: 8443,
timestampRequests: false,
});
expect(polling.uri()).to.contain(
"https://localhost:8443/engine.io?sid=test",
);
});
it("should generate a timestamped uri", () => { it("should generate a timestamped uri", () => {
const polling = new eio.transports.polling({ const polling = new eio.transports.polling({
path: "/engine.io", path: "/engine.io",

View File

@@ -53,6 +53,26 @@ async function setup(opts, cb) {
cb({ engine, h3Server, certificate }); cb({ engine, h3Server, certificate });
} }
function createHttpServer(port) {
const httpServer = createServer();
let retryCount = 0;
return new Promise((resolve, reject) => {
httpServer.listen(port, () => resolve(httpServer));
httpServer.on("error", (e) => {
if (e.code === "EADDRINUSE" && ++retryCount <= 3) {
console.warn("port already in use, retrying...");
setTimeout(() => {
httpServer.listen(port, () => resolve(httpServer));
}, 100);
}
reject(e);
});
});
}
function success(engine, h3server, done) { function success(engine, h3server, done) {
engine.close(); engine.close();
h3server.stopServer(); h3server.stopServer();
@@ -98,10 +118,9 @@ describe("WebTransport", () => {
{ {
transports: ["polling", "webtransport"], transports: ["polling", "webtransport"],
}, },
({ engine, h3Server, certificate }) => { async ({ engine, h3Server, certificate }) => {
const httpServer = createServer(); const httpServer = await createHttpServer(h3Server.port);
engine.attach(httpServer); engine.attach(httpServer);
httpServer.listen(h3Server.port);
const socket = createSocket(h3Server.port, certificate, { const socket = createSocket(h3Server.port, certificate, {
transports: ["polling", "webtransport"], transports: ["polling", "webtransport"],
@@ -120,10 +139,9 @@ describe("WebTransport", () => {
{ {
transports: ["polling", "websocket", "webtransport"], transports: ["polling", "websocket", "webtransport"],
}, },
({ engine, h3Server, certificate }) => { async ({ engine, h3Server, certificate }) => {
const httpServer = createServer(); const httpServer = await createHttpServer(h3Server.port);
engine.attach(httpServer); engine.attach(httpServer);
httpServer.listen(h3Server.port);
const socket = createSocket(h3Server.port, certificate, { const socket = createSocket(h3Server.port, certificate, {
transports: ["polling", "websocket", "webtransport"], transports: ["polling", "websocket", "webtransport"],

View File

@@ -32,7 +32,11 @@ export type RawData = any;
export interface Packet { export interface Packet {
type: PacketType; 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; data?: RawData;
} }

View File

@@ -13,7 +13,7 @@
"scripts": { "scripts": {
"compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh", "compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh",
"test": "npm run format:check && npm run compile && if test \"$BROWSERS\" = \"1\" ; then npm run test:browser; else npm run test:node; fi", "test": "npm run format:check && npm run compile && if test \"$BROWSERS\" = \"1\" ; then npm run test:browser; else npm run test:node; fi",
"test:node": "nyc mocha -r ts-node/register test/index.ts", "test:node": "nyc mocha --import=tsx test/index.ts",
"test:browser": "zuul test/index.ts --no-coverage", "test:browser": "zuul test/index.ts --no-coverage",
"format:check": "prettier --check 'lib/**/*.ts' 'test/**/*.ts'", "format:check": "prettier --check 'lib/**/*.ts' 'test/**/*.ts'",
"format:fix": "prettier --write 'lib/**/*.ts' 'test/**/*.ts'", "format:fix": "prettier --write 'lib/**/*.ts' 'test/**/*.ts'",

View File

@@ -1,7 +1,8 @@
# History # Changelog
| Version | Release date | | Version | Release date |
|------------------------------------------------------------------------------------------------------|----------------| |------------------------------------------------------------------------------------------------------|----------------|
| [6.6.5](#665-2025-12-22) | December 2025 |
| [6.6.4](#664-2025-01-28) | January 2025 | | [6.6.4](#664-2025-01-28) | January 2025 |
| [6.6.3](#663-2025-01-23) | January 2025 | | [6.6.3](#663-2025-01-23) | January 2025 |
| [6.6.2](#662-2024-10-09) | October 2024 | | [6.6.2](#662-2024-10-09) | October 2024 |
@@ -47,7 +48,16 @@
| [3.4.1](#341-2020-04-17) | April 2020 | | [3.4.1](#341-2020-04-17) | April 2020 |
# Release notes ## [6.6.5](https://github.com/socketio/socket.io/compare/engine.io@6.6.4...engine.io@6.6.5) (2025-12-22)
The `url.parse()` function is now deprecated and has been replaced by `new URL()` (see [e08293b](https://github.com/socketio/socket.io/commit/e08293bc3735de5b824b347383e86e0b8ab9fbd5)).
### Dependencies
- [`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))
## [6.6.4](https://github.com/socketio/socket.io/compare/engine.io@6.6.3...engine.io@6.6.4) (2025-01-28) ## [6.6.4](https://github.com/socketio/socket.io/compare/engine.io@6.6.3...engine.io@6.6.4) (2025-01-28)

View File

@@ -1,27 +1,36 @@
import { createServer } from "http"; import { createServer, Server as HttpServer } from "http";
import { Server, AttachOptions, ServerOptions } from "./server"; import { Server, AttachOptions, ServerOptions } from "./server";
import transports from "./transports/index"; import transports from "./transports/index";
import * as parser from "engine.io-parser"; import * as parser from "engine.io-parser";
export { Server, transports, listen, attach, 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 { uServer } from "./userver";
export { Socket } from "./socket"; export { Socket } from "./socket";
export { Transport } from "./transport"; export { Transport } from "./transport";
export const protocol = parser.protocol; 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 port
* @param {Function} callback * @param options
* @param {Object} options * @param listenCallback - callback for http.Server.listen()
* @return {Server} websocket.io server * @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) { if ("function" === typeof options) {
fn = options; listenCallback = options;
options = {}; options = {};
} }
@@ -34,7 +43,7 @@ function listen(port, options: AttachOptions & ServerOptions, fn) {
const engine = attach(server, options); const engine = attach(server, options);
engine.httpServer = server; engine.httpServer = server;
server.listen(port, fn); server.listen(port, listenCallback);
return engine; return engine;
} }
@@ -42,12 +51,15 @@ function listen(port, options: AttachOptions & ServerOptions, fn) {
/** /**
* Captures upgrade requests for a http.Server. * Captures upgrade requests for a http.Server.
* *
* @param {http.Server} server * @param server
* @param {Object} options * @param options
* @return {Server} engine server * @return engine.io server
*/ */
function attach(server, options: AttachOptions & ServerOptions) { function attach(
server: HttpServer,
options: AttachOptions & ServerOptions,
): Server {
const engine = new Server(options); const engine = new Server(options);
engine.attach(server, options); engine.attach(server, options);
return engine; return engine;

View File

@@ -59,8 +59,7 @@ const EMPTY_BUFFER = Buffer.concat([]);
* *
* @api private * @api private
*/ */
export function encodePacket (packet: any, supportsBinary?: any, utf8encode?: any, callback?: any) {
export function encodePacket (packet, supportsBinary, utf8encode, callback) {
if (typeof supportsBinary === 'function') { if (typeof supportsBinary === 'function') {
callback = supportsBinary; callback = supportsBinary;
supportsBinary = null; supportsBinary = null;
@@ -86,7 +85,7 @@ export function encodePacket (packet, supportsBinary, utf8encode, callback) {
} }
return callback('' + encoded); return callback('' + encoded);
}; }
/** /**
* Encode Buffer data * Encode Buffer data
@@ -120,16 +119,16 @@ export function encodeBase64Packet (packet, callback){
/** /**
* Decodes a packet. Data also available as an ArrayBuffer if requested. * 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 * @api private
*/ */
export function decodePacket (data, binaryType, utf8decode) { export function decodePacket (data: any, binaryType?: any, utf8decode?: any): any {
if (data === undefined) { if (data === undefined) {
return err; return err;
} }
var type; let type: string | number;
// String data // String data
if (typeof data === 'string') { if (typeof data === 'string') {
@@ -147,6 +146,7 @@ export function decodePacket (data, binaryType, utf8decode) {
} }
} }
// @ts-expect-error
if (Number(type) != type || !packetslist[type]) { if (Number(type) != type || !packetslist[type]) {
return err; return err;
} }
@@ -274,7 +274,7 @@ function map(ary, each, done) {
* @api public * @api public
*/ */
export function decodePayload (data, binaryType, callback) { export function decodePayload (data: any, binaryType?: any, callback?: any) {
if (typeof data !== 'string') { if (typeof data !== 'string') {
return decodePayloadAsBinary(data, binaryType, callback); return decodePayloadAsBinary(data, binaryType, callback);
} }

View File

@@ -1,12 +1,15 @@
import * as qs from "querystring";
import { parse } from "url";
import * as base64id from "base64id"; import * as base64id from "base64id";
import transports from "./transports"; import transports from "./transports";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { Socket } from "./socket"; import { Socket } from "./socket";
import debugModule from "debug"; import debugModule from "debug";
import { serialize } from "cookie"; 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 { import type {
IncomingMessage, IncomingMessage,
Server as HttpServer, Server as HttpServer,
@@ -16,14 +19,19 @@ import type { CorsOptions, CorsOptionsDelegate } from "cors";
import type { Duplex } from "stream"; import type { Duplex } from "stream";
import { WebTransport } from "./transports/webtransport"; import { WebTransport } from "./transports/webtransport";
import { createPacketDecoderStream } from "engine.io-parser"; import { createPacketDecoderStream } from "engine.io-parser";
import type { EngineRequest } from "./transport"; import type { EngineRequest, Transport } from "./transport";
import type { CookieSerializeOptions } from "./contrib/types.cookie"; import type { CookieSerializeOptions } from "./contrib/types.cookie";
const debug = debugModule("engine"); const debug = debugModule("engine");
const kResponseHeaders = Symbol("responseHeaders"); 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<string, unknown> & { name?: string; message?: string },
) => void;
export interface AttachOptions { export interface AttachOptions {
/** /**
@@ -90,7 +98,7 @@ export interface ServerOptions {
* *
* @default ["polling", "websocket"] * @default ["polling", "websocket"]
*/ */
transports?: Transport[]; transports?: TransportName[];
/** /**
* whether to allow transport upgrades * whether to allow transport upgrades
* @default true * @default true
@@ -100,7 +108,7 @@ export interface ServerOptions {
* parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable. * parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable.
* @default false * @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. * parameters of the http compression for the polling transports (see zlib api docs). Set to false to disable.
* @default true * @default true
@@ -149,7 +157,7 @@ type Middleware = (
next: (err?: any) => void, next: (err?: any) => void,
) => void; ) => void;
function parseSessionId(data: string) { function parseSessionId(data: string): string | undefined {
try { try {
const parsed = JSON.parse(data); const parsed = JSON.parse(data);
if (typeof parsed.sid === "string") { if (typeof parsed.sid === "string") {
@@ -224,7 +232,7 @@ export abstract class BaseServer extends EventEmitter {
this.init(); this.init();
} }
protected abstract init(); protected abstract init(): void;
/** /**
* Compute the pathname of the requests that are handled by the server * Compute the pathname of the requests that are handled by the server
@@ -244,10 +252,8 @@ export abstract class BaseServer extends EventEmitter {
/** /**
* Returns a list of available transports for upgrade given a certain transport. * 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 []; if (!this.opts.allowUpgrades) return [];
return transports[transport].upgradesTo || []; return transports[transport].upgradesTo || [];
} }
@@ -259,17 +265,18 @@ export abstract class BaseServer extends EventEmitter {
* @param upgrade - whether it's an upgrade request * @param upgrade - whether it's an upgrade request
* @param fn * @param fn
* @protected * @protected
* @return whether the request is valid
*/ */
protected verify( protected verify(
req: any, req: EngineRequest,
upgrade: boolean, upgrade: boolean,
fn: (errorCode?: number, errorContext?: any) => void, fn: ErrorCallback,
) { ): void | boolean {
// transport check // transport check
const transport = req._query.transport; const transport = req._query.transport;
// WebTransport does not go through the verify() method, see the onWebTransportSession() method // WebTransport does not go through the verify() method, see the onWebTransportSession() method
if ( if (
!~this.opts.transports.indexOf(transport) || !~this.opts.transports.indexOf(transport as TransportName) ||
transport === "webtransport" transport === "webtransport"
) { ) {
debug('unknown transport "%s"', transport); debug('unknown transport "%s"', transport);
@@ -408,7 +415,7 @@ export abstract class BaseServer extends EventEmitter {
* *
* @param {IncomingMessage} req - the request object * @param {IncomingMessage} req - the request object
*/ */
public generateId(req: IncomingMessage) { public generateId(req: IncomingMessage): string | PromiseLike<string> {
return base64id.generateId(); return base64id.generateId();
} }
@@ -422,9 +429,9 @@ export abstract class BaseServer extends EventEmitter {
* @protected * @protected
*/ */
protected async handshake( protected async handshake(
transportName: string, transportName: TransportName,
req: any, req: EngineRequest,
closeConnection: (errorCode?: number, errorContext?: any) => void, closeConnection: ErrorCallback,
) { ) {
const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
if (protocol === 3 && !this.opts.allowEIO3) { if (protocol === 3 && !this.opts.allowEIO3) {
@@ -600,7 +607,10 @@ export abstract class BaseServer extends EventEmitter {
} }
} }
protected abstract createTransport(transportName, req); protected abstract createTransport(
transportName: TransportName,
req: EngineRequest,
);
/** /**
* Protocol errors mappings. * Protocol errors mappings.
@@ -613,7 +623,7 @@ export abstract class BaseServer extends EventEmitter {
BAD_REQUEST: 3, BAD_REQUEST: 3,
FORBIDDEN: 4, FORBIDDEN: 4,
UNSUPPORTED_PROTOCOL_VERSION: 5, UNSUPPORTED_PROTOCOL_VERSION: 5,
}; } as const;
static errorMessages = { static errorMessages = {
0: "Transport unknown", 0: "Transport unknown",
@@ -622,7 +632,7 @@ export abstract class BaseServer extends EventEmitter {
3: "Bad request", 3: "Bad request",
4: "Forbidden", 4: "Forbidden",
5: "Unsupported protocol version", 5: "Unsupported protocol version",
}; } as const;
} }
/** /**
@@ -667,7 +677,7 @@ class WebSocketResponse {
*/ */
export class Server extends BaseServer { export class Server extends BaseServer {
public httpServer?: HttpServer; public httpServer?: HttpServer;
private ws: any; private ws: WsServer;
/** /**
* Initialize websocket server * Initialize websocket server
@@ -687,7 +697,7 @@ export class Server extends BaseServer {
}); });
if (typeof this.ws.on === "function") { 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) // 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 // we could also try to parse the array and then sync the values, but that will be error-prone
const additionalHeaders = req[kResponseHeaders] || {}; const additionalHeaders = req[kResponseHeaders] || {};
@@ -724,13 +734,16 @@ export class Server extends BaseServer {
private prepare(req: EngineRequest) { private prepare(req: EngineRequest) {
// try to leverage pre-existing `req._query` (e.g: from connect) // try to leverage pre-existing `req._query` (e.g: from connect)
if (!req._query) { if (!req._query) {
req._query = ( const url = new URL(req.url, "https://socket.io");
~req.url.indexOf("?") ? qs.parse(parse(req.url).query) : {} req._query = Object.fromEntries(url.searchParams.entries());
) as Record<string, string>;
} }
} }
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); return new transports[transportName](req);
} }
@@ -745,7 +758,7 @@ export class Server extends BaseServer {
this.prepare(req); this.prepare(req);
req.res = res; req.res = res;
const callback = (errorCode, errorContext) => { const callback: ErrorCallback = (errorCode, errorContext) => {
if (errorCode !== undefined) { if (errorCode !== undefined) {
this.emit("connection_error", { this.emit("connection_error", {
req, req,
@@ -763,7 +776,11 @@ export class Server extends BaseServer {
} else { } else {
const closeConnection = (errorCode, errorContext) => const closeConnection = (errorCode, errorContext) =>
abortRequest(res, errorCode, errorContext); abortRequest(res, errorCode, errorContext);
this.handshake(req._query.transport, req, closeConnection); this.handshake(
req._query.transport as TransportName,
req,
closeConnection,
);
} }
}; };
@@ -787,7 +804,7 @@ export class Server extends BaseServer {
this.prepare(req); this.prepare(req);
const res = new WebSocketResponse(req, socket); const res = new WebSocketResponse(req, socket);
const callback = (errorCode, errorContext) => { const callback: ErrorCallback = (errorCode, errorContext) => {
if (errorCode !== undefined) { if (errorCode !== undefined) {
this.emit("connection_error", { this.emit("connection_error", {
req, req,
@@ -823,11 +840,16 @@ export class Server extends BaseServer {
/** /**
* Called upon a ws.io connection. * Called upon a ws.io connection.
* * @param req
* @param {ws.Socket} websocket * @param socket
* @param websocket
* @private * @private
*/ */
private onWebSocket(req, socket, websocket) { private onWebSocket(
req: EngineRequest,
socket: Duplex,
websocket: WsWebSocket,
) {
websocket.on("error", onUpgradeError); websocket.on("error", onUpgradeError);
if ( if (
@@ -862,14 +884,22 @@ export class Server extends BaseServer {
// transport error handling takes over // transport error handling takes over
websocket.removeListener("error", onUpgradeError); 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; transport.perMessageDeflate = this.opts.perMessageDeflate;
client._maybeUpgrade(transport); client._maybeUpgrade(transport);
} }
} else { } else {
const closeConnection = (errorCode, errorContext) => const closeConnection = (errorCode, errorContext) =>
abortUpgrade(socket, errorCode, errorContext); abortUpgrade(socket, errorCode, errorContext);
this.handshake(req._query.transport, req, closeConnection); this.handshake(
req._query.transport as TransportName,
req,
closeConnection,
);
} }
function onUpgradeError() { function onUpgradeError() {
@@ -947,7 +977,11 @@ export class Server extends BaseServer {
* @private * @private
*/ */
function abortRequest(res, errorCode, errorContext) { function abortRequest(
res: ServerResponse,
errorCode: number,
errorContext?: { message?: string },
) {
const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400; const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400;
const message = const message =
errorContext && errorContext.message errorContext && errorContext.message
@@ -1030,7 +1064,7 @@ const validHdrChars = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // ... 255 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 += ""; val += "";
if (val.length < 1) return false; if (val.length < 1) return false;
if (!validHdrChars[val.charCodeAt(0)]) { if (!validHdrChars[val.charCodeAt(0)]) {

View File

@@ -5,6 +5,7 @@ import type { EngineRequest, Transport } from "./transport";
import type { BaseServer } from "./server"; import type { BaseServer } from "./server";
import { setTimeout, clearTimeout } from "timers"; import { setTimeout, clearTimeout } from "timers";
import type { Packet, PacketType, RawData } from "engine.io-parser"; import type { Packet, PacketType, RawData } from "engine.io-parser";
import type transports from "./transports";
const debug = debugModule("engine:socket"); const debug = debugModule("engine:socket");
@@ -537,9 +538,11 @@ export class Socket extends EventEmitter {
*/ */
private getAvailableUpgrades() { private getAvailableUpgrades() {
const availableUpgrades = []; 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) { 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) { if (this.server.opts.transports.indexOf(upg) !== -1) {
availableUpgrades.push(upg); availableUpgrades.push(upg);
} }

View File

@@ -4,6 +4,7 @@ import * as parser_v3 from "./parser-v3/index";
import debugModule from "debug"; import debugModule from "debug";
import type { IncomingMessage, ServerResponse } from "http"; import type { IncomingMessage, ServerResponse } from "http";
import { Packet, RawData } from "engine.io-parser"; import { Packet, RawData } from "engine.io-parser";
import type { WebSocket } from "ws";
const debug = debugModule("engine:transport"); const debug = debugModule("engine:transport");
@@ -15,7 +16,11 @@ export type EngineRequest = IncomingMessage & {
_query: Record<string, string>; _query: Record<string, string>;
res?: ServerResponse; res?: ServerResponse;
cleanup?: Function; cleanup?: Function;
websocket?: any; websocket?: WebSocket & {
_socket?: {
remoteAddress: string;
};
};
}; };
export abstract class Transport extends EventEmitter { export abstract class Transport extends EventEmitter {
@@ -37,7 +42,7 @@ export abstract class Transport extends EventEmitter {
* *
* @see https://github.com/socketio/engine.io-protocol * @see https://github.com/socketio/engine.io-protocol
*/ */
public protocol: number; public protocol: 3 | 4;
/** /**
* The current state of the transport. * 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}. * The parser to use (depends on the revision of the {@link Transport#protocol}.
* @protected * @protected
*/ */
protected parser: any; protected parser: typeof parser_v4 | typeof parser_v3;
/** /**
* Whether the transport supports binary payloads (else it will be base64-encoded) * Whether the transport supports binary payloads (else it will be base64-encoded)
* @protected * @protected
@@ -74,6 +79,11 @@ export abstract class Transport extends EventEmitter {
this._readyState = state; this._readyState = state;
} }
/**
* The list of transports this transport can be upgraded to.
*/
static upgradesTo: string[] = [];
/** /**
* Transport constructor. * Transport constructor.
* *
@@ -148,7 +158,7 @@ export abstract class Transport extends EventEmitter {
/** /**
* Called with the encoded packet data. * Called with the encoded packet data.
* *
* @param {String} data * @param data
* @protected * @protected
*/ */
protected onData(data: RawData) { protected onData(data: RawData) {

View File

@@ -3,6 +3,8 @@ import { createGzip, createDeflate } from "zlib";
import * as accepts from "accepts"; import * as accepts from "accepts";
import debugModule from "debug"; import debugModule from "debug";
import { HttpRequest, HttpResponse } from "uWebSockets.js"; 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"); const debug = debugModule("engine:polling");
@@ -228,9 +230,9 @@ export class Polling extends Transport {
}; };
if (this.protocol === 3) { if (this.protocol === 3) {
this.parser.decodePayload(data, callback); (this.parser as typeof parser_v3).decodePayload(data, callback);
} else { } 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; this.shouldClose = null;
} }
const doWrite = (data) => { const doWrite = (data: string) => {
const compress = packets.some((packet) => { const compress = packets.some((packet) => {
return packet.options && packet.options.compress; return packet.options && packet.options.compress;
}); });
@@ -271,9 +273,13 @@ export class Polling extends Transport {
}; };
if (this.protocol === 3) { if (this.protocol === 3) {
this.parser.encodePayload(packets, this.supportsBinary, doWrite); (this.parser as typeof parser_v3).encodePayload(
packets,
this.supportsBinary,
doWrite,
);
} else { } else {
this.parser.encodePayload(packets, doWrite); (this.parser as typeof parser_v4).encodePayload(packets, doWrite);
} }
} }

View File

@@ -2,9 +2,10 @@ import { Polling as XHR } from "./polling";
import { JSONP } from "./polling-jsonp"; import { JSONP } from "./polling-jsonp";
import { WebSocket } from "./websocket"; import { WebSocket } from "./websocket";
import { WebTransport } from "./webtransport"; import { WebTransport } from "./webtransport";
import type { EngineRequest } from "../transport";
export default { export default {
polling: polling, polling,
websocket: WebSocket, websocket: WebSocket,
webtransport: WebTransport, webtransport: WebTransport,
}; };
@@ -12,8 +13,7 @@ export default {
/** /**
* Polling polymorphic constructor. * Polling polymorphic constructor.
*/ */
function polling(req: EngineRequest) {
function polling(req) {
if ("string" === typeof req._query.j) { if ("string" === typeof req._query.j) {
return new JSONP(req); return new JSONP(req);
} else { } else {

View File

@@ -4,6 +4,8 @@ import * as accepts from "accepts";
import debugModule from "debug"; import debugModule from "debug";
import type { IncomingMessage, ServerResponse } from "http"; import type { IncomingMessage, ServerResponse } from "http";
import type { Packet, RawData } from "engine.io-parser"; 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"); const debug = debugModule("engine:polling");
@@ -196,9 +198,9 @@ export class Polling extends Transport {
}; };
if (this.protocol === 3) { if (this.protocol === 3) {
this.parser.decodePayload(data, callback); (this.parser as typeof parser_v3).decodePayload(data, callback);
} else { } 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; this.shouldClose = null;
} }
const doWrite = (data) => { const doWrite = (data: string) => {
const compress = packets.some((packet) => { const compress = packets.some((packet) => {
return packet.options && packet.options.compress; return packet.options && packet.options.compress;
}); });
@@ -233,9 +235,13 @@ export class Polling extends Transport {
}; };
if (this.protocol === 3) { if (this.protocol === 3) {
this.parser.encodePayload(packets, this.supportsBinary, doWrite); (this.parser as typeof parser_v3).encodePayload(
packets,
this.supportsBinary,
doWrite,
);
} else { } else {
this.parser.encodePayload(packets, doWrite); (this.parser as typeof parser_v4).encodePayload(packets, doWrite);
} }
} }

View File

@@ -1,12 +1,13 @@
import { EngineRequest, Transport } from "../transport"; import { EngineRequest, Transport } from "../transport";
import debugModule from "debug"; import debugModule from "debug";
import type { Packet, RawData } from "engine.io-parser"; import type { Packet, RawData } from "engine.io-parser";
import type { PerMessageDeflateOptions, WebSocket as WsWebSocket } from "ws";
const debug = debugModule("engine:ws"); const debug = debugModule("engine:ws");
export class WebSocket extends Transport { export class WebSocket extends Transport {
protected perMessageDeflate: any; perMessageDeflate?: boolean | PerMessageDeflateOptions;
private socket: any; private socket: WsWebSocket;
/** /**
* WebSocket transport * WebSocket transport
@@ -51,8 +52,8 @@ export class WebSocket extends Transport {
if (this._canSendPreEncodedFrame(packet)) { if (this._canSendPreEncodedFrame(packet)) {
// the WebSocket frame was computed with WebSocket.Sender.frame() // the WebSocket frame was computed with WebSocket.Sender.frame()
// see https://github.com/websockets/ws/issues/617#issuecomment-283002469 // see https://github.com/websockets/ws/issues/617#issuecomment-283002469
// @ts-expect-error use of untyped member
this.socket._sender.sendFrame( this.socket._sender.sendFrame(
// @ts-ignore
packet.options.wsPreEncodedFrame, packet.options.wsPreEncodedFrame,
isLast ? this._onSentLast : this._onSent, isLast ? this._onSentLast : this._onSent,
); );
@@ -74,8 +75,8 @@ export class WebSocket extends Transport {
private _canSendPreEncodedFrame(packet: Packet) { private _canSendPreEncodedFrame(packet: Packet) {
return ( return (
!this.perMessageDeflate && !this.perMessageDeflate &&
// @ts-expect-error use of untyped member
typeof this.socket?._sender?.sendFrame === "function" && typeof this.socket?._sender?.sendFrame === "function" &&
// @ts-ignore
packet.options?.wsPreEncodedFrame !== undefined packet.options?.wsPreEncodedFrame !== undefined
); );
} }

View File

@@ -2,6 +2,7 @@ import debugModule from "debug";
import { AttachOptions, BaseServer, Server } from "./server"; import { AttachOptions, BaseServer, Server } from "./server";
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
import transports from "./transports-uws"; import transports from "./transports-uws";
import { EngineRequest } from "./transport";
const debug = debugModule("engine:uws"); const debug = debugModule("engine:uws");
@@ -36,7 +37,7 @@ export class uServer extends BaseServer {
* *
* @private * @private
*/ */
private prepare(req, res: HttpResponse) { private prepare(req: HttpRequest & EngineRequest, res: HttpResponse) {
req.method = req.getMethod().toUpperCase(); req.method = req.getMethod().toUpperCase();
req.url = req.getUrl(); req.url = req.getUrl();
@@ -48,6 +49,7 @@ export class uServer extends BaseServer {
req.headers[key] = value; req.headers[key] = value;
}); });
// @ts-expect-error
req.connection = { req.connection = {
remoteAddress: Buffer.from(res.getRemoteAddressAsText()).toString(), 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); return new transports[transportName](req);
} }
@@ -123,7 +125,7 @@ export class uServer extends BaseServer {
req: HttpRequest & { res: any; _query: any }, req: HttpRequest & { res: any; _query: any },
) { ) {
debug('handling "%s" http request "%s"', req.getMethod(), req.getUrl()); 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; req.res = res;
@@ -146,7 +148,11 @@ export class uServer extends BaseServer {
} else { } else {
const closeConnection = (errorCode, errorContext) => const closeConnection = (errorCode, errorContext) =>
this.abortRequest(res, 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) { if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" }); callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else { } 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"); debug("on upgrade");
this.prepare(req, res); this.prepare(req as unknown as HttpRequest & EngineRequest, res);
req.res = res; req.res = res;
@@ -198,13 +208,16 @@ export class uServer extends BaseServer {
return res.close(); return res.close();
} else { } else {
debug("upgrading existing transport"); 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); client._maybeUpgrade(transport);
} }
} else { } else {
transport = await this.handshake( transport = await this.handshake(
req._query.transport, req._query.transport,
req, req as unknown as EngineRequest,
(errorCode, errorContext) => (errorCode, errorContext) =>
this.abortRequest(res, errorCode, errorContext), this.abortRequest(res, errorCode, errorContext),
); );
@@ -231,15 +244,15 @@ export class uServer extends BaseServer {
if (err) { if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" }); callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else { } else {
this.verify(req, true, callback); this.verify(req as unknown as EngineRequest, true, callback);
} }
}); });
} }
private abortRequest( private abortRequest(
res: HttpResponse | ResponseWrapper, res: HttpResponse | ResponseWrapper,
errorCode, errorCode: number,
errorContext, errorContext?: { message?: string },
) { ) {
const statusCode = const statusCode =
errorCode === Server.errors.FORBIDDEN errorCode === Server.errors.FORBIDDEN

View File

@@ -1,6 +1,6 @@
{ {
"name": "engine.io", "name": "engine.io",
"version": "6.6.4", "version": "6.6.5",
"description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server", "description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server",
"type": "commonjs", "type": "commonjs",
"main": "./build/engine.io.js", "main": "./build/engine.io.js",
@@ -37,9 +37,9 @@
"base64id": "2.0.0", "base64id": "2.0.0",
"cookie": "~0.7.2", "cookie": "~0.7.2",
"cors": "~2.8.5", "cors": "~2.8.5",
"debug": "~4.3.1", "debug": "~4.4.1",
"engine.io-parser": "~5.2.1", "engine.io-parser": "~5.2.1",
"ws": "~8.17.1" "ws": "~8.18.3"
}, },
"scripts": { "scripts": {
"compile": "rimraf ./build && tsc", "compile": "rimraf ./build && tsc",

View File

@@ -1,5 +1,6 @@
if (process.env.EIO_CLIENT === "3" && process.versions.node.startsWith("22")) { if (process.env.EIO_CLIENT === "3") {
// FIXME WebSocket error with engine.io-client@3 // we need the WebSocket object provided by the "ws" library to test the SSL certs and HTTP headers so we hide the now built-in WebSocket constructor
// ref: https://nodejs.org/api/globals.html#class-websocket
global.WebSocket = null; global.WebSocket = null;
} }

View File

@@ -1,33 +1,19 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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 2ZzA88NgFQXi
4idMFJo0xYmoLTJQeD7A59mhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQCS
gT1d5zF47DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQBRLzphOdJO
ekU+iwFLGXJiLfUQi3EORaA3mBtozvyRRUyxu0FqWdGGINcyq7xaZxkQ0P+eBMDv
V3F7FvidhaVe6TR7VJB2gyaWeLd1BSOi5gGBk5kuuqV3VbusNzY+WhJip98HEK+y
XP+Lt/LXJpPwOORF9TiR6IBFQn3fBVhPjsqQRzT468QuZ5DCGQ5UW+wWl43I8OxS
PDiUmHTlwLnLqqVFgSE+VnX4vSUZD8kDf0kkxssg1r56IzneSlRBegSVXIuRCbRf
QmWaxz+D6ffM1eNiE3nQxsgJy3dPL1Lfsaidgz39yAC099pjLyVH23cfmFmT/l5b
OdhRE5D75rL8eXAiI2/voz1M+v7XznHjZEhcVUlFsBXsk3zHb2vQQZRNPLnybTb8
biFpReSIWdpno+F5IrT7z0L8JI3LU0leEFV+Rf525Q+78Rffohxd51fUj0vLKoy9
un0IEkOcaJhHTPc2dMXb2qGcV4MaUEUsERrnanGZmdNd1aD3YAG3C+nJ8gxrEGHO
veD6Xbyf1K8e7H2sqhGCm8eyHgCFGQiul6yQ41ZwjKgoSCJvOJaYUFE18k0S9k/I
rWYvYWRYbDj4GYx+6LUTfyo30KK5jl4KAil92LrGlIfWK4IjUJyPlOJkb6gXkj0l
lfbUHTmnKthFwJS0958xBq7UM7+RzyFIOg==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@@ -1,51 +1,28 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
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 Ek+aWNHKp5nqpuY6HWUacI96
sqde/M240Pd2ltKdWxM+awyowiNPkMCHira0RrXdBT0vDbNBy3lvMtm1UpYUoCl0 -----END PRIVATE KEY-----
MMrpk2G7zeuqDwKCAQEAxSzwbKuK4guIn/FeoLHiMEazhpHub1OY2yBwnE0j44LQ
wIo9G70GTnBaH9gJDj7wOL1xwIdRdNoeWdEQ4wXH/rEH93JrZ1y9jv7LcIegJqQh
C0U4RPE1JC0pic6f1Gw58p0q85Rkkee8oaJfeLZ4eJqy52XJyKRzktiQ1mN5ABYU
gwS7GXee/tcWobWqN2Sq/4TcW/nysxQ5dsKKJPide0zKeNwhxPlahBupwjlsuoa2
wAsUeXttfY17R/vS0KXbxSzoLII/ClrT2n0OoTxmUK7ht4OuBXgg6ZkjIBQ0ki/n
CZTvClYziLk5NkGR2tw53I/zlXslXKbuTX4ByUSZ8QKCAQEAmLCPe2nF1fSfqQP7
+ghm989ileZlWT2Zy5047IbPRYibxnKbk+elOB2PD5y8YxVJXEtYDOj8BH5KW1dD
X+MAUyG/pCZ7PYRGLkm6OWhGBsbb5lQij7sk4jLlArMr1mHx8A9934RUkoIzSWkq
zZNXcfyYdFETIuEM5i9AZA1EJ48tlOxUTVDnoH+NJWNHVEVPxj9LZbJ9MT0c+nL+
5MjmEQX3vv4jZWz/3MfTwZj+iuqvcymKua3v0+LcDo8tQKDaCRzTdlR5sB+2qhWr
h7FEod5Dk5eFSl6dZXZCfYKJSiY5Jsg+4Q8prAMqN+ajuJ9qNbii+nOrovghMHXe
TCBx5QKCAQBas2BpbMO3VbzkbkCsRQeaU3uTxJ9c4KSo8BQ9IhMHPg7O8whHMT2s
aWxbx6HqxrL0NtkTymuDCC77+/r7o5YrJ75VanHTm0qrc7ObsRfPjqKQr6fBtv9O
A+Reuwi0y5AgdYHjiHh20ZXo+GtYeP+T4v23ChC3Vka/3xVJOXrYuk93MX7rqSYf
bku/2XRShOFQJwrC2Ih3Li983OJ1PVQb+ugMjp6OIHIt4RfG+2lzqDJ6xt4FP+zO
231BUKraReGBozWt+8AKAFwB3pMTQliCdt/n7g/n/imNq18IC6NfN9/cfYE0TRDp
rOKPfbwdZD7Nof5X3c0DANsQFI23yvHRAoIBAQCIdbiXKkrgv0fbSNaTpP8XNROV
M+BROigjiQnvAILKCenp1MSKcnIfL24ZfWzRhC5s0WtbsABswK+6pP4lXiXZqvyi
5SJ3/omT13CyNjTDw1LlpSE33FHJKAIpYfP8QVTkOG/8GclR/JUFXicujess2fhw
9F4sUA9txqiyWzhHauU0R4V78jsq1V4VEtGhpapzVNtvpWeCEB33WUiU/EwdLsdz
RnKkvCA0WAFuwhH3bELyFj5sVy8L6kS8QQt6w4T2L/gNkwO01RmCNXSQbYkdYA1Y
9t8FefPnf1Ry86PLdKyg8/LNLS023MpDgt7eCa2/ysnbhDZ5RepZJymcy0rP
-----END RSA PRIVATE KEY-----

View File

@@ -1,54 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,47A0291BAB679B36
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
-----END RSA PRIVATE KEY-----

View File

@@ -1,22 +1,17 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDtTCCAZ0CFCt+tjtA9647yZp8eNZurQtNp4x+MA0GCSqGSIb3DQEBDQUAMEUx MIICqjCCAZICFGMSh2AB1eNNHoG4f93Im5mtC26pMA0GCSqGSIb3DQEBCwUAMBIx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRl EDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjUxMjE1MDc0NTA3WhcNMjYxMjE1MDc0NTA3
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIxMTE4MjEyMzU4WhcNMzIxMTE1MjEy WjARMQ8wDQYDVQQDDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
MzU4WjBtMQswCQYDVQQGEwJGSTETMBEGA1UECBMKU29tZS1TdGF0ZTERMA8GA1UE AoIBAQC3qP7P4axurpA2aseTbGNFPav21BJAqMPazSbkrpWHtRDlKeY62VJ1ER6l
BxMISGVsc2lua2kxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDET k2Fw1sMQRnyEQfTWNW2xi/Buv9nbJCKLuoPDQLFpHKkwBc/gu4N6O8zUcdhmCBQx
MBEGA1UEAxMKRm9vIENsaWVudDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA RbcNB8BwWIzLvMghIqld8GZT0SiWdfd/OP+1rZVCW5WWqtV1R1O202JKH6yE/CRQ
+ukZ/D/DvjCtd7D8SCBDB1w0WYXkS6hjZTbF82gS1pykpVflsYtTnQoprQIeGQFU t73P3dbfuXBDDs4kj5CM1xO6sGsd2Ev9iHPrLNfr+3mOZnGsdIxJ1oMkZwSmVCqc
bdSZCvZkBABncQvE0cAzEtNvhFCtR9c74e+xJOGXB1vKeQlRJudxU5jvo4S/uwUG YJ8aGq2gtbK5mZJdqjHG9Kyd4LTvcEIS5WEmVkEg9GbZkrYw5UpdyAISL2Kk0X6j
F1tcov11EQb9sZt0PeSO/PeWoKazBSPB4jrvx3Yv4g0CAwEAATANBgkqhkiG9w0B pZcssAoClLndq1IOxb/pYUm603jJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAER3
AQ0FAAOCAgEAMgC3j2XJ5IPB1raLrnJh8vTncnqMe6OmXpaxk0ZYb42Y66BJKlaE QEAHfCwWE2yhCYU4gYyy8bjQgR2lf1q1g0Pw+z+0e62oyoAFKq5bK7Jqu40Z87dc
UpLmLYIiICmuH6R4lc00W5nexPyCT4g+1CUs3PhOJGEwWOBodv6dFJ4ayGWln1aD DWT3WvZnxflpYSW29WItJ8pJhQt3g6N+AfZADHzplVfYsHyRrn82Nd6knxRDh+Yk
QX+W+PRuJAazd7wruVnPxVoEspVO+hcr5byX0F3Auqd9jdQZwFXsWvAo7tZxUnvC l0PVTzvNdsu1+QE5sH92Hl4Ze4VUJjAlpJzEzhBkhn2Uo19AhJ5FSpI1q8KEAUsk
gdjnHt5QgMxqeqzZPTw7dreMsIjN6NrUPWaa26VCvLH0Nv+Jgs+RSVwBKp8tO3e+ fUpane+sPkp5+d/nhIdLoBERFwEgu0WIChlPrV7ahlbFUde0sdqq1H0ZxBu4yABk
763bi8Htpzt4YfAB7EuRykGlAI42C5ZDzcsq30NpSGgOwveHnlvdl6KhC0QaK71h tAYqgR6TRqbufvkXvxBmCFp3uq11kFLmeNu8VtaZDuaMwJ+9YT5SG8mbhFHwVz2S
QmXwBmEUNX1f+XRnvk+fNb1acfddLLYoPP0zS1BEYOOs7KkyScagsUMsnUSOfv3d I3jNu1mm750TEWWMhPk=
+etklFvaXFD3+b/KwljH3WH1dG4ro3J6GHXX05ncDydDDksYi6aC3wpPZYY7eMFx
RWSxMZHX/bD1YH80a2+jBoskTqz3ZFkkGySMfUcpDCUwQuiwjhLp4sew9RDRB/lv
kJezNSoYgnT44CT+IPoPEL1m5Evkm3C7fVzvnldO3TsWmOoza99xrQ+9gtzlWxgb
Av6jNbnGG1HgDYcvxpRMKWe+6fUAHCcP0PuO+2rcygemNtEKzfMY6Py66w5L9/WW
t0UJWU1rR+kLDS3qLfQqvnbvUMroZ9zxE9CJq6+aKEQEpc79lfiv464=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@@ -1,12 +1,15 @@
-----BEGIN CERTIFICATE REQUEST----- -----BEGIN CERTIFICATE REQUEST-----
MIIBrTCCARYCAQAwbTELMAkGA1UEBhMCRkkxEzARBgNVBAgTClNvbWUtU3RhdGUx MIICVjCCAT4CAQAwETEPMA0GA1UEAwwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEF
ETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0 AAOCAQ8AMIIBCgKCAQEAt6j+z+Gsbq6QNmrHk2xjRT2r9tQSQKjD2s0m5K6Vh7UQ
eSBMdGQxEzARBgNVBAMTCkZvbyBDbGllbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0A 5SnmOtlSdREepZNhcNbDEEZ8hEH01jVtsYvwbr/Z2yQii7qDw0CxaRypMAXP4LuD
MIGJAoGBAPrpGfw/w74wrXew/EggQwdcNFmF5EuoY2U2xfNoEtacpKVX5bGLU50K ejvM1HHYZggUMUW3DQfAcFiMy7zIISKpXfBmU9EolnX3fzj/ta2VQluVlqrVdUdT
Ka0CHhkBVG3UmQr2ZAQAZ3ELxNHAMxLTb4RQrUfXO+HvsSThlwdbynkJUSbncVOY ttNiSh+shPwkULe9z93W37lwQw7OJI+QjNcTurBrHdhL/Yhz6yzX6/t5jmZxrHSM
76OEv7sFBhdbXKL9dREG/bGbdD3kjvz3lqCmswUjweI678d2L+INAgMBAAGgADAN SdaDJGcEplQqnGCfGhqtoLWyuZmSXaoxxvSsneC073BCEuVhJlZBIPRm2ZK2MOVK
BgkqhkiG9w0BAQUFAAOBgQDKGUqjkUxGOisFN70X7ZOW7H99veR9QlixKl5e0W+7 XcgCEi9ipNF+o6WXLLAKApS53atSDsW/6WFJutN4yQIDAQABoAAwDQYJKoZIhvcN
UtJ+GUtH2WQEb4F72+ruHrdDWQI1VaH9hPOvTRCjlgXiT0RHXpGPbJK/Nc+Eq5dm AQELBQADggEBAK/qIcDwg0kFcriPBU9+LGbpedbtw0naT3PVXfYYq3TzjBtPW+4c
kuk/tQeXv6+S1fgYOm0w09rE7pBjQtuAybB55lGZ7k84UE2xTc97Ru14nYFCsZ4z f5QmAhrIMkF0xQ1YdDsx9U/jQ93xL8OCo8+pbiQOYS0uVTqi/7UL9ggkD0aCGCZs
RA== j1FEWbumouW9ZuQg6gsZ9XoZmXPTKZivIpalOTV460vQvhM4Fspx0i7LowiS9ijH
BGG8QEr1ZihhXQloJSnjwVMY8qqVwqvJDidkN7eSjZTL2yOBzgiigpk8IMe1bNrb
dIoJVEjzFooRa08MPt+BZJ7xiuCyKZe6aVmV0XZOcnHChSc8jmSRXZdNpikGdQRX
1BamYMxhR2x8QYX0DiJYZwpgUaG4EOHsqb8=
-----END CERTIFICATE REQUEST----- -----END CERTIFICATE REQUEST-----

View File

@@ -1,15 +1,28 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIICXQIBAAKBgQD66Rn8P8O+MK13sPxIIEMHXDRZheRLqGNlNsXzaBLWnKSlV+Wx MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3qP7P4axurpA2
i1OdCimtAh4ZAVRt1JkK9mQEAGdxC8TRwDMS02+EUK1H1zvh77Ek4ZcHW8p5CVEm aseTbGNFPav21BJAqMPazSbkrpWHtRDlKeY62VJ1ER6lk2Fw1sMQRnyEQfTWNW2x
53FTmO+jhL+7BQYXW1yi/XURBv2xm3Q95I7895agprMFI8HiOu/Hdi/iDQIDAQAB i/Buv9nbJCKLuoPDQLFpHKkwBc/gu4N6O8zUcdhmCBQxRbcNB8BwWIzLvMghIqld
AoGBAOFHTXd4YO1wky82Dy1LGiOPm8kNOC7d33BOv2iN9uwN9J4nzymbqNUE/OpD 8GZT0SiWdfd/OP+1rZVCW5WWqtV1R1O202JKH6yE/CRQt73P3dbfuXBDDs4kj5CM
TnaxBPcfvNFk6+PT4QxUvsB8ytzDMZ3YC4xyJf5GPP/hfzyWCRjB557WZl1cx7nC 1xO6sGsd2Ev9iHPrLNfr+3mOZnGsdIxJ1oMkZwSmVCqcYJ8aGq2gtbK5mZJdqjHG
2gA93PBZE7WT1SySXmjsiC7o/2T/0cUaawXOBczHP8oXoEkBAkEA/c1MHs13ojxh 9Kyd4LTvcEIS5WEmVkEg9GbZkrYw5UpdyAISL2Kk0X6jpZcssAoClLndq1IOxb/p
oOj/ibCpYpd2Zv5Hrc5tsh+otDdIrb79IAHnNw7WhMkLs6cLk1MY6jLeCvQtjlUY YUm603jJAgMBAAECggEAHlPTr/QuCyDcQ4AicJ+nTNnAOcQPN4omvUy/LWf+3Sfz
H5C/6Ez84QJBAP0VZMgWPw3FVNXPrj833OA6XjyWO+TADpnlrahuDQqWnR3C29Uc IERo9jLIwPgQvXq2znFISLm6+gQCMBUmhfj7vO4FRYCUC8rC000tfpPzBDERgKS2
Iq/ApVX2pt2cNIZpiuJ4BYNc44cHjvu6vq0CQQChan1cJc9NhluNLELBfnLsOmpa M0sIqdQazc5rty8x6P7ssiCNL19/FKmqmg6GdzTEpQPZ2LJK129QhGKnMvgGw5S5
bKSH3P8VR19TZsm5fvub7Lnx4WT7xKXFl5scEsCIyts/WjbTDDmwca4r/zLhAkB4 TA1UUfiF3JoJLN9YY/EAvrWx6uML4JCsjkjzwG2rXG9S9IOIuULLYyK+d+383GUI
wkeHbY4CnSDgsKr9AUPEPjWPBURo3vdYmY4mKvTQE5O+iqboZfdrEyoQ/ZMbdRhe c/okVKILR8pUpFr+y5oor7Dcxa2ApkcRzTvFXvLoMyD7SHkmVLB6OdvgMvK+rVFH
9mdNrmU7DAyI9qNUHAQ1AkAlq/vdkrcq5SRR9uti/1M0/Jaw7l3JutBaW93kdvXx eCqlxiKXAzy9FG1L/EL5pYJwUSKp4itv//WPbvAkYQKBgQC7raVpaYoQ9i+QiIR5
BX568ezO1PQtXwVSv+uJEkDoST1bkvhqt7hlMu/RkmfG XQijEnZvPdJShqWtvLF65P8WQYdMvQFrO7HM9lSILNzL2291TssWBr5p9MuwbKmc
-----END RSA PRIVATE KEY----- eKisYwzJWwyDsJiShYBBlAKZOgZ2l8U6m8SGgYUU7/OcVekwrIdm87xi7wsd52aW
ZhifzL7E/Zc5pEFdGKmYkRWoTQKBgQD6hOITk8dV461zrwS7Ev65psmWyx2SGUmq
EIBi0fgE5wov2CXT6jR8mjJgvDjefG4lw92rT8CLsyaguoi2fjktbtOfXLMKVCF4
K2r9KlD/44+WLKBYLKNCffvI+ZcdQqTvUJfZUH7k44fTP25ByqMyj6FNsC0xhL5W
h1k57W4QbQKBgQCO9LYKlVmVkfCpJ1PBHhx2GpocIfsS4X1R2WlXMxca3M8ypMVv
6QiGFxxMnd+RaJR9xobrQDfKfayptht8FGxm4wvaVIAzz1BoS0hmq1dPX+OMcg7J
ld0cl4vHZv8pFkeJy+FXilD1CyBsptZ9uUcIcezeIEwQnA0t3JtSmzDkAQKBgE63
zJYcPGmXphwQparm2BWb/AIfBsaunYALuVvT4FwiAQhxoclJ8X4psaTF4BTBApdp
SVEYOUdsXrnogybc2LiW93Y+2Z9oOjAZbH/qeRM+/RJSKXwrYo11KtXG3535H/x2
1ZAahBUGDdrqNooD024Cxcu0jS7fKDPmSbozCf+tAoGAZ3NwWzj3LpArnfOrDr7Z
rXiUvCmSjPUBF99vN080rU8fBILdQTdz5DdE3K0xWDEiwAk1NZbuwy4Fx02aHG9f
5CkmGEHOsLGT6n6BHrjuSCtvbNOAGLhSBbBJJkFOf3ktJXU64P38M7OP0j11Tzkv
VNurVfn19LNgoLisW5c9tEs=
-----END PRIVATE KEY-----

View File

@@ -1,18 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,081B123E7DBC9687
c8gE0LQMMtV7GhUcP7GHBotlNNAsbyG/gERB//BFlvRPjs43ALvm5xMGUkcsZkw9
H6ljvvgyRkud06x1mGxqlZ4fLlty0/mdXf6791R9gA/9bKLakHGlwnoVUrEPIeh3
Iu/3IF0Uz8o3uljSp9eehR8sW5V4o+Z1MszQ+GkcpCyYnDPykyN52QLTM3RcUJ0v
2iJrgxYEAyMA14iIpYM8IsFDYIBvwVfQxyBZnQ+8dDQgVOokz56g+/9emgOsVjWj
bdUKZ7+95RLxCq0hjJd8GnoDn7qtxIPOGVqYcdEwwNyzO43FnpjOFhNDWwsrNrgf
vUHKhZRHB99CkuCvGg4fjVbFd/1/n3eE5VmiQuqoHzPnXH3RDIx2rZSjFo/NkzYx
4XzZ4YOe6vOP7kR18CL+bl32WneMELoh+TPLmsSC3rIP48M8+oQINSZaBrl59Ocw
NW36xgDNSQoia2splVNo51vtZomq1Hb3co6hD43D4xnrc6Aqucm3BsW6UCc/PVKv
HAXLCb+3awIy5NJSYb0qRETE2rKB6LjmKfILtOrto6QYSlkJmQUxqoPXgRAWWNlw
1Ngws84+6UjmGWDBlpZHn0hcO/B7KJAAS/xNSFYDoSu6dAbabxTI/dZCWhw1aN5c
QeYPihCi66F6Vuq/QT89dHtZE4IMPH7R95Yp18tCcyVGxaEiphw3HJYepMxoJesQ
YH8tWQwvD5LaADzNJIKBxMjCOK23GuXWQLJJRf4QWiXaQar69qXULxBT3iqBp/rQ
VKsQByBwJlEo/YSEFjhhMgo0zSbqIRAY1XCDo+dgB2IB1KPAobhSCQ==
-----END RSA PRIVATE KEY-----

Binary file not shown.

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Generate CA private key and certificate
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 365 -out ca.crt -subj "/CN=Test CA"
# Generate server key and certificate request
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=localhost"
# Generate client key and certificate request
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=client"
# Sign server certificate with CA
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
# Sign client certificate with CA
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365
# Generate client PFX files
openssl pkcs12 -export -passout pass: -out client.pfx -inkey client.key -in client.crt -certfile ca.crt

View File

@@ -1,22 +1,17 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDtDCCAZwCFCt+tjtA9647yZp8eNZurQtNp4x/MA0GCSqGSIb3DQEBDQUAMEUx MIICrTCCAZUCFGdEcsibkaFr2RamsAlBpOyqm58fMA0GCSqGSIb3DQEBCwUAMBIx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRl EDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjUxMjE1MDc0NTA3WhcNMjYxMjE1MDc0NTA3
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIxMTE4MjEyNDExWhcNMzIxMTE1MjEy WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
NDExWjBsMQswCQYDVQQGEwJGSTETMBEGA1UECBMKU29tZS1TdGF0ZTERMA8GA1UE ggEKAoIBAQDJw97tTsSTtZrOVjd89NKW548SyaB14Ghuqpfc3KxGa5OMuaXQU9aY
BxMISGVsc2lua2kxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDES nnoUCEU/YlS76t+ShZsQB9XR5UGy7T6ACJodm2tLsMmaNt3OX8JaXUeIY+UB9qPo
MBAGA1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA aG8dLBenHays+dud2O2QUF7QhvRvVO98NrGpVaYVpes5lEQhvzKjaiK+Nc51gndR
QLNPDILqMIulvPga6ogci69xYaSp50U7Tv8Hww3m4D9w2ubyGqR7dDW20iGJI7tT daAmvCPWINIBegUwRbbsAgNQaW8UEdxYWhVF8MDa//JPR7rTlHSFq6Or8SIvR1D/
Ncv/UtLqgknEiCBXkq/MDr5gdXNI0V/j+qAOqfJSVxvxxHxLrt7ivjpZHbO7B3K7 xAtvEXyAqPlkedIjh6Or2YCEIR2Lx+9Ky0Yti5vmh3PqZG6g7lMDOhIg/fkO2NfT
bkWmCxAFzWAlE5U9FXX1b8VfetJv+cDEaXsYwuYfowIDAQABMA0GCSqGSIb3DQEB 9F+z6q7QL5+yOHzJgBepC7V5nQSQMQr9AgMBAAEwDQYJKoZIhvcNAQELBQADggEB
DQUAA4ICAQCDSuFPJ++HY5WBhpnumbZ7+T0ReWKaerdNnQ2Xgrna5mZfB2xeRkvY AJh7TfVR5lBVtWepx5ghWrWDrqEXw7nJp4ljaZ/tp+Ufb1LasMflbAnShvnhPAg4
XeJ9VBCpGgEZKCKkhZCjomn/kLkYzRk4Sqr1ivN5NWl6G/9UTttHdRa3xiR1NhKI A5/7GkCUjZOMc5QV2MYQGIPtyRdM1rdPG6EuR8SUA/+GxIbaLo7iDpAKpcvCdFfS
AMYghpel30w5e+cWtsdR06P2FvZMuiMFCyqsbPf1xcEIAXN7HJDswq6g0ppTVZ4L pd44wgDBbzbTtw3nRGy6RXm1vB2FyjvGWVXYL3XoE2r/xQPgaf93jeglbgo9C8Tj
sXljG/J0vp+jAst4XKGLaGqnt8JaBnpNX9NO2Up3h5j7Pa4Nhm/LZ3Ku5ZVDmS1d MQ2NdR4GPBtjqZNDmer6p2Jr31pkS4exT7oNysnZfoXll8F4PHos7Wc5VYo/2qQy
B98Bsgr6tQSSyPNfZW0tGXELsNX1I+wUFw9IXFadRTHkhjeT/GhFw3i12uY7rqzm uftfQqYIZLaZKzjR32Gsr6Z5yb8GlISWnRss5ebD6+wzh8sr4fat0SY/vy/3oq5V
uJegTtWDkp1QOajhYhLD9WGXb9teldkAAgZawD6ax/uAzqx/4mBFvsUa3FMcua8k 0lwYgNPo/OuwNMA7Eo/iNPY=
HF9P2lLzKAcyaKt1cvlfUYmDVZ2Gh+9PgM8SqRpMIqK5jMRvFgemxJXS9BMBrQLp
TCvgRwQZD4mUloRlGNewKfJ0oQ1rY29vwdjTL8+BBS/GR8EuzYnqJG/D2nK0guIN
ze+cSDghA5N2pp/ffnpLWmkIDO+fsGAj3eApLhbPQ1xCXnEv6fOjgUmnxdt41m8d
+pEVBICohnvYgoEERDNAi1onJlBd/eyk0Jn37QiwqhQyrmfgwncvlt2SyzS1IZ7s
cEYreG6QHghBhgYiYo0FMuDCjT6g6Ga+T8nOp0xpZtGEWvHwjLjxvQ==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@@ -1,11 +1,15 @@
-----BEGIN CERTIFICATE REQUEST----- -----BEGIN CERTIFICATE REQUEST-----
MIIBrDCCARUCAQAwbDELMAkGA1UEBhMCRkkxEzARBgNVBAgTClNvbWUtU3RhdGUx MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B
ETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0 AQEFAAOCAQ8AMIIBCgKCAQEAycPe7U7Ek7WazlY3fPTSluePEsmgdeBobqqX3Nys
eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw RmuTjLml0FPWmJ56FAhFP2JUu+rfkoWbEAfV0eVBsu0+gAiaHZtrS7DJmjbdzl/C
gYkCgYEAwECzTwyC6jCLpbz4GuqIHIuvcWGkqedFO07/B8MN5uA/cNrm8hqke3Q1 Wl1HiGPlAfaj6GhvHSwXpx2srPnbndjtkFBe0Ib0b1TvfDaxqVWmFaXrOZREIb8y
ttIhiSO7UzXL/1LS6oJJxIggV5KvzA6+YHVzSNFf4/qgDqnyUlcb8cR8S67e4r46 o2oivjXOdYJ3UXWgJrwj1iDSAXoFMEW27AIDUGlvFBHcWFoVRfDA2v/yT0e605R0
WR2zuwdyu25FpgsQBc1gJROVPRV19W/FX3rSb/nAxGl7GMLmH6MCAwEAAaAAMA0G haujq/EiL0dQ/8QLbxF8gKj5ZHnSI4ejq9mAhCEdi8fvSstGLYub5odz6mRuoO5T
CSqGSIb3DQEBBQUAA4GBAClj/K2DAH5S64T6s7jervmk4N956Ho3aTLBgE+ReXLj AzoSIP35DtjX0/Rfs+qu0C+fsjh8yYAXqQu1eZ0EkDEK/QIDAQABoAAwDQYJKoZI
btcTdk3vFbQApAlG6MrSKys4HjpKpP/RENx3Js0HHeb8ELmWtIQNxRhwIpl0K5AD hvcNAQELBQADggEBAI9mZrnDPk8PaljHRTLYlYNtL0+gVpav+deUnAzbjA5bxdaw
xorKj+mwngLtVyARb/M7O3E8jYHzBPzpsolKWIY4AavYdmHu+Zhgm4hPKUcW+bAv t0xRptSDg3sFYFJWJ0HaqJltxSmdjj20MxmSCrexFOVXpISNsC/uZOs2CFWsDBdp
kL6pIDHmxJF2q3dA7kZMeElSqUohYlA5t5rHp6w8/Vb0HnHAlke4kPd+bbo0MyFt
mL2txXAz9AU8a5sG44x05VsSahtWSWeDNFbvaX27FhNDHxBMAFCj3niRQklMpCGG
uX+OWr42iNJLhjyR7zsJ26tf1gP6+kFoAz2kRNC9qyQyTAzXNEydg6Yff1TuZv2h
ktXz5unhsb91xO2iGT8PSWDHcVReSZRdyhwjYrI=
-----END CERTIFICATE REQUEST----- -----END CERTIFICATE REQUEST-----

View File

@@ -1,15 +1,28 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIICXgIBAAKBgQDAQLNPDILqMIulvPga6ogci69xYaSp50U7Tv8Hww3m4D9w2uby MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJw97tTsSTtZrO
GqR7dDW20iGJI7tTNcv/UtLqgknEiCBXkq/MDr5gdXNI0V/j+qAOqfJSVxvxxHxL Vjd89NKW548SyaB14Ghuqpfc3KxGa5OMuaXQU9aYnnoUCEU/YlS76t+ShZsQB9XR
rt7ivjpZHbO7B3K7bkWmCxAFzWAlE5U9FXX1b8VfetJv+cDEaXsYwuYfowIDAQAB 5UGy7T6ACJodm2tLsMmaNt3OX8JaXUeIY+UB9qPoaG8dLBenHays+dud2O2QUF7Q
AoGBAL7tQmXl2fmz/mu5kHhCpKwcuT6TpxEo4aN132aY+qxn1flBHAwiE2mbTmDi hvRvVO98NrGpVaYVpes5lEQhvzKjaiK+Nc51gndRdaAmvCPWINIBegUwRbbsAgNQ
rHViq/2GNrK5UUed3p60RdJSlgwIkyqtcGxWhUJGYCR/hU60qeeLp3MhhOoOFbiV aW8UEdxYWhVF8MDa//JPR7rTlHSFq6Or8SIvR1D/xAtvEXyAqPlkedIjh6Or2YCE
YTDsoC7V/SuWbX+1qG5FxnHSnTZhAIRkZXS4uTZ5WDcQm/7BAkEA+TlZ1IT9CeU/ IR2Lx+9Ky0Yti5vmh3PqZG6g7lMDOhIg/fkO2NfT9F+z6q7QL5+yOHzJgBepC7V5
FpHpqc8RgR8377Ehjy8o4Z4EGFnxQlAUWASnhs6dw4isr3+c7hA1OEmqmcRClPVZ nQSQMQr9AgMBAAECggEAHiOf8p98TltHrXBkGAqjRY8ACIytZ0ZXG0bo6gFdy6Qo
t1JbHAPC4QJBAMV60WSJzPUccCF47T9Ao2CeWFl/9dmgQQe9idpTNuKMXNtPJN44 tZIK0qCfcwtjTYhBvdrksPCAJq1GEUI2XsUKCB4X4rzGNst/Xt5g8yQkhH459FEw
0MQvnb+xS828woJOoRI+/UTVLLBc4xwMtwMCQQDZTadExTw4v5l1nX5GoJUbp9PG TQ+tBxrOd7pX9MngG6LbZzhopb7gl9jlnO036MSNhKbP6a1lYqD3DxIWjlr3B8Fg
/ARN64nSx0u8y9evwVErucs0oL0we+BOGZAEhz9QN/M3pceESDWUwYtNbv4hAkBB JCK9T6tlkuij/METOKV0JCeQM1dwGzWTjYOXdtyci8+0/aUsLB08I72E61SP/6IK
Ku2MqvjK7k6GjTxlgjQn/zkSl+qOnZa4MjEarhlPm5hM+wokl0U1aK07BAwK4b6i zeFSaP2JkaUxSSA2vU369OODD/LE0EMdf21U2Dh7OGRZ55NbEoUztop0hdwI6smx
d8YpmkXEAEEWFiEQMZX3AkEA1SkdiFj1u7HnzO7onLJsnFzowX3pm1UFl0azOMlM 44nrmbKG23Y1LnLNFAAxopU6RAw44RG6PIwR9GrYIQKBgQDR/GuaNjfqz9livMi/
2GkjYxWeJ/4VL7Y6QkhHE0Nj3my2+MJQI9xpYgMbw/l11w== jO8EyGcLavqHQQykAT0ps1E+w+HL6Vdb+2auFTEcLMeDJIxb0iYvZyAsskvO3lgp
-----END RSA PRIVATE KEY----- rH9sOcrsQrCluE+TA/tLvYmJyfAhFb42EansphO2oTDD/2aOexSGztGtuYAMBpFQ
3AkjppZN3qe4iB6gfys6fzCtXQKBgQD1+kh4Q+a1hv+Dh+Cn8wNnhgcTI3LvbU44
86G8iYmeTsEhy12PwGkz/RBLOX0DkxSPlpWgvDeF6kAgoQdA47c3ucaVnM1taf4h
cjAuS856FigM3E4C/rsEgvI7N4yV/rsKOnyqgHUwNalciAOgrhuGNDViazH9JpkK
pFaX7lhaIQKBgQCQKUyyPaDcC0BCIjtsDlSWIwNjellu/ACyo8sa0unnPlHJTXRV
SN5wi0oA/tkmNrRJ4ZenmZQkO3ACIPHEApIkefTPiOV/kG7JDsW6dPB08XdzL1s8
AZp4RnhoPHefR7tf/C3WcyzOqPi4yNWA/t8jY53y7faVoJXuOAA0k9eWXQKBgEvk
GI/igz/mktcGCS5IL/i0xWIGQePVGZlAsdn1pVwOeE/w6sD7YHoFzg0ng4aizmku
0KNy7r6Gc5qdlBtVJqYuzzJB2q+zUBEJpgvin6XTSDAPmJIb/Z96tznF/b4ZhaO2
P9hrIbzqEYLXBCeDEELrwLzIzfeI/RbndUAS7XeBAoGAa8WGdE7ndAtjCK4jCFiB
vIt4M6EZyZdmfTJ91SSEA303mp10cV6YGLOErROnBvnUlFSbRN8VNVA/LYq3M4Vm
da5jaFCjJhvH6qFhwsAkuGKsE1OPTs56iqZlSw9/8IJ6NW7/Igy6xR/Z5xRajAzl
FsyRRSHduI5HNgDo7riv46M=
-----END PRIVATE KEY-----

View File

@@ -1,18 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,8A14F3F31BF3CDF7
84UpLjX5GbfCoP/+ZRLwHZHqmgKyc2cjQOQKhp2YTk+4E0JSO7KsKwJax55r5VqE
ECGvbd8yvVZ3GRpU6bDCLelDN+Ob5ZSGhynI0f9is6Qle1/SbvaK+qL6MLf4H38l
Y91eZxjE8hLvOMHBlbobLmy9UUzl2osBDAM1nm5d1X7pF7mN4xgY8s2G7UPq3ZZS
NWwMsnwtb/9Ahm7WXfsJbebyKspkTSHYp/ozaOGW58fuKxkwRFd1UlyblZWU3ezP
JCpvJQ7HS6Dfy6+GUaxcC6pyxqnoJHYccB5usJ2h4QD4Es3sT7Vw7M80JKw3vAWm
TH7VkFX3yGfJ1p1jNzifN5687QqrjeI3/ecTs1rFhIC4TUPN9EDvw86Y6l6Mvo04
Hl2cVzCnCrZYq0ICD0op3+7f9kuKl7bz54S/iRG2qQdICohPs7ra2yaUy+NFVDs1
XdXyF5/xew+Rr2z7ygEd+OrvXxPV0zTFbicg9GXGeB/pIYAclmoSNXD5T3voN7y9
5MjSGL375N3z+kqPzMNawYCnZLwQ5jYTDUkDTATYpjcIDVDQkzbl8mFp4i+Z9GhL
H8FRAgmBbkgy3dxhFjxr/WzuaTkUCAbGhrtPd8WCPhBXJBzWdrBVR9SE8zT9n8lq
ZwaPwkPLBrLe/XZFEQJ5cdvMVNy4QQwsyxHnCgO3VUc68UYXb39MvyM6t8+S1rSm
SyvVAT+jB8T4VlE7tedQGuvyKMmeNIJe9znd3u4S+Aq2+vw6bOKeNYBMaupR5Gyl
bJrscuTG1aLUe+XH9BuFBUoIwdXuBv4Ko/pDL0MYPghDAEGkp4Acmg==
-----END RSA PRIVATE KEY-----

View File

@@ -99,6 +99,26 @@ function setup(opts, cb) {
}); });
} }
function createHttpServer(port) {
const httpServer = createServer();
let retryCount = 0;
return new Promise((resolve, reject) => {
httpServer.listen(port, () => resolve(httpServer));
httpServer.on("error", (e) => {
if (e.code === "EADDRINUSE" && ++retryCount <= 3) {
console.warn("port already in use, retrying...");
setTimeout(() => {
httpServer.listen(port, () => resolve(httpServer));
}, 100);
}
reject(e);
});
});
}
describe("WebTransport", () => { describe("WebTransport", () => {
it("should allow to connect with WebTransport directly", (done) => { it("should allow to connect with WebTransport directly", (done) => {
setupServer({}, async ({ engine, h3Server, certificate }) => { setupServer({}, async ({ engine, h3Server, certificate }) => {
@@ -154,9 +174,8 @@ describe("WebTransport", () => {
transports: ["polling", "websocket", "webtransport"], transports: ["polling", "websocket", "webtransport"],
}, },
async ({ engine, h3Server, certificate }) => { async ({ engine, h3Server, certificate }) => {
const httpServer = createServer(); const httpServer = await createHttpServer(h3Server.port);
engine.attach(httpServer); engine.attach(httpServer);
httpServer.listen(h3Server.port);
const partialDone = createPartialDone(() => { const partialDone = createPartialDone(() => {
httpServer.close(); httpServer.close();

View File

@@ -1,26 +1,35 @@
# History # Changelog
- [2.5.5](#255-2024-06-18) (Jun 2024) | Version | Release date |
- [2.5.4](#254-2024-02-22) (Feb 2024) |------------------------------|----------------|
- [2.5.3](#253-2024-02-21) (Feb 2024) | [2.5.6](#256-2025-12-23) | December 2025 |
- [2.5.2](#252-2023-01-12) (Jan 2023) | [2.5.5](#255-2024-06-18) | June 2024 |
- [2.5.1](#251-2023-01-06) (Jan 2023) | [2.5.4](#254-2024-02-22) | February 2024 |
- [2.5.0](#250-2023-01-06) (Jan 2023) | [2.5.3](#253-2024-02-21) | February 2024 |
- [2.4.0](#240-2022-03-30) (Mar 2022) | [2.5.2](#252-2023-01-12) | January 2023 |
- [2.3.3](#233-2021-11-16) (Nov 2021) | [2.5.1](#251-2023-01-06) | January 2023 |
- [2.3.2](#232-2021-08-28) (Aug 2021) | [2.5.0](#250-2023-01-06) | January 2023 |
- [2.3.1](#231-2021-05-19) (May 2021) | [2.4.0](#240-2022-03-30) | March 2022 |
- [2.3.0](#230-2021-05-10) (May 2021) | [2.3.3](#233-2021-11-16) | November 2021 |
- [2.2.0](#220-2021-02-27) (Feb 2021) | [2.3.2](#232-2021-08-28) | August 2021 |
- [2.1.0](#210-2021-01-15) (Jan 2021) | [2.3.1](#231-2021-05-19) | May 2021 |
- [2.0.3](#203-2020-11-05) (Nov 2020) | [2.3.0](#230-2021-05-10) | May 2021 |
- [2.0.2](#202-2020-09-28) (Sep 2020) | [2.2.0](#220-2021-02-27) | February 2021 |
- [2.0.1](#201-2020-09-28) (Sep 2020) | [2.1.0](#210-2021-01-15) | January 2021 |
- [**2.0.0**](#200-2020-09-25) (Sep 2020) | [2.0.3](#203-2020-11-05) | November 2020 |
| [2.0.2](#202-2020-09-28) | September 2020 |
| [2.0.1](#201-2020-09-28) | September 2020 |
| [**2.0.0**](#200-2020-09-25) | September 2020 |
## [2.5.6](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.5...socket.io-adapter@2.5.6) (2025-12-23)
This release contains a bump of:
- `ws` from `~8.17.1` to `~8.18.3`
- `debug` from `~4.3.1` to `~4.4.1`
# Release notes
## [2.5.5](https://github.com/socketio/socket.io-adapter/compare/2.5.4...2.5.5) (2024-06-18) ## [2.5.5](https://github.com/socketio/socket.io-adapter/compare/2.5.4...2.5.5) (2024-06-18)

View File

@@ -199,6 +199,14 @@ export abstract class ClusterAdapter extends Adapter {
return debug("[%s] ignore message from self", this.uid); return debug("[%s] ignore message from self", this.uid);
} }
if (message.nsp !== this.nsp.name) {
return debug(
"[%s] ignore message from another namespace (%s)",
this.uid,
message.nsp,
);
}
debug( debug(
"[%s] new event of type %d from %s", "[%s] new event of type %d from %s",
this.uid, this.uid,
@@ -671,6 +679,8 @@ export abstract class ClusterAdapter extends Adapter {
protected publish( protected publish(
message: DistributiveOmit<ClusterMessage, "nsp" | "uid">, message: DistributiveOmit<ClusterMessage, "nsp" | "uid">,
): void { ): void {
debug("[%s] sending message %s", this.uid, message.type);
this.publishAndReturnOffset(message).catch((err) => { this.publishAndReturnOffset(message).catch((err) => {
debug("[%s] error while publishing message: %s", this.uid, err); debug("[%s] error while publishing message: %s", this.uid, err);
}); });
@@ -699,6 +709,14 @@ export abstract class ClusterAdapter extends Adapter {
) { ) {
(response as ClusterResponse).uid = this.uid; (response as ClusterResponse).uid = this.uid;
(response as ClusterResponse).nsp = this.nsp.name; (response as ClusterResponse).nsp = this.nsp.name;
debug(
"[%s] sending response %s to %s",
this.uid,
response.type,
requesterUid,
);
this.doPublishResponse(requesterUid, response as ClusterResponse).catch( this.doPublishResponse(requesterUid, response as ClusterResponse).catch(
(err) => { (err) => {
debug("[%s] error while publishing response: %s", this.uid, err); debug("[%s] error while publishing response: %s", this.uid, err);
@@ -790,17 +808,10 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
} }
if (message.uid && message.uid !== EMITTER_UID) { if (message.uid && message.uid !== EMITTER_UID) {
// we track the UID of each sender, in order to know how many servers there are in the cluster // we track the UID of each sender to know how many servers there are in the cluster
this.nodesMap.set(message.uid, Date.now()); this.nodesMap.set(message.uid, Date.now());
} }
debug(
"[%s] new event of type %d from %s",
this.uid,
message.type,
message.uid,
);
switch (message.type) { switch (message.type) {
case MessageType.INITIAL_HEARTBEAT: case MessageType.INITIAL_HEARTBEAT:
this.publish({ this.publish({

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { yeast } from "./contrib/yeast"; import { yeast } from "./contrib/yeast";
import WebSocket = require("ws"); import * as WebSocket from "ws";
// @ts-expect-error // @ts-expect-error
const canPreComputeFrame = typeof WebSocket?.Sender?.frame === "function"; const canPreComputeFrame = typeof WebSocket?.Sender?.frame === "function";
@@ -51,11 +51,11 @@ export class Adapter extends EventEmitter {
/** /**
* In-memory adapter constructor. * In-memory adapter constructor.
* *
* @param {Namespace} nsp * @param nsp
*/ */
constructor(readonly nsp: any) { constructor(readonly nsp: any) {
super(); super();
this.encoder = nsp.server.encoder; this.encoder = nsp.server.encoder; // nsp is a Namespace object
} }
/** /**

View File

@@ -1,6 +1,6 @@
{ {
"name": "socket.io-adapter", "name": "socket.io-adapter",
"version": "2.5.5", "version": "2.5.6",
"license": "MIT", "license": "MIT",
"homepage": "https://github.com/socketio/socket.io/tree/main/packages/socket.io-adapter#readme", "homepage": "https://github.com/socketio/socket.io/tree/main/packages/socket.io-adapter#readme",
"repository": { "repository": {
@@ -17,12 +17,12 @@
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"description": "default socket.io in-memory adapter", "description": "default socket.io in-memory adapter",
"dependencies": { "dependencies": {
"debug": "~4.3.4", "debug": "~4.4.1",
"ws": "~8.17.1" "ws": "~8.18.3"
}, },
"scripts": { "scripts": {
"compile": "rimraf ./dist && tsc", "compile": "rimraf ./dist && tsc",
"test": "npm run format:check && npm run compile && nyc mocha --require ts-node/register test/*.ts", "test": "npm run format:check && npm run compile && nyc mocha --import=tsx test/*.ts",
"format:check": "prettier --parser typescript --check 'lib/**/*.ts' 'test/**/*.ts'", "format:check": "prettier --parser typescript --check 'lib/**/*.ts' 'test/**/*.ts'",
"format:fix": "prettier --parser typescript --write 'lib/**/*.ts' 'test/**/*.ts'", "format:fix": "prettier --parser typescript --write 'lib/**/*.ts' 'test/**/*.ts'",
"prepack": "npm run compile" "prepack": "npm run compile"

View File

@@ -67,11 +67,6 @@ describe("cluster adapter", () => {
serverSockets.push(socket); serverSockets.push(socket);
servers.push(io); servers.push(io);
if (servers.length === NODES_COUNT) { if (servers.length === NODES_COUNT) {
// ensure all nodes know each other
servers[0].emit("ping");
servers[1].emit("ping");
servers[2].emit("ping");
done(); done();
} }
}); });

View File

@@ -1,7 +1,8 @@
# History # Changelog
| Version | Release date | Bundle size (UMD min+gzip) | | Version | Release date | Bundle size (UMD min+gzip) |
|-------------------------------------------------------------------------------------------------------------|----------------|----------------------------| |-------------------------------------------------------------------------------------------------------------|----------------|----------------------------|
| [4.8.2](#482-2025-12-22) | December 2024 | `14.4 KB` |
| [4.8.1](#481-2024-10-25) | October 2024 | `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.8.0](#480-2024-09-21) | September 2024 | `14.4 KB` |
| [4.7.5](#475-2024-03-14) | March 2024 | `14.6 KB` | | [4.7.5](#475-2024-03-14) | March 2024 | `14.6 KB` |
@@ -50,7 +51,21 @@
| [2.1.0](#210-2018-03-29) | March 2018 | `18.7 KB` | | [2.1.0](#210-2018-03-29) | March 2018 | `18.7 KB` |
# Release notes ## [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)
### Bug Fixes
* **bundle** do not mangle the "_placeholder" attribute (bis) ([cdae019](https://github.com/socketio/socket.io/commit/cdae01983a8ae840fc9812875a8b88166b377c11))
* drain queue before emitting "connect" ([#5259](https://github.com/socketio/socket.io/issues/5259)) ([d19928e](https://github.com/socketio/socket.io/commit/d19928e8d8b325310274031ed7de2ddc93ebb589)
### Dependencies
- [`engine.io-client@~6.6.1`](https://github.com/socketio/engine.io-client/releases/tag/6.5.2) (no change)
- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change)
## [4.8.1](https://github.com/socketio/socket.io/compare/socket.io-client@4.8.0...socket.io-client@4.8.1) (2024-10-25) ## [4.8.1](https://github.com/socketio/socket.io/compare/socket.io-client@4.8.0...socket.io-client@4.8.1) (2024-10-25)

View File

@@ -0,0 +1,11 @@
# Releasing
1. Update the version in `package.json`
2. Update the version in `support/package.esm.json`
3. Update the changelog `CHANGELOG.md` with `conventional-changelog -p angular`
4. Compile the TypeScript sources with `npm run compile`
5. Generate the bundles with `npm run build`
6. Commit the changes in `package.json`, `support/package.esm.json`, `CHANGELOG.md` and `dist/`
7. Create the tag `socket.io-client@x.y.z` and push it to the GitHub repository. The workflow `.github/workflows/publish.yml` will safely publish the package to npm using trusted publishing.
8. Create a new release at https://github.com/socketio/socket.io/releases
9. Copy the bundles to the repository https://github.com/socketio/socket.io-cdn so that they are available at https://cdn.socket.io/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
/*! /*!
* Socket.IO v4.8.1 * Socket.IO v4.8.2
* (c) 2014-2024 Guillermo Rauch * (c) 2014-2025 Guillermo Rauch
* Released under the MIT License. * Released under the MIT License.
*/ */
(function (global, factory) { (function (global, factory) {
@@ -899,7 +899,7 @@
return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]"; return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]";
}; };
_proto._port = function _port() { _proto._port = function _port() {
if (this.opts.port && (this.opts.secure && Number(this.opts.port !== 443) || !this.opts.secure && Number(this.opts.port) !== 80)) { if (this.opts.port && (this.opts.secure && Number(this.opts.port) !== 443 || !this.opts.secure && Number(this.opts.port) !== 80)) {
return ":" + this.opts.port; return ":" + this.opts.port;
} else { } else {
return ""; return "";
@@ -2669,21 +2669,65 @@
createDebug.namespaces = namespaces; createDebug.namespaces = namespaces;
createDebug.names = []; createDebug.names = [];
createDebug.skips = []; createDebug.skips = [];
var i; var split = (typeof namespaces === 'string' ? namespaces : '').trim().replace(/\s+/g, ',').split(',').filter(Boolean);
var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); var _iterator = _createForOfIteratorHelper(split),
var len = split.length; _step;
for (i = 0; i < len; i++) { try {
if (!split[i]) { for (_iterator.s(); !(_step = _iterator.n()).done;) {
// ignore empty strings var ns = _step.value;
continue; if (ns[0] === '-') {
createDebug.skips.push(ns.slice(1));
} else {
createDebug.names.push(ns);
}
} }
namespaces = split[i].replace(/\*/g, '.*?'); } catch (err) {
if (namespaces[0] === '-') { _iterator.e(err);
createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$')); } finally {
_iterator.f();
}
}
/**
* Checks if the given string matches a namespace template, honoring
* asterisks as wildcards.
*
* @param {String} search
* @param {String} template
* @return {Boolean}
*/
function matchesTemplate(search, template) {
var searchIndex = 0;
var templateIndex = 0;
var starIndex = -1;
var matchIndex = 0;
while (searchIndex < search.length) {
if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) {
// Match character or proceed with wildcard
if (template[templateIndex] === '*') {
starIndex = templateIndex;
matchIndex = searchIndex;
templateIndex++; // Skip the '*'
} else {
searchIndex++;
templateIndex++;
}
} else if (starIndex !== -1) {
// eslint-disable-line no-negated-condition
// Backtrack to the last '*' and try to match more characters
templateIndex = starIndex + 1;
matchIndex++;
searchIndex = matchIndex;
} else { } else {
createDebug.names.push(new RegExp('^' + namespaces + '$')); return false; // No match
} }
} }
// Handle trailing '*' in template
while (templateIndex < template.length && template[templateIndex] === '*') {
templateIndex++;
}
return templateIndex === template.length;
} }
/** /**
@@ -2693,7 +2737,7 @@
* @api public * @api public
*/ */
function disable() { function disable() {
var namespaces = [].concat(_toConsumableArray(createDebug.names.map(toNamespace)), _toConsumableArray(createDebug.skips.map(toNamespace).map(function (namespace) { var namespaces = [].concat(_toConsumableArray(createDebug.names), _toConsumableArray(createDebug.skips.map(function (namespace) {
return '-' + namespace; return '-' + namespace;
}))).join(','); }))).join(',');
createDebug.enable(''); createDebug.enable('');
@@ -2708,35 +2752,37 @@
* @api public * @api public
*/ */
function enabled(name) { function enabled(name) {
if (name[name.length - 1] === '*') { var _iterator2 = _createForOfIteratorHelper(createDebug.skips),
return true; _step2;
} try {
var i; for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var len; var skip = _step2.value;
for (i = 0, len = createDebug.skips.length; i < len; i++) { if (matchesTemplate(name, skip)) {
if (createDebug.skips[i].test(name)) { return false;
return false; }
} }
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
} }
for (i = 0, len = createDebug.names.length; i < len; i++) { var _iterator3 = _createForOfIteratorHelper(createDebug.names),
if (createDebug.names[i].test(name)) { _step3;
return true; try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var ns = _step3.value;
if (matchesTemplate(name, ns)) {
return true;
}
} }
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
} }
return false; return false;
} }
/**
* Convert regexp to namespace
*
* @param {RegExp} regxep
* @return {String} namespace
* @api private
*/
function toNamespace(regexp) {
return regexp.toString().substring(2, regexp.toString().length - 2).replace(/\.\*\?$/, '*');
}
/** /**
* Coerce `val`. * Coerce `val`.
* *
@@ -2812,15 +2858,17 @@
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
return false; return false;
} }
var m;
// Is webkit? http://stackoverflow.com/a/16459606/376773 // Is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
// eslint-disable-next-line no-return-assign
return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance ||
// Is firebug? http://stackoverflow.com/a/398120/376773 // Is firebug? http://stackoverflow.com/a/398120/376773
typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) || typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) ||
// Is firefox >= v31? // Is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 ||
// Double check webkit in userAgent just in case we are in a worker // Double check webkit in userAgent just in case we are in a worker
typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/); typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
} }
@@ -2896,7 +2944,7 @@
function load() { function load() {
var r; var r;
try { try {
r = exports.storage.getItem('debug'); r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG');
} catch (error) { } catch (error) {
// Swallow // Swallow
// XXX (@Qix-) should we be logging these? // XXX (@Qix-) should we be logging these?
@@ -3828,8 +3876,7 @@
}; };
args.push(function (err) { args.push(function (err) {
if (packet !== _this4._queue[0]) { if (packet !== _this4._queue[0]) {
// the packet has already been acknowledged return debug$2("packet [%d] already acknowledged", packet.id);
return;
} }
var hasError = err !== null; var hasError = err !== null;
if (hasError) { if (hasError) {
@@ -4100,8 +4147,8 @@
this._pid = pid; // defined only if connection state recovery is enabled this._pid = pid; // defined only if connection state recovery is enabled
this.connected = true; this.connected = true;
this.emitBuffered(); this.emitBuffered();
this.emitReserved("connect");
this._drainQueue(true); this._drainQueue(true);
this.emitReserved("connect");
} }
/** /**
* Emit buffered events (received and emitted). * Emit buffered events (received and emitted).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
import { url } from "./url.js"; import { url } from "./url.js";
import { Manager, ManagerOptions } from "./manager.js"; import { Manager, ManagerOptions } from "./manager.js";
import { Socket, SocketOptions } from "./socket.js"; import { DisconnectDescription, Socket, SocketOptions } from "./socket.js";
import debugModule from "debug"; // debug() import debugModule from "debug"; // debug()
const debug = debugModule("socket.io-client"); // debug() const debug = debugModule("socket.io-client"); // debug()
@@ -91,6 +91,7 @@ export { protocol } from "socket.io-parser";
*/ */
export { export {
DisconnectDescription,
Manager, Manager,
ManagerOptions, ManagerOptions,
Socket, Socket,

View File

@@ -542,8 +542,7 @@ export class Socket<
args.push((err, ...responseArgs) => { args.push((err, ...responseArgs) => {
if (packet !== this._queue[0]) { if (packet !== this._queue[0]) {
// the packet has already been acknowledged return debug("packet [%d] already acknowledged", packet.id);
return;
} }
const hasError = err !== null; const hasError = err !== null;
if (hasError) { if (hasError) {
@@ -834,8 +833,8 @@ export class Socket<
this._pid = pid; // defined only if connection state recovery is enabled this._pid = pid; // defined only if connection state recovery is enabled
this.connected = true; this.connected = true;
this.emitBuffered(); this.emitBuffered();
this.emitReserved("connect");
this._drainQueue(true); this._drainQueue(true);
this.emitReserved("connect");
} }
/** /**

View File

@@ -1,6 +1,6 @@
{ {
"name": "socket.io-client", "name": "socket.io-client",
"version": "4.8.1", "version": "4.8.2",
"description": "Realtime application framework client", "description": "Realtime application framework client",
"keywords": [ "keywords": [
"realtime", "realtime",
@@ -46,14 +46,14 @@
"types": "./build/esm/index.d.ts", "types": "./build/esm/index.d.ts",
"dependencies": { "dependencies": {
"@socket.io/component-emitter": "~3.1.0", "@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2", "debug": "~4.4.1",
"engine.io-client": "~6.6.1", "engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4" "socket.io-parser": "~4.2.4"
}, },
"scripts": { "scripts": {
"compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh", "compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh",
"test": "npm run format:check && npm run compile && if test \"$BROWSERS\" = \"1\" ; then npm run test:browser; else npm run test:node; fi", "test": "npm run format:check && npm run compile && if test \"$BROWSERS\" = \"1\" ; then npm run test:browser; else npm run test:node; fi",
"test:node": "mocha --require ts-node/register --require test/support/hooks.ts --exit test/index.ts", "test:node": "mocha --import=tsx --require test/support/hooks.ts --exit test/index.ts",
"test:browser": "ts-node test/browser-runner.ts", "test:browser": "ts-node test/browser-runner.ts",
"test:types": "tsd", "test:types": "tsd",
"build": "rollup -c support/rollup.config.umd.js && rollup -c support/rollup.config.esm.js && rollup -c support/rollup.config.umd.msgpack.js", "build": "rollup -c support/rollup.config.umd.js && rollup -c support/rollup.config.esm.js && rollup -c support/rollup.config.umd.msgpack.js",

View File

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

View File

@@ -20,6 +20,7 @@ module.exports = {
mangle: { mangle: {
properties: { properties: {
regex: /^_/, regex: /^_/,
reserved: ["_placeholder"],
}, },
}, },
}), }),

View File

@@ -110,4 +110,40 @@ describe("retry", () => {
}, 100); }, 100);
}); });
}); });
it("should not emit a packet twice in the 'connect' handler", () => {
return wrap((done) => {
const socket = io(BASE_URL, {
forceNew: true,
retries: 3,
});
const received: string[] = [];
const sent: string[] = [];
socket.io.engine.on("packetCreate", ({ data }) => {
sent.push(data);
});
socket.io.engine.on("packet", ({ data }) => {
received.push(data);
});
socket.on("connect", () => {
socket.emit("echo", null);
});
setTimeout(() => {
expect(sent).to.eql(["0", '20["echo",null]']);
expect(received.length).to.eql(3);
// 1: engine.io OPEN packet
// 2: socket.io CONNECT packet
// 3: ack packet
expect(received[2]).to.eql("30[null]");
success(done, socket);
}, 100);
});
});
}); });

View File

@@ -60,7 +60,7 @@ if (process.env.CI === "true") {
{ {
browserName: "internet explorer", browserName: "internet explorer",
browserVersion: "10", browserVersion: "10",
platformName: "Windows 7", platformName: "Windows 8",
"sauce:options": BASE_SAUCE_OPTIONS, "sauce:options": BASE_SAUCE_OPTIONS,
}, },
{ {

View File

@@ -0,0 +1,60 @@
# Changelog
| Version | Release date |
|--------------------------|--------------|
| [0.3.0](#030-2025-10-16) | October 2025 |
| [0.2.2](#022-2023-03-24) | March 2023 |
| [0.2.1](#021-2022-10-13) | October 2022 |
| [0.2.0](#020-2022-04-28) | April 2022 |
| [0.1.0](#010-2021-06-22) | June 2021 |
## [0.3.0](https://github.com/socketio/socket.io-cluster-adapter/compare/0.2.2...0.3.0) (2025-10-16)
This release contains an important refactor of the adapter ([this commit](https://github.com/socketio/socket.io-cluster-adapter/commit/0c431243e28913fdd2a4a3de3e67a9f38d67a3aa)), as most of the logic has been moved in the `ClusterAdapter` class of the `socket.io-adapter` package.
Besides, the `@socket.io/cluster-adapter` package is now part of the `socket.io` monorepo.
## [0.2.2](https://github.com/socketio/socket.io-cluster-adapter/compare/0.2.1...0.2.2) (2023-03-24)
The `socket.io-adapter` package was added to the list of `peerDependencies`, in order to fix sync issues with the version imported by the socket.io package (see [15fd56e](https://github.com/socketio/socket.io-cluster-adapter/commit/15fd56e78d52aa65c5fbf412dec57ab4bdaee7cc)).
Support for connection state recovery (see [here](https://github.com/socketio/socket.io/releases/4.6.0)) will be added in the next release.
## [0.2.1](https://github.com/socketio/socket.io-cluster-adapter/compare/0.2.0...0.2.1) (2022-10-13)
### Bug Fixes
* properly handle ERR_IPC_CHANNEL_CLOSED errors ([#6](https://github.com/socketio/socket.io-cluster-adapter/issues/6)) ([be0a0e3](https://github.com/socketio/socket.io-cluster-adapter/commit/be0a0e3217bd7100d569e5624194612bcc8b96ff))
## [0.2.0](https://github.com/socketio/socket.io-cluster-adapter/compare/0.1.0...0.2.0) (2022-04-28)
### Features
* broadcast and expect multiple acks ([055b784](https://github.com/socketio/socket.io-cluster-adapter/commit/055b7840d8cf88173d8299041ef3fafa9791c97a))
This feature was added in `socket.io@4.5.0`:
```js
io.timeout(1000).emit("some-event", (err, responses) => {
// ...
});
```
Thanks to this change, it will now work within a Node.js cluster.
## 0.1.0 (2021-06-22)
Initial commit

View File

@@ -0,0 +1,7 @@
Copyright (c) 2021 Damien Arrachequesne (@darrachequesne)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,100 @@
# Socket.IO cluster adapter
The `@socket.io/cluster-adapter` package allows broadcasting packets between multiple Socket.IO servers.
![Adapter diagram](./assets/adapter.png)
It can be used in conjunction with [`@socket.io/sticky`](https://github.com/socketio/socket.io-sticky) to broadcast packets between the workers of the same Node.js [cluster](https://nodejs.org/api/cluster.html).
Supported features:
- [broadcasting](https://socket.io/docs/v4/broadcasting-events/)
- [utility methods](https://socket.io/docs/v4/server-instance/#Utility-methods)
- [`socketsJoin`](https://socket.io/docs/v4/server-instance/#socketsJoin)
- [`socketsLeave`](https://socket.io/docs/v4/server-instance/#socketsLeave)
- [`disconnectSockets`](https://socket.io/docs/v4/server-instance/#disconnectSockets)
- [`fetchSockets`](https://socket.io/docs/v4/server-instance/#fetchSockets)
- [`serverSideEmit`](https://socket.io/docs/v4/server-instance/#serverSideEmit)
Related packages:
- Postgres adapter: https://github.com/socketio/socket.io-postgres-adapter/
- Redis adapter: https://github.com/socketio/socket.io-redis-adapter/
- MongoDB adapter: https://github.com/socketio/socket.io-mongo-adapter/
**Table of contents**
- [Installation](#installation)
- [Usage](#usage)
- [License](#license)
## Installation
```
npm install @socket.io/cluster-adapter
```
## Usage
```js
const cluster = require("cluster");
const http = require("http");
const { Server } = require("socket.io");
const numCPUs = require("os").cpus().length;
const { setupMaster, setupWorker } = require("@socket.io/sticky");
const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter");
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
const httpServer = http.createServer();
// setup sticky sessions
setupMaster(httpServer, {
loadBalancingMethod: "least-connection",
});
// setup connections between the workers
setupPrimary();
// needed for packets containing buffers (you can ignore it if you only send plaintext objects)
// Node.js < 16.0.0
cluster.setupMaster({
serialization: "advanced",
});
// Node.js > 16.0.0
// cluster.setupPrimary({
// serialization: "advanced",
// });
httpServer.listen(3000);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
console.log(`Worker ${process.pid} started`);
const httpServer = http.createServer();
const io = new Server(httpServer);
// use the cluster adapter
io.adapter(createAdapter());
// setup connection with the primary process
setupWorker(io);
io.on("connection", (socket) => {
/* ... */
});
}
```
## License
[MIT](LICENSE)

View File

@@ -0,0 +1,822 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"type": "text",
"version": 345,
"versionNonce": 1782313961,
"isDeleted": false,
"id": "5hUB5ALUlsn26W0PzU4fM",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 777,
"y": -89.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 78,
"height": 26,
"seed": 28708370,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"_wBO22vaQplcoKyBXbWRC"
],
"fontSize": 20,
"fontFamily": 1,
"text": "worker 1",
"baseline": 18,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"type": "rectangle",
"version": 230,
"versionNonce": 1587305255,
"isDeleted": false,
"id": "lmQ4o4New7xuXQLwavuSn",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 725,
"y": -169,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 345.00000000000006,
"height": 311,
"seed": 1594950354,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"_wBO22vaQplcoKyBXbWRC",
"BZVwnsrGk9G-X87ZHkh-6",
"eU1gfEXnHSjxc-pEgv43A",
"XZpY0rnxgeDlxu5b8fgRQ",
"4mjxZzapHnLuRx7KU2JeH",
"mV8ZNfAcYrxGLJ7b9a_kn"
]
},
{
"type": "text",
"version": 152,
"versionNonce": 742887113,
"isDeleted": false,
"id": "ZQsZmj4NaTubBHMkVG2dl",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 745,
"y": -159,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 43,
"height": 26,
"seed": 126533902,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [],
"fontSize": 20,
"fontFamily": 1,
"text": "Host",
"baseline": 18,
"textAlign": "left",
"verticalAlign": "top"
},
{
"type": "rectangle",
"version": 334,
"versionNonce": 1221877319,
"isDeleted": false,
"id": "RRrk3Vsl-pM8Z1r8Fj3Vu",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 749.5,
"y": -105,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 129,
"height": 56,
"seed": 1013161166,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"use4Bp2hbb77Fq5njtwBi",
"U7UCkn3nVHlWGYetjII_Z"
]
},
{
"type": "text",
"version": 386,
"versionNonce": 601673129,
"isDeleted": false,
"id": "qfQdcJHnwYnCMtLCV51X8",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 773,
"y": -18.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 90,
"height": 26,
"seed": 1535426147,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"_wBO22vaQplcoKyBXbWRC",
"2DIFacJXJtC5QIuMuo3pK"
],
"fontSize": 20,
"fontFamily": 1,
"text": "worker 2",
"baseline": 18,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"type": "rectangle",
"version": 330,
"versionNonce": 794754407,
"isDeleted": false,
"id": "IRd1nPQbv0PQdJQn_yLOs",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 749.5,
"y": -36,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 129,
"height": 56,
"seed": 452398413,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"2DIFacJXJtC5QIuMuo3pK",
"NhqDM6wVMhgbRvXrQJQge",
"C1IueNJdiTSkqUxSTEvKe"
]
},
{
"type": "text",
"version": 374,
"versionNonce": 942092425,
"isDeleted": false,
"id": "ENOSqQ4visNbCN7ZMZwxP",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 770.5,
"y": 48.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 89,
"height": 26,
"seed": 1916984429,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"_wBO22vaQplcoKyBXbWRC"
],
"fontSize": 20,
"fontFamily": 1,
"text": "worker 3",
"baseline": 18,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"type": "rectangle",
"version": 314,
"versionNonce": 629717127,
"isDeleted": false,
"id": "IqdB8EO7s50UY1EU9TVVP",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 750.5,
"y": 30,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 129,
"height": 56,
"seed": 1832463587,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"NhqDM6wVMhgbRvXrQJQge"
]
},
{
"type": "rectangle",
"version": 115,
"versionNonce": 997860361,
"isDeleted": false,
"id": "9grXh8d6z3-WENQLWSBP6",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 338,
"y": -95,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 140,
"height": 49,
"seed": 1667334019,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": []
},
{
"type": "text",
"version": 95,
"versionNonce": 1806744839,
"isDeleted": false,
"id": "uw4DcwoucyYZxQuvW-XdC",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 383,
"y": -83.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 50,
"height": 26,
"seed": 1216901411,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [],
"fontSize": 20,
"fontFamily": 1,
"text": "client",
"baseline": 18,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"id": "ZR4mzBF0WDviz0UhZ4jZM",
"type": "diamond",
"x": 891.5,
"y": -87,
"width": 35,
"height": 18,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"strokeSharpness": "sharp",
"seed": 1951590473,
"version": 87,
"versionNonce": 232917705,
"isDeleted": false,
"boundElementIds": [
"LgoBfF5uxSyzlQnWpzp21",
"VzzoutsEZMxQbMloZdWpR",
"OEzsLqAW3F-2LE0bnPrtx"
]
},
{
"id": "tpect-oo26kMVbG_xdqoO",
"type": "text",
"x": 895.5,
"y": -130,
"width": 157,
"height": 26,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"strokeSharpness": "sharp",
"seed": 294816809,
"version": 104,
"versionNonce": 2120602793,
"isDeleted": false,
"boundElementIds": null,
"text": "cluster adapter",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"baseline": 18
},
{
"type": "diamond",
"version": 113,
"versionNonce": 1690814889,
"isDeleted": false,
"id": "ScoFMjrxukGD1efHfKiFH",
"fillStyle": "hachure",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 894.5,
"y": -20,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 35,
"height": 18,
"seed": 696687431,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"VzzoutsEZMxQbMloZdWpR"
]
},
{
"type": "diamond",
"version": 158,
"versionNonce": 1317846759,
"isDeleted": false,
"id": "wj2HZ2scg4U6UtpfK8x3e",
"fillStyle": "hachure",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 893.5,
"y": 46,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 35,
"height": 18,
"seed": 1469547015,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"OEzsLqAW3F-2LE0bnPrtx"
]
},
{
"id": "U7UCkn3nVHlWGYetjII_Z",
"type": "arrow",
"x": 734.5,
"y": -74,
"width": 238,
"height": 4,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"strokeSharpness": "round",
"seed": 1537057031,
"version": 61,
"versionNonce": 1166788937,
"isDeleted": false,
"boundElementIds": null,
"points": [
[
0,
0
],
[
-238,
4
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "RRrk3Vsl-pM8Z1r8Fj3Vu",
"focus": -0.05720889916209189,
"gap": 15
},
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow"
},
{
"type": "rectangle",
"version": 163,
"versionNonce": 15141831,
"isDeleted": false,
"id": "mv6FEWy7Oux1XBpOeYKlJ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 337.25,
"y": -26.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 140,
"height": 49,
"seed": 1927924585,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": []
},
{
"type": "text",
"version": 143,
"versionNonce": 766883881,
"isDeleted": false,
"id": "sBk2P5AAiZa36HbnmeRLi",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 382.25,
"y": -15,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 50,
"height": 26,
"seed": 1643756455,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [],
"fontSize": 20,
"fontFamily": 1,
"text": "client",
"baseline": 18,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"type": "arrow",
"version": 111,
"versionNonce": 2076179175,
"isDeleted": false,
"id": "C1IueNJdiTSkqUxSTEvKe",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 733.75,
"y": -5.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 238,
"height": 4,
"seed": 542013001,
"groupIds": [],
"strokeSharpness": "round",
"boundElementIds": [],
"startBinding": {
"elementId": "IRd1nPQbv0PQdJQn_yLOs",
"focus": -0.03958393527882115,
"gap": 15.75
},
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-238,
4
]
]
},
{
"type": "rectangle",
"version": 138,
"versionNonce": 478071561,
"isDeleted": false,
"id": "6r_-m_ehoDx3FlKI9YqbJ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 337.25,
"y": 40.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 140,
"height": 49,
"seed": 704870919,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": []
},
{
"type": "text",
"version": 118,
"versionNonce": 133039623,
"isDeleted": false,
"id": "EEhePeT66oko7kRekPQGo",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 382.25,
"y": 52,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 50,
"height": 26,
"seed": 93212137,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [],
"fontSize": 20,
"fontFamily": 1,
"text": "client",
"baseline": 18,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"type": "arrow",
"version": 86,
"versionNonce": 979534313,
"isDeleted": false,
"id": "etOb8BtJX8fUgxXsPOX2F",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 733.75,
"y": 61.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 238,
"height": 4,
"seed": 464822567,
"groupIds": [],
"strokeSharpness": "round",
"boundElementIds": [],
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-238,
4
]
]
},
{
"type": "rectangle",
"version": 184,
"versionNonce": 1953344807,
"isDeleted": false,
"id": "G6-PJf8TRngnFyuC3A0QC",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 337.25,
"y": 105.5,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 140,
"height": 49,
"seed": 1627945223,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"JFhaNprFAz7gl6FY48Aps"
]
},
{
"type": "text",
"version": 163,
"versionNonce": 342645961,
"isDeleted": false,
"id": "UEv3oUzBr56Mj6Mz7hG-B",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 382.25,
"y": 117,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 50,
"height": 26,
"seed": 1822211817,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [],
"fontSize": 20,
"fontFamily": 1,
"text": "client",
"baseline": 18,
"textAlign": "center",
"verticalAlign": "middle"
},
{
"type": "arrow",
"version": 135,
"versionNonce": 2091610183,
"isDeleted": false,
"id": "JFhaNprFAz7gl6FY48Aps",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 731.1770639400929,
"y": 74.303452225402,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 240.42706394009292,
"height": 54.196547774598,
"seed": 1203879975,
"groupIds": [],
"strokeSharpness": "round",
"boundElementIds": [],
"startBinding": null,
"endBinding": {
"elementId": "G6-PJf8TRngnFyuC3A0QC",
"focus": 0.4300574064368146,
"gap": 13.5
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-240.42706394009292,
54.196547774598
]
]
},
{
"id": "VzzoutsEZMxQbMloZdWpR",
"type": "arrow",
"x": 929.5,
"y": -70,
"width": 17,
"height": 53,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"strokeSharpness": "round",
"seed": 1957283401,
"version": 330,
"versionNonce": 1093640775,
"isDeleted": false,
"boundElementIds": null,
"points": [
[
0,
0
],
[
16,
23
],
[
-1,
53
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "ZR4mzBF0WDviz0UhZ4jZM",
"focus": -0.853416149068323,
"gap": 8.48634645640746
},
"endBinding": {
"elementId": "ScoFMjrxukGD1efHfKiFH",
"focus": 0.7485714285714284,
"gap": 4.878378801288122
},
"startArrowhead": null,
"endArrowhead": "arrow"
},
{
"type": "arrow",
"version": 440,
"versionNonce": 2038871081,
"isDeleted": false,
"id": "OEzsLqAW3F-2LE0bnPrtx",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 930.5,
"y": -71,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 42,
"height": 116,
"seed": 1990607913,
"groupIds": [],
"strokeSharpness": "round",
"boundElementIds": [],
"startBinding": {
"elementId": "ZR4mzBF0WDviz0UhZ4jZM",
"focus": -0.8894409937888198,
"gap": 8.054406666710076
},
"endBinding": {
"elementId": "wj2HZ2scg4U6UtpfK8x3e",
"focus": 0.6,
"gap": 8.435530010560711
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
39,
46
],
[
-3,
116
]
]
}
],
"appState": {
"gridSize": null,
"viewBackgroundColor": "#ffffff"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -0,0 +1,116 @@
import cluster from "node:cluster";
import {
ClusterAdapterWithHeartbeat,
ClusterAdapterOptions,
ClusterMessage,
ServerId,
ClusterResponse,
MessageType,
} from "socket.io-adapter";
import debugModule from "debug";
const debug = debugModule("socket.io-cluster-adapter");
const MESSAGE_SOURCE = "_sio_adapter";
const hasOwnProperty = Object.prototype.hasOwnProperty;
function ignoreError() {}
/**
* Returns a function that will create a NodeClusterAdapter instance.
*
* @param opts - additional options
*
* @public
* @see https://nodejs.org/api/cluster.html
*/
export function createAdapter(opts: Partial<ClusterAdapterOptions> = {}) {
return function (nsp: any) {
return new NodeClusterAdapter(nsp, opts);
};
}
export class NodeClusterAdapter extends ClusterAdapterWithHeartbeat {
constructor(nsp: any, opts: ClusterAdapterOptions = {}) {
super(nsp, opts);
process.on("message", (message: any) => {
const isValidSource = message?.source === MESSAGE_SOURCE;
if (!isValidSource) {
debug("[%s] ignore unknown source", this.uid);
return;
}
// note: this check should be done in the onMessage() handler
if (message.nsp !== this.nsp.name) {
debug("[%s] ignore other namespace", this.uid);
return;
}
this.onMessage(message);
});
// until https://github.com/socketio/socket.io/commit/f3e1f5ebdf59158d0c8d1e20f8230275617fb355 is released
this.init();
}
protected override doPublish(message: ClusterMessage & { source: string }) {
message.source = MESSAGE_SOURCE;
process.send(message, null, {}, ignoreError);
return Promise.resolve(""); // connection state recovery is not supported
}
protected override doPublishResponse(
requesterUid: ServerId,
response: ClusterResponse & { source: string; requesterUid: string },
) {
response.source = MESSAGE_SOURCE;
response.requesterUid = requesterUid;
process.send(response, null, {}, ignoreError);
return Promise.resolve();
}
}
const UIDS = Symbol("uids");
export function setupPrimary() {
cluster.on("message", (worker, message) => {
const isValidSource = message?.source === MESSAGE_SOURCE;
if (!isValidSource) {
return;
}
// store the requester's uids (one per namespace) so that the response can be sent specifically to them
worker[UIDS] = worker[UIDS] || new Set();
worker[UIDS].add(message.uid);
switch (message.type) {
case MessageType.FETCH_SOCKETS_RESPONSE:
case MessageType.SERVER_SIDE_EMIT_RESPONSE:
const requesterUid = message.requesterUid;
for (const workerId in cluster.workers) {
if (
hasOwnProperty.call(cluster.workers, workerId) &&
cluster.workers[workerId][UIDS]?.has(requesterUid)
) {
cluster.workers[workerId].send(message, null, ignoreError);
break;
}
}
break;
default:
const emitterIdAsString = String(worker.id);
// emit to all workers but the requester
for (const workerId in cluster.workers) {
if (
hasOwnProperty.call(cluster.workers, workerId) &&
workerId !== emitterIdAsString
) {
cluster.workers[workerId].send(message, null, ignoreError);
}
}
}
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
{
"name": "@socket.io/cluster-adapter",
"version": "0.3.0",
"description": "The Socket.IO cluster adapter, allowing to broadcast events between several Socket.IO servers",
"license": "MIT",
"homepage": "https://github.com/socketio/socket.io/tree/main/packages/socket.io-cluster-adapter#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/socketio/socket.io.git"
},
"bugs": {
"url": "https://github.com/socketio/socket.io/issues"
},
"files": [
"dist/"
],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"compile": "rimraf ./dist && tsc",
"test": "npm run format:check && npm run compile && nyc mocha --import=tsx test/index.ts",
"format:check": "prettier --check \"lib/**/*.ts\" \"test/**/*.ts\"",
"format:fix": "prettier --write \"lib/**/*.ts\" \"test/**/*.ts\"",
"prepack": "npm run compile"
},
"dependencies": {
"debug": "~4.4.1"
},
"peerDependencies": {
"socket.io-adapter": "~2.5.5"
},
"engines": {
"node": ">=10.0.0"
},
"keywords": [
"socket.io",
"cluster",
"adapter"
]
}

View File

@@ -0,0 +1,372 @@
import { io as ioc, Socket as ClientSocket } from "socket.io-client";
import expect = require("expect.js");
import { setupPrimary } from "..";
import { times, sleep } from "./util";
import cluster, { Worker } from "node:cluster";
const NODES_COUNT = 3;
cluster.setupMaster({
exec: "./test/worker.js",
// @ts-ignore
serialization: "advanced", // needed for packets containing buffers
});
setupPrimary();
const getRooms = (worker): Promise<Set<string>> => {
worker.send("get rooms");
return new Promise((resolve) => {
worker.once("message", (content) => {
resolve(content);
});
});
};
describe("@socket.io/cluster-adapter", () => {
let clientSockets: ClientSocket[], workers: Worker[];
beforeEach((done) => {
clientSockets = [];
workers = [];
for (let i = 1; i <= NODES_COUNT; i++) {
const PORT = 40000 + i;
const worker = cluster.fork({
PORT,
});
worker.on("listening", () => {
const clientSocket = ioc(`http://localhost:${PORT}`);
clientSocket.on("connect", async () => {
workers.push(worker);
clientSockets.push(clientSocket);
if (clientSockets.length === NODES_COUNT) {
done();
}
});
});
}
});
afterEach(() => {
for (const id in cluster.workers) {
cluster.workers[id].kill();
}
clientSockets.forEach((socket) => {
socket.disconnect();
});
});
describe("broadcast", function () {
it("broadcasts to all clients", (done) => {
const partialDone = times(3, done);
clientSockets.forEach((clientSocket) => {
clientSocket.on("test", (arg1, arg2, arg3) => {
expect(arg1).to.eql(1);
expect(arg2).to.eql("2");
expect(Buffer.isBuffer(arg3)).to.be(true);
partialDone();
});
});
workers[0].send("broadcasts to all clients");
});
it("broadcasts to all clients in a namespace", (done) => {
const partialDone = times(3, done);
const onConnect = times(3, async () => {
workers[0].send("broadcasts to all clients in a namespace");
});
clientSockets.forEach((clientSocket) => {
const socket = clientSocket.io.socket("/custom");
socket.on("connect", onConnect);
socket.on("test", () => {
socket.disconnect();
partialDone();
});
});
});
it("broadcasts to all clients in a room", (done) => {
workers[1].send("join room1");
clientSockets[0].on("test", () => {
done(new Error("should not happen"));
});
clientSockets[1].on("test", () => {
done();
});
clientSockets[2].on("test", () => {
done(new Error("should not happen"));
});
workers[0].send("broadcasts to all clients in a room");
});
it("broadcasts to all clients except in room", (done) => {
const partialDone = times(2, done);
workers[1].send("join room1");
clientSockets[0].on("test", () => {
partialDone();
});
clientSockets[1].on("test", () => {
done(new Error("should not happen"));
});
clientSockets[2].on("test", () => {
partialDone();
});
workers[0].send("broadcasts to all clients except in room");
});
it("broadcasts to local clients only", (done) => {
clientSockets[0].on("test", () => {
done();
});
clientSockets[1].on("test", () => {
done(new Error("should not happen"));
});
clientSockets[2].on("test", () => {
done(new Error("should not happen"));
});
workers[0].send("broadcasts to local clients only");
});
it("broadcasts with multiple acknowledgements", (done) => {
clientSockets[0].on("test", (cb) => {
cb(1);
});
clientSockets[1].on("test", (cb) => {
cb(2);
});
clientSockets[2].on("test", (cb) => {
cb(3);
});
workers[0].send("broadcasts with multiple acknowledgements");
workers[0].on("message", (result) => {
if (result === "ok") {
done();
}
});
});
it("broadcasts with multiple acknowledgements (binary content)", (done) => {
clientSockets[0].on("test", (cb) => {
cb(Buffer.from([1]));
});
clientSockets[1].on("test", (cb) => {
cb(Buffer.from([2]));
});
clientSockets[2].on("test", (cb) => {
cb(Buffer.from([3]));
});
workers[0].send(
"broadcasts with multiple acknowledgements (binary content)",
);
workers[0].on("message", (result) => {
if (result === "ok") {
done();
}
});
});
it("broadcasts with multiple acknowledgements (no client)", (done) => {
workers[0].send("broadcasts with multiple acknowledgements (no client)");
workers[0].on("message", (result) => {
if (result === "ok") {
done();
}
});
});
it("broadcasts with multiple acknowledgements (timeout)", (done) => {
clientSockets[0].on("test", (cb) => {
cb(1);
});
clientSockets[1].on("test", (cb) => {
cb(2);
});
clientSockets[2].on("test", (cb) => {
// do nothing
});
workers[0].send("broadcasts with multiple acknowledgements (timeout)");
workers[0].on("message", (result) => {
if (result === "ok") {
done();
}
});
});
});
describe("socketsJoin", () => {
it("makes all socket instances join the specified room", async () => {
workers[0].send("makes all socket instances join the specified room");
await sleep(100);
expect((await getRooms(workers[0])).has("room1")).to.be(true);
expect((await getRooms(workers[1])).has("room1")).to.be(true);
expect((await getRooms(workers[2])).has("room1")).to.be(true);
});
it("makes the matching socket instances join the specified room", async () => {
workers[0].send("join room1");
workers[2].send("join room1");
workers[0].send(
"makes the matching socket instances join the specified room",
);
await sleep(100);
expect((await getRooms(workers[0])).has("room2")).to.be(true);
expect((await getRooms(workers[1])).has("room2")).to.be(false);
expect((await getRooms(workers[2])).has("room2")).to.be(true);
});
});
describe("socketsLeave", () => {
it("makes all socket instances leave the specified room", async () => {
workers[0].send("join room1");
workers[2].send("join room1");
workers[0].send("makes all socket instances leave the specified room");
await sleep(100);
expect((await getRooms(workers[0])).has("room1")).to.be(false);
expect((await getRooms(workers[1])).has("room1")).to.be(false);
expect((await getRooms(workers[2])).has("room1")).to.be(false);
});
it("makes the matching socket instances leave the specified room", async () => {
workers[0].send("join room1 & room2");
workers[2].send("join room2");
workers[0].send(
"makes the matching socket instances leave the specified room",
);
await sleep(100);
expect((await getRooms(workers[0])).has("room2")).to.be(false);
expect((await getRooms(workers[1])).has("room2")).to.be(false);
expect((await getRooms(workers[2])).has("room2")).to.be(true);
});
});
describe("disconnectSockets", () => {
it("makes all socket instances disconnect", (done) => {
const partialDone = times(3, done);
clientSockets.forEach((clientSocket) => {
clientSocket.on("disconnect", (reason) => {
expect(reason).to.eql("io server disconnect");
partialDone();
});
});
workers[0].send("makes all socket instances disconnect");
});
});
describe("fetchSockets", () => {
it("returns all socket instances", (done) => {
workers[0].send("returns all socket instances");
workers[0].on("message", (result) => {
if (result === "ok") {
done();
}
});
});
});
describe("serverSideEmit", () => {
it("sends an event to other server instances", (done) => {
const partialDone = times(2, done);
workers[0].send("sends an event to other server instances");
workers[0].on("message", (result) => {
if (result === "ok") {
done(new Error("should not happen"));
}
});
workers[1].on("message", (result) => {
expect(result).to.eql("ok");
partialDone();
});
workers[2].on("message", (result) => {
expect(result).to.eql("ok");
partialDone();
});
});
it("sends an event and receives a response from the other server instances", (done) => {
workers[0].send(
"sends an event and receives a response from the other server instances (1)",
);
workers[1].send(
"sends an event and receives a response from the other server instances (2)",
);
workers[2].send(
"sends an event and receives a response from the other server instances (3)",
);
workers[0].on("message", (result) => {
if (result === "ok") {
done();
}
});
});
it("sends an event but timeout if one server does not respond", function (done) {
this.timeout(6000); // currently not possible to configure the timeout delay
workers[0].send(
"sends an event but timeout if one server does not respond (1)",
);
workers[1].send(
"sends an event but timeout if one server does not respond (2)",
);
workers[2].send(
"sends an event but timeout if one server does not respond (3)",
);
workers[0].on("message", (result) => {
if (result === "ok") {
done();
}
});
});
});
});

View File

@@ -0,0 +1,13 @@
export function times(count: number, fn: () => void) {
let i = 0;
return () => {
i++;
if (i === count) {
fn();
}
};
}
export function sleep(duration: number) {
return new Promise((resolve) => setTimeout(resolve, duration));
}

View File

@@ -0,0 +1,182 @@
const { createServer } = require("http");
const { Server } = require("socket.io");
const { createAdapter } = require("..");
const httpServer = createServer();
const io = new Server(httpServer);
const expect = require("expect.js");
io.adapter(createAdapter());
let serverSocket;
io.on("connection", (socket) => {
serverSocket = socket;
});
const customNamespace = io.of("/custom");
process.on("message", async (msg) => {
switch (msg) {
case "broadcasts to all clients":
io.emit("test", 1, "2", Buffer.from([3, 4]));
break;
case "broadcasts to all clients in a namespace":
customNamespace.emit("test");
break;
case "join room1":
serverSocket.join("room1");
break;
case "join room1 & room2":
serverSocket.join(["room1", "room2"]);
break;
case "join room2":
serverSocket.join("room2");
break;
case "broadcasts to all clients in a room":
io.to("room1").emit("test");
break;
case "broadcasts to all clients except in room":
io.of("/").except("room1").emit("test");
break;
case "broadcasts to local clients only":
io.local.emit("test");
break;
case "broadcasts with multiple acknowledgements": {
io.timeout(500).emit("test", (err, responses) => {
expect(err).to.be(null);
expect(responses).to.contain(1);
expect(responses).to.contain(2);
expect(responses).to.contain(3);
setTimeout(() => {
expect(io.of("/").adapter.ackRequests.size).to.eql(0);
process.send("ok");
}, 500);
});
break;
}
case "broadcasts with multiple acknowledgements (binary content)": {
io.timeout(500).emit("test", (err, responses) => {
expect(err).to.be(null);
responses.forEach((response) => {
expect(Buffer.isBuffer(response)).to.be(true);
});
process.send("ok");
});
break;
}
case "broadcasts with multiple acknowledgements (no client)": {
io
.to("abc")
.timeout(500)
.emit("test", (err, responses) => {
expect(err).to.be(null);
expect(responses).to.eql([]);
process.send("ok");
});
break;
}
case "broadcasts with multiple acknowledgements (timeout)": {
io.timeout(500).emit("test", (err, responses) => {
expect(err).to.be.an(Error);
expect(responses).to.contain(1);
expect(responses).to.contain(2);
process.send("ok");
});
break;
}
case "get rooms":
process.send(serverSocket.rooms);
break;
case "makes all socket instances join the specified room":
io.socketsJoin("room1");
break;
case "makes the matching socket instances join the specified room":
io.in("room1").socketsJoin("room2");
break;
case "makes all socket instances leave the specified room":
io.socketsLeave("room1");
break;
case "makes the matching socket instances leave the specified room":
io.in("room1").socketsLeave("room2");
break;
case "makes all socket instances disconnect":
io.disconnectSockets();
break;
case "returns all socket instances":
const sockets = await io.fetchSockets();
expect(sockets).to.be.an(Array);
expect(sockets).to.have.length(3);
expect(io.of("/").adapter.requests.size).to.eql(0); // clean up
process.send("ok");
break;
case "sends an event to other server instances":
io.serverSideEmit("hello", "world", 1, "2");
break;
case "sends an event and receives a response from the other server instances (1)":
io.serverSideEmit("hello with ack", (err, response) => {
expect(err).to.be(null);
expect(response).to.be.an(Array);
expect(response).to.contain(2);
expect(response).to.contain("3");
process.send("ok");
});
break;
case "sends an event and receives a response from the other server instances (2)":
io.on("hello with ack", (cb) => {
cb(2);
});
break;
case "sends an event and receives a response from the other server instances (3)":
io.on("hello with ack", (cb) => {
cb("3");
});
break;
case "sends an event but timeout if one server does not respond (1)":
io.serverSideEmit("hello with ack", (err, response) => {
expect(err.message).to.be(
"timeout reached: missing 1 responses"
);
expect(response).to.be.an(Array);
expect(response).to.contain(2);
process.send("ok");
});
break;
case "sends an event but timeout if one server does not respond (2)":
io.on("hello with ack", (cb) => {
cb(2);
});
break;
case "sends an event but timeout if one server does not respond (3)":
io.on("hello with ack", (cb) => {
// do nothing
});
break;
}
});
io.on("hello", (arg1, arg2, arg3) => {
expect(arg1).to.eql("world");
expect(arg2).to.eql(1);
expect(arg3).to.eql("2");
process.send("ok");
});
httpServer.listen(parseInt(process.env.PORT, 10));

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"outDir": "./dist",
"allowJs": false,
"target": "es2017",
"module": "commonjs",
"declaration": true,
"esModuleInterop": true
},
"include": [
"./lib/**/*"
]
}

View File

@@ -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 { randomBytes } from "node:crypto";
import { setTimeout, clearTimeout } from "node:timers"; import { setTimeout, clearTimeout } from "node:timers";
import { type IncomingMessage } from "node:http"; import { type IncomingMessage } from "node:http";
@@ -392,9 +398,9 @@ export abstract class ClusterEngine extends Server {
override verify( override verify(
req: IncomingMessage & { _query: Record<string, string> }, req: IncomingMessage & { _query: Record<string, string> },
upgrade: boolean, upgrade: boolean,
fn: (errorCode?: number, context?: any) => void, fn: ErrorCallback,
): void { ): void {
super.verify(req, upgrade, (errorCode: number, errorContext: any) => { super.verify(req, upgrade, (errorCode, errorContext) => {
if (errorCode !== Server.errors.UNKNOWN_SID) { if (errorCode !== Server.errors.UNKNOWN_SID) {
return fn(errorCode, errorContext); return fn(errorCode, errorContext);
} }
@@ -412,7 +418,7 @@ export abstract class ClusterEngine extends Server {
req[kSenderId] = senderId; req[kSenderId] = senderId;
fn(); fn();
} else { } else {
const transport = this.createTransport(transportName, req); const transport = this.createTransport(transportName as any, req);
this._hookTransport(sid, transport, lockType, senderId); this._hookTransport(sid, transport, lockType, senderId);
transport.onRequest(req); transport.onRequest(req);
} }

View File

@@ -19,14 +19,14 @@
], ],
"dependencies": { "dependencies": {
"@msgpack/msgpack": "~2.8.0", "@msgpack/msgpack": "~2.8.0",
"debug": "~4.3.3", "debug": "~4.4.1",
"engine.io": "~6.6.0", "engine.io": "~6.6.0",
"engine.io-parser": "~5.2.3" "engine.io-parser": "~5.2.3"
}, },
"scripts": { "scripts": {
"compile": "rimraf ./dist && tsc", "compile": "rimraf ./dist && tsc",
"test": "npm run format:check && npm run compile && npm run test:unit", "test": "npm run format:check && npm run compile && npm run test:unit",
"test:unit": "mocha --require ts-node/register test/*.ts", "test:unit": "mocha --import=tsx test/*.ts",
"format:check": "prettier --check \"lib/**/*.ts\" \"test/**/*.ts\"", "format:check": "prettier --check \"lib/**/*.ts\" \"test/**/*.ts\"",
"format:fix": "prettier --write \"lib/**/*.ts\" \"test/**/*.ts\"", "format:fix": "prettier --write \"lib/**/*.ts\" \"test/**/*.ts\"",
"prepack": "npm run compile" "prepack": "npm run compile"

View File

@@ -1,7 +1,8 @@
# History # Changelog
| Version | Release date | | 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 | | [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 | | [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 | | [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 |
@@ -33,7 +34,11 @@
| [3.3.0](#330-2018-11-07) | November 2018 | | [3.3.0](#330-2018-11-07) | November 2018 |
# Release notes ## [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)
This release contains a bump of `debug` from `~4.3.1` to `~4.4.1`.
## [3.3.4](https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4) (2024-07-22) ## [3.3.4](https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4) (2024-07-22)

View File

@@ -1,6 +1,6 @@
{ {
"name": "socket.io-parser", "name": "socket.io-parser",
"version": "4.2.4", "version": "4.2.5",
"description": "socket.io protocol parser", "description": "socket.io protocol parser",
"homepage": "https://github.com/socketio/socket.io/tree/main/packages/socket.io-client#readme", "homepage": "https://github.com/socketio/socket.io/tree/main/packages/socket.io-client#readme",
"repository": { "repository": {
@@ -26,7 +26,7 @@
}, },
"dependencies": { "dependencies": {
"@socket.io/component-emitter": "~3.1.0", "@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1" "debug": "~4.4.1"
}, },
"scripts": { "scripts": {
"compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh", "compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh",

View File

@@ -53,7 +53,7 @@ if (process.env.CI === "true") {
{ {
browserName: "internet explorer", browserName: "internet explorer",
browserVersion: "10", browserVersion: "10",
platformName: "Windows 7", platformName: "Windows 8",
"sauce:options": BASE_SAUCE_OPTIONS, "sauce:options": BASE_SAUCE_OPTIONS,
}, },
{ {

View File

@@ -0,0 +1,19 @@
# History
| Version | Release date |
|--------------------------|----------------|
| [0.1.1](#011-2025-09-05) | September 2025 |
| [0.1.0](#010-2021-06-14) | June 2021 |
# Release notes
## 0.1.1 (2025-09-05)
### Bug Fixes
* use parameterized query to send the NOTIFY command ([32257b6](https://github.com/socketio/socket.io/commit/32257b6cb89f9dac15e69e1d6ee76365ff262170))
## 0.1.0 (2021-06-14)
Initial release!

View File

@@ -0,0 +1,7 @@
Copyright (c) 2021 Damien Arrachequesne (@darrachequesne)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,152 @@
# Socket.IO Postgres emitter
The `@socket.io/postgres-emitter` package allows you to easily communicate with a group of Socket.IO servers from another Node.js process (server-side).
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./assets/emitter_dark.png">
<img alt="Diagram of Socket.IO packets forwarded through PostgreSQL" src="./assets/emitter.png">
</picture>
It must be used in conjunction with [`@socket.io/postgres-adapter`](https://github.com/socketio/socket.io-postgres-adapter/).
Supported features:
- [broadcasting](https://socket.io/docs/v4/broadcasting-events/)
- [utility methods](https://socket.io/docs/v4/server-instance/#Utility-methods)
- [`socketsJoin`](https://socket.io/docs/v4/server-instance/#socketsJoin)
- [`socketsLeave`](https://socket.io/docs/v4/server-instance/#socketsLeave)
- [`disconnectSockets`](https://socket.io/docs/v4/server-instance/#disconnectSockets)
- [`serverSideEmit`](https://socket.io/docs/v4/server-instance/#serverSideEmit)
Related packages:
- Postgres adapter: https://github.com/socketio/socket.io-postgres-adapter/
- Redis adapter: https://github.com/socketio/socket.io-redis-adapter/
- Redis emitter: https://github.com/socketio/socket.io-redis-emitter/
- MongoDB adapter: https://github.com/socketio/socket.io-mongo-adapter/
- MongoDB emitter: https://github.com/socketio/socket.io-mongo-emitter/
**Table of contents**
- [Installation](#installation)
- [Usage](#usage)
- [API](#api)
- [Known errors](#known-errors)
- [License](#license)
## Installation
```
npm install @socket.io/postgres-emitter pg
```
For TypeScript users, you might also need `@types/pg`.
## Usage
```js
const { Emitter } = require("@socket.io/postgres-emitter");
const { Pool } = require("pg");
const pool = new Pool({
user: "postgres",
host: "localhost",
database: "postgres",
password: "changeit",
port: 5432,
});
const io = new Emitter(pool);
setInterval(() => {
io.emit("ping", new Date());
}, 1000);
```
## API
### `Emitter(pool[, nsp][, opts])`
```js
const io = new Emitter(pool);
```
The `pool` argument is a [Pool object](https://node-postgres.com/api/pool) from the `pg` package.
### `Emitter#to(room:string):BroadcastOperator`
### `Emitter#in(room:string):BroadcastOperator`
Specifies a specific `room` that you want to emit to.
```js
io.to("room1").emit("hello");
```
### `Emitter#except(room:string):BroadcastOperator`
Specifies a specific `room` that you want to exclude from broadcasting.
```js
io.except("room2").emit("hello");
```
### `Emitter#of(namespace:string):Emitter`
Specifies a specific namespace that you want to emit to.
```js
const customNamespace = io.of("/custom");
customNamespace.emit("hello");
```
### `Emitter#socketsJoin(rooms:string|string[])`
Makes the matching socket instances join the specified rooms:
```js
// make all Socket instances join the "room1" room
io.socketsJoin("room1");
// make all Socket instances of the "admin" namespace in the "room1" room join the "room2" room
io.of("/admin").in("room1").socketsJoin("room2");
```
### `Emitter#socketsLeave(rooms:string|string[])`
Makes the matching socket instances leave the specified rooms:
```js
// make all Socket instances leave the "room1" room
io.socketsLeave("room1");
// make all Socket instances of the "admin" namespace in the "room1" room leave the "room2" room
io.of("/admin").in("room1").socketsLeave("room2");
```
### `Emitter#disconnectSockets(close:boolean)`
Makes the matching socket instances disconnect:
```js
// make all Socket instances disconnect
io.disconnectSockets();
// make all Socket instances of the "admin" namespace in the "room1" room disconnect
io.of("/admin").in("room1").disconnectSockets();
// this also works with a single socket ID
io.of("/admin").in(theSocketId).disconnectSockets();
```
### `Emitter#serverSideEmit(ev:string[,...args:any[]])`
Emits an event that will be received by each Socket.IO server of the cluster.
```js
io.serverSideEmit("ping");
```
## License
[MIT](LICENSE)

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@@ -0,0 +1,7 @@
services:
postgres:
image: postgres:14
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: "changeit"

View File

@@ -0,0 +1,536 @@
import debugModule from "debug";
import type {
DefaultEventsMap,
EventNames,
EventParams,
EventsMap,
TypedEventBroadcaster,
} from "./typed-events";
import { encode } from "@msgpack/msgpack";
const debug = debugModule("socket.io-postgres-emitter");
const EMITTER_UID = "emitter";
const hasBinary = (obj: any, toJSON?: boolean): boolean => {
if (!obj || typeof obj !== "object") {
return false;
}
if (obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) {
return true;
}
if (Array.isArray(obj)) {
for (let i = 0, l = obj.length; i < l; i++) {
if (hasBinary(obj[i])) {
return true;
}
}
return false;
}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {
return true;
}
}
if (obj.toJSON && typeof obj.toJSON === "function" && !toJSON) {
return hasBinary(obj.toJSON(), true);
}
return false;
};
/**
* Event types, for messages between nodes
*/
enum EventType {
INITIAL_HEARTBEAT = 1,
HEARTBEAT,
BROADCAST,
SOCKETS_JOIN,
SOCKETS_LEAVE,
DISCONNECT_SOCKETS,
FETCH_SOCKETS,
FETCH_SOCKETS_RESPONSE,
SERVER_SIDE_EMIT,
SERVER_SIDE_EMIT_RESPONSE,
}
interface BroadcastFlags {
volatile?: boolean;
compress?: boolean;
}
export interface PostgresEmitterOptions {
/**
* The prefix of the notification channel
* @default "socket.io"
*/
channelPrefix: string;
/**
* The name of the table for payloads over the 8000 bytes limit or containing binary data
*/
tableName: string;
/**
* The threshold for the payload size in bytes (see https://www.postgresql.org/docs/current/sql-notify.html)
* @default 8000
*/
payloadThreshold: number;
}
export class Emitter<
EmitEvents extends EventsMap = DefaultEventsMap,
ServerSideEvents extends EventsMap = DefaultEventsMap,
> {
public readonly channel: string;
public readonly tableName: string;
public payloadThreshold: number;
constructor(
readonly pool: any,
readonly nsp: string = "/",
opts: Partial<PostgresEmitterOptions> = {},
) {
const channelPrefix = opts.channelPrefix || "socket.io";
this.channel = `${channelPrefix}#${nsp}`;
this.tableName = opts.tableName || "socket_io_attachments";
this.payloadThreshold = opts.payloadThreshold || 8000;
}
/**
* Return a new emitter for the given namespace.
*
* @param nsp - namespace
* @public
*/
public of(nsp: string): Emitter {
return new Emitter(this.pool, (nsp[0] !== "/" ? "/" : "") + nsp);
}
/**
* Emits to all clients.
*
* @return Always true
* @public
*/
public emit<Ev extends EventNames<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): true {
return new BroadcastOperator<EmitEvents, ServerSideEvents>(this).emit(
ev,
...args,
);
}
/**
* Targets a room when emitting.
*
* @param room
* @return BroadcastOperator
* @public
*/
public to(
room: string | string[],
): BroadcastOperator<EmitEvents, ServerSideEvents> {
return new BroadcastOperator(this).to(room);
}
/**
* Targets a room when emitting.
*
* @param room
* @return BroadcastOperator
* @public
*/
public in(
room: string | string[],
): BroadcastOperator<EmitEvents, ServerSideEvents> {
return new BroadcastOperator(this).in(room);
}
/**
* Excludes a room when emitting.
*
* @param room
* @return BroadcastOperator
* @public
*/
public except(
room: string | string[],
): BroadcastOperator<EmitEvents, ServerSideEvents> {
return new BroadcastOperator(this).except(room);
}
/**
* Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
* receive messages (because of network slowness or other issues, or because theyre connected through long polling
* and is in the middle of a request-response cycle).
*
* @return BroadcastOperator
* @public
*/
public get volatile(): BroadcastOperator<EmitEvents, ServerSideEvents> {
return new BroadcastOperator(this).volatile;
}
/**
* Sets the compress flag.
*
* @param compress - if `true`, compresses the sending data
* @return BroadcastOperator
* @public
*/
public compress(
compress: boolean,
): BroadcastOperator<EmitEvents, ServerSideEvents> {
return new BroadcastOperator(this).compress(compress);
}
/**
* Makes the matching socket instances join the specified rooms
*
* @param rooms
* @public
*/
public socketsJoin(rooms: string | string[]): void {
return new BroadcastOperator(this).socketsJoin(rooms);
}
/**
* Makes the matching socket instances leave the specified rooms
*
* @param rooms
* @public
*/
public socketsLeave(rooms: string | string[]): void {
return new BroadcastOperator(this).socketsLeave(rooms);
}
/**
* Makes the matching socket instances disconnect
*
* @param close - whether to close the underlying connection
* @public
*/
public disconnectSockets(close: boolean = false): void {
return new BroadcastOperator(this).disconnectSockets(close);
}
/**
* Send a packet to the Socket.IO servers in the cluster
*
* @param ev - the event name
* @param args - any number of serializable arguments
*/
public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
ev: Ev,
...args: EventParams<ServerSideEvents, Ev>
): void {
return new BroadcastOperator<EmitEvents, ServerSideEvents>(
this,
).serverSideEmit(ev, ...args);
}
}
export const RESERVED_EVENTS: ReadonlySet<string | Symbol> = new Set(<const>[
"connect",
"connect_error",
"disconnect",
"disconnecting",
"newListener",
"removeListener",
]);
export class BroadcastOperator<
EmitEvents extends EventsMap,
ServerSideEvents extends EventsMap,
> implements TypedEventBroadcaster<EmitEvents>
{
constructor(
private readonly emitter: Emitter,
private readonly rooms: Set<string> = new Set<string>(),
private readonly exceptRooms: Set<string> = new Set<string>(),
private readonly flags: BroadcastFlags = {},
) {}
/**
* Targets a room when emitting.
*
* @param room
* @return a new BroadcastOperator instance
* @public
*/
public to(
room: string | string[],
): BroadcastOperator<EmitEvents, ServerSideEvents> {
const rooms = new Set(this.rooms);
if (Array.isArray(room)) {
room.forEach((r) => rooms.add(r));
} else {
rooms.add(room);
}
return new BroadcastOperator(
this.emitter,
rooms,
this.exceptRooms,
this.flags,
);
}
/**
* Targets a room when emitting.
*
* @param room
* @return a new BroadcastOperator instance
* @public
*/
public in(
room: string | string[],
): BroadcastOperator<EmitEvents, ServerSideEvents> {
return this.to(room);
}
/**
* Excludes a room when emitting.
*
* @param room
* @return a new BroadcastOperator instance
* @public
*/
public except(
room: string | string[],
): BroadcastOperator<EmitEvents, ServerSideEvents> {
const exceptRooms = new Set(this.exceptRooms);
if (Array.isArray(room)) {
room.forEach((r) => exceptRooms.add(r));
} else {
exceptRooms.add(room);
}
return new BroadcastOperator(
this.emitter,
this.rooms,
exceptRooms,
this.flags,
);
}
/**
* Sets the compress flag.
*
* @param compress - if `true`, compresses the sending data
* @return a new BroadcastOperator instance
* @public
*/
public compress(
compress: boolean,
): BroadcastOperator<EmitEvents, ServerSideEvents> {
const flags = Object.assign({}, this.flags, { compress });
return new BroadcastOperator(
this.emitter,
this.rooms,
this.exceptRooms,
flags,
);
}
/**
* Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
* receive messages (because of network slowness or other issues, or because theyre connected through long polling
* and is in the middle of a request-response cycle).
*
* @return a new BroadcastOperator instance
* @public
*/
public get volatile(): BroadcastOperator<EmitEvents, ServerSideEvents> {
const flags = Object.assign({}, this.flags, { volatile: true });
return new BroadcastOperator(
this.emitter,
this.rooms,
this.exceptRooms,
flags,
);
}
private async publish(document: any) {
document.uid = EMITTER_UID;
try {
if (
[
EventType.BROADCAST,
EventType.SERVER_SIDE_EMIT,
EventType.SERVER_SIDE_EMIT_RESPONSE,
].includes(document.type) &&
hasBinary(document)
) {
return await this.publishWithAttachment(document);
}
const payload = JSON.stringify(document);
if (Buffer.byteLength(payload) > this.emitter.payloadThreshold) {
return await this.publishWithAttachment(document);
}
debug(
"sending event of type %s to channel %s",
document.type,
this.emitter.channel,
);
await this.emitter.pool.query("SELECT pg_notify($1, $2)", [
this.emitter.channel,
payload,
]);
} catch (err) {
// @ts-ignore
this.emit("error", err);
}
}
private async publishWithAttachment(document: any) {
const payload = encode(document);
debug(
"sending event of type %s with attachment to channel %s",
document.type,
this.emitter.channel,
);
const result = await this.emitter.pool.query(
`INSERT INTO ${this.emitter.tableName} (payload) VALUES ($1) RETURNING id;`,
[payload],
);
const attachmentId = result.rows[0].id;
const headerPayload = JSON.stringify({
uid: document.uid,
type: document.type,
attachmentId,
});
await this.emitter.pool.query("SELECT pg_notify($1, $2)", [
this.emitter.channel,
headerPayload,
]);
}
/**
* Emits to all clients.
*
* @return Always true
* @public
*/
public emit<Ev extends EventNames<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): true {
if (RESERVED_EVENTS.has(ev)) {
throw new Error(`"${String(ev)}" is a reserved event name`);
}
// set up packet object
const data = [ev, ...args];
const packet = {
type: 2, // EVENT
data: data,
nsp: this.emitter.nsp,
};
const opts = {
rooms: [...this.rooms],
flags: this.flags,
except: [...this.exceptRooms],
};
this.publish({
type: EventType.BROADCAST,
data: {
packet,
opts,
},
});
return true;
}
/**
* Makes the matching socket instances join the specified rooms
*
* @param rooms
* @public
*/
public socketsJoin(rooms: string | string[]): void {
this.publish({
type: EventType.SOCKETS_JOIN,
data: {
opts: {
rooms: [...this.rooms],
except: [...this.exceptRooms],
},
rooms: Array.isArray(rooms) ? rooms : [rooms],
},
});
}
/**
* Makes the matching socket instances leave the specified rooms
*
* @param rooms
* @public
*/
public socketsLeave(rooms: string | string[]): void {
this.publish({
type: EventType.SOCKETS_LEAVE,
data: {
opts: {
rooms: [...this.rooms],
except: [...this.exceptRooms],
},
rooms: Array.isArray(rooms) ? rooms : [rooms],
},
});
}
/**
* Makes the matching socket instances disconnect
*
* @param close - whether to close the underlying connection
* @public
*/
public disconnectSockets(close: boolean = false): void {
this.publish({
type: EventType.DISCONNECT_SOCKETS,
data: {
opts: {
rooms: [...this.rooms],
except: [...this.exceptRooms],
},
close,
},
});
}
/**
* Send a packet to the Socket.IO servers in the cluster
*
* @param ev - the event name
* @param args - any number of serializable arguments
*/
public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
ev: Ev,
...args: EventParams<ServerSideEvents, Ev>
): void {
const withAck = args.length && typeof args[args.length - 1] === "function";
if (withAck) {
throw new Error("Acknowledgements are not supported");
}
this.publish({
type: EventType.SERVER_SIDE_EMIT,
data: {
packet: [ev, ...args],
},
});
}
}

View File

@@ -0,0 +1,37 @@
/**
* An events map is an interface that maps event names to their value, which
* represents the type of the `on` listener.
*/
export interface EventsMap {
[event: string]: any;
}
/**
* The default events map, used if no EventsMap is given. Using this EventsMap
* is equivalent to accepting all event names, and any data.
*/
export interface DefaultEventsMap {
[event: string]: (...args: any[]) => void;
}
/**
* Returns a union type containing all the keys of an event map.
*/
export type EventNames<Map extends EventsMap> = keyof Map & (string | symbol);
/** The tuple type representing the parameters of an event listener */
export type EventParams<
Map extends EventsMap,
Ev extends EventNames<Map>,
> = Parameters<Map[Ev]>;
/**
* Interface for classes that aren't `EventEmitter`s, but still expose a
* strictly typed `emit` method.
*/
export interface TypedEventBroadcaster<EmitEvents extends EventsMap> {
emit<Ev extends EventNames<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): boolean;
}

View File

@@ -0,0 +1,33 @@
{
"name": "@socket.io/postgres-emitter",
"version": "0.1.1",
"description": "The Socket.IO Postgres emitter, allowing to communicate with a group of Socket.IO servers from another Node.js process",
"license": "MIT",
"homepage": "https://github.com/socketio/socket.io/tree/main/packages/socket.io-postgres-emitter#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/socketio/socket.io.git"
},
"files": [
"dist/"
],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"compile": "rimraf ./dist && tsc",
"test": "npm run format:check && npm run compile && nyc mocha --import=tsx --timeout 5000 test/index.ts",
"format:check": "prettier --parser typescript --check 'lib/**/*.ts' 'test/**/*.ts'",
"format:fix": "prettier --parser typescript --write 'lib/**/*.ts' 'test/**/*.ts'",
"prepack": "npm run compile"
},
"dependencies": {
"@msgpack/msgpack": "^2.7.0",
"debug": "~4.4.1"
},
"keywords": [
"socket.io",
"postgres",
"postgresql",
"emitter"
]
}

View File

@@ -0,0 +1,278 @@
import { createServer } from "http";
import { Server, Socket as ServerSocket } from "socket.io";
import { io as ioc, Socket as ClientSocket } from "socket.io-client";
import expect = require("expect.js");
import { createAdapter } from "@socket.io/postgres-adapter";
import type { AddressInfo } from "net";
import { Pool } from "pg";
import { times, sleep } from "./util";
import { Emitter } from "..";
const NODES_COUNT = 3;
describe("@socket.io/postgres-emitter", () => {
let servers: Server[],
serverSockets: ServerSocket[],
clientSockets: ClientSocket[],
pool: Pool,
emitter: Emitter;
beforeEach((done) => {
servers = [];
serverSockets = [];
clientSockets = [];
pool = new Pool({
user: "postgres",
host: "localhost",
database: "postgres",
password: "changeit",
port: 5432,
});
pool.query(
`
CREATE TABLE IF NOT EXISTS socket_io_attachments (
id bigserial UNIQUE,
created_at timestamptz DEFAULT NOW(),
payload bytea
);
`,
() => {},
);
emitter = new Emitter(pool);
for (let i = 1; i <= NODES_COUNT; i++) {
const httpServer = createServer();
const io = new Server(httpServer);
// @ts-ignore
io.adapter(createAdapter(pool));
httpServer.listen(() => {
const port = (httpServer.address() as AddressInfo).port;
const clientSocket = ioc(`http://localhost:${port}`);
io.on("connection", async (socket) => {
clientSockets.push(clientSocket);
serverSockets.push(socket);
servers.push(io);
if (servers.length === NODES_COUNT) {
// ensure all nodes know each other
servers[0].emit("ping");
servers[1].emit("ping");
servers[2].emit("ping");
await sleep(200);
done();
}
});
});
}
});
afterEach((done) => {
servers.forEach((server) => {
// @ts-ignore
server.httpServer.close();
server.of("/").adapter.close();
});
clientSockets.forEach((socket) => {
socket.disconnect();
});
pool.end(done);
});
describe("broadcast", function () {
it("broadcasts to all clients", (done) => {
const partialDone = times(3, done);
clientSockets.forEach((clientSocket) => {
clientSocket.on("test", (arg1, arg2, arg3) => {
expect(arg1).to.eql(1);
expect(arg2).to.eql("2");
expect(Buffer.isBuffer(arg3)).to.be(true);
partialDone();
});
});
emitter.emit("test", 1, "2", Buffer.from([3, 4]));
});
it("broadcasts to all clients in a namespace", (done) => {
const partialDone = times(3, () => {
servers.forEach((server) => server.of("/custom").adapter.close());
done();
});
servers.forEach((server) => server.of("/custom"));
const onConnect = times(3, async () => {
await sleep(200);
emitter.of("/custom").emit("test");
});
clientSockets.forEach((clientSocket) => {
const socket = clientSocket.io.socket("/custom");
socket.on("connect", onConnect);
socket.on("test", () => {
socket.disconnect();
partialDone();
});
});
});
it("broadcasts to all clients in a room", (done) => {
serverSockets[1].join("room1");
clientSockets[0].on("test", () => {
done(new Error("should not happen"));
});
clientSockets[1].on("test", () => {
done();
});
clientSockets[2].on("test", () => {
done(new Error("should not happen"));
});
emitter.to("room1").emit("test");
});
it("broadcasts to all clients except in room", (done) => {
const partialDone = times(2, done);
serverSockets[1].join("room1");
clientSockets[0].on("test", () => {
partialDone();
});
clientSockets[1].on("test", () => {
done(new Error("should not happen"));
});
clientSockets[2].on("test", () => {
partialDone();
});
emitter.of("/").except("room1").emit("test");
});
});
describe("socketsJoin", () => {
it("makes all socket instances join the specified room", async () => {
emitter.socketsJoin("room1");
await sleep(200);
expect(serverSockets[0].rooms.has("room1")).to.be(true);
expect(serverSockets[1].rooms.has("room1")).to.be(true);
expect(serverSockets[2].rooms.has("room1")).to.be(true);
});
it("makes the matching socket instances join the specified room", async () => {
serverSockets[0].join("room1");
serverSockets[2].join("room1");
emitter.in("room1").socketsJoin("room2");
await sleep(200);
expect(serverSockets[0].rooms.has("room2")).to.be(true);
expect(serverSockets[1].rooms.has("room2")).to.be(false);
expect(serverSockets[2].rooms.has("room2")).to.be(true);
});
it("makes the given socket instance join the specified room", async () => {
emitter.in(serverSockets[1].id).socketsJoin("room3");
await sleep(200);
expect(serverSockets[0].rooms.has("room3")).to.be(false);
expect(serverSockets[1].rooms.has("room3")).to.be(true);
expect(serverSockets[2].rooms.has("room3")).to.be(false);
});
});
describe("socketsLeave", () => {
it("makes all socket instances leave the specified room", async () => {
serverSockets[0].join("room1");
serverSockets[2].join("room1");
emitter.socketsLeave("room1");
await sleep(200);
expect(serverSockets[0].rooms.has("room1")).to.be(false);
expect(serverSockets[1].rooms.has("room1")).to.be(false);
expect(serverSockets[2].rooms.has("room1")).to.be(false);
});
it("makes the matching socket instances leave the specified room", async () => {
serverSockets[0].join(["room1", "room2"]);
serverSockets[1].join(["room1", "room2"]);
serverSockets[2].join(["room2"]);
emitter.in("room1").socketsLeave("room2");
await sleep(200);
expect(serverSockets[0].rooms.has("room2")).to.be(false);
expect(serverSockets[1].rooms.has("room2")).to.be(false);
expect(serverSockets[2].rooms.has("room2")).to.be(true);
});
it("makes the given socket instance leave the specified room", async () => {
serverSockets[0].join("room3");
serverSockets[1].join("room3");
serverSockets[2].join("room3");
emitter.in(serverSockets[1].id).socketsLeave("room3");
await sleep(200);
expect(serverSockets[0].rooms.has("room3")).to.be(true);
expect(serverSockets[1].rooms.has("room3")).to.be(false);
expect(serverSockets[2].rooms.has("room3")).to.be(true);
});
});
describe("disconnectSockets", () => {
it("makes all socket instances disconnect", (done) => {
const partialDone = times(3, done);
clientSockets.forEach((clientSocket) => {
clientSocket.on("disconnect", (reason) => {
expect(reason).to.eql("io server disconnect");
partialDone();
});
});
emitter.disconnectSockets();
});
});
describe("serverSideEmit", () => {
it("sends an event to other server instances", (done) => {
const partialDone = times(3, done);
emitter.serverSideEmit("hello", "world", 1, "2");
servers[0].on("hello", (arg1, arg2, arg3) => {
expect(arg1).to.eql("world");
expect(arg2).to.eql(1);
expect(arg3).to.eql("2");
partialDone();
});
servers[1].on("hello", (arg1, arg2, arg3) => {
partialDone();
});
servers[2].of("/").on("hello", () => {
partialDone();
});
});
});
});

View File

@@ -0,0 +1,13 @@
export function times(count: number, fn: () => void) {
let i = 0;
return () => {
i++;
if (i === count) {
fn();
}
};
}
export function sleep(duration: number) {
return new Promise((resolve) => setTimeout(resolve, duration));
}

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"outDir": "./dist",
"allowJs": false,
"target": "es2017",
"module": "commonjs",
"declaration": true
},
"include": [
"./lib/**/*"
]
}

Some files were not shown because too many files have changed in this diff Show More