cdp-tunnel 1.0.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.
Files changed (46) hide show
  1. package/.github/workflows/publish.yml +92 -0
  2. package/.github/workflows/release-assets.yml +50 -0
  3. package/LICENSE +81 -0
  4. package/PUBLISH.md +65 -0
  5. package/README.md +228 -0
  6. package/cli/guide.html +753 -0
  7. package/cli/icon.svg +13 -0
  8. package/cli/icon128.png +0 -0
  9. package/cli/index.js +357 -0
  10. package/docs/README_CN.md +204 -0
  11. package/docs/config-page-screenshot.png +0 -0
  12. package/extension-new/background.js +294 -0
  13. package/extension-new/cdp/handler/forward.js +44 -0
  14. package/extension-new/cdp/handler/local.js +233 -0
  15. package/extension-new/cdp/handler/special.js +442 -0
  16. package/extension-new/cdp/index.js +104 -0
  17. package/extension-new/cdp/response.js +49 -0
  18. package/extension-new/config-page-preview.html +769 -0
  19. package/extension-new/config-page.js +318 -0
  20. package/extension-new/core/debugger.js +310 -0
  21. package/extension-new/core/state.js +384 -0
  22. package/extension-new/core/websocket.js +326 -0
  23. package/extension-new/features/automation-badge.js +113 -0
  24. package/extension-new/features/screencast.js +221 -0
  25. package/extension-new/icons/icon128.png +0 -0
  26. package/extension-new/icons/icon16.png +0 -0
  27. package/extension-new/icons/icon48.png +0 -0
  28. package/extension-new/manifest.json +39 -0
  29. package/extension-new/popup.html +72 -0
  30. package/extension-new/popup.js +34 -0
  31. package/extension-new/utils/config.js +20 -0
  32. package/extension-new/utils/diagnostics.js +560 -0
  33. package/extension-new/utils/helpers.js +25 -0
  34. package/extension-new/utils/logger.js +64 -0
  35. package/package.json +42 -0
  36. package/server/modules/config.js +28 -0
  37. package/server/modules/logger.js +197 -0
  38. package/server/proxy-server.js +1431 -0
  39. package/tests/playwright-demo.js +45 -0
  40. package/tests/playwright-interactive.js +261 -0
  41. package/tests/playwright-multi-demo.js +60 -0
  42. package/tests/playwright-multi.js +85 -0
  43. package/tests/playwright-single.js +41 -0
  44. package/tests/screenshot-config.js +35 -0
  45. package/tests/test-client.js +89 -0
  46. package/tests/test-multi-client.js +129 -0
@@ -0,0 +1,113 @@
1
+ var AutomationBadge = (function() {
2
+ var PREFIX = '[Automation] ';
3
+ var BADGE_CONTAINER_ID = '__cdp_automation_badge_container__';
4
+
5
+ var INJECT_STYLE_SCRIPT = function() {
6
+ if (document.getElementById('__cdp_automation_styles__')) return;
7
+ var style = document.createElement('style');
8
+ style.id = '__cdp_automation_styles__';
9
+ style.textContent = '\n .__cdp_automation_badge__ {\n position: fixed;\n bottom: 10px;\n right: 10px;\n background: rgba(255, 152, 0, 0.95);\n color: white;\n padding: 6px 12px;\n border-radius: 4px;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n font-size: 12px;\n font-weight: 500;\n z-index: 2147483647;\n box-shadow: 0 2px 8px rgba(0,0,0,0.3);\n pointer-events: none;\n user-select: none;\n }\n ';
10
+ document.head.appendChild(style);
11
+ };
12
+
13
+ var INJECT_BADGE_SCRIPT = function() {
14
+ if (document.getElementById('__cdp_automation_badge_container__')) return;
15
+ var container = document.createElement('div');
16
+ container.id = '__cdp_automation_badge_container__';
17
+ var badge = document.createElement('div');
18
+ badge.className = '__cdp_automation_badge__';
19
+ badge.textContent = 'Automation';
20
+ container.appendChild(badge);
21
+ document.body.appendChild(container);
22
+ };
23
+
24
+ var REMOVE_BADGE_SCRIPT = function() {
25
+ var container = document.getElementById('__cdp_automation_badge_container__');
26
+ if (container) container.remove();
27
+ };
28
+
29
+ var UPDATE_TITLE_SCRIPT = function(prefix) {
30
+ if (!document.title.startsWith(prefix)) {
31
+ document.title = prefix + document.title.replace(new RegExp('^' + prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), '');
32
+ }
33
+ };
34
+
35
+ var RESTORE_TITLE_SCRIPT = function(prefix) {
36
+ if (document.title.startsWith(prefix)) {
37
+ document.title = document.title.slice(prefix.length);
38
+ }
39
+ };
40
+
41
+ function inject(tabId) {
42
+ State.addAutomatedTab(tabId);
43
+
44
+ injectStyle(tabId);
45
+ injectBadge(tabId);
46
+ updateTitle(tabId);
47
+ }
48
+
49
+ function remove(tabId) {
50
+ State.removeAutomatedTab(tabId);
51
+
52
+ removeBadge(tabId);
53
+ restoreTitle(tabId);
54
+ }
55
+
56
+ function injectStyle(tabId) {
57
+ chrome.scripting.executeScript({
58
+ target: { tabId: tabId },
59
+ func: INJECT_STYLE_SCRIPT
60
+ }).catch(function() {});
61
+ }
62
+
63
+ function injectBadge(tabId) {
64
+ chrome.scripting.executeScript({
65
+ target: { tabId: tabId },
66
+ func: INJECT_BADGE_SCRIPT
67
+ }).catch(function() {});
68
+ }
69
+
70
+ function removeBadge(tabId) {
71
+ chrome.scripting.executeScript({
72
+ target: { tabId: tabId },
73
+ func: REMOVE_BADGE_SCRIPT
74
+ }).catch(function() {});
75
+ }
76
+
77
+ function updateTitle(tabId) {
78
+ chrome.scripting.executeScript({
79
+ target: { tabId: tabId },
80
+ func: UPDATE_TITLE_SCRIPT,
81
+ args: [PREFIX]
82
+ }).catch(function() {});
83
+ }
84
+
85
+ function restoreTitle(tabId) {
86
+ chrome.scripting.executeScript({
87
+ target: { tabId: tabId },
88
+ func: RESTORE_TITLE_SCRIPT,
89
+ args: [PREFIX]
90
+ }).catch(function() {});
91
+ }
92
+
93
+ function refreshAll() {
94
+ var automatedTabs = State.getAutomatedTabs();
95
+ automatedTabs.forEach(function(tabId) {
96
+ chrome.tabs.get(tabId, function(tab) {
97
+ if (tab && tab.id) {
98
+ inject(tabId);
99
+ } else {
100
+ State.removeAutomatedTab(tabId);
101
+ }
102
+ }).catch(function() {
103
+ State.removeAutomatedTab(tabId);
104
+ });
105
+ });
106
+ }
107
+
108
+ return {
109
+ inject: inject,
110
+ remove: remove,
111
+ refreshAll: refreshAll
112
+ };
113
+ })();
@@ -0,0 +1,221 @@
1
+ var Screencast = (function() {
2
+ var CHANGE_DETECTOR_SCRIPT = (function() {
3
+ return function() {
4
+ if (window.__cdpChangeDetectorInjected) {
5
+ window.__cdpChangePaused = false;
6
+ if (typeof window.__cdpCheckAnimations === 'function') {
7
+ requestAnimationFrame(window.__cdpCheckAnimations);
8
+ }
9
+ return;
10
+ }
11
+ window.__cdpChangeDetectorInjected = true;
12
+ window.__cdpChangePaused = false;
13
+ window.__cdpChangeNotify = function() {
14
+ if (window.__cdpChangePaused) return;
15
+ if (window.__cdpChangeNotifyPending) return;
16
+ window.__cdpChangeNotifyPending = true;
17
+ requestAnimationFrame(function() {
18
+ window.__cdpChangeNotifyPending = false;
19
+ if (typeof __notifyChange === 'function') {
20
+ __notifyChange('change');
21
+ }
22
+ });
23
+ };
24
+ var observer = new MutationObserver(window.__cdpChangeNotify);
25
+ window.__cdpMutationObserver = observer;
26
+ observer.observe(document.documentElement, {
27
+ childList: true,
28
+ subtree: true,
29
+ attributes: true,
30
+ characterData: true
31
+ });
32
+ var events = ['scroll', 'wheel', 'resize'];
33
+ window.__cdpEventNames = events;
34
+ for (var i = 0; i < events.length; i++) {
35
+ document.addEventListener(events[i], window.__cdpChangeNotify, true);
36
+ }
37
+ try {
38
+ var mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
39
+ if (!mediaQuery.matches) {
40
+ var lastAnimTime = 0;
41
+ var checkAnimations = function() {
42
+ if (window.__cdpChangePaused || !window.__cdpChangeDetectorInjected) {
43
+ return;
44
+ }
45
+ var animations = document.getAnimations();
46
+ var now = Date.now();
47
+ if (animations.length > 0 && now - lastAnimTime > 100) {
48
+ lastAnimTime = now;
49
+ window.__cdpChangeNotify();
50
+ }
51
+ var videos = document.querySelectorAll('video');
52
+ for (var i = 0; i < videos.length; i++) {
53
+ var v = videos[i];
54
+ if (!v.paused && !v.ended) {
55
+ window.__cdpChangeNotify();
56
+ break;
57
+ }
58
+ }
59
+ var canvases = document.querySelectorAll('canvas');
60
+ if (canvases.length > 0) {
61
+ window.__cdpChangeNotify();
62
+ }
63
+ if (window.__cdpChangeDetectorInjected && !window.__cdpChangePaused) {
64
+ requestAnimationFrame(checkAnimations);
65
+ }
66
+ };
67
+ window.__cdpCheckAnimations = checkAnimations;
68
+ requestAnimationFrame(checkAnimations);
69
+ }
70
+ } catch (e) {}
71
+ };
72
+ })();
73
+
74
+ function startPolling(tabId, params, sessionId) {
75
+ stopPolling(tabId);
76
+
77
+ var session = {
78
+ tabId: tabId,
79
+ sessionId: sessionId,
80
+ format: (params && params.format) || 'jpeg',
81
+ quality: (params && params.quality !== undefined) ? params.quality : 80,
82
+ maxWidth: (params && params.maxWidth) || 1920,
83
+ maxHeight: (params && params.maxHeight) || 1080,
84
+ everyNthFrame: (params && params.everyNthFrame) || 1,
85
+ frameCount: 0,
86
+ pendingAck: false,
87
+ stopped: false,
88
+ lastFrameData: null,
89
+ frameId: 0
90
+ };
91
+
92
+ State.setScreencastSession(tabId, session);
93
+
94
+ return injectChangeDetector(tabId).then(function() {
95
+ captureAndSendFrame(session);
96
+ });
97
+ }
98
+
99
+ function stopPolling(tabId) {
100
+ var session = State.getScreencastSession(tabId);
101
+ if (session) {
102
+ session.stopped = true;
103
+ State.deleteScreencastSession(tabId);
104
+ disableChangeNotify(tabId);
105
+ }
106
+ }
107
+
108
+ function ackFrame(tabId, ackSessionId) {
109
+ var session = State.getScreencastSession(tabId);
110
+ if (session) {
111
+ session.pendingAck = false;
112
+ captureAndSendFrame(session);
113
+ }
114
+ }
115
+
116
+ function injectChangeDetector(tabId) {
117
+ return chrome.debugger.sendCommand({ tabId: tabId }, 'Runtime.enable')
118
+ .then(function() {
119
+ Logger.info('[Screencast] Runtime enabled');
120
+ return chrome.debugger.sendCommand(
121
+ { tabId: tabId },
122
+ 'Runtime.addBinding',
123
+ { name: '__notifyChange' }
124
+ );
125
+ })
126
+ .then(function() {
127
+ Logger.info('[Screencast] Binding __notifyChange added');
128
+ var scriptStr = CHANGE_DETECTOR_SCRIPT.toString();
129
+ return chrome.debugger.sendCommand(
130
+ { tabId: tabId },
131
+ 'Runtime.evaluate',
132
+ { expression: '(' + scriptStr + ')()' }
133
+ );
134
+ })
135
+ .then(function(result) {
136
+ if (result.exceptionDetails) {
137
+ Logger.warn('[Screencast] Failed to inject change detector:', result.exceptionDetails);
138
+ return false;
139
+ }
140
+ Logger.info('[Screencast] Change detector injected');
141
+ return true;
142
+ })
143
+ .catch(function(error) {
144
+ Logger.warn('[Screencast] Failed to inject change detector:', error.message);
145
+ return false;
146
+ });
147
+ }
148
+
149
+ function disableChangeNotify(tabId) {
150
+ return chrome.debugger.sendCommand(
151
+ { tabId: tabId },
152
+ 'Runtime.evaluate',
153
+ { expression: 'window.__cdpChangePaused = true;' }
154
+ ).catch(function() {});
155
+ }
156
+
157
+ function captureAndSendFrame(session) {
158
+ if (session.stopped) return;
159
+
160
+ session.frameCount++;
161
+ if (session.frameCount % session.everyNthFrame !== 0) return;
162
+
163
+ chrome.debugger.sendCommand(
164
+ { tabId: session.tabId },
165
+ 'Page.captureScreenshot',
166
+ {
167
+ format: session.format,
168
+ quality: session.quality,
169
+ maxWidth: session.maxWidth,
170
+ maxHeight: session.maxHeight
171
+ }
172
+ ).then(function(result) {
173
+ if (session.stopped || !result || !result.data) return;
174
+
175
+ var data = result.data;
176
+ if (session.lastFrameData === data) {
177
+ return;
178
+ }
179
+ session.lastFrameData = data;
180
+
181
+ session.frameId++;
182
+ var frameId = session.frameId;
183
+
184
+ EventBuilder.send('Page.screencastFrame', {
185
+ data: data,
186
+ sessionId: session.sessionId,
187
+ frameId: frameId,
188
+ metadata: {
189
+ deviceWidth: session.maxWidth,
190
+ deviceHeight: session.maxHeight,
191
+ pageScaleFactor: 1,
192
+ offsetTop: 0,
193
+ offsetBottom: 0,
194
+ scrollOffsetX: 0,
195
+ scrollOffsetY: 0,
196
+ timestamp: Date.now()
197
+ }
198
+ }, session.sessionId);
199
+
200
+ session.pendingAck = true;
201
+ }).catch(function(error) {
202
+ if (!session.stopped) {
203
+ Logger.warn('[Screencast] Screenshot failed:', error.message);
204
+ }
205
+ });
206
+ }
207
+
208
+ function onNotify(tabId) {
209
+ var session = State.getScreencastSession(tabId);
210
+ if (session && !session.pendingAck && !session.stopped) {
211
+ captureAndSendFrame(session);
212
+ }
213
+ }
214
+
215
+ return {
216
+ startPolling: startPolling,
217
+ stopPolling: stopPolling,
218
+ ackFrame: ackFrame,
219
+ onNotify: onNotify
220
+ };
221
+ })();
Binary file
Binary file
Binary file
@@ -0,0 +1,39 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "CDP Bridge",
4
+ "version": "2.0.0",
5
+ "description": "Chrome DevTools Protocol Bridge for Playwright/Puppeteer automation",
6
+ "permissions": [
7
+ "debugger",
8
+ "tabs",
9
+ "activeTab",
10
+ "scripting",
11
+ "storage",
12
+ "downloads"
13
+ ],
14
+ "host_permissions": [
15
+ "<all_urls>"
16
+ ],
17
+ "background": {
18
+ "service_worker": "background.js"
19
+ },
20
+ "action": {
21
+ "default_title": "CDP Bridge",
22
+ "default_icon": {
23
+ "16": "icons/icon16.png",
24
+ "48": "icons/icon48.png",
25
+ "128": "icons/icon128.png"
26
+ }
27
+ },
28
+ "icons": {
29
+ "16": "icons/icon16.png",
30
+ "48": "icons/icon48.png",
31
+ "128": "icons/icon128.png"
32
+ },
33
+ "web_accessible_resources": [
34
+ {
35
+ "resources": ["config-page-preview.html", "config-page.js"],
36
+ "matches": ["<all_urls>"]
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,72 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <style>
6
+ body {
7
+ width: 300px;
8
+ padding: 15px;
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ font-size: 14px;
11
+ }
12
+ .status {
13
+ display: flex;
14
+ align-items: center;
15
+ margin-bottom: 15px;
16
+ }
17
+ .status-dot {
18
+ width: 10px;
19
+ height: 10px;
20
+ border-radius: 50%;
21
+ margin-right: 8px;
22
+ }
23
+ .status-dot.active {
24
+ background-color: #4CAF50;
25
+ }
26
+ .status-dot.inactive {
27
+ background-color: #9E9E9E;
28
+ }
29
+ .config-section {
30
+ margin-top: 15px;
31
+ }
32
+ .config-section label {
33
+ display: block;
34
+ margin-bottom: 5px;
35
+ font-weight: 500;
36
+ }
37
+ .config-section input {
38
+ width: 100%;
39
+ padding: 8px;
40
+ border: 1px solid #ddd;
41
+ border-radius: 4px;
42
+ box-sizing: border-box;
43
+ }
44
+ .config-section button {
45
+ margin-top: 10px;
46
+ padding: 8px 16px;
47
+ background-color: #2196F3;
48
+ color: white;
49
+ border: none;
50
+ border-radius: 4px;
51
+ cursor: pointer;
52
+ }
53
+ .config-section button:hover {
54
+ background-color: #1976D2;
55
+ }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="status">
60
+ <div class="status-dot active" id="statusDot"></div>
61
+ <span id="statusText">已激活</span>
62
+ </div>
63
+
64
+ <div class="config-section">
65
+ <label>WebSocket 地址</label>
66
+ <input type="text" id="wsAddress" placeholder="ws://localhost:9221/client">
67
+ <button id="saveBtn">保存</button>
68
+ </div>
69
+
70
+ <script src="popup.js"></script>
71
+ </body>
72
+ </html>
@@ -0,0 +1,34 @@
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ var statusDot = document.getElementById('statusDot');
3
+ var statusText = document.getElementById('statusText');
4
+ var wsAddressInput = document.getElementById('wsAddress');
5
+ var saveBtn = document.getElementById('saveBtn');
6
+
7
+ chrome.storage.local.get(['wsAddress'], function(result) {
8
+ if (result.wsAddress) {
9
+ wsAddressInput.value = result.wsAddress;
10
+ } else {
11
+ wsAddressInput.value = 'ws://localhost:9221/plugin';
12
+ }
13
+ });
14
+
15
+ saveBtn.addEventListener('click', function() {
16
+ var newAddress = wsAddressInput.value.trim();
17
+ if (newAddress) {
18
+ chrome.storage.local.set({ wsAddress: newAddress }, function() {
19
+ statusText.textContent = '已保存,正在重连...';
20
+
21
+ chrome.runtime.sendMessage({ type: 'reconnect' }, function(response) {
22
+ if (response && response.success) {
23
+ statusText.textContent = '已激活';
24
+ } else {
25
+ statusText.textContent = '重连失败';
26
+ }
27
+ });
28
+ });
29
+ }
30
+ });
31
+
32
+ statusDot.classList.add('active');
33
+ statusText.textContent = '已激活';
34
+ });
@@ -0,0 +1,20 @@
1
+ var Config = {
2
+ WS_URL: 'ws://localhost:9221/plugin',
3
+ RECONNECT_DELAY: 3000,
4
+ DEBUGGER_VERSION: '1.3',
5
+ HEARTBEAT_INTERVAL: 25000,
6
+ SCREENCAST_NOTIFY_THROTTLE: 16,
7
+ BADGE_COLORS: {
8
+ ON: '#4CAF50',
9
+ ERR: '#F44336',
10
+ OFF: '#9E9E9E',
11
+ WAIT: '#FF9800'
12
+ },
13
+ DEFAULT_BROWSER_CONTEXT: 'default',
14
+ DEBUG: true,
15
+ getWsUrl: function(callback) {
16
+ chrome.storage.local.get(['wsAddress'], function(result) {
17
+ callback(result.wsAddress || Config.WS_URL);
18
+ });
19
+ }
20
+ };