dragonfly_chrome_headless 0.2.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 (228) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +12 -0
  4. data/Gemfile +4 -0
  5. data/README.md +53 -0
  6. data/Rakefile +10 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/dragonfly_chrome_headless.gemspec +27 -0
  10. data/lib/dragonfly_chrome_headless.rb +9 -0
  11. data/lib/dragonfly_chrome_headless/plugin.rb +17 -0
  12. data/lib/dragonfly_chrome_headless/processors/rasterize.rb +33 -0
  13. data/lib/dragonfly_chrome_headless/version.rb +3 -0
  14. data/node_modules/.bin/chrome-remote-interface +1 -0
  15. data/node_modules/.bin/mkdirp +1 -0
  16. data/node_modules/.bin/rimraf +1 -0
  17. data/node_modules/@types/core-js/LICENSE +21 -0
  18. data/node_modules/@types/core-js/README.md +16 -0
  19. data/node_modules/@types/core-js/index.d.ts +2452 -0
  20. data/node_modules/@types/core-js/package.json +85 -0
  21. data/node_modules/@types/mkdirp/README.md +18 -0
  22. data/node_modules/@types/mkdirp/index.d.ts +14 -0
  23. data/node_modules/@types/mkdirp/package.json +77 -0
  24. data/node_modules/@types/mkdirp/types-metadata.json +25 -0
  25. data/node_modules/@types/node/README.md +16 -0
  26. data/node_modules/@types/node/index.d.ts +4132 -0
  27. data/node_modules/@types/node/package.json +84 -0
  28. data/node_modules/balanced-match/.npmignore +5 -0
  29. data/node_modules/balanced-match/LICENSE.md +21 -0
  30. data/node_modules/balanced-match/README.md +91 -0
  31. data/node_modules/balanced-match/index.js +59 -0
  32. data/node_modules/balanced-match/package.json +112 -0
  33. data/node_modules/brace-expansion/README.md +123 -0
  34. data/node_modules/brace-expansion/index.js +201 -0
  35. data/node_modules/brace-expansion/package.json +114 -0
  36. data/node_modules/chrome-launcher/.clang-format +6 -0
  37. data/node_modules/chrome-launcher/.npmignore +11 -0
  38. data/node_modules/chrome-launcher/README.md +123 -0
  39. data/node_modules/chrome-launcher/ask.js +32 -0
  40. data/node_modules/chrome-launcher/ask.ts +35 -0
  41. data/node_modules/chrome-launcher/chrome-finder.js +157 -0
  42. data/node_modules/chrome-launcher/chrome-finder.ts +186 -0
  43. data/node_modules/chrome-launcher/chrome-launcher.js +245 -0
  44. data/node_modules/chrome-launcher/chrome-launcher.ts +312 -0
  45. data/node_modules/chrome-launcher/compiled-check.js +14 -0
  46. data/node_modules/chrome-launcher/flags.js +27 -0
  47. data/node_modules/chrome-launcher/flags.ts +26 -0
  48. data/node_modules/chrome-launcher/index.js +7 -0
  49. data/node_modules/chrome-launcher/index.ts +1 -0
  50. data/node_modules/chrome-launcher/manual-chrome-launcher.js +30 -0
  51. data/node_modules/chrome-launcher/package.json +116 -0
  52. data/node_modules/chrome-launcher/random-port.js +24 -0
  53. data/node_modules/chrome-launcher/random-port.ts +23 -0
  54. data/node_modules/chrome-launcher/tsconfig.json +19 -0
  55. data/node_modules/chrome-launcher/utils.js +52 -0
  56. data/node_modules/chrome-launcher/utils.ts +44 -0
  57. data/node_modules/chrome-launcher/yarn.lock +1486 -0
  58. data/node_modules/chrome-remote-interface/LICENSE +18 -0
  59. data/node_modules/chrome-remote-interface/README.md +849 -0
  60. data/node_modules/chrome-remote-interface/bin/client.js +337 -0
  61. data/node_modules/chrome-remote-interface/chrome-remote-interface.js +11 -0
  62. data/node_modules/chrome-remote-interface/index.js +39 -0
  63. data/node_modules/chrome-remote-interface/lib/api.js +84 -0
  64. data/node_modules/chrome-remote-interface/lib/chrome.js +307 -0
  65. data/node_modules/chrome-remote-interface/lib/defaults.js +4 -0
  66. data/node_modules/chrome-remote-interface/lib/devtools.js +245 -0
  67. data/node_modules/chrome-remote-interface/lib/external-request.js +28 -0
  68. data/node_modules/chrome-remote-interface/lib/protocol.json +13780 -0
  69. data/node_modules/chrome-remote-interface/lib/websocket-wrapper.js +32 -0
  70. data/node_modules/chrome-remote-interface/package.json +128 -0
  71. data/node_modules/chrome-remote-interface/webpack.config.js +55 -0
  72. data/node_modules/commander/Readme.md +195 -0
  73. data/node_modules/commander/index.js +851 -0
  74. data/node_modules/commander/package.json +92 -0
  75. data/node_modules/concat-map/.travis.yml +4 -0
  76. data/node_modules/concat-map/LICENSE +18 -0
  77. data/node_modules/concat-map/README.markdown +62 -0
  78. data/node_modules/concat-map/example/map.js +6 -0
  79. data/node_modules/concat-map/index.js +13 -0
  80. data/node_modules/concat-map/package.json +117 -0
  81. data/node_modules/concat-map/test/map.js +39 -0
  82. data/node_modules/debug/.coveralls.yml +1 -0
  83. data/node_modules/debug/.eslintrc +11 -0
  84. data/node_modules/debug/.npmignore +9 -0
  85. data/node_modules/debug/.travis.yml +14 -0
  86. data/node_modules/debug/CHANGELOG.md +357 -0
  87. data/node_modules/debug/LICENSE +19 -0
  88. data/node_modules/debug/Makefile +50 -0
  89. data/node_modules/debug/Readme.md +312 -0
  90. data/node_modules/debug/component.json +19 -0
  91. data/node_modules/debug/karma.conf.js +70 -0
  92. data/node_modules/debug/node.js +1 -0
  93. data/node_modules/debug/package.json +124 -0
  94. data/node_modules/debug/src/browser.js +185 -0
  95. data/node_modules/debug/src/debug.js +202 -0
  96. data/node_modules/debug/src/index.js +10 -0
  97. data/node_modules/debug/src/node.js +246 -0
  98. data/node_modules/fs.realpath/LICENSE +43 -0
  99. data/node_modules/fs.realpath/README.md +33 -0
  100. data/node_modules/fs.realpath/index.js +66 -0
  101. data/node_modules/fs.realpath/old.js +303 -0
  102. data/node_modules/fs.realpath/package.json +94 -0
  103. data/node_modules/glob/LICENSE +15 -0
  104. data/node_modules/glob/README.md +368 -0
  105. data/node_modules/glob/changelog.md +67 -0
  106. data/node_modules/glob/common.js +240 -0
  107. data/node_modules/glob/glob.js +790 -0
  108. data/node_modules/glob/package.json +112 -0
  109. data/node_modules/glob/sync.js +486 -0
  110. data/node_modules/html-pdf-chrome/.npmignore +9 -0
  111. data/node_modules/html-pdf-chrome/LICENSE +21 -0
  112. data/node_modules/html-pdf-chrome/README.md +165 -0
  113. data/node_modules/html-pdf-chrome/lib/src/ChromePrintOptions.d.ts +87 -0
  114. data/node_modules/html-pdf-chrome/lib/src/ChromePrintOptions.js +4 -0
  115. data/node_modules/html-pdf-chrome/lib/src/ChromePrintOptions.js.map +1 -0
  116. data/node_modules/html-pdf-chrome/lib/src/CompletionTrigger.d.ts +120 -0
  117. data/node_modules/html-pdf-chrome/lib/src/CompletionTrigger.js +206 -0
  118. data/node_modules/html-pdf-chrome/lib/src/CompletionTrigger.js.map +1 -0
  119. data/node_modules/html-pdf-chrome/lib/src/CreateResult.d.ts +70 -0
  120. data/node_modules/html-pdf-chrome/lib/src/CreateResult.js +98 -0
  121. data/node_modules/html-pdf-chrome/lib/src/CreateResult.js.map +1 -0
  122. data/node_modules/html-pdf-chrome/lib/src/index.d.ts +72 -0
  123. data/node_modules/html-pdf-chrome/lib/src/index.js +123 -0
  124. data/node_modules/html-pdf-chrome/lib/src/index.js.map +1 -0
  125. data/node_modules/html-pdf-chrome/package.json +133 -0
  126. data/node_modules/html-pdf-chrome/src/ChromePrintOptions.ts +99 -0
  127. data/node_modules/html-pdf-chrome/src/CompletionTrigger.ts +201 -0
  128. data/node_modules/html-pdf-chrome/src/CreateResult.ts +100 -0
  129. data/node_modules/html-pdf-chrome/src/index.ts +179 -0
  130. data/node_modules/inflight/LICENSE +15 -0
  131. data/node_modules/inflight/README.md +37 -0
  132. data/node_modules/inflight/inflight.js +54 -0
  133. data/node_modules/inflight/package.json +105 -0
  134. data/node_modules/inherits/LICENSE +16 -0
  135. data/node_modules/inherits/README.md +42 -0
  136. data/node_modules/inherits/inherits.js +7 -0
  137. data/node_modules/inherits/inherits_browser.js +23 -0
  138. data/node_modules/inherits/package.json +97 -0
  139. data/node_modules/lighthouse-logger/README.md +4 -0
  140. data/node_modules/lighthouse-logger/index.js +212 -0
  141. data/node_modules/lighthouse-logger/package.json +69 -0
  142. data/node_modules/lighthouse-logger/yarn.lock +13 -0
  143. data/node_modules/minimatch/LICENSE +15 -0
  144. data/node_modules/minimatch/README.md +209 -0
  145. data/node_modules/minimatch/minimatch.js +923 -0
  146. data/node_modules/minimatch/package.json +99 -0
  147. data/node_modules/minimist/.travis.yml +4 -0
  148. data/node_modules/minimist/LICENSE +18 -0
  149. data/node_modules/minimist/example/parse.js +2 -0
  150. data/node_modules/minimist/index.js +187 -0
  151. data/node_modules/minimist/package.json +101 -0
  152. data/node_modules/minimist/readme.markdown +73 -0
  153. data/node_modules/minimist/test/dash.js +24 -0
  154. data/node_modules/minimist/test/default_bool.js +20 -0
  155. data/node_modules/minimist/test/dotted.js +16 -0
  156. data/node_modules/minimist/test/long.js +31 -0
  157. data/node_modules/minimist/test/parse.js +318 -0
  158. data/node_modules/minimist/test/parse_modified.js +9 -0
  159. data/node_modules/minimist/test/short.js +67 -0
  160. data/node_modules/minimist/test/whitespace.js +8 -0
  161. data/node_modules/mkdirp/.travis.yml +8 -0
  162. data/node_modules/mkdirp/LICENSE +21 -0
  163. data/node_modules/mkdirp/bin/cmd.js +33 -0
  164. data/node_modules/mkdirp/bin/usage.txt +12 -0
  165. data/node_modules/mkdirp/examples/pow.js +6 -0
  166. data/node_modules/mkdirp/index.js +98 -0
  167. data/node_modules/mkdirp/package.json +93 -0
  168. data/node_modules/mkdirp/readme.markdown +100 -0
  169. data/node_modules/mkdirp/test/chmod.js +41 -0
  170. data/node_modules/mkdirp/test/clobber.js +38 -0
  171. data/node_modules/mkdirp/test/mkdirp.js +28 -0
  172. data/node_modules/mkdirp/test/opts_fs.js +29 -0
  173. data/node_modules/mkdirp/test/opts_fs_sync.js +27 -0
  174. data/node_modules/mkdirp/test/perm.js +32 -0
  175. data/node_modules/mkdirp/test/perm_sync.js +36 -0
  176. data/node_modules/mkdirp/test/race.js +37 -0
  177. data/node_modules/mkdirp/test/rel.js +32 -0
  178. data/node_modules/mkdirp/test/return.js +25 -0
  179. data/node_modules/mkdirp/test/return_sync.js +24 -0
  180. data/node_modules/mkdirp/test/root.js +19 -0
  181. data/node_modules/mkdirp/test/sync.js +32 -0
  182. data/node_modules/mkdirp/test/umask.js +28 -0
  183. data/node_modules/mkdirp/test/umask_sync.js +32 -0
  184. data/node_modules/ms/README.md +51 -0
  185. data/node_modules/ms/index.js +152 -0
  186. data/node_modules/ms/license.md +21 -0
  187. data/node_modules/ms/package.json +109 -0
  188. data/node_modules/once/LICENSE +15 -0
  189. data/node_modules/once/README.md +79 -0
  190. data/node_modules/once/once.js +42 -0
  191. data/node_modules/once/package.json +101 -0
  192. data/node_modules/path-is-absolute/index.js +20 -0
  193. data/node_modules/path-is-absolute/license +21 -0
  194. data/node_modules/path-is-absolute/package.json +111 -0
  195. data/node_modules/path-is-absolute/readme.md +59 -0
  196. data/node_modules/rimraf/LICENSE +15 -0
  197. data/node_modules/rimraf/README.md +101 -0
  198. data/node_modules/rimraf/bin.js +50 -0
  199. data/node_modules/rimraf/package.json +99 -0
  200. data/node_modules/rimraf/rimraf.js +363 -0
  201. data/node_modules/ultron/LICENSE +22 -0
  202. data/node_modules/ultron/index.js +138 -0
  203. data/node_modules/ultron/package.json +112 -0
  204. data/node_modules/wrappy/LICENSE +15 -0
  205. data/node_modules/wrappy/README.md +36 -0
  206. data/node_modules/wrappy/package.json +97 -0
  207. data/node_modules/wrappy/wrappy.js +33 -0
  208. data/node_modules/ws/LICENSE +21 -0
  209. data/node_modules/ws/README.md +259 -0
  210. data/node_modules/ws/SECURITY.md +33 -0
  211. data/node_modules/ws/index.js +15 -0
  212. data/node_modules/ws/lib/BufferUtil.fallback.js +56 -0
  213. data/node_modules/ws/lib/BufferUtil.js +15 -0
  214. data/node_modules/ws/lib/ErrorCodes.js +28 -0
  215. data/node_modules/ws/lib/EventTarget.js +158 -0
  216. data/node_modules/ws/lib/Extensions.js +69 -0
  217. data/node_modules/ws/lib/PerMessageDeflate.js +339 -0
  218. data/node_modules/ws/lib/Receiver.js +520 -0
  219. data/node_modules/ws/lib/Sender.js +438 -0
  220. data/node_modules/ws/lib/Validation.fallback.js +9 -0
  221. data/node_modules/ws/lib/Validation.js +17 -0
  222. data/node_modules/ws/lib/WebSocket.js +705 -0
  223. data/node_modules/ws/lib/WebSocketServer.js +336 -0
  224. data/node_modules/ws/package.json +122 -0
  225. data/package.json +26 -0
  226. data/samples/sample.html +13 -0
  227. data/script/rasterize.js +18 -0
  228. metadata +325 -0
@@ -0,0 +1,307 @@
1
+ 'use strict';
2
+
3
+ const EventEmitter = require('events');
4
+ const util = require('util');
5
+
6
+ const WebSocket = require('ws');
7
+
8
+ const api = require('./api');
9
+ const defaults = require('./defaults');
10
+ const devtools = require('./devtools');
11
+
12
+ class ProtocolError extends Error {
13
+ constructor(response) {
14
+ let message = response.message;
15
+ if (response.data) {
16
+ message += ` (${response.data})`;
17
+ }
18
+ super(message);
19
+ // attach the original response as well
20
+ this.response = response;
21
+ }
22
+ }
23
+
24
+ class Chrome extends EventEmitter {
25
+ constructor(options, notifier) {
26
+ super();
27
+ // options
28
+ const defaultTarget = function (targets) {
29
+ // prefer type = 'page' inspectabe targets as they represents
30
+ // browser tabs (fall back to the first instectable target
31
+ // otherwise)
32
+ let backup;
33
+ let target = targets.find((target) => {
34
+ if (target.webSocketDebuggerUrl) {
35
+ backup = backup || target;
36
+ return target.type === 'page';
37
+ } else {
38
+ return false;
39
+ }
40
+ });
41
+ target = target || backup;
42
+ if (target) {
43
+ return target;
44
+ } else {
45
+ throw new Error('No inspectable targets');
46
+ }
47
+ };
48
+ options = options || {};
49
+ this.host = options.host || defaults.HOST;
50
+ this.port = options.port || defaults.PORT;
51
+ this.secure = !!(options.secure);
52
+ this.protocol = options.protocol;
53
+ this.remote = !!(options.remote);
54
+ this.target = options.target ||
55
+ /* backward compatibility */ options.tab || options.chooseTab
56
+ || defaultTarget;
57
+ // locals
58
+ EventEmitter.call(this);
59
+ this._notifier = notifier;
60
+ this._callbacks = {};
61
+ this._nextCommandId = 1;
62
+ // properties
63
+ this.webSocketUrl = undefined;
64
+ // operations
65
+ start.call(this);
66
+ }
67
+ }
68
+
69
+ // avoid misinterpreting protocol's members as custom util.inspect functions
70
+ Chrome.prototype.inspect = function (depth, options) {
71
+ options.customInspect = false;
72
+ return util.inspect(this, options);
73
+ };
74
+
75
+ Chrome.prototype.send = function (method, params, callback) {
76
+ const chrome = this;
77
+ if (typeof params === 'function') {
78
+ callback = params;
79
+ params = undefined;
80
+ }
81
+ // return a promise when a callback is not provided
82
+ if (typeof callback === 'function') {
83
+ enqueueCommand.call(chrome, method, params, callback);
84
+ } else {
85
+ return new Promise(function (fulfill, reject) {
86
+ enqueueCommand.call(chrome, method, params, function (error, response) {
87
+ if (error) {
88
+ reject(error instanceof Error
89
+ ? error // low-level WebSocket error
90
+ : new ProtocolError(response));
91
+ } else {
92
+ fulfill(response);
93
+ }
94
+ });
95
+ });
96
+ }
97
+ };
98
+
99
+ Chrome.prototype.close = function (callback) {
100
+ const chrome = this;
101
+ function closeWebSocket(callback) {
102
+ // don't notify on user-initiated shutdown ('disconnect' event)
103
+ chrome._ws.removeAllListeners('close');
104
+ chrome._ws.close();
105
+ chrome._ws.once('close', function () {
106
+ chrome._ws.removeAllListeners();
107
+ callback();
108
+ });
109
+ }
110
+ if (typeof callback === 'function') {
111
+ closeWebSocket(callback);
112
+ } else {
113
+ return new Promise(function (fulfill, reject) {
114
+ closeWebSocket(fulfill);
115
+ });
116
+ }
117
+ };
118
+
119
+ // send a command to the remote endpoint and register a callback for the reply
120
+ function enqueueCommand(method, params, callback) {
121
+ const chrome = this;
122
+ const id = chrome._nextCommandId++;
123
+ const message = {'id': id, 'method': method, 'params': params || {}};
124
+ chrome._ws.send(JSON.stringify(message), function (err) {
125
+ if (err) {
126
+ // handle low-level WebSocket errors
127
+ if (typeof callback === 'function') {
128
+ callback(err);
129
+ }
130
+ } else {
131
+ chrome._callbacks[id] = callback;
132
+ }
133
+ });
134
+ }
135
+
136
+ // initiate the connection process
137
+ function start() {
138
+ const chrome = this;
139
+ const options = {'host': chrome.host, 'port': chrome.port, 'secure': chrome.secure};
140
+ Promise.all([
141
+ // fetch the protocol and prepare the API
142
+ fetchProtocol.call(chrome, options).then(api.prepare.bind(chrome)),
143
+ // in the meanwhile fetch the WebSocket debugger URL
144
+ fetchDebuggerURL.call(chrome, options)
145
+ ]).then(function (values) {
146
+ // finally connect to the WebSocket
147
+ const url = values[1];
148
+ return connectToWebSocket.call(chrome, url);
149
+ }).then(function () {
150
+ // since the handler is executed synchronously, the emit() must be
151
+ // performed in the next tick so that uncaught errors in the client code
152
+ // are not intercepted by the Promise mechanism and therefore reported
153
+ // via the 'error' event
154
+ process.nextTick(function () {
155
+ chrome._notifier.emit('connect', chrome);
156
+ });
157
+ }).catch(function (err) {
158
+ chrome._notifier.emit('error', err);
159
+ });
160
+ }
161
+
162
+ // fetch the protocol according to 'protocol' and 'remote'
163
+ function fetchProtocol(options) {
164
+ const chrome = this;
165
+ return new Promise(function (fulfill, reject) {
166
+ // if a protocol has been provided then use it
167
+ if (chrome.protocol) {
168
+ fulfill(chrome.protocol);
169
+ }
170
+ // otherwise user either the local or the remote version
171
+ else {
172
+ options.remote = chrome.remote;
173
+ devtools.Protocol(options).then(function (protocol) {
174
+ fulfill(protocol.descriptor);
175
+ }).catch(reject);
176
+ }
177
+ });
178
+ }
179
+
180
+ // extract the debugger URL from a target-like object
181
+ function fetchFromObject(fulfill, reject, target) {
182
+ const url = (target || {}).webSocketDebuggerUrl;
183
+ if (url) {
184
+ fulfill(url);
185
+ } else {
186
+ const targetStr = JSON.stringify(target, null, 4);
187
+ const err = new Error('Invalid target ' + targetStr);
188
+ reject(err);
189
+ }
190
+ }
191
+
192
+ // fetch the WebSocket URL according to 'target'
193
+ function fetchDebuggerURL(options) {
194
+ const chrome = this;
195
+ return new Promise(function (fulfill, reject) {
196
+ // note: when DevTools are open or another WebSocket is connected to a
197
+ // given target the 'webSocketDebuggerUrl' field is not available
198
+ let userTarget = chrome.target;
199
+ switch (typeof userTarget) {
200
+ case 'string':
201
+ // use default host and port if omitted (and a relative URL is specified)
202
+ if (userTarget.startsWith('/')) {
203
+ const prefix = 'ws://' + chrome.host + ':' + chrome.port;
204
+ userTarget = prefix + userTarget;
205
+ }
206
+ // a WebSocket URL is specified by the user (e.g., node-inspector)
207
+ if (userTarget.match(/^wss?:/i)) {
208
+ fulfill(userTarget);
209
+ }
210
+ // a target id is specified by the user
211
+ else {
212
+ devtools.List(options).then(function (targets) {
213
+ return targets.find(function (target) {
214
+ return target.id === userTarget;
215
+ });
216
+ }).then(function (target) {
217
+ fetchFromObject(fulfill, reject, target);
218
+ }).catch(reject);
219
+ }
220
+ break;
221
+ case 'object':
222
+ // a target object is specified by the user
223
+ fetchFromObject(fulfill, reject, userTarget);
224
+ break;
225
+ case 'function':
226
+ // a function is specified by the user
227
+ devtools.List(options).then(function (targets) {
228
+ const result = userTarget(targets);
229
+ if (typeof result === 'number') {
230
+ return targets[result];
231
+ } else {
232
+ return result;
233
+ }
234
+ }).then(function (target) {
235
+ fetchFromObject(fulfill, reject, target);
236
+ }).catch(reject);
237
+ break;
238
+ default:
239
+ reject(new Error('Invalid target argument "' + chrome.target + '"'));
240
+ }
241
+ });
242
+ }
243
+
244
+ // establish the WebSocket connection and start processing user commands
245
+ function connectToWebSocket(url) {
246
+ const chrome = this;
247
+ return new Promise(function (fulfill, reject) {
248
+ // create the WebSocket
249
+ try {
250
+ if (chrome.secure) {
251
+ url = url.replace(/^ws:/i, 'wss:');
252
+ }
253
+ chrome.webSocketUrl = url;
254
+ chrome._ws = new WebSocket(url);
255
+ } catch (err) {
256
+ // handles bad URLs
257
+ reject(err);
258
+ return;
259
+ }
260
+ // set up event handlers
261
+ chrome._ws.on('open', function () {
262
+ fulfill();
263
+ });
264
+ chrome._ws.on('message', function (data) {
265
+ const message = JSON.parse(data);
266
+ handleMessage.call(chrome, message);
267
+ });
268
+ chrome._ws.on('close', function (code) {
269
+ chrome.emit('disconnect');
270
+ });
271
+ chrome._ws.on('error', function (err) {
272
+ reject(err);
273
+ });
274
+ });
275
+ }
276
+
277
+ // handle the messages read from the WebSocket
278
+ function handleMessage(message) {
279
+ const chrome = this;
280
+ // command response
281
+ if (message.id) {
282
+ const callback = chrome._callbacks[message.id];
283
+ if (!callback) {
284
+ return;
285
+ }
286
+ // interpret the lack of both 'error' and 'result' as success
287
+ // (this may happen with node-inspector)
288
+ if (message.error) {
289
+ callback(true, message.error);
290
+ } else {
291
+ callback(false, message.result || {});
292
+ }
293
+ // unregister command response callback
294
+ delete chrome._callbacks[message.id];
295
+ // notify when there are no more pending commands
296
+ if (Object.keys(chrome._callbacks).length === 0) {
297
+ chrome.emit('ready');
298
+ }
299
+ }
300
+ // event
301
+ else if (message.method) {
302
+ chrome.emit('event', message);
303
+ chrome.emit(message.method, message.params);
304
+ }
305
+ }
306
+
307
+ module.exports = Chrome;
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+
3
+ module.exports.HOST = 'localhost';
4
+ module.exports.PORT = 9222;
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const https = require('https');
5
+
6
+ const defaults = require('./defaults');
7
+ const externalRequest = require('./external-request');
8
+
9
+ // callback(err, protocol)
10
+ module.exports.Protocol = promisesWrapper(function (options, callback) {
11
+ // if the local protocol is requested
12
+ if (!options.remote) {
13
+ const localDescriptor = require('./protocol.json');
14
+ callback(null, {
15
+ 'remote': false,
16
+ 'descriptor': localDescriptor
17
+ });
18
+ return;
19
+ }
20
+ // try to fecth the browser version information and the protocol (remotely)
21
+ module.exports.Version(options, function (err, info) {
22
+ if (err) {
23
+ callback(err);
24
+ return;
25
+ }
26
+ // fetch the reported browser info (Node.js returns an array)
27
+ const browser = (info[0] || info).Browser;
28
+ // use the proper protocol fetcher
29
+ let fetcher;
30
+ if (browser.match(/^(Headless)?Chrome\//)) {
31
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=538300#c10
32
+ const firstJsonProtocolVersion = '60.0.3097.0';
33
+ const firstJsonProtocolBuild = explodeChromeVersion(firstJsonProtocolVersion)[2];
34
+ const chromeBuild = explodeChromeVersion(info.Browser.split('/')[1])[2];
35
+ if (chromeBuild < firstJsonProtocolBuild) {
36
+ fetcher = fetchFromChromeRepo;
37
+ } else {
38
+ fetcher = fetchFromHttpEndpoint;
39
+ }
40
+ } else if (browser.match(/^Microsoft Edge /)) {
41
+ fetcher = fetchFromHttpEndpoint;
42
+ } else if (browser.match(/^node.js\//)) {
43
+ fetcher = fetchFromHttpEndpoint;
44
+ } else {
45
+ callback(new Error('Unknown implementation'));
46
+ return;
47
+ }
48
+ fetcher(options, info, function (err, descriptor) {
49
+ if (err) {
50
+ callback(err);
51
+ return;
52
+ }
53
+ // use the remotely fetched descriptor
54
+ callback(null, {
55
+ 'remote': true,
56
+ 'descriptor': descriptor
57
+ });
58
+ });
59
+ });
60
+ });
61
+
62
+ module.exports.List = promisesWrapper(function (options, callback) {
63
+ options.path = '/json/list';
64
+ devToolsInterface(options, function (err, tabs) {
65
+ if (err) {
66
+ callback(err);
67
+ } else {
68
+ callback(null, JSON.parse(tabs));
69
+ }
70
+ });
71
+ });
72
+
73
+ module.exports.New = promisesWrapper(function (options, callback) {
74
+ options.path = '/json/new';
75
+ if (Object.prototype.hasOwnProperty.call(options, 'url')) {
76
+ options.path += '?' + options.url;
77
+ }
78
+ devToolsInterface(options, function (err, tab) {
79
+ if (err) {
80
+ callback(err);
81
+ } else {
82
+ callback(null, JSON.parse(tab));
83
+ }
84
+ });
85
+ });
86
+
87
+ module.exports.Activate = promisesWrapper(function (options, callback) {
88
+ options.path = '/json/activate/' + options.id;
89
+ devToolsInterface(options, function (err) {
90
+ if (err) {
91
+ callback(err);
92
+ } else {
93
+ callback(null);
94
+ }
95
+ });
96
+ });
97
+
98
+ module.exports.Close = promisesWrapper(function (options, callback) {
99
+ options.path = '/json/close/' + options.id;
100
+ devToolsInterface(options, function (err) {
101
+ if (err) {
102
+ callback(err);
103
+ } else {
104
+ callback(null);
105
+ }
106
+ });
107
+ });
108
+
109
+ module.exports.Version = promisesWrapper(function (options, callback) {
110
+ options.path = '/json/version';
111
+ devToolsInterface(options, function (err, versionInfo) {
112
+ if (err) {
113
+ callback(err);
114
+ } else {
115
+ callback(null, JSON.parse(versionInfo));
116
+ }
117
+ });
118
+ });
119
+
120
+ // options.path must be specified; callback(err, data)
121
+ function devToolsInterface(options, callback) {
122
+ options.host = options.host || defaults.HOST;
123
+ options.port = options.port || defaults.PORT;
124
+ options.secure = !!(options.secure);
125
+ externalRequest(options.secure ? https : http, options, callback);
126
+ }
127
+
128
+ // wrapper that allows to return a promise if the callback is omitted, it works
129
+ // for DevTools methods
130
+ function promisesWrapper(func) {
131
+ return function (options, callback) {
132
+ // options is an optional argument
133
+ if (typeof options === 'function') {
134
+ callback = options;
135
+ options = undefined;
136
+ }
137
+ options = options || {};
138
+ // just call the function otherwise wrap a promise around its execution
139
+ if (typeof callback === 'function') {
140
+ func(options, callback);
141
+ } else {
142
+ return new Promise(function (fulfill, reject) {
143
+ func(options, function (err, result) {
144
+ if (err) {
145
+ reject(err);
146
+ } else {
147
+ fulfill(result);
148
+ }
149
+ });
150
+ });
151
+ }
152
+ };
153
+ }
154
+
155
+ function explodeChromeVersion(v) {
156
+ return v.split('.').map(function (x) {
157
+ return parseInt(x);
158
+ });
159
+ }
160
+
161
+ // callback(err, descriptor)
162
+ // XXX this function needs a proper refactor but the inconsistency of the
163
+ // fetching process makes it useless for now
164
+ function fetchFromChromeRepo(options, info, callback) {
165
+ // attempt to fetch the protocol directly from the Chromium repository
166
+ // according to the current version
167
+ //
168
+ // Thanks to Paul Irish.
169
+ // (see https://github.com/cyrus-and/chrome-remote-interface/issues/10#issuecomment-146032907)
170
+ const webKitVersion = info['WebKit-Version'];
171
+ const v8Version = info['V8-Version'];
172
+ const match = webKitVersion.match(/\s\(@(\b[0-9a-f]{5,40}\b)/);
173
+ const hash = match[1];
174
+ const fromChromiumDotOrg = (hash <= 202666);
175
+ let urls;
176
+ if (fromChromiumDotOrg) {
177
+ urls = [`https://src.chromium.org/blink/trunk/Source/devtools/protocol.json?p=${hash}`];
178
+ } else {
179
+ const lastBeforeSplitChromeVersion = '53.0.2758.1'; // before the split (https://crbug.com/580337)
180
+ const lastBeforeV8ChromeVersion = '55.0.2854.3'; // before using the JSON from the V8 repo
181
+ const chromeVersion = explodeChromeVersion(info.Browser.split('/')[1]);
182
+ // according to https://www.chromium.org/developers/version-numbers (patch not meaningful)
183
+ const beforeSplit = (chromeVersion[2] <= explodeChromeVersion(lastBeforeSplitChromeVersion)[2]);
184
+ const beforeFromV8 = (chromeVersion[2] <= explodeChromeVersion(lastBeforeV8ChromeVersion)[2]);
185
+ if (beforeSplit) {
186
+ urls = [`https://chromium.googlesource.com/chromium/src/+/${hash}/third_party/WebKit/Source/devtools/protocol.json?format=TEXT`];
187
+ } else if (beforeFromV8) {
188
+ urls = [`https://chromium.googlesource.com/chromium/src/+/${hash}/third_party/WebKit/Source/core/inspector/browser_protocol.json?format=TEXT`,
189
+ `https://chromium.googlesource.com/chromium/src/+/${hash}/third_party/WebKit/Source/platform/v8_inspector/js_protocol.json?format=TEXT`];
190
+ } else if (v8Version) {
191
+ urls = [`https://chromium.googlesource.com/chromium/src/+/${hash}/third_party/WebKit/Source/core/inspector/browser_protocol.json?format=TEXT`,
192
+ `https://chromium.googlesource.com/v8/v8/+/${v8Version}/src/inspector/js_protocol.json?format=TEXT`];
193
+ } else {
194
+ console.error('Warning: the protocol might be outdated, see: https://groups.google.com/d/topic/chrome-debugging-protocol/HjyOKainKus/discussion');
195
+ // releases which do not provide a V8 version get an old version of the V8 protocol
196
+ urls = [`https://chromium.googlesource.com/chromium/src/+/${hash}/third_party/WebKit/Source/core/inspector/browser_protocol.json?format=TEXT`,
197
+ `https://chromium.googlesource.com/chromium/src/+/${lastBeforeV8ChromeVersion}/third_party/WebKit/Source/platform/v8_inspector/js_protocol.json?format=TEXT`];
198
+ }
199
+ }
200
+ const descriptors = [];
201
+ urls.forEach(function (url) {
202
+ externalRequest(https, url, function (err, data) {
203
+ let descriptor;
204
+ if (!err) {
205
+ try {
206
+ // the file is served base64 encoded from googlesource.com
207
+ if (!fromChromiumDotOrg) {
208
+ data = new Buffer(data, 'base64').toString();
209
+ }
210
+ descriptor = JSON.parse(data);
211
+ } catch (_) {
212
+ // abort later
213
+ }
214
+ }
215
+ descriptors.push(descriptor);
216
+ if (descriptors.length === urls.length) {
217
+ // all must be defined
218
+ if (descriptors.indexOf(undefined) !== -1) {
219
+ callback(new Error('Cannot fetch from Chromium repo'));
220
+ return;
221
+ }
222
+ // merge the domains
223
+ descriptors.forEach(function (descriptor, i) {
224
+ if (i === 0) {
225
+ return;
226
+ }
227
+ Array.prototype.push.apply(descriptors[0].domains, descriptor.domains);
228
+ });
229
+ callback(null, descriptors[0]);
230
+ }
231
+ });
232
+ });
233
+ }
234
+
235
+ // callback(err, descriptor)
236
+ function fetchFromHttpEndpoint(options, info, callback) {
237
+ options.path = '/json/protocol';
238
+ devToolsInterface(options, function (err, descriptor) {
239
+ if (err) {
240
+ callback(err);
241
+ } else {
242
+ callback(null, JSON.parse(descriptor));
243
+ }
244
+ });
245
+ }