cdp-tunnel 2.2.2 → 2.4.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.
@@ -17,6 +17,7 @@ importScripts('features/automation-badge.js');
17
17
  'use strict';
18
18
 
19
19
  var keepAliveInterval = null;
20
+ var _initialized = false;
20
21
 
21
22
  function startKeepAlive() {
22
23
  if (keepAliveInterval) {
@@ -43,6 +44,11 @@ importScripts('features/automation-badge.js');
43
44
  }
44
45
 
45
46
  function init() {
47
+ if (_initialized) {
48
+ Logger.info('[Init] Already initialized, skipping');
49
+ return;
50
+ }
51
+ _initialized = true;
46
52
  Logger.info('[Init] CDP Bridge starting...');
47
53
 
48
54
  // 点击扩展图标时打开配置页面
@@ -118,11 +124,11 @@ importScripts('features/automation-badge.js');
118
124
  var sessionId = State.findSessionByTabId(tabId);
119
125
  if (sessionId) {
120
126
  var targetId = State.getTargetIdBySession(sessionId);
121
- EventBuilder.send('Target.targetDestroyed', { targetId: targetId });
122
127
  EventBuilder.send('Target.detachedFromTarget', {
123
128
  sessionId: sessionId,
124
129
  targetId: targetId
125
130
  });
131
+ EventBuilder.send('Target.targetDestroyed', { targetId: targetId });
126
132
  State.unmapSession(sessionId);
127
133
  if (removedClientId) {
128
134
  SpecialHandler.updateTabGroupName(removedClientId);
@@ -132,6 +138,7 @@ importScripts('features/automation-badge.js');
132
138
  if (State.getCurrentTabId() === tabId) {
133
139
  State.persist(null, false);
134
140
  }
141
+ State.removeTabIdToClientId(tabId);
135
142
  });
136
143
 
137
144
  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
@@ -204,6 +211,12 @@ importScripts('features/automation-badge.js');
204
211
 
205
212
  var sessionId = CDPUtils.generateSessionId();
206
213
  State.mapSession(sessionId, tabId, targetId);
214
+
215
+ var openerClientId = openerTabId ? State.getClientIdByTabId(openerTabId) : null;
216
+ if (openerClientId) {
217
+ State.setTabIdToClientId(tabId, openerClientId);
218
+ Logger.info('[Tabs] Mapped child tab', tabId, '-> clientId:', openerClientId);
219
+ }
207
220
 
208
221
  Config.getAutoMute(function(enabled) {
209
222
  if (enabled) {
@@ -9,12 +9,12 @@ var ForwardHandler = (function() {
9
9
 
10
10
  if (!tabId) {
11
11
  Logger.warn('[Forward] No tabId for command:', method);
12
- return Promise.resolve({});
12
+ return Promise.reject({ code: -32000, message: 'No target found for command: ' + method });
13
13
  }
14
14
 
15
15
  if (!State.isTabAttached(tabId)) {
16
16
  Logger.warn('[Forward] Tab not attached, skipping command:', method, 'tabId:', tabId);
17
- return Promise.resolve({});
17
+ return Promise.reject({ code: -32000, message: 'Target is not attached' });
18
18
  }
19
19
 
20
20
  Logger.debug('[Forward]', method, '-> tabId:', tabId);
@@ -228,16 +228,17 @@ var SpecialHandler = (function() {
228
228
  if (targetId) {
229
229
  var tabId = State.getTabIdByTargetId(targetId);
230
230
  if (tabId) {
231
- State.removeAttachedTab(tabId);
232
231
  var closeClientId = State.getClientIdByTabId(tabId);
233
232
  return new Promise(function(resolve) {
234
233
  chrome.tabs.remove(tabId, function() {
234
+ State.removeAttachedTab(tabId);
235
235
  if (closeClientId) {
236
236
  updateTabGroupName(closeClientId);
237
237
  }
238
238
  resolve({ success: true });
239
239
  });
240
240
  }).catch(function() {
241
+ State.removeAttachedTab(tabId);
241
242
  return { success: true };
242
243
  });
243
244
  }
@@ -82,8 +82,8 @@ function routeCDPCommand(message) {
82
82
  resolve({ result: result });
83
83
  })
84
84
  .catch(function(error) {
85
- Logger.error('[CDP] ERROR id=' + id + ' method=' + method + ' msg=' + error.message);
86
- resolve({ error: { message: error.message } });
85
+ Logger.error('[CDP] ERROR id=' + id + ' method=' + method + ' msg=' + (error.message || error));
86
+ resolve({ error: { code: error.code || -32000, message: error.message || String(error) } });
87
87
  });
88
88
  } else {
89
89
  ForwardHandler.execute({ id: id, method: method, params: params, sessionId: sessionId, clientId: clientId })
@@ -92,8 +92,8 @@ function routeCDPCommand(message) {
92
92
  resolve({ result: result });
93
93
  })
94
94
  .catch(function(error) {
95
- Logger.error('[CDP] ERROR id=' + id + ' method=' + method + ' msg=' + error.message + ' (forwarded)');
96
- resolve({ error: { message: error.message } });
95
+ Logger.error('[CDP] ERROR id=' + id + ' method=' + method + ' msg=' + (error.message || error) + ' (forwarded)');
96
+ resolve({ error: { code: error.code || -32000, message: error.message || String(error) } });
97
97
  });
98
98
  }
99
99
  }).then(function(response) {
@@ -169,6 +169,7 @@ var DebuggerManager = (function() {
169
169
  Logger.error('[Debugger] Failed to detach from tab', tabId, ':', error.message);
170
170
  State.removeAttachedTab(tabId);
171
171
  AutomationBadge.remove(tabId);
172
+ Screencast.stopPolling(tabId);
172
173
  });
173
174
  }
174
175
 
@@ -38,6 +38,9 @@ var State = (function() {
38
38
  var tabId = _state.sessionIdToTabId.get(sessionId);
39
39
  _state.sessionIdToTabId.delete(sessionId);
40
40
  _state.sessionIdToTargetId.delete(sessionId);
41
+ if (tabId && !hasOtherSessionForTab(tabId)) {
42
+ _state.attachedTabIds.delete(tabId);
43
+ }
41
44
  return tabId;
42
45
  }
43
46
 
@@ -254,6 +257,7 @@ var State = (function() {
254
257
  function clearAllState() {
255
258
  clearSessionState();
256
259
  _state.attachedTabIds.clear();
260
+ _state.emittedTargets.clear();
257
261
  _state.screencastPollingSessions.clear();
258
262
  _state.browserContextIds = new Set(['default']);
259
263
  _state.autoAttachConfig = {
@@ -265,7 +269,13 @@ var State = (function() {
265
269
  _state.hasConnectedClient = false;
266
270
  _state.tabIdToClientId.clear();
267
271
  _state.clientIdToGroupId.clear();
272
+ _state.clientIdToTabId.clear();
273
+ _state.clientIdToSessionId.clear();
268
274
  _state.preExistingTabIds.clear();
275
+ _state.pendingDebuggerTabs.clear();
276
+ _state.automatedTabs.clear();
277
+ _state.pendingCreatedTabUrls.clear();
278
+ _state.cdpClients = [];
269
279
  }
270
280
 
271
281
  function cleanupAllTabs() {
@@ -332,6 +342,10 @@ var State = (function() {
332
342
  _state.tabIdToClientId.set(tabId, clientId);
333
343
  }
334
344
 
345
+ function removeTabIdToClientId(tabId) {
346
+ _state.tabIdToClientId.delete(tabId);
347
+ }
348
+
335
349
  function getClientIdByTabId(tabId) {
336
350
  return _state.tabIdToClientId.get(tabId);
337
351
  }
@@ -471,6 +485,7 @@ var State = (function() {
471
485
  clearAllState: clearAllState,
472
486
  cleanupAllTabs: cleanupAllTabs,
473
487
  setTabIdToClientId: setTabIdToClientId,
488
+ removeTabIdToClientId: removeTabIdToClientId,
474
489
  getClientIdByTabId: getClientIdByTabId,
475
490
  setGroupIdForClient: setGroupIdForClient,
476
491
  getGroupIdForClient: getGroupIdForClient,
@@ -339,16 +339,20 @@ var WebSocketManager = (function() {
339
339
  return;
340
340
  }
341
341
  Logger.info('[WS] Closing ' + tabIds.length + ' attached tabs for clientId:', clientId);
342
+ var pending = tabIds.length;
342
343
  tabIds.forEach(function(tabId) {
343
344
  chrome.tabs.remove(tabId, function() {
344
345
  if (chrome.runtime.lastError) {
345
346
  Logger.info('[WS] Tab already closed:', tabId);
346
347
  }
348
+ chrome.debugger.detach({ tabId: tabId }).catch(function() {});
349
+ State.removeAttachedTab(tabId);
350
+ pending--;
351
+ if (pending === 0) {
352
+ resolve();
353
+ }
347
354
  });
348
- chrome.debugger.detach({ tabId: tabId }).catch(function() {});
349
- State.removeAttachedTab(tabId);
350
355
  });
351
- resolve();
352
356
  }
353
357
 
354
358
  function handleServerRestart() {
@@ -372,17 +376,27 @@ var WebSocketManager = (function() {
372
376
  Logger.info('[WS] Browser.close received, cleaning up... clientId:', clientId);
373
377
 
374
378
  closeTabGroupByClientId(clientId).then(function() {
375
- var attachedTabIds = State.getAttachedTabIds();
376
- var promises = attachedTabIds.map(function(tabId) {
377
- return chrome.debugger.detach({ tabId: tabId }).catch(function(e) {
378
- Logger.info('[WS] Detach failed for tab', tabId, ':', e.message);
379
- });
379
+ return new Promise(function(resolve) {
380
+ closeTabsByClientId(clientId, resolve);
381
+ });
382
+ }).then(function() {
383
+ var preExistingTabs = State.getPreExistingTabs();
384
+ var clientPreExisting = preExistingTabs.filter(function(tabId) {
385
+ return State.getClientIdByTabId(tabId) === clientId;
380
386
  });
381
- Promise.all(promises).then(function() {
387
+ clientPreExisting.forEach(function(tabId) {
388
+ chrome.debugger.detach({ tabId: tabId }).catch(function() {});
389
+ State.removeAttachedTab(tabId);
390
+ });
391
+ State.clearPreExistingTabsForClient(clientId);
392
+
393
+ State.removeCDPClient(clientId);
394
+ if (State.getCDPClients().length === 0) {
382
395
  State.clearAllState();
383
396
  State.persist(null, false);
384
- Logger.info('[WS] Browser.close cleanup complete');
385
- });
397
+ }
398
+ broadcastStateUpdate();
399
+ Logger.info('[WS] Browser.close cleanup complete for client:', clientId);
386
400
  });
387
401
  }
388
402
 
@@ -398,7 +412,18 @@ var WebSocketManager = (function() {
398
412
  var cdpClients = State.getCDPClients() || [];
399
413
  var attachedTabIds = State.getAttachedTabIds();
400
414
 
415
+ if (attachedTabIds.length === 0) {
416
+ chrome.runtime.sendMessage({
417
+ type: 'stateUpdate',
418
+ connected: isConnected,
419
+ cdpClients: cdpClients,
420
+ attachedPages: []
421
+ }).catch(function() {});
422
+ return;
423
+ }
424
+
401
425
  var attachedPages = [];
426
+ var pending = attachedTabIds.length;
402
427
  attachedTabIds.forEach(function(tabId) {
403
428
  chrome.tabs.get(tabId, function(tab) {
404
429
  if (tab && !chrome.runtime.lastError) {
@@ -408,15 +433,17 @@ var WebSocketManager = (function() {
408
433
  url: tab.url || ''
409
434
  });
410
435
  }
436
+ pending--;
437
+ if (pending === 0) {
438
+ chrome.runtime.sendMessage({
439
+ type: 'stateUpdate',
440
+ connected: isConnected,
441
+ cdpClients: cdpClients,
442
+ attachedPages: attachedPages
443
+ }).catch(function() {});
444
+ }
411
445
  });
412
446
  });
413
-
414
- chrome.runtime.sendMessage({
415
- type: 'stateUpdate',
416
- connected: isConnected,
417
- cdpClients: cdpClients,
418
- attachedPages: attachedPages
419
- }).catch(function() {});
420
447
  }
421
448
 
422
449
  function getQueueStats() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "2.2.2",
4
+ "version": "2.4.0",
5
5
  "description": "Chrome DevTools Protocol Bridge for Playwright/Puppeteer automation",
6
6
  "permissions": [
7
7
  "debugger",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.2.2",
3
+ "version": "2.4.0",
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",
@@ -506,8 +506,14 @@ function handlePluginConnection(ws, clientInfo) {
506
506
  const sessionId = parsed.params?.sessionId;
507
507
 
508
508
  if (targetId && sessionId) {
509
- sessionToClientId.set(sessionId, targetId);
510
- console.log(`[SESSION MAPPED] sessionId=${sessionId?.substring(0,8) || 'none'} -> targetId=${targetId?.substring(0,8) || 'none'}`);
509
+ const clientId = ws.pairedClientId;
510
+ if (clientId) {
511
+ sessionToClientId.set(sessionId, clientId);
512
+ console.log(`[SESSION MAPPED] sessionId=${sessionId?.substring(0,8) || 'none'} -> clientId=${clientId?.substring(0,8) || 'none'}`);
513
+ } else {
514
+ sessionToClientId.set(sessionId, targetId);
515
+ console.log(`[SESSION MAPPED] sessionId=${sessionId?.substring(0,8) || 'none'} -> targetId=${targetId?.substring(0,8) || 'none'} (no pairedClientId)`);
516
+ }
511
517
  }
512
518
  }
513
519
 
@@ -559,6 +565,12 @@ function handlePluginConnection(ws, clientInfo) {
559
565
  console.log(`[BROWSER CONTEXT MAPPED] browserContextId=${browserContextId} -> clientId=${mapping.clientId}`);
560
566
  }
561
567
 
568
+ // 如果是 Target.attachToTarget 响应,建立 sessionId -> clientId 映射
569
+ if (parsed.result?.sessionId && mapping.method === 'Target.attachToTarget') {
570
+ sessionToClientId.set(parsed.result.sessionId, mapping.clientId);
571
+ console.log(`[SESSION MAPPED from attach response] sessionId=${parsed.result.sessionId?.substring(0,8)} -> clientId=${mapping.clientId?.substring(0,8)}`);
572
+ }
573
+
562
574
  // 如果是 Target.createTarget 响应,先发送缓存的 Target.attachedToTarget 事件
563
575
  // 然后再发送响应
564
576
  if (mapping.isCreateTarget && parsed.result?.targetId) {
@@ -867,7 +879,8 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
867
879
  globalRequestIdMap.set(globalId, {
868
880
  clientId: id,
869
881
  originalId: originalId,
870
- sessionId: parsed.sessionId // 保存请求的 sessionId
882
+ sessionId: parsed.sessionId,
883
+ method: parsed.method
871
884
  });
872
885
 
873
886
  // 修改请求ID为全局ID
@@ -877,12 +890,6 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
877
890
  console.log(`[REQUEST ID MAPPED] client=${id} original=${originalId} -> global=${globalId} sessionId=${parsed.sessionId?.substring(0,8) || 'none'}`);
878
891
  }
879
892
 
880
- // 记录 Target.attachToTarget 请求,用于后续建立 session -> clientId 映射
881
- if (parsed && parsed.method === 'Target.attachToTarget' && parsed.id !== undefined) {
882
- pendingAttachRequests.set(parsed.id, id);
883
- console.log(`[PENDING ATTACH] Request id=${parsed.id} from client=${id}, pending size=${pendingAttachRequests.size}`);
884
- }
885
-
886
893
  // 记录 Target.createTarget 请求,用于后续建立 targetId -> clientId 映射
887
894
  // 注意:此时 parsed.id 已经是 globalId,originalId 已经保存在 mapping 中
888
895
  if (parsed && parsed.method === 'Target.createTarget' && parsed.id !== undefined) {
@@ -1010,6 +1017,20 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
1010
1017
  // 广播更新后的客户端列表
1011
1018
  broadcastClientList();
1012
1019
 
1020
+ // 清理该 client 的所有映射
1021
+ for (const [tId, cId] of targetIdToClientId.entries()) {
1022
+ if (cId === id) targetIdToClientId.delete(tId);
1023
+ }
1024
+ for (const [bcId, cId] of browserContextToClientId.entries()) {
1025
+ if (cId === id) browserContextToClientId.delete(bcId);
1026
+ }
1027
+ if (clientIdToBrowserContext.has(id)) {
1028
+ clientIdToBrowserContext.delete(id);
1029
+ }
1030
+ for (const [gId, mapping] of globalRequestIdMap.entries()) {
1031
+ if (mapping.clientId === id) globalRequestIdMap.delete(gId);
1032
+ }
1033
+
1013
1034
  // 清理配对关系
1014
1035
  if (ws.pairedPlugin) {
1015
1036
  ws.pairedPlugin.pairedClientId = null;