cdp-tunnel 2.9.3 → 2.10.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/README.md CHANGED
@@ -102,7 +102,7 @@ The server will start on `localhost:9221`.
102
102
 
103
103
  #### 3. Connect the Extension
104
104
 
105
- Click the extension icon, enter the server address in the configuration page, and click "Save and Connect".
105
+ Click the extension icon, then click **打开完整配置** to open the configuration page. Add a connection: fill in Name (e.g., "local"), WebSocket URL (default `ws://localhost:9221/plugin`), and Mode (create/takeover). The CDP address will be displayed (e.g., `http://localhost:9221` for create mode, `http://localhost:9222` for takeover mode). Copy it and use it in Playwright: `chromium.connectOverCDP('http://localhost:9221')`.
106
106
 
107
107
  ### 3. Client Connection
108
108
 
@@ -254,6 +254,27 @@
254
254
  .status-dot.error { background: #f44336; box-shadow: 0 0 0 2px rgba(244, 67, 54, 0.2); }
255
255
  .status-dot.disabled { background: #9e9e9e; }
256
256
 
257
+ .status-connected { color: #4caf50; font-size: 11px; font-weight: 600; }
258
+ .status-error { color: #f44336; font-size: 11px; font-weight: 600; }
259
+ .status-error-hint {
260
+ display: block;
261
+ font-size: 11px;
262
+ margin-top: 4px;
263
+ color: #999;
264
+ }
265
+ .status-error-hint code {
266
+ background: rgba(244, 67, 54, 0.1);
267
+ color: #f44336;
268
+ padding: 2px 4px;
269
+ border-radius: 3px;
270
+ font-family: monospace;
271
+ }
272
+ .status-disabled { color: #9e9e9e; font-size: 11px; }
273
+ .conn-config-status {
274
+ margin-top: 4px;
275
+ font-size: 11px;
276
+ }
277
+
257
278
  .conn-config-item input[type="checkbox"] {
258
279
  width: 16px;
259
280
  height: 16px;
@@ -849,6 +870,9 @@
849
870
  <option value="create">创建模式</option>
850
871
  <option value="takeover">接管模式</option>
851
872
  </select>
873
+ <div id="modeHint" style="display:none; font-size:11px; color:#999; margin-top:4px; grid-column:1/-1;">
874
+ 🔗 接管模式将使用端口 <span id="takeoverPortHint"></span> 作为 CDP 端点
875
+ </div>
852
876
  <button class="btn btn-primary btn-sm" id="addConnBtn">添加连接</button>
853
877
  </div>
854
878
  </div>
@@ -80,6 +80,12 @@
80
80
  return 'disabled';
81
81
  }
82
82
 
83
+ function getStatusHtml(status) {
84
+ if (status === 'connected') return '<span class="status-connected">已连接</span>';
85
+ if (status === 'error') return '<span class="status-error">连接失败</span><br><span class="status-error-hint">💡 运行 <code>cdp-tunnel diagnose</code> 排查</span>';
86
+ return '<span class="status-disabled">未连接</span>';
87
+ }
88
+
83
89
  function renderConnections(connections) {
84
90
  if (!connections || connections.length === 0) {
85
91
  elements.connConfigList.innerHTML =
@@ -96,6 +102,7 @@
96
102
  var statusClass = getStatusClass(conn.id, conn.enabled);
97
103
  var isActive = conn.enabled && statusClass === 'connected';
98
104
  var cdpAddr = getCdpAddress(conn.url, conn.mode);
105
+ var statusHtml = getStatusHtml(statusClass);
99
106
  html +=
100
107
  '<div class="conn-config-item' + (isActive ? ' active' : '') + '" data-id="' + conn.id + '">' +
101
108
  '<input type="checkbox" class="conn-toggle" data-id="' + conn.id + '"' + (conn.enabled ? ' checked' : '') + ' title="启用/禁用">' +
@@ -104,6 +111,7 @@
104
111
  '<div class="conn-config-tag">' + (conn.mode === 'takeover' ? '🔗 ' : '🆕 ') + escapeHtml(conn.tag) + '</div>' +
105
112
  '<div class="conn-config-url" title="' + escapeAttr(conn.url) + '">WS: ' + escapeHtml(conn.url) + '</div>' +
106
113
  (cdpAddr ? '<div class="conn-config-cdp">CDP: <span class="cdp-addr" data-cdp="' + escapeAttr(cdpAddr) + '">' + escapeHtml(cdpAddr) + '</span> <button class="btn-copy-cdp" data-cdp="' + escapeAttr(cdpAddr) + '" title="复制 CDP 地址">📋</button></div>' : '') +
114
+ '<div class="conn-config-status">' + statusHtml + '</div>' +
107
115
  '</div>' +
108
116
  '<button class="btn-delete" data-id="' + conn.id + '" title="删除">删除</button>' +
109
117
  '</div>';
@@ -326,6 +334,23 @@
326
334
  addLog('action', e.target.checked ? '自动静音已开启' : '自动静音已关闭');
327
335
  });
328
336
 
337
+ elements.inputMode.addEventListener('change', function(e) {
338
+ var modeHint = document.getElementById('modeHint');
339
+ var takeoverPortHint = document.getElementById('takeoverPortHint');
340
+ var url = elements.newConnUrl.value;
341
+
342
+ if (e.target.value === 'takeover') {
343
+ var match = url.match(/:(\d+)/);
344
+ if (match) {
345
+ var port = parseInt(match[1]) + 1;
346
+ takeoverPortHint.textContent = port;
347
+ modeHint.style.display = 'block';
348
+ }
349
+ } else {
350
+ modeHint.style.display = 'none';
351
+ }
352
+ });
353
+
329
354
  var versionBadge = document.getElementById('versionBadge');
330
355
  if (versionBadge && typeof chrome !== 'undefined' && chrome.runtime) {
331
356
  var manifest = chrome.runtime.getManifest();
@@ -41,8 +41,11 @@ var ConnectionManager = (function() {
41
41
  var entry = _connections.get(connectionId);
42
42
  if (!entry) return;
43
43
 
44
+ entry.wsManager._removed = true;
44
45
  entry.state.clearReconnectTimer();
45
46
  if (entry.state.ws) {
47
+ entry.state.ws.onclose = null;
48
+ entry.state.ws.onerror = null;
46
49
  try { entry.state.ws.close(); } catch(e) {}
47
50
  }
48
51
  entry.state.clearAllState();
@@ -97,10 +100,12 @@ var ConnectionManager = (function() {
97
100
 
98
101
  function disconnectAll() {
99
102
  _connections.forEach(function(entry) {
103
+ entry.state.clearReconnectTimer();
100
104
  if (entry.state.ws) {
105
+ entry.state.ws.onclose = null;
106
+ entry.state.ws.onerror = null;
101
107
  try { entry.state.ws.close(); } catch(e) {}
102
108
  }
103
- entry.state.clearReconnectTimer();
104
109
  });
105
110
  }
106
111
 
@@ -32,10 +32,15 @@ var WebSocketConnection = (function() {
32
32
  this._maxQueueSize = 100;
33
33
  this._bufferThreshold = 512 * 1024;
34
34
  this._groupCreationPending = new Set();
35
+ this._removed = false;
35
36
  }
36
37
 
37
38
  WebSocketConnection.prototype.connect = function() {
38
39
  var self = this;
40
+ if (self._removed) {
41
+ Logger.info('[WS:' + self.connectionId + '] Skipping connect, connection removed');
42
+ return;
43
+ }
39
44
  var ws = self.state.getWs();
40
45
  if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) {
41
46
  return;
@@ -43,6 +48,10 @@ var WebSocketConnection = (function() {
43
48
 
44
49
  var wsUrl = self.config.url;
45
50
  Config.getPluginId(function(pluginId) {
51
+ if (self._removed) {
52
+ Logger.info('[WS:' + self.connectionId + '] Aborting connect (async), connection removed');
53
+ return;
54
+ }
46
55
  if (pluginId) {
47
56
  var sep = wsUrl.indexOf('?') >= 0 ? '&' : '?';
48
57
  wsUrl += sep + 'pluginId=' + encodeURIComponent(pluginId);
@@ -150,9 +159,14 @@ var WebSocketConnection = (function() {
150
159
  };
151
160
 
152
161
  WebSocketConnection.prototype._scheduleReconnect = function() {
162
+ if (this._removed) {
163
+ Logger.info('[WS:' + this.connectionId + '] Skipping reconnect, connection removed');
164
+ return;
165
+ }
153
166
  this.state.clearReconnectTimer();
154
167
  var self = this;
155
168
  var timer = setTimeout(function() {
169
+ if (self._removed) return;
156
170
  Logger.info('[WS:' + self.connectionId + '] Attempting to reconnect...');
157
171
  self.connect();
158
172
  }, Config.RECONNECT_DELAY);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP Bridge",
4
- "version": "2.9.3",
4
+ "version": "2.10.0",
5
5
  "description": "Chrome DevTools Protocol Bridge for Playwright/Puppeteer automation",
6
6
  "permissions": [
7
7
  "debugger",
@@ -31,6 +31,27 @@ body { width: 320px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
31
31
 
32
32
  .empty-hint { text-align: center; padding: 16px 10px; color: #777; font-size: 12px; }
33
33
 
34
+ .first-time-guide {
35
+ background: rgba(33, 150, 243, 0.1);
36
+ border: 1px solid rgba(33, 150, 243, 0.3);
37
+ border-radius: 8px;
38
+ padding: 12px;
39
+ margin-bottom: 16px;
40
+ display: flex;
41
+ gap: 12px;
42
+ }
43
+ .guide-icon {
44
+ font-size: 24px;
45
+ }
46
+ .guide-text {
47
+ font-size: 13px;
48
+ line-height: 1.4;
49
+ }
50
+ .guide-text strong {
51
+ display: block;
52
+ margin-bottom: 4px;
53
+ }
54
+
34
55
  .stats-row { display: flex; gap: 16px; padding: 8px 0; font-size: 12px; color: #aaa; }
35
56
  .stats-row span b { color: #4a9eff; font-weight: 600; }
36
57
 
@@ -27,7 +27,16 @@
27
27
  connectionList.innerHTML = '';
28
28
 
29
29
  if (!connections || connections.length === 0) {
30
- connectionList.innerHTML = '<div class="empty-hint">尚未配置连接</div>';
30
+ var guideHtml = `
31
+ <div class="first-time-guide">
32
+ <div class="guide-icon">👋</div>
33
+ <div class="guide-text">
34
+ <strong>首次使用?</strong><br>
35
+ 请先启动 cdp-tunnel server,然后点击下方按钮配置连接
36
+ </div>
37
+ </div>
38
+ `;
39
+ connectionList.innerHTML = guideHtml;
31
40
  return;
32
41
  }
33
42
 
@@ -1,5 +1,5 @@
1
1
  var Config = {
2
- WS_URL: 'ws://localhost:19065/plugin',
2
+ WS_URL: 'ws://localhost:56906/plugin',
3
3
  RECONNECT_DELAY: 3000,
4
4
  DEBUGGER_VERSION: '1.3',
5
5
  HEARTBEAT_INTERVAL: 25000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-tunnel",
3
- "version": "2.9.3",
3
+ "version": "2.10.0",
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",
@@ -59,7 +59,6 @@
59
59
  "dependencies": {
60
60
  "bcryptjs": "^3.0.3",
61
61
  "better-sqlite3": "^12.10.0",
62
- "cdp-tunnel": "^2.5.17",
63
62
  "commander": "^12.0.0",
64
63
  "http-proxy": "^1.18.1",
65
64
  "jsonwebtoken": "^9.0.3",
@@ -67,6 +66,7 @@
67
66
  "ws": "^8.16.0"
68
67
  },
69
68
  "devDependencies": {
69
+ "chrome-remote-interface": "^0.34.0",
70
70
  "husky": "^9.1.7",
71
71
  "lz-string": "^1.5.0",
72
72
  "pako": "^2.1.0",
@@ -350,7 +350,8 @@ async function handleHttpRequest(req, res) {
350
350
  'User-Agent': userAgent,
351
351
  'V8-Version': ver?.jsVersion || '',
352
352
  'WebKit-Version': '537.36',
353
- webSocketDebuggerUrl: `ws://${getHost(req)}/devtools/browser/${browserId}`
353
+ webSocketDebuggerUrl: `ws://${getHost(req)}/devtools/browser/${browserId}`,
354
+ totalPlugins: pluginConnections.size
354
355
  };
355
356
  res.writeHead(200, { 'Content-Type': 'application/json' });
356
357
  res.end(JSON.stringify(payload));