diff --git a/lib/client.ts b/lib/client.ts index 3693744cce..a9c740d3e5 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -18,16 +18,23 @@ interface WriteOptions { export class Client< ListenEvents extends EventsMap, - EmitEvents extends EventsMap + EmitEvents extends EventsMap, + ServerSideEvents extends EventsMap > { public readonly conn; private readonly id: string; - private readonly server: Server; + private readonly server: Server; private readonly encoder: Encoder; private readonly decoder: Decoder; - private sockets: Map> = new Map(); - private nsps: Map> = new Map(); + private sockets: Map< + SocketId, + Socket + > = new Map(); + private nsps: Map< + string, + Socket + > = new Map(); private connectTimeout?: NodeJS.Timeout; /** @@ -37,7 +44,10 @@ export class Client< * @param conn * @package */ - constructor(server: Server, conn: any) { + constructor( + server: Server, + conn: any + ) { this.server = server; this.conn = conn; this.encoder = server.encoder; @@ -98,7 +108,11 @@ export class Client< this.server._checkNamespace( name, auth, - (dynamicNspName: Namespace | false) => { + ( + dynamicNspName: + | Namespace + | false + ) => { if (dynamicNspName) { debug("dynamic namespace %s was created", dynamicNspName); this.doConnect(name, auth); @@ -156,7 +170,7 @@ export class Client< * * @private */ - _remove(socket: Socket): void { + _remove(socket: Socket): void { if (this.sockets.has(socket.id)) { const nsp = this.sockets.get(socket.id)!.nsp.name; this.sockets.delete(socket.id); diff --git a/lib/index.ts b/lib/index.ts index afda8ebd38..0962d1b6ea 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -26,6 +26,7 @@ import { DefaultEventsMap, EventParams, StrictEventEmitter, + EventNames, } from "./typed-events"; const debug = debugModule("socket.io:server"); @@ -170,13 +171,18 @@ interface ServerOptions extends EngineAttachOptions { export class Server< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} > extends StrictEventEmitter< - {}, + ServerSideEvents, EmitEvents, - NamespaceReservedEventsMap + NamespaceReservedEventsMap > { - public readonly sockets: Namespace; + public readonly sockets: Namespace< + ListenEvents, + EmitEvents, + ServerSideEvents + >; /** * A reference to the underlying Engine.IO server. * @@ -197,10 +203,13 @@ export class Server< /** * @private */ - _nsps: Map> = new Map(); + _nsps: Map< + string, + Namespace + > = new Map(); private parentNsps: Map< ParentNspNameMatchFn, - ParentNamespace + ParentNamespace > = new Map(); private _adapter?: typeof Adapter; private _serveClient: boolean; @@ -280,7 +289,9 @@ export class Server< _checkNamespace( name: string, auth: { [key: string]: any }, - fn: (nsp: Namespace | false) => void + fn: ( + nsp: Namespace | false + ) => void ): void { if (this.parentNsps.size === 0) return fn(false); @@ -589,8 +600,8 @@ export class Server< */ public of( name: string | RegExp | ParentNspNameMatchFn, - fn?: (socket: Socket) => void - ): Namespace { + fn?: (socket: Socket) => void + ): Namespace { if (typeof name === "function" || name instanceof RegExp) { const parentNsp = new ParentNamespace(this); debug("initializing parent namespace %s", parentNsp.name); @@ -649,7 +660,7 @@ export class Server< */ public use( fn: ( - socket: Socket, + socket: Socket, next: (err?: ExtendedError) => void ) => void ): this { @@ -686,7 +697,9 @@ export class Server< * @return self * @public */ - public except(name: Room | Room[]): Server { + public except( + name: Room | Room[] + ): Server { this.sockets.except(name); return this; } @@ -713,6 +726,20 @@ export class Server< return this; } + /** + * Emit a packet to other Socket.IO servers + * + * @param ev - the event name + * @param args - an array of arguments, which may include an acknowledgement callback at the end + * @public + */ + public serverSideEmit>( + ev: Ev, + ...args: EventParams + ): boolean { + return this.sockets.serverSideEmit(ev, ...args); + } + /** * Gets a list of socket ids. * diff --git a/lib/namespace.ts b/lib/namespace.ts index 5730a48f01..1eae5318fc 100644 --- a/lib/namespace.ts +++ b/lib/namespace.ts @@ -20,35 +20,43 @@ export interface ExtendedError extends Error { export interface NamespaceReservedEventsMap< ListenEvents extends EventsMap, - EmitEvents extends EventsMap + EmitEvents extends EventsMap, + ServerSideEvents extends EventsMap > { - connect: (socket: Socket) => void; - connection: (socket: Socket) => void; + connect: (socket: Socket) => void; + connection: ( + socket: Socket + ) => void; } +export const RESERVED_EVENTS: ReadonlySet = new Set< + keyof NamespaceReservedEventsMap +>(["connect", "connection"]); + export class Namespace< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} > extends StrictEventEmitter< - {}, + ServerSideEvents, EmitEvents, - NamespaceReservedEventsMap + NamespaceReservedEventsMap > { public readonly name: string; public readonly sockets: Map< SocketId, - Socket + Socket > = new Map(); public adapter: Adapter; /** @private */ - readonly server: Server; + readonly server: Server; /** @private */ _fns: Array< ( - socket: Socket, + socket: Socket, next: (err?: ExtendedError) => void ) => void > = []; @@ -62,7 +70,10 @@ export class Namespace< * @param server instance * @param name */ - constructor(server: Server, name: string) { + constructor( + server: Server, + name: string + ) { super(); this.server = server; this.name = name; @@ -88,7 +99,7 @@ export class Namespace< */ public use( fn: ( - socket: Socket, + socket: Socket, next: (err?: ExtendedError) => void ) => void ): this { @@ -104,7 +115,7 @@ export class Namespace< * @private */ private run( - socket: Socket, + socket: Socket, fn: (err: ExtendedError | null) => void ) { const fns = this._fns.slice(0); @@ -166,10 +177,10 @@ export class Namespace< * @private */ _add( - client: Client, + client: Client, query, fn?: () => void - ): Socket { + ): Socket { debug("adding socket to nsp %s", this.name); const socket = new Socket(this, client, query); this.run(socket, (err) => { @@ -212,7 +223,7 @@ export class Namespace< * * @private */ - _remove(socket: Socket): void { + _remove(socket: Socket): void { if (this.sockets.has(socket.id)) { this.sockets.delete(socket.id); } else { @@ -255,6 +266,37 @@ export class Namespace< return this; } + /** + * Emit a packet to other Socket.IO servers + * + * @param ev - the event name + * @param args - an array of arguments, which may include an acknowledgement callback at the end + * @public + */ + public serverSideEmit>( + ev: Ev, + ...args: EventParams + ): boolean { + if (RESERVED_EVENTS.has(ev)) { + throw new Error(`"${ev}" is a reserved event name`); + } + args.unshift(ev); + this.adapter.serverSideEmit(args); + return true; + } + + /** + * Called when a packet is received from another Socket.IO server + * + * @param args - an array of arguments, which may include an acknowledgement callback at the end + * + * @private + */ + _onServerSideEmit(args: any[]) { + const event = args.shift(); + this.emitUntyped(event, args); + } + /** * Gets a list of clients. * diff --git a/lib/parent-namespace.ts b/lib/parent-namespace.ts index 353d9bd812..2c09d8b728 100644 --- a/lib/parent-namespace.ts +++ b/lib/parent-namespace.ts @@ -10,12 +10,15 @@ import type { BroadcastOptions } from "socket.io-adapter"; export class ParentNamespace< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents -> extends Namespace { + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} +> extends Namespace { private static count: number = 0; - private children: Set> = new Set(); + private children: Set< + Namespace + > = new Set(); - constructor(server: Server) { + constructor(server: Server) { super(server, "/_" + ParentNamespace.count++); } @@ -43,7 +46,9 @@ export class ParentNamespace< return true; } - createChild(name: string): Namespace { + createChild( + name: string + ): Namespace { const namespace = new Namespace(this.server, name); namespace._fns = this._fns.slice(0); this.listeners("connect").forEach((listener) => diff --git a/lib/socket.ts b/lib/socket.ts index 912274ccc8..40098d6901 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -46,7 +46,7 @@ export interface EventEmitterReservedEventsMap { export const RESERVED_EVENTS: ReadonlySet = new Set< | ClientReservedEvents - | keyof NamespaceReservedEventsMap + | keyof NamespaceReservedEventsMap | keyof SocketReservedEventsMap | keyof EventEmitterReservedEventsMap >([ @@ -110,7 +110,8 @@ export interface Handshake { export class Socket< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} > extends StrictEventEmitter< ListenEvents, EmitEvents, @@ -126,7 +127,7 @@ export class Socket< public connected: boolean; public disconnected: boolean; - private readonly server: Server; + private readonly server: Server; private readonly adapter: Adapter; private acks: Map void> = new Map(); private fns: Array< @@ -144,8 +145,8 @@ export class Socket< * @package */ constructor( - readonly nsp: Namespace, - readonly client: Client, + readonly nsp: Namespace, + readonly client: Client, auth: object ) { super(); diff --git a/test/socket.io.test-d.ts b/test/socket.io.test-d.ts index 22be55617a..deed33d62c 100644 --- a/test/socket.io.test-d.ts +++ b/test/socket.io.test-d.ts @@ -229,4 +229,48 @@ describe("server", () => { }); }); }); + + describe("listen and emit event maps", () => { + interface ClientToServerEvents { + helloFromClient: (message: string) => void; + } + + interface ServerToClientEvents { + helloFromServer: (message: string, x: number) => void; + } + + interface InterServerEvents { + helloFromServerToServer: (message: string, x: number) => void; + } + + describe("on", () => { + it("infers correct types for listener parameters", () => { + const srv = createServer(); + const sio = new Server< + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents + >(srv); + + expectType< + Server + >(sio); + srv.listen(() => { + sio.serverSideEmit("helloFromServerToServer", "hello", 10); + sio + .of("/test") + .serverSideEmit("helloFromServerToServer", "hello", 10); + + sio.on("helloFromServerToServer", (message, x) => { + expectType(message); + expectType(x); + }); + sio.of("/test").on("helloFromServerToServer", (message, x) => { + expectType(message); + expectType(x); + }); + }); + }); + }); + }); });