cdp-tunnel 2.10.8 → 2.10.11

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 CHANGED
@@ -55,45 +55,40 @@
55
55
 
56
56
  ## Quick Start
57
57
 
58
- ### Option 1: Install via npm (Recommended)
58
+ ### Option 1: Install Extension Only (No npm needed)
59
59
 
60
- ```bash
61
- # Install globally
62
- npm install -g cdp-tunnel
63
-
64
- # Start server
65
- cdp-tunnel start
60
+ Download and install the Chrome extension directly:
66
61
 
67
- # Check status
68
- cdp-tunnel status
69
-
70
- # Install Chrome extension (opens interactive guide)
71
- cdp-tunnel extension
72
- ```
62
+ 1. Download [`cdp-tunnel-extension.zip`](https://github.com/dyyz1993/cdp-tunnel/releases/latest/download/cdp-tunnel-extension.zip) from the latest release
63
+ 2. Unzip it
64
+ 3. Open `chrome://extensions/` in Chrome
65
+ 4. Enable **Developer mode** (top right)
66
+ 5. Click **Load unpacked** → select the unzipped folder
67
+ 6. Start the proxy server separately (see Option 2 or 3 below)
73
68
 
74
- ### Option 2: Download Extension from GitHub Releases
69
+ ### Option 2: Install via npm (Recommended)
75
70
 
76
- **Download Extension:** [cdp-tunnel-extension.zip (v2.10.3)](https://github.com/dyyz1993/cdp-tunnel/releases/tag/v2.10.3)
71
+ ```bash
72
+ # One command to set up everything
73
+ npx cdp-tunnel setup
77
74
 
78
- 1. Download `cdp-tunnel-extension.zip` from the latest [GitHub Releases](https://github.com/dyyz1993/cdp-tunnel/releases)
79
- 2. Unzip the file
80
- 3. Open `chrome://extensions/` in Chrome
81
- 4. Enable "Developer mode"
82
- 5. Click "Load unpacked"
83
- 6. Select the unzipped extension directory
75
+ # Or install globally
76
+ npm install -g cdp-tunnel
77
+ cdp-tunnel setup # Start server + auto-load extension into Chrome
78
+ cdp-tunnel status # Check status
79
+ cdp-tunnel diagnose # Diagnose connection issues
80
+ ```
84
81
 
85
82
  ### Option 3: Manual Installation
86
83
 
87
84
  #### 1. Start the Proxy Server
88
85
 
89
86
  ```bash
90
- cd server
91
- npm install
92
- node proxy-server.js
87
+ npx cdp-tunnel start
88
+ # or manually:
89
+ npx cdp-tunnel start -p 9221
93
90
  ```
94
91
 
95
- The server will start on `localhost:9221`.
96
-
97
92
  #### 2. Install Chrome Extension
98
93
 
99
94
  1. Open `chrome://extensions/`
package/cli/index.js CHANGED
@@ -12,6 +12,7 @@ const PID_FILE = path.join(CONFIG_DIR, 'server.pid');
12
12
  const LOG_FILE = path.join(CONFIG_DIR, 'server.log');
13
13
  const EXTENSION_STATE_FILE = path.join(CONFIG_DIR, 'extension-state.json');
14
14
  const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
15
+ const INSTANCE_LIFETIME = 2 * 60 * 60 * 1000; // 2小时
15
16
 
16
17
  const INSTANCES_DIR = path.join(CONFIG_DIR, 'instances');
17
18
 
@@ -53,6 +54,26 @@ function syncExtensionVersion() {
53
54
 
54
55
  syncExtensionVersion();
55
56
 
57
+ function cleanupStaleInstances() {
58
+ if (!fs.existsSync(INSTANCES_DIR)) return;
59
+ try {
60
+ const ports = fs.readdirSync(INSTANCES_DIR);
61
+ let cleaned = 0;
62
+ ports.forEach(port => {
63
+ const p = parseInt(port);
64
+ if (isNaN(p)) return;
65
+ if (!isServerRunning(p)) {
66
+ fs.rmSync(path.join(INSTANCES_DIR, port), { recursive: true, force: true });
67
+ cleaned++;
68
+ }
69
+ });
70
+ if (cleaned > 0) {
71
+ console.log('');
72
+ log('gray', `已清理 ${cleaned} 个已停止的实例`);
73
+ }
74
+ } catch {}
75
+ }
76
+
56
77
  function log(color, ...args) {
57
78
  const colors = {
58
79
  green: '\x1b[32m',
@@ -571,6 +592,7 @@ program
571
592
  .description('查看服务器状态')
572
593
  .option('-p, --port <port>', '指定端口', parseInt)
573
594
  .action((options) => {
595
+ cleanupStaleInstances();
574
596
  console.log('');
575
597
  console.log('CDP Tunnel 状态');
576
598
  console.log('─'.repeat(30));
@@ -220,10 +220,12 @@ importScripts('features/automation-badge.js');
220
220
  SpecialHandler.addTabToAutomationGroup(tabId, clientId, null, ctx);
221
221
  }
222
222
  });
223
- } else if (!groupPromise) {
224
- Logger.info('[Tabs] Tab', tabId, 'left group, no cache and no pending creation — skipping (onRemoved handles re-group)');
225
- } else {
223
+ } else if (groupPromise) {
226
224
  Logger.info('[Tabs] Tab', tabId, 'left group, group creation pending — skipping');
225
+ } else {
226
+ Logger.info('[Tabs] Tab', tabId, 'left group, no cached groupId — force re-group');
227
+ var ctx = { _state: state, _wsManager: wsManager, clientId: clientId, mode: state.mode };
228
+ SpecialHandler.addTabToAutomationGroup(tabId, clientId, null, ctx);
227
229
  }
228
230
  }
229
231
  }
@@ -416,11 +416,14 @@ var SpecialHandler = (function() {
416
416
  var baseName = CDPUtils.getGroupBaseName(clientId, connectionTag, mode);
417
417
  var newName = baseName + ' (' + tabs.length + ')';
418
418
 
419
+ if (chrome.runtime.lastError || tabs.length === 0) return;
419
420
  chrome.tabGroups.update(groupId, {
420
421
  title: newName
421
422
  }, function() {
422
423
  if (chrome.runtime.lastError) {
423
- Logger.error('[TabGroup] Failed to update group name:', chrome.runtime.lastError.message);
424
+ if (!chrome.runtime.lastError.message.includes('No group with id')) {
425
+ Logger.error('[TabGroup] Failed to update group name:', chrome.runtime.lastError.message);
426
+ }
424
427
  } else {
425
428
  Logger.info('[TabGroup] Updated group name:', newName);
426
429
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "2.10.8",
4
+ "version": "2.10.11",
5
5
  "description": "Chrome DevTools Protocol Bridge for Playwright/Puppeteer automation",
6
6
  "permissions": [
7
7
  "debugger",
@@ -1,5 +1,5 @@
1
1
  var Config = {
2
- WS_URL: 'ws://localhost:14026/plugin',
2
+ WS_URL: 'ws://localhost:17606/plugin',
3
3
  RECONNECT_DELAY: 3000,
4
4
  DEBUGGER_VERSION: '1.3',
5
5
  HEARTBEAT_INTERVAL: 25000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.10.8",
3
+ "version": "2.10.11",
4
4
  "description": "Bridge Chrome's debugger API to WebSocket — control your existing browser with Playwright/Puppeteer via CDP",
5
5
  "main": "server/proxy-server.js",
6
6
  "bin": "./cli/index.js",
@@ -361,6 +361,17 @@ async function handleHttpRequest(req, res) {
361
361
  if (url.pathname === '/json' || url.pathname === '/json/' ||
362
362
  url.pathname === '/json/list' || url.pathname === '/json/list/' ||
363
363
  url.pathname.match(/^\/json\/list\/[^/]+$/)) {
364
+ // create 模式(9221):HTTP /json/list 无 clientId 上下文,无法做归属过滤
365
+ // 标准客户端(Playwright/Puppeteer)走 WS Target.setAutoAttach,不依赖此接口
366
+ // takeover 模式(9222):操作的是用户自己的 tab,返回全部是预期行为
367
+ if (!req._takeoverMode) {
368
+ if (shouldLog('info')) {
369
+ console.log(`[JSON LIST] create mode — returning empty list for isolation`);
370
+ }
371
+ res.writeHead(200, { 'Content-Type': 'application/json' });
372
+ res.end(JSON.stringify([]));
373
+ return;
374
+ }
364
375
  const pluginWs = resolvePluginFromUrl(url);
365
376
  const targets = await requestTargetsFromPlugin(pluginWs);
366
377
  const browserId = pluginWs ? pluginWs.pluginId : BROWSER_ID;
@@ -444,7 +455,8 @@ wss.on('connection', (ws, req) => {
444
455
  handleClientConnection(ws, clientInfo, customClientId, targetPluginId, mode);
445
456
  } else if (path.startsWith('/devtools/page/')) {
446
457
  const targetId = path.replace('/devtools/page/', '');
447
- handlePageConnection(ws, clientInfo, targetId);
458
+ const mode = req._takeoverMode ? 'takeover' : 'create';
459
+ handlePageConnection(ws, clientInfo, targetId, mode);
448
460
  } else {
449
461
  console.log(`[REJECTED] Unknown path: ${path} from ${clientInfo.ip}:${clientInfo.port}`);
450
462
  ws.close(1008, 'Invalid path. Use /plugin or /client');
@@ -1457,11 +1469,11 @@ function handleClientConnection(ws, clientInfo, customClientId = null, targetPlu
1457
1469
  });
1458
1470
  }
1459
1471
 
1460
- function handlePageConnection(ws, clientInfo, targetId) {
1472
+ function handlePageConnection(ws, clientInfo, targetId, mode = 'create') {
1461
1473
  clientConnections.add(ws);
1462
1474
  const id = generateId('page');
1463
1475
  if (shouldLog('info')) {
1464
- console.log(`\n[PAGE CONNECTED] ID: ${id}, targetId: ${targetId}`);
1476
+ console.log(`\n[PAGE CONNECTED] ID: ${id}, targetId: ${targetId}, mode: ${mode}`);
1465
1477
  console.log(` - Remote: ${clientInfo.ip}:${clientInfo.port}`);
1466
1478
  console.log(` - Total client connections: ${clientConnections.size}`);
1467
1479
  }
@@ -1470,17 +1482,31 @@ function handlePageConnection(ws, clientInfo, targetId) {
1470
1482
  ws.isAlive = true;
1471
1483
  ws.cdpTrace = [];
1472
1484
  ws.targetId = targetId;
1485
+ ws.mode = mode;
1473
1486
  ws.lastActivityTime = Date.now();
1474
1487
  clientById.set(id, ws);
1475
1488
 
1489
+ // 查找 target 归属的 plugin
1476
1490
  let plugin = null;
1491
+ let ownerClientId = null;
1477
1492
  for (const p of pluginConnections) {
1478
1493
  const ns = getNamespace(p);
1479
1494
  if (ns.targetIdToClientId.has(targetId)) {
1480
1495
  plugin = p;
1496
+ ownerClientId = ns.targetIdToClientId.get(targetId);
1481
1497
  break;
1482
1498
  }
1483
1499
  }
1500
+
1501
+ // create 模式:target 必须有明确的 clientId 归属,否则拒绝 attach(防止跨 client 越权)
1502
+ if (mode !== 'takeover' && !ownerClientId) {
1503
+ console.log(`[PAGE REJECTED] targetId=${targetId?.substring(0, 8)} has no owner in create mode — possible cross-client attach attempt from ${clientInfo.ip}`);
1504
+ ws.close(1008, 'Target does not belong to any client');
1505
+ clientConnections.delete(ws);
1506
+ clientById.delete(id);
1507
+ return;
1508
+ }
1509
+ // takeover 模式:允许无归属 target(操作的是用户自己的 tab)
1484
1510
  if (!plugin) {
1485
1511
  plugin = pluginConnections.values().next().value;
1486
1512
  }
@@ -1996,7 +2022,10 @@ server.on('error', (err) => {
1996
2022
 
1997
2023
  server.listen(PORT, '0.0.0.0');
1998
2024
 
1999
- const takeoverServer = http.createServer((req, res) => handleHttpRequest(req, res));
2025
+ const takeoverServer = http.createServer((req, res) => {
2026
+ req._takeoverMode = true;
2027
+ handleHttpRequest(req, res);
2028
+ });
2000
2029
  takeoverServer.on('upgrade', (req, socket, head) => {
2001
2030
  req._takeoverMode = true;
2002
2031
  const url = new URL(req.url, `http://localhost:${TAKEOVER_PORT}`);