@browserless.io/browserless 2.0.0-beta-8 → 2.1.1
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 -2
- package/README.md +10 -6
- package/bin/browserless.js +49 -13
- package/bin/scaffold/README.md +7 -7
- package/bin/scaffold/tsconfig.json +1 -1
- package/build/browserless.js +9 -2
- package/build/browsers/cdp-chromium.d.ts +1 -1
- package/build/browsers/cdp-chromium.js +4 -5
- package/build/browsers/index.d.ts +10 -0
- package/build/browsers/index.js +44 -0
- package/build/browsers/playwright-chromium.d.ts +1 -1
- package/build/browsers/playwright-chromium.js +1 -2
- package/build/browsers/playwright-firefox.d.ts +1 -1
- package/build/browsers/playwright-firefox.js +1 -2
- package/build/browsers/playwright-webkit.d.ts +1 -1
- package/build/browsers/playwright-webkit.js +1 -2
- package/build/config.d.ts +9 -0
- package/build/config.js +15 -0
- package/build/constants.d.ts +1 -0
- package/build/constants.js +1 -0
- package/build/data/classes.json +1 -1
- package/build/data/selectors.json +1 -1
- package/build/file-system.spec.js +1 -1
- package/build/http.d.ts +4 -0
- package/build/http.js +4 -0
- package/build/routes/chromium/http/content-post.body.json +8 -8
- package/build/routes/chromium/http/json-list.d.ts +15 -0
- package/build/routes/chromium/http/json-list.js +23 -0
- package/build/routes/chromium/http/json-list.response.json +52 -0
- package/build/routes/chromium/http/json-new.d.ts +15 -0
- package/build/routes/chromium/http/json-new.js +23 -0
- package/build/routes/chromium/http/json-new.response.json +44 -0
- package/build/routes/chromium/http/json-protocol-get.d.ts +15 -0
- package/build/routes/chromium/http/json-protocol-get.js +20 -0
- package/build/routes/chromium/http/json-protocol-get.response.json +6 -0
- package/build/routes/chromium/http/json-version-get.d.ts +15 -0
- package/build/routes/chromium/http/json-version-get.js +30 -0
- package/build/routes/chromium/http/json-version-get.response.json +37 -0
- package/build/routes/chromium/http/pdf-post.body.json +12 -8
- 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/chromium/tests/json-version.spec.d.ts +1 -0
- package/build/routes/chromium/tests/json-version.spec.js +37 -0
- package/build/routes/chromium/utils/cdp.d.ts +2 -0
- package/build/routes/chromium/utils/cdp.js +14 -0
- package/build/types.d.ts +31 -0
- package/build/utils.d.ts +9 -0
- package/build/utils.js +17 -2
- package/package.json +15 -18
- package/src/browserless.ts +12 -1
- package/src/browsers/cdp-chromium.ts +7 -10
- package/src/browsers/index.ts +65 -2
- package/src/browsers/playwright-chromium.ts +3 -4
- package/src/browsers/playwright-firefox.ts +3 -4
- package/src/browsers/playwright-webkit.ts +3 -4
- package/src/config.ts +17 -0
- package/src/constants.ts +1 -0
- package/src/file-system.spec.ts +1 -1
- package/src/http.ts +4 -0
- package/src/routes/chromium/http/json-list.ts +50 -0
- package/src/routes/chromium/http/json-new.ts +50 -0
- package/src/routes/chromium/http/json-protocol-get.ts +38 -0
- package/src/routes/chromium/http/json-version-get.ts +55 -0
- package/src/routes/chromium/tests/json-version.spec.ts +52 -0
- package/src/routes/chromium/utils/cdp.ts +19 -0
- package/src/types.ts +38 -0
- package/src/utils.ts +26 -4
- package/static/docs/swagger.json +404 -10
- package/static/function/client.js +2328 -1975
- package/browser.json +0 -7
- package/scripts/install-cdp-json.js +0 -37
package/build/types.d.ts
CHANGED
|
@@ -438,4 +438,35 @@ export interface IBrowserlessStats {
|
|
|
438
438
|
unhealthy: number;
|
|
439
439
|
units: number;
|
|
440
440
|
}
|
|
441
|
+
export interface CDPJSONPayload {
|
|
442
|
+
/**
|
|
443
|
+
* The description of the target. Generally the page's title.
|
|
444
|
+
*/
|
|
445
|
+
description: string;
|
|
446
|
+
/**
|
|
447
|
+
* The fully-qualified URL of the Devtools inspector app.
|
|
448
|
+
*/
|
|
449
|
+
devtoolsFrontendUrl: string;
|
|
450
|
+
/**
|
|
451
|
+
* A Unique Id for the underlying target.
|
|
452
|
+
*/
|
|
453
|
+
id: string;
|
|
454
|
+
/**
|
|
455
|
+
* The title of the target. For pages this is the page's title.
|
|
456
|
+
*/
|
|
457
|
+
title: string;
|
|
458
|
+
/**
|
|
459
|
+
* The type of target, generally "page" or "background_page".
|
|
460
|
+
*/
|
|
461
|
+
type: string;
|
|
462
|
+
/**
|
|
463
|
+
* The current URL the target is consuming or visiting.
|
|
464
|
+
*/
|
|
465
|
+
url: string;
|
|
466
|
+
/**
|
|
467
|
+
* The target or page's WebSocket Debugger URL. Primarily used for legacy
|
|
468
|
+
* libraries to connect and inspect or remote automate this target.
|
|
469
|
+
*/
|
|
470
|
+
webSocketDebuggerUrl: string;
|
|
471
|
+
}
|
|
441
472
|
export {};
|
package/build/utils.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ export declare const tsExtension = ".d.ts";
|
|
|
12
12
|
export declare const jsonExtension = ".json";
|
|
13
13
|
export declare const jsExtension = ".js";
|
|
14
14
|
export declare const id: () => string;
|
|
15
|
+
/**
|
|
16
|
+
* Generates a random, Chrome-compliant page ID with "BLESS"
|
|
17
|
+
* prepended. This prepended text signals to other parts of the
|
|
18
|
+
* system that this is a Browserless-created ID so it can be appropriately
|
|
19
|
+
* handled.
|
|
20
|
+
*
|
|
21
|
+
* @returns {string} A random Page ID
|
|
22
|
+
*/
|
|
23
|
+
export declare const pageID: () => string;
|
|
15
24
|
export declare const createLogger: (domain: string) => debug.Debugger;
|
|
16
25
|
export declare const dedent: (strings: string | string[], ...values: string[]) => string;
|
|
17
26
|
export declare const isConnected: (connection: Duplex | ServerResponse) => boolean;
|
package/build/utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
|
-
import { CDPChromium, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, codes, contentTypes, encodings, encryptionAlgo, encryptionSep, } from '@browserless.io/browserless';
|
|
2
|
+
import { BLESS_PAGE_IDENTIFIER, CDPChromium, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, codes, contentTypes, encodings, encryptionAlgo, encryptionSep, } from '@browserless.io/browserless';
|
|
3
3
|
import playwright from 'playwright-core';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import debug from 'debug';
|
|
@@ -26,6 +26,21 @@ export const tsExtension = '.d.ts';
|
|
|
26
26
|
export const jsonExtension = '.json';
|
|
27
27
|
export const jsExtension = '.js';
|
|
28
28
|
export const id = () => crypto.randomUUID();
|
|
29
|
+
/**
|
|
30
|
+
* Generates a random, Chrome-compliant page ID with "BLESS"
|
|
31
|
+
* prepended. This prepended text signals to other parts of the
|
|
32
|
+
* system that this is a Browserless-created ID so it can be appropriately
|
|
33
|
+
* handled.
|
|
34
|
+
*
|
|
35
|
+
* @returns {string} A random Page ID
|
|
36
|
+
*/
|
|
37
|
+
export const pageID = () => {
|
|
38
|
+
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
39
|
+
const id = Array.from({ length: 32 - BLESS_PAGE_IDENTIFIER.length })
|
|
40
|
+
.map(() => chars[Math.floor(Math.random() * chars.length)])
|
|
41
|
+
.join('');
|
|
42
|
+
return `${BLESS_PAGE_IDENTIFIER}${id}`;
|
|
43
|
+
};
|
|
29
44
|
export const createLogger = (domain) => {
|
|
30
45
|
return debug(`browserless.io:${domain}`);
|
|
31
46
|
};
|
|
@@ -158,7 +173,7 @@ export const removeNullStringify = (json, allowNull = true) => {
|
|
|
158
173
|
return value;
|
|
159
174
|
if (value !== null)
|
|
160
175
|
return value;
|
|
161
|
-
});
|
|
176
|
+
}, ' ');
|
|
162
177
|
};
|
|
163
178
|
export const jsonOrString = (maybeJson) => safeParse(maybeJson) ?? maybeJson;
|
|
164
179
|
export const readBody = async (req) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserless.io/browserless",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"license": "SSPL",
|
|
5
5
|
"description": "The browserless platform",
|
|
6
6
|
"author": "browserless.io",
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
"dev": "npm run build:dev && env-cmd -f .env node build",
|
|
26
26
|
"install:adblock": "node scripts/install-adblock.js",
|
|
27
27
|
"install:browsers": "npx --yes playwright install chromium firefox webkit",
|
|
28
|
-
"install:
|
|
29
|
-
"install:dev": "npm run install:browsers && npm run install:cdp-json",
|
|
28
|
+
"install:dev": "npm run install:browsers",
|
|
30
29
|
"lint": "eslint . --ext .ts --fix",
|
|
31
30
|
"prepack": "npm run build:dev",
|
|
32
31
|
"prettier": "prettier '{src,functions,scripts,bin,external,bin}/**/*.{js,ts,json}' --log-level error --write",
|
|
@@ -43,7 +42,6 @@
|
|
|
43
42
|
"scripts/*",
|
|
44
43
|
"src/*",
|
|
45
44
|
"static/*",
|
|
46
|
-
"browser.json",
|
|
47
45
|
"CHANGELOG.md",
|
|
48
46
|
"tsconfig.json"
|
|
49
47
|
],
|
|
@@ -51,18 +49,18 @@
|
|
|
51
49
|
"debug": "^4.3.2",
|
|
52
50
|
"del": "^7.0.0",
|
|
53
51
|
"enjoi": "^9.0.1",
|
|
54
|
-
"file-type": "^
|
|
52
|
+
"file-type": "^19.0.0",
|
|
55
53
|
"get-port": "^7.0.0",
|
|
56
54
|
"gradient-string": "^2.0.0",
|
|
57
55
|
"http-proxy": "^1.18.1",
|
|
58
56
|
"lighthouse": "^11.1.0",
|
|
59
57
|
"micromatch": "^4.0.4",
|
|
60
|
-
"playwright-core": "^1.
|
|
61
|
-
"puppeteer-core": "^21.
|
|
58
|
+
"playwright-core": "^1.41.2",
|
|
59
|
+
"puppeteer-core": "^21.10.0",
|
|
62
60
|
"puppeteer-extra": "^3.3.6",
|
|
63
61
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
64
62
|
"queue": "^7.0.0",
|
|
65
|
-
"systeminformation": "^5.21.
|
|
63
|
+
"systeminformation": "^5.21.24"
|
|
66
64
|
},
|
|
67
65
|
"optionalDependencies": {
|
|
68
66
|
"@types/chai": "^4.3.11",
|
|
@@ -71,25 +69,24 @@
|
|
|
71
69
|
"@types/http-proxy": "^1.17.14",
|
|
72
70
|
"@types/micromatch": "^4.0.6",
|
|
73
71
|
"@types/mocha": "^10.0.6",
|
|
74
|
-
"@types/node": "^20.
|
|
75
|
-
"@types/sinon": "^17.0.
|
|
76
|
-
"@typescript-eslint/eslint-plugin": "^6.
|
|
77
|
-
"@typescript-eslint/parser": "^6.
|
|
72
|
+
"@types/node": "^20.11.16",
|
|
73
|
+
"@types/sinon": "^17.0.3",
|
|
74
|
+
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
|
75
|
+
"@typescript-eslint/parser": "^6.21.0",
|
|
78
76
|
"assert": "^2.0.0",
|
|
79
|
-
"chai": "^5.0.
|
|
77
|
+
"chai": "^5.0.3",
|
|
80
78
|
"cross-env": "^7.0.3",
|
|
81
79
|
"env-cmd": "^10.1.0",
|
|
82
|
-
"esbuild": "^0.
|
|
80
|
+
"esbuild": "^0.20.0",
|
|
83
81
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
|
84
82
|
"eslint": "^8.56.0",
|
|
85
83
|
"eslint-plugin-import": "^2.29.1",
|
|
86
84
|
"eslint-plugin-typescript-sort-keys": "^3.1.0",
|
|
87
85
|
"extract-zip": "^2.0.1",
|
|
88
|
-
"marked": "^
|
|
86
|
+
"marked": "^12.0.0",
|
|
89
87
|
"mocha": "^10.0.0",
|
|
90
88
|
"move-file": "^3.1.0",
|
|
91
|
-
"
|
|
92
|
-
"prettier": "^3.1.1",
|
|
89
|
+
"prettier": "^3.2.5",
|
|
93
90
|
"sinon": "^17.0.1",
|
|
94
91
|
"ts-node": "^10.9.2",
|
|
95
92
|
"typescript": "^5.3.3",
|
|
@@ -144,7 +141,7 @@
|
|
|
144
141
|
"multiple",
|
|
145
142
|
"single"
|
|
146
143
|
],
|
|
147
|
-
"allowSeparatedGroups":
|
|
144
|
+
"allowSeparatedGroups": true
|
|
148
145
|
}
|
|
149
146
|
]
|
|
150
147
|
}
|
package/src/browserless.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
BrowserHTTPRoute,
|
|
4
4
|
BrowserManager,
|
|
5
5
|
BrowserWebsocketRoute,
|
|
6
|
+
CDPChromium,
|
|
6
7
|
Config,
|
|
7
8
|
FileSystem,
|
|
8
9
|
HTTPRoute,
|
|
@@ -11,6 +12,9 @@ import {
|
|
|
11
12
|
Limiter,
|
|
12
13
|
Metrics,
|
|
13
14
|
Monitoring,
|
|
15
|
+
PlaywrightChromium,
|
|
16
|
+
PlaywrightFirefox,
|
|
17
|
+
PlaywrightWebkit,
|
|
14
18
|
Router,
|
|
15
19
|
Token,
|
|
16
20
|
WebHooks,
|
|
@@ -158,6 +162,12 @@ export class Browserless {
|
|
|
158
162
|
public async start() {
|
|
159
163
|
const httpRoutes: Array<HTTPRoute | BrowserHTTPRoute> = [];
|
|
160
164
|
const wsRoutes: Array<WebSocketRoute | BrowserWebsocketRoute> = [];
|
|
165
|
+
const internalBrowsers = [
|
|
166
|
+
CDPChromium,
|
|
167
|
+
PlaywrightFirefox,
|
|
168
|
+
PlaywrightChromium,
|
|
169
|
+
PlaywrightWebkit,
|
|
170
|
+
];
|
|
161
171
|
|
|
162
172
|
const [[httpRouteFiles, wsRouteFiles], installedBrowsers] =
|
|
163
173
|
await Promise.all([getRouteFiles(this.config), availableBrowsers]);
|
|
@@ -254,11 +264,12 @@ export class Browserless {
|
|
|
254
264
|
}
|
|
255
265
|
}
|
|
256
266
|
|
|
257
|
-
// Validate that
|
|
267
|
+
// Validate that we have the browsers they are asking for
|
|
258
268
|
[...httpRoutes, ...wsRoutes].forEach((route) => {
|
|
259
269
|
if (
|
|
260
270
|
'browser' in route &&
|
|
261
271
|
route.browser &&
|
|
272
|
+
internalBrowsers.includes(route.browser) &&
|
|
262
273
|
!installedBrowsers.some((b) => b.name === route.browser?.name)
|
|
263
274
|
) {
|
|
264
275
|
throw new Error(
|
|
@@ -314,17 +314,16 @@ export class CDPChromium extends EventEmitter {
|
|
|
314
314
|
|
|
315
315
|
public wsEndpoint = (): string | null => this.browserWSEndpoint;
|
|
316
316
|
|
|
317
|
-
public publicWSEndpoint = (token: string): string | null => {
|
|
317
|
+
public publicWSEndpoint = (token: string | null): string | null => {
|
|
318
318
|
if (!this.browserWSEndpoint) {
|
|
319
319
|
return null;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
const serverURL = new URL(this.config.getExternalWebSocketAddress());
|
|
322
323
|
const wsURL = new URL(this.browserWSEndpoint);
|
|
323
|
-
const serverURL = new URL(this.config.getExternalAddress());
|
|
324
|
-
|
|
325
324
|
wsURL.hostname = serverURL.hostname;
|
|
326
325
|
wsURL.port = serverURL.port;
|
|
327
|
-
|
|
326
|
+
|
|
328
327
|
if (token) {
|
|
329
328
|
wsURL.searchParams.set('token', token);
|
|
330
329
|
}
|
|
@@ -345,16 +344,14 @@ export class CDPChromium extends EventEmitter {
|
|
|
345
344
|
}
|
|
346
345
|
socket.once('close', resolve);
|
|
347
346
|
|
|
348
|
-
this.debug(
|
|
349
|
-
`Proxying ${req.parsed.href} to browser ${this.browserWSEndpoint}`,
|
|
350
|
-
);
|
|
351
|
-
|
|
352
347
|
const [page] = await this.browser.pages();
|
|
353
348
|
const pageLocation = `/devtools/page/${this.getPageId(page)}`;
|
|
354
349
|
|
|
355
350
|
this.debug(`Proxying ${req.parsed.href} to page "${pageLocation}"`);
|
|
356
351
|
|
|
357
|
-
|
|
352
|
+
const target = new URL(pageLocation, this.browserWSEndpoint).href;
|
|
353
|
+
|
|
354
|
+
req.url = '';
|
|
358
355
|
|
|
359
356
|
this.proxy.ws(
|
|
360
357
|
req,
|
|
@@ -362,7 +359,7 @@ export class CDPChromium extends EventEmitter {
|
|
|
362
359
|
head,
|
|
363
360
|
{
|
|
364
361
|
changeOrigin: true,
|
|
365
|
-
target
|
|
362
|
+
target,
|
|
366
363
|
},
|
|
367
364
|
(error) => {
|
|
368
365
|
this.debug(`Error proxying session: ${error}`);
|
package/src/browsers/index.ts
CHANGED
|
@@ -82,6 +82,69 @@ export class BrowserManager {
|
|
|
82
82
|
return dataDirPath;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
public getProtocolJSON = async (): Promise<object> => {
|
|
86
|
+
this.debug(`Launching Chrome to generate /json/protocol results`);
|
|
87
|
+
const browser = new CDPChromium({
|
|
88
|
+
blockAds: false,
|
|
89
|
+
config: this.config,
|
|
90
|
+
record: false,
|
|
91
|
+
userDataDir: null,
|
|
92
|
+
});
|
|
93
|
+
await browser.launch();
|
|
94
|
+
const wsEndpoint = browser.wsEndpoint();
|
|
95
|
+
|
|
96
|
+
if (!wsEndpoint) {
|
|
97
|
+
throw new Error('There was an error launching the browser');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { port } = new URL(wsEndpoint);
|
|
101
|
+
const res = await fetch(`http://127.0.0.1:${port}/json/protocol`);
|
|
102
|
+
const protocolJSON = await res.json();
|
|
103
|
+
|
|
104
|
+
browser.close();
|
|
105
|
+
|
|
106
|
+
return protocolJSON;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
public getVersionJSON = async (): Promise<{
|
|
110
|
+
Browser: string;
|
|
111
|
+
'Debugger-Version': string;
|
|
112
|
+
'Protocol-Version': string;
|
|
113
|
+
'User-Agent': string;
|
|
114
|
+
'V8-Version': string;
|
|
115
|
+
'WebKit-Version': string;
|
|
116
|
+
webSocketDebuggerUrl: string;
|
|
117
|
+
}> => {
|
|
118
|
+
this.debug(`Launching Chrome to generate /json/version results`);
|
|
119
|
+
const browser = new CDPChromium({
|
|
120
|
+
blockAds: false,
|
|
121
|
+
config: this.config,
|
|
122
|
+
record: false,
|
|
123
|
+
userDataDir: null,
|
|
124
|
+
});
|
|
125
|
+
await browser.launch();
|
|
126
|
+
const wsEndpoint = browser.wsEndpoint();
|
|
127
|
+
|
|
128
|
+
if (!wsEndpoint) {
|
|
129
|
+
throw new Error('There was an error launching the browser');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { port } = new URL(wsEndpoint);
|
|
133
|
+
const res = await fetch(`http://127.0.0.1:${port}/json/version`);
|
|
134
|
+
const meta = await res.json();
|
|
135
|
+
|
|
136
|
+
browser.close();
|
|
137
|
+
|
|
138
|
+
const { 'WebKit-Version': webkitVersion } = meta;
|
|
139
|
+
const debuggerVersion = webkitVersion.match(/\s\(@(\b[0-9a-f]{5,40}\b)/)[1];
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
...meta,
|
|
143
|
+
'Debugger-Version': debuggerVersion,
|
|
144
|
+
webSocketDebuggerUrl: this.config.getExternalWebSocketAddress(),
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
85
148
|
private generateSessionJson = async (
|
|
86
149
|
browser: BrowserInstance,
|
|
87
150
|
session: BrowserlessSession,
|
|
@@ -201,8 +264,8 @@ export class BrowserManager {
|
|
|
201
264
|
if (req.parsed.pathname.includes('/devtools/browser')) {
|
|
202
265
|
const sessions = Array.from(this.browsers);
|
|
203
266
|
const id = req.parsed.pathname.split('/').pop() as string;
|
|
204
|
-
const browser = sessions.find(
|
|
205
|
-
|
|
267
|
+
const browser = sessions.find(([b]) =>
|
|
268
|
+
b.wsEndpoint()?.includes(req.parsed.pathname),
|
|
206
269
|
);
|
|
207
270
|
|
|
208
271
|
if (browser) {
|
|
@@ -106,17 +106,16 @@ export class PlaywrightChromium extends EventEmitter {
|
|
|
106
106
|
|
|
107
107
|
public wsEndpoint = (): string | null => this.browserWSEndpoint;
|
|
108
108
|
|
|
109
|
-
public publicWSEndpoint = (token: string): string | null => {
|
|
109
|
+
public publicWSEndpoint = (token: string | null): string | null => {
|
|
110
110
|
if (!this.browserWSEndpoint) {
|
|
111
111
|
return null;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
const serverURL = new URL(this.config.getExternalWebSocketAddress());
|
|
114
115
|
const wsURL = new URL(this.browserWSEndpoint);
|
|
115
|
-
const serverURL = new URL(this.config.getExternalAddress());
|
|
116
|
-
|
|
117
116
|
wsURL.hostname = serverURL.hostname;
|
|
118
117
|
wsURL.port = serverURL.port;
|
|
119
|
-
|
|
118
|
+
|
|
120
119
|
if (token) {
|
|
121
120
|
wsURL.searchParams.set('token', token);
|
|
122
121
|
}
|
|
@@ -99,17 +99,16 @@ export class PlaywrightFirefox extends EventEmitter {
|
|
|
99
99
|
|
|
100
100
|
public wsEndpoint = (): string | null => this.browserWSEndpoint;
|
|
101
101
|
|
|
102
|
-
public publicWSEndpoint = (token: string): string | null => {
|
|
102
|
+
public publicWSEndpoint = (token: string | null): string | null => {
|
|
103
103
|
if (!this.browserWSEndpoint) {
|
|
104
104
|
return null;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
const serverURL = new URL(this.config.getExternalWebSocketAddress());
|
|
107
108
|
const wsURL = new URL(this.browserWSEndpoint);
|
|
108
|
-
const serverURL = new URL(this.config.getExternalAddress());
|
|
109
|
-
|
|
110
109
|
wsURL.hostname = serverURL.hostname;
|
|
111
110
|
wsURL.port = serverURL.port;
|
|
112
|
-
|
|
111
|
+
|
|
113
112
|
if (token) {
|
|
114
113
|
wsURL.searchParams.set('token', token);
|
|
115
114
|
}
|
|
@@ -99,17 +99,16 @@ export class PlaywrightWebkit extends EventEmitter {
|
|
|
99
99
|
|
|
100
100
|
public wsEndpoint = (): string | null => this.browserWSEndpoint;
|
|
101
101
|
|
|
102
|
-
public publicWSEndpoint = (token: string): string | null => {
|
|
102
|
+
public publicWSEndpoint = (token: string | null): string | null => {
|
|
103
103
|
if (!this.browserWSEndpoint) {
|
|
104
104
|
return null;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
const serverURL = new URL(this.config.getExternalWebSocketAddress());
|
|
107
108
|
const wsURL = new URL(this.browserWSEndpoint);
|
|
108
|
-
const serverURL = new URL(this.config.getExternalAddress());
|
|
109
|
-
|
|
110
109
|
wsURL.hostname = serverURL.hostname;
|
|
111
110
|
wsURL.port = serverURL.port;
|
|
112
|
-
|
|
111
|
+
|
|
113
112
|
if (token) {
|
|
114
113
|
wsURL.searchParams.set('token', token);
|
|
115
114
|
}
|
package/src/config.ts
CHANGED
|
@@ -409,6 +409,23 @@ export class Config extends EventEmitter {
|
|
|
409
409
|
public getExternalAddress = (): string =>
|
|
410
410
|
this.external ?? this.getServerAddress();
|
|
411
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Returns the the fully-qualified WebSocket URL for the
|
|
414
|
+
* external address that browserless might be
|
|
415
|
+
* running behind *or* the server address if
|
|
416
|
+
* no external URL is provided.
|
|
417
|
+
*
|
|
418
|
+
* @returns {string} The URL to reach the server
|
|
419
|
+
*/
|
|
420
|
+
public getExternalWebSocketAddress = (): string => {
|
|
421
|
+
const httpAddress = new URL(this.external ?? this.getServerAddress());
|
|
422
|
+
httpAddress.protocol = httpAddress.protocol.startsWith('https')
|
|
423
|
+
? 'wss:'
|
|
424
|
+
: 'ws:';
|
|
425
|
+
|
|
426
|
+
return httpAddress.href;
|
|
427
|
+
};
|
|
428
|
+
|
|
412
429
|
/**
|
|
413
430
|
* When CORS is enabled, returns relevant CORS headers
|
|
414
431
|
* to requests and for the OPTIONS call. Values can be
|
package/src/constants.ts
CHANGED
package/src/file-system.spec.ts
CHANGED
package/src/http.ts
CHANGED
|
@@ -92,6 +92,10 @@ export enum HTTPRoutes {
|
|
|
92
92
|
content = '/content',
|
|
93
93
|
download = '/download',
|
|
94
94
|
function = '/function',
|
|
95
|
+
jsonList = '/json/list',
|
|
96
|
+
jsonNew = '/json/new',
|
|
97
|
+
jsonProtocol = '/json/protocol',
|
|
98
|
+
jsonVersion = '/json/version',
|
|
95
99
|
pdf = '/pdf',
|
|
96
100
|
performance = '/performance',
|
|
97
101
|
scrape = '/scrape',
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APITags,
|
|
3
|
+
HTTPRoute,
|
|
4
|
+
HTTPRoutes,
|
|
5
|
+
Methods,
|
|
6
|
+
Request,
|
|
7
|
+
Response,
|
|
8
|
+
contentTypes,
|
|
9
|
+
dedent,
|
|
10
|
+
jsonResponse,
|
|
11
|
+
} from '@browserless.io/browserless';
|
|
12
|
+
import { getCDPJSONPayload } from '../utils/cdp.js';
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
Example Payload from Chrome:
|
|
16
|
+
[{
|
|
17
|
+
"description": "",
|
|
18
|
+
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/6CA38A3E207BA534C674D1057B19E9CC",
|
|
19
|
+
"id": "6CA38A3E207BA534C674D1057B19E9CC",
|
|
20
|
+
"title": "New Tab",
|
|
21
|
+
"type": "page",
|
|
22
|
+
"url": "http://localhost:9222/json/list",
|
|
23
|
+
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/6CA38A3E207BA534C674D1057B19E9CC"
|
|
24
|
+
}]
|
|
25
|
+
*/
|
|
26
|
+
export type ResponseSchema = Array<ReturnType<typeof getCDPJSONPayload>>;
|
|
27
|
+
|
|
28
|
+
export default class GetJSONList extends HTTPRoute {
|
|
29
|
+
accepts = [contentTypes.any];
|
|
30
|
+
auth = true;
|
|
31
|
+
browser = null;
|
|
32
|
+
concurrency = false;
|
|
33
|
+
contentTypes = [contentTypes.json];
|
|
34
|
+
description = dedent(`
|
|
35
|
+
Returns a JSON payload that acts as a pass-through to the DevTools /json/list HTTP API in Chromium.
|
|
36
|
+
Browserless mocks this payload so that remote clients can connect to the underlying "webSocketDebuggerUrl"
|
|
37
|
+
which will cause Browserless to start the browser and proxy that request into a blank page.
|
|
38
|
+
`);
|
|
39
|
+
method = Methods.get;
|
|
40
|
+
path = HTTPRoutes.jsonList;
|
|
41
|
+
tags = [APITags.browserAPI];
|
|
42
|
+
|
|
43
|
+
handler = async (_req: Request, res: Response): Promise<void> => {
|
|
44
|
+
const config = this.config();
|
|
45
|
+
const externalAddress = config.getExternalAddress();
|
|
46
|
+
const payload = getCDPJSONPayload(externalAddress);
|
|
47
|
+
|
|
48
|
+
return jsonResponse(res, 200, [payload] as ResponseSchema);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APITags,
|
|
3
|
+
HTTPRoute,
|
|
4
|
+
HTTPRoutes,
|
|
5
|
+
Methods,
|
|
6
|
+
Request,
|
|
7
|
+
Response,
|
|
8
|
+
contentTypes,
|
|
9
|
+
dedent,
|
|
10
|
+
jsonResponse,
|
|
11
|
+
} from '@browserless.io/browserless';
|
|
12
|
+
import { getCDPJSONPayload } from '../utils/cdp.js';
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
Example Payload from Chrome:
|
|
16
|
+
{
|
|
17
|
+
"description": "",
|
|
18
|
+
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/2F76525C32A916DF30C4F37A4970B8BF",
|
|
19
|
+
"id": "2F76525C32A916DF30C4F37A4970B8BF",
|
|
20
|
+
"title": "",
|
|
21
|
+
"type": "page",
|
|
22
|
+
"url": "about:blank",
|
|
23
|
+
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/2F76525C32A916DF30C4F37A4970B8BF"
|
|
24
|
+
}
|
|
25
|
+
*/
|
|
26
|
+
export type ResponseSchema = ReturnType<typeof getCDPJSONPayload>;
|
|
27
|
+
|
|
28
|
+
export default class GetJSONList extends HTTPRoute {
|
|
29
|
+
accepts = [contentTypes.any];
|
|
30
|
+
auth = true;
|
|
31
|
+
browser = null;
|
|
32
|
+
concurrency = false;
|
|
33
|
+
contentTypes = [contentTypes.json];
|
|
34
|
+
description = dedent(`
|
|
35
|
+
Returns a JSON payload that acts as a pass-through to the DevTools /json/list HTTP API in Chromium.
|
|
36
|
+
Browserless mocks this payload so that remote clients can connect to the underlying "webSocketDebuggerUrl"
|
|
37
|
+
which will cause Browserless to start the browser and proxy that request into a blank page.
|
|
38
|
+
`);
|
|
39
|
+
method = Methods.put;
|
|
40
|
+
path = HTTPRoutes.jsonNew;
|
|
41
|
+
tags = [APITags.browserAPI];
|
|
42
|
+
|
|
43
|
+
handler = async (_req: Request, res: Response): Promise<void> => {
|
|
44
|
+
const config = this.config();
|
|
45
|
+
const externalAddress = config.getExternalAddress();
|
|
46
|
+
const payload = getCDPJSONPayload(externalAddress);
|
|
47
|
+
|
|
48
|
+
return jsonResponse(res, 200, payload);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APITags,
|
|
3
|
+
HTTPRoute,
|
|
4
|
+
HTTPRoutes,
|
|
5
|
+
Methods,
|
|
6
|
+
Request,
|
|
7
|
+
Response,
|
|
8
|
+
contentTypes,
|
|
9
|
+
jsonResponse,
|
|
10
|
+
} from '@browserless.io/browserless';
|
|
11
|
+
|
|
12
|
+
// @TODO Figure out how to parse the Protocol JSON into a TS definition
|
|
13
|
+
// for our openapi docs.
|
|
14
|
+
export type ResponseSchema = object;
|
|
15
|
+
|
|
16
|
+
export default class GetJSONVersion extends HTTPRoute {
|
|
17
|
+
accepts = [contentTypes.any];
|
|
18
|
+
auth = true;
|
|
19
|
+
browser = null;
|
|
20
|
+
concurrency = false;
|
|
21
|
+
contentTypes = [contentTypes.json];
|
|
22
|
+
description = `Returns Protocol JSON meta-data that Chrome comes with.`;
|
|
23
|
+
method = Methods.get;
|
|
24
|
+
path = HTTPRoutes.jsonProtocol;
|
|
25
|
+
tags = [APITags.browserAPI];
|
|
26
|
+
|
|
27
|
+
private cachedProtocol: object | undefined;
|
|
28
|
+
|
|
29
|
+
handler = async (_req: Request, res: Response): Promise<void> => {
|
|
30
|
+
const browserManager = this.browserManager();
|
|
31
|
+
|
|
32
|
+
if (!this.cachedProtocol) {
|
|
33
|
+
this.cachedProtocol = await browserManager.getProtocolJSON();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return jsonResponse(res, 200, this.cachedProtocol);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APITags,
|
|
3
|
+
BrowserManager,
|
|
4
|
+
HTTPRoute,
|
|
5
|
+
HTTPRoutes,
|
|
6
|
+
Methods,
|
|
7
|
+
Request,
|
|
8
|
+
Response,
|
|
9
|
+
UnwrapPromise,
|
|
10
|
+
contentTypes,
|
|
11
|
+
jsonResponse,
|
|
12
|
+
writeResponse,
|
|
13
|
+
} from '@browserless.io/browserless';
|
|
14
|
+
|
|
15
|
+
export type ResponseSchema = UnwrapPromise<
|
|
16
|
+
ReturnType<BrowserManager['getVersionJSON']>
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
export default class GetJSONVersion extends HTTPRoute {
|
|
20
|
+
accepts = [contentTypes.any];
|
|
21
|
+
auth = true;
|
|
22
|
+
browser = null;
|
|
23
|
+
concurrency = false;
|
|
24
|
+
contentTypes = [contentTypes.json];
|
|
25
|
+
description = `Returns a JSON payload that acts as a pass-through to the DevTools /json/version protocol in Chrome.`;
|
|
26
|
+
method = Methods.get;
|
|
27
|
+
path = HTTPRoutes.jsonVersion;
|
|
28
|
+
tags = [APITags.browserAPI];
|
|
29
|
+
|
|
30
|
+
private cachedJSON: ResponseSchema | undefined;
|
|
31
|
+
|
|
32
|
+
handler = async (req: Request, res: Response): Promise<void> => {
|
|
33
|
+
const baseUrl = req.parsed.host;
|
|
34
|
+
const protocol = req.parsed.protocol.includes('s') ? 'wss' : 'ws';
|
|
35
|
+
const browserManager = this.browserManager();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (!this.cachedJSON) {
|
|
39
|
+
this.cachedJSON = {
|
|
40
|
+
...(await browserManager.getVersionJSON()),
|
|
41
|
+
webSocketDebuggerUrl: `${protocol}://${baseUrl}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return jsonResponse(res, 200, this.cachedJSON);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return writeResponse(
|
|
48
|
+
res,
|
|
49
|
+
500,
|
|
50
|
+
'There was an error handling your request',
|
|
51
|
+
contentTypes.text,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|