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,442 @@
1
+ var SpecialHandler = (function() {
2
+ function targetSetAutoAttach(context) {
3
+ var params = context.params;
4
+ State.setAutoAttachConfig({
5
+ autoAttach: !!(params && params.autoAttach),
6
+ waitForDebuggerOnStart: !!(params && params.waitForDebuggerOnStart),
7
+ flatten: !(params && params.flatten === false)
8
+ });
9
+
10
+ if (params && params.autoAttach) {
11
+ return emitAutoAttachForExistingTargets().then(function() {
12
+ return {};
13
+ });
14
+ }
15
+ return Promise.resolve({});
16
+ }
17
+
18
+ function targetAttachToTarget(context) {
19
+ var params = context.params;
20
+ var targetId = params && params.targetId;
21
+ if (!targetId) {
22
+ return Promise.resolve({});
23
+ }
24
+
25
+ return resolveTabId(targetId).then(function(tabId) {
26
+ if (!tabId) {
27
+ throw new Error('Target not found');
28
+ }
29
+
30
+ var isAlreadyAttached = State.isTabAttached(tabId);
31
+
32
+ if (isAlreadyAttached) {
33
+ var newSessionId = CDPUtils.generateSessionId();
34
+ State.mapSession(newSessionId, tabId, targetId);
35
+ Logger.info('[CDP] Created additional session:', newSessionId, 'for tab:', tabId);
36
+ return { sessionId: newSessionId };
37
+ }
38
+
39
+ return DebuggerManager.attach(tabId).then(function(attached) {
40
+ if (!attached) {
41
+ throw new Error('Failed to attach');
42
+ }
43
+
44
+ var sessionId = CDPUtils.generateSessionId();
45
+ State.mapSession(sessionId, tabId, targetId);
46
+
47
+ AutomationBadge.inject(tabId);
48
+
49
+ return { sessionId: sessionId };
50
+ });
51
+ });
52
+ }
53
+
54
+ function targetDetachFromTarget(context) {
55
+ var params = context.params;
56
+ var sessionId = params && params.sessionId;
57
+ if (!sessionId) {
58
+ return Promise.resolve({});
59
+ }
60
+
61
+ var tabId = State.getTabIdBySession(sessionId);
62
+ State.unmapSession(sessionId);
63
+
64
+ if (tabId && !State.hasOtherSessionForTab(tabId)) {
65
+ return DebuggerManager.detach(tabId).then(function() {
66
+ return {};
67
+ }).catch(function() {
68
+ return {};
69
+ });
70
+ }
71
+ return Promise.resolve({});
72
+ }
73
+
74
+ function targetCreateTarget(context) {
75
+ var params = context.params;
76
+ var url = (params && params.url) || 'about:blank';
77
+ var browserContextId = (params && params.browserContextId) || 'default';
78
+
79
+ return new Promise(function(resolve, reject) {
80
+ State.addPendingCreatedTabUrl(url);
81
+ chrome.tabs.create({ url: url, active: !(params && params.background) }, function(tab) {
82
+ if (!tab || !tab.id) {
83
+ State.removePendingCreatedTabUrl(url);
84
+ reject(new Error('Failed to create tab'));
85
+ return;
86
+ }
87
+ var targetId = String(tab.id);
88
+ State.addEmittedTarget(targetId);
89
+ getTargetIdByTabId(tab.id).then(function(targetId) {
90
+ return emitAutoAttachEvents(tab.id, targetId, browserContextId).then(function() {
91
+ resolve({ targetId: targetId });
92
+ });
93
+ });
94
+ });
95
+ });
96
+ }
97
+
98
+ function targetActivateTarget(context) {
99
+ var params = context.params;
100
+ var targetId = params && params.targetId;
101
+ if (targetId) {
102
+ return new Promise(function(resolve) {
103
+ chrome.tabs.update(parseInt(targetId, 10), { active: true }, function() {
104
+ resolve({});
105
+ });
106
+ }).catch(function() {
107
+ return {};
108
+ });
109
+ }
110
+ return Promise.resolve({});
111
+ }
112
+
113
+ function targetCloseTarget(context) {
114
+ var params = context.params;
115
+ var targetId = params && params.targetId;
116
+ if (targetId) {
117
+ var tabId = State.getTabIdByTargetId(targetId);
118
+ if (tabId) {
119
+ return new Promise(function(resolve) {
120
+ chrome.tabs.remove(tabId, function() {
121
+ resolve({ success: true });
122
+ });
123
+ }).catch(function() {
124
+ return { success: true };
125
+ });
126
+ }
127
+ }
128
+ return Promise.resolve({ success: true });
129
+ }
130
+
131
+ function pageStartScreencast(context) {
132
+ var params = context.params;
133
+ var sessionId = context.sessionId;
134
+ var tabId = sessionId ? State.getTabIdBySession(sessionId) : State.getCurrentTabId();
135
+
136
+ return checkTabVisibility(tabId).then(function(isVisible) {
137
+ if (!isVisible) {
138
+ return Screencast.startPolling(tabId, params, sessionId).then(function() {
139
+ return {};
140
+ });
141
+ }
142
+ return ForwardHandler.execute({ id: context.id, method: 'Page.startScreencast', params: params, sessionId: sessionId });
143
+ });
144
+ }
145
+
146
+ function pageStopScreencast(context) {
147
+ var sessionId = context.sessionId;
148
+ var tabId = sessionId ? State.getTabIdBySession(sessionId) : State.getCurrentTabId();
149
+ var session = tabId ? State.getScreencastSession(tabId) : null;
150
+
151
+ if (session) {
152
+ Screencast.stopPolling(tabId);
153
+ }
154
+
155
+ return {};
156
+ }
157
+
158
+ function pageScreencastFrameAck(context) {
159
+ var params = context.params;
160
+ var sessionId = context.sessionId;
161
+ var tabId = sessionId ? State.getTabIdBySession(sessionId) : State.getCurrentTabId();
162
+ var session = tabId ? State.getScreencastSession(tabId) : null;
163
+
164
+ if (session) {
165
+ Screencast.ackFrame(tabId, params && params.sessionId);
166
+ return {};
167
+ }
168
+
169
+ return {};
170
+ }
171
+
172
+ function runtimeRunIfWaitingForDebugger(context) {
173
+ var sessionId = context.sessionId;
174
+ var tabId = sessionId ? State.getTabIdBySession(sessionId) : State.getCurrentTabId();
175
+
176
+ if (tabId && State.isPendingDebuggerTab(tabId)) {
177
+ State.removePendingDebuggerTab(tabId);
178
+ }
179
+ return {};
180
+ }
181
+
182
+ function resolveTabId(targetId) {
183
+ if (/^\d+$/.test(targetId)) {
184
+ return Promise.resolve(parseInt(targetId, 10));
185
+ }
186
+
187
+ return chrome.debugger.getTargets().then(function(targets) {
188
+ var match = targets.find(function(t) { return t.id === targetId; });
189
+ return match && match.tabId ? match.tabId : null;
190
+ });
191
+ }
192
+
193
+ function getTargetIdByTabId(tabId) {
194
+ return chrome.debugger.getTargets().then(function(targets) {
195
+ var match = targets.find(function(t) { return t.tabId === tabId; });
196
+ return match ? match.id : String(tabId);
197
+ });
198
+ }
199
+
200
+ function checkTabVisibility(tabId) {
201
+ return new Promise(function(resolve) {
202
+ chrome.tabs.get(tabId, function(tab) {
203
+ if (chrome.runtime.lastError || !tab) {
204
+ resolve(false);
205
+ return;
206
+ }
207
+ chrome.windows.get(tab.windowId, function(win) {
208
+ if (chrome.runtime.lastError || !win) {
209
+ resolve(false);
210
+ return;
211
+ }
212
+ resolve(tab.active && win.focused);
213
+ });
214
+ });
215
+ }).catch(function() {
216
+ return false;
217
+ });
218
+ }
219
+
220
+ function emitAutoAttachForExistingTargets() {
221
+ return chrome.debugger.getTargets().then(function(targets) {
222
+ var config = State.getAutoAttachConfig();
223
+ var promises = [];
224
+
225
+ Logger.info('[CDP] emitAutoAttachForExistingTargets: checking', targets.length, 'targets');
226
+ Logger.info('[CDP] Current attachedTabIds:', State.getAttachedTabIds());
227
+
228
+ targets.forEach(function(target) {
229
+ if (target.type !== 'page' && target.type !== 'background_page') return;
230
+ if (!target.tabId) return;
231
+
232
+ var targetId = target.id;
233
+ var hasEmitted = State.hasEmittedTarget(targetId);
234
+ Logger.info('[CDP] emitAutoAttachForExistingTargets: targetId=', targetId, 'tabId=', target.tabId, 'attached=', target.attached, 'hasEmitted=', hasEmitted);
235
+
236
+ if (hasEmitted) {
237
+ Logger.info('[CDP] Target already emitted in emitAutoAttachForExistingTargets, skipping:', targetId);
238
+ return;
239
+ }
240
+ State.addEmittedTarget(targetId);
241
+
242
+ var isAttachedByUs = State.isTabAttached(target.tabId);
243
+ var targetInfo = LocalHandler.mapToTargetInfo(target);
244
+
245
+ Logger.info('[CDP] isAttachedByUs=', isAttachedByUs, 'for tabId=', target.tabId);
246
+
247
+ if (target.attached && !isAttachedByUs) {
248
+ targetInfo.attached = false;
249
+ Logger.info('[CDP] Target attached by another debugger, reporting as not attached:', targetId, 'tabId:', target.tabId);
250
+ }
251
+
252
+ EventBuilder.send('Target.targetCreated', { targetInfo: targetInfo });
253
+
254
+ if (target.attached && isAttachedByUs) {
255
+ var promise = Promise.resolve();
256
+
257
+ promises.push(promise.then(function() {
258
+ var sessionId = CDPUtils.generateSessionId();
259
+ State.mapSession(sessionId, target.tabId, targetId);
260
+
261
+ AutomationBadge.inject(target.tabId);
262
+
263
+ if (config.waitForDebuggerOnStart) {
264
+ State.addPendingDebuggerTab(target.tabId);
265
+ }
266
+
267
+ EventBuilder.send('Target.attachedToTarget', {
268
+ sessionId: sessionId,
269
+ targetInfo: Object.assign({}, targetInfo, { attached: true }),
270
+ waitingForDebugger: config.waitForDebuggerOnStart
271
+ });
272
+ }));
273
+ }
274
+ });
275
+
276
+ return Promise.all(promises);
277
+ });
278
+ }
279
+
280
+ function emitAutoAttachEvents(tabId, targetId, browserContextId) {
281
+ if (State.hasEmittedTarget(targetId)) {
282
+ Logger.info('[CDP] Target already emitted, skipping emitAutoAttachEvents:', targetId);
283
+ return Promise.resolve();
284
+ }
285
+
286
+ State.addEmittedTarget(targetId);
287
+
288
+ return LocalHandler.getTargetInfoById(targetId).then(function(targetInfo) {
289
+ if (browserContextId && browserContextId !== 'default') {
290
+ targetInfo.browserContextId = browserContextId;
291
+ }
292
+ EventBuilder.send('Target.targetCreated', { targetInfo: targetInfo });
293
+
294
+ return DebuggerManager.attach(tabId).then(function(attached) {
295
+ if (!attached) return;
296
+
297
+ var sessionId = CDPUtils.generateSessionId();
298
+ State.mapSession(sessionId, tabId, targetId);
299
+
300
+ AutomationBadge.inject(tabId);
301
+
302
+ var config = State.getAutoAttachConfig();
303
+ if (config.waitForDebuggerOnStart) {
304
+ State.addPendingDebuggerTab(tabId);
305
+ }
306
+
307
+ EventBuilder.send('Target.attachedToTarget', {
308
+ sessionId: sessionId,
309
+ targetInfo: targetInfo,
310
+ waitingForDebugger: config.waitForDebuggerOnStart
311
+ });
312
+ });
313
+ });
314
+ }
315
+
316
+ function pageCreateIsolatedWorld(context) {
317
+ var params = context.params;
318
+ var sessionId = context.sessionId;
319
+ var tabId = sessionId ? State.getTabIdBySession(sessionId) : State.getCurrentTabId();
320
+
321
+ return ForwardHandler.execute({ id: context.id, method: 'Page.createIsolatedWorld', params: params, sessionId: sessionId });
322
+ }
323
+
324
+ function pageAddScriptToEvaluateOnNewDocument(context) {
325
+ var params = context.params;
326
+ var sessionId = context.sessionId;
327
+ var tabId = sessionId ? State.getTabIdBySession(sessionId) : State.getCurrentTabId();
328
+
329
+ return ForwardHandler.execute({ id: context.id, method: 'Page.addScriptToEvaluateOnNewDocument', params: params, sessionId: sessionId });
330
+ }
331
+
332
+ function domSetFileInputFiles(context) {
333
+ var params = context.params;
334
+ var sessionId = context.sessionId;
335
+ var files = params && params.files;
336
+
337
+ if (!files || !Array.isArray(files) || files.length === 0) {
338
+ return ForwardHandler.execute({ id: context.id, method: 'DOM.setFileInputFiles', params: params, sessionId: sessionId });
339
+ }
340
+
341
+ var hasUrl = files.some(function(f) {
342
+ return typeof f === 'string' && (f.startsWith('http://') || f.startsWith('https://'));
343
+ });
344
+
345
+ if (!hasUrl) {
346
+ return ForwardHandler.execute({ id: context.id, method: 'DOM.setFileInputFiles', params: params, sessionId: sessionId });
347
+ }
348
+
349
+ Logger.info('[CDP] DOM.setFileInputFiles: 检测到远程 URL, 开始下载...');
350
+
351
+ return downloadRemoteFiles(files).then(function(localFiles) {
352
+ Logger.info('[CDP] DOM.setFileInputFiles: 下载完成, 本地路径:', localFiles);
353
+
354
+ var newParams = Object.assign({}, params, { files: localFiles });
355
+ return ForwardHandler.execute({ id: context.id, method: 'DOM.setFileInputFiles', params: newParams, sessionId: sessionId });
356
+ });
357
+ }
358
+
359
+ function downloadRemoteFiles(files) {
360
+ var promises = files.map(function(file) {
361
+ if (typeof file === 'string' && (file.startsWith('http://') || file.startsWith('https://'))) {
362
+ return downloadRemoteFile(file);
363
+ }
364
+ return Promise.resolve(file);
365
+ });
366
+
367
+ return Promise.all(promises);
368
+ }
369
+
370
+ function downloadRemoteFile(url) {
371
+ return new Promise(function(resolve, reject) {
372
+ var filename = 'cdp_upload_' + Date.now() + '_' + url.split('/').pop().split('?')[0];
373
+
374
+ chrome.downloads.download({
375
+ url: url,
376
+ filename: filename,
377
+ saveAs: false
378
+ }, function(downloadId) {
379
+ if (chrome.runtime.lastError) {
380
+ Logger.error('[CDP] 下载失败:', chrome.runtime.lastError.message);
381
+ reject(new Error('Download failed: ' + chrome.runtime.lastError.message));
382
+ return;
383
+ }
384
+
385
+ Logger.info('[CDP] 开始下载, downloadId:', downloadId);
386
+
387
+ waitForDownloadComplete(downloadId).then(function() {
388
+ return chrome.downloads.search({ id: downloadId });
389
+ }).then(function(results) {
390
+ if (results.length === 0) {
391
+ reject(new Error('Download item not found'));
392
+ return;
393
+ }
394
+
395
+ var filePath = results[0].filename;
396
+ Logger.info('[CDP] 下载完成, 本地路径:', filePath);
397
+ resolve(filePath);
398
+ }).catch(reject);
399
+ });
400
+ });
401
+ }
402
+
403
+ function waitForDownloadComplete(downloadId) {
404
+ return new Promise(function(resolve, reject) {
405
+ var timeout = setTimeout(function() {
406
+ reject(new Error('Download timeout after 60s'));
407
+ }, 60000);
408
+
409
+ function listener(delta) {
410
+ if (delta.id !== downloadId) return;
411
+
412
+ if (delta.state && delta.state.current === 'complete') {
413
+ clearTimeout(timeout);
414
+ chrome.downloads.onChanged.removeListener(listener);
415
+ resolve();
416
+ } else if (delta.state && delta.state.current === 'interrupted') {
417
+ clearTimeout(timeout);
418
+ chrome.downloads.onChanged.removeListener(listener);
419
+ reject(new Error('Download interrupted: ' + (delta.error && delta.error.current)));
420
+ }
421
+ }
422
+
423
+ chrome.downloads.onChanged.addListener(listener);
424
+ });
425
+ }
426
+
427
+ return {
428
+ targetSetAutoAttach: targetSetAutoAttach,
429
+ targetAttachToTarget: targetAttachToTarget,
430
+ targetDetachFromTarget: targetDetachFromTarget,
431
+ targetCreateTarget: targetCreateTarget,
432
+ targetActivateTarget: targetActivateTarget,
433
+ targetCloseTarget: targetCloseTarget,
434
+ pageStartScreencast: pageStartScreencast,
435
+ pageStopScreencast: pageStopScreencast,
436
+ pageScreencastFrameAck: pageScreencastFrameAck,
437
+ pageCreateIsolatedWorld: pageCreateIsolatedWorld,
438
+ pageAddScriptToEvaluateOnNewDocument: pageAddScriptToEvaluateOnNewDocument,
439
+ runtimeRunIfWaitingForDebugger: runtimeRunIfWaitingForDebugger,
440
+ domSetFileInputFiles: domSetFileInputFiles
441
+ };
442
+ })();
@@ -0,0 +1,104 @@
1
+ var CDP_HANDLERS = {
2
+ 'Browser.getVersion': { type: 'LOCAL', handler: LocalHandler.browserGetVersion },
3
+ 'Browser.setDownloadBehavior': { type: 'LOCAL', handler: LocalHandler.emptyResult },
4
+ 'Browser.close': { type: 'LOCAL', handler: LocalHandler.browserClose },
5
+ 'Browser.crash': { type: 'LOCAL', handler: LocalHandler.emptyResult },
6
+ 'Browser.crashGpuProcess': { type: 'LOCAL', handler: LocalHandler.emptyResult },
7
+ 'Browser.getWindowForTarget': { type: 'LOCAL', handler: LocalHandler.getWindowForTarget },
8
+ 'Browser.setWindowBounds': { type: 'LOCAL', handler: LocalHandler.emptyResult },
9
+ 'Browser.getWindowBounds': { type: 'LOCAL', handler: LocalHandler.getWindowBounds },
10
+ 'Browser.getBrowserCommandLine': { type: 'LOCAL', handler: LocalHandler.emptyArray },
11
+ 'Browser.getHistograms': { type: 'LOCAL', handler: LocalHandler.emptyArray },
12
+ 'Browser.getHistogram': { type: 'LOCAL', handler: LocalHandler.emptyObject },
13
+ 'Browser.grantPermissions': { type: 'LOCAL', handler: LocalHandler.emptyResult },
14
+ 'Browser.resetPermissions': { type: 'LOCAL', handler: LocalHandler.emptyResult },
15
+ 'Browser.setPermission': { type: 'LOCAL', handler: LocalHandler.emptyResult },
16
+
17
+ 'Target.setDiscoverTargets': { type: 'LOCAL', handler: LocalHandler.targetSetDiscoverTargets },
18
+ 'Target.getTargets': { type: 'LOCAL', handler: LocalHandler.targetGetTargets },
19
+ 'Target.getTargetInfo': { type: 'LOCAL', handler: LocalHandler.targetGetTargetInfo },
20
+ 'Target.createBrowserContext': { type: 'LOCAL', handler: LocalHandler.targetCreateBrowserContext },
21
+ 'Target.disposeBrowserContext': { type: 'LOCAL', handler: LocalHandler.targetDisposeBrowserContext },
22
+ 'Target.getBrowserContexts': { type: 'LOCAL', handler: LocalHandler.targetGetBrowserContexts },
23
+ 'Target.attachToBrowserTarget': { type: 'LOCAL', handler: LocalHandler.targetAttachToBrowserTarget },
24
+
25
+ 'SystemInfo.getInfo': { type: 'LOCAL', handler: LocalHandler.systemInfoGetInfo },
26
+ 'SystemInfo.getProcessInfo': { type: 'LOCAL', handler: LocalHandler.systemInfoGetProcessInfo },
27
+
28
+ 'Tethering.bind': { type: 'LOCAL', handler: LocalHandler.tetheringBind },
29
+ 'Tethering.unbind': { type: 'LOCAL', handler: LocalHandler.emptyResult },
30
+
31
+ 'IO.close': { type: 'LOCAL', handler: LocalHandler.emptyResult },
32
+ 'IO.read': { type: 'LOCAL', handler: LocalHandler.ioRead },
33
+ 'IO.resolveBlob': { type: 'LOCAL', handler: LocalHandler.ioResolveBlob },
34
+
35
+ 'Schema.getDomains': { type: 'LOCAL', handler: LocalHandler.schemaGetDomains },
36
+
37
+ 'Target.setAutoAttach': { type: 'SPECIAL', handler: SpecialHandler.targetSetAutoAttach },
38
+ 'Target.attachToTarget': { type: 'SPECIAL', handler: SpecialHandler.targetAttachToTarget },
39
+ 'Target.detachFromTarget': { type: 'SPECIAL', handler: SpecialHandler.targetDetachFromTarget },
40
+ 'Target.createTarget': { type: 'SPECIAL', handler: SpecialHandler.targetCreateTarget },
41
+ 'Target.activateTarget': { type: 'SPECIAL', handler: SpecialHandler.targetActivateTarget },
42
+ 'Target.closeTarget': { type: 'SPECIAL', handler: SpecialHandler.targetCloseTarget },
43
+
44
+ 'Page.startScreencast': { type: 'SPECIAL', handler: SpecialHandler.pageStartScreencast },
45
+ 'Page.stopScreencast': { type: 'SPECIAL', handler: SpecialHandler.pageStopScreencast },
46
+ 'Page.screencastFrameAck': { type: 'SPECIAL', handler: SpecialHandler.pageScreencastFrameAck },
47
+ 'Page.createIsolatedWorld': { type: 'FORWARD', handler: SpecialHandler.pageCreateIsolatedWorld },
48
+ 'Page.addScriptToEvaluateOnNewDocument': { type: 'FORWARD', handler: SpecialHandler.pageAddScriptToEvaluateOnNewDocument },
49
+
50
+ 'Runtime.runIfWaitingForDebugger': { type: 'SPECIAL', handler: SpecialHandler.runtimeRunIfWaitingForDebugger },
51
+
52
+ 'DOM.setFileInputFiles': { type: 'SPECIAL', handler: SpecialHandler.domSetFileInputFiles }
53
+ };
54
+
55
+ function routeCDPCommand(message) {
56
+ var id = message.id;
57
+ var method = message.method;
58
+ var params = message.params;
59
+ var sessionId = message.sessionId;
60
+
61
+ console.log('[CDP] routeCDPCommand id=' + id + ' (type: ' + typeof id + ') method=' + method);
62
+
63
+ var route = CDP_HANDLERS[method];
64
+ var logType = route ? route.type : 'FORWARD';
65
+ Logger.info('[CDP] RECV id=' + id + ' method=' + method + ' type=' + logType + ' sessionId=' + (sessionId || 'null'));
66
+
67
+ return new Promise(function(resolve) {
68
+ if (route) {
69
+ Promise.resolve(route.handler({ id: id, method: method, params: params, sessionId: sessionId }))
70
+ .then(function(result) {
71
+ if (result === null && route.type === 'SPECIAL') {
72
+ Logger.info('[CDP] SPECIAL null -> FORWARD id=' + id + ' method=' + method);
73
+ return ForwardHandler.execute({ id: id, method: method, params: params, sessionId: sessionId });
74
+ }
75
+ return result;
76
+ })
77
+ .then(function(result) {
78
+ Logger.info('[CDP] SEND id=' + id + ' method=' + method + ' hasError=false');
79
+ resolve({ result: result });
80
+ })
81
+ .catch(function(error) {
82
+ Logger.error('[CDP] ERROR id=' + id + ' method=' + method + ' msg=' + error.message);
83
+ resolve({ error: { message: error.message } });
84
+ });
85
+ } else {
86
+ ForwardHandler.execute({ id: id, method: method, params: params, sessionId: sessionId })
87
+ .then(function(result) {
88
+ Logger.info('[CDP] SEND id=' + id + ' method=' + method + ' hasError=false (forwarded)');
89
+ resolve({ result: result });
90
+ })
91
+ .catch(function(error) {
92
+ Logger.error('[CDP] ERROR id=' + id + ' method=' + method + ' msg=' + error.message + ' (forwarded)');
93
+ resolve({ error: { message: error.message } });
94
+ });
95
+ }
96
+ }).then(function(response) {
97
+ if (response.error) {
98
+ ResponseBuilder.send(id, null, sessionId, response.error.message);
99
+ } else {
100
+ ResponseBuilder.send(id, response.result, sessionId);
101
+ }
102
+ return response;
103
+ });
104
+ }
@@ -0,0 +1,49 @@
1
+ var ResponseBuilder = (function() {
2
+ function success(id, result, sessionId) {
3
+ var response = { id: id, result: result || {} };
4
+ if (sessionId) response.sessionId = sessionId;
5
+ return response;
6
+ }
7
+
8
+ function error(id, message, sessionId) {
9
+ var response = { id: id, error: { message: message } };
10
+ if (sessionId) response.sessionId = sessionId;
11
+ return response;
12
+ }
13
+
14
+ function send(id, result, sessionId, errorMessage) {
15
+ var response;
16
+ if (errorMessage) {
17
+ response = ResponseBuilder.error(id, errorMessage, sessionId);
18
+ } else {
19
+ response = ResponseBuilder.success(id, result, sessionId);
20
+ }
21
+ WebSocketManager.send(response);
22
+ return response;
23
+ }
24
+
25
+ return {
26
+ success: success,
27
+ error: error,
28
+ send: send
29
+ };
30
+ })();
31
+
32
+ var EventBuilder = (function() {
33
+ function build(method, params, sessionId) {
34
+ var event = { type: 'event', method: method, params: params };
35
+ if (sessionId) event.sessionId = sessionId;
36
+ return event;
37
+ }
38
+
39
+ function send(method, params, sessionId) {
40
+ var event = EventBuilder.build(method, params, sessionId);
41
+ WebSocketManager.send(event);
42
+ return event;
43
+ }
44
+
45
+ return {
46
+ build: build,
47
+ send: send
48
+ };
49
+ })();