mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
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: f5294126a8
- 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: https://github.com/socketio/socket.io/discussions/4510
120 lines
2.8 KiB
TypeScript
120 lines
2.8 KiB
TypeScript
import type { Server } from "../..";
|
|
import {
|
|
io as ioc,
|
|
ManagerOptions,
|
|
Socket as ClientSocket,
|
|
SocketOptions,
|
|
} from "socket.io-client";
|
|
import request from "supertest";
|
|
|
|
const expect = require("expect.js");
|
|
const i = expect.stringify;
|
|
|
|
// add support for Set/Map
|
|
const contain = expect.Assertion.prototype.contain;
|
|
expect.Assertion.prototype.contain = function (...args) {
|
|
if (this.obj instanceof Set || this.obj instanceof Map) {
|
|
args.forEach((obj) => {
|
|
this.assert(
|
|
this.obj.has(obj),
|
|
function () {
|
|
return "expected " + i(this.obj) + " to contain " + i(obj);
|
|
},
|
|
function () {
|
|
return "expected " + i(this.obj) + " to not contain " + i(obj);
|
|
}
|
|
);
|
|
});
|
|
return this;
|
|
}
|
|
return contain.apply(this, args);
|
|
};
|
|
|
|
export function createClient(
|
|
io: Server,
|
|
nsp: string = "/",
|
|
opts?: Partial<ManagerOptions & SocketOptions>
|
|
): ClientSocket {
|
|
// @ts-ignore
|
|
const port = io.httpServer.address().port;
|
|
return ioc(`http://localhost:${port}${nsp}`, opts);
|
|
}
|
|
|
|
export function success(
|
|
done: Function,
|
|
io: Server,
|
|
...clients: ClientSocket[]
|
|
) {
|
|
io.close();
|
|
clients.forEach((client) => client.disconnect());
|
|
done();
|
|
}
|
|
|
|
export function successFn(
|
|
done: () => void,
|
|
sio: Server,
|
|
...clientSockets: ClientSocket[]
|
|
) {
|
|
return () => success(done, sio, ...clientSockets);
|
|
}
|
|
|
|
export function getPort(io: Server): number {
|
|
// @ts-ignore
|
|
return io.httpServer.address().port;
|
|
}
|
|
|
|
export function createPartialDone(count: number, done: (err?: Error) => void) {
|
|
let i = 0;
|
|
return () => {
|
|
if (++i === count) {
|
|
done();
|
|
} else if (i > count) {
|
|
done(new Error(`partialDone() called too many times: ${i} > ${count}`));
|
|
}
|
|
};
|
|
}
|
|
|
|
export function waitFor<T = unknown>(emitter, event) {
|
|
return new Promise<T>((resolve) => {
|
|
emitter.once(event, resolve);
|
|
});
|
|
}
|
|
|
|
// TODO: update superagent as latest release now supports promises
|
|
export function eioHandshake(httpServer): Promise<string> {
|
|
return new Promise((resolve) => {
|
|
request(httpServer)
|
|
.get("/socket.io/")
|
|
.query({ transport: "polling", EIO: 4 })
|
|
.end((err, res) => {
|
|
const sid = JSON.parse(res.text.substring(1)).sid;
|
|
resolve(sid);
|
|
});
|
|
});
|
|
}
|
|
|
|
export function eioPush(httpServer, sid: string, body: string): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
request(httpServer)
|
|
.post("/socket.io/")
|
|
.send(body)
|
|
.query({ transport: "polling", EIO: 4, sid })
|
|
.expect(200)
|
|
.end(() => {
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
export function eioPoll(httpServer, sid): Promise<string> {
|
|
return new Promise((resolve) => {
|
|
request(httpServer)
|
|
.get("/socket.io/")
|
|
.query({ transport: "polling", EIO: 4, sid })
|
|
.expect(200)
|
|
.end((err, res) => {
|
|
resolve(res.text);
|
|
});
|
|
});
|
|
}
|