From 54bf4a44e9e896dfb64764ee7bd4e8823eb7dc7b Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Fri, 30 Oct 2020 22:41:50 +0100 Subject: [PATCH] feat: emit an Error object upon middleware error This commit restores the ability to send additional data in the middleware functions, which was removed during the rewrite to Typescript ([1]). The only difference with the previous implementation is that the client will now emit a "connect_error" (previously, "error") event with an actual Error object, with both the message and an optional "data" attribute. ```js // server-side io.use((socket, next) => { const err = new Error("not authorized"); err.data = { content: "Please retry later" }; next(err); }); // client-side socket.on("connect_error", err => { console.log(err.message); // not authorized console.log(err.data.content); // Please retry later }); ``` [1]: https://github.com/socketio/socket.io/commit/a5581a978979ff2c2c53dacdc931e7025c5d6adb --- lib/client.ts | 4 +++- lib/index.ts | 4 ++-- lib/namespace.ts | 18 ++++++++++++++---- test/socket.io.ts | 29 ++++++++++++++++++++++++++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index cfb346130e..a730425eb6 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -93,7 +93,9 @@ export class Client { this._packet({ type: PacketType.CONNECT_ERROR, nsp: name, - data: "Invalid namespace" + data: { + message: "Invalid namespace" + } }); } }); diff --git a/lib/index.ts b/lib/index.ts index 97f702717a..981fef9ade 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -7,7 +7,7 @@ import path from "path"; import engine from "engine.io"; import { Client } from "./client"; import { EventEmitter } from "events"; -import { Namespace } from "./namespace"; +import { ExtendedError, Namespace } from "./namespace"; import { ParentNamespace } from "./parent-namespace"; import { Adapter, Room, SocketId } from "socket.io-adapter"; import * as parser from "socket.io-parser"; @@ -593,7 +593,7 @@ export class Server extends EventEmitter { * @public */ public use( - fn: (socket: Socket, next: (err?: Error) => void) => void + fn: (socket: Socket, next: (err?: ExtendedError) => void) => void ): Server { this.sockets.use(fn); return this; diff --git a/lib/namespace.ts b/lib/namespace.ts index feeaa15c33..e85c0cd019 100644 --- a/lib/namespace.ts +++ b/lib/namespace.ts @@ -8,6 +8,10 @@ import { Adapter, Room, SocketId } from "socket.io-adapter"; const debug = debugModule("socket.io:namespace"); +export interface ExtendedError extends Error { + data?: any; +} + export class Namespace extends EventEmitter { public readonly name: string; public readonly sockets: Map = new Map(); @@ -18,7 +22,9 @@ export class Namespace extends EventEmitter { readonly server: Server; /** @private */ - _fns: Array<(socket: Socket, next: (err: Error) => void) => void> = []; + _fns: Array< + (socket: Socket, next: (err: ExtendedError) => void) => void + > = []; /** @private */ _rooms: Set = new Set(); @@ -60,7 +66,7 @@ export class Namespace extends EventEmitter { * @public */ public use( - fn: (socket: Socket, next: (err?: Error) => void) => void + fn: (socket: Socket, next: (err?: ExtendedError) => void) => void ): Namespace { this._fns.push(fn); return this; @@ -73,7 +79,7 @@ export class Namespace extends EventEmitter { * @param {Function} fn - last fn call in the middleware * @private */ - private run(socket: Socket, fn: (err: Error) => void) { + private run(socket: Socket, fn: (err: ExtendedError) => void) { const fns = this._fns.slice(0); if (!fns.length) return fn(null); @@ -129,7 +135,11 @@ export class Namespace extends EventEmitter { this.run(socket, err => { process.nextTick(() => { if ("open" == client.conn.readyState) { - if (err) return socket._error(err.message); + if (err) + return socket._error({ + message: err.message, + data: err.data + }); // track socket this.sockets.set(socket.id, socket); diff --git a/test/socket.io.ts b/test/socket.io.ts index bc7377bce9..c6a44b2f96 100644 --- a/test/socket.io.ts +++ b/test/socket.io.ts @@ -522,7 +522,7 @@ describe("socket.io", () => { srv.listen(() => { const socket = client(srv, "/doesnotexist"); socket.on("connect_error", err => { - expect(err).to.be("Invalid namespace"); + expect(err.message).to.be("Invalid namespace"); done(); }); }); @@ -814,7 +814,7 @@ describe("socket.io", () => { sio.of(/^\/dynamic-\d+$/); sio.of((name, query, next) => next(null, "/dynamic-101" === name)); socket.on("connect_error", err => { - expect(err).to.be("Invalid namespace"); + expect(err.message).to.be("Invalid namespace"); done(); }); }); @@ -2185,7 +2185,30 @@ describe("socket.io", () => { done(new Error("nope")); }); socket.on("connect_error", err => { - expect(err).to.be("Authentication error"); + expect(err.message).to.be("Authentication error"); + done(); + }); + }); + }); + + it("should pass an object", done => { + const srv = createServer(); + const sio = new Server(srv); + sio.use((socket, next) => { + const err = new Error("Authentication error"); + // @ts-ignore + err.data = { a: "b", c: 3 }; + next(err); + }); + srv.listen(() => { + const socket = client(srv); + socket.on("connect", () => { + done(new Error("nope")); + }); + socket.on("connect_error", err => { + expect(err).to.be.an(Error); + expect(err.message).to.eql("Authentication error"); + expect(err.data).to.eql({ a: "b", c: 3 }); done(); }); });