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