cdp-tunnel 2.8.2 → 2.8.3

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.
@@ -453,21 +453,22 @@ importScripts('features/automation-badge.js');
453
453
  sendResponse({ success: true });
454
454
  } else if (message.type === 'get-connection-statuses') {
455
455
  Config.getConnections(function(connections) {
456
- var statuses = {};
457
- (connections || []).forEach(function(conn) {
458
- if (!conn.enabled) {
459
- statuses[conn.id] = 'disabled';
460
- } else {
456
+ var list = (connections || []).map(function(conn) {
457
+ var status = 'disabled';
458
+ var attachedCount = 0;
459
+ if (conn.enabled) {
461
460
  var entry = ConnectionManager.getConnection(conn.id);
462
461
  if (entry) {
463
462
  var ws = entry.state.getWs();
464
- statuses[conn.id] = (ws && ws.readyState === WebSocket.OPEN) ? 'connected' : 'error';
463
+ status = (ws && ws.readyState === WebSocket.OPEN) ? 'connected' : 'error';
464
+ attachedCount = entry.state.getAttachedTabIds().length;
465
465
  } else {
466
- statuses[conn.id] = 'error';
466
+ status = 'error';
467
467
  }
468
468
  }
469
+ return { id: conn.id, tag: conn.tag, url: conn.url, status: status, attachedCount: attachedCount };
469
470
  });
470
- sendResponse({ statuses: statuses });
471
+ sendResponse({ connections: list });
471
472
  });
472
473
  return true;
473
474
  } else if (message.type === 'add-connection') {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "2.8.2",
4
+ "version": "2.8.3",
5
5
  "description": "Chrome DevTools Protocol Bridge for Playwright/Puppeteer automation",
6
6
  "permissions": [
7
7
  "debugger",
@@ -4,76 +4,51 @@
4
4
  <meta charset="utf-8">
5
5
  <style>
6
6
  * { margin: 0; padding: 0; box-sizing: border-box; }
7
- body { width: 340px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #1a1a2e; color: #e0e0e0; font-size: 13px; }
7
+ body { width: 320px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #1a1a2e; color: #e0e0e0; font-size: 13px; }
8
8
 
9
- .header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid #2a2a4a; }
9
+ .header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid #2a2a4a; }
10
10
  .header h1 { font-size: 15px; font-weight: 600; color: #fff; }
11
- .status-badge { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; padding: 3px 10px; border-radius: 12px; }
12
- .status-badge.on { background: rgba(76,175,80,0.15); color: #4CAF50; }
13
- .status-badge.off { background: rgba(158,158,158,0.15); color: #9E9E9E; }
14
- .status-badge.err { background: rgba(244,67,54,0.15); color: #F44336; }
15
- .status-dot { width: 8px; height: 8px; border-radius: 50%; }
16
- .status-badge.on .status-dot { background: #4CAF50; box-shadow: 0 0 6px #4CAF50; }
17
- .status-badge.off .status-dot { background: #9E9E9E; }
18
- .status-badge.err .status-dot { background: #F44336; }
19
11
 
20
- .section { padding: 12px 16px; border-bottom: 1px solid #2a2a4a; }
21
- .section-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 8px; }
12
+ .body { padding: 10px 14px; }
22
13
 
23
- .ws-address { display: flex; align-items: center; gap: 8px; }
24
- .ws-address input { flex: 1; background: #16213e; border: 1px solid #2a2a4a; border-radius: 6px; padding: 7px 10px; color: #e0e0e0; font-size: 12px; font-family: monospace; outline: none; }
25
- .ws-address input:focus { border-color: #4a9eff; }
26
- .btn-sm { background: #2a2a4a; border: 1px solid #3a3a5a; border-radius: 6px; padding: 6px 12px; color: #e0e0e0; font-size: 12px; cursor: pointer; white-space: nowrap; }
27
- .btn-sm:hover { background: #3a3a5a; }
14
+ #connectionList { background: #16213e; border-radius: 6px; overflow: hidden; }
28
15
 
29
- .cdp-row { margin-bottom: 8px; }
30
- .cdp-row:last-child { margin-bottom: 0; }
31
- .cdp-label { font-size: 11px; color: #888; margin-bottom: 3px; }
32
- .cdp-url { display: flex; align-items: center; gap: 6px; background: #16213e; border-radius: 6px; padding: 6px 10px; }
33
- .cdp-url code { flex: 1; font-size: 11px; font-family: monospace; color: #4a9eff; word-break: break-all; line-height: 1.4; }
34
- .copy-btn { background: none; border: 1px solid #3a3a5a; border-radius: 4px; padding: 3px 8px; color: #aaa; font-size: 11px; cursor: pointer; flex-shrink: 0; }
35
- .copy-btn:hover { background: #3a3a5a; color: #fff; }
36
- .copy-btn.copied { color: #4CAF50; border-color: #4CAF50; }
16
+ .conn-item { padding: 8px 10px; border-bottom: 1px solid rgba(255,255,255,0.08); }
17
+ .conn-item:last-child { border-bottom: none; }
18
+ .conn-header { display: flex; align-items: center; gap: 6px; }
19
+ .conn-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
20
+ .conn-dot.connected { background: #4caf50; box-shadow: 0 0 4px #4caf50; }
21
+ .conn-dot.error { background: #f44336; box-shadow: 0 0 4px #f44336; }
22
+ .conn-dot.disabled { background: #9e9e9e; }
23
+ .conn-tag { font-weight: 600; font-size: 13px; color: #e0e0e0; }
24
+ .conn-url { color: #888; font-size: 11px; font-family: 'SF Mono', Menlo, monospace; word-break: break-all; margin-top: 3px; line-height: 1.4; }
37
25
 
38
- .clients-info { font-size: 12px; color: #aaa; }
39
- .clients-info span { color: #4a9eff; }
26
+ .empty-hint { text-align: center; padding: 16px 10px; color: #777; font-size: 12px; }
40
27
 
41
- .btn-full { width: 100%; background: linear-gradient(135deg, #4a9eff, #357abd); border: none; border-radius: 6px; padding: 10px; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }
28
+ .stats-row { display: flex; gap: 16px; padding: 8px 0; font-size: 12px; color: #aaa; }
29
+ .stats-row span b { color: #4a9eff; font-weight: 600; }
30
+
31
+ .btn-full { width: 100%; background: linear-gradient(135deg, #4a9eff, #357abd); border: none; border-radius: 6px; padding: 10px; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer; transition: opacity 0.15s; margin-top: 6px; }
42
32
  .btn-full:hover { opacity: 0.85; }
43
33
 
44
- .footer { padding: 8px 16px; text-align: center; }
45
- .footer a { color: #666; font-size: 11px; text-decoration: none; }
34
+ .footer { padding: 6px 14px; text-align: right; }
35
+ .footer a { color: #555; font-size: 11px; text-decoration: none; }
46
36
  .footer a:hover { color: #4a9eff; }
47
37
  </style>
48
38
  </head>
49
39
  <body>
50
40
  <div class="header">
51
41
  <h1>CDP Bridge</h1>
52
- <div class="status-badge off" id="statusBadge">
53
- <div class="status-dot"></div>
54
- <span id="statusText">未连接</span>
55
- </div>
56
- </div>
57
-
58
- <div class="section">
59
- <div class="section-title">WebSocket 地址</div>
60
- <div class="ws-address">
61
- <input type="text" id="wsInput" placeholder="ws://localhost:9221/plugin" spellcheck="false">
62
- <button class="btn-sm" id="saveBtn">保存</button>
63
- </div>
64
42
  </div>
65
43
 
66
- <div class="section" id="cdpSection" style="display:none;">
67
- <div class="section-title">CDP 连接地址</div>
68
- <div id="cdpAddresses"></div>
69
- </div>
44
+ <div class="body">
45
+ <div id="connectionList"></div>
70
46
 
71
- <div class="section" id="clientsSection" style="display:none;">
72
- <div class="section-title">连接信息</div>
73
- <div class="clients-info" id="clientsInfo"></div>
74
- </div>
47
+ <div class="stats-row" id="statsRow" style="display:none;">
48
+ <span>活跃连接: <b id="statClients">0</b></span>
49
+ <span>页面: <b id="statPages">0</b></span>
50
+ </div>
75
51
 
76
- <div class="section">
77
52
  <button class="btn-full" id="openConfigBtn">打开完整配置</button>
78
53
  </div>
79
54
 
@@ -1,127 +1,79 @@
1
1
  (function() {
2
2
  var $ = function(id) { return document.getElementById(id); };
3
3
 
4
- var wsInput = $('wsInput');
5
- var saveBtn = $('saveBtn');
6
- var statusBadge = $('statusBadge');
7
- var statusText = $('statusText');
8
- var cdpSection = $('cdpSection');
9
- var cdpAddresses = $('cdpAddresses');
10
- var clientsSection = $('clientsSection');
11
- var clientsInfo = $('clientsInfo');
4
+ var connectionList = $('connectionList');
5
+ var statsRow = $('statsRow');
6
+ var statClients = $('statClients');
7
+ var statPages = $('statPages');
12
8
 
13
9
  function loadState() {
14
- chrome.storage.local.get(['wsAddress'], function(result) {
15
- wsInput.value = result.wsAddress || '';
16
- });
17
- chrome.runtime.sendMessage({ type: 'popup-query' }, function(state) {
18
- if (!state) return;
19
- updateStatus(state);
20
- updateCDP(state);
21
- updateClients(state);
10
+ chrome.runtime.sendMessage({ type: 'get-connection-statuses' }, function(resp) {
11
+ if (!resp || !resp.connections) return;
12
+ renderConnectionList(resp.connections);
13
+ updateStats(resp.connections);
22
14
  });
23
15
  }
24
16
 
25
- function updateStatus(state) {
26
- var cls = state.connected ? 'on' : 'off';
27
- var text = state.connected ? '已连接' : '未连接';
28
- if (!state.connected && state.lastError) {
29
- cls = 'err';
30
- text = '错误';
31
- }
32
- statusBadge.className = 'status-badge ' + cls;
33
- statusText.textContent = text;
34
- }
17
+ function renderConnectionList(connections) {
18
+ connectionList.innerHTML = '';
35
19
 
36
- function updateCDP(state) {
37
- if (!state.connected || !state.pluginId) {
38
- cdpSection.style.display = 'none';
20
+ if (!connections || connections.length === 0) {
21
+ connectionList.innerHTML = '<div class="empty-hint">尚未配置连接</div>';
39
22
  return;
40
23
  }
41
- cdpSection.style.display = '';
42
24
 
43
- var wsUrl = wsInput.value || 'ws://localhost:9221/plugin';
44
- var cdpBase = wsUrl.replace(/\/plugin(\?.*)?$/, '');
45
- var pluginId = state.pluginId;
46
- var cdpPath = '/devtools/browser/' + pluginId;
25
+ connections.forEach(function(conn) {
26
+ var item = document.createElement('div');
27
+ item.className = 'conn-item';
47
28
 
48
- var urls = [];
49
- var parsed = parseWsUrl(wsUrl);
50
- if (parsed) {
51
- urls.push({ label: 'CDP 地址', url: parsed.protocol + '://' + parsed.host + cdpPath });
52
- }
29
+ var header = document.createElement('div');
30
+ header.className = 'conn-header';
53
31
 
54
- var html = '';
55
- urls.forEach(function(item) {
56
- html += '<div class="cdp-row">';
57
- html += '<div class="cdp-label">' + item.label + '</div>';
58
- html += '<div class="cdp-url">';
59
- html += '<code>' + escapeHtml(item.url) + '</code>';
60
- html += '<button class="copy-btn" data-url="' + escapeAttr(item.url) + '">复制</button>';
61
- html += '</div></div>';
62
- });
63
- cdpAddresses.innerHTML = html;
64
- }
32
+ var dot = document.createElement('span');
33
+ dot.className = 'conn-dot ' + conn.status;
65
34
 
66
- function updateClients(state) {
67
- if (!state.connected) {
68
- clientsSection.style.display = 'none';
69
- return;
70
- }
71
- clientsSection.style.display = '';
72
- var clients = state.cdpClients || [];
73
- var attached = state.attachedPages || [];
74
- clientsInfo.innerHTML = '活跃连接: <span>' + clients.length + '</span> | 已附加页面: <span>' + attached.length + '</span>';
75
- }
35
+ var tag = document.createElement('span');
36
+ tag.className = 'conn-tag';
37
+ tag.textContent = conn.tag;
76
38
 
77
- function parseWsUrl(url) {
78
- var m = url.match(/^(wss?):\/\/([^\/]+)/);
79
- if (!m) return null;
80
- return { protocol: m[1], host: m[2] };
81
- }
39
+ header.appendChild(dot);
40
+ header.appendChild(tag);
82
41
 
83
- function escapeHtml(s) {
84
- return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
85
- }
86
- function escapeAttr(s) {
87
- return s.replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
88
- }
42
+ var url = document.createElement('div');
43
+ url.className = 'conn-url';
44
+ url.textContent = conn.url;
89
45
 
90
- saveBtn.addEventListener('click', function() {
91
- var val = wsInput.value.trim();
92
- chrome.storage.local.set({ wsAddress: val }, function() {
93
- saveBtn.textContent = '已保存';
94
- setTimeout(function() { saveBtn.textContent = '保存'; }, 1500);
95
- chrome.runtime.sendMessage({ type: 'ws-reconnect' });
46
+ item.appendChild(header);
47
+ item.appendChild(url);
48
+ connectionList.appendChild(item);
96
49
  });
97
- });
50
+ }
98
51
 
99
- cdpAddresses.addEventListener('click', function(e) {
100
- var btn = e.target.closest('.copy-btn');
101
- if (!btn) return;
102
- var url = btn.getAttribute('data-url');
103
- navigator.clipboard.writeText(url).then(function() {
104
- btn.textContent = '已复制';
105
- btn.classList.add('copied');
106
- setTimeout(function() {
107
- btn.textContent = '复制';
108
- btn.classList.remove('copied');
109
- }, 2000);
52
+ function updateStats(connections) {
53
+ var active = 0;
54
+ var pages = 0;
55
+ connections.forEach(function(conn) {
56
+ if (conn.status === 'connected') active++;
57
+ pages += conn.attachedCount || 0;
110
58
  });
111
- });
112
59
 
113
- var openConfigBtn = $('openConfigBtn');
114
- if (openConfigBtn) {
115
- openConfigBtn.addEventListener('click', function() {
116
- chrome.tabs.create({ url: chrome.runtime.getURL('config-page-preview.html') });
117
- });
60
+ if (active > 0 || pages > 0) {
61
+ statsRow.style.display = '';
62
+ } else {
63
+ statsRow.style.display = 'none';
64
+ }
65
+ statClients.textContent = active;
66
+ statPages.textContent = pages;
118
67
  }
119
68
 
69
+ var openConfigBtn = $('openConfigBtn');
70
+ openConfigBtn.addEventListener('click', function() {
71
+ chrome.tabs.create({ url: chrome.runtime.getURL('config-page-preview.html') });
72
+ });
73
+
120
74
  var versionLink = $('versionLink');
121
- if (versionLink) {
122
- var manifest = chrome.runtime.getManifest();
123
- versionLink.textContent = 'v' + manifest.version;
124
- }
75
+ var manifest = chrome.runtime.getManifest();
76
+ versionLink.textContent = 'v' + manifest.version;
125
77
 
126
78
  loadState();
127
79
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.8.2",
3
+ "version": "2.8.3",
4
4
  "description": "Bridge Chrome's debugger API to WebSocket — control your existing browser with Playwright/Puppeteer via CDP",
5
5
  "main": "server/proxy-server.js",
6
6
  "bin": "./cli/index.js",