cdp-tunnel 3.3.0 → 3.3.2

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.
@@ -161,11 +161,24 @@ var SpecialHandler = (function() {
161
161
  var needsNavigate = url !== 'about:blank' && url !== '';
162
162
 
163
163
  return new Promise(function(resolve, reject) {
164
- chrome.tabs.create({ url: 'about:blank', active: false }, function(tab) {
165
- if (!tab || !tab.id) {
166
- reject(new Error('Failed to create tab'));
167
- return;
168
- }
164
+ var retryCount = 0;
165
+ var maxRetries = 3;
166
+
167
+ function doCreate() {
168
+ chrome.tabs.create({ url: 'about:blank', active: false }, function(tab) {
169
+ if (!tab || !tab.id) {
170
+ retryCount++;
171
+ if (retryCount <= maxRetries) {
172
+ Logger.warn('[CreateTarget] Empty tab (tab=' + (!!tab) + '), retry ' + retryCount);
173
+ setTimeout(doCreate, 100 * retryCount);
174
+ return;
175
+ }
176
+ Logger.error('[CreateTarget] Failed after ' + maxRetries + ' retries');
177
+ reject(new Error('Failed to create tab after ' + maxRetries + ' retries'));
178
+ return;
179
+ }
180
+
181
+ Logger.info('[CreateTarget] tab created: ' + tab.id);
169
182
 
170
183
  if (clientId) {
171
184
  state.setTabIdToClientId(tab.id, clientId);
@@ -189,7 +202,10 @@ var SpecialHandler = (function() {
189
202
  Logger.error('[CreateTarget] Error:', err.message || err);
190
203
  reject(err);
191
204
  });
192
- });
205
+ }); // chrome.tabs.create callback end
206
+ } // doCreate end
207
+
208
+ doCreate();
193
209
  });
194
210
  }
195
211
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "3.3.0",
4
+ "version": "3.3.2",
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": "3.3.0",
3
+ "version": "3.3.2",
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",
@@ -20,7 +20,8 @@ const { CONFIG } = require('./config');
20
20
 
21
21
  class PortPoolManager {
22
22
  constructor(mainProxy) {
23
- this.mainProxy = mainProxy; // 现有 proxy 的引用(拿 plugin 连接)
23
+ this.mainProxy = mainProxy;
24
+ this.idCounter = 0; // 全局唯一 id 计数器 // 现有 proxy 的引用(拿 plugin 连接)
24
25
  this.createServers = []; // [http.Server] 每个 create 端口一个
25
26
  this.createWss = []; // [WebSocket.Server] 每个 create 端口一个
26
27
  this.portSessions = []; // [PortSession] 每个 create 端口一个
@@ -209,10 +210,11 @@ class PortPoolManager {
209
210
  try { msg = JSON.parse(data.toString()); } catch { return; }
210
211
 
211
212
  if (msg.id !== undefined) {
212
- // createTarget 串行化(避免并发 chrome.tabs.create 回调串)
213
+ // createTarget 串行化 + 标记 pending
213
214
  if (msg.method === 'Target.createTarget') {
214
215
  while (session.createTargetLock) { await session.createTargetLock; }
215
216
  session.createTargetLock = new Promise(r => { session._releaseCreateTarget = r; });
217
+ session.pendingCreate = true; // 标记:下一个 targetCreated 事件归属这个端口
216
218
  }
217
219
 
218
220
  // 合成输入命令:先 bringToFront + 等待,再发原命令
@@ -220,8 +222,12 @@ class PortPoolManager {
220
222
  await this._ensureVisible(session, pluginWs, msg.sessionId);
221
223
  }
222
224
 
223
- // 命令:分配新 id,记录映射(含 method 名供响应后处理),转发给 plugin
224
- const newId = `pool${session.portIndex}_${msg.id}`;
225
+ // 命令:分配全局唯一 id(避免不同 client msg.id 冲突)
226
+ const uniqueId = ++this.idCounter;
227
+ const newId = `pool${session.portIndex}_${uniqueId}`;
228
+ // 记录 originalId(用于响应时恢复)
229
+ if (!ws._origIds) ws._origIds = new Map();
230
+ ws._origIds.set(uniqueId, msg.id);
225
231
  session.pendingRequests.set(newId, {
226
232
  originalId: msg.id,
227
233
  method: msg.method,
@@ -273,7 +279,10 @@ class PortPoolManager {
273
279
  const pending = session.pendingRequests.get(msg.id);
274
280
  session.pendingRequests.delete(msg.id);
275
281
 
276
- // 如果是 createTarget 响应,记录 targetId → portIndex 归属 + 初始 url + 释放锁
282
+ // 如果是 createTarget 响应
283
+ if (pending && pending.method === 'Target.createTarget') {
284
+ console.log('[PORT POOL] createTarget resp: ' + JSON.stringify(msg.result).slice(0, 80) + ' pending=' + !!pending);
285
+ }
277
286
  if (msg.result && msg.result.targetId) {
278
287
  const tid = msg.result.targetId;
279
288
  session.targetIds.add(tid);
@@ -311,7 +320,7 @@ class PortPoolManager {
311
320
  }
312
321
 
313
322
  // 恢复原始 id,发给发起请求的 client
314
- const response = { ...msg, id: this._parseOriginalId(originalId) };
323
+ const response = { ...msg, id: pending ? pending.originalId : this._parseOriginalId(originalId) };
315
324
  delete response.__portIndex;
316
325
  delete response.__clientId;
317
326
  delete response.type;
@@ -340,6 +349,19 @@ class PortPoolManager {
340
349
  return true;
341
350
  }
342
351
  }
352
+ // 竞态处理:targetCreated 在 createTarget 响应之前到达
353
+ // 如果有 pendingCreate,把事件归属到对应端口
354
+ for (let pi = 0; pi < this.portSessions.length; pi++) {
355
+ const sess = this.portSessions[pi];
356
+ if (sess && sess.pendingCreate) {
357
+ sess.targetIds.add(targetId);
358
+ sess.targetUrls.set(targetId, 'about:blank');
359
+ this.targetToPort.set(targetId, pi);
360
+ sess.pendingCreate = false; // 消费掉
361
+ this._broadcastToPort(pi, msg);
362
+ return true;
363
+ }
364
+ }
343
365
  }
344
366
 
345
367
  // sessionId 路由(Network/Screencast/Input 等 session 事件)