@browserless.io/browserless 2.3.0 → 2.4.0-beta-3

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 (100) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/bin/browserless.js +2 -0
  3. package/bin/scaffold/README.md +26 -1
  4. package/build/browserless.d.ts +6 -2
  5. package/build/browserless.js +23 -4
  6. package/build/browsers/chromium.cdp.d.ts +1 -5
  7. package/build/browsers/chromium.cdp.js +5 -115
  8. package/build/browsers/chromium.playwright.d.ts +1 -3
  9. package/build/browsers/chromium.playwright.js +1 -6
  10. package/build/browsers/firefox.playwright.d.ts +1 -3
  11. package/build/browsers/firefox.playwright.js +1 -6
  12. package/build/browsers/index.d.ts +5 -1
  13. package/build/browsers/index.js +6 -5
  14. package/build/browsers/webkit.playwright.d.ts +1 -3
  15. package/build/browsers/webkit.playwright.js +1 -6
  16. package/build/config.d.ts +9 -0
  17. package/build/config.js +11 -0
  18. package/build/constants.d.ts +0 -1
  19. package/build/constants.js +0 -1
  20. package/build/data/selectors.json +1 -1
  21. package/build/file-system.d.ts +12 -1
  22. package/build/file-system.js +14 -1
  23. package/build/http.d.ts +0 -7
  24. package/build/limiter.d.ts +9 -0
  25. package/build/limiter.js +11 -0
  26. package/build/metrics.d.ts +12 -1
  27. package/build/metrics.js +13 -1
  28. package/build/monitoring.d.ts +12 -1
  29. package/build/monitoring.js +14 -1
  30. package/build/router.d.ts +12 -2
  31. package/build/router.js +16 -6
  32. package/build/routes/chrome/http/content.post.body.json +8 -8
  33. package/build/routes/chrome/http/content.post.query.json +0 -4
  34. package/build/routes/chrome/http/download.post.query.json +0 -4
  35. package/build/routes/chrome/http/function.post.query.json +0 -4
  36. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  37. package/build/routes/chrome/http/pdf.post.query.json +0 -4
  38. package/build/routes/chrome/http/performance.post.query.json +0 -4
  39. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  40. package/build/routes/chrome/http/scrape.post.query.json +0 -4
  41. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  42. package/build/routes/chrome/http/screenshot.post.query.json +0 -4
  43. package/build/routes/chrome/tests/function.spec.js +32 -0
  44. package/build/routes/chrome/ws/browser.query.json +0 -4
  45. package/build/routes/chrome/ws/cdp.query.json +0 -4
  46. package/build/routes/chrome/ws/page.query.json +0 -4
  47. package/build/routes/chrome/ws/playwright.query.json +0 -4
  48. package/build/routes/chromium/http/content.post.body.json +8 -8
  49. package/build/routes/chromium/http/content.post.query.json +0 -4
  50. package/build/routes/chromium/http/download.post.query.json +0 -4
  51. package/build/routes/chromium/http/function.post.query.json +0 -4
  52. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  53. package/build/routes/chromium/http/pdf.post.query.json +0 -4
  54. package/build/routes/chromium/http/performance.post.query.json +0 -4
  55. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  56. package/build/routes/chromium/http/scrape.post.query.json +0 -4
  57. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  58. package/build/routes/chromium/http/screenshot.post.query.json +0 -4
  59. package/build/routes/chromium/ws/browser.query.json +0 -4
  60. package/build/routes/chromium/ws/cdp.query.json +0 -4
  61. package/build/routes/chromium/ws/page.query.json +0 -4
  62. package/build/routes/chromium/ws/playwright.query.json +0 -4
  63. package/build/routes/firefox/ws/playwright.query.json +0 -4
  64. package/build/routes/management/http/static.get.js +17 -11
  65. package/build/routes/webkit/ws/playwright.query.json +0 -4
  66. package/build/server.d.ts +8 -3
  67. package/build/server.js +15 -13
  68. package/build/token.d.ts +12 -1
  69. package/build/token.js +14 -1
  70. package/build/types.d.ts +9 -12
  71. package/build/types.js +10 -1
  72. package/build/webhooks.d.ts +12 -1
  73. package/build/webhooks.js +14 -1
  74. package/extensions/.gitkeep +0 -0
  75. package/package.json +5 -5
  76. package/src/browserless.ts +24 -2
  77. package/src/browsers/chromium.cdp.ts +10 -157
  78. package/src/browsers/chromium.playwright.ts +0 -8
  79. package/src/browsers/firefox.playwright.ts +0 -8
  80. package/src/browsers/index.ts +7 -6
  81. package/src/browsers/webkit.playwright.ts +0 -8
  82. package/src/config.ts +13 -0
  83. package/src/constants.ts +0 -1
  84. package/src/file-system.ts +16 -1
  85. package/src/http.ts +0 -8
  86. package/src/limiter.ts +13 -0
  87. package/src/metrics.ts +16 -1
  88. package/src/monitoring.ts +18 -2
  89. package/src/router.ts +20 -9
  90. package/src/routes/chrome/tests/function.spec.ts +35 -0
  91. package/src/routes/management/http/static.get.ts +25 -15
  92. package/src/server.ts +18 -16
  93. package/src/token.ts +18 -2
  94. package/src/types.ts +9 -13
  95. package/src/webhooks.ts +18 -2
  96. package/static/docs/swagger.json +10 -186
  97. package/static/docs/swagger.min.json +9 -185
  98. package/extensions/screencast/background.js +0 -143
  99. package/extensions/screencast/content_script.js +0 -18
  100. package/extensions/screencast/manifest.json +0 -19
package/src/limiter.ts CHANGED
@@ -226,4 +226,17 @@ export class Limiter extends q {
226
226
  return bound;
227
227
  });
228
228
  };
229
+
230
+ /**
231
+ * Implement any browserless-core-specific shutdown logic here.
232
+ * Calls the empty-SDK stop method for downstream implementations.
233
+ */
234
+ public shutdown = async () => {
235
+ await this.stop();
236
+ };
237
+
238
+ /**
239
+ * Left blank for downstream SDK modules to optionally implement.
240
+ */
241
+ public stop = () => {};
229
242
  }
package/src/metrics.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { IBrowserlessStats } from '@browserless.io/browserless';
2
2
 
3
- export class Metrics {
3
+ import { EventEmitter } from 'events';
4
+
5
+ export class Metrics extends EventEmitter {
4
6
  protected sessionTimes: number[] = [];
5
7
  protected successful = 0;
6
8
  protected queued = 0;
@@ -105,4 +107,17 @@ export class Metrics {
105
107
  ),
106
108
  };
107
109
  }
110
+
111
+ /**
112
+ * Implement any browserless-core-specific shutdown logic here.
113
+ * Calls the empty-SDK stop method for downstream implementations.
114
+ */
115
+ public shutdown = async () => {
116
+ await this.stop();
117
+ };
118
+
119
+ /**
120
+ * Left blank for downstream SDK modules to optionally implement.
121
+ */
122
+ public stop = () => {};
108
123
  }
package/src/monitoring.ts CHANGED
@@ -3,11 +3,14 @@ import {
3
3
  IResourceLoad,
4
4
  createLogger,
5
5
  } from '@browserless.io/browserless';
6
+ import { EventEmitter } from 'events';
6
7
  import si from 'systeminformation';
7
8
 
8
- export class Monitoring {
9
+ export class Monitoring extends EventEmitter {
9
10
  protected log = createLogger('hardware');
10
- constructor(protected config: Config) {}
11
+ constructor(protected config: Config) {
12
+ super();
13
+ }
11
14
 
12
15
  public getMachineStats = async (): Promise<IResourceLoad> => {
13
16
  const [cpuLoad, memLoad] = await Promise.all([
@@ -50,4 +53,17 @@ export class Monitoring {
50
53
  memoryOverloaded,
51
54
  };
52
55
  };
56
+
57
+ /**
58
+ * Implement any browserless-core-specific shutdown logic here.
59
+ * Calls the empty-SDK stop method for downstream implementations.
60
+ */
61
+ public shutdown = async () => {
62
+ await this.stop();
63
+ };
64
+
65
+ /**
66
+ * Left blank for downstream SDK modules to optionally implement.
67
+ */
68
+ public stop = () => {};
53
69
  }
package/src/router.ts CHANGED
@@ -16,10 +16,11 @@ import {
16
16
  isConnected,
17
17
  writeResponse,
18
18
  } from '@browserless.io/browserless';
19
+ import { EventEmitter } from 'events';
19
20
  import micromatch from 'micromatch';
20
21
  import stream from 'stream';
21
22
 
22
- export class Router {
23
+ export class Router extends EventEmitter {
23
24
  protected log = createLogger('router');
24
25
  protected verbose = createLogger('router:verbose');
25
26
  protected httpRoutes: Array<HTTPRoute | BrowserHTTPRoute> = [];
@@ -29,7 +30,9 @@ export class Router {
29
30
  protected config: Config,
30
31
  protected browserManager: BrowserManager,
31
32
  protected limiter: Limiter,
32
- ) {}
33
+ ) {
34
+ super();
35
+ }
33
36
 
34
37
  protected getTimeout(req: Request) {
35
38
  const timer = req.parsed.searchParams.get('timeout');
@@ -207,13 +210,6 @@ export class Router {
207
210
  return route;
208
211
  }
209
212
 
210
- public teardown() {
211
- this.httpRoutes = [];
212
- this.webSocketRoutes = [];
213
-
214
- return this.browserManager.stop();
215
- }
216
-
217
213
  public getStaticHandler() {
218
214
  return this.httpRoutes.find((route) =>
219
215
  route.path.includes(HTTPManagementRoutes.static),
@@ -253,4 +249,19 @@ export class Router {
253
249
  (r.path as Array<PathTypes>).some((p) => micromatch.isMatch(pathname, p)),
254
250
  );
255
251
  }
252
+
253
+ /**
254
+ * Implement any browserless-core-specific shutdown logic here.
255
+ * Calls the empty-SDK stop method for downstream implementations.
256
+ */
257
+ public shutdown = async () => {
258
+ this.httpRoutes = [];
259
+ this.webSocketRoutes = [];
260
+ await this.stop();
261
+ };
262
+
263
+ /**
264
+ * Left blank for downstream SDK modules to optionally implement.
265
+ */
266
+ public stop = () => {};
256
267
  }
@@ -46,6 +46,41 @@ describe('/chrome/function API', function () {
46
46
  });
47
47
  });
48
48
 
49
+ it('runs functions with "context"', async () => {
50
+ const config = new Config();
51
+ config.setToken('browserless');
52
+ const metrics = new Metrics();
53
+ await start({ config, metrics });
54
+ const body = {
55
+ code: `export default async function ({ page, context }) {
56
+ if (!!context.ok) {
57
+ return Promise.resolve({
58
+ data: "ok",
59
+ type: "application/text",
60
+ });
61
+ }
62
+ return Promise.reject(new Error('Bad context!'));
63
+ }`,
64
+ context: {
65
+ ok: true,
66
+ },
67
+ };
68
+
69
+ await fetch('http://localhost:3000/chrome/function?token=browserless', {
70
+ body: JSON.stringify(body),
71
+ headers: {
72
+ 'content-type': 'application/json',
73
+ },
74
+ method: 'POST',
75
+ }).then(async (res) => {
76
+ const json = await res.json();
77
+
78
+ expect(json).to.have.property('data');
79
+ expect(json.data).to.equal('ok');
80
+ expect(res.status).to.equal(200);
81
+ });
82
+ });
83
+
49
84
  it('runs "application/javascript" functions', async () => {
50
85
  const config = new Config();
51
86
  config.setToken('browserless');
@@ -70,39 +70,49 @@ export default class StaticGetRoute extends HTTPRoute {
70
70
  }
71
71
 
72
72
  const config = this.config();
73
+ const sdkDir = this.staticSDKDir();
73
74
  const file = path.join(config.getStatic(), pathname);
74
75
  const indexFile = path.join(file, 'index.html');
76
+ const locations = [file, indexFile];
75
77
 
76
- const filePath = (
77
- await Promise.all([
78
- fileExists(file).then((exists) => (exists ? file : undefined)),
79
- fileExists(indexFile).then((exists) =>
80
- exists ? indexFile : undefined,
81
- ),
82
- ])
83
- ).find((_) => !!_);
78
+ if (sdkDir) {
79
+ const sdkPath = path.join(sdkDir, pathname);
80
+ locations.push(...[sdkPath, path.join(sdkPath, 'index.html')]);
81
+ }
82
+
83
+ const foundFilePaths = (
84
+ await Promise.all(
85
+ locations.map((l) => fileExists(l).then((e) => (e ? l : undefined))),
86
+ )
87
+ ).filter((_) => !!_) as string[];
84
88
 
85
- if (!filePath) {
89
+ if (!foundFilePaths.length) {
86
90
  throw new NotFound(
87
91
  `No route or file found for resource ${req.method}: ${pathname}`,
88
92
  );
89
93
  }
90
94
 
91
- verbose(`Found new file "${filePath}", caching path and serving`);
95
+ if (foundFilePaths.length > 1) {
96
+ debug(
97
+ `Multiple files found for request to "${pathname}". Only the first file is served, so please name your files uniquely.`,
98
+ );
99
+ }
100
+
101
+ const [foundFilePath] = foundFilePaths;
102
+ verbose(`Found new file "${foundFilePath}", caching path and serving`);
92
103
 
93
- const contentType = mimeTypes.get(path.extname(filePath));
104
+ const contentType = mimeTypes.get(path.extname(foundFilePath));
94
105
 
95
106
  if (contentType) {
96
107
  res.setHeader('Content-Type', contentType);
97
108
  }
98
109
 
99
- // Cache the assets location so we don't have to
100
- // do stat checks again when requests come back
110
+ // Cache the file as being found so we don't have to call 'stat'
101
111
  pathMap.set(pathname, {
102
112
  contentType,
103
- path: filePath,
113
+ path: foundFilePath,
104
114
  });
105
115
 
106
- return streamFile(verbose, res, filePath, contentType);
116
+ return streamFile(verbose, res, foundFilePath, contentType);
107
117
  };
108
118
  }
package/src/server.ts CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  shimLegacyRequests,
24
24
  writeResponse,
25
25
  } from '@browserless.io/browserless';
26
+ import { EventEmitter } from 'events';
26
27
 
27
28
  // @ts-ignore
28
29
  import Enjoi from 'enjoi';
@@ -35,7 +36,7 @@ export interface HTTPServerOptions {
35
36
  timeout: number;
36
37
  }
37
38
 
38
- export class HTTPServer {
39
+ export class HTTPServer extends EventEmitter {
39
40
  protected server: http.Server = http.createServer();
40
41
  protected port: number;
41
42
  protected host?: string;
@@ -48,6 +49,7 @@ export class HTTPServer {
48
49
  protected token: Token,
49
50
  protected router: Router,
50
51
  ) {
52
+ super();
51
53
  this.host = config.getHost();
52
54
  this.port = config.getPort();
53
55
 
@@ -100,21 +102,6 @@ export class HTTPServer {
100
102
  });
101
103
  }
102
104
 
103
- public async stop(): Promise<void> {
104
- this.log(`HTTP Server is shutting down`);
105
- await new Promise((r) => this.server.close(r));
106
- await Promise.all([this.tearDown(), this.router.teardown()]);
107
- this.log(`HTTP Server shutdown complete`);
108
- }
109
-
110
- protected tearDown() {
111
- this.log(`Tearing down all listeners and internal routes`);
112
- this.server && this.server.removeAllListeners();
113
-
114
- // @ts-ignore garbage collect this reference
115
- this.server = null;
116
- }
117
-
118
105
  protected handleRequest = async (
119
106
  request: http.IncomingMessage,
120
107
  res: http.ServerResponse,
@@ -410,4 +397,19 @@ export class HTTPServer {
410
397
  this.log(`No matching WebSocket route handler for "${req.parsed.href}"`);
411
398
  return writeResponse(socket, 404, 'Not Found');
412
399
  };
400
+
401
+ public async shutdown(): Promise<void> {
402
+ this.log(`HTTP Server is shutting down`);
403
+ await new Promise((r) => this.server.close(r));
404
+ this.server && this.server.removeAllListeners();
405
+
406
+ // @ts-ignore garbage collect this reference
407
+ this.server = null;
408
+ this.log(`HTTP Server shutdown complete`);
409
+ }
410
+
411
+ /**
412
+ * Left blank for downstream SDK modules to optionally implement.
413
+ */
414
+ public stop = () => {};
413
415
  }
package/src/token.ts CHANGED
@@ -7,9 +7,12 @@ import {
7
7
  WebSocketRoute,
8
8
  getTokenFromRequest,
9
9
  } from '@browserless.io/browserless';
10
+ import { EventEmitter } from 'events';
10
11
 
11
- export class Token {
12
- constructor(protected config: Config) {}
12
+ export class Token extends EventEmitter {
13
+ constructor(protected config: Config) {
14
+ super();
15
+ }
13
16
 
14
17
  public isAuthorized = async (
15
18
  req: Request,
@@ -37,4 +40,17 @@ export class Token {
37
40
 
38
41
  return (Array.isArray(token) ? token : [token]).includes(requestToken);
39
42
  };
43
+
44
+ /**
45
+ * Implement any browserless-core-specific shutdown logic here.
46
+ * Calls the empty-SDK stop method for downstream implementations.
47
+ */
48
+ public shutdown = async () => {
49
+ await this.stop();
50
+ };
51
+
52
+ /**
53
+ * Left blank for downstream SDK modules to optionally implement.
54
+ */
55
+ public stop = () => {};
40
56
  }
package/src/types.ts CHANGED
@@ -102,6 +102,7 @@ abstract class Route {
102
102
  protected _debug: Browserless['debug'],
103
103
  protected _metrics: Browserless['metrics'],
104
104
  protected _monitoring: Browserless['monitoring'],
105
+ protected _staticSDKDir: Browserless['staticSDKDir'],
105
106
  ) {}
106
107
 
107
108
  /**
@@ -195,6 +196,14 @@ abstract class Route {
195
196
  */
196
197
  monitoring = () => this._monitoring;
197
198
 
199
+ /**
200
+ * When running in an SDK environment, this returns the fully-qualified
201
+ * directory of that static directory. When "null" then no SDK directory
202
+ * has been set.
203
+ * @returns {string | null} The full path location of the SDK's static directory
204
+ */
205
+ staticSDKDir = () => this._staticSDKDir;
206
+
198
207
  /**
199
208
  * The HTTP path that this route handles, eg '/my-route' OR an
200
209
  * array of paths that this route can handle.
@@ -469,19 +478,6 @@ export const debugScreenshotOpts: ScreenshotOptions = {
469
478
  type: 'jpeg',
470
479
  };
471
480
 
472
- declare global {
473
- interface Window {
474
- browserless: BrowserlessEmbeddedAPI;
475
- }
476
- }
477
-
478
- export interface BrowserlessEmbeddedAPI {
479
- getRecording: () => Promise<string>;
480
- liveUrl: () => string;
481
- saveRecording: () => Promise<boolean>;
482
- startRecording: () => void;
483
- }
484
-
485
481
  /**
486
482
  * When bestAttempt is set to true, browserless attempt to proceed
487
483
  * when "awaited" events fail or timeout. This includes things like
package/src/webhooks.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { Config, fetchTimeout, noop } from '@browserless.io/browserless';
2
+ import { EventEmitter } from 'events';
2
3
 
3
- export class WebHooks {
4
- constructor(protected config: Config) {}
4
+ export class WebHooks extends EventEmitter {
5
+ constructor(protected config: Config) {
6
+ super();
7
+ }
5
8
 
6
9
  protected callURL(url: string | null) {
7
10
  if (url) {
@@ -47,4 +50,17 @@ export class WebHooks {
47
50
  );
48
51
  }
49
52
  }
53
+
54
+ /**
55
+ * Implement any browserless-core-specific shutdown logic here.
56
+ * Calls the empty-SDK stop method for downstream implementations.
57
+ */
58
+ public shutdown = async () => {
59
+ await this.stop();
60
+ };
61
+
62
+ /**
63
+ * Left blank for downstream SDK modules to optionally implement.
64
+ */
65
+ public stop = () => {};
50
66
  }