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/lib/constants.js CHANGED
@@ -21,7 +21,7 @@
21
21
  * Source.
22
22
  */
23
23
 
24
- const BROWSER_ARGS = [
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 BROWSER_PATHS = {
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
- BROWSER_ARGS,
152
- BROWSER_PATHS
173
+ CHROMIUM_ARGS,
174
+ CHROMIUM_PATHS,
175
+ FIREFOX_PATHS
153
176
  };
@@ -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
+ }