cdp-tunnel 3.2.7 → 3.3.1

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.2.7",
4
+ "version": "3.3.1",
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:29931/plugin',
2
+ WS_URL: 'ws://localhost:9221/plugin',
3
3
  RECONNECT_DELAY: 3000,
4
4
  DEBUGGER_VERSION: '1.3',
5
5
  HEARTBEAT_INTERVAL: 25000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "3.2.7",
3
+ "version": "3.3.1",
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 端口一个
@@ -204,57 +205,42 @@ class PortPoolManager {
204
205
  const SYNTHETIC_INPUT = ['Input.dispatchKeyEvent', 'Input.dispatchMouseEvent'];
205
206
 
206
207
  // 消息处理:client → plugin(带 portIndex 标记)
207
- // session 级别的命令队列:保证跨 client 的命令串行处理
208
- const handleMessage = async (data) => {
208
+ ws.on('message', async (data) => {
209
209
  let msg;
210
210
  try { msg = JSON.parse(data.toString()); } catch { return; }
211
211
 
212
212
  if (msg.id !== undefined) {
213
- // createTarget:等前一个 createTarget 的响应回来
214
- if (msg.method === 'Target.createTarget' && session.createTargetChain) {
215
- await session.createTargetChain;
216
- }
217
-
218
- // attachToTarget:已 attach 的 target 返回虚拟 session
219
- if (msg.method === 'Target.attachToTarget' && msg.params && msg.params.targetId) {
220
- const targetId = msg.params.targetId;
221
- if (session.attachedTargets && session.attachedTargets.has(targetId)) {
222
- const vsid = `vs_${session.portIndex}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
223
- session.virtualSessions.set(vsid, targetId);
224
- this.sessionToPort.set(vsid, session.portIndex);
225
- ws.send(JSON.stringify({ id: msg.id, result: { sessionId: vsid } }));
226
- return;
227
- }
228
- if (!session.attachedTargets) session.attachedTargets = new Set();
229
- session.attachedTargets.add(targetId);
213
+ // createTarget 串行化(避免并发 chrome.tabs.create 回调串)
214
+ if (msg.method === 'Target.createTarget') {
215
+ while (session.createTargetLock) { await session.createTargetLock; }
216
+ session.createTargetLock = new Promise(r => { session._releaseCreateTarget = r; });
230
217
  }
231
218
 
219
+ // 合成输入命令:先 bringToFront + 等待,再发原命令
232
220
  if (SYNTHETIC_INPUT.indexOf(msg.method) >= 0 && msg.sessionId) {
233
221
  await this._ensureVisible(session, pluginWs, msg.sessionId);
234
222
  }
235
223
 
236
- // createTarget:设置串行链
237
- if (msg.method === 'Target.createTarget') {
238
- let release;
239
- session.createTargetChain = new Promise(r => { release = r; });
240
- session._releaseCT = release;
241
- }
242
-
243
- const newId = `pool${session.portIndex}_${msg.id}`;
224
+ // 命令:分配全局唯一 id(避免不同 client 的 msg.id 冲突)
225
+ const uniqueId = ++this.idCounter;
226
+ const newId = `pool${session.portIndex}_${uniqueId}`;
227
+ // 记录 originalId(用于响应时恢复)
228
+ if (!ws._origIds) ws._origIds = new Map();
229
+ ws._origIds.set(uniqueId, msg.id);
244
230
  session.pendingRequests.set(newId, {
245
- originalId: msg.id, method: msg.method, clientWs: ws,
246
- params: msg.params, sessionId: msg.sessionId
231
+ originalId: msg.id,
232
+ method: msg.method,
233
+ clientWs: ws,
234
+ params: msg.params,
235
+ sessionId: msg.sessionId
247
236
  });
237
+
248
238
  const forwarded = { ...msg, id: newId, __portIndex: session.portIndex, __clientId: `pool_${session.port}` };
249
239
  pluginWs.send(JSON.stringify(forwarded));
250
240
  } else {
241
+ // 无 id 的消息(事件),直接转发
251
242
  pluginWs.send(JSON.stringify({ ...msg, __portIndex: session.portIndex }));
252
243
  }
253
- };
254
-
255
- ws.on('message', (data) => {
256
- if (!session.cmdQueue) session.cmdQueue = Promise.resolve();
257
- session.cmdQueue = session.cmdQueue.then(() => handleMessage(data)).catch(() => {});
258
244
  });
259
245
 
260
246
  ws.on('close', () => {
@@ -292,18 +278,21 @@ class PortPoolManager {
292
278
  const pending = session.pendingRequests.get(msg.id);
293
279
  session.pendingRequests.delete(msg.id);
294
280
 
295
- // 如果是 createTarget 响应,记录 targetId + 释放串行链
281
+ // 如果是 createTarget 响应
282
+ if (pending && pending.method === 'Target.createTarget') {
283
+ console.log('[PORT POOL] createTarget resp: ' + JSON.stringify(msg.result).slice(0, 80) + ' pending=' + !!pending);
284
+ }
296
285
  if (msg.result && msg.result.targetId) {
297
286
  const tid = msg.result.targetId;
298
287
  session.targetIds.add(tid);
299
288
  session.targetUrls.set(tid, pending?.params?.url || 'about:blank');
300
289
  this.targetToPort.set(tid, portIndex);
301
- }
302
- // 释放 createTarget 串行链
303
- if (pending && pending.method === 'Target.createTarget' && session._releaseCT) {
304
- session._releaseCT();
305
- session._releaseCT = null;
306
- session.createTargetChain = null;
290
+ // 释放 createTarget 锁
291
+ if (session._releaseCreateTarget) {
292
+ session._releaseCreateTarget();
293
+ session._releaseCreateTarget = null;
294
+ session.createTargetLock = null;
295
+ }
307
296
  }
308
297
 
309
298
  // 如果是 Page.navigate 响应,更新 targetId 的 url
@@ -330,7 +319,7 @@ class PortPoolManager {
330
319
  }
331
320
 
332
321
  // 恢复原始 id,发给发起请求的 client
333
- const response = { ...msg, id: this._parseOriginalId(originalId) };
322
+ const response = { ...msg, id: pending ? pending.originalId : this._parseOriginalId(originalId) };
334
323
  delete response.__portIndex;
335
324
  delete response.__clientId;
336
325
  delete response.type;