cdp-tunnel 2.5.10 → 2.5.12

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server/proxy-server.js +133 -177
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.5.10",
3
+ "version": "2.5.12",
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",
@@ -322,6 +322,132 @@ wss.on('connection', (ws, req) => {
322
322
  }
323
323
  });
324
324
 
325
+ function cleanupClient(ws, id, reason) {
326
+ const sessionsToClean = [];
327
+ for (const [sessionId, clientId] of sessionToClientId.entries()) {
328
+ if (clientId === id) {
329
+ sessionsToClean.push(sessionId);
330
+ sessionToClientId.delete(sessionId);
331
+ }
332
+ }
333
+
334
+ clientConnections.delete(ws);
335
+ clientById.delete(id);
336
+
337
+ logConnectionEvent('CLIENT_DISCONNECTED', {
338
+ id,
339
+ reason,
340
+ sessionsCleaned: sessionsToClean.length,
341
+ totalPlugins: pluginConnections.size,
342
+ totalClients: clientConnections.size
343
+ });
344
+
345
+ logDisconnect('CLIENT_CLEANUP', {
346
+ clientId: id,
347
+ reason,
348
+ sessionsLost: sessionsToClean.length,
349
+ cdpMethodsUsed: ws.cdpTrace ? [...new Set(ws.cdpTrace)] : [],
350
+ uptime: ws.connectedAt ? `${((Date.now() - ws.connectedAt) / 1000).toFixed(0)}s` : 'unknown',
351
+ remainingClients: clientConnections.size,
352
+ pluginAlive: pluginConnections.size > 0,
353
+ pairedPluginId: ws.pairedPlugin?.id || null
354
+ });
355
+
356
+ if (ws.cdpTrace && ws.cdpTrace.length && shouldLog('debug')) {
357
+ const unique = [...new Set(ws.cdpTrace)];
358
+ console.log(`[CDP TRACE] ${id} methods (${ws.cdpTrace.length}): ${unique.join(', ')}`);
359
+ }
360
+
361
+ if (ws.pairedPlugin) {
362
+ safeSend(ws.pairedPlugin, JSON.stringify({
363
+ type: 'client-disconnected',
364
+ clientId: id,
365
+ sessions: sessionsToClean
366
+ }), 'plugin');
367
+ }
368
+
369
+ broadcastClientList();
370
+
371
+ for (const [tId, cId] of targetIdToClientId.entries()) {
372
+ if (cId === id) targetIdToClientId.delete(tId);
373
+ }
374
+ for (const [bcId, cId] of browserContextToClientId.entries()) {
375
+ if (cId === id) browserContextToClientId.delete(bcId);
376
+ }
377
+ if (clientIdToBrowserContext.has(id)) {
378
+ clientIdToBrowserContext.delete(id);
379
+ }
380
+ for (const [gId, mapping] of globalRequestIdMap.entries()) {
381
+ if (mapping.clientId === id) globalRequestIdMap.delete(gId);
382
+ }
383
+
384
+ if (ws.pairedPlugin) {
385
+ ws.pairedPlugin.pairedClientId = null;
386
+ }
387
+ connectionPairs.delete(id);
388
+ }
389
+
390
+ function sendPendingRequestErrors(pluginWs) {
391
+ const toDelete = [];
392
+ for (const [gId, mapping] of globalRequestIdMap.entries()) {
393
+ const clientWs = clientById.get(mapping.clientId);
394
+ if (clientWs && clientWs.pairedPlugin === pluginWs) {
395
+ const errorResponse = {
396
+ id: mapping.originalId,
397
+ error: { code: -32000, message: 'Plugin disconnected: request cancelled' }
398
+ };
399
+ if (mapping.sessionId) {
400
+ errorResponse.sessionId = mapping.sessionId;
401
+ }
402
+ safeSend(clientWs, JSON.stringify(errorResponse), 'client');
403
+ toDelete.push(gId);
404
+ }
405
+ }
406
+ toDelete.forEach(gId => globalRequestIdMap.delete(gId));
407
+ }
408
+
409
+ function cleanupPlugin(ws, id, reason) {
410
+ pluginConnections.delete(ws);
411
+
412
+ if (pluginConnections.size === 0) {
413
+ updateExtensionState(false);
414
+ }
415
+
416
+ sendPendingRequestErrors(ws);
417
+
418
+ const affectedClients = [];
419
+ clientConnections.forEach(clientWs => {
420
+ if (clientWs.pairedPlugin === ws) {
421
+ if (clientWs.pluginMessageHandler) {
422
+ ws.off('message', clientWs.pluginMessageHandler);
423
+ clientWs.pluginMessageHandler = null;
424
+ }
425
+ clientWs.pairedPlugin = null;
426
+ affectedClients.push(clientWs.id);
427
+ if (clientWs.readyState === WebSocket.OPEN) {
428
+ clientWs.send(JSON.stringify({
429
+ type: 'plugin-disconnected',
430
+ message: 'Plugin connection lost'
431
+ }));
432
+ }
433
+ }
434
+ });
435
+
436
+ logDisconnect('PLUGIN_CLEANUP', {
437
+ pluginId: id,
438
+ reason,
439
+ remainingPlugins: pluginConnections.size,
440
+ affectedClients,
441
+ uptime: ws.connectedAt ? `${((Date.now() - ws.connectedAt) / 1000).toFixed(0)}s` : 'unknown',
442
+ activeSessions: sessionToClientId.size,
443
+ pendingRequests: pendingAttachRequests.size
444
+ });
445
+
446
+ if (ws.pairedClientId) {
447
+ connectionPairs.delete(ws.pairedClientId);
448
+ }
449
+ }
450
+
325
451
  /**
326
452
  * 处理 Chrome 扩展连接
327
453
  */
@@ -687,15 +813,12 @@ function handlePluginConnection(ws, clientInfo) {
687
813
  }
688
814
  });
689
815
 
690
- // 连接关闭
691
816
  ws.on('close', (code, reason) => {
692
- pluginConnections.delete(ws);
693
817
  if (shouldLog('info')) {
694
818
  console.log(`\n[PLUGIN DISCONNECTED] ${id}`);
695
819
  console.log(` - Code: ${code}, Reason: ${reason || 'none'}`);
696
820
  console.log(` - Total plugin connections: ${pluginConnections.size}`);
697
821
  }
698
-
699
822
  logConnectionEvent('PLUGIN_DISCONNECTED', {
700
823
  id,
701
824
  code,
@@ -703,51 +826,7 @@ function handlePluginConnection(ws, clientInfo) {
703
826
  totalPlugins: pluginConnections.size,
704
827
  totalClients: clientConnections.size
705
828
  });
706
-
707
- if (pluginConnections.size === 0) {
708
- updateExtensionState(false);
709
- }
710
-
711
- // 清理配对关系并通知所有受影响的 Client
712
- const affectedClients = [];
713
- clientConnections.forEach(clientWs => {
714
- if (clientWs.pairedPlugin === ws) {
715
- // 清理 page 连接的事件监听器
716
- if (clientWs.pluginMessageHandler) {
717
- ws.off('message', clientWs.pluginMessageHandler);
718
- clientWs.pluginMessageHandler = null;
719
- }
720
- clientWs.pairedPlugin = null;
721
- affectedClients.push(clientWs.id);
722
- if (clientWs.readyState === WebSocket.OPEN) {
723
- clientWs.send(JSON.stringify({
724
- type: 'plugin-disconnected',
725
- message: 'Plugin connection lost'
726
- }));
727
- }
728
- if (shouldLog('debug')) {
729
- console.log(` - Cleared pairedPlugin for client: ${clientWs.id}`);
730
- }
731
- }
732
- });
733
-
734
- logDisconnect('PLUGIN_DISCONNECTED', {
735
- pluginId: id,
736
- code, reason: reason?.toString() || 'none',
737
- remainingPlugins: pluginConnections.size,
738
- affectedClients,
739
- uptime: ws.connectedAt ? `${((Date.now() - ws.connectedAt) / 1000).toFixed(0)}s` : 'unknown',
740
- activeSessions: sessionToClientId.size,
741
- pendingRequests: pendingAttachRequests.size
742
- });
743
-
744
- if (affectedClients.length > 0) {
745
- logConnectionEvent('PLUGIN_DISCONNECT_AFFECTED_CLIENTS', { pluginId: id, affectedClients });
746
- }
747
-
748
- if (ws.pairedClientId) {
749
- connectionPairs.delete(ws.pairedClientId);
750
- }
829
+ cleanupPlugin(ws, id, `close:${code}`);
751
830
  });
752
831
 
753
832
  // 错误处理
@@ -998,111 +1077,24 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
998
1077
  }
999
1078
  });
1000
1079
 
1001
- // 连接关闭
1002
1080
  ws.on('close', async (code, reason) => {
1003
- // 记录断开事件到日志文件
1004
1081
  logCDP('EVENT', `CLIENT DISCONNECTED id=${id} code=${code} reason=${reason.toString() || 'none'}`);
1005
-
1006
- // 收集该 client 的所有 session
1007
- const sessionsToClean = [];
1008
- for (const [sessionId, clientId] of sessionToClientId.entries()) {
1009
- if (clientId === id) {
1010
- sessionsToClean.push(sessionId);
1011
- sessionToClientId.delete(sessionId);
1012
- }
1013
- }
1014
-
1015
- clientConnections.delete(ws);
1016
- clientById.delete(id);
1017
1082
  if (shouldLog('info')) {
1018
1083
  console.log(`\n[CLIENT DISCONNECTED] ${id}`);
1019
1084
  console.log(` - Code: ${code}, Reason: ${reason || 'none'}`);
1020
- console.log(` - Sessions to clean: ${sessionsToClean.length}`);
1021
- console.log(` - Total client connections: ${clientConnections.size}`);
1022
- }
1023
-
1024
- logConnectionEvent('CLIENT_DISCONNECTED', {
1025
- id,
1026
- code,
1027
- reason: reason?.toString() || 'none',
1028
- sessionsCleaned: sessionsToClean.length,
1029
- totalPlugins: pluginConnections.size,
1030
- totalClients: clientConnections.size
1031
- });
1032
-
1033
- const isUnexpected = code !== 1000 && code !== 1001;
1034
- if (isUnexpected) {
1035
- logDisconnect('CLIENT_DISCONNECTED_UNEXPECTED', {
1036
- clientId: id,
1037
- code, reason: reason?.toString() || 'none',
1038
- sessionsLost: sessionsToClean.length,
1039
- cdpMethodsUsed: ws.cdpTrace ? [...new Set(ws.cdpTrace)] : [],
1040
- uptime: ws.connectedAt ? `${((Date.now() - ws.connectedAt) / 1000).toFixed(0)}s` : 'unknown',
1041
- remainingClients: clientConnections.size,
1042
- pluginAlive: pluginConnections.size > 0,
1043
- pairedPluginId: ws.pairedPlugin?.id || null
1044
- });
1045
- }
1046
-
1047
- if (ws.cdpTrace && ws.cdpTrace.length && shouldLog('debug')) {
1048
- const unique = [...new Set(ws.cdpTrace)];
1049
- console.log(`[CDP TRACE] ${id} methods (${ws.cdpTrace.length}): ${unique.join(', ')}`);
1050
- }
1051
-
1052
- // 向 plugin 发送清理命令
1053
- if (ws.pairedPlugin) {
1054
- safeSend(ws.pairedPlugin, JSON.stringify({
1055
- type: 'client-disconnected',
1056
- clientId: id,
1057
- sessions: sessionsToClean
1058
- }), 'plugin');
1059
- if (shouldLog('debug')) {
1060
- console.log(` - Notified plugin of client disconnect`);
1061
- }
1062
- }
1063
-
1064
- // 广播更新后的客户端列表
1065
- broadcastClientList();
1066
-
1067
- // 清理该 client 的所有映射
1068
- for (const [tId, cId] of targetIdToClientId.entries()) {
1069
- if (cId === id) targetIdToClientId.delete(tId);
1070
- }
1071
- for (const [bcId, cId] of browserContextToClientId.entries()) {
1072
- if (cId === id) browserContextToClientId.delete(bcId);
1073
- }
1074
- if (clientIdToBrowserContext.has(id)) {
1075
- clientIdToBrowserContext.delete(id);
1076
- }
1077
- for (const [gId, mapping] of globalRequestIdMap.entries()) {
1078
- if (mapping.clientId === id) globalRequestIdMap.delete(gId);
1079
1085
  }
1080
-
1081
- // 清理配对关系
1082
- if (ws.pairedPlugin) {
1083
- ws.pairedPlugin.pairedClientId = null;
1084
- }
1085
- connectionPairs.delete(id);
1086
+ cleanupClient(ws, id, `close:${code}`);
1086
1087
  });
1087
1088
 
1088
- // 错误处理
1089
1089
  ws.on('error', (error) => {
1090
1090
  console.error(`[CLIENT ERROR] ${id}:`, error.message);
1091
-
1092
1091
  logConnectionEvent('CLIENT_ERROR', {
1093
1092
  id,
1094
1093
  error: error.message,
1095
1094
  totalPlugins: pluginConnections.size,
1096
1095
  totalClients: clientConnections.size
1097
1096
  });
1098
-
1099
- clientConnections.delete(ws);
1100
- clientById.delete(id);
1101
-
1102
- if (ws.pairedPlugin) {
1103
- ws.pairedPlugin.pairedClientId = null;
1104
- }
1105
- connectionPairs.delete(id);
1097
+ cleanupClient(ws, id, `error:${error.message}`);
1106
1098
  });
1107
1099
  }
1108
1100
 
@@ -1430,29 +1422,13 @@ const heartbeatInterval = setInterval(() => {
1430
1422
  const now = new Date().toISOString();
1431
1423
  const nowMs = Date.now();
1432
1424
 
1433
- // 检查 plugin 连接
1434
1425
  pluginConnections.forEach((ws) => {
1435
1426
  if (!ws.isAlive) {
1436
1427
  if (shouldLog('warn')) {
1437
1428
  console.log(`[${now}] Plugin ${ws.id} not responding, terminating...`);
1438
1429
  }
1439
1430
  logConnectionEvent('HEARTBEAT_TIMEOUT', { type: 'plugin', id: ws.id });
1440
- logDisconnect('HEARTBEAT_TIMEOUT_PLUGIN', {
1441
- pluginId: ws.id,
1442
- pairedClientId: ws.pairedClientId || null,
1443
- uptime: ws.connectedAt ? `${((Date.now() - ws.connectedAt) / 1000).toFixed(0)}s` : 'unknown',
1444
- remainingPlugins: pluginConnections.size,
1445
- activeClients: clientConnections.size,
1446
- activeSessions: sessionToClientId.size
1447
- });
1448
- pluginConnections.delete(ws);
1449
- if (ws.pairedClientId) {
1450
- connectionPairs.delete(ws.pairedClientId);
1451
- const clientWs = clientById.get(ws.pairedClientId);
1452
- if (clientWs) {
1453
- clientWs.pairedPlugin = null;
1454
- }
1455
- }
1431
+ cleanupPlugin(ws, ws.id, 'heartbeat_timeout');
1456
1432
  return ws.terminate();
1457
1433
  }
1458
1434
  ws.isAlive = false;
@@ -1460,29 +1436,16 @@ const heartbeatInterval = setInterval(() => {
1460
1436
  logConnectionEvent('HEARTBEAT_PING', { type: 'plugin', id: ws.id, bufferedAmount: ws.bufferedAmount });
1461
1437
  });
1462
1438
 
1463
- // 检查 client 连接
1464
1439
  clientConnections.forEach((ws) => {
1465
1440
  if (!ws.isAlive) {
1466
1441
  if (shouldLog('warn')) {
1467
1442
  console.log(`[${now}] Client ${ws.id} not responding, terminating...`);
1468
1443
  }
1469
1444
  logConnectionEvent('HEARTBEAT_TIMEOUT', { type: 'client', id: ws.id });
1470
- logDisconnect('HEARTBEAT_TIMEOUT_CLIENT', {
1471
- clientId: ws.id,
1472
- uptime: ws.connectedAt ? `${((Date.now() - ws.connectedAt) / 1000).toFixed(0)}s` : 'unknown',
1473
- remainingClients: clientConnections.size,
1474
- pluginAlive: pluginConnections.size > 0
1475
- });
1476
- clientConnections.delete(ws);
1477
- clientById.delete(ws.id);
1478
- if (ws.pairedPlugin) {
1479
- ws.pairedPlugin.pairedClientId = null;
1480
- }
1481
- connectionPairs.delete(ws.id);
1445
+ cleanupClient(ws, ws.id, 'heartbeat_timeout');
1482
1446
  return ws.terminate();
1483
1447
  }
1484
1448
 
1485
- // 检查空闲超时
1486
1449
  if (ws.lastActivityTime && (nowMs - ws.lastActivityTime > CONFIG.CLIENT_IDLE_TIMEOUT)) {
1487
1450
  const idleSeconds = Math.round((nowMs - ws.lastActivityTime) / 1000);
1488
1451
  if (shouldLog('info')) {
@@ -1512,10 +1475,7 @@ setInterval(() => {
1512
1475
  }
1513
1476
  });
1514
1477
  toRemove.forEach(ws => {
1515
- pluginConnections.delete(ws);
1516
- if (shouldLog('debug')) {
1517
- console.log(`[CLEANUP] Removed zombie plugin: ${ws.id}, state: ${ws.readyState}`);
1518
- }
1478
+ cleanupPlugin(ws, ws.id, 'zombie_cleanup');
1519
1479
  });
1520
1480
 
1521
1481
  toRemove.length = 0;
@@ -1525,11 +1485,7 @@ setInterval(() => {
1525
1485
  }
1526
1486
  });
1527
1487
  toRemove.forEach(ws => {
1528
- clientConnections.delete(ws);
1529
- clientById.delete(ws.id);
1530
- if (shouldLog('debug')) {
1531
- console.log(`[CLEANUP] Removed zombie client: ${ws.id}, state: ${ws.readyState}`);
1532
- }
1488
+ cleanupClient(ws, ws.id, 'zombie_cleanup');
1533
1489
  });
1534
1490
  }, 60000);
1535
1491