@browserless.io/browserless 2.4.0 → 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.
- package/CHANGELOG.md +7 -0
- package/bin/browserless.js +17 -127
- package/bin/scaffold/README.md +95 -14
- package/build/browserless.d.ts +4 -2
- package/build/browserless.js +9 -6
- package/build/browsers/index.d.ts +3 -2
- package/build/browsers/index.js +6 -4
- package/build/data/selectors.json +1 -1
- package/build/exports.d.ts +1 -0
- package/build/exports.js +1 -0
- package/build/file-system.d.ts +2 -3
- package/build/file-system.js +10 -21
- package/build/file-system.spec.js +35 -18
- package/build/hooks.d.ts +9 -4
- package/build/hooks.js +26 -8
- package/build/limiter.d.ts +3 -2
- package/build/limiter.js +5 -3
- package/build/limiter.spec.js +45 -26
- package/build/routes/chrome/http/content.post.body.json +8 -8
- package/build/routes/chrome/http/pdf.post.body.json +8 -8
- package/build/routes/chrome/http/scrape.post.body.json +8 -8
- package/build/routes/chrome/http/screenshot.post.body.json +8 -8
- package/build/routes/chromium/http/content.post.body.json +8 -8
- package/build/routes/chromium/http/pdf.post.body.json +8 -8
- package/build/routes/chromium/http/scrape.post.body.json +8 -8
- package/build/routes/chromium/http/screenshot.post.body.json +8 -8
- package/build/routes/management/http/metrics-total.get.js +1 -1
- package/build/routes/management/http/metrics.get.js +2 -2
- package/build/sdk-utils.d.ts +13 -0
- package/build/sdk-utils.js +94 -0
- package/build/server.d.ts +3 -2
- package/build/server.js +6 -4
- package/build/utils.js +2 -1
- package/package.json +7 -7
- package/src/browserless.ts +20 -3
- package/src/browsers/index.ts +7 -5
- package/src/exports.ts +1 -0
- package/src/file-system.spec.ts +43 -18
- package/src/file-system.ts +16 -30
- package/src/hooks.ts +32 -8
- package/src/limiter.spec.ts +82 -112
- package/src/limiter.ts +3 -3
- package/src/routes/management/http/metrics-total.get.ts +3 -3
- package/src/routes/management/http/metrics.get.ts +2 -2
- package/src/sdk-utils.ts +136 -0
- package/src/server.ts +4 -3
- package/src/shared/content.http.ts +0 -1
- package/src/utils.ts +2 -1
- package/static/docs/swagger.json +11 -11
- package/static/docs/swagger.min.json +10 -10
- package/static/function/client.js +76 -63
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
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`
|
|
2
9
|
- Dependency updates.
|
|
3
10
|
|
|
4
11
|
# [v2.4.0](https://github.com/browserless/chrome/compare/v2.3.0...v2.4.0)
|
package/bin/browserless.js
CHANGED
|
@@ -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 } =
|
|
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
|
};
|
package/bin/scaffold/README.md
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
package/build/browserless.d.ts
CHANGED
|
@@ -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'];
|
package/build/browserless.js
CHANGED
|
@@ -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 =
|
|
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) => {
|
|
@@ -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>;
|
package/build/browsers/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { BLESS_PAGE_IDENTIFIER, BadRequest, ChromeCDP, ChromePlaywright, ChromiumCDP, ChromiumPlaywright, FirefoxPlaywright, HTTPManagementRoutes, NotFound, ServerError, WebkitPlaywright, availableBrowsers,
|
|
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
|
|
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
|
|
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);
|