cdp-tunnel 2.7.5 → 2.7.7
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/extension-new/cdp/handler/local.js +1 -3
- package/extension-new/cdp/handler/special.js +48 -13
- package/extension-new/core/debugger.js +5 -3
- package/extension-new/core/state.js +17 -8
- package/extension-new/core/websocket.js +65 -14
- package/extension-new/manifest.json +1 -1
- package/extension-new/utils/helpers.js +8 -2
- package/package.json +5 -1
- package/server/proxy-server.js +31 -3
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
var SpecialHandler = (function() {
|
|
2
|
+
var _groupQueue = new Map();
|
|
3
|
+
|
|
2
4
|
function muteTabIfNeeded(tabId) {
|
|
3
5
|
Config.getAutoMute(function(enabled) {
|
|
4
6
|
if (!enabled) return;
|
|
@@ -65,8 +67,7 @@ var SpecialHandler = (function() {
|
|
|
65
67
|
|
|
66
68
|
if (State.isCDPCreatedTab(tabId)) {
|
|
67
69
|
addTabToAutomationGroup(tabId, clientId);
|
|
68
|
-
} else {
|
|
69
|
-
State.addPreExistingTab(tabId);
|
|
70
|
+
} else { State.addPreExistingTab(tabId);
|
|
70
71
|
Logger.info('[CDP] Target.attachToTarget: user tab not CDP-created, treating as pre-existing. tabId:', tabId);
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -141,12 +142,9 @@ var SpecialHandler = (function() {
|
|
|
141
142
|
|
|
142
143
|
function groupTabSilently(tabId, clientId) {
|
|
143
144
|
return new Promise(function(resolve) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
Logger.error('[TabGroup] addTabToAutomationGroup threw:', e.message || e);
|
|
148
|
-
}
|
|
149
|
-
setTimeout(resolve, 100);
|
|
145
|
+
addTabToAutomationGroup(tabId, clientId, function(success) {
|
|
146
|
+
setTimeout(resolve, 50);
|
|
147
|
+
});
|
|
150
148
|
});
|
|
151
149
|
}
|
|
152
150
|
|
|
@@ -162,7 +160,34 @@ var SpecialHandler = (function() {
|
|
|
162
160
|
});
|
|
163
161
|
}
|
|
164
162
|
|
|
165
|
-
function addTabToAutomationGroup(tabId, clientId) {
|
|
163
|
+
function addTabToAutomationGroup(tabId, clientId, callback) {
|
|
164
|
+
var key = clientId || '__no_client__';
|
|
165
|
+
var prev = _groupQueue.get(key) || Promise.resolve();
|
|
166
|
+
|
|
167
|
+
var next = prev.then(function() {
|
|
168
|
+
return new Promise(function(resolve) {
|
|
169
|
+
_addTabToAutomationGroupInner(tabId, clientId, function(success) {
|
|
170
|
+
resolve(success);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}).catch(function(err) {
|
|
174
|
+
Logger.error('[addTabToAutomationGroup] queue error:', err.message || err);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
_groupQueue.set(key, next);
|
|
178
|
+
|
|
179
|
+
next.finally(function() {
|
|
180
|
+
if (_groupQueue.get(key) === next) {
|
|
181
|
+
_groupQueue.delete(key);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (callback) {
|
|
186
|
+
next.then(function(success) { callback(success); });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function _addTabToAutomationGroupInner(tabId, clientId, callback) {
|
|
166
191
|
Logger.info('[TabGroup] Starting addTabToAutomationGroup for tabId:', tabId, 'clientId:', clientId);
|
|
167
192
|
|
|
168
193
|
WebSocketManager.send({ type: 'tabgroup-debug', tabId: tabId, clientId: clientId, phase: 'start' });
|
|
@@ -183,21 +208,23 @@ var SpecialHandler = (function() {
|
|
|
183
208
|
Logger.warn('[TabGroup] No clientId for tab:', tabId, 'fallback to first client:', groupClientId);
|
|
184
209
|
} else {
|
|
185
210
|
Logger.warn('[TabGroup] No clientId for tab:', tabId, '— skipping group operation');
|
|
211
|
+
if (callback) callback(false);
|
|
186
212
|
return;
|
|
187
213
|
}
|
|
188
214
|
}
|
|
189
215
|
var baseName = CDPUtils.getGroupBaseName(groupClientId);
|
|
190
216
|
|
|
191
217
|
Logger.info('[TabGroup] Grouping tab immediately for:', baseName);
|
|
192
|
-
doGroup(tabId, groupClientId, baseName);
|
|
218
|
+
doGroup(tabId, groupClientId, baseName, 0, callback);
|
|
193
219
|
}
|
|
194
220
|
|
|
195
|
-
function doGroup(tabId, clientId, baseName, retries) {
|
|
221
|
+
function doGroup(tabId, clientId, baseName, retries, callback) {
|
|
196
222
|
retries = retries || 0;
|
|
197
223
|
Logger.info('[TabGroup] doGroup: tabId=' + tabId + ' clientId=' + (clientId || 'none') + ' baseName=' + baseName + ' retry=' + retries);
|
|
198
224
|
if (!chrome.tabGroups) {
|
|
199
225
|
Logger.warn('[TabGroup] chrome.tabGroups API not available (headless mode?), skipping grouping for tab:', tabId);
|
|
200
226
|
EventBuilder.send('CDPTunnel.debug', { source: 'doGroup', phase: 'skip', reason: 'tabGroups-unavailable', tabId: tabId });
|
|
227
|
+
if (callback) callback(false);
|
|
201
228
|
return;
|
|
202
229
|
}
|
|
203
230
|
chrome.tabGroups.query({}, function(allGroups) {
|
|
@@ -214,12 +241,15 @@ var SpecialHandler = (function() {
|
|
|
214
241
|
Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message, 'retries:', retries);
|
|
215
242
|
EventBuilder.send('CDPTunnel.debug', { source: 'doGroup', phase: 'addToExisting', error: chrome.runtime.lastError.message, tabId: tabId, groupId: existing.id });
|
|
216
243
|
if (retries < 3) {
|
|
217
|
-
setTimeout(function() { doGroup(tabId, clientId, baseName, retries + 1); }, 500);
|
|
244
|
+
setTimeout(function() { doGroup(tabId, clientId, baseName, retries + 1, callback); }, 500);
|
|
245
|
+
} else {
|
|
246
|
+
if (callback) callback(false);
|
|
218
247
|
}
|
|
219
248
|
} else {
|
|
220
249
|
State.setGroupIdForClient(clientId, existing.id);
|
|
221
250
|
updateTabGroupName(clientId);
|
|
222
251
|
Logger.info('[TabGroup] Tab', tabId, 'added to existing group:', existing.id);
|
|
252
|
+
if (callback) callback(true);
|
|
223
253
|
}
|
|
224
254
|
});
|
|
225
255
|
} else {
|
|
@@ -229,7 +259,9 @@ var SpecialHandler = (function() {
|
|
|
229
259
|
Logger.error('[TabGroup] Failed to create group:', chrome.runtime.lastError.message, 'retries:', retries);
|
|
230
260
|
EventBuilder.send('CDPTunnel.debug', { source: 'doGroup', phase: 'createGroup', error: chrome.runtime.lastError.message, tabId: tabId });
|
|
231
261
|
if (retries < 3) {
|
|
232
|
-
setTimeout(function() { doGroup(tabId, clientId, baseName, retries + 1); }, 500);
|
|
262
|
+
setTimeout(function() { doGroup(tabId, clientId, baseName, retries + 1, callback); }, 500);
|
|
263
|
+
} else {
|
|
264
|
+
if (callback) callback(false);
|
|
233
265
|
}
|
|
234
266
|
return;
|
|
235
267
|
}
|
|
@@ -250,13 +282,16 @@ var SpecialHandler = (function() {
|
|
|
250
282
|
updateTabGroupName(clientId);
|
|
251
283
|
Logger.info('[TabGroup] Group updated:', groupId, baseName);
|
|
252
284
|
}
|
|
285
|
+
if (callback) callback(true);
|
|
253
286
|
});
|
|
254
287
|
} else {
|
|
255
288
|
State.setGroupIdForClient(clientId, groupId);
|
|
256
289
|
Logger.info('[TabGroup] Group created but tabGroups.update unavailable (headless):', groupId);
|
|
290
|
+
if (callback) callback(true);
|
|
257
291
|
}
|
|
258
292
|
} else {
|
|
259
293
|
Logger.error('[TabGroup] chrome.tabs.group returned null groupId');
|
|
294
|
+
if (callback) callback(false);
|
|
260
295
|
}
|
|
261
296
|
});
|
|
262
297
|
}
|
|
@@ -203,8 +203,8 @@ var DebuggerManager = (function() {
|
|
|
203
203
|
return;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
var
|
|
207
|
-
Logger.info('[Event] method=' + method + ' tabId=' + source.tabId + '
|
|
206
|
+
var sessionIds = State.findSessionsByTabId(source.tabId);
|
|
207
|
+
Logger.info('[Event] method=' + method + ' tabId=' + source.tabId + ' sessions=' + sessionIds.length);
|
|
208
208
|
|
|
209
209
|
if (method === 'Runtime.executionContextCreated' && params && params.context) {
|
|
210
210
|
var context = params.context;
|
|
@@ -285,7 +285,9 @@ var DebuggerManager = (function() {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
|
|
288
|
+
for (var i = 0; i < sessionIds.length; i++) {
|
|
289
|
+
EventBuilder.send(method, params, sessionIds[i]);
|
|
290
|
+
}
|
|
289
291
|
}
|
|
290
292
|
|
|
291
293
|
function handleDetach(source, reason) {
|
|
@@ -54,15 +54,23 @@ var State = (function() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function findSessionByTabId(tabId) {
|
|
57
|
-
var
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return entry.value[0];
|
|
57
|
+
var lastMatch = null;
|
|
58
|
+
_state.sessionIdToTabId.forEach(function(mappedTabId, sessionId) {
|
|
59
|
+
if (mappedTabId === tabId) {
|
|
60
|
+
lastMatch = sessionId;
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
});
|
|
63
|
+
return lastMatch;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function findSessionsByTabId(tabId) {
|
|
67
|
+
var sessions = [];
|
|
68
|
+
_state.sessionIdToTabId.forEach(function(mappedTabId, sessionId) {
|
|
69
|
+
if (mappedTabId === tabId) {
|
|
70
|
+
sessions.push(sessionId);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return sessions;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
function findSessionByTargetId(targetId) {
|
|
@@ -453,6 +461,7 @@ var State = (function() {
|
|
|
453
461
|
getTabIdBySession: getTabIdBySession,
|
|
454
462
|
getTargetIdBySession: getTargetIdBySession,
|
|
455
463
|
findSessionByTabId: findSessionByTabId,
|
|
464
|
+
findSessionsByTabId: findSessionsByTabId,
|
|
456
465
|
findSessionByTargetId: findSessionByTargetId,
|
|
457
466
|
getTabIdByTargetId: getTabIdByTargetId,
|
|
458
467
|
hasOtherSessionForTab: hasOtherSessionForTab,
|
|
@@ -281,22 +281,50 @@ var WebSocketManager = (function() {
|
|
|
281
281
|
Logger.info('[WS] Closing tab group for client:', clientId);
|
|
282
282
|
|
|
283
283
|
return new Promise(function(resolve) {
|
|
284
|
-
|
|
284
|
+
var timeoutId = setTimeout(function() {
|
|
285
|
+
Logger.warn('[WS] closeTabGroupByClientId timeout for client:', clientId, '— forcing cleanup');
|
|
286
|
+
cleanupStaleState(clientId);
|
|
287
|
+
resolve();
|
|
288
|
+
}, 5000);
|
|
285
289
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
290
|
+
var groupId = State.getGroupIdForClient(clientId);
|
|
291
|
+
|
|
292
|
+
if (groupId) {
|
|
293
|
+
closeGroupById(groupId, clientId, function() {
|
|
294
|
+
clearTimeout(timeoutId);
|
|
295
|
+
cleanupStaleState(clientId);
|
|
296
|
+
resolve();
|
|
297
|
+
});
|
|
298
|
+
} else {
|
|
299
|
+
var baseName = CDPUtils.getGroupBaseName(clientId);
|
|
300
|
+
chrome.tabGroups.query({}, function(allGroups) {
|
|
301
|
+
var match = CDPUtils.findGroupByName(allGroups, baseName);
|
|
302
|
+
if (match) {
|
|
303
|
+
closeGroupById(match.id, clientId, function() {
|
|
304
|
+
clearTimeout(timeoutId);
|
|
305
|
+
cleanupStaleState(clientId);
|
|
306
|
+
resolve();
|
|
298
307
|
});
|
|
299
|
-
|
|
308
|
+
} else {
|
|
309
|
+
Logger.info('[WS] No tab group found, closing tabs by clientId:', clientId);
|
|
310
|
+
closeTabsByClientId(clientId, function() {
|
|
311
|
+
clearTimeout(timeoutId);
|
|
312
|
+
cleanupStaleState(clientId);
|
|
313
|
+
resolve();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function cleanupStaleState(clientId) {
|
|
322
|
+
if (!clientId) return;
|
|
323
|
+
var attachedTabs = State.getAttachedTabIds();
|
|
324
|
+
attachedTabs.forEach(function(tabId) {
|
|
325
|
+
if (State.getClientIdByTabId(tabId) === clientId) {
|
|
326
|
+
State.removeTabIdToClientId(tabId);
|
|
327
|
+
}
|
|
300
328
|
});
|
|
301
329
|
}
|
|
302
330
|
|
|
@@ -306,6 +334,7 @@ var WebSocketManager = (function() {
|
|
|
306
334
|
if (!tabs || tabs.length === 0) {
|
|
307
335
|
Logger.info('[WS] No tabs in group:', groupId);
|
|
308
336
|
State.removeGroupForClient(clientId);
|
|
337
|
+
removeEmptyGroup(groupId);
|
|
309
338
|
resolve();
|
|
310
339
|
return;
|
|
311
340
|
}
|
|
@@ -338,11 +367,33 @@ var WebSocketManager = (function() {
|
|
|
338
367
|
});
|
|
339
368
|
|
|
340
369
|
State.removeGroupForClient(clientId);
|
|
370
|
+
removeEmptyGroup(groupId);
|
|
341
371
|
resolve();
|
|
342
372
|
});
|
|
343
373
|
});
|
|
344
374
|
}
|
|
345
375
|
|
|
376
|
+
function removeEmptyGroup(groupId) {
|
|
377
|
+
if (!groupId || !chrome.tabGroups) return;
|
|
378
|
+
setTimeout(function() {
|
|
379
|
+
chrome.tabGroups.query({ groupId: groupId }, function(groups) {
|
|
380
|
+
if (chrome.runtime.lastError) return;
|
|
381
|
+
if (groups && groups.length > 0) {
|
|
382
|
+
chrome.tabs.query({ groupId: groupId }, function(tabs) {
|
|
383
|
+
if (chrome.runtime.lastError) return;
|
|
384
|
+
if (!tabs || tabs.length === 0) {
|
|
385
|
+
chrome.tabGroups.remove(groupId, function() {
|
|
386
|
+
if (!chrome.runtime.lastError) {
|
|
387
|
+
Logger.info('[WS] Removed empty group:', groupId);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}, 500);
|
|
395
|
+
}
|
|
396
|
+
|
|
346
397
|
function closeTabsByClientId(clientId, resolve) {
|
|
347
398
|
var attachedTabs = State.getAttachedTabIds();
|
|
348
399
|
var cdpCreatedTabs = State.getCDPCreatedTabIds();
|
|
@@ -44,7 +44,13 @@ var CDPUtils = (function() {
|
|
|
44
44
|
|
|
45
45
|
function buildGroupName(clientId) {
|
|
46
46
|
if (!clientId) return 'CDP';
|
|
47
|
-
var
|
|
47
|
+
var hash = 0;
|
|
48
|
+
for (var i = 0; i < clientId.length; i++) {
|
|
49
|
+
var chr = clientId.charCodeAt(i);
|
|
50
|
+
hash = ((hash << 5) - hash) + chr;
|
|
51
|
+
hash = hash | 0;
|
|
52
|
+
}
|
|
53
|
+
var suffix = Math.abs(hash).toString(16).substring(0, 8).padStart(8, '0');
|
|
48
54
|
return 'CDP-' + suffix;
|
|
49
55
|
}
|
|
50
56
|
|
|
@@ -55,7 +61,7 @@ var CDPUtils = (function() {
|
|
|
55
61
|
function findGroupByName(allGroups, baseName) {
|
|
56
62
|
for (var i = 0; i < allGroups.length; i++) {
|
|
57
63
|
var g = allGroups[i];
|
|
58
|
-
if (g.title && g.title.indexOf(baseName) === 0) return g;
|
|
64
|
+
if (g.title === baseName || (g.title && g.title.indexOf(baseName + ' (') === 0)) return g;
|
|
59
65
|
}
|
|
60
66
|
return null;
|
|
61
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-tunnel",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.7",
|
|
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",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"benchmark:proxy": "node benchmark/run-all.js --proxy",
|
|
17
17
|
"demo": "node demo/server.js",
|
|
18
18
|
"test:compare": "node tests/cdp-compare/cdp-compare-runner.js",
|
|
19
|
+
"test": "node tests/e2e/run-all.js",
|
|
20
|
+
"test:e2e": "node tests/e2e/run-all.js",
|
|
21
|
+
"test:core": "SKIP_EXTENDED=1 node tests/e2e/run-all.js",
|
|
22
|
+
"test:smoke": "ONLY=test-default-page,test-strict-isolation,test-no-user-tab-grab node tests/e2e/run-all.js",
|
|
19
23
|
"prepare": "husky"
|
|
20
24
|
},
|
|
21
25
|
"keywords": [
|
package/server/proxy-server.js
CHANGED
|
@@ -487,11 +487,33 @@ function cleanupClient(ws, id, reason) {
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
if (ws.pairedPlugin) {
|
|
490
|
-
safeSend(ws.pairedPlugin, JSON.stringify({
|
|
490
|
+
const sendOk = safeSend(ws.pairedPlugin, JSON.stringify({
|
|
491
491
|
type: 'client-disconnected',
|
|
492
492
|
clientId: id,
|
|
493
493
|
sessions: []
|
|
494
494
|
}), 'plugin');
|
|
495
|
+
if (!sendOk) {
|
|
496
|
+
console.log(`[WARN] cleanupClient: failed to send client-disconnected for ${id} to plugin`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const pluginNs = getNamespace(ws.pairedPlugin);
|
|
500
|
+
if (pluginNs) {
|
|
501
|
+
const targetsToClose = [];
|
|
502
|
+
for (const [tId, cId] of pluginNs.targetIdToClientId.entries()) {
|
|
503
|
+
if (cId === id) {
|
|
504
|
+
targetsToClose.push(tId);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
targetsToClose.forEach(function(tId) {
|
|
508
|
+
const closeReq = JSON.stringify({
|
|
509
|
+
id: -1,
|
|
510
|
+
method: 'Target.closeTarget',
|
|
511
|
+
params: { targetId: tId },
|
|
512
|
+
__clientId: id
|
|
513
|
+
});
|
|
514
|
+
safeSend(ws.pairedPlugin, closeReq, 'plugin');
|
|
515
|
+
});
|
|
516
|
+
}
|
|
495
517
|
}
|
|
496
518
|
|
|
497
519
|
broadcastClientList();
|
|
@@ -1307,9 +1329,15 @@ function handleClientConnection(ws, clientInfo, customClientId = null, targetPlu
|
|
|
1307
1329
|
}
|
|
1308
1330
|
|
|
1309
1331
|
if (parsed && parsed.method === 'Browser.close') {
|
|
1310
|
-
|
|
1311
|
-
|
|
1332
|
+
console.log(`[BROWSER CLOSE] Client ${id} requested Browser.close — closing client group only`);
|
|
1333
|
+
if (ws.pairedPlugin) {
|
|
1334
|
+
safeSend(ws.pairedPlugin, JSON.stringify({
|
|
1335
|
+
type: 'browser-close',
|
|
1336
|
+
clientId: id
|
|
1337
|
+
}), 'plugin');
|
|
1312
1338
|
}
|
|
1339
|
+
safeSend(ws, JSON.stringify({ id: originalId, result: {} }), 'client');
|
|
1340
|
+
return;
|
|
1313
1341
|
}
|
|
1314
1342
|
|
|
1315
1343
|
if (parsed && parsed.method) {
|