cdp-tunnel 1.0.15 → 1.2.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.
@@ -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
- .action((options) => {
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', ' 端口: ') + console.log(port);
220
- log('cyan', ' PID: ') + console.log(getServerPid());
221
- return;
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 (!extStatus.installed) {
226
- printExtensionGuide();
227
- openChromeExtensions();
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
- startServer(port, options.watchdog);
234
-
235
- if (port !== config.port) {
236
- config.port = port;
237
- saveConfig(config);
238
- }
239
-
240
- console.log('');
241
- log('green', '✓ CDP Tunnel 服务器已启动');
242
- console.log('');
243
- console.log(' 端口: ' + port);
244
- console.log(' 插件: ws://localhost:' + port + '/plugin');
245
- console.log(' CDP: http://localhost:' + port);
246
- if (options.watchdog) {
247
- console.log(' 看门狗: 已启用(崩溃自动重启)');
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('gray', ' 日志: ' + LOG_FILE);
251
- console.log('');
252
-
253
- if (!extStatus.installed) {
254
- console.log('请先安装扩展,然后点击扩展图标连接服务器');
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) {
255
328
  console.log('');
329
+ log('green', '✅ Ready! 插件已连接');
330
+ log('cyan', ` 连接: ws://localhost:${port}/devtools/browser/...`);
331
+ } else {
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
@@ -278,64 +358,83 @@ program
278
358
  program
279
359
  .command('update')
280
360
  .description('自动更新 cdp-tunnel 并重启服务')
281
- .option('-p, --port <port>', '重启时指定端口', parseInt)
282
- .option('-w, --watchdog', '重启时启用看门狗')
361
+ .option('-p, --port <port>', '指定端口', parseInt)
362
+ .option('-w, --watchdog', '启用看门狗')
283
363
  .action(async (options) => {
284
364
  const config = getConfig();
285
365
  const wasRunning = isServerRunning();
286
366
  const savedPort = options.port || config.port;
287
367
  const savedWatchdog = options.watchdog;
288
-
289
- console.log('');
290
- log('cyan', '⬆ 正在检查更新...');
291
-
368
+
292
369
  try {
293
- const beforeVersion = execSync('npm view cdp-tunnel version', { encoding: 'utf8' }).trim();
294
- const localVersion = require(path.join(__dirname, '..', 'package.json')).version;
370
+ log('cyan', '🔍 检查更新...');
371
+ let latestVersion;
372
+ try {
373
+ latestVersion = execSync('npm view cdp-tunnel version', {
374
+ encoding: 'utf8',
375
+ timeout: 30000
376
+ }).trim();
377
+ } catch (err) {
378
+ log('red', '❌ 无法连接 npm registry: ' + err.message);
379
+ process.exit(1);
380
+ }
381
+
382
+ const localVersion = JSON.parse(fs.readFileSync(
383
+ path.join(__dirname, '..', 'package.json'), 'utf8'
384
+ )).version;
385
+
295
386
  log('gray', ' 当前版本: ' + localVersion);
296
- log('gray', ' 最新版本: ' + beforeVersion);
297
-
387
+ log('gray', ' 最新版本: ' + latestVersion);
388
+
389
+ if (localVersion === latestVersion) {
390
+ log('green', '✅ 已是最新版本 (' + localVersion + ')');
391
+ if (wasRunning) {
392
+ log('cyan', ' 服务器仍在运行中');
393
+ }
394
+ process.exit(0);
395
+ }
396
+
298
397
  if (wasRunning) {
299
- log('yellow', ' 正在停止服务器...');
300
- try {
301
- const pid = getServerPid();
302
- process.kill(pid, 'SIGTERM');
303
- fs.unlinkSync(PID_FILE);
398
+ log('yellow', '⏸ 停止服务器...');
399
+ const pid = getServerPid();
400
+ if (pid) {
401
+ try { process.kill(pid, 'SIGTERM'); } catch {}
304
402
  await new Promise(r => setTimeout(r, 1500));
305
- log('green', ' ✓ 服务器已停止');
306
- } catch (e) {
307
- log('yellow', ' ⚠ 停止服务器失败: ' + e.message);
308
403
  }
309
404
  }
310
-
311
- log('cyan', ' 正在更新...');
312
- execSync('npm update -g cdp-tunnel', { stdio: 'inherit' });
313
-
314
- const afterVersion = require(path.join(__dirname, '..', 'package.json')).version;
315
- if (afterVersion !== localVersion) {
316
- log('green', ' ✓ 已更新: ' + localVersion + ' → ' + afterVersion);
405
+
406
+ log('cyan', '📦 更新中 (' + localVersion + ' → ' + latestVersion + ')...');
407
+ try {
408
+ execSync('npm update -g cdp-tunnel', {
409
+ stdio: 'inherit',
410
+ timeout: 120000
411
+ });
412
+ } catch (err) {
413
+ log('red', '❌ 更新失败: ' + err.message);
414
+ process.exit(1);
415
+ }
416
+
417
+ const newVersion = JSON.parse(fs.readFileSync(
418
+ path.join(__dirname, '..', 'package.json'), 'utf8'
419
+ )).version;
420
+
421
+ if (newVersion !== localVersion) {
422
+ log('green', '✅ 已更新: ' + localVersion + ' → ' + newVersion);
317
423
  } else {
318
- log('green', ' 已是最新版本');
424
+ log('yellow', ' 版本未变化,可能需要手动更新');
319
425
  }
320
-
321
- ensureConfigDir();
322
- cleanupLogFile();
323
- startServer(savedPort, savedWatchdog);
324
- log('green', ' ✓ 服务器已重启 (端口: ' + savedPort + ')');
325
- console.log('');
326
- } catch (e) {
327
- log('red', '✗ 更新失败: ' + e.message);
328
- console.log('');
426
+
329
427
  if (wasRunning) {
330
- log('yellow', '正在尝试恢复服务器...');
331
- try {
332
- ensureConfigDir();
333
- startServer(savedPort, savedWatchdog);
334
- log('green', ' 服务器已恢复');
335
- } catch (re) {
336
- log('red', '✗ 恢复失败,请手动启动: cdp-tunnel start');
337
- }
428
+ log('cyan', '🔄 重启服务器...');
429
+ startServer(savedPort, savedWatchdog, config.autoRestart);
430
+ log('green', '✅ 服务器已重启');
431
+ } else {
432
+ log('cyan', ' 运行 cdp-tunnel start 启动服务器');
338
433
  }
434
+
435
+ process.exit(0);
436
+ } catch (err) {
437
+ log('red', '❌ 更新出错: ' + err.message);
339
438
  process.exit(1);
340
439
  }
341
440
  });
@@ -359,6 +458,10 @@ program
359
458
  console.log(' PID: ' + getServerPid());
360
459
  console.log(' CDP: http://localhost:' + config.port);
361
460
  }
461
+
462
+ if (config.autoRestart) {
463
+ console.log(' 自动重启: 已启用');
464
+ }
362
465
 
363
466
  console.log('');
364
467
  if (extStatus.installed && extStatus.connected) {
@@ -492,4 +595,19 @@ program
492
595
  }
493
596
  });
494
597
 
598
+ program.addHelpText('after', `
599
+
600
+ 常用命令:
601
+ $ cdp-tunnel start 启动服务(自动启动 Chrome)
602
+ $ cdp-tunnel start --auto-restart Chrome 断连时自动重启
603
+ $ cdp-tunnel start --watchdog 服务崩溃时自动重启
604
+ $ cdp-tunnel status 查看状态
605
+ $ cdp-tunnel update 检查并更新
606
+ $ cdp-tunnel extension 安装 Chrome 扩展
607
+
608
+ 快速开始:
609
+ $ npm install -g cdp-tunnel
610
+ $ cdp-tunnel start # 一行命令搞定!
611
+ `);
612
+
495
613
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "1.0.15",
3
+ "version": "1.2.0",
4
4
  "description": "Chrome Extension CDP Proxy - 通过 Chrome 扩展将 chrome.debugger API 暴露为 WebSocket 端点",
5
5
  "main": "server/proxy-server.js",
6
6
  "bin": "./cli/index.js",
@@ -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 = {
@@ -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