single-file-cli 2.3.1 → 2.4.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/README.MD +9 -0
- package/build.sh +37 -33
- package/deno.json +1 -1
- package/dev-build.sh +15 -14
- package/lib/browser.js +150 -17
- package/lib/cdp-client.js +15 -21
- package/lib/constants.js +0 -1
- package/lib/deno-polyfill.js +42 -0
- package/lib/single-file-archive.js +10 -6
- package/lib/single-file-bundle.js +1 -1
- package/lib/version.js +1 -1
- package/options.js +13 -1
- package/package.json +1 -1
- package/single-file-cli-api.js +2 -2
- package/single-file-launcher.js +14 -2
- package/test/e2e/automation-detection.test.js +103 -0
- package/test/e2e/browser-profile.test.js +67 -0
- package/test/unit/archive-packager.test.js +1 -2
- package/test/unit/browser-profile.test.js +70 -0
- package/test/unit/cli-usage.test.js +15 -0
- package/lib/archive-packager.js +0 -228
package/README.MD
CHANGED
|
@@ -124,6 +124,15 @@ Make sure Chrome or a Chromium-based browser is installed in the default folder.
|
|
|
124
124
|
single-file https://www.wikipedia.org wikipedia.html
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
- Save a page needing a logged-in session. Create the profile first, log in
|
|
128
|
+
to the website in the browser window, and quit the browser to save it. Then
|
|
129
|
+
reuse the profile as many times as needed, it is copied and left unmodified.
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
single-file --create-browser-profile ./profiles/example https://www.example.com/login
|
|
133
|
+
single-file https://www.example.com/account account.html --browser-profile=./profiles/example
|
|
134
|
+
```
|
|
135
|
+
|
|
127
136
|
- Save a list of URLs stored into `list-urls.txt` in the current folder
|
|
128
137
|
|
|
129
138
|
```sh
|
package/build.sh
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
CORE_PACKAGE="npm:single-file-core@1.5.107"
|
|
6
|
+
ESBUILD_PACKAGE="npm:esbuild@0.27.7"
|
|
7
|
+
|
|
8
|
+
project_dir=$(pwd)
|
|
9
|
+
build_dir=$(mktemp -d)
|
|
10
|
+
trap 'rm -rf "$build_dir"' EXIT
|
|
11
|
+
|
|
12
|
+
cd "$build_dir"
|
|
13
|
+
deno install --vendor --quiet --minimum-dependency-age=0 "$CORE_PACKAGE"
|
|
10
14
|
|
|
11
15
|
echo "
|
|
12
|
-
import { build } from '
|
|
16
|
+
import { build } from '$ESBUILD_PACKAGE';
|
|
17
|
+
|
|
18
|
+
const core = '$build_dir/node_modules/single-file-core';
|
|
19
|
+
const lib = '$project_dir/lib';
|
|
13
20
|
|
|
14
21
|
await build({
|
|
15
22
|
entryPoints: [
|
|
16
|
-
'
|
|
23
|
+
core + '/single-file.js'
|
|
17
24
|
],
|
|
18
25
|
bundle: true,
|
|
19
26
|
globalName: 'singlefile',
|
|
20
|
-
outdir:
|
|
27
|
+
outdir: lib,
|
|
21
28
|
platform: 'browser',
|
|
22
29
|
sourcemap: false,
|
|
23
30
|
minify: true,
|
|
@@ -27,11 +34,11 @@ await build({
|
|
|
27
34
|
|
|
28
35
|
await build({
|
|
29
36
|
entryPoints: [
|
|
30
|
-
'
|
|
37
|
+
core + '/single-file-bootstrap.js'
|
|
31
38
|
],
|
|
32
39
|
bundle: true,
|
|
33
40
|
globalName: 'singlefileBootstrap',
|
|
34
|
-
outdir:
|
|
41
|
+
outdir: lib,
|
|
35
42
|
platform: 'browser',
|
|
36
43
|
sourcemap: false,
|
|
37
44
|
minify: true,
|
|
@@ -41,10 +48,10 @@ await build({
|
|
|
41
48
|
|
|
42
49
|
await build({
|
|
43
50
|
entryPoints: [
|
|
44
|
-
'
|
|
51
|
+
core + '/single-file-hooks-frames.js'
|
|
45
52
|
],
|
|
46
53
|
bundle: true,
|
|
47
|
-
outdir:
|
|
54
|
+
outdir: lib,
|
|
48
55
|
platform: 'browser',
|
|
49
56
|
sourcemap: false,
|
|
50
57
|
minify: true,
|
|
@@ -54,11 +61,11 @@ await build({
|
|
|
54
61
|
|
|
55
62
|
await build({
|
|
56
63
|
entryPoints: [
|
|
57
|
-
'
|
|
64
|
+
core + '/vendor/zip/zip.min.js'
|
|
58
65
|
],
|
|
59
66
|
bundle: true,
|
|
60
67
|
globalName: 'zip',
|
|
61
|
-
outdir:
|
|
68
|
+
outdir: lib,
|
|
62
69
|
platform: 'browser',
|
|
63
70
|
sourcemap: false,
|
|
64
71
|
minify: true,
|
|
@@ -67,12 +74,11 @@ await build({
|
|
|
67
74
|
});
|
|
68
75
|
|
|
69
76
|
await build({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
77
|
+
entryPoints: [
|
|
78
|
+
core + '/single-file-archive.js'
|
|
79
|
+
],
|
|
74
80
|
bundle: true,
|
|
75
|
-
outfile: '
|
|
81
|
+
outfile: lib + '/single-file-archive.js',
|
|
76
82
|
platform: 'neutral',
|
|
77
83
|
sourcemap: false,
|
|
78
84
|
minify: true,
|
|
@@ -81,25 +87,23 @@ await build({
|
|
|
81
87
|
});
|
|
82
88
|
|
|
83
89
|
const SCRIPTS = [
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
90
|
+
lib + '/single-file.js',
|
|
91
|
+
lib + '/single-file-bootstrap.js',
|
|
92
|
+
lib + '/zip.min.js'
|
|
87
93
|
];
|
|
88
94
|
|
|
89
95
|
let script = '';
|
|
90
96
|
const scripts = SCRIPTS.map(script => Deno.readTextFile(script));
|
|
91
97
|
const sources = await Promise.all(scripts);
|
|
92
98
|
script += 'const script = ' + JSON.stringify(sources.join(';')) + ';';
|
|
93
|
-
const hookScript = await Deno.readTextFile('
|
|
99
|
+
const hookScript = await Deno.readTextFile(lib + '/single-file-hooks-frames.js');
|
|
94
100
|
script += 'const hookScript = ' + JSON.stringify(hookScript) + ';';
|
|
95
|
-
const zipScript = await Deno.readTextFile('
|
|
101
|
+
const zipScript = await Deno.readTextFile(lib + '/zip.min.js');
|
|
96
102
|
script += 'const zipScript = ' + JSON.stringify(zipScript) + ';';
|
|
97
103
|
script += 'export { script, zipScript, hookScript };';
|
|
98
|
-
await Deno.writeTextFile('
|
|
104
|
+
await Deno.writeTextFile(lib + '/single-file-bundle.js', script)
|
|
99
105
|
await Promise.all(SCRIPTS.map(script => Deno.remove(script)));
|
|
100
|
-
await Deno.remove('
|
|
101
|
-
const version = JSON.parse(await Deno.readTextFile('
|
|
102
|
-
await Deno.writeTextFile('
|
|
103
|
-
" | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock=
|
|
104
|
-
|
|
105
|
-
rm -rf node_modules
|
|
106
|
+
await Deno.remove(lib + '/single-file-hooks-frames.js');
|
|
107
|
+
const version = JSON.parse(await Deno.readTextFile('$project_dir/deno.json')).version;
|
|
108
|
+
await Deno.writeTextFile(lib + '/version.js', 'export const version = ' + JSON.stringify(version) + ';');
|
|
109
|
+
" | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock="$build_dir/build.lock" -
|
package/deno.json
CHANGED
package/dev-build.sh
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
ESBUILD_PACKAGE="npm:esbuild@0.27.7"
|
|
6
|
+
|
|
7
|
+
build_dir=$(mktemp -d)
|
|
8
|
+
trap 'rm -rf "$build_dir"' EXIT
|
|
5
9
|
|
|
6
10
|
echo "
|
|
7
|
-
import { build } from '
|
|
11
|
+
import { build } from '$ESBUILD_PACKAGE';
|
|
8
12
|
|
|
9
13
|
await build({
|
|
10
14
|
entryPoints: [
|
|
11
|
-
'
|
|
15
|
+
'../single-file-core/single-file.js'
|
|
12
16
|
],
|
|
13
17
|
bundle: true,
|
|
14
18
|
globalName: 'singlefile',
|
|
@@ -22,7 +26,7 @@ await build({
|
|
|
22
26
|
|
|
23
27
|
await build({
|
|
24
28
|
entryPoints: [
|
|
25
|
-
'
|
|
29
|
+
'../single-file-core/single-file-bootstrap.js'
|
|
26
30
|
],
|
|
27
31
|
bundle: true,
|
|
28
32
|
globalName: 'singlefileBootstrap',
|
|
@@ -36,7 +40,7 @@ await build({
|
|
|
36
40
|
|
|
37
41
|
await build({
|
|
38
42
|
entryPoints: [
|
|
39
|
-
'
|
|
43
|
+
'../single-file-core/single-file-hooks-frames.js'
|
|
40
44
|
],
|
|
41
45
|
bundle: true,
|
|
42
46
|
outdir: 'lib/',
|
|
@@ -49,7 +53,7 @@ await build({
|
|
|
49
53
|
|
|
50
54
|
await build({
|
|
51
55
|
entryPoints: [
|
|
52
|
-
'
|
|
56
|
+
'../single-file-core/vendor/zip/zip.min.js'
|
|
53
57
|
],
|
|
54
58
|
bundle: true,
|
|
55
59
|
globalName: 'zip',
|
|
@@ -62,10 +66,9 @@ await build({
|
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
await build({
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
+
entryPoints: [
|
|
70
|
+
'../single-file-core/single-file-archive.js'
|
|
71
|
+
],
|
|
69
72
|
bundle: true,
|
|
70
73
|
outfile: 'lib/single-file-archive.js',
|
|
71
74
|
platform: 'neutral',
|
|
@@ -95,6 +98,4 @@ await Promise.all(SCRIPTS.map(script => Deno.remove(script)));
|
|
|
95
98
|
await Deno.remove('lib/single-file-hooks-frames.js');
|
|
96
99
|
const version = JSON.parse(await Deno.readTextFile('./deno.json')).version;
|
|
97
100
|
await Deno.writeTextFile('lib/version.js', 'export const version = ' + JSON.stringify(version) + ';');
|
|
98
|
-
" | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock=
|
|
99
|
-
|
|
100
|
-
rm -rf node_modules
|
|
101
|
+
" | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock="$build_dir/build.lock" -
|
package/lib/browser.js
CHANGED
|
@@ -21,27 +21,90 @@
|
|
|
21
21
|
* Source.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
/* global fetch, setTimeout */
|
|
24
|
+
/* global fetch, setTimeout, WebSocket */
|
|
25
25
|
|
|
26
26
|
import { BROWSER_PATHS, BROWSER_ARGS } from "./constants.js";
|
|
27
|
-
import { Deno } from "./deno-polyfill.js";
|
|
27
|
+
import { Deno, path } from "./deno-polyfill.js";
|
|
28
28
|
|
|
29
29
|
const NULL_STD_CONFIG = "null";
|
|
30
30
|
const DEBUG_PORT_MIN = 9222;
|
|
31
31
|
const DEBUG_PORT_RANGE = 256;
|
|
32
32
|
const READY_TIMEOUT = 30000;
|
|
33
33
|
const READY_RETRY_DELAY = 250;
|
|
34
|
+
const CLOSE_TIMEOUT = 15000;
|
|
35
|
+
const LOCALHOST = "http://localhost:";
|
|
36
|
+
const PROFILE_LOCK_ENTRY_PREFIX = "Singleton";
|
|
37
|
+
const PROFILE_IGNORED_ENTRY_NAMES = [
|
|
38
|
+
"AutofillAiModelCache",
|
|
39
|
+
"Cache",
|
|
40
|
+
"Code Cache",
|
|
41
|
+
"CacheStorage",
|
|
42
|
+
"Crashpad",
|
|
43
|
+
"DawnCache",
|
|
44
|
+
"DawnGraphiteCache",
|
|
45
|
+
"DawnWebGPUCache",
|
|
46
|
+
"GPUCache",
|
|
47
|
+
"GPUPersistentCache",
|
|
48
|
+
"GraphiteDawnCache",
|
|
49
|
+
"GrShaderCache",
|
|
50
|
+
"ShaderCache",
|
|
51
|
+
"component_crx_cache",
|
|
52
|
+
"extensions_crx_cache",
|
|
53
|
+
"optimization_guide_hint_cache_store",
|
|
54
|
+
"optimization_guide_model_store"
|
|
55
|
+
];
|
|
56
|
+
const PROFILE_INCOMPATIBLE_ARGS = [
|
|
57
|
+
"--no-startup-window",
|
|
58
|
+
"--bwsi",
|
|
59
|
+
"--deny-permission-prompts"
|
|
60
|
+
];
|
|
34
61
|
|
|
35
|
-
const { build, makeTempDir, Command, errors, remove } = Deno;
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
const { build, makeTempDir, readDir, copyFile, mkdir, Command, errors, remove } = Deno;
|
|
63
|
+
const { join } = path;
|
|
64
|
+
let child, profilePath, childExited, keepProfilePath, browserDebugPort;
|
|
65
|
+
export { launchBrowser, createBrowserProfile, getBrowserOptions, copyProfile, closeBrowser, browserExited };
|
|
66
|
+
|
|
67
|
+
function getBrowserOptions(options) {
|
|
68
|
+
return {
|
|
69
|
+
args: options.browserArgs,
|
|
70
|
+
headless: options.browserHeadless,
|
|
71
|
+
executablePath: options.browserExecutablePath,
|
|
72
|
+
debug: options.browserDebug,
|
|
73
|
+
singleProcess: options.browserSingleProcess,
|
|
74
|
+
disableWebSecurity: options.browserDisableWebSecurity,
|
|
75
|
+
width: options.browserWidth,
|
|
76
|
+
height: options.browserHeight,
|
|
77
|
+
userAgent: options.userAgent,
|
|
78
|
+
httpProxyServer: options.httpProxyServer,
|
|
79
|
+
profile: options.browserProfile
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function createBrowserProfile(options = {}) {
|
|
84
|
+
await mkdir(options.profile, { recursive: true });
|
|
85
|
+
await launchBrowser(Object.assign({}, options, { headless: false, persistProfile: true }));
|
|
86
|
+
if (child !== undefined) {
|
|
87
|
+
await child.status;
|
|
88
|
+
}
|
|
89
|
+
await closeBrowser();
|
|
90
|
+
}
|
|
38
91
|
|
|
39
92
|
async function launchBrowser(options = {}, indexPath = 0) {
|
|
40
93
|
const executablePath = options.executablePath || BROWSER_PATHS[build.os][indexPath];
|
|
41
94
|
let args = Array.from(BROWSER_ARGS);
|
|
42
95
|
const debugPort = await getDebugPort();
|
|
96
|
+
browserDebugPort = debugPort;
|
|
43
97
|
args.push("--remote-debugging-port=" + debugPort);
|
|
44
|
-
|
|
98
|
+
keepProfilePath = Boolean(options.persistProfile);
|
|
99
|
+
if (keepProfilePath) {
|
|
100
|
+
profilePath = options.profile;
|
|
101
|
+
args = args.filter(arg => !PROFILE_INCOMPATIBLE_ARGS.includes(arg));
|
|
102
|
+
} else {
|
|
103
|
+
profilePath = await makeTempDir();
|
|
104
|
+
if (options.profile) {
|
|
105
|
+
await copyProfile(options.profile, profilePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
45
108
|
if (options.headless && !options.debug) {
|
|
46
109
|
args.push("--headless");
|
|
47
110
|
} else {
|
|
@@ -74,11 +137,16 @@ async function launchBrowser(options = {}, indexPath = 0) {
|
|
|
74
137
|
!args.includes("--headless")) {
|
|
75
138
|
args.push("--disable-site-isolation-trials");
|
|
76
139
|
}
|
|
140
|
+
if (options.startUrl) {
|
|
141
|
+
args.push(options.startUrl);
|
|
142
|
+
}
|
|
77
143
|
const command = new Command(executablePath, { args, stdout: NULL_STD_CONFIG, stderr: NULL_STD_CONFIG });
|
|
78
144
|
try {
|
|
79
145
|
child = await command.spawn();
|
|
80
146
|
} catch (error) {
|
|
81
|
-
|
|
147
|
+
if (!keepProfilePath) {
|
|
148
|
+
await remove(profilePath, { recursive: true }).catch(() => { });
|
|
149
|
+
}
|
|
82
150
|
profilePath = undefined;
|
|
83
151
|
if (error instanceof errors.NotFound) {
|
|
84
152
|
if (!options.executablePath && indexPath + 1 < BROWSER_PATHS[build.os].length) {
|
|
@@ -155,21 +223,86 @@ function getRandomDebugPort() {
|
|
|
155
223
|
}
|
|
156
224
|
|
|
157
225
|
async function closeBrowser() {
|
|
158
|
-
|
|
226
|
+
const closedChild = child;
|
|
227
|
+
const closedProfilePath = profilePath;
|
|
228
|
+
child = undefined;
|
|
229
|
+
profilePath = undefined;
|
|
230
|
+
if (closedChild !== undefined) {
|
|
231
|
+
if (keepProfilePath && !childExited) {
|
|
232
|
+
await closeBrowserGracefully(closedChild);
|
|
233
|
+
}
|
|
159
234
|
try {
|
|
160
|
-
|
|
235
|
+
closedChild.kill();
|
|
161
236
|
} catch {
|
|
162
237
|
// ignored
|
|
163
238
|
}
|
|
164
|
-
await
|
|
165
|
-
child = undefined;
|
|
239
|
+
await closedChild.status;
|
|
166
240
|
}
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
await
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
241
|
+
if (closedProfilePath !== undefined) {
|
|
242
|
+
if (keepProfilePath) {
|
|
243
|
+
await removeProfileLockEntries(closedProfilePath).catch(() => { });
|
|
244
|
+
} else {
|
|
245
|
+
try {
|
|
246
|
+
await remove(closedProfilePath, { recursive: true });
|
|
247
|
+
} catch {
|
|
248
|
+
console.log("Warning: failed to remove profile directory: " + closedProfilePath); // eslint-disable-line no-console
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function closeBrowserGracefully(closedChild) {
|
|
255
|
+
try {
|
|
256
|
+
const response = await fetch(LOCALHOST + browserDebugPort + "/json/version");
|
|
257
|
+
const { webSocketDebuggerUrl } = await response.json();
|
|
258
|
+
const socket = new WebSocket(webSocketDebuggerUrl);
|
|
259
|
+
await new Promise((resolve, reject) => {
|
|
260
|
+
socket.addEventListener("open", resolve);
|
|
261
|
+
socket.addEventListener("error", reject);
|
|
262
|
+
});
|
|
263
|
+
socket.send(JSON.stringify({ id: 0, method: "Browser.close" }));
|
|
264
|
+
await Promise.race([
|
|
265
|
+
closedChild.status,
|
|
266
|
+
new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT))
|
|
267
|
+
]);
|
|
268
|
+
} catch {
|
|
269
|
+
// ignored
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function copyProfile(sourcePath, destinationPath) {
|
|
274
|
+
let entries;
|
|
275
|
+
try {
|
|
276
|
+
entries = await readDir(sourcePath);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw error instanceof errors.NotFound ?
|
|
279
|
+
new Error(`The browser profile directory was not found at ${JSON.stringify(sourcePath)}`) :
|
|
280
|
+
error;
|
|
281
|
+
}
|
|
282
|
+
await copyProfileEntries(entries, sourcePath, destinationPath);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function copyProfileEntries(entries, sourcePath, destinationPath) {
|
|
286
|
+
for (const entry of entries) {
|
|
287
|
+
if (entry.isSymlink || entry.name.startsWith(PROFILE_LOCK_ENTRY_PREFIX) || PROFILE_IGNORED_ENTRY_NAMES.includes(entry.name)) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const entrySourcePath = join(sourcePath, entry.name);
|
|
291
|
+
const entryDestinationPath = join(destinationPath, entry.name);
|
|
292
|
+
if (entry.isDirectory) {
|
|
293
|
+
await mkdir(entryDestinationPath, { recursive: true });
|
|
294
|
+
await copyProfileEntries(await readDir(entrySourcePath), entrySourcePath, entryDestinationPath);
|
|
295
|
+
} else {
|
|
296
|
+
await copyFile(entrySourcePath, entryDestinationPath);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function removeProfileLockEntries(profileDirectory) {
|
|
302
|
+
const entries = await readDir(profileDirectory);
|
|
303
|
+
for (const entry of entries) {
|
|
304
|
+
if (entry.name.startsWith(PROFILE_LOCK_ENTRY_PREFIX)) {
|
|
305
|
+
await remove(join(profileDirectory, entry.name)).catch(() => { });
|
|
173
306
|
}
|
|
174
307
|
}
|
|
175
308
|
}
|
package/lib/cdp-client.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
27
|
launchBrowser,
|
|
28
|
+
getBrowserOptions,
|
|
28
29
|
closeBrowser,
|
|
29
30
|
browserExited
|
|
30
31
|
} from "./browser.js";
|
|
@@ -59,6 +60,7 @@ const SET_PDF_FUNCTION_NAME = "setPDF";
|
|
|
59
60
|
const SET_PAGE_DATA_FUNCTION_NAME = "setPageData";
|
|
60
61
|
const BINDING_CALLED_EVENT_TYPE = "bindingCalled";
|
|
61
62
|
const LOCALHOST = "http://localhost:";
|
|
63
|
+
const HEADLESS_USER_AGENT_TOKEN = "Headless";
|
|
62
64
|
const BROWSER_EXITED_MAX_DELAY = 2000;
|
|
63
65
|
|
|
64
66
|
let browserOptions, relaunchBrowserPromise;
|
|
@@ -73,17 +75,7 @@ async function initialize(singleFileOptions) {
|
|
|
73
75
|
if (singleFileOptions.browserServer) {
|
|
74
76
|
options.apiUrl = singleFileOptions.browserServer;
|
|
75
77
|
} else {
|
|
76
|
-
browserOptions =
|
|
77
|
-
browserOptions.args = singleFileOptions.browserArgs;
|
|
78
|
-
browserOptions.headless = singleFileOptions.browserHeadless;
|
|
79
|
-
browserOptions.executablePath = singleFileOptions.browserExecutablePath;
|
|
80
|
-
browserOptions.debug = singleFileOptions.browserDebug;
|
|
81
|
-
browserOptions.singleProcess = singleFileOptions.browserSingleProcess;
|
|
82
|
-
browserOptions.disableWebSecurity = singleFileOptions.browserDisableWebSecurity;
|
|
83
|
-
browserOptions.width = singleFileOptions.browserWidth;
|
|
84
|
-
browserOptions.height = singleFileOptions.browserHeight;
|
|
85
|
-
browserOptions.userAgent = singleFileOptions.userAgent;
|
|
86
|
-
browserOptions.httpProxyServer = singleFileOptions.httpProxyServer;
|
|
78
|
+
browserOptions = getBrowserOptions(singleFileOptions);
|
|
87
79
|
options.apiUrl = LOCALHOST + (await launchBrowser(browserOptions));
|
|
88
80
|
}
|
|
89
81
|
}
|
|
@@ -221,9 +213,7 @@ async function setupDeviceEmulation({ Browser, Emulation, Runtime }, { options,
|
|
|
221
213
|
if (needsDeviceMetrics) {
|
|
222
214
|
await setupDeviceMetrics({ Emulation, Runtime }, { options, debugMessages });
|
|
223
215
|
}
|
|
224
|
-
|
|
225
|
-
await setupUserAgent({ Browser, Emulation }, { options, debugMessages });
|
|
226
|
-
}
|
|
216
|
+
await setupUserAgent({ Browser, Emulation }, { options, debugMessages });
|
|
227
217
|
}
|
|
228
218
|
|
|
229
219
|
function needsUserAgentOverride(options) {
|
|
@@ -253,9 +243,9 @@ async function setupDeviceMetrics({ Emulation, Runtime }, { options, debugMessag
|
|
|
253
243
|
async function setupUserAgent({ Browser, Emulation }, { options, debugMessages }, sessionId) {
|
|
254
244
|
const ANDROID_PLATFORM = "Android";
|
|
255
245
|
const { userAgent, product } = await Browser.getVersion();
|
|
256
|
-
const defaultMobileUA = `Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) ${product} Mobile Safari/537.36`;
|
|
246
|
+
const defaultMobileUA = `Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) ${removeHeadlessToken(product)} Mobile Safari/537.36`;
|
|
257
247
|
const agentOptions = {
|
|
258
|
-
userAgent: options.userAgent || (options.browserMobileEmulation ? defaultMobileUA : userAgent)
|
|
248
|
+
userAgent: options.userAgent || (options.browserMobileEmulation ? defaultMobileUA : removeHeadlessToken(userAgent))
|
|
259
249
|
};
|
|
260
250
|
if (options.acceptLanguage) {
|
|
261
251
|
agentOptions.acceptLanguage = options.acceptLanguage;
|
|
@@ -263,8 +253,14 @@ async function setupUserAgent({ Browser, Emulation }, { options, debugMessages }
|
|
|
263
253
|
if (options.platform || options.browserMobileEmulation) {
|
|
264
254
|
agentOptions.platform = options.platform || ANDROID_PLATFORM;
|
|
265
255
|
}
|
|
266
|
-
|
|
267
|
-
|
|
256
|
+
if (needsUserAgentOverride(options) || agentOptions.userAgent !== userAgent) {
|
|
257
|
+
logData(["Emulating user agent", JSON.stringify(agentOptions)], { options, debugMessages });
|
|
258
|
+
await Emulation.setUserAgentOverride(agentOptions, sessionId);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function removeHeadlessToken(userAgent) {
|
|
263
|
+
return userAgent.replace(HEADLESS_USER_AGENT_TOKEN, "");
|
|
268
264
|
}
|
|
269
265
|
|
|
270
266
|
async function setupNetworkInterception({ Browser, Emulation, Fetch, Network }, { options, debugMessages, httpInfo, blockedURLPatterns }) {
|
|
@@ -481,9 +477,7 @@ async function setupFrameSession({ Browser, Emulation, Fetch, Network, Security
|
|
|
481
477
|
if (options.emulateMediaFeatures) {
|
|
482
478
|
await setupMediaFeatures({ Emulation }, { options, debugMessages }, sessionId);
|
|
483
479
|
}
|
|
484
|
-
|
|
485
|
-
await setupUserAgent({ Browser, Emulation }, { options, debugMessages }, sessionId);
|
|
486
|
-
}
|
|
480
|
+
await setupUserAgent({ Browser, Emulation }, { options, debugMessages }, sessionId);
|
|
487
481
|
}
|
|
488
482
|
|
|
489
483
|
async function getContextId({ Debugger, Page, Runtime }, { options, debugMessages }) {
|
package/lib/constants.js
CHANGED
package/lib/deno-polyfill.js
CHANGED
|
@@ -101,8 +101,10 @@ const DenoAPI = {
|
|
|
101
101
|
args,
|
|
102
102
|
readFile,
|
|
103
103
|
readTextFile,
|
|
104
|
+
readDir,
|
|
104
105
|
writeTextFile,
|
|
105
106
|
writeFile,
|
|
107
|
+
copyFile,
|
|
106
108
|
mkdir,
|
|
107
109
|
makeTempDir,
|
|
108
110
|
stat,
|
|
@@ -118,6 +120,7 @@ const DenoAPI = {
|
|
|
118
120
|
|
|
119
121
|
const pathAPI = {
|
|
120
122
|
dirname,
|
|
123
|
+
join,
|
|
121
124
|
toFileUrl,
|
|
122
125
|
fromFileUrl,
|
|
123
126
|
SEPARATOR: DENO_RUNTIME_DETECTED ? path.SEPARATOR : path.sep
|
|
@@ -154,6 +157,41 @@ async function readTextFile(path) {
|
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
async function readDir(path) {
|
|
161
|
+
if (DENO_RUNTIME_DETECTED) {
|
|
162
|
+
const entries = [];
|
|
163
|
+
try {
|
|
164
|
+
for await (const entry of Deno.readDir(path)) {
|
|
165
|
+
entries.push({ name: entry.name, isDirectory: entry.isDirectory, isSymlink: entry.isSymlink });
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw mapNotFoundError(error);
|
|
169
|
+
}
|
|
170
|
+
return entries;
|
|
171
|
+
} else {
|
|
172
|
+
const fsPromise = await import(NODE_MODULES["fs"]);
|
|
173
|
+
try {
|
|
174
|
+
const entries = await fsPromise.readdir(path, { withFileTypes: true });
|
|
175
|
+
return entries.map(entry => ({ name: entry.name, isDirectory: entry.isDirectory(), isSymlink: entry.isSymbolicLink() }));
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw mapNotFoundError(error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function copyFile(sourcePath, destinationPath) {
|
|
183
|
+
if (DENO_RUNTIME_DETECTED) {
|
|
184
|
+
return Deno.copyFile(sourcePath, destinationPath);
|
|
185
|
+
} else {
|
|
186
|
+
const fsPromise = await import(NODE_MODULES["fs"]);
|
|
187
|
+
return fsPromise.copyFile(sourcePath, destinationPath);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function mapNotFoundError(error) {
|
|
192
|
+
return error.code == "ENOENT" || error.code == "ENOTDIR" ? new errors.NotFound(error.message) : error;
|
|
193
|
+
}
|
|
194
|
+
|
|
157
195
|
async function writeTextFile(path, data, options = {}) {
|
|
158
196
|
if (DENO_RUNTIME_DETECTED) {
|
|
159
197
|
return Deno.writeTextFile(path, data, options);
|
|
@@ -259,6 +297,10 @@ function dirname(filePath) {
|
|
|
259
297
|
return path.dirname(filePath);
|
|
260
298
|
}
|
|
261
299
|
|
|
300
|
+
function join(...filePaths) {
|
|
301
|
+
return path.join(...filePaths);
|
|
302
|
+
}
|
|
303
|
+
|
|
262
304
|
async function toFileUrl(filePath) {
|
|
263
305
|
if (DENO_RUNTIME_DETECTED) {
|
|
264
306
|
return path.toFileUrl(filePath);
|