cdp-tunnel 2.7.9 → 2.8.0
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/background.js +234 -130
- package/extension-new/cdp/handler/forward.js +9 -7
- package/extension-new/cdp/handler/local.js +60 -39
- package/extension-new/cdp/handler/special.js +145 -119
- package/extension-new/cdp/index.js +16 -7
- package/extension-new/cdp/response.js +12 -4
- package/extension-new/config-page-preview.html +174 -57
- package/extension-new/config-page.js +169 -70
- package/extension-new/core/connection-manager.js +120 -0
- package/extension-new/core/connection-state.js +355 -0
- package/extension-new/core/debugger.js +65 -52
- package/extension-new/core/state.js +87 -438
- package/extension-new/core/websocket.js +345 -279
- package/extension-new/features/screencast.js +42 -20
- package/extension-new/manifest.json +3 -2
- package/extension-new/utils/config.js +83 -2
- package/extension-new/utils/helpers.js +5 -4
- package/package.json +1 -1
|
@@ -1,69 +1,75 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
var WebSocketConnection = (function() {
|
|
2
|
+
function WebSocketConnection(connectionId, state, config) {
|
|
3
|
+
this.connectionId = connectionId;
|
|
4
|
+
this.state = state;
|
|
5
|
+
this.config = config;
|
|
6
|
+
this._sendQueue = [];
|
|
7
|
+
this._isSending = false;
|
|
8
|
+
this._maxQueueSize = 100;
|
|
9
|
+
this._bufferThreshold = 512 * 1024;
|
|
10
|
+
this._groupCreationPending = new Set();
|
|
11
|
+
}
|
|
6
12
|
|
|
7
|
-
function
|
|
8
|
-
var
|
|
13
|
+
WebSocketConnection.prototype.connect = function() {
|
|
14
|
+
var self = this;
|
|
15
|
+
var ws = self.state.getWs();
|
|
9
16
|
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) {
|
|
10
17
|
return;
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
var wsUrl = self.config.url;
|
|
21
|
+
Config.getPluginId(function(pluginId) {
|
|
22
|
+
if (pluginId) {
|
|
23
|
+
var sep = wsUrl.indexOf('?') >= 0 ? '&' : '?';
|
|
24
|
+
wsUrl += sep + 'pluginId=' + encodeURIComponent(pluginId);
|
|
25
|
+
}
|
|
26
|
+
Logger.info('[WS:' + self.connectionId + '] Connecting to', wsUrl);
|
|
27
|
+
setBadgeStatus('ON');
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
setBadgeStatus('ERR');
|
|
47
|
-
broadcastStateUpdate();
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
ws.onmessage = function(event) {
|
|
51
|
-
handleRawMessage(event.data);
|
|
52
|
-
};
|
|
53
|
-
} catch (error) {
|
|
54
|
-
Logger.error('[WS] Failed to create:', error);
|
|
29
|
+
try {
|
|
30
|
+
ws = new WebSocket(wsUrl);
|
|
31
|
+
self.state.setWs(ws);
|
|
32
|
+
|
|
33
|
+
ws.onopen = function() {
|
|
34
|
+
Logger.info('[WS:' + self.connectionId + '] Connected');
|
|
35
|
+
setBadgeStatus('ON');
|
|
36
|
+
self.state.clearReconnectTimer();
|
|
37
|
+
self._processQueue();
|
|
38
|
+
self._broadcastStateUpdate();
|
|
39
|
+
var extVersion = (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.getManifest)
|
|
40
|
+
? chrome.runtime.getManifest().version : 'unknown';
|
|
41
|
+
self.send({ type: 'plugin-hello', version: extVersion });
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
ws.onclose = function(event) {
|
|
45
|
+
Logger.info('[WS:' + self.connectionId + '] Closed:', event.code, event.reason);
|
|
46
|
+
setBadgeStatus('OFF');
|
|
47
|
+
self._scheduleReconnect();
|
|
48
|
+
self._broadcastStateUpdate();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
ws.onerror = function(error) {
|
|
52
|
+
Logger.error('[WS:' + self.connectionId + '] Error:', error);
|
|
55
53
|
setBadgeStatus('ERR');
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
54
|
+
self._broadcastStateUpdate();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
ws.onmessage = function(event) {
|
|
58
|
+
self._handleRawMessage(event.data);
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
Logger.error('[WS:' + self.connectionId + '] Failed to create:', error);
|
|
62
|
+
setBadgeStatus('ERR');
|
|
63
|
+
self._scheduleReconnect();
|
|
64
|
+
}
|
|
59
65
|
});
|
|
60
|
-
}
|
|
66
|
+
};
|
|
61
67
|
|
|
62
|
-
function
|
|
63
|
-
var ws =
|
|
68
|
+
WebSocketConnection.prototype.send = function(message) {
|
|
69
|
+
var ws = this.state.getWs();
|
|
64
70
|
var wsState = ws ? ws.readyState : 'no ws';
|
|
65
71
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
66
|
-
Logger.warn('[WS] Cannot send, WebSocket not open. State:', wsState);
|
|
72
|
+
Logger.warn('[WS:' + this.connectionId + '] Cannot send, WebSocket not open. State:', wsState);
|
|
67
73
|
return false;
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -71,91 +77,91 @@ var WebSocketManager = (function() {
|
|
|
71
77
|
try {
|
|
72
78
|
jsonStr = JSON.stringify(message);
|
|
73
79
|
} catch (e) {
|
|
74
|
-
Logger.error('[WS] Failed to stringify message:', e);
|
|
80
|
+
Logger.error('[WS:' + this.connectionId + '] Failed to stringify message:', e);
|
|
75
81
|
return false;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
var msgSize = jsonStr.length;
|
|
79
|
-
|
|
80
85
|
if (msgSize > 1024 * 1024) {
|
|
81
|
-
Logger.warn('[WS] Large message:', msgSize, 'bytes, method:', message.method || message.type);
|
|
86
|
+
Logger.warn('[WS:' + this.connectionId + '] Large message:', msgSize, 'bytes, method:', message.method || message.type);
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
if (ws.bufferedAmount > _bufferThreshold) {
|
|
85
|
-
Logger.warn('[WS] Buffer full, queuing message. Buffered:', ws.bufferedAmount);
|
|
86
|
-
if (_sendQueue.length < _maxQueueSize) {
|
|
87
|
-
_sendQueue.push(jsonStr);
|
|
89
|
+
if (ws.bufferedAmount > this._bufferThreshold) {
|
|
90
|
+
Logger.warn('[WS:' + this.connectionId + '] Buffer full, queuing message. Buffered:', ws.bufferedAmount);
|
|
91
|
+
if (this._sendQueue.length < this._maxQueueSize) {
|
|
92
|
+
this._sendQueue.push(jsonStr);
|
|
88
93
|
} else {
|
|
89
|
-
Logger.error('[WS] Queue full, dropping message');
|
|
94
|
+
Logger.error('[WS:' + this.connectionId + '] Queue full, dropping message');
|
|
90
95
|
}
|
|
91
96
|
return false;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
try {
|
|
95
100
|
ws.send(jsonStr);
|
|
96
|
-
Logger.info('[WS] SEND: ' + jsonStr.substring(0, 200));
|
|
101
|
+
Logger.info('[WS:' + this.connectionId + '] SEND: ' + jsonStr.substring(0, 200));
|
|
97
102
|
return true;
|
|
98
103
|
} catch (e) {
|
|
99
|
-
Logger.error('[WS] Send error:', e);
|
|
104
|
+
Logger.error('[WS:' + this.connectionId + '] Send error:', e);
|
|
100
105
|
return false;
|
|
101
106
|
}
|
|
102
|
-
}
|
|
107
|
+
};
|
|
103
108
|
|
|
104
|
-
function
|
|
105
|
-
var ws =
|
|
106
|
-
if (!ws || ws.readyState !== WebSocket.OPEN)
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
+
WebSocketConnection.prototype._processQueue = function() {
|
|
110
|
+
var ws = this.state.getWs();
|
|
111
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
109
112
|
|
|
110
|
-
while (_sendQueue.length > 0 && ws.bufferedAmount < _bufferThreshold) {
|
|
111
|
-
var data = _sendQueue.shift();
|
|
113
|
+
while (this._sendQueue.length > 0 && ws.bufferedAmount < this._bufferThreshold) {
|
|
114
|
+
var data = this._sendQueue.shift();
|
|
112
115
|
try {
|
|
113
116
|
ws.send(data);
|
|
114
117
|
} catch (e) {
|
|
115
|
-
Logger.error('[WS] Queue send error:', e);
|
|
118
|
+
Logger.error('[WS:' + this.connectionId + '] Queue send error:', e);
|
|
116
119
|
break;
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
if (_sendQueue.length > 0) {
|
|
121
|
-
setTimeout(
|
|
123
|
+
if (this._sendQueue.length > 0) {
|
|
124
|
+
setTimeout(this._processQueue.bind(this), 100);
|
|
122
125
|
}
|
|
123
|
-
}
|
|
126
|
+
};
|
|
124
127
|
|
|
125
|
-
function
|
|
126
|
-
|
|
128
|
+
WebSocketConnection.prototype._scheduleReconnect = function() {
|
|
129
|
+
this.state.clearReconnectTimer();
|
|
130
|
+
var self = this;
|
|
127
131
|
var timer = setTimeout(function() {
|
|
128
|
-
Logger.info('[WS] Attempting to reconnect...');
|
|
129
|
-
connect();
|
|
132
|
+
Logger.info('[WS:' + self.connectionId + '] Attempting to reconnect...');
|
|
133
|
+
self.connect();
|
|
130
134
|
}, Config.RECONNECT_DELAY);
|
|
131
|
-
|
|
132
|
-
}
|
|
135
|
+
self.state.setReconnectTimer(timer);
|
|
136
|
+
};
|
|
133
137
|
|
|
134
|
-
function
|
|
138
|
+
WebSocketConnection.prototype._handleRawMessage = function(data) {
|
|
139
|
+
var self = this;
|
|
135
140
|
try {
|
|
136
141
|
if (data instanceof Blob) {
|
|
137
142
|
data.text().then(function(text) {
|
|
138
143
|
try {
|
|
139
|
-
|
|
144
|
+
self._handleMessage(JSON.parse(text));
|
|
140
145
|
} catch (e) {
|
|
141
|
-
Logger.error('[WS] Failed to parse Blob message:', e);
|
|
146
|
+
Logger.error('[WS:' + self.connectionId + '] Failed to parse Blob message:', e);
|
|
142
147
|
}
|
|
143
148
|
}).catch(function(e) {
|
|
144
|
-
Logger.error('[WS] Failed to read Blob:', e);
|
|
149
|
+
Logger.error('[WS:' + self.connectionId + '] Failed to read Blob:', e);
|
|
145
150
|
});
|
|
146
151
|
} else {
|
|
147
152
|
try {
|
|
148
|
-
|
|
153
|
+
self._handleMessage(JSON.parse(data));
|
|
149
154
|
} catch (e) {
|
|
150
|
-
Logger.error('[WS] Failed to parse message:', e);
|
|
155
|
+
Logger.error('[WS:' + self.connectionId + '] Failed to parse message:', e);
|
|
151
156
|
}
|
|
152
157
|
}
|
|
153
158
|
} catch (e) {
|
|
154
|
-
Logger.error('[WS] handleRawMessage error:', e);
|
|
159
|
+
Logger.error('[WS:' + self.connectionId + '] handleRawMessage error:', e);
|
|
155
160
|
}
|
|
156
|
-
}
|
|
161
|
+
};
|
|
157
162
|
|
|
158
|
-
function
|
|
163
|
+
WebSocketConnection.prototype._handleMessage = function(message) {
|
|
164
|
+
var self = this;
|
|
159
165
|
var type = message.type;
|
|
160
166
|
var method = message.method;
|
|
161
167
|
var params = message.params;
|
|
@@ -166,80 +172,81 @@ var WebSocketManager = (function() {
|
|
|
166
172
|
switch (type) {
|
|
167
173
|
case 'connected':
|
|
168
174
|
if (message.fresh) {
|
|
169
|
-
Logger.info('[WS] Received fresh connection from server');
|
|
170
|
-
|
|
175
|
+
Logger.info('[WS:' + self.connectionId + '] Received fresh connection from server');
|
|
176
|
+
self._handleServerRestart();
|
|
171
177
|
}
|
|
172
178
|
break;
|
|
173
179
|
|
|
174
180
|
case 'ping':
|
|
175
|
-
send({ type: 'pong' });
|
|
181
|
+
self.send({ type: 'pong' });
|
|
176
182
|
break;
|
|
177
183
|
|
|
178
184
|
case 'attach':
|
|
179
|
-
var attachTabId = tabId ||
|
|
180
|
-
DebuggerManager.attach(attachTabId).then(function(success) {
|
|
181
|
-
send({ type: 'attach_result', tabId: attachTabId, success: success });
|
|
185
|
+
var attachTabId = tabId || self.state.getCurrentTabId();
|
|
186
|
+
DebuggerManager.attach(attachTabId, self.state).then(function(success) {
|
|
187
|
+
self.send({ type: 'attach_result', tabId: attachTabId, success: success });
|
|
182
188
|
});
|
|
183
189
|
break;
|
|
184
190
|
|
|
185
191
|
case 'detach':
|
|
186
|
-
var detachTabId = tabId ||
|
|
187
|
-
DebuggerManager.detach(detachTabId).then(function() {
|
|
188
|
-
send({ type: 'detach_result', tabId: detachTabId, success: true });
|
|
192
|
+
var detachTabId = tabId || self.state.getCurrentTabId();
|
|
193
|
+
DebuggerManager.detach(detachTabId, self.state).then(function() {
|
|
194
|
+
self.send({ type: 'detach_result', tabId: detachTabId, success: true });
|
|
189
195
|
});
|
|
190
196
|
break;
|
|
191
197
|
|
|
192
198
|
case 'browser-close':
|
|
193
|
-
|
|
199
|
+
self._handleBrowserClose(message.sessions, message.clientId);
|
|
194
200
|
break;
|
|
195
201
|
|
|
196
202
|
case 'client-connected':
|
|
197
|
-
Logger.info('[WS] Client connected, resuming event forwarding');
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
Logger.info('[WS:' + self.connectionId + '] Client connected, resuming event forwarding');
|
|
204
|
+
self.state.setHasConnectedClient(true);
|
|
205
|
+
self.state.addCDPClient(message.clientId, message.clientId);
|
|
206
|
+
self._createGroupForClient(message.clientId);
|
|
207
|
+
self._broadcastStateUpdate();
|
|
202
208
|
break;
|
|
203
209
|
|
|
204
210
|
case 'client-disconnected':
|
|
205
|
-
Logger.info('[WS] Client disconnected:', message.clientId);
|
|
211
|
+
Logger.info('[WS:' + self.connectionId + '] Client disconnected:', message.clientId);
|
|
206
212
|
var discClientId = message.clientId;
|
|
207
|
-
|
|
213
|
+
self._groupCreationPending.delete(discClientId);
|
|
214
|
+
self._closeTabGroupByClientId(discClientId).then(function() {
|
|
208
215
|
return new Promise(function(resolve) {
|
|
209
|
-
|
|
216
|
+
self._closeTabsByClientId(discClientId, resolve);
|
|
210
217
|
});
|
|
211
218
|
}).then(function() {
|
|
212
|
-
var preExistingTabs =
|
|
213
|
-
var clientPreExisting = preExistingTabs.filter(function(
|
|
214
|
-
return
|
|
219
|
+
var preExistingTabs = self.state.getPreExistingTabs();
|
|
220
|
+
var clientPreExisting = preExistingTabs.filter(function(tid) {
|
|
221
|
+
return self.state.getClientIdByTabId(tid) === discClientId;
|
|
215
222
|
});
|
|
216
|
-
clientPreExisting.forEach(function(
|
|
217
|
-
chrome.debugger.detach({ tabId:
|
|
218
|
-
|
|
223
|
+
clientPreExisting.forEach(function(tid) {
|
|
224
|
+
chrome.debugger.detach({ tabId: tid }).catch(function() {});
|
|
225
|
+
self.state.removeAttachedTab(tid);
|
|
219
226
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
|
|
227
|
+
self.state.clearPreExistingTabsForClient(discClientId);
|
|
228
|
+
self.state.removeCDPClient(discClientId);
|
|
229
|
+
if (self.state.getCDPClients().length === 0) {
|
|
230
|
+
self.state.setHasConnectedClient(false);
|
|
224
231
|
}
|
|
225
|
-
|
|
232
|
+
self._broadcastStateUpdate();
|
|
226
233
|
});
|
|
227
234
|
break;
|
|
228
|
-
|
|
235
|
+
|
|
229
236
|
case 'client-list':
|
|
230
|
-
Logger.info('[WS] Received client list:', message.clients);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
Logger.info('[WS:' + self.connectionId + '] Received client list:', message.clients);
|
|
238
|
+
self.state.setCDPClients(message.clients || []);
|
|
239
|
+
self.state.setHasConnectedClient((message.clients || []).length > 0);
|
|
240
|
+
self._broadcastStateUpdate();
|
|
234
241
|
break;
|
|
235
242
|
|
|
236
243
|
case 'plugin-disconnected':
|
|
237
|
-
Logger.info('[WS] Plugin disconnected from server');
|
|
244
|
+
Logger.info('[WS:' + self.connectionId + '] Plugin disconnected from server');
|
|
238
245
|
break;
|
|
239
246
|
|
|
240
247
|
case 'server-restart':
|
|
241
|
-
Logger.info('[WS] Server restart detected, cleaning up...');
|
|
242
|
-
|
|
248
|
+
Logger.info('[WS:' + self.connectionId + '] Server restart detected, cleaning up...');
|
|
249
|
+
self._handleServerRestart();
|
|
243
250
|
break;
|
|
244
251
|
|
|
245
252
|
case 'cdp':
|
|
@@ -250,8 +257,9 @@ var WebSocketManager = (function() {
|
|
|
250
257
|
params: params,
|
|
251
258
|
tabId: tabId,
|
|
252
259
|
sessionId: sessionId,
|
|
253
|
-
clientId: message.__clientId
|
|
254
|
-
|
|
260
|
+
clientId: message.__clientId,
|
|
261
|
+
connectionId: self.connectionId
|
|
262
|
+
}, self.state, self);
|
|
255
263
|
}
|
|
256
264
|
break;
|
|
257
265
|
|
|
@@ -263,111 +271,115 @@ var WebSocketManager = (function() {
|
|
|
263
271
|
params: params,
|
|
264
272
|
tabId: tabId,
|
|
265
273
|
sessionId: sessionId,
|
|
266
|
-
clientId: message.__clientId
|
|
267
|
-
|
|
274
|
+
clientId: message.__clientId,
|
|
275
|
+
connectionId: self.connectionId
|
|
276
|
+
}, self.state, self);
|
|
268
277
|
}
|
|
269
278
|
}
|
|
270
|
-
}
|
|
279
|
+
};
|
|
271
280
|
|
|
272
|
-
function
|
|
281
|
+
WebSocketConnection.prototype._closeTabGroupByClientId = function(clientId) {
|
|
282
|
+
var self = this;
|
|
273
283
|
if (!clientId) return Promise.resolve();
|
|
274
|
-
|
|
275
|
-
Logger.info('[WS] Closing tab group for client:', clientId);
|
|
276
|
-
|
|
284
|
+
|
|
285
|
+
Logger.info('[WS:' + self.connectionId + '] Closing tab group for client:', clientId);
|
|
286
|
+
|
|
277
287
|
return new Promise(function(resolve) {
|
|
278
288
|
var timeoutId = setTimeout(function() {
|
|
279
|
-
Logger.warn('[WS] closeTabGroupByClientId timeout for client:', clientId, '— forcing cleanup');
|
|
280
|
-
|
|
289
|
+
Logger.warn('[WS:' + self.connectionId + '] closeTabGroupByClientId timeout for client:', clientId, '— forcing cleanup');
|
|
290
|
+
self._cleanupStaleState(clientId);
|
|
281
291
|
resolve();
|
|
282
292
|
}, 5000);
|
|
283
|
-
|
|
284
|
-
var groupId =
|
|
285
|
-
|
|
293
|
+
|
|
294
|
+
var groupId = self.state.getGroupIdForClient(clientId);
|
|
295
|
+
|
|
286
296
|
if (groupId) {
|
|
287
|
-
|
|
297
|
+
self._closeGroupById(groupId, clientId, function() {
|
|
288
298
|
clearTimeout(timeoutId);
|
|
289
|
-
|
|
299
|
+
self._cleanupStaleState(clientId);
|
|
290
300
|
resolve();
|
|
291
301
|
});
|
|
292
302
|
} else {
|
|
293
|
-
var baseName = CDPUtils.getGroupBaseName(clientId);
|
|
303
|
+
var baseName = CDPUtils.getGroupBaseName(clientId, self.config ? self.config.tag : null);
|
|
294
304
|
chrome.tabGroups.query({}, function(allGroups) {
|
|
295
305
|
var match = CDPUtils.findGroupByName(allGroups, baseName);
|
|
296
306
|
if (match) {
|
|
297
|
-
|
|
307
|
+
self._closeGroupById(match.id, clientId, function() {
|
|
298
308
|
clearTimeout(timeoutId);
|
|
299
|
-
|
|
309
|
+
self._cleanupStaleState(clientId);
|
|
300
310
|
resolve();
|
|
301
311
|
});
|
|
302
312
|
} else {
|
|
303
|
-
Logger.info('[WS] No tab group found, closing tabs by clientId:', clientId);
|
|
304
|
-
|
|
313
|
+
Logger.info('[WS:' + self.connectionId + '] No tab group found, closing tabs by clientId:', clientId);
|
|
314
|
+
self._closeTabsByClientId(clientId, function() {
|
|
305
315
|
clearTimeout(timeoutId);
|
|
306
|
-
|
|
316
|
+
self._cleanupStaleState(clientId);
|
|
307
317
|
resolve();
|
|
308
318
|
});
|
|
309
319
|
}
|
|
310
320
|
});
|
|
311
321
|
}
|
|
312
322
|
});
|
|
313
|
-
}
|
|
323
|
+
};
|
|
314
324
|
|
|
315
|
-
function
|
|
325
|
+
WebSocketConnection.prototype._cleanupStaleState = function(clientId) {
|
|
316
326
|
if (!clientId) return;
|
|
317
|
-
var
|
|
327
|
+
var self = this;
|
|
328
|
+
var attachedTabs = self.state.getAttachedTabIds();
|
|
318
329
|
attachedTabs.forEach(function(tabId) {
|
|
319
|
-
if (
|
|
320
|
-
|
|
330
|
+
if (self.state.getClientIdByTabId(tabId) === clientId) {
|
|
331
|
+
self.state.removeTabIdToClientId(tabId);
|
|
321
332
|
}
|
|
322
333
|
});
|
|
323
|
-
}
|
|
334
|
+
};
|
|
324
335
|
|
|
325
|
-
function
|
|
326
|
-
|
|
336
|
+
WebSocketConnection.prototype._closeGroupById = function(groupId, clientId, resolve) {
|
|
337
|
+
var self = this;
|
|
338
|
+
Logger.info('[WS:' + self.connectionId + '] closeGroupById: groupId=' + groupId + ' clientId=' + clientId);
|
|
327
339
|
chrome.tabs.query({ groupId: groupId }, function(tabs) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
340
|
+
if (!tabs || tabs.length === 0) {
|
|
341
|
+
Logger.info('[WS:' + self.connectionId + '] No tabs in group:', groupId);
|
|
342
|
+
self.state.removeGroupForClient(clientId);
|
|
343
|
+
self._removeEmptyGroup(groupId);
|
|
344
|
+
resolve();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
var ownTabs = tabs.filter(function(tab) {
|
|
349
|
+
return self.state.getClientIdByTabId(tab.id) === clientId;
|
|
350
|
+
});
|
|
351
|
+
var otherTabs = tabs.filter(function(tab) {
|
|
352
|
+
return self.state.getClientIdByTabId(tab.id) !== clientId;
|
|
353
|
+
});
|
|
354
|
+
var tabIds = ownTabs.map(function(tab) { return tab.id; });
|
|
355
|
+
Logger.info('[WS:' + self.connectionId + '] Closing ' + tabIds.length + ' tabs in group (skipping ' + otherTabs.length + ' from other clients):', groupId);
|
|
356
|
+
|
|
357
|
+
if (tabIds.length === 0) {
|
|
358
|
+
Logger.info('[WS:' + self.connectionId + '] No own tabs to close in group:', groupId);
|
|
359
|
+
self.state.removeGroupForClient(clientId);
|
|
360
|
+
resolve();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
chrome.tabs.remove(tabIds, function() {
|
|
365
|
+
if (chrome.runtime.lastError) {
|
|
366
|
+
Logger.error('[WS:' + self.connectionId + '] Failed to close tabs:', chrome.runtime.lastError.message);
|
|
367
|
+
} else {
|
|
368
|
+
Logger.info('[WS:' + self.connectionId + '] Successfully closed ' + tabIds.length + ' tabs');
|
|
350
369
|
}
|
|
351
370
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
Logger.error('[WS] Failed to close tabs:', chrome.runtime.lastError.message);
|
|
355
|
-
} else {
|
|
356
|
-
Logger.info('[WS] Successfully closed ' + tabIds.length + ' tabs');
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
tabIds.forEach(function(tabId) {
|
|
360
|
-
chrome.debugger.detach({ tabId: tabId }).catch(function() {});
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
State.removeGroupForClient(clientId);
|
|
364
|
-
removeEmptyGroup(groupId);
|
|
365
|
-
resolve();
|
|
371
|
+
tabIds.forEach(function(tabId) {
|
|
372
|
+
chrome.debugger.detach({ tabId: tabId }).catch(function() {});
|
|
366
373
|
});
|
|
374
|
+
|
|
375
|
+
self.state.removeGroupForClient(clientId);
|
|
376
|
+
self._removeEmptyGroup(groupId);
|
|
377
|
+
resolve();
|
|
378
|
+
});
|
|
367
379
|
});
|
|
368
|
-
}
|
|
380
|
+
};
|
|
369
381
|
|
|
370
|
-
function
|
|
382
|
+
WebSocketConnection.prototype._removeEmptyGroup = function(groupId) {
|
|
371
383
|
if (!groupId || !chrome.tabGroups) return;
|
|
372
384
|
setTimeout(function() {
|
|
373
385
|
chrome.tabGroups.query({ groupId: groupId }, function(groups) {
|
|
@@ -386,166 +398,173 @@ var WebSocketManager = (function() {
|
|
|
386
398
|
}
|
|
387
399
|
});
|
|
388
400
|
}, 500);
|
|
389
|
-
}
|
|
401
|
+
};
|
|
390
402
|
|
|
391
|
-
function
|
|
392
|
-
var
|
|
393
|
-
var
|
|
403
|
+
WebSocketConnection.prototype._closeTabsByClientId = function(clientId, resolve) {
|
|
404
|
+
var self = this;
|
|
405
|
+
var attachedTabs = self.state.getAttachedTabIds();
|
|
406
|
+
var cdpCreatedTabs = self.state.getCDPCreatedTabIds();
|
|
394
407
|
var tabsToClose = [];
|
|
395
408
|
var tabsToCloseSet = new Set();
|
|
396
|
-
|
|
397
|
-
Logger.info('[WS] closeTabsByClientId: clientId=' + clientId + ' attachedTabs=' + JSON.stringify(attachedTabs) + ' cdpCreatedTabs=' + JSON.stringify(cdpCreatedTabs));
|
|
398
|
-
|
|
409
|
+
|
|
410
|
+
Logger.info('[WS:' + self.connectionId + '] closeTabsByClientId: clientId=' + clientId + ' attachedTabs=' + JSON.stringify(attachedTabs) + ' cdpCreatedTabs=' + JSON.stringify(cdpCreatedTabs));
|
|
411
|
+
|
|
399
412
|
attachedTabs.forEach(function(tabId) {
|
|
400
|
-
var tabClientId =
|
|
401
|
-
var isPre =
|
|
402
|
-
var isCDP =
|
|
403
|
-
Logger.info('[WS] [attached] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isCDP=' + isCDP);
|
|
413
|
+
var tabClientId = self.state.getClientIdByTabId(tabId);
|
|
414
|
+
var isPre = self.state.isPreExistingTab(tabId);
|
|
415
|
+
var isCDP = self.state.isCDPCreatedTab(tabId);
|
|
416
|
+
Logger.info('[WS:' + self.connectionId + '] [attached] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isCDP=' + isCDP);
|
|
404
417
|
if (tabClientId === clientId && !isPre) {
|
|
405
418
|
tabsToCloseSet.add(tabId);
|
|
406
419
|
}
|
|
407
420
|
});
|
|
408
|
-
|
|
421
|
+
|
|
409
422
|
cdpCreatedTabs.forEach(function(tabId) {
|
|
410
423
|
if (tabsToCloseSet.has(tabId)) return;
|
|
411
|
-
|
|
412
|
-
var tabClientId =
|
|
413
|
-
var isPre =
|
|
414
|
-
Logger.info('[WS] [cdpCreated] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isAttached=' + attachedTabs.includes(tabId));
|
|
424
|
+
|
|
425
|
+
var tabClientId = self.state.getClientIdByTabId(tabId);
|
|
426
|
+
var isPre = self.state.isPreExistingTab(tabId);
|
|
427
|
+
Logger.info('[WS:' + self.connectionId + '] [cdpCreated] tabId=' + tabId + ' clientId=' + tabClientId + ' isPre=' + isPre + ' isAttached=' + attachedTabs.includes(tabId));
|
|
415
428
|
if (tabClientId === clientId && !isPre && !attachedTabs.includes(tabId)) {
|
|
416
429
|
tabsToCloseSet.add(tabId);
|
|
417
|
-
Logger.info('[WS] -> Added to close list (not yet attached)');
|
|
430
|
+
Logger.info('[WS:' + self.connectionId + '] -> Added to close list (not yet attached)');
|
|
418
431
|
}
|
|
419
432
|
});
|
|
420
|
-
|
|
433
|
+
|
|
421
434
|
tabsToClose = Array.from(tabsToCloseSet);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
435
|
+
Logger.info('[WS:' + self.connectionId + '] closeTabsByClientId: will close ' + tabsToClose.length + ' tabs');
|
|
436
|
+
|
|
425
437
|
if (tabsToClose.length === 0) {
|
|
426
|
-
Logger.info('[WS] No tabs found for clientId:', clientId);
|
|
438
|
+
Logger.info('[WS:' + self.connectionId + '] No tabs found for clientId:', clientId);
|
|
427
439
|
resolve();
|
|
428
440
|
return;
|
|
429
441
|
}
|
|
430
442
|
|
|
431
|
-
|
|
432
|
-
}
|
|
443
|
+
self._doCloseTabs(tabsToClose, clientId, resolve);
|
|
444
|
+
};
|
|
433
445
|
|
|
434
|
-
function
|
|
446
|
+
WebSocketConnection.prototype._doCloseTabs = function(tabIds, clientId, resolve) {
|
|
447
|
+
var self = this;
|
|
435
448
|
if (tabIds.length === 0) {
|
|
436
449
|
resolve();
|
|
437
450
|
return;
|
|
438
451
|
}
|
|
439
|
-
Logger.info('[WS] Closing ' + tabIds.length + ' attached tabs for clientId:', clientId);
|
|
452
|
+
Logger.info('[WS:' + self.connectionId + '] Closing ' + tabIds.length + ' attached tabs for clientId:', clientId);
|
|
440
453
|
var pending = tabIds.length;
|
|
441
454
|
tabIds.forEach(function(tabId) {
|
|
442
455
|
chrome.tabs.remove(tabId, function() {
|
|
443
456
|
if (chrome.runtime.lastError) {
|
|
444
|
-
Logger.info('[WS] Tab already closed:', tabId);
|
|
457
|
+
Logger.info('[WS:' + self.connectionId + '] Tab already closed:', tabId);
|
|
445
458
|
}
|
|
446
459
|
chrome.debugger.detach({ tabId: tabId }).catch(function() {});
|
|
447
|
-
|
|
460
|
+
self.state.removeAttachedTab(tabId);
|
|
448
461
|
pending--;
|
|
449
|
-
if (pending === 0)
|
|
450
|
-
resolve();
|
|
451
|
-
}
|
|
462
|
+
if (pending === 0) resolve();
|
|
452
463
|
});
|
|
453
464
|
});
|
|
454
|
-
}
|
|
465
|
+
};
|
|
455
466
|
|
|
456
|
-
function
|
|
467
|
+
WebSocketConnection.prototype._createGroupForClient = function(clientId) {
|
|
468
|
+
var self = this;
|
|
457
469
|
if (!clientId || !chrome.tabGroups) return;
|
|
458
470
|
|
|
459
|
-
|
|
471
|
+
if (self._groupCreationPending.has(clientId)) {
|
|
472
|
+
Logger.info('[WS:' + self.connectionId + '] Group creation already pending for client:', clientId);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
var existingGroupId = self.state.getGroupIdForClient(clientId);
|
|
460
477
|
if (existingGroupId) {
|
|
461
|
-
Logger.info('[WS] Group already cached for client:', clientId, 'groupId:', existingGroupId);
|
|
478
|
+
Logger.info('[WS:' + self.connectionId + '] Group already cached for client:', clientId, 'groupId:', existingGroupId);
|
|
462
479
|
return;
|
|
463
480
|
}
|
|
464
481
|
|
|
465
|
-
|
|
482
|
+
self._groupCreationPending.add(clientId);
|
|
483
|
+
|
|
484
|
+
var baseName = CDPUtils.getGroupBaseName(clientId, self.config ? self.config.tag : null);
|
|
466
485
|
chrome.tabs.query({ currentWindow: true }, function(tabs) {
|
|
467
486
|
if (!tabs || tabs.length === 0) {
|
|
468
|
-
Logger.warn('[WS] No tabs found for group creation');
|
|
487
|
+
Logger.warn('[WS:' + self.connectionId + '] No tabs found for group creation');
|
|
488
|
+
self._groupCreationPending.delete(clientId);
|
|
469
489
|
return;
|
|
470
490
|
}
|
|
471
491
|
var windowId = tabs[0].windowId;
|
|
472
492
|
chrome.tabs.group({ createProperties: { windowId: windowId } }, function(groupId) {
|
|
473
493
|
if (chrome.runtime.lastError) {
|
|
474
|
-
Logger.warn('[WS] Failed to create group on connect:', chrome.runtime.lastError.message);
|
|
494
|
+
Logger.warn('[WS:' + self.connectionId + '] Failed to create group on connect:', chrome.runtime.lastError.message);
|
|
495
|
+
self._groupCreationPending.delete(clientId);
|
|
475
496
|
return;
|
|
476
497
|
}
|
|
498
|
+
self._groupCreationPending.delete(clientId);
|
|
477
499
|
chrome.tabGroups.update(groupId, {
|
|
478
500
|
title: baseName,
|
|
479
501
|
color: CDPUtils.getGroupColorForClient(clientId),
|
|
480
502
|
collapsed: true
|
|
481
503
|
}, function() {
|
|
482
504
|
if (chrome.runtime.lastError) {
|
|
483
|
-
Logger.warn('[WS] Failed to set group title:', chrome.runtime.lastError.message);
|
|
505
|
+
Logger.warn('[WS:' + self.connectionId + '] Failed to set group title:', chrome.runtime.lastError.message);
|
|
484
506
|
}
|
|
485
507
|
});
|
|
486
|
-
|
|
487
|
-
Logger.info('[WS] Created group for client:', clientId, 'groupId:', groupId, 'title:', baseName);
|
|
508
|
+
self.state.setGroupIdForClient(clientId, groupId);
|
|
509
|
+
Logger.info('[WS:' + self.connectionId + '] Created group for client:', clientId, 'groupId:', groupId, 'title:', baseName);
|
|
488
510
|
});
|
|
489
511
|
});
|
|
490
|
-
}
|
|
512
|
+
};
|
|
491
513
|
|
|
492
|
-
function
|
|
493
|
-
|
|
514
|
+
WebSocketConnection.prototype._handleServerRestart = function() {
|
|
515
|
+
var self = this;
|
|
516
|
+
Logger.info('[WS:' + self.connectionId + '] Server restarted, cleaning up all state...');
|
|
494
517
|
|
|
495
|
-
var attachedTabIds =
|
|
518
|
+
var attachedTabIds = self.state.getAttachedTabIds();
|
|
496
519
|
var promises = attachedTabIds.map(function(tabId) {
|
|
497
520
|
return chrome.debugger.detach({ tabId: tabId }).catch(function(e) {
|
|
498
|
-
Logger.info('[WS] Detach failed for tab', tabId, ':', e.message);
|
|
521
|
+
Logger.info('[WS:' + self.connectionId + '] Detach failed for tab', tabId, ':', e.message);
|
|
499
522
|
});
|
|
500
523
|
});
|
|
501
524
|
|
|
502
525
|
Promise.all(promises).then(function() {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
Logger.info('[WS] State cleaned up after server restart');
|
|
526
|
+
self.state.clearAllState();
|
|
527
|
+
self.state.persist(null, false);
|
|
528
|
+
Logger.info('[WS:' + self.connectionId + '] State cleaned up after server restart');
|
|
506
529
|
});
|
|
507
|
-
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
WebSocketConnection.prototype._handleBrowserClose = function(sessions, clientId) {
|
|
533
|
+
var self = this;
|
|
534
|
+
Logger.info('[WS:' + self.connectionId + '] Browser.close received, cleaning up... clientId:', clientId);
|
|
508
535
|
|
|
509
|
-
|
|
510
|
-
Logger.info('[WS] Browser.close received, cleaning up... clientId:', clientId);
|
|
511
|
-
|
|
512
|
-
closeTabGroupByClientId(clientId).then(function() {
|
|
536
|
+
self._closeTabGroupByClientId(clientId).then(function() {
|
|
513
537
|
return new Promise(function(resolve) {
|
|
514
|
-
|
|
538
|
+
self._closeTabsByClientId(clientId, resolve);
|
|
515
539
|
});
|
|
516
540
|
}).then(function() {
|
|
517
|
-
var preExistingTabs =
|
|
541
|
+
var preExistingTabs = self.state.getPreExistingTabs();
|
|
518
542
|
var clientPreExisting = preExistingTabs.filter(function(tabId) {
|
|
519
|
-
return
|
|
543
|
+
return self.state.getClientIdByTabId(tabId) === clientId;
|
|
520
544
|
});
|
|
521
545
|
clientPreExisting.forEach(function(tabId) {
|
|
522
546
|
chrome.debugger.detach({ tabId: tabId }).catch(function() {});
|
|
523
|
-
|
|
547
|
+
self.state.removeAttachedTab(tabId);
|
|
524
548
|
});
|
|
525
|
-
|
|
549
|
+
self.state.clearPreExistingTabsForClient(clientId);
|
|
526
550
|
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
|
|
551
|
+
self.state.removeCDPClient(clientId);
|
|
552
|
+
if (self.state.getCDPClients().length === 0) {
|
|
553
|
+
self.state.clearAllState();
|
|
554
|
+
self.state.persist(null, false);
|
|
531
555
|
}
|
|
532
|
-
|
|
533
|
-
Logger.info('[WS] Browser.close cleanup complete for client:', clientId);
|
|
556
|
+
self._broadcastStateUpdate();
|
|
557
|
+
Logger.info('[WS:' + self.connectionId + '] Browser.close cleanup complete for client:', clientId);
|
|
534
558
|
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function setBadgeStatus(status) {
|
|
538
|
-
var colors = Config.BADGE_COLORS;
|
|
539
|
-
chrome.action.setBadgeText({ text: status });
|
|
540
|
-
chrome.action.setBadgeBackgroundColor({ color: colors[status] || colors.OFF });
|
|
541
|
-
}
|
|
559
|
+
};
|
|
542
560
|
|
|
543
|
-
function
|
|
544
|
-
var
|
|
561
|
+
WebSocketConnection.prototype._broadcastStateUpdate = function() {
|
|
562
|
+
var self = this;
|
|
563
|
+
var ws = self.state.getWs();
|
|
545
564
|
var isConnected = ws && ws.readyState === WebSocket.OPEN;
|
|
546
|
-
var cdpClients =
|
|
547
|
-
var attachedTabIds =
|
|
548
|
-
|
|
565
|
+
var cdpClients = self.state.getCDPClients() || [];
|
|
566
|
+
var attachedTabIds = self.state.getAttachedTabIds();
|
|
567
|
+
|
|
549
568
|
if (attachedTabIds.length === 0) {
|
|
550
569
|
chrome.runtime.sendMessage({
|
|
551
570
|
type: 'stateUpdate',
|
|
@@ -578,14 +597,61 @@ var WebSocketManager = (function() {
|
|
|
578
597
|
}
|
|
579
598
|
});
|
|
580
599
|
});
|
|
581
|
-
}
|
|
600
|
+
};
|
|
582
601
|
|
|
583
|
-
function
|
|
602
|
+
WebSocketConnection.prototype.getQueueStats = function() {
|
|
584
603
|
return {
|
|
585
|
-
queueLength: _sendQueue.length,
|
|
586
|
-
maxQueueSize: _maxQueueSize,
|
|
587
|
-
bufferThreshold: _bufferThreshold
|
|
604
|
+
queueLength: this._sendQueue.length,
|
|
605
|
+
maxQueueSize: this._maxQueueSize,
|
|
606
|
+
bufferThreshold: this._bufferThreshold
|
|
588
607
|
};
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
return WebSocketConnection;
|
|
611
|
+
})();
|
|
612
|
+
|
|
613
|
+
function setBadgeStatus(status) {
|
|
614
|
+
var colors = Config.BADGE_COLORS;
|
|
615
|
+
chrome.action.setBadgeText({ text: status });
|
|
616
|
+
chrome.action.setBadgeBackgroundColor({ color: colors[status] || colors.OFF });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
var WebSocketManager = (function() {
|
|
620
|
+
function connect() {
|
|
621
|
+
ConnectionManager.connectAll();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function send(message) {
|
|
625
|
+
var sent = false;
|
|
626
|
+
ConnectionManager.forEachConnection(function(entry) {
|
|
627
|
+
if (entry.wsManager.send(message)) {
|
|
628
|
+
sent = true;
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
return sent;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function scheduleReconnect() {
|
|
635
|
+
ConnectionManager.forEachConnection(function(entry) {
|
|
636
|
+
entry.wsManager._scheduleReconnect();
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function getQueueStats() {
|
|
641
|
+
var stats = [];
|
|
642
|
+
ConnectionManager.forEachConnection(function(entry) {
|
|
643
|
+
stats.push({
|
|
644
|
+
connectionId: entry.id,
|
|
645
|
+
stats: entry.wsManager.getQueueStats()
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
return stats;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function processQueue() {
|
|
652
|
+
ConnectionManager.forEachConnection(function(entry) {
|
|
653
|
+
entry.wsManager._processQueue();
|
|
654
|
+
});
|
|
589
655
|
}
|
|
590
656
|
|
|
591
657
|
return {
|