Skip to content

Commit e5bf00e

Browse files
tawseefnabitawseefnabi
andauthored
feat(websocket): add handshake response info to undici:websocket:open diagnostic event (#4396)
* feat(websocket): add handshake response info to undici:websocket:open diagnostic event - Add handshakeResponse object to undici:websocket:open diagnostic event - Include status, statusText, and headers from HTTP handshake response - Enables Chrome DevTools Protocol Network.webSocketHandshakeResponseReceived support - Add comprehensive test coverage for handshake response diagnostic data Fixes #4394 * fix(websocket/diagnostics): use headersList.headersMap.entries() for diagnostics channel event emission - Fixes diagnostics channel WebSocket event emission and related test timeouts in Node.js 20+. - Cleans up debug/test scripts and logs. * fix(websocket/diagnostics): clean up after review, remove debug logs, redundant checks, and improve handshake header assertions in test * refactor: use HeadersList.entries getter for WebSocket diagnostic headers - Replace Object.fromEntries(response.headersList.headersMap.entries()) with response.headersList.entries - Use the built-in getter method from HeadersList class for cleaner, more efficient code - Update test to match new plain object format (remove .value property access) - Fix test plan count to match actual number of assertions (11) Addresses KhafraDev's code review feedback to use the purpose-built entries getter. * docs: document handshakeResponse in websocket:open diagnostics - Add handshakeResponse object documentation to DiagnosticsChannel.md - Remove console.log statements from test file - Fix linting issues in test file * test: remove console.log and mark unused WebSocket variable --------- Co-authored-by: tawseefnabi <tawseefnabi9@gamil.com>
1 parent c3f5591 commit e5bf00e

3 files changed

Lines changed: 84 additions & 2 deletions

File tree

docs/docs/api/DiagnosticsChannel.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,38 @@ This message is published after the client has successfully connected to a serve
169169
```js
170170
import diagnosticsChannel from 'diagnostics_channel'
171171

172-
diagnosticsChannel.channel('undici:websocket:open').subscribe(({ address, protocol, extensions, websocket }) => {
172+
diagnosticsChannel.channel('undici:websocket:open').subscribe(({
173+
address, // { address: string, family: string, port: number }
174+
protocol, // string - negotiated subprotocol
175+
extensions, // string - negotiated extensions
176+
websocket, // WebSocket - the WebSocket instance
177+
handshakeResponse // object - HTTP response that upgraded the connection
178+
}) => {
173179
console.log(address) // address, family, and port
174180
console.log(protocol) // negotiated subprotocols
175181
console.log(extensions) // negotiated extensions
176182
console.log(websocket) // the WebSocket instance
183+
184+
// Handshake response details
185+
console.log(handshakeResponse.status) // 101 for successful WebSocket upgrade
186+
console.log(handshakeResponse.statusText) // 'Switching Protocols'
187+
console.log(handshakeResponse.headers) // Object containing response headers
177188
})
178189
```
179190

191+
### Handshake Response Object
192+
193+
The `handshakeResponse` object contains the HTTP response that upgraded the connection to WebSocket:
194+
195+
- `status` (number): The HTTP status code (101 for successful WebSocket upgrade)
196+
- `statusText` (string): The HTTP status message ('Switching Protocols' for successful upgrade)
197+
- `headers` (object): The HTTP response headers from the server, including:
198+
- `upgrade: 'websocket'`
199+
- `connection: 'upgrade'`
200+
- `sec-websocket-accept` and other WebSocket-related headers
201+
202+
This information is particularly useful for debugging and monitoring WebSocket connections, as it provides access to the initial HTTP handshake response that established the WebSocket connection.
203+
180204
## `undici:websocket:close`
181205

182206
This message is published after the connection has closed.

lib/web/websocket/websocket.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,11 +482,18 @@ class WebSocket extends EventTarget {
482482
fireEvent('open', this)
483483

484484
if (channels.open.hasSubscribers) {
485+
// Convert headers to a plain object for the event
486+
const headers = response.headersList.entries
485487
channels.open.publish({
486488
address: response.socket.address(),
487489
protocol: this.#protocol,
488490
extensions: this.#extensions,
489-
websocket: this
491+
websocket: this,
492+
handshakeResponse: {
493+
status: response.status,
494+
statusText: response.statusText,
495+
headers
496+
}
490497
})
491498
}
492499
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict'
2+
3+
const { test } = require('node:test')
4+
const dc = require('node:diagnostics_channel')
5+
const { WebSocketServer } = require('ws')
6+
const { WebSocket } = require('../..')
7+
const { tspl } = require('@matteo.collina/tspl')
8+
9+
test('diagnostics channel - undici:websocket:open includes handshake response', async (t) => {
10+
const { equal, ok, completed } = tspl(t, { plan: 11 })
11+
12+
const server = new WebSocketServer({ port: 0 })
13+
const { port } = server.address()
14+
15+
server.on('connection', (ws) => {
16+
setTimeout(() => {
17+
ws.close(1000, 'test')
18+
}, 50)
19+
})
20+
21+
const openListener = (data) => {
22+
// Verify handshake response data
23+
ok(data.handshakeResponse, 'handshakeResponse should be defined')
24+
equal(data.handshakeResponse.status, 101, 'status should be 101')
25+
equal(data.handshakeResponse.statusText, 'Switching Protocols', 'statusText should be correct')
26+
// Check handshake response headers
27+
const headers = data.handshakeResponse.headers
28+
ok(headers, 'headers should be defined')
29+
ok(typeof headers === 'object', 'headers should be an object')
30+
ok('upgrade' in headers, 'upgrade header should be present')
31+
ok('connection' in headers, 'connection header should be present')
32+
ok('sec-websocket-accept' in headers, 'sec-websocket-accept header should be present')
33+
// Optionally, check values
34+
equal(headers.upgrade.toLowerCase(), 'websocket', 'upgrade header should be websocket')
35+
equal(headers.connection.toLowerCase(), 'upgrade', 'connection header should be upgrade')
36+
ok(typeof headers['sec-websocket-accept'] === 'string', 'sec-websocket-accept header should be a string')
37+
}
38+
39+
dc.channel('undici:websocket:open').subscribe(openListener)
40+
41+
t.after(() => {
42+
server.close()
43+
dc.channel('undici:websocket:open').unsubscribe(openListener)
44+
})
45+
46+
// Create WebSocket connection
47+
// eslint-disable-next-line no-unused-vars
48+
const _ws = new WebSocket(`ws://localhost:${port}`)
49+
50+
await completed
51+
})

0 commit comments

Comments
 (0)