From 966ee7ba663bc2ad6ffd11021e43764398b71226 Mon Sep 17 00:00:00 2001 From: Khafra Date: Wed, 11 Jun 2025 18:52:43 -0400 Subject: [PATCH] add cause to WebSocket error --- lib/web/websocket/connection.js | 7 ++++--- lib/web/websocket/websocket.js | 6 +++--- test/types/websocket.test-d.ts | 6 +++++- test/websocket/issue-4273.js | 17 +++++++++++++++++ types/websocket.d.ts | 2 +- 5 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 test/websocket/issue-4273.js diff --git a/lib/web/websocket/connection.js b/lib/web/websocket/connection.js index 0d0c5801a3e..2e34bea1e04 100644 --- a/lib/web/websocket/connection.js +++ b/lib/web/websocket/connection.js @@ -105,7 +105,7 @@ function establishWebSocketConnection (url, protocols, client, handler, options) // 1. If response is a network error or its status is not 101, // fail the WebSocket connection. if (response.type === 'error' || response.status !== 101) { - failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.') + failWebsocketConnection(handler, 1002, 'Received network error or non-101 status code.', response.error) return } @@ -298,9 +298,10 @@ function closeWebSocketConnection (object, code, reason, validate = false) { * @param {import('./websocket').Handler} handler * @param {number} code * @param {string|undefined} reason + * @param {unknown} cause * @returns {void} */ -function failWebsocketConnection (handler, code, reason) { +function failWebsocketConnection (handler, code, reason, cause) { // If _The WebSocket Connection is Established_ prior to the point where // the endpoint is required to _Fail the WebSocket Connection_, the // endpoint SHOULD send a Close frame with an appropriate status code @@ -315,7 +316,7 @@ function failWebsocketConnection (handler, code, reason) { handler.socket.destroy() } - handler.onFail(code, reason) + handler.onFail(code, reason, cause) } module.exports = { diff --git a/lib/web/websocket/websocket.js b/lib/web/websocket/websocket.js index f1397200c86..c2915933408 100644 --- a/lib/web/websocket/websocket.js +++ b/lib/web/websocket/websocket.js @@ -60,7 +60,7 @@ class WebSocket extends EventTarget { /** @type {Handler} */ #handler = { onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions), - onFail: (code, reason) => this.#onFail(code, reason), + onFail: (code, reason, cause) => this.#onFail(code, reason, cause), onMessage: (opcode, data) => this.#onMessage(opcode, data), onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message), onParserDrain: () => this.#onParserDrain(), @@ -462,11 +462,11 @@ class WebSocket extends EventTarget { fireEvent('open', this) } - #onFail (code, reason) { + #onFail (code, reason, cause) { if (reason) { // TODO: process.nextTick fireEvent('error', this, (type, init) => new ErrorEvent(type, init), { - error: new Error(reason), + error: new Error(reason, cause ? { cause } : undefined), message: reason }) } diff --git a/test/types/websocket.test-d.ts b/test/types/websocket.test-d.ts index 00a22f4e351..5db3d20fc54 100644 --- a/test/types/websocket.test-d.ts +++ b/test/types/websocket.test-d.ts @@ -1,10 +1,14 @@ import { ReadableStream, WritableStream } from 'stream/web' import { expectType } from 'tsd' -import { WebSocketStream } from '../../types' +import { WebSocketStream, ErrorEvent } from '../../types' declare const webSocketStream: WebSocketStream const webSocketStreamOpened = await webSocketStream.opened +declare const errorEvent: ErrorEvent + // Test that the readable and writable streams are of identical types to ones from stream/web expectType(webSocketStreamOpened.writable) expectType(webSocketStreamOpened.readable) + +expectType(errorEvent.error) diff --git a/test/websocket/issue-4273.js b/test/websocket/issue-4273.js new file mode 100644 index 00000000000..e0e0543c8bb --- /dev/null +++ b/test/websocket/issue-4273.js @@ -0,0 +1,17 @@ +'use strict' + +const { test } = require('node:test') +const { WebSocket } = require('../..') +const { tspl } = require('@matteo.collina/tspl') + +test('first error than close event is fired on failed connection', async (t) => { + const { completed, ok } = tspl(t, { plan: 1 }) + const ws = new WebSocket('ws://localhost:1') + + ws.addEventListener('error', (ev) => { + const { cause } = ev.error + ok(cause instanceof Error) + }) + + await completed +}) diff --git a/types/websocket.d.ts b/types/websocket.d.ts index 02e4865c7d8..e97c4e3e1d8 100644 --- a/types/websocket.d.ts +++ b/types/websocket.d.ts @@ -136,7 +136,7 @@ interface ErrorEvent extends Event { readonly filename: string readonly lineno: number readonly colno: number - readonly error: any + readonly error: Error } export declare const ErrorEvent: {