cdp-tunnel 1.7.0 → 2.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.
@@ -13,17 +13,6 @@ importScripts('cdp/index.js');
13
13
  importScripts('features/screencast.js');
14
14
  importScripts('features/automation-badge.js');
15
15
 
16
- // 为字符串添加hashCode方法(用于生成颜色索引)
17
- String.prototype.hashCode = function() {
18
- var hash = 0;
19
- for (var i = 0; i < this.length; i++) {
20
- var char = this.charCodeAt(i);
21
- hash = ((hash << 5) - hash) + char;
22
- hash = hash & hash; // Convert to 32bit integer
23
- }
24
- return hash;
25
- };
26
-
27
16
  (function() {
28
17
  'use strict';
29
18
 
@@ -230,66 +219,54 @@ String.prototype.hashCode = function() {
230
219
 
231
220
  // 将标签页添加到CDP组(添加延迟等待)
232
221
  setTimeout(function() {
233
- // 获取openerTabId对应的clientId
234
222
  var openerClientId = openerTabId ? State.getClientIdByTabId(openerTabId) : null;
235
- var groupName;
236
-
237
- // 如果有指定的clientId,使用该clientId作为组名
238
- if (openerClientId) {
239
- groupName = 'CDP-' + openerClientId.substring(0, 8);
240
- Logger.info('[TabGroup] Using opener clientId for group name:', groupName, 'openerTabId:', openerTabId);
241
- } else {
242
- // 回退到使用第一个CDP客户端的ID
223
+ var groupClientId = openerClientId;
224
+ if (!groupClientId) {
243
225
  var cdpClients = State.getCDPClients() || [];
244
226
  if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
245
- groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
246
- } else {
247
- // 如果没有CDP客户端,使用时间戳作为组名
248
- groupName = 'CDP-' + Date.now().toString(36);
227
+ groupClientId = cdpClients[0].id;
249
228
  }
250
- Logger.info('[TabGroup] Using fallback clientId for group name:', groupName);
251
229
  }
252
230
 
253
- chrome.tabGroups.query({ title: groupName }, function(groups) {
254
- if (groups.length > 0) {
255
- // 找到现有的组,将标签页添加到组
256
- 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) {
257
239
  if (chrome.runtime.lastError) {
258
240
  Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message);
259
241
  } else {
260
- 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);
261
244
  }
262
245
  });
263
246
  } else {
264
- // 创建新组并添加标签页
265
247
  chrome.tabs.group({ tabIds: tabId }, function(groupId) {
266
248
  if (chrome.runtime.lastError) {
267
249
  Logger.error('[TabGroup] Failed to create group:', chrome.runtime.lastError.message);
268
250
  return;
269
251
  }
270
- // 更新组的标题和颜色
271
252
  if (groupId) {
272
- // 为不同的组使用不同的颜色
273
- var colors = ['blue', 'red', 'yellow', 'green', 'pink', 'purple', 'cyan', 'orange'];
274
- var colorIndex = Math.abs(groupName.hashCode ? groupName.hashCode() : 0) % colors.length;
275
- var groupColor = colors[colorIndex];
276
-
277
253
  chrome.tabGroups.update(groupId, {
278
- title: groupName,
279
- color: groupColor,
254
+ title: baseName,
255
+ color: CDPUtils.getGroupColorForClient(groupClientId),
280
256
  collapsed: true
281
257
  }, function(group) {
282
258
  if (chrome.runtime.lastError) {
283
259
  Logger.error('[TabGroup] Failed to update group:', chrome.runtime.lastError.message);
284
260
  } else {
285
- Logger.info('[TabGroup] Group created and updated:', group);
261
+ State.setGroupIdForClient(groupClientId, groupId);
262
+ Logger.info('[TabGroup] Group created:', group);
286
263
  }
287
264
  });
288
265
  }
289
266
  });
290
267
  }
291
268
  });
292
- }, 2000); // 等待2秒
269
+ }, 2000);
293
270
 
294
271
  Logger.info('[Tabs] Sending Target.attachedToTarget event');
295
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(groupName.hashCode ? groupName.hashCode() : 0) % 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
@@ -217,17 +207,6 @@ var SpecialHandler = (function() {
217
207
  });
218
208
  }
219
209
 
220
- // 为字符串添加hashCode方法(用于生成颜色索引)
221
- String.prototype.hashCode = function() {
222
- var hash = 0;
223
- for (var i = 0; i < this.length; i++) {
224
- var char = this.charCodeAt(i);
225
- hash = ((hash << 5) - hash) + char;
226
- hash = hash & hash; // Convert to 32bit integer
227
- }
228
- return hash;
229
- };
230
-
231
210
  function targetActivateTarget(context) {
232
211
  var params = context.params;
233
212
  var targetId = params && params.targetId;
@@ -399,7 +378,7 @@ function checkTabVisibility(tabId) {
399
378
  if (clientId) {
400
379
  State.setTabIdToClientId(target.tabId, clientId);
401
380
  }
402
- addTabToAutomationGroup(target.tabId, clientId);
381
+ State.addPreExistingTab(target.tabId);
403
382
 
404
383
  if (config.waitForDebuggerOnStart) {
405
384
  State.addPendingDebuggerTab(target.tabId);
@@ -421,7 +400,7 @@ function checkTabVisibility(tabId) {
421
400
  if (clientId) {
422
401
  State.setTabIdToClientId(target.tabId, clientId);
423
402
  }
424
- addTabToAutomationGroup(target.tabId, clientId);
403
+ State.addPreExistingTab(target.tabId);
425
404
 
426
405
  if (config.waitForDebuggerOnStart) {
427
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
  });
@@ -17,9 +17,59 @@ var CDPUtils = (function() {
17
17
  });
18
18
  }
19
19
 
20
+ function hashCode(str) {
21
+ var hash = 0;
22
+ for (var i = 0; i < str.length; i++) {
23
+ var char = str.charCodeAt(i);
24
+ hash = ((hash << 5) - hash) + char;
25
+ hash = hash & hash;
26
+ }
27
+ return hash;
28
+ }
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
+
20
63
  return {
21
64
  generateSessionId: generateSessionId,
22
65
  getChromeVersion: getChromeVersion,
23
- sleep: sleep
66
+ sleep: sleep,
67
+ hashCode: hashCode,
68
+ GROUP_COLORS: GROUP_COLORS,
69
+ getGroupColorForClient: getGroupColorForClient,
70
+ getClientIndex: getClientIndex,
71
+ buildGroupName: buildGroupName,
72
+ getGroupBaseName: getGroupBaseName,
73
+ findGroupByName: findGroupByName
24
74
  };
25
75
  })();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "1.7.0",
4
- "description": "Chrome Extension CDP Proxy - 通过 Chrome 扩展将 chrome.debugger API 暴露为 WebSocket 端点",
3
+ "version": "2.1.0",
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",
7
7
  "scripts": {
@@ -19,12 +19,30 @@
19
19
  "keywords": [
20
20
  "cdp",
21
21
  "chrome-devtools-protocol",
22
+ "cdp-proxy",
22
23
  "playwright",
23
24
  "puppeteer",
24
- "chrome-extension"
25
+ "chrome-extension",
26
+ "browser-automation",
27
+ "web-scraping",
28
+ "remote-debugging",
29
+ "websocket",
30
+ "testing",
31
+ "browser-control"
25
32
  ],
26
33
  "author": "dyyz1993",
27
34
  "license": "Apache-2.0",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/dyyz1993/cdp-tunnel.git"
38
+ },
39
+ "homepage": "https://github.com/dyyz1993/cdp-tunnel#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/dyyz1993/cdp-tunnel/issues"
42
+ },
43
+ "engines": {
44
+ "node": ">=16.0.0"
45
+ },
28
46
  "files": [
29
47
  "server/",
30
48
  "cli/",
@@ -1,72 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="UTF-8">
5
- <style>
6
- body {
7
- width: 300px;
8
- padding: 15px;
9
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
- font-size: 14px;
11
- }
12
- .status {
13
- display: flex;
14
- align-items: center;
15
- margin-bottom: 15px;
16
- }
17
- .status-dot {
18
- width: 10px;
19
- height: 10px;
20
- border-radius: 50%;
21
- margin-right: 8px;
22
- }
23
- .status-dot.active {
24
- background-color: #4CAF50;
25
- }
26
- .status-dot.inactive {
27
- background-color: #9E9E9E;
28
- }
29
- .config-section {
30
- margin-top: 15px;
31
- }
32
- .config-section label {
33
- display: block;
34
- margin-bottom: 5px;
35
- font-weight: 500;
36
- }
37
- .config-section input {
38
- width: 100%;
39
- padding: 8px;
40
- border: 1px solid #ddd;
41
- border-radius: 4px;
42
- box-sizing: border-box;
43
- }
44
- .config-section button {
45
- margin-top: 10px;
46
- padding: 8px 16px;
47
- background-color: #2196F3;
48
- color: white;
49
- border: none;
50
- border-radius: 4px;
51
- cursor: pointer;
52
- }
53
- .config-section button:hover {
54
- background-color: #1976D2;
55
- }
56
- </style>
57
- </head>
58
- <body>
59
- <div class="status">
60
- <div class="status-dot active" id="statusDot"></div>
61
- <span id="statusText">已激活</span>
62
- </div>
63
-
64
- <div class="config-section">
65
- <label>WebSocket 地址</label>
66
- <input type="text" id="wsAddress" placeholder="ws://localhost:9221/client">
67
- <button id="saveBtn">保存</button>
68
- </div>
69
-
70
- <script src="popup.js"></script>
71
- </body>
72
- </html>
@@ -1,34 +0,0 @@
1
- document.addEventListener('DOMContentLoaded', function() {
2
- var statusDot = document.getElementById('statusDot');
3
- var statusText = document.getElementById('statusText');
4
- var wsAddressInput = document.getElementById('wsAddress');
5
- var saveBtn = document.getElementById('saveBtn');
6
-
7
- chrome.storage.local.get(['wsAddress'], function(result) {
8
- if (result.wsAddress) {
9
- wsAddressInput.value = result.wsAddress;
10
- } else {
11
- wsAddressInput.value = 'ws://localhost:9221/plugin';
12
- }
13
- });
14
-
15
- saveBtn.addEventListener('click', function() {
16
- var newAddress = wsAddressInput.value.trim();
17
- if (newAddress) {
18
- chrome.storage.local.set({ wsAddress: newAddress }, function() {
19
- statusText.textContent = '已保存,正在重连...';
20
-
21
- chrome.runtime.sendMessage({ type: 'reconnect' }, function(response) {
22
- if (response && response.success) {
23
- statusText.textContent = '已激活';
24
- } else {
25
- statusText.textContent = '重连失败';
26
- }
27
- });
28
- });
29
- }
30
- });
31
-
32
- statusDot.classList.add('active');
33
- statusText.textContent = '已激活';
34
- });