@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/bin/browserless.js +17 -127
  3. package/bin/scaffold/README.md +95 -14
  4. package/build/browserless.d.ts +5 -3
  5. package/build/browserless.js +10 -6
  6. package/build/browsers/index.d.ts +5 -3
  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 +18 -4
  15. package/build/hooks.js +33 -4
  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 -8
  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 -8
  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/types.d.ts +2 -2
  34. package/build/utils.js +6 -10
  35. package/external/after.js +1 -1
  36. package/external/before.js +1 -1
  37. package/external/browser.js +1 -1
  38. package/external/page.js +1 -1
  39. package/package.json +7 -7
  40. package/src/browserless.ts +21 -3
  41. package/src/browsers/index.ts +9 -6
  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 +46 -4
  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 +0 -1
  53. package/src/types.ts +2 -2
  54. package/src/utils.ts +10 -11
  55. package/static/docs/swagger.json +11 -11
  56. package/static/docs/swagger.min.json +10 -10
  57. 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
- afterRequest(jobInfo);
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 = (await fileSystem.read(config.getMetricsJSONPath())).map(
33
- (m) => JSON.parse(m),
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 = `Gets total metric details from the time the server started.`;
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);
@@ -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 beforeRequest({ req, res });
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 beforeRequest({ head, req, socket });
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
 
@@ -180,7 +180,6 @@ export default class ChromiumContentPostRoute extends BrowserHTTPRoute {
180
180
  bestAttemptCatch(bestAttempt),
181
181
  );
182
182
 
183
-
184
183
  if (addStyleTag.length) {
185
184
  for (const tag in addStyleTag) {
186
185
  await page.addStyleTag(addStyleTag[tag]);
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: Request;
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: http.IncomingMessage;
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
- const entries = params.entries();
468
-
469
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
470
- const result: any = {};
471
- for (const [key, value] of entries) {
472
- result[key] = value === '' ? true : jsonOrString(value);
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,