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.
@@ -1,12 +1,12 @@
1
1
  (function() {
2
2
  var state = {
3
3
  connected: false,
4
- serverAddress: 'ws://localhost:9221/plugin',
5
4
  cdpClients: [],
6
5
  attachedPages: [],
7
6
  logs: [],
8
7
  startTime: Date.now(),
9
- cdpCommandCount: 0
8
+ cdpCommandCount: 0,
9
+ connectionStatuses: {}
10
10
  };
11
11
 
12
12
  var elements = {
@@ -21,10 +21,14 @@
21
21
  clientList: document.getElementById('clientList'),
22
22
  pageList: document.getElementById('pageList'),
23
23
  activityLog: document.getElementById('activityLog'),
24
- serverAddress: document.getElementById('serverAddress'),
25
- connectBtn: document.getElementById('connectBtn'),
26
24
  refreshBtn: document.getElementById('refreshBtn'),
27
- toast: document.getElementById('toast')
25
+ toast: document.getElementById('toast'),
26
+ connConfigList: document.getElementById('connConfigList'),
27
+ newConnTag: document.getElementById('newConnTag'),
28
+ newConnUrl: document.getElementById('newConnUrl'),
29
+ addConnBtn: document.getElementById('addConnBtn'),
30
+ autoMuteToggle: document.getElementById('autoMuteToggle'),
31
+ pluginIdDisplay: document.getElementById('pluginIdDisplay')
28
32
  };
29
33
 
30
34
  function showToast(message, type) {
@@ -53,7 +57,7 @@
53
57
  elements.activeConnections.textContent = state.cdpClients.length;
54
58
  elements.controlledPages.textContent = state.attachedPages.length;
55
59
  elements.cdpCommands.textContent = state.cdpCommandCount;
56
-
60
+
57
61
  var elapsed = Math.floor((Date.now() - state.startTime) / 1000);
58
62
  if (elapsed < 60) {
59
63
  elements.uptime.textContent = elapsed + 's';
@@ -62,14 +66,51 @@
62
66
  } else {
63
67
  elements.uptime.textContent = Math.floor(elapsed / 3600) + 'h ' + Math.floor((elapsed % 3600) / 60) + 'm';
64
68
  }
65
-
69
+
66
70
  elements.clientCount.textContent = state.cdpClients.length + ' 个';
67
71
  elements.pageCount.textContent = state.attachedPages.length + ' 个';
68
72
  }
69
73
 
74
+ function getStatusClass(connId, enabled) {
75
+ if (!enabled) return 'disabled';
76
+ var s = state.connectionStatuses[connId];
77
+ if (s === 'connected') return 'connected';
78
+ if (s === 'error') return 'error';
79
+ return 'disabled';
80
+ }
81
+
82
+ function renderConnections(connections) {
83
+ if (!connections || connections.length === 0) {
84
+ elements.connConfigList.innerHTML =
85
+ '<div class="empty-state">' +
86
+ '<div class="icon">🔗</div>' +
87
+ '<div class="title">暂无连接配置</div>' +
88
+ '<div class="desc">在下方添加一个 WebSocket 连接</div>' +
89
+ '</div>';
90
+ return;
91
+ }
92
+
93
+ var html = '';
94
+ connections.forEach(function(conn) {
95
+ var statusClass = getStatusClass(conn.id, conn.enabled);
96
+ var isActive = conn.enabled && statusClass === 'connected';
97
+ html +=
98
+ '<div class="conn-config-item' + (isActive ? ' active' : '') + '" data-id="' + conn.id + '">' +
99
+ '<input type="checkbox" class="conn-toggle" data-id="' + conn.id + '"' + (conn.enabled ? ' checked' : '') + ' title="启用/禁用">' +
100
+ '<span class="status-dot ' + statusClass + '" title="' + statusClass + '"></span>' +
101
+ '<div class="conn-config-info">' +
102
+ '<div class="conn-config-tag">' + escapeHtml(conn.tag) + '</div>' +
103
+ '<div class="conn-config-url" title="' + escapeAttr(conn.url) + '">' + escapeHtml(conn.url) + '</div>' +
104
+ '</div>' +
105
+ '<button class="btn-delete" data-id="' + conn.id + '" title="删除">删除</button>' +
106
+ '</div>';
107
+ });
108
+ elements.connConfigList.innerHTML = html;
109
+ }
110
+
70
111
  function renderClients() {
71
112
  if (state.cdpClients.length === 0) {
72
- elements.clientList.innerHTML =
113
+ elements.clientList.innerHTML =
73
114
  '<div class="empty-state">' +
74
115
  '<div class="icon">🔌</div>' +
75
116
  '<div class="title">暂无客户端</div>' +
@@ -83,8 +124,8 @@
83
124
  var clientId = client.id || '';
84
125
  var shortId = clientId.length > 25 ? clientId.substring(0, 25) + '...' : clientId;
85
126
  var connectedAt = client.connectedAt ? new Date(client.connectedAt).toLocaleTimeString() : 'N/A';
86
-
87
- html +=
127
+
128
+ html +=
88
129
  '<div class="connection-item active">' +
89
130
  '<div class="status-indicator online"></div>' +
90
131
  '<div class="connection-info">' +
@@ -93,8 +134,8 @@
93
134
  '<span class="connection-tag playwright">CDP</span>' +
94
135
  '</div>' +
95
136
  '<div class="connection-details">' +
96
- '<span>🔗 Playwright/Puppeteer</span>' +
97
- '<span>⏰ ' + connectedAt + '</span>' +
137
+ '<span>Playwright/Puppeteer</span>' +
138
+ '<span>' + connectedAt + '</span>' +
98
139
  '</div>' +
99
140
  '</div>' +
100
141
  '</div>';
@@ -104,7 +145,7 @@
104
145
 
105
146
  function renderPages() {
106
147
  if (state.attachedPages.length === 0) {
107
- elements.pageList.innerHTML =
148
+ elements.pageList.innerHTML =
108
149
  '<div class="empty-state">' +
109
150
  '<div class="icon">📄</div>' +
110
151
  '<div class="title">暂无页面</div>' +
@@ -118,8 +159,8 @@
118
159
  var pageUrl = page.url || '';
119
160
  var displayUrl = pageUrl.length > 35 ? pageUrl.substring(0, 35) + '...' : pageUrl;
120
161
  var pageTitle = page.title || 'Untitled';
121
-
122
- html +=
162
+
163
+ html +=
123
164
  '<div class="connection-item active">' +
124
165
  '<div class="status-indicator online"></div>' +
125
166
  '<div class="connection-info">' +
@@ -141,7 +182,7 @@
141
182
 
142
183
  function renderLogs() {
143
184
  if (state.logs.length === 0) {
144
- elements.activityLog.innerHTML =
185
+ elements.activityLog.innerHTML =
145
186
  '<div class="empty-state">' +
146
187
  '<div class="icon">📋</div>' +
147
188
  '<div class="title">暂无日志</div>' +
@@ -152,7 +193,7 @@
152
193
 
153
194
  var html = '';
154
195
  state.logs.slice(0, 20).forEach(function(log) {
155
- html +=
196
+ html +=
156
197
  '<div class="log-item">' +
157
198
  '<div class="log-icon ' + log.type + '">' + log.icon + '</div>' +
158
199
  '<div class="log-content">' +
@@ -171,14 +212,14 @@
171
212
  action: '⚡',
172
213
  page: '📄'
173
214
  };
174
-
215
+
175
216
  state.logs.unshift({
176
217
  type: type,
177
218
  icon: icons[type] || '•',
178
219
  message: message,
179
220
  time: new Date().toLocaleTimeString()
180
221
  });
181
-
222
+
182
223
  renderLogs();
183
224
  }
184
225
 
@@ -189,10 +230,8 @@
189
230
  chrome.runtime.sendMessage({ type: 'getState' }, function(response) {
190
231
  if (response) {
191
232
  state.connected = response.connected || false;
192
- state.serverAddress = response.serverAddress || state.serverAddress;
193
233
  state.cdpClients = response.cdpClients || [];
194
234
  state.attachedPages = response.attachedPages || [];
195
- elements.serverAddress.value = state.serverAddress;
196
235
  updateStatus(state.connected);
197
236
  updateStats();
198
237
  renderClients();
@@ -207,7 +246,7 @@
207
246
  } catch (e) {
208
247
  console.error('[Config] Failed to fetch state:', e);
209
248
  }
210
-
249
+
211
250
  updateStatus(false);
212
251
  updateStats();
213
252
  renderClients();
@@ -216,73 +255,131 @@
216
255
  });
217
256
  }
218
257
 
219
- function saveAndConnect() {
220
- var address = elements.serverAddress.value.trim();
221
-
222
- if (!address) {
223
- showToast('请输入服务器地址', 'error');
224
- return;
225
- }
226
-
227
- state.serverAddress = address;
228
-
229
- if (typeof chrome !== 'undefined' && chrome.storage) {
230
- chrome.storage.local.set({ wsAddress: address });
231
- }
232
-
258
+ function loadConnectionStatuses() {
233
259
  if (typeof chrome !== 'undefined' && chrome.runtime) {
234
- try {
235
- chrome.runtime.sendMessage({
236
- type: 'connect',
237
- serverAddress: address
238
- });
239
- showToast('正在连接...');
240
- } catch (e) {
241
- showToast('连接请求发送失败', 'error');
242
- }
260
+ chrome.runtime.sendMessage({ type: 'get-connection-statuses' }, function(response) {
261
+ if (response && response.statuses) {
262
+ state.connectionStatuses = response.statuses;
263
+ }
264
+ loadAndRenderConnections();
265
+ });
243
266
  } else {
244
- showToast('请在扩展环境中使用', 'error');
267
+ loadAndRenderConnections();
245
268
  }
246
269
  }
247
270
 
248
- function refresh() {
249
- fetchState();
250
- showToast('已刷新');
271
+ function loadAndRenderConnections() {
272
+ if (typeof Config !== 'undefined' && Config.getConnections) {
273
+ Config.getConnections(function(connections) {
274
+ renderConnections(connections);
275
+ });
276
+ }
277
+ }
278
+
279
+ function escapeHtml(s) {
280
+ return (s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
281
+ }
282
+
283
+ function escapeAttr(s) {
284
+ return (s || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
251
285
  }
252
286
 
253
287
  function init() {
254
288
  if (typeof chrome !== 'undefined' && chrome.storage) {
255
- chrome.storage.local.get('wsAddress', function(result) {
256
- if (result.wsAddress) {
257
- state.serverAddress = result.wsAddress;
258
- elements.serverAddress.value = result.wsAddress;
259
- }
260
- });
261
289
  chrome.storage.local.get(['autoMute'], function(result) {
262
- document.getElementById('autoMuteToggle').checked = result.autoMute !== false;
290
+ elements.autoMuteToggle.checked = result.autoMute !== false;
291
+ });
292
+
293
+ Config.getPluginId(function(id) {
294
+ elements.pluginIdDisplay.textContent = id || '-';
263
295
  });
264
296
  }
265
-
266
- document.getElementById('autoMuteToggle').addEventListener('change', function(e) {
267
- chrome.storage.local.set({ autoMute: e.target.checked });
268
- addLog(e.target.checked ? '🔇 自动静音已开启' : '🔊 自动静音已关闭', 'action');
297
+
298
+ elements.autoMuteToggle.addEventListener('change', function(e) {
299
+ if (typeof Config !== 'undefined') {
300
+ Config.setAutoMute(e.target.checked);
301
+ }
302
+ addLog('action', e.target.checked ? '自动静音已开启' : '自动静音已关闭');
269
303
  });
270
304
 
271
305
  var versionBadge = document.getElementById('versionBadge');
272
- if (versionBadge) {
306
+ if (versionBadge && typeof chrome !== 'undefined' && chrome.runtime) {
273
307
  var manifest = chrome.runtime.getManifest();
274
308
  versionBadge.textContent = 'v' + manifest.version;
275
309
  }
276
-
310
+
311
+ loadConnectionStatuses();
277
312
  fetchState();
278
-
279
- setInterval(fetchState, 5000);
313
+
314
+ setInterval(function() {
315
+ fetchState();
316
+ loadConnectionStatuses();
317
+ }, 5000);
280
318
  setInterval(updateStats, 1000);
281
319
  }
282
320
 
283
- elements.connectBtn.addEventListener('click', saveAndConnect);
284
- elements.refreshBtn.addEventListener('click', refresh);
285
-
321
+ elements.addConnBtn.addEventListener('click', function() {
322
+ var tag = elements.newConnTag.value.trim();
323
+ var url = elements.newConnUrl.value.trim();
324
+
325
+ if (!tag) {
326
+ showToast('请输入连接名称', 'error');
327
+ return;
328
+ }
329
+ if (!url) {
330
+ showToast('请输入 WebSocket 地址', 'error');
331
+ return;
332
+ }
333
+
334
+ if (typeof chrome !== 'undefined' && chrome.runtime) {
335
+ chrome.runtime.sendMessage({ type: 'add-connection', tag: tag, url: url }, function() {
336
+ elements.newConnTag.value = '';
337
+ elements.newConnUrl.value = '';
338
+ loadAndRenderConnections();
339
+ showToast('连接已添加');
340
+ });
341
+ } else if (typeof Config !== 'undefined') {
342
+ Config.addConnection({ tag: tag, url: url }, function() {
343
+ elements.newConnTag.value = '';
344
+ elements.newConnUrl.value = '';
345
+ loadAndRenderConnections();
346
+ showToast('连接已添加');
347
+ });
348
+ }
349
+ });
350
+
351
+ elements.connConfigList.addEventListener('click', function(e) {
352
+ var toggleEl = e.target.closest('.conn-toggle');
353
+ if (toggleEl) {
354
+ var connId = toggleEl.dataset.id;
355
+ var enabled = toggleEl.checked;
356
+ if (typeof chrome !== 'undefined' && chrome.runtime) {
357
+ chrome.runtime.sendMessage({ type: 'toggle-connection', connectionId: connId, enabled: enabled }, function() {
358
+ loadAndRenderConnections();
359
+ showToast(enabled ? '连接已启用' : '连接已禁用');
360
+ });
361
+ }
362
+ return;
363
+ }
364
+
365
+ var deleteBtn = e.target.closest('.btn-delete');
366
+ if (deleteBtn) {
367
+ var deleteId = deleteBtn.dataset.id;
368
+ if (typeof chrome !== 'undefined' && chrome.runtime) {
369
+ chrome.runtime.sendMessage({ type: 'remove-connection', connectionId: deleteId }, function() {
370
+ loadAndRenderConnections();
371
+ showToast('连接已删除');
372
+ });
373
+ }
374
+ }
375
+ });
376
+
377
+ elements.refreshBtn.addEventListener('click', function() {
378
+ fetchState();
379
+ loadConnectionStatuses();
380
+ showToast('已刷新');
381
+ });
382
+
286
383
  elements.pageList.addEventListener('click', function(e) {
287
384
  var btn = e.target.closest('.action-btn');
288
385
  if (btn) {
@@ -299,7 +396,7 @@
299
396
  });
300
397
 
301
398
  if (typeof chrome !== 'undefined' && chrome.runtime) {
302
- chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
399
+ chrome.runtime.onMessage.addListener(function(message) {
303
400
  if (message.type === 'stateUpdate') {
304
401
  var wasConnected = state.connected;
305
402
  state.connected = message.connected;
@@ -309,13 +406,13 @@
309
406
  updateStats();
310
407
  renderClients();
311
408
  renderPages();
312
-
409
+
313
410
  if (state.connected && !wasConnected) {
314
411
  addLog('connect', '已连接到服务器');
315
412
  } else if (!state.connected && wasConnected) {
316
413
  addLog('disconnect', '已断开连接');
317
414
  }
318
-
415
+
319
416
  if (state.cdpClients.length > 0) {
320
417
  addLog('connect', state.cdpClients.length + ' 个 CDP 客户端在线');
321
418
  }
@@ -324,6 +421,8 @@
324
421
  }
325
422
  } else if (message.type === 'log') {
326
423
  addLog(message.logType, message.message);
424
+ } else if (message.type === 'connections-updated') {
425
+ loadAndRenderConnections();
327
426
  }
328
427
  });
329
428
  }
@@ -0,0 +1,120 @@
1
+ var ConnectionManager = (function() {
2
+ var _connections = new Map();
3
+
4
+ function init(connectionConfigs) {
5
+ var enabled = (connectionConfigs || []).filter(function(c) { return c.enabled; });
6
+ if (enabled.length === 0) {
7
+ enabled.push({
8
+ id: 'conn_default',
9
+ tag: 'default',
10
+ url: Config.WS_URL,
11
+ enabled: true
12
+ });
13
+ }
14
+ enabled.forEach(function(config) {
15
+ addConnection(config);
16
+ });
17
+ }
18
+
19
+ function addConnection(config) {
20
+ if (!config || !config.id) return;
21
+ if (_connections.has(config.id)) {
22
+ Logger.warn('[CM] Connection already exists:', config.id);
23
+ return;
24
+ }
25
+
26
+ var state = new ConnectionState(config.id);
27
+ var wsManager = new WebSocketConnection(config.id, state, config);
28
+
29
+ _connections.set(config.id, {
30
+ id: config.id,
31
+ config: config,
32
+ state: state,
33
+ wsManager: wsManager
34
+ });
35
+
36
+ Logger.info('[CM] Added connection:', config.id, config.url);
37
+ return wsManager;
38
+ }
39
+
40
+ function removeConnection(connectionId) {
41
+ var entry = _connections.get(connectionId);
42
+ if (!entry) return;
43
+
44
+ entry.state.clearReconnectTimer();
45
+ if (entry.state.ws) {
46
+ try { entry.state.ws.close(); } catch(e) {}
47
+ }
48
+ entry.state.clearAllState();
49
+ _connections.delete(connectionId);
50
+ Logger.info('[CM] Removed connection:', connectionId);
51
+ }
52
+
53
+ function getConnection(connectionId) {
54
+ return _connections.get(connectionId);
55
+ }
56
+
57
+ function getConnectionByTabId(tabId) {
58
+ var result = null;
59
+ _connections.forEach(function(entry) {
60
+ if (entry.state.isTabAttached(tabId) || entry.state.tabIdToClientId.has(tabId)) {
61
+ result = entry;
62
+ }
63
+ });
64
+ return result;
65
+ }
66
+
67
+ function getConnectionBySessionId(sessionId) {
68
+ var result = null;
69
+ _connections.forEach(function(entry) {
70
+ if (entry.state.sessionIdToTabId.has(sessionId)) {
71
+ result = entry;
72
+ }
73
+ });
74
+ return result;
75
+ }
76
+
77
+ function getAllConnections() {
78
+ return Array.from(_connections.values());
79
+ }
80
+
81
+ function forEachConnection(callback) {
82
+ _connections.forEach(function(entry, id) {
83
+ callback(entry, id);
84
+ });
85
+ }
86
+
87
+ function getPrimaryConnection() {
88
+ var first = _connections.values().next();
89
+ return first.done ? null : first.value;
90
+ }
91
+
92
+ function connectAll() {
93
+ _connections.forEach(function(entry) {
94
+ entry.wsManager.connect();
95
+ });
96
+ }
97
+
98
+ function disconnectAll() {
99
+ _connections.forEach(function(entry) {
100
+ if (entry.state.ws) {
101
+ try { entry.state.ws.close(); } catch(e) {}
102
+ }
103
+ entry.state.clearReconnectTimer();
104
+ });
105
+ }
106
+
107
+ return {
108
+ init: init,
109
+ addConnection: addConnection,
110
+ removeConnection: removeConnection,
111
+ getConnection: getConnection,
112
+ getConnectionByTabId: getConnectionByTabId,
113
+ getConnectionBySessionId: getConnectionBySessionId,
114
+ getAllConnections: getAllConnections,
115
+ forEachConnection: forEachConnection,
116
+ getPrimaryConnection: getPrimaryConnection,
117
+ connectAll: connectAll,
118
+ disconnectAll: disconnectAll
119
+ };
120
+ })();