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,294 @@
1
+ importScripts('utils/config.js');
2
+ importScripts('utils/logger.js');
3
+ importScripts('utils/helpers.js');
4
+ importScripts('utils/diagnostics.js');
5
+ importScripts('core/state.js');
6
+ importScripts('core/websocket.js');
7
+ importScripts('core/debugger.js');
8
+ importScripts('cdp/response.js');
9
+ importScripts('cdp/handler/local.js');
10
+ importScripts('cdp/handler/special.js');
11
+ importScripts('cdp/handler/forward.js');
12
+ importScripts('cdp/index.js');
13
+ importScripts('features/screencast.js');
14
+ importScripts('features/automation-badge.js');
15
+
16
+ (function() {
17
+ 'use strict';
18
+
19
+ var keepAliveInterval = null;
20
+
21
+ function startKeepAlive() {
22
+ if (keepAliveInterval) {
23
+ clearInterval(keepAliveInterval);
24
+ }
25
+
26
+ keepAliveInterval = setInterval(function() {
27
+ var ws = State.getWs();
28
+ if (ws && ws.readyState === WebSocket.OPEN) {
29
+ Logger.info('[KeepAlive] Sending heartbeat');
30
+ WebSocketManager.send({ type: 'keepalive', timestamp: Date.now() });
31
+ }
32
+ }, 20000);
33
+
34
+ Logger.info('[KeepAlive] Started keepalive interval');
35
+ }
36
+
37
+ function stopKeepAlive() {
38
+ if (keepAliveInterval) {
39
+ clearInterval(keepAliveInterval);
40
+ keepAliveInterval = null;
41
+ Logger.info('[KeepAlive] Stopped keepalive interval');
42
+ }
43
+ }
44
+
45
+ function init() {
46
+ Logger.info('[Init] CDP Bridge starting...');
47
+
48
+ // 点击扩展图标时打开配置页面
49
+ chrome.action.onClicked.addListener(function(tab) {
50
+ Logger.info('[Action] Extension icon clicked, opening config page');
51
+ chrome.tabs.create({
52
+ url: chrome.runtime.getURL('config-page-preview.html')
53
+ });
54
+ });
55
+
56
+ State.loadPersisted().then(function(result) {
57
+ Logger.info('[Init] Loaded persisted state:', result);
58
+
59
+ if (result.currentTabId != null) {
60
+ validatePersistedState(result.currentTabId, result.isAttached);
61
+ }
62
+
63
+ WebSocketManager.connect();
64
+ startKeepAlive();
65
+
66
+ setTimeout(function() {
67
+ Diagnostics.start();
68
+ }, 2000);
69
+ });
70
+ }
71
+
72
+ function validatePersistedState(tabId, expectedAttached) {
73
+ chrome.tabs.get(tabId, function(tab) {
74
+ if (chrome.runtime.lastError) {
75
+ Logger.info('[Init] Failed to validate persisted state, resetting');
76
+ State.persist(null, false);
77
+ return;
78
+ }
79
+ if (!tab || !tab.id) {
80
+ Logger.info('[Init] Persisted tab no longer exists, resetting state');
81
+ State.persist(null, false);
82
+ return;
83
+ }
84
+
85
+ DebuggerManager.getActualAttachState(tabId).then(function(isActuallyAttached) {
86
+ if (expectedAttached && !isActuallyAttached) {
87
+ Logger.info('[Init] Persisted state mismatch, resetting');
88
+ State.persist(null, false);
89
+ }
90
+ }).catch(function() {
91
+ Logger.info('[Init] Failed to get attach state, resetting');
92
+ State.persist(null, false);
93
+ });
94
+ });
95
+ }
96
+
97
+ chrome.debugger.onEvent.addListener(function(source, method, params) {
98
+ if (method === 'Runtime.bindingCalled' && params && params.name === '__notifyChange') {
99
+ Screencast.onNotify(source.tabId);
100
+ return;
101
+ }
102
+
103
+ DebuggerManager.handleDebuggerEvent(source, method, params);
104
+ });
105
+
106
+ chrome.debugger.onDetach.addListener(function(source, reason) {
107
+ DebuggerManager.handleDetach(source, reason);
108
+ });
109
+
110
+ chrome.tabs.onRemoved.addListener(function(tabId) {
111
+ Logger.info('[Tabs] Tab removed:', tabId);
112
+
113
+ State.removeAttachedTab(tabId);
114
+ Screencast.stopPolling(tabId);
115
+ AutomationBadge.remove(tabId);
116
+
117
+ var sessionId = State.findSessionByTabId(tabId);
118
+ if (sessionId) {
119
+ var targetId = State.getTargetIdBySession(sessionId);
120
+ EventBuilder.send('Target.targetDestroyed', { targetId: targetId });
121
+ EventBuilder.send('Target.detachedFromTarget', {
122
+ sessionId: sessionId,
123
+ targetId: targetId
124
+ });
125
+ State.unmapSession(sessionId);
126
+ }
127
+
128
+ if (State.getCurrentTabId() === tabId) {
129
+ State.persist(null, false);
130
+ }
131
+ });
132
+
133
+ chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
134
+ if (changeInfo.status === 'complete' && State.isTabAttached(tabId)) {
135
+ AutomationBadge.inject(tabId);
136
+ }
137
+ });
138
+
139
+ chrome.tabs.onCreated.addListener(function(tab) {
140
+ Logger.info('[Tabs] Tab created:', tab.id, tab.url, 'openerTabId:', tab.openerTabId);
141
+
142
+ if (!State.hasConnectedClient()) {
143
+ Logger.info('[Tabs] No connected client, skipping');
144
+ return;
145
+ }
146
+
147
+ var tabId = tab.id;
148
+
149
+ var tabUrl = tab.url || tab.pendingUrl || 'about:blank';
150
+ if (State.hasPendingCreatedTabUrl(tabUrl)) {
151
+ Logger.info('[Tabs] Tab created by Target.createTarget, will be handled by createTarget:', tabUrl);
152
+ State.removePendingCreatedTabUrl(tabUrl);
153
+ return;
154
+ }
155
+
156
+ var openerTabId = tab.openerTabId;
157
+ var isOpenerControlled = openerTabId && State.isTabAttached(openerTabId);
158
+
159
+ if (!isOpenerControlled) {
160
+ Logger.info('[Tabs] Tab not controlled (no controlled opener), skipping. openerTabId:', openerTabId);
161
+ return;
162
+ }
163
+
164
+ Logger.info('[Tabs] Tab has controlled opener:', openerTabId, ', will attach');
165
+
166
+ LocalHandler.getTargetInfoById(String(tabId)).then(function(targetInfo) {
167
+ if (!targetInfo) return;
168
+
169
+ var targetId = targetInfo.targetId;
170
+
171
+ if (State.hasEmittedTarget(targetId)) {
172
+ Logger.info('[Tabs] Target already emitted, skipping:', targetId);
173
+ return;
174
+ }
175
+
176
+ State.addEmittedTarget(targetId);
177
+
178
+ EventBuilder.send('Target.targetCreated', { targetInfo: targetInfo });
179
+
180
+ return DebuggerManager.attach(tabId).then(function(attached) {
181
+ if (!attached) return;
182
+
183
+ var sessionId = CDPUtils.generateSessionId();
184
+ State.mapSession(sessionId, tabId, targetId);
185
+
186
+ AutomationBadge.inject(tabId);
187
+
188
+ EventBuilder.send('Target.attachedToTarget', {
189
+ sessionId: sessionId,
190
+ targetInfo: targetInfo,
191
+ waitingForDebugger: false
192
+ });
193
+ });
194
+ });
195
+ });
196
+
197
+ chrome.runtime.onInstalled.addListener(function(details) {
198
+ Logger.info('[Runtime] Extension installed/updated:', details.reason);
199
+ State.persist(null, false);
200
+ WebSocketManager.setBadgeStatus('ON');
201
+ init();
202
+ });
203
+
204
+ chrome.runtime.onStartup.addListener(function() {
205
+ Logger.info('[Runtime] Browser started');
206
+ init();
207
+ });
208
+
209
+ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
210
+ if (message.type === 'reconnect') {
211
+ Logger.info('[Runtime] Received reconnect request from popup');
212
+ var ws = State.getWs();
213
+ if (ws) {
214
+ ws.close();
215
+ }
216
+ WebSocketManager.connect();
217
+ sendResponse({ success: true });
218
+ } else if (message.type === 'getState') {
219
+ var ws = State.getWs();
220
+ var isConnected = ws && ws.readyState === WebSocket.OPEN;
221
+
222
+ chrome.storage.local.get(['wsAddress'], function(result) {
223
+ var attachedTabs = State.getAttachedTabIds();
224
+ var cdpClients = State.getCDPClients() || [];
225
+
226
+ if (attachedTabs.length === 0) {
227
+ sendResponse({
228
+ connected: isConnected,
229
+ serverAddress: result.wsAddress || Config.WS_URL,
230
+ cdpClients: cdpClients,
231
+ attachedPages: []
232
+ });
233
+ return;
234
+ }
235
+
236
+ var attachedPages = [];
237
+ var pendingTabs = attachedTabs.length;
238
+
239
+ attachedTabs.forEach(function(tabId) {
240
+ chrome.tabs.get(tabId, function(tab) {
241
+ pendingTabs--;
242
+
243
+ if (chrome.runtime.lastError) {
244
+ Logger.info('[Runtime] Tab not found:', tabId);
245
+ } else if (tab) {
246
+ attachedPages.push({
247
+ tabId: tabId,
248
+ title: tab.title || 'Untitled',
249
+ url: tab.url || ''
250
+ });
251
+ }
252
+
253
+ if (pendingTabs === 0) {
254
+ sendResponse({
255
+ connected: isConnected,
256
+ serverAddress: result.wsAddress || Config.WS_URL,
257
+ cdpClients: cdpClients,
258
+ attachedPages: attachedPages
259
+ });
260
+ }
261
+ });
262
+ });
263
+ });
264
+
265
+ return true;
266
+ } else if (message.type === 'connect') {
267
+ var address = message.serverAddress;
268
+ if (address) {
269
+ Logger.info('[Runtime] Saving and connecting to:', address);
270
+ chrome.storage.local.set({ wsAddress: address }, function() {
271
+ var ws = State.getWs();
272
+ if (ws) {
273
+ ws.close();
274
+ }
275
+ WebSocketManager.connect();
276
+ sendResponse({ success: true });
277
+ });
278
+ } else {
279
+ sendResponse({ success: false, error: 'No address provided' });
280
+ }
281
+ return true;
282
+ } else if (message.type === 'disconnect') {
283
+ Logger.info('[Runtime] Disconnecting...');
284
+ var ws = State.getWs();
285
+ if (ws) {
286
+ ws.close();
287
+ }
288
+ sendResponse({ success: true });
289
+ }
290
+ return true;
291
+ });
292
+
293
+ init();
294
+ })();
@@ -0,0 +1,44 @@
1
+ var ForwardHandler = (function() {
2
+ function execute(context) {
3
+ var id = context.id;
4
+ var method = context.method;
5
+ var params = context.params;
6
+ var sessionId = context.sessionId;
7
+
8
+ var tabId = resolveTabId(sessionId);
9
+
10
+ if (!tabId) {
11
+ Logger.warn('[Forward] No tabId for command:', method);
12
+ return Promise.resolve({});
13
+ }
14
+
15
+ if (!State.isTabAttached(tabId)) {
16
+ Logger.warn('[Forward] Tab not attached, skipping command:', method, 'tabId:', tabId);
17
+ return Promise.resolve({});
18
+ }
19
+
20
+ Logger.debug('[Forward]', method, '-> tabId:', tabId);
21
+ return chrome.debugger.sendCommand({ tabId: tabId }, method, params).then(function(result) {
22
+ return result || {};
23
+ });
24
+ }
25
+
26
+ function resolveTabId(sessionId) {
27
+ if (sessionId) {
28
+ return State.getTabIdBySession(sessionId);
29
+ }
30
+ var currentTabId = State.getCurrentTabId();
31
+ if (currentTabId != null && State.isTabAttached(currentTabId)) {
32
+ return currentTabId;
33
+ }
34
+ var attachedTabs = State.getAttachedTabIds();
35
+ if (attachedTabs.length > 0) {
36
+ return attachedTabs[0];
37
+ }
38
+ return null;
39
+ }
40
+
41
+ return {
42
+ execute: execute
43
+ };
44
+ })();
@@ -0,0 +1,233 @@
1
+ var LocalHandler = (function() {
2
+ function browserGetVersion() {
3
+ var userAgent = navigator.userAgent || '';
4
+ var chromeVersion = CDPUtils.getChromeVersion(userAgent);
5
+ return {
6
+ protocolVersion: '1.3',
7
+ product: chromeVersion ? 'Chrome/' + chromeVersion : 'Chrome',
8
+ revision: '',
9
+ userAgent: userAgent,
10
+ jsVersion: ''
11
+ };
12
+ }
13
+
14
+ function browserClose() {
15
+ return State.cleanupAllTabs().then(function() {
16
+ return {};
17
+ });
18
+ }
19
+
20
+ function getWindowForTarget() {
21
+ return {
22
+ windowId: 1,
23
+ bounds: { left: 0, top: 0, width: 1920, height: 1080, windowState: 'normal' }
24
+ };
25
+ }
26
+
27
+ function getWindowBounds() {
28
+ return {
29
+ bounds: { left: 0, top: 0, width: 1920, height: 1080, windowState: 'normal' }
30
+ };
31
+ }
32
+
33
+ function targetSetDiscoverTargets(params) {
34
+ State.setDiscoverTargets(!!(params && params.discover));
35
+
36
+ if (params && params.discover) {
37
+ return getTargetInfos().then(function(targets) {
38
+ targets.forEach(function(targetInfo) {
39
+ EventBuilder.send('Target.targetCreated', { targetInfo: targetInfo });
40
+ });
41
+ return {};
42
+ });
43
+ }
44
+ return Promise.resolve({});
45
+ }
46
+
47
+ function targetGetTargets() {
48
+ return getTargetInfos().then(function(targetInfos) {
49
+ return { targetInfos: targetInfos };
50
+ });
51
+ }
52
+
53
+ function targetGetTargetInfo(params) {
54
+ return getFallbackTargetId().then(function(fallbackId) {
55
+ var targetId = (params && params.targetId) || fallbackId;
56
+ if (!targetId) {
57
+ throw new Error('targetId is required');
58
+ }
59
+ return getTargetInfoById(targetId).then(function(targetInfo) {
60
+ if (!targetInfo) {
61
+ throw new Error('Target not found');
62
+ }
63
+ return { targetInfo: targetInfo };
64
+ });
65
+ });
66
+ }
67
+
68
+ function targetCreateBrowserContext() {
69
+ var browserContextId = 'context-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9);
70
+ State.addBrowserContext(browserContextId);
71
+ return { browserContextId: browserContextId };
72
+ }
73
+
74
+ function targetGetBrowserContexts() {
75
+ return { browserContextIds: State.getBrowserContexts() };
76
+ }
77
+
78
+ function targetDisposeBrowserContext(params) {
79
+ if (params && params.browserContextId) {
80
+ State.removeBrowserContext(params.browserContextId);
81
+ }
82
+ return {};
83
+ }
84
+
85
+ function targetAttachToBrowserTarget() {
86
+ return { sessionId: 'browser-session' };
87
+ }
88
+
89
+ function systemInfoGetInfo() {
90
+ return {
91
+ gpu: { devices: [], drivers: [], auxAttributes: {}, featureStatus: {} },
92
+ modelName: 'CDP Bridge',
93
+ modelVersion: '1.0.0',
94
+ commandLine: ''
95
+ };
96
+ }
97
+
98
+ function systemInfoGetProcessInfo() {
99
+ return { processInfo: [] };
100
+ }
101
+
102
+ function tetheringBind() {
103
+ return { port: 0 };
104
+ }
105
+
106
+ function ioRead() {
107
+ return { data: '', eof: true };
108
+ }
109
+
110
+ function ioResolveBlob() {
111
+ return { uuid: 'mock-uuid' };
112
+ }
113
+
114
+ function schemaGetDomains() {
115
+ return {
116
+ domains: [
117
+ { name: 'Page', version: '1.0' },
118
+ { name: 'Runtime', version: '1.0' },
119
+ { name: 'Network', version: '1.0' },
120
+ { name: 'DOM', version: '1.0' },
121
+ { name: 'Target', version: '1.0' }
122
+ ]
123
+ };
124
+ }
125
+
126
+ function emptyResult() {
127
+ return {};
128
+ }
129
+
130
+ function emptyArray() {
131
+ return { items: [] };
132
+ }
133
+
134
+ function emptyObject() {
135
+ return {};
136
+ }
137
+
138
+ function getTargetInfos() {
139
+ return chrome.debugger.getTargets().then(function(targets) {
140
+ return targets.map(mapToTargetInfo).filter(Boolean);
141
+ });
142
+ }
143
+
144
+ function getTargetInfoById(targetId) {
145
+ return chrome.debugger.getTargets().then(function(targets) {
146
+ var match = targets.find(function(t) {
147
+ return t.id === targetId || String(t.tabId) === String(targetId);
148
+ });
149
+ return match ? mapToTargetInfo(match) : null;
150
+ });
151
+ }
152
+
153
+ function getFallbackTargetId() {
154
+ var currentTabId = State.getCurrentTabId();
155
+ if (currentTabId != null) {
156
+ return ensureTabExists(currentTabId).then(function(exists) {
157
+ if (exists) return String(currentTabId);
158
+ return getActiveTabId().then(function(activeId) {
159
+ if (activeId != null) return String(activeId);
160
+ return getTargetInfos().then(function(infos) {
161
+ var page = infos.find(function(t) { return t.type === 'page'; });
162
+ return page ? page.targetId : null;
163
+ });
164
+ });
165
+ });
166
+ }
167
+ return getActiveTabId().then(function(activeId) {
168
+ if (activeId != null) return String(activeId);
169
+ return getTargetInfos().then(function(infos) {
170
+ var page = infos.find(function(t) { return t.type === 'page'; });
171
+ return page ? page.targetId : null;
172
+ });
173
+ });
174
+ }
175
+
176
+ function getActiveTabId() {
177
+ return new Promise(function(resolve) {
178
+ chrome.tabs.query({ active: true, lastFocusedWindow: true }, function(tabs) {
179
+ resolve(tabs[0] ? tabs[0].id : null);
180
+ });
181
+ });
182
+ }
183
+
184
+ function ensureTabExists(tabId) {
185
+ if (tabId == null) return Promise.resolve(false);
186
+ return new Promise(function(resolve) {
187
+ chrome.tabs.get(tabId, function(tab) {
188
+ resolve(!!(tab && tab.id));
189
+ });
190
+ }).catch(function() {
191
+ return false;
192
+ });
193
+ }
194
+
195
+ function mapToTargetInfo(target) {
196
+ if (!target) return null;
197
+ return {
198
+ targetId: target.id || String(target.tabId),
199
+ type: target.type || 'page',
200
+ title: target.title || '',
201
+ url: target.url || '',
202
+ attached: !!target.attached,
203
+ canAccessOpener: false,
204
+ browserContextId: 'default'
205
+ };
206
+ }
207
+
208
+ return {
209
+ browserGetVersion: browserGetVersion,
210
+ browserClose: browserClose,
211
+ getWindowForTarget: getWindowForTarget,
212
+ getWindowBounds: getWindowBounds,
213
+ targetSetDiscoverTargets: targetSetDiscoverTargets,
214
+ targetGetTargets: targetGetTargets,
215
+ targetGetTargetInfo: targetGetTargetInfo,
216
+ targetCreateBrowserContext: targetCreateBrowserContext,
217
+ targetGetBrowserContexts: targetGetBrowserContexts,
218
+ targetDisposeBrowserContext: targetDisposeBrowserContext,
219
+ targetAttachToBrowserTarget: targetAttachToBrowserTarget,
220
+ systemInfoGetInfo: systemInfoGetInfo,
221
+ systemInfoGetProcessInfo: systemInfoGetProcessInfo,
222
+ tetheringBind: tetheringBind,
223
+ ioRead: ioRead,
224
+ ioResolveBlob: ioResolveBlob,
225
+ schemaGetDomains: schemaGetDomains,
226
+ emptyResult: emptyResult,
227
+ emptyArray: emptyArray,
228
+ emptyObject: emptyObject,
229
+ getTargetInfos: getTargetInfos,
230
+ getTargetInfoById: getTargetInfoById,
231
+ mapToTargetInfo: mapToTargetInfo
232
+ };
233
+ })();