From 74e3e601a43133b2c0ea43c3de2764cc55b57b5a Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Sat, 23 Apr 2022 23:57:03 +0200 Subject: [PATCH] feat: add support for catch-all listeners for outgoing packets This is similar to `onAny()`, but for outgoing packets. Syntax: ```js socket.onAnyOutgoing((event, ...args) => { console.log(event); }); ``` Related: https://github.com/socketio/socket.io/commit/531104d332690138b7aab84d5583d6204132c8b4 --- lib/socket.ts | 115 ++++++++++++++++++++++++++++++++++++++++++++++++- test/socket.ts | 54 +++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/lib/socket.ts b/lib/socket.ts index 4ac58863..b174759e 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -76,6 +76,7 @@ export class Socket< private flags: Flags = {}; private subs?: Array; private _anyListeners: Array<(...args: any[]) => void>; + private _anyOutgoingListeners: Array<(...args: any[]) => void>; /** * `Socket` constructor. @@ -193,6 +194,7 @@ export class Socket< if (discardPacket) { debug("discard packet as the transport is not currently writable"); } else if (this.connected) { + this.notifyOutgoingListeners(packet); this.packet(packet); } else { this.sendBuffer.push(packet); @@ -440,7 +442,10 @@ export class Socket< this.receiveBuffer.forEach((args) => this.emitEvent(args)); this.receiveBuffer = []; - this.sendBuffer.forEach((packet) => this.packet(packet)); + this.sendBuffer.forEach((packet) => { + this.notifyOutgoingListeners(packet); + this.packet(packet); + }); this.sendBuffer = []; } @@ -606,6 +611,114 @@ export class Socket< public listenersAny() { return this._anyListeners || []; } + + /** + * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the + * callback. + * + * @param listener + * + *

+   *
+   * socket.onAnyOutgoing((event, ...args) => {
+   *   console.log(event);
+   * });
+   *
+   * 
+ * + * @public + */ + public onAnyOutgoing(listener: (...args: any[]) => void): this { + this._anyOutgoingListeners = this._anyOutgoingListeners || []; + this._anyOutgoingListeners.push(listener); + return this; + } + + /** + * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the + * callback. The listener is added to the beginning of the listeners array. + * + * @param listener + * + *

+   *
+   * socket.prependAnyOutgoing((event, ...args) => {
+   *   console.log(event);
+   * });
+   *
+   * 
+ * + * @public + */ + public prependAnyOutgoing(listener: (...args: any[]) => void): this { + this._anyOutgoingListeners = this._anyOutgoingListeners || []; + this._anyOutgoingListeners.unshift(listener); + return this; + } + + /** + * Removes the listener that will be fired when any event is emitted. + * + * @param listener + * + *

+   *
+   * const handler = (event, ...args) => {
+   *   console.log(event);
+   * }
+   *
+   * socket.onAnyOutgoing(handler);
+   *
+   * // then later
+   * socket.offAnyOutgoing(handler);
+   *
+   * 
+ * + * @public + */ + public offAnyOutgoing(listener?: (...args: any[]) => void): this { + if (!this._anyOutgoingListeners) { + return this; + } + if (listener) { + const listeners = this._anyOutgoingListeners; + for (let i = 0; i < listeners.length; i++) { + if (listener === listeners[i]) { + listeners.splice(i, 1); + return this; + } + } + } else { + this._anyOutgoingListeners = []; + } + return this; + } + + /** + * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated, + * e.g. to remove listeners. + * + * @public + */ + public listenersAnyOutgoing() { + return this._anyOutgoingListeners || []; + } + + /** + * Notify the listeners for each packet sent + * + * @param packet + * + * @private + */ + private notifyOutgoingListeners(packet: Packet) { + if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) { + const listeners = this._anyOutgoingListeners.slice(); + for (const listener of listeners) { + listener.apply(this, packet.data); + } + } + } } export namespace Socket { diff --git a/test/socket.ts b/test/socket.ts index 5092b32f..e2a10a20 100644 --- a/test/socket.ts +++ b/test/socket.ts @@ -369,6 +369,60 @@ describe("socket", function () { }); }); + describe("onAnyOutgoing", () => { + it("should call listener", (done) => { + const socket = io("/abc"); + + socket.on("connect", () => { + socket.onAnyOutgoing((event, arg1) => { + expect(event).to.be("my-event"); + expect(arg1).to.be("123"); + + success(done, socket); + }); + + socket.emit("my-event", "123"); + }); + }); + + it("should prepend listener", (done) => { + const socket = io("/abc"); + + let count = 0; + + socket.onAnyOutgoing(() => { + expect(count).to.be(2); + + success(done, socket); + }); + + socket.prependAnyOutgoing(() => { + expect(count++).to.be(1); + }); + + socket.prependAnyOutgoing(() => { + expect(count++).to.be(0); + }); + + socket.emit("my-event", "123"); + }); + + it("should remove listener", (done) => { + const socket = io("/abc"); + const fail = () => done(new Error("fail")); + + socket.onAnyOutgoing(fail); + socket.offAnyOutgoing(fail); + expect(socket.listenersAnyOutgoing.length).to.be(0); + + socket.onAnyOutgoing(() => { + success(done, socket); + }); + + socket.emit("my-event", "123"); + }); + }); + describe("timeout", () => { it("should timeout after the given delay when socket is not connected", (done) => { const socket = io("/", {