cdp-tunnel 2.4.3 → 2.5.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.
@@ -59,12 +59,16 @@ var SpecialHandler = (function() {
59
59
  var sessionId = CDPUtils.generateSessionId();
60
60
  State.mapSession(sessionId, tabId, targetId);
61
61
 
62
- // 保存tabId到clientId的映射
63
62
  if (clientId) {
64
63
  State.setTabIdToClientId(tabId, clientId);
65
64
  }
66
65
 
67
- addTabToAutomationGroup(tabId, clientId);
66
+ if (State.isCDPCreatedTab(tabId)) {
67
+ addTabToAutomationGroup(tabId, clientId);
68
+ } else {
69
+ State.addPreExistingTab(tabId);
70
+ Logger.info('[CDP] Target.attachToTarget: user tab not CDP-created, treating as pre-existing. tabId:', tabId);
71
+ }
68
72
 
69
73
  return { sessionId: sessionId };
70
74
  });
@@ -110,10 +114,10 @@ var SpecialHandler = (function() {
110
114
  // 保存tabId到clientId的映射,用于后续分组
111
115
  if (clientId) {
112
116
  State.setTabIdToClientId(tab.id, clientId);
113
- Logger.info('[TabGroup] Mapped tabId:', tab.id, '-> clientId:', clientId);
114
117
  }
115
118
 
116
- // 将标签页添加到CDP Automation组,使用对应的clientId
119
+ State.addCDPCreatedTab(tab.id);
120
+
117
121
  addTabToAutomationGroup(tab.id, clientId);
118
122
 
119
123
  getTargetIdByTabId(tab.id).then(function(targetId) {
@@ -128,59 +132,53 @@ var SpecialHandler = (function() {
128
132
  function addTabToAutomationGroup(tabId, clientId) {
129
133
  Logger.info('[TabGroup] Starting addTabToAutomationGroup for tabId:', tabId, 'clientId:', clientId);
130
134
 
131
- setTimeout(function() {
132
- muteTabIfNeeded(tabId);
133
- }, 500);
135
+ muteTabIfNeeded(tabId);
134
136
 
135
137
  var groupClientId = clientId;
136
138
  if (!groupClientId) {
137
- var cdpClients = State.getCDPClients() || [];
138
- if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
139
- groupClientId = cdpClients[0].id;
140
- }
139
+ Logger.warn('[TabGroup] No clientId for tab:', tabId, '— skipping group operation');
140
+ return;
141
141
  }
142
-
143
- if (!groupClientId) return;
144
142
  var baseName = CDPUtils.getGroupBaseName(groupClientId);
145
143
 
146
- setTimeout(function() {
147
- Logger.info('[TabGroup] Executing group operation for:', baseName);
148
-
149
- chrome.tabGroups.query({}, function(allGroups) {
150
- var existing = CDPUtils.findGroupByName(allGroups, baseName);
151
- if (existing) {
152
- chrome.tabs.group({ tabIds: tabId, groupId: existing.id }, function(result) {
153
- if (chrome.runtime.lastError) {
154
- Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message);
155
- } else {
156
- State.setGroupIdForClient(groupClientId, existing.id);
157
- updateTabGroupName(groupClientId);
158
- }
159
- });
160
- } else {
161
- chrome.tabs.group({ tabIds: tabId }, function(groupId) {
162
- if (chrome.runtime.lastError) {
163
- Logger.error('[TabGroup] Failed to create group:', chrome.runtime.lastError.message);
164
- return;
165
- }
166
- if (groupId) {
167
- chrome.tabGroups.update(groupId, {
168
- title: baseName,
169
- color: CDPUtils.getGroupColorForClient(groupClientId),
170
- collapsed: true
171
- }, function() {
172
- if (chrome.runtime.lastError) {
173
- Logger.error('[TabGroup] Failed to update group:', chrome.runtime.lastError.message);
174
- } else {
175
- State.setGroupIdForClient(groupClientId, groupId);
176
- updateTabGroupName(groupClientId);
177
- }
178
- });
179
- }
180
- });
181
- }
182
- });
183
- }, 2000);
144
+ Logger.info('[TabGroup] Executing group operation for:', baseName);
145
+
146
+ chrome.tabGroups.query({}, function(allGroups) {
147
+ var existing = CDPUtils.findGroupByName(allGroups, baseName);
148
+ if (existing) {
149
+ chrome.tabs.group({ tabIds: tabId, groupId: existing.id }, function(result) {
150
+ if (chrome.runtime.lastError) {
151
+ Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message);
152
+ } else {
153
+ State.setGroupIdForClient(groupClientId, existing.id);
154
+ updateTabGroupName(groupClientId);
155
+ Logger.info('[TabGroup] Tab', tabId, 'added to existing group:', existing.id);
156
+ }
157
+ });
158
+ } else {
159
+ chrome.tabs.group({ tabIds: tabId }, function(groupId) {
160
+ if (chrome.runtime.lastError) {
161
+ Logger.error('[TabGroup] Failed to create group:', chrome.runtime.lastError.message);
162
+ return;
163
+ }
164
+ if (groupId) {
165
+ chrome.tabGroups.update(groupId, {
166
+ title: baseName,
167
+ color: CDPUtils.getGroupColorForClient(groupClientId),
168
+ collapsed: true
169
+ }, function() {
170
+ if (chrome.runtime.lastError) {
171
+ Logger.error('[TabGroup] Failed to update group:', chrome.runtime.lastError.message);
172
+ } else {
173
+ State.setGroupIdForClient(groupClientId, groupId);
174
+ updateTabGroupName(groupClientId);
175
+ Logger.info('[TabGroup] Created new group:', groupId, 'with tab:', tabId);
176
+ }
177
+ });
178
+ }
179
+ });
180
+ }
181
+ });
184
182
  }
185
183
 
186
184
  function updateTabGroupName(clientId) {
@@ -342,47 +340,44 @@ function checkTabVisibility(tabId) {
342
340
  return chrome.debugger.getTargets().then(function(targets) {
343
341
  var promises = [];
344
342
 
345
- Logger.info('[CDP] emitAutoAttachForExistingTargets: checking', targets.length, 'targets');
346
- Logger.info('[CDP] Current attachedTabIds:', State.getAttachedTabIds());
343
+ Logger.info('[CDP] emitAutoAttachForExistingTargets: checking', targets.length, 'targets, clientId:', clientId);
347
344
 
348
345
  targets.forEach(function(target) {
349
346
  if (target.type !== 'page' && target.type !== 'background_page') return;
350
347
  if (!target.tabId) return;
351
348
 
352
349
  var targetId = target.id;
350
+ var tabId = target.tabId;
353
351
  var hasEmitted = State.hasEmittedTarget(targetId);
354
- Logger.info('[CDP] emitAutoAttachForExistingTargets: targetId=', targetId, 'tabId=', target.tabId, 'attached=', target.attached, 'hasEmitted=', hasEmitted);
355
352
 
356
353
  if (hasEmitted) {
357
- Logger.info('[CDP] Target already emitted in emitAutoAttachForExistingTargets, skipping:', targetId);
354
+ Logger.info('[CDP] Target already emitted, skipping:', targetId);
358
355
  return;
359
356
  }
360
- State.addEmittedTarget(targetId);
361
357
 
362
- var isAttachedByUs = State.isTabAttached(target.tabId);
358
+ var isCDPCreated = State.isCDPCreatedTab(tabId);
359
+ var isOwnedByClient = isCDPCreated && State.getClientIdByTabId(tabId) === clientId;
360
+
361
+ if (!isOwnedByClient) {
362
+ Logger.info('[CDP] Skipping user/other-client tab:', targetId, 'tabId:', tabId, 'cdpCreated:', isCDPCreated);
363
+ State.addEmittedTarget(targetId);
364
+ return;
365
+ }
366
+
367
+ State.addEmittedTarget(targetId);
363
368
  var targetInfo = LocalHandler.mapToTargetInfo(target);
364
369
 
365
- Logger.info('[CDP] isAttachedByUs=', isAttachedByUs, 'for tabId=', target.tabId);
366
-
367
- if (target.attached && !isAttachedByUs) {
368
- targetInfo.attached = false;
369
- Logger.info('[CDP] Target attached by another debugger, reporting as not attached:', targetId, 'tabId:', target.tabId);
370
- }
370
+ Logger.info('[CDP] Emitting CDP-owned target:', targetId, 'tabId:', tabId);
371
371
 
372
372
  EventBuilder.send('Target.targetCreated', { targetInfo: targetInfo });
373
373
 
374
- if (target.attached && isAttachedByUs) {
374
+ if (target.attached) {
375
375
  promises.push(Promise.resolve().then(function() {
376
376
  var sessionId = CDPUtils.generateSessionId();
377
- State.mapSession(sessionId, target.tabId, targetId);
378
-
379
- if (clientId) {
380
- State.setTabIdToClientId(target.tabId, clientId);
381
- }
382
- State.addPreExistingTab(target.tabId);
377
+ State.mapSession(sessionId, tabId, targetId);
383
378
 
384
379
  if (config.waitForDebuggerOnStart) {
385
- State.addPendingDebuggerTab(target.tabId);
380
+ State.addPendingDebuggerTab(tabId);
386
381
  }
387
382
 
388
383
  EventBuilder.send('Target.attachedToTarget', {
@@ -391,20 +386,15 @@ function checkTabVisibility(tabId) {
391
386
  waitingForDebugger: config.waitForDebuggerOnStart || false
392
387
  });
393
388
  }));
394
- } else if (!target.attached) {
389
+ } else {
395
390
  promises.push(
396
- DebuggerManager.attach(target.tabId).then(function(attached) {
391
+ DebuggerManager.attach(tabId).then(function(attached) {
397
392
  if (!attached) return;
398
393
  var sessionId = CDPUtils.generateSessionId();
399
- State.mapSession(sessionId, target.tabId, targetId);
400
-
401
- if (clientId) {
402
- State.setTabIdToClientId(target.tabId, clientId);
403
- }
404
- State.addPreExistingTab(target.tabId);
394
+ State.mapSession(sessionId, tabId, targetId);
405
395
 
406
396
  if (config.waitForDebuggerOnStart) {
407
- State.addPendingDebuggerTab(target.tabId);
397
+ State.addPendingDebuggerTab(tabId);
408
398
  }
409
399
 
410
400
  EventBuilder.send('Target.attachedToTarget', {
@@ -25,7 +25,8 @@ var State = (function() {
25
25
  clientIdToSessionId: new Map(),
26
26
  tabIdToClientId: new Map(),
27
27
  clientIdToGroupId: new Map(),
28
- preExistingTabIds: new Set()
28
+ preExistingTabIds: new Set(),
29
+ cdpCreatedTabIds: new Set()
29
30
  };
30
31
 
31
32
  function mapSession(sessionId, tabId, targetId) {
@@ -254,6 +255,14 @@ var State = (function() {
254
255
  _state.emittedTargets.clear();
255
256
  }
256
257
 
258
+ function addCDPCreatedTab(tabId) {
259
+ _state.cdpCreatedTabIds.add(tabId);
260
+ }
261
+
262
+ function isCDPCreatedTab(tabId) {
263
+ return _state.cdpCreatedTabIds.has(tabId);
264
+ }
265
+
257
266
  function clearAllState() {
258
267
  clearSessionState();
259
268
  _state.attachedTabIds.clear();
@@ -275,6 +284,7 @@ var State = (function() {
275
284
  _state.pendingDebuggerTabs.clear();
276
285
  _state.automatedTabs.clear();
277
286
  _state.pendingCreatedTabUrls.clear();
287
+ _state.cdpCreatedTabIds.clear();
278
288
  _state.cdpClients = [];
279
289
  }
280
290
 
@@ -494,6 +504,8 @@ var State = (function() {
494
504
  isPreExistingTab: isPreExistingTab,
495
505
  getPreExistingTabs: getPreExistingTabs,
496
506
  removePreExistingTab: removePreExistingTab,
497
- clearPreExistingTabsForClient: clearPreExistingTabsForClient
507
+ clearPreExistingTabsForClient: clearPreExistingTabsForClient,
508
+ addCDPCreatedTab: addCDPCreatedTab,
509
+ isCDPCreatedTab: isCDPCreatedTab
498
510
  };
499
511
  })();
@@ -24,6 +24,9 @@ var WebSocketManager = (function() {
24
24
  State.clearReconnectTimer();
25
25
  processQueue();
26
26
  broadcastStateUpdate();
27
+ var extVersion = (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.getManifest)
28
+ ? chrome.runtime.getManifest().version : 'unknown';
29
+ send({ type: 'plugin-hello', version: extVersion });
27
30
  };
28
31
 
29
32
  ws.onclose = function(event) {
@@ -208,7 +211,6 @@ var WebSocketManager = (function() {
208
211
  State.removeAttachedTab(tabId);
209
212
  });
210
213
  State.clearPreExistingTabsForClient(discClientId);
211
-
212
214
  State.removeCDPClient(discClientId);
213
215
  if (State.getCDPClients().length === 0) {
214
216
  State.setHasConnectedClient(false);
@@ -286,6 +288,7 @@ var WebSocketManager = (function() {
286
288
  }
287
289
 
288
290
  function closeGroupById(groupId, clientId, resolve) {
291
+ Logger.info('[WS] closeGroupById: groupId=' + groupId + ' clientId=' + clientId);
289
292
  chrome.tabs.query({ groupId: groupId }, function(tabs) {
290
293
  if (!tabs || tabs.length === 0) {
291
294
  Logger.info('[WS] No tabs in group:', groupId);
@@ -294,9 +297,22 @@ var WebSocketManager = (function() {
294
297
  return;
295
298
  }
296
299
 
297
- var tabIds = tabs.map(function(tab) { return tab.id; });
298
- Logger.info('[WS] Closing ' + tabIds.length + ' tabs in group:', groupId);
300
+ var ownTabs = tabs.filter(function(tab) {
301
+ return State.getClientIdByTabId(tab.id) === clientId;
302
+ });
303
+ var otherTabs = tabs.filter(function(tab) {
304
+ return State.getClientIdByTabId(tab.id) !== clientId;
305
+ });
306
+ var tabIds = ownTabs.map(function(tab) { return tab.id; });
307
+ Logger.info('[WS] Closing ' + tabIds.length + ' tabs in group (skipping ' + otherTabs.length + ' from other clients):', groupId);
299
308
 
309
+ if (tabIds.length === 0) {
310
+ Logger.info('[WS] No own tabs to close in group:', groupId);
311
+ State.removeGroupForClient(clientId);
312
+ resolve();
313
+ return;
314
+ }
315
+
300
316
  chrome.tabs.remove(tabIds, function() {
301
317
  if (chrome.runtime.lastError) {
302
318
  Logger.error('[WS] Failed to close tabs:', chrome.runtime.lastError.message);
@@ -318,12 +334,19 @@ var WebSocketManager = (function() {
318
334
  var attachedTabs = State.getAttachedTabIds();
319
335
  var tabsToClose = [];
320
336
 
337
+ Logger.info('[WS] closeTabsByClientId: clientId=' + clientId + ' attachedTabs=' + JSON.stringify(attachedTabs));
321
338
  attachedTabs.forEach(function(tabId) {
322
- if (State.getClientIdByTabId(tabId) === clientId && !State.isPreExistingTab(tabId)) {
339
+ var tabClientId = State.getClientIdByTabId(tabId);
340
+ var isPre = State.isPreExistingTab(tabId);
341
+ var isCDP = State.isCDPCreatedTab(tabId);
342
+ Logger.info('[WS] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isCDP=' + isCDP);
343
+ if (tabClientId === clientId && !isPre) {
323
344
  tabsToClose.push(tabId);
324
345
  }
325
346
  });
326
347
 
348
+ Logger.info('[WS] closeTabsByClientId: will close ' + tabsToClose.length + ' tabs');
349
+
327
350
  if (tabsToClose.length === 0) {
328
351
  Logger.info('[WS] No attached tabs found for clientId:', clientId);
329
352
  resolve();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "2.4.3",
4
+ "version": "2.5.0",
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:9221/plugin',
2
+ WS_URL: 'ws://localhost:43930/plugin',
3
3
  RECONNECT_DELAY: 3000,
4
4
  DEBUGGER_VERSION: '1.3',
5
5
  HEARTBEAT_INTERVAL: 25000,
@@ -44,8 +44,7 @@ var CDPUtils = (function() {
44
44
 
45
45
  function buildGroupName(clientId) {
46
46
  if (!clientId) return 'CDP';
47
- var idx = getClientIndex(clientId);
48
- return idx > 0 ? ('CDP #' + idx) : ('CDP-' + clientId.substring(0, 6));
47
+ return 'CDP-' + clientId.substring(0, 8);
49
48
  }
50
49
 
51
50
  function getGroupBaseName(clientId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.4.3",
3
+ "version": "2.5.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",
@@ -150,6 +150,7 @@ const clientIdToPlugin = new Map();
150
150
  const globalRequestIdMap = new Map();
151
151
  const targetIdToClientId = new Map();
152
152
  const pendingAttachedEvents = new Map();
153
+ const pendingTargetCreatedEvents = new Map();
153
154
  const browserContextToClientId = new Map();
154
155
  const clientIdToBrowserContext = new Map();
155
156
  let globalRequestIdCounter = 0;
@@ -448,8 +449,22 @@ function handlePluginConnection(ws, clientInfo) {
448
449
  logConnectionEvent('KEEPALIVE_RECEIVED', { type: 'plugin', id: ws.id });
449
450
  return;
450
451
  }
452
+
453
+ if (parsed && parsed.type === 'plugin-hello') {
454
+ const extVersion = parsed.version || 'unknown';
455
+ ws.extVersion = extVersion;
456
+ const match = extVersion === PKG_VERSION;
457
+ const level = match ? 'info' : 'warn';
458
+ const label = match ? '✅' : '⚠️ VERSION MISMATCH';
459
+ const msg = `[VERSION CHECK] ${label} server=${PKG_VERSION} extension=${extVersion}`;
460
+ console.log(msg);
461
+ logCDP('VERSION', msg);
462
+ if (!match) {
463
+ console.log(` ↳ Run "cdp-tunnel update" or reload the extension to sync versions`);
464
+ }
465
+ return;
466
+ }
451
467
 
452
- // 调试:打印所有收到的消息
453
468
  console.log(`[PLUGIN MSG] id=${parsed?.id} method=${parsed?.method || 'none'} type=${parsed?.type || 'none'} sessionId=${parsed?.sessionId?.substring(0,8) || 'none'}`);
454
469
 
455
470
  // 记录所有 PLUGIN -> CLIENT 消息到日志文件
@@ -494,9 +509,12 @@ function handlePluginConnection(ws, clientInfo) {
494
509
  clientWs.send(cdpData);
495
510
  console.log(`[TARGET EVENT ROUTED] ${parsed.method} targetId=${targetId?.substring(0,8)} -> clientId=${eventClientId}`);
496
511
  }
512
+ } else if (targetId && (parsed.method === 'Target.targetCreated' || parsed.method === 'Target.attachedToTarget')) {
513
+ const pendingMap = parsed.method === 'Target.targetCreated' ? pendingTargetCreatedEvents : pendingAttachedEvents;
514
+ pendingMap.set(targetId, { parsed: JSON.parse(JSON.stringify(parsed)), cdpData });
515
+ console.log(`[TARGET EVENT PENDING] ${parsed.method} targetId=${targetId?.substring(0,8)} (cached, waiting for createTarget response)`);
497
516
  } else {
498
- console.log(`[TARGET EVENT BROADCAST] ${parsed.method} targetId=${targetId?.substring(0,8) || 'none'} (no owner, broadcasting)`);
499
- broadcastToClients(cdpData, null);
517
+ console.log(`[TARGET EVENT DROPPED] ${parsed.method} targetId=${targetId?.substring(0,8) || 'none'} (no owner, dropped for isolation)`);
500
518
  }
501
519
  }
502
520
 
@@ -576,20 +594,23 @@ function handlePluginConnection(ws, clientInfo) {
576
594
  if (mapping.isCreateTarget && parsed.result?.targetId) {
577
595
  const targetId = parsed.result.targetId;
578
596
  targetIdToClientId.set(targetId, mapping.clientId);
579
- console.log(`[TARGET MAPPED] targetId=${targetId} -> clientId=${mapping.clientId}`);
597
+ console.log(`[TARGET MAPPED] targetId=${targetId} -> clientId=${mapping.clientId} mapSize=${targetIdToClientId.size}`);
598
+
599
+ const cachedCreated = pendingTargetCreatedEvents.get(targetId);
600
+ if (cachedCreated) {
601
+ clientWs.send(cachedCreated.cdpData);
602
+ console.log(`[TARGET CREATED EVENT] Sent cached Target.targetCreated to client: ${mapping.clientId}`);
603
+ pendingTargetCreatedEvents.delete(targetId);
604
+ }
580
605
 
581
- // 检查是否有缓存的 Target.attachedToTarget 事件
582
606
  const cachedEvent = pendingAttachedEvents.get(targetId);
583
607
  if (cachedEvent) {
584
- if (cachedEvent.sessionId) {
585
- sessionToClientId.set(cachedEvent.sessionId, mapping.clientId);
608
+ if (cachedEvent.parsed.sessionId) {
609
+ sessionToClientId.set(cachedEvent.parsed.sessionId, mapping.clientId);
586
610
  }
587
- console.log(`[SESSION MAPPED from cached] sessionId=${cachedEvent.sessionId?.substring(0,8) || 'none'} -> clientId=${mapping.clientId} (targetId=${targetId})`);
611
+ console.log(`[SESSION MAPPED from cached] sessionId=${cachedEvent.parsed.sessionId?.substring(0,8) || 'none'} -> clientId=${mapping.clientId} (targetId=${targetId})`);
588
612
  pendingAttachedEvents.delete(targetId);
589
613
 
590
- // 先发送缓存的事件给客户端
591
- // 注意:Target.attachedToTarget 事件必须发送给 root session(没有顶层 sessionId)
592
- // sessionId 在 params 里面,不在消息顶层
593
614
  const cdpMsg = {
594
615
  method: cachedEvent.parsed.method,
595
616
  params: cachedEvent.parsed.params
@@ -606,7 +627,7 @@ function handlePluginConnection(ws, clientInfo) {
606
627
  parsed.result.targetInfos = parsed.result.targetInfos.filter(t => {
607
628
  if (t.type !== 'page') return true;
608
629
  const ownerClient = targetIdToClientId.get(t.targetId);
609
- return !ownerClient || ownerClient === clientId;
630
+ return ownerClient === clientId;
610
631
  });
611
632
  console.log(`[GET TARGETS FILTERED] client=${clientId} returned ${parsed.result.targetInfos.filter(t => t.type === 'page').length} page targets`);
612
633
  }