cdp-tunnel 1.0.9 → 1.0.11

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 (58) hide show
  1. package/console-test.js +52 -0
  2. package/extension-new/background.js +25 -12
  3. package/extension-new/cdp/handler/special.js +40 -22
  4. package/extension-new/cdp/index.js +6 -5
  5. package/extension-new/core/debugger.js +12 -5
  6. package/extension-new/core/state.js +13 -2
  7. package/extension-new/core/websocket.js +4 -2
  8. package/final-console-test.js +105 -0
  9. package/package.json +1 -1
  10. package/server/modules/config.js +1 -1
  11. package/server/modules/logger.js +1 -1
  12. package/server/proxy-server.js +13 -9
  13. package/simple-tab-group-test.js +56 -0
  14. package/test-cdp-connection.js +85 -0
  15. package/test-cdp-groups.js +71 -0
  16. package/test-check-newtab.js +144 -0
  17. package/test-chrome-native.js +140 -0
  18. package/test-client-connected.js +99 -0
  19. package/test-compare-formats.js +88 -0
  20. package/test-context-features.js +113 -0
  21. package/test-create-tab.js +113 -0
  22. package/test-debug-broadcast.js +52 -0
  23. package/test-debug-targets.js +127 -0
  24. package/test-expose-newtab.js +164 -0
  25. package/test-expose-shared.js +189 -0
  26. package/test-final-logs.js +110 -0
  27. package/test-fresh-chromium.js +153 -0
  28. package/test-init-script.js +128 -0
  29. package/test-keepalive.js +89 -0
  30. package/test-launch-chromium.js +140 -0
  31. package/test-launch-vs-connect.js +149 -0
  32. package/test-listen-events.js +102 -0
  33. package/test-monitor.js +83 -0
  34. package/test-multiple-cdp-groups.js +78 -0
  35. package/test-native.js +96 -0
  36. package/test-page-connection.js +74 -0
  37. package/test-playwright-connection.js +45 -0
  38. package/test-playwright-groups.js +47 -0
  39. package/test-playwright-pages.js +47 -0
  40. package/test-playwright-sequence.js +81 -0
  41. package/test-proper-context.js +129 -0
  42. package/test-real-final.js +251 -0
  43. package/test-real-scenario-v2.js +166 -0
  44. package/test-real-scenario-v3.js +231 -0
  45. package/test-real-scenario.js +104 -0
  46. package/test-server-logs.js +98 -0
  47. package/test-session-id.js +91 -0
  48. package/test-simple-cdp-groups.js +44 -0
  49. package/test-simple-context.js +137 -0
  50. package/test-tab-group-simple.js +58 -0
  51. package/test-tab-grouping.js +48 -0
  52. package/test-wait-for-page.js +95 -0
  53. package/test-with-logs.js +118 -0
  54. package/test-ws-groups.js +59 -0
  55. package/tests/e2e-auto-test.js +304 -0
  56. package/tests/iframe-test-page.html +89 -0
  57. package/tests/test-douyin-iframe.js +171 -0
  58. package/tests/test-iframe-debug.js +204 -0
@@ -0,0 +1,52 @@
1
+ // 测试标签分组功能的脚本
2
+ // 复制到Chrome扩展的控制台中运行
3
+
4
+ // 1. 测试查询现有标签组
5
+ console.log('Testing tab group functionality...');
6
+ chrome.tabGroups.query({ title: 'lo' }, function(groups) {
7
+ console.log('Found tab groups:', groups);
8
+
9
+ // 2. 测试创建新标签
10
+ chrome.tabs.create({ url: 'https://www.baidu.com/', active: false }, function(tab) {
11
+ console.log('Created tab:', tab);
12
+
13
+ if (groups.length > 0) {
14
+ // 添加到现有组
15
+ console.log('Adding tab to existing group...');
16
+ chrome.tabs.group({ tabIds: tab.id, groupId: groups[0].id }, function(groupId) {
17
+ console.log('Added tab to group:', groupId);
18
+ });
19
+ } else {
20
+ // 创建新组并添加标签页
21
+ console.log('Creating new group and adding tab...');
22
+ chrome.tabs.group({ tabIds: tab.id }, function(groupId) {
23
+ console.log('Created group:', groupId);
24
+ // 更新组的标题和颜色
25
+ if (groupId) {
26
+ chrome.tabGroups.update(groupId, {
27
+ title: 'lo',
28
+ color: 'blue'
29
+ }, function(group) {
30
+ console.log('Updated group:', group);
31
+ });
32
+ }
33
+ });
34
+ }
35
+ });
36
+ });
37
+
38
+ // 3. 测试现有标签的分组
39
+ chrome.tabs.query({ active: true }, function(tabs) {
40
+ if (tabs.length > 0) {
41
+ console.log('Current active tab:', tabs[0]);
42
+
43
+ chrome.tabGroups.query({ title: 'lo' }, function(groups) {
44
+ if (groups.length > 0) {
45
+ console.log('Adding current tab to group...');
46
+ chrome.tabs.group({ tabIds: tabs[0].id, groupId: groups[0].id }, function(groupId) {
47
+ console.log('Added current tab to group:', groupId);
48
+ });
49
+ }
50
+ });
51
+ }
52
+ });
@@ -167,14 +167,19 @@ String.prototype.hashCode = function() {
167
167
  var openerTabId = tab.openerTabId;
168
168
  var isOpenerControlled = openerTabId && State.isTabAttached(openerTabId);
169
169
 
170
- // 修改逻辑:只要有 opener,就尝试处理(不管 opener 是否被控制)
171
- // 因为 Playwright 创建的页面 opener 可能没有被扩展识别
170
+ // 只有当 opener CDP 控制时才跟踪新页面
171
+ // 这样可以避免跟踪用户手动点击链接打开的页面
172
172
  if (!openerTabId) {
173
173
  Logger.info('[Tabs] Tab has no opener, skipping. tabId:', tabId);
174
174
  return;
175
175
  }
176
176
 
177
- Logger.info('[Tabs] Tab has opener:', openerTabId, ', controlled:', isOpenerControlled, ', will attach');
177
+ if (!isOpenerControlled) {
178
+ Logger.info('[Tabs] Opener not controlled by CDP, skipping. tabId:', tabId, 'openerTabId:', openerTabId);
179
+ return;
180
+ }
181
+
182
+ Logger.info('[Tabs] Tab has controlled opener, will attach. tabId:', tabId, 'openerTabId:', openerTabId);
178
183
 
179
184
  LocalHandler.getTargetInfoById(String(tabId)).then(function(targetInfo) {
180
185
  Logger.info('[Tabs] getTargetInfoById result:', targetInfo ? targetInfo.targetId : 'null');
@@ -209,18 +214,26 @@ String.prototype.hashCode = function() {
209
214
 
210
215
  // 将标签页添加到CDP组(添加延迟等待)
211
216
  setTimeout(function() {
212
- // 获取当前CDP客户端列表
213
- var cdpClients = State.getCDPClients() || [];
217
+ // 获取openerTabId对应的clientId
218
+ var openerClientId = openerTabId ? State.getClientIdByTabId(openerTabId) : null;
214
219
  var groupName;
215
-
216
- // 如果有CDP客户端,使用第一个客户端的ID作为组名
217
- if (cdpClients.length > 0) {
218
- groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
220
+
221
+ // 如果有指定的clientId,使用该clientId作为组名
222
+ if (openerClientId) {
223
+ groupName = 'CDP-' + openerClientId.substring(0, 8);
224
+ Logger.info('[TabGroup] Using opener clientId for group name:', groupName, 'openerTabId:', openerTabId);
219
225
  } else {
220
- // 如果没有CDP客户端,使用时间戳作为组名
221
- groupName = 'CDP-' + Date.now().toString(36);
226
+ // 回退到使用第一个CDP客户端的ID
227
+ var cdpClients = State.getCDPClients() || [];
228
+ if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
229
+ groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
230
+ } else {
231
+ // 如果没有CDP客户端,使用时间戳作为组名
232
+ groupName = 'CDP-' + Date.now().toString(36);
233
+ }
234
+ Logger.info('[TabGroup] Using fallback clientId for group name:', groupName);
222
235
  }
223
-
236
+
224
237
  chrome.tabGroups.query({ title: groupName }, function(groups) {
225
238
  if (groups.length > 0) {
226
239
  // 找到现有的组,将标签页添加到组
@@ -18,6 +18,7 @@ var SpecialHandler = (function() {
18
18
  function targetAttachToTarget(context) {
19
19
  var params = context.params;
20
20
  var targetId = params && params.targetId;
21
+ var clientId = context.clientId;
21
22
  if (!targetId) {
22
23
  return Promise.resolve({});
23
24
  }
@@ -28,7 +29,7 @@ var SpecialHandler = (function() {
28
29
  }
29
30
 
30
31
  var isAlreadyAttached = State.isTabAttached(tabId);
31
-
32
+
32
33
  if (isAlreadyAttached) {
33
34
  var newSessionId = CDPUtils.generateSessionId();
34
35
  State.mapSession(newSessionId, tabId, targetId);
@@ -43,9 +44,14 @@ var SpecialHandler = (function() {
43
44
 
44
45
  var sessionId = CDPUtils.generateSessionId();
45
46
  State.mapSession(sessionId, tabId, targetId);
46
-
47
- addTabToAutomationGroup(tabId);
48
-
47
+
48
+ // 保存tabId到clientId的映射
49
+ if (clientId) {
50
+ State.setTabIdToClientId(tabId, clientId);
51
+ }
52
+
53
+ addTabToAutomationGroup(tabId, clientId);
54
+
49
55
  return { sessionId: sessionId };
50
56
  });
51
57
  });
@@ -75,6 +81,7 @@ var SpecialHandler = (function() {
75
81
  var params = context.params;
76
82
  var url = (params && params.url) || 'about:blank';
77
83
  var browserContextId = (params && params.browserContextId) || 'default';
84
+ var clientId = context.clientId;
78
85
 
79
86
  return new Promise(function(resolve, reject) {
80
87
  State.addPendingCreatedTabUrl(url);
@@ -85,10 +92,16 @@ var SpecialHandler = (function() {
85
92
  reject(new Error('Failed to create tab'));
86
93
  return;
87
94
  }
88
-
89
- // 将标签页添加到CDP Automation组
90
- addTabToAutomationGroup(tab.id);
91
-
95
+
96
+ // 保存tabId到clientId的映射,用于后续分组
97
+ if (clientId) {
98
+ State.setTabIdToClientId(tab.id, clientId);
99
+ Logger.info('[TabGroup] Mapped tabId:', tab.id, '-> clientId:', clientId);
100
+ }
101
+
102
+ // 将标签页添加到CDP Automation组,使用对应的clientId
103
+ addTabToAutomationGroup(tab.id, clientId);
104
+
92
105
  var targetId = String(tab.id);
93
106
  State.addEmittedTarget(targetId);
94
107
  getTargetIdByTabId(tab.id).then(function(targetId) {
@@ -99,22 +112,27 @@ var SpecialHandler = (function() {
99
112
  });
100
113
  });
101
114
  }
102
-
103
- function addTabToAutomationGroup(tabId) {
104
- Logger.info('[TabGroup] Starting addTabToAutomationGroup for tabId:', tabId);
105
-
106
- // 获取当前CDP客户端列表
107
- var cdpClients = State.getCDPClients() || [];
115
+
116
+ function addTabToAutomationGroup(tabId, clientId) {
117
+ Logger.info('[TabGroup] Starting addTabToAutomationGroup for tabId:', tabId, 'clientId:', clientId);
118
+
108
119
  var groupName;
109
-
110
- // 如果有CDP客户端,使用第一个客户端的ID作为组名
111
- if (cdpClients.length > 0) {
112
- groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
113
- Logger.info('[TabGroup] Using CDP client ID for group name:', groupName);
120
+
121
+ // 如果有指定的clientId,使用该clientId作为组名
122
+ if (clientId) {
123
+ groupName = 'CDP-' + clientId.substring(0, 8);
124
+ Logger.info('[TabGroup] Using specific clientId for group name:', groupName);
114
125
  } else {
115
- // 如果没有CDP客户端,使用固定的组名
116
- groupName = 'CDP-Automation';
117
- Logger.info('[TabGroup] No CDP client, using default group name:', groupName);
126
+ // 如果没有clientId,回退到使用第一个CDP客户端的ID
127
+ var cdpClients = State.getCDPClients() || [];
128
+ if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
129
+ groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
130
+ Logger.info('[TabGroup] Using first CDP client ID for group name:', groupName);
131
+ } else {
132
+ // 如果没有CDP客户端,使用固定的组名
133
+ groupName = 'CDP-Automation';
134
+ Logger.info('[TabGroup] No CDP client, using default group name:', groupName);
135
+ }
118
136
  }
119
137
 
120
138
  // 添加延迟等待,确保标签页完全加载后再分组
@@ -57,20 +57,21 @@ function routeCDPCommand(message) {
57
57
  var method = message.method;
58
58
  var params = message.params;
59
59
  var sessionId = message.sessionId;
60
+ var clientId = message.clientId;
60
61
 
61
- console.log('[CDP] routeCDPCommand id=' + id + ' (type: ' + typeof id + ') method=' + method);
62
+ console.log('[CDP] routeCDPCommand id=' + id + ' (type: ' + typeof id + ') method=' + method + ' clientId=' + (clientId || 'none'));
62
63
 
63
64
  var route = CDP_HANDLERS[method];
64
65
  var logType = route ? route.type : 'FORWARD';
65
- Logger.info('[CDP] RECV id=' + id + ' method=' + method + ' type=' + logType + ' sessionId=' + (sessionId || 'null'));
66
+ Logger.info('[CDP] RECV id=' + id + ' method=' + method + ' type=' + logType + ' sessionId=' + (sessionId || 'null') + ' clientId=' + (clientId || 'null'));
66
67
 
67
68
  return new Promise(function(resolve) {
68
69
  if (route) {
69
- Promise.resolve(route.handler({ id: id, method: method, params: params, sessionId: sessionId }))
70
+ Promise.resolve(route.handler({ id: id, method: method, params: params, sessionId: sessionId, clientId: clientId }))
70
71
  .then(function(result) {
71
72
  if (result === null && route.type === 'SPECIAL') {
72
73
  Logger.info('[CDP] SPECIAL null -> FORWARD id=' + id + ' method=' + method);
73
- return ForwardHandler.execute({ id: id, method: method, params: params, sessionId: sessionId });
74
+ return ForwardHandler.execute({ id: id, method: method, params: params, sessionId: sessionId, clientId: clientId });
74
75
  }
75
76
  return result;
76
77
  })
@@ -83,7 +84,7 @@ function routeCDPCommand(message) {
83
84
  resolve({ error: { message: error.message } });
84
85
  });
85
86
  } else {
86
- ForwardHandler.execute({ id: id, method: method, params: params, sessionId: sessionId })
87
+ ForwardHandler.execute({ id: id, method: method, params: params, sessionId: sessionId, clientId: clientId })
87
88
  .then(function(result) {
88
89
  Logger.info('[CDP] SEND id=' + id + ' method=' + method + ' hasError=false (forwarded)');
89
90
  resolve({ result: result });
@@ -4,15 +4,25 @@ var DebuggerManager = (function() {
4
4
  if (window.__internalUrlBlockInjected) return;
5
5
  window.__internalUrlBlockInjected = true;
6
6
 
7
- var blockedProtocols = ['bitbrowser:', 'chrome:', 'edge:', 'chrome-extension:'];
7
+ var blockedProtocols = ['bitbrowser:', 'chrome:', 'edge:', 'chrome-extension:', 'bytedance:', 'sslocal:', 'alipays:', 'weixin:', 'mqq:', 'taobao:', 'tmall:'];
8
8
 
9
9
  function isInternalUrl(url) {
10
10
  if (!url) return false;
11
+ // Whitelist: only allow http/https/about/data/blob/file
12
+ if (url.startsWith('http:') || url.startsWith('https:') || url.startsWith('about:') || url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('file:')) {
13
+ return false;
14
+ }
15
+ // Also check explicit blocked list for common custom protocols
11
16
  for (var i = 0; i < blockedProtocols.length; i++) {
12
17
  if (url.startsWith(blockedProtocols[i])) {
13
18
  return true;
14
19
  }
15
20
  }
21
+ // Block any other custom protocol (xxx://)
22
+ var colonIdx = url.indexOf(':');
23
+ if (colonIdx > 0 && colonIdx < 20 && url.substring(colonIdx, colonIdx + 3) === '://') {
24
+ return true;
25
+ }
16
26
  return false;
17
27
  }
18
28
 
@@ -232,10 +242,7 @@ var DebuggerManager = (function() {
232
242
  Logger.warn(' Disposition:', disposition);
233
243
  Logger.warn(' FrameId:', frameId);
234
244
 
235
- if (url.startsWith('chrome://') ||
236
- url.startsWith('bitbrowser://') ||
237
- url.startsWith('edge://') ||
238
- url.startsWith('chrome-extension://')) {
245
+ if (url && !url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('about:') && !url.startsWith('data:') && !url.startsWith('blob:') && !url.startsWith('file://')) {
239
246
  Logger.error('[NAVIGATION] ⚠️ 检测到导航到内部页面,尝试阻止!');
240
247
  Logger.error('[NAVIGATION] 目标URL:', url);
241
248
 
@@ -22,7 +22,8 @@ var State = (function() {
22
22
  isAttached: false,
23
23
  pendingCreatedTabUrls: new Set(),
24
24
  clientIdToTabId: new Map(),
25
- clientIdToSessionId: new Map()
25
+ clientIdToSessionId: new Map(),
26
+ tabIdToClientId: new Map()
26
27
  };
27
28
 
28
29
  function mapSession(sessionId, tabId, targetId) {
@@ -282,6 +283,14 @@ var State = (function() {
282
283
  }
283
284
  }
284
285
 
286
+ function setTabIdToClientId(tabId, clientId) {
287
+ _state.tabIdToClientId.set(tabId, clientId);
288
+ }
289
+
290
+ function getClientIdByTabId(tabId) {
291
+ return _state.tabIdToClientId.get(tabId);
292
+ }
293
+
285
294
  function getTabIdByClientId(clientId) {
286
295
  return _state.clientIdToTabId.get(clientId);
287
296
  }
@@ -379,6 +388,8 @@ var State = (function() {
379
388
  getAutomatedTabs: getAutomatedTabs,
380
389
  clearSessionState: clearSessionState,
381
390
  clearAllState: clearAllState,
382
- cleanupAllTabs: cleanupAllTabs
391
+ cleanupAllTabs: cleanupAllTabs,
392
+ setTabIdToClientId: setTabIdToClientId,
393
+ getClientIdByTabId: getClientIdByTabId
383
394
  };
384
395
  })();
@@ -223,7 +223,8 @@ var WebSocketManager = (function() {
223
223
  method: method,
224
224
  params: params,
225
225
  tabId: tabId,
226
- sessionId: sessionId
226
+ sessionId: sessionId,
227
+ clientId: message.__clientId
227
228
  });
228
229
  }
229
230
  break;
@@ -235,7 +236,8 @@ var WebSocketManager = (function() {
235
236
  method: method,
236
237
  params: params,
237
238
  tabId: tabId,
238
- sessionId: sessionId
239
+ sessionId: sessionId,
240
+ clientId: message.__clientId
239
241
  });
240
242
  }
241
243
  }
@@ -0,0 +1,105 @@
1
+ // 完整的标签分组测试脚本
2
+ // 复制到Chrome扩展的控制台中运行
3
+
4
+ console.log('=== 开始测试标签分组功能 ===');
5
+
6
+ // 1. 测试1:创建新标签并添加到lo组
7
+ function test1_CreateTabAndAddToGroup() {
8
+ console.log('\n=== 测试1:创建新标签并添加到lo组 ===');
9
+
10
+ // 先查询是否存在lo组
11
+ chrome.tabGroups.query({ title: 'lo' }, function(groups) {
12
+ console.log('找到的标签组:', groups);
13
+
14
+ // 创建新标签页
15
+ chrome.tabs.create({ url: 'https://www.baidu.com/', active: false }, function(tab) {
16
+ console.log('创建的标签页:', tab);
17
+
18
+ if (groups.length > 0) {
19
+ // 如果存在lo组,直接添加
20
+ console.log('将标签页添加到现有lo组');
21
+ chrome.tabs.group({ tabIds: tab.id, groupId: groups[0].id }, function(groupId) {
22
+ console.log('标签页已添加到组:', groupId);
23
+ test2_AddExistingTabToGroup();
24
+ });
25
+ } else {
26
+ // 如果不存在lo组,创建新组
27
+ console.log('创建新的lo组并添加标签页');
28
+ chrome.tabs.group({ tabIds: tab.id }, function(groupId) {
29
+ console.log('创建的组ID:', groupId);
30
+ // 更新组的标题和颜色
31
+ chrome.tabGroups.update(groupId, {
32
+ title: 'lo',
33
+ color: 'blue'
34
+ }, function(group) {
35
+ console.log('更新后的组:', group);
36
+ test2_AddExistingTabToGroup();
37
+ });
38
+ });
39
+ }
40
+ });
41
+ });
42
+ }
43
+
44
+ // 2. 测试2:将现有标签添加到lo组
45
+ function test2_AddExistingTabToGroup() {
46
+ console.log('\n=== 测试2:将现有标签添加到lo组 ===');
47
+
48
+ // 获取当前活动标签
49
+ chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
50
+ if (tabs.length > 0) {
51
+ var activeTab = tabs[0];
52
+ console.log('当前活动标签:', activeTab);
53
+
54
+ // 查询lo组
55
+ chrome.tabGroups.query({ title: 'lo' }, function(groups) {
56
+ if (groups.length > 0) {
57
+ console.log('将当前标签添加到lo组');
58
+ chrome.tabs.group({ tabIds: activeTab.id, groupId: groups[0].id }, function(groupId) {
59
+ console.log('当前标签已添加到组:', groupId);
60
+ test3_VerifyGroupMembers();
61
+ });
62
+ } else {
63
+ console.log('未找到lo组');
64
+ test3_VerifyGroupMembers();
65
+ }
66
+ });
67
+ } else {
68
+ console.log('未找到活动标签');
69
+ test3_VerifyGroupMembers();
70
+ }
71
+ });
72
+ }
73
+
74
+ // 3. 测试3:验证lo组的成员
75
+ function test3_VerifyGroupMembers() {
76
+ console.log('\n=== 测试3:验证lo组的成员 ===');
77
+
78
+ // 查询lo组
79
+ chrome.tabGroups.query({ title: 'lo' }, function(groups) {
80
+ if (groups.length > 0) {
81
+ var loGroup = groups[0];
82
+ console.log('lo组信息:', loGroup);
83
+
84
+ // 查询组内的标签
85
+ chrome.tabs.query({ groupId: loGroup.id }, function(tabs) {
86
+ console.log('lo组内的标签:', tabs);
87
+ console.log('lo组内标签数量:', tabs.length);
88
+
89
+ // 打印每个标签的信息
90
+ tabs.forEach(function(tab, index) {
91
+ console.log('标签', index + 1, ':', tab.title, '-', tab.url);
92
+ });
93
+
94
+ console.log('\n=== 测试完成 ===');
95
+ console.log('请检查Chrome浏览器中的标签组是否正确创建,并且标签页是否被正确添加到组中。');
96
+ });
97
+ } else {
98
+ console.log('未找到lo组');
99
+ console.log('\n=== 测试完成 ===');
100
+ }
101
+ });
102
+ }
103
+
104
+ // 开始执行测试
105
+ test1_CreateTabAndAddToGroup();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Chrome Extension CDP Proxy - 通过 Chrome 扩展将 chrome.debugger API 暴露为 WebSocket 端点",
5
5
  "main": "server/proxy-server.js",
6
6
  "bin": "./cli/index.js",
@@ -1,5 +1,5 @@
1
1
  const CONFIG = {
2
- PORT: 9221,
2
+ PORT: process.env.PORT ? parseInt(process.env.PORT) : 9221,
3
3
  HEARTBEAT_INTERVAL: 30000,
4
4
  STATUS_PRINT_INTERVAL: 60000,
5
5
  TARGETS_CACHE_TTL: 2000,
@@ -137,7 +137,7 @@ function logCDP(direction, message, sessionId = null, pluginType = null) {
137
137
  return;
138
138
  }
139
139
  const timestamp = new Date().toISOString();
140
- const sessionPrefix = sessionId ? `[session:${sessionId.substring(0, 8)}]` : '';
140
+ const sessionPrefix = sessionId && typeof sessionId === 'string' ? `[session:${sessionId.substring(0, 8)}]` : '';
141
141
  const typePrefix = pluginType ? `[${pluginType}]` : '';
142
142
  const logLine = `[${timestamp}] ${typePrefix}${sessionPrefix}[${direction}] ${truncatedMessage}\n`;
143
143
 
@@ -362,7 +362,7 @@ function handlePluginConnection(ws, clientInfo) {
362
362
  const openerClientId = targetIdToClientId.get(openerId);
363
363
  if (openerClientId) {
364
364
  targetIdToClientId.set(targetId, openerClientId);
365
- console.log(`[TARGET CREATED with opener] targetId=${targetId.substring(0,8)} openerId=${openerId.substring(0,8)} -> clientId=${openerClientId}`);
365
+ console.log(`[TARGET CREATED with opener] targetId=${targetId?.substring(0,8) || 'none'} openerId=${openerId?.substring(0,8) || 'none'} -> clientId=${openerClientId}`);
366
366
  }
367
367
  }
368
368
  }
@@ -381,7 +381,7 @@ function handlePluginConnection(ws, clientInfo) {
381
381
 
382
382
  if (targetId && sessionId) {
383
383
  sessionToClientId.set(sessionId, targetId);
384
- console.log(`[SESSION MAPPED] sessionId=${sessionId.substring(0,8)} -> targetId=${targetId.substring(0,8)}`);
384
+ console.log(`[SESSION MAPPED] sessionId=${sessionId?.substring(0,8) || 'none'} -> targetId=${targetId?.substring(0,8) || 'none'}`);
385
385
  }
386
386
  }
387
387
 
@@ -443,8 +443,10 @@ function handlePluginConnection(ws, clientInfo) {
443
443
  // 检查是否有缓存的 Target.attachedToTarget 事件
444
444
  const cachedEvent = pendingAttachedEvents.get(targetId);
445
445
  if (cachedEvent) {
446
- sessionToClientId.set(cachedEvent.sessionId, mapping.clientId);
447
- console.log(`[SESSION MAPPED from cached] sessionId=${cachedEvent.sessionId.substring(0,8)} -> clientId=${mapping.clientId} (targetId=${targetId})`);
446
+ if (cachedEvent.sessionId) {
447
+ sessionToClientId.set(cachedEvent.sessionId, mapping.clientId);
448
+ }
449
+ console.log(`[SESSION MAPPED from cached] sessionId=${cachedEvent.sessionId?.substring(0,8) || 'none'} -> clientId=${mapping.clientId} (targetId=${targetId})`);
448
450
  pendingAttachedEvents.delete(targetId);
449
451
 
450
452
  // 先发送缓存的事件给客户端
@@ -483,7 +485,7 @@ function handlePluginConnection(ws, clientInfo) {
483
485
  // 2. sessionId 路由:消息属于特定 session(事件,没有 id)
484
486
  if (parsed && parsed.sessionId) {
485
487
  const targetClientId = sessionToClientId.get(parsed.sessionId);
486
- console.log(`[SESSION ROUTE] sessionId=${parsed.sessionId?.substring(0,8)} -> clientId=${targetClientId || 'not found'}`);
488
+ console.log(`[SESSION ROUTE] sessionId=${parsed.sessionId?.substring(0,8) || 'none'} -> clientId=${targetClientId || 'not found'}`);
487
489
  if (targetClientId) {
488
490
  const clientWs = clientById.get(targetClientId);
489
491
  if (clientWs && clientWs.readyState === WebSocket.OPEN) {
@@ -491,7 +493,7 @@ function handlePluginConnection(ws, clientInfo) {
491
493
  logCDP('DEBUG', `FORWARDED to client: ${targetClientId} (sessionId route)`, parsed?.sessionId);
492
494
  }
493
495
  } else {
494
- console.log(`[WARN] No clientId for sessionId: ${parsed.sessionId?.substring(0, 8)}`);
496
+ console.log(`[WARN] No clientId for sessionId: ${parsed.sessionId?.substring(0, 8) || 'none'}`);
495
497
  }
496
498
  return;
497
499
  }
@@ -787,8 +789,10 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
787
789
 
788
790
  // 发送给配对的 plugin (或广播)
789
791
  if (ws.pairedPlugin && ws.pairedPlugin.readyState === WebSocket.OPEN) {
790
- console.log(`[SEND TO PLUGIN] id=${parsed?.id} method=${parsed?.method} sessionId=${parsed?.sessionId?.substring(0,8) || 'none'}`);
791
- ws.pairedPlugin.send(modifiedData);
792
+ console.log(`[SEND TO PLUGIN] id=${parsed?.id} method=${parsed?.method} sessionId=${parsed?.sessionId?.substring(0,8) || 'none'} clientId=${id}`);
793
+ // 在消息中附加 clientId 信息
794
+ const pluginMsg = { ...parsed, __clientId: id };
795
+ ws.pairedPlugin.send(JSON.stringify(pluginMsg));
792
796
  } else {
793
797
  broadcastToPlugins(modifiedData, ws);
794
798
  }
@@ -1045,7 +1049,7 @@ function rewriteBrowserContextId(cdpMsg) {
1045
1049
  if (clientId) {
1046
1050
  const contextId = clientIdToBrowserContext.get(clientId);
1047
1051
  if (contextId) {
1048
- console.log(`[CONTEXT REWRITE] targetId=${targetInfo.targetId?.substring(0,8)} browserContextId: 'default' -> '${contextId}' (via openerId=${targetInfo.openerId?.substring(0,8) || 'none'}, clientId=${clientId})`);
1052
+ console.log(`[CONTEXT REWRITE] targetId=${targetInfo.targetId?.substring(0,8) || 'none'} browserContextId: 'default' -> '${contextId}' (via openerId=${targetInfo.openerId?.substring(0,8) || 'none'}, clientId=${clientId})`);
1049
1053
  targetInfo.browserContextId = contextId;
1050
1054
  }
1051
1055
  }
@@ -0,0 +1,56 @@
1
+ // 最简单的标签组测试脚本
2
+ // 复制到Chrome扩展的控制台中运行
3
+
4
+ console.log('=== 最简单的标签组测试 ===');
5
+
6
+ // 步骤1: 检查API是否可用
7
+ console.log('步骤1: 检查API是否可用');
8
+ console.log('chrome.tabs.group:', typeof chrome.tabs.group);
9
+ console.log('chrome.tabGroups:', chrome.tabGroups);
10
+ console.log('chrome.tabGroups.query:', typeof chrome.tabGroups.query);
11
+ console.log('chrome.tabGroups.update:', typeof chrome.tabGroups.update);
12
+
13
+ // 步骤2: 创建一个标签页
14
+ console.log('\n步骤2: 创建一个标签页');
15
+ chrome.tabs.create({ url: 'https://www.baidu.com/', active: false }, function(tab) {
16
+ console.log('标签页创建成功:', tab.id);
17
+
18
+ // 步骤3: 等待5秒
19
+ console.log('\n步骤3: 等待5秒...');
20
+ setTimeout(function() {
21
+ console.log('等待完成,开始创建标签组...');
22
+
23
+ // 步骤4: 创建标签组
24
+ console.log('\n步骤4: 创建标签组');
25
+ chrome.tabs.group({ tabIds: tab.id }, function(groupId) {
26
+ if (chrome.runtime.lastError) {
27
+ console.error('创建标签组失败:', chrome.runtime.lastError.message);
28
+ return;
29
+ }
30
+
31
+ console.log('标签组创建成功,组ID:', groupId);
32
+
33
+ // 步骤5: 更新标签组
34
+ console.log('\n步骤5: 更新标签组');
35
+ chrome.tabGroups.update(groupId, {
36
+ title: 'Test',
37
+ color: 'blue'
38
+ }, function(group) {
39
+ if (chrome.runtime.lastError) {
40
+ console.error('更新标签组失败:', chrome.runtime.lastError.message);
41
+ return;
42
+ }
43
+
44
+ console.log('标签组更新成功:', group);
45
+
46
+ // 步骤6: 查询所有标签组
47
+ console.log('\n步骤6: 查询所有标签组');
48
+ chrome.tabGroups.query({}, function(groups) {
49
+ console.log('所有标签组:', groups);
50
+ console.log('\n=== 测试完成 ===');
51
+ console.log('请检查Chrome浏览器中是否有一个名为"Test"的蓝色标签组');
52
+ });
53
+ });
54
+ });
55
+ }, 5000);
56
+ });