@browserless.io/browserless 2.15.0 → 2.16.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 +7 -1
- package/README.md +7 -6
- package/build/browsers/browsers.cdp.d.ts +1 -1
- package/build/browsers/browsers.cdp.js +12 -10
- package/build/browsers/index.js +1 -1
- package/build/limiter.spec.js +19 -0
- package/build/logger.js +1 -1
- package/build/routes/chrome/http/content.post.body.json +8 -8
- package/build/routes/chrome/http/pdf.post.body.json +13 -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 +13 -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/utils.d.ts +1 -0
- package/build/utils.js +5 -1
- package/package.json +8 -8
- package/scripts/build-schemas.js +7 -3
- package/src/browsers/browsers.cdp.ts +25 -18
- package/src/browsers/browsers.playwright.ts +12 -5
- package/src/browsers/index.ts +1 -1
- package/src/limiter.spec.ts +27 -0
- package/src/logger.ts +1 -1
- package/src/utils.ts +8 -2
- package/static/docs/swagger.json +15 -11
- package/static/docs/swagger.min.json +14 -10
- package/static/function/client.js +446 -163
- package/static/function/index.html +446 -163
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserless.io/browserless",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"license": "SSPL",
|
|
5
5
|
"description": "The browserless platform",
|
|
6
6
|
"author": "browserless.io",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"debug": "^4.3.5",
|
|
52
52
|
"del": "^7.0.0",
|
|
53
53
|
"enjoi": "^9.0.1",
|
|
54
|
-
"file-type": "^19.
|
|
54
|
+
"file-type": "^19.1.1",
|
|
55
55
|
"get-port": "^7.1.0",
|
|
56
56
|
"gradient-string": "^2.0.0",
|
|
57
57
|
"http-proxy": "^1.18.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"playwright-1.43": "npm:playwright-core@1.43.1",
|
|
63
63
|
"playwright-1.44": "npm:playwright-core@1.44.1",
|
|
64
64
|
"playwright-core": "^1.45.1",
|
|
65
|
-
"puppeteer-core": "^22.
|
|
65
|
+
"puppeteer-core": "^22.13.0",
|
|
66
66
|
"puppeteer-extra": "^3.3.6",
|
|
67
67
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
68
68
|
"queue": "^7.0.0",
|
|
@@ -76,10 +76,10 @@
|
|
|
76
76
|
"@types/http-proxy": "^1.17.14",
|
|
77
77
|
"@types/micromatch": "^4.0.9",
|
|
78
78
|
"@types/mocha": "^10.0.7",
|
|
79
|
-
"@types/node": "^20.14.
|
|
79
|
+
"@types/node": "^20.14.10",
|
|
80
80
|
"@types/sinon": "^17.0.3",
|
|
81
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
82
|
-
"@typescript-eslint/parser": "^7.
|
|
81
|
+
"@typescript-eslint/eslint-plugin": "^7.16.0",
|
|
82
|
+
"@typescript-eslint/parser": "^7.16.0",
|
|
83
83
|
"assert": "^2.0.0",
|
|
84
84
|
"chai": "^5.1.1",
|
|
85
85
|
"cross-env": "^7.0.3",
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
"eslint-plugin-typescript-sort-keys": "^3.2.0",
|
|
91
91
|
"extract-zip": "^2.0.1",
|
|
92
92
|
"gunzip-maybe": "^1.4.2",
|
|
93
|
-
"marked": "^13.0.
|
|
93
|
+
"marked": "^13.0.2",
|
|
94
94
|
"mocha": "^10.6.0",
|
|
95
95
|
"move-file": "^3.1.0",
|
|
96
|
-
"prettier": "^3.3.
|
|
96
|
+
"prettier": "^3.3.3",
|
|
97
97
|
"sinon": "^18.0.0",
|
|
98
98
|
"ts-node": "^10.9.2",
|
|
99
99
|
"typescript": "^5.5.3",
|
package/scripts/build-schemas.js
CHANGED
|
@@ -12,7 +12,7 @@ const moduleMain = path.normalize(import.meta.url).endsWith(process.argv[1]);
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Find an exported interface in a TypeScript AST
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* @param {ts.Node} node The node to search for the exported interface
|
|
17
17
|
* @param {string} interfaceName The name of the interface to search for
|
|
18
18
|
* @returns {ts.InterfaceDeclaration | ts.Identifier | null}
|
|
@@ -29,7 +29,11 @@ const findExportedInterface = (node, interfaceName) => {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Check for re-exported interfaces
|
|
32
|
-
if (
|
|
32
|
+
if (
|
|
33
|
+
ts.isExportDeclaration(node) &&
|
|
34
|
+
node.exportClause &&
|
|
35
|
+
ts.isNamedExports(node.exportClause)
|
|
36
|
+
) {
|
|
33
37
|
const elements = node.exportClause.elements;
|
|
34
38
|
for (const element of elements) {
|
|
35
39
|
if (element.name.text === interfaceName) {
|
|
@@ -50,7 +54,7 @@ const findExportedInterface = (node, interfaceName) => {
|
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
56
|
* Creates an standard JSON schema file for each route (see https://json-schema.org/specification)
|
|
53
|
-
*
|
|
57
|
+
*
|
|
54
58
|
* @param {string[]} externalHTTPRoutes Additional HTTP routes to parse
|
|
55
59
|
* @param {string[]} externalWebSocketRoutes Additional WS routes to parse
|
|
56
60
|
*/
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
chromeExecutablePath,
|
|
9
9
|
noop,
|
|
10
10
|
once,
|
|
11
|
+
ublockPath,
|
|
11
12
|
} from '@browserless.io/browserless';
|
|
12
13
|
import puppeteer, { Browser, Page, Target } from 'puppeteer-core';
|
|
13
14
|
import { Duplex } from 'stream';
|
|
14
15
|
import { EventEmitter } from 'events';
|
|
15
16
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
16
|
-
import { fileURLToPath } from 'url';
|
|
17
17
|
import getPort from 'get-port';
|
|
18
18
|
import httpProxy from 'http-proxy';
|
|
19
19
|
import path from 'path';
|
|
@@ -170,11 +170,29 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
170
170
|
return this.browser?.process() || null;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
public async launch(
|
|
174
|
-
|
|
173
|
+
public async launch({
|
|
174
|
+
options,
|
|
175
|
+
stealth,
|
|
176
|
+
}: BrowserLauncherOptions): Promise<Browser> {
|
|
175
177
|
this.port = await getPort();
|
|
176
178
|
this.logger.info(`${this.constructor.name} got open port ${this.port}`);
|
|
177
|
-
|
|
179
|
+
|
|
180
|
+
const extensionLaunchArgs = options.args?.find((a) =>
|
|
181
|
+
a.startsWith('--load-extension'),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Remove extension flags as we recompile them below with our own
|
|
185
|
+
options.args = options.args?.filter(
|
|
186
|
+
(a) =>
|
|
187
|
+
!a.startsWith('--load-extension') &&
|
|
188
|
+
!a.startsWith('--disable-extensions-except'),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const extensions = [
|
|
192
|
+
this.blockAds ? ublockPath : null,
|
|
193
|
+
extensionLaunchArgs ? extensionLaunchArgs.split('=')[1] : null,
|
|
194
|
+
].filter((_) => !!_);
|
|
195
|
+
|
|
178
196
|
const finalOptions = {
|
|
179
197
|
...options,
|
|
180
198
|
args: [
|
|
@@ -186,21 +204,10 @@ export class ChromiumCDP extends EventEmitter {
|
|
|
186
204
|
executablePath: this.executablePath,
|
|
187
205
|
};
|
|
188
206
|
|
|
189
|
-
if (
|
|
190
|
-
// Necessary to load extensions
|
|
191
|
-
finalOptions.headless = false;
|
|
192
|
-
|
|
193
|
-
const loadExtensionPaths: string = path.join(
|
|
194
|
-
__dirname,
|
|
195
|
-
'..',
|
|
196
|
-
'..',
|
|
197
|
-
'extensions',
|
|
198
|
-
'ublock',
|
|
199
|
-
);
|
|
200
|
-
|
|
207
|
+
if (extensions.length) {
|
|
201
208
|
finalOptions.args.push(
|
|
202
|
-
'--load-extension=' +
|
|
203
|
-
'--disable-extensions-except=' +
|
|
209
|
+
'--load-extension=' + extensions.join(','),
|
|
210
|
+
'--disable-extensions-except=' + extensions.join(','),
|
|
204
211
|
);
|
|
205
212
|
}
|
|
206
213
|
|
|
@@ -27,8 +27,10 @@ class BasePlaywright extends EventEmitter {
|
|
|
27
27
|
protected proxy = httpProxy.createProxyServer();
|
|
28
28
|
protected browser: playwright.BrowserServer | null = null;
|
|
29
29
|
protected browserWSEndpoint: string | null = null;
|
|
30
|
-
protected playwrightBrowserType: PlaywrightBrowserTypes =
|
|
31
|
-
|
|
30
|
+
protected playwrightBrowserType: PlaywrightBrowserTypes =
|
|
31
|
+
PlaywrightBrowserTypes.chromium;
|
|
32
|
+
protected executablePath = () =>
|
|
33
|
+
playwright[this.playwrightBrowserType].executablePath();
|
|
32
34
|
|
|
33
35
|
constructor({
|
|
34
36
|
config,
|
|
@@ -107,17 +109,22 @@ class BasePlaywright extends EventEmitter {
|
|
|
107
109
|
`${this.constructor.name} hasn't been launched yet!`,
|
|
108
110
|
);
|
|
109
111
|
}
|
|
110
|
-
const browser = await playwright[this.playwrightBrowserType].connect(
|
|
112
|
+
const browser = await playwright[this.playwrightBrowserType].connect(
|
|
113
|
+
this.browserWSEndpoint,
|
|
114
|
+
);
|
|
111
115
|
return await browser.newPage();
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
public async launch(
|
|
118
|
+
public async launch(
|
|
119
|
+
laucherOpts: BrowserLauncherOptions,
|
|
120
|
+
): Promise<playwright.BrowserServer> {
|
|
115
121
|
const { options, pwVersion } = laucherOpts;
|
|
116
122
|
this.logger.info(`Launching ${this.constructor.name} Handler`);
|
|
117
123
|
|
|
118
124
|
const opts = this.makeLaunchOptions(options);
|
|
119
125
|
const versionedPw = await this.config.loadPwVersion(pwVersion!);
|
|
120
|
-
const browser =
|
|
126
|
+
const browser =
|
|
127
|
+
await versionedPw[this.playwrightBrowserType].launchServer(opts);
|
|
121
128
|
const browserWSEndpoint = browser.wsEndpoint();
|
|
122
129
|
|
|
123
130
|
this.logger.info(
|
package/src/browsers/index.ts
CHANGED
package/src/limiter.spec.ts
CHANGED
|
@@ -22,6 +22,7 @@ describe(`Limiter`, () => {
|
|
|
22
22
|
webHooks.callRejectAlertURL.resetHistory();
|
|
23
23
|
webHooks.callTimeoutAlertURL.resetHistory();
|
|
24
24
|
webHooks.callErrorAlertURL.resetHistory();
|
|
25
|
+
|
|
25
26
|
hooks.before.resetHistory();
|
|
26
27
|
hooks.after.resetHistory();
|
|
27
28
|
hooks.browser.resetHistory();
|
|
@@ -119,6 +120,32 @@ describe(`Limiter`, () => {
|
|
|
119
120
|
expect(handlerTwo.calledOnce).to.be.true;
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
it('continues to process jobs even if an earlier job errors', (d) => {
|
|
124
|
+
const config = new Config();
|
|
125
|
+
const monitoring = new Monitoring(config);
|
|
126
|
+
const metrics = new Metrics();
|
|
127
|
+
|
|
128
|
+
config.setConcurrent(1);
|
|
129
|
+
config.setQueued(1);
|
|
130
|
+
config.setTimeout(-1);
|
|
131
|
+
|
|
132
|
+
const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
|
|
133
|
+
const errorJob = () =>
|
|
134
|
+
Promise.reject(new Error('Danger, danger. High voltage!'));
|
|
135
|
+
const okJob = spy();
|
|
136
|
+
|
|
137
|
+
const jobOne = limiter.limit(errorJob, asyncNoop, asyncNoop, noop);
|
|
138
|
+
const jobTwo = limiter.limit(okJob, asyncNoop, asyncNoop, noop);
|
|
139
|
+
|
|
140
|
+
jobOne();
|
|
141
|
+
jobTwo();
|
|
142
|
+
|
|
143
|
+
limiter.addEventListener('end', () => {
|
|
144
|
+
expect(okJob.calledOnce).to.be.true;
|
|
145
|
+
d(undefined);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
122
149
|
it('bubbles up errors', async () => {
|
|
123
150
|
const config = new Config();
|
|
124
151
|
const monitoring = new Monitoring(config);
|
package/src/logger.ts
CHANGED
|
@@ -23,7 +23,7 @@ export class Logger {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
protected get reqInfo() {
|
|
26
|
-
return this.request ? this.request.socket.remoteAddress ?? 'Unknown' : '';
|
|
26
|
+
return this.request ? (this.request.socket.remoteAddress ?? 'Unknown') : '';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
public trace(...messages: unknown[]) {
|
package/src/utils.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { Page } from 'puppeteer-core';
|
|
|
23
23
|
import { ServerResponse } from 'http';
|
|
24
24
|
import crypto from 'crypto';
|
|
25
25
|
import debug from 'debug';
|
|
26
|
+
import { fileURLToPath } from 'url';
|
|
26
27
|
import gradient from 'gradient-string';
|
|
27
28
|
import { homedir } from 'os';
|
|
28
29
|
import path from 'path';
|
|
@@ -33,6 +34,8 @@ const isHTTP = (
|
|
|
33
34
|
return (writeable as ServerResponse).writeHead !== undefined;
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
|
|
36
39
|
const getAuthHeaderToken = (header: string) => {
|
|
37
40
|
if (header.startsWith('Basic')) {
|
|
38
41
|
const username = header.split(/\s+/).pop() || '';
|
|
@@ -475,10 +478,11 @@ export const queryParamsToObject = (
|
|
|
475
478
|
): Record<string, unknown> =>
|
|
476
479
|
[...params.entries()].reduce(
|
|
477
480
|
(accum, [key, value]) => {
|
|
478
|
-
accum[key] =
|
|
481
|
+
accum[key] =
|
|
482
|
+
value === '' || value === undefined || value === null ? true : value;
|
|
479
483
|
return accum;
|
|
480
484
|
},
|
|
481
|
-
{} as
|
|
485
|
+
{} as ReturnType<typeof queryParamsToObject>,
|
|
482
486
|
);
|
|
483
487
|
|
|
484
488
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
@@ -844,3 +848,5 @@ export const getCDPClient = (page: Page): CDPSession => {
|
|
|
844
848
|
|
|
845
849
|
return typeof c === 'function' ? c.call(page) : c;
|
|
846
850
|
};
|
|
851
|
+
|
|
852
|
+
export const ublockPath = path.join(__dirname, '..', 'extensions', 'ublock');
|