cdp-tunnel 3.0.1 → 3.0.3

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "3.0.1",
4
+ "version": "3.0.3",
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.0.1",
3
+ "version": "3.0.3",
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",
@@ -181,24 +181,28 @@ class PortPoolManager {
181
181
  return;
182
182
  }
183
183
 
184
+ // 合成输入命令需要 ensureVisible(和 forward.js 的逻辑一致)
185
+ const SYNTHETIC_INPUT = ['Input.dispatchKeyEvent', 'Input.dispatchMouseEvent'];
186
+
184
187
  // 消息处理:client → plugin(带 portIndex 标记)
185
- ws.on('message', (data) => {
188
+ ws.on('message', async (data) => {
186
189
  let msg;
187
190
  try { msg = JSON.parse(data.toString()); } catch { return; }
188
191
 
189
192
  if (msg.id !== undefined) {
190
- // 命令:分配新 id,记录映射,转发给 plugin
193
+ // 合成输入命令:先 bringToFront + 等待,再发原命令
194
+ if (SYNTHETIC_INPUT.indexOf(msg.method) >= 0 && msg.sessionId) {
195
+ await this._ensureVisible(session, pluginWs, msg.sessionId);
196
+ }
197
+
198
+ // 命令:分配新 id,记录映射(含 method 名供响应后处理),转发给 plugin
191
199
  const newId = `pool${session.portIndex}_${msg.id}`;
192
200
  session.pendingRequests.set(newId, {
193
201
  originalId: msg.id,
202
+ method: msg.method,
194
203
  clientWs: ws
195
204
  });
196
205
 
197
- // 特殊处理 createTarget:记录 targetId 归属
198
- if (msg.method === 'Target.createTarget') {
199
- msg.params = msg.params || {};
200
- }
201
-
202
206
  const forwarded = { ...msg, id: newId, __portIndex: session.portIndex };
203
207
  pluginWs.send(JSON.stringify(forwarded));
204
208
  } else {
@@ -227,6 +231,10 @@ class PortPoolManager {
227
231
 
228
232
  // 1. 响应消息:id 以 pool 开头
229
233
  if (msg.id && typeof msg.id === 'string' && msg.id.startsWith('pool')) {
234
+ // 内部命令(ensureVisible 用的)响应直接丢弃
235
+ if (msg.id.includes('_internal')) {
236
+ return true;
237
+ }
230
238
  const match = msg.id.match(/^pool(\d+)_(.+)$/);
231
239
  if (!match) return false;
232
240
 
@@ -251,6 +259,12 @@ class PortPoolManager {
251
259
  console.log(`[PORT POOL] sessionId=${msg.result.sessionId.slice(0,12)} → port ${session.port}`);
252
260
  }
253
261
 
262
+ // 如果是 getTargets 响应,按 portIndex 过滤 targetInfos
263
+ if (pending && pending.method === 'Target.getTargets' && msg.result && msg.result.targetInfos) {
264
+ msg.result.targetInfos = msg.result.targetInfos.filter(t => session.targetIds.has(t.targetId));
265
+ console.log(`[PORT POOL] getTargets filtered: ${msg.result.targetInfos.length} targets for port ${session.port}`);
266
+ }
267
+
254
268
  // 恢复原始 id,发给发起请求的 client
255
269
  const response = { ...msg, id: this._parseOriginalId(originalId) };
256
270
  if (pending && pending.clientWs && pending.clientWs.readyState === WebSocket.OPEN) {
@@ -298,6 +312,54 @@ class PortPoolManager {
298
312
  return isNaN(n) ? idStr : n;
299
313
  }
300
314
 
315
+ /**
316
+ * 让 tab 变 visible:Page.bringToFront + 等待 + 恢复焦点。
317
+ * 内部命令用 _internal_ 前缀,handlePluginMessage 会丢弃它们的响应。
318
+ */
319
+ async _ensureVisible(session, pluginWs, sessionId) {
320
+ const pfx = `pool${session.portIndex}_internal`;
321
+
322
+ // 1. 保存焦点
323
+ pluginWs.send(JSON.stringify({
324
+ id: `${pfx}_save_${Date.now()}`,
325
+ method: 'Runtime.evaluate',
326
+ params: { expression: '(function(){var el=document.activeElement;if(el&&el!==document.body&&el.focus){el.setAttribute("data-cdp-saved-focus","1");return 1}return 0})()', returnByValue: true },
327
+ sessionId, __portIndex: session.portIndex
328
+ }));
329
+
330
+ // 2. bringToFront
331
+ pluginWs.send(JSON.stringify({
332
+ id: `${pfx}_front_${Date.now()}`,
333
+ method: 'Page.bringToFront',
334
+ params: {},
335
+ sessionId, __portIndex: session.portIndex
336
+ }));
337
+
338
+ // 3. 等 visibilitychange + 双 rAF(renderer 完成切换)
339
+ pluginWs.send(JSON.stringify({
340
+ id: `${pfx}_vis_${Date.now()}`,
341
+ method: 'Runtime.evaluate',
342
+ params: {
343
+ expression: 'new Promise(function(r){function ok(){requestAnimationFrame(function(){requestAnimationFrame(function(){r(1)})})}if(document.visibilityState==="visible"){ok()}else{var d=function(){if(document.visibilityState==="visible"){document.removeEventListener("visibilitychange",d);ok()}};document.addEventListener("visibilitychange",d);setTimeout(function(){document.removeEventListener("visibilitychange",d);ok()},3000)}})',
344
+ awaitPromise: true
345
+ },
346
+ sessionId, __portIndex: session.portIndex
347
+ }));
348
+
349
+ // 给 bringToFront + visibility 等待时间(不等具体响应,内部命令响应被丢弃)
350
+ await new Promise(r => setTimeout(r, 300));
351
+
352
+ // 4. 恢复焦点
353
+ pluginWs.send(JSON.stringify({
354
+ id: `${pfx}_restore_${Date.now()}`,
355
+ method: 'Runtime.evaluate',
356
+ params: { expression: '(function(){var el=document.querySelector("[data-cdp-saved-focus]");if(el){el.removeAttribute("data-cdp-saved-focus");el.focus();return 1}return 0})()', returnByValue: true },
357
+ sessionId, __portIndex: session.portIndex
358
+ }));
359
+
360
+ await new Promise(r => setTimeout(r, 50));
361
+ }
362
+
301
363
  _broadcastToPort(portIndex, msg) {
302
364
  const session = this.portSessions[portIndex];
303
365
  if (!session) return;