From 55f464f59ed523fa1c1948ec10752bfdf808262d Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 26 Oct 2020 00:07:53 +0100 Subject: [PATCH] feat: add support for catch-all listeners Inspired from EventEmitter2 [1] The API is similar to the one on the server-side: ```js socket.onAny((event, ...args) => {}); socket.prependAny((event, ...args) => {}); socket.offAny(); // remove all listeners socket.offAny(listener); const listeners = socket.listenersAny(); ``` [1]: https://github.com/EventEmitter2/EventEmitter2 --- lib/socket.ts | 75 ++++++++++++++++++++++++++++++++++++++++++++++++-- test/socket.ts | 47 +++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/lib/socket.ts b/lib/socket.ts index f2a181369..fe9b3370d 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -49,6 +49,7 @@ export class Socket extends Emitter { private sendBuffer: Array = []; private flags: Flags = {}; private subs: Array; + private _anyListeners: Array<(...args: any[]) => void>; /** * `Socket` constructor. @@ -272,12 +273,22 @@ export class Socket extends Emitter { } if (this.connected) { - super.emit.apply(this, args); + this.emitEvent(args); } else { this.receiveBuffer.push(args); } } + private emitEvent(args) { + if (this._anyListeners && this._anyListeners.length) { + const listeners = this._anyListeners.slice(); + for (const listener of listeners) { + listener.apply(this, args); + } + } + super.emit.apply(this, args); + } + /** * Produces an ack callback to emit with an event. * @@ -337,7 +348,7 @@ export class Socket extends Emitter { */ private emitBuffered() { for (let i = 0; i < this.receiveBuffer.length; i++) { - super.emit.apply(this, this.receiveBuffer[i]); + this.emitEvent(this.receiveBuffer[i]); } this.receiveBuffer = []; @@ -432,4 +443,64 @@ export class Socket extends Emitter { this.flags.volatile = true; 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. + * + * @param listener + * @public + */ + public onAny(listener: (...args: any[]) => void): Socket { + this._anyListeners = this._anyListeners || []; + this._anyListeners.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 + * @public + */ + public prependAny(listener: (...args: any[]) => void): Socket { + this._anyListeners = this._anyListeners || []; + this._anyListeners.unshift(listener); + return this; + } + + /** + * Removes the listener that will be fired when any event is emitted. + * + * @param listener + * @public + */ + public offAny(listener?: (...args: any[]) => void): Socket { + if (!this._anyListeners) { + return this; + } + if (listener) { + const listeners = this._anyListeners; + for (let i = 0; i < listeners.length; i++) { + if (listener === listeners[i]) { + listeners.splice(i, 1); + return this; + } + } + } else { + this._anyListeners = []; + } + 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 listenersAny() { + return this._anyListeners || []; + } } diff --git a/test/socket.ts b/test/socket.ts index d03c9d8b1..dd57c0f93 100644 --- a/test/socket.ts +++ b/test/socket.ts @@ -234,4 +234,51 @@ describe("socket", function () { }, 200); }); }); + + describe("onAny", () => { + it("should call listener", (done) => { + const socket = io("/abc"); + + socket.onAny((event, arg1) => { + expect(event).to.be("handshake"); + expect(arg1).to.be.an(Object); + done(); + }); + }); + + it("should prepend listener", (done) => { + const socket = io("/abc"); + + let count = 0; + + socket.onAny((event, arg1) => { + expect(count).to.be(2); + done(); + }); + + socket.prependAny(() => { + expect(count++).to.be(1); + }); + + socket.prependAny(() => { + expect(count++).to.be(0); + }); + }); + + it("should remove listener", (done) => { + const socket = io("/abc"); + + let count = 0; + + const fail = () => done(new Error("fail")); + + socket.onAny(fail); + socket.offAny(fail); + expect(socket.listenersAny.length).to.be(0); + + socket.onAny(() => { + done(); + }); + }); + }); });