arn-browser 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arn-browser",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A lightweight, browser autmation helper.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  "@aws-sdk/client-ec2": "^3.953.0",
15
15
  "@ghostery/adblocker": "^2.13.0",
16
16
  "arn-knexjs": "^0.0.3",
17
- "camoufox-js": "^0.8.4",
17
+ "camoufox-js": "^0.9.1",
18
18
  "dotenv": "^17.2.3",
19
19
  "fingerprint-generator": "^2.1.78",
20
20
  "fingerprint-injector": "^2.1.78",
@@ -31,15 +31,14 @@ export async function deleteDirectoryWithRetries(targetPath, maxRetries = 5, ret
31
31
 
32
32
  // If it's the last attempt, log error
33
33
  if (isLastAttempt) {
34
- console.error(`❌ Failed to delete directory after ${maxRetries} attempts: ${targetPath}`);
34
+ console.error(`░░ Failed to delete directory after ${maxRetries} attempts: ${targetPath}`);
35
35
  console.error(` Error: ${error.message}`);
36
36
  return false;
37
37
  }
38
38
 
39
39
  // Log warning and wait
40
40
  console.warn(
41
- `⚠️ Delete failed (Attempt ${attempt + 1}/${maxRetries}). File might be locked. Retrying in ${
42
- retryDelayMs / 1000
41
+ `░░ Delete failed (Attempt ${attempt + 1}/${maxRetries}). File might be locked. Retrying in ${retryDelayMs / 1000
43
42
  }s...`
44
43
  );
45
44
  await sleep(retryDelayMs);
@@ -150,16 +150,24 @@ export interface LaunchOptions {
150
150
  // ========================================================================
151
151
 
152
152
  /**
153
- * Path to a persistent profile.
153
+ * Path to a browser profile.
154
154
  * - If absolute path: Uses that path exactly (no prefix added).
155
- * - If folder name: Creates inside `.data/browser_profiles/persistent/` AND adds a browser prefix (e.g., `brave_myProfile`).
156
- * - If null: Creates a temporary, random profile in `.data/browser_profiles/temp/`.
155
+ * - If folder name: Creates inside `~/.arn-browser/browser_profiles/` with a browser prefix (e.g., `brave_myProfile`).
156
+ * - If null: Creates a temporary, random profile that is deleted on browser close.
157
+ *
158
+ * When combined with `cleanupMinutes`, named profiles can be auto-deleted
159
+ * after a period of inactivity (based on `_profile_meta.json`).
157
160
  */
158
161
  profile_path?: string | null;
159
162
 
160
163
  /**
161
- * If set to > 10, deletes temporary profiles older than X minutes.
162
- * Default: 0 (disabled)
164
+ * Auto-delete schedule for named profiles (in minutes).
165
+ * - `0` or not set: Profile is permanent (never auto-deleted).
166
+ * - `> 0`: Profile is auto-deleted after this many minutes of inactivity.
167
+ * The inactivity timer resets on each launch.
168
+ * - Only applies to named profiles (with `profile_path`).
169
+ * Unnamed profiles are always deleted on close regardless.
170
+ * Default: 0
163
171
  */
164
172
  cleanupMinutes?: number;
165
173
 
@@ -222,6 +230,22 @@ export interface LaunchOptions {
222
230
  * Camoufox uses its own humanize option in camoufox_options.
223
231
  */
224
232
  humanize_options?: HumanizeOptions;
233
+
234
+ // ========================================================================
235
+ // 7. LOGGING
236
+ // ========================================================================
237
+
238
+ /**
239
+ * Enable launch-related console logs (browser start, fingerprint, tab cleanup, close).
240
+ * Default: false
241
+ */
242
+ launch_logs?: boolean;
243
+
244
+ /**
245
+ * Enable cleanup-related console logs (expired profile deletion).
246
+ * Default: false
247
+ */
248
+ cleanup_logs?: boolean;
225
249
  }
226
250
 
227
251
  /**
@@ -40,13 +40,8 @@ const detectedOs = osMap[process.platform] || "windows";
40
40
  const MULTILOGIN_LAUNCHER_URL = "https://launcher.mlx.yt:45001";
41
41
  const MULTILOGIN_FOLDER_ID = "bad9e7e1-cfab-4c8d-bd19-91aa82929711";
42
42
 
43
- const PROJECT_ROOT = process.cwd();
44
- const BASE_PROFILE_DIR = path.join(PROJECT_ROOT, ".data", "browser_profiles");
45
- const PERSISTENT_DIR = path.join(BASE_PROFILE_DIR, "persistent");
46
- const TEMP_DIR = path.join(BASE_PROFILE_DIR, "temp");
47
-
48
- if (!fs.existsSync(PERSISTENT_DIR)) fs.mkdirSync(PERSISTENT_DIR, { recursive: true });
49
- if (!fs.existsSync(TEMP_DIR)) fs.mkdirSync(TEMP_DIR, { recursive: true });
43
+ const BASE_PROFILE_DIR = path.join(os.homedir(), ".arn-browser", "browser_profiles");
44
+ if (!fs.existsSync(BASE_PROFILE_DIR)) fs.mkdirSync(BASE_PROFILE_DIR, { recursive: true });
50
45
 
51
46
  // ==========================================================================
52
47
  // 2. HELPER FUNCTIONS
@@ -67,7 +62,7 @@ function resolveProfilePath(nameOrPath, browserName) {
67
62
  else if (prefix.includes("camoufox")) prefix = "camoufox";
68
63
 
69
64
  const folderName = `${prefix}_${nameOrPath}`;
70
- return path.join(PERSISTENT_DIR, folderName);
65
+ return path.join(BASE_PROFILE_DIR, folderName);
71
66
  }
72
67
 
73
68
  /**
@@ -101,7 +96,7 @@ function getBinaryPath(browserName) {
101
96
 
102
97
  if (!binaryPath || !fs.existsSync(binaryPath)) {
103
98
  throw new Error(
104
- `❌ [LaunchBrowser] Binary not found for ${browserName} at: ${binaryPath}\n` +
99
+ `░░ [LaunchBrowser] Binary not found for ${browserName} at: ${binaryPath}\n` +
105
100
  ` Linux checked: ~/.cache/brave/brave AND ~/Downloads/brave/brave`
106
101
  );
107
102
  }
@@ -109,27 +104,58 @@ function getBinaryPath(browserName) {
109
104
  return binaryPath;
110
105
  }
111
106
 
112
- function cleanUpTempProfiles(ageLimitMinutes) {
113
- if (!ageLimitMinutes || ageLimitMinutes < 10) return;
107
+ /**
108
+ * Write or update _profile_meta.json inside a profile directory.
109
+ * Tracks creation time, last use, and auto-deletion schedule.
110
+ */
111
+ function writeProfileMeta(profileDir, deleteAfterMinutes) {
112
+ fs.mkdirSync(profileDir, { recursive: true });
113
+ const metaPath = path.join(profileDir, "_profile_meta.json");
114
114
 
115
- console.log(`🧹 [Cleanup] Checking for temp profiles older than ${ageLimitMinutes} mins...`);
116
- const now = Date.now();
117
- const limit = ageLimitMinutes * 60 * 1000;
115
+ let meta = {};
116
+ if (fs.existsSync(metaPath)) {
117
+ try { meta = JSON.parse(fs.readFileSync(metaPath, "utf-8")); } catch (e) { meta = {}; }
118
+ } else {
119
+ meta.created_at = new Date().toISOString();
120
+ }
118
121
 
122
+ meta.last_used_at = new Date().toISOString();
123
+ meta.delete_after_minutes = deleteAfterMinutes || null;
124
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
125
+ }
126
+
127
+ /**
128
+ * Scan all profiles in BASE_PROFILE_DIR and delete expired ones
129
+ * based on their _profile_meta.json metadata.
130
+ */
131
+ function cleanUpProfiles(cleanup_logs = false) {
132
+ if (!fs.existsSync(BASE_PROFILE_DIR)) return;
133
+
134
+ const now = Date.now();
119
135
  try {
120
- const files = fs.readdirSync(TEMP_DIR);
121
- files.forEach((file) => {
122
- const curPath = path.join(TEMP_DIR, file);
136
+ const entries = fs.readdirSync(BASE_PROFILE_DIR);
137
+ for (const entry of entries) {
138
+ const profileDir = path.join(BASE_PROFILE_DIR, entry);
123
139
  try {
124
- const stats = fs.statSync(curPath);
125
- if (stats.isDirectory() && now - stats.mtimeMs > limit) {
126
- console.log(` Deleting expired temp profile: ${file}`);
127
- deleteDirectoryWithRetries(curPath);
140
+ if (!fs.statSync(profileDir).isDirectory()) continue;
141
+
142
+ const metaPath = path.join(profileDir, "_profile_meta.json");
143
+ if (!fs.existsSync(metaPath)) continue; // No metadata = skip
144
+
145
+ const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
146
+ if (!meta.delete_after_minutes || meta.delete_after_minutes <= 0) continue;
147
+
148
+ const lastUsed = new Date(meta.last_used_at).getTime();
149
+ const expiresAt = lastUsed + meta.delete_after_minutes * 60 * 1000;
150
+
151
+ if (now > expiresAt) {
152
+ if (cleanup_logs) console.log(`░░ [Cleanup] Deleting expired profile: ${entry}`);
153
+ deleteDirectoryWithRetries(profileDir);
128
154
  }
129
155
  } catch (e) {
130
- // Ignore file lock errors
156
+ // Ignore errors for individual profiles
131
157
  }
132
- });
158
+ }
133
159
  } catch (err) {
134
160
  console.error("Cleanup Error:", err);
135
161
  }
@@ -196,13 +222,29 @@ export async function launchBrowser({
196
222
  camoufox_options = {}, // { geoip, humanize, ... }
197
223
  multilogin_options = {}, // { profileId, os_type, canvas_noise, ... }
198
224
 
225
+ // Logging
226
+ launch_logs = false,
227
+ cleanup_logs = false,
228
+
199
229
  }) {
200
230
  try {
201
231
  if (custom_profile_path) throw new Error("Please use profile_path");
202
232
 
203
- // Resolve path using the browser type to ensure prefixes
204
- const fullPath = resolveProfilePath(profile_path, which_browser);
205
- cleanUpTempProfiles(cleanupMinutes);
233
+ // ====== Centralized Path Resolution ======
234
+ const isNamed = !!profile_path;
235
+ const resolvedPath = resolveProfilePath(profile_path, which_browser);
236
+ const activePath = resolvedPath || path.join(BASE_PROFILE_DIR, crypto.randomUUID());
237
+
238
+ // Write/update metadata for all profiles
239
+ // For unnamed profiles: use provided cleanupMinutes, or default to 15 min
240
+ // (safety net — if process crashes before closeBrowser, cleanup catches it)
241
+ writeProfileMeta(activePath, isNamed ? cleanupMinutes : (cleanupMinutes || 15));
242
+
243
+ // Run cleanup (scans all profiles, deletes expired ones)
244
+ cleanUpProfiles(cleanup_logs);
245
+
246
+ // Only delete on close for unnamed (throwaway) profiles
247
+ const dirToDelete = isNamed ? null : activePath;
206
248
 
207
249
  let browserInstance;
208
250
 
@@ -217,7 +259,10 @@ export async function launchBrowser({
217
259
  case "chromium":
218
260
  case "chrome":
219
261
  browserInstance = await chromiumLauncher({
220
- profilePath: fullPath,
262
+ activePath,
263
+ isPersistent: isNamed,
264
+ dirToDelete,
265
+ launch_logs,
221
266
  proxy,
222
267
  timezoneId,
223
268
  CapSolver,
@@ -227,7 +272,10 @@ export async function launchBrowser({
227
272
  break;
228
273
  case "firefox":
229
274
  browserInstance = await firefoxLauncher({
230
- profilePath: fullPath,
275
+ activePath,
276
+ isPersistent: isNamed,
277
+ dirToDelete,
278
+ launch_logs,
231
279
  proxy,
232
280
  timezoneId,
233
281
  humanize_options: effectiveHumanizeOptions,
@@ -237,7 +285,10 @@ export async function launchBrowser({
237
285
  case "brave":
238
286
  case "braveLauncher":
239
287
  browserInstance = await braveLauncher({
240
- profilePath: fullPath,
288
+ activePath,
289
+ isPersistent: isNamed,
290
+ dirToDelete,
291
+ launch_logs,
241
292
  proxy,
242
293
  timezoneId,
243
294
  CapSolver,
@@ -248,7 +299,10 @@ export async function launchBrowser({
248
299
  case "camoufox":
249
300
  // Camoufox already has its own humanize in camoufox_options
250
301
  browserInstance = await camoufoxLauncher({
251
- profilePath: fullPath,
302
+ activePath,
303
+ isPersistent: isNamed,
304
+ dirToDelete,
305
+ launch_logs,
252
306
  proxy,
253
307
  timezoneId,
254
308
  camoufox_options,
@@ -260,6 +314,7 @@ export async function launchBrowser({
260
314
  proxy,
261
315
  multilogin_options,
262
316
  humanize_options: effectiveHumanizeOptions,
317
+ launch_logs,
263
318
  // Spoof Fingerprint not needed for Multilogin
264
319
  });
265
320
  break;
@@ -269,7 +324,7 @@ export async function launchBrowser({
269
324
 
270
325
  return browserInstance;
271
326
  } catch (error) {
272
- console.error(" [LaunchBrowser] Critical Error:", error.message || error);
327
+ console.error("░░ [LaunchBrowser] Critical Error:", error.message || error);
273
328
  return { browser: null, context: null, page: null, isBrowserRunning: () => false, closeBrowser: async () => false, launchError: error };
274
329
  }
275
330
  }
@@ -277,12 +332,9 @@ export async function launchBrowser({
277
332
  // ==========================================================================
278
333
  // 4. ENGINE: CHROMIUM
279
334
  // ==========================================================================
280
- async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, humanize_options, spoof_fingerprint }) {
281
- const isPersistent = !!profilePath;
335
+ async function chromiumLauncher({ activePath, isPersistent, dirToDelete, launch_logs, proxy, timezoneId, CapSolver, humanize_options, spoof_fingerprint }) {
282
336
 
283
- // 1. Determine Path (Temp needs it for fingerprint storage, Persistent needs it for data)
284
- const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
285
- console.log(`🚀 Starting Chromium [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
337
+ if (launch_logs) console.log(`░░ Starting Chromium [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
286
338
 
287
339
  // 2. Define Args
288
340
  const args = [
@@ -362,8 +414,8 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, hum
362
414
 
363
415
  const page = context.pages()[0] || (await context.newPage());
364
416
 
365
- // Pass 'activePath' so the temp folder (containing fingerprint.json) gets deleted on close
366
- return createBrowserController(browser, context, page, activePath, humanize_options);
417
+ // Pass dirToDelete so the temp folder gets deleted on close
418
+ return createBrowserController(browser, context, page, dirToDelete, humanize_options, launch_logs);
367
419
  } catch (err) {
368
420
  console.error("Chromium Temp Launch Error:", err);
369
421
  throw err;
@@ -392,7 +444,7 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, hum
392
444
 
393
445
  const page = context.pages()[0];
394
446
 
395
- return createBrowserController(context, context, page, null, humanize_options);
447
+ return createBrowserController(context, context, page, dirToDelete, humanize_options, launch_logs);
396
448
  } catch (err) {
397
449
  console.error("Chromium Persistent Launch Error:", err);
398
450
  throw err;
@@ -403,12 +455,9 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, hum
403
455
  // ==========================================================================
404
456
  // 5. ENGINE: FIREFOX
405
457
  // ==========================================================================
406
- async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_options, spoof_fingerprint }) {
407
- const isPersistent = !!profilePath;
458
+ async function firefoxLauncher({ activePath, isPersistent, dirToDelete, launch_logs, proxy, timezoneId, humanize_options, spoof_fingerprint }) {
408
459
 
409
- // 1. Determine Path
410
- const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
411
- console.log(`🚀 Starting Firefox [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
460
+ if (launch_logs) console.log(`░░ Starting Firefox [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
412
461
 
413
462
  const proxyObj = formatProxy(proxy);
414
463
  const tz = timezoneId || undefined;
@@ -457,8 +506,8 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_option
457
506
 
458
507
  const page = context.pages()[0] || (await context.newPage());
459
508
 
460
- // Pass 'activePath' to delete temp folder later
461
- return createBrowserController(browser, context, page, activePath, humanize_options);
509
+ // Pass dirToDelete so the temp folder gets deleted on close
510
+ return createBrowserController(browser, context, page, dirToDelete, humanize_options, launch_logs);
462
511
  } catch (err) {
463
512
  console.error("Firefox Temp Launch Error:", err);
464
513
  throw err;
@@ -486,7 +535,7 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_option
486
535
 
487
536
  const page = context.pages()[0];
488
537
 
489
- return createBrowserController(context, context, page, null, humanize_options);
538
+ return createBrowserController(context, context, page, dirToDelete, humanize_options, launch_logs);
490
539
  } catch (err) {
491
540
  console.error("Firefox Persistent Launch Error:", err);
492
541
  throw err;
@@ -497,11 +546,9 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_option
497
546
  // ==========================================================================
498
547
  // 6. ENGINE: BRAVE
499
548
  // ==========================================================================
500
- async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humanize_options, spoof_fingerprint }) {
501
- const isPersistent = !!profilePath;
502
- const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
549
+ async function braveLauncher({ activePath, isPersistent, dirToDelete, launch_logs, proxy, CapSolver, timezoneId, humanize_options, spoof_fingerprint }) {
503
550
 
504
- console.log(`🚀 Starting Brave: ${activePath}`);
551
+ if (launch_logs) console.log(`░░ Starting Brave: ${activePath}`);
505
552
  fs.mkdirSync(activePath, { recursive: true });
506
553
 
507
554
  // ======================================================
@@ -516,12 +563,12 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
516
563
  // Load saved settings from first launch
517
564
  const savedSettings = JSON.parse(fs.readFileSync(settingsFilePath, "utf-8"));
518
565
  effectiveSpoofFingerprint = savedSettings.spoof_fingerprint;
519
- console.log(`📋 Using saved spoof_fingerprint: ${JSON.stringify(effectiveSpoofFingerprint)} (ignoring current: ${JSON.stringify(spoof_fingerprint)})`);
566
+ if (launch_logs) console.log(`░░ Using saved spoof_fingerprint: ${JSON.stringify(effectiveSpoofFingerprint)} (ignoring current: ${JSON.stringify(spoof_fingerprint)})`);
520
567
  } else {
521
568
  // First launch - save the current setting
522
569
  const settings = { spoof_fingerprint: spoof_fingerprint };
523
570
  fs.writeFileSync(settingsFilePath, JSON.stringify(settings, null, 2), "utf-8");
524
- console.log(`📝 Saved spoof_fingerprint setting: ${JSON.stringify(spoof_fingerprint)}`);
571
+ if (launch_logs) console.log(`░░ Saved spoof_fingerprint setting: ${JSON.stringify(spoof_fingerprint)}`);
525
572
  }
526
573
  }
527
574
 
@@ -545,7 +592,7 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
545
592
 
546
593
  if (isPersistent) {
547
594
  fs.writeFileSync(fingerprintFilePath, JSON.stringify(fingerprintData, null, 2), "utf-8");
548
- console.log("📝 Generated and saved new fingerprint for Brave");
595
+ if (launch_logs) console.log("░░ Generated and saved new fingerprint for Brave");
549
596
  }
550
597
  }
551
598
 
@@ -569,7 +616,7 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
569
616
  prefs.brave.sidebar.sidebar_show_option = 3;
570
617
  fs.writeFileSync(prefsFilePath, JSON.stringify(prefs, null, 2), "utf-8");
571
618
  } catch (e) {
572
- console.warn("⚠️ Could not modify Brave preferences:", e.message);
619
+ if (launch_logs) console.warn("░░ Could not modify Brave preferences:", e.message);
573
620
  }
574
621
 
575
622
  const args = [
@@ -656,11 +703,11 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
656
703
 
657
704
 
658
705
  // ======================================================
659
- // 🛠️ FIX: Close Extra Tabs (Brave Dashboard / Restore)
706
+ // FIX: Close Extra Tabs (Brave Dashboard / Restore)
660
707
  // ======================================================
661
708
  const pages = context.pages();
662
709
  if (pages.length > 1) {
663
- console.log(`🧹 Found ${pages.length} tabs open in Brave. Closing extras...`);
710
+ if (launch_logs) console.log(`░░ Found ${pages.length} tabs open in Brave. Closing extras...`);
664
711
  // Close all tabs except the last one (which is usually the fresh active one)
665
712
  for (let i = 0; i < pages.length - 1; i++) {
666
713
  await pages[i].close();
@@ -668,19 +715,16 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
668
715
  }
669
716
  const page = context.pages()[0];
670
717
 
671
- const dirToDelete = isPersistent ? null : activePath;
672
718
  // Return the remaining open page (context.pages() might change, so we grab index 0 after cleanup)
673
- return createBrowserController(context, context, page, dirToDelete, humanize_options);
719
+ return createBrowserController(context, context, page, dirToDelete, humanize_options, launch_logs);
674
720
  }
675
721
 
676
722
  // ==========================================================================
677
723
  // 7. ENGINE: CAMOUFOX
678
724
  // ==========================================================================
679
- async function camoufoxLauncher({ profilePath, proxy, timezoneId, camoufox_options = {} }) {
680
- const isPersistent = !!profilePath;
681
- const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
725
+ async function camoufoxLauncher({ activePath, isPersistent, dirToDelete, launch_logs, proxy, timezoneId, camoufox_options = {} }) {
682
726
 
683
- console.log(`🚀 Starting camoufoxJs: ${activePath}`);
727
+ if (launch_logs) console.log(`░░ Starting camoufoxJs: ${activePath}`);
684
728
  fs.mkdirSync(activePath, { recursive: true });
685
729
 
686
730
  const proxyObj = formatProxy(proxy);
@@ -746,7 +790,7 @@ async function camoufoxLauncher({ profilePath, proxy, timezoneId, camoufox_optio
746
790
  // Save persistent config
747
791
  if (isPersistent) {
748
792
  fs.writeFileSync(fingerprintFilePath, JSON.stringify(launchConfig, null, 2), "utf-8");
749
- console.log("📝 Generated and saved new launch config for Camoufox");
793
+ if (launch_logs) console.log("░░ Generated and saved new launch config for Camoufox");
750
794
  }
751
795
  }
752
796
 
@@ -764,14 +808,13 @@ async function camoufoxLauncher({ profilePath, proxy, timezoneId, camoufox_optio
764
808
  // Removed viewport: null, handled by Camoufox args
765
809
  });
766
810
 
767
- const dirToDelete = isPersistent ? null : activePath;
768
- return createBrowserController(context, context, context.pages()[0], dirToDelete);
811
+ return createBrowserController(context, context, context.pages()[0], dirToDelete, null, launch_logs);
769
812
  }
770
813
 
771
814
  // ==========================================================================
772
815
  // 8. ENGINE: MULTILOGIN
773
816
  // ==========================================================================
774
- async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_options = null }) {
817
+ async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_options = null, launch_logs = false }) {
775
818
  // Destructure defaults from multilogin_options
776
819
  const {
777
820
  profileId = null,
@@ -783,7 +826,7 @@ async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_opt
783
826
  } = multilogin_options;
784
827
 
785
828
  if (profileId) {
786
- return await launchExistingMultiloginProfile(profileId, humanize_options);
829
+ return await launchExistingMultiloginProfile(profileId, humanize_options, launch_logs);
787
830
  } else {
788
831
  return await launchQuickMultiloginProfile({
789
832
  os_type,
@@ -792,11 +835,12 @@ async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_opt
792
835
  media_masking,
793
836
  audio_masking,
794
837
  humanize_options,
838
+ launch_logs,
795
839
  });
796
840
  }
797
841
  }
798
842
 
799
- async function launchExistingMultiloginProfile(profileId, humanize_options = null) {
843
+ async function launchExistingMultiloginProfile(profileId, humanize_options = null, launch_logs = false) {
800
844
  const startUrl = `${MULTILOGIN_LAUNCHER_URL}/api/v2/profile/f/${MULTILOGIN_FOLDER_ID}/p/${profileId}/start?automation_type=playwright&headless_mode=false`;
801
845
  let browser, context, page;
802
846
 
@@ -810,7 +854,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
810
854
  if (response.status === 400) {
811
855
  const errorJson = await response.json().catch(() => null);
812
856
  if (errorJson?.status?.error_code === "PROFILE_ALREADY_RUNNING") {
813
- console.log(`⚠️ Profile ${profileId} is running. Restarting...`);
857
+ if (launch_logs) console.log(`░░ Profile ${profileId} is running. Restarting...`);
814
858
  await stopMultiloginProfile(profileId);
815
859
  await sleep(5000);
816
860
  response = await fetch(startUrl, {
@@ -830,7 +874,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
830
874
 
831
875
  const data = await response.json();
832
876
  const port = data.data.port;
833
- console.log(`✅ Multilogin Profile ${profileId} started on port ${port}`);
877
+ if (launch_logs) console.log(`░░ Multilogin Profile ${profileId} started on port ${port}`);
834
878
 
835
879
  browser = await chromium.connectOverCDP(`http://localhost:${port}`);
836
880
  context = browser.contexts()[0];
@@ -856,7 +900,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
856
900
  }
857
901
  }
858
902
 
859
- async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, media_masking, audio_masking, humanize_options = null }) {
903
+ async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, media_masking, audio_masking, humanize_options = null, launch_logs = false }) {
860
904
  const createUrl = `${MULTILOGIN_LAUNCHER_URL}/api/v3/profile/quick`;
861
905
  let browser, context, page, profileId;
862
906
 
@@ -904,7 +948,7 @@ async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, medi
904
948
 
905
949
  profileId = json.data.id;
906
950
  const port = json.data.port;
907
- console.log(`✅ Quick Profile Created: ${profileId} on port ${port}`);
951
+ if (launch_logs) console.log(`░░ Quick Profile Created: ${profileId} on port ${port}`);
908
952
 
909
953
  browser = await chromium.connectOverCDP(`http://localhost:${port}`);
910
954
  context = browser.contexts()[0];
@@ -944,7 +988,7 @@ async function stopMultiloginProfile(profileId) {
944
988
  }
945
989
  throw new Error(txt);
946
990
  }
947
- console.log(`🛑 Profile ${profileId} stopped.`);
991
+ if (launch_logs) console.log(`░░ Profile ${profileId} stopped.`);
948
992
  return { error: null };
949
993
  } catch (err) {
950
994
  console.error(`Error stopping ${profileId}:`, err);
@@ -990,7 +1034,7 @@ function formatProxy(proxy) {
990
1034
  return p;
991
1035
  }
992
1036
 
993
- function createBrowserController(browser, context, page, dirToDelete = null, humanize_options = null) {
1037
+ function createBrowserController(browser, context, page, dirToDelete = null, humanize_options = null, launch_logs = false) {
994
1038
  // Apply human cursor if options provided
995
1039
  let humanPage = page;
996
1040
  if (humanize_options && page) {
@@ -999,7 +1043,7 @@ function createBrowserController(browser, context, page, dirToDelete = null, hum
999
1043
 
1000
1044
  const closeBrowser = async () => {
1001
1045
  try {
1002
- console.log("🔒 Closing browser session...");
1046
+ if (launch_logs) console.log("░░ Closing browser session...");
1003
1047
  if (context) await context.close();
1004
1048
  if (browser && typeof browser.close === "function" && browser !== context) {
1005
1049
  try {
@@ -1007,12 +1051,8 @@ function createBrowserController(browser, context, page, dirToDelete = null, hum
1007
1051
  } catch (e) { }
1008
1052
  }
1009
1053
  if (dirToDelete) {
1010
- if (dirToDelete.includes("persistent")) {
1011
- console.warn(`⚠️ Safety Block: Attempted to delete persistent profile: ${dirToDelete}`);
1012
- } else {
1013
- console.log(`🗑️ Deleting temp profile: ${dirToDelete}`);
1014
- await deleteDirectoryWithRetries(dirToDelete);
1015
- }
1054
+ if (launch_logs) console.log(`░░ Deleting temp profile: ${dirToDelete}`);
1055
+ await deleteDirectoryWithRetries(dirToDelete);
1016
1056
  }
1017
1057
  return true;
1018
1058
  } catch (err) {
@@ -190,7 +190,7 @@ export async function startProxyServer({
190
190
  // 2. Port
191
191
  const selectedPort = await findAvailablePort(50001, 50010);
192
192
  if (!selectedPort) {
193
- console.error(" Critical Error: No available ports.");
193
+ console.error("░░ Critical Error: No available ports.");
194
194
  return null;
195
195
  }
196
196
 
@@ -218,8 +218,8 @@ export async function startProxyServer({
218
218
  ]);
219
219
 
220
220
  // Warn only if proxy was configured (URL exists) but details came back null (Connection failed)
221
- if (upstreamProxies.p1 && !p1Details) console.warn("⚠️ Warning: PROXY_1 configured but unreachable.");
222
- if (upstreamProxies.p2 && !p2Details) console.warn("⚠️ Warning: PROXY_2 configured but unreachable.");
221
+ if (upstreamProxies.p1 && !p1Details) console.warn("░░ Warning: PROXY_1 configured but unreachable.");
222
+ if (upstreamProxies.p2 && !p2Details) console.warn("░░ Warning: PROXY_2 configured but unreachable.");
223
223
 
224
224
  // 5. Stats
225
225
  const stats = {
@@ -327,13 +327,13 @@ export async function startProxyServer({
327
327
  try {
328
328
  await server.listen();
329
329
  serverRunning = true;
330
- console.log(`✅ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
330
+ console.log(`░░ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
331
331
  } catch (err) {
332
- console.error(" Failed to start proxy server:", err);
332
+ console.error("░░ Failed to start proxy server:", err);
333
333
  return null;
334
334
  }
335
335
 
336
- const formatBytes = (bytes) => (bytes / 1024 / 1024).toFixed(2);
336
+ const formatBytes = (bytes) => (bytes / 1024 / 1024).toFixed(3);
337
337
 
338
338
  const getProxyStatsFormatted = () => {
339
339
  const formatted = {};
@@ -399,13 +399,13 @@ export async function startProxyServer({
399
399
 
400
400
  // Auto console.log stats on close
401
401
  if (proxy_stats) {
402
- console.log("📊 Proxy Stats:", getProxyStatsFormatted());
402
+ console.log("░░ Proxy Stats:", getProxyStatsFormatted());
403
403
  }
404
404
  if (host_stats) {
405
- console.log("🌐 Host Stats:", getHostStatsFormatted());
405
+ console.log("░░ Host Stats:", getHostStatsFormatted());
406
406
  }
407
407
 
408
- console.log("🔒 Proxy server closed.");
408
+ console.log("░░ Proxy server closed.");
409
409
  },
410
410
 
411
411
  getProxyStats: getProxyStatsFormatted,