@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
package/src/router.ts ADDED
@@ -0,0 +1,234 @@
1
+ import {
2
+ BrowserHTTPRoute,
3
+ BrowserManager,
4
+ BrowserWebsocketRoute,
5
+ Config,
6
+ HTTPManagementRoutes,
7
+ HTTPRoute,
8
+ Limiter,
9
+ Methods,
10
+ Request,
11
+ Response,
12
+ WebSocketRoute,
13
+ contentTypes,
14
+ createLogger,
15
+ isConnected,
16
+ writeResponse,
17
+ } from '@browserless.io/browserless';
18
+ import micromatch from 'micromatch';
19
+ import stream from 'stream';
20
+
21
+ export class Router {
22
+ protected log = createLogger('router');
23
+ protected verbose = createLogger('router:verbose');
24
+ protected httpRoutes: Array<HTTPRoute | BrowserHTTPRoute> = [];
25
+ protected webSocketRoutes: Array<WebSocketRoute | BrowserWebsocketRoute> = [];
26
+
27
+ constructor(
28
+ protected config: Config,
29
+ protected browserManager: BrowserManager,
30
+ protected limiter: Limiter,
31
+ ) {}
32
+
33
+ protected getTimeout(req: Request) {
34
+ const timer = req.parsed.searchParams.get('timeout');
35
+
36
+ return timer ? +timer : undefined;
37
+ }
38
+
39
+ protected onQueueFullHTTP = (_req: Request, res: Response) => {
40
+ this.log(`Queue is full, sending 429 response`);
41
+ return writeResponse(res, 429, 'Too many requests');
42
+ };
43
+
44
+ protected onQueueFullWebSocket = (_req: Request, socket: stream.Duplex) => {
45
+ this.log(`Queue is full, sending 429 response`);
46
+ return writeResponse(socket, 429, 'Too many requests');
47
+ };
48
+
49
+ protected onHTTPTimeout = (_req: Request, res: Response) => {
50
+ this.log(`HTTP job has timedout, sending 429 response`);
51
+ return writeResponse(res, 408, 'Request has timed out');
52
+ };
53
+
54
+ protected onWebsocketTimeout = (_req: Request, socket: stream.Duplex) => {
55
+ this.log(`Websocket job has timedout, sending 429 response`);
56
+ return writeResponse(socket, 408, 'Request has timed out');
57
+ };
58
+
59
+ protected wrapHTTPHandler =
60
+ (
61
+ route: HTTPRoute | BrowserHTTPRoute,
62
+ handler: HTTPRoute['handler'] | BrowserHTTPRoute['handler'],
63
+ ) =>
64
+ async (req: Request, res: Response) => {
65
+ if (!isConnected(res)) {
66
+ this.log(`HTTP Request has closed prior to running`);
67
+ return Promise.resolve();
68
+ }
69
+
70
+ if (route.browser) {
71
+ const browser = await this.browserManager.getBrowserForRequest(
72
+ req,
73
+ route,
74
+ );
75
+
76
+ if (!isConnected(res)) {
77
+ this.log(`HTTP Request has closed prior to running`);
78
+ this.browserManager.complete(browser);
79
+ return Promise.resolve();
80
+ }
81
+
82
+ if (!browser) {
83
+ return writeResponse(res, 500, `Error loading the browser.`);
84
+ }
85
+
86
+ if (!isConnected(res)) {
87
+ this.log(`HTTP Request has closed prior to running`);
88
+ return Promise.resolve();
89
+ }
90
+
91
+ try {
92
+ this.verbose(`Running found HTTP handler.`);
93
+ return await handler(req, res, browser);
94
+ } finally {
95
+ this.verbose(`HTTP Request handler has finished.`);
96
+ this.browserManager.complete(browser);
97
+ }
98
+ }
99
+
100
+ return (handler as HTTPRoute['handler'])(req, res);
101
+ };
102
+
103
+ protected wrapWebSocketHandler =
104
+ (
105
+ route: WebSocketRoute | BrowserWebsocketRoute,
106
+ handler: WebSocketRoute['handler'] | BrowserWebsocketRoute['handler'],
107
+ ) =>
108
+ async (req: Request, socket: stream.Duplex, head: Buffer) => {
109
+ if (!isConnected(socket)) {
110
+ this.log(`WebSocket Request has closed prior to running`);
111
+ return Promise.resolve();
112
+ }
113
+
114
+ if (route.browser) {
115
+ const browser = await this.browserManager.getBrowserForRequest(
116
+ req,
117
+ route,
118
+ );
119
+
120
+ if (!isConnected(socket)) {
121
+ this.log(`WebSocket Request has closed prior to running`);
122
+ this.browserManager.complete(browser);
123
+ return Promise.resolve();
124
+ }
125
+
126
+ if (!browser) {
127
+ return writeResponse(socket, 500, `Error loading the browser.`);
128
+ }
129
+
130
+ try {
131
+ this.verbose(`Running found WebSocket handler.`);
132
+ await handler(req, socket, head, browser);
133
+ } finally {
134
+ this.verbose(`WebSocket Request handler has finished.`);
135
+ this.browserManager.complete(browser);
136
+ }
137
+ return;
138
+ }
139
+ return (handler as WebSocketRoute['handler'])(req, socket, head);
140
+ };
141
+
142
+ public registerHTTPRoute(
143
+ route: HTTPRoute | BrowserHTTPRoute,
144
+ ): HTTPRoute | BrowserHTTPRoute {
145
+ this.verbose(
146
+ `Registering HTTP ${route.method.toUpperCase()} ${route.path}`,
147
+ );
148
+
149
+ route.getBrowserManager = () => this.browserManager;
150
+
151
+ const bound = route.handler.bind(route);
152
+ const wrapped = this.wrapHTTPHandler(route, bound);
153
+
154
+ route.handler = route.concurrency
155
+ ? this.limiter.limit(
156
+ wrapped,
157
+ this.onQueueFullHTTP,
158
+ this.onHTTPTimeout,
159
+ this.getTimeout,
160
+ )
161
+ : wrapped;
162
+
163
+ this.httpRoutes.push(route);
164
+
165
+ return route;
166
+ }
167
+
168
+ public registerWebSocketRoute(
169
+ route: WebSocketRoute | BrowserWebsocketRoute,
170
+ ): WebSocketRoute | BrowserWebsocketRoute {
171
+ this.verbose(`Registering WebSocket "${route.path}"`);
172
+
173
+ route.getBrowserManager = () => this.browserManager;
174
+
175
+ const bound = route.handler.bind(route);
176
+ const wrapped = this.wrapWebSocketHandler(route, bound);
177
+
178
+ route.handler = route.concurrency
179
+ ? this.limiter.limit(
180
+ wrapped,
181
+ this.onQueueFullWebSocket,
182
+ this.onWebsocketTimeout,
183
+ this.getTimeout,
184
+ )
185
+ : wrapped;
186
+
187
+ this.webSocketRoutes.push(route);
188
+
189
+ return route;
190
+ }
191
+
192
+ public teardown() {
193
+ this.httpRoutes = [];
194
+ this.webSocketRoutes = [];
195
+
196
+ return this.browserManager.stop();
197
+ }
198
+
199
+ public getStaticHandler() {
200
+ return this.httpRoutes.find(
201
+ (route) => route.path === HTTPManagementRoutes.static,
202
+ ) as HTTPRoute;
203
+ }
204
+
205
+ public async getRouteForHTTPRequest(req: Request) {
206
+ const accepts = (req.headers['accept']?.toLowerCase() || '*/*').split(',');
207
+ const contentType = req.headers['content-type']?.toLowerCase() as
208
+ | contentTypes
209
+ | undefined;
210
+
211
+ return (
212
+ this.httpRoutes.find(
213
+ (r) =>
214
+ micromatch.isMatch(req.parsed.pathname, r.path) &&
215
+ r.method === (req.method?.toLocaleLowerCase() as Methods) &&
216
+ (accepts.some((a) => a.startsWith('*/*')) ||
217
+ r.contentTypes.some((contentType) =>
218
+ accepts.includes(contentType),
219
+ )) &&
220
+ ((!contentType && r.accepts.includes(contentTypes.any)) ||
221
+ r.accepts.includes(contentType as contentTypes)),
222
+ ) ||
223
+ (req.method?.toLowerCase() === 'get' ? this.getStaticHandler() : null)
224
+ );
225
+ }
226
+
227
+ public async getRouteForWebSocketRequest(req: Request) {
228
+ const { pathname } = req.parsed;
229
+
230
+ return this.webSocketRoutes.find((r) =>
231
+ micromatch.isMatch(pathname, r.path),
232
+ );
233
+ }
234
+ }
@@ -60,7 +60,7 @@ const route: BrowserHTTPRoute = {
60
60
  browser: BrowserInstance,
61
61
  ): Promise<void> =>
62
62
  new Promise(async (resolve, reject) => {
63
- const { _config: getConfig, _debug: getDebug } = route;
63
+ const { getConfig: getConfig, getDebug: getDebug } = route;
64
64
 
65
65
  if (!getConfig || !getDebug) {
66
66
  return reject(
@@ -55,7 +55,7 @@ const route: BrowserHTTPRoute = {
55
55
  res: ServerResponse,
56
56
  browser: BrowserInstance,
57
57
  ): Promise<void> => {
58
- const { _config: getConfig, _debug: getDebug } = route;
58
+ const { getConfig: getConfig, getDebug: getDebug } = route;
59
59
 
60
60
  if (!getConfig || !getDebug) {
61
61
  throw new ServerError(`Couldn't load configuration for request`);
@@ -46,7 +46,7 @@ const route: BrowserHTTPRoute = {
46
46
  res: ServerResponse,
47
47
  browser: BrowserInstance,
48
48
  ): Promise<void> => {
49
- const { _config: getConfig } = route;
49
+ const { getConfig: getConfig } = route;
50
50
  if (!req.body) {
51
51
  throw new BadRequest(`No JSON body present`);
52
52
  }
@@ -8,8 +8,8 @@ type codeHandler = (params: {
8
8
  }) => Promise<unknown>;
9
9
 
10
10
  export class FunctionRunner {
11
- private browser?: Browser;
12
- private page?: Page;
11
+ protected browser?: Browser;
12
+ protected page?: Page;
13
13
 
14
14
  public log = () => console.log.bind(console);
15
15
 
@@ -41,7 +41,7 @@ const route: HTTPRoute = {
41
41
  contentTypes: [contentTypes.json],
42
42
  description: `Returns a JSON payload of the current system configuration.`,
43
43
  handler: async (_req: Request, res: ServerResponse): Promise<void> => {
44
- const { _config: getConfig } = route;
44
+ const { getConfig: getConfig } = route;
45
45
 
46
46
  if (!getConfig) {
47
47
  throw new ServerError(`Couldn't locate the config object`);
@@ -21,7 +21,7 @@ const route: HTTPRoute = {
21
21
  contentTypes: [contentTypes.json],
22
22
  description: `Gets total metric details from the time the server started.`,
23
23
  handler: async (_req: Request, res: ServerResponse): Promise<void> => {
24
- const { _fileSystem, _config } = route;
24
+ const { getFileSystem: _fileSystem, getConfig: _config } = route;
25
25
 
26
26
  if (!_fileSystem || !_config) {
27
27
  throw new ServerError(`Couldn't locate the file-system or config module`);
@@ -23,7 +23,7 @@ const route: HTTPRoute = {
23
23
  contentTypes: [contentTypes.json],
24
24
  description: `Gets total metric details summed from the time the server started.`,
25
25
  handler: async (_req: Request, res: ServerResponse): Promise<void> => {
26
- const { _fileSystem, _config } = route;
26
+ const { getFileSystem: _fileSystem, getConfig: _config } = route;
27
27
 
28
28
  if (!_fileSystem || !_config) {
29
29
  throw new ServerError(`Couldn't locate the file-system or config module`);
@@ -20,14 +20,14 @@ const route: HTTPRoute = {
20
20
  concurrency: false,
21
21
  contentTypes: [contentTypes.json],
22
22
  description: `Lists all currently running sessions and relevant meta-data excluding potentially open pages.`,
23
- handler: async (req: Request, res: ServerResponse): Promise<void> => {
24
- const { _browserManager: browserManager } = route;
23
+ handler: async (_req: Request, res: ServerResponse): Promise<void> => {
24
+ const { getBrowserManager: browserManager } = route;
25
25
 
26
26
  if (!browserManager) {
27
27
  throw new BadRequest(`Couldn't load browsers running`);
28
28
  }
29
29
 
30
- const response: ResponseSchema = await browserManager().getAllSessions(req);
30
+ const response: ResponseSchema = await browserManager().getAllSessions();
31
31
 
32
32
  return jsonResponse(res, 200, response);
33
33
  },
@@ -55,7 +55,7 @@ const route: HTTPRoute = {
55
55
  contentTypes: [contentTypes.any],
56
56
  description: `Serves static files inside of this "static" directory. Content-types will vary depending on the type of file being returned.`,
57
57
  handler: async (req: Request, res: ServerResponse): Promise<unknown> => {
58
- const { _config: getConfig } = route;
58
+ const { getConfig: getConfig } = route;
59
59
  const { pathname } = req.parsed;
60
60
  const fileCache = pathMap.get(pathname);
61
61