refactor(typings): add types for io.engine (#4591)
This adds typings for the socket.io engine field, which offers better IntelliSense when retrieving the server, as well as more confidence on the developer-side of what types of fields are entering the server. Related: #4590
docs: add Rust client implementation (#4592)
client-only implementation -- it *may* add server-side support in the future.
perf: precompute the WebSocket frames when broadcasting
Note: - only packets without binary attachments are affected - the permessage-deflate extension must be disabled (which is the default) Related: - socketio/socket.io-adapter@5f7b47d - socketio/engine.io@5e34722
feat: implement connection state recovery
Connection state recovery allows a client to reconnect after a
temporary disconnection and restore its state:
- id
- rooms
- data
- missed packets
Usage:
```js
import { Server } from "socket.io";
const io = new Server({
connectionStateRecovery: {
// default values
maxDisconnectionDuration: 2 * 60 * 1000,
skipMiddlewares: true,
},
});
io.on("connection", (socket) => {
console.log(socket.recovered); // whether the state was recovered or not
});
```
Here's how it works:
- the server sends a session ID during the handshake (which is
different from the current `id` attribute, which is public and can be
freely shared)
- the server also includes an offset in each packet (added at the end
of the data array, for backward compatibility)
- upon temporary disconnection, the server stores the client state for
a given delay (implemented at the adapter level)
- upon reconnection, the client sends both the session ID and the last
offset it has processed, and the server tries to restore the state
A few notes:
- the base adapter exposes two additional methods, persistSession() and
restoreSession(), that must be implemented by the other adapters in
order to allow the feature to work within a cluster
See: socketio/socket.io-adapter@f529412
- acknowledgements are not affected, because it won't work if the
client reconnects on another server (as the ack id is local)
- any disconnection that lasts longer than the
`maxDisconnectionDuration` value will result in a new session, so users
will still need to care for the state reconciliation between the server
and the client
Related: #4510fix(typings): properly type emits with timeout
When emitting with a timeout (added in version 4.4.0), the "err"
argument was not properly typed and would require to split the client
and server typings. It will now be automatically inferred as an Error
object.
Workaround for previous versions:
```ts
type WithTimeoutAck<isEmitter extends boolean, args extends any[]> = isEmitter extends true ? [Error, ...args] : args;
interface ClientToServerEvents<isEmitter extends boolean = false> {
withAck: (data: { argName: boolean }, callback: (...args: WithTimeoutAck<isEmitter, [string]>) => void) => void;
}
interface ServerToClientEvents<isEmitter extends boolean = false> {
}
const io = new Server<ClientToServerEvents, ServerToClientEvents<true>>(3000);
io.on("connection", (socket) => {
socket.on("withAck", (val, cb) => {
cb("123");
});
});
const socket: Socket<ServerToClientEvents, ClientToServerEvents<true>> = ioc("http://localhost:3000");
socket.timeout(100).emit("withAck", { argName: true }, (err, val) => {
// ...
});
```
Related: socketio/socket.io-client#1555test: add test with onAnyOutgoing() and binary attachments
Related: - #4374 - socketio/socket.io-parser@ae8dd88
feat: add the ability to clean up empty child namespaces (#4602)
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: socketio/socket.io-redis-adapter#480
feat: add promise-based acknowledgements
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:
- #4175
- #4577
- #4583refactor: add charset when serving the bundle files
Reference: https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Type Related: #4589
fix: add timeout method to remote socket (#4558)
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: #4595refactor: do not include the pid by default
So that the client knows whether the connection state recovery feature is enabled. See also: 54d5ee0
feat: add description to the disconnecting and disconnect events (#4622)
See also: socketio/socket.io-client@b862924
revert: feat: expose current offset to allow deduplication
This reverts commit 4e64123. 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.
chore: bump engine.io to version 6.4.0
Diff: socketio/engine.io@6.3.1...6.4.0 Release notes: https://github.com/socketio/engine.io/releases/tag/6.4.0
refactor: do not persist session if connection state recovery if disa…
…bled 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]: 54d5ee0
fix(types): fix nodenext module resolution compatibility (#4625)