From 61ee20b3da48a4ff68eaa338b21a9aff9a33af20 Mon Sep 17 00:00:00 2001 From: neo Date: Mon, 1 Dec 2025 16:11:39 +0900 Subject: [PATCH] feat: leave multiple rooms at once --- .../lib/in-memory-adapter.ts | 27 ++++++++++++++----- packages/socket.io-adapter/test/index.ts | 13 +++++++++ packages/socket.io/lib/socket.ts | 13 +++++---- packages/socket.io/test/socket.ts | 17 ++++++++++++ 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/socket.io-adapter/lib/in-memory-adapter.ts b/packages/socket.io-adapter/lib/in-memory-adapter.ts index cf178170e0..60e63adf50 100644 --- a/packages/socket.io-adapter/lib/in-memory-adapter.ts +++ b/packages/socket.io-adapter/lib/in-memory-adapter.ts @@ -104,17 +104,30 @@ export class Adapter extends EventEmitter { } /** - * Removes a socket from a room. + * Removes a socket from one or multiple rooms. * - * @param {SocketId} id the socket id - * @param {Room} room the room name + * @param {SocketId} id the socket id + * @param {Room|Set} rooms the room name or a set of rooms */ - public del(id: SocketId, room: Room): Promise | void { - if (this.sids.has(id)) { - this.sids.get(id).delete(room); + public del(id: SocketId, rooms: Room | Set): Promise | void { + const roomsToRemove = rooms instanceof Set ? rooms : new Set([rooms]); + const socketRooms = this.sids.get(id); + + if (!socketRooms) { + for (const room of roomsToRemove) { + this._del(room, id); + } + return; } - this._del(room, id); + for (const room of roomsToRemove) { + socketRooms.delete(room); + this._del(room, id); + } + + if (socketRooms.size === 0) { + this.sids.delete(id); + } } private _del(room: Room, id: SocketId) { diff --git a/packages/socket.io-adapter/test/index.ts b/packages/socket.io-adapter/test/index.ts index 285ae66f02..80e3ce8391 100644 --- a/packages/socket.io-adapter/test/index.ts +++ b/packages/socket.io-adapter/test/index.ts @@ -26,6 +26,19 @@ describe("socket.io-adapter", () => { expect(adapter.sids.has("s2")).to.be(false); }); + it("should remove multiple rooms with a single call", () => { + const adapter = new Adapter({ server: { encoder: null } }); + adapter.addAll("s1", new Set(["r1", "r2", "r3"])); + + adapter.del("s1", new Set(["r1", "r3"])); + + expect(adapter.rooms.has("r1")).to.be(false); + expect(adapter.rooms.has("r3")).to.be(false); + expect(adapter.rooms.has("r2")).to.be(true); + expect(adapter.sids.get("s1").size).to.be(1); + expect(adapter.sids.get("s1").has("r2")).to.be(true); + }); + it("should return a list of sockets", async () => { const adapter = new Adapter({ server: { encoder: null }, diff --git a/packages/socket.io/lib/socket.ts b/packages/socket.io/lib/socket.ts index 55131fcee7..6448f4c4fa 100644 --- a/packages/socket.io/lib/socket.ts +++ b/packages/socket.io/lib/socket.ts @@ -476,16 +476,19 @@ export class Socket< * socket.leave("room1"); * * // leave multiple rooms - * socket.leave("room1").leave("room2"); + * socket.leave(["room1", "room2"]); * }); * - * @param {String} room + * @param {String|Array} rooms - room or array of rooms * @return a Promise or nothing, depending on the adapter */ - public leave(room: string): Promise | void { - debug("leave room %s", room); + public leave(rooms: Room | Array): Promise | void { + debug("leave room %s", rooms); - return this.adapter.del(this.id, room); + return this.adapter.del( + this.id, + new Set(Array.isArray(rooms) ? rooms : [rooms]), + ); } /** diff --git a/packages/socket.io/test/socket.ts b/packages/socket.io/test/socket.ts index 41882a7987..0a13398c0b 100644 --- a/packages/socket.io/test/socket.ts +++ b/packages/socket.io/test/socket.ts @@ -941,6 +941,23 @@ describe("socket", () => { }); }); + it("should leave multiple rooms at once", (done) => { + const io = new Server(0); + const client = createClient(io, "/"); + + io.on("connection", (socket) => { + Promise.resolve(socket.join(["room1", "room2"])) + .then(() => Promise.resolve(socket.leave(["room1", "room2"]))) + .then(() => { + const adapter = io.of("/").adapter; + expect(adapter.rooms.has("room1")).to.be(false); + expect(adapter.rooms.has("room2")).to.be(false); + success(done, io, client); + }) + .catch(done); + }); + }); + describe("onAny", () => { it("should call listener", (done) => { const io = new Server(0);