From bf5432742158e4d5ba2722cff4a614967dffa5b9 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 5 Jan 2021 11:31:46 +0100 Subject: [PATCH] revert: restore the socket middleware functionality This functionality was removed in [1] (included in 3.0.0), but catch-all listeners and socket middleware features are complementary rather than mutually exclusive. The only difference with the previous implementation is that passing an error to the `next` handler will create an error on the server-side, and not on the client-side anymore. ```js io.on("connection", (socket) => { socket.use(([ event, ...args ], next) => { next(new Error("stop")); }); socket.on("error", (err) => { // to restore the previous behavior socket.emit("error", err); // or close the connection, depending on your use case socket.disconnect(true); }); }); ``` This creates additional possibilities about custom error handlers, which may be implemented in the future. ```js // user-defined error handler socket.use((err, [ event ], next) => { // either handle it socket.disconnect(); // or forward the error to the default error handler next(err); }); // default error handler socket.use((err, _, next) => { socket.emit("error", err); }); ``` Related: https://github.com/socketio/socket.io/issues/3678 [1]: https://github.com/socketio/socket.io/commit/5c737339858d59eab4b5ee2dd6feff0e82c4fe5a --- lib/socket.ts | 61 +++++++++++++++++++++++++++++++++++++++- test/socket.io.ts | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/lib/socket.ts b/lib/socket.ts index f7c14cff89..0ce963bd87 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -344,7 +344,7 @@ export class Socket extends EventEmitter { listener.apply(this, args); } } - super.emit.apply(this, args); + this.dispatch(args); } /** @@ -511,6 +511,65 @@ export class Socket extends EventEmitter { return this; } + /** + * Dispatch incoming event to socket listeners. + * + * @param {Array} event - event that will get emitted + * @private + */ + private dispatch(event): void { + debug("dispatching an event %j", event); + this.run(event, (err) => { + process.nextTick(() => { + if (err) { + return this._onerror(err); + } + super.emit.apply(this, event); + }); + }); + } + + /** + * Sets up socket middleware. + * + * @param {Function} fn - middleware function (event, next) + * @return {Socket} self + * @public + */ + public use( + fn: (event: Array, next: (err: Error) => void) => void + ): Socket { + this.fns.push(fn); + return this; + } + + /** + * Executes the middleware for an incoming event. + * + * @param {Array} event - event that will get emitted + * @param {Function} fn - last fn call in the middleware + * @private + */ + private run(event: Array, fn: (err: Error | null) => void) { + const fns = this.fns.slice(0); + if (!fns.length) return fn(null); + + function run(i) { + fns[i](event, function (err) { + // upon error, short-circuit + if (err) return fn(err); + + // if no middleware left, summon callback + if (!fns[i + 1]) return fn(null); + + // go on to next + run(i + 1); + }); + } + + run(0); + } + /** * A reference to the request that originated the underlying Engine.IO Socket. * diff --git a/test/socket.io.ts b/test/socket.io.ts index 163fb47c4e..ba05df2aca 100644 --- a/test/socket.io.ts +++ b/test/socket.io.ts @@ -12,6 +12,7 @@ import type { AddressInfo } from "net"; const ioc = require("socket.io-client"); import "./support/util"; +import exp = require("constants"); // Creates a socket.io client for the given server function client(srv, nsp?: string | object, opts?: object) { @@ -2359,4 +2360,74 @@ describe("socket.io", () => { }); }); }); + + describe("socket middleware", () => { + const { Socket } = require("../dist/socket"); + + it("should call functions", (done) => { + const srv = createServer(); + const sio = new Server(srv); + let run = 0; + + srv.listen(() => { + const socket = client(srv, { multiplex: false }); + + socket.emit("join", "woot"); + + sio.on("connection", (socket) => { + socket.use((event, next) => { + expect(event).to.eql(["join", "woot"]); + event.unshift("wrap"); + run++; + next(); + }); + socket.use((event, next) => { + expect(event).to.eql(["wrap", "join", "woot"]); + run++; + next(); + }); + socket.on("wrap", (data1, data2) => { + expect(data1).to.be("join"); + expect(data2).to.be("woot"); + expect(run).to.be(2); + done(); + }); + }); + }); + }); + + it("should pass errors", (done) => { + const srv = createServer(); + const sio = new Server(srv); + + srv.listen(() => { + const socket = client(srv, { multiplex: false }); + + socket.emit("join", "woot"); + + const success = () => { + socket.close(); + sio.close(); + done(); + }; + + sio.on("connection", (socket) => { + socket.use((event, next) => { + next(new Error("Authentication error")); + }); + socket.use((event, next) => { + done(new Error("should not happen")); + }); + socket.on("join", () => { + done(new Error("should not happen")); + }); + socket.on("error", (err) => { + expect(err).to.be.an(Error); + expect(err.message).to.eql("Authentication error"); + success(); + }); + }); + }); + }); + }); });