mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
inspector: initial support websocket inspection
Refs: https://github.com/nodejs/node/issues/53946 PR-URL: https://github.com/nodejs/node/pull/59404 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
3f51cb6229
commit
ee9c8cf0cb
|
|
@ -602,6 +602,48 @@ This feature is only available with the `--experimental-network-inspection` flag
|
||||||
Broadcasts the `Network.loadingFailed` event to connected frontends. This event indicates that
|
Broadcasts the `Network.loadingFailed` event to connected frontends. This event indicates that
|
||||||
HTTP request has failed to load.
|
HTTP request has failed to load.
|
||||||
|
|
||||||
|
### `inspector.Network.webSocketCreated([params])`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added:
|
||||||
|
- REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `params` {Object}
|
||||||
|
|
||||||
|
This feature is only available with the `--experimental-network-inspection` flag enabled.
|
||||||
|
|
||||||
|
Broadcasts the `Network.webSocketCreated` event to connected frontends. This event indicates that
|
||||||
|
a WebSocket connection has been initiated.
|
||||||
|
|
||||||
|
### `inspector.Network.webSocketHandshakeResponseReceived([params])`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added:
|
||||||
|
- REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `params` {Object}
|
||||||
|
|
||||||
|
This feature is only available with the `--experimental-network-inspection` flag enabled.
|
||||||
|
|
||||||
|
Broadcasts the `Network.webSocketHandshakeResponseReceived` event to connected frontends.
|
||||||
|
This event indicates that the WebSocket handshake response has been received.
|
||||||
|
|
||||||
|
### `inspector.Network.webSocketClosed([params])`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added:
|
||||||
|
- REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `params` {Object}
|
||||||
|
|
||||||
|
This feature is only available with the `--experimental-network-inspection` flag enabled.
|
||||||
|
|
||||||
|
Broadcasts the `Network.webSocketClosed` event to connected frontends.
|
||||||
|
This event indicates that a WebSocket connection has been closed.
|
||||||
|
|
||||||
### `inspector.NetworkResources.put`
|
### `inspector.NetworkResources.put`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,10 @@ const Network = {
|
||||||
loadingFailed: (params) => broadcastToFrontend('Network.loadingFailed', params),
|
loadingFailed: (params) => broadcastToFrontend('Network.loadingFailed', params),
|
||||||
dataSent: (params) => broadcastToFrontend('Network.dataSent', params),
|
dataSent: (params) => broadcastToFrontend('Network.dataSent', params),
|
||||||
dataReceived: (params) => broadcastToFrontend('Network.dataReceived', params),
|
dataReceived: (params) => broadcastToFrontend('Network.dataReceived', params),
|
||||||
|
webSocketCreated: (params) => broadcastToFrontend('Network.webSocketCreated', params),
|
||||||
|
webSocketClosed: (params) => broadcastToFrontend('Network.webSocketClosed', params),
|
||||||
|
webSocketHandshakeResponseReceived:
|
||||||
|
(params) => broadcastToFrontend('Network.webSocketHandshakeResponseReceived', params),
|
||||||
};
|
};
|
||||||
|
|
||||||
const NetworkResources = {
|
const NetworkResources = {
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,39 @@ function onClientResponseFinish({ request }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move Network.webSocketCreated to the actual creation time of the WebSocket.
|
||||||
|
// undici:websocket:open fires when the connection is established, but this results
|
||||||
|
// in an inaccurate stack trace.
|
||||||
|
function onWebSocketOpen({ websocket }) {
|
||||||
|
websocket[kInspectorRequestId] = getNextRequestId();
|
||||||
|
const url = websocket.url.toString();
|
||||||
|
Network.webSocketCreated({
|
||||||
|
requestId: websocket[kInspectorRequestId],
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
// TODO: Use handshake response data from undici diagnostics when available.
|
||||||
|
// https://github.com/nodejs/undici/pull/4396
|
||||||
|
Network.webSocketHandshakeResponseReceived({
|
||||||
|
requestId: websocket[kInspectorRequestId],
|
||||||
|
timestamp: getMonotonicTime(),
|
||||||
|
response: {
|
||||||
|
status: 101,
|
||||||
|
statusText: 'Switching Protocols',
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWebSocketClose({ websocket }) {
|
||||||
|
if (typeof websocket[kInspectorRequestId] !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Network.webSocketClosed({
|
||||||
|
requestId: websocket[kInspectorRequestId],
|
||||||
|
timestamp: getMonotonicTime(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function enable() {
|
function enable() {
|
||||||
dc.subscribe('undici:request:create', onClientRequestStart);
|
dc.subscribe('undici:request:create', onClientRequestStart);
|
||||||
dc.subscribe('undici:request:error', onClientRequestError);
|
dc.subscribe('undici:request:error', onClientRequestError);
|
||||||
|
|
@ -214,6 +247,8 @@ function enable() {
|
||||||
dc.subscribe('undici:request:bodyChunkSent', onClientRequestBodyChunkSent);
|
dc.subscribe('undici:request:bodyChunkSent', onClientRequestBodyChunkSent);
|
||||||
dc.subscribe('undici:request:bodySent', onClientRequestBodySent);
|
dc.subscribe('undici:request:bodySent', onClientRequestBodySent);
|
||||||
dc.subscribe('undici:request:bodyChunkReceived', onClientRequestBodyChunkReceived);
|
dc.subscribe('undici:request:bodyChunkReceived', onClientRequestBodyChunkReceived);
|
||||||
|
dc.subscribe('undici:websocket:open', onWebSocketOpen);
|
||||||
|
dc.subscribe('undici:websocket:close', onWebSocketClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disable() {
|
function disable() {
|
||||||
|
|
@ -224,6 +259,8 @@ function disable() {
|
||||||
dc.unsubscribe('undici:request:bodyChunkSent', onClientRequestBodyChunkSent);
|
dc.unsubscribe('undici:request:bodyChunkSent', onClientRequestBodyChunkSent);
|
||||||
dc.unsubscribe('undici:request:bodySent', onClientRequestBodySent);
|
dc.unsubscribe('undici:request:bodySent', onClientRequestBodySent);
|
||||||
dc.unsubscribe('undici:request:bodyChunkReceived', onClientRequestBodyChunkReceived);
|
dc.unsubscribe('undici:request:bodyChunkReceived', onClientRequestBodyChunkReceived);
|
||||||
|
dc.unsubscribe('undici:websocket:open', onWebSocketOpen);
|
||||||
|
dc.unsubscribe('undici:websocket:close', onWebSocketClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,35 @@ std::unique_ptr<protocol::Network::Response> createResponseFromObject(
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<protocol::Network::WebSocketResponse> createWebSocketResponse(
|
||||||
|
v8::Local<v8::Context> context, Local<Object> response) {
|
||||||
|
HandleScope handle_scope(context->GetIsolate());
|
||||||
|
int status;
|
||||||
|
if (!ObjectGetInt(context, response, "status").To(&status)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
protocol::String statusText;
|
||||||
|
if (!ObjectGetProtocolString(context, response, "statusText")
|
||||||
|
.To(&statusText)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Local<Object> headers_obj;
|
||||||
|
if (!ObjectGetObject(context, response, "headers").ToLocal(&headers_obj)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::unique_ptr<protocol::Network::Headers> headers =
|
||||||
|
createHeadersFromObject(context, headers_obj);
|
||||||
|
if (!headers) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return protocol::Network::WebSocketResponse::create()
|
||||||
|
.setStatus(status)
|
||||||
|
.setStatusText(statusText)
|
||||||
|
.setHeaders(std::move(headers))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
NetworkAgent::NetworkAgent(
|
NetworkAgent::NetworkAgent(
|
||||||
NetworkInspector* inspector,
|
NetworkInspector* inspector,
|
||||||
v8_inspector::V8Inspector* v8_inspector,
|
v8_inspector::V8Inspector* v8_inspector,
|
||||||
|
|
@ -223,6 +252,64 @@ NetworkAgent::NetworkAgent(
|
||||||
event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished;
|
event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished;
|
||||||
event_notifier_map_["dataSent"] = &NetworkAgent::dataSent;
|
event_notifier_map_["dataSent"] = &NetworkAgent::dataSent;
|
||||||
event_notifier_map_["dataReceived"] = &NetworkAgent::dataReceived;
|
event_notifier_map_["dataReceived"] = &NetworkAgent::dataReceived;
|
||||||
|
event_notifier_map_["webSocketCreated"] = &NetworkAgent::webSocketCreated;
|
||||||
|
event_notifier_map_["webSocketClosed"] = &NetworkAgent::webSocketClosed;
|
||||||
|
event_notifier_map_["webSocketHandshakeResponseReceived"] =
|
||||||
|
&NetworkAgent::webSocketHandshakeResponseReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkAgent::webSocketCreated(v8::Local<v8::Context> context,
|
||||||
|
v8::Local<v8::Object> params) {
|
||||||
|
protocol::String request_id;
|
||||||
|
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
protocol::String url;
|
||||||
|
if (!ObjectGetProtocolString(context, params, "url").To(&url)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::unique_ptr<protocol::Network::Initiator> initiator =
|
||||||
|
protocol::Network::Initiator::create()
|
||||||
|
.setType(protocol::Network::Initiator::TypeEnum::Script)
|
||||||
|
.setStack(
|
||||||
|
v8_inspector_->captureStackTrace(true)->buildInspectorObject(0))
|
||||||
|
.build();
|
||||||
|
frontend_->webSocketCreated(request_id, url, std::move(initiator));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkAgent::webSocketClosed(v8::Local<v8::Context> context,
|
||||||
|
v8::Local<v8::Object> params) {
|
||||||
|
protocol::String request_id;
|
||||||
|
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double timestamp;
|
||||||
|
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frontend_->webSocketClosed(request_id, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkAgent::webSocketHandshakeResponseReceived(
|
||||||
|
v8::Local<v8::Context> context, v8::Local<v8::Object> params) {
|
||||||
|
protocol::String request_id;
|
||||||
|
if (!ObjectGetProtocolString(context, params, "requestId").To(&request_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double timestamp;
|
||||||
|
if (!ObjectGetDouble(context, params, "timestamp").To(×tamp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Local<Object> response_obj;
|
||||||
|
if (!ObjectGetObject(context, params, "response").ToLocal(&response_obj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto response = createWebSocketResponse(context, response_obj);
|
||||||
|
if (!response) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frontend_->webSocketHandshakeResponseReceived(
|
||||||
|
request_id, timestamp, std::move(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkAgent::emitNotification(v8::Local<v8::Context> context,
|
void NetworkAgent::emitNotification(v8::Local<v8::Context> context,
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,13 @@ class NetworkAgent : public protocol::Network::Backend {
|
||||||
void dataReceived(v8::Local<v8::Context> context,
|
void dataReceived(v8::Local<v8::Context> context,
|
||||||
v8::Local<v8::Object> params);
|
v8::Local<v8::Object> params);
|
||||||
|
|
||||||
|
void webSocketCreated(v8::Local<v8::Context> context,
|
||||||
|
v8::Local<v8::Object> params);
|
||||||
|
void webSocketClosed(v8::Local<v8::Context> context,
|
||||||
|
v8::Local<v8::Object> params);
|
||||||
|
void webSocketHandshakeResponseReceived(v8::Local<v8::Context> context,
|
||||||
|
v8::Local<v8::Object> params);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NetworkInspector* inspector_;
|
NetworkInspector* inspector_;
|
||||||
v8_inspector::V8Inspector* v8_inspector_;
|
v8_inspector::V8Inspector* v8_inspector_;
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,16 @@ experimental domain Network
|
||||||
boolean success
|
boolean success
|
||||||
optional IO.StreamHandle stream
|
optional IO.StreamHandle stream
|
||||||
|
|
||||||
|
# WebSocket response data.
|
||||||
|
type WebSocketResponse extends object
|
||||||
|
properties
|
||||||
|
# HTTP response status code.
|
||||||
|
integer status
|
||||||
|
# HTTP response status text.
|
||||||
|
string statusText
|
||||||
|
# HTTP response headers.
|
||||||
|
Headers headers
|
||||||
|
|
||||||
# Disables network tracking, prevents network events from being sent to the client.
|
# Disables network tracking, prevents network events from being sent to the client.
|
||||||
command disable
|
command disable
|
||||||
|
|
||||||
|
|
@ -285,6 +295,31 @@ experimental domain Network
|
||||||
integer encodedDataLength
|
integer encodedDataLength
|
||||||
# Data that was received.
|
# Data that was received.
|
||||||
experimental optional binary data
|
experimental optional binary data
|
||||||
|
# Fired upon WebSocket creation.
|
||||||
|
event webSocketCreated
|
||||||
|
parameters
|
||||||
|
# Request identifier.
|
||||||
|
RequestId requestId
|
||||||
|
# WebSocket request URL.
|
||||||
|
string url
|
||||||
|
# Request initiator.
|
||||||
|
Initiator initiator
|
||||||
|
# Fired when WebSocket is closed.
|
||||||
|
event webSocketClosed
|
||||||
|
parameters
|
||||||
|
# Request identifier.
|
||||||
|
RequestId requestId
|
||||||
|
# Timestamp.
|
||||||
|
MonotonicTime timestamp
|
||||||
|
# Fired when WebSocket handshake response becomes available.
|
||||||
|
event webSocketHandshakeResponseReceived
|
||||||
|
parameters
|
||||||
|
# Request identifier.
|
||||||
|
RequestId requestId
|
||||||
|
# Timestamp.
|
||||||
|
MonotonicTime timestamp
|
||||||
|
# WebSocket response data.
|
||||||
|
WebSocketResponse response
|
||||||
|
|
||||||
# Support for inspecting node process state.
|
# Support for inspecting node process state.
|
||||||
experimental domain NodeRuntime
|
experimental domain NodeRuntime
|
||||||
|
|
|
||||||
105
test/common/websocket-server.js
Normal file
105
test/common/websocket-server.js
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
'use strict';
|
||||||
|
const common = require('./index');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
const http = require('http');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
class WebSocketServer {
|
||||||
|
constructor({
|
||||||
|
port = 0,
|
||||||
|
}) {
|
||||||
|
this.port = port;
|
||||||
|
this.server = http.createServer();
|
||||||
|
this.clients = new Set();
|
||||||
|
|
||||||
|
this.server.on('upgrade', this.handleUpgrade.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.server.listen(this.port, () => {
|
||||||
|
this.port = this.server.address().port;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to start WebSocket server:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUpgrade(req, socket, head) {
|
||||||
|
const key = req.headers['sec-websocket-key'];
|
||||||
|
const acceptKey = this.generateAcceptValue(key);
|
||||||
|
const responseHeaders = [
|
||||||
|
'HTTP/1.1 101 Switching Protocols',
|
||||||
|
'Upgrade: websocket',
|
||||||
|
'Connection: Upgrade',
|
||||||
|
`Sec-WebSocket-Accept: ${acceptKey}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');
|
||||||
|
this.clients.add(socket);
|
||||||
|
|
||||||
|
socket.on('data', (buffer) => {
|
||||||
|
const opcode = buffer[0] & 0x0f;
|
||||||
|
|
||||||
|
if (opcode === 0x8) {
|
||||||
|
socket.end();
|
||||||
|
this.clients.delete(socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.write(this.encodeMessage('Hello from server!'));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('close', () => {
|
||||||
|
this.clients.delete(socket);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', (err) => {
|
||||||
|
console.error('Socket error:', err);
|
||||||
|
this.clients.delete(socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generateAcceptValue(secWebSocketKey) {
|
||||||
|
return crypto
|
||||||
|
.createHash('sha1')
|
||||||
|
.update(secWebSocketKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary')
|
||||||
|
.digest('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeMessage(buffer) {
|
||||||
|
const secondByte = buffer[1];
|
||||||
|
const length = secondByte & 127;
|
||||||
|
const maskStart = 2;
|
||||||
|
const dataStart = maskStart + 4;
|
||||||
|
const masks = buffer.slice(maskStart, dataStart);
|
||||||
|
const data = buffer.slice(dataStart, dataStart + length);
|
||||||
|
const result = Buffer.alloc(length);
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result[i] = data[i] ^ masks[i % 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeMessage(message) {
|
||||||
|
const msgBuffer = Buffer.from(message);
|
||||||
|
const length = msgBuffer.length;
|
||||||
|
const frame = [0x81];
|
||||||
|
|
||||||
|
if (length < 126) {
|
||||||
|
frame.push(length);
|
||||||
|
} else if (length < 65536) {
|
||||||
|
frame.push(126, (length >> 8) & 0xff, length & 0xff);
|
||||||
|
} else {
|
||||||
|
throw new Error('Message too long');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.concat([Buffer.from(frame), msgBuffer]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebSocketServer;
|
||||||
|
|
@ -88,6 +88,34 @@ const EXPECTED_EVENTS = {
|
||||||
errorText: 'Failed to load resource'
|
errorText: 'Failed to load resource'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'webSocketCreated',
|
||||||
|
params: {
|
||||||
|
requestId: 'websocket-id-1',
|
||||||
|
url: 'ws://example.com:8080',
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webSocketHandshakeResponseReceived',
|
||||||
|
params: {
|
||||||
|
requestId: 'websocket-id-1',
|
||||||
|
response: {
|
||||||
|
status: 101,
|
||||||
|
statusText: 'Switching Protocols',
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
timestamp: 1000,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webSocketClosed',
|
||||||
|
params: {
|
||||||
|
requestId: 'websocket-id-1',
|
||||||
|
timestamp: 1000,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -124,7 +152,7 @@ const runAsyncTest = async () => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
session.on(`${domain}.${event.name}`, common.mustCall(({ params }) => {
|
session.on(`${domain}.${event.name}`, common.mustCall(({ params }) => {
|
||||||
if (event.name === 'requestWillBeSent') {
|
if (event.name === 'requestWillBeSent' || event.name === 'webSocketCreated') {
|
||||||
// Initiator is automatically captured and contains caller info.
|
// Initiator is automatically captured and contains caller info.
|
||||||
// No need to validate it.
|
// No need to validate it.
|
||||||
delete params.initiator;
|
delete params.initiator;
|
||||||
|
|
|
||||||
63
test/parallel/test-inspector-network-websocket.js
Normal file
63
test/parallel/test-inspector-network-websocket.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Flags: --inspect=0 --experimental-network-inspection
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
common.skipIfInspectorDisabled();
|
||||||
|
|
||||||
|
const assert = require('node:assert');
|
||||||
|
const { once } = require('node:events');
|
||||||
|
const WebSocketServer = require('../common/websocket-server');
|
||||||
|
const inspector = require('node:inspector/promises');
|
||||||
|
const dc = require('diagnostics_channel');
|
||||||
|
|
||||||
|
const session = new inspector.Session();
|
||||||
|
session.connect();
|
||||||
|
|
||||||
|
dc.channel('undici:websocket:socket_error').subscribe((message) => {
|
||||||
|
console.error('WebSocket error:', message);
|
||||||
|
});
|
||||||
|
|
||||||
|
function findFrameInInitiator(scriptName, initiator) {
|
||||||
|
const frame = initiator.stack.callFrames.find((it) => {
|
||||||
|
return it.url === scriptName;
|
||||||
|
});
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test() {
|
||||||
|
await session.post('Network.enable');
|
||||||
|
const server = new WebSocketServer({
|
||||||
|
responseError: true,
|
||||||
|
});
|
||||||
|
await server.start();
|
||||||
|
const url = `ws://127.0.0.1:${server.port}/`;
|
||||||
|
let requestId;
|
||||||
|
once(session, 'Network.webSocketCreated').then(common.mustCall(([message]) => {
|
||||||
|
assert.strictEqual(message.method, 'Network.webSocketCreated');
|
||||||
|
assert.strictEqual(message.params.url, url);
|
||||||
|
assert.ok(message.params.requestId);
|
||||||
|
assert.strictEqual(typeof message.params.initiator, 'object');
|
||||||
|
assert.strictEqual(message.params.initiator.type, 'script');
|
||||||
|
assert.ok(findFrameInInitiator('node:internal/deps/undici/undici', message.params.initiator));
|
||||||
|
requestId = message.params.requestId;
|
||||||
|
}));
|
||||||
|
|
||||||
|
once(session, 'Network.webSocketHandshakeResponseReceived').then(common.mustCall(([message]) => {
|
||||||
|
assert.strictEqual(message.params.requestId, requestId);
|
||||||
|
assert.strictEqual(message.params.response.status, 101);
|
||||||
|
assert.strictEqual(message.params.response.statusText, 'Switching Protocols');
|
||||||
|
assert.strictEqual(typeof message.params.timestamp, 'number');
|
||||||
|
socket.close();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const socket = new WebSocket(url);
|
||||||
|
once(session, 'Network.webSocketClosed').then(common.mustCall(([message]) => {
|
||||||
|
assert.strictEqual(message.method, 'Network.webSocketClosed');
|
||||||
|
assert.strictEqual(message.params.requestId, requestId);
|
||||||
|
session.disconnect();
|
||||||
|
server.server.close();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
test().then(common.mustCall());
|
||||||
Loading…
Reference in New Issue
Block a user