From 3fba91836bb01d18d8f22fadaeee5bc134283198 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 20 Feb 2023 00:15:19 +0100 Subject: [PATCH] fix: properly handle manually created dynamic namespaces Namespaces that match the regex of a parent namespace will now be added as a child of this namespace: ```js const parentNamespace = io.of(/^\/dynamic-\d+$/); const childNamespace = io.of("/dynamic-101"); ``` Related: - https://github.com/socketio/socket.io/issues/4615 - https://github.com/socketio/socket.io/issues/4164 - https://github.com/socketio/socket.io/issues/4015 - https://github.com/socketio/socket.io/issues/3960 --- lib/index.ts | 22 ++++++++++++++++++++-- lib/parent-namespace.ts | 19 +++++++++++++++++++ test/namespaces.ts | 11 +++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index c0581aa7ca..3ecdcfb8dc 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -178,6 +178,18 @@ export class Server< ParentNspNameMatchFn, ParentNamespace > = new Map(); + + /** + * A subset of the {@link parentNsps} map, only containing {@link ParentNamespace} which are based on a regular + * expression. + * + * @private + */ + private parentNamespacesFromRegExp: Map< + RegExp, + ParentNamespace + > = new Map(); + private _adapter?: AdapterConstructor; private _serveClient: boolean; private readonly opts: Partial; @@ -314,8 +326,6 @@ export class Server< } const namespace = this.parentNsps.get(nextFn.value)!.createChild(name); debug("dynamic namespace %s was created", name); - // @ts-ignore - this.sockets.emitReserved("new_namespace", namespace); fn(namespace); }); }; @@ -691,6 +701,7 @@ export class Server< (nsp, conn, next) => next(null, (name as RegExp).test(nsp)), parentNsp ); + this.parentNamespacesFromRegExp.set(name, parentNsp); } if (fn) { // @ts-ignore @@ -703,6 +714,13 @@ export class Server< let nsp = this._nsps.get(name); if (!nsp) { + for (const [regex, parentNamespace] of this.parentNamespacesFromRegExp) { + if (regex.test(name as string)) { + debug("attaching namespace %s to parent namespace %s", name, regex); + return parentNamespace.createChild(name as string); + } + } + debug("initializing namespace %s", name); nsp = new Namespace(this, name); this._nsps.set(name, nsp); diff --git a/lib/parent-namespace.ts b/lib/parent-namespace.ts index 1206f6ccc5..14787a060c 100644 --- a/lib/parent-namespace.ts +++ b/lib/parent-namespace.ts @@ -11,6 +11,21 @@ import debugModule from "debug"; const debug = debugModule("socket.io:parent-namespace"); +/** + * A parent namespace is a special {@link Namespace} that holds a list of child namespaces which were created either + * with a regular expression or with a function. + * + * @example + * const parentNamespace = io.of(/\/dynamic-\d+/); + * + * parentNamespace.on("connection", (socket) => { + * const childNamespace = socket.nsp; + * } + * + * // will reach all the clients that are in one of the child namespaces, like "/dynamic-101" + * parentNamespace.emit("hello", "world"); + * + */ export class ParentNamespace< ListenEvents extends EventsMap = DefaultEventsMap, EmitEvents extends EventsMap = ListenEvents, @@ -81,6 +96,10 @@ export class ParentNamespace< } this.server._nsps.set(name, namespace); + + // @ts-ignore + this.server.sockets.emitReserved("new_namespace", namespace); + return namespace; } diff --git a/test/namespaces.ts b/test/namespaces.ts index 380a2d263c..dd82f4bc3e 100644 --- a/test/namespaces.ts +++ b/test/namespaces.ts @@ -652,5 +652,16 @@ describe("namespaces", () => { io.of(/^\/dynamic-\d+$/); }); + + it("should attach a child namespace to its parent upon manual creation", () => { + const io = new Server(0); + const parentNamespace = io.of(/^\/dynamic-\d+$/); + const childNamespace = io.of("/dynamic-101"); + + // @ts-ignore + expect(parentNamespace.children.has(childNamespace)).to.be(true); + + io.close(); + }); }); });