cdp-tunnel 1.0.13 → 1.0.15
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.
- package/cli/index.js +65 -0
- package/extension-new/background.js +6 -1
- package/extension-new/cdp/handler/special.js +47 -27
- package/extension-new/core/state.js +64 -6
- package/extension-new/core/websocket.js +124 -19
- package/package.json +9 -2
- package/server/proxy-server.js +45 -34
- package/.github/workflows/publish.yml +0 -92
- package/.github/workflows/release-assets.yml +0 -50
- package/PUBLISH.md +0 -65
- package/console-test.js +0 -52
- package/docs/README_CN.md +0 -204
- package/docs/config-page-screenshot.png +0 -0
- package/final-console-test.js +0 -105
- package/simple-tab-group-test.js +0 -56
- package/test-cdp-connection.js +0 -85
- package/test-cdp-groups.js +0 -71
- package/test-check-newtab.js +0 -144
- package/test-chrome-native.js +0 -140
- package/test-client-connected.js +0 -99
- package/test-compare-formats.js +0 -88
- package/test-context-features.js +0 -113
- package/test-create-tab.js +0 -113
- package/test-debug-broadcast.js +0 -52
- package/test-debug-targets.js +0 -127
- package/test-expose-newtab.js +0 -164
- package/test-expose-shared.js +0 -189
- package/test-final-logs.js +0 -110
- package/test-fresh-chromium.js +0 -153
- package/test-init-script.js +0 -128
- package/test-keepalive.js +0 -89
- package/test-launch-chromium.js +0 -140
- package/test-launch-vs-connect.js +0 -149
- package/test-listen-events.js +0 -102
- package/test-monitor.js +0 -83
- package/test-multiple-cdp-groups.js +0 -78
- package/test-native.js +0 -96
- package/test-page-connection.js +0 -74
- package/test-playwright-connection.js +0 -45
- package/test-playwright-groups.js +0 -47
- package/test-playwright-pages.js +0 -47
- package/test-playwright-sequence.js +0 -81
- package/test-proper-context.js +0 -129
- package/test-real-final.js +0 -251
- package/test-real-scenario-v2.js +0 -166
- package/test-real-scenario-v3.js +0 -231
- package/test-real-scenario.js +0 -104
- package/test-server-logs.js +0 -98
- package/test-session-id.js +0 -91
- package/test-simple-cdp-groups.js +0 -44
- package/test-simple-context.js +0 -137
- package/test-tab-group-simple.js +0 -58
- package/test-tab-grouping.js +0 -48
- package/test-three-pages.js +0 -192
- package/test-wait-for-page.js +0 -95
- package/test-with-logs.js +0 -118
- package/test-ws-groups.js +0 -59
- package/tests/e2e-auto-test.js +0 -304
- package/tests/iframe-test-page.html +0 -89
- package/tests/playwright-demo.js +0 -45
- package/tests/playwright-interactive.js +0 -261
- package/tests/playwright-multi-demo.js +0 -60
- package/tests/playwright-multi.js +0 -85
- package/tests/playwright-single.js +0 -41
- package/tests/screenshot-config.js +0 -35
- package/tests/test-client.js +0 -89
- package/tests/test-douyin-iframe.js +0 -171
- package/tests/test-iframe-debug.js +0 -204
- package/tests/test-multi-client.js +0 -129
package/cli/index.js
CHANGED
|
@@ -275,6 +275,71 @@ program
|
|
|
275
275
|
}
|
|
276
276
|
});
|
|
277
277
|
|
|
278
|
+
program
|
|
279
|
+
.command('update')
|
|
280
|
+
.description('自动更新 cdp-tunnel 并重启服务')
|
|
281
|
+
.option('-p, --port <port>', '重启时指定端口', parseInt)
|
|
282
|
+
.option('-w, --watchdog', '重启时启用看门狗')
|
|
283
|
+
.action(async (options) => {
|
|
284
|
+
const config = getConfig();
|
|
285
|
+
const wasRunning = isServerRunning();
|
|
286
|
+
const savedPort = options.port || config.port;
|
|
287
|
+
const savedWatchdog = options.watchdog;
|
|
288
|
+
|
|
289
|
+
console.log('');
|
|
290
|
+
log('cyan', '⬆ 正在检查更新...');
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const beforeVersion = execSync('npm view cdp-tunnel version', { encoding: 'utf8' }).trim();
|
|
294
|
+
const localVersion = require(path.join(__dirname, '..', 'package.json')).version;
|
|
295
|
+
log('gray', ' 当前版本: ' + localVersion);
|
|
296
|
+
log('gray', ' 最新版本: ' + beforeVersion);
|
|
297
|
+
|
|
298
|
+
if (wasRunning) {
|
|
299
|
+
log('yellow', ' 正在停止服务器...');
|
|
300
|
+
try {
|
|
301
|
+
const pid = getServerPid();
|
|
302
|
+
process.kill(pid, 'SIGTERM');
|
|
303
|
+
fs.unlinkSync(PID_FILE);
|
|
304
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
305
|
+
log('green', ' ✓ 服务器已停止');
|
|
306
|
+
} catch (e) {
|
|
307
|
+
log('yellow', ' ⚠ 停止服务器失败: ' + e.message);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
log('cyan', ' 正在更新...');
|
|
312
|
+
execSync('npm update -g cdp-tunnel', { stdio: 'inherit' });
|
|
313
|
+
|
|
314
|
+
const afterVersion = require(path.join(__dirname, '..', 'package.json')).version;
|
|
315
|
+
if (afterVersion !== localVersion) {
|
|
316
|
+
log('green', ' ✓ 已更新: ' + localVersion + ' → ' + afterVersion);
|
|
317
|
+
} else {
|
|
318
|
+
log('green', ' ✓ 已是最新版本');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
ensureConfigDir();
|
|
322
|
+
cleanupLogFile();
|
|
323
|
+
startServer(savedPort, savedWatchdog);
|
|
324
|
+
log('green', ' ✓ 服务器已重启 (端口: ' + savedPort + ')');
|
|
325
|
+
console.log('');
|
|
326
|
+
} catch (e) {
|
|
327
|
+
log('red', '✗ 更新失败: ' + e.message);
|
|
328
|
+
console.log('');
|
|
329
|
+
if (wasRunning) {
|
|
330
|
+
log('yellow', '正在尝试恢复服务器...');
|
|
331
|
+
try {
|
|
332
|
+
ensureConfigDir();
|
|
333
|
+
startServer(savedPort, savedWatchdog);
|
|
334
|
+
log('green', '✓ 服务器已恢复');
|
|
335
|
+
} catch (re) {
|
|
336
|
+
log('red', '✗ 恢复失败,请手动启动: cdp-tunnel start');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
278
343
|
program
|
|
279
344
|
.command('status')
|
|
280
345
|
.description('查看服务器状态')
|
|
@@ -122,6 +122,7 @@ String.prototype.hashCode = function() {
|
|
|
122
122
|
Logger.info('[Tabs] Tab removed:', tabId);
|
|
123
123
|
|
|
124
124
|
State.removeAttachedTab(tabId);
|
|
125
|
+
var removedClientId = State.getClientIdByTabId(tabId);
|
|
125
126
|
Screencast.stopPolling(tabId);
|
|
126
127
|
AutomationBadge.remove(tabId);
|
|
127
128
|
|
|
@@ -134,6 +135,9 @@ String.prototype.hashCode = function() {
|
|
|
134
135
|
targetId: targetId
|
|
135
136
|
});
|
|
136
137
|
State.unmapSession(sessionId);
|
|
138
|
+
if (removedClientId) {
|
|
139
|
+
SpecialHandler.updateTabGroupName(removedClientId);
|
|
140
|
+
}
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
if (State.getCurrentTabId() === tabId) {
|
|
@@ -260,7 +264,8 @@ String.prototype.hashCode = function() {
|
|
|
260
264
|
|
|
261
265
|
chrome.tabGroups.update(groupId, {
|
|
262
266
|
title: groupName,
|
|
263
|
-
color: groupColor
|
|
267
|
+
color: groupColor,
|
|
268
|
+
collapsed: true
|
|
264
269
|
}, function(group) {
|
|
265
270
|
if (chrome.runtime.lastError) {
|
|
266
271
|
Logger.error('[TabGroup] Failed to update group:', chrome.runtime.lastError.message);
|
|
@@ -117,74 +117,88 @@ var SpecialHandler = (function() {
|
|
|
117
117
|
Logger.info('[TabGroup] Starting addTabToAutomationGroup for tabId:', tabId, 'clientId:', clientId);
|
|
118
118
|
|
|
119
119
|
var groupName;
|
|
120
|
+
var groupClientId = clientId;
|
|
120
121
|
|
|
121
|
-
// 如果有指定的clientId,使用该clientId作为组名
|
|
122
122
|
if (clientId) {
|
|
123
123
|
groupName = 'CDP-' + clientId.substring(0, 8);
|
|
124
|
-
Logger.info('[TabGroup] Using specific clientId for group name:', groupName);
|
|
125
124
|
} else {
|
|
126
|
-
// 如果没有clientId,回退到使用第一个CDP客户端的ID
|
|
127
125
|
var cdpClients = State.getCDPClients() || [];
|
|
128
126
|
if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
|
|
129
127
|
groupName = 'CDP-' + cdpClients[0].id.substring(0, 8);
|
|
130
|
-
|
|
128
|
+
groupClientId = cdpClients[0].id;
|
|
131
129
|
} else {
|
|
132
|
-
// 如果没有CDP客户端,使用固定的组名
|
|
133
130
|
groupName = 'CDP-Automation';
|
|
134
|
-
Logger.info('[TabGroup] No CDP client, using default group name:', groupName);
|
|
135
131
|
}
|
|
136
132
|
}
|
|
137
133
|
|
|
138
|
-
// 添加延迟等待,确保标签页完全加载后再分组
|
|
139
134
|
setTimeout(function() {
|
|
140
135
|
Logger.info('[TabGroup] Executing group operation after delay...');
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
137
|
+
chrome.tabGroups.query({}, function(allGroups) {
|
|
138
|
+
var groups = allGroups.filter(function(g) {
|
|
139
|
+
return g.title && g.title.indexOf(groupName) === 0;
|
|
140
|
+
});
|
|
146
141
|
if (groups.length > 0) {
|
|
147
|
-
|
|
148
|
-
Logger.info('[TabGroup] Adding tab to existing group:', groups[0].id);
|
|
149
|
-
chrome.tabs.group({ tabIds: tabId, groupId: groups[0].id }, function(groupId) {
|
|
142
|
+
chrome.tabs.group({ tabIds: tabId, groupId: groups[0].id }, function(result) {
|
|
150
143
|
if (chrome.runtime.lastError) {
|
|
151
144
|
Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message);
|
|
152
145
|
} else {
|
|
153
|
-
|
|
146
|
+
State.setGroupIdForClient(groupClientId, groups[0].id);
|
|
147
|
+
updateTabGroupName(groupClientId);
|
|
154
148
|
}
|
|
155
149
|
});
|
|
156
150
|
} else {
|
|
157
|
-
// 创建新组并添加标签页
|
|
158
|
-
Logger.info('[TabGroup] Creating new group...');
|
|
159
151
|
chrome.tabs.group({ tabIds: tabId }, function(groupId) {
|
|
160
152
|
if (chrome.runtime.lastError) {
|
|
161
153
|
Logger.error('[TabGroup] Failed to create group:', chrome.runtime.lastError.message);
|
|
162
154
|
return;
|
|
163
155
|
}
|
|
164
|
-
// 更新组的标题和颜色
|
|
165
156
|
if (groupId) {
|
|
166
|
-
Logger.info('[TabGroup] Group created with ID:', groupId);
|
|
167
|
-
// 为不同的组使用不同的颜色
|
|
168
157
|
var colors = ['blue', 'red', 'yellow', 'green', 'pink', 'purple', 'cyan', 'orange'];
|
|
169
158
|
var colorIndex = Math.abs(groupName.hashCode ? groupName.hashCode() : 0) % colors.length;
|
|
170
|
-
var groupColor = colors[colorIndex];
|
|
171
159
|
|
|
172
|
-
Logger.info('[TabGroup] Updating group with title:', groupName, 'color:', groupColor);
|
|
173
160
|
chrome.tabGroups.update(groupId, {
|
|
174
161
|
title: groupName,
|
|
175
|
-
color:
|
|
176
|
-
|
|
162
|
+
color: colors[colorIndex],
|
|
163
|
+
collapsed: true
|
|
164
|
+
}, function() {
|
|
177
165
|
if (chrome.runtime.lastError) {
|
|
178
166
|
Logger.error('[TabGroup] Failed to update group:', chrome.runtime.lastError.message);
|
|
179
167
|
} else {
|
|
180
|
-
|
|
168
|
+
State.setGroupIdForClient(groupClientId, groupId);
|
|
169
|
+
updateTabGroupName(groupClientId);
|
|
181
170
|
}
|
|
182
171
|
});
|
|
183
172
|
}
|
|
184
173
|
});
|
|
185
174
|
}
|
|
186
175
|
});
|
|
187
|
-
}, 2000);
|
|
176
|
+
}, 2000);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function updateTabGroupName(clientId) {
|
|
180
|
+
if (!clientId) return;
|
|
181
|
+
|
|
182
|
+
var groupId = State.getGroupIdForClient(clientId);
|
|
183
|
+
if (!groupId) return;
|
|
184
|
+
|
|
185
|
+
chrome.tabs.query({ groupId: groupId }, function(tabs) {
|
|
186
|
+
if (chrome.runtime.lastError || !tabs) return;
|
|
187
|
+
|
|
188
|
+
var count = tabs.length;
|
|
189
|
+
var baseName = 'CDP-' + clientId.substring(0, 8);
|
|
190
|
+
var newName = baseName + ' (' + count + ')';
|
|
191
|
+
|
|
192
|
+
chrome.tabGroups.update(groupId, {
|
|
193
|
+
title: newName
|
|
194
|
+
}, function() {
|
|
195
|
+
if (chrome.runtime.lastError) {
|
|
196
|
+
Logger.error('[TabGroup] Failed to update group name:', chrome.runtime.lastError.message);
|
|
197
|
+
} else {
|
|
198
|
+
Logger.info('[TabGroup] Updated group name:', newName);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
188
202
|
}
|
|
189
203
|
|
|
190
204
|
// 为字符串添加hashCode方法(用于生成颜色索引)
|
|
@@ -219,8 +233,13 @@ var SpecialHandler = (function() {
|
|
|
219
233
|
if (targetId) {
|
|
220
234
|
var tabId = State.getTabIdByTargetId(targetId);
|
|
221
235
|
if (tabId) {
|
|
236
|
+
State.removeAttachedTab(tabId);
|
|
237
|
+
var closeClientId = State.getClientIdByTabId(tabId);
|
|
222
238
|
return new Promise(function(resolve) {
|
|
223
239
|
chrome.tabs.remove(tabId, function() {
|
|
240
|
+
if (closeClientId) {
|
|
241
|
+
updateTabGroupName(closeClientId);
|
|
242
|
+
}
|
|
224
243
|
resolve({ success: true });
|
|
225
244
|
});
|
|
226
245
|
}).catch(function() {
|
|
@@ -540,6 +559,7 @@ function checkTabVisibility(tabId) {
|
|
|
540
559
|
pageCreateIsolatedWorld: pageCreateIsolatedWorld,
|
|
541
560
|
pageAddScriptToEvaluateOnNewDocument: pageAddScriptToEvaluateOnNewDocument,
|
|
542
561
|
runtimeRunIfWaitingForDebugger: runtimeRunIfWaitingForDebugger,
|
|
543
|
-
domSetFileInputFiles: domSetFileInputFiles
|
|
562
|
+
domSetFileInputFiles: domSetFileInputFiles,
|
|
563
|
+
updateTabGroupName: updateTabGroupName
|
|
544
564
|
};
|
|
545
565
|
})();
|
|
@@ -23,7 +23,8 @@ var State = (function() {
|
|
|
23
23
|
pendingCreatedTabUrls: new Set(),
|
|
24
24
|
clientIdToTabId: new Map(),
|
|
25
25
|
clientIdToSessionId: new Map(),
|
|
26
|
-
tabIdToClientId: new Map()
|
|
26
|
+
tabIdToClientId: new Map(),
|
|
27
|
+
clientIdToGroupId: new Map()
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
function mapSession(sessionId, tabId, targetId) {
|
|
@@ -261,17 +262,59 @@ var State = (function() {
|
|
|
261
262
|
};
|
|
262
263
|
_state.discoverTargetsEnabled = false;
|
|
263
264
|
_state.hasConnectedClient = false;
|
|
265
|
+
_state.tabIdToClientId.clear();
|
|
266
|
+
_state.clientIdToGroupId.clear();
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
function cleanupAllTabs() {
|
|
267
270
|
return new Promise(function(resolve) {
|
|
268
271
|
var tabIds = Array.from(_state.attachedTabIds);
|
|
269
|
-
|
|
272
|
+
|
|
273
|
+
var detachPromises = tabIds.map(function(tabId) {
|
|
270
274
|
return chrome.debugger.detach({ tabId: tabId }).catch(function() {});
|
|
271
275
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
|
|
277
|
+
Promise.all(detachPromises).then(function() {
|
|
278
|
+
chrome.tabGroups.query({}, function(groups) {
|
|
279
|
+
if (!groups || groups.length === 0) {
|
|
280
|
+
clearAllState();
|
|
281
|
+
persist(null, false).then(resolve);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
var cdpGroups = groups.filter(function(g) {
|
|
286
|
+
return g.title && g.title.indexOf('CDP-') === 0;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (cdpGroups.length === 0) {
|
|
290
|
+
clearAllState();
|
|
291
|
+
persist(null, false).then(resolve);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
var closePromises = cdpGroups.map(function(group) {
|
|
296
|
+
return new Promise(function(res) {
|
|
297
|
+
chrome.tabs.query({ groupId: group.id }, function(tabs) {
|
|
298
|
+
if (!tabs || tabs.length === 0) {
|
|
299
|
+
res();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
var ids = tabs.map(function(t) { return t.id; });
|
|
303
|
+
chrome.tabs.remove(ids, function() {
|
|
304
|
+
if (chrome.runtime.lastError) {
|
|
305
|
+
console.error('[State] Failed to close tabs:', chrome.runtime.lastError.message);
|
|
306
|
+
}
|
|
307
|
+
res();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
Promise.all(closePromises).then(function() {
|
|
314
|
+
clearAllState();
|
|
315
|
+
persist(null, false).then(resolve);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
275
318
|
});
|
|
276
319
|
});
|
|
277
320
|
}
|
|
@@ -291,6 +334,18 @@ var State = (function() {
|
|
|
291
334
|
return _state.tabIdToClientId.get(tabId);
|
|
292
335
|
}
|
|
293
336
|
|
|
337
|
+
function setGroupIdForClient(clientId, groupId) {
|
|
338
|
+
_state.clientIdToGroupId.set(clientId, groupId);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function getGroupIdForClient(clientId) {
|
|
342
|
+
return _state.clientIdToGroupId.get(clientId);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function removeGroupForClient(clientId) {
|
|
346
|
+
_state.clientIdToGroupId.delete(clientId);
|
|
347
|
+
}
|
|
348
|
+
|
|
294
349
|
function getTabIdByClientId(clientId) {
|
|
295
350
|
return _state.clientIdToTabId.get(clientId);
|
|
296
351
|
}
|
|
@@ -390,6 +445,9 @@ var State = (function() {
|
|
|
390
445
|
clearAllState: clearAllState,
|
|
391
446
|
cleanupAllTabs: cleanupAllTabs,
|
|
392
447
|
setTabIdToClientId: setTabIdToClientId,
|
|
393
|
-
getClientIdByTabId: getClientIdByTabId
|
|
448
|
+
getClientIdByTabId: getClientIdByTabId,
|
|
449
|
+
setGroupIdForClient: setGroupIdForClient,
|
|
450
|
+
getGroupIdForClient: getGroupIdForClient,
|
|
451
|
+
removeGroupForClient: removeGroupForClient
|
|
394
452
|
};
|
|
395
453
|
})();
|
|
@@ -181,7 +181,7 @@ var WebSocketManager = (function() {
|
|
|
181
181
|
break;
|
|
182
182
|
|
|
183
183
|
case 'browser-close':
|
|
184
|
-
handleBrowserClose(message.sessions);
|
|
184
|
+
handleBrowserClose(message.sessions, message.clientId);
|
|
185
185
|
break;
|
|
186
186
|
|
|
187
187
|
case 'client-connected':
|
|
@@ -193,11 +193,18 @@ var WebSocketManager = (function() {
|
|
|
193
193
|
|
|
194
194
|
case 'client-disconnected':
|
|
195
195
|
Logger.info('[WS] Client disconnected:', message.clientId);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
var discClientId = message.clientId;
|
|
197
|
+
closeTabGroupByClientId(discClientId).then(function() {
|
|
198
|
+
return new Promise(function(resolve) {
|
|
199
|
+
closeTabsByClientId(discClientId, resolve);
|
|
200
|
+
});
|
|
201
|
+
}).then(function() {
|
|
202
|
+
State.removeCDPClient(discClientId);
|
|
203
|
+
if (State.getCDPClients().length === 0) {
|
|
204
|
+
State.setHasConnectedClient(false);
|
|
205
|
+
}
|
|
206
|
+
broadcastStateUpdate();
|
|
207
|
+
});
|
|
201
208
|
break;
|
|
202
209
|
|
|
203
210
|
case 'client-list':
|
|
@@ -243,6 +250,103 @@ var WebSocketManager = (function() {
|
|
|
243
250
|
}
|
|
244
251
|
}
|
|
245
252
|
|
|
253
|
+
function closeTabGroupByClientId(clientId) {
|
|
254
|
+
if (!clientId) return Promise.resolve();
|
|
255
|
+
|
|
256
|
+
Logger.info('[WS] Closing tab group for client:', clientId);
|
|
257
|
+
|
|
258
|
+
return new Promise(function(resolve) {
|
|
259
|
+
var groupId = State.getGroupIdForClient(clientId);
|
|
260
|
+
|
|
261
|
+
if (groupId) {
|
|
262
|
+
closeGroupById(groupId, clientId, resolve);
|
|
263
|
+
} else {
|
|
264
|
+
var groupName = 'CDP-' + clientId.substring(0, 8);
|
|
265
|
+
chrome.tabGroups.query({ title: groupName }, function(groups) {
|
|
266
|
+
if (!groups || groups.length === 0) {
|
|
267
|
+
chrome.tabGroups.query({}, function(allGroups) {
|
|
268
|
+
if (allGroups) {
|
|
269
|
+
var match = allGroups.find(function(g) {
|
|
270
|
+
return g.title && g.title.indexOf(groupName) === 0;
|
|
271
|
+
});
|
|
272
|
+
if (match) {
|
|
273
|
+
closeGroupById(match.id, clientId, resolve);
|
|
274
|
+
} else {
|
|
275
|
+
Logger.info('[WS] No tab group found, closing tabs by clientId:', clientId);
|
|
276
|
+
closeTabsByClientId(clientId, resolve);
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
resolve();
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
closeGroupById(groups[0].id, clientId, resolve);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function closeGroupById(groupId, clientId, resolve) {
|
|
291
|
+
chrome.tabs.query({ groupId: groupId }, function(tabs) {
|
|
292
|
+
if (!tabs || tabs.length === 0) {
|
|
293
|
+
Logger.info('[WS] No tabs in group:', groupId);
|
|
294
|
+
State.removeGroupForClient(clientId);
|
|
295
|
+
resolve();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
var tabIds = tabs.map(function(tab) { return tab.id; });
|
|
300
|
+
Logger.info('[WS] Closing ' + tabIds.length + ' tabs in group:', groupId);
|
|
301
|
+
|
|
302
|
+
chrome.tabs.remove(tabIds, function() {
|
|
303
|
+
if (chrome.runtime.lastError) {
|
|
304
|
+
Logger.error('[WS] Failed to close tabs:', chrome.runtime.lastError.message);
|
|
305
|
+
} else {
|
|
306
|
+
Logger.info('[WS] Successfully closed ' + tabIds.length + ' tabs');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
tabIds.forEach(function(tabId) {
|
|
310
|
+
chrome.debugger.detach({ tabId: tabId }).catch(function() {});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
State.removeGroupForClient(clientId);
|
|
314
|
+
resolve();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function closeTabsByClientId(clientId, resolve) {
|
|
320
|
+
var attachedTabs = State.getAttachedTabIds();
|
|
321
|
+
var tabsToClose = [];
|
|
322
|
+
|
|
323
|
+
attachedTabs.forEach(function(tabId) {
|
|
324
|
+
if (State.getClientIdByTabId(tabId) === clientId) {
|
|
325
|
+
tabsToClose.push(tabId);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (tabsToClose.length === 0) {
|
|
330
|
+
Logger.info('[WS] No attached tabs found for clientId:', clientId);
|
|
331
|
+
resolve();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
Logger.info('[WS] Closing ' + tabsToClose.length + ' attached tabs for clientId:', clientId);
|
|
336
|
+
|
|
337
|
+
tabsToClose.forEach(function(tabId) {
|
|
338
|
+
chrome.tabs.remove(tabId, function() {
|
|
339
|
+
if (chrome.runtime.lastError) {
|
|
340
|
+
Logger.info('[WS] Tab already closed:', tabId);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
chrome.debugger.detach({ tabId: tabId }).catch(function() {});
|
|
344
|
+
State.removeAttachedTab(tabId);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
resolve();
|
|
348
|
+
}
|
|
349
|
+
|
|
246
350
|
function handleServerRestart() {
|
|
247
351
|
Logger.info('[WS] Server restarted, cleaning up all state...');
|
|
248
352
|
|
|
@@ -260,20 +364,21 @@ var WebSocketManager = (function() {
|
|
|
260
364
|
});
|
|
261
365
|
}
|
|
262
366
|
|
|
263
|
-
function handleBrowserClose(sessions) {
|
|
264
|
-
Logger.info('[WS] Browser.close received, cleaning up...');
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
367
|
+
function handleBrowserClose(sessions, clientId) {
|
|
368
|
+
Logger.info('[WS] Browser.close received, cleaning up... clientId:', clientId);
|
|
369
|
+
|
|
370
|
+
closeTabGroupByClientId(clientId).then(function() {
|
|
371
|
+
var attachedTabIds = State.getAttachedTabIds();
|
|
372
|
+
var promises = attachedTabIds.map(function(tabId) {
|
|
373
|
+
return chrome.debugger.detach({ tabId: tabId }).catch(function(e) {
|
|
374
|
+
Logger.info('[WS] Detach failed for tab', tabId, ':', e.message);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
Promise.all(promises).then(function() {
|
|
378
|
+
State.clearAllState();
|
|
379
|
+
State.persist(null, false);
|
|
380
|
+
Logger.info('[WS] Browser.close cleanup complete');
|
|
270
381
|
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
Promise.all(promises).then(function() {
|
|
274
|
-
State.clearAllState();
|
|
275
|
-
State.persist(null, false);
|
|
276
|
-
Logger.info('[WS] Browser.close cleanup complete');
|
|
277
382
|
});
|
|
278
383
|
}
|
|
279
384
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-tunnel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
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",
|
|
@@ -25,14 +25,21 @@
|
|
|
25
25
|
],
|
|
26
26
|
"author": "dyyz1993",
|
|
27
27
|
"license": "Apache-2.0",
|
|
28
|
+
"files": [
|
|
29
|
+
"server/",
|
|
30
|
+
"cli/",
|
|
31
|
+
"extension-new/",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
28
35
|
"dependencies": {
|
|
29
36
|
"commander": "^12.0.0",
|
|
30
|
-
"playwright": "^1.58.2",
|
|
31
37
|
"ws": "^8.16.0"
|
|
32
38
|
},
|
|
33
39
|
"devDependencies": {
|
|
34
40
|
"lz-string": "^1.5.0",
|
|
35
41
|
"pako": "^2.1.0",
|
|
42
|
+
"playwright": "^1.58.2",
|
|
36
43
|
"playwright-core": "^1.58.2",
|
|
37
44
|
"puppeteer-core": "^22.0.0",
|
|
38
45
|
"sharp": "^0.34.0"
|
package/server/proxy-server.js
CHANGED
|
@@ -369,9 +369,20 @@ function handlePluginConnection(ws, clientInfo) {
|
|
|
369
369
|
|
|
370
370
|
rewriteBrowserContextId(cdpMsg);
|
|
371
371
|
const cdpData = JSON.stringify(cdpMsg);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
372
|
+
|
|
373
|
+
const targetId = parsed.params?.targetInfo?.targetId;
|
|
374
|
+
const eventClientId = targetId ? targetIdToClientId.get(targetId) : null;
|
|
375
|
+
|
|
376
|
+
if (eventClientId) {
|
|
377
|
+
const clientWs = clientById.get(eventClientId);
|
|
378
|
+
if (clientWs && clientWs.readyState === WebSocket.OPEN) {
|
|
379
|
+
clientWs.send(cdpData);
|
|
380
|
+
console.log(`[TARGET EVENT ROUTED] ${parsed.method} targetId=${targetId?.substring(0,8)} -> clientId=${eventClientId}`);
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
console.log(`[TARGET EVENT BROADCAST] ${parsed.method} targetId=${targetId?.substring(0,8) || 'none'} (no owner, broadcasting)`);
|
|
384
|
+
broadcastToClients(cdpData, null);
|
|
385
|
+
}
|
|
375
386
|
}
|
|
376
387
|
|
|
377
388
|
// 对于 Target.attachedToTarget 事件,建立 sessionId -> clientId 映射
|
|
@@ -462,6 +473,23 @@ function handlePluginConnection(ws, clientInfo) {
|
|
|
462
473
|
console.log(`[ATTACHED EVENT] Sent cached event to client: ${mapping.clientId}`);
|
|
463
474
|
}
|
|
464
475
|
}
|
|
476
|
+
// 过滤 Target.getTargets 响应,只返回该客户端拥有的 target
|
|
477
|
+
if (mapping.isGetTargets && parsed.result && parsed.result.targetInfos) {
|
|
478
|
+
const clientId = mapping.clientId;
|
|
479
|
+
parsed.result.targetInfos = parsed.result.targetInfos.filter(t => {
|
|
480
|
+
if (t.type !== 'page') return true;
|
|
481
|
+
const ownerClient = targetIdToClientId.get(t.targetId);
|
|
482
|
+
return !ownerClient || ownerClient === clientId;
|
|
483
|
+
});
|
|
484
|
+
console.log(`[GET TARGETS FILTERED] client=${clientId} returned ${parsed.result.targetInfos.filter(t => t.type === 'page').length} page targets`);
|
|
485
|
+
}
|
|
486
|
+
// 清理 Target.closeTarget 成功后的映射
|
|
487
|
+
if (parsed.result && parsed.result.success !== undefined && mapping.method === 'Target.closeTarget') {
|
|
488
|
+
if (mapping.closeTargetId) {
|
|
489
|
+
targetIdToClientId.delete(mapping.closeTargetId);
|
|
490
|
+
console.log(`[CLOSE TARGET CLEANUP] removed targetId=${mapping.closeTargetId?.substring(0,8)} from mapping`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
465
493
|
|
|
466
494
|
// 然后发送响应给客户端
|
|
467
495
|
const originalId = mapping.originalId;
|
|
@@ -731,6 +759,13 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
|
|
|
731
759
|
}
|
|
732
760
|
console.log(`[PENDING CREATE TARGET] Request id=${parsed.id} from client=${id}`);
|
|
733
761
|
}
|
|
762
|
+
if (parsed && parsed.method === 'Target.closeTarget' && parsed.id !== undefined) {
|
|
763
|
+
const currentMapping = globalRequestIdMap.get(parsed.id);
|
|
764
|
+
if (currentMapping) {
|
|
765
|
+
currentMapping.method = 'Target.closeTarget';
|
|
766
|
+
currentMapping.closeTargetId = parsed.params?.targetId;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
734
769
|
|
|
735
770
|
// 记录 Target.createBrowserContext 请求,用于后续建立 browserContextId -> clientId 映射
|
|
736
771
|
if (parsed && parsed.method === 'Target.createBrowserContext' && parsed.id !== undefined) {
|
|
@@ -740,41 +775,17 @@ function handleClientConnection(ws, clientInfo, customClientId = null) {
|
|
|
740
775
|
}
|
|
741
776
|
console.log(`[PENDING CREATE CONTEXT] Request id=${parsed.id} from client=${id}`);
|
|
742
777
|
}
|
|
778
|
+
if (parsed && parsed.method === 'Target.getTargets' && parsed.id !== undefined) {
|
|
779
|
+
const currentMapping = globalRequestIdMap.get(parsed.id);
|
|
780
|
+
if (currentMapping) {
|
|
781
|
+
currentMapping.isGetTargets = true;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
743
784
|
|
|
744
|
-
// 拦截 Browser.close - 清理会话状态
|
|
745
785
|
if (parsed && parsed.method === 'Browser.close') {
|
|
746
786
|
if (shouldLog('info')) {
|
|
747
|
-
console.log(`\n[BROWSER CLOSE] Client ${id} requested Browser.close`);
|
|
787
|
+
console.log(`\n[BROWSER CLOSE] Client ${id} requested Browser.close, forwarding to plugin`);
|
|
748
788
|
}
|
|
749
|
-
|
|
750
|
-
// 清理该客户端的所有 session 映射
|
|
751
|
-
const sessionsToClean = [];
|
|
752
|
-
for (const [sessionId, clientId] of sessionToClientId.entries()) {
|
|
753
|
-
if (clientId === id) {
|
|
754
|
-
sessionsToClean.push(sessionId);
|
|
755
|
-
sessionToClientId.delete(sessionId);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
if (shouldLog('info')) {
|
|
759
|
-
console.log(` - Cleaned ${sessionsToClean.length} sessions`);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// 通知扩展清理状态
|
|
763
|
-
if (ws.pairedPlugin && ws.pairedPlugin.readyState === WebSocket.OPEN) {
|
|
764
|
-
ws.pairedPlugin.send(JSON.stringify({
|
|
765
|
-
type: 'browser-close',
|
|
766
|
-
clientId: id,
|
|
767
|
-
sessions: sessionsToClean
|
|
768
|
-
}));
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// 返回 mock 响应(包含 sessionId)
|
|
772
|
-
const response = { id: parsed.id, result: {} };
|
|
773
|
-
if (parsed.sessionId) {
|
|
774
|
-
response.sessionId = parsed.sessionId;
|
|
775
|
-
}
|
|
776
|
-
ws.send(JSON.stringify(response));
|
|
777
|
-
return;
|
|
778
789
|
}
|
|
779
790
|
|
|
780
791
|
if (parsed && parsed.method) {
|