diff --git a/apps/koa/package.json b/apps/koa/package.json index f138102..27a8a17 100644 --- a/apps/koa/package.json +++ b/apps/koa/package.json @@ -6,8 +6,8 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "watch": "nodemon --exec NODE_OPTIONS=--inspect-brk NODE_ENV=development ts-node --files ./src/index.ts", - "dev": "cross-env NETWORK_DEBUG_MODE=true node --inspect --watch ./src/index.js", - "start": "cross-env NODE_ENV=production node --inspect ./src/index.js", + "dev": "cross-env NETWORK_DEBUG_MODE=true node --inspect --experimental-network-inspection --watch ./src/index.js", + "start": "cross-env NODE_ENV=production node ./src/index.js", "build": "vite build" }, "keywords": [], diff --git a/apps/koa/src/index.js b/apps/koa/src/index.js index 43d6d92..10522d3 100644 --- a/apps/koa/src/index.js +++ b/apps/koa/src/index.js @@ -1,10 +1,21 @@ const WebSocket = require('ws') +function ensureNode16() { + if (typeof global.ReadableStream === 'undefined') { + const { ReadableStream, WritableStream, TransformStream } = require('node:stream/web') + global.ReadableStream = ReadableStream + global.WritableStream = WritableStream + global.TransformStream = TransformStream + } +} + const run = () => { let register try { + ensureNode16() register = require('node-network-devtools').register - } catch { + } catch (e) { + console.error(e) setTimeout(run, 1000) return } diff --git a/packages/network-debugger/package.json b/packages/network-debugger/package.json index 026a1de..37ebd1a 100644 --- a/packages/network-debugger/package.json +++ b/packages/network-debugger/package.json @@ -1,6 +1,6 @@ { "name": "node-network-devtools", - "version": "1.0.28", + "version": "1.0.29", "description": "Inspecting Node.js's Network with Chrome DevTools", "homepage": "https://grinzero.github.io/node-network-devtools/", "main": "./dist/index.js", diff --git a/packages/network-debugger/src/fork/module/health/index.ts b/packages/network-debugger/src/fork/module/health/index.ts index 2b4fbc7..c37b179 100644 --- a/packages/network-debugger/src/fork/module/health/index.ts +++ b/packages/network-debugger/src/fork/module/health/index.ts @@ -5,9 +5,9 @@ export const healthPlugin = createPlugin('health', ({ devtool }) => { process.exit(0) } - let id = setTimeout(exitProcess, 5000) + let id = setTimeout(exitProcess, 10000) useHandler('healthcheck', () => { clearTimeout(id) - id = setTimeout(exitProcess, 5000) + id = setTimeout(exitProcess, 10000) }) }) diff --git a/packages/network-debugger/src/fork/module/network/index.ts b/packages/network-debugger/src/fork/module/network/index.ts index 0ddb17c..572fef2 100644 --- a/packages/network-debugger/src/fork/module/network/index.ts +++ b/packages/network-debugger/src/fork/module/network/index.ts @@ -4,6 +4,11 @@ import { BodyTransformer, RequestHeaderPipe } from '../../pipe' import { createPlugin, useHandler } from '../common' import zlib from 'node:zlib' import { ResourceService } from '../../resource-service' +import type { Network, Runtime } from 'node:inspector' + +const inspector = (async () => { + return await import('node:inspector') +})() const frameId = '517.528' const loaderId = '517.529' @@ -12,6 +17,44 @@ export const toMimeType = (contentType: string) => { return contentType.split(';')[0] || 'text/plain' } +/** + * 将本项目中的 Initiator 结构转换为 node:inspector 的 Network.Initiator + * - 本地格式:{ type: string; stack: { callFrames: CDPCallFrame[] } } + * - 目标格式:Network.Initiator(可包含 url/lineNumber/columnNumber 等可选字段) + */ +export const toInspectorInitiator = (initiator?: RequestDetail['initiator']): Network.Initiator => { + if (!initiator) { + // 提供一个最小可用的 Initiator,避免类型不满足要求 + return { type: 'other' } + } + + const callFrames = initiator.stack?.callFrames ?? [] + + // 仅在所有必要字段存在时构造 StackTrace,以满足类型约束 + const framesForInspector: Runtime.CallFrame[] = callFrames + .filter((f) => typeof f.scriptId === 'string' && !!f.scriptId) + .map((f) => ({ + functionName: f.functionName || '', + scriptId: f.scriptId as string, + url: f.url || '', + lineNumber: typeof f.lineNumber === 'number' ? f.lineNumber : 0, + columnNumber: typeof f.columnNumber === 'number' ? f.columnNumber : 0 + })) + + const stack: Runtime.StackTrace | undefined = + framesForInspector.length > 0 ? { callFrames: framesForInspector } : undefined + + const topFrame = callFrames[0] + + return { + type: initiator.type, + ...(stack ? { stack } : {}), + ...(topFrame?.url ? { url: topFrame.url } : {}), + ...(typeof topFrame?.lineNumber === 'number' ? { lineNumber: topFrame.lineNumber } : {}), + ...(typeof topFrame?.columnNumber === 'number' ? { columnNumber: topFrame.columnNumber } : {}) + } +} + export const networkPlugin = createPlugin('network', ({ devtool, core }) => { const requests: Record = {} @@ -22,6 +65,7 @@ export const networkPlugin = createPlugin('network', ({ devtool, core }) => { requests[request.id] = request } const endRequest = (request: RequestDetail) => { + requests[request.id] = request request.requestEndTime = request.requestEndTime || Date.now() devtool.updateTimestamp() const headers = new RequestHeaderPipe(request.responseHeaders) @@ -145,9 +189,11 @@ export const networkPlugin = createPlugin('network', ({ devtool, core }) => { requests[request.id] = request }) - useHandler('registerRequest', ({ data }) => { + useHandler('registerRequest', async ({ data }) => { const request = new RequestDetail(data) + const inspect = await inspector.catch(() => null) + requests[request.id] = request // replace callFrames' scriptId if (request.initiator) { @@ -166,6 +212,21 @@ export const networkPlugin = createPlugin('network', ({ devtool, core }) => { const headerPipe = new RequestHeaderPipe(request.requestHeaders) const contentType = headerPipe.getHeader('content-type') + if (inspect && inspect.Network) { + const newInitiator = toInspectorInitiator(request.initiator) + inspect.Network.requestWillBeSent({ + requestId: request.id, + timestamp: devtool.timestamp, + wallTime: request.requestStartTime!, + request: { + url: request.url!, + method: request.method!, + headers: headerPipe.getData() + }, + initiator: newInitiator! + }) + } + return devtool.send({ method: 'Network.requestWillBeSent', params: {