cdp-tunnel 2.5.11 → 2.5.13
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.
|
@@ -230,56 +230,7 @@ importScripts('features/automation-badge.js');
|
|
|
230
230
|
}
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
setTimeout(function() {
|
|
235
|
-
var openerClientId = openerTabId ? State.getClientIdByTabId(openerTabId) : null;
|
|
236
|
-
var groupClientId = openerClientId;
|
|
237
|
-
if (!groupClientId) {
|
|
238
|
-
var cdpClients = State.getCDPClients() || [];
|
|
239
|
-
if (cdpClients.length > 0 && cdpClients[0] && cdpClients[0].id) {
|
|
240
|
-
groupClientId = cdpClients[0].id;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!groupClientId) return;
|
|
245
|
-
var baseName = CDPUtils.getGroupBaseName(groupClientId);
|
|
246
|
-
Logger.info('[TabGroup] background onCreated, baseName:', baseName);
|
|
247
|
-
|
|
248
|
-
chrome.tabGroups.query({}, function(allGroups) {
|
|
249
|
-
var existing = CDPUtils.findGroupByName(allGroups, baseName);
|
|
250
|
-
if (existing) {
|
|
251
|
-
chrome.tabs.group({ tabIds: tabId, groupId: existing.id }, function(groupId) {
|
|
252
|
-
if (chrome.runtime.lastError) {
|
|
253
|
-
Logger.error('[TabGroup] Failed to add tab to group:', chrome.runtime.lastError.message);
|
|
254
|
-
} else {
|
|
255
|
-
State.setGroupIdForClient(groupClientId, existing.id);
|
|
256
|
-
Logger.info('[TabGroup] Tab added to group:', groupId);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
} else {
|
|
260
|
-
chrome.tabs.group({ tabIds: tabId }, function(groupId) {
|
|
261
|
-
if (chrome.runtime.lastError) {
|
|
262
|
-
Logger.error('[TabGroup] Failed to create group:', chrome.runtime.lastError.message);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
if (groupId) {
|
|
266
|
-
chrome.tabGroups.update(groupId, {
|
|
267
|
-
title: baseName,
|
|
268
|
-
color: CDPUtils.getGroupColorForClient(groupClientId),
|
|
269
|
-
collapsed: true
|
|
270
|
-
}, function(group) {
|
|
271
|
-
if (chrome.runtime.lastError) {
|
|
272
|
-
Logger.error('[TabGroup] Failed to update group:', chrome.runtime.lastError.message);
|
|
273
|
-
} else {
|
|
274
|
-
State.setGroupIdForClient(groupClientId, groupId);
|
|
275
|
-
Logger.info('[TabGroup] Group created:', group);
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}, 2000);
|
|
233
|
+
SpecialHandler.addTabToAutomationGroup(tabId, openerClientId);
|
|
283
234
|
|
|
284
235
|
Logger.info('[Tabs] Sending Target.attachedToTarget event');
|
|
285
236
|
|
|
@@ -135,17 +135,96 @@ var LocalHandler = (function() {
|
|
|
135
135
|
return {};
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function
|
|
138
|
+
function tabUngroup(context) {
|
|
139
139
|
var clientId = context.clientId;
|
|
140
140
|
var groupId = null;
|
|
141
|
-
var baseName = null;
|
|
142
141
|
try {
|
|
143
142
|
groupId = State.getGroupIdForClient(clientId);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
Logger.error('[TabUngroup] Error getting groupId: ' + (e.message || e));
|
|
145
|
+
return Promise.resolve({ success: false, ungroupedCount: 0, error: e.message || String(e) });
|
|
146
|
+
}
|
|
147
|
+
if (groupId == null) {
|
|
148
|
+
return Promise.resolve({ success: true, ungroupedCount: 0 });
|
|
149
|
+
}
|
|
150
|
+
return new Promise(function(resolve) {
|
|
151
|
+
chrome.tabs.query({ groupId: groupId }, function(tabs) {
|
|
152
|
+
if (chrome.runtime.lastError) {
|
|
153
|
+
Logger.error('[TabUngroup] chrome.runtime.lastError: ' + chrome.runtime.lastError.message);
|
|
154
|
+
resolve({ success: false, ungroupedCount: 0, error: chrome.runtime.lastError.message });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!tabs || tabs.length === 0) {
|
|
158
|
+
resolve({ success: true, ungroupedCount: 0 });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
var tabIds = tabs.map(function(tab) { return tab.id; });
|
|
162
|
+
chrome.tabs.ungroup(tabIds, function() {
|
|
163
|
+
if (chrome.runtime.lastError) {
|
|
164
|
+
Logger.error('[TabUngroup] ungroup lastError: ' + chrome.runtime.lastError.message);
|
|
165
|
+
resolve({ success: false, ungroupedCount: 0, error: chrome.runtime.lastError.message });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
State.removeGroupForClient(clientId);
|
|
169
|
+
resolve({ success: true, ungroupedCount: tabIds.length });
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function tabGetGroupInfo(context) {
|
|
176
|
+
var clientId = context.clientId;
|
|
177
|
+
var cachedGroupId = null;
|
|
178
|
+
var baseName = null;
|
|
179
|
+
try {
|
|
180
|
+
cachedGroupId = State.getGroupIdForClient(clientId);
|
|
144
181
|
baseName = CDPUtils.getGroupBaseName(clientId);
|
|
145
182
|
} catch (e) {
|
|
146
183
|
Logger.error('[TabGetGroupInfo] Error: ' + (e.message || e));
|
|
147
184
|
}
|
|
148
|
-
|
|
185
|
+
|
|
186
|
+
var attachedTabIds = State.getAttachedTabIds();
|
|
187
|
+
var matchedTabId = null;
|
|
188
|
+
for (var i = 0; i < attachedTabIds.length; i++) {
|
|
189
|
+
if (State.getClientIdByTabId(attachedTabIds[i]) === clientId) {
|
|
190
|
+
matchedTabId = attachedTabIds[i];
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (matchedTabId == null) {
|
|
196
|
+
return Promise.resolve({
|
|
197
|
+
groupId: -1,
|
|
198
|
+
cachedGroupId: cachedGroupId,
|
|
199
|
+
baseName: baseName,
|
|
200
|
+
clientId: clientId,
|
|
201
|
+
tabId: null
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
var tabId = matchedTabId;
|
|
206
|
+
return new Promise(function(resolve) {
|
|
207
|
+
chrome.tabs.get(tabId, function(tab) {
|
|
208
|
+
if (chrome.runtime.lastError) {
|
|
209
|
+
Logger.error('[TabGetGroupInfo] chrome.tabs.get error: ' + chrome.runtime.lastError.message);
|
|
210
|
+
resolve({
|
|
211
|
+
groupId: -1,
|
|
212
|
+
cachedGroupId: cachedGroupId,
|
|
213
|
+
baseName: baseName,
|
|
214
|
+
clientId: clientId,
|
|
215
|
+
tabId: tabId
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
resolve({
|
|
220
|
+
groupId: tab.groupId != null ? tab.groupId : -1,
|
|
221
|
+
cachedGroupId: cachedGroupId,
|
|
222
|
+
baseName: baseName,
|
|
223
|
+
clientId: clientId,
|
|
224
|
+
tabId: tabId
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
149
228
|
}
|
|
150
229
|
|
|
151
230
|
function tabGetMuteStatus(params) {
|
|
@@ -322,6 +401,7 @@ var LocalHandler = (function() {
|
|
|
322
401
|
getTargetInfoById: getTargetInfoById,
|
|
323
402
|
mapToTargetInfo: mapToTargetInfo,
|
|
324
403
|
tabGetMuteStatus: tabGetMuteStatus,
|
|
325
|
-
tabGetGroupInfo: tabGetGroupInfo
|
|
404
|
+
tabGetGroupInfo: tabGetGroupInfo,
|
|
405
|
+
tabUngroup: tabUngroup
|
|
326
406
|
};
|
|
327
407
|
})();
|
|
@@ -149,28 +149,8 @@ var SpecialHandler = (function() {
|
|
|
149
149
|
}
|
|
150
150
|
var baseName = CDPUtils.getGroupBaseName(groupClientId);
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
function tryGroup() {
|
|
155
|
-
chrome.tabs.get(tabId, function(tab) {
|
|
156
|
-
if (chrome.runtime.lastError || !tab) {
|
|
157
|
-
Logger.error('[TabGroup] Tab not found:', tabId, 'retries:', retries);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (tab.status === 'complete') {
|
|
161
|
-
Logger.info('[TabGroup] Tab ready, executing group operation for:', baseName);
|
|
162
|
-
doGroup(tabId, groupClientId, baseName);
|
|
163
|
-
} else if (retries < maxRetries) {
|
|
164
|
-
retries++;
|
|
165
|
-
Logger.info('[TabGroup] Tab not ready (', tab.status, '), retry', retries, '/', maxRetries);
|
|
166
|
-
setTimeout(tryGroup, 200);
|
|
167
|
-
} else {
|
|
168
|
-
Logger.warn('[TabGroup] Tab never reached complete status, grouping anyway. tabId:', tabId);
|
|
169
|
-
doGroup(tabId, groupClientId, baseName);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
tryGroup();
|
|
152
|
+
Logger.info('[TabGroup] Grouping tab immediately for:', baseName);
|
|
153
|
+
doGroup(tabId, groupClientId, baseName);
|
|
174
154
|
}
|
|
175
155
|
|
|
176
156
|
function doGroup(tabId, clientId, baseName, retries) {
|
|
@@ -608,6 +588,7 @@ function checkTabVisibility(tabId) {
|
|
|
608
588
|
pageAddScriptToEvaluateOnNewDocument: pageAddScriptToEvaluateOnNewDocument,
|
|
609
589
|
runtimeRunIfWaitingForDebugger: runtimeRunIfWaitingForDebugger,
|
|
610
590
|
domSetFileInputFiles: domSetFileInputFiles,
|
|
611
|
-
updateTabGroupName: updateTabGroupName
|
|
591
|
+
updateTabGroupName: updateTabGroupName,
|
|
592
|
+
addTabToAutomationGroup: addTabToAutomationGroup
|
|
612
593
|
};
|
|
613
594
|
})();
|
|
@@ -24,6 +24,7 @@ var CDP_HANDLERS = {
|
|
|
24
24
|
|
|
25
25
|
'Tab.getMuteStatus': { type: 'LOCAL', handler: LocalHandler.tabGetMuteStatus },
|
|
26
26
|
'Tab.getGroupInfo': { type: 'LOCAL', handler: LocalHandler.tabGetGroupInfo },
|
|
27
|
+
'Tab.ungroup': { type: 'LOCAL', handler: LocalHandler.tabUngroup },
|
|
27
28
|
|
|
28
29
|
'SystemInfo.getInfo': { type: 'LOCAL', handler: LocalHandler.systemInfoGetInfo },
|
|
29
30
|
'SystemInfo.getProcessInfo': { type: 'LOCAL', handler: LocalHandler.systemInfoGetProcessInfo },
|
|
@@ -409,8 +409,8 @@ var WebSocketManager = (function() {
|
|
|
409
409
|
return;
|
|
410
410
|
}
|
|
411
411
|
var groupId = State.getGroupIdForClient(clientId);
|
|
412
|
-
Logger.info('[Monitor] Tab', tabId, 'groupId=' + (tab.groupId
|
|
413
|
-
if (tab.groupId) return;
|
|
412
|
+
Logger.info('[Monitor] Tab', tabId, 'groupId=' + (tab.groupId > -1 ? tab.groupId : 'none'), 'expectedGroup=' + (groupId > -1 ? groupId : 'none'), 'clientId=' + (clientId || 'none'));
|
|
413
|
+
if (tab.groupId > -1) return;
|
|
414
414
|
Logger.info('[Monitor] Tab', tabId, 'escaped! Forcing regroup for client:', clientId);
|
|
415
415
|
if (groupId) {
|
|
416
416
|
chrome.tabs.group({ tabIds: tabId, groupId: groupId }, function() {
|
package/package.json
CHANGED
package/server/proxy-server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|