@browserless.io/browserless 2.4.0-beta-3 → 2.5.0-beta-1

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 (58) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/bin/browserless.js +17 -127
  3. package/bin/scaffold/README.md +95 -14
  4. package/build/browserless.d.ts +4 -2
  5. package/build/browserless.js +11 -8
  6. package/build/browsers/index.d.ts +3 -2
  7. package/build/browsers/index.js +6 -4
  8. package/build/data/selectors.json +1 -1
  9. package/build/exports.d.ts +1 -0
  10. package/build/exports.js +1 -0
  11. package/build/file-system.d.ts +2 -3
  12. package/build/file-system.js +10 -21
  13. package/build/file-system.spec.js +35 -18
  14. package/build/hooks.d.ts +9 -4
  15. package/build/hooks.js +26 -8
  16. package/build/limiter.d.ts +3 -2
  17. package/build/limiter.js +5 -3
  18. package/build/limiter.spec.js +45 -26
  19. package/build/routes/chrome/http/content.post.body.json +8 -8
  20. package/build/routes/chrome/http/pdf.post.body.json +8 -11
  21. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  22. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  23. package/build/routes/chromium/http/content.post.body.json +8 -8
  24. package/build/routes/chromium/http/pdf.post.body.json +8 -11
  25. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  26. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  27. package/build/routes/management/http/metrics-total.get.js +1 -1
  28. package/build/routes/management/http/metrics.get.js +2 -2
  29. package/build/sdk-utils.d.ts +13 -0
  30. package/build/sdk-utils.js +94 -0
  31. package/build/server.d.ts +3 -2
  32. package/build/server.js +6 -4
  33. package/build/shared/content.http.js +1 -1
  34. package/build/shared/pdf.http.d.ts +0 -1
  35. package/build/shared/pdf.http.js +1 -1
  36. package/build/shared/screenshot.http.js +1 -1
  37. package/build/types.d.ts +3 -3
  38. package/build/utils.js +2 -1
  39. package/package.json +10 -10
  40. package/src/browserless.ts +22 -5
  41. package/src/browsers/index.ts +7 -5
  42. package/src/exports.ts +1 -0
  43. package/src/file-system.spec.ts +43 -18
  44. package/src/file-system.ts +16 -30
  45. package/src/hooks.ts +32 -8
  46. package/src/limiter.spec.ts +82 -112
  47. package/src/limiter.ts +3 -3
  48. package/src/routes/management/http/metrics-total.get.ts +3 -3
  49. package/src/routes/management/http/metrics.get.ts +2 -2
  50. package/src/sdk-utils.ts +136 -0
  51. package/src/server.ts +4 -3
  52. package/src/shared/content.http.ts +4 -4
  53. package/src/shared/pdf.http.ts +4 -5
  54. package/src/shared/screenshot.http.ts +4 -4
  55. package/src/utils.ts +2 -1
  56. package/static/docs/swagger.json +11 -17
  57. package/static/docs/swagger.min.json +10 -16
  58. package/static/function/client.js +76 -63
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserless.io/browserless",
3
- "version": "2.4.0-beta-3",
3
+ "version": "2.5.0-beta-1",
4
4
  "license": "SSPL",
5
5
  "description": "The browserless platform",
6
6
  "author": "browserless.io",
@@ -29,7 +29,7 @@
29
29
  "install:dev": "npm run install:browsers",
30
30
  "lint": "eslint . --ext .ts --fix",
31
31
  "prepack": "npm run build:dev",
32
- "prettier": "prettier '{src,functions,scripts,bin,external,bin}/**/*.{js,ts,json}' --log-level error --write",
32
+ "prettier": "prettier '{src,functions,scripts,bin,external,.github}/**/*.{js,ts,json,yml,yaml,md}' --log-level error --write",
33
33
  "test": "cross-env DEBUG=quiet mocha",
34
34
  "start": "env-cmd -f .env node build"
35
35
  },
@@ -57,12 +57,12 @@
57
57
  "http-proxy": "^1.18.1",
58
58
  "lighthouse": "^11.1.0",
59
59
  "micromatch": "^4.0.4",
60
- "playwright-core": "^1.42.1",
61
- "puppeteer-core": "^22.3.0",
60
+ "playwright-core": "1.42.1",
61
+ "puppeteer-core": "^22.4.1",
62
62
  "puppeteer-extra": "^3.3.6",
63
63
  "puppeteer-extra-plugin-stealth": "^2.11.2",
64
64
  "queue": "^7.0.0",
65
- "systeminformation": "^5.22.0"
65
+ "systeminformation": "^5.22.1"
66
66
  },
67
67
  "optionalDependencies": {
68
68
  "@types/chai": "^4.3.12",
@@ -71,10 +71,10 @@
71
71
  "@types/http-proxy": "^1.17.14",
72
72
  "@types/micromatch": "^4.0.6",
73
73
  "@types/mocha": "^10.0.6",
74
- "@types/node": "^20.11.24",
74
+ "@types/node": "^20.11.26",
75
75
  "@types/sinon": "^17.0.3",
76
- "@typescript-eslint/eslint-plugin": "^7.1.1",
77
- "@typescript-eslint/parser": "^7.1.1",
76
+ "@typescript-eslint/eslint-plugin": "^7.2.0",
77
+ "@typescript-eslint/parser": "^7.2.0",
78
78
  "assert": "^2.0.0",
79
79
  "chai": "^5.1.0",
80
80
  "cross-env": "^7.0.3",
@@ -84,13 +84,13 @@
84
84
  "eslint": "^8.57.0",
85
85
  "eslint-plugin-typescript-sort-keys": "^3.2.0",
86
86
  "extract-zip": "^2.0.1",
87
- "marked": "^12.0.0",
87
+ "marked": "^12.0.1",
88
88
  "mocha": "^10.3.0",
89
89
  "move-file": "^3.1.0",
90
90
  "prettier": "^3.2.5",
91
91
  "sinon": "^17.0.1",
92
92
  "ts-node": "^10.9.2",
93
- "typescript": "^5.3.3",
93
+ "typescript": "^5.4.2",
94
94
  "typescript-json-schema": "^0.63.0"
95
95
  },
96
96
  "eslintConfig": {
@@ -11,6 +11,7 @@ import {
11
11
  FirefoxPlaywright,
12
12
  HTTPRoute,
13
13
  HTTPServer,
14
+ Hooks,
14
15
  IBrowserlessStats,
15
16
  Limiter,
16
17
  Metrics,
@@ -48,6 +49,7 @@ export class Browserless extends EventEmitter {
48
49
  protected browserManager: BrowserManager;
49
50
  protected config: Config;
50
51
  protected fileSystem: FileSystem;
52
+ protected hooks: Hooks;
51
53
  protected limiter: Limiter;
52
54
  protected metrics: Metrics;
53
55
  protected monitoring: Monitoring;
@@ -67,6 +69,7 @@ export class Browserless extends EventEmitter {
67
69
  browserManager,
68
70
  config,
69
71
  fileSystem,
72
+ hooks,
70
73
  limiter,
71
74
  metrics,
72
75
  monitoring,
@@ -77,6 +80,7 @@ export class Browserless extends EventEmitter {
77
80
  browserManager?: Browserless['browserManager'];
78
81
  config?: Browserless['config'];
79
82
  fileSystem?: Browserless['fileSystem'];
83
+ hooks?: Browserless['hooks'];
80
84
  limiter?: Browserless['limiter'];
81
85
  metrics?: Browserless['metrics'];
82
86
  monitoring?: Browserless['monitoring'];
@@ -88,13 +92,21 @@ export class Browserless extends EventEmitter {
88
92
  this.config = config || new Config();
89
93
  this.metrics = metrics || new Metrics();
90
94
  this.token = token || new Token(this.config);
95
+ this.hooks = hooks || new Hooks();
91
96
  this.webhooks = webhooks || new WebHooks(this.config);
92
- this.browserManager = browserManager || new BrowserManager(this.config);
97
+ this.browserManager =
98
+ browserManager || new BrowserManager(this.config, this.hooks);
93
99
  this.monitoring = monitoring || new Monitoring(this.config);
94
100
  this.fileSystem = fileSystem || new FileSystem(this.config);
95
101
  this.limiter =
96
102
  limiter ||
97
- new Limiter(this.config, this.metrics, this.monitoring, this.webhooks);
103
+ new Limiter(
104
+ this.config,
105
+ this.metrics,
106
+ this.monitoring,
107
+ this.webhooks,
108
+ this.hooks,
109
+ );
98
110
  this.router =
99
111
  router || new Router(this.config, this.browserManager, this.limiter);
100
112
  }
@@ -129,7 +141,11 @@ export class Browserless extends EventEmitter {
129
141
 
130
142
  if (metricsPath) {
131
143
  this.debug(`Saving metrics to "${metricsPath}"`);
132
- this.fileSystem.append(metricsPath, JSON.stringify(aggregatedStats));
144
+ this.fileSystem.append(
145
+ metricsPath,
146
+ JSON.stringify(aggregatedStats),
147
+ false,
148
+ );
133
149
  }
134
150
  };
135
151
 
@@ -214,8 +230,8 @@ export class Browserless extends EventEmitter {
214
230
  this.debug('Starting import of HTTP Routes');
215
231
 
216
232
  for (const httpRoute of [
217
- ...internalHttpRouteFiles,
218
233
  ...this.httpRouteFiles,
234
+ ...internalHttpRouteFiles,
219
235
  ]) {
220
236
  if (httpRoute.endsWith('js')) {
221
237
  const { name } = path.parse(httpRoute);
@@ -264,8 +280,8 @@ export class Browserless extends EventEmitter {
264
280
 
265
281
  this.debug('Starting import of WebSocket Routes');
266
282
  for (const wsRoute of [
267
- ...internalWsRouteFiles,
268
283
  ...this.webSocketRouteFiles,
284
+ ...internalWsRouteFiles,
269
285
  ]) {
270
286
  if (wsRoute.endsWith('js')) {
271
287
  const { name } = path.parse(wsRoute);
@@ -350,6 +366,7 @@ export class Browserless extends EventEmitter {
350
366
  this.metrics,
351
367
  this.token,
352
368
  this.router,
369
+ this.hooks,
353
370
  );
354
371
 
355
372
  await this.server.start();
@@ -16,19 +16,18 @@ import {
16
16
  Config,
17
17
  FirefoxPlaywright,
18
18
  HTTPManagementRoutes,
19
+ Hooks,
19
20
  NotFound,
20
21
  Request,
21
22
  ServerError,
22
23
  WebkitPlaywright,
23
24
  availableBrowsers,
24
- browserHook,
25
25
  convertIfBase64,
26
26
  createLogger,
27
27
  exists,
28
28
  generateDataDir,
29
29
  makeExternalURL,
30
30
  noop,
31
- pageHook,
32
31
  parseBooleanParam,
33
32
  } from '@browserless.io/browserless';
34
33
  import { deleteAsync } from 'del';
@@ -47,7 +46,10 @@ export class BrowserManager {
47
46
  WebkitPlaywright.name,
48
47
  ];
49
48
 
50
- constructor(protected config: Config) {}
49
+ constructor(
50
+ protected config: Config,
51
+ protected hooks: Hooks,
52
+ ) {}
51
53
 
52
54
  private browserIsChrome = (b: BrowserInstance) =>
53
55
  this.chromeBrowsers.some((chromeBrowser) => b instanceof chromeBrowser);
@@ -64,7 +66,7 @@ export class BrowserManager {
64
66
  };
65
67
 
66
68
  protected onNewPage = async (req: Request, page: unknown) => {
67
- await pageHook({ meta: req.parsed, page });
69
+ await this.hooks.page({ meta: req.parsed, page });
68
70
  };
69
71
 
70
72
  /**
@@ -463,7 +465,7 @@ export class BrowserManager {
463
465
  this.browsers.set(browser, connectionMeta);
464
466
 
465
467
  await browser.launch(launchOptions as object);
466
- await browserHook({ browser, meta: req.parsed });
468
+ await this.hooks.browser({ browser, meta: req.parsed });
467
469
 
468
470
  browser.on('newPage', async (page) => {
469
471
  await this.onNewPage(req, page);
package/src/exports.ts CHANGED
@@ -10,6 +10,7 @@ export * from './metrics.js';
10
10
  export * from './mime-types.js';
11
11
  export * from './monitoring.js';
12
12
  export * from './router.js';
13
+ export * from './sdk-utils.js';
13
14
  export * from './server.js';
14
15
  export * from './shim.js';
15
16
  export * from './token.js';
@@ -1,11 +1,11 @@
1
- import { Config, FileSystem, sleep } from '@browserless.io/browserless';
1
+ import { Config, FileSystem, noop } from '@browserless.io/browserless';
2
2
  import { readFile, unlink } from 'fs/promises';
3
3
  import { expect } from 'chai';
4
4
 
5
5
  const filePath = '/tmp/_browserless_test_fs_';
6
6
 
7
7
  describe('File-System', () => {
8
- afterEach(async () => unlink(filePath));
8
+ afterEach(async () => unlink(filePath).catch(noop));
9
9
 
10
10
  it('saves and encodes files', async () => {
11
11
  const mySecretContents = 'pony-foo';
@@ -13,46 +13,71 @@ describe('File-System', () => {
13
13
  config.setToken('browserless.io');
14
14
  const f = new FileSystem(config);
15
15
 
16
- await f.append(filePath, mySecretContents);
16
+ await f.append(filePath, mySecretContents, true);
17
17
 
18
- expect(await f.read(filePath)).to.eql([mySecretContents]);
18
+ expect(await f.read(filePath, true)).to.eql([mySecretContents]);
19
19
  const rawText = (await readFile(filePath)).toString();
20
20
 
21
21
  expect(rawText.toString()).to.not.include(mySecretContents);
22
22
  });
23
23
 
24
- it('appends newlines to files', async () => {
24
+ it('saves files without encoding', async () => {
25
+ const mySecretContents = 'pony-foo';
26
+ const config = new Config();
27
+ config.setToken('browserless.io');
28
+ const f = new FileSystem(config);
29
+
30
+ await f.append(filePath, mySecretContents, false);
31
+
32
+ expect(await f.read(filePath, false)).to.eql([mySecretContents]);
33
+ const rawText = (await readFile(filePath)).toString();
34
+
35
+ expect(rawText.toString()).to.include(mySecretContents);
36
+ });
37
+
38
+ it('appends newlines to files and encodes them', async () => {
25
39
  const mySecretContents = 'pony-foo';
26
40
  const moreSecretContents = 'pony-pony-foo-foo';
27
41
  const config = new Config();
28
42
  config.setToken('browserless.io');
29
43
  const f = new FileSystem(config);
30
44
 
31
- await f.append(filePath, mySecretContents);
45
+ await f.append(filePath, mySecretContents, true);
32
46
 
33
- expect(await f.read(filePath)).to.eql([mySecretContents]);
47
+ expect(await f.read(filePath, true)).to.eql([mySecretContents]);
34
48
 
35
- await f.append(filePath, moreSecretContents);
49
+ await f.append(filePath, moreSecretContents, true);
36
50
 
37
- expect(await f.read(filePath)).to.eql([
51
+ expect(await f.read(filePath, true)).to.eql([
38
52
  mySecretContents,
39
53
  moreSecretContents,
40
54
  ]);
55
+ const rawText = (await readFile(filePath)).toString();
56
+
57
+ expect(rawText).to.not.include(mySecretContents);
58
+ expect(rawText).to.not.include(moreSecretContents);
41
59
  });
42
60
 
43
- it('re-encodes files on token changes', async () => {
61
+ it('appends newlines to files and does not encode them', async () => {
62
+ const mySecretContents = 'pony-foo';
63
+ const moreSecretContents = 'pony-pony-foo-foo';
44
64
  const config = new Config();
45
65
  config.setToken('browserless.io');
46
66
  const f = new FileSystem(config);
47
- const mySecretContents = 'pony-foo';
48
67
 
49
- await f.append(filePath, mySecretContents);
50
- const oldText = (await readFile(filePath)).toString();
51
- config.setToken('super-browserless-64');
52
- await sleep(200);
53
- const newText = (await readFile(filePath)).toString();
68
+ await f.append(filePath, mySecretContents, false);
69
+
70
+ expect(await f.read(filePath, false)).to.eql([mySecretContents]);
71
+
72
+ await f.append(filePath, moreSecretContents, false);
73
+
74
+ expect(await f.read(filePath, false)).to.eql([
75
+ mySecretContents,
76
+ moreSecretContents,
77
+ ]);
78
+ const rawText = (await readFile(filePath)).toString();
54
79
 
55
- expect(oldText).to.not.equal(newText);
56
- expect(await f.read(filePath)).to.eql([mySecretContents]);
80
+ expect(rawText).to.include(mySecretContents);
81
+ expect(rawText).to.include(moreSecretContents);
57
82
  });
58
83
  });
@@ -15,28 +15,8 @@ export class FileSystem extends EventEmitter {
15
15
  constructor(protected config: Config) {
16
16
  super();
17
17
  this.currentAESKey = config.getAESKey();
18
- this.config.on('token', this.handleTokenChange);
19
18
  }
20
19
 
21
- private handleTokenChange = async () => {
22
- this.log(`Token has changed, updating file-system contents`);
23
- const start = Date.now();
24
- const newAESKey = this.config.getAESKey();
25
- await Promise.all(
26
- Array.from(this.fsMap).map(async ([filePath, contents]) => {
27
- const newlyEncoded = encrypt(
28
- contents.join('\n'),
29
- Buffer.from(newAESKey),
30
- );
31
- return writeFile(filePath, newlyEncoded);
32
- }),
33
- ).catch((e) => {
34
- this.log(`Error in setting new token: "${e}"`);
35
- });
36
- this.log(`Successfully updated file encodings in ${Date.now() - start}ms`);
37
- this.currentAESKey = this.config.getAESKey();
38
- };
39
-
40
20
  /**
41
21
  * Appends contents to a file-path for persistance. File contents are
42
22
  * encrypted before being saved to disk. Reads happen via the in-memory
@@ -46,15 +26,19 @@ export class FileSystem extends EventEmitter {
46
26
  * @param newContent A string of new content to add to the file
47
27
  * @returns void
48
28
  */
49
- append = async (path: string, newContent: string): Promise<void> => {
50
- const contents = await this.read(path);
29
+ append = async (
30
+ path: string,
31
+ newContent: string,
32
+ shouldEncode: boolean,
33
+ ): Promise<void> => {
34
+ const contents = await this.read(path, shouldEncode);
51
35
 
52
36
  contents.push(newContent);
53
37
  this.fsMap.set(path, contents);
54
- const encoded = await encrypt(
55
- contents.join('\n'),
56
- Buffer.from(this.currentAESKey),
57
- );
38
+
39
+ const encoded = shouldEncode
40
+ ? await encrypt(contents.join('\n'), this.currentAESKey)
41
+ : contents.join('\n');
58
42
 
59
43
  return writeFile(path, encoded.toString());
60
44
  };
@@ -66,16 +50,18 @@ export class FileSystem extends EventEmitter {
66
50
  * @param path The filepath of the contents to read
67
51
  * @returns Promise of the contents separated by newlines
68
52
  */
69
- read = async (path: string): Promise<string[]> => {
53
+ read = async (path: string, encoded: boolean): Promise<string[]> => {
70
54
  const hasKey = this.fsMap.has(path);
71
55
 
72
56
  if (hasKey) {
73
57
  return this.fsMap.get(path) as string[];
74
58
  }
75
59
  const contents = (await readFile(path).catch(() => '')).toString();
76
- const splitContents = contents.length
77
- ? (await decrypt(contents, Buffer.from(this.currentAESKey))).split('\n')
78
- : [];
60
+ const decoded =
61
+ encoded && contents.length
62
+ ? await decrypt(contents, this.currentAESKey)
63
+ : contents;
64
+ const splitContents = decoded.length ? decoded.split('\n') : [];
79
65
 
80
66
  this.fsMap.set(path, splitContents);
81
67
 
package/src/hooks.ts CHANGED
@@ -1,8 +1,32 @@
1
- // @ts-ignore
2
- export { default as beforeRequest } from '../external/before.js';
3
- // @ts-ignore
4
- export { default as afterRequest } from '../external/after.js';
5
- // @ts-ignore
6
- export { default as pageHook } from '../external/page.js';
7
- // @ts-ignore
8
- export { default as browserHook } from '../external/browser.js';
1
+ // @ts-nocheck Unknown external files
2
+ import { EventEmitter } from 'events';
3
+
4
+ // KEPT for backwards compatibility reasons since some downstream
5
+ // docker images will override these files to inject their own hook
6
+ // behaviors
7
+ import { default as afterRequest } from '../external/after.js';
8
+ import { default as beforeRequest } from '../external/before.js';
9
+ import { default as browserHook } from '../external/browser.js';
10
+ import { default as pageHook } from '../external/page.js';
11
+
12
+ export class Hooks extends EventEmitter {
13
+ constructor() {
14
+ super();
15
+ }
16
+
17
+ before(...args: unknown[]): Promise<boolean> {
18
+ return beforeRequest(...args);
19
+ }
20
+
21
+ after(...args: unknown[]): Promise<boolean> {
22
+ return afterRequest(...args);
23
+ }
24
+
25
+ page(...args: unknown[]): Promise<unknown> {
26
+ return pageHook(...args);
27
+ }
28
+
29
+ browser(...args: unknown[]): Promise<unknown> {
30
+ return browserHook(...args);
31
+ }
32
+ }