@browserless.io/browserless 2.4.0 → 2.5.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.
- package/CHANGELOG.md +7 -0
- package/bin/browserless.js +17 -127
- package/bin/scaffold/README.md +95 -14
- package/build/browserless.d.ts +5 -3
- package/build/browserless.js +10 -6
- package/build/browsers/index.d.ts +5 -3
- 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 +18 -4
- package/build/hooks.js +33 -4
- 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/types.d.ts +2 -2
- package/build/utils.js +6 -10
- package/external/after.js +1 -1
- package/external/before.js +1 -1
- package/external/browser.js +1 -1
- package/external/page.js +1 -1
- package/package.json +7 -7
- package/src/browserless.ts +21 -3
- package/src/browsers/index.ts +9 -6
- 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 +46 -4
- 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/types.ts +2 -2
- package/src/utils.ts +10 -11
- package/static/docs/swagger.json +11 -11
- package/static/docs/swagger.min.json +10 -10
- package/static/function/client.js +76 -63
package/src/limiter.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AfterResponse,
|
|
3
3
|
Config,
|
|
4
|
+
Hooks,
|
|
4
5
|
Metrics,
|
|
5
6
|
Monitoring,
|
|
6
7
|
TooManyRequests,
|
|
7
8
|
WebHooks,
|
|
8
|
-
afterRequest,
|
|
9
9
|
createLogger,
|
|
10
10
|
} from '@browserless.io/browserless';
|
|
11
11
|
import q from 'queue';
|
|
@@ -33,13 +33,13 @@ export class Limiter extends q {
|
|
|
33
33
|
protected metrics: Metrics,
|
|
34
34
|
protected monitor: Monitoring,
|
|
35
35
|
protected webhooks: WebHooks,
|
|
36
|
+
protected hooks: Hooks,
|
|
36
37
|
) {
|
|
37
38
|
super({
|
|
38
39
|
autostart: true,
|
|
39
40
|
concurrency: config.getConcurrent(),
|
|
40
41
|
timeout: config.getTimeout(),
|
|
41
42
|
});
|
|
42
|
-
|
|
43
43
|
this.queued = config.getQueued();
|
|
44
44
|
|
|
45
45
|
this.debug(
|
|
@@ -78,7 +78,7 @@ export class Limiter extends q {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
protected jobEnd(jobInfo: AfterResponse) {
|
|
81
|
-
|
|
81
|
+
this.hooks.after(jobInfo);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
protected handleSuccess({ detail: { job } }: { detail: { job: Job } }) {
|
|
@@ -29,9 +29,9 @@ export default class MetricsTotalGetRoute extends HTTPRoute {
|
|
|
29
29
|
handler = async (_req: Request, res: ServerResponse): Promise<void> => {
|
|
30
30
|
const fileSystem = this.fileSystem();
|
|
31
31
|
const config = this.config();
|
|
32
|
-
const metrics = (
|
|
33
|
-
|
|
34
|
-
);
|
|
32
|
+
const metrics = (
|
|
33
|
+
await fileSystem.read(config.getMetricsJSONPath(), false)
|
|
34
|
+
).map((m) => JSON.parse(m));
|
|
35
35
|
const availableMetrics = metrics.length;
|
|
36
36
|
const totals: IBrowserlessMetricTotals = metrics.reduce(
|
|
37
37
|
(accum, metric) => ({
|
|
@@ -20,7 +20,7 @@ export default class MetricsGetRoute extends HTTPRoute {
|
|
|
20
20
|
browser = null;
|
|
21
21
|
concurrency = false;
|
|
22
22
|
contentTypes = [contentTypes.json];
|
|
23
|
-
description = `
|
|
23
|
+
description = `Returns a list of metric details as far back as possible.`;
|
|
24
24
|
method = Methods.get;
|
|
25
25
|
path = HTTPManagementRoutes.metrics;
|
|
26
26
|
tags = [APITags.management];
|
|
@@ -28,7 +28,7 @@ export default class MetricsGetRoute extends HTTPRoute {
|
|
|
28
28
|
const fileSystem = this.fileSystem();
|
|
29
29
|
const config = this.config();
|
|
30
30
|
|
|
31
|
-
const stats = await fileSystem.read(config.getMetricsJSONPath());
|
|
31
|
+
const stats = await fileSystem.read(config.getMetricsJSONPath(), false);
|
|
32
32
|
const response = `[${stats.join(',')}]`;
|
|
33
33
|
|
|
34
34
|
return writeResponse(res, 200, response, contentTypes.json);
|
package/src/sdk-utils.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
|
|
7
|
+
export const getArgSwitches = () => {
|
|
8
|
+
return process.argv.reduce(
|
|
9
|
+
(accum, arg, idx) => {
|
|
10
|
+
if (!arg.startsWith('--')) {
|
|
11
|
+
return accum;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (arg.includes('=')) {
|
|
15
|
+
const [parameter, value] = arg.split('=');
|
|
16
|
+
accum[parameter.replace(/-/gi, '')] = value || true;
|
|
17
|
+
return accum;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const nextSwitchOrParameter = process.argv[idx + 1];
|
|
21
|
+
const param = arg.replace(/-/gi, '');
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
typeof nextSwitchOrParameter === 'undefined' ||
|
|
25
|
+
nextSwitchOrParameter?.startsWith('--')
|
|
26
|
+
) {
|
|
27
|
+
accum[param] = true;
|
|
28
|
+
return accum;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
accum[param] = nextSwitchOrParameter;
|
|
32
|
+
|
|
33
|
+
return accum;
|
|
34
|
+
},
|
|
35
|
+
{} as { [key: string]: string | true },
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const getSourceFiles = async (cwd: string) => {
|
|
40
|
+
const buildDir = path.join(cwd, 'build');
|
|
41
|
+
const files = await fs.readdir(buildDir, { recursive: true });
|
|
42
|
+
const [httpRoutes, webSocketRoutes] = files.reduce(
|
|
43
|
+
([httpRoutes, webSocketRoutes], file) => {
|
|
44
|
+
const parsed = path.parse(file);
|
|
45
|
+
if (parsed.name.endsWith('http')) {
|
|
46
|
+
httpRoutes.push(path.join(buildDir, file));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (parsed.name.endsWith('ws')) {
|
|
50
|
+
webSocketRoutes.push(path.join(buildDir, file));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return [httpRoutes, webSocketRoutes];
|
|
54
|
+
},
|
|
55
|
+
[[] as string[], [] as string[]],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
files,
|
|
60
|
+
httpRoutes,
|
|
61
|
+
webSocketRoutes,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const camelCase = (str: string) =>
|
|
66
|
+
str.replace(/-([a-z])/g, (_, w) => w.toUpperCase());
|
|
67
|
+
|
|
68
|
+
export const prompt = async (question: string) => {
|
|
69
|
+
const promptLog = debug('browserless.io:prompt');
|
|
70
|
+
const rl = createInterface({
|
|
71
|
+
input: process.stdin,
|
|
72
|
+
output: process.stdout,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
promptLog(question);
|
|
77
|
+
rl.question(' > ', (a) => {
|
|
78
|
+
rl.close();
|
|
79
|
+
resolve(a.trim());
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const installDependencies = async (
|
|
85
|
+
workingDirectory: string,
|
|
86
|
+
): Promise<void> =>
|
|
87
|
+
new Promise((resolve, reject) => {
|
|
88
|
+
spawn('npm', ['i'], {
|
|
89
|
+
cwd: workingDirectory,
|
|
90
|
+
stdio: 'inherit',
|
|
91
|
+
}).once('close', (code) => {
|
|
92
|
+
if (code === 0) {
|
|
93
|
+
return resolve();
|
|
94
|
+
}
|
|
95
|
+
return reject(
|
|
96
|
+
`Error when installing dependencies, see output for more details`,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export const buildDockerImage = async (
|
|
102
|
+
cmd: string,
|
|
103
|
+
projectDir: string,
|
|
104
|
+
): Promise<void> =>
|
|
105
|
+
new Promise((resolve, reject) => {
|
|
106
|
+
const [docker, ...args] = cmd.split(' ');
|
|
107
|
+
spawn(docker, args, {
|
|
108
|
+
cwd: projectDir,
|
|
109
|
+
stdio: 'inherit',
|
|
110
|
+
}).once('close', (code) => {
|
|
111
|
+
if (code === 0) {
|
|
112
|
+
return resolve();
|
|
113
|
+
}
|
|
114
|
+
return reject(
|
|
115
|
+
`Error when building Docker image, see output for more details`,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export const buildTypeScript = async (
|
|
121
|
+
buildDir: string,
|
|
122
|
+
projectDir: string,
|
|
123
|
+
): Promise<void> =>
|
|
124
|
+
new Promise((resolve, reject) => {
|
|
125
|
+
spawn('npx', ['tsc', '--outDir', buildDir], {
|
|
126
|
+
cwd: projectDir,
|
|
127
|
+
stdio: 'inherit',
|
|
128
|
+
}).once('close', (code) => {
|
|
129
|
+
if (code === 0) {
|
|
130
|
+
return resolve();
|
|
131
|
+
}
|
|
132
|
+
return reject(
|
|
133
|
+
`Error in building TypeScript, see output for more details`,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
});
|
package/src/server.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
BadRequest,
|
|
5
5
|
Config,
|
|
6
6
|
HTTPRoute,
|
|
7
|
+
Hooks,
|
|
7
8
|
Metrics,
|
|
8
9
|
NotFound,
|
|
9
10
|
Request,
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
TooManyRequests,
|
|
15
16
|
Unauthorized,
|
|
16
17
|
WebSocketRoute,
|
|
17
|
-
beforeRequest,
|
|
18
18
|
contentTypes,
|
|
19
19
|
convertPathToURL,
|
|
20
20
|
createLogger,
|
|
@@ -48,6 +48,7 @@ export class HTTPServer extends EventEmitter {
|
|
|
48
48
|
protected metrics: Metrics,
|
|
49
49
|
protected token: Token,
|
|
50
50
|
protected router: Router,
|
|
51
|
+
protected hooks: Hooks,
|
|
51
52
|
) {
|
|
52
53
|
super();
|
|
53
54
|
this.host = config.getHost();
|
|
@@ -111,7 +112,7 @@ export class HTTPServer extends EventEmitter {
|
|
|
111
112
|
);
|
|
112
113
|
|
|
113
114
|
const req = request as Request;
|
|
114
|
-
const proceed = await
|
|
115
|
+
const proceed = await this.hooks.before({ req, res });
|
|
115
116
|
req.parsed = convertPathToURL(request.url || '', this.config);
|
|
116
117
|
shimLegacyRequests(req.parsed);
|
|
117
118
|
|
|
@@ -297,7 +298,7 @@ export class HTTPServer extends EventEmitter {
|
|
|
297
298
|
this.verbose(`Handling inbound WebSocket request on "${request.url}"`);
|
|
298
299
|
|
|
299
300
|
const req = request as Request;
|
|
300
|
-
const proceed = await
|
|
301
|
+
const proceed = await this.hooks.before({ head, req, socket });
|
|
301
302
|
req.parsed = convertPathToURL(request.url || '', this.config);
|
|
302
303
|
shimLegacyRequests(req.parsed);
|
|
303
304
|
|
package/src/types.ts
CHANGED
|
@@ -31,13 +31,13 @@ export type PathTypes =
|
|
|
31
31
|
|
|
32
32
|
export interface BeforeRequest {
|
|
33
33
|
head?: Buffer;
|
|
34
|
-
req:
|
|
34
|
+
req: http.IncomingMessage;
|
|
35
35
|
res?: http.ServerResponse;
|
|
36
36
|
socket?: stream.Duplex;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface AfterResponse {
|
|
40
|
-
req:
|
|
40
|
+
req: Request;
|
|
41
41
|
start: number;
|
|
42
42
|
status: 'successful' | 'error' | 'timedout';
|
|
43
43
|
}
|
package/src/utils.ts
CHANGED
|
@@ -463,16 +463,14 @@ export const availableBrowsers = Promise.all([
|
|
|
463
463
|
|
|
464
464
|
export const queryParamsToObject = (
|
|
465
465
|
params: URLSearchParams,
|
|
466
|
-
): Record<string, unknown> =>
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
return result;
|
|
475
|
-
};
|
|
466
|
+
): Record<string, unknown> =>
|
|
467
|
+
[...params.entries()].reduce(
|
|
468
|
+
(accum, [key, value]) => {
|
|
469
|
+
accum[key] = value;
|
|
470
|
+
return accum;
|
|
471
|
+
},
|
|
472
|
+
{} as Record<string, string>,
|
|
473
|
+
);
|
|
476
474
|
|
|
477
475
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
478
476
|
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
@@ -745,7 +743,8 @@ export const encrypt = (text: string, secret: Buffer) => {
|
|
|
745
743
|
};
|
|
746
744
|
|
|
747
745
|
export const decrypt = (encryptedText: string, secret: Buffer) => {
|
|
748
|
-
const [encrypted, iv] = encryptedText.split(encryptionSep);
|
|
746
|
+
const [encrypted, iv] = encryptedText.toString().split(encryptionSep);
|
|
747
|
+
console.log('>>', encryptedText.toString());
|
|
749
748
|
if (!iv) throw new ServerError('Bad or invalid encrypted format');
|
|
750
749
|
const decipher = crypto.createDecipheriv(
|
|
751
750
|
encryptionAlgo,
|