Skip to content

Commit 5c73733

Browse files
feat: add support for catch-all listeners
Inspired from EventEmitter2 [1] ```js io.on("connect", socket => { socket.onAny((event, ...args) => {}); socket.prependAny((event, ...args) => {}); socket.offAny(); // remove all listeners socket.offAny(listener); const listeners = socket.listenersAny(); }); ``` Breaking change: the socket.use() method is removed This method was introduced in [2] for the same feature (having a catch-all listener), but there were two issues: - the API is not very user-friendly, since the user has to know the structure of the packet argument - it uses an ERROR packet, which is reserved for Namespace authentication issues (see [3]) [1]: https://github.com/EventEmitter2/EventEmitter2 [2]: #434 [3]: https://github.com/socketio/socket.io-protocol
1 parent 129c641 commit 5c73733

File tree

2 files changed

+132
-117
lines changed

2 files changed

+132
-117
lines changed

lib/socket.ts

+60-52
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class Socket extends EventEmitter {
8686
> = [];
8787
private flags: BroadcastFlags = {};
8888
private _rooms: Set<Room> = new Set();
89+
private _anyListeners: Array<(...args: any[]) => void>;
8990

9091
/**
9192
* Interface to a `Client` for a given `Namespace`.
@@ -335,7 +336,13 @@ export class Socket extends EventEmitter {
335336
args.push(this.ack(packet.id));
336337
}
337338

338-
this.dispatch(args);
339+
if (this._anyListeners && this._anyListeners.length) {
340+
const listeners = this._anyListeners.slice();
341+
for (const listener of listeners) {
342+
listener.apply(this, args);
343+
}
344+
}
345+
super.emit.apply(this, args);
339346
}
340347

341348
/**
@@ -502,86 +509,87 @@ export class Socket extends EventEmitter {
502509
}
503510

504511
/**
505-
* Dispatch incoming event to socket listeners.
512+
* A reference to the request that originated the underlying Engine.IO Socket.
506513
*
507-
* @param {Array} event - event that will get emitted
508-
* @private
514+
* @public
509515
*/
510-
private dispatch(event: Array<string>): void {
511-
debug("dispatching an event %j", event);
512-
this.run(event, err => {
513-
process.nextTick(() => {
514-
if (err) {
515-
return this._error(err.message);
516-
}
517-
super.emit.apply(this, event);
518-
});
519-
});
516+
public get request(): IncomingMessage {
517+
return this.client.request;
520518
}
521519

522520
/**
523-
* Sets up socket middleware.
521+
* A reference to the underlying Client transport connection (Engine.IO Socket object).
524522
*
525-
* @param {Function} fn - middleware function (event, next)
526-
* @return {Socket} self
527523
* @public
528524
*/
529-
public use(
530-
fn: (event: Array<any>, next: (err: Error) => void) => void
531-
): Socket {
532-
this.fns.push(fn);
533-
return this;
525+
public get conn() {
526+
return this.client.conn;
534527
}
535528

536529
/**
537-
* Executes the middleware for an incoming event.
538-
*
539-
* @param {Array} event - event that will get emitted
540-
* @param {Function} fn - last fn call in the middleware
541-
* @private
530+
* @public
542531
*/
543-
private run(event: Array<any>, fn: (err: Error) => void) {
544-
const fns = this.fns.slice(0);
545-
if (!fns.length) return fn(null);
546-
547-
function run(i) {
548-
fns[i](event, function(err) {
549-
// upon error, short-circuit
550-
if (err) return fn(err);
551-
552-
// if no middleware left, summon callback
553-
if (!fns[i + 1]) return fn(null);
554-
555-
// go on to next
556-
run(i + 1);
557-
});
558-
}
532+
public get rooms(): Set<Room> {
533+
return this.adapter.socketRooms(this.id) || new Set();
534+
}
559535

560-
run(0);
536+
/**
537+
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
538+
* callback.
539+
*
540+
* @param listener
541+
* @public
542+
*/
543+
public onAny(listener: (...args: any[]) => void): Socket {
544+
this._anyListeners = this._anyListeners || [];
545+
this._anyListeners.push(listener);
546+
return this;
561547
}
562548

563549
/**
564-
* A reference to the request that originated the underlying Engine.IO Socket.
550+
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
551+
* callback. The listener is added to the beginning of the listeners array.
565552
*
553+
* @param listener
566554
* @public
567555
*/
568-
public get request(): IncomingMessage {
569-
return this.client.request;
556+
public prependAny(listener: (...args: any[]) => void): Socket {
557+
this._anyListeners = this._anyListeners || [];
558+
this._anyListeners.unshift(listener);
559+
return this;
570560
}
571561

572562
/**
573-
* A reference to the underlying Client transport connection (Engine.IO Socket object).
563+
* Removes the listener that will be fired when any event is emitted.
574564
*
565+
* @param listener
575566
* @public
576567
*/
577-
public get conn() {
578-
return this.client.conn;
568+
public offAny(listener?: (...args: any[]) => void): Socket {
569+
if (!this._anyListeners) {
570+
return this;
571+
}
572+
if (listener) {
573+
const listeners = this._anyListeners;
574+
for (let i = 0; i < listeners.length; i++) {
575+
if (listener === listeners[i]) {
576+
listeners.splice(i, 1);
577+
return this;
578+
}
579+
}
580+
} else {
581+
this._anyListeners = [];
582+
}
583+
return this;
579584
}
580585

581586
/**
587+
* Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
588+
* e.g. to remove listeners.
589+
*
582590
* @public
583591
*/
584-
public get rooms(): Set<Room> {
585-
return this.adapter.socketRooms(this.id) || new Set();
592+
public listenersAny() {
593+
return this._anyListeners || [];
586594
}
587595
}

test/socket.io.ts

+72-65
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,78 @@ describe("socket.io", () => {
17191719
});
17201720
});
17211721
});
1722+
1723+
describe("onAny", () => {
1724+
it("should call listener", done => {
1725+
const srv = createServer();
1726+
const sio = new Server(srv);
1727+
1728+
srv.listen(() => {
1729+
const socket = client(srv, { multiplex: false });
1730+
1731+
socket.emit("my-event", "123");
1732+
1733+
sio.on("connection", (socket: Socket) => {
1734+
socket.onAny((event, arg1) => {
1735+
expect(event).to.be("my-event");
1736+
expect(arg1).to.be("123");
1737+
done();
1738+
});
1739+
});
1740+
});
1741+
});
1742+
1743+
it("should prepend listener", done => {
1744+
const srv = createServer();
1745+
const sio = new Server(srv);
1746+
1747+
srv.listen(() => {
1748+
const socket = client(srv, { multiplex: false });
1749+
1750+
socket.emit("my-event", "123");
1751+
1752+
sio.on("connection", (socket: Socket) => {
1753+
let count = 0;
1754+
1755+
socket.onAny((event, arg1) => {
1756+
expect(count).to.be(2);
1757+
done();
1758+
});
1759+
1760+
socket.prependAny(() => {
1761+
expect(count++).to.be(1);
1762+
});
1763+
1764+
socket.prependAny(() => {
1765+
expect(count++).to.be(0);
1766+
});
1767+
});
1768+
});
1769+
});
1770+
1771+
it("should remove listener", done => {
1772+
const srv = createServer();
1773+
const sio = new Server(srv);
1774+
1775+
srv.listen(() => {
1776+
const socket = client(srv, { multiplex: false });
1777+
1778+
socket.emit("my-event", "123");
1779+
1780+
sio.on("connection", (socket: Socket) => {
1781+
const fail = () => done(new Error("fail"));
1782+
1783+
socket.onAny(fail);
1784+
socket.offAny(fail);
1785+
expect(socket.listenersAny.length).to.be(0);
1786+
1787+
socket.onAny(() => {
1788+
done();
1789+
});
1790+
});
1791+
});
1792+
});
1793+
});
17221794
});
17231795

17241796
describe("messaging many", () => {
@@ -2226,69 +2298,4 @@ describe("socket.io", () => {
22262298
});
22272299
});
22282300
});
2229-
2230-
describe("socket middleware", () => {
2231-
const { Socket } = require("../dist/socket");
2232-
2233-
it("should call functions", done => {
2234-
const srv = createServer();
2235-
const sio = new Server(srv);
2236-
let run = 0;
2237-
2238-
srv.listen(() => {
2239-
const socket = client(srv, { multiplex: false });
2240-
2241-
socket.emit("join", "woot");
2242-
2243-
sio.on("connection", socket => {
2244-
socket.use((event, next) => {
2245-
expect(event).to.eql(["join", "woot"]);
2246-
event.unshift("wrap");
2247-
run++;
2248-
next();
2249-
});
2250-
socket.use((event, next) => {
2251-
expect(event).to.eql(["wrap", "join", "woot"]);
2252-
run++;
2253-
next();
2254-
});
2255-
socket.on("wrap", (data1, data2) => {
2256-
expect(data1).to.be("join");
2257-
expect(data2).to.be("woot");
2258-
expect(run).to.be(2);
2259-
done();
2260-
});
2261-
});
2262-
});
2263-
});
2264-
2265-
it("should pass errors", done => {
2266-
const srv = createServer();
2267-
const sio = new Server(srv);
2268-
2269-
srv.listen(() => {
2270-
const clientSocket = client(srv, { multiplex: false });
2271-
2272-
clientSocket.emit("join", "woot");
2273-
2274-
clientSocket.on("error", err => {
2275-
expect(err).to.be("Authentication error");
2276-
done();
2277-
});
2278-
2279-
sio.on("connection", socket => {
2280-
socket.use((event, next) => {
2281-
next(new Error("Authentication error"));
2282-
});
2283-
socket.use((event, next) => {
2284-
done(new Error("nope"));
2285-
});
2286-
2287-
socket.on("join", () => {
2288-
done(new Error("nope"));
2289-
});
2290-
});
2291-
});
2292-
});
2293-
});
22942301
});

0 commit comments

Comments
 (0)