Compare commits

...

43 Commits

Author SHA1 Message Date
Damien Arrachequesne
c251ae7ba7 chore(release): engine.io-client@6.6.1
Diff: https://github.com/socketio/socket.io/compare/engine.io-client@6.6.0...engine.io-client@6.6.1
2024-09-21 09:07:48 +02:00
Damien Arrachequesne
8a2f5a3da0 fix(eio-client): move 'offline' event listener at the top
Related: https://github.com/socketio/socket.io/issues/5125
2024-09-21 08:47:20 +02:00
Damien Arrachequesne
b04fa64365 fix(sio): allow to join a room in a middleware (uws)
Related:

- https://github.com/socketio/socket.io/issues/4810
- https://github.com/socketio/socket.io/issues/5139
2024-09-21 08:19:32 +02:00
Damien Arrachequesne
7085f0e3e4 refactor(sio-client): mangle private attributes
|          | before  | after   |
|----------|---------|---------|
| min+gzip | 14.6 KB | 14.3 KB |
| min+br   | 13.1 KB | 12.9 KB |

Reference: https://terser.org/docs/options/#mangle-properties-options
2024-09-21 07:48:50 +02:00
Damien Arrachequesne
4f66708210 chore(sio-client): use babel loose mode when transpiling classes
By default, Babel uses `Object.defineProperty()` when transpiling
classes. We'll now use the loose mode which creates a more terse
output.

|          | before  | after   |
|----------|---------|---------|
| min+gzip | 14.9 KB | 14.6 KB |
| min+br   | 13.4 KB | 13.1 KB |

Reference: https://babeljs.io/docs/babel-plugin-transform-classes
2024-09-21 07:44:15 +02:00
Damien Arrachequesne
1a95db2145 chore(sio-client): add a script to compute the bundle size 2024-09-21 07:34:49 +02:00
Damien Arrachequesne
282ae922a4 chore(sio-client): restore the debug package in the dev bundle
The debug package was not included anymore in the dev bundle since the
migration from webpack to rollup ([1]) in version 4.3.0.

[1]: 0661564dc2

Related: https://github.com/socketio/socket.io/issues/5108
2024-09-21 07:33:53 +02:00
Damien Arrachequesne
93010ca3c4 chore(eio-client): bump xmlhttprequest-ssl to version 2.1.1
Related:

- https://github.com/socketio/socket.io/issues/4402
- b01f69a689
2024-09-21 07:32:12 +02:00
Damien Arrachequesne
132d05fc0b fix(sio): expose type of default engine
Related: https://github.com/socketio/socket.io/issues/4693
2024-09-20 11:18:23 +02:00
Damien Arrachequesne
d5095fe98c fix(eio): prevent the client from upgrading twice (uws)
Related: https://github.com/socketio/socket.io/issues/5066
2024-09-19 12:13:42 +02:00
Damien Arrachequesne
da613810fd test(eio): bump uWebSockets.js to version 20.48.0 2024-09-19 12:12:58 +02:00
Damien Arrachequesne
19c48a44e6 refactor(sio): break circular dependency in source code
Related: https://github.com/socketio/socket.io/issues/4329
2024-09-19 09:27:12 +02:00
Damien Arrachequesne
9b3c9abeca fix(eio-client): only remove the event listener if it exists
Related: https://github.com/socketio/socket.io/issues/5088#issuecomment-2217202350
2024-09-19 09:26:26 +02:00
Damien Arrachequesne
043b55c418 refactor(sio): simplify middleware execution (bis) 2024-09-18 22:15:32 +02:00
Damien Arrachequesne
32c761f02f refactor(sio): export the ExtendedError type
Related: https://github.com/socketio/socket.io/issues/4798
2024-09-18 18:22:06 +02:00
Damien Arrachequesne
1f54ee08c6 refactor(sio): simplify middleware execution 2024-09-18 18:21:58 +02:00
Damien Arrachequesne
923a12e2de fix(eio): discard all pending packets when the server is closed
In some specific cases, the transport was not closed right away,
leaving the Node.js process alive even after closing the server.
The HTTP long-polling transport would be closed after the heartbeat
failure and the `closeTimeout` delay (20 + 25 + 30 seconds).

Example:

```js
io.on("connection", (socket) => {
  // the writeBuffer is not empty, so the transport is not closed right away
  io.close();
});
```

Related: https://github.com/socketio/socket.io/issues/5088
2024-09-18 18:17:14 +02:00
Damien Arrachequesne
13c6d2e89d fix(sio-client): allow to manually stop the reconnection loop
```js
socket.io.on("reconnect_attempt", () => {
  socket.io.reconnection(false); // will now work properly
});
```

Related: https://github.com/socketio/socket.io/issues/5126
2024-09-18 11:19:28 +02:00
Tom Jenkinson
8adcfbfde5 fix(sio-client): do not send a packet on an expired connection (#5134)
When a laptop is suspended or a phone is locked, the timer that is used
to check the liveness of the connection is paused and is not able to
detect that the heartbeat has failed.

Previously, emitting a message after resuming the page would lose the
message. The status of the timer will now be checked before sending the
message, so that it gets buffered and sent upon reconnection.

Note: we could also have used the Page Visibility API or a custom
setTimeout() method based on setInterval(), but this would not be as
reliable as the current solution.

Reference: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API

Related: https://github.com/socketio/socket.io/issues/5135
2024-09-18 11:11:22 +02:00
Damien Arrachequesne
7a23dde6ef perf: do not reset the hearbeat timer on each packet
This reverts ed34a45a07

See also: 5359bae683
2024-09-18 11:06:39 +02:00
Damien Arrachequesne
60c757f718 fix(sio-client): accept string | undefined as init argument (bis)
Following: 5a3eafed1c

Related: https://github.com/socketio/socket.io/issues/4873
2024-09-18 08:09:47 +02:00
Damien Arrachequesne
04c8dd979c fix(sio-client): close the engine upon decoding exception
Related: https://github.com/socketio/socket.io/issues/5128
2024-09-18 07:52:37 +02:00
Damien Arrachequesne
2194264820 test(eio-client): reduce test suite duration 2024-09-18 07:52:12 +02:00
Damien Arrachequesne
09f573cad8 test(sio-client): reduce test suite duration 2024-09-18 07:52:12 +02:00
Damien Arrachequesne
fcbecd4f46 ci: restore package-specific tests 2024-09-17 15:07:23 +02:00
Damien Arrachequesne
fd99f2e15f refactor(sio): export the DefaultEventsMap type
Related: https://github.com/socketio/socket.io/issues/4747
2024-09-16 15:49:15 +02:00
Damien Arrachequesne
02d59a0e99 chore: re-enable publish workflow
This reverts commit 7160eb7eb0.

[skip ci]
2024-09-16 09:11:35 +02:00
Damien Arrachequesne
7160eb7eb0 chore: temporarily disable publish workflow
In order to import tags from other repositories.

[skip ci]
2024-09-16 08:56:07 +02:00
Wang Guan
a1ccba3a77 chore: use prettier v3 everywhere (#5169) 2024-09-16 08:43:08 +02:00
Mark Nelissen
e347a3c24e fix(sio): correctly await async close on adapters (#4971)
Following: bf64870957
2024-09-14 08:51:08 +02:00
nayounsang
b5ccfd4838 refactor(eio-client): improve transports type (#5188)
Related: https://github.com/socketio/socket.io/issues/5187
2024-09-14 08:17:36 +02:00
Wang Guan
5d9a2d5544 chore(socket.io-client): bump engine.io-client to version 6.6.0 2024-08-23 23:19:45 +02:00
Wang Guan
d5b22f5a76 chore(socket.io): bump engine.io to version 6.6.0 2024-08-23 23:19:31 +02:00
Damien Arrachequesne
582655f679 test(cluster-engine): fix flaky test cleanup 2024-07-26 09:26:47 +02:00
KartikeSingh
b79d80aa59 docs: fix conjunction with fastify example (#5057)
[skip ci]
2024-07-22 11:32:58 +02:00
Damien Arrachequesne
7fd75e6aac docs(changelog): add changelog for socket.io-parser@3.3.4
Diff: https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4

[skip ci]
2024-07-22 11:28:47 +02:00
Damien Arrachequesne
b7577556e3 docs: add example with NestJS
Reference: https://docs.nestjs.com/websockets/gateways

[skip ci]
2024-07-22 10:07:45 +02:00
Damien Arrachequesne
6e9bff4fcf docs: add example with @socket.io/postgres-adapter
[skip ci]
2024-07-19 15:20:12 +02:00
Damien Arrachequesne
8b0a40fd4a docs: add examples with @socket.io/cluster-engine
[skip ci]
2024-07-19 08:42:38 +02:00
Damien Arrachequesne
1f09a3e979 ci(publish): compile all packages before publishing
As some packages depend on the types of others.

[skip ci]
2024-07-18 10:07:47 +02:00
Damien Arrachequesne
0af50758f6 chore(cluster-engine): update homepage URL 2024-07-18 10:03:57 +02:00
Damien Arrachequesne
b9b16132c2 chore(socket.io-adapter): remove dist before compilation 2024-07-17 10:34:57 +02:00
Damien Arrachequesne
be1e4cb24d ci(publish): update the tag regex
In order to also match "@socket.io/some-package@1.2.3".

Reference: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet

[skip ci]
2024-07-17 10:26:56 +02:00
144 changed files with 2640 additions and 1339 deletions

View File

@@ -14,7 +14,7 @@ permissions:
jobs:
test-browser:
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 20
steps:
- name: Checkout repository

View File

@@ -44,13 +44,16 @@ jobs:
- name: Install dependencies
run: npm ci
# in order to test our compliance with TypeScript v4.2 (older versions are not tested)
- name: Install TypeScript 4.2
run: npm i typescript@4.2
if: ${{ matrix.node-version == '16' }}
- name: Compile each package
run: npm run compile --workspaces --if-present
- name: Run tests
run: npm test --workspaces
- name: Run tests with uws (engine.io)
run: npm run test:uws --workspace=engine.io
if: ${{ matrix.node-version == '18' }}
- name: Run tests with fetch instead of XHR (engine.io-client)
run: npm run test:node-fetch --workspace=engine.io-client
if: ${{ matrix.node-version == '18' }}

View File

@@ -6,7 +6,7 @@ on:
push:
tags:
# expected format: <package>@<version> (example: socket.io@1.2.3)
- '*@*'
- '**@*'
jobs:
publish:
@@ -28,6 +28,9 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Compile each package
run: npm run compile --workspaces --if-present
- name: Publish package
run: npm publish --workspace=${GITHUB_REF_NAME%@*} --provenance --access public
env:

View File

@@ -150,3 +150,18 @@ For a specific workspace:
```bash
npm test --workspace=socket.io
```
### Generate the changelog
Install the [`conventional-changelog-cli`](https://www.npmjs.com/package/conventional-changelog-cli) package:
```bash
npm i -g conventional-changelog-cli
```
Then run:
```bash
cd packages/engine.io-client
conventional-changelog -p angular --tag-prefix "engine.io-client@" --commit-path .
```

View File

@@ -0,0 +1,19 @@
# Example with `@socket.io/cluster-engine` and Node.js cluster
## How to use
```bash
# run the server
$ node server.js
# run the client
$ node client.js
```
## Explanation
The `server.js` script will create one Socket.IO server per core, each listening on the same port (`3000`).
With the default engine (provided by the `engine.io` package), sticky sessions would be required, so that each HTTP request of the same Engine.IO session reaches the same worker.
The `NodeClusterEngine` is a custom engine which takes care of the synchronization between the servers by using [the IPC channel](https://nodejs.org/api/cluster.html#workersendmessage-sendhandle-options-callback) and removes the need for sticky sessions when scaling horizontally.

View File

@@ -0,0 +1,26 @@
import { io } from "socket.io-client";
const CLIENTS_COUNT = 3;
for (let i = 0; i < CLIENTS_COUNT; i++) {
const socket = io("ws://localhost:3000/", {
// transports: ["polling"],
// transports: ["websocket"],
});
socket.on("connect", () => {
console.log(`connected as ${socket.id}`);
});
socket.on("disconnect", (reason) => {
console.log(`disconnected due to ${reason}`);
});
socket.on("hello", (socketId, workerId) => {
console.log(`received "hello" from ${socketId} (worker: ${workerId})`);
});
setInterval(() => {
socket.emit("hello");
}, 2000);
}

View File

@@ -0,0 +1,12 @@
{
"private": true,
"name": "cluster-engine-node-cluster",
"version": "0.0.1",
"type": "module",
"dependencies": {
"@socket.io/cluster-adapter": "^0.2.2",
"@socket.io/cluster-engine": "^0.1.0",
"socket.io": "^4.7.5",
"socket.io-client": "^4.7.5"
}
}

View File

@@ -0,0 +1,63 @@
import cluster from "node:cluster";
import process from "node:process";
import { availableParallelism } from "node:os";
import {
setupPrimary as setupPrimaryEngine,
NodeClusterEngine,
} from "@socket.io/cluster-engine";
import {
setupPrimary as setupPrimaryAdapter,
createAdapter,
} from "@socket.io/cluster-adapter";
import { createServer } from "node:http";
import { Server } from "socket.io";
if (cluster.isPrimary) {
console.log(`Primary ${process.pid} is running`);
const numCPUs = availableParallelism();
// fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
setupPrimaryEngine();
setupPrimaryAdapter();
// needed for packets containing Buffer objects (you can ignore it if you only send plaintext objects)
cluster.setupPrimary({
serialization: "advanced",
});
cluster.on("exit", (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
const httpServer = createServer((req, res) => {
res.writeHead(404).end();
});
const engine = new NodeClusterEngine();
engine.attach(httpServer, {
path: "/socket.io/",
});
const io = new Server({
adapter: createAdapter(),
});
io.bind(engine);
io.on("connection", (socket) => {
socket.on("hello", () => {
socket.broadcast.emit("hello", socket.id, process.pid);
});
});
// workers will share the same port
httpServer.listen(3000);
console.log(`Worker ${process.pid} started`);
}

View File

@@ -0,0 +1,22 @@
# Example with `@socket.io/cluster-engine` and Redis
## How to use
```bash
# start the redis server
$ docker compose up -d
# run the server
$ node server.js
# run the client
$ node client.js
```
## Explanation
The `server.js` script will create 3 Socket.IO servers, each listening on a distinct port (`3001`, `3002` and `3003`), and a proxy server listening on port `3000` which randomly redirects to one of those servers.
With the default engine (provided by the `engine.io` package), sticky sessions would be required, so that each HTTP request of the same Engine.IO session reaches the same server.
The `RedisEngine` is a custom engine which takes care of the synchronization between the servers by using [Redis pub/sub](https://redis.io/docs/latest/develop/interact/pubsub/) and removes the need for sticky sessions when scaling horizontally.

View File

@@ -0,0 +1,26 @@
import { io } from "socket.io-client";
const CLIENTS_COUNT = 3;
for (let i = 0; i < CLIENTS_COUNT; i++) {
const socket = io("ws://localhost:3000/", {
// transports: ["polling"],
// transports: ["websocket"],
});
socket.on("connect", () => {
console.log(`connected as ${socket.id}`);
});
socket.on("disconnect", (reason) => {
console.log(`disconnected due to ${reason}`);
});
socket.on("hello", (socketId, workerId) => {
console.log(`received "hello" from ${socketId} (worker: ${workerId})`);
});
setInterval(() => {
socket.emit("hello");
}, 2000);
}

View File

@@ -0,0 +1,5 @@
services:
redis:
image: redis:7
ports:
- "6379:6379"

View File

@@ -0,0 +1,14 @@
{
"private": true,
"name": "cluster-engine-redis",
"version": "0.0.1",
"type": "module",
"dependencies": {
"@socket.io/cluster-engine": "^0.1.0",
"@socket.io/redis-adapter": "^8.3.0",
"http-proxy": "^1.18.1",
"redis": "^4.6.15",
"socket.io": "^4.7.5",
"socket.io-client": "^4.7.5"
}
}

View File

@@ -0,0 +1,65 @@
import { RedisEngine } from "@socket.io/cluster-engine";
import { createServer } from "node:http";
import { createClient } from "redis";
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import proxyModule from "http-proxy";
const { createProxyServer } = proxyModule;
async function initServer(port) {
const httpServer = createServer((req, res) => {
res.writeHead(404).end();
});
const pubClient = createClient();
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
const engine = new RedisEngine(pubClient, subClient);
engine.attach(httpServer, {
path: "/socket.io/",
});
const io = new Server({
adapter: createAdapter(pubClient, subClient),
});
io.bind(engine);
io.on("connection", (socket) => {
socket.on("hello", () => {
socket.broadcast.emit("hello", socket.id, port);
});
});
httpServer.listen(port);
}
function initProxy() {
const proxy = createProxyServer();
function randomTarget() {
return [
"http://localhost:3001",
"http://localhost:3002",
"http://localhost:3003",
][Math.floor(Math.random() * 3)];
}
const httpServer = createServer((req, res) => {
proxy.web(req, res, { target: randomTarget() });
});
httpServer.on("upgrade", function (req, socket, head) {
proxy.ws(req, socket, head, { target: randomTarget() });
});
httpServer.listen(3000);
}
await Promise.all([initServer(3001), initServer(3002), initServer(3003)]);
initProxy();

View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

56
examples/nestjs-example/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View File

@@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

View File

@@ -0,0 +1,72 @@
{
"name": "nestjs-example",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-socket.io": "^10.3.10",
"@nestjs/websockets": "^10.3.10",
"hbs": "^4.2.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

View File

@@ -0,0 +1,13 @@
import { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@Render('index')
root() {
return { message: 'Hello world2!' };
}
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventsModule } from './events/events.module';
@Module({
imports: [EventsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@@ -0,0 +1,18 @@
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway({})
export class EventsGateway {
@WebSocketServer()
io: Server;
@SubscribeMessage('hello')
handleEvent(@MessageBody() data: string): string {
return data.split('').reverse().join('');
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { EventsGateway } from './events.gateway';
@Module({
providers: [EventsGateway],
})
export class EventsModule {}

View File

@@ -0,0 +1,15 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'node:path';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs');
await app.listen(3000);
}
bootstrap();

View File

@@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>App</title>
</head>
<body>
<p>Status: <span id="status"></span></p>
<p>Transport: <span id="transport"></span></p>
<script src="/socket.io/socket.io.js"></script>
<script>
const statusSpan = document.getElementById("status");
const transportSpan = document.getElementById("transport");
const socket = io({
// transports: ["polling"],
// transports: ["websocket"],
});
statusSpan.innerText = "Disconnected";
transportSpan.innerText = "N/A";
socket.on("connect", () => {
statusSpan.innerText = "Connected";
transportSpan.innerText = socket.io.engine.transport.name;
socket.io.engine.on("upgrade", (transport) => {
transportSpan.innerText = transport.name;
});
console.log(`connect ${socket.id}`);
socket.emit("hello", "world", (val) => {
console.log(`got ${val}`);
});
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err.message}`);
});
socket.on("disconnect", (reason) => {
statusSpan.innerText = "Disconnected";
transportSpan.innerText = "N/A";
console.log(`disconnect due to ${reason}`);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,25 @@
# Example with `@socket.io/postgres-adapter`
**Table of contents**
<!-- TOC -->
* [How to use](#how-to-use)
* [Documentation](#documentation)
<!-- TOC -->
## How to use
```bash
# start the postgres server
$ docker compose up -d
# run the cluster
$ node cluster.js
# run the client
$ node client.js
```
## Documentation
The documentation can be found here: https://socket.io/docs/v4/postgres-adapter/

View File

@@ -0,0 +1,31 @@
import { io } from "socket.io-client";
const CLIENTS_COUNT = 3;
const PORTS = [3000, 3001, 3002];
for (let i = 0; i < CLIENTS_COUNT; i++) {
const socket = io(`ws://localhost:${PORTS[i % 3]}`, {
// transports: ["polling"],
// transports: ["websocket"],
});
socket.on("connect", () => {
console.log(`connected as ${socket.id}`);
});
socket.on("connect_error", () => {
console.log(`connect_error`);
});
socket.on("disconnect", (reason) => {
console.log(`disconnected due to ${reason}`);
});
socket.on("hello", (socketId, pid) => {
console.log(`received "hello" from ${socketId} (process: ${pid})`);
});
setInterval(() => {
socket.emit("hello");
}, 2000);
}

View File

@@ -0,0 +1,13 @@
import cluster from 'node:cluster';
const SERVERS_COUNT = 3;
cluster.setupPrimary({
exec: 'server.js',
});
for (let i = 0; i < SERVERS_COUNT; i++) {
cluster.fork({
PORT: 3000 + i
});
}

View File

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

View File

@@ -0,0 +1,12 @@
{
"private": true,
"name": "postgres-adapter-example",
"version": "0.0.1",
"type": "module",
"dependencies": {
"@socket.io/postgres-adapter": "^0.4.0",
"pg": "^8.12.0",
"socket.io": "^4.7.5",
"socket.io-client": "^4.7.5"
}
}

View File

@@ -0,0 +1,40 @@
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/postgres-adapter";
import pg from "pg";
import process from "node:process";
const PORT = process.env.PORT || 3000;
const pool = new pg.Pool({
user: "postgres",
host: "localhost",
database: "postgres",
password: "changeit",
port: 5432,
});
await pool.query(`
CREATE TABLE IF NOT EXISTS socket_io_attachments (
id bigserial UNIQUE,
created_at timestamptz DEFAULT NOW(),
payload bytea
);
`);
pool.on("error", (err) => {
console.error("Postgres error", err);
});
const io = new Server({
adapter: createAdapter(pool)
});
io.on("connection", (socket) => {
socket.on("hello", () => {
// send to anyone except the sender
socket.broadcast.emit("hello", socket.id, process.pid);
});
});
io.listen(PORT);
console.log(`server listening on port ${PORT}`);

95
package-lock.json generated
View File

@@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
"name": "socket.io",
"workspaces": [
"packages/socket.io-component-emitter",
"packages/engine.io-parser",
@@ -51,7 +52,7 @@
"mocha": "^10.6.0",
"node-forge": "^1.3.1",
"nyc": "^17.0.0",
"prettier": "^2.8.8",
"prettier": "^3.3.2",
"redis": "^4.6.15",
"rimraf": "^6.0.0",
"rollup": "^2.79.1",
@@ -66,7 +67,7 @@
"ts-node": "^10.9.2",
"tsd": "^0.31.1",
"typescript": "^5.5.3",
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.30.0",
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0",
"wdio-geckodriver-service": "^5.0.2"
}
},
@@ -11320,15 +11321,15 @@
}
},
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=10.13.0"
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
@@ -14206,9 +14207,10 @@
}
},
"node_modules/uWebSockets.js": {
"version": "20.30.0",
"resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142",
"dev": true
"version": "20.48.0",
"resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#51ae1d1fd92dff77cbbdc7c431021f85578da1a6",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
@@ -14945,14 +14947,6 @@
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -15194,34 +15188,24 @@
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
"xmlhttprequest-ssl": "~2.1.1"
}
},
"packages/engine.io-client/node_modules/xmlhttprequest-ssl": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
"engines": {
"node": ">=0.4.0"
}
},
"packages/engine.io-parser": {
"version": "5.2.3",
"license": "MIT",
"devDependencies": {
"prettier": "^3.3.2"
},
"engines": {
"node": ">=10.0.0"
}
},
"packages/engine.io-parser/node_modules/prettier": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"packages/engine.io/node_modules/debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
@@ -15246,7 +15230,7 @@
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.5.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
@@ -15268,7 +15252,7 @@
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"engine.io-client": "~6.6.0",
"socket.io-parser": "~4.2.4"
},
"engines": {
@@ -15291,19 +15275,8 @@
}
}
},
"packages/socket.io-client/node_modules/engine.io-client": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"packages/socket.io-cluster-engine": {
"name": "@socket.io/cluster-engine",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
@@ -15359,26 +15332,6 @@
"optional": true
}
}
},
"packages/socket.io/node_modules/engine.io": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
}
}
}

View File

@@ -1,4 +1,5 @@
{
"name": "socket.io",
"private": true,
"workspaces": [
"packages/socket.io-component-emitter",
@@ -52,7 +53,7 @@
"mocha": "^10.6.0",
"node-forge": "^1.3.1",
"nyc": "^17.0.0",
"prettier": "^2.8.8",
"prettier": "^3.3.2",
"redis": "^4.6.15",
"rimraf": "^6.0.0",
"rollup": "^2.79.1",
@@ -67,7 +68,7 @@
"ts-node": "^10.9.2",
"tsd": "^0.31.1",
"typescript": "^5.5.3",
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.30.0",
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0",
"wdio-geckodriver-service": "^5.0.2"
}
}

View File

@@ -2,6 +2,7 @@
| Version | Release date | Bundle size (UMD min+gzip) |
|-------------------------------------------------------------------------------------------------------------|----------------|----------------------------|
| [6.6.1](#661-2024-09-21) | September 2024 | `8.7 KB` |
| [6.6.0](#660-2024-06-21) | June 2024 | `8.6 KB` |
| [6.5.4](#654-2024-06-18) (from the [6.5.x](https://github.com/socketio/engine.io-client/tree/6.5.x) branch) | June 2024 | `8.8 KB` |
| [3.5.4](#354-2024-06-18) (from the [3.5.x](https://github.com/socketio/engine.io-client/tree/3.5.x) branch) | June 2024 | `-` |
@@ -38,6 +39,27 @@
# Release notes
## [6.6.1](https://github.com/socketio/socket.io/compare/engine.io-client@6.6.0...engine.io-client@6.6.1) (2024-09-21)
### Bug Fixes
* move 'offline' event listener at the top ([8a2f5a3](https://github.com/socketio/socket.io/commit/8a2f5a3da0addb386e7a0f4970e1a9696b82797e))
* only remove the event listener if it exists ([9b3c9ab](https://github.com/socketio/socket.io/commit/9b3c9abecab028822357beb6e2b502f548e312eb)), closes [/github.com/socketio/socket.io/issues/5088#issuecomment-2217202350](https://github.com//github.com/socketio/socket.io/issues/5088/issues/issuecomment-2217202350)
* do not send a packet on an expired connection ([#5134](https://github.com/socketio/socket.io/issues/5134)) ([8adcfbf](https://github.com/socketio/socket.io/commit/8adcfbfde50679095ec2abe376650cf2b6814325))
### Performance Improvements
* do not reset the heartbeat timer on each packet ([7a23dde](https://github.com/socketio/socket.io/commit/7a23dde6efff3079edeeda951fe0ee25516da833))
### Dependencies
- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change)
## [6.6.0](https://github.com/socketio/engine.io-client/compare/6.5.3...6.6.0) (2024-06-21)

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 it is too large Load Diff

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

@@ -55,7 +55,7 @@ export function parse(setCookieString: string): Cookie {
case "Max-Age":
const expiration = new Date();
expiration.setUTCSeconds(
expiration.getUTCSeconds() + parseInt(value, 10)
expiration.getUTCSeconds() + parseInt(value, 10),
);
cookie.expires = expiration;
break;

View File

@@ -10,11 +10,34 @@ import {
CookieJar,
createCookieJar,
defaultBinaryType,
nextTick,
} from "./globals.node.js";
import debugModule from "debug"; // debug()
const debug = debugModule("engine.io-client:socket"); // debug()
const withEventListeners =
typeof addEventListener === "function" &&
typeof removeEventListener === "function";
const OFFLINE_EVENT_LISTENERS = [];
if (withEventListeners) {
// within a ServiceWorker, any event handler for the 'offline' event must be added on the initial evaluation of the
// script, so we create one single event listener here which will forward the event to the socket instances
addEventListener(
"offline",
() => {
debug(
"closing %d connection(s) because the network was lost",
OFFLINE_EVENT_LISTENERS.length,
);
OFFLINE_EVENT_LISTENERS.forEach((listener) => listener());
},
false,
);
}
export interface SocketOptions {
/**
* The host that we're connecting to. Set from the URI passed when connecting
@@ -85,7 +108,9 @@ export interface SocketOptions {
*
* @default ['polling','websocket', 'webtransport']
*/
transports?: string[] | TransportCtor[];
transports?:
| ("polling" | "websocket" | "webtransport" | string)[]
| TransportCtor[];
/**
* Whether all the transports should be tested, instead of just the first one.
@@ -318,6 +343,11 @@ export class SocketWithoutUpgrade extends Emitter<
private _pingTimeout: number = -1;
private _maxPayload?: number = -1;
private _pingTimeoutTimer: NodeJS.Timer;
/**
* The expiration timestamp of the {@link _pingTimeoutTimer} object is tracked, in case the timer is throttled and the
* callback is not fired on time. This can happen for example when a laptop is suspended or when a phone is locked.
*/
private _pingTimeoutTime = Infinity;
private clearTimeoutFn: typeof clearTimeout;
private readonly _beforeunloadEventListener: () => void;
private readonly _offlineEventListener: () => void;
@@ -379,8 +409,8 @@ export class SocketWithoutUpgrade extends Emitter<
(typeof location !== "undefined" && location.port
? location.port
: this.secure
? "443"
: "80");
? "443"
: "80");
this.transports = [];
this._transportsByName = {};
@@ -406,7 +436,7 @@ export class SocketWithoutUpgrade extends Emitter<
transportOptions: {},
closeOnBeforeunload: false,
},
opts
opts,
);
this.opts.path =
@@ -417,7 +447,7 @@ export class SocketWithoutUpgrade extends Emitter<
this.opts.query = decode(this.opts.query);
}
if (typeof addEventListener === "function") {
if (withEventListeners) {
if (this.opts.closeOnBeforeunload) {
// Firefox closes the connection when the "beforeunload" event is emitted but not Chrome. This event listener
// ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is
@@ -432,16 +462,17 @@ export class SocketWithoutUpgrade extends Emitter<
addEventListener(
"beforeunload",
this._beforeunloadEventListener,
false
false,
);
}
if (this.hostname !== "localhost") {
debug("adding listener for the 'offline' event");
this._offlineEventListener = () => {
this._onClose("transport close", {
description: "network connection lost",
});
};
addEventListener("offline", this._offlineEventListener, false);
OFFLINE_EVENT_LISTENERS.push(this._offlineEventListener);
}
}
@@ -482,7 +513,7 @@ export class SocketWithoutUpgrade extends Emitter<
secure: this.secure,
port: this.port,
},
this.opts.transportOptions[name]
this.opts.transportOptions[name],
);
debug("options: %j", opts);
@@ -572,7 +603,6 @@ export class SocketWithoutUpgrade extends Emitter<
// Socket is live - any packet counts
this.emitReserved("heartbeat");
this._resetPingTimeout();
switch (packet.type) {
case "open":
@@ -583,6 +613,7 @@ export class SocketWithoutUpgrade extends Emitter<
this._sendPacket("pong");
this.emitReserved("ping");
this.emitReserved("pong");
this._resetPingTimeout();
break;
case "error":
@@ -628,9 +659,11 @@ export class SocketWithoutUpgrade extends Emitter<
*/
private _resetPingTimeout() {
this.clearTimeoutFn(this._pingTimeoutTimer);
const delay = this._pingInterval + this._pingTimeout;
this._pingTimeoutTime = Date.now() + delay;
this._pingTimeoutTimer = this.setTimeoutFn(() => {
this._onClose("ping timeout");
}, this._pingInterval + this._pingTimeout);
}, delay);
if (this.opts.autoUnref) {
this._pingTimeoutTimer.unref();
}
@@ -708,6 +741,31 @@ export class SocketWithoutUpgrade extends Emitter<
return this.writeBuffer;
}
/**
* Checks whether the heartbeat timer has expired but the socket has not yet been notified.
*
* Note: this method is private for now because it does not really fit the WebSocket API, but if we put it in the
* `write()` method then the message would not be buffered by the Socket.IO client.
*
* @return {boolean}
* @private
*/
/* private */ _hasPingExpired() {
if (!this._pingTimeoutTime) return true;
const hasExpired = Date.now() > this._pingTimeoutTime;
if (hasExpired) {
debug("throttled timer detected, scheduling connection close");
this._pingTimeoutTime = 0;
nextTick(() => {
this._onClose("ping timeout");
}, this.setTimeoutFn);
}
return hasExpired;
}
/**
* Sends a message.
*
@@ -747,7 +805,7 @@ export class SocketWithoutUpgrade extends Emitter<
type: PacketType,
data?: RawData,
options?: WriteOptions,
fn?: () => void
fn?: () => void,
) {
if ("function" === typeof data) {
fn = data;
@@ -868,13 +926,21 @@ export class SocketWithoutUpgrade extends Emitter<
// ignore further transport communication
this.transport.removeAllListeners();
if (typeof removeEventListener === "function") {
removeEventListener(
"beforeunload",
this._beforeunloadEventListener,
false
);
removeEventListener("offline", this._offlineEventListener, false);
if (withEventListeners) {
if (this._beforeunloadEventListener) {
removeEventListener(
"beforeunload",
this._beforeunloadEventListener,
false,
);
}
if (this._offlineEventListener) {
const i = OFFLINE_EVENT_LISTENERS.indexOf(this._offlineEventListener);
if (i !== -1) {
debug("removing listener for the 'offline' event");
OFFLINE_EVENT_LISTENERS.splice(i, 1);
}
}
}
// set ready state

View File

@@ -14,7 +14,7 @@ export class TransportError extends Error {
constructor(
reason: string,
readonly description: any,
readonly context: any
readonly context: any,
) {
super(reason);
}
@@ -79,7 +79,7 @@ export abstract class Transport extends Emitter<
protected onError(reason: string, description: any, context?: any) {
super.emitReserved(
"error",
new TransportError(reason, description, context)
new TransportError(reason, description, context),
);
return this;
}

View File

@@ -15,12 +15,12 @@ export class XHR extends BaseXHR {
Object.assign(
opts,
{ xd: this.xd, cookieJar: this.socket?._cookieJar },
this.opts
this.opts,
);
return new Request(
(opts) => new XMLHttpRequest(opts),
this.uri(),
opts as RequestOptions
opts as RequestOptions,
);
}
}

View File

@@ -122,7 +122,7 @@ export class Request extends Emitter<
constructor(
private readonly createRequest: (opts: RequestOptions) => XMLHttpRequest,
uri: string,
opts: RequestOptions
opts: RequestOptions,
) {
super();
installTimerFunctions(this, opts);
@@ -151,7 +151,7 @@ export class Request extends Emitter<
"ca",
"ciphers",
"rejectUnauthorized",
"autoUnref"
"autoUnref",
);
opts.xdomain = !!this._opts.xd;
@@ -197,7 +197,7 @@ export class Request extends Emitter<
if (xhr.readyState === 3) {
this._opts.cookieJar?.parseCookies(
// @ts-ignore
xhr.getResponseHeader("set-cookie")
xhr.getResponseHeader("set-cookie"),
);
}
@@ -354,7 +354,7 @@ function newRequest(opts) {
if (!xdomain) {
try {
return new globalThis[["Active"].concat("Object").join("X")](
"Microsoft.XMLHTTP"
"Microsoft.XMLHTTP",
);
} catch (e) {}
}

View File

@@ -14,7 +14,7 @@ export class WS extends BaseWS {
createSocket(
uri: string,
protocols: string | string[] | undefined,
opts: Record<string, any>
opts: Record<string, any>,
) {
if (this.socket?._cookieJar) {
opts.headers = opts.headers || {};

View File

@@ -43,7 +43,7 @@ export abstract class BaseWS extends Transport {
"origin",
"maxPayload",
"family",
"checkServerIdentity"
"checkServerIdentity",
);
if (this.opts.extraHeaders) {
@@ -64,7 +64,7 @@ export abstract class BaseWS extends Transport {
abstract createSocket(
uri: string,
protocols: string | string[] | undefined,
opts: Record<string, any>
opts: Record<string, any>,
);
/**
@@ -166,7 +166,7 @@ export class WS extends BaseWS {
createSocket(
uri: string,
protocols: string | string[] | undefined,
opts: Record<string, any>
opts: Record<string, any>,
) {
return !isReactNative
? protocols

View File

@@ -30,7 +30,7 @@ export class WT extends Transport {
// @ts-ignore
this._transport = new WebTransport(
this.createUri("https"),
this.opts.transportOptions[this.name]
this.opts.transportOptions[this.name],
);
} catch (err) {
return this.emitReserved("error", err);
@@ -51,7 +51,7 @@ export class WT extends Transport {
this._transport.createBidirectionalStream().then((stream) => {
const decoderStream = createPacketDecoderStream(
Number.MAX_SAFE_INTEGER,
this.socket.binaryType
this.socket.binaryType,
);
const reader = stream.readable.pipeThrough(decoderStream).getReader();

View File

@@ -2,7 +2,7 @@
"name": "engine.io-client",
"description": "Client for the realtime Engine",
"license": "MIT",
"version": "6.6.0",
"version": "6.6.1",
"main": "./build/cjs/index.js",
"module": "./build/esm/index.js",
"exports": {
@@ -56,7 +56,7 @@
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
"xmlhttprequest-ssl": "~2.1.1"
},
"scripts": {
"compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh",

View File

@@ -34,12 +34,12 @@ describe("connection", function () {
const socket = new Socket();
socket.on("open", () => {
socket.send(
"\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF"
"\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF",
);
socket.on("message", (data) => {
if ("hi" === data) return;
expect(data).to.be(
"\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF"
"\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF",
);
socket.close();
done();
@@ -59,7 +59,7 @@ describe("connection", function () {
setTimeout(() => {
expect(noPacket).to.be(true);
done();
}, 1200);
}, 200);
});
});
@@ -177,7 +177,7 @@ describe("connection", function () {
setTimeout(() => {
expect(noPacket).to.be(true);
done();
}, 1200);
}, 200);
});
});

View File

@@ -5,4 +5,4 @@ const socket = new Socket("http://localhost:3000", {
setTimeout(() => {
console.log("process should not exit");
}, 500);
}, 50);

View File

@@ -10,4 +10,4 @@ socket.on("open", () => {
setTimeout(() => {
console.log("process should exit now");
}, 500);
}, 50);

View File

@@ -10,4 +10,4 @@ socket.on("open", () => {
setTimeout(() => {
console.log("process should exit now");
}, 500);
}, 50);

View File

@@ -9,4 +9,4 @@ socket.on("open", () => {
setTimeout(() => {
console.log("process should exit now");
}, 500);
}, 50);

View File

@@ -34,7 +34,7 @@ describe("node.js", () => {
isComplete = true;
process.kill();
done();
}, 1000);
}, 100);
});
});

View File

@@ -90,7 +90,7 @@ describe("Socket", function () {
socket.on("error", (err) => {
expect(err.message).to.eql(
useFetch ? "fetch read error" : "xhr poll error"
useFetch ? "fetch read error" : "xhr poll error",
);
done();
});
@@ -237,7 +237,7 @@ describe("Socket", function () {
// err.context is a XMLHttpRequest object
expect(err.context.readyState).to.eql(4);
expect(err.context.responseText).to.eql(
'{"code":1,"message":"Session ID unknown"}'
'{"code":1,"message":"Session ID unknown"}',
);
}
});
@@ -270,4 +270,30 @@ describe("Socket", function () {
});
});
});
describe("throttled timer", () => {
it("checks the state of the timer", (done) => {
const socket = new Socket();
expect(socket._hasPingExpired()).to.be(false);
socket.on("open", () => {
expect(socket._hasPingExpired()).to.be(false);
// simulate a throttled timer
socket._pingTimeoutTime = Date.now() - 1;
expect(socket._hasPingExpired()).to.be(true);
// subsequent calls should not trigger more 'close' events
expect(socket._hasPingExpired()).to.be(true);
expect(socket._hasPingExpired()).to.be(true);
});
socket.on("close", (reason) => {
expect(reason).to.eql("ping timeout");
done();
});
});
});
});

View File

@@ -21,7 +21,7 @@ exports.mochaHooks = {
maxHttpBufferSize: 100,
allowRequest: (req, fn) => {
const denyRequest = new URL(`http://${req.url}`).searchParams.has(
"deny"
"deny",
);
fn(null, !denyRequest);
},

View File

@@ -100,7 +100,7 @@ describe("Transport", () => {
timestampRequests: false,
});
expect(polling.uri()).to.contain(
"http://localhost:3000/engine.io?sid=test"
"http://localhost:3000/engine.io?sid=test",
);
});
@@ -124,7 +124,7 @@ describe("Transport", () => {
timestampRequests: true,
});
expect(polling.uri()).to.match(
/http:\/\/localhost\/engine\.io\?(j=[0-9]+&)?(t=[0-9A-Za-z-_]+)/
/http:\/\/localhost\/engine\.io\?(j=[0-9]+&)?(t=[0-9A-Za-z-_]+)/,
);
});
@@ -180,7 +180,7 @@ describe("Transport", () => {
timestampRequests: true,
});
expect(ws.uri()).to.match(
/ws:\/\/localhost\/engine\.io\?woot=[0-9A-Za-z-_]+/
/ws:\/\/localhost\/engine\.io\?woot=[0-9A-Za-z-_]+/,
);
});

View File

@@ -17,7 +17,7 @@ async function setup(opts, cb) {
[{ shortName: "CN", value: "localhost" }],
{
days: 14, // the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements)
}
},
);
const engine = new Server(opts);
@@ -73,8 +73,8 @@ function createSocket(port, certificate, opts) {
},
},
},
opts
)
opts,
),
);
}
@@ -113,7 +113,7 @@ describe("WebTransport", () => {
httpServer.close();
success(engine, h3Server, done);
});
}
},
);
});
@@ -137,7 +137,7 @@ describe("WebTransport", () => {
httpServer.close();
success(engine, h3Server, done);
});
}
},
);
});
@@ -160,7 +160,7 @@ describe("WebTransport", () => {
success(engine, h3Server, done);
}
});
}
},
);
});

View File

@@ -1,56 +0,0 @@
'use strict';
const browsers = require('socket.io-browsers');
const zuulConfig = module.exports = {
ui: 'mocha-bdd',
// test on localhost by default
local: true,
open: true,
concurrency: 2, // ngrok only accepts two tunnels by default
// if browser does not sends output in 120s since last output:
// stop testing, something is wrong
browser_output_timeout: 120 * 1000,
browser_open_timeout: 60 * 4 * 1000,
// we want to be notified something is wrong asap, so no retry
browser_retries: 1,
server: './test/support/server.js',
builder: 'zuul-builder-webpack',
webpack: require('./support/webpack.config.js')
};
if (process.env.CI === 'true') {
zuulConfig.local = false;
zuulConfig.tunnel = {
type: 'ngrok',
bind_tls: true
};
}
zuulConfig.browsers = [
{
name: 'firefox',
version: 'latest'
}, {
name: 'internet explorer',
version: '9..11'
}, {
name: 'safari',
version: '14'
}, {
name: 'iphone',
version: '14'
}, {
name: 'android',
version: '5.1..6.0'
}, {
name: 'ipad',
version: '14'
}, {
name: 'MicrosoftEdge',
version: 'latest'
}
];

View File

@@ -10,9 +10,6 @@
"require": "./build/cjs/index.js"
},
"types": "build/esm/index.d.ts",
"devDependencies": {
"prettier": "^3.3.2"
},
"scripts": {
"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",

View File

@@ -78,7 +78,7 @@ export interface ServerOptions {
*/
allowRequest?: (
req: IncomingMessage,
fn: (err: string | null | undefined, success: boolean) => void
fn: (err: string | null | undefined, success: boolean) => void,
) => void;
/**
* The low-level transports that are enabled. WebTransport is disabled by default and must be manually enabled:
@@ -146,7 +146,7 @@ export interface ServerOptions {
type Middleware = (
req: IncomingMessage,
res: ServerResponse,
next: (err?: any) => void
next: (err?: any) => void,
) => void;
function parseSessionId(data: string) {
@@ -192,7 +192,7 @@ export abstract class BaseServer extends EventEmitter {
cors: false,
allowEIO3: false,
},
opts
opts,
);
if (opts.cookie) {
@@ -204,7 +204,7 @@ export abstract class BaseServer extends EventEmitter {
httpOnly: opts.cookie.path !== false,
sameSite: "lax",
},
opts.cookie
opts.cookie,
);
}
@@ -217,7 +217,7 @@ export abstract class BaseServer extends EventEmitter {
{
threshold: 1024,
},
opts.perMessageDeflate
opts.perMessageDeflate,
);
}
@@ -263,7 +263,7 @@ export abstract class BaseServer extends EventEmitter {
protected verify(
req: any,
upgrade: boolean,
fn: (errorCode?: number, errorContext?: any) => void
fn: (errorCode?: number, errorContext?: any) => void,
) {
// transport check
const transport = req._query.transport;
@@ -361,7 +361,7 @@ export abstract class BaseServer extends EventEmitter {
protected _applyMiddlewares(
req: IncomingMessage,
res: ServerResponse,
callback: (err?: any) => void
callback: (err?: any) => void,
) {
if (this.middlewares.length === 0) {
debug("no middleware to apply, skipping");
@@ -424,7 +424,7 @@ export abstract class BaseServer extends EventEmitter {
protected async handshake(
transportName: string,
req: any,
closeConnection: (errorCode?: number, errorContext?: any) => void
closeConnection: (errorCode?: number, errorContext?: any) => void,
) {
const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
if (protocol === 3 && !this.opts.allowEIO3) {
@@ -519,7 +519,7 @@ export abstract class BaseServer extends EventEmitter {
public async onWebTransportSession(session: any) {
const timeout = setTimeout(() => {
debug(
"the client failed to establish a bidirectional stream in the given period"
"the client failed to establish a bidirectional stream in the given period",
);
session.close();
}, this.opts.upgradeTimeout);
@@ -535,7 +535,7 @@ export abstract class BaseServer extends EventEmitter {
const stream = result.value;
const transformStream = createPacketDecoderStream(
this.opts.maxHttpBufferSize,
"nodebuffer"
"nodebuffer",
);
const reader = stream.readable.pipeThrough(transformStream).getReader();
@@ -632,7 +632,10 @@ export abstract class BaseServer extends EventEmitter {
* @see https://nodejs.org/api/http.html#class-httpserverresponse
*/
class WebSocketResponse {
constructor(readonly req, readonly socket: Duplex) {
constructor(
readonly req,
readonly socket: Duplex,
) {
// temporarily store the response headers on the req object (see the "headers" event)
req[kResponseHeaders] = {};
}
@@ -659,6 +662,9 @@ class WebSocketResponse {
}
}
/**
* An Engine.IO server based on Node.js built-in HTTP server and the `ws` package for WebSocket connections.
*/
export class Server extends BaseServer {
public httpServer?: HttpServer;
private ws: any;
@@ -776,7 +782,7 @@ export class Server extends BaseServer {
public handleUpgrade(
req: EngineRequest,
socket: Duplex,
upgradeHead: Buffer
upgradeHead: Buffer,
) {
this.prepare(req);
@@ -953,7 +959,7 @@ function abortRequest(res, errorCode, errorContext) {
JSON.stringify({
code: errorCode,
message,
})
}),
);
}
@@ -968,7 +974,7 @@ function abortRequest(res, errorCode, errorContext) {
function abortUpgrade(
socket,
errorCode,
errorContext: { message?: string } = {}
errorContext: { message?: string } = {},
) {
socket.on("error", () => {
debug("ignoring error from closed connection");
@@ -984,7 +990,7 @@ function abortUpgrade(
length +
"\r\n" +
"\r\n" +
message
message,
);
}
socket.destroy();

View File

@@ -80,7 +80,7 @@ export class Socket extends EventEmitter {
server: BaseServer,
transport: Transport,
req: EngineRequest,
protocol: number
protocol: number,
) {
super();
this.id = id;
@@ -125,7 +125,7 @@ export class Socket extends EventEmitter {
pingInterval: this.server.opts.pingInterval,
pingTimeout: this.server.opts.pingTimeout,
maxPayload: this.server.opts.maxHttpBufferSize,
})
}),
);
if (this.server.opts.initialPacket) {
@@ -212,7 +212,7 @@ export class Socket extends EventEmitter {
this.pingIntervalTimer = setTimeout(() => {
debug(
"writing ping packet - expecting pong within %sms",
this.server.opts.pingTimeout
this.server.opts.pingTimeout,
);
this.sendPacket("ping");
this.resetPingTimeout();
@@ -233,7 +233,7 @@ export class Socket extends EventEmitter {
},
this.protocol === 3
? this.server.opts.pingInterval + this.server.opts.pingTimeout
: this.server.opts.pingTimeout
: this.server.opts.pingTimeout,
);
}
@@ -293,7 +293,7 @@ export class Socket extends EventEmitter {
debug(
'might upgrade socket transport from "%s" to "%s"',
this.transport.name,
transport.name
transport.name,
);
this.upgrading = true;
@@ -468,7 +468,7 @@ export class Socket extends EventEmitter {
type: PacketType,
data?: RawData,
options: SendOptions = {},
callback?: SendCallback
callback?: SendCallback,
) {
if ("function" === typeof options) {
callback = options;
@@ -554,6 +554,13 @@ export class Socket extends EventEmitter {
* @return {Socket} for chaining
*/
public close(discard?: boolean) {
if (
discard &&
(this.readyState === "open" || this.readyState === "closing")
) {
return this.closeTransport(discard);
}
if ("open" !== this.readyState) return;
this.readyState = "closing";
@@ -561,7 +568,7 @@ export class Socket extends EventEmitter {
if (this.writeBuffer.length) {
debug(
"there are %d remaining packets in the buffer, waiting for the 'drain' event",
this.writeBuffer.length
this.writeBuffer.length,
);
this.once("drain", () => {
debug("all packets have been sent, closing the transport");
@@ -570,7 +577,7 @@ export class Socket extends EventEmitter {
return;
}
debug("the buffer is empty, closing the transport right away", discard);
debug("the buffer is empty, closing the transport right away");
this.closeTransport(discard);
}
@@ -581,7 +588,7 @@ export class Socket extends EventEmitter {
* @private
*/
private closeTransport(discard: boolean) {
debug("closing the transport (discard? %s)", discard);
debug("closing the transport (discard? %s)", !!discard);
if (discard) this.transport.discard();
this.transport.close(this.onClose.bind(this, "forced close"));
}

View File

@@ -69,7 +69,7 @@ export abstract class Transport extends EventEmitter {
"readyState updated from %s to %s (%s)",
this._readyState,
state,
this.name
this.name,
);
this._readyState = state;
}

View File

@@ -54,13 +54,13 @@ export class WebSocket extends Transport {
this.socket._sender.sendFrame(
// @ts-ignore
packet.options.wsPreEncodedFrame,
isLast ? this._onSentLast : this._onSent
isLast ? this._onSentLast : this._onSent,
);
} else {
this.parser.encodePacket(
packet,
this.supportsBinary,
isLast ? this._doSendLast : this._doSend
isLast ? this._doSendLast : this._doSend,
);
}
}

View File

@@ -10,7 +10,11 @@ const debug = debugModule("engine:webtransport");
export class WebTransport extends Transport {
private readonly writer;
constructor(private readonly session, stream, reader) {
constructor(
private readonly session,
stream,
reader,
) {
super({ _query: { EIO: "4" } });
const transformStream = createPacketEncoderStream();

View File

@@ -23,6 +23,10 @@ export interface uOptions {
maxBackpressure?: number;
}
/**
* An Engine.IO server based on the `uWebSockets.js` package.
*/
// TODO export it into its own package
export class uServer extends BaseServer {
protected init() {}
protected cleanup() {}
@@ -64,7 +68,7 @@ export class uServer extends BaseServer {
*/
public attach(
app /* : TemplatedApp */,
options: AttachOptions & uOptions = {}
options: AttachOptions & uOptions = {},
) {
const path = this._computePath(options);
(app as TemplatedApp)
@@ -84,7 +88,7 @@ export class uServer extends BaseServer {
},
message: (ws, message, isBinary) => {
ws.getUserData().transport.onData(
isBinary ? message : Buffer.from(message).toString()
isBinary ? message : Buffer.from(message).toString(),
);
},
close: (ws, code, message) => {
@@ -96,7 +100,7 @@ export class uServer extends BaseServer {
override _applyMiddlewares(
req: any,
res: any,
callback: (err?: any) => void
callback: (err?: any) => void,
): void {
if (this.middlewares.length === 0) {
return callback();
@@ -116,7 +120,7 @@ export class uServer extends BaseServer {
private handleRequest(
res: HttpResponse,
req: HttpRequest & { res: any; _query: any }
req: HttpRequest & { res: any; _query: any },
) {
debug('handling "%s" http request "%s"', req.getMethod(), req.getUrl());
this.prepare(req, res);
@@ -158,7 +162,7 @@ export class uServer extends BaseServer {
private handleUpgrade(
res: HttpResponse,
req: HttpRequest & { res: any; _query: any },
context
context,
) {
debug("on upgrade");
@@ -185,13 +189,13 @@ export class uServer extends BaseServer {
const client = this.clients[id];
if (!client) {
debug("upgrade attempt for closed client");
res.close();
return res.close();
} else if (client.upgrading) {
debug("transport has already been trying to upgrade");
res.close();
return res.close();
} else if (client.upgraded) {
debug("transport had already been upgraded");
res.close();
return res.close();
} else {
debug("upgrading existing transport");
transport = this.createTransport(req._query.transport, req);
@@ -202,7 +206,7 @@ export class uServer extends BaseServer {
req._query.transport,
req,
(errorCode, errorContext) =>
this.abortRequest(res, errorCode, errorContext)
this.abortRequest(res, errorCode, errorContext),
);
if (!transport) {
return;
@@ -219,7 +223,7 @@ export class uServer extends BaseServer {
req.getHeader("sec-websocket-key"),
req.getHeader("sec-websocket-protocol"),
req.getHeader("sec-websocket-extensions"),
context
context,
);
};
@@ -235,7 +239,7 @@ export class uServer extends BaseServer {
private abortRequest(
res: HttpResponse | ResponseWrapper,
errorCode,
errorContext
errorContext,
) {
const statusCode =
errorCode === Server.errors.FORBIDDEN
@@ -252,7 +256,7 @@ export class uServer extends BaseServer {
JSON.stringify({
code: errorCode,
message,
})
}),
);
}
}

View File

@@ -4,6 +4,17 @@ const { Socket } =
? require("engine.io-client-v3")
: require("engine.io-client");
switch (process.env.EIO_WS_ENGINE) {
case "uws":
console.log(
"[WARN] testing with uWebSockets.js instead of Node.js built-in HTTP server",
);
break;
case "eiows":
console.log("[WARN] testing with eiows instead of ws");
break;
}
/**
* Listen shortcut that fires a callback on an ephemeral port.
*/

View File

@@ -93,7 +93,7 @@ describe("engine", () => {
"Upgrade: IRC/6.9",
"",
"",
].join("\r\n")
].join("\r\n"),
);
const check = setTimeout(() => {
@@ -122,7 +122,7 @@ describe("engine", () => {
"Upgrade: IRC/6.9",
"",
"",
].join("\r\n")
].join("\r\n"),
);
setTimeout(() => {
@@ -154,7 +154,7 @@ describe("engine", () => {
"Upgrade: IRC/6.9",
"",
"",
].join("\r\n")
].join("\r\n"),
);
// send from client to server
@@ -198,7 +198,7 @@ describe("engine", () => {
"Upgrade: IRC/6.9",
"",
"",
].join("\r\n")
].join("\r\n"),
);
// test that socket is still open by writing after the timeout period
@@ -245,7 +245,7 @@ describe("engine", () => {
server.once("close", done);
server.close();
});
}
},
);
});
});

View File

@@ -38,7 +38,7 @@ describe("middlewares", () => {
});
const socket = new WebSocket(
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`,
);
socket.on("upgrade", (res) => {
@@ -131,7 +131,7 @@ describe("middlewares", () => {
});
const socket = new WebSocket(
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`,
);
socket.addEventListener("error", () => {
@@ -169,7 +169,7 @@ describe("middlewares", () => {
engine.use(helmet());
const socket = new WebSocket(
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`,
);
socket.on("upgrade", (res) => {
@@ -196,7 +196,7 @@ describe("middlewares", () => {
resave: false,
saveUninitialized: true,
cookie: {},
})
}),
);
request
@@ -206,7 +206,7 @@ describe("middlewares", () => {
expect(err).to.be(null);
// expect(res.status).to.eql(200);
expect(res.headers["set-cookie"][0].startsWith("connect.sid=")).to.be(
true
true,
);
if (engine.httpServer) {
@@ -225,16 +225,16 @@ describe("middlewares", () => {
resave: false,
saveUninitialized: true,
cookie: {},
})
}),
);
const socket = new WebSocket(
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`,
);
socket.on("upgrade", (res) => {
expect(res.headers["set-cookie"][0].startsWith("connect.sid=")).to.be(
true
true,
);
if (engine.httpServer) {
@@ -280,7 +280,7 @@ describe("middlewares", () => {
});
const socket = new WebSocket(
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`,
);
socket.addEventListener("error", () => {

View File

@@ -16,7 +16,7 @@ describe("parser", () => {
expect(decoded.data).to.eql("€€€€");
done();
});
}
},
);
});
});

View File

@@ -143,7 +143,7 @@ describe("server", () => {
expect(res.body.message).to.be("Thou shall not pass");
partialDone();
});
}
},
);
});
@@ -161,7 +161,7 @@ describe("server", () => {
client.on("error", () => {
done();
});
}
},
);
});
@@ -205,6 +205,26 @@ describe("server", () => {
});
});
it("should prevent the client from upgrading twice", (done) => {
engine = listen((port) => {
const client = new ClientSocket(`ws://localhost:${port}`);
client.on("upgrade", () => {
const socket = new WebSocket(
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket&sid=${client.id}`,
);
socket.on("error", () => {});
socket.on("close", () => {
client.close();
done();
});
});
});
});
it("should disallow `__proto__` as transport (polling)", (done) => {
const partialDone = createPartialDone(done, 2);
@@ -243,7 +263,7 @@ describe("server", () => {
});
const socket = new WebSocket(
`ws://localhost:${port}/engine.io/?EIO=4&transport=__proto__`
`ws://localhost:${port}/engine.io/?EIO=4&transport=__proto__`,
);
socket.onerror = partialDone;
@@ -262,7 +282,7 @@ describe("server", () => {
// hack-obtain sid
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; Path=/; HttpOnly; SameSite=Lax`
`io=${sid}; Path=/; HttpOnly; SameSite=Lax`,
);
done();
});
@@ -278,7 +298,7 @@ describe("server", () => {
expect(err).to.be(null);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`woot=${sid}; Path=/; HttpOnly; SameSite=Lax`
`woot=${sid}; Path=/; HttpOnly; SameSite=Lax`,
);
done();
});
@@ -294,7 +314,7 @@ describe("server", () => {
expect(err).to.be(null);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; Path=/custom; HttpOnly; SameSite=Lax`
`io=${sid}; Path=/custom; HttpOnly; SameSite=Lax`,
);
done();
});
@@ -310,7 +330,7 @@ describe("server", () => {
expect(err).to.be(null);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; SameSite=Lax`
`io=${sid}; SameSite=Lax`,
);
done();
});
@@ -326,7 +346,7 @@ describe("server", () => {
expect(err).to.be(null);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; Path=/; HttpOnly; SameSite=Lax`
`io=${sid}; Path=/; HttpOnly; SameSite=Lax`,
);
done();
});
@@ -342,7 +362,7 @@ describe("server", () => {
expect(err).to.be(null);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; Path=/; HttpOnly; SameSite=Strict`
`io=${sid}; Path=/; HttpOnly; SameSite=Strict`,
);
done();
});
@@ -358,7 +378,7 @@ describe("server", () => {
expect(err).to.be(null);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; Path=/; SameSite=Lax`
`io=${sid}; Path=/; SameSite=Lax`,
);
done();
});
@@ -374,7 +394,7 @@ describe("server", () => {
expect(err).to.be(null);
const sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers["set-cookie"][0]).to.be(
`io=${sid}; Path=/; HttpOnly; SameSite=Lax`
`io=${sid}; Path=/; HttpOnly; SameSite=Lax`,
);
done();
});
@@ -681,14 +701,14 @@ describe("server", () => {
expect(res.body.code).to.be(3);
expect(res.body.message).to.be("Bad request");
expect(res.header["access-control-allow-credentials"]).to.be(
"true"
"true",
);
expect(res.header["access-control-allow-origin"]).to.be(
"http://engine.io"
"http://engine.io",
);
partialDone();
});
}
},
);
});
@@ -817,7 +837,7 @@ describe("server", () => {
.end((err, res) => {
if (process.env.EIO_WS_ENGINE === "uws") {
expect(err).to.not.be(null);
expect(err.message).to.be("socket hang up");
expect(err.status).to.be(404);
} else {
expect(err).to.be(null);
// this should not work, but it is kept for backward-compatibility
@@ -1130,7 +1150,7 @@ describe("server", () => {
expect(res.statusCode).to.eql(400);
res.resume();
res.on("end", done);
}
},
);
req.end();
});
@@ -1174,7 +1194,7 @@ describe("server", () => {
// OPENED readyState is expected - we are actually polling
expect(
socket.transport.pollXhr[IS_CLIENT_V3 ? "xhr" : "_xhr"]
.readyState
.readyState,
).to.be(1);
// 2 requests sent to the server over an unique port means
@@ -1194,7 +1214,7 @@ describe("server", () => {
}, 50);
});
});
}
},
);
it("should not trigger with connection: close header", ($done) => {
@@ -1260,7 +1280,7 @@ describe("server", () => {
done();
}, 200);
});
}
},
);
it(
@@ -1295,7 +1315,7 @@ describe("server", () => {
done();
}, 100);
});
}
},
);
it(
@@ -1331,7 +1351,7 @@ describe("server", () => {
});
});
});
}
},
);
if (IS_CLIENT_V3) {
@@ -1370,7 +1390,7 @@ describe("server", () => {
});
});
});
}
},
);
} else {
it(
@@ -1408,7 +1428,7 @@ describe("server", () => {
});
});
});
}
},
);
}
@@ -1435,7 +1455,7 @@ describe("server", () => {
socket.send("test");
});
});
}
},
);
// tests https://github.com/LearnBoost/engine.io-client/issues/207
@@ -1634,6 +1654,67 @@ describe("server", () => {
});
});
it("should discard the packets in the writeBuffer when stopping the server", (done) => {
engine = listen((port) => {
const clientSocket = new ClientSocket(`ws://localhost:${port}`);
clientSocket.on("data", () => {
done(new Error("should not happen"));
});
clientSocket.on("close", (reason) => {
expect(reason).to.eql("transport error");
clientSocket.close();
done();
});
engine.on("connection", (socket) => {
socket.write("hello");
engine.close();
});
});
});
it("should discard the packets in the writeBuffer when stopping the server (2)", (done) => {
engine = listen((port) => {
const clientSocket = new ClientSocket(`ws://localhost:${port}`);
clientSocket.on("data", () => {
done(new Error("should not happen"));
});
clientSocket.on("close", (reason) => {
expect(reason).to.eql("transport error");
clientSocket.close();
done();
});
engine.on("connection", (socket) => {
socket.write("hello");
socket.close(); // readyState is now "closing"
engine.close();
});
});
});
it("should not discard the packets in the writeBuffer when closing gracefully", (done) => {
engine = listen((port) => {
const clientSocket = new ClientSocket(`ws://localhost:${port}`);
clientSocket.on("data", (val) => {
expect(val).to.eql("hello");
done();
});
engine.on("connection", (socket) => {
socket.write("hello");
socket.close();
});
});
});
describe("graceful close", () => {
before(function () {
if (process.env.EIO_WS_ENGINE === "uws") {
@@ -1727,7 +1808,7 @@ describe("server", () => {
engine.on("connection", (conn) => {
conn.on("message", (msg) => {
done(
new Error("Test invalidation (message is longer than allowed)")
new Error("Test invalidation (message is longer than allowed)"),
);
});
});
@@ -1749,7 +1830,7 @@ describe("server", () => {
engine.on("connection", (conn) => {
conn.on("message", (msg) => {
done(
new Error("Test invalidation (message is longer than allowed)")
new Error("Test invalidation (message is longer than allowed)"),
);
});
});
@@ -2060,7 +2141,7 @@ describe("server", () => {
client.on("open", () => {
client.send("a".repeat(1e6));
});
}
},
);
});
@@ -2202,7 +2283,7 @@ describe("server", () => {
});
});
});
}
},
);
it("should support chinese", (done) => {
@@ -2546,7 +2627,7 @@ describe("server", () => {
j,
((value) => {
j++;
})(j)
})(j),
);
}
@@ -2585,7 +2666,7 @@ describe("server", () => {
j,
((value) => {
j++;
})(j)
})(j),
);
}
@@ -2999,7 +3080,7 @@ describe("server", () => {
done();
});
});
}
},
);
});
});
@@ -3031,7 +3112,7 @@ describe("server", () => {
done();
});
});
}
},
);
});
});
@@ -3174,11 +3255,11 @@ describe("server", () => {
.on("error", done)
.on("end", done)
.resume();
}
},
);
}
},
);
}
},
);
});
@@ -3212,11 +3293,11 @@ describe("server", () => {
.on("error", done)
.on("end", done)
.resume();
}
},
);
}
},
);
}
},
);
});
@@ -3250,11 +3331,11 @@ describe("server", () => {
(res) => {
expect(res.headers["content-encoding"]).to.equal("gzip");
done();
}
},
);
}
},
);
}
},
);
});
@@ -3284,11 +3365,11 @@ describe("server", () => {
(res) => {
expect(res.headers["content-encoding"]).to.be(undefined);
done();
}
},
);
}
},
);
}
},
);
});
@@ -3318,11 +3399,11 @@ describe("server", () => {
(res) => {
expect(res.headers["content-encoding"]).to.be(undefined);
done();
}
},
);
}
},
);
}
},
);
});
@@ -3352,11 +3433,11 @@ describe("server", () => {
(res) => {
expect(res.headers["content-encoding"]).to.be(undefined);
done();
}
},
);
}
},
);
}
},
);
});
});
@@ -3599,20 +3680,20 @@ describe("server", () => {
expect(res.status).to.be(204);
expect(res.body).to.be.empty();
expect(res.header["access-control-allow-origin"]).to.be(
"http://engine.io"
"http://engine.io",
);
expect(res.header["access-control-allow-methods"]).to.be(
"GET,HEAD,PUT,PATCH,POST,DELETE"
"GET,HEAD,PUT,PATCH,POST,DELETE",
);
expect(res.header["access-control-allow-headers"]).to.be(
"my-header"
"my-header",
);
expect(res.header["access-control-allow-credentials"]).to.be(
"true"
"true",
);
done();
});
}
},
);
});
@@ -3629,20 +3710,20 @@ describe("server", () => {
expect(res.status).to.be(200);
expect(res.body).to.be.empty();
expect(res.header["access-control-allow-origin"]).to.be(
"http://engine.io"
"http://engine.io",
);
expect(res.header["access-control-allow-methods"]).to.be(
undefined
undefined,
);
expect(res.header["access-control-allow-headers"]).to.be(
undefined
undefined,
);
expect(res.header["access-control-allow-credentials"]).to.be(
"true"
"true",
);
done();
});
}
},
);
});
@@ -3663,14 +3744,14 @@ describe("server", () => {
expect(res.status).to.be(204);
expect(res.body).to.be.empty();
expect(res.header["access-control-allow-origin"]).to.be(
undefined
undefined,
);
expect(res.header["access-control-allow-credentials"]).to.be(
undefined
undefined,
);
done();
});
}
},
);
});
@@ -3697,24 +3778,24 @@ describe("server", () => {
expect(res.status).to.be(200);
expect(res.body).to.be.empty();
expect(res.header["access-control-allow-origin"]).to.be(
"http://good-domain.com"
"http://good-domain.com",
);
expect(res.header["access-control-allow-methods"]).to.be(
"GET,PUT,POST"
"GET,PUT,POST",
);
expect(res.header["access-control-allow-headers"]).to.be(
"my-header"
"my-header",
);
expect(res.header["access-control-expose-headers"]).to.be(
"my-exposed-header"
"my-exposed-header",
);
expect(res.header["access-control-allow-credentials"]).to.be(
"true"
"true",
);
expect(res.header["access-control-max-age"]).to.be("123");
done();
});
}
},
);
});
@@ -3739,7 +3820,7 @@ describe("server", () => {
client.close();
done();
});
}
},
);
});
});
@@ -3767,7 +3848,7 @@ describe("server", () => {
done();
});
});
}
},
);
});
});

View File

@@ -31,7 +31,7 @@ async function setupServer(opts, cb) {
[{ shortName: "CN", value: "localhost" }],
{
days: 14, // the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements)
}
},
);
const engine = new eio.Server(opts);
@@ -76,7 +76,7 @@ function setup(opts, cb) {
value: certificate.hash,
},
],
}
},
);
await client.ready;
@@ -106,7 +106,7 @@ describe("WebTransport", () => {
setupServer({}, async ({ engine, h3Server, certificate }) => {
const partialDone = createPartialDone(
() => success(engine, h3Server, done),
2
2,
);
engine.on("connection", (socket) => {
@@ -123,7 +123,7 @@ describe("WebTransport", () => {
value: certificate.hash,
},
],
}
},
);
await client.ready;
@@ -188,7 +188,7 @@ describe("WebTransport", () => {
value: certificate.hash,
},
],
}
},
);
await client.ready;
@@ -216,14 +216,14 @@ describe("WebTransport", () => {
await writer.write(Uint8Array.of(31));
await writer.write(
TEXT_ENCODER.encode(`0{"sid":"${payload.sid}"}`)
TEXT_ENCODER.encode(`0{"sid":"${payload.sid}"}`),
);
await writer.write(Uint8Array.of(6));
await writer.write(TEXT_ENCODER.encode(`2probe`));
await writer.write(Uint8Array.of(1));
await writer.write(TEXT_ENCODER.encode(`5`));
});
}
},
);
});
@@ -242,7 +242,7 @@ describe("WebTransport", () => {
value: certificate.hash,
},
],
}
},
);
await client.ready;
@@ -250,7 +250,7 @@ describe("WebTransport", () => {
client.closed.then(() => {
success(engine, h3Server, done);
});
}
},
);
});
@@ -269,7 +269,7 @@ describe("WebTransport", () => {
value: certificate.hash,
},
],
}
},
);
await client.ready;
@@ -281,7 +281,7 @@ describe("WebTransport", () => {
client.closed.then(() => {
success(engine, h3Server, done);
});
}
},
);
});
@@ -304,7 +304,7 @@ describe("WebTransport", () => {
}
success(engine, h3Server, done);
}
},
);
});
@@ -322,7 +322,7 @@ describe("WebTransport", () => {
});
client.closed.then(() => success(engine, h3Server, partialDone));
}
},
);
});
@@ -440,7 +440,7 @@ describe("WebTransport", () => {
const header = await reader.read();
expect(header.value).to.eql(
Uint8Array.of(255, 0, 0, 0, 0, 0, 15, 66, 64)
Uint8Array.of(255, 0, 0, 0, 0, 0, 15, 66, 64),
);
const chunk1 = await reader.read();

View File

@@ -203,7 +203,7 @@ export abstract class ClusterAdapter extends Adapter {
"[%s] new event of type %d from %s",
this.uid,
message.type,
message.uid
message.uid,
);
switch (message.type) {
@@ -217,7 +217,7 @@ export abstract class ClusterAdapter extends Adapter {
debug(
"[%s] waiting for %d client acknowledgements",
this.uid,
clientCount
clientCount,
);
this.publishResponse(message.uid, {
type: MessageType.BROADCAST_CLIENT_COUNT,
@@ -231,7 +231,7 @@ export abstract class ClusterAdapter extends Adapter {
debug(
"[%s] received acknowledgement with value %j",
this.uid,
arg
arg,
);
this.publishResponse(message.uid, {
type: MessageType.BROADCAST_ACK,
@@ -240,7 +240,7 @@ export abstract class ClusterAdapter extends Adapter {
packet: arg,
},
});
}
},
);
} else {
const packet = message.data.packet;
@@ -264,7 +264,7 @@ export abstract class ClusterAdapter extends Adapter {
case MessageType.DISCONNECT_SOCKETS:
super.disconnectSockets(
decodeOptions(message.data.opts),
message.data.close
message.data.close,
);
break;
@@ -272,7 +272,7 @@ export abstract class ClusterAdapter extends Adapter {
debug(
"[%s] calling fetchSockets with opts %j",
this.uid,
message.data.opts
message.data.opts,
);
super
.fetchSockets(decodeOptions(message.data.opts))
@@ -356,7 +356,7 @@ export abstract class ClusterAdapter extends Adapter {
"[%s] received response %s to request %s",
this.uid,
response.type,
requestId
requestId,
);
switch (response.type) {
@@ -381,7 +381,7 @@ export abstract class ClusterAdapter extends Adapter {
request.current++;
response.data.sockets.forEach((socket) =>
request.responses.push(socket)
request.responses.push(socket),
);
if (request.current === request.expected) {
@@ -433,7 +433,7 @@ export abstract class ClusterAdapter extends Adapter {
return debug(
"[%s] error while broadcasting message: %s",
this.uid,
e.message
e.message,
);
}
}
@@ -453,7 +453,7 @@ export abstract class ClusterAdapter extends Adapter {
private addOffsetIfNecessary(
packet: any,
opts: BroadcastOptions,
offset: Offset
offset: Offset,
) {
if (!this.nsp.server.opts.connectionStateRecovery) {
return;
@@ -473,7 +473,7 @@ export abstract class ClusterAdapter extends Adapter {
packet: any,
opts: BroadcastOptions,
clientCountCallback: (clientCount: number) => void,
ack: (...args: any[]) => void
ack: (...args: any[]) => void,
) {
const onlyLocal = opts?.flags?.local;
if (!onlyLocal) {
@@ -582,8 +582,8 @@ export abstract class ClusterAdapter extends Adapter {
if (storedRequest) {
reject(
new Error(
`timeout reached: only ${storedRequest.current} responses received out of ${storedRequest.expected}`
)
`timeout reached: only ${storedRequest.current} responses received out of ${storedRequest.expected}`,
),
);
this.requests.delete(requestId);
}
@@ -627,7 +627,7 @@ export abstract class ClusterAdapter extends Adapter {
debug(
'[%s] waiting for %d responses to "serverSideEmit" request',
this.uid,
expectedResponseCount
expectedResponseCount,
);
if (expectedResponseCount <= 0) {
@@ -641,9 +641,9 @@ export abstract class ClusterAdapter extends Adapter {
if (storedRequest) {
ack(
new Error(
`timeout reached: only ${storedRequest.current} responses received out of ${storedRequest.expected}`
`timeout reached: only ${storedRequest.current} responses received out of ${storedRequest.expected}`,
),
storedRequest.responses
storedRequest.responses,
);
this.requests.delete(requestId);
}
@@ -669,7 +669,7 @@ export abstract class ClusterAdapter extends Adapter {
}
protected publish(
message: DistributiveOmit<ClusterMessage, "nsp" | "uid">
message: DistributiveOmit<ClusterMessage, "nsp" | "uid">,
): void {
this.publishAndReturnOffset(message).catch((err) => {
debug("[%s] error while publishing message: %s", this.uid, err);
@@ -677,7 +677,7 @@ export abstract class ClusterAdapter extends Adapter {
}
protected publishAndReturnOffset(
message: DistributiveOmit<ClusterMessage, "nsp" | "uid">
message: DistributiveOmit<ClusterMessage, "nsp" | "uid">,
) {
(message as ClusterMessage).uid = this.uid;
(message as ClusterMessage).nsp = this.nsp.name;
@@ -695,14 +695,14 @@ export abstract class ClusterAdapter extends Adapter {
protected publishResponse(
requesterUid: ServerId,
response: Omit<ClusterResponse, "nsp" | "uid">
response: Omit<ClusterResponse, "nsp" | "uid">,
) {
(response as ClusterResponse).uid = this.uid;
(response as ClusterResponse).nsp = this.nsp.name;
this.doPublishResponse(requesterUid, response as ClusterResponse).catch(
(err) => {
debug("[%s] error while publishing response: %s", this.uid, err);
}
},
);
}
@@ -715,7 +715,7 @@ export abstract class ClusterAdapter extends Adapter {
*/
protected abstract doPublishResponse(
requesterUid: ServerId,
response: ClusterResponse
response: ClusterResponse,
): Promise<void>;
}
@@ -742,7 +742,7 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
heartbeatInterval: 5_000,
heartbeatTimeout: 10_000,
},
opts
opts,
);
this.cleanupTimer = setInterval(() => {
const now = Date.now();
@@ -798,7 +798,7 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
"[%s] new event of type %d from %s",
this.uid,
message.type,
message.uid
message.uid,
);
switch (message.type) {
@@ -846,7 +846,7 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
debug(
'[%s] waiting for %d responses to "serverSideEmit" request',
this.uid,
expectedResponseCount
expectedResponseCount,
);
if (expectedResponseCount <= 0) {
@@ -860,9 +860,9 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
if (storedRequest) {
ack(
new Error(
`timeout reached: missing ${storedRequest.missingUids.size} responses`
`timeout reached: missing ${storedRequest.missingUids.size} responses`,
),
storedRequest.responses
storedRequest.responses,
);
this.customRequests.delete(requestId);
}
@@ -911,8 +911,8 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
if (storedRequest) {
reject(
new Error(
`timeout reached: missing ${storedRequest.missingUids.size} responses`
)
`timeout reached: missing ${storedRequest.missingUids.size} responses`,
),
);
this.customRequests.delete(requestId);
}
@@ -944,7 +944,7 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
"[%s] received response %s to request %s",
this.uid,
response.type,
requestId
requestId,
);
switch (response.type) {
@@ -956,7 +956,7 @@ export abstract class ClusterAdapterWithHeartbeat extends ClusterAdapter {
}
(response.data.sockets as any[]).forEach((socket) =>
request.responses.push(socket)
request.responses.push(socket),
);
request.missingUids.delete(response.uid);

View File

@@ -3,7 +3,7 @@
const alphabet =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(
""
"",
),
length = 64,
map = {};

View File

@@ -198,7 +198,7 @@ export class Adapter extends EventEmitter {
packet: any,
opts: BroadcastOptions,
clientCountCallback: (clientCount: number) => void,
ack: (...args: any[]) => void
ack: (...args: any[]) => void,
) {
const flags = opts.flags || {};
const packetOpts = {
@@ -375,7 +375,7 @@ export class Adapter extends EventEmitter {
*/
public serverSideEmit(packet: any[]): void {
console.warn(
"this adapter does not support the serverSideEmit() functionality"
"this adapter does not support the serverSideEmit() functionality",
);
}
@@ -391,7 +391,7 @@ export class Adapter extends EventEmitter {
*/
public restoreSession(
pid: PrivateSessionId,
offset: string
offset: string,
): Promise<Session> {
return null;
}
@@ -444,7 +444,7 @@ export class SessionAwareAdapter extends Adapter {
override restoreSession(
pid: PrivateSessionId,
offset: string
offset: string,
): Promise<Session> {
const session = this.sessions.get(pid);
if (!session) {
@@ -500,7 +500,7 @@ export class SessionAwareAdapter extends Adapter {
function shouldIncludePacket(
sessionRooms: Room[],
opts: BroadcastOptions
opts: BroadcastOptions,
): boolean {
const included =
opts.rooms.size === 0 || sessionRooms.some((room) => opts.rooms.has(room));

View File

@@ -21,7 +21,7 @@
"ws": "~8.17.1"
},
"scripts": {
"compile": "tsc",
"compile": "rimraf ./dist && tsc",
"test": "npm run format:check && npm run compile && nyc mocha --require ts-node/register test/*.ts",
"format:check": "prettier --parser typescript --check 'lib/**/*.ts' 'test/**/*.ts'",
"format:fix": "prettier --parser typescript --write 'lib/**/*.ts' 'test/**/*.ts'",

View File

@@ -16,7 +16,10 @@ const NODES_COUNT = 3;
class EventEmitterAdapter extends ClusterAdapterWithHeartbeat {
private offset = 1;
constructor(nsp, readonly eventBus) {
constructor(
nsp,
readonly eventBus,
) {
super(nsp, {});
this.eventBus.on("message", (message) => {
this.onMessage(message as ClusterMessage);
@@ -30,7 +33,7 @@ class EventEmitterAdapter extends ClusterAdapterWithHeartbeat {
protected doPublishResponse(
requesterUid: string,
response: ClusterResponse
response: ClusterResponse,
): Promise<void> {
this.eventBus.emit("message", response);
return Promise.resolve();

View File

@@ -153,7 +153,7 @@ describe("socket.io-adapter", () => {
expect(opts.wsPreEncodedFrame.length).to.eql(2);
expect(opts.wsPreEncodedFrame[0]).to.eql(Buffer.from([129, 4]));
expect(opts.wsPreEncodedFrame[1]).to.eql(
Buffer.from([52, 49, 50, 51])
Buffer.from([52, 49, 50, 51]),
);
},
},
@@ -352,7 +352,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(),
except: new Set(),
}
},
);
const offset = packetData[1];
@@ -398,7 +398,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(),
except: new Set(),
}
},
);
adapter.broadcast(
@@ -410,7 +410,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(),
except: new Set(),
}
},
);
adapter.broadcast(
@@ -422,7 +422,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(["r1"]),
except: new Set(),
}
},
);
adapter.broadcast(
@@ -434,7 +434,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(),
except: new Set(["r2"]),
}
},
);
adapter.broadcast(
@@ -446,7 +446,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(),
except: new Set(["r3"]),
}
},
);
adapter.broadcast(
@@ -459,7 +459,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(),
except: new Set(),
}
},
);
adapter.broadcast(
@@ -471,7 +471,7 @@ describe("socket.io-adapter", () => {
{
rooms: new Set(),
except: new Set(),
}
},
);
adapter.broadcast(
@@ -486,7 +486,7 @@ describe("socket.io-adapter", () => {
flags: {
volatile: true,
},
}
},
);
const offset = packetData[1];

View File

@@ -1,69 +1,51 @@
# History
## 2024
- [4.7.5](#475-2024-03-14) (Mar 2024)
- [4.7.4](#474-2024-01-12) (Jan 2024)
- [4.7.3](#473-2024-01-03) (Jan 2024)
## 2023
- [4.7.2](#472-2023-08-02) (Aug 2023)
- [4.7.1](#471-2023-06-28) (Jun 2023)
- [4.7.0](#470-2023-06-22) (Jun 2023)
- [4.6.2](#462-2023-05-31) (May 2023)
- [4.6.1](#461-2023-02-20) (Feb 2023)
- [4.6.0](#460-2023-02-07) (Feb 2023)
## 2022
- [4.5.4](#454-2022-11-22) (Nov 2022)
- [4.5.3](#453-2022-10-15) (Oct 2022)
- [4.5.2](#452-2022-09-02) (Sep 2022)
- [2.5.0](#250-2022-06-26) (Jun 2022) (from the [2.x](https://github.com/socketio/socket.io-client/tree/2.x) branch)
- [4.5.1](#451-2022-05-17) (May 2022)
- [4.5.0](#450-2022-04-23) (Apr 2022)
- [4.4.1](#441-2022-01-06) (Jan 2022)
## 2021
- [4.4.0](#440-2021-11-18) (Nov 2021)
- [4.3.2](#432-2021-10-16) (Oct 2021)
- [4.3.1](#431-2021-10-15) (Oct 2021)
- [4.3.0](#430-2021-10-14) (Oct 2021)
- [4.2.0](#420-2021-08-30) (Aug 2021)
- [4.1.3](#413-2021-07-10) (Jul 2021)
- [4.1.2](#412-2021-05-17) (May 2021)
- [4.1.1](#411-2021-05-11) (May 2021)
- [4.1.0](#410-2021-05-11) (May 2021)
- [4.0.2](#402-2021-05-06) (May 2021)
- [4.0.1](#401-2021-03-31) (Mar 2021)
- [3.1.3](#313-2021-03-12) (Mar 2021) (from the [3.1.x](https://github.com/socketio/socket.io-client/tree/3.1.x) branch)
- [**4.0.0**](#400-2021-03-10) (Mar 2021)
- [3.1.2](#312-2021-02-26) (Feb 2021)
- [3.1.1](#311-2021-02-03) (Feb 2021)
- [3.1.0](#310-2021-01-15) (Jan 2021)
- [3.0.5](#305-2021-01-05) (Jan 2021)
- [2.4.0](#240-2021-01-04) (Jan 2021) (from the [2.x](https://github.com/socketio/socket.io-client/tree/2.x) branch)
## 2020
- [3.0.4](#304-2020-12-07) (Dec 2020)
- [3.0.3](#303-2020-11-19) (Nov 2020)
- [3.0.2](#302-2020-11-17) (Nov 2020)
- [3.0.1](#301-2020-11-09) (Nov 2020)
- [**3.0.0**](#300-2020-11-05) (Nov 2020)
- [2.3.1](#231-2020-09-30) (Sep 2020)
## 2019
- [2.3.0](#230-2019-09-20) (Sep 2019)
## 2018
- [2.2.0](#220-2018-11-29) (Nov 2018)
- [2.1.1](#211-2018-05-17) (May 2018)
- [2.1.0](#210-2018-03-29) (Mar 2018)
| Version | Release date | Bundle size (UMD min+gzip) |
|-------------------------------------------------------------------------------------------------------------|----------------|----------------------------|
| [4.7.5](#475-2024-03-14) | March 2024 | `14.6 KB` |
| [4.7.4](#474-2024-01-12) | January 2024 | `14.5 KB` |
| [4.7.3](#473-2024-01-03) | January 2024 | `14.5 KB` |
| [4.7.2](#472-2023-08-02) | August 2023 | `14.5 KB` |
| [4.7.1](#471-2023-06-28) | June 2023 | `14.1 KB` |
| [4.7.0](#470-2023-06-22) | June 2023 | `14.0 KB` |
| [4.6.2](#462-2023-05-31) | May 2023 | `13.4 KB` |
| [4.6.1](#461-2023-02-20) | February 2023 | `13.3 KB` |
| [4.6.0](#460-2023-02-07) | February 2023 | `13.3 KB` |
| [4.5.4](#454-2022-11-22) | November 2022 | `12.8 KB` |
| [4.5.3](#453-2022-10-15) | October 2022 | `12.8 KB` |
| [4.5.2](#452-2022-09-02) | September 2022 | `12.7 KB` |
| [2.5.0](#250-2022-06-26) (from the [2.x](https://github.com/socketio/socket.io-client/tree/2.x) branch) | June 2022 | `18.8 KB` |
| [4.5.1](#451-2022-05-17) | May 2022 | `12.7 KB` |
| [4.5.0](#450-2022-04-23) | April 2022 | `12.7 KB` |
| [4.4.1](#441-2022-01-06) | January 2022 | `12.3 KB` |
| [4.4.0](#440-2021-11-18) | November 2021 | `12.3 KB` |
| [4.3.2](#432-2021-10-16) | October 2021 | `12.1 KB` |
| [4.3.1](#431-2021-10-15) | October 2021 | `12.1 KB` |
| [4.3.0](#430-2021-10-14) | October 2021 | `12.1 KB` |
| [4.2.0](#420-2021-08-30) | August 2021 | `15.2 KB` |
| [4.1.3](#413-2021-07-10) | July 2021 | `14.9 KB` |
| [4.1.2](#412-2021-05-17) | May 2021 | `14.9 KB` |
| [4.1.1](#411-2021-05-11) | May 2021 | `14.9 KB` |
| [4.1.0](#410-2021-05-11) | May 2021 | `14.9 KB` |
| [4.0.2](#402-2021-05-06) | May 2021 | `14.9 KB` |
| [4.0.1](#401-2021-03-31) | March 2021 | `14.9 KB` |
| [3.1.3](#313-2021-03-12) (from the [3.1.x](https://github.com/socketio/socket.io-client/tree/3.1.x) branch) | March 2021 | `14.6 KB` |
| [**4.0.0**](#400-2021-03-10) | March 2021 | `14.9 KB` |
| [3.1.2](#312-2021-02-26) | February 2021 | `14.6 KB` |
| [3.1.1](#311-2021-02-03) | February 2021 | `14.5 KB` |
| [3.1.0](#310-2021-01-15) | January 2021 | `14.5 KB` |
| [3.0.5](#305-2021-01-05) | January 2021 | `14.5 KB` |
| [2.4.0](#240-2021-01-04) (from the [2.x](https://github.com/socketio/socket.io-client/tree/2.x) branch) | January 2021 | `18.8 KB` |
| [3.0.4](#304-2020-12-07) | December 2020 | `14.6 KB` |
| [3.0.3](#303-2020-11-19) | November 2020 | `14.5 KB` |
| [3.0.2](#302-2020-11-17) | November 2020 | `14.5 KB` |
| [3.0.1](#301-2020-11-09) | November 2020 | `14.7 KB` |
| [**3.0.0**](#300-2020-11-05) | November 2020 | `14.6 KB` |
| [2.3.1](#231-2020-09-30) | September 2020 | `18.8 KB` |
| [2.3.0](#230-2019-09-20) | September 2019 | `19.6 KB` |
| [2.2.0](#220-2018-11-29) | November 2018 | `18.6 KB` |
| [2.1.1](#211-2018-05-17) | May 2018 | `18.7 KB` |
| [2.1.0](#210-2018-03-29) | March 2018 | `18.7 KB` |
# Release notes

View File

@@ -24,12 +24,12 @@ const cache: Record<string, Manager> = {};
*/
function lookup(opts?: Partial<ManagerOptions & SocketOptions>): Socket;
function lookup(
uri: string,
opts?: Partial<ManagerOptions & SocketOptions>
uri?: string,
opts?: Partial<ManagerOptions & SocketOptions>,
): Socket;
function lookup(
uri?: string | Partial<ManagerOptions & SocketOptions>,
opts?: Partial<ManagerOptions & SocketOptions>
opts?: Partial<ManagerOptions & SocketOptions>,
): Socket {
if (typeof uri === "object") {
opts = uri;

View File

@@ -100,7 +100,7 @@ interface ManagerReservedEvents {
export class Manager<
ListenEvents extends EventsMap = DefaultEventsMap,
EmitEvents extends EventsMap = ListenEvents
EmitEvents extends EventsMap = ListenEvents,
> extends Emitter<{}, {}, ManagerReservedEvents> {
/**
* The Engine.IO client instance
@@ -152,11 +152,11 @@ export class Manager<
constructor(uri?: string, opts?: Partial<ManagerOptions>);
constructor(
uri?: string | Partial<ManagerOptions>,
opts?: Partial<ManagerOptions>
opts?: Partial<ManagerOptions>,
);
constructor(
uri?: string | Partial<ManagerOptions>,
opts?: Partial<ManagerOptions>
opts?: Partial<ManagerOptions>,
) {
super();
if (uri && "object" === typeof uri) {
@@ -201,6 +201,9 @@ export class Manager<
public reconnection(v?: boolean): this | boolean {
if (!arguments.length) return this._reconnection;
this._reconnection = !!v;
if (!v) {
this.skipReconnect = true;
}
return this;
}
@@ -405,7 +408,7 @@ export class Manager<
on(socket, "error", this.onerror.bind(this)),
on(socket, "close", this.onclose.bind(this)),
// @ts-ignore
on(this.decoder, "decoded", this.ondecoded.bind(this))
on(this.decoder, "decoded", this.ondecoded.bind(this)),
);
}
@@ -531,7 +534,6 @@ export class Manager<
this.skipReconnect = true;
this._reconnecting = false;
this.onclose("forced close");
if (this.engine) this.engine.close();
}
/**
@@ -544,7 +546,11 @@ export class Manager<
}
/**
* Called upon engine close.
* Called when:
*
* - the low-level engine is closed
* - the parser encountered a badly formatted packet
* - all sockets are disconnected
*
* @private
*/
@@ -552,6 +558,7 @@ export class Manager<
debug("closed due to %s", reason);
this.cleanup();
this.engine?.close();
this.backoff.reset();
this._readyState = "closed";
this.emitReserved("close", reason, description);

View File

@@ -3,7 +3,7 @@ import { Emitter } from "@socket.io/component-emitter";
export function on(
obj: Emitter<any, any>,
ev: string,
fn: (err?: any) => any
fn: (err?: any) => any,
): VoidFunction {
obj.on(ev, fn);
return function subDestroy(): void {

View File

@@ -116,7 +116,7 @@ interface SocketReservedEvents {
connect_error: (err: Error) => void;
disconnect: (
reason: Socket.DisconnectReason,
description?: DisconnectDescription
description?: DisconnectDescription,
) => void;
}
@@ -146,7 +146,7 @@ interface SocketReservedEvents {
*/
export class Socket<
ListenEvents extends EventsMap = DefaultEventsMap,
EmitEvents extends EventsMap = ListenEvents
EmitEvents extends EventsMap = ListenEvents,
> extends Emitter<ListenEvents, EmitEvents, SocketReservedEvents> {
public readonly io: Manager<ListenEvents, EmitEvents>;
@@ -440,16 +440,13 @@ export class Socket<
packet.id = id;
}
const isTransportWritable =
this.io.engine &&
this.io.engine.transport &&
this.io.engine.transport.writable;
const isTransportWritable = this.io.engine?.transport?.writable;
const isConnected = this.connected && !this.io.engine?._hasPingExpired();
const discardPacket =
this.flags.volatile && (!isTransportWritable || !this.connected);
const discardPacket = this.flags.volatile && !isTransportWritable;
if (discardPacket) {
debug("discard packet as the transport is not currently writable");
} else if (this.connected) {
} else if (isConnected) {
this.notifyOutgoingListeners(packet);
this.packet(packet);
} else {
@@ -554,7 +551,7 @@ export class Socket<
debug(
"packet [%d] is discarded after %d tries",
packet.id,
packet.tryCount
packet.tryCount,
);
this._queue.shift();
if (ack) {
@@ -591,7 +588,7 @@ export class Socket<
if (packet.pending && !force) {
debug(
"packet [%d] has already been sent and is waiting for an ack",
packet.id
packet.id,
);
return;
}
@@ -665,7 +662,7 @@ export class Socket<
*/
private onclose(
reason: Socket.DisconnectReason,
description?: DisconnectDescription
description?: DisconnectDescription,
): void {
debug("close (%s)", reason);
this.connected = false;
@@ -683,7 +680,7 @@ export class Socket<
private _clearAcks() {
Object.keys(this.acks).forEach((id) => {
const isBuffered = this.sendBuffer.some(
(packet) => String(packet.id) === id
(packet) => String(packet.id) === id,
);
if (!isBuffered) {
// note: handlers that do not accept an error as first argument are ignored here
@@ -716,8 +713,8 @@ export class Socket<
this.emitReserved(
"connect_error",
new Error(
"It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"
)
"It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)",
),
);
}
break;
@@ -967,7 +964,7 @@ export class Socket<
* @returns self
*/
public timeout(
timeout: number
timeout: number,
): Socket<ListenEvents, DecorateAcknowledgements<EmitEvents>> {
this.flags.timeout = timeout;
return this;

View File

@@ -39,7 +39,7 @@ type ParsedUrl = {
export function url(
uri: string | ParsedUrl,
path: string = "",
loc?: Location
loc?: Location,
): ParsedUrl {
let obj = uri as ParsedUrl;

View File

@@ -47,16 +47,17 @@
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"engine.io-client": "~6.6.0",
"socket.io-parser": "~4.2.4"
},
"scripts": {
"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:node": "mocha --require ts-node/register --reporter dot --require test/support/hooks.ts --timeout 5000 --exit test/index.ts",
"test:node": "mocha --require ts-node/register --require test/support/hooks.ts --exit test/index.ts",
"test:browser": "ts-node test/browser-runner.ts",
"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",
"bundle-size": "node support/bundle-size.js",
"format:check": "prettier --check \"*.js\" \"lib/**/*.ts\" \"test/**/*.ts\" \"support/**/*.js\"",
"format:fix": "prettier --write \"*.js\" \"lib/**/*.ts\" \"test/**/*.ts\" \"support/**/*.js\"",
"prepack": "npm run compile"

View File

@@ -0,0 +1,35 @@
const { resolve } = require("node:path");
const { readFile } = require("node:fs/promises");
const { gzipSync, brotliCompressSync } = require("node:zlib");
const bundles = [
{
name: "UMD bundle",
path: "dist/socket.io.min.js",
},
{
name: "ESM bundle",
path: "dist/socket.io.esm.min.js",
},
];
function format(size) {
return (size / 1024).toFixed(1);
}
async function main() {
for (const bundle of bundles) {
const path = resolve(bundle.path);
const content = await readFile(path);
const gzip = gzipSync(content);
const brotli = brotliCompressSync(content);
console.log(`${bundle.name}`);
console.log(`min: ${format(content.length)} KB`);
console.log(`min+gzip: ${format(gzip.length)} KB`);
console.log(`min+br: ${format(brotli.length)} KB`);
console.log();
}
}
main();

View File

@@ -15,7 +15,15 @@ module.exports = {
file: "./dist/socket.io.esm.min.js",
format: "esm",
sourcemap: true,
plugins: [terser()],
plugins: [
terser({
mangle: {
properties: {
regex: /^_/,
},
},
}),
],
banner,
},
plugins: [

View File

@@ -10,25 +10,15 @@ const banner = `/*!
* Released under the MIT License.
*/`;
module.exports = {
input: "./build/esm/browser-entrypoint.js",
output: [
{
file: "./dist/socket.io.js",
format: "umd",
name: "io",
sourcemap: true,
banner,
},
{
file: "./dist/socket.io.min.js",
format: "umd",
name: "io",
sourcemap: true,
plugins: [terser()],
banner,
},
],
const devBundle = {
input: "./build/esm-debug/browser-entrypoint.js",
output: {
file: "./dist/socket.io.js",
format: "umd",
name: "io",
sourcemap: true,
banner,
},
plugins: [
nodeResolve({
browser: true,
@@ -37,7 +27,55 @@ module.exports = {
babel({
babelHelpers: "bundled",
presets: [["@babel/env"]],
plugins: ["@babel/plugin-transform-object-assign"],
plugins: [
"@babel/plugin-transform-object-assign",
[
"@babel/plugin-transform-classes",
{
loose: true,
},
],
],
}),
],
};
const prodBundle = {
input: "./build/esm/browser-entrypoint.js",
output: {
file: "./dist/socket.io.min.js",
format: "umd",
name: "io",
sourcemap: true,
plugins: [
terser({
mangle: {
properties: {
regex: /^_/,
},
},
}),
],
banner,
},
plugins: [
nodeResolve({
browser: true,
}),
babel({
babelHelpers: "bundled",
presets: [["@babel/env"]],
plugins: [
"@babel/plugin-transform-object-assign",
[
"@babel/plugin-transform-classes",
{
loose: true,
},
],
],
}),
],
};
module.exports = [devBundle, prodBundle];

View File

@@ -1,13 +1,15 @@
const base = require("./rollup.config.umd.js");
const base = require("./rollup.config.umd.js")[1];
const alias = require("@rollup/plugin-alias");
const commonjs = require("@rollup/plugin-commonjs");
module.exports = {
...base,
output: {
...base.output[1],
...base.output,
file: "./dist/socket.io.msgpack.min.js",
},
plugins: [
commonjs(),
alias({
entries: [
{

View File

@@ -1,4 +1,4 @@
import expect from "expect.js";
import expect = require("expect.js");
import { io } from "..";
import { wrap, BASE_URL, success } from "./support/util";
@@ -7,6 +7,7 @@ describe("connection state recovery", () => {
return wrap((done) => {
const socket = io(BASE_URL, {
forceNew: true,
reconnectionDelay: 10,
});
expect(socket.recovered).to.eql(false);

View File

@@ -1,9 +1,10 @@
import expect from "expect.js";
import expect = require("expect.js");
import { io, Manager, ManagerOptions } from "..";
import hasCORS from "has-cors";
import { install } from "@sinonjs/fake-timers";
import textBlobBuilder from "text-blob-builder";
import { BASE_URL, wrap } from "./support/util";
import { nextTick } from "engine.io-client";
describe("connection", () => {
it("should connect to localhost", () => {
@@ -158,7 +159,7 @@ describe("connection", () => {
it("should reconnect by default", () => {
return wrap((done) => {
const socket = io(BASE_URL, { forceNew: true, reconnectionDelay: 0 });
const socket = io(BASE_URL, { forceNew: true, reconnectionDelay: 10 });
socket.io.on("reconnect", () => {
socket.disconnect();
done();
@@ -166,7 +167,7 @@ describe("connection", () => {
setTimeout(() => {
socket.io.engine.close();
}, 500);
}, 100);
});
});
@@ -189,7 +190,7 @@ describe("connection", () => {
it("should reconnect automatically after reconnecting manually", () => {
return wrap((done) => {
const socket = io(BASE_URL, { forceNew: true });
const socket = io(BASE_URL, { forceNew: true, reconnectionDelay: 10 });
socket
.once("connect", () => {
socket.disconnect();
@@ -202,7 +203,7 @@ describe("connection", () => {
socket.connect();
setTimeout(() => {
socket.io.engine.close();
}, 500);
}, 100);
});
});
});
@@ -281,13 +282,13 @@ describe("connection", () => {
});
socket.io.once("error", () => {
socket.io.on("reconnect_attempt", () => {
expect().fail();
done(new Error("should not happen"));
});
socket.disconnect();
// set a timeout to let reconnection possibly fire
setTimeout(() => {
done();
}, 500);
}, 100);
});
});
});
@@ -301,13 +302,13 @@ describe("connection", () => {
});
socket.io.once("reconnect_attempt", () => {
socket.io.on("reconnect_attempt", () => {
expect().fail();
done(new Error("should not happen"));
});
socket.disconnect();
// set a timeout to let reconnection possibly fire
setTimeout(() => {
done();
}, 500);
}, 100);
});
});
});
@@ -332,27 +333,29 @@ describe("connection", () => {
it("should stop reconnecting on a socket and keep to reconnect on another", () => {
return wrap((done) => {
const manager = new Manager(BASE_URL);
const manager = new Manager(BASE_URL, {
reconnectionDelay: 10,
});
const socket1 = manager.socket("/");
const socket2 = manager.socket("/asd");
manager.on("reconnect_attempt", () => {
socket1.on("connect", () => {
expect().fail();
done(new Error("should not happen"));
});
socket2.on("connect", () => {
setTimeout(() => {
socket2.disconnect();
manager._close();
done();
}, 500);
}, 50);
});
socket1.disconnect();
});
setTimeout(() => {
manager.engine.close();
}, 1000);
}, 100);
});
});
@@ -441,20 +444,19 @@ describe("connection", () => {
reconnection: true,
reconnectionDelay: 10,
});
const cb = () => {
socket.close();
expect().fail();
};
manager.on("reconnect_attempt", cb);
var socket = manager.socket("/valid");
manager.on("reconnect_attempt", () => {
done(new Error("should not happen"));
});
const socket = manager.socket("/valid");
socket.on("connect", () => {
// set a timeout to let reconnection possibly fire
setTimeout(() => {
socket.close();
manager._close();
done();
}, 1000);
}, 100);
});
});
});
@@ -518,6 +520,28 @@ describe("connection", () => {
});
});
it("should stop trying to reconnect", () => {
return wrap((done) => {
const manager = new Manager("http://localhost:9823", {
reconnectionDelay: 10,
});
manager.on("reconnect_error", () => {
// disable current reconnection loop
manager.reconnection(false);
manager.on("reconnect_attempt", () => {
done(new Error("should not happen"));
});
setTimeout(() => {
manager._close();
done();
}, 100);
});
});
});
// Ignore incorrect connection test for old IE due to no support for
// `script.onerror` (see: http://requirejs.org/docs/api.html#ieloadfail)
if (!global.document || hasCORS) {
@@ -549,12 +573,12 @@ describe("connection", () => {
return wrap((done) => {
const manager = new Manager("http://localhost:9823", {
reconnection: false,
reconnectionDelay: 10,
});
manager.on("reconnect_attempt", () => {
done(new Error("should not happen"));
});
const cb = () => {
socket.close();
expect().fail();
};
manager.on("reconnect_attempt", cb);
manager.on("error", () => {
// set a timeout to let reconnection possibly fire
@@ -562,7 +586,7 @@ describe("connection", () => {
socket.disconnect();
manager._close();
done();
}, 1000);
}, 100);
});
var socket = manager.socket("/invalid");
@@ -574,9 +598,10 @@ describe("connection", () => {
const manager = new Manager("http://localhost:9823", {
reconnection: true,
reconnectionAttempts: 2,
reconnectionDelay: 10,
});
let delay = Math.floor(
manager.reconnectionDelay() * manager.randomizationFactor() * 0.5
manager.reconnectionDelay() * manager.randomizationFactor() * 0.5,
);
delay = Math.max(delay, 10);
@@ -894,4 +919,30 @@ describe("connection", () => {
});
});
});
it("should close the engine upon decoding exception", () => {
return wrap((done) => {
const manager = new Manager(BASE_URL, {
autoConnect: true,
reconnectionDelay: 50,
});
let engine = manager.engine;
manager.on("open", () => {
nextTick(() => {
// @ts-expect-error emit() is private
manager.engine.emit("data", "bad");
});
});
manager.on("reconnect", () => {
expect(manager.engine === engine).to.be(false);
expect(engine.readyState).to.eql("closed");
manager._close();
done();
});
});
});
});

View File

@@ -5,4 +5,4 @@ const socket = io("http://localhost:3211", {
setTimeout(() => {
console.log("process should not exit");
}, 500);
}, 50);

View File

@@ -9,4 +9,4 @@ socket.on("open", () => {
setTimeout(() => {
console.log("process should exit now");
}, 500);
}, 50);

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