cdp-tunnel 2.7.10 → 2.8.1

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.
@@ -3,6 +3,8 @@ importScripts('utils/logger.js');
3
3
  importScripts('utils/helpers.js');
4
4
  importScripts('utils/diagnostics.js');
5
5
  importScripts('core/state.js');
6
+ importScripts('core/connection-state.js');
7
+ importScripts('core/connection-manager.js');
6
8
  importScripts('core/websocket.js');
7
9
  importScripts('core/debugger.js');
8
10
  importScripts('cdp/response.js');
@@ -23,12 +25,14 @@ importScripts('features/automation-badge.js');
23
25
  if (keepAliveInterval) {
24
26
  clearInterval(keepAliveInterval);
25
27
  }
26
-
28
+
27
29
  keepAliveInterval = setInterval(function() {
28
- var ws = State.getWs();
29
- if (ws && ws.readyState === WebSocket.OPEN) {
30
- WebSocketManager.send({ type: 'keepalive', timestamp: Date.now() });
31
- }
30
+ ConnectionManager.forEachConnection(function(entry) {
31
+ var ws = entry.state.getWs();
32
+ if (ws && ws.readyState === WebSocket.OPEN) {
33
+ entry.wsManager.send({ type: 'keepalive', timestamp: Date.now() });
34
+ }
35
+ });
32
36
  }, 20000);
33
37
 
34
38
  chrome.alarms.clear('sw-keepalive', function() {
@@ -52,7 +56,6 @@ importScripts('features/automation-badge.js');
52
56
  _initialized = true;
53
57
  Logger.info('[Init] CDP Bridge starting...');
54
58
 
55
- // 点击扩展图标时打开配置页面
56
59
  chrome.action.onClicked.addListener(function(tab) {
57
60
  Logger.info('[Action] Extension icon clicked, opening config page');
58
61
  chrome.tabs.create({
@@ -67,12 +70,22 @@ importScripts('features/automation-badge.js');
67
70
  validatePersistedState(result.currentTabId, result.isAttached);
68
71
  }
69
72
 
70
- WebSocketManager.connect();
71
- startKeepAlive();
72
-
73
- setTimeout(function() {
74
- Diagnostics.start();
75
- }, 2000);
73
+ Config.getConnections(function(connections) {
74
+ ConnectionManager.init(connections);
75
+
76
+ var primary = ConnectionManager.getPrimaryConnection();
77
+ if (primary && result.currentTabId != null) {
78
+ primary.state.currentTabId = result.currentTabId;
79
+ primary.state.isAttached = result.isAttached;
80
+ }
81
+
82
+ ConnectionManager.connectAll();
83
+ startKeepAlive();
84
+
85
+ setTimeout(function() {
86
+ Diagnostics.start();
87
+ }, 2000);
88
+ });
76
89
  });
77
90
  }
78
91
 
@@ -117,29 +130,37 @@ importScripts('features/automation-badge.js');
117
130
  chrome.tabs.onRemoved.addListener(function(tabId) {
118
131
  Logger.info('[Tabs] Tab removed:', tabId);
119
132
 
120
- State.removeAttachedTab(tabId);
121
- var removedClientId = State.getClientIdByTabId(tabId);
122
- Screencast.stopPolling(tabId);
133
+ var entry = ConnectionManager.getConnectionByTabId(tabId);
134
+ var state = entry ? entry.state : null;
135
+ var wsManager = entry ? entry.wsManager : null;
136
+
137
+ if (state) {
138
+ state.removeAttachedTab(tabId);
139
+ }
140
+ var removedClientId = state ? state.getClientIdByTabId(tabId) : null;
141
+ Screencast.stopPolling(tabId, state);
123
142
  AutomationBadge.remove(tabId);
124
143
 
125
- var sessionId = State.findSessionByTabId(tabId);
126
- if (sessionId) {
127
- var targetId = State.getTargetIdBySession(sessionId);
128
- EventBuilder.send('Target.detachedFromTarget', {
129
- sessionId: sessionId,
130
- targetId: targetId
131
- });
132
- EventBuilder.send('Target.targetDestroyed', { targetId: targetId });
133
- State.unmapSession(sessionId);
134
- if (removedClientId) {
135
- SpecialHandler.updateTabGroupName(removedClientId);
144
+ if (state) {
145
+ var sessionId = state.findSessionByTabId(tabId);
146
+ if (sessionId) {
147
+ var targetId = state.getTargetIdBySession(sessionId);
148
+ EventBuilder.send('Target.detachedFromTarget', {
149
+ sessionId: sessionId,
150
+ targetId: targetId
151
+ }, null, wsManager);
152
+ EventBuilder.send('Target.targetDestroyed', { targetId: targetId }, null, wsManager);
153
+ state.unmapSession(sessionId);
154
+ if (removedClientId) {
155
+ SpecialHandler.updateTabGroupName(removedClientId, state, wsManager);
156
+ }
136
157
  }
137
- }
138
158
 
139
- if (State.getCurrentTabId() === tabId) {
140
- State.persist(null, false);
159
+ if (state.getCurrentTabId() === tabId) {
160
+ state.persist(null, false);
161
+ }
162
+ state.removeTabIdToClientId(tabId);
141
163
  }
142
- State.removeTabIdToClientId(tabId);
143
164
  });
144
165
 
145
166
  if (chrome.tabGroups) {
@@ -148,47 +169,59 @@ importScripts('features/automation-badge.js');
148
169
  var removedGroupId = group.id;
149
170
  Logger.info('[TabGroups] Group removed:', removedGroupId);
150
171
 
151
- var clients = State.getCDPClients() || [];
152
- for (var i = 0; i < clients.length; i++) {
153
- var clientId = clients[i].id;
154
- if (State.getGroupIdForClient(clientId) === removedGroupId) {
155
- Logger.info('[TabGroups] Clearing cached groupId for client:', clientId);
156
- State.setGroupIdForClient(clientId, null);
157
-
158
- var attached = State.getAttachedTabIds();
159
- attached.forEach(function(tid) {
160
- if (State.getClientIdByTabId(tid) === clientId && !State.isPreExistingTab(tid)) {
161
- Logger.info('[TabGroups] Re-grouping tab', tid, 'for client:', clientId);
162
- SpecialHandler.addTabToAutomationGroup(tid, clientId);
163
- }
164
- });
165
- break;
172
+ ConnectionManager.forEachConnection(function(entry) {
173
+ var state = entry.state;
174
+ var wsManager = entry.wsManager;
175
+ var clients = state.getCDPClients() || [];
176
+ for (var i = 0; i < clients.length; i++) {
177
+ var clientId = clients[i].id;
178
+ if (state.getGroupIdForClient(clientId) === removedGroupId) {
179
+ Logger.info('[TabGroups] Clearing cached groupId for client:', clientId);
180
+ state.setGroupIdForClient(clientId, null);
181
+
182
+ var attached = state.getAttachedTabIds();
183
+ attached.forEach(function(tid) {
184
+ if (state.getClientIdByTabId(tid) === clientId && !state.isPreExistingTab(tid)) {
185
+ Logger.info('[TabGroups] Re-grouping tab', tid, 'for client:', clientId);
186
+ var ctx = { _state: state, _wsManager: wsManager, clientId: clientId };
187
+ SpecialHandler.addTabToAutomationGroup(tid, clientId, null, ctx);
188
+ }
189
+ });
190
+ break;
191
+ }
166
192
  }
167
- }
193
+ });
168
194
  });
169
195
  }
170
196
 
171
197
  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
172
- if (changeInfo.status === 'complete' && State.isTabAttached(tabId)) {
173
- // 不再注入自动化标识,改为通过标签分组区分
198
+ if (changeInfo.status === 'complete') {
199
+ var entry = ConnectionManager.getConnectionByTabId(tabId);
200
+ if (entry && entry.state.isTabAttached(tabId)) {
201
+ }
174
202
  }
175
203
 
176
204
  if (changeInfo.groupId !== undefined && changeInfo.groupId === -1) {
177
- if (State.isTabAttached(tabId) && !State.isPreExistingTab(tabId)) {
178
- var clientId = State.getClientIdByTabId(tabId);
205
+ var entry = ConnectionManager.getConnectionByTabId(tabId);
206
+ if (entry && entry.state.isTabAttached(tabId) && !entry.state.isPreExistingTab(tabId)) {
207
+ var state = entry.state;
208
+ var wsManager = entry.wsManager;
209
+ var clientId = state.getClientIdByTabId(tabId);
179
210
  if (clientId) {
180
- var cachedGroupId = State.getGroupIdForClient(clientId);
211
+ var cachedGroupId = state.getGroupIdForClient(clientId);
181
212
  if (cachedGroupId) {
182
213
  Logger.info('[Tabs] Tab', tabId, 'left group, re-adding to cached group:', cachedGroupId);
183
214
  chrome.tabs.group({ tabIds: tabId, groupId: cachedGroupId }, function() {
184
215
  if (chrome.runtime.lastError) {
185
216
  Logger.warn('[Tabs] Failed to re-add tab to group:', chrome.runtime.lastError.message);
186
- SpecialHandler.addTabToAutomationGroup(tabId, clientId);
217
+ var ctx = { _state: state, _wsManager: wsManager, clientId: clientId };
218
+ SpecialHandler.addTabToAutomationGroup(tabId, clientId, null, ctx);
187
219
  }
188
220
  });
189
221
  } else {
190
222
  Logger.info('[Tabs] Tab', tabId, 'left group, no cached groupId — delegating to addTabToAutomationGroup');
191
- SpecialHandler.addTabToAutomationGroup(tabId, clientId);
223
+ var ctx = { _state: state, _wsManager: wsManager, clientId: clientId };
224
+ SpecialHandler.addTabToAutomationGroup(tabId, clientId, null, ctx);
192
225
  }
193
226
  }
194
227
  }
@@ -197,75 +230,83 @@ importScripts('features/automation-badge.js');
197
230
 
198
231
  chrome.tabs.onCreated.addListener(function(tab) {
199
232
  Logger.info('[Tabs] Tab created:', tab.id, tab.url, 'openerTabId:', tab.openerTabId);
200
-
201
- if (!State.hasConnectedClient()) {
233
+
234
+ var entry = ConnectionManager.getPrimaryConnection();
235
+ if (!entry) return;
236
+ var state = entry.state;
237
+ var wsManager = entry.wsManager;
238
+
239
+ if (!state.hasConnectedClient()) {
202
240
  Logger.info('[Tabs] No connected client, skipping');
203
241
  return;
204
242
  }
205
-
243
+
206
244
  var tabId = tab.id;
207
-
245
+
208
246
  var tabUrl = tab.url || tab.pendingUrl || 'about:blank';
209
- if (State.hasPendingCreatedTabUrl(tabUrl)) {
247
+ if (state.hasPendingCreatedTabUrl(tabUrl)) {
210
248
  Logger.info('[Tabs] Tab created by Target.createTarget, will be handled by createTarget:', tabUrl);
211
- State.removePendingCreatedTabUrl(tabUrl);
249
+ state.removePendingCreatedTabUrl(tabUrl);
212
250
  return;
213
251
  }
214
-
252
+
215
253
  var openerTabId = tab.openerTabId;
216
- var isOpenerControlled = openerTabId && State.isTabAttached(openerTabId) && !State.isPreExistingTab(openerTabId);
217
-
218
- // 只有当 opener 是 CDP 主动管理的 tab 时才跟踪新页面
219
- // pre-existing tab 虽然也 attach 了 debugger,但属于用户 tab,不应继承 CDP 控制
254
+ var openerEntry = openerTabId ? ConnectionManager.getConnectionByTabId(openerTabId) : null;
255
+ if (openerEntry) {
256
+ state = openerEntry.state;
257
+ wsManager = openerEntry.wsManager;
258
+ }
259
+ var isOpenerControlled = openerTabId && state.isTabAttached(openerTabId) && !state.isPreExistingTab(openerTabId);
260
+
220
261
  if (!openerTabId) {
221
262
  Logger.info('[Tabs] Tab has no opener, skipping. tabId:', tabId);
222
263
  return;
223
264
  }
224
-
265
+
225
266
  if (!isOpenerControlled) {
226
267
  Logger.info('[Tabs] Opener not controlled by CDP, skipping. tabId:', tabId, 'openerTabId:', openerTabId);
227
268
  return;
228
269
  }
229
-
270
+
230
271
  Logger.info('[Tabs] Tab has controlled opener, will attach. tabId:', tabId, 'openerTabId:', openerTabId);
231
-
272
+
232
273
  LocalHandler.getTargetInfoById(String(tabId)).then(function(targetInfo) {
233
274
  Logger.info('[Tabs] getTargetInfoById result:', targetInfo ? targetInfo.targetId : 'null');
234
275
  if (!targetInfo) {
235
276
  Logger.error('[Tabs] getTargetInfoById returned null for tabId:', tabId);
236
277
  return;
237
278
  }
238
-
279
+
239
280
  var targetId = targetInfo.targetId;
240
281
  Logger.info('[Tabs] targetId:', targetId);
241
-
242
- if (State.hasEmittedTarget(targetId)) {
282
+
283
+ if (state.hasEmittedTarget(targetId)) {
243
284
  Logger.info('[Tabs] Target already emitted, skipping:', targetId);
244
285
  return;
245
286
  }
246
-
247
- State.addEmittedTarget(targetId);
287
+
288
+ state.addEmittedTarget(targetId);
248
289
  Logger.info('[Tabs] Sending Target.targetCreated event');
249
-
250
- EventBuilder.send('Target.targetCreated', { targetInfo: targetInfo });
290
+
291
+ EventBuilder.send('Target.targetCreated', { targetInfo: targetInfo }, null, wsManager);
251
292
  Logger.info('[Tabs] Target.targetCreated sent, now attaching to tab:', tabId);
252
-
253
- return DebuggerManager.attach(tabId).then(function(attached) {
293
+
294
+ return DebuggerManager.attach(tabId, state).then(function(attached) {
254
295
  Logger.info('[Tabs] DebuggerManager.attach result:', attached);
255
296
  if (!attached) {
256
297
  Logger.error('[Tabs] Failed to attach to tab:', tabId);
257
298
  return;
258
299
  }
259
-
300
+
260
301
  var sessionId = CDPUtils.generateSessionId();
261
- State.mapSession(sessionId, tabId, targetId);
302
+ state.mapSession(sessionId, tabId, targetId);
262
303
 
263
- var openerClientId = openerTabId ? State.getClientIdByTabId(openerTabId) : null;
304
+ var openerClientId = openerTabId ? state.getClientIdByTabId(openerTabId) : null;
264
305
  if (openerClientId) {
265
- State.setTabIdToClientId(tabId, openerClientId);
306
+ state.setTabIdToClientId(tabId, openerClientId);
266
307
  Logger.info('[Tabs] Mapped child tab', tabId, '-> clientId:', openerClientId);
267
308
  }
268
-
309
+
269
310
  Config.getAutoMute(function(enabled) {
270
311
  if (enabled) {
271
312
  chrome.tabs.update(tabId, { muted: true }, function() {
@@ -277,16 +318,17 @@ importScripts('features/automation-badge.js');
277
318
  });
278
319
  }
279
320
  });
280
-
281
- SpecialHandler.addTabToAutomationGroup(tabId, openerClientId);
282
-
321
+
322
+ var ctx = { _state: state, _wsManager: wsManager, clientId: openerClientId };
323
+ SpecialHandler.addTabToAutomationGroup(tabId, openerClientId, null, ctx);
324
+
283
325
  Logger.info('[Tabs] Sending Target.attachedToTarget event');
284
-
326
+
285
327
  EventBuilder.send('Target.attachedToTarget', {
286
328
  sessionId: sessionId,
287
329
  targetInfo: targetInfo,
288
330
  waitingForDebugger: false
289
- });
331
+ }, null, wsManager);
290
332
  Logger.info('[Tabs] Target.attachedToTarget sent');
291
333
  }).catch(function(err) {
292
334
  Logger.error('[Tabs] DebuggerManager.attach error:', err);
@@ -299,7 +341,7 @@ importScripts('features/automation-badge.js');
299
341
  chrome.runtime.onInstalled.addListener(function(details) {
300
342
  Logger.info('[Runtime] Extension installed/updated:', details.reason);
301
343
  State.persist(null, false);
302
- WebSocketManager.setBadgeStatus('ON');
344
+ setBadgeStatus('ON');
303
345
  init();
304
346
  });
305
347
 
@@ -308,58 +350,67 @@ importScripts('features/automation-badge.js');
308
350
  init();
309
351
  });
310
352
 
353
+ function broadcastConnectionsUpdated() {
354
+ chrome.runtime.sendMessage({ type: 'connections-updated' }).catch(function() {});
355
+ }
356
+
357
+ function _getAggregatedState() {
358
+ var isConnected = false;
359
+ var cdpClients = [];
360
+ var attachedTabIds = [];
361
+ ConnectionManager.forEachConnection(function(entry) {
362
+ var ws = entry.state.getWs();
363
+ if (ws && ws.readyState === WebSocket.OPEN) isConnected = true;
364
+ cdpClients = cdpClients.concat(entry.state.getCDPClients() || []);
365
+ attachedTabIds = attachedTabIds.concat(entry.state.getAttachedTabIds());
366
+ });
367
+ return { isConnected: isConnected, cdpClients: cdpClients, attachedTabIds: attachedTabIds };
368
+ }
369
+
311
370
  chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
312
371
  if (message.type === 'popup-query') {
313
- var ws = State.getWs();
314
- var isConnected = ws && ws.readyState === WebSocket.OPEN;
372
+ var agg = _getAggregatedState();
315
373
  chrome.storage.local.get(['wsAddress', 'pluginId'], function(result) {
316
374
  sendResponse({
317
- connected: isConnected,
375
+ connected: agg.isConnected,
318
376
  pluginId: result.pluginId || null,
319
- cdpClients: State.getCDPClients() || [],
320
- attachedPages: State.getAttachedTabIds().map(function(tid) { return { tabId: tid }; })
377
+ cdpClients: agg.cdpClients,
378
+ attachedPages: agg.attachedTabIds.map(function(tid) { return { tabId: tid }; })
321
379
  });
322
380
  });
323
381
  return true;
324
382
  } else if (message.type === 'ws-reconnect') {
325
383
  Logger.info('[Runtime] WS address changed, reconnecting');
326
- var ws = State.getWs();
327
- if (ws) ws.close();
328
- WebSocketManager.connect();
384
+ ConnectionManager.disconnectAll();
385
+ ConnectionManager.connectAll();
329
386
  sendResponse({ success: true });
330
387
  } else if (message.type === 'reconnect') {
331
388
  Logger.info('[Runtime] Received reconnect request from popup');
332
- var ws = State.getWs();
333
- if (ws) {
334
- ws.close();
335
- }
336
- WebSocketManager.connect();
389
+ ConnectionManager.disconnectAll();
390
+ ConnectionManager.connectAll();
337
391
  sendResponse({ success: true });
338
392
  } else if (message.type === 'getState') {
339
- var ws = State.getWs();
340
- var isConnected = ws && ws.readyState === WebSocket.OPEN;
341
-
393
+ var agg = _getAggregatedState();
342
394
  chrome.storage.local.get(['wsAddress'], function(result) {
343
- var attachedTabs = State.getAttachedTabIds();
344
- var cdpClients = State.getCDPClients() || [];
345
-
395
+ var attachedTabs = agg.attachedTabIds;
396
+
346
397
  if (attachedTabs.length === 0) {
347
398
  sendResponse({
348
- connected: isConnected,
399
+ connected: agg.isConnected,
349
400
  serverAddress: result.wsAddress || Config.WS_URL,
350
- cdpClients: cdpClients,
401
+ cdpClients: agg.cdpClients,
351
402
  attachedPages: []
352
403
  });
353
404
  return;
354
405
  }
355
-
406
+
356
407
  var attachedPages = [];
357
408
  var pendingTabs = attachedTabs.length;
358
-
409
+
359
410
  attachedTabs.forEach(function(tabId) {
360
411
  chrome.tabs.get(tabId, function(tab) {
361
412
  pendingTabs--;
362
-
413
+
363
414
  if (chrome.runtime.lastError) {
364
415
  Logger.info('[Runtime] Tab not found:', tabId);
365
416
  } else if (tab) {
@@ -369,30 +420,27 @@ importScripts('features/automation-badge.js');
369
420
  url: tab.url || ''
370
421
  });
371
422
  }
372
-
423
+
373
424
  if (pendingTabs === 0) {
374
425
  sendResponse({
375
- connected: isConnected,
426
+ connected: agg.isConnected,
376
427
  serverAddress: result.wsAddress || Config.WS_URL,
377
- cdpClients: cdpClients,
428
+ cdpClients: agg.cdpClients,
378
429
  attachedPages: attachedPages
379
430
  });
380
431
  }
381
432
  });
382
433
  });
383
434
  });
384
-
435
+
385
436
  return true;
386
437
  } else if (message.type === 'connect') {
387
438
  var address = message.serverAddress;
388
439
  if (address) {
389
440
  Logger.info('[Runtime] Saving and connecting to:', address);
390
441
  chrome.storage.local.set({ wsAddress: address }, function() {
391
- var ws = State.getWs();
392
- if (ws) {
393
- ws.close();
394
- }
395
- WebSocketManager.connect();
442
+ ConnectionManager.disconnectAll();
443
+ ConnectionManager.connectAll();
396
444
  sendResponse({ success: true });
397
445
  });
398
446
  } else {
@@ -401,23 +449,79 @@ importScripts('features/automation-badge.js');
401
449
  return true;
402
450
  } else if (message.type === 'disconnect') {
403
451
  Logger.info('[Runtime] Disconnecting...');
404
- var ws = State.getWs();
405
- if (ws) {
406
- ws.close();
407
- }
452
+ ConnectionManager.disconnectAll();
408
453
  sendResponse({ success: true });
454
+ } else if (message.type === 'get-connection-statuses') {
455
+ Config.getConnections(function(connections) {
456
+ var statuses = {};
457
+ (connections || []).forEach(function(conn) {
458
+ if (!conn.enabled) {
459
+ statuses[conn.id] = 'disabled';
460
+ } else {
461
+ var entry = ConnectionManager.getConnection(conn.id);
462
+ if (entry) {
463
+ var ws = entry.state.getWs();
464
+ statuses[conn.id] = (ws && ws.readyState === WebSocket.OPEN) ? 'connected' : 'error';
465
+ } else {
466
+ statuses[conn.id] = 'error';
467
+ }
468
+ }
469
+ });
470
+ sendResponse({ statuses: statuses });
471
+ });
472
+ return true;
473
+ } else if (message.type === 'add-connection') {
474
+ Logger.info('[Runtime] Adding connection:', message.tag, message.url);
475
+ Config.addConnection({ tag: message.tag, url: message.url }, function(conn) {
476
+ if (conn && conn.enabled) {
477
+ ConnectionManager.addConnection(conn);
478
+ var entry = ConnectionManager.getConnection(conn.id);
479
+ if (entry) entry.wsManager.connect();
480
+ }
481
+ broadcastConnectionsUpdated();
482
+ sendResponse({ success: true });
483
+ });
484
+ return true;
485
+ } else if (message.type === 'remove-connection') {
486
+ Logger.info('[Runtime] Removing connection:', message.connectionId);
487
+ ConnectionManager.removeConnection(message.connectionId);
488
+ Config.removeConnection(message.connectionId, function() {
489
+ broadcastConnectionsUpdated();
490
+ sendResponse({ success: true });
491
+ });
492
+ return true;
493
+ } else if (message.type === 'toggle-connection') {
494
+ Logger.info('[Runtime] Toggling connection:', message.connectionId, message.enabled);
495
+ Config.toggleConnection(message.connectionId, message.enabled, function() {
496
+ if (message.enabled) {
497
+ Config.getConnections(function(connections) {
498
+ var conn = (connections || []).find(function(c) { return c.id === message.connectionId; });
499
+ if (conn) {
500
+ var wsMgr = ConnectionManager.addConnection(conn);
501
+ if (wsMgr) wsMgr.connect();
502
+ }
503
+ });
504
+ } else {
505
+ ConnectionManager.removeConnection(message.connectionId);
506
+ }
507
+ broadcastConnectionsUpdated();
508
+ sendResponse({ success: true });
509
+ });
510
+ return true;
409
511
  }
410
512
  return true;
411
513
  });
412
514
 
413
515
  chrome.alarms.onAlarm.addListener(function(alarm) {
414
516
  if (alarm.name === 'sw-keepalive') {
415
- var ws = State.getWs();
416
- if (ws && ws.readyState === WebSocket.OPEN) {
417
- WebSocketManager.send({ type: 'keepalive', timestamp: Date.now() });
418
- } else {
419
- WebSocketManager.connect();
420
- }
517
+ ConnectionManager.forEachConnection(function(entry) {
518
+ var ws = entry.state.getWs();
519
+ if (ws && ws.readyState === WebSocket.OPEN) {
520
+ entry.wsManager.send({ type: 'keepalive', timestamp: Date.now() });
521
+ } else {
522
+ entry.wsManager.connect();
523
+ }
524
+ });
421
525
  }
422
526
  });
423
527
 
@@ -4,15 +4,16 @@ var ForwardHandler = (function() {
4
4
  var method = context.method;
5
5
  var params = context.params;
6
6
  var sessionId = context.sessionId;
7
+ var state = context._state;
7
8
 
8
- var tabId = resolveTabId(sessionId);
9
+ var tabId = resolveTabId(sessionId, state);
9
10
 
10
11
  if (!tabId) {
11
12
  Logger.warn('[Forward] No tabId for command:', method);
12
13
  return Promise.reject({ code: -32000, message: 'No target found for command: ' + method });
13
14
  }
14
15
 
15
- if (!State.isTabAttached(tabId)) {
16
+ if (!state.isTabAttached(tabId)) {
16
17
  Logger.warn('[Forward] Tab not attached, skipping command:', method, 'tabId:', tabId);
17
18
  return Promise.reject({ code: -32000, message: 'Target is not attached' });
18
19
  }
@@ -23,15 +24,16 @@ var ForwardHandler = (function() {
23
24
  });
24
25
  }
25
26
 
26
- function resolveTabId(sessionId) {
27
+ function resolveTabId(sessionId, state) {
28
+ if (!state) return null;
27
29
  if (sessionId) {
28
- return State.getTabIdBySession(sessionId);
30
+ return state.getTabIdBySession(sessionId);
29
31
  }
30
- var currentTabId = State.getCurrentTabId();
31
- if (currentTabId != null && State.isTabAttached(currentTabId)) {
32
+ var currentTabId = state.getCurrentTabId();
33
+ if (currentTabId != null && state.isTabAttached(currentTabId)) {
32
34
  return currentTabId;
33
35
  }
34
- var attachedTabs = State.getAttachedTabIds();
36
+ var attachedTabs = state.getAttachedTabIds();
35
37
  if (attachedTabs.length > 0) {
36
38
  return attachedTabs[0];
37
39
  }