@browserless.io/browserless 2.0.0-beta-1 → 2.0.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 (69) hide show
  1. package/bin/browserless.js +128 -8
  2. package/build/browserless.d.ts +23 -18
  3. package/build/browserless.js +24 -17
  4. package/build/browsers/cdp-chromium.d.ts +17 -14
  5. package/build/browsers/index.d.ts +27 -10
  6. package/build/browsers/index.js +2 -12
  7. package/build/browsers/playwright-chromium.d.ts +12 -9
  8. package/build/browsers/playwright-firefox.d.ts +12 -9
  9. package/build/browsers/playwright-webkit.d.ts +12 -9
  10. package/build/config.d.ts +31 -31
  11. package/build/exports.d.ts +2 -0
  12. package/build/exports.js +2 -0
  13. package/build/file-system.d.ts +2 -2
  14. package/build/limiter.d.ts +34 -11
  15. package/build/metrics.d.ts +17 -11
  16. package/build/monitoring.d.ts +3 -2
  17. package/build/router.d.ts +28 -0
  18. package/build/router.js +138 -0
  19. package/build/routes/chromium/http/content-post.body.json +8 -8
  20. package/build/routes/chromium/http/download-post.js +1 -1
  21. package/build/routes/chromium/http/function-post.js +1 -1
  22. package/build/routes/chromium/http/pdf-post.body.json +8 -8
  23. package/build/routes/chromium/http/performance.js +1 -1
  24. package/build/routes/chromium/http/scrape-post.body.json +8 -8
  25. package/build/routes/chromium/http/screenshot-post.body.json +8 -8
  26. package/build/routes/chromium/utils/function/client.d.ts +3 -3
  27. package/build/routes/management/http/config-get.js +1 -1
  28. package/build/routes/management/http/metrics-get.js +1 -1
  29. package/build/routes/management/http/metrics-total-get.js +1 -1
  30. package/build/routes/management/http/sessions-get.js +3 -3
  31. package/build/routes/management/http/static-get.js +1 -1
  32. package/build/server.d.ts +22 -27
  33. package/build/server.js +29 -149
  34. package/build/token.d.ts +6 -0
  35. package/build/token.js +21 -0
  36. package/build/types.d.ts +85 -14
  37. package/build/utils.d.ts +1 -2
  38. package/build/utils.js +0 -13
  39. package/build/webhooks.d.ts +2 -2
  40. package/docker/sdk/Dockerfile +2 -6
  41. package/package.json +4 -4
  42. package/src/browserless.ts +44 -32
  43. package/src/browsers/cdp-chromium.ts +13 -13
  44. package/src/browsers/index.ts +9 -24
  45. package/src/browsers/playwright-chromium.ts +9 -9
  46. package/src/browsers/playwright-firefox.ts +9 -9
  47. package/src/browsers/playwright-webkit.ts +9 -9
  48. package/src/config.ts +32 -31
  49. package/src/exports.ts +2 -0
  50. package/src/file-system.ts +2 -2
  51. package/src/limiter.ts +11 -11
  52. package/src/metrics.ts +11 -11
  53. package/src/monitoring.ts +2 -2
  54. package/src/router.ts +234 -0
  55. package/src/routes/chromium/http/download-post.ts +1 -1
  56. package/src/routes/chromium/http/function-post.ts +1 -1
  57. package/src/routes/chromium/http/performance.ts +1 -1
  58. package/src/routes/chromium/utils/function/client.ts +2 -2
  59. package/src/routes/management/http/config-get.ts +1 -1
  60. package/src/routes/management/http/metrics-get.ts +1 -1
  61. package/src/routes/management/http/metrics-total-get.ts +1 -1
  62. package/src/routes/management/http/sessions-get.ts +3 -3
  63. package/src/routes/management/http/static-get.ts +1 -1
  64. package/src/server.ts +43 -238
  65. package/src/token.ts +40 -0
  66. package/src/types.ts +92 -17
  67. package/src/utils.ts +0 -25
  68. package/src/webhooks.ts +2 -2
  69. package/static/docs/swagger.json +9 -9
@@ -7,7 +7,7 @@ const route = {
7
7
  contentTypes: [contentTypes.json],
8
8
  description: `Gets total metric details from the time the server started.`,
9
9
  handler: async (_req, res) => {
10
- const { _fileSystem, _config } = route;
10
+ const { getFileSystem: _fileSystem, getConfig: _config } = route;
11
11
  if (!_fileSystem || !_config) {
12
12
  throw new ServerError(`Couldn't locate the file-system or config module`);
13
13
  }
@@ -8,7 +8,7 @@ const route = {
8
8
  contentTypes: [contentTypes.json],
9
9
  description: `Gets total metric details summed from the time the server started.`,
10
10
  handler: async (_req, res) => {
11
- const { _fileSystem, _config } = route;
11
+ const { getFileSystem: _fileSystem, getConfig: _config } = route;
12
12
  if (!_fileSystem || !_config) {
13
13
  throw new ServerError(`Couldn't locate the file-system or config module`);
14
14
  }
@@ -6,12 +6,12 @@ const route = {
6
6
  concurrency: false,
7
7
  contentTypes: [contentTypes.json],
8
8
  description: `Lists all currently running sessions and relevant meta-data excluding potentially open pages.`,
9
- handler: async (req, res) => {
10
- const { _browserManager: browserManager } = route;
9
+ handler: async (_req, res) => {
10
+ const { getBrowserManager: browserManager } = route;
11
11
  if (!browserManager) {
12
12
  throw new BadRequest(`Couldn't load browsers running`);
13
13
  }
14
- const response = await browserManager().getAllSessions(req);
14
+ const response = await browserManager().getAllSessions();
15
15
  return jsonResponse(res, 200, response);
16
16
  },
17
17
  method: Methods.get,
@@ -28,7 +28,7 @@ const route = {
28
28
  contentTypes: [contentTypes.any],
29
29
  description: `Serves static files inside of this "static" directory. Content-types will vary depending on the type of file being returned.`,
30
30
  handler: async (req, res) => {
31
- const { _config: getConfig } = route;
31
+ const { getConfig: getConfig } = route;
32
32
  const { pathname } = req.parsed;
33
33
  const fileCache = pathMap.get(pathname);
34
34
  if (fileCache) {
package/build/server.d.ts CHANGED
@@ -1,4 +1,10 @@
1
- import { BrowserHTTPRoute, BrowserManager, BrowserWebsocketRoute, Config, HTTPRoute, Limiter, Metrics, WebSocketRoute } from '@browserless.io/browserless';
1
+ /// <reference types="node" />
2
+ /// <reference types="debug" />
3
+ /// <reference types="node" />
4
+ /// <reference types="node" />
5
+ import * as http from 'http';
6
+ import * as stream from 'stream';
7
+ import { Config, Metrics, Request, Response, Router, Token } from '@browserless.io/browserless';
2
8
  export interface HTTPServerOptions {
3
9
  concurrent: number;
4
10
  host: string;
@@ -7,32 +13,21 @@ export interface HTTPServerOptions {
7
13
  timeout: number;
8
14
  }
9
15
  export declare class HTTPServer {
10
- private config;
11
- private metrics;
12
- private browserManager;
13
- private limiter;
14
- private httpRoutes;
15
- private webSocketRoutes;
16
- private server;
17
- private port;
18
- private host?;
19
- private log;
20
- private verbose;
21
- constructor(config: Config, metrics: Metrics, browserManager: BrowserManager, limiter: Limiter, httpRoutes: Array<HTTPRoute | BrowserHTTPRoute>, webSocketRoutes: Array<WebSocketRoute | BrowserWebsocketRoute>);
22
- private onQueueFullHTTP;
23
- private onQueueFullWebSocket;
24
- private onHTTPTimeout;
25
- private onWebsocketTimeout;
26
- private onHTTPUnauthorized;
27
- private onWebsocketUnauthorized;
28
- private wrapHTTPHandler;
29
- private wrapWebSocketHandler;
30
- private getTimeout;
31
- private registerHTTPRoute;
32
- private registerWebSocketRoute;
16
+ protected config: Config;
17
+ protected metrics: Metrics;
18
+ protected token: Token;
19
+ protected router: Router;
20
+ protected server: http.Server;
21
+ protected port: number;
22
+ protected host?: string;
23
+ protected log: import("debug").Debugger;
24
+ protected verbose: import("debug").Debugger;
25
+ constructor(config: Config, metrics: Metrics, token: Token, router: Router);
26
+ protected onHTTPUnauthorized: (_req: Request, res: Response) => void;
27
+ protected onWebsocketUnauthorized: (_req: Request, socket: stream.Duplex) => void;
33
28
  start(): Promise<void>;
34
29
  stop(): Promise<void>;
35
- private tearDown;
36
- private handleRequest;
37
- private handleWebSocket;
30
+ protected tearDown(): void;
31
+ protected handleRequest: (request: http.IncomingMessage, res: http.ServerResponse) => Promise<void | http.ServerResponse<http.IncomingMessage>>;
32
+ protected handleWebSocket: (request: http.IncomingMessage, socket: stream.Duplex, head: Buffer) => Promise<void>;
38
33
  }
package/build/server.js CHANGED
@@ -1,49 +1,26 @@
1
1
  import * as http from 'http';
2
- import { BadRequest, HTTPManagementRoutes, NotFound, Timeout, TooManyRequests, Unauthorized, beforeRequest, contentTypes, convertPathToURL, createLogger, isAuthorized, isConnected, queryParamsToObject, readBody, shimLegacyRequests, writeResponse, } from '@browserless.io/browserless';
2
+ import { BadRequest, NotFound, Timeout, TooManyRequests, Unauthorized, beforeRequest, contentTypes, convertPathToURL, createLogger, queryParamsToObject, readBody, shimLegacyRequests, writeResponse, } from '@browserless.io/browserless';
3
3
  // @ts-ignore
4
4
  import Enjoi from 'enjoi';
5
- import micromatch from 'micromatch';
6
5
  export class HTTPServer {
7
6
  config;
8
7
  metrics;
9
- browserManager;
10
- limiter;
11
- httpRoutes;
12
- webSocketRoutes;
8
+ token;
9
+ router;
13
10
  server = http.createServer();
14
11
  port;
15
12
  host;
16
13
  log = createLogger('server');
17
14
  verbose = createLogger('server:verbose');
18
- constructor(config, metrics, browserManager, limiter, httpRoutes, webSocketRoutes) {
15
+ constructor(config, metrics, token, router) {
19
16
  this.config = config;
20
17
  this.metrics = metrics;
21
- this.browserManager = browserManager;
22
- this.limiter = limiter;
23
- this.httpRoutes = httpRoutes;
24
- this.webSocketRoutes = webSocketRoutes;
18
+ this.token = token;
19
+ this.router = router;
25
20
  this.host = config.getHost();
26
21
  this.port = config.getPort();
27
- this.httpRoutes = httpRoutes.map((r) => this.registerHTTPRoute(r));
28
- this.webSocketRoutes = webSocketRoutes.map((r) => this.registerWebSocketRoute(r));
29
22
  this.log(`Server instantiated with host "${this.host}" on port "${this.port}" using token "${this.config.getToken()}"`);
30
23
  }
31
- onQueueFullHTTP = (_req, res) => {
32
- this.log(`Queue is full, sending 429 response`);
33
- return writeResponse(res, 429, 'Too many requests');
34
- };
35
- onQueueFullWebSocket = (_req, socket) => {
36
- this.log(`Queue is full, sending 429 response`);
37
- return writeResponse(socket, 429, 'Too many requests');
38
- };
39
- onHTTPTimeout = (_req, res) => {
40
- this.log(`HTTP job has timedout, sending 429 response`);
41
- return writeResponse(res, 408, 'Request has timed out');
42
- };
43
- onWebsocketTimeout = (_req, socket) => {
44
- this.log(`Websocket job has timedout, sending 429 response`);
45
- return writeResponse(socket, 408, 'Request has timed out');
46
- };
47
24
  onHTTPUnauthorized = (_req, res) => {
48
25
  this.log(`HTTP request is not properly authorized, responding with 401`);
49
26
  this.metrics.addUnauthorized();
@@ -54,87 +31,6 @@ export class HTTPServer {
54
31
  this.metrics.addUnauthorized();
55
32
  return writeResponse(socket, 401, 'Bad or missing authentication.');
56
33
  };
57
- wrapHTTPHandler = (route, handler) => async (req, res) => {
58
- if (!isConnected(res)) {
59
- this.log(`HTTP Request has closed prior to running`);
60
- return Promise.resolve();
61
- }
62
- if (route.browser) {
63
- const browser = await this.browserManager.getBrowserForRequest(req, route);
64
- if (!isConnected(res)) {
65
- this.log(`HTTP Request has closed prior to running`);
66
- this.browserManager.complete(browser);
67
- return Promise.resolve();
68
- }
69
- if (!browser) {
70
- return writeResponse(res, 500, `Error loading the browser.`);
71
- }
72
- if (!isConnected(res)) {
73
- this.log(`HTTP Request has closed prior to running`);
74
- return Promise.resolve();
75
- }
76
- try {
77
- this.verbose(`Running found HTTP handler.`);
78
- return await handler(req, res, browser);
79
- }
80
- finally {
81
- this.verbose(`HTTP Request handler has finished.`);
82
- this.browserManager.complete(browser);
83
- }
84
- }
85
- return handler(req, res);
86
- };
87
- wrapWebSocketHandler = (route, handler) => async (req, socket, head) => {
88
- if (!isConnected(socket)) {
89
- this.log(`WebSocket Request has closed prior to running`);
90
- return Promise.resolve();
91
- }
92
- if (route.browser) {
93
- const browser = await this.browserManager.getBrowserForRequest(req, route);
94
- if (!isConnected(socket)) {
95
- this.log(`WebSocket Request has closed prior to running`);
96
- this.browserManager.complete(browser);
97
- return Promise.resolve();
98
- }
99
- if (!browser) {
100
- return writeResponse(socket, 500, `Error loading the browser.`);
101
- }
102
- try {
103
- this.verbose(`Running found WebSocket handler.`);
104
- await handler(req, socket, head, browser);
105
- }
106
- finally {
107
- this.verbose(`WebSocket Request handler has finished.`);
108
- this.browserManager.complete(browser);
109
- }
110
- return;
111
- }
112
- return handler(req, socket, head);
113
- };
114
- getTimeout(req) {
115
- const timer = req.parsed.searchParams.get('timeout');
116
- return timer ? +timer : undefined;
117
- }
118
- registerHTTPRoute(route) {
119
- this.verbose(`Registering HTTP ${route.method.toUpperCase()} ${route.path}`);
120
- route._browserManager = () => this.browserManager;
121
- const bound = route.handler.bind(route);
122
- const wrapped = this.wrapHTTPHandler(route, bound);
123
- route.handler = route.concurrency
124
- ? this.limiter.limit(wrapped, this.onQueueFullHTTP, this.onHTTPTimeout, this.getTimeout)
125
- : wrapped;
126
- return route;
127
- }
128
- registerWebSocketRoute(route) {
129
- this.verbose(`Registering WebSocket "${route.path}"`);
130
- route._browserManager = () => this.browserManager;
131
- const bound = route.handler.bind(route);
132
- const wrapped = this.wrapWebSocketHandler(route, bound);
133
- route.handler = route.concurrency
134
- ? this.limiter.limit(wrapped, this.onQueueFullWebSocket, this.onWebsocketTimeout, this.getTimeout)
135
- : wrapped;
136
- return route;
137
- }
138
34
  async start() {
139
35
  this.log(`HTTP Server is starting`);
140
36
  this.server.on('request', this.handleRequest);
@@ -156,14 +52,12 @@ export class HTTPServer {
156
52
  async stop() {
157
53
  this.log(`HTTP Server is shutting down`);
158
54
  await new Promise((r) => this.server.close(r));
159
- await Promise.all([this.tearDown(), this.browserManager.stop()]);
55
+ await Promise.all([this.tearDown(), this.router.teardown()]);
160
56
  this.log(`HTTP Server shutdown complete`);
161
57
  }
162
58
  tearDown() {
163
59
  this.log(`Tearing down all listeners and internal routes`);
164
60
  this.server && this.server.removeAllListeners();
165
- this.httpRoutes = [];
166
- this.webSocketRoutes = [];
167
61
  // @ts-ignore garbage collect this reference
168
62
  this.server = null;
169
63
  }
@@ -175,7 +69,6 @@ export class HTTPServer {
175
69
  shimLegacyRequests(req.parsed);
176
70
  if (!proceed)
177
71
  return;
178
- const staticHandler = this.httpRoutes.find((route) => route.path === HTTPManagementRoutes.static);
179
72
  if (this.config.getAllowCORS()) {
180
73
  Object.entries(this.config.getCORSHeaders()).forEach(([header, value]) => res.setHeader(header, value));
181
74
  if (req.method === 'OPTIONS') {
@@ -191,24 +84,16 @@ export class HTTPServer {
191
84
  req.body = req.parsed.searchParams.get('body');
192
85
  req.parsed.searchParams.delete('body');
193
86
  }
194
- const accepts = (req.headers['accept']?.toLowerCase() || '*/*').split(',');
195
- const contentType = req.headers['content-type']?.toLowerCase();
196
- const found = this.httpRoutes.find((r) => micromatch.isMatch(req.parsed.pathname, r.path) &&
197
- r.method === req.method?.toLocaleLowerCase() &&
198
- (accepts.some((a) => a.startsWith('*/*')) ||
199
- r.contentTypes.some((contentType) => accepts.includes(contentType))) &&
200
- ((!contentType && r.accepts.includes(contentTypes.any)) ||
201
- r.accepts.includes(contentType))) || (req.method?.toLowerCase() === 'get' ? staticHandler : null);
202
- if (!found) {
87
+ const route = await this.router.getRouteForHTTPRequest(req);
88
+ if (!route) {
203
89
  this.log(`No matching WebSocket route handler for "${req.parsed.href}"`);
204
90
  writeResponse(res, 404, 'Not Found');
205
91
  return Promise.resolve();
206
92
  }
207
- this.verbose(`Found matching HTTP route handler "${found.path}"`);
208
- if (found?.auth) {
93
+ this.verbose(`Found matching HTTP route handler "${route.path}"`);
94
+ if (route?.auth) {
209
95
  this.verbose(`Authorizing HTTP request to "${request.url}"`);
210
- const tokens = this.config.getToken();
211
- const isPermitted = isAuthorized(req, found, tokens);
96
+ const isPermitted = await this.token.isAuthorized(req, route);
212
97
  if (!isPermitted) {
213
98
  return this.onHTTPUnauthorized(req, res);
214
99
  }
@@ -217,17 +102,17 @@ export class HTTPServer {
217
102
  req.body = body;
218
103
  req.queryParams = queryParamsToObject(req.parsed.searchParams);
219
104
  if (((req.headers['content-type']?.includes(contentTypes.json) ||
220
- (found.accepts.length === 1 &&
221
- found.accepts.includes(contentTypes.json))) &&
105
+ (route.accepts.length === 1 &&
106
+ route.accepts.includes(contentTypes.json))) &&
222
107
  typeof body !== 'object') ||
223
108
  body === null) {
224
109
  writeResponse(res, 400, `Couldn't parse JSON body`);
225
110
  return Promise.resolve();
226
111
  }
227
- if (found.querySchema) {
112
+ if (route.querySchema) {
228
113
  this.verbose(`Validating route query-params with QUERY schema`);
229
114
  try {
230
- const schema = Enjoi.schema(found.querySchema);
115
+ const schema = Enjoi.schema(route.querySchema);
231
116
  const valid = schema.validate(req.queryParams, {
232
117
  abortEarly: false,
233
118
  });
@@ -246,10 +131,10 @@ export class HTTPServer {
246
131
  return Promise.resolve();
247
132
  }
248
133
  }
249
- if (found.bodySchema) {
134
+ if (route.bodySchema) {
250
135
  this.verbose(`Validating route payload with BODY schema`);
251
136
  try {
252
- const schema = Enjoi.schema(found.bodySchema);
137
+ const schema = Enjoi.schema(route.bodySchema);
253
138
  const valid = schema.validate(body, { abortEarly: false });
254
139
  if (valid.error) {
255
140
  const errorDetails = valid.error.details
@@ -266,9 +151,7 @@ export class HTTPServer {
266
151
  return Promise.resolve();
267
152
  }
268
153
  }
269
- // #wrapHTTPHandler will take care of applying the extra browser
270
- // argument for this to to work properly
271
- return found
154
+ return route
272
155
  .handler(req, res)
273
156
  .then(() => {
274
157
  this.verbose('HTTP connection complete');
@@ -289,7 +172,7 @@ export class HTTPServer {
289
172
  if (e instanceof Timeout) {
290
173
  return writeResponse(res, 408, e.message);
291
174
  }
292
- this.log(`Error handling request at "${found.path}": ${e}`);
175
+ this.log(`Error handling request at "${route.path}": ${e}`);
293
176
  return writeResponse(res, 500, e.toString());
294
177
  });
295
178
  };
@@ -301,22 +184,21 @@ export class HTTPServer {
301
184
  shimLegacyRequests(req.parsed);
302
185
  if (!proceed)
303
186
  return;
304
- const { pathname } = req.parsed;
305
187
  req.queryParams = queryParamsToObject(req.parsed.searchParams);
306
- const found = this.webSocketRoutes.find((r) => micromatch.isMatch(pathname, r.path));
307
- if (found) {
308
- this.verbose(`Found matching WebSocket route handler "${found.path}"`);
309
- if (found?.auth) {
188
+ const route = await this.router.getRouteForWebSocketRequest(req);
189
+ if (route) {
190
+ this.verbose(`Found matching WebSocket route handler "${route.path}"`);
191
+ if (route?.auth) {
310
192
  this.verbose(`Authorizing WebSocket request to "${req.parsed.href}"`);
311
- const isPermitted = isAuthorized(req, found, this.config.getToken());
193
+ const isPermitted = await this.token.isAuthorized(req, route);
312
194
  if (!isPermitted) {
313
195
  return this.onWebsocketUnauthorized(req, socket);
314
196
  }
315
197
  }
316
- if (found.querySchema) {
198
+ if (route.querySchema) {
317
199
  this.verbose(`Validating route query-params with QUERY schema`);
318
200
  try {
319
- const schema = Enjoi.schema(found.querySchema);
201
+ const schema = Enjoi.schema(route.querySchema);
320
202
  const valid = schema.validate(req.queryParams, {
321
203
  abortEarly: false,
322
204
  });
@@ -335,9 +217,7 @@ export class HTTPServer {
335
217
  return Promise.resolve();
336
218
  }
337
219
  }
338
- // #wrapWebSocketHandler will take care of applying the extra browser
339
- // argument for this to to work properly
340
- return found
220
+ return route
341
221
  .handler(req, socket, head)
342
222
  .then(() => {
343
223
  this.verbose('Websocket connection complete');
@@ -355,7 +235,7 @@ export class HTTPServer {
355
235
  if (e instanceof TooManyRequests) {
356
236
  return writeResponse(socket, 429, e.message);
357
237
  }
358
- this.log(`Error handling request at "${found.path}": ${e}\n${e.stack}`);
238
+ this.log(`Error handling request at "${route.path}": ${e}\n${e.stack}`);
359
239
  return writeResponse(socket, 500, e.message);
360
240
  });
361
241
  }
@@ -0,0 +1,6 @@
1
+ import { BrowserHTTPRoute, BrowserWebsocketRoute, Config, HTTPRoute, Request, WebSocketRoute } from '@browserless.io/browserless';
2
+ export declare class Token {
3
+ protected config: Config;
4
+ constructor(config: Config);
5
+ isAuthorized: (req: Request, route: BrowserHTTPRoute | BrowserWebsocketRoute | HTTPRoute | WebSocketRoute) => Promise<boolean>;
6
+ }
package/build/token.js ADDED
@@ -0,0 +1,21 @@
1
+ import { getTokenFromRequest, } from '@browserless.io/browserless';
2
+ export class Token {
3
+ config;
4
+ constructor(config) {
5
+ this.config = config;
6
+ }
7
+ isAuthorized = async (req, route) => {
8
+ const token = this.config.getToken();
9
+ if (token === null) {
10
+ return true;
11
+ }
12
+ if (route.auth !== true) {
13
+ return true;
14
+ }
15
+ const requestToken = getTokenFromRequest(req);
16
+ if (!requestToken) {
17
+ return false;
18
+ }
19
+ return (Array.isArray(token) ? token : [token]).includes(requestToken);
20
+ };
21
+ }
package/build/types.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  /// <reference types="debug" />
5
5
  import * as http from 'http';
6
6
  import * as stream from 'stream';
7
- import { APITags, BrowserManager, CDPChromium, Config, FileSystem, HTTPManagementRoutes, HTTPRoutes, Methods, Metrics, Monitoring, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, Request, WebsocketRoutes, contentTypes } from '@browserless.io/browserless';
7
+ import { APITags, Browserless, CDPChromium, Config, HTTPManagementRoutes, HTTPRoutes, Methods, Metrics, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, Request, WebsocketRoutes, contentTypes } from '@browserless.io/browserless';
8
8
  import { HTTPRequest, Page, ResponseForRequest, ScreenshotOptions } from 'puppeteer-core';
9
9
  export interface BeforeRequest {
10
10
  head?: Buffer;
@@ -42,43 +42,87 @@ export interface BrowserJSON {
42
42
  'V8-Version': string;
43
43
  'WebKit-Version': string;
44
44
  }
45
+ /**
46
+ * The default launch options or a function, accepting
47
+ * the request object, that produces the launch options.
48
+ */
45
49
  type defaultLaunchOptions = CDPLaunchOptions | BrowserlessLaunch | ((req: Request) => CDPLaunchOptions | BrowserlessLaunch);
46
50
  interface Route {
47
- _browserManager?: () => BrowserManager;
48
- _config?: () => Config;
49
- _debug?: () => debug.Debugger;
50
- _fileSystem?: () => FileSystem;
51
- _metrics?: () => Metrics;
52
- _monitor?: () => Monitoring;
53
51
  /**
54
- * Whether the route requires a token to access.
52
+ * A boolean, or a function that returns a boolean, on
53
+ * whether the route requires an API token to access.
55
54
  */
56
55
  auth: boolean | ((req: Request) => Promise<boolean>);
57
56
  /**
58
57
  * The schematic of the submitted BODY (typically)
59
- * an object when the route is json-based.
58
+ * an object when the route is json-based. This is generated
59
+ * automatically if your route defines a BodySchema type.
60
60
  */
61
61
  bodySchema?: unknown;
62
62
  /**
63
63
  * Whether the route should be bound by the global
64
- * concurrency limit
64
+ * concurrency limit defined in your configuration.
65
65
  */
66
66
  concurrency: boolean;
67
67
  /**
68
- * Description of the route and what it does
68
+ * Description of the route and what it does. This description
69
+ * is then used in the embedded documentation site.
69
70
  */
70
71
  description: string;
71
72
  /**
72
- * The HTTP path that this route handles
73
+ * Helper function to load the browser-manager instance. Defined
74
+ * and injected by browserless after initialization.
75
+ * @returns BrowserManager
76
+ */
77
+ getBrowserManager?: () => Browserless['browserManager'];
78
+ /**
79
+ * Helper function that loads the config module. Defined and injected by
80
+ * browserless after initialization.
81
+ * @returns Config
82
+ */
83
+ getConfig?: () => Browserless['config'];
84
+ /**
85
+ * Helper function that loads the debug module, useful
86
+ * for logging messages scoped to the routes path. Defined
87
+ * and injected by browserless after initialization.
88
+ * @returns Debug
89
+ */
90
+ getDebug?: () => debug.Debugger;
91
+ /**
92
+ * Helper function that loads the file-system module
93
+ * for interacting with file-systems. Defined and injected by
94
+ * browserless after initialization.
95
+ * @returns FileSystem
96
+ */
97
+ getFileSystem?: () => Browserless['fileSystem'];
98
+ /**
99
+ * Helper function that loads the metrics module for
100
+ * collecting and aggregating statistics. Defined and injected by
101
+ * browserless after initialization.
102
+ * @returns Metrics
103
+ */
104
+ getMetrics?: () => Browserless['metrics'];
105
+ /**
106
+ * Helper function that loads the monitoring module useful
107
+ * for monitoring system health. Defined and injected by
108
+ * browserless after initialization.
109
+ * @returns Monitor
110
+ */
111
+ getMonitoring?: () => Browserless['monitoring'];
112
+ /**
113
+ * The HTTP path that this route handles, eg '/my-route'
73
114
  */
74
115
  path: HTTPRoutes | WebsocketRoutes | HTTPManagementRoutes | string;
75
116
  /**
76
117
  * The query parameters accepted by the route, defined in
77
- * an object format.
118
+ * an object format. This is auto-generated for you if your
119
+ * route defines and exports a QuerySchema type.
78
120
  */
79
121
  querySchema?: unknown;
80
122
  /**
81
- * The structure of the routes response when successful
123
+ * The structure of the routes response when successful. This
124
+ * is auto-generated for you if your route defines a ResponseSchema
125
+ * type and exports it in your route.
82
126
  */
83
127
  responseSchema?: unknown;
84
128
  /**
@@ -87,6 +131,11 @@ interface Route {
87
131
  */
88
132
  tags: APITags[];
89
133
  }
134
+ /**
135
+ * A primitive HTTP-based route that doesn't require a
136
+ * browser in order to fulfill requests. Used by downstream HTTPRoute
137
+ * and WebSocketRoute
138
+ */
90
139
  interface BasicHTTPRoute extends Route {
91
140
  /**
92
141
  * The allowed Content-Types that this route can read and handle.
@@ -105,6 +154,10 @@ interface BasicHTTPRoute extends Route {
105
154
  */
106
155
  method: Methods;
107
156
  }
157
+ /**
158
+ * A HTTP-based route, with a handler, that can fulfill requests without
159
+ * a browser required.
160
+ */
108
161
  export interface HTTPRoute extends BasicHTTPRoute {
109
162
  browser: null;
110
163
  /**
@@ -112,6 +165,11 @@ export interface HTTPRoute extends BasicHTTPRoute {
112
165
  */
113
166
  handler: (req: Request, res: http.ServerResponse) => Promise<unknown>;
114
167
  }
168
+ /**
169
+ * A HTTP-based route, with a handler, that can fulfill requests but
170
+ * requires a browser in order to do so. Handler will then be called
171
+ * with a 3rd argument of the browser class specified.
172
+ */
115
173
  export interface BrowserHTTPRoute extends BasicHTTPRoute {
116
174
  browser: BrowserClasses;
117
175
  defaultLaunchOptions?: defaultLaunchOptions;
@@ -122,6 +180,10 @@ export interface BrowserHTTPRoute extends BasicHTTPRoute {
122
180
  handler: (req: Request, res: http.ServerResponse, browser: BrowserInstance) => Promise<unknown>;
123
181
  onNewPage?: undefined;
124
182
  }
183
+ /**
184
+ * A WebSocket-based route, with a handler, that can fulfill requests
185
+ * that do not require a browser in order to operate.
186
+ */
125
187
  export interface WebSocketRoute extends Route {
126
188
  browser: null;
127
189
  /**
@@ -129,6 +191,11 @@ export interface WebSocketRoute extends Route {
129
191
  */
130
192
  handler: (req: Request, socket: stream.Duplex, head: Buffer) => Promise<unknown>;
131
193
  }
194
+ /**
195
+ * A WebSocket-based route, with a handler, that can fulfill requests
196
+ * that need a browser. Handler is called with an additional argument of
197
+ * browser (the browser class required to run the route).
198
+ */
132
199
  export interface BrowserWebsocketRoute extends Route {
133
200
  browser: BrowserClasses;
134
201
  defaultLaunchOptions?: defaultLaunchOptions;
@@ -137,6 +204,10 @@ export interface BrowserWebsocketRoute extends Route {
137
204
  * with the prior set browser being injected.
138
205
  */
139
206
  handler(req: Request, socket: stream.Duplex, head: Buffer, browser: BrowserInstance): Promise<unknown>;
207
+ /**
208
+ * An optional function to automatically set up or handle new page
209
+ * creation. Useful for injecting behaviors or other functionality.
210
+ */
140
211
  onNewPage?: (url: URL, page: Page) => Promise<void>;
141
212
  }
142
213
  interface BrowserlessLaunch {
package/build/utils.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
- import { BrowserHTTPRoute, BrowserWebsocketRoute, CDPChromium, Config, HTTPRoute, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, Request, WaitForEventOptions, WaitForFunctionOptions, WebSocketRoute, codes, contentTypes } from '@browserless.io/browserless';
4
+ import { CDPChromium, Config, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, Request, WaitForEventOptions, WaitForFunctionOptions, codes, contentTypes } from '@browserless.io/browserless';
5
5
  import { CDPSession } from 'playwright-core';
6
6
  import { Duplex } from 'stream';
7
7
  import { Page } from 'puppeteer-core';
@@ -19,7 +19,6 @@ export declare const writeResponse: (writeable: Duplex | ServerResponse, httpCod
19
19
  export declare const jsonResponse: (response: ServerResponse, httpCode?: keyof typeof codes, json?: unknown, allowNull?: boolean) => void;
20
20
  export declare const fetchJson: (url: string, init?: RequestInit | undefined) => Promise<unknown>;
21
21
  export declare const getTokenFromRequest: (req: Request) => string | null;
22
- export declare const isAuthorized: (req: Request, route: BrowserHTTPRoute | BrowserWebsocketRoute | HTTPRoute | WebSocketRoute, token: string | string[] | null) => boolean;
23
22
  export declare const readRequestBody: (req: Request) => Promise<string>;
24
23
  export declare const safeParse: (maybeJson: string) => unknown | null;
25
24
  export declare const removeNullStringify: (json: unknown, allowNull?: boolean) => string;
package/build/utils.js CHANGED
@@ -118,19 +118,6 @@ export const getTokenFromRequest = (req) => {
118
118
  const tokenParam = req.parsed.searchParams.get('token');
119
119
  return tokenParam ?? getAuthHeaderToken(authHeader || '');
120
120
  };
121
- export const isAuthorized = (req, route, token) => {
122
- if (token === null) {
123
- return true;
124
- }
125
- if (route.auth === false) {
126
- return true;
127
- }
128
- const requestToken = getTokenFromRequest(req);
129
- if (!requestToken) {
130
- return false;
131
- }
132
- return (Array.isArray(token) ? token : [token]).includes(requestToken);
133
- };
134
121
  // NOTE, if proxying request elsewhere, you must re-stream the body again
135
122
  export const readRequestBody = async (req) => {
136
123
  return new Promise((resolve) => {
@@ -1,8 +1,8 @@
1
1
  import { Config } from '@browserless.io/browserless';
2
2
  export declare class WebHooks {
3
- private config;
3
+ protected config: Config;
4
4
  constructor(config: Config);
5
- private callURL;
5
+ protected callURL(url: string | null): Promise<void | Response> | undefined;
6
6
  callFailedHealthURL(): Promise<void | Response> | undefined;
7
7
  callQueueAlertURL(): Promise<void | Response> | undefined;
8
8
  callRejectAlertURL(): Promise<void | Response> | undefined;