Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions packages/socket.io-adapter/lib/in-memory-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Room>} rooms the room name or a set of rooms
*/
public del(id: SocketId, room: Room): Promise<void> | void {
if (this.sids.has(id)) {
this.sids.get(id).delete(room);
public del(id: SocketId, rooms: Room | Set<Room>): Promise<void> | void {
const roomsToRemove = rooms instanceof Set ? rooms : new Set<Room>([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) {
Expand Down
13 changes: 13 additions & 0 deletions packages/socket.io-adapter/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
13 changes: 8 additions & 5 deletions packages/socket.io/lib/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> | void {
debug("leave room %s", room);
public leave(rooms: Room | Array<Room>): Promise<void> | 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]),
);
}

/**
Expand Down
17 changes: 17 additions & 0 deletions packages/socket.io/test/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down