arn-browser 0.1.31 → 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 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 browser binaries.
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 all browser binaries (Brave, Camoufox, Chromium, Firefox)
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 Download and install browser binaries (Brave, Camoufox, Chromium, Firefox)
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 Brave, Camoufox, and optionally Chromium to ~/.cache/
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 URLS
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(); // "x64", "arm64", etc.
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
- // BROWSER INSTALLERS
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
- async function installChromium(osName, arch) {
333
- const url = getDownloadUrl(CHROMIUM_URLS, osName, arch);
334
- if (!url) {
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
- console.log(`📦 Extracting Chromium to ${CHROMIUM_DIR}...`);
351
- if (ext === ".zip") {
352
- unzipFile(dlPath, CHROMIUM_DIR);
353
- flattenExtract(CHROMIUM_DIR);
354
- } else {
355
- console.log(`⚠️ Chromium downloaded as DMG to ${dlPath}. Manual extraction required.`);
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
- console.log("✅ Chromium installed!");
359
- }
360
-
361
- async function installFirefox(osName, arch) {
362
- const url = getDownloadUrl(FIREFOX_URLS, osName, arch);
363
- if (!url) {
364
- console.log(`⚠️ Firefox not available for ${osName} ${arch}`);
365
- return;
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
- // Clean previous install
369
- rmDir(FIREFOX_DIR);
370
-
371
- const ext = url.endsWith(".dmg") ? ".dmg" : ".zip";
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
- console.log("⬇️ Downloading Firefox...");
375
- await downloadFile(url, dlPath, "Firefox");
243
+ // ==========================================================================
244
+ // OS BROWSER SCAN
245
+ // ==========================================================================
376
246
 
377
- fs.mkdirSync(FIREFOX_DIR, { recursive: true });
247
+ function scanOsBrowsers() {
248
+ console.log("\n🔍 Scanning for OS-installed browsers...\n");
378
249
 
379
- console.log(`📦 Extracting Firefox to ${FIREFOX_DIR}...`);
380
- if (ext === ".zip") {
381
- unzipFile(dlPath, FIREFOX_DIR);
382
- flattenExtract(FIREFOX_DIR);
383
- } else {
384
- console.log(`⚠️ Firefox downloaded as DMG to ${dlPath}. Manual extraction required.`);
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
- // Prepare temp directory
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
- if (INSTALL_BRAVE) await installBrave(osName, arch);
406
- if (INSTALL_CAMOUFOX) await installCamoufox(osName, arch);
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(` Chromium: ${CHROMIUM_DIR}`);
418
- console.log(` Firefox: ${FIREFOX_DIR}`);
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.31",
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
+ }
@@ -18,7 +18,8 @@ import { FingerprintGenerator } from "fingerprint-generator";
18
18
 
19
19
  // Internal Utilities
20
20
  import { getMultiloginToken } from "../mlx_token.js";
21
- import { deleteDirectoryWithRetries } from "../deleteDirectory.js";
21
+ import { deleteDirectoryWithRetries } from "./deleteDirectory.js";
22
+ import { findBrowserPath } from "./findBrowserPath.js";
22
23
 
23
24
  // Human Cursor - for human-like mouse movements
24
25
  import { createCursor } from "./human-cursor/index.js";
@@ -78,70 +79,8 @@ function resolveProfilePath(nameOrPath, browserName) {
78
79
  return path.join(PERSISTENT_DIR, folderName);
79
80
  }
80
81
 
81
- /**
82
- * Locates binaries for manual browsers (Brave).
83
- * Uses ~/.arn-browser/browsers/{browser}/{executable}
84
- */
85
- function getBinaryPath(browserName) {
86
- const isWindows = process.platform === "win32";
87
- const browsersDir = path.join(ARN_BROWSER_DIR, "browsers");
88
-
89
- const binaryMap = {
90
- brave: isWindows
91
- ? path.join(browsersDir, "brave", "brave.exe")
92
- : path.join(browsersDir, "brave", "brave"),
93
- };
94
-
95
- const binaryPath = binaryMap[browserName];
96
- if (!binaryPath) {
97
- throw new Error(
98
- `░░░░░ [LaunchBrowser] Unknown browser for getBinaryPath: ${browserName}`
99
- );
100
- }
101
-
102
- if (!fs.existsSync(binaryPath)) {
103
- throw new Error(
104
- `░░░░░ [LaunchBrowser] ${browserName} binary not found at: ${binaryPath}\n` +
105
- ` Run "node bin/cli.js install" to install browsers.`
106
- );
107
- }
108
-
109
- return binaryPath;
110
- }
111
-
112
- /**
113
- * Gets the executable path for Playwright-managed browsers (Chromium, Firefox).
114
- * Uses ~/.arn-browser/browsers/{browser}/{executable}
115
- */
116
- function getPlaywrightExePath(browserName) {
117
- const isWindows = process.platform === "win32";
118
- const browsersDir = path.join(ARN_BROWSER_DIR, "browsers");
119
-
120
- const binaryMap = {
121
- chromium: isWindows
122
- ? path.join(browsersDir, "chromium", "chrome.exe")
123
- : path.join(browsersDir, "chromium", "chrome"),
124
- firefox: isWindows
125
- ? path.join(browsersDir, "firefox", "firefox.exe")
126
- : path.join(browsersDir, "firefox", "firefox"),
127
- };
128
-
129
- const binaryPath = binaryMap[browserName];
130
- if (!binaryPath) {
131
- throw new Error(
132
- `░░░░░ [LaunchBrowser] Unknown browser: ${browserName}`
133
- );
134
- }
135
-
136
- if (!fs.existsSync(binaryPath)) {
137
- throw new Error(
138
- `░░░░░ [LaunchBrowser] ${browserName} binary not found at: ${binaryPath}\n` +
139
- ` Run "node bin/cli.js install" to install browsers.`
140
- );
141
- }
142
-
143
- return binaryPath;
144
- }
82
+ // Browser path resolution now uses OS-installed browsers via findBrowserPath.
83
+ // See src/utility/findBrowserPath.js for detection logic.
145
84
 
146
85
  const PROFILE_META_FILE = "_profile_meta.json";
147
86
 
@@ -385,6 +324,7 @@ export async function pwLaunch({
385
324
  humanize_options: effectiveHumanizeOptions,
386
325
  spoof_fingerprint,
387
326
  cleanupMinutes: effectiveCleanupMinutes,
327
+ browserType: which_browser, // "chrome" or "chromium"
388
328
  });
389
329
  break;
390
330
  case "firefox":
@@ -442,7 +382,7 @@ export async function pwLaunch({
442
382
  // ==========================================================================
443
383
  // 4. ENGINE: CHROMIUM
444
384
  // ==========================================================================
445
- async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, humanize_options, spoof_fingerprint, cleanupMinutes }) {
385
+ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, humanize_options, spoof_fingerprint, cleanupMinutes, browserType = "chromium" }) {
446
386
  const isPersistent = !!profilePath;
447
387
 
448
388
  // 1. Determine Path (Temp needs it for fingerprint storage, Persistent needs it for data)
@@ -504,7 +444,7 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, hum
504
444
  // Launch standard browser (not persistent context)
505
445
  const browser = await chromium.launch({
506
446
  headless: false,
507
- executablePath: getPlaywrightExePath("chromium"),
447
+ executablePath: findBrowserPath(browserType),
508
448
  proxy: proxyObj,
509
449
  args: args,
510
450
  ignoreDefaultArgs: ignoreDefaultArgs,
@@ -571,7 +511,7 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, hum
571
511
  // Logic: Native Persistent Launch. No fingerprint-injector.
572
512
  const context = await chromium.launchPersistentContext(activePath, {
573
513
  headless: false,
574
- executablePath: getPlaywrightExePath("chromium"),
514
+ executablePath: findBrowserPath(browserType),
575
515
  proxy: proxyObj,
576
516
  args: args,
577
517
  timezoneId: tz,
@@ -625,7 +565,7 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_option
625
565
 
626
566
  const browser = await firefox.launch({
627
567
  headless: false,
628
- executablePath: getPlaywrightExePath("firefox"),
568
+ executablePath: findBrowserPath("firefox"),
629
569
  proxy: proxyObj,
630
570
  ignoreDefaultArgs: ["--enable-automation"],
631
571
  firefoxUserPrefs: firefoxUserPrefs,
@@ -667,7 +607,7 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_option
667
607
 
668
608
  const context = await firefox.launchPersistentContext(activePath, {
669
609
  headless: false,
670
- executablePath: getPlaywrightExePath("firefox"),
610
+ executablePath: findBrowserPath("firefox"),
671
611
  proxy: proxyObj,
672
612
  timezoneId: tz,
673
613
  ignoreDefaultArgs: ["--enable-automation"],
@@ -744,7 +684,7 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
744
684
  }
745
685
  }
746
686
 
747
- const braveBin = getBinaryPath("brave");
687
+ const braveBin = findBrowserPath("brave");
748
688
 
749
689
  // ======================================================
750
690
  // Disable Brave Sidebar via Preferences
@@ -0,0 +1,104 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { setTimeout as sleep } from "timers/promises";
4
+
5
+ /**
6
+ * Deletes a directory with built-in retry logic.
7
+ * Useful for browser profiles where files might still be locked by the OS process.
8
+ * * @param {string} targetPath - The absolute path to delete.
9
+ * @param {number} maxRetries - How many times to retry (default: 5).
10
+ * @param {number} retryDelayMs - Time to wait between retries (default: 2000ms).
11
+ */
12
+ export async function deleteDirectoryWithRetries(targetPath, maxRetries = 5, retryDelayMs = 2000) {
13
+ if (!fs.existsSync(targetPath)) {
14
+ return true; // Already gone
15
+ }
16
+
17
+ let attempt = 0;
18
+
19
+ while (attempt < maxRetries) {
20
+ try {
21
+ // Force: true allows deleting files even if read-only
22
+ // Recursive: true deletes inner folders
23
+ await fs.promises.rm(targetPath, { recursive: true, force: true });
24
+
25
+ // Double check if it's actually gone
26
+ if (!fs.existsSync(targetPath)) {
27
+ return true;
28
+ }
29
+ } catch (error) {
30
+ const isLastAttempt = attempt === maxRetries - 1;
31
+
32
+ // If it's the last attempt, log error
33
+ if (isLastAttempt) {
34
+ console.error(`░░░░░ Failed to delete directory after ${maxRetries} attempts: ${targetPath}`);
35
+ console.error(` Error: ${error.message}`);
36
+ return false;
37
+ }
38
+
39
+ // Log warning and wait
40
+ console.warn(
41
+ `░░░░░ Delete failed (Attempt ${attempt + 1}/${maxRetries}). File might be locked. Retrying in ${retryDelayMs / 1000
42
+ }s...`
43
+ );
44
+ await sleep(retryDelayMs);
45
+ }
46
+ attempt++;
47
+ }
48
+
49
+ return false;
50
+ }
51
+
52
+ /**
53
+ * Deletes specific contents inside a directory but keeps the parent folder.
54
+ * (Optional utility if you need to clear cache without removing the profile folder itself)
55
+ */
56
+ export async function deleteAllItemsInDirectory(directoryPath) {
57
+ try {
58
+ if (!fs.existsSync(directoryPath)) return;
59
+
60
+ const files = await fs.promises.readdir(directoryPath);
61
+ for (const file of files) {
62
+ const curPath = path.join(directoryPath, file);
63
+ await deleteDirectoryWithRetries(curPath);
64
+ }
65
+ } catch (error) {
66
+ console.error(`Error emptying directory ${directoryPath}:`, error);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Legacy utility: Scan a directory and delete folders older than X minutes.
72
+ * (Note: launchBrowser.js now has its own internal version of this, but keeping this
73
+ * here is useful if you want to run cleanup scripts independently).
74
+ */
75
+ export function deleteOldDirectories({ directoryPath, ageLimitMinutes }) {
76
+ if (!fs.existsSync(directoryPath)) return;
77
+
78
+ const now = Date.now();
79
+ const limit = ageLimitMinutes * 60 * 1000;
80
+
81
+ try {
82
+ const files = fs.readdirSync(directoryPath);
83
+
84
+ files.forEach((file) => {
85
+ const curPath = path.join(directoryPath, file);
86
+ try {
87
+ const stats = fs.statSync(curPath);
88
+ if (stats.isDirectory()) {
89
+ const age = now - stats.mtimeMs;
90
+ if (age > limit) {
91
+ console.log(`Cleaning up old directory: ${file}`);
92
+ // We use the sync version of retry logic or just fire-and-forget async here
93
+ // For simplicity in a sync loop, we often just do a force removal:
94
+ fs.rmSync(curPath, { recursive: true, force: true });
95
+ }
96
+ }
97
+ } catch (e) {
98
+ // Ignore access errors on specific files
99
+ }
100
+ });
101
+ } catch (err) {
102
+ console.error("Error during old directory cleanup:", err);
103
+ }
104
+ }
@@ -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
+ }
@@ -17,7 +17,8 @@ import { FingerprintGenerator } from "fingerprint-generator";
17
17
 
18
18
  // Internal Utilities
19
19
  import { getMultiloginToken } from "../mlx_token.js";
20
- import { deleteDirectoryWithRetries } from "../deleteDirectory.js";
20
+ import { deleteDirectoryWithRetries } from "./deleteDirectory.js";
21
+ import { findBrowserPath } from "./findBrowserPath.js";
21
22
 
22
23
  // ==========================================================================
23
24
  // 1. CONFIGURATION & CONSTANTS
@@ -133,37 +134,8 @@ function resolveProfilePath(nameOrPath, browserName) {
133
134
  return path.join(PERSISTENT_DIR, folderName);
134
135
  }
135
136
 
136
- /**
137
- * Locates binaries for Chrome and Brave from the installed browsers.
138
- * Uses ~/.arn-browser/browsers/{browser}/{executable}
139
- */
140
- function getBinaryPath(browserName) {
141
- const isWindows = process.platform === "win32";
142
- const browsersDir = path.join(ARN_BROWSER_DIR, "browsers");
143
-
144
- const binaryMap = {
145
- brave: isWindows
146
- ? path.join(browsersDir, "brave", "brave.exe")
147
- : path.join(browsersDir, "brave", "brave"),
148
- chrome: isWindows
149
- ? path.join(browsersDir, "chromium", "chrome.exe")
150
- : path.join(browsersDir, "chromium", "chrome"),
151
- };
152
-
153
- const binaryPath = binaryMap[browserName];
154
- if (!binaryPath) {
155
- throw new Error(`░░░░░ [LaunchPuppeteer] Unknown browser: ${browserName}`);
156
- }
157
-
158
- if (!fs.existsSync(binaryPath)) {
159
- throw new Error(
160
- `░░░░░ [LaunchPuppeteer] ${browserName} binary not found at: ${binaryPath}\n` +
161
- ` Run "node bin/cli.js install" to install browsers.`
162
- );
163
- }
164
-
165
- return binaryPath;
166
- }
137
+ // Browser path resolution now uses OS-installed browsers via findBrowserPath.
138
+ // See src/utility/findBrowserPath.js for detection logic.
167
139
 
168
140
  /**
169
141
  * Writes or updates the profile metadata file.
@@ -315,6 +287,16 @@ export async function ppLaunch({
315
287
 
316
288
  switch (which_browser) {
317
289
  case "chrome":
290
+ result = await chromeLauncher({
291
+ profilePath: fullPath,
292
+ proxy,
293
+ timezoneId,
294
+ extraArgs,
295
+ spoof_fingerprint,
296
+ cleanupMinutes: effectiveCleanupMinutes,
297
+ browserType: "chrome",
298
+ });
299
+ break;
318
300
  case "chromium":
319
301
  result = await chromeLauncher({
320
302
  profilePath: fullPath,
@@ -323,6 +305,7 @@ export async function ppLaunch({
323
305
  extraArgs,
324
306
  spoof_fingerprint,
325
307
  cleanupMinutes: effectiveCleanupMinutes,
308
+ browserType: "chromium",
326
309
  });
327
310
  break;
328
311
  case "brave":
@@ -356,15 +339,15 @@ export async function ppLaunch({
356
339
  // 4. ENGINE: CHROME (CDP)
357
340
  // ==========================================================================
358
341
 
359
- async function chromeLauncher({ profilePath, proxy, timezoneId, extraArgs, spoof_fingerprint, cleanupMinutes }) {
342
+ async function chromeLauncher({ profilePath, proxy, timezoneId, extraArgs, spoof_fingerprint, cleanupMinutes, browserType = "chrome" }) {
360
343
  const isPersistent = !!profilePath;
361
344
  const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
362
345
 
363
346
  fs.mkdirSync(activePath, { recursive: true });
364
347
  writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
365
- if (_launchLogs) console.log(`░░░░░ Starting Chrome [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
348
+ if (_launchLogs) console.log(`░░░░░ Starting ${browserType === "chromium" ? "Chromium" : "Chrome"} [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
366
349
 
367
- const binaryPath = getBinaryPath("chrome");
350
+ const binaryPath = findBrowserPath(browserType);
368
351
  return await spawnAndConnect({
369
352
  binaryPath,
370
353
  profilePath: activePath,
@@ -373,7 +356,7 @@ async function chromeLauncher({ profilePath, proxy, timezoneId, extraArgs, spoof
373
356
  timezoneId,
374
357
  extraArgs,
375
358
  spoof_fingerprint,
376
- browserLabel: "Chrome",
359
+ browserLabel: browserType === "chromium" ? "Chromium" : "Chrome",
377
360
  });
378
361
  }
379
362
 
@@ -439,7 +422,7 @@ async function braveLauncher({ profilePath, proxy, timezoneId, extraArgs, spoof_
439
422
  console.warn("░░░░░ Could not modify Brave Local State:", e.message);
440
423
  }
441
424
 
442
- const binaryPath = getBinaryPath("brave");
425
+ const binaryPath = findBrowserPath("brave");
443
426
  const braveArgs = [
444
427
  "--disable-features=Translate,BraveRewards,BraveWallet,BraveNews,Sidebar,SidePanel,BraveNTPBrandedWallpaper,NTPBackgroundImages",
445
428
  "--homepage=about:blank",