Skip to content

Commit b25495c

Browse files
feat: add some utility methods
This commit adds the following methods: - fetchSockets: returns the matching socket instances Syntax: ```js // return all Socket instances const sockets = await io.fetchSockets(); // return all Socket instances of the "admin" namespace in the "room1" room const sockets = await io.of("/admin").in("room1").fetchSockets(); ``` - socketsJoin: makes the matching socket instances join the specified rooms Syntax: ```js // make all Socket instances join the "room1" room io.socketsJoin("room1"); // make all Socket instances of the "admin" namespace in the "room1" room join the "room2" room io.of("/admin").in("room1").socketsJoin("room2"); ``` - socketsLeave: makes the matching socket instances leave the specified rooms Syntax: ```js // make all Socket instances leave the "room1" room io.socketsLeave("room1"); // make all Socket instances of the "admin" namespace in the "room1" room leave the "room2" room io.of("/admin").in("room1").socketsLeave("room2"); ``` - disconnectSockets: makes the matching socket instances disconnect Syntax: ```js // make all Socket instances disconnect io.disconnectSockets(); // make all Socket instances of the "admin" namespace in the "room1" room disconnect io.of("/admin").in("room1").disconnectSockets(); ``` Those methods share the same semantics as broadcasting. They will also work with multiple Socket.IO servers when using the Redis adapter. In that case, the fetchSockets() method will return a list of RemoteSocket instances, which expose a subset of the methods and attributes of the Socket class (the "request" attribute cannot be mocked, for example). Related: - #3042 - #3418 - #3570 - socketio/socket.io-redis-adapter#283
1 parent 085d1de commit b25495c

File tree

6 files changed

+401
-4
lines changed

6 files changed

+401
-4
lines changed

lib/broadcast-operator.ts

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { BroadcastFlags, Room, SocketId } from "socket.io-adapter";
2-
import { RESERVED_EVENTS } from "./socket";
2+
import { Handshake, RESERVED_EVENTS, Socket } from "./socket";
33
import { PacketType } from "socket.io-parser";
44
import type { Adapter } from "socket.io-adapter";
55

@@ -160,4 +160,142 @@ export class BroadcastOperator {
160160
}
161161
return this.adapter.sockets(this.rooms);
162162
}
163+
164+
/**
165+
* Returns the matching socket instances
166+
*
167+
* @public
168+
*/
169+
public fetchSockets(): Promise<RemoteSocket[]> {
170+
return this.adapter
171+
.fetchSockets({
172+
rooms: this.rooms,
173+
except: this.exceptRooms,
174+
})
175+
.then((sockets) => {
176+
return sockets.map((socket) => {
177+
if (socket instanceof Socket) {
178+
// FIXME the TypeScript compiler complains about missing private properties
179+
return (socket as unknown) as RemoteSocket;
180+
} else {
181+
return new RemoteSocket(this.adapter, socket as SocketDetails);
182+
}
183+
});
184+
});
185+
}
186+
187+
/**
188+
* Makes the matching socket instances join the specified rooms
189+
*
190+
* @param room
191+
* @public
192+
*/
193+
public socketsJoin(room: Room | Room[]): void {
194+
this.adapter.addSockets(
195+
{
196+
rooms: this.rooms,
197+
except: this.exceptRooms,
198+
},
199+
Array.isArray(room) ? room : [room]
200+
);
201+
}
202+
203+
/**
204+
* Makes the matching socket instances leave the specified rooms
205+
*
206+
* @param room
207+
* @public
208+
*/
209+
public socketsLeave(room: Room | Room[]): void {
210+
this.adapter.delSockets(
211+
{
212+
rooms: this.rooms,
213+
except: this.exceptRooms,
214+
},
215+
Array.isArray(room) ? room : [room]
216+
);
217+
}
218+
219+
/**
220+
* Makes the matching socket instances disconnect
221+
*
222+
* @param close - whether to close the underlying connection
223+
* @public
224+
*/
225+
public disconnectSockets(close: boolean = false): void {
226+
this.adapter.disconnectSockets(
227+
{
228+
rooms: this.rooms,
229+
except: this.exceptRooms,
230+
},
231+
close
232+
);
233+
}
234+
}
235+
236+
/**
237+
* Format of the data when the Socket instance exists on another Socket.IO server
238+
*/
239+
interface SocketDetails {
240+
id: SocketId;
241+
handshake: Handshake;
242+
rooms: Room[];
243+
data: any;
244+
}
245+
246+
/**
247+
* Expose of subset of the attributes and methods of the Socket class
248+
*/
249+
export class RemoteSocket {
250+
public readonly id: SocketId;
251+
public readonly handshake: Handshake;
252+
public readonly rooms: Set<Room>;
253+
public readonly data: any;
254+
255+
private readonly operator: BroadcastOperator;
256+
257+
constructor(adapter: Adapter, details: SocketDetails) {
258+
this.id = details.id;
259+
this.handshake = details.handshake;
260+
this.rooms = new Set(details.rooms);
261+
this.data = details.data;
262+
this.operator = new BroadcastOperator(adapter, new Set([this.id]));
263+
}
264+
265+
public emit(ev: string, ...args: any[]): boolean {
266+
return this.operator.emit(ev, ...args);
267+
}
268+
269+
/**
270+
* Joins a room.
271+
*
272+
* @param {String|Array} room - room or array of rooms
273+
* @public
274+
*/
275+
public join(room: Room | Room[]): void {
276+
return this.operator.socketsJoin(room);
277+
}
278+
279+
/**
280+
* Leaves a room.
281+
*
282+
* @param {String} room
283+
* @public
284+
*/
285+
public leave(room: Room): void {
286+
return this.operator.socketsLeave(room);
287+
}
288+
289+
/**
290+
* Disconnects this client.
291+
*
292+
* @param {Boolean} close - if `true`, closes the underlying connection
293+
* @return {Socket} self
294+
*
295+
* @public
296+
*/
297+
public disconnect(close = false): this {
298+
this.operator.disconnectSockets(close);
299+
return this;
300+
}
163301
}

lib/index.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import debugModule from "debug";
1616
import { Socket } from "./socket";
1717
import type { CookieSerializeOptions } from "cookie";
1818
import type { CorsOptions } from "cors";
19-
import type { BroadcastOperator } from "./broadcast-operator";
19+
import type { BroadcastOperator, RemoteSocket } from "./broadcast-operator";
2020

2121
const debug = debugModule("socket.io:server");
2222

@@ -719,6 +719,45 @@ export class Server extends EventEmitter {
719719
public get local(): BroadcastOperator {
720720
return this.sockets.local;
721721
}
722+
723+
/**
724+
* Returns the matching socket instances
725+
*
726+
* @public
727+
*/
728+
public fetchSockets(): Promise<RemoteSocket[]> {
729+
return this.sockets.fetchSockets();
730+
}
731+
732+
/**
733+
* Makes the matching socket instances join the specified rooms
734+
*
735+
* @param room
736+
* @public
737+
*/
738+
public socketsJoin(room: Room | Room[]): void {
739+
return this.sockets.socketsJoin(room);
740+
}
741+
742+
/**
743+
* Makes the matching socket instances leave the specified rooms
744+
*
745+
* @param room
746+
* @public
747+
*/
748+
public socketsLeave(room: Room | Room[]): void {
749+
return this.sockets.socketsLeave(room);
750+
}
751+
752+
/**
753+
* Makes the matching socket instances disconnect
754+
*
755+
* @param close - whether to close the underlying connection
756+
* @public
757+
*/
758+
public disconnectSockets(close: boolean = false): void {
759+
return this.sockets.disconnectSockets(close);
760+
}
722761
}
723762

724763
/**
@@ -740,4 +779,4 @@ emitterMethods.forEach(function (fn) {
740779
module.exports = (srv?, opts?) => new Server(srv, opts);
741780
module.exports.Server = Server;
742781

743-
export { Socket, ServerOptions, Namespace };
782+
export { Socket, ServerOptions, Namespace, BroadcastOperator, RemoteSocket };

lib/namespace.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Client } from "./client";
44
import { EventEmitter } from "events";
55
import debugModule from "debug";
66
import type { Adapter, Room, SocketId } from "socket.io-adapter";
7-
import { BroadcastOperator } from "./broadcast-operator";
7+
import { BroadcastOperator, RemoteSocket } from "./broadcast-operator";
88

99
const debug = debugModule("socket.io:namespace");
1010

@@ -257,4 +257,43 @@ export class Namespace extends EventEmitter {
257257
public get local(): BroadcastOperator {
258258
return new BroadcastOperator(this.adapter).local;
259259
}
260+
261+
/**
262+
* Returns the matching socket instances
263+
*
264+
* @public
265+
*/
266+
public fetchSockets(): Promise<RemoteSocket[]> {
267+
return new BroadcastOperator(this.adapter).fetchSockets();
268+
}
269+
270+
/**
271+
* Makes the matching socket instances join the specified rooms
272+
*
273+
* @param room
274+
* @public
275+
*/
276+
public socketsJoin(room: Room | Room[]): void {
277+
return new BroadcastOperator(this.adapter).socketsJoin(room);
278+
}
279+
280+
/**
281+
* Makes the matching socket instances leave the specified rooms
282+
*
283+
* @param room
284+
* @public
285+
*/
286+
public socketsLeave(room: Room | Room[]): void {
287+
return new BroadcastOperator(this.adapter).socketsLeave(room);
288+
}
289+
290+
/**
291+
* Makes the matching socket instances disconnect
292+
*
293+
* @param close - whether to close the underlying connection
294+
* @public
295+
*/
296+
public disconnectSockets(close: boolean = false): void {
297+
return new BroadcastOperator(this.adapter).disconnectSockets(close);
298+
}
260299
}

lib/socket.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export interface Handshake {
8181
export class Socket extends EventEmitter {
8282
public readonly id: SocketId;
8383
public readonly handshake: Handshake;
84+
/**
85+
* Additional information that can be attached to the Socket instance and which will be used in the fetchSockets method
86+
*/
87+
public data: any = {};
8488

8589
public connected: boolean;
8690
public disconnected: boolean;

test/socket.io.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as io_v2 from "socket.io-client-v2";
1313
const ioc = require("socket.io-client");
1414

1515
import "./support/util";
16+
import "./utility-methods";
1617

1718
// Creates a socket.io client for the given server
1819
function client(srv, nsp?: string | object, opts?: object) {

0 commit comments

Comments
 (0)