From aabfe336f461824d0448bdb07d26225027f91b80 Mon Sep 17 00:00:00 2001 From: nabsei Date: Wed, 15 Jul 2026 01:58:42 +0200 Subject: [PATCH] fix(eio): expose EventEmitter interface on WebSocket upgrade response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WebSocketResponse (the fake response object passed to middlewares during a WebSocket upgrade) did not implement EventEmitter, so any middleware calling res.on(...) — e.g. pino-http listening for "close" to know when the request finished — crashed with "res.on is not a function". Make WebSocketResponse extend EventEmitter and emit "close" when the underlying socket closes, and "finish" when end() is called, mirroring the events a real http.ServerResponse emits. Fixes #5072 Co-authored-by: Claude --- packages/engine.io/lib/server.ts | 7 ++++++- packages/engine.io/test/middlewares.js | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/engine.io/lib/server.ts b/packages/engine.io/lib/server.ts index c4f2c30d4c..293c741974 100644 --- a/packages/engine.io/lib/server.ts +++ b/packages/engine.io/lib/server.ts @@ -669,13 +669,17 @@ export abstract class BaseServer extends EventEmitter { * * @see https://nodejs.org/api/http.html#class-httpserverresponse */ -class WebSocketResponse { +class WebSocketResponse extends EventEmitter { constructor( readonly req, readonly socket: Duplex, ) { + super(); // temporarily store the response headers on the req object (see the "headers" event) req[kResponseHeaders] = {}; + // some middlewares (like pino-http) rely on the "close" event to know when the response is done, which the + // underlying socket does not expose by default + socket.once("close", () => this.emit("close")); } public setHeader(name: string, value: any) { @@ -695,6 +699,7 @@ class WebSocketResponse { public writeHead() {} public end() { + this.emit("finish"); // we could return a proper error code, but the WebSocket client will emit an "error" event anyway. this.socket.destroy(); } diff --git a/packages/engine.io/test/middlewares.js b/packages/engine.io/test/middlewares.js index 60e1cc6b22..6e97711999 100644 --- a/packages/engine.io/test/middlewares.js +++ b/packages/engine.io/test/middlewares.js @@ -56,6 +56,29 @@ describe("middlewares", () => { }); }); + it("should expose EventEmitter methods on the response object during upgrade (regression for pino-http)", (done) => { + const engine = listen((port) => { + engine.use((req, res, next) => { + expect(res.on).to.be.a("function"); + res.on("close", () => { + if (engine.httpServer) { + engine.httpServer.close(); + } + done(); + }); + next(); + }); + + const socket = new WebSocket( + `ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`, + ); + + socket.on("open", () => { + socket.close(); + }); + }); + }); + it("should apply all middlewares in order", (done) => { const engine = listen((port) => { let count = 0;