@browserless.io/browserless 2.7.1 → 2.9.0

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 (138) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/README.md +41 -3
  3. package/assets/debugger.png +0 -0
  4. package/bin/browserless.js +8 -4
  5. package/bin/scaffold/README.md +6 -4
  6. package/bin/scaffold/src/hello-world.http.ts +7 -1
  7. package/build/browserless.d.ts +8 -5
  8. package/build/browserless.js +23 -22
  9. package/build/browsers/chrome.cdp.d.ts +2 -2
  10. package/build/browsers/chrome.cdp.js +2 -2
  11. package/build/browsers/chrome.playwright.d.ts +2 -2
  12. package/build/browsers/chrome.playwright.js +2 -2
  13. package/build/browsers/chromium.cdp.d.ts +4 -4
  14. package/build/browsers/chromium.cdp.js +49 -32
  15. package/build/browsers/chromium.playwright.d.ts +4 -4
  16. package/build/browsers/chromium.playwright.js +14 -13
  17. package/build/browsers/firefox.playwright.d.ts +4 -4
  18. package/build/browsers/firefox.playwright.js +14 -13
  19. package/build/browsers/index.d.ts +24 -8
  20. package/build/browsers/index.js +20 -15
  21. package/build/browsers/webkit.playwright.d.ts +4 -4
  22. package/build/browsers/webkit.playwright.js +14 -13
  23. package/build/config.d.ts +3 -0
  24. package/build/config.js +3 -0
  25. package/build/exports.d.ts +1 -0
  26. package/build/exports.js +1 -0
  27. package/build/file-system.d.ts +2 -3
  28. package/build/file-system.js +2 -2
  29. package/build/index.js +7 -7
  30. package/build/limiter.d.ts +2 -3
  31. package/build/limiter.js +11 -11
  32. package/build/logger.d.ts +19 -0
  33. package/build/logger.js +43 -0
  34. package/build/monitoring.d.ts +2 -3
  35. package/build/monitoring.js +4 -4
  36. package/build/router.d.ts +4 -5
  37. package/build/router.js +31 -28
  38. package/build/routes/chrome/http/content.post.body.json +8 -8
  39. package/build/routes/chrome/http/pdf.post.body.json +9 -9
  40. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  41. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  42. package/build/routes/chromium/http/content.post.body.json +8 -8
  43. package/build/routes/chromium/http/pdf.post.body.json +9 -9
  44. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  45. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  46. package/build/routes/firefox/ws/playwright.d.ts +2 -2
  47. package/build/routes/firefox/ws/playwright.js +1 -1
  48. package/build/routes/management/http/static.get.d.ts +2 -2
  49. package/build/routes/management/http/static.get.js +8 -10
  50. package/build/routes/management/tests/management.spec.js +9 -0
  51. package/build/routes/webkit/ws/playwright.d.ts +2 -2
  52. package/build/routes/webkit/ws/playwright.js +1 -1
  53. package/build/sdk-utils.js +23 -10
  54. package/build/server.d.ts +4 -5
  55. package/build/server.js +38 -33
  56. package/build/shared/browser.ws.d.ts +2 -2
  57. package/build/shared/browser.ws.js +1 -1
  58. package/build/shared/chromium.playwright.ws.d.ts +2 -2
  59. package/build/shared/chromium.playwright.ws.js +1 -1
  60. package/build/shared/chromium.ws.d.ts +2 -2
  61. package/build/shared/chromium.ws.js +1 -1
  62. package/build/shared/content.http.d.ts +2 -2
  63. package/build/shared/content.http.js +4 -1
  64. package/build/shared/download.http.d.ts +2 -2
  65. package/build/shared/download.http.js +11 -12
  66. package/build/shared/function.http.d.ts +2 -2
  67. package/build/shared/function.http.js +4 -5
  68. package/build/shared/json-protocol.http.d.ts +3 -3
  69. package/build/shared/json-protocol.http.js +2 -2
  70. package/build/shared/json-version.http.d.ts +3 -3
  71. package/build/shared/json-version.http.js +2 -2
  72. package/build/shared/page.ws.d.ts +2 -2
  73. package/build/shared/page.ws.js +1 -1
  74. package/build/shared/pdf.http.d.ts +2 -2
  75. package/build/shared/pdf.http.js +6 -4
  76. package/build/shared/performance.http.d.ts +2 -2
  77. package/build/shared/performance.http.js +2 -1
  78. package/build/shared/scrape.http.d.ts +2 -2
  79. package/build/shared/scrape.http.js +4 -1
  80. package/build/shared/screenshot.http.d.ts +2 -2
  81. package/build/shared/screenshot.http.js +4 -1
  82. package/build/shared/utils/function/handler.d.ts +2 -3
  83. package/build/shared/utils/function/handler.js +8 -8
  84. package/build/shared/utils/performance/child.js +4 -4
  85. package/build/shared/utils/performance/main.d.ts +1 -1
  86. package/build/shared/utils/performance/main.js +5 -7
  87. package/build/shared/utils/performance/types.d.ts +2 -1
  88. package/build/types.d.ts +6 -15
  89. package/build/types.js +1 -10
  90. package/build/utils.d.ts +1 -1
  91. package/build/utils.js +6 -2
  92. package/package.json +18 -15
  93. package/scripts/install-debugger.js +55 -15
  94. package/src/browserless.ts +29 -21
  95. package/src/browsers/chrome.cdp.ts +2 -5
  96. package/src/browsers/chrome.playwright.ts +2 -5
  97. package/src/browsers/chromium.cdp.ts +84 -35
  98. package/src/browsers/chromium.playwright.ts +26 -13
  99. package/src/browsers/firefox.playwright.ts +28 -13
  100. package/src/browsers/index.ts +24 -16
  101. package/src/browsers/webkit.playwright.ts +28 -13
  102. package/src/config.ts +4 -0
  103. package/src/exports.ts +1 -0
  104. package/src/file-system.ts +2 -7
  105. package/src/index.ts +7 -7
  106. package/src/limiter.ts +13 -11
  107. package/src/logger.ts +52 -0
  108. package/src/monitoring.ts +6 -8
  109. package/src/router.ts +29 -27
  110. package/src/routes/firefox/ws/playwright.ts +2 -0
  111. package/src/routes/management/http/static.get.ts +13 -10
  112. package/src/routes/management/tests/management.spec.ts +15 -0
  113. package/src/routes/webkit/ws/playwright.ts +2 -0
  114. package/src/sdk-utils.ts +20 -2
  115. package/src/server.ts +47 -32
  116. package/src/shared/browser.ws.ts +2 -0
  117. package/src/shared/chromium.playwright.ws.ts +2 -0
  118. package/src/shared/chromium.ws.ts +2 -0
  119. package/src/shared/content.http.ts +6 -0
  120. package/src/shared/download.http.ts +14 -11
  121. package/src/shared/function.http.ts +5 -4
  122. package/src/shared/json-protocol.http.ts +8 -3
  123. package/src/shared/json-version.http.ts +8 -4
  124. package/src/shared/page.ws.ts +2 -0
  125. package/src/shared/pdf.http.ts +7 -3
  126. package/src/shared/performance.http.ts +3 -0
  127. package/src/shared/scrape.http.ts +6 -0
  128. package/src/shared/screenshot.http.ts +5 -0
  129. package/src/shared/utils/function/handler.ts +9 -13
  130. package/src/shared/utils/performance/child.ts +4 -4
  131. package/src/shared/utils/performance/main.ts +5 -6
  132. package/src/shared/utils/performance/types.ts +2 -1
  133. package/src/types.ts +5 -9
  134. package/src/utils.ts +7 -2
  135. package/static/docs/swagger.json +11 -11
  136. package/static/docs/swagger.min.json +10 -10
  137. package/static/function/client.js +1656 -2916
  138. package/static/function/index.html +1656 -2916
package/src/router.ts CHANGED
@@ -6,13 +6,13 @@ import {
6
6
  HTTPManagementRoutes,
7
7
  HTTPRoute,
8
8
  Limiter,
9
+ Logger,
9
10
  Methods,
10
11
  PathTypes,
11
12
  Request,
12
13
  Response,
13
14
  WebSocketRoute,
14
15
  contentTypes,
15
- createLogger,
16
16
  isConnected,
17
17
  writeResponse,
18
18
  } from '@browserless.io/browserless';
@@ -21,8 +21,7 @@ import micromatch from 'micromatch';
21
21
  import stream from 'stream';
22
22
 
23
23
  export class Router extends EventEmitter {
24
- protected log = createLogger('router');
25
- protected verbose = createLogger('router:verbose');
24
+ protected log = new Logger('router');
26
25
  protected httpRoutes: Array<HTTPRoute | BrowserHTTPRoute> = [];
27
26
  protected webSocketRoutes: Array<WebSocketRoute | BrowserWebsocketRoute> = [];
28
27
 
@@ -30,6 +29,7 @@ export class Router extends EventEmitter {
30
29
  protected config: Config,
31
30
  protected browserManager: BrowserManager,
32
31
  protected limiter: Limiter,
32
+ protected logger: typeof Logger,
33
33
  ) {
34
34
  super();
35
35
  }
@@ -41,22 +41,22 @@ export class Router extends EventEmitter {
41
41
  }
42
42
 
43
43
  protected onQueueFullHTTP = (_req: Request, res: Response) => {
44
- this.log(`Queue is full, sending 429 response`);
44
+ this.log.warn(`Queue is full, sending 429 response`);
45
45
  return writeResponse(res, 429, 'Too many requests');
46
46
  };
47
47
 
48
48
  protected onQueueFullWebSocket = (_req: Request, socket: stream.Duplex) => {
49
- this.log(`Queue is full, sending 429 response`);
49
+ this.log.warn(`Queue is full, sending 429 response`);
50
50
  return writeResponse(socket, 429, 'Too many requests');
51
51
  };
52
52
 
53
53
  protected onHTTPTimeout = (_req: Request, res: Response) => {
54
- this.log(`HTTP job has timedout, sending 429 response`);
54
+ this.log.error(`HTTP job has timedout, sending 429 response`);
55
55
  return writeResponse(res, 408, 'Request has timed out');
56
56
  };
57
57
 
58
58
  protected onWebsocketTimeout = (_req: Request, socket: stream.Duplex) => {
59
- this.log(`Websocket job has timedout, sending 429 response`);
59
+ this.log.error(`Websocket job has timedout, sending 429 response`);
60
60
  return writeResponse(socket, 408, 'Request has timed out');
61
61
  };
62
62
 
@@ -67,18 +67,19 @@ export class Router extends EventEmitter {
67
67
  ) =>
68
68
  async (req: Request, res: Response) => {
69
69
  if (!isConnected(res)) {
70
- this.log(`HTTP Request has closed prior to running`);
70
+ this.log.warn(`HTTP Request has closed prior to running`);
71
71
  return Promise.resolve();
72
72
  }
73
-
73
+ const logger = new this.logger(route.name, req);
74
74
  if ('browser' in route && route.browser) {
75
75
  const browser = await this.browserManager.getBrowserForRequest(
76
76
  req,
77
77
  route,
78
+ logger,
78
79
  );
79
80
 
80
81
  if (!isConnected(res)) {
81
- this.log(`HTTP Request has closed prior to running`);
82
+ this.log.warn(`HTTP Request has closed prior to running`);
82
83
  this.browserManager.complete(browser);
83
84
  return Promise.resolve();
84
85
  }
@@ -88,26 +89,26 @@ export class Router extends EventEmitter {
88
89
  }
89
90
 
90
91
  try {
91
- this.verbose(`Running found HTTP handler.`);
92
+ this.log.trace(`Running found HTTP handler.`);
92
93
  return await Promise.race([
93
- handler(req, res, browser),
94
+ handler(req, res, logger, browser),
94
95
  new Promise((resolve, reject) => {
95
96
  res.once('close', () => {
96
97
  if (!res.writableEnded) {
97
98
  reject(new Error(`Request closed prior to writing results`));
98
99
  }
99
- this.verbose(`Response has been written, resolving`);
100
+ this.log.trace(`Response has been written, resolving`);
100
101
  resolve(null);
101
102
  });
102
103
  }),
103
104
  ]);
104
105
  } finally {
105
- this.verbose(`HTTP Request handler has finished.`);
106
+ this.log.trace(`HTTP Request handler has finished.`);
106
107
  this.browserManager.complete(browser);
107
108
  }
108
109
  }
109
110
 
110
- return (handler as HTTPRoute['handler'])(req, res);
111
+ return (handler as HTTPRoute['handler'])(req, res, logger);
111
112
  };
112
113
 
113
114
  protected wrapWebSocketHandler =
@@ -117,18 +118,19 @@ export class Router extends EventEmitter {
117
118
  ) =>
118
119
  async (req: Request, socket: stream.Duplex, head: Buffer) => {
119
120
  if (!isConnected(socket)) {
120
- this.log(`WebSocket Request has closed prior to running`);
121
+ this.log.warn(`WebSocket Request has closed prior to running`);
121
122
  return Promise.resolve();
122
123
  }
123
-
124
+ const logger = new this.logger(route.name, req);
124
125
  if ('browser' in route && route.browser) {
125
126
  const browser = await this.browserManager.getBrowserForRequest(
126
127
  req,
127
128
  route,
129
+ logger,
128
130
  );
129
131
 
130
132
  if (!isConnected(socket)) {
131
- this.log(`WebSocket Request has closed prior to running`);
133
+ this.log.warn(`WebSocket Request has closed prior to running`);
132
134
  this.browserManager.complete(browser);
133
135
  return Promise.resolve();
134
136
  }
@@ -138,21 +140,21 @@ export class Router extends EventEmitter {
138
140
  }
139
141
 
140
142
  try {
141
- this.verbose(`Running found WebSocket handler.`);
142
- await handler(req, socket, head, browser);
143
+ this.log.trace(`Running found WebSocket handler.`);
144
+ await handler(req, socket, head, logger, browser);
143
145
  } finally {
144
- this.verbose(`WebSocket Request handler has finished.`);
146
+ this.log.trace(`WebSocket Request handler has finished.`);
145
147
  this.browserManager.complete(browser);
146
148
  }
147
149
  return;
148
150
  }
149
- return (handler as WebSocketRoute['handler'])(req, socket, head);
151
+ return (handler as WebSocketRoute['handler'])(req, socket, head, logger);
150
152
  };
151
153
 
152
154
  public registerHTTPRoute(
153
155
  route: HTTPRoute | BrowserHTTPRoute,
154
156
  ): HTTPRoute | BrowserHTTPRoute {
155
- this.verbose(
157
+ this.log.trace(
156
158
  `Registering HTTP ${route.method.toUpperCase()} ${route.path}`,
157
159
  );
158
160
 
@@ -174,7 +176,7 @@ export class Router extends EventEmitter {
174
176
  );
175
177
 
176
178
  if (duplicatePaths.length) {
177
- this.log(`Found duplicate routes: ${duplicatePaths.join(', ')}`);
179
+ this.log.warn(`Found duplicate routes: ${duplicatePaths.join(', ')}`);
178
180
  }
179
181
  this.httpRoutes.push(route);
180
182
 
@@ -184,7 +186,7 @@ export class Router extends EventEmitter {
184
186
  public registerWebSocketRoute(
185
187
  route: WebSocketRoute | BrowserWebsocketRoute,
186
188
  ): WebSocketRoute | BrowserWebsocketRoute {
187
- this.verbose(`Registering WebSocket "${route.path}"`);
189
+ this.log.trace(`Registering WebSocket "${route.path}"`);
188
190
 
189
191
  const bound = route.handler.bind(route);
190
192
  const wrapped = this.wrapWebSocketHandler(route, bound);
@@ -204,7 +206,7 @@ export class Router extends EventEmitter {
204
206
  );
205
207
 
206
208
  if (duplicatePaths.length) {
207
- this.log(`Found duplicate routes: ${duplicatePaths.join(', ')}`);
209
+ this.log.warn(`Found duplicate routes: ${duplicatePaths.join(', ')}`);
208
210
  }
209
211
  this.webSocketRoutes.push(route);
210
212
  return route;
@@ -230,7 +232,7 @@ export class Router extends EventEmitter {
230
232
  micromatch.isMatch(req.parsed.pathname, p),
231
233
  ) &&
232
234
  r.method === (req.method?.toLocaleLowerCase() as Methods) &&
233
- (accepts.some((a) => a.startsWith('*/*')) ||
235
+ (accepts.some((a) => a.includes('*/*')) ||
234
236
  r.contentTypes.some((contentType) =>
235
237
  accepts.includes(contentType),
236
238
  )) &&
@@ -5,6 +5,7 @@ import {
5
5
  BrowserWebsocketRoute,
6
6
  BrowserlessRoutes,
7
7
  FirefoxPlaywright,
8
+ Logger,
8
9
  Request,
9
10
  SystemQueryParameters,
10
11
  WebsocketRoutes,
@@ -29,6 +30,7 @@ export default class FirefoxPlaywrightWebSocketRoute extends BrowserWebsocketRou
29
30
  req: Request,
30
31
  socket: Duplex,
31
32
  head: Buffer,
33
+ _logger: Logger,
32
34
  browser: FirefoxPlaywright,
33
35
  ): Promise<void> => {
34
36
  const isPlaywright = req.headers['user-agent']
@@ -3,6 +3,7 @@ import {
3
3
  BrowserlessRoutes,
4
4
  HTTPManagementRoutes,
5
5
  HTTPRoute,
6
+ Logger,
6
7
  Methods,
7
8
  NotFound,
8
9
  Request,
@@ -23,21 +24,21 @@ const pathMap: Map<
23
24
  > = new Map();
24
25
 
25
26
  const streamFile = (
26
- debug: debug.Debugger,
27
+ logger: Logger,
27
28
  res: ServerResponse,
28
29
  file: string,
29
30
  contentType?: string,
30
31
  ) =>
31
32
  new Promise((resolve, reject) => {
32
33
  if (contentType) {
33
- debug(`Setting content-type ${contentType}`);
34
+ logger.debug(`Setting content-type ${contentType}`);
34
35
  res.setHeader('Content-Type', contentType);
35
36
  }
36
37
 
37
38
  return createReadStream(file)
38
39
  .on('error', (error) => {
39
40
  if (error) {
40
- debug(`Error finding file ${file}, sending 404`);
41
+ logger.error(`Error finding file ${file}, sending 404`);
41
42
  pathMap.delete(file);
42
43
  return reject(
43
44
  new NotFound(`Request for file "${file}" was not found`),
@@ -59,14 +60,16 @@ export default class StaticGetRoute extends HTTPRoute {
59
60
  method = Methods.get;
60
61
  path = HTTPManagementRoutes.static;
61
62
  tags = [APITags.management];
62
- handler = async (req: Request, res: ServerResponse): Promise<unknown> => {
63
+ handler = async (
64
+ req: Request,
65
+ res: ServerResponse,
66
+ logger: Logger,
67
+ ): Promise<unknown> => {
63
68
  const { pathname } = req.parsed;
64
69
  const fileCache = pathMap.get(pathname);
65
- const debug = this.debug();
66
- const verbose = debug.extend('verbose');
67
70
 
68
71
  if (fileCache) {
69
- return streamFile(verbose, res, fileCache.path, fileCache.contentType);
72
+ return streamFile(logger, res, fileCache.path, fileCache.contentType);
70
73
  }
71
74
 
72
75
  const config = this.config();
@@ -93,13 +96,13 @@ export default class StaticGetRoute extends HTTPRoute {
93
96
  }
94
97
 
95
98
  if (foundFilePaths.length > 1) {
96
- debug(
99
+ logger.warn(
97
100
  `Multiple files found for request to "${pathname}". Only the first file is served, so please name your files uniquely.`,
98
101
  );
99
102
  }
100
103
 
101
104
  const [foundFilePath] = foundFilePaths;
102
- verbose(`Found new file "${foundFilePath}", caching path and serving`);
105
+ logger.info(`Found new file "${foundFilePath}", caching path and serving`);
103
106
 
104
107
  const contentType = mimeTypes.get(path.extname(foundFilePath));
105
108
 
@@ -113,6 +116,6 @@ export default class StaticGetRoute extends HTTPRoute {
113
116
  path: foundFilePath,
114
117
  });
115
118
 
116
- return streamFile(verbose, res, foundFilePath, contentType);
119
+ return streamFile(logger, res, foundFilePath, contentType);
117
120
  };
118
121
  }
@@ -81,4 +81,19 @@ describe('Management APIs', function () {
81
81
  },
82
82
  );
83
83
  });
84
+
85
+ it('allows HEAD requests to /active', async () => {
86
+ await start();
87
+
88
+ await fetch('http://localhost:3000/active?token=6R0W53R135510', {
89
+ method: 'HEAD',
90
+ }).then(
91
+ async (res) => {
92
+ expect(res.headers.get('content-type')).to.equal(
93
+ 'text/plain; charset=UTF-8',
94
+ );
95
+ expect(res.status).to.equal(204);
96
+ },
97
+ );
98
+ });
84
99
  });
@@ -4,6 +4,7 @@ import {
4
4
  BrowserServerOptions,
5
5
  BrowserWebsocketRoute,
6
6
  BrowserlessRoutes,
7
+ Logger,
7
8
  Request,
8
9
  SystemQueryParameters,
9
10
  WebkitPlaywright,
@@ -27,6 +28,7 @@ export default class WebKitPlaywrightWebSocketRoute extends BrowserWebsocketRout
27
28
  req: Request,
28
29
  socket: Duplex,
29
30
  head: Buffer,
31
+ _logger: Logger,
30
32
  browser: WebkitPlaywright,
31
33
  ): Promise<void> => {
32
34
  const isPlaywright = req.headers['user-agent']
package/src/sdk-utils.ts CHANGED
@@ -83,8 +83,8 @@ export const prompt = async (question: string) => {
83
83
 
84
84
  export const installDependencies = async (
85
85
  workingDirectory: string,
86
- ): Promise<void> =>
87
- new Promise((resolve, reject) => {
86
+ ): Promise<void> => {
87
+ await new Promise<void>((resolve, reject) => {
88
88
  spawn('npm', ['i'], {
89
89
  cwd: workingDirectory,
90
90
  stdio: 'inherit',
@@ -97,6 +97,24 @@ export const installDependencies = async (
97
97
  );
98
98
  });
99
99
  });
100
+ await new Promise<void>((resolve, reject) => {
101
+ spawn(
102
+ 'npx',
103
+ 'playwright-core install --with-deps chromium firefox webkit'.split(' '),
104
+ {
105
+ cwd: workingDirectory,
106
+ stdio: 'inherit',
107
+ },
108
+ ).once('close', (code) => {
109
+ if (code === 0) {
110
+ return resolve();
111
+ }
112
+ return reject(
113
+ `Error when installing dependencies, see output for more details`,
114
+ );
115
+ });
116
+ });
117
+ };
100
118
 
101
119
  export const buildDockerImage = async (
102
120
  cmd: string,
package/src/server.ts CHANGED
@@ -2,6 +2,7 @@ import * as http from 'http';
2
2
  import * as stream from 'stream';
3
3
  import {
4
4
  BadRequest,
5
+ Logger as BlessLogger,
5
6
  Config,
6
7
  HTTPRoute,
7
8
  Hooks,
@@ -17,7 +18,6 @@ import {
17
18
  WebSocketRoute,
18
19
  contentTypes,
19
20
  convertPathToURL,
20
- createLogger,
21
21
  queryParamsToObject,
22
22
  readBody,
23
23
  shimLegacyRequests,
@@ -40,8 +40,7 @@ export class HTTPServer extends EventEmitter {
40
40
  protected server: http.Server = http.createServer();
41
41
  protected port: number;
42
42
  protected host?: string;
43
- protected log = createLogger('server');
44
- protected verbose = createLogger('server:verbose');
43
+ protected logger = new BlessLogger('server');
45
44
 
46
45
  constructor(
47
46
  protected config: Config,
@@ -49,12 +48,13 @@ export class HTTPServer extends EventEmitter {
49
48
  protected token: Token,
50
49
  protected router: Router,
51
50
  protected hooks: Hooks,
51
+ protected Logger: typeof BlessLogger,
52
52
  ) {
53
53
  super();
54
54
  this.host = config.getHost();
55
55
  this.port = config.getPort();
56
56
 
57
- this.log(
57
+ this.logger.info(
58
58
  `Server instantiated with host "${this.host}" on port "${
59
59
  this.port
60
60
  }" using token "${this.config.getToken()}"`,
@@ -62,7 +62,9 @@ export class HTTPServer extends EventEmitter {
62
62
  }
63
63
 
64
64
  protected onHTTPUnauthorized = (_req: Request, res: Response) => {
65
- this.log(`HTTP request is not properly authorized, responding with 401`);
65
+ this.logger.error(
66
+ `HTTP request is not properly authorized, responding with 401`,
67
+ );
66
68
  this.metrics.addUnauthorized();
67
69
  return writeResponse(res, 401, 'Bad or missing authentication.');
68
70
  };
@@ -71,7 +73,7 @@ export class HTTPServer extends EventEmitter {
71
73
  _req: Request,
72
74
  socket: stream.Duplex,
73
75
  ) => {
74
- this.log(
76
+ this.logger.error(
75
77
  `Websocket request is not properly authorized, responding with 401`,
76
78
  );
77
79
  this.metrics.addUnauthorized();
@@ -79,7 +81,7 @@ export class HTTPServer extends EventEmitter {
79
81
  };
80
82
 
81
83
  public async start(): Promise<void> {
82
- this.log(`HTTP Server is starting`);
84
+ this.logger.info(`HTTP Server is starting`);
83
85
 
84
86
  this.server.on('request', this.handleRequest);
85
87
  this.server.on('upgrade', this.handleWebSocket);
@@ -96,7 +98,7 @@ export class HTTPServer extends EventEmitter {
96
98
  },
97
99
  undefined,
98
100
  () => {
99
- this.log(listenMessage);
101
+ this.logger.info(listenMessage);
100
102
  r(undefined);
101
103
  },
102
104
  );
@@ -107,7 +109,7 @@ export class HTTPServer extends EventEmitter {
107
109
  request: http.IncomingMessage,
108
110
  res: http.ServerResponse,
109
111
  ) => {
110
- this.verbose(
112
+ this.logger.trace(
111
113
  `Handling inbound HTTP request on "${request.method}: ${request.url}"`,
112
114
  );
113
115
 
@@ -129,6 +131,11 @@ export class HTTPServer extends EventEmitter {
129
131
  }
130
132
  }
131
133
 
134
+ if (req.method?.toLowerCase() === 'head') {
135
+ this.logger.debug(`Inbound HEAD request, setting to GET`);
136
+ req.method = 'GET';
137
+ }
138
+
132
139
  if (
133
140
  this.config.getAllowGetCalls() &&
134
141
  req.method === 'GET' &&
@@ -143,17 +150,17 @@ export class HTTPServer extends EventEmitter {
143
150
  const route = await this.router.getRouteForHTTPRequest(req);
144
151
 
145
152
  if (!route) {
146
- this.log(
153
+ this.logger.error(
147
154
  `No matching HTTP route handler for "${req.method}: ${req.parsed.href}"`,
148
155
  );
149
156
  writeResponse(res, 404, 'Not Found');
150
157
  return Promise.resolve();
151
158
  }
152
159
 
153
- this.verbose(`Found matching HTTP route handler "${route.path}"`);
160
+ this.logger.trace(`Found matching HTTP route handler "${route.path}"`);
154
161
 
155
162
  if (route?.auth) {
156
- this.verbose(`Authorizing HTTP request to "${request.url}"`);
163
+ this.logger.trace(`Authorizing HTTP request to "${request.url}"`);
157
164
  const isPermitted = await this.token.isAuthorized(req, route);
158
165
 
159
166
  if (!isPermitted) {
@@ -177,7 +184,7 @@ export class HTTPServer extends EventEmitter {
177
184
  }
178
185
 
179
186
  if (route.querySchema) {
180
- this.verbose(`Validating route query-params with QUERY schema`);
187
+ this.logger.trace(`Validating route query-params with QUERY schema`);
181
188
  try {
182
189
  const schema = Enjoi.schema(route.querySchema);
183
190
  const valid = schema.validate(req.queryParams, {
@@ -197,7 +204,7 @@ export class HTTPServer extends EventEmitter {
197
204
  )
198
205
  .join('\n');
199
206
 
200
- this.log(
207
+ this.logger.error(
201
208
  `HTTP query-params contain errors sending 400:${errorDetails}`,
202
209
  );
203
210
 
@@ -210,7 +217,7 @@ export class HTTPServer extends EventEmitter {
210
217
  return Promise.resolve();
211
218
  }
212
219
  } catch (e) {
213
- this.log(`Error parsing body schema`, e);
220
+ this.logger.error(`Error parsing body schema`, e);
214
221
  writeResponse(
215
222
  res,
216
223
  500,
@@ -222,7 +229,7 @@ export class HTTPServer extends EventEmitter {
222
229
  }
223
230
 
224
231
  if (route.bodySchema) {
225
- this.verbose(`Validating route payload with BODY schema`);
232
+ this.logger.trace(`Validating route payload with BODY schema`);
226
233
  try {
227
234
  const schema = Enjoi.schema(route.bodySchema);
228
235
  const valid = schema.validate(body, { abortEarly: false });
@@ -240,7 +247,9 @@ export class HTTPServer extends EventEmitter {
240
247
  )
241
248
  .join('\n');
242
249
 
243
- this.log(`HTTP body contain errors sending 400:${errorDetails}`);
250
+ this.logger.error(
251
+ `HTTP body contain errors sending 400:${errorDetails}`,
252
+ );
244
253
 
245
254
  writeResponse(
246
255
  res,
@@ -251,7 +260,7 @@ export class HTTPServer extends EventEmitter {
251
260
  return Promise.resolve();
252
261
  }
253
262
  } catch (e) {
254
- this.log(`Error parsing body schema`, e);
263
+ this.logger.error(`Error parsing body schema`, e);
255
264
  writeResponse(
256
265
  res,
257
266
  500,
@@ -263,9 +272,9 @@ export class HTTPServer extends EventEmitter {
263
272
  }
264
273
 
265
274
  return (route as HTTPRoute)
266
- .handler(req, res)
275
+ .handler(req, res, new this.Logger(route.name, req))
267
276
  .then(() => {
268
- this.verbose('HTTP connection complete');
277
+ this.logger.trace('HTTP connection complete');
269
278
  })
270
279
  .catch((e) => {
271
280
  if (e instanceof BadRequest) {
@@ -297,7 +306,7 @@ export class HTTPServer extends EventEmitter {
297
306
  socket: stream.Duplex,
298
307
  head: Buffer,
299
308
  ) => {
300
- this.verbose(`Handling inbound WebSocket request on "${request.url}"`);
309
+ this.logger.trace(`Handling inbound WebSocket request on "${request.url}"`);
301
310
 
302
311
  const req = request as Request;
303
312
  const proceed = await this.hooks.before({ head, req, socket });
@@ -311,10 +320,14 @@ export class HTTPServer extends EventEmitter {
311
320
  const route = await this.router.getRouteForWebSocketRequest(req);
312
321
 
313
322
  if (route) {
314
- this.verbose(`Found matching WebSocket route handler "${route.path}"`);
323
+ this.logger.trace(
324
+ `Found matching WebSocket route handler "${route.path}"`,
325
+ );
315
326
 
316
327
  if (route?.auth) {
317
- this.verbose(`Authorizing WebSocket request to "${req.parsed.href}"`);
328
+ this.logger.trace(
329
+ `Authorizing WebSocket request to "${req.parsed.href}"`,
330
+ );
318
331
  const isPermitted = await this.token.isAuthorized(req, route);
319
332
 
320
333
  if (!isPermitted) {
@@ -323,7 +336,7 @@ export class HTTPServer extends EventEmitter {
323
336
  }
324
337
 
325
338
  if (route.querySchema) {
326
- this.verbose(`Validating route query-params with QUERY schema`);
339
+ this.logger.trace(`Validating route query-params with QUERY schema`);
327
340
  try {
328
341
  const schema = Enjoi.schema(route.querySchema);
329
342
  const valid = schema.validate(req.queryParams, {
@@ -343,7 +356,7 @@ export class HTTPServer extends EventEmitter {
343
356
  )
344
357
  .join('\n');
345
358
 
346
- this.log(
359
+ this.logger.error(
347
360
  `WebSocket query-params contain errors sending 400:${errorDetails}`,
348
361
  );
349
362
 
@@ -356,7 +369,7 @@ export class HTTPServer extends EventEmitter {
356
369
  return Promise.resolve();
357
370
  }
358
371
  } catch (e) {
359
- this.log(`Error parsing query-params schema`, e);
372
+ this.logger.error(`Error parsing query-params schema`, e);
360
373
  writeResponse(
361
374
  socket,
362
375
  500,
@@ -368,9 +381,9 @@ export class HTTPServer extends EventEmitter {
368
381
  }
369
382
 
370
383
  return (route as WebSocketRoute)
371
- .handler(req, socket, head)
384
+ .handler(req, socket, head, new this.Logger(route.name, req))
372
385
  .then(() => {
373
- this.verbose('Websocket connection complete');
386
+ this.logger.trace('Websocket connection complete');
374
387
  })
375
388
  .catch((e) => {
376
389
  if (e instanceof BadRequest) {
@@ -389,7 +402,7 @@ export class HTTPServer extends EventEmitter {
389
402
  return writeResponse(socket, 429, e.message);
390
403
  }
391
404
 
392
- this.log(
405
+ this.logger.error(
393
406
  `Error handling request at "${route.path}": ${e}\n${e.stack}`,
394
407
  );
395
408
 
@@ -397,18 +410,20 @@ export class HTTPServer extends EventEmitter {
397
410
  });
398
411
  }
399
412
 
400
- this.log(`No matching WebSocket route handler for "${req.parsed.href}"`);
413
+ this.logger.error(
414
+ `No matching WebSocket route handler for "${req.parsed.href}"`,
415
+ );
401
416
  return writeResponse(socket, 404, 'Not Found');
402
417
  };
403
418
 
404
419
  public async shutdown(): Promise<void> {
405
- this.log(`HTTP Server is shutting down`);
420
+ this.logger.info(`HTTP Server is shutting down`);
406
421
  await new Promise((r) => this.server.close(r));
407
422
  this.server && this.server.removeAllListeners();
408
423
 
409
424
  // @ts-ignore garbage collect this reference
410
425
  this.server = null;
411
- this.log(`HTTP Server shutdown complete`);
426
+ this.logger.info(`HTTP Server shutdown complete`);
412
427
  }
413
428
 
414
429
  /**
@@ -4,6 +4,7 @@ import {
4
4
  BrowserlessRoutes,
5
5
  CDPLaunchOptions,
6
6
  ChromiumCDP,
7
+ Logger,
7
8
  Request,
8
9
  SystemQueryParameters,
9
10
  WebsocketRoutes,
@@ -31,6 +32,7 @@ export default class ChromiumBrowserWebSocketRoute extends BrowserWebsocketRoute
31
32
  req: Request,
32
33
  socket: Duplex,
33
34
  head: Buffer,
35
+ _logger: Logger,
34
36
  browser: ChromiumCDP,
35
37
  ): Promise<void> => browser.proxyWebSocket(req, socket, head);
36
38
  }
@@ -5,6 +5,7 @@ import {
5
5
  BrowserWebsocketRoute,
6
6
  BrowserlessRoutes,
7
7
  ChromiumPlaywright,
8
+ Logger,
8
9
  Request,
9
10
  SystemQueryParameters,
10
11
  WebsocketRoutes,
@@ -30,6 +31,7 @@ export default class ChromiumPlaywrightWebSocketRoute extends BrowserWebsocketRo
30
31
  req: Request,
31
32
  socket: Duplex,
32
33
  head: Buffer,
34
+ _logger: Logger,
33
35
  browser: ChromiumPlaywright,
34
36
  ): Promise<void> => {
35
37
  const isPlaywright = req.headers['user-agent']
@@ -4,6 +4,7 @@ import {
4
4
  BrowserlessRoutes,
5
5
  CDPLaunchOptions,
6
6
  ChromiumCDP,
7
+ Logger,
7
8
  Request,
8
9
  SystemQueryParameters,
9
10
  WebsocketRoutes,
@@ -26,6 +27,7 @@ export default class ChromiumCDPWebSocketRoute extends BrowserWebsocketRoute {
26
27
  req: Request,
27
28
  socket: Duplex,
28
29
  head: Buffer,
30
+ _logger: Logger,
29
31
  browser: ChromiumCDP,
30
32
  ): Promise<void> => browser.proxyWebSocket(req, socket, head);
31
33
  }