@mt0926/node-network-devtools 0.2.0 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -109
- package/README.zh-CN.md +62 -108
- package/dist/cjs/adapters/axios.js +2 -0
- package/dist/cjs/adapters/axios.js.map +1 -0
- package/dist/cjs/adapters/nextjs.js +133 -0
- package/dist/cjs/adapters/nextjs.js.map +1 -0
- package/dist/cjs/config.js +150 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/context/context-manager.js +139 -0
- package/dist/cjs/context/context-manager.js.map +1 -0
- package/dist/cjs/gui/browser-detector.js +263 -0
- package/dist/cjs/gui/browser-detector.js.map +1 -0
- package/dist/cjs/gui/browser-launcher.js +365 -0
- package/dist/cjs/gui/browser-launcher.js.map +1 -0
- package/dist/cjs/gui/event-bridge.js +197 -0
- package/dist/cjs/gui/event-bridge.js.map +1 -0
- package/dist/cjs/gui/port-utils.js +85 -0
- package/dist/cjs/gui/port-utils.js.map +1 -0
- package/dist/cjs/gui/server.js +267 -0
- package/dist/cjs/gui/server.js.map +1 -0
- package/dist/cjs/gui/websocket-hub.js +336 -0
- package/dist/cjs/gui/websocket-hub.js.map +1 -0
- package/dist/cjs/index.js +170 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/interceptors/http-patcher.js +275 -0
- package/dist/cjs/interceptors/http-patcher.js.map +1 -0
- package/dist/cjs/interceptors/undici-patcher.js +362 -0
- package/dist/cjs/interceptors/undici-patcher.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/register.js +95 -0
- package/dist/cjs/register.js.map +1 -0
- package/dist/cjs/store/ring-buffer.js +241 -0
- package/dist/cjs/store/ring-buffer.js.map +1 -0
- package/dist/cjs/test-setup.js +8 -0
- package/dist/cjs/test-setup.js.map +1 -0
- package/dist/cjs/utils/module-compat.js +83 -0
- package/dist/cjs/utils/module-compat.js.map +1 -0
- package/dist/esm/gui/server.js +41 -6
- package/dist/esm/gui/server.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/interceptors/undici-patcher.js +9 -4
- package/dist/esm/interceptors/undici-patcher.js.map +1 -1
- package/dist/esm/utils/module-compat.js +78 -0
- package/dist/esm/utils/module-compat.js.map +1 -0
- package/dist/gui/assets/index.css +1 -1
- package/dist/gui/assets/index.js +10 -10
- package/dist/types/gui/server.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/interceptors/undici-patcher.d.ts.map +1 -1
- package/dist/types/utils/module-compat.d.ts +34 -0
- package/dist/types/utils/module-compat.d.ts.map +1 -0
- package/package.json +42 -35
- package/dist/esm/cli.js +0 -203
- package/dist/esm/cli.js.map +0 -1
- package/dist/types/cli.d.ts +0 -8
- package/dist/types/cli.d.ts.map +0 -1
package/README.zh-CN.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# 🔍 Node Network DevTools
|
|
4
4
|
|
|
5
|
-
**Node.js
|
|
5
|
+
**Node.js 强大的零配置网络调试助手。**
|
|
6
|
+
*实时监控所有 HTTP、HTTPS 和 Fetch/Undici 请求,提供类似 Chrome DevTools 的极简 Web GUI 体验。*
|
|
6
7
|
|
|
7
|
-
[](https://www.npmjs.com/package/node-network-devtools)
|
|
8
|
+
[](https://www.npmjs.com/package/@mt0926/node-network-devtools)
|
|
8
9
|
[](https://opensource.org/licenses/MIT)
|
|
9
|
-
[](https://nodejs.org)
|
|
10
10
|
|
|
11
11
|
[English](./README.md) | [中文文档](#)
|
|
12
12
|
|
|
@@ -14,151 +14,105 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## 🚀 为什么选择 Node Network DevTools?
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- 使用 Puppeteer 启动极简浏览器窗口显示 GUI
|
|
22
|
-
- 拦截所有网络请求可能影响性能
|
|
23
|
-
- 在内存中存储请求/响应数据
|
|
24
|
-
- 不适合生产环境工作负载
|
|
25
|
-
|
|
26
|
-
### 在生产环境中禁用
|
|
27
|
-
|
|
28
|
-
```javascript
|
|
29
|
-
// 条件安装
|
|
30
|
-
if (process.env.NODE_ENV === 'development') {
|
|
31
|
-
await install();
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
或使用环境变量:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
# 禁用 GUI 和自动打开
|
|
39
|
-
NND_GUI_ENABLED=false NND_AUTO_OPEN=false node your-app.js
|
|
40
|
-
```
|
|
19
|
+
还在用 `console.log` 打印每一个请求和响应吗?**Node Network DevTools** 将浏览器“网络”面板的熟悉体验带到了 Node.js 后端。无论是在调试外部 API 调用、微服务还是 Next.js Server Actions,你都可以实时查看每一个细节。
|
|
41
20
|
|
|
42
21
|
## ✨ 特性
|
|
43
22
|
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- 🔗
|
|
49
|
-
- 🛡️
|
|
50
|
-
- ⚡
|
|
51
|
-
- 📦
|
|
23
|
+
- 💎 **类似 DevTools 的体验** - 熟悉的响应式 Web GUI,用于检查 Header、Payload 和 Response。
|
|
24
|
+
- 🔌 **全能拦截** - 原生支持 `http/https` 模块以及现代的 `fetch/undici` (Node.js 18+)。
|
|
25
|
+
- 🛠️ **零侵入开发** - 只需一行代码或一个简单的 CLI 标志即可接入项目。
|
|
26
|
+
- 🖥️ **极简浏览器窗口** - 自动启动基于系统原生浏览器 (Chrome, Edge, 或 Chromium) 的紧凑 App 模式窗口。
|
|
27
|
+
- 🔗 **智能请求追踪** - 利用 `AsyncLocalStorage` 自动关联同一业务流中的多个异步请求。
|
|
28
|
+
- 🛡️ **内置脱敏** - 自动隐藏 `Authorization` 和 `Cookie` 等敏感信息,保障安全。
|
|
29
|
+
- ⚡ **框架友好** - 无缝集成 Next.js, Express, Fastify 等主流框架。
|
|
30
|
+
- 📦 **双模块支持** - 完美兼容 **ESM** 和 **CommonJS**。
|
|
52
31
|
|
|
53
32
|
## 📸 截图
|
|
54
33
|
|
|
55
34
|
### Web GUI 界面
|
|
56
35
|

|
|
57
36
|
|
|
58
|
-
### Chrome DevTools 集成
|
|
59
|
-

|
|
60
|
-
|
|
61
37
|
## 🚀 快速开始
|
|
62
38
|
|
|
63
|
-
### 安装
|
|
39
|
+
### 1. 安装
|
|
64
40
|
|
|
65
41
|
```bash
|
|
66
|
-
npm install node-network-devtools
|
|
67
|
-
# 或
|
|
68
|
-
pnpm add node-network-devtools puppeteer
|
|
42
|
+
npm install @mt0926/node-network-devtools
|
|
69
43
|
# 或
|
|
70
|
-
|
|
44
|
+
pnpm add @mt0926/node-network-devtools
|
|
71
45
|
```
|
|
72
46
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
### 使用
|
|
76
|
-
|
|
77
|
-
#### 方式一:CLI(推荐)
|
|
47
|
+
> **注意**: 无需安装 Puppeteer 等额外依赖!工具会自动检测并使用系统中已有的浏览器。
|
|
78
48
|
|
|
79
|
-
|
|
80
|
-
npx node-network-devtools your-script.js
|
|
81
|
-
# 或使用短别名
|
|
82
|
-
npx nnd your-script.js
|
|
83
|
-
```
|
|
49
|
+
### 2. 使用方式 (推荐)
|
|
84
50
|
|
|
85
|
-
|
|
51
|
+
在应用入口文件的最顶部调用 `install()`。
|
|
86
52
|
|
|
87
|
-
|
|
53
|
+
**ESM:**
|
|
54
|
+
```typescript
|
|
55
|
+
import { install } from '@mt0926/node-network-devtools';
|
|
88
56
|
|
|
89
|
-
|
|
90
|
-
node -r node-network-devtools/register your-script.js
|
|
57
|
+
await install(); // 确保在发送任何网络请求的 import 之前调用
|
|
91
58
|
```
|
|
92
59
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
import { install } from 'node-network-devtools';
|
|
97
|
-
|
|
98
|
-
await install();
|
|
60
|
+
**CommonJS:**
|
|
61
|
+
```javascript
|
|
62
|
+
const { install } = require('@mt0926/node-network-devtools');
|
|
99
63
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// ...
|
|
64
|
+
(async () => {
|
|
65
|
+
await install();
|
|
66
|
+
})();
|
|
104
67
|
```
|
|
105
68
|
|
|
106
|
-
###
|
|
69
|
+
### 3. 高级方案:零代码注入
|
|
107
70
|
|
|
108
|
-
|
|
71
|
+
如果你不想修改源代码,可以使用 Node.js 的命令行参数来注入工具。
|
|
109
72
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
- 无浏览器工具栏或地址栏(app 模式)
|
|
114
|
-
|
|
115
|
-
要手动访问 GUI,请查看控制台输出中的 URL:
|
|
73
|
+
**ESM:**
|
|
74
|
+
```bash
|
|
75
|
+
node --import @mt0926/node-network-devtools/register your-script.js
|
|
116
76
|
```
|
|
117
|
-
|
|
77
|
+
|
|
78
|
+
**CommonJS:**
|
|
79
|
+
```bash
|
|
80
|
+
node -r @mt0926/node-network-devtools/register your-script.js
|
|
118
81
|
```
|
|
119
82
|
|
|
120
83
|
## 🖥️ Web GUI
|
|
121
84
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
### 极简浏览器窗口
|
|
125
|
-
|
|
126
|
-
GUI 在极简的 Puppeteer 控制的浏览器窗口中打开:
|
|
85
|
+
启动后,一个极简的浏览器窗口会自动打开并显示实时请求列表。
|
|
127
86
|
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
87
|
+
- **紧凑尺寸** (800x600),方便分屏调试。
|
|
88
|
+
- **搜索与过滤** - 按 URL、方法或状态码筛选。
|
|
89
|
+
- **详情面板** - 查看 Header、Payload 和 Response。
|
|
90
|
+
- **深色/浅色模式**支持。
|
|
132
91
|
|
|
133
|
-
|
|
92
|
+
如果需要手动访问,请在控制台输出中查找 URL:
|
|
93
|
+
`🚀 Node Network DevTools GUI started at http://localhost:9229`
|
|
134
94
|
|
|
135
|
-
- 📋 **请求列表** - 实时显示所有网络请求
|
|
136
|
-
- 🔍 **搜索过滤** - 按 URL、方法、状态码和类型过滤
|
|
137
|
-
- 📝 **详情面板** - 查看请求头、请求体、响应和时序信息
|
|
138
|
-
- 🎨 **主题切换** - 支持深色/浅色主题
|
|
139
|
-
- ⏸️ **暂停/恢复** - 暂停请求捕获以便分析
|
|
140
|
-
- 🔄 **实时更新** - 基于 WebSocket 的实时更新
|
|
141
95
|
|
|
142
96
|
### GUI 配置
|
|
143
97
|
|
|
144
98
|
```bash
|
|
145
99
|
# 自定义窗口大小
|
|
146
|
-
NND_BROWSER_WIDTH=1024 NND_BROWSER_HEIGHT=768
|
|
100
|
+
NND_BROWSER_WIDTH=1024 NND_BROWSER_HEIGHT=768 node --import @mt0926/node-network-devtools/register your-script.js
|
|
147
101
|
|
|
148
102
|
# 自定义窗口标题
|
|
149
|
-
NND_BROWSER_TITLE="我的应用网络监控"
|
|
103
|
+
NND_BROWSER_TITLE="我的应用网络监控" node --import @mt0926/node-network-devtools/register your-script.js
|
|
150
104
|
|
|
151
105
|
# 指定 GUI 端口
|
|
152
|
-
NND_GUI_PORT=9230
|
|
106
|
+
NND_GUI_PORT=9230 node --import @mt0926/node-network-devtools/register your-script.js
|
|
153
107
|
|
|
154
108
|
# 指定 WebSocket 端口
|
|
155
|
-
NND_WS_PORT=9231
|
|
109
|
+
NND_WS_PORT=9231 node --import @mt0926/node-network-devtools/register your-script.js
|
|
156
110
|
|
|
157
111
|
# 禁用 GUI
|
|
158
|
-
NND_GUI_ENABLED=false
|
|
112
|
+
NND_GUI_ENABLED=false node --import @mt0926/node-network-devtools/register your-script.js
|
|
159
113
|
|
|
160
114
|
# 禁用自动打开浏览器
|
|
161
|
-
NND_AUTO_OPEN=false
|
|
115
|
+
NND_AUTO_OPEN=false node --import @mt0926/node-network-devtools/register your-script.js
|
|
162
116
|
```
|
|
163
117
|
|
|
164
118
|
## 🔧 配置
|
|
@@ -190,7 +144,7 @@ NND_AUTO_OPEN=false npx nnd your-script.js
|
|
|
190
144
|
### 编程配置
|
|
191
145
|
|
|
192
146
|
```typescript
|
|
193
|
-
import { setConfig } from 'node-network-devtools';
|
|
147
|
+
import { setConfig } from '@mt0926/node-network-devtools';
|
|
194
148
|
|
|
195
149
|
setConfig({
|
|
196
150
|
maxRequests: 500,
|
|
@@ -210,7 +164,7 @@ setConfig({
|
|
|
210
164
|
```typescript
|
|
211
165
|
// 根据环境条件安装
|
|
212
166
|
if (process.env.NODE_ENV === 'development') {
|
|
213
|
-
const { install } = await import('node-network-devtools');
|
|
167
|
+
const { install } = await import('@mt0926/node-network-devtools');
|
|
214
168
|
await install();
|
|
215
169
|
}
|
|
216
170
|
```
|
|
@@ -259,7 +213,7 @@ npm run dev
|
|
|
259
213
|
|
|
260
214
|
```typescript
|
|
261
215
|
import express from 'express';
|
|
262
|
-
import { install } from 'node-network-devtools';
|
|
216
|
+
import { install } from '@mt0926/node-network-devtools';
|
|
263
217
|
|
|
264
218
|
await install();
|
|
265
219
|
|
|
@@ -277,23 +231,23 @@ const app = express();
|
|
|
277
231
|
|
|
278
232
|
```typescript
|
|
279
233
|
// 快速安装
|
|
280
|
-
import { install, startGUI, stopGUI } from 'node-network-devtools';
|
|
234
|
+
import { install, startGUI, stopGUI } from '@mt0926/node-network-devtools';
|
|
281
235
|
|
|
282
236
|
// 配置
|
|
283
|
-
import { getConfig, setConfig, resetConfig } from 'node-network-devtools';
|
|
237
|
+
import { getConfig, setConfig, resetConfig } from '@mt0926/node-network-devtools';
|
|
284
238
|
|
|
285
239
|
// 请求存储
|
|
286
|
-
import { getRequestStore } from 'node-network-devtools';
|
|
240
|
+
import { getRequestStore } from '@mt0926/node-network-devtools';
|
|
287
241
|
|
|
288
242
|
// 上下文追踪
|
|
289
243
|
import {
|
|
290
244
|
runWithTrace,
|
|
291
245
|
getCurrentTraceId,
|
|
292
246
|
generateTraceId
|
|
293
|
-
} from 'node-network-devtools';
|
|
247
|
+
} from '@mt0926/node-network-devtools';
|
|
294
248
|
|
|
295
249
|
// 拦截器
|
|
296
|
-
import { HttpPatcher, UndiciPatcher } from 'node-network-devtools';
|
|
250
|
+
import { HttpPatcher, UndiciPatcher } from '@mt0926/node-network-devtools';
|
|
297
251
|
```
|
|
298
252
|
|
|
299
253
|
### 请求追踪
|
|
@@ -301,7 +255,7 @@ import { HttpPatcher, UndiciPatcher } from 'node-network-devtools';
|
|
|
301
255
|
关联同一业务流程中的多个请求:
|
|
302
256
|
|
|
303
257
|
```typescript
|
|
304
|
-
import { runWithTrace, getRequestStore } from 'node-network-devtools';
|
|
258
|
+
import { runWithTrace, getRequestStore } from '@mt0926/node-network-devtools';
|
|
305
259
|
|
|
306
260
|
await runWithTrace('user-login', async () => {
|
|
307
261
|
// 这些请求会被关联到同一个 traceId
|
|
@@ -331,7 +285,7 @@ const requests = store.getByTraceId('user-login');
|
|
|
331
285
|
2. **Undici 拦截**:使用 `Agent.compose()` 注册拦截器捕获 fetch 请求
|
|
332
286
|
3. **上下文传递**:使用 `AsyncLocalStorage` 在异步调用链中传递 TraceID
|
|
333
287
|
4. **事件桥接**:将拦截的请求转发到 WebSocket 客户端以实现 GUI 实时更新
|
|
334
|
-
5.
|
|
288
|
+
5. **原生浏览器启动**:自动检测并启动系统中的浏览器 (Chrome/Edge/Chromium),并以专用的 App 模式窗口运行。
|
|
335
289
|
|
|
336
290
|
## 🤝 贡献
|
|
337
291
|
|
|
@@ -369,7 +323,7 @@ MIT © [ddddd](https://github.com/dong0926)
|
|
|
369
323
|
|
|
370
324
|
- 🐛 [报告问题](https://github.com/dong0926/node-network-devtools/issues)
|
|
371
325
|
- 💬 [讨论](https://github.com/dong0926/node-network-devtools/discussions)
|
|
372
|
-
- 📧 邮箱:
|
|
326
|
+
- 📧 邮箱:xx630133368@gmail.com
|
|
373
327
|
|
|
374
328
|
---
|
|
375
329
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axios.js","sourceRoot":"","sources":["../../../src/adapters/axios.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Next.js 框架适配器
|
|
4
|
+
*
|
|
5
|
+
* 确保与 Next.js 的 Data Cache 和 Request Memoization 兼容
|
|
6
|
+
* 保留 next.revalidate、next.tags 等选项
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.NextJsAdapter = void 0;
|
|
10
|
+
exports.isNextJsEnvironment = isNextJsEnvironment;
|
|
11
|
+
exports.getCurrentRoute = getCurrentRoute;
|
|
12
|
+
exports.runWithRoute = runWithRoute;
|
|
13
|
+
exports.runWithRouteAsync = runWithRouteAsync;
|
|
14
|
+
exports.extractNextJsOptions = extractNextJsOptions;
|
|
15
|
+
exports.extractNextJsMetadata = extractNextJsMetadata;
|
|
16
|
+
exports.createInstrumentationConfig = createInstrumentationConfig;
|
|
17
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
18
|
+
// 用于存储当前路由信息的 AsyncLocalStorage
|
|
19
|
+
const routeStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
20
|
+
/**
|
|
21
|
+
* 检测是否在 Next.js 环境中
|
|
22
|
+
*/
|
|
23
|
+
function isNextJsEnvironment() {
|
|
24
|
+
// 检查 Next.js 特有的环境变量
|
|
25
|
+
return !!(process.env.NEXT_RUNTIME ||
|
|
26
|
+
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT ||
|
|
27
|
+
process.env.NEXT_DEPLOYMENT_ID);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 获取当前路由信息
|
|
31
|
+
*/
|
|
32
|
+
function getCurrentRoute() {
|
|
33
|
+
return routeStorage.getStore();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 在指定路由上下文中运行函数
|
|
37
|
+
*/
|
|
38
|
+
function runWithRoute(route, type, fn) {
|
|
39
|
+
return routeStorage.run({ route, type }, fn);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 异步版本的 runWithRoute
|
|
43
|
+
*/
|
|
44
|
+
async function runWithRouteAsync(route, type, fn) {
|
|
45
|
+
return routeStorage.run({ route, type }, fn);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 提取 Next.js fetch 选项
|
|
49
|
+
*/
|
|
50
|
+
function extractNextJsOptions(init) {
|
|
51
|
+
if (!init)
|
|
52
|
+
return undefined;
|
|
53
|
+
const options = {};
|
|
54
|
+
// 提取 next 选项
|
|
55
|
+
if ('next' in init && init.next) {
|
|
56
|
+
const nextOpts = init.next;
|
|
57
|
+
options.next = {
|
|
58
|
+
revalidate: nextOpts.revalidate,
|
|
59
|
+
tags: nextOpts.tags,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// 提取 cache 选项
|
|
63
|
+
if ('cache' in init && init.cache) {
|
|
64
|
+
options.cache = init.cache;
|
|
65
|
+
}
|
|
66
|
+
return Object.keys(options).length > 0 ? options : undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 从请求中提取 Next.js 元数据
|
|
70
|
+
*/
|
|
71
|
+
function extractNextJsMetadata(init, responseHeaders) {
|
|
72
|
+
const metadata = {};
|
|
73
|
+
// 获取当前路由
|
|
74
|
+
const routeInfo = getCurrentRoute();
|
|
75
|
+
if (routeInfo) {
|
|
76
|
+
metadata.route = routeInfo.route;
|
|
77
|
+
metadata.requestType = routeInfo.type;
|
|
78
|
+
}
|
|
79
|
+
// 提取 fetch 选项
|
|
80
|
+
const nextOptions = extractNextJsOptions(init);
|
|
81
|
+
if (nextOptions?.next) {
|
|
82
|
+
metadata.revalidate = nextOptions.next.revalidate;
|
|
83
|
+
metadata.tags = nextOptions.next.tags;
|
|
84
|
+
}
|
|
85
|
+
// 从响应头提取缓存状态
|
|
86
|
+
if (responseHeaders) {
|
|
87
|
+
const cacheStatus = getHeader(responseHeaders, 'x-nextjs-cache');
|
|
88
|
+
if (cacheStatus) {
|
|
89
|
+
metadata.cacheStatus = cacheStatus;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return metadata;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 辅助函数:获取响应头
|
|
96
|
+
*/
|
|
97
|
+
function getHeader(headers, name) {
|
|
98
|
+
if (headers instanceof Headers) {
|
|
99
|
+
return headers.get(name) ?? undefined;
|
|
100
|
+
}
|
|
101
|
+
return headers[name] || headers[name.toLowerCase()];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 创建 Next.js instrumentation 配置
|
|
105
|
+
*/
|
|
106
|
+
function createInstrumentationConfig() {
|
|
107
|
+
return `// instrumentation.ts
|
|
108
|
+
// 将此文件放在 Next.js 项目根目录
|
|
109
|
+
|
|
110
|
+
export async function register() {
|
|
111
|
+
// 仅在服务端运行
|
|
112
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
113
|
+
// 动态导入以避免客户端打包
|
|
114
|
+
const { install } = await import('node-network-devtools');
|
|
115
|
+
await install();
|
|
116
|
+
console.log('[node-network-devtools] 已在 Next.js 服务端初始化');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Next.js 适配器对象
|
|
123
|
+
*/
|
|
124
|
+
exports.NextJsAdapter = {
|
|
125
|
+
isNextJsEnvironment,
|
|
126
|
+
getCurrentRoute,
|
|
127
|
+
runWithRoute,
|
|
128
|
+
runWithRouteAsync,
|
|
129
|
+
extractNextJsOptions,
|
|
130
|
+
extractNextJsMetadata,
|
|
131
|
+
createInstrumentationConfig,
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=nextjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../../../src/adapters/nextjs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 配置管理模块
|
|
4
|
+
*
|
|
5
|
+
* 支持通过环境变量和编程方式配置工具行为
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.getConfig = getConfig;
|
|
9
|
+
exports.setConfig = setConfig;
|
|
10
|
+
exports.resetConfig = resetConfig;
|
|
11
|
+
exports.getDefaultConfig = getDefaultConfig;
|
|
12
|
+
/**
|
|
13
|
+
* 默认配置
|
|
14
|
+
*/
|
|
15
|
+
const defaultConfig = {
|
|
16
|
+
maxRequests: 1000,
|
|
17
|
+
maxBodySize: 1024 * 1024, // 1MB
|
|
18
|
+
interceptHttp: true,
|
|
19
|
+
interceptUndici: true,
|
|
20
|
+
ignoreUrls: [],
|
|
21
|
+
redactHeaders: ['authorization', 'cookie'],
|
|
22
|
+
disableBodyCapture: false,
|
|
23
|
+
// GUI 配置默认值
|
|
24
|
+
guiEnabled: true,
|
|
25
|
+
guiPort: 'auto',
|
|
26
|
+
wsPort: 'auto',
|
|
27
|
+
guiHost: '127.0.0.1',
|
|
28
|
+
autoOpen: true,
|
|
29
|
+
// 浏览器窗口默认配置
|
|
30
|
+
browserWindowSize: {
|
|
31
|
+
width: 800,
|
|
32
|
+
height: 600,
|
|
33
|
+
},
|
|
34
|
+
browserWindowTitle: 'Node Network DevTools',
|
|
35
|
+
browserPath: undefined,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 当前配置(合并后的结果)
|
|
39
|
+
*/
|
|
40
|
+
let currentConfig = { ...defaultConfig };
|
|
41
|
+
/**
|
|
42
|
+
* 从环境变量解析布尔值
|
|
43
|
+
*/
|
|
44
|
+
function parseEnvBoolean(value, defaultValue) {
|
|
45
|
+
if (value === undefined)
|
|
46
|
+
return defaultValue;
|
|
47
|
+
const lower = value.toLowerCase();
|
|
48
|
+
if (lower === 'true' || lower === '1' || lower === 'yes')
|
|
49
|
+
return true;
|
|
50
|
+
if (lower === 'false' || lower === '0' || lower === 'no')
|
|
51
|
+
return false;
|
|
52
|
+
return defaultValue;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 从环境变量解析数字
|
|
56
|
+
*/
|
|
57
|
+
function parseEnvNumber(value, defaultValue) {
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return defaultValue;
|
|
60
|
+
const num = parseInt(value, 10);
|
|
61
|
+
return isNaN(num) ? defaultValue : num;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 从环境变量解析字符串数组(逗号分隔)
|
|
65
|
+
*/
|
|
66
|
+
function parseEnvStringArray(value, defaultValue) {
|
|
67
|
+
if (value === undefined || value.trim() === '')
|
|
68
|
+
return defaultValue;
|
|
69
|
+
return value.split(',').map(s => s.trim().toLowerCase()).filter(s => s.length > 0);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 从环境变量解析端口(支持 'auto' 或数字)
|
|
73
|
+
*/
|
|
74
|
+
function parseEnvPort(value, defaultValue) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return defaultValue;
|
|
77
|
+
if (value.toLowerCase() === 'auto')
|
|
78
|
+
return 'auto';
|
|
79
|
+
const num = parseInt(value, 10);
|
|
80
|
+
return isNaN(num) ? defaultValue : num;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 从环境变量加载配置
|
|
84
|
+
*/
|
|
85
|
+
function loadFromEnv() {
|
|
86
|
+
const env = process.env;
|
|
87
|
+
return {
|
|
88
|
+
maxRequests: parseEnvNumber(env.NND_MAX_REQUESTS, defaultConfig.maxRequests),
|
|
89
|
+
maxBodySize: parseEnvNumber(env.NND_MAX_BODY_SIZE, defaultConfig.maxBodySize),
|
|
90
|
+
interceptHttp: parseEnvBoolean(env.NND_INTERCEPT_HTTP, defaultConfig.interceptHttp),
|
|
91
|
+
interceptUndici: parseEnvBoolean(env.NND_INTERCEPT_UNDICI, defaultConfig.interceptUndici),
|
|
92
|
+
redactHeaders: parseEnvStringArray(env.NND_REDACT_HEADERS, defaultConfig.redactHeaders),
|
|
93
|
+
disableBodyCapture: parseEnvBoolean(env.NND_DISABLE_BODY_CAPTURE, defaultConfig.disableBodyCapture),
|
|
94
|
+
// GUI 配置
|
|
95
|
+
guiEnabled: parseEnvBoolean(env.NND_GUI_ENABLED, defaultConfig.guiEnabled),
|
|
96
|
+
guiPort: parseEnvPort(env.NND_GUI_PORT, defaultConfig.guiPort),
|
|
97
|
+
wsPort: parseEnvPort(env.NND_WS_PORT, defaultConfig.wsPort),
|
|
98
|
+
guiHost: env.NND_GUI_HOST || defaultConfig.guiHost,
|
|
99
|
+
autoOpen: parseEnvBoolean(env.NND_AUTO_OPEN, defaultConfig.autoOpen),
|
|
100
|
+
// 浏览器窗口配置
|
|
101
|
+
browserWindowSize: {
|
|
102
|
+
width: parseEnvNumber(env.NND_BROWSER_WIDTH, defaultConfig.browserWindowSize?.width ?? 800),
|
|
103
|
+
height: parseEnvNumber(env.NND_BROWSER_HEIGHT, defaultConfig.browserWindowSize?.height ?? 600),
|
|
104
|
+
},
|
|
105
|
+
browserWindowTitle: env.NND_BROWSER_TITLE || defaultConfig.browserWindowTitle,
|
|
106
|
+
browserPath: env.NND_BROWSER_PATH,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 初始化配置(合并默认值和环境变量)
|
|
111
|
+
*/
|
|
112
|
+
function initConfig() {
|
|
113
|
+
const envConfig = loadFromEnv();
|
|
114
|
+
return {
|
|
115
|
+
...defaultConfig,
|
|
116
|
+
...envConfig,
|
|
117
|
+
// ignoreUrls 需要特殊处理,环境变量不支持正则
|
|
118
|
+
ignoreUrls: defaultConfig.ignoreUrls,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// 初始化时加载配置
|
|
122
|
+
currentConfig = initConfig();
|
|
123
|
+
/**
|
|
124
|
+
* 获取当前配置
|
|
125
|
+
*/
|
|
126
|
+
function getConfig() {
|
|
127
|
+
return currentConfig;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 设置配置(合并到当前配置)
|
|
131
|
+
*/
|
|
132
|
+
function setConfig(config) {
|
|
133
|
+
currentConfig = {
|
|
134
|
+
...currentConfig,
|
|
135
|
+
...config,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 重置配置为默认值
|
|
140
|
+
*/
|
|
141
|
+
function resetConfig() {
|
|
142
|
+
currentConfig = initConfig();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 获取默认配置(用于测试)
|
|
146
|
+
*/
|
|
147
|
+
function getDefaultConfig() {
|
|
148
|
+
return defaultConfig;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|