@mt0926/node-network-devtools 0.2.0 → 0.3.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.
Files changed (51) hide show
  1. package/README.md +96 -1
  2. package/dist/cjs/adapters/axios.js +2 -0
  3. package/dist/cjs/adapters/axios.js.map +1 -0
  4. package/dist/cjs/adapters/nextjs.js +133 -0
  5. package/dist/cjs/adapters/nextjs.js.map +1 -0
  6. package/dist/cjs/cli.js +223 -0
  7. package/dist/cjs/cli.js.map +1 -0
  8. package/dist/cjs/config.js +150 -0
  9. package/dist/cjs/config.js.map +1 -0
  10. package/dist/cjs/context/context-manager.js +139 -0
  11. package/dist/cjs/context/context-manager.js.map +1 -0
  12. package/dist/cjs/gui/browser-detector.js +263 -0
  13. package/dist/cjs/gui/browser-detector.js.map +1 -0
  14. package/dist/cjs/gui/browser-launcher.js +365 -0
  15. package/dist/cjs/gui/browser-launcher.js.map +1 -0
  16. package/dist/cjs/gui/event-bridge.js +197 -0
  17. package/dist/cjs/gui/event-bridge.js.map +1 -0
  18. package/dist/cjs/gui/port-utils.js +85 -0
  19. package/dist/cjs/gui/port-utils.js.map +1 -0
  20. package/dist/cjs/gui/server.js +251 -0
  21. package/dist/cjs/gui/server.js.map +1 -0
  22. package/dist/cjs/gui/websocket-hub.js +336 -0
  23. package/dist/cjs/gui/websocket-hub.js.map +1 -0
  24. package/dist/cjs/index.js +170 -0
  25. package/dist/cjs/index.js.map +1 -0
  26. package/dist/cjs/interceptors/http-patcher.js +275 -0
  27. package/dist/cjs/interceptors/http-patcher.js.map +1 -0
  28. package/dist/cjs/interceptors/undici-patcher.js +362 -0
  29. package/dist/cjs/interceptors/undici-patcher.js.map +1 -0
  30. package/dist/cjs/package.json +3 -0
  31. package/dist/cjs/register.js +95 -0
  32. package/dist/cjs/register.js.map +1 -0
  33. package/dist/cjs/store/ring-buffer.js +241 -0
  34. package/dist/cjs/store/ring-buffer.js.map +1 -0
  35. package/dist/cjs/test-setup.js +8 -0
  36. package/dist/cjs/test-setup.js.map +1 -0
  37. package/dist/cjs/utils/module-compat.js +83 -0
  38. package/dist/cjs/utils/module-compat.js.map +1 -0
  39. package/dist/esm/cli.js +22 -4
  40. package/dist/esm/cli.js.map +1 -1
  41. package/dist/esm/gui/server.js +25 -6
  42. package/dist/esm/gui/server.js.map +1 -1
  43. package/dist/esm/interceptors/undici-patcher.js +9 -4
  44. package/dist/esm/interceptors/undici-patcher.js.map +1 -1
  45. package/dist/esm/utils/module-compat.js +78 -0
  46. package/dist/esm/utils/module-compat.js.map +1 -0
  47. package/dist/types/gui/server.d.ts.map +1 -1
  48. package/dist/types/interceptors/undici-patcher.d.ts.map +1 -1
  49. package/dist/types/utils/module-compat.d.ts +34 -0
  50. package/dist/types/utils/module-compat.d.ts.map +1 -0
  51. package/package.json +22 -8
@@ -0,0 +1,365 @@
1
+ "use strict";
2
+ /**
3
+ * 浏览器启动器模块
4
+ *
5
+ * 使用原生浏览器检测和启动机制打开 GUI
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.BrowserLaunchError = exports.BrowserNotFoundError = void 0;
9
+ exports.getBrowserLauncher = getBrowserLauncher;
10
+ exports.resetBrowserLauncher = resetBrowserLauncher;
11
+ exports.createBrowserLauncher = createBrowserLauncher;
12
+ exports.buildGUIUrl = buildGUIUrl;
13
+ exports.openBrowser = openBrowser;
14
+ exports.closeBrowser = closeBrowser;
15
+ exports.buildLaunchArgs = buildLaunchArgs;
16
+ exports.createUserDataDir = createUserDataDir;
17
+ const child_process_1 = require("child_process");
18
+ const os_1 = require("os");
19
+ const path_1 = require("path");
20
+ const nanoid_1 = require("nanoid");
21
+ const config_js_1 = require("../config.js");
22
+ const browser_detector_js_1 = require("./browser-detector.js");
23
+ /**
24
+ * 浏览器未检测到错误
25
+ *
26
+ * 当系统中未检测到任何支持的浏览器时抛出此错误
27
+ */
28
+ class BrowserNotFoundError extends Error {
29
+ constructor() {
30
+ super('未检测到已安装的浏览器');
31
+ this.name = 'BrowserNotFoundError';
32
+ // 保持原型链正确(TypeScript 继承 Error 的最佳实践)
33
+ Object.setPrototypeOf(this, BrowserNotFoundError.prototype);
34
+ }
35
+ }
36
+ exports.BrowserNotFoundError = BrowserNotFoundError;
37
+ /**
38
+ * 浏览器启动失败错误
39
+ *
40
+ * 当浏览器启动过程中发生错误时抛出此错误
41
+ */
42
+ class BrowserLaunchError extends Error {
43
+ cause;
44
+ /**
45
+ * @param message 错误消息
46
+ * @param cause 原始错误(可选)
47
+ */
48
+ constructor(message, cause) {
49
+ super(message);
50
+ this.cause = cause;
51
+ this.name = 'BrowserLaunchError';
52
+ // 保持原型链正确(TypeScript 继承 Error 的最佳实践)
53
+ Object.setPrototypeOf(this, BrowserLaunchError.prototype);
54
+ }
55
+ }
56
+ exports.BrowserLaunchError = BrowserLaunchError;
57
+ /**
58
+ * 浏览器启动器实现
59
+ */
60
+ class BrowserLauncherImpl {
61
+ isOpened = false;
62
+ /**
63
+ * 打开浏览器
64
+ *
65
+ * 执行以下步骤:
66
+ * 1. 调用 BrowserDetector.detect() 检测浏览器
67
+ * 2. 处理未找到浏览器的情况
68
+ * 3. 构建启动参数
69
+ * 4. 调用启动逻辑
70
+ * 5. 处理启动失败的情况
71
+ *
72
+ * @param url GUI 访问 URL
73
+ *
74
+ * @remarks
75
+ * 此方法不会抛出异常,所有错误都会被捕获并显示友好的错误消息。
76
+ * 这确保浏览器启动失败不会中断主进程。
77
+ *
78
+ * **验证需求:2.5, 4.1, 4.2, 4.3, 4.4, 4.5, 6.1, 6.4**
79
+ */
80
+ async open(url) {
81
+ try {
82
+ // 步骤 1: 调用 BrowserDetector.detect() 检测浏览器
83
+ const browserInfo = (0, browser_detector_js_1.detectBrowser)();
84
+ // 步骤 2: 处理未找到浏览器的情况
85
+ if (!browserInfo) {
86
+ // 浏览器未找到,显示友好提示(需求 6.1)
87
+ this.handleBrowserNotFound(url);
88
+ return;
89
+ }
90
+ // 获取配置
91
+ const config = (0, config_js_1.getConfig)();
92
+ const windowConfig = {
93
+ width: config.browserWindowSize?.width || 1280,
94
+ height: config.browserWindowSize?.height || 800,
95
+ title: config.browserWindowTitle || 'Node Network DevTools',
96
+ };
97
+ // 创建用户数据目录(需求 4.2)
98
+ const userDataDir = createUserDataDir();
99
+ // 步骤 3: 构建启动参数(需求 4.1)
100
+ const args = this.buildLaunchArgs({
101
+ url,
102
+ windowConfig,
103
+ userDataDir,
104
+ });
105
+ // 步骤 4: 调用启动逻辑(需求 4.3, 4.4, 4.5)
106
+ this.launchBrowser(browserInfo.path, args);
107
+ this.isOpened = true;
108
+ console.log(`[node-network-devtools] 已使用 ${browserInfo.name} 打开 GUI: ${url}`);
109
+ }
110
+ catch (err) {
111
+ // 步骤 5: 处理启动失败的情况(需求 6.4)
112
+ this.handleLaunchError(err, url);
113
+ }
114
+ }
115
+ /**
116
+ * 启动浏览器进程
117
+ *
118
+ * 使用 child_process.spawn() 启动浏览器,并配置为分离进程。
119
+ * 这样浏览器进程不会阻塞父进程,父进程退出后浏览器仍可继续运行。
120
+ *
121
+ * @param browserPath 浏览器可执行文件路径
122
+ * @param args 启动参数数组
123
+ * @throws {BrowserLaunchError} 当浏览器启动失败时抛出
124
+ *
125
+ * @remarks
126
+ * 进程配置:
127
+ * - detached: true - 进程分离,允许父进程退出(需求 4.3)
128
+ * - stdio: 'ignore' - 忽略标准输入输出,避免管道阻塞(需求 4.4)
129
+ * - child.unref() - 允许父进程退出而不等待子进程(需求 4.5)
130
+ *
131
+ * **验证需求:4.3, 4.4, 4.5**
132
+ */
133
+ launchBrowser(browserPath, args) {
134
+ try {
135
+ // 启动分离进程
136
+ const child = (0, child_process_1.spawn)(browserPath, args, {
137
+ detached: true, // 进程分离,允许父进程退出(需求 4.3)
138
+ stdio: 'ignore', // 忽略标准输入输出,避免管道阻塞(需求 4.4)
139
+ });
140
+ // 允许父进程退出而不等待子进程(需求 4.5)
141
+ child.unref();
142
+ // 监听错误事件(虽然 stdio: 'ignore',但 spawn 本身可能失败)
143
+ child.on('error', (err) => {
144
+ throw new BrowserLaunchError(`浏览器进程启动失败: ${err.message}`, err);
145
+ });
146
+ }
147
+ catch (err) {
148
+ // 将原始错误包装为 BrowserLaunchError
149
+ if (err instanceof BrowserLaunchError) {
150
+ throw err;
151
+ }
152
+ throw new BrowserLaunchError(`无法启动浏览器: ${err.message}`, err);
153
+ }
154
+ }
155
+ /**
156
+ * 处理浏览器未找到的情况
157
+ *
158
+ * 显示友好的错误消息,包含:
159
+ * - 支持的浏览器列表
160
+ * - GUI 访问 URL(需求 6.2)
161
+ * - 自定义浏览器路径设置说明
162
+ * - 浏览器安装指引链接(需求 6.3)
163
+ *
164
+ * @param guiUrl GUI 访问 URL
165
+ *
166
+ * **验证需求:6.1, 6.2, 6.3**
167
+ */
168
+ handleBrowserNotFound(guiUrl) {
169
+ console.error(`
170
+ [node-network-devtools] 未检测到已安装的浏览器
171
+
172
+ 支持的浏览器:
173
+ - Google Chrome
174
+ - Microsoft Edge
175
+ - Chromium
176
+
177
+ 请安装其中一个浏览器,或手动访问 GUI:
178
+ ${guiUrl}
179
+
180
+ 自定义浏览器路径:
181
+ export NND_BROWSER_PATH=/path/to/browser
182
+
183
+ 浏览器安装指引:
184
+ Chrome: https://www.google.com/chrome/
185
+ Edge: https://www.microsoft.com/edge
186
+ 更多信息: https://github.com/dong0926/node-network-devtools#readme
187
+ `);
188
+ }
189
+ /**
190
+ * 处理浏览器启动失败的情况
191
+ *
192
+ * 显示友好的错误消息,包含:
193
+ * - 错误信息(需求 6.4)
194
+ * - 可能的原因分析
195
+ * - 解决方案建议(需求 6.5)
196
+ * - 手动访问 URL 提示
197
+ *
198
+ * @param err 错误对象
199
+ * @param guiUrl GUI 访问 URL
200
+ *
201
+ * **验证需求:6.4, 6.5**
202
+ */
203
+ handleLaunchError(err, guiUrl) {
204
+ console.error(`
205
+ [node-network-devtools] 浏览器启动失败
206
+
207
+ 错误信息:${err.message}
208
+
209
+ 可能的原因:
210
+ 1. 浏览器路径无效
211
+ 2. 权限不足
212
+ 3. 系统资源不足
213
+
214
+ 解决方案:
215
+ - 手动访问 GUI: ${guiUrl}
216
+ - 设置 NND_AUTO_OPEN=false 禁用自动打开
217
+ - 检查浏览器是否正确安装
218
+ - 尝试设置自定义浏览器路径: export NND_BROWSER_PATH=/path/to/browser
219
+ `);
220
+ }
221
+ /**
222
+ * 构建浏览器启动参数
223
+ *
224
+ * @param options 启动选项
225
+ * @returns Chrome 命令行参数数组
226
+ */
227
+ buildLaunchArgs(options) {
228
+ const { url, windowConfig, userDataDir } = options;
229
+ const args = [
230
+ // App 模式(极简窗口,无地址栏和工具栏)
231
+ `--app=${url}`,
232
+ // 窗口大小
233
+ `--window-size=${windowConfig.width},${windowConfig.height}`,
234
+ ];
235
+ // 用户数据目录(如果提供)
236
+ if (userDataDir) {
237
+ args.push(`--user-data-dir=${userDataDir}`);
238
+ }
239
+ // 优化参数
240
+ args.push('--no-first-run', // 跳过首次运行向导
241
+ '--no-default-browser-check', // 跳过默认浏览器检查
242
+ '--disable-extensions', // 禁用扩展
243
+ '--disable-sync', // 禁用同步
244
+ '--disable-background-networking', // 禁用后台网络请求
245
+ '--disable-features=TranslateUI', // 禁用翻译 UI
246
+ '--disable-component-extensions-with-background-pages', '--disable-default-apps', // 禁用默认应用
247
+ '--mute-audio', // 静音
248
+ // 性能优化
249
+ '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-background-timer-throttling',
250
+ // 安全相关(某些环境需要)
251
+ '--no-sandbox', '--disable-setuid-sandbox');
252
+ return args;
253
+ }
254
+ /**
255
+ * 关闭浏览器
256
+ */
257
+ async close() {
258
+ // TODO: 实现浏览器关闭逻辑(注:由于进程分离,实际无法关闭)
259
+ this.isOpened = false;
260
+ }
261
+ /**
262
+ * 检查浏览器是否已打开
263
+ */
264
+ isOpen() {
265
+ return this.isOpened;
266
+ }
267
+ }
268
+ // 全局单例
269
+ let globalLauncher = null;
270
+ /**
271
+ * 获取全局浏览器启动器实例
272
+ */
273
+ function getBrowserLauncher() {
274
+ if (!globalLauncher) {
275
+ globalLauncher = new BrowserLauncherImpl();
276
+ }
277
+ return globalLauncher;
278
+ }
279
+ /**
280
+ * 重置全局浏览器启动器(用于测试)
281
+ */
282
+ async function resetBrowserLauncher() {
283
+ if (globalLauncher) {
284
+ await globalLauncher.close();
285
+ }
286
+ globalLauncher = null;
287
+ }
288
+ /**
289
+ * 创建新的浏览器启动器实例(用于测试)
290
+ */
291
+ function createBrowserLauncher() {
292
+ return new BrowserLauncherImpl();
293
+ }
294
+ /**
295
+ * 构建带 WebSocket 端口参数的 GUI URL
296
+ */
297
+ function buildGUIUrl(host, guiPort, wsPort) {
298
+ return `http://${host}:${guiPort}?wsPort=${wsPort}`;
299
+ }
300
+ /**
301
+ * 打开浏览器(便捷函数)
302
+ */
303
+ async function openBrowser(url) {
304
+ const launcher = getBrowserLauncher();
305
+ await launcher.open(url);
306
+ }
307
+ /**
308
+ * 关闭浏览器(便捷函数)
309
+ */
310
+ async function closeBrowser() {
311
+ const launcher = getBrowserLauncher();
312
+ await launcher.close();
313
+ }
314
+ /**
315
+ * 构建浏览器启动参数(导出用于测试)
316
+ *
317
+ * @param options 启动选项
318
+ * @returns Chrome 命令行参数数组
319
+ */
320
+ function buildLaunchArgs(options) {
321
+ const { url, windowConfig, userDataDir } = options;
322
+ const args = [
323
+ // App 模式(极简窗口,无地址栏和工具栏)
324
+ `--app=${url}`,
325
+ // 窗口大小
326
+ `--window-size=${windowConfig.width},${windowConfig.height}`,
327
+ ];
328
+ // 用户数据目录(如果提供)
329
+ if (userDataDir) {
330
+ args.push(`--user-data-dir=${userDataDir}`);
331
+ }
332
+ // 优化参数
333
+ args.push('--no-first-run', // 跳过首次运行向导
334
+ '--no-default-browser-check', // 跳过默认浏览器检查
335
+ '--disable-extensions', // 禁用扩展
336
+ '--disable-sync', // 禁用同步
337
+ '--disable-background-networking', // 禁用后台网络请求
338
+ '--disable-features=TranslateUI', // 禁用翻译 UI
339
+ '--disable-component-extensions-with-background-pages', '--disable-default-apps', // 禁用默认应用
340
+ '--mute-audio', // 静音
341
+ // 性能优化
342
+ '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-background-timer-throttling',
343
+ // 安全相关(某些环境需要)
344
+ '--no-sandbox', '--disable-setuid-sandbox');
345
+ return args;
346
+ }
347
+ /**
348
+ * 创建用户数据目录路径
349
+ *
350
+ * 生成一个唯一的临时目录路径用于浏览器用户数据。
351
+ * 使用 nanoid 生成 8 位随机 ID 确保每次启动都使用独立的会话。
352
+ *
353
+ * @returns 用户数据目录的完整路径,格式:`{tmpdir}/nnd-browser-{sessionId}`
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * const userDataDir = createUserDataDir();
358
+ * // 返回类似:/tmp/nnd-browser-a1b2c3d4
359
+ * ```
360
+ */
361
+ function createUserDataDir() {
362
+ const sessionId = (0, nanoid_1.nanoid)(8);
363
+ return (0, path_1.join)((0, os_1.tmpdir)(), `nnd-browser-${sessionId}`);
364
+ }
365
+ //# sourceMappingURL=browser-launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-launcher.js","sourceRoot":"","sources":["../../../src/gui/browser-launcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ /**
3
+ * Event Bridge 模块
4
+ *
5
+ * 连接请求拦截器和 WebSocket Hub,实现事件转发
6
+ * 支持暂停/恢复和清空功能
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getEventBridge = getEventBridge;
10
+ exports.resetEventBridge = resetEventBridge;
11
+ exports.createEventBridge = createEventBridge;
12
+ const ring_buffer_js_1 = require("../store/ring-buffer.js");
13
+ const websocket_hub_js_1 = require("./websocket-hub.js");
14
+ /**
15
+ * Event Bridge 实现
16
+ */
17
+ class EventBridgeImpl {
18
+ paused = false;
19
+ running = false;
20
+ wsHub;
21
+ pendingEvents = [];
22
+ constructor(wsHub) {
23
+ this.wsHub = wsHub ?? (0, websocket_hub_js_1.getWebSocketHub)();
24
+ }
25
+ /**
26
+ * 启动 Event Bridge
27
+ * 注册 WebSocket 客户端连接回调,发送初始数据
28
+ */
29
+ start() {
30
+ if (this.running)
31
+ return;
32
+ this.running = true;
33
+ // 当新客户端连接时,发送初始数据
34
+ this.wsHub.onClientConnect((clientId) => {
35
+ const store = (0, ring_buffer_js_1.getRequestStore)();
36
+ const requests = store.getAll();
37
+ const message = (0, websocket_hub_js_1.createInitialDataMessage)(requests);
38
+ this.wsHub.send(clientId, message);
39
+ });
40
+ // 处理客户端消息(控制命令)
41
+ this.wsHub.onClientMessage((_clientId, message) => {
42
+ this.handleClientMessage(message);
43
+ });
44
+ }
45
+ /**
46
+ * 停止 Event Bridge
47
+ */
48
+ stop() {
49
+ this.running = false;
50
+ this.pendingEvents = [];
51
+ }
52
+ /**
53
+ * 检查是否正在运行
54
+ */
55
+ isRunning() {
56
+ return this.running;
57
+ }
58
+ /**
59
+ * 处理客户端消息
60
+ */
61
+ handleClientMessage(message) {
62
+ switch (message.type) {
63
+ case 'control:pause':
64
+ this.pause();
65
+ break;
66
+ case 'control:resume':
67
+ this.resume();
68
+ break;
69
+ case 'requests:clear':
70
+ this.clear();
71
+ break;
72
+ }
73
+ }
74
+ /**
75
+ * 发送请求开始事件
76
+ */
77
+ emitRequestStart(request) {
78
+ if (!this.running)
79
+ return;
80
+ const message = (0, websocket_hub_js_1.createRequestStartMessage)(request);
81
+ if (this.paused) {
82
+ // 暂停时缓存事件
83
+ this.pendingEvents.push(message);
84
+ }
85
+ else {
86
+ this.wsHub.broadcast(message);
87
+ }
88
+ }
89
+ /**
90
+ * 发送请求完成事件
91
+ */
92
+ emitRequestComplete(requestId, response) {
93
+ if (!this.running)
94
+ return;
95
+ const message = (0, websocket_hub_js_1.createRequestCompleteMessage)(requestId, response);
96
+ if (this.paused) {
97
+ // 暂停时缓存事件
98
+ this.pendingEvents.push(message);
99
+ }
100
+ else {
101
+ this.wsHub.broadcast(message);
102
+ }
103
+ }
104
+ /**
105
+ * 发送请求错误事件
106
+ */
107
+ emitRequestError(requestId, error) {
108
+ if (!this.running)
109
+ return;
110
+ const message = (0, websocket_hub_js_1.createRequestErrorMessage)(requestId, error);
111
+ if (this.paused) {
112
+ // 暂停时缓存事件
113
+ this.pendingEvents.push(message);
114
+ }
115
+ else {
116
+ this.wsHub.broadcast(message);
117
+ }
118
+ }
119
+ /**
120
+ * 暂停事件广播
121
+ */
122
+ pause() {
123
+ if (this.paused)
124
+ return;
125
+ this.paused = true;
126
+ // 广播暂停状态
127
+ this.wsHub.broadcast({
128
+ type: 'control:pause',
129
+ payload: null,
130
+ timestamp: Date.now(),
131
+ });
132
+ }
133
+ /**
134
+ * 恢复事件广播
135
+ */
136
+ resume() {
137
+ if (!this.paused)
138
+ return;
139
+ this.paused = false;
140
+ // 广播恢复状态
141
+ this.wsHub.broadcast({
142
+ type: 'control:resume',
143
+ payload: null,
144
+ timestamp: Date.now(),
145
+ });
146
+ // 发送暂停期间缓存的事件
147
+ for (const event of this.pendingEvents) {
148
+ this.wsHub.broadcast(event);
149
+ }
150
+ this.pendingEvents = [];
151
+ }
152
+ /**
153
+ * 检查是否暂停
154
+ */
155
+ isPaused() {
156
+ return this.paused;
157
+ }
158
+ /**
159
+ * 清空请求
160
+ */
161
+ clear() {
162
+ // 清空存储
163
+ const store = (0, ring_buffer_js_1.getRequestStore)();
164
+ store.clear();
165
+ // 清空缓存的事件
166
+ this.pendingEvents = [];
167
+ // 广播清空消息
168
+ this.wsHub.broadcast((0, websocket_hub_js_1.createClearMessage)());
169
+ }
170
+ }
171
+ // 全局单例
172
+ let globalBridge = null;
173
+ /**
174
+ * 获取全局 Event Bridge 实例
175
+ */
176
+ function getEventBridge() {
177
+ if (!globalBridge) {
178
+ globalBridge = new EventBridgeImpl();
179
+ }
180
+ return globalBridge;
181
+ }
182
+ /**
183
+ * 重置全局 Event Bridge(用于测试)
184
+ */
185
+ function resetEventBridge() {
186
+ if (globalBridge) {
187
+ globalBridge.stop();
188
+ }
189
+ globalBridge = null;
190
+ }
191
+ /**
192
+ * 创建新的 Event Bridge 实例(用于测试)
193
+ */
194
+ function createEventBridge(wsHub) {
195
+ return new EventBridgeImpl(wsHub);
196
+ }
197
+ //# 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,85 @@
1
+ "use strict";
2
+ /**
3
+ * 端口工具模块
4
+ *
5
+ * 提供获取可用端口的功能
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.isPortAvailable = isPortAvailable;
9
+ exports.getAvailablePort = getAvailablePort;
10
+ exports.getRandomAvailablePort = getRandomAvailablePort;
11
+ const node_net_1 = require("node:net");
12
+ /**
13
+ * 检查端口是否可用
14
+ */
15
+ function isPortAvailable(port, host = '127.0.0.1') {
16
+ return new Promise((resolve) => {
17
+ const server = (0, node_net_1.createServer)();
18
+ server.once('error', () => {
19
+ resolve(false);
20
+ });
21
+ server.once('listening', () => {
22
+ server.close(() => {
23
+ resolve(true);
24
+ });
25
+ });
26
+ server.listen(port, host);
27
+ });
28
+ }
29
+ /**
30
+ * 获取可用端口
31
+ *
32
+ * @param preferredPort 首选端口,如果为 'auto' 则自动获取
33
+ * @param host 监听地址
34
+ * @param maxAttempts 最大尝试次数
35
+ * @returns 可用端口号
36
+ */
37
+ async function getAvailablePort(preferredPort = 'auto', host = '127.0.0.1', maxAttempts = 10) {
38
+ // 如果是 'auto',从随机端口开始
39
+ if (preferredPort === 'auto') {
40
+ return getRandomAvailablePort(host);
41
+ }
42
+ // 尝试首选端口
43
+ if (await isPortAvailable(preferredPort, host)) {
44
+ return preferredPort;
45
+ }
46
+ // 首选端口不可用,尝试后续端口
47
+ for (let i = 1; i < maxAttempts; i++) {
48
+ const port = preferredPort + i;
49
+ if (await isPortAvailable(port, host)) {
50
+ return port;
51
+ }
52
+ }
53
+ // 所有尝试都失败,获取随机可用端口
54
+ return getRandomAvailablePort(host);
55
+ }
56
+ /**
57
+ * 获取随机可用端口
58
+ *
59
+ * 通过让系统分配端口来获取可用端口
60
+ */
61
+ function getRandomAvailablePort(host = '127.0.0.1') {
62
+ return new Promise((resolve, reject) => {
63
+ const server = (0, node_net_1.createServer)();
64
+ server.once('error', (err) => {
65
+ reject(err);
66
+ });
67
+ server.once('listening', () => {
68
+ const address = server.address();
69
+ if (address && typeof address === 'object') {
70
+ const port = address.port;
71
+ server.close(() => {
72
+ resolve(port);
73
+ });
74
+ }
75
+ else {
76
+ server.close(() => {
77
+ reject(new Error('无法获取端口'));
78
+ });
79
+ }
80
+ });
81
+ // 端口 0 让系统自动分配可用端口
82
+ server.listen(0, host);
83
+ });
84
+ }
85
+ //# 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;;AAOH,0CAgBC;AAUD,4CAyBC;AAOD,wDAyBC;AAxFD,uCAAwC;AAExC;;GAEG;AACH,SAAgB,eAAe,CAAC,IAAY,EAAE,OAAe,WAAW;IACtE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,IAAA,uBAAY,GAAE,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;AACI,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,SAAgB,sBAAsB,CAAC,OAAe,WAAW;IAC/D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAA,uBAAY,GAAE,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"}