socket.io
Advanced tools
Comparing version 4.5.4 to 4.6.0-alpha1
import type { BroadcastFlags, Room, SocketId } from "socket.io-adapter"; | ||
import { Handshake } from "./socket"; | ||
import type { Adapter } from "socket.io-adapter"; | ||
import type { EventParams, EventNames, EventsMap, TypedEventBroadcaster } from "./typed-events"; | ||
import type { EventParams, EventNames, EventsMap, TypedEventBroadcaster, DecorateAcknowledgements, DecorateAcknowledgementsWithTimeoutAndMultipleResponses, AllButLast, Last, SecondArg } from "./typed-events"; | ||
export declare class BroadcastOperator<EmitEvents extends EventsMap, SocketData> implements TypedEventBroadcaster<EmitEvents> { | ||
@@ -10,3 +10,5 @@ private readonly adapter; | ||
private readonly flags; | ||
constructor(adapter: Adapter, rooms?: Set<Room>, exceptRooms?: Set<Room>, flags?: BroadcastFlags); | ||
constructor(adapter: Adapter, rooms?: Set<Room>, exceptRooms?: Set<Room>, flags?: BroadcastFlags & { | ||
expectSingleResponse?: boolean; | ||
}); | ||
/** | ||
@@ -102,3 +104,3 @@ * Targets a room when emitting. | ||
*/ | ||
timeout(timeout: number): BroadcastOperator<EmitEvents, SocketData>; | ||
timeout(timeout: number): BroadcastOperator<DecorateAcknowledgementsWithTimeoutAndMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -127,2 +129,16 @@ * Emits to all clients. | ||
/** | ||
* Emits an event and waits for an acknowledgement from all clients. | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* | ||
* @return a Promise that will be fulfilled when all clients have acknowledged the event | ||
*/ | ||
emitWithAck<Ev extends EventNames<EmitEvents>>(ev: Ev, ...args: AllButLast<EventParams<EmitEvents, Ev>>): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>>; | ||
/** | ||
* Gets a list of clients. | ||
@@ -225,2 +241,26 @@ * | ||
constructor(adapter: Adapter, details: SocketDetails<SocketData>); | ||
/** | ||
* Adds a timeout in milliseconds for the next operation. | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* }); | ||
* } | ||
* } | ||
* | ||
* // note: if possible, using a room instead of looping over all sockets is preferable | ||
* io.timeout(1000).to(someConditionRoom).emit("some-event", (err, responses) => { | ||
* // ... | ||
* }); | ||
* | ||
* @param timeout | ||
*/ | ||
timeout(timeout: number): BroadcastOperator<DecorateAcknowledgements<EmitEvents>, SocketData>; | ||
emit<Ev extends EventNames<EmitEvents>>(ev: Ev, ...args: EventParams<EmitEvents, Ev>): boolean; | ||
@@ -227,0 +267,0 @@ /** |
@@ -181,3 +181,6 @@ "use strict"; | ||
timedOut = true; | ||
ack.apply(this, [new Error("operation has timed out"), responses]); | ||
ack.apply(this, [ | ||
new Error("operation has timed out"), | ||
this.flags.expectSingleResponse ? null : responses, | ||
]); | ||
}, this.flags.timeout); | ||
@@ -192,3 +195,6 @@ let expectedServerCount = -1; | ||
clearTimeout(timer); | ||
ack.apply(this, [null, responses]); | ||
ack.apply(this, [ | ||
null, | ||
this.flags.expectSingleResponse ? null : responses, | ||
]); | ||
} | ||
@@ -217,2 +223,29 @@ }; | ||
/** | ||
* Emits an event and waits for an acknowledgement from all clients. | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* | ||
* @return a Promise that will be fulfilled when all clients have acknowledged the event | ||
*/ | ||
emitWithAck(ev, ...args) { | ||
return new Promise((resolve, reject) => { | ||
args.push((err, responses) => { | ||
if (err) { | ||
err.responses = responses; | ||
return reject(err); | ||
} | ||
else { | ||
return resolve(responses); | ||
} | ||
}); | ||
this.emit(ev, ...args); | ||
}); | ||
} | ||
/** | ||
* Gets a list of clients. | ||
@@ -347,4 +380,32 @@ * | ||
this.data = details.data; | ||
this.operator = new BroadcastOperator(adapter, new Set([this.id])); | ||
this.operator = new BroadcastOperator(adapter, new Set([this.id]), new Set(), { | ||
expectSingleResponse: true, // so that remoteSocket.emit() with acknowledgement behaves like socket.emit() | ||
}); | ||
} | ||
/** | ||
* Adds a timeout in milliseconds for the next operation. | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* }); | ||
* } | ||
* } | ||
* | ||
* // note: if possible, using a room instead of looping over all sockets is preferable | ||
* io.timeout(1000).to(someConditionRoom).emit("some-event", (err, responses) => { | ||
* // ... | ||
* }); | ||
* | ||
* @param timeout | ||
*/ | ||
timeout(timeout) { | ||
return this.operator.timeout(timeout); | ||
} | ||
emit(ev, ...args) { | ||
@@ -351,0 +412,0 @@ return this.operator.emit(ev, ...args); |
@@ -97,3 +97,3 @@ "use strict"; | ||
const nsp = this.server.of(name); | ||
const socket = nsp._add(this, auth, () => { | ||
nsp._add(this, auth, (socket) => { | ||
this.sockets.set(socket.id, socket); | ||
@@ -100,0 +100,0 @@ this.nsps.set(nsp.name, socket); |
@@ -12,5 +12,6 @@ /// <reference types="node" /> | ||
import type { Encoder } from "socket.io-parser"; | ||
import { Socket } from "./socket"; | ||
import { Socket, DisconnectReason } from "./socket"; | ||
import type { BroadcastOperator, RemoteSocket } from "./broadcast-operator"; | ||
import { EventsMap, DefaultEventsMap, EventParams, StrictEventEmitter, EventNames } from "./typed-events"; | ||
import { EventsMap, DefaultEventsMap, EventParams, StrictEventEmitter, EventNames, DecorateAcknowledgementsWithTimeoutAndMultipleResponses, AllButLast, Last, FirstArg, SecondArg } from "./typed-events"; | ||
import type { BaseServer } from "engine.io/build/server"; | ||
declare type ParentNspNameMatchFn = (name: string, auth: { | ||
@@ -46,2 +47,26 @@ [key: string]: any; | ||
connectTimeout: number; | ||
/** | ||
* Whether to enable the recovery of connection state when a client temporarily disconnects. | ||
* | ||
* The connection state includes the missed packets, the rooms the socket was in and the `data` attribute. | ||
*/ | ||
connectionStateRecovery: { | ||
/** | ||
* The backup duration of the sessions and the packets. | ||
* | ||
* @default 120000 (2 minutes) | ||
*/ | ||
maxDisconnectionDuration?: number; | ||
/** | ||
* Whether to skip middlewares upon successful connection state recovery. | ||
* | ||
* @default true | ||
*/ | ||
skipMiddlewares?: boolean; | ||
}; | ||
/** | ||
* Whether to remove child namespaces that have no sockets connected to them | ||
* @default false | ||
*/ | ||
cleanupEmptyChildNamespaces: boolean; | ||
} | ||
@@ -83,3 +108,3 @@ /** | ||
*/ | ||
engine: any; | ||
engine: BaseServer; | ||
/** @private */ | ||
@@ -96,3 +121,3 @@ readonly _parser: typeof parser; | ||
private _serveClient; | ||
private opts; | ||
private readonly opts; | ||
private eio; | ||
@@ -115,2 +140,3 @@ private _path; | ||
constructor(srv: undefined | Partial<ServerOptions> | http.Server | HTTPSServer | Http2SecureServer | number, opts?: Partial<ServerOptions>); | ||
get _opts(): Partial<ServerOptions>; | ||
/** | ||
@@ -211,6 +237,6 @@ * Sets/gets whether client code is being served. | ||
* | ||
* @param {engine.Server} engine engine.io (or compatible) server | ||
* @param engine engine.io (or compatible) server | ||
* @return self | ||
*/ | ||
bind(engine: any): this; | ||
bind(engine: BaseServer): this; | ||
/** | ||
@@ -307,2 +333,16 @@ * Called with each incoming transport connection. | ||
/** | ||
* Emits an event and waits for an acknowledgement from all clients. | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* | ||
* @return a Promise that will be fulfilled when all clients have acknowledged the event | ||
*/ | ||
emitWithAck<Ev extends EventNames<EmitEvents>>(ev: Ev, ...args: AllButLast<EventParams<EmitEvents, Ev>>): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>>; | ||
/** | ||
* Sends a `message` event to all clients. | ||
@@ -342,5 +382,5 @@ * | ||
* if (err) { | ||
* // some clients did not acknowledge the event in the given delay | ||
* // some servers did not acknowledge the event in the given delay | ||
* } else { | ||
* console.log(responses); // one response per client | ||
* console.log(responses); // one response per server (except the current one) | ||
* } | ||
@@ -356,4 +396,21 @@ * }); | ||
*/ | ||
serverSideEmit<Ev extends EventNames<ServerSideEvents>>(ev: Ev, ...args: EventParams<ServerSideEvents, Ev>): boolean; | ||
serverSideEmit<Ev extends EventNames<ServerSideEvents>>(ev: Ev, ...args: EventParams<DecorateAcknowledgementsWithTimeoutAndMultipleResponses<ServerSideEvents>, Ev>): boolean; | ||
/** | ||
* Sends a message and expect an acknowledgement from the other Socket.IO servers of the cluster. | ||
* | ||
* @example | ||
* try { | ||
* const responses = await io.serverSideEmitWithAck("ping"); | ||
* console.log(responses); // one response per server (except the current one) | ||
* } catch (e) { | ||
* // some servers did not acknowledge the event in the given delay | ||
* } | ||
* | ||
* @param ev - the event name | ||
* @param args - an array of arguments | ||
* | ||
* @return a Promise that will be fulfilled when all servers have acknowledged the event | ||
*/ | ||
serverSideEmitWithAck<Ev extends EventNames<ServerSideEvents>>(ev: Ev, ...args: AllButLast<EventParams<ServerSideEvents, Ev>>): Promise<FirstArg<Last<EventParams<ServerSideEvents, Ev>>>[]>; | ||
/** | ||
* Gets a list of socket ids. | ||
@@ -410,3 +467,3 @@ * | ||
*/ | ||
timeout(timeout: number): BroadcastOperator<EmitEvents, SocketData>; | ||
timeout(timeout: number): BroadcastOperator<DecorateAcknowledgementsWithTimeoutAndMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -484,3 +541,3 @@ * Returns the matching socket instances. | ||
} | ||
export { Socket, ServerOptions, Namespace, BroadcastOperator, RemoteSocket }; | ||
export { Socket, DisconnectReason, ServerOptions, Namespace, BroadcastOperator, RemoteSocket, }; | ||
export { Event } from "./socket"; |
@@ -97,8 +97,21 @@ "use strict"; | ||
this.encoder = new this._parser.Encoder(); | ||
this.adapter(opts.adapter || socket_io_adapter_1.Adapter); | ||
this.opts = opts; | ||
if (opts.connectionStateRecovery) { | ||
opts.connectionStateRecovery = Object.assign({ | ||
maxDisconnectionDuration: 2 * 60 * 1000, | ||
skipMiddlewares: true, | ||
}, opts.connectionStateRecovery); | ||
this.adapter(opts.adapter || socket_io_adapter_1.SessionAwareAdapter); | ||
} | ||
else { | ||
this.adapter(opts.adapter || socket_io_adapter_1.Adapter); | ||
} | ||
opts.cleanupEmptyChildNamespaces = !!opts.cleanupEmptyChildNamespaces; | ||
this.sockets = this.of("/"); | ||
this.opts = opts; | ||
if (srv || typeof srv == "number") | ||
this.attach(srv); | ||
} | ||
get _opts() { | ||
return this.opts; | ||
} | ||
serveClient(v) { | ||
@@ -254,3 +267,3 @@ if (!arguments.length) | ||
res.writeHeader("cache-control", "public, max-age=0"); | ||
res.writeHeader("content-type", "application/" + (isMap ? "json" : "javascript")); | ||
res.writeHeader("content-type", "application/" + (isMap ? "json" : "javascript") + "; charset=utf-8"); | ||
res.writeHeader("etag", expectedEtag); | ||
@@ -329,3 +342,3 @@ const filepath = path.join(__dirname, "../client-dist/", filename); | ||
res.setHeader("Cache-Control", "public, max-age=0"); | ||
res.setHeader("Content-Type", "application/" + (isMap ? "json" : "javascript")); | ||
res.setHeader("Content-Type", "application/" + (isMap ? "json" : "javascript") + "; charset=utf-8"); | ||
res.setHeader("ETag", expectedEtag); | ||
@@ -370,3 +383,3 @@ Server.sendFile(filename, req, res); | ||
* | ||
* @param {engine.Server} engine engine.io (or compatible) server | ||
* @param engine engine.io (or compatible) server | ||
* @return self | ||
@@ -531,2 +544,18 @@ */ | ||
/** | ||
* Emits an event and waits for an acknowledgement from all clients. | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* | ||
* @return a Promise that will be fulfilled when all clients have acknowledged the event | ||
*/ | ||
emitWithAck(ev, ...args) { | ||
return this.sockets.emitWithAck(ev, ...args); | ||
} | ||
/** | ||
* Sends a `message` event to all clients. | ||
@@ -572,5 +601,5 @@ * | ||
* if (err) { | ||
* // some clients did not acknowledge the event in the given delay | ||
* // some servers did not acknowledge the event in the given delay | ||
* } else { | ||
* console.log(responses); // one response per client | ||
* console.log(responses); // one response per server (except the current one) | ||
* } | ||
@@ -590,2 +619,21 @@ * }); | ||
/** | ||
* Sends a message and expect an acknowledgement from the other Socket.IO servers of the cluster. | ||
* | ||
* @example | ||
* try { | ||
* const responses = await io.serverSideEmitWithAck("ping"); | ||
* console.log(responses); // one response per server (except the current one) | ||
* } catch (e) { | ||
* // some servers did not acknowledge the event in the given delay | ||
* } | ||
* | ||
* @param ev - the event name | ||
* @param args - an array of arguments | ||
* | ||
* @return a Promise that will be fulfilled when all servers have acknowledged the event | ||
*/ | ||
serverSideEmitWithAck(ev, ...args) { | ||
return this.sockets.serverSideEmitWithAck(ev, ...args); | ||
} | ||
/** | ||
* Gets a list of socket ids. | ||
@@ -592,0 +640,0 @@ * |
import { Socket } from "./socket"; | ||
import type { Server } from "./index"; | ||
import { EventParams, EventNames, EventsMap, StrictEventEmitter, DefaultEventsMap } from "./typed-events"; | ||
import { EventParams, EventNames, EventsMap, StrictEventEmitter, DefaultEventsMap, DecorateAcknowledgementsWithTimeoutAndMultipleResponses, AllButLast, Last, FirstArg, SecondArg } from "./typed-events"; | ||
import type { Client } from "./client"; | ||
import type { Adapter, Room, SocketId } from "socket.io-adapter"; | ||
import { BroadcastOperator, RemoteSocket } from "./broadcast-operator"; | ||
import { BroadcastOperator } from "./broadcast-operator"; | ||
export interface ExtendedError extends Error { | ||
@@ -175,3 +175,5 @@ data?: any; | ||
*/ | ||
_add(client: Client<ListenEvents, EmitEvents, ServerSideEvents>, query: any, fn?: () => void): Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>; | ||
_add(client: Client<ListenEvents, EmitEvents, ServerSideEvents>, auth: Record<string, unknown>, fn: (socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>) => void): any; | ||
private _createSocket; | ||
private _doConnect; | ||
/** | ||
@@ -207,2 +209,18 @@ * Removes a client. Called by each `Socket`. | ||
/** | ||
* Emits an event and waits for an acknowledgement from all clients. | ||
* | ||
* @example | ||
* const myNamespace = io.of("/my-namespace"); | ||
* | ||
* try { | ||
* const responses = await myNamespace.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 | ||
* } | ||
* | ||
* @return a Promise that will be fulfilled when all clients have acknowledged the event | ||
*/ | ||
emitWithAck<Ev extends EventNames<EmitEvents>>(ev: Ev, ...args: AllButLast<EventParams<EmitEvents, Ev>>): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>>; | ||
/** | ||
* Sends a `message` event to all clients. | ||
@@ -246,5 +264,5 @@ * | ||
* if (err) { | ||
* // some clients did not acknowledge the event in the given delay | ||
* // some servers did not acknowledge the event in the given delay | ||
* } else { | ||
* console.log(responses); // one response per client | ||
* console.log(responses); // one response per server (except the current one) | ||
* } | ||
@@ -260,4 +278,23 @@ * }); | ||
*/ | ||
serverSideEmit<Ev extends EventNames<ServerSideEvents>>(ev: Ev, ...args: EventParams<ServerSideEvents, Ev>): boolean; | ||
serverSideEmit<Ev extends EventNames<ServerSideEvents>>(ev: Ev, ...args: EventParams<DecorateAcknowledgementsWithTimeoutAndMultipleResponses<ServerSideEvents>, Ev>): boolean; | ||
/** | ||
* Sends a message and expect an acknowledgement from the other Socket.IO servers of the cluster. | ||
* | ||
* @example | ||
* const myNamespace = io.of("/my-namespace"); | ||
* | ||
* try { | ||
* const responses = await myNamespace.serverSideEmitWithAck("ping"); | ||
* console.log(responses); // one response per server (except the current one) | ||
* } catch (e) { | ||
* // some servers did not acknowledge the event in the given delay | ||
* } | ||
* | ||
* @param ev - the event name | ||
* @param args - an array of arguments | ||
* | ||
* @return a Promise that will be fulfilled when all servers have acknowledged the event | ||
*/ | ||
serverSideEmitWithAck<Ev extends EventNames<ServerSideEvents>>(ev: Ev, ...args: AllButLast<EventParams<ServerSideEvents, Ev>>): Promise<FirstArg<Last<EventParams<ServerSideEvents, Ev>>>[]>; | ||
/** | ||
* Called when a packet is received from another Socket.IO server | ||
@@ -330,3 +367,3 @@ * | ||
*/ | ||
timeout(timeout: number): BroadcastOperator<EmitEvents, SocketData>; | ||
timeout(timeout: number): BroadcastOperator<DecorateAcknowledgementsWithTimeoutAndMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -358,3 +395,3 @@ * Returns the matching socket instances. | ||
*/ | ||
fetchSockets(): Promise<RemoteSocket<EmitEvents, SocketData>[]>; | ||
fetchSockets(): Promise<import("./broadcast-operator").RemoteSocket<EmitEvents, SocketData>[]>; | ||
/** | ||
@@ -361,0 +398,0 @@ * Makes the matching socket instances join the specified rooms. |
@@ -200,5 +200,13 @@ "use strict"; | ||
*/ | ||
_add(client, query, fn) { | ||
async _add(client, auth, fn) { | ||
var _a; | ||
debug("adding socket to nsp %s", this.name); | ||
const socket = new socket_1.Socket(this, client, query); | ||
const socket = await this._createSocket(client, auth); | ||
if ( | ||
// @ts-ignore | ||
((_a = this.server.opts.connectionStateRecovery) === null || _a === void 0 ? void 0 : _a.skipMiddlewares) && | ||
socket.recovered && | ||
client.conn.readyState === "open") { | ||
return this._doConnect(socket, fn); | ||
} | ||
this.run(socket, (err) => { | ||
@@ -224,18 +232,39 @@ process.nextTick(() => { | ||
} | ||
// track socket | ||
this.sockets.set(socket.id, socket); | ||
// it's paramount that the internal `onconnect` logic | ||
// fires before user-set events to prevent state order | ||
// violations (such as a disconnection before the connection | ||
// logic is complete) | ||
socket._onconnect(); | ||
if (fn) | ||
fn(); | ||
// fire user-set events | ||
this.emitReserved("connect", socket); | ||
this.emitReserved("connection", socket); | ||
this._doConnect(socket, fn); | ||
}); | ||
}); | ||
return socket; | ||
} | ||
async _createSocket(client, auth) { | ||
const sessionId = auth.pid; | ||
const offset = auth.offset; | ||
if ( | ||
// @ts-ignore | ||
this.server.opts.connectionStateRecovery && | ||
typeof sessionId === "string" && | ||
typeof offset === "string") { | ||
const session = await this.adapter.restoreSession(sessionId, offset); | ||
if (session) { | ||
debug("connection state recovered for sid %s", session.sid); | ||
return new socket_1.Socket(this, client, auth, session); | ||
} | ||
else { | ||
debug("unable to restore session state"); | ||
} | ||
} | ||
return new socket_1.Socket(this, client, auth); | ||
} | ||
_doConnect(socket, fn) { | ||
// track socket | ||
this.sockets.set(socket.id, socket); | ||
// it's paramount that the internal `onconnect` logic | ||
// fires before user-set events to prevent state order | ||
// violations (such as a disconnection before the connection | ||
// logic is complete) | ||
socket._onconnect(); | ||
if (fn) | ||
fn(socket); | ||
// fire user-set events | ||
this.emitReserved("connect", socket); | ||
this.emitReserved("connection", socket); | ||
} | ||
/** | ||
@@ -280,2 +309,20 @@ * Removes a client. Called by each `Socket`. | ||
/** | ||
* Emits an event and waits for an acknowledgement from all clients. | ||
* | ||
* @example | ||
* const myNamespace = io.of("/my-namespace"); | ||
* | ||
* try { | ||
* const responses = await myNamespace.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 | ||
* } | ||
* | ||
* @return a Promise that will be fulfilled when all clients have acknowledged the event | ||
*/ | ||
emitWithAck(ev, ...args) { | ||
return new broadcast_operator_1.BroadcastOperator(this.adapter).emitWithAck(ev, ...args); | ||
} | ||
/** | ||
* Sends a `message` event to all clients. | ||
@@ -325,5 +372,5 @@ * | ||
* if (err) { | ||
* // some clients did not acknowledge the event in the given delay | ||
* // some servers did not acknowledge the event in the given delay | ||
* } else { | ||
* console.log(responses); // one response per client | ||
* console.log(responses); // one response per server (except the current one) | ||
* } | ||
@@ -348,2 +395,34 @@ * }); | ||
/** | ||
* Sends a message and expect an acknowledgement from the other Socket.IO servers of the cluster. | ||
* | ||
* @example | ||
* const myNamespace = io.of("/my-namespace"); | ||
* | ||
* try { | ||
* const responses = await myNamespace.serverSideEmitWithAck("ping"); | ||
* console.log(responses); // one response per server (except the current one) | ||
* } catch (e) { | ||
* // some servers did not acknowledge the event in the given delay | ||
* } | ||
* | ||
* @param ev - the event name | ||
* @param args - an array of arguments | ||
* | ||
* @return a Promise that will be fulfilled when all servers have acknowledged the event | ||
*/ | ||
serverSideEmitWithAck(ev, ...args) { | ||
return new Promise((resolve, reject) => { | ||
args.push((err, responses) => { | ||
if (err) { | ||
err.responses = responses; | ||
return reject(err); | ||
} | ||
else { | ||
return resolve(responses); | ||
} | ||
}); | ||
this.serverSideEmit(ev, ...args); | ||
}); | ||
} | ||
/** | ||
* Called when a packet is received from another Socket.IO server | ||
@@ -350,0 +429,0 @@ * |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ParentNamespace = void 0; | ||
const namespace_1 = require("./namespace"); | ||
const debug_1 = __importDefault(require("debug")); | ||
const debug = (0, debug_1.default)("socket.io:parent-namespace"); | ||
class ParentNamespace extends namespace_1.Namespace { | ||
@@ -29,2 +34,3 @@ constructor(server) { | ||
createChild(name) { | ||
debug("creating child namespace %s", name); | ||
const namespace = new namespace_1.Namespace(this.server, name); | ||
@@ -35,2 +41,14 @@ namespace._fns = this._fns.slice(0); | ||
this.children.add(namespace); | ||
if (this.server._opts.cleanupEmptyChildNamespaces) { | ||
const remove = namespace._remove; | ||
namespace._remove = (socket) => { | ||
remove.call(namespace, socket); | ||
if (namespace.sockets.size === 0) { | ||
debug("closing child namespace %s", name); | ||
namespace.adapter.close(); | ||
this.server._nsps.delete(namespace.name); | ||
this.children.delete(namespace); | ||
} | ||
}; | ||
} | ||
this.server._nsps.set(name, namespace); | ||
@@ -37,0 +55,0 @@ return namespace; |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { Packet } from "socket.io-parser"; | ||
import { EventParams, EventNames, EventsMap, StrictEventEmitter, DefaultEventsMap } from "./typed-events"; | ||
import { AllButLast, DecorateAcknowledgements, DecorateAcknowledgementsWithMultipleResponses, DefaultEventsMap, EventNames, EventParams, EventsMap, FirstArg, Last, StrictEventEmitter } from "./typed-events"; | ||
import type { Client } from "./client"; | ||
import type { Namespace } from "./namespace"; | ||
import type { IncomingMessage, IncomingHttpHeaders } from "http"; | ||
import type { Room, SocketId } from "socket.io-adapter"; | ||
import type { IncomingHttpHeaders, IncomingMessage } from "http"; | ||
import type { Room, Session, SocketId } from "socket.io-adapter"; | ||
import type { ParsedUrlQuery } from "querystring"; | ||
@@ -108,2 +108,7 @@ import { BroadcastOperator } from "./broadcast-operator"; | ||
/** | ||
* Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will | ||
* be transmitted to the client, the data attribute and the rooms will be restored. | ||
*/ | ||
readonly recovered: boolean; | ||
/** | ||
* The handshake details. | ||
@@ -131,2 +136,8 @@ */ | ||
connected: boolean; | ||
/** | ||
* The session ID, which must not be shared (unlike {@link id}). | ||
* | ||
* @private | ||
*/ | ||
private readonly pid; | ||
private readonly server; | ||
@@ -147,3 +158,3 @@ private readonly adapter; | ||
*/ | ||
constructor(nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents>, client: Client<ListenEvents, EmitEvents, ServerSideEvents>, auth: object); | ||
constructor(nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents>, client: Client<ListenEvents, EmitEvents, ServerSideEvents>, auth: Record<string, unknown>, previousSession?: Session); | ||
/** | ||
@@ -175,2 +186,21 @@ * Builds the `handshake` BC object | ||
/** | ||
* Emits an event and waits for an acknowledgement | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* }); | ||
* | ||
* @return a Promise that will be fulfilled when the client acknowledges the event | ||
*/ | ||
emitWithAck<Ev extends EventNames<EmitEvents>>(ev: Ev, ...args: AllButLast<EventParams<EmitEvents, Ev>>): Promise<FirstArg<Last<EventParams<EmitEvents, Ev>>>>; | ||
/** | ||
* @private | ||
@@ -200,3 +230,3 @@ */ | ||
*/ | ||
to(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData>; | ||
to(room: Room | Room[]): BroadcastOperator<DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -214,3 +244,3 @@ * Targets a room when broadcasting. Similar to `to()`, but might feel clearer in some cases: | ||
*/ | ||
in(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData>; | ||
in(room: Room | Room[]): BroadcastOperator<DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -235,3 +265,3 @@ * Excludes a room when broadcasting. | ||
*/ | ||
except(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData>; | ||
except(room: Room | Room[]): BroadcastOperator<DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -431,3 +461,3 @@ * Sends a `message` event. | ||
*/ | ||
get broadcast(): BroadcastOperator<EmitEvents, SocketData>; | ||
get broadcast(): BroadcastOperator<DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -444,3 +474,3 @@ * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node. | ||
*/ | ||
get local(): BroadcastOperator<EmitEvents, SocketData>; | ||
get local(): BroadcastOperator<DecorateAcknowledgementsWithMultipleResponses<EmitEvents>, SocketData>; | ||
/** | ||
@@ -461,3 +491,3 @@ * Sets a modifier for a subsequent event emission that the callback will be called with an error when the | ||
*/ | ||
timeout(timeout: number): this; | ||
timeout(timeout: number): Socket<ListenEvents, DecorateAcknowledgements<EmitEvents>, ServerSideEvents, SocketData>; | ||
/** | ||
@@ -464,0 +494,0 @@ * Dispatch incoming event to socket listeners. |
@@ -13,2 +13,10 @@ "use strict"; | ||
const debug = (0, debug_1.default)("socket.io:socket"); | ||
const RECOVERABLE_DISCONNECT_REASONS = new Set([ | ||
"transport error", | ||
"transport close", | ||
"forced close", | ||
"ping timeout", | ||
"server shutting down", | ||
"forced server close", | ||
]); | ||
exports.RESERVED_EVENTS = new Set([ | ||
@@ -63,3 +71,3 @@ "connect", | ||
*/ | ||
constructor(nsp, client, auth) { | ||
constructor(nsp, client, auth, previousSession) { | ||
super(); | ||
@@ -69,2 +77,7 @@ this.nsp = nsp; | ||
/** | ||
* Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will | ||
* be transmitted to the client, the data attribute and the rooms will be restored. | ||
*/ | ||
this.recovered = false; | ||
/** | ||
* Additional information that can be attached to the Socket instance and which will be used in the | ||
@@ -93,8 +106,24 @@ * {@link Server.fetchSockets()} method. | ||
this.adapter = this.nsp.adapter; | ||
if (client.conn.protocol === 3) { | ||
// @ts-ignore | ||
this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id; | ||
if (previousSession) { | ||
this.id = previousSession.sid; | ||
this.pid = previousSession.pid; | ||
previousSession.rooms.forEach((room) => this.join(room)); | ||
this.data = previousSession.data; | ||
previousSession.missedPackets.forEach((packet) => { | ||
this.packet({ | ||
type: socket_io_parser_1.PacketType.EVENT, | ||
data: packet, | ||
}); | ||
}); | ||
this.recovered = true; | ||
} | ||
else { | ||
this.id = base64id_1.default.generateId(); // don't reuse the Engine.IO id because it's sensitive information | ||
if (client.conn.protocol === 3) { | ||
// @ts-ignore | ||
this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id; | ||
} | ||
else { | ||
this.id = base64id_1.default.generateId(); // don't reuse the Engine.IO id because it's sensitive information | ||
} | ||
this.pid = base64id_1.default.generateId(); | ||
} | ||
@@ -159,7 +188,51 @@ this.handshake = this.buildHandshake(auth); | ||
this.flags = {}; | ||
this.notifyOutgoingListeners(packet); | ||
this.packet(packet, flags); | ||
// @ts-ignore | ||
if (this.nsp.server.opts.connectionStateRecovery) { | ||
// this ensures the packet is stored and can be transmitted upon reconnection | ||
this.adapter.broadcast(packet, { | ||
rooms: new Set([this.id]), | ||
except: new Set(), | ||
flags, | ||
}); | ||
} | ||
else { | ||
this.notifyOutgoingListeners(packet); | ||
this.packet(packet, flags); | ||
} | ||
return true; | ||
} | ||
/** | ||
* Emits an event and waits for an acknowledgement | ||
* | ||
* @example | ||
* 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 | ||
* } | ||
* }); | ||
* | ||
* @return a Promise that will be fulfilled when the client acknowledges the event | ||
*/ | ||
emitWithAck(ev, ...args) { | ||
// the timeout flag is optional | ||
const withErr = this.flags.timeout !== undefined; | ||
return new Promise((resolve, reject) => { | ||
args.push((arg1, arg2) => { | ||
if (withErr) { | ||
return arg1 ? reject(arg1) : resolve(arg2); | ||
} | ||
else { | ||
return resolve(arg1); | ||
} | ||
}); | ||
this.emit(ev, ...args); | ||
}); | ||
} | ||
/** | ||
* @private | ||
@@ -348,3 +421,6 @@ */ | ||
else { | ||
this.packet({ type: socket_io_parser_1.PacketType.CONNECT, data: { sid: this.id } }); | ||
this.packet({ | ||
type: socket_io_parser_1.PacketType.CONNECT, | ||
data: { sid: this.id, pid: this.pid }, | ||
}); | ||
} | ||
@@ -474,2 +550,11 @@ } | ||
this.emitReserved("disconnecting", reason); | ||
if (RECOVERABLE_DISCONNECT_REASONS.has(reason)) { | ||
debug("connection state recovery is enabled for sid %s", this.id); | ||
this.adapter.persistSession({ | ||
sid: this.id, | ||
pid: this.pid, | ||
rooms: [...this.rooms], | ||
data: this.data, | ||
}); | ||
} | ||
this._cleanup(); | ||
@@ -476,0 +561,0 @@ this.nsp._remove(this); |
@@ -110,2 +110,40 @@ /// <reference types="node" /> | ||
} | ||
export declare type Last<T extends any[]> = T extends [...infer H, infer L] ? L : any; | ||
export declare type AllButLast<T extends any[]> = T extends [...infer H, infer L] ? H : any[]; | ||
export declare type FirstArg<T> = T extends (arg: infer Param) => infer Result ? Param : any; | ||
export declare type SecondArg<T> = T extends (err: Error, arg: infer Param) => infer Result ? Param : any; | ||
declare type PrependTimeoutError<T extends any[]> = { | ||
[K in keyof T]: T[K] extends (...args: infer Params) => infer Result ? (err: Error, ...args: Params) => Result : T[K]; | ||
}; | ||
declare type ExpectMultipleResponses<T extends any[]> = { | ||
[K in keyof T]: T[K] extends (err: Error, arg: infer Param) => infer Result ? (err: Error, arg: Param[]) => Result : T[K]; | ||
}; | ||
/** | ||
* Utility type to decorate the acknowledgement callbacks with a timeout error. | ||
* | ||
* This is needed because the timeout() flag breaks the symmetry between the sender and the receiver: | ||
* | ||
* @example | ||
* interface Events { | ||
* "my-event": (val: string) => void; | ||
* } | ||
* | ||
* socket.on("my-event", (cb) => { | ||
* cb("123"); // one single argument here | ||
* }); | ||
* | ||
* socket.timeout(1000).emit("my-event", (err, val) => { | ||
* // two arguments there (the "err" argument is not properly typed) | ||
* }); | ||
* | ||
*/ | ||
export declare type DecorateAcknowledgements<E> = { | ||
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result ? (...args: PrependTimeoutError<Params>) => Result : E[K]; | ||
}; | ||
export declare type DecorateAcknowledgementsWithTimeoutAndMultipleResponses<E> = { | ||
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result ? (...args: ExpectMultipleResponses<PrependTimeoutError<Params>>) => Result : E[K]; | ||
}; | ||
export declare type DecorateAcknowledgementsWithMultipleResponses<E> = { | ||
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result ? (...args: ExpectMultipleResponses<Params>) => Result : E[K]; | ||
}; | ||
export {}; |
{ | ||
"name": "socket.io", | ||
"version": "4.5.4", | ||
"version": "4.6.0-alpha1", | ||
"description": "node.js realtime framework server", | ||
@@ -52,4 +52,4 @@ "keywords": [ | ||
"debug": "~4.3.2", | ||
"engine.io": "~6.2.1", | ||
"socket.io-adapter": "~2.4.0", | ||
"engine.io": "~6.3.1", | ||
"socket.io-adapter": "~2.5.2", | ||
"socket.io-parser": "~4.2.1" | ||
@@ -56,0 +56,0 @@ }, |
@@ -24,2 +24,3 @@ # socket.io | ||
- [.NET](https://github.com/doghappy/socket.io-client-csharp) | ||
- [Rust](https://github.com/1c3t3a/rust-socketio) | ||
@@ -26,0 +27,0 @@ Its main features are: |
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
1181677
9880
271
2
+ Addedengine.io@6.3.1(transitive)
+ Addedsocket.io-adapter@2.5.5(transitive)
+ Addedws@8.11.08.17.1(transitive)
- Removedengine.io@6.2.1(transitive)
- Removedsocket.io-adapter@2.4.0(transitive)
- Removedws@8.2.3(transitive)
Updatedengine.io@~6.3.1
Updatedsocket.io-adapter@~2.5.2