@mt0926/node-network-devtools 0.1.0
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/BUILD.md +204 -0
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/README.zh-CN.md +310 -0
- package/dist/esm/adapters/nextjs.js +123 -0
- package/dist/esm/adapters/nextjs.js.map +1 -0
- package/dist/esm/cdp/cdp-bridge.js +312 -0
- package/dist/esm/cdp/cdp-bridge.js.map +1 -0
- package/dist/esm/cli.js +203 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/config.js +136 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/context/context-manager.js +126 -0
- package/dist/esm/context/context-manager.js.map +1 -0
- package/dist/esm/gui/browser-launcher.js +165 -0
- package/dist/esm/gui/browser-launcher.js.map +1 -0
- package/dist/esm/gui/event-bridge.js +192 -0
- package/dist/esm/gui/event-bridge.js.map +1 -0
- package/dist/esm/gui/port-utils.js +80 -0
- package/dist/esm/gui/port-utils.js.map +1 -0
- package/dist/esm/gui/server.js +227 -0
- package/dist/esm/gui/server.js.map +1 -0
- package/dist/esm/gui/websocket-hub.js +326 -0
- package/dist/esm/gui/websocket-hub.js.map +1 -0
- package/dist/esm/index.js +90 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/interceptors/http-patcher.js +203 -0
- package/dist/esm/interceptors/http-patcher.js.map +1 -0
- package/dist/esm/interceptors/undici-patcher.js +324 -0
- package/dist/esm/interceptors/undici-patcher.js.map +1 -0
- package/dist/esm/register.js +132 -0
- package/dist/esm/register.js.map +1 -0
- package/dist/esm/store/ring-buffer.js +236 -0
- package/dist/esm/store/ring-buffer.js.map +1 -0
- package/dist/esm/test-setup.js +7 -0
- package/dist/esm/test-setup.js.map +1 -0
- package/dist/gui/assets/index.css +1 -0
- package/dist/gui/assets/index.js +40 -0
- package/dist/gui/index.html +14 -0
- package/dist/types/adapters/nextjs.d.ts +80 -0
- package/dist/types/adapters/nextjs.d.ts.map +1 -0
- package/dist/types/cdp/cdp-bridge.d.ts +86 -0
- package/dist/types/cdp/cdp-bridge.d.ts.map +1 -0
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/config.d.ts +57 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/context/context-manager.d.ts +96 -0
- package/dist/types/context/context-manager.d.ts.map +1 -0
- package/dist/types/gui/browser-launcher.d.ts +52 -0
- package/dist/types/gui/browser-launcher.d.ts.map +1 -0
- package/dist/types/gui/event-bridge.d.ts +36 -0
- package/dist/types/gui/event-bridge.d.ts.map +1 -0
- package/dist/types/gui/port-utils.d.ts +25 -0
- package/dist/types/gui/port-utils.d.ts.map +1 -0
- package/dist/types/gui/server.d.ts +50 -0
- package/dist/types/gui/server.d.ts.map +1 -0
- package/dist/types/gui/websocket-hub.d.ts +67 -0
- package/dist/types/gui/websocket-hub.d.ts.map +1 -0
- package/dist/types/index.d.ts +44 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/interceptors/http-patcher.d.ts +32 -0
- package/dist/types/interceptors/http-patcher.d.ts.map +1 -0
- package/dist/types/interceptors/undici-patcher.d.ts +37 -0
- package/dist/types/interceptors/undici-patcher.d.ts.map +1 -0
- package/dist/types/register.d.ts +18 -0
- package/dist/types/register.d.ts.map +1 -0
- package/dist/types/store/ring-buffer.d.ts +148 -0
- package/dist/types/store/ring-buffer.d.ts.map +1 -0
- package/dist/types/test-setup.d.ts +7 -0
- package/dist/types/test-setup.d.ts.map +1 -0
- package/package.json +103 -0
- package/templates/instrumentation.ts +32 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上下文管理器
|
|
3
|
+
*
|
|
4
|
+
* 使用 AsyncLocalStorage 实现请求追踪上下文
|
|
5
|
+
* 支持跨异步边界传递 TraceID
|
|
6
|
+
*/
|
|
7
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
|
+
import { nanoid } from 'nanoid';
|
|
9
|
+
// AsyncLocalStorage 实例
|
|
10
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
11
|
+
/**
|
|
12
|
+
* 生成唯一的 TraceID
|
|
13
|
+
*/
|
|
14
|
+
export function generateTraceId() {
|
|
15
|
+
return `trace_${nanoid(16)}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 获取当前追踪上下文
|
|
19
|
+
*/
|
|
20
|
+
export function getCurrentContext() {
|
|
21
|
+
return asyncLocalStorage.getStore();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 获取当前 TraceID
|
|
25
|
+
*/
|
|
26
|
+
export function getCurrentTraceId() {
|
|
27
|
+
return asyncLocalStorage.getStore()?.traceId;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 开始新的追踪
|
|
31
|
+
*
|
|
32
|
+
* @param traceId 可选的 TraceID,不提供则自动生成
|
|
33
|
+
* @param metadata 可选的元数据
|
|
34
|
+
* @returns 新的追踪上下文
|
|
35
|
+
*/
|
|
36
|
+
export function startTrace(traceId, metadata) {
|
|
37
|
+
const currentContext = getCurrentContext();
|
|
38
|
+
const context = {
|
|
39
|
+
traceId: traceId || generateTraceId(),
|
|
40
|
+
parentTraceId: currentContext?.traceId,
|
|
41
|
+
startTime: Date.now(),
|
|
42
|
+
metadata,
|
|
43
|
+
};
|
|
44
|
+
return context;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 在追踪上下文中运行函数
|
|
48
|
+
*
|
|
49
|
+
* @param context 追踪上下文
|
|
50
|
+
* @param fn 要运行的函数
|
|
51
|
+
* @returns 函数的返回值
|
|
52
|
+
*/
|
|
53
|
+
export function runWithContext(context, fn) {
|
|
54
|
+
return asyncLocalStorage.run(context, fn);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 在新的追踪上下文中运行函数
|
|
58
|
+
*
|
|
59
|
+
* @param fn 要运行的函数
|
|
60
|
+
* @param traceId 可选的 TraceID
|
|
61
|
+
* @param metadata 可选的元数据
|
|
62
|
+
* @returns 函数的返回值
|
|
63
|
+
*/
|
|
64
|
+
export function runWithTrace(fn, traceId, metadata) {
|
|
65
|
+
const context = startTrace(traceId, metadata);
|
|
66
|
+
return runWithContext(context, fn);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 在新的追踪上下文中运行异步函数
|
|
70
|
+
*
|
|
71
|
+
* @param fn 要运行的异步函数
|
|
72
|
+
* @param traceId 可选的 TraceID
|
|
73
|
+
* @param metadata 可选的元数据
|
|
74
|
+
* @returns Promise
|
|
75
|
+
*/
|
|
76
|
+
export async function runWithTraceAsync(fn, traceId, metadata) {
|
|
77
|
+
const context = startTrace(traceId, metadata);
|
|
78
|
+
return asyncLocalStorage.run(context, fn);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 更新当前上下文的元数据
|
|
82
|
+
*
|
|
83
|
+
* @param metadata 要合并的元数据
|
|
84
|
+
*/
|
|
85
|
+
export function updateContextMetadata(metadata) {
|
|
86
|
+
const context = getCurrentContext();
|
|
87
|
+
if (context) {
|
|
88
|
+
context.metadata = {
|
|
89
|
+
...context.metadata,
|
|
90
|
+
...metadata,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 获取追踪持续时间(毫秒)
|
|
96
|
+
*/
|
|
97
|
+
export function getTraceDuration() {
|
|
98
|
+
const context = getCurrentContext();
|
|
99
|
+
if (context) {
|
|
100
|
+
return Date.now() - context.startTime;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 创建获取当前 TraceID 的回调函数
|
|
106
|
+
* 用于传递给拦截器
|
|
107
|
+
*/
|
|
108
|
+
export function createTraceIdGetter() {
|
|
109
|
+
return getCurrentTraceId;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* ContextManager 对象
|
|
113
|
+
*/
|
|
114
|
+
export const ContextManager = {
|
|
115
|
+
generateTraceId,
|
|
116
|
+
getCurrentContext,
|
|
117
|
+
getCurrentTraceId,
|
|
118
|
+
startTrace,
|
|
119
|
+
runWithContext,
|
|
120
|
+
runWithTrace,
|
|
121
|
+
runWithTraceAsync,
|
|
122
|
+
updateContextMetadata,
|
|
123
|
+
getTraceDuration,
|
|
124
|
+
createTraceIdGetter,
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=context-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-manager.js","sourceRoot":"","sources":["../../../src/context/context-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAgBhC,uBAAuB;AACvB,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,EAAgB,CAAC;AAEhE;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,SAAS,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,iBAAiB,CAAC,QAAQ,EAAE,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,iBAAiB,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,OAAgB,EAAE,QAAkC;IAC7E,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;IAE3C,MAAM,OAAO,GAAiB;QAC5B,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE;QACrC,aAAa,EAAE,cAAc,EAAE,OAAO;QACtC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,QAAQ;KACT,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAI,OAAqB,EAAE,EAAW;IAClE,OAAO,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,EAAW,EACX,OAAgB,EAChB,QAAkC;IAElC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC9C,OAAO,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAoB,EACpB,OAAgB,EAChB,QAAkC;IAElC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC9C,OAAO,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAiC;IACrE,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,QAAQ,GAAG;YACjB,GAAG,OAAO,CAAC,QAAQ;YACnB,GAAG,QAAQ;SACZ,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IACxC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,UAAU;IACV,cAAc;IACd,YAAY;IACZ,iBAAiB;IACjB,qBAAqB;IACrB,gBAAgB;IAChB,mBAAmB;CACpB,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器启动器模块
|
|
3
|
+
*
|
|
4
|
+
* 支持使用系统默认浏览器或 Puppeteer 打开 GUI
|
|
5
|
+
*/
|
|
6
|
+
import { getConfig } from '../config.js';
|
|
7
|
+
/**
|
|
8
|
+
* 浏览器启动器实现
|
|
9
|
+
*/
|
|
10
|
+
class BrowserLauncherImpl {
|
|
11
|
+
puppeteerBrowser = null;
|
|
12
|
+
puppeteerPage = null;
|
|
13
|
+
isOpened = false;
|
|
14
|
+
/**
|
|
15
|
+
* 打开浏览器
|
|
16
|
+
*/
|
|
17
|
+
async open(url) {
|
|
18
|
+
const config = getConfig();
|
|
19
|
+
if (config.usePuppeteer) {
|
|
20
|
+
await this.openWithPuppeteer(url);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
await this.openWithDefaultBrowser(url);
|
|
24
|
+
}
|
|
25
|
+
this.isOpened = true;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 使用系统默认浏览器打开
|
|
29
|
+
*/
|
|
30
|
+
async openWithDefaultBrowser(url) {
|
|
31
|
+
try {
|
|
32
|
+
// 动态导入 open 包
|
|
33
|
+
const openModule = await import('open');
|
|
34
|
+
const open = openModule.default;
|
|
35
|
+
await open(url);
|
|
36
|
+
console.log(`[node-network-devtools] 已在默认浏览器中打开: ${url}`);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error('[node-network-devtools] 无法打开默认浏览器:', err);
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 使用 Puppeteer 打开
|
|
45
|
+
*/
|
|
46
|
+
async openWithPuppeteer(url) {
|
|
47
|
+
try {
|
|
48
|
+
// 动态导入 puppeteer(可选依赖)
|
|
49
|
+
const puppeteer = await import('puppeteer');
|
|
50
|
+
// 启动浏览器
|
|
51
|
+
this.puppeteerBrowser = await puppeteer.default.launch({
|
|
52
|
+
headless: false,
|
|
53
|
+
defaultViewport: null,
|
|
54
|
+
args: [
|
|
55
|
+
'--no-sandbox',
|
|
56
|
+
'--disable-setuid-sandbox',
|
|
57
|
+
`--window-size=1200,800`,
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
// 创建新页面
|
|
61
|
+
const browser = this.puppeteerBrowser;
|
|
62
|
+
this.puppeteerPage = await browser.newPage();
|
|
63
|
+
// 导航到 URL
|
|
64
|
+
const page = this.puppeteerPage;
|
|
65
|
+
await page.goto(url);
|
|
66
|
+
console.log(`[node-network-devtools] 已使用 Puppeteer 打开: ${url}`);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
if (err.code === 'MODULE_NOT_FOUND') {
|
|
70
|
+
console.error('[node-network-devtools] Puppeteer 未安装,请运行: pnpm add puppeteer');
|
|
71
|
+
// 回退到默认浏览器
|
|
72
|
+
console.log('[node-network-devtools] 回退到默认浏览器...');
|
|
73
|
+
await this.openWithDefaultBrowser(url);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error('[node-network-devtools] Puppeteer 启动失败:', err);
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 关闭浏览器
|
|
83
|
+
*/
|
|
84
|
+
async close() {
|
|
85
|
+
if (this.puppeteerBrowser) {
|
|
86
|
+
try {
|
|
87
|
+
const browser = this.puppeteerBrowser;
|
|
88
|
+
await browser.close();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// 忽略关闭错误
|
|
92
|
+
}
|
|
93
|
+
this.puppeteerBrowser = null;
|
|
94
|
+
this.puppeteerPage = null;
|
|
95
|
+
}
|
|
96
|
+
this.isOpened = false;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 检查浏览器是否已打开
|
|
100
|
+
*/
|
|
101
|
+
isOpen() {
|
|
102
|
+
return this.isOpened;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// 全局单例
|
|
106
|
+
let globalLauncher = null;
|
|
107
|
+
/**
|
|
108
|
+
* 获取全局浏览器启动器实例
|
|
109
|
+
*/
|
|
110
|
+
export function getBrowserLauncher() {
|
|
111
|
+
if (!globalLauncher) {
|
|
112
|
+
globalLauncher = new BrowserLauncherImpl();
|
|
113
|
+
}
|
|
114
|
+
return globalLauncher;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 重置全局浏览器启动器(用于测试)
|
|
118
|
+
*/
|
|
119
|
+
export async function resetBrowserLauncher() {
|
|
120
|
+
if (globalLauncher) {
|
|
121
|
+
await globalLauncher.close();
|
|
122
|
+
}
|
|
123
|
+
globalLauncher = null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 创建新的浏览器启动器实例(用于测试)
|
|
127
|
+
*/
|
|
128
|
+
export function createBrowserLauncher() {
|
|
129
|
+
return new BrowserLauncherImpl();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 构建带 WebSocket 端口参数的 GUI URL
|
|
133
|
+
*/
|
|
134
|
+
export function buildGUIUrl(host, guiPort, wsPort) {
|
|
135
|
+
return `http://${host}:${guiPort}?wsPort=${wsPort}`;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 打开浏览器(便捷函数)
|
|
139
|
+
*/
|
|
140
|
+
export async function openBrowser(url, options) {
|
|
141
|
+
const launcher = getBrowserLauncher();
|
|
142
|
+
// 如果指定了 usePuppeteer,临时修改配置
|
|
143
|
+
if (options?.usePuppeteer !== undefined) {
|
|
144
|
+
const { setConfig, getConfig } = await import('../config.js');
|
|
145
|
+
const originalConfig = getConfig();
|
|
146
|
+
setConfig({ usePuppeteer: options.usePuppeteer });
|
|
147
|
+
try {
|
|
148
|
+
await launcher.open(url);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
setConfig({ usePuppeteer: originalConfig.usePuppeteer });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
await launcher.open(url);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 关闭浏览器(便捷函数)
|
|
160
|
+
*/
|
|
161
|
+
export async function closeBrowser() {
|
|
162
|
+
const launcher = getBrowserLauncher();
|
|
163
|
+
await launcher.close();
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=browser-launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-launcher.js","sourceRoot":"","sources":["../../../src/gui/browser-launcher.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAmBzC;;GAEG;AACH,MAAM,mBAAmB;IACf,gBAAgB,GAAY,IAAI,CAAC;IACjC,aAAa,GAAY,IAAI,CAAC;IAC9B,QAAQ,GAAY,KAAK,CAAC;IAElC;;OAEG;IACH,KAAK,CAAC,IAAI,CAAC,GAAW;QACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAE3B,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;IACvB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,sBAAsB,CAAC,GAAW;QAC9C,IAAI,CAAC;YACH,cAAc;YACd,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;YACxC,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC;YAChC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,uCAAuC,GAAG,EAAE,CAAC,CAAC;QAC5D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;YACzD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB,CAAC,GAAW;QACzC,IAAI,CAAC;YACH,uBAAuB;YACvB,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YAE5C,QAAQ;YACR,IAAI,CAAC,gBAAgB,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC;gBACrD,QAAQ,EAAE,KAAK;gBACf,eAAe,EAAE,IAAI;gBACrB,IAAI,EAAE;oBACJ,cAAc;oBACd,0BAA0B;oBAC1B,wBAAwB;iBACzB;aACF,CAAC,CAAC;YAEH,QAAQ;YACR,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAuD,CAAC;YAC7E,IAAI,CAAC,aAAa,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YAE7C,UAAU;YACV,MAAM,IAAI,GAAG,IAAI,CAAC,aAAyD,CAAC;YAC5E,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAErB,OAAO,CAAC,GAAG,CAAC,6CAA6C,GAAG,EAAE,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBAC/D,OAAO,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;gBAC/E,WAAW;gBACX,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;gBACnD,MAAM,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,GAAG,CAAC,CAAC;gBAC9D,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAkD,CAAC;gBACxE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;YAC7B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF;AAED,OAAO;AACP,IAAI,cAAc,GAA+B,IAAI,CAAC;AAEtD;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAC7C,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IACD,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,IAAI,mBAAmB,EAAE,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAe,EAAE,MAAc;IACvE,OAAO,UAAU,IAAI,IAAI,OAAO,WAAW,MAAM,EAAE,CAAC;AACtD,CAAC;AAUD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,OAAgC;IAC7E,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IAEtC,4BAA4B;IAC5B,IAAI,OAAO,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QAC9D,MAAM,cAAc,GAAG,SAAS,EAAE,CAAC;QACnC,SAAS,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,EAAE,YAAY,EAAE,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Bridge 模块
|
|
3
|
+
*
|
|
4
|
+
* 连接请求拦截器和 WebSocket Hub,实现事件转发
|
|
5
|
+
* 支持暂停/恢复和清空功能
|
|
6
|
+
*/
|
|
7
|
+
import { getRequestStore } from '../store/ring-buffer.js';
|
|
8
|
+
import { getWebSocketHub, createRequestStartMessage, createRequestCompleteMessage, createRequestErrorMessage, createInitialDataMessage, createClearMessage, } from './websocket-hub.js';
|
|
9
|
+
/**
|
|
10
|
+
* Event Bridge 实现
|
|
11
|
+
*/
|
|
12
|
+
class EventBridgeImpl {
|
|
13
|
+
paused = false;
|
|
14
|
+
running = false;
|
|
15
|
+
wsHub;
|
|
16
|
+
pendingEvents = [];
|
|
17
|
+
constructor(wsHub) {
|
|
18
|
+
this.wsHub = wsHub ?? getWebSocketHub();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 启动 Event Bridge
|
|
22
|
+
* 注册 WebSocket 客户端连接回调,发送初始数据
|
|
23
|
+
*/
|
|
24
|
+
start() {
|
|
25
|
+
if (this.running)
|
|
26
|
+
return;
|
|
27
|
+
this.running = true;
|
|
28
|
+
// 当新客户端连接时,发送初始数据
|
|
29
|
+
this.wsHub.onClientConnect((clientId) => {
|
|
30
|
+
const store = getRequestStore();
|
|
31
|
+
const requests = store.getAll();
|
|
32
|
+
const message = createInitialDataMessage(requests);
|
|
33
|
+
this.wsHub.send(clientId, message);
|
|
34
|
+
});
|
|
35
|
+
// 处理客户端消息(控制命令)
|
|
36
|
+
this.wsHub.onClientMessage((_clientId, message) => {
|
|
37
|
+
this.handleClientMessage(message);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 停止 Event Bridge
|
|
42
|
+
*/
|
|
43
|
+
stop() {
|
|
44
|
+
this.running = false;
|
|
45
|
+
this.pendingEvents = [];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 检查是否正在运行
|
|
49
|
+
*/
|
|
50
|
+
isRunning() {
|
|
51
|
+
return this.running;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 处理客户端消息
|
|
55
|
+
*/
|
|
56
|
+
handleClientMessage(message) {
|
|
57
|
+
switch (message.type) {
|
|
58
|
+
case 'control:pause':
|
|
59
|
+
this.pause();
|
|
60
|
+
break;
|
|
61
|
+
case 'control:resume':
|
|
62
|
+
this.resume();
|
|
63
|
+
break;
|
|
64
|
+
case 'requests:clear':
|
|
65
|
+
this.clear();
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 发送请求开始事件
|
|
71
|
+
*/
|
|
72
|
+
emitRequestStart(request) {
|
|
73
|
+
if (!this.running)
|
|
74
|
+
return;
|
|
75
|
+
const message = createRequestStartMessage(request);
|
|
76
|
+
if (this.paused) {
|
|
77
|
+
// 暂停时缓存事件
|
|
78
|
+
this.pendingEvents.push(message);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.wsHub.broadcast(message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 发送请求完成事件
|
|
86
|
+
*/
|
|
87
|
+
emitRequestComplete(requestId, response) {
|
|
88
|
+
if (!this.running)
|
|
89
|
+
return;
|
|
90
|
+
const message = createRequestCompleteMessage(requestId, response);
|
|
91
|
+
if (this.paused) {
|
|
92
|
+
// 暂停时缓存事件
|
|
93
|
+
this.pendingEvents.push(message);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.wsHub.broadcast(message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 发送请求错误事件
|
|
101
|
+
*/
|
|
102
|
+
emitRequestError(requestId, error) {
|
|
103
|
+
if (!this.running)
|
|
104
|
+
return;
|
|
105
|
+
const message = createRequestErrorMessage(requestId, error);
|
|
106
|
+
if (this.paused) {
|
|
107
|
+
// 暂停时缓存事件
|
|
108
|
+
this.pendingEvents.push(message);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.wsHub.broadcast(message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 暂停事件广播
|
|
116
|
+
*/
|
|
117
|
+
pause() {
|
|
118
|
+
if (this.paused)
|
|
119
|
+
return;
|
|
120
|
+
this.paused = true;
|
|
121
|
+
// 广播暂停状态
|
|
122
|
+
this.wsHub.broadcast({
|
|
123
|
+
type: 'control:pause',
|
|
124
|
+
payload: null,
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 恢复事件广播
|
|
130
|
+
*/
|
|
131
|
+
resume() {
|
|
132
|
+
if (!this.paused)
|
|
133
|
+
return;
|
|
134
|
+
this.paused = false;
|
|
135
|
+
// 广播恢复状态
|
|
136
|
+
this.wsHub.broadcast({
|
|
137
|
+
type: 'control:resume',
|
|
138
|
+
payload: null,
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
});
|
|
141
|
+
// 发送暂停期间缓存的事件
|
|
142
|
+
for (const event of this.pendingEvents) {
|
|
143
|
+
this.wsHub.broadcast(event);
|
|
144
|
+
}
|
|
145
|
+
this.pendingEvents = [];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 检查是否暂停
|
|
149
|
+
*/
|
|
150
|
+
isPaused() {
|
|
151
|
+
return this.paused;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 清空请求
|
|
155
|
+
*/
|
|
156
|
+
clear() {
|
|
157
|
+
// 清空存储
|
|
158
|
+
const store = getRequestStore();
|
|
159
|
+
store.clear();
|
|
160
|
+
// 清空缓存的事件
|
|
161
|
+
this.pendingEvents = [];
|
|
162
|
+
// 广播清空消息
|
|
163
|
+
this.wsHub.broadcast(createClearMessage());
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// 全局单例
|
|
167
|
+
let globalBridge = null;
|
|
168
|
+
/**
|
|
169
|
+
* 获取全局 Event Bridge 实例
|
|
170
|
+
*/
|
|
171
|
+
export function getEventBridge() {
|
|
172
|
+
if (!globalBridge) {
|
|
173
|
+
globalBridge = new EventBridgeImpl();
|
|
174
|
+
}
|
|
175
|
+
return globalBridge;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 重置全局 Event Bridge(用于测试)
|
|
179
|
+
*/
|
|
180
|
+
export function resetEventBridge() {
|
|
181
|
+
if (globalBridge) {
|
|
182
|
+
globalBridge.stop();
|
|
183
|
+
}
|
|
184
|
+
globalBridge = null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 创建新的 Event Bridge 实例(用于测试)
|
|
188
|
+
*/
|
|
189
|
+
export function createEventBridge(wsHub) {
|
|
190
|
+
return new EventBridgeImpl(wsHub);
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=event-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-bridge.js","sourceRoot":"","sources":["../../../src/gui/event-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 端口工具模块
|
|
3
|
+
*
|
|
4
|
+
* 提供获取可用端口的功能
|
|
5
|
+
*/
|
|
6
|
+
import { createServer } from 'node:net';
|
|
7
|
+
/**
|
|
8
|
+
* 检查端口是否可用
|
|
9
|
+
*/
|
|
10
|
+
export function isPortAvailable(port, host = '127.0.0.1') {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const server = createServer();
|
|
13
|
+
server.once('error', () => {
|
|
14
|
+
resolve(false);
|
|
15
|
+
});
|
|
16
|
+
server.once('listening', () => {
|
|
17
|
+
server.close(() => {
|
|
18
|
+
resolve(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
server.listen(port, host);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 获取可用端口
|
|
26
|
+
*
|
|
27
|
+
* @param preferredPort 首选端口,如果为 'auto' 则自动获取
|
|
28
|
+
* @param host 监听地址
|
|
29
|
+
* @param maxAttempts 最大尝试次数
|
|
30
|
+
* @returns 可用端口号
|
|
31
|
+
*/
|
|
32
|
+
export async function getAvailablePort(preferredPort = 'auto', host = '127.0.0.1', maxAttempts = 10) {
|
|
33
|
+
// 如果是 'auto',从随机端口开始
|
|
34
|
+
if (preferredPort === 'auto') {
|
|
35
|
+
return getRandomAvailablePort(host);
|
|
36
|
+
}
|
|
37
|
+
// 尝试首选端口
|
|
38
|
+
if (await isPortAvailable(preferredPort, host)) {
|
|
39
|
+
return preferredPort;
|
|
40
|
+
}
|
|
41
|
+
// 首选端口不可用,尝试后续端口
|
|
42
|
+
for (let i = 1; i < maxAttempts; i++) {
|
|
43
|
+
const port = preferredPort + i;
|
|
44
|
+
if (await isPortAvailable(port, host)) {
|
|
45
|
+
return port;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// 所有尝试都失败,获取随机可用端口
|
|
49
|
+
return getRandomAvailablePort(host);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 获取随机可用端口
|
|
53
|
+
*
|
|
54
|
+
* 通过让系统分配端口来获取可用端口
|
|
55
|
+
*/
|
|
56
|
+
export function getRandomAvailablePort(host = '127.0.0.1') {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const server = createServer();
|
|
59
|
+
server.once('error', (err) => {
|
|
60
|
+
reject(err);
|
|
61
|
+
});
|
|
62
|
+
server.once('listening', () => {
|
|
63
|
+
const address = server.address();
|
|
64
|
+
if (address && typeof address === 'object') {
|
|
65
|
+
const port = address.port;
|
|
66
|
+
server.close(() => {
|
|
67
|
+
resolve(port);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
server.close(() => {
|
|
72
|
+
reject(new Error('无法获取端口'));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// 端口 0 让系统自动分配可用端口
|
|
77
|
+
server.listen(0, host);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=port-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-utils.js","sourceRoot":"","sources":["../../../src/gui/port-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,OAAe,WAAW;IACtE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAE9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACxB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;gBAChB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,gBAAiC,MAAM,EACvC,OAAe,WAAW,EAC1B,cAAsB,EAAE;IAExB,qBAAqB;IACrB,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;QAC7B,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,SAAS;IACT,IAAI,MAAM,eAAe,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC;QAC/C,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,iBAAiB;IACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,aAAa,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe,WAAW;IAC/D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAE9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC3B,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;gBAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;oBAChB,OAAO,CAAC,IAAI,CAAC,CAAC;gBAChB,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;oBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC9B,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,mBAAmB;QACnB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC"}
|