Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

broadcast-operator.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.RemoteSocket = exports.BroadcastOperator = void 0;
  4. const socket_1 = require("./socket");
  5. const socket_io_parser_1 = require("socket.io-parser");
  6. class BroadcastOperator {
  7. constructor(adapter, rooms = new Set(), exceptRooms = new Set(), flags = {}) {
  8. this.adapter = adapter;
  9. this.rooms = rooms;
  10. this.exceptRooms = exceptRooms;
  11. this.flags = flags;
  12. }
  13. /**
  14. * Targets a room when emitting.
  15. *
  16. * @example
  17. * // the “foo” event will be broadcast to all connected clients in the “room-101” room
  18. * io.to("room-101").emit("foo", "bar");
  19. *
  20. * // with an array of rooms (a client will be notified at most once)
  21. * io.to(["room-101", "room-102"]).emit("foo", "bar");
  22. *
  23. * // with multiple chained calls
  24. * io.to("room-101").to("room-102").emit("foo", "bar");
  25. *
  26. * @param room - a room, or an array of rooms
  27. * @return a new {@link BroadcastOperator} instance for chaining
  28. */
  29. to(room) {
  30. const rooms = new Set(this.rooms);
  31. if (Array.isArray(room)) {
  32. room.forEach((r) => rooms.add(r));
  33. }
  34. else {
  35. rooms.add(room);
  36. }
  37. return new BroadcastOperator(this.adapter, rooms, this.exceptRooms, this.flags);
  38. }
  39. /**
  40. * Targets a room when emitting. Similar to `to()`, but might feel clearer in some cases:
  41. *
  42. * @example
  43. * // disconnect all clients in the "room-101" room
  44. * io.in("room-101").disconnectSockets();
  45. *
  46. * @param room - a room, or an array of rooms
  47. * @return a new {@link BroadcastOperator} instance for chaining
  48. */
  49. in(room) {
  50. return this.to(room);
  51. }
  52. /**
  53. * Excludes a room when emitting.
  54. *
  55. * @example
  56. * // the "foo" event will be broadcast to all connected clients, except the ones that are in the "room-101" room
  57. * io.except("room-101").emit("foo", "bar");
  58. *
  59. * // with an array of rooms
  60. * io.except(["room-101", "room-102"]).emit("foo", "bar");
  61. *
  62. * // with multiple chained calls
  63. * io.except("room-101").except("room-102").emit("foo", "bar");
  64. *
  65. * @param room - a room, or an array of rooms
  66. * @return a new {@link BroadcastOperator} instance for chaining
  67. */
  68. except(room) {
  69. const exceptRooms = new Set(this.exceptRooms);
  70. if (Array.isArray(room)) {
  71. room.forEach((r) => exceptRooms.add(r));
  72. }
  73. else {
  74. exceptRooms.add(room);
  75. }
  76. return new BroadcastOperator(this.adapter, this.rooms, exceptRooms, this.flags);
  77. }
  78. /**
  79. * Sets the compress flag.
  80. *
  81. * @example
  82. * io.compress(false).emit("hello");
  83. *
  84. * @param compress - if `true`, compresses the sending data
  85. * @return a new BroadcastOperator instance
  86. */
  87. compress(compress) {
  88. const flags = Object.assign({}, this.flags, { compress });
  89. return new BroadcastOperator(this.adapter, this.rooms, this.exceptRooms, flags);
  90. }
  91. /**
  92. * Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to
  93. * receive messages (because of network slowness or other issues, or because they’re connected through long polling
  94. * and is in the middle of a request-response cycle).
  95. *
  96. * @example
  97. * io.volatile.emit("hello"); // the clients may or may not receive it
  98. *
  99. * @return a new BroadcastOperator instance
  100. */
  101. get volatile() {
  102. const flags = Object.assign({}, this.flags, { volatile: true });
  103. return new BroadcastOperator(this.adapter, this.rooms, this.exceptRooms, flags);
  104. }
  105. /**
  106. * Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
  107. *
  108. * @example
  109. * // the “foo” event will be broadcast to all connected clients on this node
  110. * io.local.emit("foo", "bar");
  111. *
  112. * @return a new {@link BroadcastOperator} instance for chaining
  113. */
  114. get local() {
  115. const flags = Object.assign({}, this.flags, { local: true });
  116. return new BroadcastOperator(this.adapter, this.rooms, this.exceptRooms, flags);
  117. }
  118. /**
  119. * Adds a timeout in milliseconds for the next operation
  120. *
  121. * @example
  122. * io.timeout(1000).emit("some-event", (err, responses) => {
  123. * if (err) {
  124. * // some clients did not acknowledge the event in the given delay
  125. * } else {
  126. * console.log(responses); // one response per client
  127. * }
  128. * });
  129. *
  130. * @param timeout
  131. */
  132. timeout(timeout) {
  133. const flags = Object.assign({}, this.flags, { timeout });
  134. return new BroadcastOperator(this.adapter, this.rooms, this.exceptRooms, flags);
  135. }
  136. /**
  137. * Emits to all clients.
  138. *
  139. * @example
  140. * // the “foo” event will be broadcast to all connected clients
  141. * io.emit("foo", "bar");
  142. *
  143. * // the “foo” event will be broadcast to all connected clients in the “room-101” room
  144. * io.to("room-101").emit("foo", "bar");
  145. *
  146. * // with an acknowledgement expected from all connected clients
  147. * io.timeout(1000).emit("some-event", (err, responses) => {
  148. * if (err) {
  149. * // some clients did not acknowledge the event in the given delay
  150. * } else {
  151. * console.log(responses); // one response per client
  152. * }
  153. * });
  154. *
  155. * @return Always true
  156. */
  157. emit(ev, ...args) {
  158. if (socket_1.RESERVED_EVENTS.has(ev)) {
  159. throw new Error(`"${String(ev)}" is a reserved event name`);
  160. }
  161. // set up packet object
  162. const data = [ev, ...args];
  163. const packet = {
  164. type: socket_io_parser_1.PacketType.EVENT,
  165. data: data,
  166. };
  167. const withAck = typeof data[data.length - 1] === "function";
  168. if (!withAck) {
  169. this.adapter.broadcast(packet, {
  170. rooms: this.rooms,
  171. except: this.exceptRooms,
  172. flags: this.flags,
  173. });
  174. return true;
  175. }
  176. const ack = data.pop();
  177. let timedOut = false;
  178. let responses = [];
  179. const timer = setTimeout(() => {
  180. timedOut = true;
  181. ack.apply(this, [new Error("operation has timed out"), responses]);
  182. }, this.flags.timeout);
  183. let expectedServerCount = -1;
  184. let actualServerCount = 0;
  185. let expectedClientCount = 0;
  186. const checkCompleteness = () => {
  187. if (!timedOut &&
  188. expectedServerCount === actualServerCount &&
  189. responses.length === expectedClientCount) {
  190. clearTimeout(timer);
  191. ack.apply(this, [null, responses]);
  192. }
  193. };
  194. this.adapter.broadcastWithAck(packet, {
  195. rooms: this.rooms,
  196. except: this.exceptRooms,
  197. flags: this.flags,
  198. }, (clientCount) => {
  199. // each Socket.IO server in the cluster sends the number of clients that were notified
  200. expectedClientCount += clientCount;
  201. actualServerCount++;
  202. checkCompleteness();
  203. }, (clientResponse) => {
  204. // each client sends an acknowledgement
  205. responses.push(clientResponse);
  206. checkCompleteness();
  207. });
  208. this.adapter.serverCount().then((serverCount) => {
  209. expectedServerCount = serverCount;
  210. checkCompleteness();
  211. });
  212. return true;
  213. }
  214. /**
  215. * Gets a list of clients.
  216. *
  217. * @deprecated this method will be removed in the next major release, please use {@link Server#serverSideEmit} or
  218. * {@link fetchSockets} instead.
  219. */
  220. allSockets() {
  221. if (!this.adapter) {
  222. throw new Error("No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?");
  223. }
  224. return this.adapter.sockets(this.rooms);
  225. }
  226. /**
  227. * Returns the matching socket instances. This method works across a cluster of several Socket.IO servers.
  228. *
  229. * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
  230. *
  231. * @example
  232. * // return all Socket instances
  233. * const sockets = await io.fetchSockets();
  234. *
  235. * // return all Socket instances in the "room1" room
  236. * const sockets = await io.in("room1").fetchSockets();
  237. *
  238. * for (const socket of sockets) {
  239. * console.log(socket.id);
  240. * console.log(socket.handshake);
  241. * console.log(socket.rooms);
  242. * console.log(socket.data);
  243. *
  244. * socket.emit("hello");
  245. * socket.join("room1");
  246. * socket.leave("room2");
  247. * socket.disconnect();
  248. * }
  249. */
  250. fetchSockets() {
  251. return this.adapter
  252. .fetchSockets({
  253. rooms: this.rooms,
  254. except: this.exceptRooms,
  255. flags: this.flags,
  256. })
  257. .then((sockets) => {
  258. return sockets.map((socket) => {
  259. if (socket instanceof socket_1.Socket) {
  260. // FIXME the TypeScript compiler complains about missing private properties
  261. return socket;
  262. }
  263. else {
  264. return new RemoteSocket(this.adapter, socket);
  265. }
  266. });
  267. });
  268. }
  269. /**
  270. * Makes the matching socket instances join the specified rooms.
  271. *
  272. * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
  273. *
  274. * @example
  275. *
  276. * // make all socket instances join the "room1" room
  277. * io.socketsJoin("room1");
  278. *
  279. * // make all socket instances in the "room1" room join the "room2" and "room3" rooms
  280. * io.in("room1").socketsJoin(["room2", "room3"]);
  281. *
  282. * @param room - a room, or an array of rooms
  283. */
  284. socketsJoin(room) {
  285. this.adapter.addSockets({
  286. rooms: this.rooms,
  287. except: this.exceptRooms,
  288. flags: this.flags,
  289. }, Array.isArray(room) ? room : [room]);
  290. }
  291. /**
  292. * Makes the matching socket instances leave the specified rooms.
  293. *
  294. * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
  295. *
  296. * @example
  297. * // make all socket instances leave the "room1" room
  298. * io.socketsLeave("room1");
  299. *
  300. * // make all socket instances in the "room1" room leave the "room2" and "room3" rooms
  301. * io.in("room1").socketsLeave(["room2", "room3"]);
  302. *
  303. * @param room - a room, or an array of rooms
  304. */
  305. socketsLeave(room) {
  306. this.adapter.delSockets({
  307. rooms: this.rooms,
  308. except: this.exceptRooms,
  309. flags: this.flags,
  310. }, Array.isArray(room) ? room : [room]);
  311. }
  312. /**
  313. * Makes the matching socket instances disconnect.
  314. *
  315. * Note: this method also works within a cluster of multiple Socket.IO servers, with a compatible {@link Adapter}.
  316. *
  317. * @example
  318. * // make all socket instances disconnect (the connections might be kept alive for other namespaces)
  319. * io.disconnectSockets();
  320. *
  321. * // make all socket instances in the "room1" room disconnect and close the underlying connections
  322. * io.in("room1").disconnectSockets(true);
  323. *
  324. * @param close - whether to close the underlying connection
  325. */
  326. disconnectSockets(close = false) {
  327. this.adapter.disconnectSockets({
  328. rooms: this.rooms,
  329. except: this.exceptRooms,
  330. flags: this.flags,
  331. }, close);
  332. }
  333. }
  334. exports.BroadcastOperator = BroadcastOperator;
  335. /**
  336. * Expose of subset of the attributes and methods of the Socket class
  337. */
  338. class RemoteSocket {
  339. constructor(adapter, details) {
  340. this.id = details.id;
  341. this.handshake = details.handshake;
  342. this.rooms = new Set(details.rooms);
  343. this.data = details.data;
  344. this.operator = new BroadcastOperator(adapter, new Set([this.id]));
  345. }
  346. emit(ev, ...args) {
  347. return this.operator.emit(ev, ...args);
  348. }
  349. /**
  350. * Joins a room.
  351. *
  352. * @param {String|Array} room - room or array of rooms
  353. */
  354. join(room) {
  355. return this.operator.socketsJoin(room);
  356. }
  357. /**
  358. * Leaves a room.
  359. *
  360. * @param {String} room
  361. */
  362. leave(room) {
  363. return this.operator.socketsLeave(room);
  364. }
  365. /**
  366. * Disconnects this client.
  367. *
  368. * @param {Boolean} close - if `true`, closes the underlying connection
  369. * @return {Socket} self
  370. */
  371. disconnect(close = false) {
  372. this.operator.disconnectSockets(close);
  373. return this;
  374. }
  375. }
  376. exports.RemoteSocket = RemoteSocket;