arn-browser 0.1.30 → 0.1.32
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/bin/cli.js +7 -3
- package/bin/install.js +65 -191
- package/package.json +2 -1
- package/src/utility/playwright/findBrowserPath.js +211 -0
- package/src/utility/playwright/pwLaunch.js +11 -71
- package/src/utility/proxy-utility/proxy-chain.js +96 -79
- package/src/utility/puppeteer/deleteDirectory.js +104 -0
- package/src/utility/puppeteer/findBrowserPath.js +211 -0
- package/src/utility/puppeteer/ppLaunch.js +20 -37
- package/src/utility/puppeteer/routes/ppRoute.js +3 -0
- /package/src/utility/{deleteDirectory.js → playwright/deleteDirectory.js} +0 -0
package/bin/cli.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file CLI for arn-browser
|
|
5
|
-
* @description Provides `npx arn-browser install` to download
|
|
5
|
+
* @description Provides `npx arn-browser install` to download Camoufox and scan OS browsers.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* npx arn-browser install - Install
|
|
8
|
+
* npx arn-browser install - Install Camoufox, clean stale folders, scan OS browsers
|
|
9
9
|
* npx arn-browser help - Show help
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -19,8 +19,12 @@ function showHelp() {
|
|
|
19
19
|
npx arn-browser <command>
|
|
20
20
|
|
|
21
21
|
Commands:
|
|
22
|
-
install
|
|
22
|
+
install Install Camoufox, clean up old browser folders, and scan for OS-installed browsers
|
|
23
23
|
help Show this help message
|
|
24
|
+
|
|
25
|
+
Note:
|
|
26
|
+
Chrome, Chromium, Firefox, and Brave are detected from your OS installation.
|
|
27
|
+
Only Camoufox needs to be downloaded via this installer.
|
|
24
28
|
`);
|
|
25
29
|
}
|
|
26
30
|
|
package/bin/install.js
CHANGED
|
@@ -1,44 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file install.js
|
|
3
3
|
* @description Cross-platform browser installer for arn-browser.
|
|
4
|
-
* Downloads and extracts
|
|
4
|
+
* Downloads and extracts Camoufox to ~/.cache/camoufox
|
|
5
|
+
* Cleans up stale browser folders from previous versions.
|
|
6
|
+
* Scans for OS-installed browsers and reports status.
|
|
5
7
|
*
|
|
6
8
|
* Works on Linux, macOS, and Windows.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
|
-
// ==========================================================================
|
|
10
|
-
// INSTALL TOGGLES — set to false to skip a browser
|
|
11
|
-
// ==========================================================================
|
|
12
|
-
const INSTALL_BRAVE = true;
|
|
13
|
-
const INSTALL_CAMOUFOX = true;
|
|
14
|
-
const INSTALL_CHROMIUM = true;
|
|
15
|
-
const INSTALL_FIREFOX = true;
|
|
16
|
-
|
|
17
11
|
import fs from "fs";
|
|
18
12
|
import path from "path";
|
|
19
13
|
import os from "os";
|
|
20
14
|
import { execSync } from "child_process";
|
|
21
15
|
import { createWriteStream } from "fs";
|
|
22
16
|
|
|
17
|
+
import { checkBrowserAvailability } from "../src/utility/playwright/findBrowserPath.js";
|
|
18
|
+
|
|
23
19
|
// ==========================================================================
|
|
24
|
-
// CONFIGURATION
|
|
20
|
+
// CONFIGURATION
|
|
25
21
|
// ==========================================================================
|
|
26
|
-
// OS identifiers used in our dictionaries
|
|
27
|
-
// process.platform: 'win32', 'darwin', 'linux'
|
|
28
|
-
// os.arch(): 'x64', 'arm64'
|
|
29
|
-
|
|
30
|
-
const BRAVE_URLS = {
|
|
31
|
-
linux: {
|
|
32
|
-
x64: "https://github.com/brave/brave-browser/releases/download/v1.88.134/brave-browser-1.88.134-linux-amd64.zip",
|
|
33
|
-
arm64: "https://github.com/brave/brave-browser/releases/download/v1.88.134/brave-browser-1.88.134-linux-arm64.zip",
|
|
34
|
-
},
|
|
35
|
-
darwin: {
|
|
36
|
-
arm64: "https://github.com/brave/brave-browser/releases/download/v1.88.134/Brave-Browser-arm64.dmg",
|
|
37
|
-
},
|
|
38
|
-
win32: {
|
|
39
|
-
x64: "https://github.com/brave/brave-browser/releases/download/v1.88.134/brave-v1.88.134-win32-x64.zip",
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
22
|
|
|
43
23
|
const CAM_URLS = {
|
|
44
24
|
linux: {
|
|
@@ -53,42 +33,6 @@ const CAM_URLS = {
|
|
|
53
33
|
}
|
|
54
34
|
};
|
|
55
35
|
|
|
56
|
-
// Chromium x64 uses Chrome for Testing (CFT) URLs with browserVersion.
|
|
57
|
-
// Chromium arm64 uses legacy revision-based URLs.
|
|
58
|
-
// CDN: cdn.playwright.dev
|
|
59
|
-
const CHROMIUM_REVISION = "1214";
|
|
60
|
-
const CHROMIUM_BROWSER_VERSION = "146.0.7680.31";
|
|
61
|
-
|
|
62
|
-
const CHROMIUM_URLS = {
|
|
63
|
-
linux: {
|
|
64
|
-
x64: `https://cdn.playwright.dev/builds/cft/${CHROMIUM_BROWSER_VERSION}/linux64/chrome-linux64.zip`,
|
|
65
|
-
|
|
66
|
-
arm64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/chromium/${CHROMIUM_REVISION}/chromium-linux-arm64.zip`,
|
|
67
|
-
},
|
|
68
|
-
darwin: {
|
|
69
|
-
arm64: `https://cdn.playwright.dev/builds/cft/${CHROMIUM_BROWSER_VERSION}/mac-arm64/chrome-mac-arm64.zip`,
|
|
70
|
-
},
|
|
71
|
-
win32: {
|
|
72
|
-
x64: `https://cdn.playwright.dev/builds/cft/${CHROMIUM_BROWSER_VERSION}/win64/chrome-win64.zip`,
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Firefox uses revision-based URLs on the new CDN.
|
|
77
|
-
const FIREFOX_REVISION = "1509";
|
|
78
|
-
|
|
79
|
-
const FIREFOX_URLS = {
|
|
80
|
-
linux: {
|
|
81
|
-
x64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-ubuntu-22.04.zip`,
|
|
82
|
-
arm64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-ubuntu-22.04-arm64.zip`,
|
|
83
|
-
},
|
|
84
|
-
darwin: {
|
|
85
|
-
arm64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-mac-arm64.zip`,
|
|
86
|
-
},
|
|
87
|
-
win32: {
|
|
88
|
-
x64: `https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/${FIREFOX_REVISION}/firefox-win64.zip`,
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
36
|
const CAM_VERSION = { version: "135.0.1", release: "beta.24" };
|
|
93
37
|
|
|
94
38
|
// ==========================================================================
|
|
@@ -96,25 +40,26 @@ const CAM_VERSION = { version: "135.0.1", release: "beta.24" };
|
|
|
96
40
|
// ==========================================================================
|
|
97
41
|
|
|
98
42
|
const ARN_BROWSERS_DIR = path.join(os.homedir(), ".arn-browser", "browsers");
|
|
99
|
-
const BRAVE_DIR = path.join(ARN_BROWSERS_DIR, "brave");
|
|
100
|
-
const CHROMIUM_DIR = path.join(ARN_BROWSERS_DIR, "chromium");
|
|
101
|
-
const FIREFOX_DIR = path.join(ARN_BROWSERS_DIR, "firefox");
|
|
102
43
|
const CAM_DIR = path.join(os.homedir(), ".cache", "camoufox");
|
|
103
44
|
const TEMP_DIR = path.join(os.tmpdir(), "arn-browser-install");
|
|
104
45
|
|
|
46
|
+
// Stale folders from previous versions that downloaded browsers
|
|
47
|
+
const STALE_BROWSER_DIRS = [
|
|
48
|
+
path.join(ARN_BROWSERS_DIR, "brave"),
|
|
49
|
+
path.join(ARN_BROWSERS_DIR, "chromium"),
|
|
50
|
+
path.join(ARN_BROWSERS_DIR, "firefox"),
|
|
51
|
+
];
|
|
52
|
+
|
|
105
53
|
// ==========================================================================
|
|
106
54
|
// HELPERS
|
|
107
55
|
// ==========================================================================
|
|
108
56
|
|
|
109
57
|
function getArch() {
|
|
110
|
-
const arch = os.arch();
|
|
58
|
+
const arch = os.arch();
|
|
111
59
|
if (arch === "x64" || arch === "arm64") return arch;
|
|
112
60
|
throw new Error(`❌ Unsupported architecture: ${arch}`);
|
|
113
61
|
}
|
|
114
62
|
|
|
115
|
-
/**
|
|
116
|
-
* Download a file with redirect following (GitHub releases use 302 redirects).
|
|
117
|
-
*/
|
|
118
63
|
function formatBytes(bytes) {
|
|
119
64
|
if (!Number.isFinite(bytes) || bytes < 0) return "0 B";
|
|
120
65
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
@@ -181,9 +126,6 @@ async function downloadFile(url, destPath, label) {
|
|
|
181
126
|
process.stdout.write("\n");
|
|
182
127
|
}
|
|
183
128
|
|
|
184
|
-
/**
|
|
185
|
-
* Cross-platform unzip.
|
|
186
|
-
*/
|
|
187
129
|
function unzipFile(zipPath, destDir) {
|
|
188
130
|
const isWindows = process.platform === "win32";
|
|
189
131
|
|
|
@@ -194,38 +136,12 @@ function unzipFile(zipPath, destDir) {
|
|
|
194
136
|
}
|
|
195
137
|
}
|
|
196
138
|
|
|
197
|
-
/**
|
|
198
|
-
* If the extracted zip contains a single top-level directory,
|
|
199
|
-
* move its contents up to destDir (flatten).
|
|
200
|
-
*/
|
|
201
|
-
function flattenExtract(destDir) {
|
|
202
|
-
const entries = fs.readdirSync(destDir, { withFileTypes: true });
|
|
203
|
-
if (entries.length === 1 && entries[0].isDirectory()) {
|
|
204
|
-
const subDir = path.join(destDir, entries[0].name);
|
|
205
|
-
// Rename subfolder to a temp name to avoid name collisions
|
|
206
|
-
// (e.g. firefox/firefox where the dir and executable share a name)
|
|
207
|
-
const tmpDir = subDir + "__tmp";
|
|
208
|
-
fs.renameSync(subDir, tmpDir);
|
|
209
|
-
const subEntries = fs.readdirSync(tmpDir);
|
|
210
|
-
for (const item of subEntries) {
|
|
211
|
-
fs.renameSync(path.join(tmpDir, item), path.join(destDir, item));
|
|
212
|
-
}
|
|
213
|
-
fs.rmdirSync(tmpDir);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Recursively delete a directory.
|
|
219
|
-
*/
|
|
220
139
|
function rmDir(dirPath) {
|
|
221
140
|
if (fs.existsSync(dirPath)) {
|
|
222
141
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
223
142
|
}
|
|
224
143
|
}
|
|
225
144
|
|
|
226
|
-
/**
|
|
227
|
-
* Set executable permission (no-op on Windows).
|
|
228
|
-
*/
|
|
229
145
|
function setExecutable(dirPath, filename) {
|
|
230
146
|
if (process.platform === "win32") return;
|
|
231
147
|
|
|
@@ -235,9 +151,6 @@ function setExecutable(dirPath, filename) {
|
|
|
235
151
|
}
|
|
236
152
|
}
|
|
237
153
|
|
|
238
|
-
/**
|
|
239
|
-
* Recursively find files by name.
|
|
240
|
-
*/
|
|
241
154
|
function findFiles(dir, name) {
|
|
242
155
|
const results = [];
|
|
243
156
|
if (!fs.existsSync(dir)) return results;
|
|
@@ -254,48 +167,15 @@ function findFiles(dir, name) {
|
|
|
254
167
|
return results;
|
|
255
168
|
}
|
|
256
169
|
|
|
257
|
-
/**
|
|
258
|
-
* Helper to get the correct URL for the current platform and architecture.
|
|
259
|
-
*/
|
|
260
170
|
function getDownloadUrl(urlMap, osName, arch) {
|
|
261
171
|
if (!urlMap[osName]) return null;
|
|
262
172
|
return urlMap[osName][arch] || null;
|
|
263
173
|
}
|
|
264
174
|
|
|
265
|
-
|
|
266
175
|
// ==========================================================================
|
|
267
|
-
//
|
|
176
|
+
// CAMOUFOX INSTALLER
|
|
268
177
|
// ==========================================================================
|
|
269
178
|
|
|
270
|
-
async function installBrave(osName, arch) {
|
|
271
|
-
const url = getDownloadUrl(BRAVE_URLS, osName, arch);
|
|
272
|
-
if (!url) {
|
|
273
|
-
console.log(`⚠️ Brave not available for ${osName} ${arch}`);
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Clean previous install
|
|
278
|
-
rmDir(BRAVE_DIR);
|
|
279
|
-
|
|
280
|
-
const ext = url.endsWith(".dmg") ? ".dmg" : ".zip";
|
|
281
|
-
const dlPath = path.join(TEMP_DIR, `brave${ext}`);
|
|
282
|
-
|
|
283
|
-
console.log("⬇️ Downloading Brave...");
|
|
284
|
-
await downloadFile(url, dlPath, "Brave");
|
|
285
|
-
|
|
286
|
-
fs.mkdirSync(BRAVE_DIR, { recursive: true });
|
|
287
|
-
|
|
288
|
-
console.log(`📦 Extracting Brave to ${BRAVE_DIR}...`);
|
|
289
|
-
if (ext === ".zip") {
|
|
290
|
-
unzipFile(dlPath, BRAVE_DIR);
|
|
291
|
-
flattenExtract(BRAVE_DIR);
|
|
292
|
-
} else if (ext === ".dmg") {
|
|
293
|
-
console.log(`⚠️ Brave downloaded as DMG to ${dlPath}. Automatic extraction of DMG is not fully supported in this script. Recommend manual installation.`);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
console.log("✅ Brave installed!");
|
|
297
|
-
}
|
|
298
|
-
|
|
299
179
|
async function installCamoufox(osName, arch) {
|
|
300
180
|
const url = getDownloadUrl(CAM_URLS, osName, arch);
|
|
301
181
|
if (!url) {
|
|
@@ -329,62 +209,53 @@ async function installCamoufox(osName, arch) {
|
|
|
329
209
|
console.log("✅ Camoufox installed!");
|
|
330
210
|
}
|
|
331
211
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
console.log(`⚠️ Chromium not available for ${osName} ${arch}`);
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Clean previous install
|
|
340
|
-
rmDir(CHROMIUM_DIR);
|
|
341
|
-
|
|
342
|
-
const ext = url.endsWith(".dmg") ? ".dmg" : ".zip";
|
|
343
|
-
const dlPath = path.join(TEMP_DIR, `chromium${ext}`);
|
|
344
|
-
|
|
345
|
-
console.log("⬇️ Downloading Chromium...");
|
|
346
|
-
await downloadFile(url, dlPath, "Chromium");
|
|
347
|
-
|
|
348
|
-
fs.mkdirSync(CHROMIUM_DIR, { recursive: true });
|
|
212
|
+
// ==========================================================================
|
|
213
|
+
// STALE FOLDER CLEANUP
|
|
214
|
+
// ==========================================================================
|
|
349
215
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
216
|
+
function cleanupStaleBrowserFolders() {
|
|
217
|
+
let cleaned = false;
|
|
218
|
+
for (const dir of STALE_BROWSER_DIRS) {
|
|
219
|
+
if (fs.existsSync(dir)) {
|
|
220
|
+
console.log(`🗑️ Removing stale browser folder: ${dir}`);
|
|
221
|
+
rmDir(dir);
|
|
222
|
+
cleaned = true;
|
|
223
|
+
}
|
|
356
224
|
}
|
|
357
225
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
226
|
+
// Also clean up the parent browsers dir if it's now empty
|
|
227
|
+
if (fs.existsSync(ARN_BROWSERS_DIR)) {
|
|
228
|
+
try {
|
|
229
|
+
const remaining = fs.readdirSync(ARN_BROWSERS_DIR);
|
|
230
|
+
if (remaining.length === 0) {
|
|
231
|
+
fs.rmdirSync(ARN_BROWSERS_DIR);
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
// Non-critical
|
|
235
|
+
}
|
|
366
236
|
}
|
|
367
237
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const dlPath = path.join(TEMP_DIR, `firefox${ext}`);
|
|
238
|
+
if (!cleaned) {
|
|
239
|
+
console.log(" No stale browser folders found.");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
373
242
|
|
|
374
|
-
|
|
375
|
-
|
|
243
|
+
// ==========================================================================
|
|
244
|
+
// OS BROWSER SCAN
|
|
245
|
+
// ==========================================================================
|
|
376
246
|
|
|
377
|
-
|
|
247
|
+
function scanOsBrowsers() {
|
|
248
|
+
console.log("\n🔍 Scanning for OS-installed browsers...\n");
|
|
378
249
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
250
|
+
const browsers = ["chrome", "chromium", "firefox", "brave"];
|
|
251
|
+
for (const name of browsers) {
|
|
252
|
+
const result = checkBrowserAvailability(name);
|
|
253
|
+
if (result.found) {
|
|
254
|
+
console.log(` ✅ ${name.padEnd(10)} → ${result.path}`);
|
|
255
|
+
} else {
|
|
256
|
+
console.log(` ❌ ${name.padEnd(10)} → Not found`);
|
|
257
|
+
}
|
|
385
258
|
}
|
|
386
|
-
|
|
387
|
-
console.log("✅ Firefox installed!");
|
|
388
259
|
}
|
|
389
260
|
|
|
390
261
|
// ==========================================================================
|
|
@@ -398,23 +269,26 @@ export async function installBrowsers() {
|
|
|
398
269
|
console.log(`\n🚀 arn-browser: Installing browser binaries`);
|
|
399
270
|
console.log(` Platform: ${osName} | Architecture: ${arch}\n`);
|
|
400
271
|
|
|
401
|
-
//
|
|
272
|
+
// 1. Clean up stale browser folders from previous versions
|
|
273
|
+
console.log("🧹 Cleaning up old downloaded browser folders...");
|
|
274
|
+
cleanupStaleBrowserFolders();
|
|
275
|
+
|
|
276
|
+
// 2. Prepare temp directory
|
|
402
277
|
fs.mkdirSync(TEMP_DIR, { recursive: true });
|
|
403
278
|
|
|
404
279
|
try {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (INSTALL_CHROMIUM) await installChromium(osName, arch);
|
|
408
|
-
if (INSTALL_FIREFOX) await installFirefox(osName, arch);
|
|
280
|
+
// 3. Install Camoufox
|
|
281
|
+
await installCamoufox(osName, arch);
|
|
409
282
|
} finally {
|
|
410
283
|
// Cleanup temp directory
|
|
411
284
|
rmDir(TEMP_DIR);
|
|
412
285
|
}
|
|
413
286
|
|
|
287
|
+
// 4. Scan for OS-installed browsers
|
|
288
|
+
scanOsBrowsers();
|
|
289
|
+
|
|
414
290
|
console.log(`\n🎉 Done!`);
|
|
415
|
-
console.log(` Brave: ${BRAVE_DIR}`);
|
|
416
291
|
console.log(` Camoufox: ${CAM_DIR}`);
|
|
417
|
-
console.log(`
|
|
418
|
-
console.log(
|
|
419
|
-
console.log(` Version: ${CAM_DIR}/version.json\n`);
|
|
292
|
+
console.log(` Version: ${CAM_DIR}/version.json`);
|
|
293
|
+
console.log(`\n Other browsers (Chrome, Chromium, Firefox, Brave) use your OS installation.\n`);
|
|
420
294
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arn-browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "A lightweight, browser autmation helper.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@ghostery/adblocker": "^2.13.0",
|
|
17
17
|
"arn-knexjs": "^0.0.3",
|
|
18
18
|
"camoufox-js": "^0.9.3",
|
|
19
|
+
"devtools-detector": "^2.0.25",
|
|
19
20
|
"dotenv": "^17.2.3",
|
|
20
21
|
"fingerprint-generator": "^2.1.78",
|
|
21
22
|
"fingerprint-injector": "^2.1.78",
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file findBrowserPath.js
|
|
3
|
+
* @description Cross-platform utility to find OS-installed browser executables.
|
|
4
|
+
* Supports: chrome, chromium, firefox, brave
|
|
5
|
+
* Works on: Linux (x64/arm64), macOS (arm64), Windows (x64)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
|
|
12
|
+
// ==========================================================================
|
|
13
|
+
// KNOWN BINARY PATHS PER PLATFORM
|
|
14
|
+
// ==========================================================================
|
|
15
|
+
|
|
16
|
+
const BROWSER_PATHS = {
|
|
17
|
+
chrome: {
|
|
18
|
+
linux: [
|
|
19
|
+
"google-chrome-stable",
|
|
20
|
+
"google-chrome",
|
|
21
|
+
"/opt/google/chrome/google-chrome",
|
|
22
|
+
"/usr/bin/google-chrome-stable",
|
|
23
|
+
"/usr/bin/google-chrome",
|
|
24
|
+
],
|
|
25
|
+
darwin: [
|
|
26
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
27
|
+
],
|
|
28
|
+
win32: [
|
|
29
|
+
path.join(process.env.PROGRAMFILES || "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
|
|
30
|
+
path.join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "Google", "Chrome", "Application", "chrome.exe"),
|
|
31
|
+
path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
chromium: {
|
|
35
|
+
linux: [
|
|
36
|
+
"chromium-browser",
|
|
37
|
+
"chromium",
|
|
38
|
+
"/usr/bin/chromium-browser",
|
|
39
|
+
"/usr/bin/chromium",
|
|
40
|
+
"/snap/bin/chromium",
|
|
41
|
+
],
|
|
42
|
+
darwin: [
|
|
43
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
44
|
+
],
|
|
45
|
+
win32: [
|
|
46
|
+
path.join(process.env.PROGRAMFILES || "C:\\Program Files", "Chromium", "Application", "chrome.exe"),
|
|
47
|
+
path.join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "Chromium", "Application", "chrome.exe"),
|
|
48
|
+
path.join(process.env.LOCALAPPDATA || "", "Chromium", "Application", "chrome.exe"),
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
firefox: {
|
|
52
|
+
linux: [
|
|
53
|
+
"firefox",
|
|
54
|
+
"/usr/bin/firefox",
|
|
55
|
+
"/snap/bin/firefox",
|
|
56
|
+
"/usr/lib/firefox/firefox",
|
|
57
|
+
],
|
|
58
|
+
darwin: [
|
|
59
|
+
"/Applications/Firefox.app/Contents/MacOS/firefox",
|
|
60
|
+
],
|
|
61
|
+
win32: [
|
|
62
|
+
path.join(process.env.PROGRAMFILES || "C:\\Program Files", "Mozilla Firefox", "firefox.exe"),
|
|
63
|
+
path.join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "Mozilla Firefox", "firefox.exe"),
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
brave: {
|
|
67
|
+
linux: [
|
|
68
|
+
"brave-browser",
|
|
69
|
+
"brave-browser-stable",
|
|
70
|
+
"/opt/brave.com/brave/brave-browser",
|
|
71
|
+
"/usr/bin/brave-browser",
|
|
72
|
+
"/usr/bin/brave-browser-stable",
|
|
73
|
+
"/snap/bin/brave",
|
|
74
|
+
],
|
|
75
|
+
darwin: [
|
|
76
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
77
|
+
],
|
|
78
|
+
win32: [
|
|
79
|
+
path.join(process.env.PROGRAMFILES || "C:\\Program Files", "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
80
|
+
path.join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
81
|
+
path.join(process.env.LOCALAPPDATA || "", "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Friendly display names for error messages
|
|
87
|
+
const BROWSER_DISPLAY_NAMES = {
|
|
88
|
+
chrome: "Google Chrome",
|
|
89
|
+
chromium: "Chromium",
|
|
90
|
+
firefox: "Mozilla Firefox",
|
|
91
|
+
brave: "Brave Browser",
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Install hints per platform
|
|
95
|
+
const INSTALL_HINTS = {
|
|
96
|
+
chrome: {
|
|
97
|
+
linux: "sudo apt install google-chrome-stable OR download from https://www.google.com/chrome/",
|
|
98
|
+
darwin: "Download from https://www.google.com/chrome/",
|
|
99
|
+
win32: "Download from https://www.google.com/chrome/",
|
|
100
|
+
},
|
|
101
|
+
chromium: {
|
|
102
|
+
linux: "sudo apt install chromium-browser OR sudo snap install chromium",
|
|
103
|
+
darwin: "brew install --cask chromium OR download from https://www.chromium.org/",
|
|
104
|
+
win32: "Download from https://www.chromium.org/getting-involved/download-chromium/",
|
|
105
|
+
},
|
|
106
|
+
firefox: {
|
|
107
|
+
linux: "sudo apt install firefox OR sudo snap install firefox",
|
|
108
|
+
darwin: "brew install --cask firefox OR download from https://www.mozilla.org/firefox/",
|
|
109
|
+
win32: "Download from https://www.mozilla.org/firefox/",
|
|
110
|
+
},
|
|
111
|
+
brave: {
|
|
112
|
+
linux: "See https://brave.com/linux/ OR sudo apt install brave-browser",
|
|
113
|
+
darwin: "brew install --cask brave-browser OR download from https://brave.com/",
|
|
114
|
+
win32: "Download from https://brave.com/",
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// ==========================================================================
|
|
119
|
+
// DETECTION LOGIC
|
|
120
|
+
// ==========================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Try to resolve a command name using `which` (Unix) or `where` (Windows).
|
|
124
|
+
* Returns the absolute path if found, null otherwise.
|
|
125
|
+
*/
|
|
126
|
+
function resolveFromPath(command) {
|
|
127
|
+
const isWindows = process.platform === "win32";
|
|
128
|
+
try {
|
|
129
|
+
const cmd = isWindows ? `where "${command}"` : `which "${command}"`;
|
|
130
|
+
const result = execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
131
|
+
// `where` on Windows may return multiple lines — take the first
|
|
132
|
+
const firstLine = result.split("\n")[0].trim();
|
|
133
|
+
if (firstLine && fs.existsSync(firstLine)) {
|
|
134
|
+
return firstLine;
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
// Command not found in PATH
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Find the OS-installed executable path for a given browser.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} browserName - One of: "chrome", "chromium", "firefox", "brave"
|
|
146
|
+
* @returns {string} Absolute path to the browser executable
|
|
147
|
+
* @throws {Error} If the browser is not found on the system
|
|
148
|
+
*/
|
|
149
|
+
export function findBrowserPath(browserName) {
|
|
150
|
+
const name = browserName.toLowerCase();
|
|
151
|
+
const candidates = BROWSER_PATHS[name];
|
|
152
|
+
|
|
153
|
+
if (!candidates) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`❌ [findBrowserPath] Unknown browser: "${browserName}"\n` +
|
|
156
|
+
` Supported: chrome, chromium, firefox, brave`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const platform = process.platform; // 'linux', 'darwin', 'win32'
|
|
161
|
+
const paths = candidates[platform];
|
|
162
|
+
|
|
163
|
+
if (!paths || paths.length === 0) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`❌ [findBrowserPath] ${BROWSER_DISPLAY_NAMES[name]} is not supported on platform: ${platform}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Strategy 1: Try PATH lookup first (works for short names like "google-chrome", "firefox")
|
|
170
|
+
for (const candidate of paths) {
|
|
171
|
+
// If it's not an absolute path, try resolving from PATH
|
|
172
|
+
if (!path.isAbsolute(candidate)) {
|
|
173
|
+
const resolved = resolveFromPath(candidate);
|
|
174
|
+
if (resolved) return resolved;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Strategy 2: Check known absolute paths
|
|
179
|
+
for (const candidate of paths) {
|
|
180
|
+
if (path.isAbsolute(candidate) && fs.existsSync(candidate)) {
|
|
181
|
+
return candidate;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Not found — throw descriptive error
|
|
186
|
+
const displayName = BROWSER_DISPLAY_NAMES[name];
|
|
187
|
+
const hint = INSTALL_HINTS[name]?.[platform] || `Install ${displayName} from its official website.`;
|
|
188
|
+
|
|
189
|
+
throw new Error(
|
|
190
|
+
`❌ [findBrowserPath] ${displayName} not found on this system.\n` +
|
|
191
|
+
` Platform: ${platform}\n` +
|
|
192
|
+
` Searched: ${paths.filter(p => path.isAbsolute(p)).join(", ") || "(PATH lookup only)"}\n\n` +
|
|
193
|
+
` To install:\n` +
|
|
194
|
+
` ${hint}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check if a browser is available without throwing.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} browserName - One of: "chrome", "chromium", "firefox", "brave"
|
|
202
|
+
* @returns {{ found: boolean, path: string|null, error: string|null }}
|
|
203
|
+
*/
|
|
204
|
+
export function checkBrowserAvailability(browserName) {
|
|
205
|
+
try {
|
|
206
|
+
const p = findBrowserPath(browserName);
|
|
207
|
+
return { found: true, path: p, error: null };
|
|
208
|
+
} catch (err) {
|
|
209
|
+
return { found: false, path: null, error: err.message };
|
|
210
|
+
}
|
|
211
|
+
}
|