socket.io-adapter
Advanced tools
Comparing version 2.4.0 to 2.5.0
/// <reference types="node" /> | ||
import { EventEmitter } from "events"; | ||
export declare type SocketId = string; | ||
export declare type Room = string; | ||
/** | ||
* A public ID, sent by the server at the beginning of the Socket.IO session and which can be used for private messaging | ||
*/ | ||
export type SocketId = string; | ||
/** | ||
* A private ID, sent by the server at the beginning of the Socket.IO session and used for connection state recovery | ||
* upon reconnection | ||
*/ | ||
export type PrivateSessionId = string; | ||
export type Room = string; | ||
export interface BroadcastFlags { | ||
@@ -15,5 +23,14 @@ volatile?: boolean; | ||
rooms: Set<Room>; | ||
except?: Set<SocketId>; | ||
except?: Set<Room>; | ||
flags?: BroadcastFlags; | ||
} | ||
interface SessionToPersist { | ||
sid: SocketId; | ||
pid: PrivateSessionId; | ||
rooms: Room[]; | ||
data: unknown; | ||
} | ||
export type Session = SessionToPersist & { | ||
missedPackets: unknown[][]; | ||
}; | ||
export declare class Adapter extends EventEmitter { | ||
@@ -95,2 +112,3 @@ readonly nsp: any; | ||
broadcastWithAck(packet: any, opts: BroadcastOptions, clientCountCallback: (clientCount: number) => void, ack: (...args: any[]) => void): void; | ||
private _encode; | ||
/** | ||
@@ -142,2 +160,23 @@ * Gets a list of sockets by sid. | ||
serverSideEmit(packet: any[]): void; | ||
/** | ||
* Save the client session in order to restore it upon reconnection. | ||
*/ | ||
persistSession(session: SessionToPersist): void; | ||
/** | ||
* Restore the session and find the packets that were missed by the client. | ||
* @param pid | ||
* @param offset | ||
*/ | ||
restoreSession(pid: PrivateSessionId, offset: string): Promise<Session>; | ||
} | ||
export declare class SessionAwareAdapter extends Adapter { | ||
readonly nsp: any; | ||
private readonly maxDisconnectionDuration; | ||
private sessions; | ||
private packets; | ||
constructor(nsp: any); | ||
persistSession(session: SessionToPersist): void; | ||
restoreSession(pid: PrivateSessionId, offset: string): Promise<Session>; | ||
broadcast(packet: any, opts: BroadcastOptions): void; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Adapter = void 0; | ||
exports.SessionAwareAdapter = exports.Adapter = void 0; | ||
const events_1 = require("events"); | ||
const yeast_1 = require("./contrib/yeast"); | ||
const ws_1 = require("ws"); | ||
class Adapter extends events_1.EventEmitter { | ||
@@ -112,7 +114,7 @@ /** | ||
volatile: flags.volatile, | ||
compress: flags.compress | ||
compress: flags.compress, | ||
}; | ||
packet.nsp = this.nsp.name; | ||
const encodedPackets = this.encoder.encode(packet); | ||
this.apply(opts, socket => { | ||
const encodedPackets = this._encode(packet, packetOpts); | ||
this.apply(opts, (socket) => { | ||
if (typeof socket.notifyOutgoingListeners === "function") { | ||
@@ -144,3 +146,3 @@ socket.notifyOutgoingListeners(packet); | ||
volatile: flags.volatile, | ||
compress: flags.compress | ||
compress: flags.compress, | ||
}; | ||
@@ -150,5 +152,5 @@ packet.nsp = this.nsp.name; | ||
packet.id = this.nsp._ids++; | ||
const encodedPackets = this.encoder.encode(packet); | ||
const encodedPackets = this._encode(packet, packetOpts); | ||
let clientCount = 0; | ||
this.apply(opts, socket => { | ||
this.apply(opts, (socket) => { | ||
// track the total number of acknowledgements that are expected | ||
@@ -165,2 +167,18 @@ clientCount++; | ||
} | ||
_encode(packet, packetOpts) { | ||
const encodedPackets = this.encoder.encode(packet); | ||
if (encodedPackets.length === 1 && typeof encodedPackets[0] === "string") { | ||
// "4" being the "message" packet type in the Engine.IO protocol | ||
const data = Buffer.from("4" + encodedPackets[0]); | ||
// see https://github.com/websockets/ws/issues/617#issuecomment-283002469 | ||
packetOpts.wsPreEncodedFrame = ws_1.WebSocket.Sender.frame(data, { | ||
readOnly: false, | ||
mask: false, | ||
rsv1: false, | ||
opcode: 1, | ||
fin: true, | ||
}); | ||
} | ||
return encodedPackets; | ||
} | ||
/** | ||
@@ -173,3 +191,3 @@ * Gets a list of sockets by sid. | ||
const sids = new Set(); | ||
this.apply({ rooms }, socket => { | ||
this.apply({ rooms }, (socket) => { | ||
sids.add(socket.id); | ||
@@ -194,3 +212,3 @@ }); | ||
const sockets = []; | ||
this.apply(opts, socket => { | ||
this.apply(opts, (socket) => { | ||
sockets.push(socket); | ||
@@ -207,3 +225,3 @@ }); | ||
addSockets(opts, rooms) { | ||
this.apply(opts, socket => { | ||
this.apply(opts, (socket) => { | ||
socket.join(rooms); | ||
@@ -219,4 +237,4 @@ }); | ||
delSockets(opts, rooms) { | ||
this.apply(opts, socket => { | ||
rooms.forEach(room => socket.leave(room)); | ||
this.apply(opts, (socket) => { | ||
rooms.forEach((room) => socket.leave(room)); | ||
}); | ||
@@ -231,3 +249,3 @@ } | ||
disconnectSockets(opts, close) { | ||
this.apply(opts, socket => { | ||
this.apply(opts, (socket) => { | ||
socket.disconnect(close); | ||
@@ -270,3 +288,3 @@ }); | ||
if (this.rooms.has(room)) { | ||
this.rooms.get(room).forEach(sid => exceptSids.add(sid)); | ||
this.rooms.get(room).forEach((sid) => exceptSids.add(sid)); | ||
} | ||
@@ -284,3 +302,100 @@ } | ||
} | ||
/** | ||
* Save the client session in order to restore it upon reconnection. | ||
*/ | ||
persistSession(session) { } | ||
/** | ||
* Restore the session and find the packets that were missed by the client. | ||
* @param pid | ||
* @param offset | ||
*/ | ||
restoreSession(pid, offset) { | ||
return null; | ||
} | ||
} | ||
exports.Adapter = Adapter; | ||
class SessionAwareAdapter extends Adapter { | ||
constructor(nsp) { | ||
super(nsp); | ||
this.nsp = nsp; | ||
this.sessions = new Map(); | ||
this.packets = []; | ||
this.maxDisconnectionDuration = | ||
nsp.server.opts.connectionStateRecovery.maxDisconnectionDuration; | ||
const timer = setInterval(() => { | ||
const threshold = Date.now() - this.maxDisconnectionDuration; | ||
this.sessions.forEach((session, sessionId) => { | ||
const hasExpired = session.disconnectedAt < threshold; | ||
if (hasExpired) { | ||
this.sessions.delete(sessionId); | ||
} | ||
}); | ||
for (let i = this.packets.length - 1; i >= 0; i--) { | ||
const hasExpired = this.packets[i].emittedAt < threshold; | ||
if (hasExpired) { | ||
this.packets.splice(0, i + 1); | ||
break; | ||
} | ||
} | ||
}, 60 * 1000); | ||
// prevents the timer from keeping the process alive | ||
timer.unref(); | ||
} | ||
persistSession(session) { | ||
session.disconnectedAt = Date.now(); | ||
this.sessions.set(session.pid, session); | ||
} | ||
restoreSession(pid, offset) { | ||
const session = this.sessions.get(pid); | ||
if (!session) { | ||
// the session may have expired | ||
return null; | ||
} | ||
const hasExpired = session.disconnectedAt + this.maxDisconnectionDuration < Date.now(); | ||
if (hasExpired) { | ||
// the session has expired | ||
this.sessions.delete(pid); | ||
return null; | ||
} | ||
const index = this.packets.findIndex((packet) => packet.id === offset); | ||
if (index === -1) { | ||
// the offset may be too old | ||
return null; | ||
} | ||
const missedPackets = []; | ||
for (let i = index + 1; i < this.packets.length; i++) { | ||
const packet = this.packets[i]; | ||
if (shouldIncludePacket(session.rooms, packet.opts)) { | ||
missedPackets.push(packet.data); | ||
} | ||
} | ||
return Promise.resolve(Object.assign(Object.assign({}, session), { missedPackets })); | ||
} | ||
broadcast(packet, opts) { | ||
var _a; | ||
const isEventPacket = packet.type === 2; | ||
// packets with acknowledgement are not stored because the acknowledgement function cannot be serialized and | ||
// restored on another server upon reconnection | ||
const withoutAcknowledgement = packet.id === undefined; | ||
const notVolatile = ((_a = opts.flags) === null || _a === void 0 ? void 0 : _a.volatile) === undefined; | ||
if (isEventPacket && withoutAcknowledgement && notVolatile) { | ||
const id = (0, yeast_1.yeast)(); | ||
// the offset is stored at the end of the data array, so the client knows the ID of the last packet it has | ||
// processed (and the format is backward-compatible) | ||
packet.data.push(id); | ||
this.packets.push({ | ||
id, | ||
opts, | ||
data: packet.data, | ||
emittedAt: Date.now(), | ||
}); | ||
} | ||
super.broadcast(packet, opts); | ||
} | ||
} | ||
exports.SessionAwareAdapter = SessionAwareAdapter; | ||
function shouldIncludePacket(sessionRooms, opts) { | ||
const included = opts.rooms.size === 0 || sessionRooms.some((room) => opts.rooms.has(room)); | ||
const notExcluded = sessionRooms.every((room) => !opts.except.has(room)); | ||
return included && notExcluded; | ||
} |
{ | ||
"name": "socket.io-adapter", | ||
"version": "2.4.0", | ||
"version": "2.5.0", | ||
"license": "MIT", | ||
@@ -15,16 +15,21 @@ "repository": { | ||
"description": "default socket.io in-memory adapter", | ||
"peerDependencies": { | ||
"ws": "*" | ||
}, | ||
"devDependencies": { | ||
"@types/mocha": "^10.0.1", | ||
"@types/node": "^14.11.2", | ||
"expect.js": "^0.3.1", | ||
"mocha": "^8.1.3", | ||
"mocha": "^10.2.0", | ||
"nyc": "^15.1.0", | ||
"prettier": "^1.19.1", | ||
"typescript": "^4.0.3" | ||
"prettier": "^2.8.1", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.9.4" | ||
}, | ||
"scripts": { | ||
"test": "npm run format:check && tsc && nyc mocha test/index.js", | ||
"format:check": "prettier --parser typescript --check 'lib/**/*.ts' 'test/**/*.js'", | ||
"format:fix": "prettier --parser typescript --write 'lib/**/*.ts' 'test/**/*.js'", | ||
"test": "npm run format:check && tsc && nyc mocha --require ts-node/register test/index.ts", | ||
"format:check": "prettier --parser typescript --check 'lib/**/*.ts' 'test/**/*.ts'", | ||
"format:fix": "prettier --parser typescript --write 'lib/**/*.ts' 'test/**/*.ts'", | ||
"prepack": "tsc" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
23216
7
647
0
1
8