cdp-tunnel 1.0.14 → 1.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/cli/chrome-manager.js +111 -0
- package/cli/index.js +186 -37
- package/package.json +1 -1
- package/server/modules/config.js +3 -1
- package/server/proxy-server.js +119 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { execSync, spawn } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
function findChromePath() {
|
|
10
|
+
if (process.env.CHROME_PATH && fs.existsSync(process.env.CHROME_PATH)) {
|
|
11
|
+
return process.env.CHROME_PATH;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const platform = os.platform();
|
|
15
|
+
const candidates = {
|
|
16
|
+
darwin: [
|
|
17
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
18
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
19
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
20
|
+
],
|
|
21
|
+
win32: [
|
|
22
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
23
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
24
|
+
path.join(os.homedir(), 'AppData\\Local\\Google\\Chrome\\Application\\chrome.exe'),
|
|
25
|
+
],
|
|
26
|
+
linux: [
|
|
27
|
+
'/usr/bin/google-chrome-stable',
|
|
28
|
+
'/usr/bin/google-chrome',
|
|
29
|
+
'/usr/bin/chromium-browser',
|
|
30
|
+
'/usr/bin/chromium',
|
|
31
|
+
'/snap/bin/chromium',
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const paths = candidates[platform] || [];
|
|
36
|
+
for (const p of paths) {
|
|
37
|
+
if (fs.existsSync(p)) return p;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isChromeRunning() {
|
|
43
|
+
const platform = os.platform();
|
|
44
|
+
try {
|
|
45
|
+
if (platform === 'darwin') {
|
|
46
|
+
const result = execSync('pgrep -x "Google Chrome" || pgrep -x "Chromium" || pgrep -x "Google Chrome Helper" || true', { encoding: 'utf8' });
|
|
47
|
+
return result.trim().length > 0;
|
|
48
|
+
}
|
|
49
|
+
if (platform === 'win32') {
|
|
50
|
+
const result = execSync('tasklist /FI "IMAGENAME eq chrome.exe" /NH', { encoding: 'utf8' });
|
|
51
|
+
return result.includes('chrome.exe');
|
|
52
|
+
}
|
|
53
|
+
const result = execSync('pgrep -f "chrome|chromium" || true', { encoding: 'utf8' });
|
|
54
|
+
return result.trim().length > 0;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getExtensionPath() {
|
|
61
|
+
return path.resolve(__dirname, '..', 'extension-new');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function launchChromeWithExtension() {
|
|
65
|
+
const chromePath = findChromePath();
|
|
66
|
+
if (!chromePath) {
|
|
67
|
+
console.error('[AUTO-RESTART] Chrome not found. Set CHROME_PATH env var.');
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const extensionPath = getExtensionPath();
|
|
72
|
+
if (!fs.existsSync(extensionPath)) {
|
|
73
|
+
console.error('[AUTO-RESTART] Extension directory not found:', extensionPath);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const platform = os.platform();
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
if (platform === 'darwin') {
|
|
81
|
+
const appName = chromePath.replace(/\/Contents\/MacOS\/.*$/, '');
|
|
82
|
+
execSync(`open -a "${appName}" --args --load-extension="${extensionPath}"`, {
|
|
83
|
+
timeout: 10000,
|
|
84
|
+
stdio: 'ignore',
|
|
85
|
+
});
|
|
86
|
+
} else if (platform === 'win32') {
|
|
87
|
+
spawn(chromePath, [`--load-extension=${extensionPath}`], {
|
|
88
|
+
detached: true,
|
|
89
|
+
stdio: 'ignore',
|
|
90
|
+
}).unref();
|
|
91
|
+
} else {
|
|
92
|
+
spawn(chromePath, [`--load-extension=${extensionPath}`], {
|
|
93
|
+
detached: true,
|
|
94
|
+
stdio: 'ignore',
|
|
95
|
+
}).unref();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(`[AUTO-RESTART] Chrome launched with extension: ${chromePath}`);
|
|
99
|
+
return true;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error('[AUTO-RESTART] Failed to launch Chrome:', err.message);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
findChromePath,
|
|
108
|
+
isChromeRunning,
|
|
109
|
+
getExtensionPath,
|
|
110
|
+
launchChromeWithExtension,
|
|
111
|
+
};
|
package/cli/index.js
CHANGED
|
@@ -143,14 +143,14 @@ function openChromeExtensions() {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
function startServer(port, watchdog) {
|
|
146
|
+
function startServer(port, watchdog, autoRestart) {
|
|
147
147
|
const serverPath = path.join(__dirname, '..', 'server', 'proxy-server.js');
|
|
148
148
|
const logFd = fs.openSync(LOG_FILE, 'a');
|
|
149
149
|
|
|
150
150
|
const child = spawn('node', [serverPath], {
|
|
151
151
|
detached: !watchdog,
|
|
152
152
|
stdio: ['ignore', logFd, logFd],
|
|
153
|
-
env: { ...process.env, PORT: port.toString() }
|
|
153
|
+
env: { ...process.env, PORT: port.toString(), AUTO_RESTART: autoRestart ? 'true' : 'false' }
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
fs.writeFileSync(PID_FILE, child.pid.toString());
|
|
@@ -181,7 +181,7 @@ function startServer(port, watchdog) {
|
|
|
181
181
|
console.log(' 重启次数: ' + restartTimestamps.length + '/' + MAX_RESTARTS + ' (60秒内)');
|
|
182
182
|
console.log('');
|
|
183
183
|
|
|
184
|
-
setTimeout(() => startServer(port, true), 3000);
|
|
184
|
+
setTimeout(() => startServer(port, true, autoRestart), 3000);
|
|
185
185
|
});
|
|
186
186
|
|
|
187
187
|
process.on('SIGINT', () => {
|
|
@@ -204,56 +204,136 @@ function startServer(port, watchdog) {
|
|
|
204
204
|
return child;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
function waitForPluginConnection(maxWaitMs) {
|
|
208
|
+
return new Promise((resolve) => {
|
|
209
|
+
const start = Date.now();
|
|
210
|
+
const interval = setInterval(() => {
|
|
211
|
+
const status = checkChromeExtension();
|
|
212
|
+
if (status.connected) {
|
|
213
|
+
clearInterval(interval);
|
|
214
|
+
resolve(true);
|
|
215
|
+
} else if (Date.now() - start > maxWaitMs) {
|
|
216
|
+
clearInterval(interval);
|
|
217
|
+
resolve(false);
|
|
218
|
+
}
|
|
219
|
+
}, 3000);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function generateAndSaveGuideHtml() {
|
|
224
|
+
return generateGuideHtml();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function openInBrowser(filePath) {
|
|
228
|
+
const platform = os.platform();
|
|
229
|
+
try {
|
|
230
|
+
if (platform === 'darwin') {
|
|
231
|
+
execSync(`open "${filePath}"`);
|
|
232
|
+
} else if (platform === 'win32') {
|
|
233
|
+
execSync(`start "" "${filePath}"`);
|
|
234
|
+
} else {
|
|
235
|
+
execSync(`xdg-open "${filePath}"`);
|
|
236
|
+
}
|
|
237
|
+
} catch {}
|
|
238
|
+
}
|
|
239
|
+
|
|
207
240
|
program
|
|
208
241
|
.command('start')
|
|
209
242
|
.description('启动 CDP Tunnel 服务器')
|
|
210
243
|
.option('-p, --port <port>', '指定端口', parseInt)
|
|
211
244
|
.option('-w, --watchdog', '启用看门狗,服务器崩溃时自动重启')
|
|
212
|
-
.
|
|
245
|
+
.option('-a, --auto-restart', '浏览器断连时自动重启 Chrome(带插件)')
|
|
246
|
+
.action(async (options) => {
|
|
213
247
|
const config = getConfig();
|
|
214
|
-
const port = options.port || config.port;
|
|
215
|
-
|
|
248
|
+
const port = options.port || config.port || 9221;
|
|
249
|
+
|
|
250
|
+
config.port = port;
|
|
251
|
+
config.autoRestart = !!options.autoRestart;
|
|
252
|
+
saveConfig(config);
|
|
253
|
+
|
|
216
254
|
if (isServerRunning()) {
|
|
217
255
|
console.log('');
|
|
218
|
-
log('yellow',
|
|
219
|
-
log('cyan',
|
|
220
|
-
|
|
221
|
-
|
|
256
|
+
log('yellow', `⚠ 服务器已在运行 (PID: ${getServerPid()})`);
|
|
257
|
+
log('cyan', ` CDP: http://localhost:${port}`);
|
|
258
|
+
} else {
|
|
259
|
+
ensureConfigDir();
|
|
260
|
+
cleanupLogFile();
|
|
261
|
+
startServer(port, options.watchdog, options.autoRestart);
|
|
262
|
+
console.log('');
|
|
263
|
+
log('green', '✅ 服务器已启动');
|
|
264
|
+
log('cyan', ` CDP: http://localhost:${port}`);
|
|
222
265
|
}
|
|
223
|
-
|
|
266
|
+
|
|
267
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
268
|
+
|
|
224
269
|
const extStatus = checkChromeExtension();
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
270
|
+
if (extStatus.connected) {
|
|
271
|
+
console.log('');
|
|
272
|
+
log('green', '✅ Ready! Chrome 扩展已连接');
|
|
273
|
+
log('cyan', ` 连接: ws://localhost:${port}/devtools/browser/...`);
|
|
274
|
+
if (!options.watchdog) {
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
228
278
|
}
|
|
229
|
-
|
|
230
|
-
ensureConfigDir();
|
|
231
|
-
cleanupLogFile();
|
|
232
279
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
280
|
+
const { isChromeRunning, launchChromeWithExtension } = require('./chrome-manager');
|
|
281
|
+
const chromeRunning = isChromeRunning();
|
|
282
|
+
|
|
283
|
+
if (!chromeRunning) {
|
|
284
|
+
log('cyan', '🔍 Chrome 未运行,正在启动...');
|
|
285
|
+
const launched = launchChromeWithExtension();
|
|
286
|
+
if (launched) {
|
|
287
|
+
log('cyan', '⏳ 等待插件连接...');
|
|
288
|
+
const connected = await waitForPluginConnection(15000);
|
|
289
|
+
if (connected) {
|
|
290
|
+
console.log('');
|
|
291
|
+
log('green', '✅ Ready! Chrome 已启动,插件已连接');
|
|
292
|
+
log('cyan', ` 连接: ws://localhost:${port}/devtools/browser/...`);
|
|
293
|
+
} else {
|
|
294
|
+
console.log('');
|
|
295
|
+
log('yellow', '⚠ Chrome 已启动,但插件未自动连接。请点击浏览器工具栏上的 CDP Bridge 图标。');
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
console.log('');
|
|
299
|
+
log('yellow', '⚠ 无法自动启动 Chrome。请手动安装插件:');
|
|
300
|
+
printExtensionGuide();
|
|
301
|
+
openChromeExtensions();
|
|
302
|
+
const connected = await waitForPluginConnection(120000);
|
|
303
|
+
if (connected) {
|
|
304
|
+
console.log('');
|
|
305
|
+
log('green', '✅ Ready! 插件已连接');
|
|
306
|
+
log('cyan', ` 连接: ws://localhost:${port}/devtools/browser/...`);
|
|
307
|
+
} else {
|
|
308
|
+
console.log('');
|
|
309
|
+
log('yellow', '⚠ 等待超时。插件安装完成后,运行 cdp-tunnel start 即可。');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!options.watchdog) process.exit(0);
|
|
313
|
+
return;
|
|
248
314
|
}
|
|
315
|
+
|
|
249
316
|
console.log('');
|
|
250
|
-
log('
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
317
|
+
log('yellow', '⚠ Chrome 正在运行但插件未连接');
|
|
318
|
+
log('cyan', '📖 正在打开安装引导...');
|
|
319
|
+
|
|
320
|
+
const guidePath = generateAndSaveGuideHtml();
|
|
321
|
+
openInBrowser(guidePath);
|
|
322
|
+
openChromeExtensions();
|
|
323
|
+
printExtensionGuide();
|
|
324
|
+
|
|
325
|
+
log('cyan', '⏳ 等待插件安装并连接(最多 2 分钟)...');
|
|
326
|
+
const connected = await waitForPluginConnection(120000);
|
|
327
|
+
if (connected) {
|
|
328
|
+
console.log('');
|
|
329
|
+
log('green', '✅ Ready! 插件已连接');
|
|
330
|
+
log('cyan', ` 连接: ws://localhost:${port}/devtools/browser/...`);
|
|
331
|
+
} else {
|
|
255
332
|
console.log('');
|
|
333
|
+
log('yellow', '⚠ 等待超时。插件安装完成后,运行 cdp-tunnel start 即可。');
|
|
256
334
|
}
|
|
335
|
+
|
|
336
|
+
if (!options.watchdog) process.exit(0);
|
|
257
337
|
});
|
|
258
338
|
|
|
259
339
|
program
|
|
@@ -275,6 +355,71 @@ program
|
|
|
275
355
|
}
|
|
276
356
|
});
|
|
277
357
|
|
|
358
|
+
program
|
|
359
|
+
.command('update')
|
|
360
|
+
.description('自动更新 cdp-tunnel 并重启服务')
|
|
361
|
+
.option('-p, --port <port>', '重启时指定端口', parseInt)
|
|
362
|
+
.option('-w, --watchdog', '重启时启用看门狗')
|
|
363
|
+
.action(async (options) => {
|
|
364
|
+
const config = getConfig();
|
|
365
|
+
const wasRunning = isServerRunning();
|
|
366
|
+
const savedPort = options.port || config.port;
|
|
367
|
+
const savedWatchdog = options.watchdog;
|
|
368
|
+
|
|
369
|
+
console.log('');
|
|
370
|
+
log('cyan', '⬆ 正在检查更新...');
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const beforeVersion = execSync('npm view cdp-tunnel version', { encoding: 'utf8' }).trim();
|
|
374
|
+
const localVersion = require(path.join(__dirname, '..', 'package.json')).version;
|
|
375
|
+
log('gray', ' 当前版本: ' + localVersion);
|
|
376
|
+
log('gray', ' 最新版本: ' + beforeVersion);
|
|
377
|
+
|
|
378
|
+
if (wasRunning) {
|
|
379
|
+
log('yellow', ' 正在停止服务器...');
|
|
380
|
+
try {
|
|
381
|
+
const pid = getServerPid();
|
|
382
|
+
process.kill(pid, 'SIGTERM');
|
|
383
|
+
fs.unlinkSync(PID_FILE);
|
|
384
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
385
|
+
log('green', ' ✓ 服务器已停止');
|
|
386
|
+
} catch (e) {
|
|
387
|
+
log('yellow', ' ⚠ 停止服务器失败: ' + e.message);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
log('cyan', ' 正在更新...');
|
|
392
|
+
execSync('npm update -g cdp-tunnel', { stdio: 'inherit' });
|
|
393
|
+
|
|
394
|
+
const afterVersion = require(path.join(__dirname, '..', 'package.json')).version;
|
|
395
|
+
if (afterVersion !== localVersion) {
|
|
396
|
+
log('green', ' ✓ 已更新: ' + localVersion + ' → ' + afterVersion);
|
|
397
|
+
} else {
|
|
398
|
+
log('green', ' ✓ 已是最新版本');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
ensureConfigDir();
|
|
402
|
+
cleanupLogFile();
|
|
403
|
+
startServer(savedPort, savedWatchdog);
|
|
404
|
+
log('green', ' ✓ 服务器已重启 (端口: ' + savedPort + ')');
|
|
405
|
+
console.log('');
|
|
406
|
+
} catch (e) {
|
|
407
|
+
log('red', '✗ 更新失败: ' + e.message);
|
|
408
|
+
console.log('');
|
|
409
|
+
if (wasRunning) {
|
|
410
|
+
log('yellow', '正在尝试恢复服务器...');
|
|
411
|
+
try {
|
|
412
|
+
ensureConfigDir();
|
|
413
|
+
startServer(savedPort, savedWatchdog);
|
|
414
|
+
log('green', '✓ 服务器已恢复');
|
|
415
|
+
} catch (re) {
|
|
416
|
+
log('red', '✗ 恢复失败,请手动启动: cdp-tunnel start');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
278
423
|
program
|
|
279
424
|
.command('status')
|
|
280
425
|
.description('查看服务器状态')
|
|
@@ -294,6 +439,10 @@ program
|
|
|
294
439
|
console.log(' PID: ' + getServerPid());
|
|
295
440
|
console.log(' CDP: http://localhost:' + config.port);
|
|
296
441
|
}
|
|
442
|
+
|
|
443
|
+
if (config.autoRestart) {
|
|
444
|
+
console.log(' 自动重启: 已启用');
|
|
445
|
+
}
|
|
297
446
|
|
|
298
447
|
console.log('');
|
|
299
448
|
if (extStatus.installed && extStatus.connected) {
|
package/package.json
CHANGED
package/server/modules/config.js
CHANGED
|
@@ -7,7 +7,9 @@ const CONFIG = {
|
|
|
7
7
|
CDP_TRACE_MAX_LENGTH: 300,
|
|
8
8
|
LOG_MESSAGE_PREVIEW_LENGTH: 1000,
|
|
9
9
|
CLIENT_IDLE_TIMEOUT: 300000,
|
|
10
|
-
LOG_LEVEL: process.env.LOG_LEVEL || 'info'
|
|
10
|
+
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
|
11
|
+
AUTO_RESTART: process.env.AUTO_RESTART === 'true',
|
|
12
|
+
CHROME_RESTART_COOLDOWN: 30000
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
const LOG_LEVELS = {
|
package/server/proxy-server.js
CHANGED
|
@@ -14,12 +14,112 @@ const WebSocket = require('ws');
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const os = require('os');
|
|
17
|
+
const { execSync, spawn: spawnProcess } = require('child_process');
|
|
17
18
|
const { CONFIG, BROWSER_ID, shouldLog } = require('./modules/config');
|
|
18
19
|
const { logCDP, logEvent, clearLog, logStatus, logConnectionEvent, flushAllLogs } = require('./modules/logger');
|
|
19
20
|
|
|
20
21
|
const PORT = CONFIG.PORT;
|
|
21
22
|
const CONFIG_DIR = path.join(os.homedir(), '.cdp-tunnel');
|
|
22
23
|
const EXTENSION_STATE_FILE = path.join(CONFIG_DIR, 'extension-state.json');
|
|
24
|
+
const PLUGIN_EVER_CONNECTED_FILE = path.join(CONFIG_DIR, 'plugin-ever-connected');
|
|
25
|
+
|
|
26
|
+
let lastChromeRestartAttempt = 0;
|
|
27
|
+
const CHROME_RESTART_COOLDOWN = CONFIG.CHROME_RESTART_COOLDOWN;
|
|
28
|
+
const autoRestartEnabled = CONFIG.AUTO_RESTART;
|
|
29
|
+
|
|
30
|
+
function findChromePath() {
|
|
31
|
+
if (process.env.CHROME_PATH && fs.existsSync(process.env.CHROME_PATH)) {
|
|
32
|
+
return process.env.CHROME_PATH;
|
|
33
|
+
}
|
|
34
|
+
const platform = os.platform();
|
|
35
|
+
const candidates = {
|
|
36
|
+
darwin: [
|
|
37
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
38
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
39
|
+
],
|
|
40
|
+
win32: [
|
|
41
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
42
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
43
|
+
],
|
|
44
|
+
linux: [
|
|
45
|
+
'/usr/bin/google-chrome-stable',
|
|
46
|
+
'/usr/bin/google-chrome',
|
|
47
|
+
'/usr/bin/chromium-browser',
|
|
48
|
+
'/usr/bin/chromium',
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
const paths = candidates[platform] || [];
|
|
52
|
+
for (const p of paths) {
|
|
53
|
+
if (fs.existsSync(p)) return p;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isChromeRunning() {
|
|
59
|
+
const platform = os.platform();
|
|
60
|
+
try {
|
|
61
|
+
if (platform === 'darwin') {
|
|
62
|
+
const result = execSync('pgrep -x "Google Chrome" || pgrep -x "Chromium" || true', { encoding: 'utf8' });
|
|
63
|
+
return result.trim().length > 0;
|
|
64
|
+
}
|
|
65
|
+
if (platform === 'win32') {
|
|
66
|
+
const result = execSync('tasklist /FI "IMAGENAME eq chrome.exe" /NH', { encoding: 'utf8' });
|
|
67
|
+
return result.includes('chrome.exe');
|
|
68
|
+
}
|
|
69
|
+
const result = execSync('pgrep -f "chrome|chromium" || true', { encoding: 'utf8' });
|
|
70
|
+
return result.trim().length > 0;
|
|
71
|
+
} catch { return false; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function tryAutoRestartChrome() {
|
|
75
|
+
if (!autoRestartEnabled) return false;
|
|
76
|
+
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
if (now - lastChromeRestartAttempt < CHROME_RESTART_COOLDOWN) {
|
|
79
|
+
console.log('[AUTO-RESTART] Cooldown active, skipping restart');
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
lastChromeRestartAttempt = now;
|
|
83
|
+
|
|
84
|
+
if (isChromeRunning()) {
|
|
85
|
+
console.log('[AUTO-RESTART] Chrome is already running. Cannot add extension to running Chrome.');
|
|
86
|
+
console.log('[AUTO-RESTART] Please click the CDP Bridge extension icon to connect.');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const chromePath = findChromePath();
|
|
91
|
+
if (!chromePath) {
|
|
92
|
+
console.log('[AUTO-RESTART] Chrome not found. Set CHROME_PATH env var.');
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const extensionPath = path.join(__dirname, '..', 'extension-new');
|
|
97
|
+
if (!fs.existsSync(extensionPath)) {
|
|
98
|
+
console.log('[AUTO-RESTART] Extension directory not found:', extensionPath);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const platform = os.platform();
|
|
104
|
+
if (platform === 'darwin') {
|
|
105
|
+
const appName = chromePath.replace(/\/Contents\/MacOS\/.*$/, '');
|
|
106
|
+
execSync(`open -a "${appName}" --args --load-extension="${extensionPath}"`, {
|
|
107
|
+
timeout: 10000,
|
|
108
|
+
stdio: 'ignore',
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
spawnProcess(chromePath, [`--load-extension=${extensionPath}`], {
|
|
112
|
+
detached: true,
|
|
113
|
+
stdio: 'ignore',
|
|
114
|
+
}).unref();
|
|
115
|
+
}
|
|
116
|
+
console.log('[AUTO-RESTART] Chrome launched with extension:', chromePath);
|
|
117
|
+
return true;
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error('[AUTO-RESTART] Failed to launch Chrome:', err.message);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
23
123
|
|
|
24
124
|
function updateExtensionState(connected) {
|
|
25
125
|
try {
|
|
@@ -268,6 +368,15 @@ function handlePluginConnection(ws, clientInfo) {
|
|
|
268
368
|
|
|
269
369
|
updateExtensionState(true);
|
|
270
370
|
|
|
371
|
+
try {
|
|
372
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
373
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
374
|
+
}
|
|
375
|
+
if (!fs.existsSync(PLUGIN_EVER_CONNECTED_FILE)) {
|
|
376
|
+
fs.writeFileSync(PLUGIN_EVER_CONNECTED_FILE, new Date().toISOString());
|
|
377
|
+
}
|
|
378
|
+
} catch {}
|
|
379
|
+
|
|
271
380
|
// 如果有待配对的客户端,自动配对
|
|
272
381
|
if (clientConnections.size > 0) {
|
|
273
382
|
for (const clientWs of clientConnections) {
|
|
@@ -659,6 +768,16 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
|
|
|
659
768
|
console.log(` - Please ensure Chrome extension is connected.`);
|
|
660
769
|
}
|
|
661
770
|
logConnectionEvent('CLIENT_NO_PLUGIN', { clientId: id });
|
|
771
|
+
|
|
772
|
+
if (autoRestartEnabled) {
|
|
773
|
+
const wasConnectedBefore = fs.existsSync(PLUGIN_EVER_CONNECTED_FILE);
|
|
774
|
+
if (wasConnectedBefore) {
|
|
775
|
+
console.log('[AUTO-RESTART] Plugin disconnected, client connecting. Attempting to restart Chrome...');
|
|
776
|
+
tryAutoRestartChrome();
|
|
777
|
+
} else {
|
|
778
|
+
console.log('[AUTO-RESTART] No previous plugin connection found. New user? Run "cdp-tunnel extension" to install.');
|
|
779
|
+
}
|
|
780
|
+
}
|
|
662
781
|
} else {
|
|
663
782
|
// 多客户端模式: 所有客户端共享同一个 plugin
|
|
664
783
|
// 每个 clientId 对应不同的 tab
|