From a844473c09d9b3d078bbbd8ca7eb83ebb6ec6e21 Mon Sep 17 00:00:00 2001 From: "otoneko." Date: Sat, 11 Jul 2026 01:42:37 +0900 Subject: [PATCH] fix(websocket): polyfill missing ErrorEvent global Node.js has no global ErrorEvent, so `new ErrorEvent(...)` threw a ReferenceError and silently swallowed the user's onError handler. --- src/websocket.ts | 43 ++++++++++++++++++++++++++++++++++++++++++ test/websocket.test.ts | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/websocket.ts b/src/websocket.ts index eff1103..814ea40 100644 --- a/src/websocket.ts +++ b/src/websocket.ts @@ -38,6 +38,49 @@ export const CloseEvent: typeof globalThis.CloseEvent = } } +interface ErrorEventInit extends EventInit { + message?: string + filename?: string + lineno?: number + colno?: number + error?: unknown +} + +/** + * Node.js has no global `ErrorEvent`, unlike `Event`/`MessageEvent`/`CloseEvent`. + * @link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent + */ +export const ErrorEvent: typeof globalThis.ErrorEvent = + globalThis.ErrorEvent ?? + class extends Event { + #eventInitDict + + constructor(type: string, eventInitDict: ErrorEventInit = {}) { + super(type, eventInitDict) + this.#eventInitDict = eventInitDict + } + + get message(): string { + return this.#eventInitDict.message ?? '' + } + + get filename(): string { + return this.#eventInitDict.filename ?? '' + } + + get lineno(): number { + return this.#eventInitDict.lineno ?? 0 + } + + get colno(): number { + return this.#eventInitDict.colno ?? 0 + } + + get error(): unknown { + return this.#eventInitDict.error ?? null + } + } + const generateConnectionSymbol = () => Symbol('connection') type WaitForWebSocket = ( diff --git a/test/websocket.test.ts b/test/websocket.test.ts index a6d3c75..05738d1 100644 --- a/test/websocket.test.ts +++ b/test/websocket.test.ts @@ -162,4 +162,44 @@ describe('WebSocket', () => { await new Promise((resolve) => server.close(() => resolve())) } }) + + it('should call onError with the underlying error when the raw socket errors', async () => { + const app = new Hono() + const receivedError = new Error('boom') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let rawWs: any + const onErrorCalled = new Promise((resolve) => { + app.get( + '/ws', + upgradeWebSocket(() => ({ + onOpen(_evt, ws) { + rawWs = ws.raw + }, + onError(evt) { + resolve(evt) + }, + })) + ) + }) + + const { server, address } = await startServer(app) + + try { + const ws = new WebSocket(`ws://127.0.0.1:${address.port}/ws`) + await new Promise((resolve, reject) => { + ws.once('open', resolve) + ws.once('error', reject) + }) + + rawWs.emit('error', receivedError) + + const evt = await onErrorCalled + expect(evt).toBeInstanceOf(Event) + expect((evt as ErrorEvent).error).toBe(receivedError) + + ws.close() + } finally { + await new Promise((resolve) => server.close(() => resolve())) + } + }) })