@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.
- package/CHANGELOG.md +19 -1
- package/README.md +41 -3
- package/assets/debugger.png +0 -0
- package/bin/browserless.js +8 -4
- package/bin/scaffold/README.md +6 -4
- package/bin/scaffold/src/hello-world.http.ts +7 -1
- package/build/browserless.d.ts +8 -5
- package/build/browserless.js +23 -22
- package/build/browsers/chrome.cdp.d.ts +2 -2
- package/build/browsers/chrome.cdp.js +2 -2
- package/build/browsers/chrome.playwright.d.ts +2 -2
- package/build/browsers/chrome.playwright.js +2 -2
- package/build/browsers/chromium.cdp.d.ts +4 -4
- package/build/browsers/chromium.cdp.js +49 -32
- package/build/browsers/chromium.playwright.d.ts +4 -4
- package/build/browsers/chromium.playwright.js +14 -13
- package/build/browsers/firefox.playwright.d.ts +4 -4
- package/build/browsers/firefox.playwright.js +14 -13
- package/build/browsers/index.d.ts +24 -8
- package/build/browsers/index.js +20 -15
- package/build/browsers/webkit.playwright.d.ts +4 -4
- package/build/browsers/webkit.playwright.js +14 -13
- package/build/config.d.ts +3 -0
- package/build/config.js +3 -0
- package/build/exports.d.ts +1 -0
- package/build/exports.js +1 -0
- package/build/file-system.d.ts +2 -3
- package/build/file-system.js +2 -2
- package/build/index.js +7 -7
- package/build/limiter.d.ts +2 -3
- package/build/limiter.js +11 -11
- package/build/logger.d.ts +19 -0
- package/build/logger.js +43 -0
- package/build/monitoring.d.ts +2 -3
- package/build/monitoring.js +4 -4
- package/build/router.d.ts +4 -5
- package/build/router.js +31 -28
- package/build/routes/chrome/http/content.post.body.json +8 -8
- package/build/routes/chrome/http/pdf.post.body.json +9 -9
- package/build/routes/chrome/http/scrape.post.body.json +8 -8
- package/build/routes/chrome/http/screenshot.post.body.json +8 -8
- package/build/routes/chromium/http/content.post.body.json +8 -8
- package/build/routes/chromium/http/pdf.post.body.json +9 -9
- package/build/routes/chromium/http/scrape.post.body.json +8 -8
- package/build/routes/chromium/http/screenshot.post.body.json +8 -8
- package/build/routes/firefox/ws/playwright.d.ts +2 -2
- package/build/routes/firefox/ws/playwright.js +1 -1
- package/build/routes/management/http/static.get.d.ts +2 -2
- package/build/routes/management/http/static.get.js +8 -10
- package/build/routes/management/tests/management.spec.js +9 -0
- package/build/routes/webkit/ws/playwright.d.ts +2 -2
- package/build/routes/webkit/ws/playwright.js +1 -1
- package/build/sdk-utils.js +23 -10
- package/build/server.d.ts +4 -5
- package/build/server.js +38 -33
- package/build/shared/browser.ws.d.ts +2 -2
- package/build/shared/browser.ws.js +1 -1
- package/build/shared/chromium.playwright.ws.d.ts +2 -2
- package/build/shared/chromium.playwright.ws.js +1 -1
- package/build/shared/chromium.ws.d.ts +2 -2
- package/build/shared/chromium.ws.js +1 -1
- package/build/shared/content.http.d.ts +2 -2
- package/build/shared/content.http.js +4 -1
- package/build/shared/download.http.d.ts +2 -2
- package/build/shared/download.http.js +11 -12
- package/build/shared/function.http.d.ts +2 -2
- package/build/shared/function.http.js +4 -5
- package/build/shared/json-protocol.http.d.ts +3 -3
- package/build/shared/json-protocol.http.js +2 -2
- package/build/shared/json-version.http.d.ts +3 -3
- package/build/shared/json-version.http.js +2 -2
- package/build/shared/page.ws.d.ts +2 -2
- package/build/shared/page.ws.js +1 -1
- package/build/shared/pdf.http.d.ts +2 -2
- package/build/shared/pdf.http.js +6 -4
- package/build/shared/performance.http.d.ts +2 -2
- package/build/shared/performance.http.js +2 -1
- package/build/shared/scrape.http.d.ts +2 -2
- package/build/shared/scrape.http.js +4 -1
- package/build/shared/screenshot.http.d.ts +2 -2
- package/build/shared/screenshot.http.js +4 -1
- package/build/shared/utils/function/handler.d.ts +2 -3
- package/build/shared/utils/function/handler.js +8 -8
- package/build/shared/utils/performance/child.js +4 -4
- package/build/shared/utils/performance/main.d.ts +1 -1
- package/build/shared/utils/performance/main.js +5 -7
- package/build/shared/utils/performance/types.d.ts +2 -1
- package/build/types.d.ts +6 -15
- package/build/types.js +1 -10
- package/build/utils.d.ts +1 -1
- package/build/utils.js +6 -2
- package/package.json +18 -15
- package/scripts/install-debugger.js +55 -15
- package/src/browserless.ts +29 -21
- package/src/browsers/chrome.cdp.ts +2 -5
- package/src/browsers/chrome.playwright.ts +2 -5
- package/src/browsers/chromium.cdp.ts +84 -35
- package/src/browsers/chromium.playwright.ts +26 -13
- package/src/browsers/firefox.playwright.ts +28 -13
- package/src/browsers/index.ts +24 -16
- package/src/browsers/webkit.playwright.ts +28 -13
- package/src/config.ts +4 -0
- package/src/exports.ts +1 -0
- package/src/file-system.ts +2 -7
- package/src/index.ts +7 -7
- package/src/limiter.ts +13 -11
- package/src/logger.ts +52 -0
- package/src/monitoring.ts +6 -8
- package/src/router.ts +29 -27
- package/src/routes/firefox/ws/playwright.ts +2 -0
- package/src/routes/management/http/static.get.ts +13 -10
- package/src/routes/management/tests/management.spec.ts +15 -0
- package/src/routes/webkit/ws/playwright.ts +2 -0
- package/src/sdk-utils.ts +20 -2
- package/src/server.ts +47 -32
- package/src/shared/browser.ws.ts +2 -0
- package/src/shared/chromium.playwright.ws.ts +2 -0
- package/src/shared/chromium.ws.ts +2 -0
- package/src/shared/content.http.ts +6 -0
- package/src/shared/download.http.ts +14 -11
- package/src/shared/function.http.ts +5 -4
- package/src/shared/json-protocol.http.ts +8 -3
- package/src/shared/json-version.http.ts +8 -4
- package/src/shared/page.ws.ts +2 -0
- package/src/shared/pdf.http.ts +7 -3
- package/src/shared/performance.http.ts +3 -0
- package/src/shared/scrape.http.ts +6 -0
- package/src/shared/screenshot.http.ts +5 -0
- package/src/shared/utils/function/handler.ts +9 -13
- package/src/shared/utils/performance/child.ts +4 -4
- package/src/shared/utils/performance/main.ts +5 -6
- package/src/shared/utils/performance/types.ts +2 -1
- package/src/types.ts +5 -9
- package/src/utils.ts +7 -2
- package/static/docs/swagger.json +11 -11
- package/static/docs/swagger.min.json +10 -10
- package/static/function/client.js +1656 -2916
- 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 =
|
|
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.
|
|
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.
|
|
100
|
+
this.log.trace(`Response has been written, resolving`);
|
|
100
101
|
resolve(null);
|
|
101
102
|
});
|
|
102
103
|
}),
|
|
103
104
|
]);
|
|
104
105
|
} finally {
|
|
105
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
160
|
+
this.logger.trace(`Found matching HTTP route handler "${route.path}"`);
|
|
154
161
|
|
|
155
162
|
if (route?.auth) {
|
|
156
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
323
|
+
this.logger.trace(
|
|
324
|
+
`Found matching WebSocket route handler "${route.path}"`,
|
|
325
|
+
);
|
|
315
326
|
|
|
316
327
|
if (route?.auth) {
|
|
317
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
426
|
+
this.logger.info(`HTTP Server shutdown complete`);
|
|
412
427
|
}
|
|
413
428
|
|
|
414
429
|
/**
|
package/src/shared/browser.ws.ts
CHANGED
|
@@ -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
|
}
|