@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/CHANGELOG.md CHANGED
@@ -1,10 +1,24 @@
1
- # [Latest](https://github.com/browserless/chrome/compare/v2.3.0...main)
1
+ # [Latest](https://github.com/browserless/chrome/compare/v2.4.0...main)
2
+ - Support for a new "Hooks" module for setting up SDK hooks in a more SDK-friendly manner.
3
+ - Adds new exports for building downstream SDK projects more easily, versus using our CLI:
4
+ - `getArgSwitches`
5
+ - `getSourceFiles`
6
+ - `installDependencies`
7
+ - `buildDockerImage`
8
+ - `buildTypeScript`
9
+ - Dependency updates.
10
+
11
+ # [v2.4.0](https://github.com/browserless/chrome/compare/v2.3.0...v2.4.0)
2
12
  **Potentially Breaking**
3
13
  - Drops support for recording and screencasting in favor of library-based approaches.
14
+ - `playwright@1.42.1` and `puppeteer@22.4.0`.
4
15
 
5
16
  **Other Changes**
17
+ - SDK routes now take precedence over core routes when a path-collision occurs.
18
+ - Support SDK projects with their own `static` directories.
6
19
  - Adds support for user-specified `stop` methods in SDK Module extensions.
7
20
  - Core modules now extend `EventEmitter`, making them able to publish events.
21
+ - Fixes issues with `addStyleTag` happening before `goto` calls in REST APIs.
8
22
  - Dependency updates.
9
23
 
10
24
  # [v2.3.0](https://github.com/browserless/chrome/compare/v2.2.0...v2.3.0)
@@ -1,25 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable no-undef */
3
3
  'use strict';
4
+ import {
5
+ Browserless,
6
+ buildTypeScript,
7
+ camelCase,
8
+ getArgSwitches,
9
+ getSourceFiles,
10
+ installDependencies,
11
+ prompt,
12
+ } from '@browserless.io/browserless';
4
13
  import { readFile, writeFile } from 'fs/promises';
5
- import { Browserless } from '@browserless.io/browserless';
6
14
  import buildOpenAPI from '../scripts/build-open-api.js';
7
15
  import buildSchemas from '../scripts/build-schemas.js';
8
16
 
9
- import { createInterface } from 'readline';
10
17
  import debug from 'debug';
11
18
  import { dedent } from '../build/utils.js';
12
19
  import { fileURLToPath } from 'url';
13
20
  import fs from 'fs/promises';
14
21
  import path from 'path';
15
- import { spawn } from 'child_process';
16
22
 
17
23
  if (typeof process.env.DEBUG === 'undefined') {
18
24
  debug.enable('browserless*');
19
25
  }
20
26
 
21
27
  const log = debug('browserless.io:sdk:log');
22
- const promptLog = debug('browserless.io:prompt');
23
28
 
24
29
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
30
  const cmd = process.argv[2];
@@ -53,23 +58,6 @@ const browserlessPackageJSON = readFile(
53
58
  path.join(__dirname, '..', 'package.json'),
54
59
  ).then((r) => JSON.parse(r.toString()));
55
60
 
56
- const camelCase = (str) => str.replace(/-([a-z])/g, (_, w) => w.toUpperCase());
57
-
58
- const prompt = async (question) => {
59
- const rl = createInterface({
60
- input: process.stdin,
61
- output: process.stdout,
62
- });
63
-
64
- return new Promise((resolve) => {
65
- promptLog(question);
66
- rl.question(' > ', (a) => {
67
- rl.close();
68
- resolve(a.trim());
69
- });
70
- });
71
- };
72
-
73
61
  const translateSrcToBuild = (directory) => {
74
62
  const srcToBuild = directory.replace(srcDir, '');
75
63
  const pathParsed = path.parse(srcToBuild);
@@ -115,109 +103,6 @@ const clean = async () =>
115
103
  recursive: true,
116
104
  });
117
105
 
118
- const installDependencies = async (workingDirectory) =>
119
- new Promise((resolve, reject) => {
120
- spawn('npm', ['i'], {
121
- cwd: workingDirectory,
122
- stdio: 'inherit',
123
- }).once('close', (code) => {
124
- if (code === 0) {
125
- log(`Successfully installed Dependencies.`);
126
- return resolve();
127
- }
128
- return reject(
129
- `Error when installing dependencies, see output for more details`,
130
- );
131
- });
132
- });
133
-
134
- const buildDockerImage = async (cmd) => {
135
- new Promise((resolve, reject) => {
136
- const [docker, ...args] = cmd.split(' ');
137
- spawn(docker, args, {
138
- cwd: projectDir,
139
- stdio: 'inherit',
140
- }).once('close', (code) => {
141
- if (code === 0) {
142
- log(`Successfully built the docker image.`);
143
- return resolve();
144
- }
145
- return reject(
146
- `Error when building Docker image, see output for more details`,
147
- );
148
- });
149
- });
150
- };
151
-
152
- const buildTypeScript = async () =>
153
- new Promise((resolve, reject) => {
154
- spawn('npx', ['tsc', '--outDir', buildDir], {
155
- cwd: projectDir,
156
- stdio: 'inherit',
157
- }).once('close', (code) => {
158
- if (code === 0) {
159
- return resolve();
160
- }
161
- return reject(
162
- `Error in building TypeScript, see output for more details`,
163
- );
164
- });
165
- });
166
-
167
- const getSourceFiles = async () => {
168
- const files = await fs.readdir(compiledDir, { recursive: true });
169
- const [httpRoutes, webSocketRoutes] = files.reduce(
170
- ([httpRoutes, webSocketRoutes], file) => {
171
- const parsed = path.parse(file);
172
- if (parsed.name.endsWith('http')) {
173
- httpRoutes.push(path.join(compiledDir, file));
174
- }
175
-
176
- if (parsed.name.endsWith('ws')) {
177
- webSocketRoutes.push(path.join(compiledDir, file));
178
- }
179
-
180
- return [httpRoutes, webSocketRoutes];
181
- },
182
- [[], []],
183
- );
184
-
185
- return {
186
- files,
187
- httpRoutes,
188
- webSocketRoutes,
189
- };
190
- };
191
-
192
- const getArgSwitches = () => {
193
- return process.argv.reduce((accum, arg, idx) => {
194
- if (!arg.startsWith('--')) {
195
- return accum;
196
- }
197
-
198
- if (arg.includes('=')) {
199
- const [parameter, value] = arg.split('=');
200
- accum[parameter.replace(/-/gi, '')] = value || true;
201
- return accum;
202
- }
203
-
204
- const nextSwitchOrParameter = process.argv[idx + 1];
205
- const param = arg.replace(/-/gi, '');
206
-
207
- if (
208
- typeof nextSwitchOrParameter === 'undefined' ||
209
- nextSwitchOrParameter?.startsWith('--')
210
- ) {
211
- accum[param] = true;
212
- return accum;
213
- }
214
-
215
- accum[param] = nextSwitchOrParameter;
216
-
217
- return accum;
218
- }, {});
219
- };
220
-
221
106
  /**
222
107
  * Build
223
108
  * Responsible for compiling TypeScript, generating routes meta-data
@@ -228,10 +113,11 @@ const build = async () => {
228
113
  await clean();
229
114
 
230
115
  log(`Compiling TypeScript`);
231
- await buildTypeScript();
116
+ await buildTypeScript(buildDir, projectDir);
232
117
 
233
118
  log(`Building custom routes`);
234
- const { files, httpRoutes, webSocketRoutes } = await getSourceFiles();
119
+ const { files, httpRoutes, webSocketRoutes } =
120
+ await getSourceFiles(projectDir);
235
121
 
236
122
  log(`Building route runtime schema validation`);
237
123
  await buildSchemas(
@@ -272,6 +158,7 @@ const start = async (dev = false) => {
272
158
  Token,
273
159
  Webhooks,
274
160
  disabledRoutes,
161
+ Hooks,
275
162
  ] = await Promise.all([
276
163
  importDefault(files, 'browser-manager'),
277
164
  importDefault(files, 'config'),
@@ -283,6 +170,7 @@ const start = async (dev = false) => {
283
170
  importDefault(files, 'token'),
284
171
  importDefault(files, 'webhooks'),
285
172
  importDefault(files, 'disabled-routes'),
173
+ importDefault(files,' hooks'),
286
174
  ]);
287
175
 
288
176
  log(`Starting Browserless`);
@@ -291,6 +179,7 @@ const start = async (dev = false) => {
291
179
  const metrics = isConstructor(Metrics) ? new Metrics() : Metrics;
292
180
  const token = isConstructor(Token) ? new Token(config) : Token;
293
181
  const webhooks = isConstructor(Webhooks) ? new Webhooks(config) : Webhooks;
182
+ const hooks = isConstructor(Hooks) ? new Hooks() : Hooks;
294
183
  const browserManager = isConstructor(BrowserManager)
295
184
  ? new BrowserManager(config)
296
185
  : BrowserManager;
@@ -311,6 +200,7 @@ const start = async (dev = false) => {
311
200
  browserManager,
312
201
  config,
313
202
  fileSystem,
203
+ hooks,
314
204
  limiter,
315
205
  metrics,
316
206
  monitoring,
@@ -433,7 +323,7 @@ const buildDocker = async () => {
433
323
 
434
324
  if (proceed || !proceed.includes('n')) {
435
325
  log(`Starting docker build`);
436
- await buildDockerImage(cmd);
326
+ await buildDockerImage(cmd, projectDir);
437
327
  process.exit(0);
438
328
  }
439
329
  };
@@ -7,6 +7,7 @@ Please note that, as of right now, breaking changes aren't yet reflected in our
7
7
  Finally, this SDK and Browserless.io are built to support businesses and enterprise clients. If you're looking to use our code and modules in a production environment, [please contact us to get appropriately licensed](https://www.browserless.io/contact).
8
8
 
9
9
  ## Table of Contents
10
+
10
11
  - [Quick Start](#quick-start)
11
12
  - [About](#about)
12
13
  - [The CLI](#the-cli)
@@ -15,6 +16,7 @@ Finally, this SDK and Browserless.io are built to support businesses and enterpr
15
16
  - [Extending Modules](#extending-modules)
16
17
  - [Disabling Routes](#disabling-routes)
17
18
  - [Serving Static Files](#serving-static-files)
19
+ - [Implementing Hooks](#implementing-hooks)
18
20
  - [Running in Development](#running-in-development)
19
21
  - [Building for Production](#building-for-production)
20
22
  - [Running without Building](#running-without-building)
@@ -35,7 +37,7 @@ browserless will install a scaffolded project, install dependencies, and establi
35
37
 
36
38
  The Browserless.io SDK and accompanying CLI were written with intention that developers can add and enhance functionality into Browserless for your needs. This way you can get results into a database, third-party uploads, work within your enterprises requirements, all while using your favorite modern libraries. The Browserless platform simply ensure system stability, authorization, and the best developer experience.
37
39
 
38
- When creating a new project, the scaffold will ask a series of questions and generate the project for you. Once complete, a list of files it created for you. Here's the list so far:
40
+ When creating a new project, the scaffold will ask a series of questions and generate the project for you. Once complete, a list of files it created for you. Here's the list so far:
39
41
 
40
42
  ```
41
43
  ├── node_modules
@@ -67,6 +69,7 @@ Aside form scaffolding the project, Browserless also looks for the following:
67
69
  - `*.websocket.ts` Files with this naming convention are treated as WebSocket routes.
68
70
  - `config.ts` Loads the default export here as a config override.
69
71
  - `file-system.ts` Loads the default export here as a file-system override.
72
+ - `hooks.ts` Loads the default export as a Hooks override.
70
73
  - `limiter.ts` Loads the default export here as a limiter override (concurrency).
71
74
  - `metrics.ts` Loads the default export as a metrics override.
72
75
  - `monitoring.ts` Loads the default export as a monitoring override.
@@ -120,6 +123,7 @@ Browserless has 4 different types of primitive routes:
120
123
  Internally, we use this same class-based system, so feel free to see how those work in our open-source repositories. All routes are TypeScript-based and all our modules are documented, so you should be able to effectively write routes and modules with your code editor and not necessarily need these examples open. Below are a few examples:
121
124
 
122
125
  ### Basic HTTP Route
126
+
123
127
  ```ts
124
128
  import {
125
129
  APITags,
@@ -178,6 +182,7 @@ export default class HelloWorldRoute extends HTTPRoute {
178
182
  ```
179
183
 
180
184
  ### Chromium WebSocket Route
185
+
181
186
  ```ts
182
187
  import {
183
188
  APITags,
@@ -221,12 +226,8 @@ export default class ChromiumWebSocketRoute extends BrowserWebsocketRoute {
221
226
  // Routes with a browser type get a browser argument of the Browser instance, otherwise
222
227
  // request, socket, and head are the other 3 arguments. Here we pass them through
223
228
  // and proxy the request into Chromium to handle.
224
- handler = async (
225
- req,
226
- socket,
227
- head,
228
- chromium,
229
- ): Promise<void> => chromium.proxyWebSocket(req, socket, head);
229
+ handler = async (req, socket, head, chromium): Promise<void> =>
230
+ chromium.proxyWebSocket(req, socket, head);
230
231
  }
231
232
  ```
232
233
 
@@ -256,6 +257,7 @@ Browserless comes out-of-the-box with many utilities and functions to help with
256
257
  - `untildify(path)` Remove `~` characters from a path and returns the full filepath.
257
258
 
258
259
  ### Error helpers:
260
+
259
261
  - `BadRequest` An error that will cause browserless to return a `400` response with the error text being the message.
260
262
  - `TooManyRequests` When thrown causes browserless to return a `429` with the error as the message.
261
263
  - `ServerError` Returns a `500` code and shows the corresponding message.
@@ -284,7 +286,7 @@ export default class MyConfig extends Config {
284
286
  // Load from environment variables or default to some other named bucket.
285
287
  return process.env.S3_BUCKET ?? 'my-fun-s3-bucket';
286
288
  };
287
- };
289
+ }
288
290
  ```
289
291
 
290
292
  Then, later, in your route you can define some functionality and load the config object. Let's make a PDF route that generates a PDF from a URL and then saves the result to this S3 bucket.
@@ -340,11 +342,7 @@ export default class PDFToS3Route extends BrowserHTTPRoute {
340
342
  tags = [APITags.browserAPI];
341
343
 
342
344
  // Handler's are where we embed the logic that facilitates this route.
343
- handler = async (
344
- req,
345
- res,
346
- browser,
347
- ): Promise<void> => {
345
+ handler = async (req, res, browser): Promise<void> => {
348
346
  // Modules like Config are injected via this internal methods.
349
347
  // Use them to load core modules within the platform.
350
348
  const config = this.config() as MyConfig;
@@ -422,6 +420,89 @@ Will be available to be served under:
422
420
 
423
421
  Which prevents this route from colliding with our internal `/docs` route.
424
422
 
423
+ ## Implementing Hooks
424
+
425
+ Browserless support writing a custom hooks module, where you can run or do custom checks during lifecycle events in Browserless. There are 4 events you can write custom functions for, which are detailed below. By default these hooks are benign and do nothing, so implementing them will alter how Browserless functions. Here's the default class written out with types:
426
+
427
+ ```ts
428
+ // src/hooks.ts file
429
+ import {
430
+ Request,
431
+ Response,
432
+ ChromiumCDP,
433
+ FirefoxPlaywright,
434
+ ChromiumPlaywright,
435
+ WebkitPlaywright,
436
+ } from '@browserless.io/browserless';
437
+ import * as stream from 'stream';
438
+ import puppeteer from 'puppeteer-core';
439
+
440
+ export class Hooks extends EventEmitter {
441
+ constructor() {
442
+ super();
443
+ }
444
+
445
+ // Called in src/server.ts for incoming HTTP and WebSocket requests, which
446
+ // is why certain arguments might not be present -- only the Request is
447
+ // guaranteed to be present as it is shared in both WS and HTTP requests.
448
+ // MUST return a true/false indicating if Browserless should continue
449
+ // handling the request or not.
450
+ before({
451
+ req,
452
+ res,
453
+ socket,
454
+ head,
455
+ }: {
456
+ req: Request;
457
+ res?: Response;
458
+ socket?: stream.Duplex;
459
+ head?: Buffer;
460
+ }): Promise<boolean> {
461
+ return Promise.resolve(true);
462
+ }
463
+
464
+ // Called in src/limiter.ts and provides details regarding the result of the
465
+ // session and a "start" time (Date.now()) of when the session started to run.
466
+ // No return value or type required.
467
+ after(args: {
468
+ status: 'successful' | 'error' | 'timedout';
469
+ start: number;
470
+ req: Request;
471
+ }): Promise<void> {
472
+ return Promise.resolve(undefined);
473
+ }
474
+
475
+ // Called in src/browsers/index.ts
476
+ // Called for every new CDP or Puppeteer-like "Page" creation in a browser.
477
+ // Can be used to inject behaviors or add events to a page's lifecycle.
478
+ // "meta" property is a parsed URL of the original incoming request.
479
+ // No return value or type required.
480
+ page(args: { meta: URL, page: puppeteer.Page }): Promise<void> {
481
+ return Promise.resolve(undefined);
482
+ }
483
+
484
+ // Called in src/browsers/index.ts
485
+ // Called for every new Browser creation in browserless, regardless of type.
486
+ // Can be used to inject behaviors or add events to a browser's lifecycle.
487
+ // "meta" property is a parsed URL of the original incoming request.
488
+ // No return value or type required.
489
+ browser(args: {
490
+ browser:
491
+ | ChromiumCDP
492
+ | FirefoxPlaywright
493
+ | ChromiumPlaywright
494
+ | WebkitPlaywright;
495
+ meta: URL;
496
+ }): Promise<unknown> {
497
+ return Promise.resolve(undefined);
498
+ }
499
+ }
500
+ ```
501
+
502
+ Of these hooks only `before` needs to return a true/false condition on whether or not to allow the request to proceed. If `false` Browserless does NOT write a response and simply closes the connection. Your `before` function should take care of any writing, closing, or streaming responses back before returning a `boolean`.
503
+
504
+ Otherwise all other hooks are there for injecting side-effect like behaviors and don't necessarily need to return any kind of value. If a certain value is returned, Browserless simply ignores it.
505
+
425
506
  ## Running in Development
426
507
 
427
508
  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:
@@ -443,7 +524,7 @@ While the end-goal is a docker image being built, you can simply do a complete b
443
524
  $ npm run build
444
525
  ```
445
526
 
446
- Similar to development builds, this will compile all assets, generate OpenAPI JSON, and build out the runtime validation files, but *won't start the http server*.
527
+ Similar to development builds, this will compile all assets, generate OpenAPI JSON, and build out the runtime validation files, but _won't start the http server_.
447
528
 
448
529
  If you wish to simply run the server without having to rebuild assets, then read more below.
449
530
 
@@ -1,13 +1,14 @@
1
1
  /// <reference types="debug" />
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
- import { BrowserManager, Config, FileSystem, HTTPServer, Limiter, Metrics, Monitoring, Router, Token, WebHooks } from '@browserless.io/browserless';
4
+ import { BrowserManager, Config, FileSystem, HTTPServer, Hooks, Limiter, Metrics, Monitoring, Router, Token, WebHooks } from '@browserless.io/browserless';
5
5
  import { EventEmitter } from 'events';
6
6
  export declare class Browserless extends EventEmitter {
7
7
  protected debug: debug.Debugger;
8
8
  protected browserManager: BrowserManager;
9
9
  protected config: Config;
10
10
  protected fileSystem: FileSystem;
11
+ protected hooks: Hooks;
11
12
  protected limiter: Limiter;
12
13
  protected metrics: Metrics;
13
14
  protected monitoring: Monitoring;
@@ -21,10 +22,11 @@ export declare class Browserless extends EventEmitter {
21
22
  server?: HTTPServer;
22
23
  metricsSaveInterval: number;
23
24
  metricsSaveIntervalID?: NodeJS.Timer;
24
- constructor({ browserManager, config, fileSystem, limiter, metrics, monitoring, router, token, webhooks, }?: {
25
+ constructor({ browserManager, config, fileSystem, hooks, limiter, metrics, monitoring, router, token, webhooks, }?: {
25
26
  browserManager?: Browserless['browserManager'];
26
27
  config?: Browserless['config'];
27
28
  fileSystem?: Browserless['fileSystem'];
29
+ hooks?: Browserless['hooks'];
28
30
  limiter?: Browserless['limiter'];
29
31
  metrics?: Browserless['metrics'];
30
32
  monitoring?: Browserless['monitoring'];
@@ -1,5 +1,5 @@
1
1
  import * as path from 'path';
2
- import { BrowserManager, ChromeCDP, ChromiumCDP, ChromiumPlaywright, Config, FileSystem, FirefoxPlaywright, HTTPServer, Limiter, Metrics, Monitoring, Router, Token, WebHooks, WebkitPlaywright, availableBrowsers, createLogger, getRouteFiles, makeExternalURL, printLogo, safeParse, } from '@browserless.io/browserless';
2
+ import { BrowserManager, ChromeCDP, ChromiumCDP, ChromiumPlaywright, Config, FileSystem, FirefoxPlaywright, HTTPServer, Hooks, Limiter, Metrics, Monitoring, Router, Token, WebHooks, WebkitPlaywright, availableBrowsers, createLogger, getRouteFiles, makeExternalURL, printLogo, safeParse, } from '@browserless.io/browserless';
3
3
  import { EventEmitter } from 'events';
4
4
  import { readFile } from 'fs/promises';
5
5
  import { userInfo } from 'os';
@@ -9,6 +9,7 @@ export class Browserless extends EventEmitter {
9
9
  browserManager;
10
10
  config;
11
11
  fileSystem;
12
+ hooks;
12
13
  limiter;
13
14
  metrics;
14
15
  monitoring;
@@ -22,18 +23,20 @@ export class Browserless extends EventEmitter {
22
23
  server;
23
24
  metricsSaveInterval = 5 * 60 * 1000;
24
25
  metricsSaveIntervalID;
25
- constructor({ browserManager, config, fileSystem, limiter, metrics, monitoring, router, token, webhooks, } = {}) {
26
+ constructor({ browserManager, config, fileSystem, hooks, limiter, metrics, monitoring, router, token, webhooks, } = {}) {
26
27
  super();
27
28
  this.config = config || new Config();
28
29
  this.metrics = metrics || new Metrics();
29
30
  this.token = token || new Token(this.config);
31
+ this.hooks = hooks || new Hooks();
30
32
  this.webhooks = webhooks || new WebHooks(this.config);
31
- this.browserManager = browserManager || new BrowserManager(this.config);
33
+ this.browserManager =
34
+ browserManager || new BrowserManager(this.config, this.hooks);
32
35
  this.monitoring = monitoring || new Monitoring(this.config);
33
36
  this.fileSystem = fileSystem || new FileSystem(this.config);
34
37
  this.limiter =
35
38
  limiter ||
36
- new Limiter(this.config, this.metrics, this.monitoring, this.webhooks);
39
+ new Limiter(this.config, this.metrics, this.monitoring, this.webhooks, this.hooks);
37
40
  this.router =
38
41
  router || new Router(this.config, this.browserManager, this.limiter);
39
42
  }
@@ -62,7 +65,7 @@ export class Browserless extends EventEmitter {
62
65
  })}`);
63
66
  if (metricsPath) {
64
67
  this.debug(`Saving metrics to "${metricsPath}"`);
65
- this.fileSystem.append(metricsPath, JSON.stringify(aggregatedStats));
68
+ this.fileSystem.append(metricsPath, JSON.stringify(aggregatedStats), false);
66
69
  }
67
70
  };
68
71
  setMetricsSaveInterval = (interval) => {
@@ -125,8 +128,8 @@ export class Browserless extends EventEmitter {
125
128
  this.debug(`Running as user "${userInfo().username}"`);
126
129
  this.debug('Starting import of HTTP Routes');
127
130
  for (const httpRoute of [
128
- ...internalHttpRouteFiles,
129
131
  ...this.httpRouteFiles,
132
+ ...internalHttpRouteFiles,
130
133
  ]) {
131
134
  if (httpRoute.endsWith('js')) {
132
135
  const { name } = path.parse(httpRoute);
@@ -154,8 +157,8 @@ export class Browserless extends EventEmitter {
154
157
  }
155
158
  this.debug('Starting import of WebSocket Routes');
156
159
  for (const wsRoute of [
157
- ...internalWsRouteFiles,
158
160
  ...this.webSocketRouteFiles,
161
+ ...internalWsRouteFiles,
159
162
  ]) {
160
163
  if (wsRoute.endsWith('js')) {
161
164
  const { name } = path.parse(wsRoute);
@@ -199,7 +202,7 @@ export class Browserless extends EventEmitter {
199
202
  httpRoutes.forEach((r) => this.router.registerHTTPRoute(r));
200
203
  wsRoutes.forEach((r) => this.router.registerWebSocketRoute(r));
201
204
  this.debug(`Imported and validated all route files, starting up server.`);
202
- this.server = new HTTPServer(this.config, this.metrics, this.token, this.router);
205
+ this.server = new HTTPServer(this.config, this.metrics, this.token, this.router, this.hooks);
203
206
  await this.server.start();
204
207
  this.debug(`Starting metrics collection.`);
205
208
  this.metricsSaveIntervalID = setInterval(() => this.saveMetrics(), this.metricsSaveInterval);
@@ -1,14 +1,15 @@
1
1
  /// <reference types="debug" />
2
- import { BrowserHTTPRoute, BrowserInstance, BrowserWebsocketRoute, BrowserlessSession, BrowserlessSessionJSON, CDPJSONPayload, ChromiumCDP, Config, Request } from '@browserless.io/browserless';
2
+ import { BrowserHTTPRoute, BrowserInstance, BrowserWebsocketRoute, BrowserlessSession, BrowserlessSessionJSON, CDPJSONPayload, ChromiumCDP, Config, Hooks, Request } from '@browserless.io/browserless';
3
3
  export declare class BrowserManager {
4
4
  protected config: Config;
5
+ protected hooks: Hooks;
5
6
  protected browsers: Map<BrowserInstance, BrowserlessSession>;
6
7
  protected launching: Map<string, Promise<unknown>>;
7
8
  protected timers: Map<string, number>;
8
9
  protected debug: import("debug").Debugger;
9
10
  protected chromeBrowsers: (typeof ChromiumCDP)[];
10
11
  protected playwrightBrowserNames: string[];
11
- constructor(config: Config);
12
+ constructor(config: Config, hooks: Hooks);
12
13
  private browserIsChrome;
13
14
  protected removeUserDataDir: (userDataDir: string | null) => Promise<void>;
14
15
  protected onNewPage: (req: Request, page: unknown) => Promise<void>;
@@ -1,8 +1,9 @@
1
- import { BLESS_PAGE_IDENTIFIER, BadRequest, ChromeCDP, ChromePlaywright, ChromiumCDP, ChromiumPlaywright, FirefoxPlaywright, HTTPManagementRoutes, NotFound, ServerError, WebkitPlaywright, availableBrowsers, browserHook, convertIfBase64, createLogger, exists, generateDataDir, makeExternalURL, noop, pageHook, parseBooleanParam, } from '@browserless.io/browserless';
1
+ import { BLESS_PAGE_IDENTIFIER, BadRequest, ChromeCDP, ChromePlaywright, ChromiumCDP, ChromiumPlaywright, FirefoxPlaywright, HTTPManagementRoutes, NotFound, ServerError, WebkitPlaywright, availableBrowsers, convertIfBase64, createLogger, exists, generateDataDir, makeExternalURL, noop, parseBooleanParam, } from '@browserless.io/browserless';
2
2
  import { deleteAsync } from 'del';
3
3
  import path from 'path';
4
4
  export class BrowserManager {
5
5
  config;
6
+ hooks;
6
7
  browsers = new Map();
7
8
  launching = new Map();
8
9
  timers = new Map();
@@ -14,8 +15,9 @@ export class BrowserManager {
14
15
  FirefoxPlaywright.name,
15
16
  WebkitPlaywright.name,
16
17
  ];
17
- constructor(config) {
18
+ constructor(config, hooks) {
18
19
  this.config = config;
20
+ this.hooks = hooks;
19
21
  }
20
22
  browserIsChrome = (b) => this.chromeBrowsers.some((chromeBrowser) => b instanceof chromeBrowser);
21
23
  removeUserDataDir = async (userDataDir) => {
@@ -27,7 +29,7 @@ export class BrowserManager {
27
29
  }
28
30
  };
29
31
  onNewPage = async (req, page) => {
30
- await pageHook({ meta: req.parsed, page });
32
+ await this.hooks.page({ meta: req.parsed, page });
31
33
  };
32
34
  /**
33
35
  * Returns the /json/protocol API contents from Chromium or Chrome, whichever is installed,
@@ -313,7 +315,7 @@ export class BrowserManager {
313
315
  };
314
316
  this.browsers.set(browser, connectionMeta);
315
317
  await browser.launch(launchOptions);
316
- await browserHook({ browser, meta: req.parsed });
318
+ await this.hooks.browser({ browser, meta: req.parsed });
317
319
  browser.on('newPage', async (page) => {
318
320
  await this.onNewPage(req, page);
319
321
  (router.onNewPage || noop)(req.parsed || '', page);