cdp-tunnel 2.0.0 → 2.1.1

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/index.js CHANGED
@@ -181,6 +181,18 @@ function startServer(port, watchdog, autoRestart) {
181
181
  console.log(' 重启次数: ' + restartTimestamps.length + '/' + MAX_RESTARTS + ' (60秒内)');
182
182
  console.log('');
183
183
 
184
+ // Kill any leftover process occupying the port before restarting
185
+ try {
186
+ const result = execSync(`lsof -ti:${port} 2>/dev/null || true`).toString().trim();
187
+ if (result) {
188
+ const pids = result.split('\n').filter(p => p && parseInt(p) !== process.pid);
189
+ pids.forEach(p => { try { process.kill(parseInt(p), 'SIGKILL'); } catch {} });
190
+ if (pids.length > 0) {
191
+ log('gray', ' 已清理占用端口 ' + port + ' 的残留进程: ' + pids.join(', '));
192
+ }
193
+ }
194
+ } catch {}
195
+
184
196
  setTimeout(() => startServer(port, true, autoRestart), 3000);
185
197
  });
186
198
 
@@ -219,66 +219,54 @@ importScripts('features/automation-badge.js');
219
219
 
220
220
  // 将标签页添加到CDP组(添加延迟等待)
221
221
  setTimeout(function() {
222
- // 获取openerTabId对应的clientId
223
222
  var openerClientId = openerTabId ? State.getClientIdByTabId(openerTabId) : null;
224
- var groupName;
225
-
226
- // 如果有指定的clientId,使用该clientId作为组名
227
- if (openerClientId) {
228
- groupName = 'CDP-' + openerClientId.substring(0, 8);
229
- Logger.info('[TabGroup] Using opener clientId for group name:', groupName, 'openerTabId:', openerTabId);
230
- } else {
231
- // 回退到使用第一个CDP客户端的ID
223
+ var groupClientId = openerClientId;
224
+ if (!groupClientId) {
232
225
  var cdpClients = State.getCDPClients() || [];
233
226
  if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
234
- groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
235
- } else {
236
- // 如果没有CDP客户端,使用时间戳作为组名
237
- groupName = 'CDP-' + Date.now().toString(36);
227
+ groupClientId = cdpClients[0].id;
238
228
  }
239
- Logger.info('[TabGroup] Using fallback clientId for group name:', groupName);
240
229
  }
241
230
 
242
- chrome.tabGroups.query({ title: groupName }, function(groups) {
243
- if (groups.length > 0) {
244
- // 找到现有的组,将标签页添加到组
245
- chrome.tabs.group({ tabIds: tabId, groupId: groups[0].id }, function(groupId) {
231
+ if (!groupClientId) return;
232
+ var baseName = CDPUtils.getGroupBaseName(groupClientId);
233
+ Logger.info('[TabGroup] background onCreated, baseName:', baseName);
234
+
235
+ chrome.tabGroups.query({}, function(allGroups) {
236
+ var existing = CDPUtils.findGroupByName(allGroups, baseName);
237
+ if (existing) {
238
+ chrome.tabs.group({ tabIds: tabId, groupId: existing.id }, function(groupId) {
246
239
  if (chrome.runtime.lastError) {
247
240
  Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message);
248
241
  } else {
249
- Logger.info('[TabGroup] Tab added to group:', groupId, 'Group name:', groupName);
242
+ State.setGroupIdForClient(groupClientId, existing.id);
243
+ Logger.info('[TabGroup] Tab added to group:', groupId);
250
244
  }
251
245
  });
252
246
  } else {
253
- // 创建新组并添加标签页
254
247
  chrome.tabs.group({ tabIds: tabId }, function(groupId) {
255
248
  if (chrome.runtime.lastError) {
256
249
  Logger.error('[TabGroup] Failed to create group:', chrome.runtime.lastError.message);
257
250
  return;
258
251
  }
259
- // 更新组的标题和颜色
260
252
  if (groupId) {
261
- // 为不同的组使用不同的颜色
262
- var colors = ['blue', 'red', 'yellow', 'green', 'pink', 'purple', 'cyan', 'orange'];
263
- var colorIndex = Math.abs(CDPUtils.hashCode(groupName)) % colors.length;
264
- var groupColor = colors[colorIndex];
265
-
266
253
  chrome.tabGroups.update(groupId, {
267
- title: groupName,
268
- color: groupColor,
254
+ title: baseName,
255
+ color: CDPUtils.getGroupColorForClient(groupClientId),
269
256
  collapsed: true
270
257
  }, function(group) {
271
258
  if (chrome.runtime.lastError) {
272
259
  Logger.error('[TabGroup] Failed to update group:', chrome.runtime.lastError.message);
273
260
  } else {
274
- Logger.info('[TabGroup] Group created and updated:', group);
261
+ State.setGroupIdForClient(groupClientId, groupId);
262
+ Logger.info('[TabGroup] Group created:', group);
275
263
  }
276
264
  });
277
265
  }
278
266
  });
279
267
  }
280
268
  });
281
- }, 2000); // 等待2秒
269
+ }, 2000);
282
270
 
283
271
  Logger.info('[Tabs] Sending Target.attachedToTarget event');
284
272
 
@@ -132,34 +132,28 @@ var SpecialHandler = (function() {
132
132
  muteTabIfNeeded(tabId);
133
133
  }, 500);
134
134
 
135
- var groupName;
136
135
  var groupClientId = clientId;
137
-
138
- if (clientId) {
139
- groupName = 'CDP-' + clientId.substring(0, 8);
140
- } else {
136
+ if (!groupClientId) {
141
137
  var cdpClients = State.getCDPClients() || [];
142
138
  if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
143
- groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
144
139
  groupClientId = cdpClients[0].id;
145
- } else {
146
- groupName = 'CDP-Automation';
147
140
  }
148
141
  }
142
+
143
+ if (!groupClientId) return;
144
+ var baseName = CDPUtils.getGroupBaseName(groupClientId);
149
145
 
150
146
  setTimeout(function() {
151
- Logger.info('[TabGroup] Executing group operation after delay...');
147
+ Logger.info('[TabGroup] Executing group operation for:', baseName);
152
148
 
153
149
  chrome.tabGroups.query({}, function(allGroups) {
154
- var groups = allGroups.filter(function(g) {
155
- return g.title && g.title.indexOf(groupName) === 0;
156
- });
157
- if (groups.length > 0) {
158
- chrome.tabs.group({ tabIds: tabId, groupId: groups[0].id }, function(result) {
150
+ var existing = CDPUtils.findGroupByName(allGroups, baseName);
151
+ if (existing) {
152
+ chrome.tabs.group({ tabIds: tabId, groupId: existing.id }, function(result) {
159
153
  if (chrome.runtime.lastError) {
160
154
  Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message);
161
155
  } else {
162
- State.setGroupIdForClient(groupClientId, groups[0].id);
156
+ State.setGroupIdForClient(groupClientId, existing.id);
163
157
  updateTabGroupName(groupClientId);
164
158
  }
165
159
  });
@@ -170,12 +164,9 @@ var SpecialHandler = (function() {
170
164
  return;
171
165
  }
172
166
  if (groupId) {
173
- var colors = ['blue', 'red', 'yellow', 'green', 'pink', 'purple', 'cyan', 'orange'];
174
- var colorIndex = Math.abs(CDPUtils.hashCode(groupName)) % colors.length;
175
-
176
167
  chrome.tabGroups.update(groupId, {
177
- title: groupName,
178
- color: colors[colorIndex],
168
+ title: baseName,
169
+ color: CDPUtils.getGroupColorForClient(groupClientId),
179
170
  collapsed: true
180
171
  }, function() {
181
172
  if (chrome.runtime.lastError) {
@@ -201,9 +192,8 @@ var SpecialHandler = (function() {
201
192
  chrome.tabs.query({ groupId: groupId }, function(tabs) {
202
193
  if (chrome.runtime.lastError || !tabs) return;
203
194
 
204
- var count = tabs.length;
205
- var baseName = 'CDP-' + clientId.substring(0, 8);
206
- var newName = baseName + ' (' + count + ')';
195
+ var baseName = CDPUtils.getGroupBaseName(clientId);
196
+ var newName = baseName + ' (' + tabs.length + ')';
207
197
 
208
198
  chrome.tabGroups.update(groupId, {
209
199
  title: newName
@@ -388,7 +378,7 @@ function checkTabVisibility(tabId) {
388
378
  if (clientId) {
389
379
  State.setTabIdToClientId(target.tabId, clientId);
390
380
  }
391
- addTabToAutomationGroup(target.tabId, clientId);
381
+ State.addPreExistingTab(target.tabId);
392
382
 
393
383
  if (config.waitForDebuggerOnStart) {
394
384
  State.addPendingDebuggerTab(target.tabId);
@@ -410,7 +400,7 @@ function checkTabVisibility(tabId) {
410
400
  if (clientId) {
411
401
  State.setTabIdToClientId(target.tabId, clientId);
412
402
  }
413
- addTabToAutomationGroup(target.tabId, clientId);
403
+ State.addPreExistingTab(target.tabId);
414
404
 
415
405
  if (config.waitForDebuggerOnStart) {
416
406
  State.addPendingDebuggerTab(target.tabId);
@@ -24,7 +24,8 @@ var State = (function() {
24
24
  clientIdToTabId: new Map(),
25
25
  clientIdToSessionId: new Map(),
26
26
  tabIdToClientId: new Map(),
27
- clientIdToGroupId: new Map()
27
+ clientIdToGroupId: new Map(),
28
+ preExistingTabIds: new Set()
28
29
  };
29
30
 
30
31
  function mapSession(sessionId, tabId, targetId) {
@@ -264,6 +265,7 @@ var State = (function() {
264
265
  _state.hasConnectedClient = false;
265
266
  _state.tabIdToClientId.clear();
266
267
  _state.clientIdToGroupId.clear();
268
+ _state.preExistingTabIds.clear();
267
269
  }
268
270
 
269
271
  function cleanupAllTabs() {
@@ -346,6 +348,30 @@ var State = (function() {
346
348
  _state.clientIdToGroupId.delete(clientId);
347
349
  }
348
350
 
351
+ function addPreExistingTab(tabId) {
352
+ _state.preExistingTabIds.add(tabId);
353
+ }
354
+
355
+ function isPreExistingTab(tabId) {
356
+ return _state.preExistingTabIds.has(tabId);
357
+ }
358
+
359
+ function getPreExistingTabs() {
360
+ return Array.from(_state.preExistingTabIds);
361
+ }
362
+
363
+ function removePreExistingTab(tabId) {
364
+ _state.preExistingTabIds.delete(tabId);
365
+ }
366
+
367
+ function clearPreExistingTabsForClient(clientId) {
368
+ _state.preExistingTabIds.forEach(function(tabId) {
369
+ if (State.getClientIdByTabId(tabId) === clientId) {
370
+ _state.preExistingTabIds.delete(tabId);
371
+ }
372
+ });
373
+ }
374
+
349
375
  function getTabIdByClientId(clientId) {
350
376
  return _state.clientIdToTabId.get(clientId);
351
377
  }
@@ -448,6 +474,11 @@ var State = (function() {
448
474
  getClientIdByTabId: getClientIdByTabId,
449
475
  setGroupIdForClient: setGroupIdForClient,
450
476
  getGroupIdForClient: getGroupIdForClient,
451
- removeGroupForClient: removeGroupForClient
477
+ removeGroupForClient: removeGroupForClient,
478
+ addPreExistingTab: addPreExistingTab,
479
+ isPreExistingTab: isPreExistingTab,
480
+ getPreExistingTabs: getPreExistingTabs,
481
+ removePreExistingTab: removePreExistingTab,
482
+ clearPreExistingTabsForClient: clearPreExistingTabsForClient
452
483
  };
453
484
  })();
@@ -199,6 +199,16 @@ var WebSocketManager = (function() {
199
199
  closeTabsByClientId(discClientId, resolve);
200
200
  });
201
201
  }).then(function() {
202
+ var preExistingTabs = State.getPreExistingTabs();
203
+ var clientPreExisting = preExistingTabs.filter(function(tabId) {
204
+ return State.getClientIdByTabId(tabId) === discClientId;
205
+ });
206
+ clientPreExisting.forEach(function(tabId) {
207
+ chrome.debugger.detach({ tabId: tabId }).catch(function() {});
208
+ State.removeAttachedTab(tabId);
209
+ });
210
+ State.clearPreExistingTabsForClient(discClientId);
211
+
202
212
  State.removeCDPClient(discClientId);
203
213
  if (State.getCDPClients().length === 0) {
204
214
  State.setHasConnectedClient(false);
@@ -261,26 +271,14 @@ var WebSocketManager = (function() {
261
271
  if (groupId) {
262
272
  closeGroupById(groupId, clientId, resolve);
263
273
  } else {
264
- var groupName = 'CDP-' + clientId.substring(0, 8);
265
- chrome.tabGroups.query({ title: groupName }, function(groups) {
266
- if (!groups || groups.length === 0) {
267
- chrome.tabGroups.query({}, function(allGroups) {
268
- if (allGroups) {
269
- var match = allGroups.find(function(g) {
270
- return g.title && g.title.indexOf(groupName) === 0;
271
- });
272
- if (match) {
273
- closeGroupById(match.id, clientId, resolve);
274
- } else {
275
- Logger.info('[WS] No tab group found, closing tabs by clientId:', clientId);
276
- closeTabsByClientId(clientId, resolve);
277
- }
278
- } else {
279
- resolve();
280
- }
281
- });
274
+ var baseName = CDPUtils.getGroupBaseName(clientId);
275
+ chrome.tabGroups.query({}, function(allGroups) {
276
+ var match = CDPUtils.findGroupByName(allGroups, baseName);
277
+ if (match) {
278
+ closeGroupById(match.id, clientId, resolve);
282
279
  } else {
283
- closeGroupById(groups[0].id, clientId, resolve);
280
+ Logger.info('[WS] No tab group found, closing tabs by clientId:', clientId);
281
+ closeTabsByClientId(clientId, resolve);
284
282
  }
285
283
  });
286
284
  }
@@ -321,7 +319,7 @@ var WebSocketManager = (function() {
321
319
  var tabsToClose = [];
322
320
 
323
321
  attachedTabs.forEach(function(tabId) {
324
- if (State.getClientIdByTabId(tabId) === clientId) {
322
+ if (State.getClientIdByTabId(tabId) === clientId && !State.isPreExistingTab(tabId)) {
325
323
  tabsToClose.push(tabId);
326
324
  }
327
325
  });
@@ -27,10 +27,49 @@ var CDPUtils = (function() {
27
27
  return hash;
28
28
  }
29
29
 
30
+ var GROUP_COLORS = ['blue', 'cyan', 'green', 'yellow', 'orange', 'red', 'pink', 'purple'];
31
+
32
+ function getGroupColorForClient(clientId) {
33
+ var idx = Math.abs(hashCode(clientId)) % GROUP_COLORS.length;
34
+ return GROUP_COLORS[idx];
35
+ }
36
+
37
+ function getClientIndex(clientId) {
38
+ var clients = (typeof State !== 'undefined') ? (State.getCDPClients() || []) : [];
39
+ for (var i = 0; i < clients.length; i++) {
40
+ if (clients[i].id === clientId) return i + 1;
41
+ }
42
+ return 0;
43
+ }
44
+
45
+ function buildGroupName(clientId) {
46
+ if (!clientId) return 'CDP';
47
+ var idx = getClientIndex(clientId);
48
+ return idx > 0 ? ('CDP #' + idx) : ('CDP-' + clientId.substring(0, 6));
49
+ }
50
+
51
+ function getGroupBaseName(clientId) {
52
+ return buildGroupName(clientId);
53
+ }
54
+
55
+ function findGroupByName(allGroups, baseName) {
56
+ for (var i = 0; i < allGroups.length; i++) {
57
+ var g = allGroups[i];
58
+ if (g.title && g.title.indexOf(baseName) === 0) return g;
59
+ }
60
+ return null;
61
+ }
62
+
30
63
  return {
31
64
  generateSessionId: generateSessionId,
32
65
  getChromeVersion: getChromeVersion,
33
66
  sleep: sleep,
34
- hashCode: hashCode
67
+ hashCode: hashCode,
68
+ GROUP_COLORS: GROUP_COLORS,
69
+ getGroupColorForClient: getGroupColorForClient,
70
+ getClientIndex: getClientIndex,
71
+ buildGroupName: buildGroupName,
72
+ getGroupBaseName: getGroupBaseName,
73
+ findGroupByName: findGroupByName
35
74
  };
36
75
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
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",
@@ -22,6 +22,7 @@ const PORT = CONFIG.PORT;
22
22
  const CONFIG_DIR = path.join(os.homedir(), '.cdp-tunnel');
23
23
  const EXTENSION_STATE_FILE = path.join(CONFIG_DIR, 'extension-state.json');
24
24
  const PLUGIN_EVER_CONNECTED_FILE = path.join(CONFIG_DIR, 'plugin-ever-connected');
25
+ const SERVER_START_TIME = Date.now();
25
26
 
26
27
  let lastChromeRestartAttempt = 0;
27
28
  const CHROME_RESTART_COOLDOWN = CONFIG.CHROME_RESTART_COOLDOWN;
@@ -739,7 +740,7 @@ function handlePluginConnection(ws, clientInfo) {
739
740
  type: 'connected',
740
741
  role: 'plugin',
741
742
  id: id,
742
- fresh: true,
743
+ fresh: (Date.now() - SERVER_START_TIME) < 5000,
743
744
  timestamp: Date.now()
744
745
  }));
745
746
  }
@@ -1513,4 +1514,14 @@ process.on('SIGTERM', () => {
1513
1514
  process.exit(0);
1514
1515
  });
1515
1516
 
1517
+ server.on('error', (err) => {
1518
+ if (err.code === 'EADDRINUSE') {
1519
+ console.error(`[FATAL] Port ${PORT} is already in use. Is another cdp-tunnel running?`);
1520
+ console.error(` Run "cdp-tunnel stop" first, or kill the process on port ${PORT}.`);
1521
+ process.exit(2);
1522
+ }
1523
+ console.error('[FATAL] Server error:', err.message);
1524
+ process.exit(1);
1525
+ });
1526
+
1516
1527
  server.listen(PORT, '0.0.0.0');