single-file-cli 2.8.0 → 2.9.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 +8 -2
- package/build-dev.sh +3 -1
- package/build.sh +6 -3
- package/deno.json +1 -1
- package/lib/bidi-client.js +1221 -0
- package/lib/bidi.js +141 -0
- package/lib/browser.js +14 -209
- package/lib/cdp-client.js +11 -11
- package/lib/chromium.js +225 -0
- package/lib/constants.js +27 -4
- package/lib/deno-polyfill.js +5 -0
- package/lib/firefox.js +182 -0
- package/lib/single-file-archive.js +5 -5
- package/lib/single-file-bundle.js +1 -1
- package/lib/version.js +1 -1
- package/options.js +19 -2
- package/package.json +2 -1
- package/single-file-cli-api.js +11 -5
- package/single-file-launcher.js +3 -3
- package/test/e2e/automation-detection.test.js +4 -3
- package/test/e2e/password.test.js +47 -0
- package/test/e2e/proxy-auth.test.js +53 -0
- package/test/e2e/service-worker.test.js +2 -2
- package/test/fidelity/browser.js +3 -3
- package/test/target.js +5 -1
package/lib/constants.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Source.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const CHROMIUM_ARGS = [
|
|
25
25
|
"--disable-field-trial-config",
|
|
26
26
|
"--disable-background-networking",
|
|
27
27
|
"--enable-features=NetworkService,NetworkServiceInProcess",
|
|
@@ -54,7 +54,7 @@ const BROWSER_ARGS = [
|
|
|
54
54
|
"--mute-audio",
|
|
55
55
|
"--deny-permission-prompts"
|
|
56
56
|
];
|
|
57
|
-
const
|
|
57
|
+
const CHROMIUM_PATHS = {
|
|
58
58
|
linux: [
|
|
59
59
|
"/usr/bin/chromium",
|
|
60
60
|
"/usr/bin/chromium-beta",
|
|
@@ -146,8 +146,31 @@ const BROWSER_PATHS = {
|
|
|
146
146
|
"C:\\Program Files (x86)\\Yandex\\YandexBrowser\\Application\\browser.exe"
|
|
147
147
|
]
|
|
148
148
|
};
|
|
149
|
+
const FIREFOX_PATHS = {
|
|
150
|
+
linux: [
|
|
151
|
+
"/usr/bin/firefox",
|
|
152
|
+
"/usr/bin/firefox-esr",
|
|
153
|
+
"/usr/bin/firefox-developer-edition",
|
|
154
|
+
"/usr/bin/firefox-nightly",
|
|
155
|
+
"/usr/lib/firefox/firefox",
|
|
156
|
+
"/snap/bin/firefox",
|
|
157
|
+
"/opt/firefox/firefox"
|
|
158
|
+
],
|
|
159
|
+
darwin: [
|
|
160
|
+
"/Applications/Firefox.app/Contents/MacOS/firefox",
|
|
161
|
+
"/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox",
|
|
162
|
+
"/Applications/Firefox Nightly.app/Contents/MacOS/firefox"
|
|
163
|
+
],
|
|
164
|
+
windows: [
|
|
165
|
+
"C:\\Program Files\\Mozilla Firefox\\firefox.exe",
|
|
166
|
+
"C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe",
|
|
167
|
+
"C:\\Program Files\\Firefox Developer Edition\\firefox.exe",
|
|
168
|
+
"C:\\Program Files\\Firefox Nightly\\firefox.exe"
|
|
169
|
+
]
|
|
170
|
+
};
|
|
149
171
|
|
|
150
172
|
export {
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
CHROMIUM_ARGS,
|
|
174
|
+
CHROMIUM_PATHS,
|
|
175
|
+
FIREFOX_PATHS
|
|
153
176
|
};
|
package/lib/deno-polyfill.js
CHANGED
|
@@ -39,6 +39,10 @@ const NODE_MODULES = {
|
|
|
39
39
|
|
|
40
40
|
const args = DENO_RUNTIME_DETECTED ? Deno.args : process.argv.slice(2);
|
|
41
41
|
|
|
42
|
+
const env = {
|
|
43
|
+
get: DENO_RUNTIME_DETECTED ? name => Deno.env.get(name) : name => process.env[name]
|
|
44
|
+
};
|
|
45
|
+
|
|
42
46
|
const stdout = {
|
|
43
47
|
write: DENO_RUNTIME_DETECTED ? data => Deno.stdout.write(data) : data => process.stdout.write(data)
|
|
44
48
|
};
|
|
@@ -99,6 +103,7 @@ const Command = DENO_RUNTIME_DETECTED ? Deno.Command : class Command {
|
|
|
99
103
|
|
|
100
104
|
const DenoAPI = {
|
|
101
105
|
args,
|
|
106
|
+
env,
|
|
102
107
|
readFile,
|
|
103
108
|
readTextFile,
|
|
104
109
|
readDir,
|
package/lib/firefox.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2010-2026 Gildas Lormeau
|
|
3
|
+
* contact : gildas.lormeau <at> gmail.com
|
|
4
|
+
*
|
|
5
|
+
* This file is part of SingleFile.
|
|
6
|
+
*
|
|
7
|
+
* The code in this file is free software: you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU Affero General Public License
|
|
9
|
+
* (GNU AGPL) as published by the Free Software Foundation, either version 3
|
|
10
|
+
* of the License, or (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* The code in this file is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
15
|
+
* General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* As additional permission under GNU AGPL version 3 section 7, you may
|
|
18
|
+
* distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
|
|
19
|
+
* AGPL normally required by section 4, provided you include this license
|
|
20
|
+
* notice and a URL through which recipients can access the Corresponding
|
|
21
|
+
* Source.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* global URL, setTimeout */
|
|
25
|
+
|
|
26
|
+
import { FIREFOX_PATHS } from "./constants.js";
|
|
27
|
+
import { copyProfile, spawnBrowser, getDebugPort } from "./browser.js";
|
|
28
|
+
import { Deno, path } from "./deno-polyfill.js";
|
|
29
|
+
|
|
30
|
+
const READY_TIMEOUT = 30000;
|
|
31
|
+
const READY_RETRY_DELAY = 250;
|
|
32
|
+
const SERVER_FILE_NAME = "WebDriverBiDiServer.json";
|
|
33
|
+
const PROFILE_PREFS = {
|
|
34
|
+
"app.update.auto": false,
|
|
35
|
+
"app.update.disabledForTesting": true,
|
|
36
|
+
"browser.aboutwelcome.enabled": false,
|
|
37
|
+
"browser.newtabpage.enabled": false,
|
|
38
|
+
"browser.sessionstore.resume_from_crash": false,
|
|
39
|
+
"browser.shell.checkDefaultBrowser": false,
|
|
40
|
+
"browser.startup.homepage": "about:blank",
|
|
41
|
+
"browser.startup.page": 0,
|
|
42
|
+
"browser.tabs.warnOnClose": false,
|
|
43
|
+
"datareporting.policy.dataSubmissionEnabled": false,
|
|
44
|
+
"dom.disable_beforeunload": true,
|
|
45
|
+
"media.autoplay.default": 0,
|
|
46
|
+
"media.volume_scale": "0.0",
|
|
47
|
+
"toolkit.telemetry.reportingpolicy.firstRun": false
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const { build, makeTempDir, readTextFile, writeTextFile, errors, remove } = Deno;
|
|
51
|
+
const { join } = path;
|
|
52
|
+
let child, profilePath, childExited;
|
|
53
|
+
|
|
54
|
+
export { launchFirefox, closeFirefox, hasFirefoxExited, getFirefoxOptions };
|
|
55
|
+
|
|
56
|
+
function getFirefoxOptions(options) {
|
|
57
|
+
return {
|
|
58
|
+
args: options.browserArgs,
|
|
59
|
+
headless: options.browserHeadless,
|
|
60
|
+
executablePath: options.browserExecutablePath,
|
|
61
|
+
debug: options.browserDebug,
|
|
62
|
+
width: options.browserWidth,
|
|
63
|
+
height: options.browserHeight,
|
|
64
|
+
userAgent: options.userAgent,
|
|
65
|
+
httpProxyServer: options.httpProxyServer,
|
|
66
|
+
profile: options.browserProfile
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function launchFirefox(options = {}, indexPath = 0) {
|
|
71
|
+
const executablePath = options.executablePath || FIREFOX_PATHS[build.os][indexPath];
|
|
72
|
+
const debugPort = await getDebugPort();
|
|
73
|
+
profilePath = await makeTempDir();
|
|
74
|
+
if (options.profile) {
|
|
75
|
+
try {
|
|
76
|
+
await copyProfile(options.profile, profilePath);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
await remove(profilePath, { recursive: true }).catch(() => { });
|
|
79
|
+
profilePath = undefined;
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await writeTextFile(join(profilePath, "user.js"), getProfilePrefs(options));
|
|
84
|
+
let args = [
|
|
85
|
+
"--remote-debugging-port=" + debugPort,
|
|
86
|
+
"--profile", profilePath,
|
|
87
|
+
"--no-remote",
|
|
88
|
+
"--new-instance"
|
|
89
|
+
];
|
|
90
|
+
if (options.headless && !options.debug) {
|
|
91
|
+
args.push("--headless");
|
|
92
|
+
}
|
|
93
|
+
if (options.width && options.height) {
|
|
94
|
+
args.push("--width=" + options.width, "--height=" + options.height);
|
|
95
|
+
}
|
|
96
|
+
if (options.args) {
|
|
97
|
+
const argNames = options.args.map(arg => arg.split("=")[0]);
|
|
98
|
+
args = args.filter(arg => !argNames.includes(arg.split("=")[0]));
|
|
99
|
+
args.push(...options.args);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
child = await spawnBrowser(executablePath, args, profilePath);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
await remove(profilePath, { recursive: true }).catch(() => { });
|
|
105
|
+
profilePath = undefined;
|
|
106
|
+
if (error instanceof errors.NotFound) {
|
|
107
|
+
if (!options.executablePath && indexPath + 1 < FIREFOX_PATHS[build.os].length) {
|
|
108
|
+
return launchFirefox(options, indexPath + 1);
|
|
109
|
+
}
|
|
110
|
+
throw new Error(options.executablePath ?
|
|
111
|
+
`The browser executable was not found at ${JSON.stringify(executablePath)}` :
|
|
112
|
+
"The browser executable was not found, use --browser-executable-path to set its location");
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
child.ref();
|
|
117
|
+
childExited = false;
|
|
118
|
+
child.status.then(() => childExited = true);
|
|
119
|
+
try {
|
|
120
|
+
const { ws_host: host, ws_port: port } = await waitUntilReady(profilePath);
|
|
121
|
+
return { url: "ws://" + host + ":" + port + "/session", timeout: READY_TIMEOUT };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
await closeFirefox();
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function waitUntilReady(profilePath) {
|
|
129
|
+
const timeoutTime = Date.now() + READY_TIMEOUT;
|
|
130
|
+
while (!childExited && Date.now() < timeoutTime) {
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(await readTextFile(join(profilePath, SERVER_FILE_NAME)));
|
|
133
|
+
} catch {
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, READY_RETRY_DELAY));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw new Error(childExited ? "The browser exited unexpectedly" : "The browser is not responding");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getProfilePrefs(options) {
|
|
141
|
+
const prefs = Object.assign({}, PROFILE_PREFS);
|
|
142
|
+
if (options.userAgent) {
|
|
143
|
+
prefs["general.useragent.override"] = options.userAgent;
|
|
144
|
+
}
|
|
145
|
+
if (options.httpProxyServer) {
|
|
146
|
+
const proxyUrl = new URL(options.httpProxyServer.includes("://") ? options.httpProxyServer : "http://" + options.httpProxyServer);
|
|
147
|
+
prefs["network.proxy.type"] = 1;
|
|
148
|
+
prefs["network.proxy.http"] = proxyUrl.hostname;
|
|
149
|
+
prefs["network.proxy.http_port"] = Number(proxyUrl.port);
|
|
150
|
+
prefs["network.proxy.ssl"] = proxyUrl.hostname;
|
|
151
|
+
prefs["network.proxy.ssl_port"] = Number(proxyUrl.port);
|
|
152
|
+
}
|
|
153
|
+
return Object.entries(prefs)
|
|
154
|
+
.map(([name, value]) => "user_pref(" + JSON.stringify(name) + ", " + JSON.stringify(value) + ");")
|
|
155
|
+
.join("\n") + "\n";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function hasFirefoxExited() {
|
|
159
|
+
return Boolean(childExited);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function closeFirefox() {
|
|
163
|
+
const closedChild = child;
|
|
164
|
+
const closedProfilePath = profilePath;
|
|
165
|
+
child = undefined;
|
|
166
|
+
profilePath = undefined;
|
|
167
|
+
if (closedChild !== undefined) {
|
|
168
|
+
try {
|
|
169
|
+
closedChild.kill();
|
|
170
|
+
} catch {
|
|
171
|
+
// ignored
|
|
172
|
+
}
|
|
173
|
+
await closedChild.status;
|
|
174
|
+
}
|
|
175
|
+
if (closedProfilePath !== undefined) {
|
|
176
|
+
try {
|
|
177
|
+
await remove(closedProfilePath, { recursive: true });
|
|
178
|
+
} catch {
|
|
179
|
+
console.log("Warning: failed to remove profile directory: " + closedProfilePath); // eslint-disable-line no-console
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|