@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BLESS_PAGE_IDENTIFIER, ServerError,
|
|
1
|
+
import { BLESS_PAGE_IDENTIFIER, ServerError, noop, once, } from '@browserless.io/browserless';
|
|
2
2
|
import puppeteer from 'puppeteer-core';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
4
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
@@ -17,15 +17,16 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
17
17
|
browser = null;
|
|
18
18
|
browserWSEndpoint = null;
|
|
19
19
|
port;
|
|
20
|
-
|
|
20
|
+
logger;
|
|
21
21
|
proxy = httpProxy.createProxyServer();
|
|
22
22
|
executablePath = playwright.chromium.executablePath();
|
|
23
|
-
constructor({ blockAds, config, userDataDir, }) {
|
|
23
|
+
constructor({ blockAds, config, userDataDir, logger, }) {
|
|
24
24
|
super();
|
|
25
25
|
this.userDataDir = userDataDir;
|
|
26
26
|
this.config = config;
|
|
27
27
|
this.blockAds = blockAds;
|
|
28
|
-
this.
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
this.logger.info(`Starting new ${this.constructor.name} instance`);
|
|
29
30
|
}
|
|
30
31
|
cleanListeners() {
|
|
31
32
|
this.browser?.removeAllListeners();
|
|
@@ -38,27 +39,44 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
38
39
|
onTargetCreated = async (target) => {
|
|
39
40
|
if (target.type() === 'page') {
|
|
40
41
|
const page = await target.page().catch((e) => {
|
|
41
|
-
this.
|
|
42
|
+
this.logger.error(`Error in ${this.constructor.name} new page ${e}`);
|
|
42
43
|
return null;
|
|
43
44
|
});
|
|
44
45
|
if (page) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
46
|
+
this.logger.trace(`Setting up file:// protocol request rejection`);
|
|
47
|
+
page.on('error', (err) => {
|
|
48
|
+
this.logger.error(err);
|
|
49
|
+
});
|
|
50
|
+
page.on('pageerror', (err) => {
|
|
51
|
+
this.logger.warn(err);
|
|
52
|
+
});
|
|
53
|
+
page.on('framenavigated', (frame) => {
|
|
54
|
+
this.logger.trace(`Navigation to ${frame.url()}`);
|
|
55
|
+
});
|
|
56
|
+
page.on('console', (message) => {
|
|
57
|
+
this.logger.trace(`${message.type()}: ${message.text()}`);
|
|
58
|
+
});
|
|
59
|
+
page.on('requestfailed', (req) => {
|
|
60
|
+
this.logger.warn(`"${req.failure()?.errorText}": ${req.url()}`);
|
|
61
|
+
});
|
|
62
|
+
page.on('request', async (request) => {
|
|
63
|
+
this.logger.trace(`${request.method()}: ${request.url()}`);
|
|
64
|
+
if (!this.config.getAllowFileProtocol() &&
|
|
65
|
+
request.url().startsWith('file://')) {
|
|
66
|
+
this.logger.error(`File protocol request found in request to ${this.constructor.name}, terminating`);
|
|
67
|
+
page.close().catch(noop);
|
|
68
|
+
this.close();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
page.on('response', async (response) => {
|
|
72
|
+
this.logger.trace(`${response.status()}: ${response.url()}`);
|
|
73
|
+
if (!this.config.getAllowFileProtocol() &&
|
|
74
|
+
response.url().startsWith('file://')) {
|
|
75
|
+
this.logger.error(`File protocol request found in response to ${this.constructor.name}, terminating`);
|
|
76
|
+
page.close().catch(noop);
|
|
77
|
+
this.close();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
62
80
|
this.emit('newPage', page);
|
|
63
81
|
}
|
|
64
82
|
}
|
|
@@ -66,13 +84,13 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
66
84
|
isRunning = () => this.running;
|
|
67
85
|
newPage = async () => {
|
|
68
86
|
if (!this.browser) {
|
|
69
|
-
throw new ServerError(
|
|
87
|
+
throw new ServerError(`${this.constructor.name} hasn't been launched yet!`);
|
|
70
88
|
}
|
|
71
89
|
return this.browser.newPage();
|
|
72
90
|
};
|
|
73
91
|
close = async () => {
|
|
74
92
|
if (this.browser) {
|
|
75
|
-
this.
|
|
93
|
+
this.logger.info(`Closing ${this.constructor.name} process and all listeners`);
|
|
76
94
|
this.emit('close');
|
|
77
95
|
this.cleanListeners();
|
|
78
96
|
this.browser.removeAllListeners();
|
|
@@ -86,7 +104,7 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
86
104
|
process = () => this.browser?.process() || null;
|
|
87
105
|
launch = async (options = {}) => {
|
|
88
106
|
this.port = await getPort();
|
|
89
|
-
this.
|
|
107
|
+
this.logger.info(`${this.constructor.name} got open port ${this.port}`);
|
|
90
108
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
91
109
|
const finalOptions = {
|
|
92
110
|
...options,
|
|
@@ -107,13 +125,12 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
107
125
|
const launch = options.stealth
|
|
108
126
|
? puppeteerStealth.launch.bind(puppeteerStealth)
|
|
109
127
|
: puppeteer.launch.bind(puppeteer);
|
|
110
|
-
this.
|
|
111
|
-
// @ts-ignore mis-matched types from stealth...
|
|
128
|
+
this.logger.info(finalOptions, `Launching ${this.constructor.name} Handler`);
|
|
112
129
|
this.browser = (await launch(finalOptions));
|
|
113
130
|
this.browser.on('targetcreated', this.onTargetCreated);
|
|
114
131
|
this.running = true;
|
|
115
132
|
this.browserWSEndpoint = this.browser.wsEndpoint();
|
|
116
|
-
this.
|
|
133
|
+
this.logger.info(`${this.constructor.name} is running on ${this.browserWSEndpoint}`);
|
|
117
134
|
return this.browser;
|
|
118
135
|
};
|
|
119
136
|
wsEndpoint = () => this.browserWSEndpoint;
|
|
@@ -134,7 +151,7 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
134
151
|
throw new ServerError(`No browserWSEndpoint found, did you launch first?`);
|
|
135
152
|
}
|
|
136
153
|
socket.once('close', resolve);
|
|
137
|
-
this.
|
|
154
|
+
this.logger.info(`Proxying ${req.parsed.href} to ${this.constructor.name}`);
|
|
138
155
|
const shouldMakePage = req.parsed.pathname.includes(BLESS_PAGE_IDENTIFIER);
|
|
139
156
|
const page = shouldMakePage ? await this.browser.newPage() : null;
|
|
140
157
|
const pathname = page
|
|
@@ -148,7 +165,7 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
148
165
|
changeOrigin: true,
|
|
149
166
|
target,
|
|
150
167
|
}, (error) => {
|
|
151
|
-
this.
|
|
168
|
+
this.logger.error(`Error proxying session to ${this.constructor.name}: ${error}`);
|
|
152
169
|
this.close();
|
|
153
170
|
return reject(error);
|
|
154
171
|
});
|
|
@@ -166,7 +183,7 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
166
183
|
this.browser?.once('close', close);
|
|
167
184
|
this.browser?.process()?.once('close', close);
|
|
168
185
|
socket.once('close', close);
|
|
169
|
-
this.
|
|
186
|
+
this.logger.info(`Proxying ${req.parsed.href} to ${this.constructor.name} ${this.browserWSEndpoint}`);
|
|
170
187
|
req.url = '';
|
|
171
188
|
// Delete headers known to cause issues
|
|
172
189
|
delete req.headers.origin;
|
|
@@ -174,7 +191,7 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
174
191
|
changeOrigin: true,
|
|
175
192
|
target: this.browserWSEndpoint,
|
|
176
193
|
}, (error) => {
|
|
177
|
-
this.
|
|
194
|
+
this.logger.error(`Error proxying session to ${this.constructor.name}: ${error}`);
|
|
178
195
|
this.close();
|
|
179
196
|
return reject(error);
|
|
180
197
|
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="debug" />
|
|
3
2
|
/// <reference types="node" />
|
|
4
3
|
/// <reference types="node" />
|
|
5
4
|
/// <reference types="node" />
|
|
6
|
-
import { BrowserServerOptions, Config, Request } from '@browserless.io/browserless';
|
|
5
|
+
import { BrowserServerOptions, Config, Logger, Request } from '@browserless.io/browserless';
|
|
7
6
|
import playwright, { Page } from 'playwright-core';
|
|
8
7
|
import { Duplex } from 'stream';
|
|
9
8
|
import { EventEmitter } from 'events';
|
|
@@ -12,13 +11,14 @@ export declare class ChromiumPlaywright extends EventEmitter {
|
|
|
12
11
|
protected config: Config;
|
|
13
12
|
protected userDataDir: string | null;
|
|
14
13
|
protected running: boolean;
|
|
14
|
+
protected logger: Logger;
|
|
15
15
|
protected proxy: httpProxy<import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>>;
|
|
16
16
|
protected browser: playwright.BrowserServer | null;
|
|
17
17
|
protected browserWSEndpoint: string | null;
|
|
18
|
-
protected debug: import("debug").Debugger;
|
|
19
18
|
protected executablePath: string;
|
|
20
|
-
constructor({ config, userDataDir, }: {
|
|
19
|
+
constructor({ config, userDataDir, logger, }: {
|
|
21
20
|
config: Config;
|
|
21
|
+
logger: Logger;
|
|
22
22
|
userDataDir: ChromiumPlaywright['userDataDir'];
|
|
23
23
|
});
|
|
24
24
|
protected cleanListeners(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServerError,
|
|
1
|
+
import { ServerError, } from '@browserless.io/browserless';
|
|
2
2
|
import playwright from 'playwright-core';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
4
|
import httpProxy from 'http-proxy';
|
|
@@ -7,16 +7,17 @@ export class ChromiumPlaywright extends EventEmitter {
|
|
|
7
7
|
config;
|
|
8
8
|
userDataDir;
|
|
9
9
|
running = false;
|
|
10
|
+
logger;
|
|
10
11
|
proxy = httpProxy.createProxyServer();
|
|
11
12
|
browser = null;
|
|
12
13
|
browserWSEndpoint = null;
|
|
13
|
-
debug = createLogger('browsers:chromium:playwright');
|
|
14
14
|
executablePath = playwright.chromium.executablePath();
|
|
15
|
-
constructor({ config, userDataDir, }) {
|
|
15
|
+
constructor({ config, userDataDir, logger, }) {
|
|
16
16
|
super();
|
|
17
17
|
this.userDataDir = userDataDir;
|
|
18
18
|
this.config = config;
|
|
19
|
-
this.
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.logger.info(`Starting new ${this.constructor.name} instance`);
|
|
20
21
|
}
|
|
21
22
|
cleanListeners() {
|
|
22
23
|
this.removeAllListeners();
|
|
@@ -24,7 +25,7 @@ export class ChromiumPlaywright extends EventEmitter {
|
|
|
24
25
|
isRunning = () => this.running;
|
|
25
26
|
close = async () => {
|
|
26
27
|
if (this.browser) {
|
|
27
|
-
this.
|
|
28
|
+
this.logger.info(`Closing ${this.constructor.name} process and all listeners`);
|
|
28
29
|
this.emit('close');
|
|
29
30
|
this.cleanListeners();
|
|
30
31
|
this.browser.close();
|
|
@@ -35,20 +36,20 @@ export class ChromiumPlaywright extends EventEmitter {
|
|
|
35
36
|
};
|
|
36
37
|
pages = async () => [];
|
|
37
38
|
getPageId = () => {
|
|
38
|
-
throw new ServerError(`#getPageId is not yet supported with this
|
|
39
|
+
throw new ServerError(`#getPageId is not yet supported with ${this.constructor.name}.`);
|
|
39
40
|
};
|
|
40
41
|
makeLiveURL = () => {
|
|
41
|
-
throw new ServerError(`Live URLs are not yet supported with this
|
|
42
|
+
throw new ServerError(`Live URLs are not yet supported with ${this.constructor.name}. In the future this will be at "${this.config.getExternalAddress()}"`);
|
|
42
43
|
};
|
|
43
44
|
newPage = async () => {
|
|
44
45
|
if (!this.browser || !this.browserWSEndpoint) {
|
|
45
|
-
throw new ServerError(
|
|
46
|
+
throw new ServerError(`${this.constructor.name} hasn't been launched yet!`);
|
|
46
47
|
}
|
|
47
48
|
const browser = await playwright.chromium.connect(this.browserWSEndpoint);
|
|
48
49
|
return await browser.newPage();
|
|
49
50
|
};
|
|
50
51
|
launch = async (options = {}) => {
|
|
51
|
-
this.
|
|
52
|
+
this.logger.info(`Launching ${this.constructor.name} Handler`);
|
|
52
53
|
this.browser = await playwright.chromium.launchServer({
|
|
53
54
|
...options,
|
|
54
55
|
args: [
|
|
@@ -59,7 +60,7 @@ export class ChromiumPlaywright extends EventEmitter {
|
|
|
59
60
|
executablePath: this.executablePath,
|
|
60
61
|
});
|
|
61
62
|
const browserWSEndpoint = this.browser.wsEndpoint();
|
|
62
|
-
this.
|
|
63
|
+
this.logger.info(`${this.constructor.name} is running on ${browserWSEndpoint}`);
|
|
63
64
|
this.running = true;
|
|
64
65
|
this.browserWSEndpoint = browserWSEndpoint;
|
|
65
66
|
return this.browser;
|
|
@@ -78,14 +79,14 @@ export class ChromiumPlaywright extends EventEmitter {
|
|
|
78
79
|
return externalURL.href;
|
|
79
80
|
};
|
|
80
81
|
proxyPageWebSocket = async () => {
|
|
81
|
-
|
|
82
|
+
this.logger.warn(`${this.constructor.name} Not yet implemented`);
|
|
82
83
|
};
|
|
83
84
|
proxyWebSocket = async (req, socket, head) => new Promise((resolve, reject) => {
|
|
84
85
|
if (!this.browserWSEndpoint) {
|
|
85
86
|
throw new ServerError(`No browserWSEndpoint found, did you launch first?`);
|
|
86
87
|
}
|
|
87
88
|
socket.once('close', resolve);
|
|
88
|
-
this.
|
|
89
|
+
this.logger.info(`Proxying ${req.parsed.href} to ${this.constructor.name} ${this.browserWSEndpoint}`);
|
|
89
90
|
// Delete headers known to cause issues
|
|
90
91
|
delete req.headers.origin;
|
|
91
92
|
req.url = '';
|
|
@@ -93,7 +94,7 @@ export class ChromiumPlaywright extends EventEmitter {
|
|
|
93
94
|
changeOrigin: true,
|
|
94
95
|
target: this.browserWSEndpoint,
|
|
95
96
|
}, (error) => {
|
|
96
|
-
this.
|
|
97
|
+
this.logger.error(`Error proxying session to ${this.constructor.name}: ${error}`);
|
|
97
98
|
this.close();
|
|
98
99
|
return reject(error);
|
|
99
100
|
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="debug" />
|
|
3
2
|
/// <reference types="node" />
|
|
4
3
|
/// <reference types="node" />
|
|
5
4
|
/// <reference types="node" />
|
|
6
|
-
import { BrowserServerOptions, Config, Request } from '@browserless.io/browserless';
|
|
5
|
+
import { BrowserServerOptions, Config, Logger, Request } from '@browserless.io/browserless';
|
|
7
6
|
import playwright, { Page } from 'playwright-core';
|
|
8
7
|
import { Duplex } from 'stream';
|
|
9
8
|
import { EventEmitter } from 'events';
|
|
@@ -12,12 +11,13 @@ export declare class FirefoxPlaywright extends EventEmitter {
|
|
|
12
11
|
protected config: Config;
|
|
13
12
|
protected userDataDir: string | null;
|
|
14
13
|
protected running: boolean;
|
|
14
|
+
protected logger: Logger;
|
|
15
15
|
protected proxy: httpProxy<import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>>;
|
|
16
16
|
protected browser: playwright.BrowserServer | null;
|
|
17
17
|
protected browserWSEndpoint: string | null;
|
|
18
|
-
|
|
19
|
-
constructor({ config, userDataDir, }: {
|
|
18
|
+
constructor({ config, userDataDir, logger, }: {
|
|
20
19
|
config: Config;
|
|
20
|
+
logger: Logger;
|
|
21
21
|
userDataDir: FirefoxPlaywright['userDataDir'];
|
|
22
22
|
});
|
|
23
23
|
protected cleanListeners(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServerError,
|
|
1
|
+
import { ServerError, } from '@browserless.io/browserless';
|
|
2
2
|
import playwright from 'playwright-core';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
4
|
import httpProxy from 'http-proxy';
|
|
@@ -7,15 +7,16 @@ export class FirefoxPlaywright extends EventEmitter {
|
|
|
7
7
|
config;
|
|
8
8
|
userDataDir;
|
|
9
9
|
running = false;
|
|
10
|
+
logger;
|
|
10
11
|
proxy = httpProxy.createProxyServer();
|
|
11
12
|
browser = null;
|
|
12
13
|
browserWSEndpoint = null;
|
|
13
|
-
|
|
14
|
-
constructor({ config, userDataDir, }) {
|
|
14
|
+
constructor({ config, userDataDir, logger, }) {
|
|
15
15
|
super();
|
|
16
16
|
this.userDataDir = userDataDir;
|
|
17
17
|
this.config = config;
|
|
18
|
-
this.
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
this.logger.info(`Starting new ${this.constructor.name} instance`);
|
|
19
20
|
}
|
|
20
21
|
cleanListeners() {
|
|
21
22
|
this.removeAllListeners();
|
|
@@ -23,7 +24,7 @@ export class FirefoxPlaywright extends EventEmitter {
|
|
|
23
24
|
isRunning = () => this.running;
|
|
24
25
|
close = async () => {
|
|
25
26
|
if (this.browser) {
|
|
26
|
-
this.
|
|
27
|
+
this.logger.trace(`Closing ${this.constructor.name} process and all listeners`);
|
|
27
28
|
this.emit('close');
|
|
28
29
|
this.cleanListeners();
|
|
29
30
|
this.browser.close();
|
|
@@ -34,16 +35,16 @@ export class FirefoxPlaywright extends EventEmitter {
|
|
|
34
35
|
};
|
|
35
36
|
pages = async () => [];
|
|
36
37
|
getPageId = () => {
|
|
37
|
-
throw new ServerError(`#getPageId is not yet supported with this
|
|
38
|
+
throw new ServerError(`#getPageId is not yet supported with ${this.constructor.name}.`);
|
|
38
39
|
};
|
|
39
40
|
makeLiveURL = () => {
|
|
40
|
-
throw new ServerError(`Live URLs are not yet supported with this
|
|
41
|
+
throw new ServerError(`Live URLs are not yet supported with ${this.constructor.name}.`);
|
|
41
42
|
};
|
|
42
43
|
newPage = async () => {
|
|
43
|
-
throw new ServerError(`Can't create new page with this
|
|
44
|
+
throw new ServerError(`Can't create new page with ${this.constructor.name}`);
|
|
44
45
|
};
|
|
45
46
|
launch = async (options = {}) => {
|
|
46
|
-
this.
|
|
47
|
+
this.logger.info(`Launching ${this.constructor.name} Handler`);
|
|
47
48
|
this.browser = await playwright.firefox.launchServer({
|
|
48
49
|
...options,
|
|
49
50
|
args: [
|
|
@@ -53,7 +54,7 @@ export class FirefoxPlaywright extends EventEmitter {
|
|
|
53
54
|
executablePath: playwright.firefox.executablePath(),
|
|
54
55
|
});
|
|
55
56
|
const browserWSEndpoint = this.browser.wsEndpoint();
|
|
56
|
-
this.
|
|
57
|
+
this.logger.info(`${this.constructor.name} is running on ${browserWSEndpoint}`);
|
|
57
58
|
this.browserWSEndpoint = browserWSEndpoint;
|
|
58
59
|
this.running = true;
|
|
59
60
|
return this.browser;
|
|
@@ -72,14 +73,14 @@ export class FirefoxPlaywright extends EventEmitter {
|
|
|
72
73
|
return externalURL.href;
|
|
73
74
|
};
|
|
74
75
|
proxyPageWebSocket = async () => {
|
|
75
|
-
|
|
76
|
+
this.logger.warn(`Not yet implemented in ${this.constructor.name}`);
|
|
76
77
|
};
|
|
77
78
|
proxyWebSocket = async (req, socket, head) => new Promise((resolve, reject) => {
|
|
78
79
|
if (!this.browserWSEndpoint) {
|
|
79
80
|
throw new ServerError(`No browserWSEndpoint found, did you launch first?`);
|
|
80
81
|
}
|
|
81
82
|
socket.once('close', resolve);
|
|
82
|
-
this.
|
|
83
|
+
this.logger.info(`Proxying ${req.parsed.href} to ${this.constructor.name} ${this.browserWSEndpoint}`);
|
|
83
84
|
// Delete headers known to cause issues
|
|
84
85
|
delete req.headers.origin;
|
|
85
86
|
req.url = '';
|
|
@@ -87,7 +88,7 @@ export class FirefoxPlaywright extends EventEmitter {
|
|
|
87
88
|
changeOrigin: true,
|
|
88
89
|
target: this.browserWSEndpoint,
|
|
89
90
|
}, (error) => {
|
|
90
|
-
this.
|
|
91
|
+
this.logger.error(`Error proxying session to ${this.constructor.name}: ${error}`);
|
|
91
92
|
this.close();
|
|
92
93
|
return reject(error);
|
|
93
94
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { BrowserHTTPRoute, BrowserInstance, BrowserWebsocketRoute, BrowserlessSession, BrowserlessSessionJSON, CDPJSONPayload, ChromiumCDP, Config, Hooks, Request } from '@browserless.io/browserless';
|
|
1
|
+
import { BrowserHTTPRoute, BrowserInstance, BrowserServerOptions, BrowserWebsocketRoute, BrowserlessSession, BrowserlessSessionJSON, CDPJSONPayload, CDPLaunchOptions, ChromiumCDP, Config, Hooks, Logger, Request } from '@browserless.io/browserless';
|
|
3
2
|
import { Page } from 'puppeteer-core';
|
|
4
3
|
export declare class BrowserManager {
|
|
5
4
|
protected config: Config;
|
|
@@ -7,11 +6,11 @@ export declare class BrowserManager {
|
|
|
7
6
|
protected browsers: Map<BrowserInstance, BrowserlessSession>;
|
|
8
7
|
protected launching: Map<string, Promise<unknown>>;
|
|
9
8
|
protected timers: Map<string, number>;
|
|
10
|
-
protected
|
|
9
|
+
protected log: Logger;
|
|
11
10
|
protected chromeBrowsers: (typeof ChromiumCDP)[];
|
|
12
11
|
protected playwrightBrowserNames: string[];
|
|
13
12
|
constructor(config: Config, hooks: Hooks);
|
|
14
|
-
|
|
13
|
+
protected browserIsChrome: (b: BrowserInstance) => boolean;
|
|
15
14
|
protected removeUserDataDir: (userDataDir: string | null) => Promise<void>;
|
|
16
15
|
protected onNewPage: (req: Request, page: Page) => Promise<void>;
|
|
17
16
|
/**
|
|
@@ -19,24 +18,41 @@ export declare class BrowserManager {
|
|
|
19
18
|
* and modifies URLs to set them to the appropriate addresses configured.
|
|
20
19
|
* When both Chrome and Chromium are installed, defaults to Chromium.
|
|
21
20
|
*/
|
|
22
|
-
getProtocolJSON: () => Promise<object>;
|
|
21
|
+
getProtocolJSON: (logger: Logger) => Promise<object>;
|
|
23
22
|
/**
|
|
24
23
|
* Returns the /json/version API from Chromium or Chrome, whichever is installed,
|
|
25
24
|
* and modifies URLs to set them to the appropriate addresses configured.
|
|
26
25
|
* When both Chrome and Chromium are installed, defaults to Chromium.
|
|
27
26
|
*/
|
|
28
|
-
getVersionJSON: () => Promise<CDPJSONPayload>;
|
|
27
|
+
getVersionJSON: (logger: Logger) => Promise<CDPJSONPayload>;
|
|
29
28
|
/**
|
|
30
29
|
* Returns a list of all Chrome-like browsers (both Chromium and Chrome) with
|
|
31
30
|
* their respective /json/list contents. URLs are modified so that subsequent
|
|
32
31
|
* calls can be forwarded to the appropriate destination
|
|
33
32
|
*/
|
|
34
33
|
getJSONList: () => Promise<Array<CDPJSONPayload>>;
|
|
35
|
-
|
|
34
|
+
protected generateSessionJson: (browser: BrowserInstance, session: BrowserlessSession) => Promise<{
|
|
35
|
+
browser: string;
|
|
36
|
+
browserId: string;
|
|
37
|
+
initialConnectURL: string;
|
|
38
|
+
killURL: string | null;
|
|
39
|
+
running: boolean;
|
|
40
|
+
timeAliveMs: number;
|
|
41
|
+
type: string;
|
|
42
|
+
id: string | null;
|
|
43
|
+
isTempDataDir: boolean;
|
|
44
|
+
launchOptions: CDPLaunchOptions | BrowserServerOptions;
|
|
45
|
+
numbConnected: number;
|
|
46
|
+
resolver: (val: unknown) => void;
|
|
47
|
+
routePath: string | string[];
|
|
48
|
+
startedOn: number;
|
|
49
|
+
ttl: number;
|
|
50
|
+
userDataDir: string | null;
|
|
51
|
+
}[]>;
|
|
36
52
|
close: (browser: BrowserInstance, session: BrowserlessSession) => Promise<void>;
|
|
37
53
|
getAllSessions: () => Promise<BrowserlessSessionJSON[]>;
|
|
38
54
|
complete: (browser: BrowserInstance) => Promise<void>;
|
|
39
|
-
getBrowserForRequest: (req: Request, router: BrowserHTTPRoute | BrowserWebsocketRoute) => Promise<BrowserInstance>;
|
|
55
|
+
getBrowserForRequest: (req: Request, router: BrowserHTTPRoute | BrowserWebsocketRoute, logger: Logger) => Promise<BrowserInstance>;
|
|
40
56
|
shutdown: () => Promise<void>;
|
|
41
57
|
/**
|
|
42
58
|
* Left blank for downstream SDK modules to optionally implement.
|
package/build/browsers/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BLESS_PAGE_IDENTIFIER, BadRequest, ChromeCDP, ChromePlaywright, ChromiumCDP, ChromiumPlaywright, FirefoxPlaywright, HTTPManagementRoutes, NotFound, ServerError, WebkitPlaywright, availableBrowsers, convertIfBase64,
|
|
1
|
+
import { BLESS_PAGE_IDENTIFIER, BadRequest, ChromeCDP, ChromePlaywright, ChromiumCDP, ChromiumPlaywright, FirefoxPlaywright, HTTPManagementRoutes, Logger, NotFound, ServerError, WebkitPlaywright, availableBrowsers, convertIfBase64, exists, generateDataDir, makeExternalURL, noop, parseBooleanParam, } from '@browserless.io/browserless';
|
|
2
2
|
import { deleteAsync } from 'del';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
export class BrowserManager {
|
|
@@ -7,7 +7,7 @@ export class BrowserManager {
|
|
|
7
7
|
browsers = new Map();
|
|
8
8
|
launching = new Map();
|
|
9
9
|
timers = new Map();
|
|
10
|
-
|
|
10
|
+
log = new Logger('browser-manager');
|
|
11
11
|
chromeBrowsers = [ChromiumCDP, ChromeCDP];
|
|
12
12
|
playwrightBrowserNames = [
|
|
13
13
|
ChromiumPlaywright.name,
|
|
@@ -22,9 +22,9 @@ export class BrowserManager {
|
|
|
22
22
|
browserIsChrome = (b) => this.chromeBrowsers.some((chromeBrowser) => b instanceof chromeBrowser);
|
|
23
23
|
removeUserDataDir = async (userDataDir) => {
|
|
24
24
|
if (userDataDir && (await exists(userDataDir))) {
|
|
25
|
-
this.
|
|
25
|
+
this.log.info(`Deleting data directory "${userDataDir}"`);
|
|
26
26
|
await deleteAsync(userDataDir, { force: true }).catch((err) => {
|
|
27
|
-
this.
|
|
27
|
+
this.log.error(`Error cleaning up user-data-dir "${err}" at ${userDataDir}`);
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
};
|
|
@@ -36,7 +36,7 @@ export class BrowserManager {
|
|
|
36
36
|
* and modifies URLs to set them to the appropriate addresses configured.
|
|
37
37
|
* When both Chrome and Chromium are installed, defaults to Chromium.
|
|
38
38
|
*/
|
|
39
|
-
getProtocolJSON = async () => {
|
|
39
|
+
getProtocolJSON = async (logger) => {
|
|
40
40
|
const Browser = (await availableBrowsers).find((InstalledBrowser) => this.chromeBrowsers.some((ChromeBrowser) => InstalledBrowser === ChromeBrowser));
|
|
41
41
|
if (!Browser) {
|
|
42
42
|
throw new Error(`No Chrome or Chromium browsers are installed!`);
|
|
@@ -44,6 +44,7 @@ export class BrowserManager {
|
|
|
44
44
|
const browser = new Browser({
|
|
45
45
|
blockAds: false,
|
|
46
46
|
config: this.config,
|
|
47
|
+
logger,
|
|
47
48
|
userDataDir: null,
|
|
48
49
|
});
|
|
49
50
|
await browser.launch();
|
|
@@ -62,8 +63,8 @@ export class BrowserManager {
|
|
|
62
63
|
* and modifies URLs to set them to the appropriate addresses configured.
|
|
63
64
|
* When both Chrome and Chromium are installed, defaults to Chromium.
|
|
64
65
|
*/
|
|
65
|
-
getVersionJSON = async () => {
|
|
66
|
-
this.
|
|
66
|
+
getVersionJSON = async (logger) => {
|
|
67
|
+
this.log.info(`Launching Chromium to generate /json/version results`);
|
|
67
68
|
const Browser = (await availableBrowsers).find((InstalledBrowser) => this.chromeBrowsers.some((ChromeBrowser) => InstalledBrowser === ChromeBrowser));
|
|
68
69
|
if (!Browser) {
|
|
69
70
|
throw new ServerError(`No Chrome or Chromium browsers are installed!`);
|
|
@@ -71,6 +72,7 @@ export class BrowserManager {
|
|
|
71
72
|
const browser = new Browser({
|
|
72
73
|
blockAds: false,
|
|
73
74
|
config: this.config,
|
|
75
|
+
logger,
|
|
74
76
|
userDataDir: null,
|
|
75
77
|
});
|
|
76
78
|
await browser.launch();
|
|
@@ -178,15 +180,15 @@ export class BrowserManager {
|
|
|
178
180
|
};
|
|
179
181
|
close = async (browser, session) => {
|
|
180
182
|
const cleanupACtions = [];
|
|
181
|
-
this.
|
|
183
|
+
this.log.info(`${session.numbConnected} Client(s) are currently connected`);
|
|
182
184
|
// Don't close if there's clients still connected
|
|
183
185
|
if (session.numbConnected > 0) {
|
|
184
186
|
return;
|
|
185
187
|
}
|
|
186
|
-
this.
|
|
188
|
+
this.log.info(`Closing browser session`);
|
|
187
189
|
cleanupACtions.push(() => browser.close());
|
|
188
190
|
if (session.isTempDataDir) {
|
|
189
|
-
this.
|
|
191
|
+
this.log.info(`Deleting "${session.userDataDir}" user-data-dir and session from memory`);
|
|
190
192
|
this.browsers.delete(browser);
|
|
191
193
|
cleanupACtions.push(() => this.removeUserDataDir(session.userDataDir));
|
|
192
194
|
}
|
|
@@ -204,7 +206,7 @@ export class BrowserManager {
|
|
|
204
206
|
complete = async (browser) => {
|
|
205
207
|
const session = this.browsers.get(browser);
|
|
206
208
|
if (!session) {
|
|
207
|
-
this.
|
|
209
|
+
this.log.info(`Couldn't locate session for browser, proceeding with close`);
|
|
208
210
|
return browser.close();
|
|
209
211
|
}
|
|
210
212
|
const { id, resolver } = session;
|
|
@@ -215,7 +217,7 @@ export class BrowserManager {
|
|
|
215
217
|
--session.numbConnected;
|
|
216
218
|
this.close(browser, session);
|
|
217
219
|
};
|
|
218
|
-
getBrowserForRequest = async (req, router) => {
|
|
220
|
+
getBrowserForRequest = async (req, router, logger) => {
|
|
219
221
|
const { browser: Browser } = router;
|
|
220
222
|
const blockAds = parseBooleanParam(req.parsed.searchParams, 'blockAds', false);
|
|
221
223
|
const decodedLaunchOptions = convertIfBase64(req.parsed.searchParams.get('launch') || '{}');
|
|
@@ -228,7 +230,7 @@ export class BrowserManager {
|
|
|
228
230
|
if (found) {
|
|
229
231
|
const [browser, session] = found;
|
|
230
232
|
++session.numbConnected;
|
|
231
|
-
this.debug(`Located browser with ID ${id}`);
|
|
233
|
+
this.log.debug(`Located browser with ID ${id}`);
|
|
232
234
|
return browser;
|
|
233
235
|
}
|
|
234
236
|
throw new NotFound(`Couldn't locate browser "${id}" for request "${req.parsed.pathname}"`);
|
|
@@ -259,6 +261,8 @@ export class BrowserManager {
|
|
|
259
261
|
}));
|
|
260
262
|
const found = allPages.flat().find((b) => b.id === id);
|
|
261
263
|
if (found) {
|
|
264
|
+
const session = this.browsers.get(found.browser);
|
|
265
|
+
++session.numbConnected;
|
|
262
266
|
return found.browser;
|
|
263
267
|
}
|
|
264
268
|
throw new NotFound(`Couldn't locate browser "${id}" for request "${req.parsed.pathname}"`);
|
|
@@ -299,6 +303,7 @@ export class BrowserManager {
|
|
|
299
303
|
const browser = new Browser({
|
|
300
304
|
blockAds,
|
|
301
305
|
config: this.config,
|
|
306
|
+
logger,
|
|
302
307
|
userDataDir,
|
|
303
308
|
});
|
|
304
309
|
const connectionMeta = {
|
|
@@ -323,7 +328,7 @@ export class BrowserManager {
|
|
|
323
328
|
return browser;
|
|
324
329
|
};
|
|
325
330
|
shutdown = async () => {
|
|
326
|
-
this.
|
|
331
|
+
this.log.info(`Closing down browser instances`);
|
|
327
332
|
const sessions = Array.from(this.browsers);
|
|
328
333
|
await Promise.all(sessions.map(([b]) => b.close()));
|
|
329
334
|
const timers = Array.from(this.timers);
|
|
@@ -332,7 +337,7 @@ export class BrowserManager {
|
|
|
332
337
|
this.browsers = new Map();
|
|
333
338
|
this.timers = new Map();
|
|
334
339
|
await this.stop();
|
|
335
|
-
this.
|
|
340
|
+
this.log.info(`Shutdown complete`);
|
|
336
341
|
};
|
|
337
342
|
/**
|
|
338
343
|
* Left blank for downstream SDK modules to optionally implement.
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="debug" />
|
|
3
2
|
/// <reference types="node" />
|
|
4
3
|
/// <reference types="node" />
|
|
5
4
|
/// <reference types="node" />
|
|
6
|
-
import { BrowserServerOptions, Config, Request } from '@browserless.io/browserless';
|
|
5
|
+
import { BrowserServerOptions, Config, Logger, Request } from '@browserless.io/browserless';
|
|
7
6
|
import playwright, { Page } from 'playwright-core';
|
|
8
7
|
import { Duplex } from 'stream';
|
|
9
8
|
import { EventEmitter } from 'events';
|
|
@@ -15,9 +14,10 @@ export declare class WebkitPlaywright extends EventEmitter {
|
|
|
15
14
|
protected proxy: httpProxy<import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>>;
|
|
16
15
|
protected browser: playwright.BrowserServer | null;
|
|
17
16
|
protected browserWSEndpoint: string | null;
|
|
18
|
-
protected
|
|
19
|
-
constructor({ config, userDataDir, }: {
|
|
17
|
+
protected logger: Logger;
|
|
18
|
+
constructor({ config, userDataDir, logger, }: {
|
|
20
19
|
config: Config;
|
|
20
|
+
logger: Logger;
|
|
21
21
|
userDataDir: WebkitPlaywright['userDataDir'];
|
|
22
22
|
});
|
|
23
23
|
protected cleanListeners(): void;
|