Skip to main content
Version: 4.x

Connection state recovery

Connection state recovery is a feature which allows restoring a client's state after a temporary disconnection, including any missed packets.

Disclaimer

Under real conditions, a Socket.IO client will inevitably experience temporary disconnections, regardless of the quality of the connection.

This feature will help you cope with such disconnections, but please be aware that the recovery will not always be successful. That's why you will still need to handle the case where the states of the client and the server must be synchronized.

Usage

The server must explicitly enable connection state recovery:

const io = new Server(httpServer, {
connectionStateRecovery: {
// the backup duration of the sessions and the packets
maxDisconnectionDuration: 2 * 60 * 1000,
// whether to skip middlewares upon successful recovery
skipMiddlewares: false,
}
});
tip

The connection state recovery feature is designed for dealing with intermittent disconnections, so please use a sensible value for maxDisconnectionDuration (that is, not Infinity).

Upon an unexpected disconnection (i.e. no manual disconnection with socket.disconnect()), the server will store the id, the rooms and the data attribute of the socket.

Then upon reconnection, the server will try to restore the state of the client. The recovered attribute indicates whether this recovery was successful:

Server

io.on("connection", (socket) => {
if (socket.recovered) {
// recovery was successful: socket.id, socket.rooms and socket.data were restored
} else {
// new or unrecoverable session
}
});

Client

socket.on("connect", () => {
if (socket.recovered) {
// any event missed during the disconnection period will be received now
} else {
// new or unrecoverable session
}
});

You can check that the recovery is working by forcefully closing the underlying engine:

import { io } from "socket.io-client";

const socket = io({
reconnectionDelay: 10000, // defaults to 1000
reconnectionDelayMax: 10000 // defaults to 5000
});

socket.on("connect", () => {
console.log("recovered?", socket.recovered);

setTimeout(() => {
if (socket.io.engine) {
// close the low-level connection and trigger a reconnection
socket.io.engine.close();
}
}, 10000);
});
tip

You can also run this example directly in your browser on:

skipMiddlewares option

If the skipMiddlewares option is set to true, then the middlewares will be skipped when the connection is successfully recovered:

function computeUserIdFromHeaders(headers) {
// to be implemented
}

// this middleware will be skipped if the connection is successfully recovered
io.use(async (socket, next) => {
socket.data.userId = await computeUserIdFromHeaders(socket.handshake.headers);

next();
});

io.on("connection", (socket) => {
// the userId attribute will either come:
// - from the middleware above (first connection or failed recovery)
// - from the recovery mechanism
console.log("userId", socket.data.userId);
});
caution

This might, for example, allow users blocked during the disconnection period to reconnect without going through the middleware validations. So please use this option with caution.

Compatibility with existing adapters

AdapterSupport?
Built-in adapter (in memory)YES ✅
Redis adapterNO1
Redis Streams adapterYES ✅
MongoDB adapterYES ✅ (since version 0.3.0)
Postgres adapterWIP
Cluster adapterWIP

[1] Persisting the packets is not compatible with the Redis PUB/SUB mechanism.

How it works under the hood

  • the server sends a session ID during the handshake (which is different from the existing id attribute, which is public and can be freely shared)

Example:

40{"sid":"GNpWD7LbGCBNCr8GAAAB","pid":"YHcX2sdAF1z452-HAAAW"}

where

4 => the Engine.IO message type
0 => the Socket.IO CONNECT type
GN...AB => the public id of the session
YH...AW => the private id of the session
  • the server also includes an offset in each packet (added at the end of the data array, for backward compatibility)

Example:

42["foo","MzUPkW0"]

where

4 => the Engine.IO message type
2 => the Socket.IO EVENT type
foo => the event name (socket.emit("foo"))
MzUPkW0 => the offset
note

For the recovery to succeed, the server must send at least one event, in order to initialize the offset on the client side.

  • 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

Example:

40{"pid":"YHcX2sdAF1z452-HAAAW","offset":"MzUPkW0"}

where

4 => the Engine.IO message type
0 => the Socket.IO CONNECT type
YH...AW => the private id of the session
MzUPkW0 => the last processed offset