@browserless.io/browserless 2.2.0 → 2.3.0-beta-2

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 (159) hide show
  1. package/README.md +2 -2
  2. package/bin/browserless.js +67 -27
  3. package/bin/scaffold/README.md +50 -0
  4. package/bin/scaffold/src/hello-world.http.ts +2 -1
  5. package/build/browserless.d.ts +3 -0
  6. package/build/browserless.js +43 -19
  7. package/build/browsers/chrome.cdp.js +1 -1
  8. package/build/browsers/chrome.playwright.js +1 -1
  9. package/build/browsers/index.d.ts +1 -0
  10. package/build/browsers/index.js +4 -1
  11. package/build/data/classes.json +1 -1
  12. package/build/data/selectors.json +1 -1
  13. package/build/exports.js +1 -2
  14. package/build/limiter.d.ts +2 -1
  15. package/build/limiter.js +6 -3
  16. package/build/routes/chrome/http/content.post.body.json +8 -8
  17. package/build/routes/chrome/http/content.post.d.ts +2 -1
  18. package/build/routes/chrome/http/content.post.js +3 -2
  19. package/build/routes/chrome/http/download.post.d.ts +2 -1
  20. package/build/routes/chrome/http/download.post.js +3 -2
  21. package/build/routes/chrome/http/function.post.d.ts +2 -1
  22. package/build/routes/chrome/http/function.post.js +3 -2
  23. package/build/routes/chrome/http/json-list.get.d.ts +5 -1
  24. package/build/routes/chrome/http/json-list.get.js +5 -1
  25. package/build/routes/chrome/http/json-new.put.d.ts +5 -1
  26. package/build/routes/chrome/http/json-new.put.js +5 -1
  27. package/build/routes/chrome/http/json-protocol.get.d.ts +5 -1
  28. package/build/routes/chrome/http/json-protocol.get.js +5 -1
  29. package/build/routes/chrome/http/json-version.get.d.ts +5 -1
  30. package/build/routes/chrome/http/json-version.get.js +5 -1
  31. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  32. package/build/routes/chrome/http/pdf.post.d.ts +2 -1
  33. package/build/routes/chrome/http/pdf.post.js +3 -2
  34. package/build/routes/chrome/http/performance.post.d.ts +2 -1
  35. package/build/routes/chrome/http/performance.post.js +3 -2
  36. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  37. package/build/routes/chrome/http/scrape.post.d.ts +2 -1
  38. package/build/routes/chrome/http/scrape.post.js +3 -2
  39. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  40. package/build/routes/chrome/http/screenshot.post.d.ts +2 -1
  41. package/build/routes/chrome/http/screenshot.post.js +3 -2
  42. package/build/routes/chrome/ws/browser.d.ts +2 -1
  43. package/build/routes/chrome/ws/browser.js +3 -2
  44. package/build/routes/chrome/ws/cdp.d.ts +2 -1
  45. package/build/routes/chrome/ws/cdp.js +3 -2
  46. package/build/routes/chrome/ws/page.d.ts +3 -2
  47. package/build/routes/chrome/ws/page.js +3 -2
  48. package/build/routes/chrome/ws/playwright.d.ts +1 -0
  49. package/build/routes/chrome/ws/playwright.js +2 -1
  50. package/build/routes/chromium/http/content.post.body.json +8 -8
  51. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  52. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  53. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  54. package/build/routes/firefox/ws/playwright.d.ts +7 -2
  55. package/build/routes/firefox/ws/playwright.js +3 -2
  56. package/build/routes/firefox/ws/playwright.query.json +29 -31
  57. package/build/routes/management/http/config.get.d.ts +1 -0
  58. package/build/routes/management/http/config.get.js +2 -1
  59. package/build/routes/management/http/metrics-total.get.d.ts +1 -0
  60. package/build/routes/management/http/metrics-total.get.js +2 -1
  61. package/build/routes/management/http/metrics.get.d.ts +1 -0
  62. package/build/routes/management/http/metrics.get.js +2 -1
  63. package/build/routes/management/http/sessions.get.d.ts +1 -0
  64. package/build/routes/management/http/sessions.get.js +2 -1
  65. package/build/routes/management/http/static.get.d.ts +1 -0
  66. package/build/routes/management/http/static.get.js +8 -7
  67. package/build/routes/webkit/ws/playwright.d.ts +2 -1
  68. package/build/routes/webkit/ws/playwright.js +3 -2
  69. package/build/shared/browser.ws.d.ts +2 -1
  70. package/build/shared/browser.ws.js +3 -2
  71. package/build/shared/chromium.playwright.ws.d.ts +2 -1
  72. package/build/shared/chromium.playwright.ws.js +3 -2
  73. package/build/shared/chromium.ws.d.ts +2 -1
  74. package/build/shared/chromium.ws.js +3 -2
  75. package/build/shared/content.http.d.ts +2 -1
  76. package/build/shared/content.http.js +3 -2
  77. package/build/shared/download.http.d.ts +2 -1
  78. package/build/shared/download.http.js +3 -2
  79. package/build/shared/function.http.d.ts +2 -1
  80. package/build/shared/function.http.js +3 -2
  81. package/build/shared/json-list.http.d.ts +2 -1
  82. package/build/shared/json-list.http.js +3 -2
  83. package/build/shared/json-new.http.d.ts +2 -1
  84. package/build/shared/json-new.http.js +3 -2
  85. package/build/shared/json-protocol.http.d.ts +2 -1
  86. package/build/shared/json-protocol.http.js +3 -2
  87. package/build/shared/json-version.http.d.ts +2 -1
  88. package/build/shared/json-version.http.js +3 -2
  89. package/build/shared/page.ws.d.ts +2 -1
  90. package/build/shared/page.ws.js +3 -2
  91. package/build/shared/pdf.http.d.ts +2 -1
  92. package/build/shared/pdf.http.js +3 -2
  93. package/build/shared/performance.http.d.ts +1 -0
  94. package/build/shared/performance.http.js +2 -1
  95. package/build/shared/scrape.http.d.ts +2 -1
  96. package/build/shared/scrape.http.js +3 -2
  97. package/build/shared/screenshot.http.d.ts +1 -0
  98. package/build/shared/screenshot.http.js +2 -1
  99. package/build/shared/utils/performance/main.js +1 -1
  100. package/build/types.d.ts +91 -0
  101. package/build/types.js +54 -0
  102. package/build/utils.d.ts +1 -1
  103. package/build/utils.js +11 -6
  104. package/docker/chromium/Dockerfile +1 -2
  105. package/docker/firefox/Dockerfile +1 -2
  106. package/docker/multi/Dockerfile +2 -4
  107. package/docker/webkit/Dockerfile +1 -2
  108. package/package.json +11 -11
  109. package/scripts/build-open-api.js +142 -124
  110. package/src/browserless.ts +60 -22
  111. package/src/browsers/chrome.cdp.ts +1 -1
  112. package/src/browsers/chrome.playwright.ts +1 -1
  113. package/src/browsers/index.ts +5 -1
  114. package/src/exports.ts +1 -3
  115. package/src/limiter.ts +7 -3
  116. package/src/router.ts +6 -2
  117. package/src/routes/chrome/http/content.post.ts +7 -2
  118. package/src/routes/chrome/http/download.post.ts +7 -2
  119. package/src/routes/chrome/http/function.post.ts +7 -2
  120. package/src/routes/chrome/http/json-list.get.ts +7 -1
  121. package/src/routes/chrome/http/json-new.put.ts +7 -1
  122. package/src/routes/chrome/http/json-protocol.get.ts +7 -1
  123. package/src/routes/chrome/http/json-version.get.ts +7 -1
  124. package/src/routes/chrome/http/pdf.post.ts +7 -2
  125. package/src/routes/chrome/http/performance.post.ts +7 -2
  126. package/src/routes/chrome/http/scrape.post.ts +7 -2
  127. package/src/routes/chrome/http/screenshot.post.ts +7 -2
  128. package/src/routes/chrome/ws/browser.ts +3 -2
  129. package/src/routes/chrome/ws/cdp.ts +7 -2
  130. package/src/routes/chrome/ws/page.ts +3 -2
  131. package/src/routes/chrome/ws/playwright.ts +6 -1
  132. package/src/routes/firefox/ws/playwright.ts +6 -2
  133. package/src/routes/management/http/config.get.ts +2 -0
  134. package/src/routes/management/http/metrics-total.get.ts +2 -0
  135. package/src/routes/management/http/metrics.get.ts +2 -0
  136. package/src/routes/management/http/sessions.get.ts +2 -0
  137. package/src/routes/management/http/static.get.ts +16 -9
  138. package/src/routes/webkit/ws/playwright.ts +3 -1
  139. package/src/shared/browser.ws.ts +3 -1
  140. package/src/shared/chromium.playwright.ws.ts +3 -1
  141. package/src/shared/chromium.ws.ts +3 -1
  142. package/src/shared/content.http.ts +3 -1
  143. package/src/shared/download.http.ts +3 -1
  144. package/src/shared/function.http.ts +3 -1
  145. package/src/shared/json-list.http.ts +3 -1
  146. package/src/shared/json-new.http.ts +3 -1
  147. package/src/shared/json-protocol.http.ts +3 -1
  148. package/src/shared/json-version.http.ts +3 -1
  149. package/src/shared/page.ws.ts +3 -1
  150. package/src/shared/pdf.http.ts +3 -1
  151. package/src/shared/performance.http.ts +2 -0
  152. package/src/shared/scrape.http.ts +3 -1
  153. package/src/shared/screenshot.http.ts +2 -0
  154. package/src/shared/utils/performance/main.ts +1 -2
  155. package/src/types.ts +66 -0
  156. package/src/utils.ts +13 -7
  157. package/static/docs/swagger.json +91 -49
  158. package/static/docs/swagger.min.json +6697 -0
  159. package/static/function/client.js +323 -323
package/README.md CHANGED
@@ -23,7 +23,7 @@ If you've been struggling to deploy headless browsers without running into issue
23
23
  4. [Hosting](#hosting-providers)
24
24
  5. [Puppeteer](#puppeteer)
25
25
  6. [Playwright](#playwright)
26
- 7. [Extending with NodeJS SDK](#extending-nodejs-skd)
26
+ 7. [Extending with NodeJS SDK](#extending-nodejs-sdk)
27
27
  8. [Licensing](#licensing)
28
28
  9. [Changelog](https://github.com/browserless/chrome/blob/master/CHANGELOG.md)
29
29
 
@@ -102,7 +102,7 @@ const browser = await pw.chromium.connectOverCDP('ws://localhost:3000');
102
102
 
103
103
  After that, the rest of your code remains the same with no other changes required.
104
104
 
105
- # Extending (NodeJS SKD)
105
+ # Extending (NodeJS SDK)
106
106
 
107
107
  Browserless comes with built-in extension capabilities, and allows for extending nearly any aspect of the system (for Version 2+). For more details on how to write your own routes, build docker images, and more, [see our SDK README.md](/bin/scaffold/README.md) or simply run "npx @browserless.io/browserless create" in a terminal and follow the onscreen prompts.
108
108
 
@@ -33,11 +33,6 @@ const allowedCMDs = [
33
33
  'help',
34
34
  'clean',
35
35
  ];
36
- const projectDir = process.cwd();
37
- const compiledDir = path.join(projectDir, 'build');
38
- const packageJSON = readFile(path.join(__dirname, '..', 'package.json')).then(
39
- (r) => JSON.parse(r.toString()),
40
- );
41
36
 
42
37
  if (!allowedCMDs.includes(cmd)) {
43
38
  throw new Error(
@@ -45,6 +40,21 @@ if (!allowedCMDs.includes(cmd)) {
45
40
  );
46
41
  }
47
42
 
43
+ const projectDir = process.cwd();
44
+ const buildDir = 'build';
45
+ const srcDir = 'src';
46
+ const compiledDir = path.join(projectDir, buildDir);
47
+
48
+ const projectPackageJSON = readFile(path.join(projectDir, 'package.json'))
49
+ .then((r) => JSON.parse(r.toString()))
50
+ .catch(() => null);
51
+
52
+ const browserlessPackageJSON = readFile(
53
+ path.join(__dirname, '..', 'package.json'),
54
+ ).then((r) => JSON.parse(r.toString()));
55
+
56
+ const camelCase = (str) => str.replace(/-([a-z])/g, (_, w) => w.toUpperCase());
57
+
48
58
  const prompt = async (question) => {
49
59
  const rl = createInterface({
50
60
  input: process.stdin,
@@ -60,22 +70,43 @@ const prompt = async (question) => {
60
70
  });
61
71
  };
62
72
 
63
- const importClassOverride = async (files, className) => {
73
+ const translateSrcToBuild = (directory) => {
74
+ const srcToBuild = directory.replace(srcDir, '');
75
+ const pathParsed = path.parse(srcToBuild);
76
+ return path.format({ ...pathParsed, base: '', ext: '.js' });
77
+ };
78
+
79
+ const importDefault = async (files, fileName) => {
80
+ const pJSON = await projectPackageJSON;
81
+ // Check first if overrides are manually specified in the project's package.json
82
+ if (pJSON && pJSON.browserless && typeof pJSON.browserless === 'object') {
83
+ const camelCaseFileName = camelCase(fileName);
84
+ const relativePath = pJSON.browserless[camelCaseFileName];
85
+ if (relativePath) {
86
+ const fullFilePath = path.join(
87
+ compiledDir,
88
+ translateSrcToBuild(relativePath),
89
+ );
90
+ log(`Importing module from package.json: "${fullFilePath}"`);
91
+ return (await import(fullFilePath)).default;
92
+ }
93
+ }
94
+
64
95
  const classModuleFile = files.find((f) =>
65
- path.parse(f).name.endsWith(className),
96
+ path.parse(f).name.endsWith(fileName),
66
97
  );
67
98
 
68
99
  if (!classModuleFile) {
69
100
  return;
70
101
  }
71
102
 
72
- const classModuleFullFilePath = path.join(compiledDir, classModuleFile);
103
+ const fullFilePath = path.join(compiledDir, classModuleFile);
73
104
 
74
105
  if (!classModuleFile) {
75
106
  return;
76
107
  }
77
- log(`Importing module override "${classModuleFullFilePath}"`);
78
- return (await import(classModuleFullFilePath)).default;
108
+ log(`Importing module from found files: "${fullFilePath}"`);
109
+ return (await import(fullFilePath)).default;
79
110
  };
80
111
 
81
112
  const clean = async () =>
@@ -120,7 +151,7 @@ const buildDockerImage = async (cmd) => {
120
151
 
121
152
  const buildTypeScript = async () =>
122
153
  new Promise((resolve, reject) => {
123
- spawn('npx', ['tsc', '--outDir', 'build'], {
154
+ spawn('npx', ['tsc', '--outDir', buildDir], {
124
155
  cwd: projectDir,
125
156
  stdio: 'inherit',
126
157
  }).once('close', (code) => {
@@ -209,7 +240,8 @@ const build = async () => {
209
240
  );
210
241
 
211
242
  log(`Generating OpenAPI JSON file`);
212
- await buildOpenAPI(httpRoutes, webSocketRoutes);
243
+ const disabledRoutes = await importDefault(files, 'disabled-routes');
244
+ await buildOpenAPI(httpRoutes, webSocketRoutes, disabledRoutes);
213
245
 
214
246
  log(`All built assets complete`);
215
247
 
@@ -239,16 +271,18 @@ const start = async (dev = false) => {
239
271
  Router,
240
272
  Token,
241
273
  Webhooks,
274
+ disabledRoutes,
242
275
  ] = await Promise.all([
243
- importClassOverride(files, 'browser-manager'),
244
- importClassOverride(files, 'config'),
245
- importClassOverride(files, 'file-system'),
246
- importClassOverride(files, 'limiter'),
247
- importClassOverride(files, 'metrics'),
248
- importClassOverride(files, 'monitoring'),
249
- importClassOverride(files, 'router'),
250
- importClassOverride(files, 'token'),
251
- importClassOverride(files, 'webhooks'),
276
+ importDefault(files, 'browser-manager'),
277
+ importDefault(files, 'config'),
278
+ importDefault(files, 'file-system'),
279
+ importDefault(files, 'limiter'),
280
+ importDefault(files, 'metrics'),
281
+ importDefault(files, 'monitoring'),
282
+ importDefault(files, 'router'),
283
+ importDefault(files, 'token'),
284
+ importDefault(files, 'webhooks'),
285
+ importDefault(files, 'disabled-routes'),
252
286
  ]);
253
287
 
254
288
  log(`Starting Browserless`);
@@ -285,6 +319,15 @@ const start = async (dev = false) => {
285
319
  webhooks,
286
320
  });
287
321
 
322
+ if (disabledRoutes !== undefined) {
323
+ if (!Array.isArray(disabledRoutes)) {
324
+ throw new Error(
325
+ `The "disabled-routes.ts" default export should be an array of Route classes.`,
326
+ );
327
+ }
328
+ browserless.disableRoutes(...disabledRoutes);
329
+ }
330
+
288
331
  httpRoutes.forEach((r) => browserless.addHTTPRoute(r));
289
332
  webSocketRoutes.forEach((r) => browserless.addWebSocketRoute(r));
290
333
 
@@ -328,7 +371,7 @@ const start = async (dev = false) => {
328
371
  };
329
372
 
330
373
  const buildDocker = async () => {
331
- const finalDockerPath = path.join(projectDir, 'build', 'Dockerfile');
374
+ const finalDockerPath = path.join(compiledDir, 'Dockerfile');
332
375
  const argSwitches = getArgSwitches();
333
376
 
334
377
  await build();
@@ -339,10 +382,7 @@ const buildDocker = async () => {
339
382
 
340
383
  log(`Generating Dockerfile at "${finalDockerPath}"`);
341
384
 
342
- await fs.writeFile(
343
- path.join(projectDir, 'build', 'Dockerfile'),
344
- dockerContents,
345
- );
385
+ await fs.writeFile(compiledDir, dockerContents);
346
386
 
347
387
  const from =
348
388
  argSwitches.from ||
@@ -424,7 +464,7 @@ const create = async () => {
424
464
  const to = path.join(installPath, sdkFile);
425
465
  if (sdkFile === 'package.json') {
426
466
  const sdkPackageJSONTemplate = (await readFile(from)).toString();
427
- const { version } = await packageJSON;
467
+ const { version } = await browserlessPackageJSON;
428
468
  const sdkPackageJSON = sdkPackageJSONTemplate.replace(
429
469
  '${BROWSERLESS_VERSION}',
430
470
  version,
@@ -13,6 +13,7 @@ Finally, this SDK and Browserless.io are built to support businesses and enterpr
13
13
  - [Routing](#routing)
14
14
  - [Utilities](#utilities)
15
15
  - [Extending Modules](#extending-modules)
16
+ - [Disabling Routes](#dis)
16
17
  - [Running in Development](#running-in-development)
17
18
  - [Building for Production](#building-for-production)
18
19
  - [Running without Building](#running-without-building)
@@ -133,6 +134,9 @@ export type ResponseSchema = string;
133
134
 
134
135
  // Similar to React and other ecosystems, extend our basic HTTPRoute
135
136
  export default class HelloWorldRoute extends HTTPRoute {
137
+ // Must have a unique name for things like disabling to work if desired
138
+ name = 'PDFToS3Route';
139
+
136
140
  // Detail any content-types that this route should except. "contentTypes.any" here means any content-type.
137
141
  // If the content-type does not match then a 404 will be sent back
138
142
  accepts = [contentTypes.any];
@@ -192,6 +196,9 @@ export interface QuerySchema extends SystemQueryParameters {
192
196
  }
193
197
 
194
198
  export default class ChromiumWebSocketRoute extends BrowserWebsocketRoute {
199
+ // Must have a unique name for things like disabling to work if desired
200
+ name = 'ChromiumWebSocketRoute';
201
+
195
202
  // This route requires a valid authorization token.
196
203
  auth = true;
197
204
 
@@ -297,6 +304,9 @@ export interface BodySchema {
297
304
  }
298
305
 
299
306
  export default class PDFToS3Route extends BrowserHTTPRoute {
307
+ // Must have a unique name for things like disabling to work if desired
308
+ name = 'PDFToS3Route';
309
+
300
310
  // Our route only accepts JSON content-types, and the rest 404
301
311
  accepts = [contentTypes.json];
302
312
 
@@ -347,6 +357,46 @@ export default class PDFToS3Route extends BrowserHTTPRoute {
347
357
 
348
358
  With this approach you can effectively write, extend and author your own workflows within browserless!
349
359
 
360
+ ## Disabling Routes
361
+
362
+ You can disable access to core routes by specifying the route names you want to disable in a file named `disabled-routes.ts`. Browserless will scan all directories for a file named as such, and disable the named classes exported by this file. The alternative is to create a `browserless` property in your package.json file that contains a `disabledRoutes` string pointing to the relative path of your disabled routes file.
363
+
364
+ For example, if you want to disable all metrics, config, and session information your `src/disabled-routes.ts` file would look like this:
365
+
366
+ ```ts
367
+ import { BrowserlessRoutes } from '@browserless.io/browserless';
368
+
369
+ export default [
370
+ BrowserlessRoutes.ConfigGetRoute,
371
+ BrowserlessRoutes.SessionsGetGetRoute,
372
+ BrowserlessRoutes.MetricsGetRoute,
373
+ BrowserlessRoutes.MetricsTotalGetRoute,
374
+ ];
375
+ ```
376
+
377
+ And in the package.json file, it'd look like this:
378
+
379
+ ```json
380
+ {
381
+ // ... lots of package.json stuff
382
+ "browserless": {
383
+ "disabledRoutes": "./src/disabled-routes.ts"
384
+ }
385
+ }
386
+ ```
387
+
388
+ In order for route-disabling to work, you must have a `default` export that's an array of names. Browserless exports every route name it builds and runs internally, meaning you simply need to pass them through this `disabled-routes.ts` file after importing them.
389
+
390
+ Disabling a route will do several things:
391
+
392
+ - Return a `404` HTTP response when trying to call any of these routes.
393
+ - Remove them from the embedded documentation site that is auto-generated.
394
+ - Removes them from the OpenAPI JSON Schematic.
395
+ - Prevents their type information from being converted from TypeScript to runtime validation.
396
+ - It doesn't, however, remove them from Node's Module cache.
397
+
398
+ All of Browserless' internal routes are side-effect free, meaning their largely state-less and don't do exhibit kind of behavior aside from route handling and metrics reporting. Having them in Node's module cache is fine since they're never mounted in the router and set up as a potential route.
399
+
350
400
  ## Running in Development
351
401
 
352
402
  After the project has been set up, you can use npm commands to build and run your code. The most important of these is the `npm run dev` command, which will do the following:
@@ -10,7 +10,8 @@ import {
10
10
 
11
11
  export type ResponseSchema = string;
12
12
 
13
- export default class HelloWorldRoute extends HTTPRoute {
13
+ export default class HelloWorldHTTPRoute extends HTTPRoute {
14
+ name = 'HelloWorldHTTPRoute';
14
15
  accepts = [contentTypes.any];
15
16
  auth = true;
16
17
  browser = null;
@@ -12,6 +12,7 @@ export declare class Browserless {
12
12
  protected router: Router;
13
13
  protected token: Token;
14
14
  protected webhooks: WebHooks;
15
+ disabledRouteNames: string[];
15
16
  webSocketRouteFiles: string[];
16
17
  httpRouteFiles: string[];
17
18
  server?: HTTPServer;
@@ -30,6 +31,8 @@ export declare class Browserless {
30
31
  });
31
32
  protected saveMetrics: () => Promise<void>;
32
33
  setMetricsSaveInterval: (interval: number) => void;
34
+ private routeIsDisabled;
35
+ disableRoutes(...routeNames: string[]): void;
33
36
  addHTTPRoute(httpRouteFilePath: string): void;
34
37
  addWebSocketRoute(webSocketRouteFilePath: string): void;
35
38
  setPort(port: number): void;
@@ -14,6 +14,7 @@ export class Browserless {
14
14
  router;
15
15
  token;
16
16
  webhooks;
17
+ disabledRouteNames = [];
17
18
  webSocketRouteFiles = [];
18
19
  httpRouteFiles = [];
19
20
  server;
@@ -69,6 +70,12 @@ export class Browserless {
69
70
  this.metricsSaveInterval = interval;
70
71
  this.metricsSaveIntervalID = setInterval(this.saveMetrics, this.metricsSaveInterval);
71
72
  };
73
+ routeIsDisabled(route) {
74
+ return this.disabledRouteNames.some((name) => name === route.name);
75
+ }
76
+ disableRoutes(...routeNames) {
77
+ this.disabledRouteNames.push(...routeNames);
78
+ }
72
79
  addHTTPRoute(httpRouteFilePath) {
73
80
  this.httpRouteFiles.push(httpRouteFilePath);
74
81
  }
@@ -95,12 +102,15 @@ export class Browserless {
95
102
  ChromiumPlaywright,
96
103
  WebkitPlaywright,
97
104
  ];
98
- const [[httpRouteFiles, wsRouteFiles], installedBrowsers] = await Promise.all([getRouteFiles(this.config), availableBrowsers]);
105
+ const [[internalHttpRouteFiles, internalWsRouteFiles], installedBrowsers] = await Promise.all([getRouteFiles(this.config), availableBrowsers]);
99
106
  const docsLink = makeExternalURL(this.config.getExternalAddress(), '/docs');
100
107
  this.debug(printLogo(docsLink));
101
108
  this.debug(`Running as user "${userInfo().username}"`);
102
109
  this.debug('Starting import of HTTP Routes');
103
- for (const httpRoute of [...httpRouteFiles, ...this.httpRouteFiles]) {
110
+ for (const httpRoute of [
111
+ ...internalHttpRouteFiles,
112
+ ...this.httpRouteFiles,
113
+ ]) {
104
114
  if (httpRoute.endsWith('js')) {
105
115
  const { name } = path.parse(httpRoute);
106
116
  const [bodySchema, querySchema] = await Promise.all(routeSchemas.map(async (schemaType) => {
@@ -112,18 +122,23 @@ export class Browserless {
112
122
  const logger = createLogger(`http:${name}`);
113
123
  const { default: Route, } = await import(routeImport + `?cb=${Date.now()}`);
114
124
  const route = new Route(this.browserManager, this.config, this.fileSystem, logger, this.metrics, this.monitoring);
115
- route.bodySchema = safeParse(bodySchema);
116
- route.querySchema = safeParse(querySchema);
117
- route.config = () => this.config;
118
- route.metrics = () => this.metrics;
119
- route.monitoring = () => this.monitoring;
120
- route.fileSystem = () => this.fileSystem;
121
- route.debug = () => logger;
122
- httpRoutes.push(route);
125
+ if (!this.routeIsDisabled(route)) {
126
+ route.bodySchema = safeParse(bodySchema);
127
+ route.querySchema = safeParse(querySchema);
128
+ route.config = () => this.config;
129
+ route.metrics = () => this.metrics;
130
+ route.monitoring = () => this.monitoring;
131
+ route.fileSystem = () => this.fileSystem;
132
+ route.debug = () => logger;
133
+ httpRoutes.push(route);
134
+ }
123
135
  }
124
136
  }
125
137
  this.debug('Starting import of WebSocket Routes');
126
- for (const wsRoute of [...wsRouteFiles, ...this.webSocketRouteFiles]) {
138
+ for (const wsRoute of [
139
+ ...internalWsRouteFiles,
140
+ ...this.webSocketRouteFiles,
141
+ ]) {
127
142
  if (wsRoute.endsWith('js')) {
128
143
  const { name } = path.parse(wsRoute);
129
144
  const [, querySchema] = await Promise.all(routeSchemas.map(async (schemaType) => {
@@ -135,17 +150,20 @@ export class Browserless {
135
150
  const logger = createLogger(`ws:${name}`);
136
151
  const { default: Route, } = await import(wsImport + `?cb=${Date.now()}`);
137
152
  const route = new Route(this.browserManager, this.config, this.fileSystem, logger, this.metrics, this.monitoring);
138
- route.querySchema = safeParse(querySchema);
139
- route.config = () => this.config;
140
- route.metrics = () => this.metrics;
141
- route.monitoring = () => this.monitoring;
142
- route.fileSystem = () => this.fileSystem;
143
- route.debug = () => logger;
144
- wsRoutes.push(route);
153
+ if (!this.routeIsDisabled(route)) {
154
+ route.querySchema = safeParse(querySchema);
155
+ route.config = () => this.config;
156
+ route.metrics = () => this.metrics;
157
+ route.monitoring = () => this.monitoring;
158
+ route.fileSystem = () => this.fileSystem;
159
+ route.debug = () => logger;
160
+ wsRoutes.push(route);
161
+ }
145
162
  }
146
163
  }
164
+ const allRoutes = [...httpRoutes, ...wsRoutes];
147
165
  // Validate that we have the browsers they are asking for
148
- [...httpRoutes, ...wsRoutes].forEach((route) => {
166
+ allRoutes.forEach((route) => {
149
167
  if ('browser' in route &&
150
168
  route.browser &&
151
169
  internalBrowsers.includes(route.browser) &&
@@ -153,6 +171,12 @@ export class Browserless {
153
171
  throw new Error(`Couldn't load route "${route.path}" due to missing browser binary for "${route.browser?.name}"`);
154
172
  }
155
173
  });
174
+ const duplicateNamedRoutes = allRoutes
175
+ .filter((e, i, a) => a.findIndex((r) => r.name === e.name) !== i)
176
+ .map((r) => r.name);
177
+ if (duplicateNamedRoutes.length) {
178
+ this.debug(`Found duplicate routing names. Route names must be unique:`, duplicateNamedRoutes);
179
+ }
156
180
  httpRoutes.forEach((r) => this.router.registerHTTPRoute(r));
157
181
  wsRoutes.forEach((r) => this.router.registerWebSocketRoute(r));
158
182
  this.debug(`Imported and validated all route files, starting up server.`);
@@ -1,6 +1,6 @@
1
1
  import { chromeExecutablePath, createLogger, } from '@browserless.io/browserless';
2
2
  import { ChromiumCDP } from './chromium.cdp.js';
3
3
  export class ChromeCDP extends ChromiumCDP {
4
- executablePath = chromeExecutablePath;
4
+ executablePath = chromeExecutablePath();
5
5
  debug = createLogger('browsers:chrome:cdp');
6
6
  }
@@ -1,6 +1,6 @@
1
1
  import { chromeExecutablePath, createLogger, } from '@browserless.io/browserless';
2
2
  import { ChromiumPlaywright } from './chromium.playwright.js';
3
3
  export class ChromePlaywright extends ChromiumPlaywright {
4
- executablePath = chromeExecutablePath;
4
+ executablePath = chromeExecutablePath();
5
5
  debug = createLogger('browsers:chrome:playwright');
6
6
  }
@@ -11,6 +11,7 @@ export declare class BrowserManager {
11
11
  constructor(config: Config);
12
12
  private browserIsChrome;
13
13
  protected removeUserDataDir: (userDataDir: string | null) => Promise<void>;
14
+ protected onNewPage: (req: Request, page: unknown) => Promise<void>;
14
15
  /**
15
16
  * Returns the /json/protocol API contents from Chromium or Chrome, whichever is installed,
16
17
  * and modifies URLs to set them to the appropriate addresses configured.
@@ -26,6 +26,9 @@ export class BrowserManager {
26
26
  });
27
27
  }
28
28
  };
29
+ onNewPage = async (req, page) => {
30
+ await pageHook({ meta: req.parsed, page });
31
+ };
29
32
  /**
30
33
  * Returns the /json/protocol API contents from Chromium or Chrome, whichever is installed,
31
34
  * and modifies URLs to set them to the appropriate addresses configured.
@@ -316,7 +319,7 @@ export class BrowserManager {
316
319
  await browser.launch(launchOptions);
317
320
  await browserHook({ browser, meta: req.parsed });
318
321
  browser.on('newPage', async (page) => {
319
- await pageHook({ meta: req.parsed, page });
322
+ await this.onNewPage(req, page);
320
323
  (router.onNewPage || noop)(req.parsed || '', page);
321
324
  });
322
325
  return browser;
@@ -1 +1 @@
1
- ["b-modal-banner--disabled","blocked","bodyBlocked","c24-cc-visible","cbar","ccm-blocked","cmp-open","consent-modal-open","consent-modal-overflow","CookiePolicy--show","cookies","cu_k_cookie_consent_modal_open","gcdc-locked","gdpr-infobar-visible","gdprbanner_consent_gdpr_consent","gdprCookieBanner-acceptedAll","hasCookieBanner","ibeugdpr-disabled","idxrcookies-block-user-nav","modal--is-open","modal-open","no_scroll","no-scroll","noScroll","popin-gdpr-no-scroll","shopee-no-scroll","show-cookie-policy-info","sp-message-open","start-cookies","ta-cc-modal-open","touchevents-false","wt-cli-eu-country","wt-cli-geoip-on"]
1
+ ["appconsent_noscroll","b-modal-banner--disabled","blocked","bodyBlocked","c24-cc-visible","cbar","ccm-blocked","cmp-open","consent-modal-open","consent-modal-overflow","cookie_consent","CookiePolicy--show","cookies","cu_k_cookie_consent_modal_open","didomi-popup-open","gcdc-locked","gdpr-infobar-visible","gdprbanner_consent_gdpr_consent","gdprCookieBanner-acceptedAll","hasCookieBanner","ibeugdpr-disabled","idxrcookies-block-user-nav","modal--is-open","modal-open","no_scroll","no-scroll","noScroll","popin-gdpr-no-scroll","shopee-no-scroll","show-cookie-policy-info","sp-message-open","start-cookies","ta-cc-modal-open","touchevents-false","wt-cli-eu-country","wt-cli-geoip-on"]