cdp-tunnel 2.5.18 → 2.5.20

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.
@@ -228,6 +228,56 @@ var LocalHandler = (function() {
228
228
  });
229
229
  }
230
230
 
231
+ function tabSimulateUserOpen(context) {
232
+ var attachedTabIds = State.getAttachedTabIds();
233
+ var openerTabId = null;
234
+ for (var i = 0; i < attachedTabIds.length; i++) {
235
+ if (State.isCDPCreatedTab(attachedTabIds[i])) {
236
+ openerTabId = attachedTabIds[i];
237
+ break;
238
+ }
239
+ }
240
+ if (openerTabId == null) {
241
+ return Promise.resolve({ success: false, error: 'No CDP-created tab found to use as opener' });
242
+ }
243
+ return new Promise(function(resolve) {
244
+ chrome.tabs.create({ url: 'https://example.com', openerTabId: openerTabId }, function(tab) {
245
+ if (chrome.runtime.lastError) {
246
+ Logger.error('[TabSimulateUserOpen] Error: ' + chrome.runtime.lastError.message);
247
+ resolve({ success: false, error: chrome.runtime.lastError.message });
248
+ return;
249
+ }
250
+ setTimeout(function() {
251
+ chrome.tabs.get(tab.id, function(t) {
252
+ resolve({ success: true, newTabId: tab.id, openerTabId: openerTabId, actualOpenerTabId: t.openerTabId, actualGroupId: t.groupId });
253
+ });
254
+ }, 1000);
255
+ });
256
+ });
257
+ }
258
+
259
+ function tabGetTabGroup(context) {
260
+ var tabId = context.params && context.params.tabId;
261
+ if (tabId == null) {
262
+ return Promise.resolve({ error: 'tabId is required' });
263
+ }
264
+ return new Promise(function(resolve) {
265
+ chrome.tabs.get(tabId, function(tab) {
266
+ if (chrome.runtime.lastError) {
267
+ Logger.error('[TabGetTabGroup] Error: ' + chrome.runtime.lastError.message);
268
+ resolve({ tabId: tabId, groupId: -1, error: chrome.runtime.lastError.message });
269
+ return;
270
+ }
271
+ resolve({
272
+ tabId: tab.id,
273
+ groupId: tab.groupId != null ? tab.groupId : -1,
274
+ url: tab.url || '',
275
+ openerTabId: tab.openerTabId || null
276
+ });
277
+ });
278
+ });
279
+ }
280
+
231
281
  function tabGetMuteStatus(params) {
232
282
  var cdpOnly = params && params.cdpOnly;
233
283
  var attachedTabIds = State.getAttachedTabIds();
@@ -403,6 +453,8 @@ var LocalHandler = (function() {
403
453
  mapToTargetInfo: mapToTargetInfo,
404
454
  tabGetMuteStatus: tabGetMuteStatus,
405
455
  tabGetGroupInfo: tabGetGroupInfo,
406
- tabUngroup: tabUngroup
456
+ tabUngroup: tabUngroup,
457
+ tabSimulateUserOpen: tabSimulateUserOpen,
458
+ tabGetTabGroup: tabGetTabGroup
407
459
  };
408
460
  })();
@@ -100,35 +100,65 @@ var SpecialHandler = (function() {
100
100
  var url = (params && params.url) || 'about:blank';
101
101
  var browserContextId = (params && params.browserContextId) || 'default';
102
102
  var clientId = context.clientId;
103
+ var needsNavigate = url !== 'about:blank' && url !== '';
103
104
 
104
105
  return new Promise(function(resolve, reject) {
105
- State.addPendingCreatedTabUrl(url);
106
- // 默认设置active: false,避免自动切换标签页
107
- chrome.tabs.create({ url: url, active: false }, function(tab) {
106
+ // Step 1: 先创建 about:blank (不加载任何资源,tab bar 闪烁最小)
107
+ chrome.tabs.create({ url: 'about:blank', active: false }, function(tab) {
108
108
  if (!tab || !tab.id) {
109
- State.removePendingCreatedTabUrl(url);
110
109
  reject(new Error('Failed to create tab'));
111
110
  return;
112
111
  }
113
112
 
114
- // 保存tabId到clientId的映射,用于后续分组
115
113
  if (clientId) {
116
114
  State.setTabIdToClientId(tab.id, clientId);
117
115
  }
118
116
 
119
117
  State.addCDPCreatedTab(tab.id);
120
118
 
121
- addTabToAutomationGroup(tab.id, clientId);
122
-
123
- getTargetIdByTabId(tab.id).then(function(targetId) {
124
- return emitAutoAttachEvents(tab.id, targetId, browserContextId).then(function() {
125
- resolve({ targetId: targetId });
119
+ // Step 2: 立刻分组折叠 (用户在 tab bar 上看不到)
120
+ groupTabSilently(tab.id, clientId).then(function() {
121
+ // Step 3: 获取 targetId 并 attach debugger
122
+ return getTargetIdByTabId(tab.id).then(function(targetId) {
123
+ return emitAutoAttachEvents(tab.id, targetId, browserContextId).then(function() {
124
+ // Step 4: 折叠+attach 完成后,再导航到真实 URL
125
+ if (needsNavigate) {
126
+ State.addPendingCreatedTabUrl(url);
127
+ return navigateTabQuietly(tab.id, url).then(function() {
128
+ resolve({ targetId: targetId });
129
+ });
130
+ }
131
+ resolve({ targetId: targetId });
132
+ });
126
133
  });
134
+ }).catch(function(err) {
135
+ Logger.error('[CreateTarget] Error:', err.message || err);
136
+ reject(err);
127
137
  });
128
138
  });
129
139
  });
130
140
  }
131
141
 
142
+ function groupTabSilently(tabId, clientId) {
143
+ return new Promise(function(resolve) {
144
+ addTabToAutomationGroup(tabId, clientId);
145
+ // 分组操作是异步的,给一点时间让 tab 被收进折叠的 group
146
+ setTimeout(resolve, 100);
147
+ });
148
+ }
149
+
150
+ function navigateTabQuietly(tabId, url) {
151
+ return new Promise(function(resolve) {
152
+ chrome.tabs.update(tabId, { url: url }, function() {
153
+ if (chrome.runtime.lastError) {
154
+ Logger.warn('[NavigateQuietly] Failed:', chrome.runtime.lastError.message);
155
+ }
156
+ // 不管成功失败都 resolve,让调用者继续
157
+ resolve();
158
+ });
159
+ });
160
+ }
161
+
132
162
  function addTabToAutomationGroup(tabId, clientId) {
133
163
  Logger.info('[TabGroup] Starting addTabToAutomationGroup for tabId:', tabId, 'clientId:', clientId);
134
164
 
@@ -25,6 +25,8 @@ var CDP_HANDLERS = {
25
25
  'Tab.getMuteStatus': { type: 'LOCAL', handler: LocalHandler.tabGetMuteStatus },
26
26
  'Tab.getGroupInfo': { type: 'LOCAL', handler: LocalHandler.tabGetGroupInfo },
27
27
  'Tab.ungroup': { type: 'LOCAL', handler: LocalHandler.tabUngroup },
28
+ 'Tab.simulateUserOpen': { type: 'LOCAL', handler: LocalHandler.tabSimulateUserOpen },
29
+ 'Tab.getTabGroup': { type: 'LOCAL', handler: LocalHandler.tabGetTabGroup },
28
30
 
29
31
  'SystemInfo.getInfo': { type: 'LOCAL', handler: LocalHandler.systemInfoGetInfo },
30
32
  'SystemInfo.getProcessInfo': { type: 'LOCAL', handler: LocalHandler.systemInfoGetProcessInfo },
@@ -263,6 +263,10 @@ var State = (function() {
263
263
  return _state.cdpCreatedTabIds.has(tabId);
264
264
  }
265
265
 
266
+ function getCDPCreatedTabIds() {
267
+ return Array.from(_state.cdpCreatedTabIds);
268
+ }
269
+
266
270
  function clearAllState() {
267
271
  clearSessionState();
268
272
  _state.attachedTabIds.clear();
@@ -506,6 +510,7 @@ var State = (function() {
506
510
  removePreExistingTab: removePreExistingTab,
507
511
  clearPreExistingTabsForClient: clearPreExistingTabsForClient,
508
512
  addCDPCreatedTab: addCDPCreatedTab,
509
- isCDPCreatedTab: isCDPCreatedTab
513
+ isCDPCreatedTab: isCDPCreatedTab,
514
+ getCDPCreatedTabIds: getCDPCreatedTabIds
510
515
  };
511
516
  })();
@@ -339,23 +339,40 @@ var WebSocketManager = (function() {
339
339
 
340
340
  function closeTabsByClientId(clientId, resolve) {
341
341
  var attachedTabs = State.getAttachedTabIds();
342
+ var cdpCreatedTabs = State.getCDPCreatedTabIds();
342
343
  var tabsToClose = [];
344
+ var tabsToCloseSet = new Set();
345
+
346
+ Logger.info('[WS] closeTabsByClientId: clientId=' + clientId + ' attachedTabs=' + JSON.stringify(attachedTabs) + ' cdpCreatedTabs=' + JSON.stringify(cdpCreatedTabs));
343
347
 
344
- Logger.info('[WS] closeTabsByClientId: clientId=' + clientId + ' attachedTabs=' + JSON.stringify(attachedTabs));
345
348
  attachedTabs.forEach(function(tabId) {
346
349
  var tabClientId = State.getClientIdByTabId(tabId);
347
350
  var isPre = State.isPreExistingTab(tabId);
348
351
  var isCDP = State.isCDPCreatedTab(tabId);
349
- Logger.info('[WS] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isCDP=' + isCDP);
352
+ Logger.info('[WS] [attached] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isCDP=' + isCDP);
350
353
  if (tabClientId === clientId && !isPre) {
351
- tabsToClose.push(tabId);
354
+ tabsToCloseSet.add(tabId);
352
355
  }
353
356
  });
354
357
 
358
+ cdpCreatedTabs.forEach(function(tabId) {
359
+ if (tabsToCloseSet.has(tabId)) return;
360
+
361
+ var tabClientId = State.getClientIdByTabId(tabId);
362
+ var isPre = State.isPreExistingTab(tabId);
363
+ Logger.info('[WS] [cdpCreated] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isAttached=' + attachedTabs.includes(tabId));
364
+ if (tabClientId === clientId && !isPre && !attachedTabs.includes(tabId)) {
365
+ tabsToCloseSet.add(tabId);
366
+ Logger.info('[WS] -> Added to close list (not yet attached)');
367
+ }
368
+ });
369
+
370
+ tabsToClose = Array.from(tabsToCloseSet);
371
+
355
372
  Logger.info('[WS] closeTabsByClientId: will close ' + tabsToClose.length + ' tabs');
356
373
 
357
374
  if (tabsToClose.length === 0) {
358
- Logger.info('[WS] No attached tabs found for clientId:', clientId);
375
+ Logger.info('[WS] No tabs found for clientId:', clientId);
359
376
  resolve();
360
377
  return;
361
378
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "2.5.18",
4
+ "version": "2.5.20",
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.5.18",
3
+ "version": "2.5.20",
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",
@@ -1169,7 +1169,6 @@ function handlePageConnection(ws, clientInfo, targetId) {
1169
1169
  const plugin = pluginConnections.values().next().value;
1170
1170
  if (plugin && plugin.readyState === WebSocket.OPEN) {
1171
1171
  ws.pairedPlugin = plugin;
1172
- plugin.pairedClientId = id;
1173
1172
  if (shouldLog('info')) {
1174
1173
  console.log(` - Paired with plugin: ${plugin.id}`);
1175
1174
  }
@@ -1268,7 +1267,9 @@ function handlePageConnection(ws, clientInfo, targetId) {
1268
1267
 
1269
1268
  if (ws.pairedPlugin && ws.pluginMessageHandler) {
1270
1269
  ws.pairedPlugin.off('message', ws.pluginMessageHandler);
1271
- ws.pairedPlugin.pairedClientId = null;
1270
+ if (ws.pairedPlugin.pairedClientId === id) {
1271
+ ws.pairedPlugin.pairedClientId = null;
1272
+ }
1272
1273
 
1273
1274
  safeSend(ws.pairedPlugin, JSON.stringify({
1274
1275
  type: 'client-disconnected',
@@ -1291,7 +1292,9 @@ function handlePageConnection(ws, clientInfo, targetId) {
1291
1292
 
1292
1293
  if (ws.pairedPlugin && ws.pluginMessageHandler) {
1293
1294
  ws.pairedPlugin.off('message', ws.pluginMessageHandler);
1294
- ws.pairedPlugin.pairedClientId = null;
1295
+ if (ws.pairedPlugin.pairedClientId === id) {
1296
+ ws.pairedPlugin.pairedClientId = null;
1297
+ }
1295
1298
  }
1296
1299
 
1297
1300
  ws.pluginMessageHandler = null;
@@ -1336,8 +1339,8 @@ function broadcastToClients(data, excludeWs = null) {
1336
1339
  let sent = 0;
1337
1340
  logCDP('BROADCAST', `Starting broadcast to ${clientConnections.size} clients, data preview: ${data.substring(0, 200)}`);
1338
1341
  clientConnections.forEach((client) => {
1339
- logCDP('BROADCAST', `Checking client ${client.id}, state=${client.readyState}, excluded=${client === excludeWs}`);
1340
- if (client !== excludeWs && safeSend(client, data, 'client')) {
1342
+ logCDP('BROADCAST', `Checking client ${client.id}, state=${client.readyState}, excluded=${client === excludeWs}, hasOwnHandler=${!!client.pluginMessageHandler}`);
1343
+ if (client !== excludeWs && !client.pluginMessageHandler && safeSend(client, data, 'client')) {
1341
1344
  sent++;
1342
1345
  logCDP('BROADCAST', `Sent to client ${client.id}`);
1343
1346
  }