The packet ID cannot be used for deduplication, because it's only
unique for the given session. If you reconnect on another server and
try to resend a packet, then the server won't be able to know whether
the packet has already been processed or not.
This bug was introduced in [1]: a multiplexed socket could in some
cases send multiple CONNECT packets, resulting in duplicate connections
on the server side.
A cached socket will now be reopened only if it was inactive, that is,
if one had explicitly called socket.disconnect() before.
Related: https://github.com/socketio/socket.io-client/issues/1460
[1]: b7dd891e89
In the previous implementation added in [1], the socket would try to
send the packet even if it was disconnected, which would needlessly
exhaust the number of retries.
[1]: 655dce9755
This is a follow-up commit of [1]. Without it, adapter.persistSession()
would be called even if the connection state recovery feature was
disabled.
[1]: 54d5ee05a6
This reverts commit 4e64123862.
Using the id of the socket is not possible, since it is lost upon
reconnection (unless connection recovery is successful), so we revert
the previous change.
This commit implements middlewares at the Engine.IO level, because
Socket.IO middlewares are meant for namespace authorization and are not
executed during a classic HTTP request/response cycle.
A workaround was possible by using the allowRequest option and the
"headers" event, but this feels way cleaner and works with upgrade
requests too.
Syntax:
```js
engine.use((req, res, next) => {
// do something
next();
});
// with express-session
import session from "express-session";
engine.use(session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: { secure: true }
});
// with helmet
import helmet from "helmet";
engine.use(helmet());
```
Related:
- https://github.com/socketio/engine.io/issues/668
- https://github.com/socketio/engine.io/issues/651
- https://github.com/socketio/socket.io/issues/4609
- https://github.com/socketio/socket.io/issues/3933
- a lot of other issues asking for compatibility with express-session
For some reason, "import { type A }" does not work in some cases:
> node_modules/engine.io-client/build/esm/socket.d.ts:2:15 - error TS1005: ',' expected.
> 2 import { type Packet, type BinaryType, RawData } from "engine.io-parser";
References: https://www.typescriptlang.org/docs/handbook/modules.html#importing-types
Syntax:
```js
const socket = io({
retries: 3,
ackTimeout: 10000
});
// "my-event" will be sent up to 4 times (1 + 3), until the server sends an acknowledgement
socket.emit("my-event", (err) => {});
```
Notes:
- the order of the packets is guaranteed, as we send packets one by one
- the same packet id is reused for consecutive retries, in order to
allow deduplication on the server side
This commit adds some syntactic sugar around acknowledgements:
```js
// without timeout
const response = await socket.emitWithAck("hello", "world");
// with a specific timeout
try {
const response = await socket.timeout(1000).emitWithAck("hello", "world");
} catch (err) {
// the server did not acknowledge the event in the given delay
}
```
Note: enviroments that do not support Promises ([1]) will need to add a
polyfill in order to use this feature
See also: 184f3cf7af
[1]: https://caniuse.com/promises
Connection state recovery allows a client to reconnect after a
temporary disconnection and restore its state:
- id
- rooms
- data
- missed packets
See also: 54d5ee05a6
The RemoteSocket interface, which is returned when the client is
connected on another Socket.IO server of the cluster, was lacking the
`timeout()` method.
Syntax:
```js
const sockets = await io.fetchSockets();
for (const socket of sockets) {
if (someCondition) {
socket.timeout(1000).emit("some-event", (err) => {
if (err) {
// the client did not acknowledge the event in the given delay
}
});
}
}
```
Related: https://github.com/socketio/socket.io/issues/4595
This commit adds some syntactic sugar around acknowledgements:
- `emitWithAck()`
```js
try {
const responses = await io.timeout(1000).emitWithAck("some-event");
console.log(responses); // one response per client
} catch (e) {
// some clients did not acknowledge the event in the given delay
}
io.on("connection", async (socket) => {
// without timeout
const response = await socket.emitWithAck("hello", "world");
// with a specific timeout
try {
const response = await socket.timeout(1000).emitWithAck("hello", "world");
} catch (err) {
// the client did not acknowledge the event in the given delay
}
});
```
- `serverSideEmitWithAck()`
```js
try {
const responses = await io.timeout(1000).serverSideEmitWithAck("some-event");
console.log(responses); // one response per server (except itself)
} catch (e) {
// some servers did not acknowledge the event in the given delay
}
```
Related:
- https://github.com/socketio/socket.io/issues/4175
- https://github.com/socketio/socket.io/issues/4577
- https://github.com/socketio/socket.io/issues/4583
This commit adds a new option, "cleanupEmptyChildNamespaces". With this
option enabled (disabled by default), when a socket disconnects from a
dynamic namespace and if there are no other sockets connected to it
then the namespace will be cleaned up and its adapter will be closed.
Note: the namespace can be connected to later (it will be recreated)
Related: https://github.com/socketio/socket.io-redis-adapter/issues/480