@mt0926/node-network-devtools 0.1.1 → 0.1.3
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 +91 -19
- package/README.zh-CN.md +95 -23
- package/dist/esm/adapters/axios.js +2 -0
- package/dist/esm/adapters/axios.js.map +1 -0
- package/dist/esm/config.js +14 -6
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/gui/browser-detector.js +256 -0
- package/dist/esm/gui/browser-detector.js.map +1 -0
- package/dist/esm/gui/browser-launcher.js +265 -78
- package/dist/esm/gui/browser-launcher.js.map +1 -1
- package/dist/esm/gui/server.js.map +1 -1
- package/dist/esm/index.js +6 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interceptors/http-patcher.js +68 -3
- package/dist/esm/interceptors/http-patcher.js.map +1 -1
- package/dist/esm/interceptors/undici-patcher.js +20 -0
- package/dist/esm/interceptors/undici-patcher.js.map +1 -1
- package/dist/esm/register.js +5 -47
- package/dist/esm/register.js.map +1 -1
- package/dist/types/adapters/axios.d.ts +2 -0
- package/dist/types/adapters/axios.d.ts.map +1 -0
- package/dist/types/config.d.ts +15 -6
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/gui/browser-detector.d.ts +68 -0
- package/dist/types/gui/browser-detector.d.ts.map +1 -0
- package/dist/types/gui/browser-launcher.d.ts +58 -13
- package/dist/types/gui/browser-launcher.d.ts.map +1 -1
- package/dist/types/gui/server.d.ts +0 -1
- package/dist/types/gui/server.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/interceptors/http-patcher.d.ts.map +1 -1
- package/dist/types/interceptors/undici-patcher.d.ts.map +1 -1
- package/dist/types/register.d.ts +2 -6
- package/dist/types/register.d.ts.map +1 -1
- package/package.json +1 -5
- package/BUILD.md +0 -204
- package/dist/esm/cdp/cdp-bridge.js +0 -312
- package/dist/esm/cdp/cdp-bridge.js.map +0 -1
- package/dist/types/cdp/cdp-bridge.d.ts +0 -86
- package/dist/types/cdp/cdp-bridge.d.ts.map +0 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器检测器模块
|
|
3
|
+
*
|
|
4
|
+
* 跨平台检测系统已安装的浏览器(Chrome、Edge、Chromium)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 浏览器类型枚举
|
|
8
|
+
*/
|
|
9
|
+
export var BrowserType;
|
|
10
|
+
(function (BrowserType) {
|
|
11
|
+
BrowserType["Chrome"] = "chrome";
|
|
12
|
+
BrowserType["Edge"] = "edge";
|
|
13
|
+
BrowserType["Chromium"] = "chromium";
|
|
14
|
+
})(BrowserType || (BrowserType = {}));
|
|
15
|
+
import { existsSync } from 'fs';
|
|
16
|
+
import { platform } from 'os';
|
|
17
|
+
/**
|
|
18
|
+
* Windows 平台浏览器路径配置
|
|
19
|
+
*/
|
|
20
|
+
const WINDOWS_BROWSER_PATHS = {
|
|
21
|
+
[BrowserType.Chrome]: [
|
|
22
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
23
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
24
|
+
...(process.env.LOCALAPPDATA
|
|
25
|
+
? [`${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`]
|
|
26
|
+
: []),
|
|
27
|
+
],
|
|
28
|
+
[BrowserType.Edge]: [
|
|
29
|
+
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
30
|
+
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
31
|
+
],
|
|
32
|
+
[BrowserType.Chromium]: [
|
|
33
|
+
...(process.env.LOCALAPPDATA
|
|
34
|
+
? [`${process.env.LOCALAPPDATA}\\Chromium\\Application\\chrome.exe`]
|
|
35
|
+
: []),
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* macOS 平台浏览器路径配置
|
|
40
|
+
*/
|
|
41
|
+
const MACOS_BROWSER_PATHS = {
|
|
42
|
+
[BrowserType.Chrome]: [
|
|
43
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
44
|
+
...(process.env.HOME
|
|
45
|
+
? [`${process.env.HOME}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`]
|
|
46
|
+
: []),
|
|
47
|
+
],
|
|
48
|
+
[BrowserType.Edge]: [
|
|
49
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
50
|
+
],
|
|
51
|
+
[BrowserType.Chromium]: [
|
|
52
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Linux 平台浏览器路径配置
|
|
57
|
+
*/
|
|
58
|
+
const LINUX_BROWSER_PATHS = {
|
|
59
|
+
[BrowserType.Chrome]: [
|
|
60
|
+
'/usr/bin/google-chrome',
|
|
61
|
+
'/usr/bin/google-chrome-stable',
|
|
62
|
+
'/usr/local/bin/google-chrome',
|
|
63
|
+
'/snap/bin/chromium',
|
|
64
|
+
],
|
|
65
|
+
[BrowserType.Chromium]: [
|
|
66
|
+
'/usr/bin/chromium',
|
|
67
|
+
'/usr/bin/chromium-browser',
|
|
68
|
+
],
|
|
69
|
+
[BrowserType.Edge]: [
|
|
70
|
+
'/usr/bin/microsoft-edge',
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* 浏览器显示名称映射
|
|
75
|
+
*/
|
|
76
|
+
const BROWSER_NAMES = {
|
|
77
|
+
[BrowserType.Chrome]: 'Google Chrome',
|
|
78
|
+
[BrowserType.Edge]: 'Microsoft Edge',
|
|
79
|
+
[BrowserType.Chromium]: 'Chromium',
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* 浏览器优先级顺序(数字越小优先级越高)
|
|
83
|
+
*/
|
|
84
|
+
const BROWSER_PRIORITY = {
|
|
85
|
+
[BrowserType.Chrome]: 1,
|
|
86
|
+
[BrowserType.Edge]: 2,
|
|
87
|
+
[BrowserType.Chromium]: 3,
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* 根据当前平台获取浏览器路径配置
|
|
91
|
+
*/
|
|
92
|
+
function getBrowserPathsForPlatform() {
|
|
93
|
+
const currentPlatform = platform();
|
|
94
|
+
switch (currentPlatform) {
|
|
95
|
+
case 'win32':
|
|
96
|
+
return WINDOWS_BROWSER_PATHS;
|
|
97
|
+
case 'darwin':
|
|
98
|
+
return MACOS_BROWSER_PATHS;
|
|
99
|
+
case 'linux':
|
|
100
|
+
return LINUX_BROWSER_PATHS;
|
|
101
|
+
default:
|
|
102
|
+
// 未知平台,返回空配置
|
|
103
|
+
return {
|
|
104
|
+
[BrowserType.Chrome]: [],
|
|
105
|
+
[BrowserType.Edge]: [],
|
|
106
|
+
[BrowserType.Chromium]: [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 检测指定类型的浏览器
|
|
112
|
+
*
|
|
113
|
+
* @param type 浏览器类型
|
|
114
|
+
* @returns 浏览器信息,如果未找到则返回 null
|
|
115
|
+
*/
|
|
116
|
+
function detectBrowserByType(type) {
|
|
117
|
+
const paths = getBrowserPathsForPlatform()[type];
|
|
118
|
+
for (const path of paths) {
|
|
119
|
+
if (existsSync(path)) {
|
|
120
|
+
return {
|
|
121
|
+
type,
|
|
122
|
+
path,
|
|
123
|
+
name: BROWSER_NAMES[type],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 浏览器检测器实现类
|
|
131
|
+
*/
|
|
132
|
+
class BrowserDetector {
|
|
133
|
+
/**
|
|
134
|
+
* 检测系统已安装的浏览器(按优先级返回第一个)
|
|
135
|
+
*/
|
|
136
|
+
detect() {
|
|
137
|
+
// 优先检查自定义浏览器路径
|
|
138
|
+
const customPath = process.env.NND_BROWSER_PATH;
|
|
139
|
+
if (customPath && existsSync(customPath)) {
|
|
140
|
+
// 尝试从路径推断浏览器类型
|
|
141
|
+
const lowerPath = customPath.toLowerCase();
|
|
142
|
+
let type;
|
|
143
|
+
if (lowerPath.includes('chrome') && !lowerPath.includes('chromium')) {
|
|
144
|
+
type = BrowserType.Chrome;
|
|
145
|
+
}
|
|
146
|
+
else if (lowerPath.includes('edge') || lowerPath.includes('msedge')) {
|
|
147
|
+
type = BrowserType.Edge;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
type = BrowserType.Chromium;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
type,
|
|
154
|
+
path: customPath,
|
|
155
|
+
name: BROWSER_NAMES[type],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// 按优先级检测浏览器
|
|
159
|
+
const browserTypes = [
|
|
160
|
+
BrowserType.Chrome,
|
|
161
|
+
BrowserType.Edge,
|
|
162
|
+
BrowserType.Chromium,
|
|
163
|
+
];
|
|
164
|
+
for (const type of browserTypes) {
|
|
165
|
+
const browser = detectBrowserByType(type);
|
|
166
|
+
if (browser) {
|
|
167
|
+
return browser;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 检测所有已安装的浏览器
|
|
174
|
+
*/
|
|
175
|
+
detectAll() {
|
|
176
|
+
const browsers = [];
|
|
177
|
+
// 检查自定义浏览器路径
|
|
178
|
+
const customPath = process.env.NND_BROWSER_PATH;
|
|
179
|
+
if (customPath && existsSync(customPath)) {
|
|
180
|
+
const lowerPath = customPath.toLowerCase();
|
|
181
|
+
let type;
|
|
182
|
+
if (lowerPath.includes('chrome') && !lowerPath.includes('chromium')) {
|
|
183
|
+
type = BrowserType.Chrome;
|
|
184
|
+
}
|
|
185
|
+
else if (lowerPath.includes('edge') || lowerPath.includes('msedge')) {
|
|
186
|
+
type = BrowserType.Edge;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
type = BrowserType.Chromium;
|
|
190
|
+
}
|
|
191
|
+
browsers.push({
|
|
192
|
+
type,
|
|
193
|
+
path: customPath,
|
|
194
|
+
name: `${BROWSER_NAMES[type]} (自定义)`,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// 检测所有浏览器类型
|
|
198
|
+
const browserTypes = [
|
|
199
|
+
BrowserType.Chrome,
|
|
200
|
+
BrowserType.Edge,
|
|
201
|
+
BrowserType.Chromium,
|
|
202
|
+
];
|
|
203
|
+
for (const type of browserTypes) {
|
|
204
|
+
const browser = detectBrowserByType(type);
|
|
205
|
+
if (browser) {
|
|
206
|
+
// 避免重复添加自定义路径的浏览器
|
|
207
|
+
if (!browsers.some(b => b.path === browser.path)) {
|
|
208
|
+
browsers.push(browser);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// 按优先级排序
|
|
213
|
+
browsers.sort((a, b) => BROWSER_PRIORITY[a.type] - BROWSER_PRIORITY[b.type]);
|
|
214
|
+
return browsers;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 浏览器检测器单例
|
|
219
|
+
*/
|
|
220
|
+
let detectorInstance = null;
|
|
221
|
+
/**
|
|
222
|
+
* 获取浏览器检测器单例
|
|
223
|
+
*/
|
|
224
|
+
export function getBrowserDetector() {
|
|
225
|
+
if (!detectorInstance) {
|
|
226
|
+
detectorInstance = new BrowserDetector();
|
|
227
|
+
}
|
|
228
|
+
return detectorInstance;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 创建新的浏览器检测器实例
|
|
232
|
+
*/
|
|
233
|
+
export function createBrowserDetector() {
|
|
234
|
+
return new BrowserDetector();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* 检测浏览器(便捷函数)
|
|
238
|
+
*
|
|
239
|
+
* 使用单例检测器检测系统已安装的浏览器
|
|
240
|
+
*
|
|
241
|
+
* @returns 浏览器信息,如果未检测到则返回 null
|
|
242
|
+
*/
|
|
243
|
+
export function detectBrowser() {
|
|
244
|
+
return getBrowserDetector().detect();
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 检测所有浏览器(便捷函数)
|
|
248
|
+
*
|
|
249
|
+
* 使用单例检测器检测所有已安装的浏览器
|
|
250
|
+
*
|
|
251
|
+
* @returns 浏览器信息数组,按优先级排序
|
|
252
|
+
*/
|
|
253
|
+
export function detectAllBrowsers() {
|
|
254
|
+
return getBrowserDetector().detectAll();
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=browser-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-detector.js","sourceRoot":"","sources":["../../../src/gui/browser-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,98 +1,248 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 浏览器启动器模块
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 使用原生浏览器检测和启动机制打开 GUI
|
|
5
5
|
*/
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import { tmpdir } from 'os';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { nanoid } from 'nanoid';
|
|
6
10
|
import { getConfig } from '../config.js';
|
|
11
|
+
import { detectBrowser } from './browser-detector.js';
|
|
12
|
+
/**
|
|
13
|
+
* 浏览器未检测到错误
|
|
14
|
+
*
|
|
15
|
+
* 当系统中未检测到任何支持的浏览器时抛出此错误
|
|
16
|
+
*/
|
|
17
|
+
export class BrowserNotFoundError extends Error {
|
|
18
|
+
constructor() {
|
|
19
|
+
super('未检测到已安装的浏览器');
|
|
20
|
+
this.name = 'BrowserNotFoundError';
|
|
21
|
+
// 保持原型链正确(TypeScript 继承 Error 的最佳实践)
|
|
22
|
+
Object.setPrototypeOf(this, BrowserNotFoundError.prototype);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 浏览器启动失败错误
|
|
27
|
+
*
|
|
28
|
+
* 当浏览器启动过程中发生错误时抛出此错误
|
|
29
|
+
*/
|
|
30
|
+
export class BrowserLaunchError extends Error {
|
|
31
|
+
cause;
|
|
32
|
+
/**
|
|
33
|
+
* @param message 错误消息
|
|
34
|
+
* @param cause 原始错误(可选)
|
|
35
|
+
*/
|
|
36
|
+
constructor(message, cause) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.cause = cause;
|
|
39
|
+
this.name = 'BrowserLaunchError';
|
|
40
|
+
// 保持原型链正确(TypeScript 继承 Error 的最佳实践)
|
|
41
|
+
Object.setPrototypeOf(this, BrowserLaunchError.prototype);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
7
44
|
/**
|
|
8
45
|
* 浏览器启动器实现
|
|
9
46
|
*/
|
|
10
47
|
class BrowserLauncherImpl {
|
|
11
|
-
puppeteerBrowser = null;
|
|
12
|
-
puppeteerPage = null;
|
|
13
48
|
isOpened = false;
|
|
14
49
|
/**
|
|
15
50
|
* 打开浏览器
|
|
51
|
+
*
|
|
52
|
+
* 执行以下步骤:
|
|
53
|
+
* 1. 调用 BrowserDetector.detect() 检测浏览器
|
|
54
|
+
* 2. 处理未找到浏览器的情况
|
|
55
|
+
* 3. 构建启动参数
|
|
56
|
+
* 4. 调用启动逻辑
|
|
57
|
+
* 5. 处理启动失败的情况
|
|
58
|
+
*
|
|
59
|
+
* @param url GUI 访问 URL
|
|
60
|
+
*
|
|
61
|
+
* @remarks
|
|
62
|
+
* 此方法不会抛出异常,所有错误都会被捕获并显示友好的错误消息。
|
|
63
|
+
* 这确保浏览器启动失败不会中断主进程。
|
|
64
|
+
*
|
|
65
|
+
* **验证需求:2.5, 4.1, 4.2, 4.3, 4.4, 4.5, 6.1, 6.4**
|
|
16
66
|
*/
|
|
17
67
|
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
68
|
try {
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
// 步骤 1: 调用 BrowserDetector.detect() 检测浏览器
|
|
70
|
+
const browserInfo = detectBrowser();
|
|
71
|
+
// 步骤 2: 处理未找到浏览器的情况
|
|
72
|
+
if (!browserInfo) {
|
|
73
|
+
// 浏览器未找到,显示友好提示(需求 6.1)
|
|
74
|
+
this.handleBrowserNotFound(url);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// 获取配置
|
|
78
|
+
const config = getConfig();
|
|
79
|
+
const windowConfig = {
|
|
80
|
+
width: config.browserWindowSize?.width || 1280,
|
|
81
|
+
height: config.browserWindowSize?.height || 800,
|
|
82
|
+
title: config.browserWindowTitle || 'Node Network DevTools',
|
|
83
|
+
};
|
|
84
|
+
// 创建用户数据目录(需求 4.2)
|
|
85
|
+
const userDataDir = createUserDataDir();
|
|
86
|
+
// 步骤 3: 构建启动参数(需求 4.1)
|
|
87
|
+
const args = this.buildLaunchArgs({
|
|
88
|
+
url,
|
|
89
|
+
windowConfig,
|
|
90
|
+
userDataDir,
|
|
91
|
+
});
|
|
92
|
+
// 步骤 4: 调用启动逻辑(需求 4.3, 4.4, 4.5)
|
|
93
|
+
this.launchBrowser(browserInfo.path, args);
|
|
94
|
+
this.isOpened = true;
|
|
95
|
+
console.log(`[node-network-devtools] 已使用 ${browserInfo.name} 打开 GUI: ${url}`);
|
|
37
96
|
}
|
|
38
97
|
catch (err) {
|
|
39
|
-
|
|
40
|
-
|
|
98
|
+
// 步骤 5: 处理启动失败的情况(需求 6.4)
|
|
99
|
+
this.handleLaunchError(err, url);
|
|
41
100
|
}
|
|
42
101
|
}
|
|
43
102
|
/**
|
|
44
|
-
*
|
|
103
|
+
* 启动浏览器进程
|
|
104
|
+
*
|
|
105
|
+
* 使用 child_process.spawn() 启动浏览器,并配置为分离进程。
|
|
106
|
+
* 这样浏览器进程不会阻塞父进程,父进程退出后浏览器仍可继续运行。
|
|
107
|
+
*
|
|
108
|
+
* @param browserPath 浏览器可执行文件路径
|
|
109
|
+
* @param args 启动参数数组
|
|
110
|
+
* @throws {BrowserLaunchError} 当浏览器启动失败时抛出
|
|
111
|
+
*
|
|
112
|
+
* @remarks
|
|
113
|
+
* 进程配置:
|
|
114
|
+
* - detached: true - 进程分离,允许父进程退出(需求 4.3)
|
|
115
|
+
* - stdio: 'ignore' - 忽略标准输入输出,避免管道阻塞(需求 4.4)
|
|
116
|
+
* - child.unref() - 允许父进程退出而不等待子进程(需求 4.5)
|
|
117
|
+
*
|
|
118
|
+
* **验证需求:4.3, 4.4, 4.5**
|
|
45
119
|
*/
|
|
46
|
-
|
|
120
|
+
launchBrowser(browserPath, args) {
|
|
47
121
|
try {
|
|
48
|
-
//
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
],
|
|
122
|
+
// 启动分离进程
|
|
123
|
+
const child = spawn(browserPath, args, {
|
|
124
|
+
detached: true, // 进程分离,允许父进程退出(需求 4.3)
|
|
125
|
+
stdio: 'ignore', // 忽略标准输入输出,避免管道阻塞(需求 4.4)
|
|
126
|
+
});
|
|
127
|
+
// 允许父进程退出而不等待子进程(需求 4.5)
|
|
128
|
+
child.unref();
|
|
129
|
+
// 监听错误事件(虽然 stdio: 'ignore',但 spawn 本身可能失败)
|
|
130
|
+
child.on('error', (err) => {
|
|
131
|
+
throw new BrowserLaunchError(`浏览器进程启动失败: ${err.message}`, err);
|
|
59
132
|
});
|
|
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
133
|
}
|
|
68
134
|
catch (err) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// 回退到默认浏览器
|
|
72
|
-
console.log('[node-network-devtools] 回退到默认浏览器...');
|
|
73
|
-
await this.openWithDefaultBrowser(url);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
console.error('[node-network-devtools] Puppeteer 启动失败:', err);
|
|
135
|
+
// 将原始错误包装为 BrowserLaunchError
|
|
136
|
+
if (err instanceof BrowserLaunchError) {
|
|
77
137
|
throw err;
|
|
78
138
|
}
|
|
139
|
+
throw new BrowserLaunchError(`无法启动浏览器: ${err.message}`, err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 处理浏览器未找到的情况
|
|
144
|
+
*
|
|
145
|
+
* 显示友好的错误消息,包含:
|
|
146
|
+
* - 支持的浏览器列表
|
|
147
|
+
* - GUI 访问 URL(需求 6.2)
|
|
148
|
+
* - 自定义浏览器路径设置说明
|
|
149
|
+
* - 浏览器安装指引链接(需求 6.3)
|
|
150
|
+
*
|
|
151
|
+
* @param guiUrl GUI 访问 URL
|
|
152
|
+
*
|
|
153
|
+
* **验证需求:6.1, 6.2, 6.3**
|
|
154
|
+
*/
|
|
155
|
+
handleBrowserNotFound(guiUrl) {
|
|
156
|
+
console.error(`
|
|
157
|
+
[node-network-devtools] 未检测到已安装的浏览器
|
|
158
|
+
|
|
159
|
+
支持的浏览器:
|
|
160
|
+
- Google Chrome
|
|
161
|
+
- Microsoft Edge
|
|
162
|
+
- Chromium
|
|
163
|
+
|
|
164
|
+
请安装其中一个浏览器,或手动访问 GUI:
|
|
165
|
+
${guiUrl}
|
|
166
|
+
|
|
167
|
+
自定义浏览器路径:
|
|
168
|
+
export NND_BROWSER_PATH=/path/to/browser
|
|
169
|
+
|
|
170
|
+
浏览器安装指引:
|
|
171
|
+
Chrome: https://www.google.com/chrome/
|
|
172
|
+
Edge: https://www.microsoft.com/edge
|
|
173
|
+
更多信息: https://github.com/dong0926/node-network-devtools#readme
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 处理浏览器启动失败的情况
|
|
178
|
+
*
|
|
179
|
+
* 显示友好的错误消息,包含:
|
|
180
|
+
* - 错误信息(需求 6.4)
|
|
181
|
+
* - 可能的原因分析
|
|
182
|
+
* - 解决方案建议(需求 6.5)
|
|
183
|
+
* - 手动访问 URL 提示
|
|
184
|
+
*
|
|
185
|
+
* @param err 错误对象
|
|
186
|
+
* @param guiUrl GUI 访问 URL
|
|
187
|
+
*
|
|
188
|
+
* **验证需求:6.4, 6.5**
|
|
189
|
+
*/
|
|
190
|
+
handleLaunchError(err, guiUrl) {
|
|
191
|
+
console.error(`
|
|
192
|
+
[node-network-devtools] 浏览器启动失败
|
|
193
|
+
|
|
194
|
+
错误信息:${err.message}
|
|
195
|
+
|
|
196
|
+
可能的原因:
|
|
197
|
+
1. 浏览器路径无效
|
|
198
|
+
2. 权限不足
|
|
199
|
+
3. 系统资源不足
|
|
200
|
+
|
|
201
|
+
解决方案:
|
|
202
|
+
- 手动访问 GUI: ${guiUrl}
|
|
203
|
+
- 设置 NND_AUTO_OPEN=false 禁用自动打开
|
|
204
|
+
- 检查浏览器是否正确安装
|
|
205
|
+
- 尝试设置自定义浏览器路径: export NND_BROWSER_PATH=/path/to/browser
|
|
206
|
+
`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 构建浏览器启动参数
|
|
210
|
+
*
|
|
211
|
+
* @param options 启动选项
|
|
212
|
+
* @returns Chrome 命令行参数数组
|
|
213
|
+
*/
|
|
214
|
+
buildLaunchArgs(options) {
|
|
215
|
+
const { url, windowConfig, userDataDir } = options;
|
|
216
|
+
const args = [
|
|
217
|
+
// App 模式(极简窗口,无地址栏和工具栏)
|
|
218
|
+
`--app=${url}`,
|
|
219
|
+
// 窗口大小
|
|
220
|
+
`--window-size=${windowConfig.width},${windowConfig.height}`,
|
|
221
|
+
];
|
|
222
|
+
// 用户数据目录(如果提供)
|
|
223
|
+
if (userDataDir) {
|
|
224
|
+
args.push(`--user-data-dir=${userDataDir}`);
|
|
79
225
|
}
|
|
226
|
+
// 优化参数
|
|
227
|
+
args.push('--no-first-run', // 跳过首次运行向导
|
|
228
|
+
'--no-default-browser-check', // 跳过默认浏览器检查
|
|
229
|
+
'--disable-extensions', // 禁用扩展
|
|
230
|
+
'--disable-sync', // 禁用同步
|
|
231
|
+
'--disable-background-networking', // 禁用后台网络请求
|
|
232
|
+
'--disable-features=TranslateUI', // 禁用翻译 UI
|
|
233
|
+
'--disable-component-extensions-with-background-pages', '--disable-default-apps', // 禁用默认应用
|
|
234
|
+
'--mute-audio', // 静音
|
|
235
|
+
// 性能优化
|
|
236
|
+
'--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-background-timer-throttling',
|
|
237
|
+
// 安全相关(某些环境需要)
|
|
238
|
+
'--no-sandbox', '--disable-setuid-sandbox');
|
|
239
|
+
return args;
|
|
80
240
|
}
|
|
81
241
|
/**
|
|
82
242
|
* 关闭浏览器
|
|
83
243
|
*/
|
|
84
244
|
async close() {
|
|
85
|
-
|
|
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
|
-
}
|
|
245
|
+
// TODO: 实现浏览器关闭逻辑(注:由于进程分离,实际无法关闭)
|
|
96
246
|
this.isOpened = false;
|
|
97
247
|
}
|
|
98
248
|
/**
|
|
@@ -137,23 +287,9 @@ export function buildGUIUrl(host, guiPort, wsPort) {
|
|
|
137
287
|
/**
|
|
138
288
|
* 打开浏览器(便捷函数)
|
|
139
289
|
*/
|
|
140
|
-
export async function openBrowser(url
|
|
290
|
+
export async function openBrowser(url) {
|
|
141
291
|
const launcher = getBrowserLauncher();
|
|
142
|
-
|
|
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
|
-
}
|
|
292
|
+
await launcher.open(url);
|
|
157
293
|
}
|
|
158
294
|
/**
|
|
159
295
|
* 关闭浏览器(便捷函数)
|
|
@@ -162,4 +298,55 @@ export async function closeBrowser() {
|
|
|
162
298
|
const launcher = getBrowserLauncher();
|
|
163
299
|
await launcher.close();
|
|
164
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* 构建浏览器启动参数(导出用于测试)
|
|
303
|
+
*
|
|
304
|
+
* @param options 启动选项
|
|
305
|
+
* @returns Chrome 命令行参数数组
|
|
306
|
+
*/
|
|
307
|
+
export function buildLaunchArgs(options) {
|
|
308
|
+
const { url, windowConfig, userDataDir } = options;
|
|
309
|
+
const args = [
|
|
310
|
+
// App 模式(极简窗口,无地址栏和工具栏)
|
|
311
|
+
`--app=${url}`,
|
|
312
|
+
// 窗口大小
|
|
313
|
+
`--window-size=${windowConfig.width},${windowConfig.height}`,
|
|
314
|
+
];
|
|
315
|
+
// 用户数据目录(如果提供)
|
|
316
|
+
if (userDataDir) {
|
|
317
|
+
args.push(`--user-data-dir=${userDataDir}`);
|
|
318
|
+
}
|
|
319
|
+
// 优化参数
|
|
320
|
+
args.push('--no-first-run', // 跳过首次运行向导
|
|
321
|
+
'--no-default-browser-check', // 跳过默认浏览器检查
|
|
322
|
+
'--disable-extensions', // 禁用扩展
|
|
323
|
+
'--disable-sync', // 禁用同步
|
|
324
|
+
'--disable-background-networking', // 禁用后台网络请求
|
|
325
|
+
'--disable-features=TranslateUI', // 禁用翻译 UI
|
|
326
|
+
'--disable-component-extensions-with-background-pages', '--disable-default-apps', // 禁用默认应用
|
|
327
|
+
'--mute-audio', // 静音
|
|
328
|
+
// 性能优化
|
|
329
|
+
'--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-background-timer-throttling',
|
|
330
|
+
// 安全相关(某些环境需要)
|
|
331
|
+
'--no-sandbox', '--disable-setuid-sandbox');
|
|
332
|
+
return args;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* 创建用户数据目录路径
|
|
336
|
+
*
|
|
337
|
+
* 生成一个唯一的临时目录路径用于浏览器用户数据。
|
|
338
|
+
* 使用 nanoid 生成 8 位随机 ID 确保每次启动都使用独立的会话。
|
|
339
|
+
*
|
|
340
|
+
* @returns 用户数据目录的完整路径,格式:`{tmpdir}/nnd-browser-{sessionId}`
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```typescript
|
|
344
|
+
* const userDataDir = createUserDataDir();
|
|
345
|
+
* // 返回类似:/tmp/nnd-browser-a1b2c3d4
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
export function createUserDataDir() {
|
|
349
|
+
const sessionId = nanoid(8);
|
|
350
|
+
return join(tmpdir(), `nnd-browser-${sessionId}`);
|
|
351
|
+
}
|
|
165
352
|
//# sourceMappingURL=browser-launcher.js.map
|