socket.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. import { PacketType } from "socket.io-parser";
  2. import { on } from "./on.js";
  3. import { Emitter, } from "@socket.io/component-emitter";
  4. import debugModule from "debug"; // debug()
  5. const debug = debugModule("socket.io-client:socket"); // debug()
  6. /**
  7. * Internal events.
  8. * These events can't be emitted by the user.
  9. */
  10. const RESERVED_EVENTS = Object.freeze({
  11. connect: 1,
  12. connect_error: 1,
  13. disconnect: 1,
  14. disconnecting: 1,
  15. // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener
  16. newListener: 1,
  17. removeListener: 1,
  18. });
  19. /**
  20. * A Socket is the fundamental class for interacting with the server.
  21. *
  22. * A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.
  23. *
  24. * @example
  25. * const socket = io();
  26. *
  27. * socket.on("connect", () => {
  28. * console.log("connected");
  29. * });
  30. *
  31. * // send an event to the server
  32. * socket.emit("foo", "bar");
  33. *
  34. * socket.on("foobar", () => {
  35. * // an event was received from the server
  36. * });
  37. *
  38. * // upon disconnection
  39. * socket.on("disconnect", (reason) => {
  40. * console.log(`disconnected due to ${reason}`);
  41. * });
  42. */
  43. export class Socket extends Emitter {
  44. /**
  45. * `Socket` constructor.
  46. */
  47. constructor(io, nsp, opts) {
  48. super();
  49. /**
  50. * Whether the socket is currently connected to the server.
  51. *
  52. * @example
  53. * const socket = io();
  54. *
  55. * socket.on("connect", () => {
  56. * console.log(socket.connected); // true
  57. * });
  58. *
  59. * socket.on("disconnect", () => {
  60. * console.log(socket.connected); // false
  61. * });
  62. */
  63. this.connected = false;
  64. /**
  65. * Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will
  66. * be transmitted by the server.
  67. */
  68. this.recovered = false;
  69. /**
  70. * Buffer for packets received before the CONNECT packet
  71. */
  72. this.receiveBuffer = [];
  73. /**
  74. * Buffer for packets that will be sent once the socket is connected
  75. */
  76. this.sendBuffer = [];
  77. /**
  78. * The queue of packets to be sent with retry in case of failure.
  79. *
  80. * Packets are sent one by one, each waiting for the server acknowledgement, in order to guarantee the delivery order.
  81. * @private
  82. */
  83. this._queue = [];
  84. /**
  85. * A sequence to generate the ID of the {@link QueuedPacket}.
  86. * @private
  87. */
  88. this._queueSeq = 0;
  89. this.ids = 0;
  90. this.acks = {};
  91. this.flags = {};
  92. this.io = io;
  93. this.nsp = nsp;
  94. if (opts && opts.auth) {
  95. this.auth = opts.auth;
  96. }
  97. this._opts = Object.assign({}, opts);
  98. if (this.io._autoConnect)
  99. this.open();
  100. }
  101. /**
  102. * Whether the socket is currently disconnected
  103. *
  104. * @example
  105. * const socket = io();
  106. *
  107. * socket.on("connect", () => {
  108. * console.log(socket.disconnected); // false
  109. * });
  110. *
  111. * socket.on("disconnect", () => {
  112. * console.log(socket.disconnected); // true
  113. * });
  114. */
  115. get disconnected() {
  116. return !this.connected;
  117. }
  118. /**
  119. * Subscribe to open, close and packet events
  120. *
  121. * @private
  122. */
  123. subEvents() {
  124. if (this.subs)
  125. return;
  126. const io = this.io;
  127. this.subs = [
  128. on(io, "open", this.onopen.bind(this)),
  129. on(io, "packet", this.onpacket.bind(this)),
  130. on(io, "error", this.onerror.bind(this)),
  131. on(io, "close", this.onclose.bind(this)),
  132. ];
  133. }
  134. /**
  135. * Whether the Socket will try to reconnect when its Manager connects or reconnects.
  136. *
  137. * @example
  138. * const socket = io();
  139. *
  140. * console.log(socket.active); // true
  141. *
  142. * socket.on("disconnect", (reason) => {
  143. * if (reason === "io server disconnect") {
  144. * // the disconnection was initiated by the server, you need to manually reconnect
  145. * console.log(socket.active); // false
  146. * }
  147. * // else the socket will automatically try to reconnect
  148. * console.log(socket.active); // true
  149. * });
  150. */
  151. get active() {
  152. return !!this.subs;
  153. }
  154. /**
  155. * "Opens" the socket.
  156. *
  157. * @example
  158. * const socket = io({
  159. * autoConnect: false
  160. * });
  161. *
  162. * socket.connect();
  163. */
  164. connect() {
  165. if (this.connected)
  166. return this;
  167. this.subEvents();
  168. if (!this.io["_reconnecting"])
  169. this.io.open(); // ensure open
  170. if ("open" === this.io._readyState)
  171. this.onopen();
  172. return this;
  173. }
  174. /**
  175. * Alias for {@link connect()}.
  176. */
  177. open() {
  178. return this.connect();
  179. }
  180. /**
  181. * Sends a `message` event.
  182. *
  183. * This method mimics the WebSocket.send() method.
  184. *
  185. * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
  186. *
  187. * @example
  188. * socket.send("hello");
  189. *
  190. * // this is equivalent to
  191. * socket.emit("message", "hello");
  192. *
  193. * @return self
  194. */
  195. send(...args) {
  196. args.unshift("message");
  197. this.emit.apply(this, args);
  198. return this;
  199. }
  200. /**
  201. * Override `emit`.
  202. * If the event is in `events`, it's emitted normally.
  203. *
  204. * @example
  205. * socket.emit("hello", "world");
  206. *
  207. * // all serializable datastructures are supported (no need to call JSON.stringify)
  208. * socket.emit("hello", 1, "2", { 3: ["4"], 5: Uint8Array.from([6]) });
  209. *
  210. * // with an acknowledgement from the server
  211. * socket.emit("hello", "world", (val) => {
  212. * // ...
  213. * });
  214. *
  215. * @return self
  216. */
  217. emit(ev, ...args) {
  218. if (RESERVED_EVENTS.hasOwnProperty(ev)) {
  219. throw new Error('"' + ev.toString() + '" is a reserved event name');
  220. }
  221. args.unshift(ev);
  222. if (this._opts.retries && !this.flags.fromQueue && !this.flags.volatile) {
  223. this._addToQueue(args);
  224. return this;
  225. }
  226. const packet = {
  227. type: PacketType.EVENT,
  228. data: args,
  229. };
  230. packet.options = {};
  231. packet.options.compress = this.flags.compress !== false;
  232. // event ack callback
  233. if ("function" === typeof args[args.length - 1]) {
  234. const id = this.ids++;
  235. debug("emitting packet with ack id %d", id);
  236. const ack = args.pop();
  237. this._registerAckCallback(id, ack);
  238. packet.id = id;
  239. }
  240. const isTransportWritable = this.io.engine &&
  241. this.io.engine.transport &&
  242. this.io.engine.transport.writable;
  243. const discardPacket = this.flags.volatile && (!isTransportWritable || !this.connected);
  244. if (discardPacket) {
  245. debug("discard packet as the transport is not currently writable");
  246. }
  247. else if (this.connected) {
  248. this.notifyOutgoingListeners(packet);
  249. this.packet(packet);
  250. }
  251. else {
  252. this.sendBuffer.push(packet);
  253. }
  254. this.flags = {};
  255. return this;
  256. }
  257. /**
  258. * @private
  259. */
  260. _registerAckCallback(id, ack) {
  261. var _a;
  262. const timeout = (_a = this.flags.timeout) !== null && _a !== void 0 ? _a : this._opts.ackTimeout;
  263. if (timeout === undefined) {
  264. this.acks[id] = ack;
  265. return;
  266. }
  267. // @ts-ignore
  268. const timer = this.io.setTimeoutFn(() => {
  269. delete this.acks[id];
  270. for (let i = 0; i < this.sendBuffer.length; i++) {
  271. if (this.sendBuffer[i].id === id) {
  272. debug("removing packet with ack id %d from the buffer", id);
  273. this.sendBuffer.splice(i, 1);
  274. }
  275. }
  276. debug("event with ack id %d has timed out after %d ms", id, timeout);
  277. ack.call(this, new Error("operation has timed out"));
  278. }, timeout);
  279. this.acks[id] = (...args) => {
  280. // @ts-ignore
  281. this.io.clearTimeoutFn(timer);
  282. ack.apply(this, [null, ...args]);
  283. };
  284. }
  285. /**
  286. * Emits an event and waits for an acknowledgement
  287. *
  288. * @example
  289. * // without timeout
  290. * const response = await socket.emitWithAck("hello", "world");
  291. *
  292. * // with a specific timeout
  293. * try {
  294. * const response = await socket.timeout(1000).emitWithAck("hello", "world");
  295. * } catch (err) {
  296. * // the server did not acknowledge the event in the given delay
  297. * }
  298. *
  299. * @return a Promise that will be fulfilled when the server acknowledges the event
  300. */
  301. emitWithAck(ev, ...args) {
  302. // the timeout flag is optional
  303. const withErr = this.flags.timeout !== undefined || this._opts.ackTimeout !== undefined;
  304. return new Promise((resolve, reject) => {
  305. args.push((arg1, arg2) => {
  306. if (withErr) {
  307. return arg1 ? reject(arg1) : resolve(arg2);
  308. }
  309. else {
  310. return resolve(arg1);
  311. }
  312. });
  313. this.emit(ev, ...args);
  314. });
  315. }
  316. /**
  317. * Add the packet to the queue.
  318. * @param args
  319. * @private
  320. */
  321. _addToQueue(args) {
  322. let ack;
  323. if (typeof args[args.length - 1] === "function") {
  324. ack = args.pop();
  325. }
  326. const packet = {
  327. id: this._queueSeq++,
  328. tryCount: 0,
  329. pending: false,
  330. args,
  331. flags: Object.assign({ fromQueue: true }, this.flags),
  332. };
  333. args.push((err, ...responseArgs) => {
  334. if (packet !== this._queue[0]) {
  335. // the packet has already been acknowledged
  336. return;
  337. }
  338. const hasError = err !== null;
  339. if (hasError) {
  340. if (packet.tryCount > this._opts.retries) {
  341. debug("packet [%d] is discarded after %d tries", packet.id, packet.tryCount);
  342. this._queue.shift();
  343. if (ack) {
  344. ack(err);
  345. }
  346. }
  347. }
  348. else {
  349. debug("packet [%d] was successfully sent", packet.id);
  350. this._queue.shift();
  351. if (ack) {
  352. ack(null, ...responseArgs);
  353. }
  354. }
  355. packet.pending = false;
  356. return this._drainQueue();
  357. });
  358. this._queue.push(packet);
  359. this._drainQueue();
  360. }
  361. /**
  362. * Send the first packet of the queue, and wait for an acknowledgement from the server.
  363. * @param force - whether to resend a packet that has not been acknowledged yet
  364. *
  365. * @private
  366. */
  367. _drainQueue(force = false) {
  368. debug("draining queue");
  369. if (!this.connected || this._queue.length === 0) {
  370. return;
  371. }
  372. const packet = this._queue[0];
  373. if (packet.pending && !force) {
  374. debug("packet [%d] has already been sent and is waiting for an ack", packet.id);
  375. return;
  376. }
  377. packet.pending = true;
  378. packet.tryCount++;
  379. debug("sending packet [%d] (try n°%d)", packet.id, packet.tryCount);
  380. this.flags = packet.flags;
  381. this.emit.apply(this, packet.args);
  382. }
  383. /**
  384. * Sends a packet.
  385. *
  386. * @param packet
  387. * @private
  388. */
  389. packet(packet) {
  390. packet.nsp = this.nsp;
  391. this.io._packet(packet);
  392. }
  393. /**
  394. * Called upon engine `open`.
  395. *
  396. * @private
  397. */
  398. onopen() {
  399. debug("transport is open - connecting");
  400. if (typeof this.auth == "function") {
  401. this.auth((data) => {
  402. this._sendConnectPacket(data);
  403. });
  404. }
  405. else {
  406. this._sendConnectPacket(this.auth);
  407. }
  408. }
  409. /**
  410. * Sends a CONNECT packet to initiate the Socket.IO session.
  411. *
  412. * @param data
  413. * @private
  414. */
  415. _sendConnectPacket(data) {
  416. this.packet({
  417. type: PacketType.CONNECT,
  418. data: this._pid
  419. ? Object.assign({ pid: this._pid, offset: this._lastOffset }, data)
  420. : data,
  421. });
  422. }
  423. /**
  424. * Called upon engine or manager `error`.
  425. *
  426. * @param err
  427. * @private
  428. */
  429. onerror(err) {
  430. if (!this.connected) {
  431. this.emitReserved("connect_error", err);
  432. }
  433. }
  434. /**
  435. * Called upon engine `close`.
  436. *
  437. * @param reason
  438. * @param description
  439. * @private
  440. */
  441. onclose(reason, description) {
  442. debug("close (%s)", reason);
  443. this.connected = false;
  444. delete this.id;
  445. this.emitReserved("disconnect", reason, description);
  446. }
  447. /**
  448. * Called with socket packet.
  449. *
  450. * @param packet
  451. * @private
  452. */
  453. onpacket(packet) {
  454. const sameNamespace = packet.nsp === this.nsp;
  455. if (!sameNamespace)
  456. return;
  457. switch (packet.type) {
  458. case PacketType.CONNECT:
  459. if (packet.data && packet.data.sid) {
  460. this.onconnect(packet.data.sid, packet.data.pid);
  461. }
  462. else {
  463. this.emitReserved("connect_error", new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));
  464. }
  465. break;
  466. case PacketType.EVENT:
  467. case PacketType.BINARY_EVENT:
  468. this.onevent(packet);
  469. break;
  470. case PacketType.ACK:
  471. case PacketType.BINARY_ACK:
  472. this.onack(packet);
  473. break;
  474. case PacketType.DISCONNECT:
  475. this.ondisconnect();
  476. break;
  477. case PacketType.CONNECT_ERROR:
  478. this.destroy();
  479. const err = new Error(packet.data.message);
  480. // @ts-ignore
  481. err.data = packet.data.data;
  482. this.emitReserved("connect_error", err);
  483. break;
  484. }
  485. }
  486. /**
  487. * Called upon a server event.
  488. *
  489. * @param packet
  490. * @private
  491. */
  492. onevent(packet) {
  493. const args = packet.data || [];
  494. debug("emitting event %j", args);
  495. if (null != packet.id) {
  496. debug("attaching ack callback to event");
  497. args.push(this.ack(packet.id));
  498. }
  499. if (this.connected) {
  500. this.emitEvent(args);
  501. }
  502. else {
  503. this.receiveBuffer.push(Object.freeze(args));
  504. }
  505. }
  506. emitEvent(args) {
  507. if (this._anyListeners && this._anyListeners.length) {
  508. const listeners = this._anyListeners.slice();
  509. for (const listener of listeners) {
  510. listener.apply(this, args);
  511. }
  512. }
  513. super.emit.apply(this, args);
  514. if (this._pid && args.length && typeof args[args.length - 1] === "string") {
  515. this._lastOffset = args[args.length - 1];
  516. }
  517. }
  518. /**
  519. * Produces an ack callback to emit with an event.
  520. *
  521. * @private
  522. */
  523. ack(id) {
  524. const self = this;
  525. let sent = false;
  526. return function (...args) {
  527. // prevent double callbacks
  528. if (sent)
  529. return;
  530. sent = true;
  531. debug("sending ack %j", args);
  532. self.packet({
  533. type: PacketType.ACK,
  534. id: id,
  535. data: args,
  536. });
  537. };
  538. }
  539. /**
  540. * Called upon a server acknowlegement.
  541. *
  542. * @param packet
  543. * @private
  544. */
  545. onack(packet) {
  546. const ack = this.acks[packet.id];
  547. if ("function" === typeof ack) {
  548. debug("calling ack %s with %j", packet.id, packet.data);
  549. ack.apply(this, packet.data);
  550. delete this.acks[packet.id];
  551. }
  552. else {
  553. debug("bad ack %s", packet.id);
  554. }
  555. }
  556. /**
  557. * Called upon server connect.
  558. *
  559. * @private
  560. */
  561. onconnect(id, pid) {
  562. debug("socket connected with id %s", id);
  563. this.id = id;
  564. this.recovered = pid && this._pid === pid;
  565. this._pid = pid; // defined only if connection state recovery is enabled
  566. this.connected = true;
  567. this.emitBuffered();
  568. this.emitReserved("connect");
  569. this._drainQueue(true);
  570. }
  571. /**
  572. * Emit buffered events (received and emitted).
  573. *
  574. * @private
  575. */
  576. emitBuffered() {
  577. this.receiveBuffer.forEach((args) => this.emitEvent(args));
  578. this.receiveBuffer = [];
  579. this.sendBuffer.forEach((packet) => {
  580. this.notifyOutgoingListeners(packet);
  581. this.packet(packet);
  582. });
  583. this.sendBuffer = [];
  584. }
  585. /**
  586. * Called upon server disconnect.
  587. *
  588. * @private
  589. */
  590. ondisconnect() {
  591. debug("server disconnect (%s)", this.nsp);
  592. this.destroy();
  593. this.onclose("io server disconnect");
  594. }
  595. /**
  596. * Called upon forced client/server side disconnections,
  597. * this method ensures the manager stops tracking us and
  598. * that reconnections don't get triggered for this.
  599. *
  600. * @private
  601. */
  602. destroy() {
  603. if (this.subs) {
  604. // clean subscriptions to avoid reconnections
  605. this.subs.forEach((subDestroy) => subDestroy());
  606. this.subs = undefined;
  607. }
  608. this.io["_destroy"](this);
  609. }
  610. /**
  611. * Disconnects the socket manually. In that case, the socket will not try to reconnect.
  612. *
  613. * If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.
  614. *
  615. * @example
  616. * const socket = io();
  617. *
  618. * socket.on("disconnect", (reason) => {
  619. * // console.log(reason); prints "io client disconnect"
  620. * });
  621. *
  622. * socket.disconnect();
  623. *
  624. * @return self
  625. */
  626. disconnect() {
  627. if (this.connected) {
  628. debug("performing disconnect (%s)", this.nsp);
  629. this.packet({ type: PacketType.DISCONNECT });
  630. }
  631. // remove socket from pool
  632. this.destroy();
  633. if (this.connected) {
  634. // fire events
  635. this.onclose("io client disconnect");
  636. }
  637. return this;
  638. }
  639. /**
  640. * Alias for {@link disconnect()}.
  641. *
  642. * @return self
  643. */
  644. close() {
  645. return this.disconnect();
  646. }
  647. /**
  648. * Sets the compress flag.
  649. *
  650. * @example
  651. * socket.compress(false).emit("hello");
  652. *
  653. * @param compress - if `true`, compresses the sending data
  654. * @return self
  655. */
  656. compress(compress) {
  657. this.flags.compress = compress;
  658. return this;
  659. }
  660. /**
  661. * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not
  662. * ready to send messages.
  663. *
  664. * @example
  665. * socket.volatile.emit("hello"); // the server may or may not receive it
  666. *
  667. * @returns self
  668. */
  669. get volatile() {
  670. this.flags.volatile = true;
  671. return this;
  672. }
  673. /**
  674. * Sets a modifier for a subsequent event emission that the callback will be called with an error when the
  675. * given number of milliseconds have elapsed without an acknowledgement from the server:
  676. *
  677. * @example
  678. * socket.timeout(5000).emit("my-event", (err) => {
  679. * if (err) {
  680. * // the server did not acknowledge the event in the given delay
  681. * }
  682. * });
  683. *
  684. * @returns self
  685. */
  686. timeout(timeout) {
  687. this.flags.timeout = timeout;
  688. return this;
  689. }
  690. /**
  691. * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
  692. * callback.
  693. *
  694. * @example
  695. * socket.onAny((event, ...args) => {
  696. * console.log(`got ${event}`);
  697. * });
  698. *
  699. * @param listener
  700. */
  701. onAny(listener) {
  702. this._anyListeners = this._anyListeners || [];
  703. this._anyListeners.push(listener);
  704. return this;
  705. }
  706. /**
  707. * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
  708. * callback. The listener is added to the beginning of the listeners array.
  709. *
  710. * @example
  711. * socket.prependAny((event, ...args) => {
  712. * console.log(`got event ${event}`);
  713. * });
  714. *
  715. * @param listener
  716. */
  717. prependAny(listener) {
  718. this._anyListeners = this._anyListeners || [];
  719. this._anyListeners.unshift(listener);
  720. return this;
  721. }
  722. /**
  723. * Removes the listener that will be fired when any event is emitted.
  724. *
  725. * @example
  726. * const catchAllListener = (event, ...args) => {
  727. * console.log(`got event ${event}`);
  728. * }
  729. *
  730. * socket.onAny(catchAllListener);
  731. *
  732. * // remove a specific listener
  733. * socket.offAny(catchAllListener);
  734. *
  735. * // or remove all listeners
  736. * socket.offAny();
  737. *
  738. * @param listener
  739. */
  740. offAny(listener) {
  741. if (!this._anyListeners) {
  742. return this;
  743. }
  744. if (listener) {
  745. const listeners = this._anyListeners;
  746. for (let i = 0; i < listeners.length; i++) {
  747. if (listener === listeners[i]) {
  748. listeners.splice(i, 1);
  749. return this;
  750. }
  751. }
  752. }
  753. else {
  754. this._anyListeners = [];
  755. }
  756. return this;
  757. }
  758. /**
  759. * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
  760. * e.g. to remove listeners.
  761. */
  762. listenersAny() {
  763. return this._anyListeners || [];
  764. }
  765. /**
  766. * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
  767. * callback.
  768. *
  769. * Note: acknowledgements sent to the server are not included.
  770. *
  771. * @example
  772. * socket.onAnyOutgoing((event, ...args) => {
  773. * console.log(`sent event ${event}`);
  774. * });
  775. *
  776. * @param listener
  777. */
  778. onAnyOutgoing(listener) {
  779. this._anyOutgoingListeners = this._anyOutgoingListeners || [];
  780. this._anyOutgoingListeners.push(listener);
  781. return this;
  782. }
  783. /**
  784. * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
  785. * callback. The listener is added to the beginning of the listeners array.
  786. *
  787. * Note: acknowledgements sent to the server are not included.
  788. *
  789. * @example
  790. * socket.prependAnyOutgoing((event, ...args) => {
  791. * console.log(`sent event ${event}`);
  792. * });
  793. *
  794. * @param listener
  795. */
  796. prependAnyOutgoing(listener) {
  797. this._anyOutgoingListeners = this._anyOutgoingListeners || [];
  798. this._anyOutgoingListeners.unshift(listener);
  799. return this;
  800. }
  801. /**
  802. * Removes the listener that will be fired when any event is emitted.
  803. *
  804. * @example
  805. * const catchAllListener = (event, ...args) => {
  806. * console.log(`sent event ${event}`);
  807. * }
  808. *
  809. * socket.onAnyOutgoing(catchAllListener);
  810. *
  811. * // remove a specific listener
  812. * socket.offAnyOutgoing(catchAllListener);
  813. *
  814. * // or remove all listeners
  815. * socket.offAnyOutgoing();
  816. *
  817. * @param [listener] - the catch-all listener (optional)
  818. */
  819. offAnyOutgoing(listener) {
  820. if (!this._anyOutgoingListeners) {
  821. return this;
  822. }
  823. if (listener) {
  824. const listeners = this._anyOutgoingListeners;
  825. for (let i = 0; i < listeners.length; i++) {
  826. if (listener === listeners[i]) {
  827. listeners.splice(i, 1);
  828. return this;
  829. }
  830. }
  831. }
  832. else {
  833. this._anyOutgoingListeners = [];
  834. }
  835. return this;
  836. }
  837. /**
  838. * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
  839. * e.g. to remove listeners.
  840. */
  841. listenersAnyOutgoing() {
  842. return this._anyOutgoingListeners || [];
  843. }
  844. /**
  845. * Notify the listeners for each packet sent
  846. *
  847. * @param packet
  848. *
  849. * @private
  850. */
  851. notifyOutgoingListeners(packet) {
  852. if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {
  853. const listeners = this._anyOutgoingListeners.slice();
  854. for (const listener of listeners) {
  855. listener.apply(this, packet.data);
  856. }
  857. }
  858. }
  859. }