Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions lib/web/websocket/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,11 +303,12 @@ function failWebsocketConnection (handler, code, reason, cause) {

handler.controller.abort()

if (handler.socket?.destroyed === false) {
if (!handler.socket) {
// If the connection was not established, we must still emit an 'error' and 'close' events
handler.onSocketClose()
} else if (handler.socket.destroyed === false) {
handler.socket.destroy()
}

handler.onFail(code, reason, cause)
}

module.exports = {
Expand Down
1 change: 0 additions & 1 deletion lib/web/websocket/stream/websocketstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class WebSocketStream {
#handler = {
// https://whatpr.org/websockets/48/7b748d3...d5570f3.html#feedback-to-websocket-stream-from-the-protocol
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
onFail: (_code, _reason) => {},
onMessage: (opcode, data) => this.#onMessage(opcode, data),
onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message),
onParserDrain: () => this.#handler.socket.resume(),
Expand Down
44 changes: 13 additions & 31 deletions lib/web/websocket/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const { channels } = require('../../core/diagnostics')
/**
* @typedef {object} Handler
* @property {(response: any, extensions?: string[]) => void} onConnectionEstablished
* @property {(code: number, reason: any) => void} onFail
* @property {(opcode: number, data: Buffer) => void} onMessage
* @property {(error: Error) => void} onParserError
* @property {() => void} onParserDrain
Expand Down Expand Up @@ -64,7 +63,6 @@ class WebSocket extends EventTarget {
/** @type {Handler} */
#handler = {
onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions),
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(),
Expand Down Expand Up @@ -506,26 +504,6 @@ class WebSocket extends EventTarget {
}
}

#onFail (code, reason, cause) {
if (reason) {
// TODO: process.nextTick
fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
error: new Error(reason, cause ? { cause } : undefined),
message: reason
})
}

if (!this.#handler.wasEverConnected) {
this.#handler.readyState = states.CLOSED

// If the WebSocket connection could not be established, it is also said
// that _The WebSocket Connection is Closed_, but not _cleanly_.
fireEvent('close', this, (type, init) => new CloseEvent(type, init), {
wasClean: false, code, reason
})
}
}

#onMessage (type, data) {
// 1. If ready state is not OPEN (1), then return.
if (this.#handler.readyState !== states.OPEN) {
Expand Down Expand Up @@ -586,18 +564,11 @@ class WebSocket extends EventTarget {
let code = 1005
let reason = ''

const result = this.#parser.closingInfo
const result = this.#parser?.closingInfo

if (result && !result.error) {
code = result.code ?? 1005
reason = result.reason
} else if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
// If _The WebSocket
// Connection is Closed_ and no Close control frame was received by the
// endpoint (such as could occur if the underlying transport connection
// is lost), _The WebSocket Connection Close Code_ is considered to be
// 1006.
code = 1006
}

// 1. Change the ready state to CLOSED (3).
Expand All @@ -607,7 +578,18 @@ class WebSocket extends EventTarget {
// connection, or if the WebSocket connection was closed
// after being flagged as full, fire an event named error
// at the WebSocket object.
// TODO
if (!this.#handler.closeState.has(sentCloseFrameState.RECEIVED)) {
// If _The WebSocket
// Connection is Closed_ and no Close control frame was received by the
// endpoint (such as could occur if the underlying transport connection
// is lost), _The WebSocket Connection Close Code_ is considered to be
// 1006.
code = 1006

fireEvent('error', this, (type, init) => new ErrorEvent(type, init), {
error: new TypeError(reason)
})
}

// 3. Fire an event named close at the WebSocket object,
// using CloseEvent, with the wasClean attribute
Expand Down
3 changes: 1 addition & 2 deletions test/websocket/issue-4273.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ test('first error than close event is fired on failed connection', async (t) =>
const ws = new WebSocket('ws://localhost:1')

ws.addEventListener('error', (ev) => {
const { cause } = ev.error
ok(cause instanceof Error)
ok(ev.error instanceof TypeError)
})

await completed
Expand Down
27 changes: 27 additions & 0 deletions test/websocket/issue-4487.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict'

const { test } = require('node:test')
const assert = require('node:assert')
const { once } = require('node:events')

const { WebSocketServer } = require('ws')
const { WebSocket } = require('../..')

test('should be emit error event on aborted connection', async (t) => {
const wss = new WebSocketServer({ port: 0 })

t.after(() => wss.close())

wss.on('connection', (ws) => ws.terminate())

await once(wss, 'listening')

const ws = new WebSocket(`http://localhost:${wss.address().port}`)

let errorEmitted = false
// eslint-disable-next-line no-return-assign
ws.onerror = () => errorEmitted = true
await once(ws, 'close')

assert(errorEmitted)
})
Loading