arn-browser 0.1.4 → 0.1.6

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.6",
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);
@@ -152,14 +152,21 @@ export interface LaunchOptions {
152
152
  /**
153
153
  * Path to a persistent 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/persistent/` AND adds a browser prefix (e.g., `brave_myProfile`).
156
+ * - If null: Creates a temporary, random profile in `~/.arn-browser/temp/`.
157
157
  */
158
158
  profile_path?: string | null;
159
159
 
160
160
  /**
161
- * If set to > 10, deletes temporary profiles older than X minutes.
162
- * Default: 0 (disabled)
161
+ * Auto-cleanup profiles after X minutes since last launch.
162
+ *
163
+ * **Persistent profiles:** Cleaned after X minutes since last launch. Default: 0 (never clean).
164
+ * **Temp profiles:** Default: 15 minutes if not specified.
165
+ *
166
+ * Each profile stores a `_profile_meta.json` with `created_at`, `last_launched_at`,
167
+ * and `cleanup_minutes`. The profile currently being launched is always skipped during cleanup.
168
+ *
169
+ * Default: 0
163
170
  */
164
171
  cleanupMinutes?: number;
165
172
 
@@ -222,6 +229,22 @@ export interface LaunchOptions {
222
229
  * Camoufox uses its own humanize option in camoufox_options.
223
230
  */
224
231
  humanize_options?: HumanizeOptions;
232
+
233
+ // ========================================================================
234
+ // 6. LOGGING
235
+ // ========================================================================
236
+
237
+ /**
238
+ * Enable launch-related console logs (browser start, fingerprint, tab cleanup, close).
239
+ * Default: false
240
+ */
241
+ launch_logs?: boolean;
242
+
243
+ /**
244
+ * Enable cleanup-related console logs (expired profile deletion).
245
+ * Default: false
246
+ */
247
+ cleanup_logs?: boolean;
225
248
  }
226
249
 
227
250
  /**
@@ -40,14 +40,17 @@ 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");
43
+ const BASE_PROFILE_DIR = path.join(os.homedir(), ".arn-browser");
45
44
  const PERSISTENT_DIR = path.join(BASE_PROFILE_DIR, "persistent");
46
45
  const TEMP_DIR = path.join(BASE_PROFILE_DIR, "temp");
47
46
 
48
47
  if (!fs.existsSync(PERSISTENT_DIR)) fs.mkdirSync(PERSISTENT_DIR, { recursive: true });
49
48
  if (!fs.existsSync(TEMP_DIR)) fs.mkdirSync(TEMP_DIR, { recursive: true });
50
49
 
50
+ // Module-level log flags (set per-launch via launchBrowser options)
51
+ let _launchLogs = false;
52
+ let _cleanupLogs = false;
53
+
51
54
  // ==========================================================================
52
55
  // 2. HELPER FUNCTIONS
53
56
  // ==========================================================================
@@ -101,7 +104,7 @@ function getBinaryPath(browserName) {
101
104
 
102
105
  if (!binaryPath || !fs.existsSync(binaryPath)) {
103
106
  throw new Error(
104
- `❌ [LaunchBrowser] Binary not found for ${browserName} at: ${binaryPath}\n` +
107
+ `░░░░░ [LaunchBrowser] Binary not found for ${browserName} at: ${binaryPath}\n` +
105
108
  ` Linux checked: ~/.cache/brave/brave AND ~/Downloads/brave/brave`
106
109
  );
107
110
  }
@@ -109,30 +112,92 @@ function getBinaryPath(browserName) {
109
112
  return binaryPath;
110
113
  }
111
114
 
112
- function cleanUpTempProfiles(ageLimitMinutes) {
113
- if (!ageLimitMinutes || ageLimitMinutes < 10) return;
115
+ const PROFILE_META_FILE = "_profile_meta.json";
114
116
 
115
- console.log(`🧹 [Cleanup] Checking for temp profiles older than ${ageLimitMinutes} mins...`);
117
+ /**
118
+ * Writes or updates the profile metadata file in a profile directory.
119
+ * On first creation: sets all fields.
120
+ * On subsequent launches: only updates `last_launched_at`.
121
+ */
122
+ function writeProfileMeta(dirPath, type, cleanupMinutes) {
123
+ try {
124
+ const metaPath = path.join(dirPath, PROFILE_META_FILE);
125
+ const now = new Date().toISOString();
126
+
127
+ if (fs.existsSync(metaPath)) {
128
+ // Update last_launched_at only
129
+ const existing = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
130
+ existing.last_launched_at = now;
131
+ fs.writeFileSync(metaPath, JSON.stringify(existing, null, 2), "utf-8");
132
+ } else {
133
+ // Create new meta
134
+ const meta = {
135
+ type,
136
+ created_at: now,
137
+ last_launched_at: now,
138
+ cleanup_minutes: cleanupMinutes,
139
+ };
140
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
141
+ }
142
+ } catch (e) {
143
+ // Non-critical — don't break the launch
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Scans both persistent and temp profile directories.
149
+ * Deletes profiles whose `_profile_meta.json` indicates they are expired.
150
+ * Skips the profile currently being launched (skipPath).
151
+ */
152
+ function cleanUpProfiles(skipPath) {
116
153
  const now = Date.now();
117
- const limit = ageLimitMinutes * 60 * 1000;
118
154
 
119
- try {
120
- const files = fs.readdirSync(TEMP_DIR);
121
- files.forEach((file) => {
122
- const curPath = path.join(TEMP_DIR, file);
123
- 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);
155
+ function scanDir(dir) {
156
+ if (!fs.existsSync(dir)) return;
157
+ try {
158
+ const folders = fs.readdirSync(dir);
159
+ for (const folder of folders) {
160
+ const folderPath = path.join(dir, folder);
161
+ try {
162
+ const stats = fs.statSync(folderPath);
163
+ if (!stats.isDirectory()) continue;
164
+
165
+ // Skip the profile being launched right now
166
+ if (skipPath && path.resolve(folderPath) === path.resolve(skipPath)) continue;
167
+
168
+ const metaPath = path.join(folderPath, PROFILE_META_FILE);
169
+ if (!fs.existsSync(metaPath)) {
170
+ // Legacy temp profile without meta — use mtime, default 15 min
171
+ if (dir === TEMP_DIR && now - stats.mtimeMs > 15 * 60 * 1000) {
172
+ if (_cleanupLogs) console.log(`░░░░░ [Cleanup] Deleting legacy temp profile: ${folder}`);
173
+ deleteDirectoryWithRetries(folderPath);
174
+ }
175
+ continue;
176
+ }
177
+
178
+ const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
179
+ const cleanupMs = meta.cleanup_minutes;
180
+
181
+ // Skip if cleanup is disabled (null or 0)
182
+ if (!cleanupMs || cleanupMs <= 0) continue;
183
+
184
+ const lastLaunch = new Date(meta.last_launched_at).getTime();
185
+ if (now - lastLaunch > cleanupMs * 60 * 1000) {
186
+ if (_cleanupLogs) console.log(`░░░░░ [Cleanup] Deleting expired ${meta.type} profile: ${folder} (last launched ${Math.round((now - lastLaunch) / 60000)} mins ago)`);
187
+ deleteDirectoryWithRetries(folderPath);
188
+ }
189
+ } catch (e) {
190
+ // Ignore per-folder errors (locks, permission, corrupted meta)
128
191
  }
129
- } catch (e) {
130
- // Ignore file lock errors
131
192
  }
132
- });
133
- } catch (err) {
134
- console.error("Cleanup Error:", err);
193
+ } catch (err) {
194
+ console.error("Cleanup scan error:", err);
195
+ }
135
196
  }
197
+
198
+ if (_cleanupLogs) console.log("░░░░░ [Cleanup] Scanning for expired profiles...");
199
+ scanDir(PERSISTENT_DIR);
200
+ scanDir(TEMP_DIR);
136
201
  }
137
202
 
138
203
  /**
@@ -196,13 +261,31 @@ export async function launchBrowser({
196
261
  camoufox_options = {}, // { geoip, humanize, ... }
197
262
  multilogin_options = {}, // { profileId, os_type, canvas_noise, ... }
198
263
 
264
+ // Logging
265
+ launch_logs = false,
266
+ cleanup_logs = false,
267
+
199
268
  }) {
200
269
  try {
201
270
  if (custom_profile_path) throw new Error("Please use profile_path");
202
271
 
272
+ // Set module-level log flags
273
+ _launchLogs = launch_logs;
274
+ _cleanupLogs = cleanup_logs;
275
+
203
276
  // Resolve path using the browser type to ensure prefixes
204
277
  const fullPath = resolveProfilePath(profile_path, which_browser);
205
- cleanUpTempProfiles(cleanupMinutes);
278
+
279
+ // Calculate effective cleanup minutes:
280
+ // Persistent: user value if > 0, otherwise null (never clean)
281
+ // Temp: user value if > 0, otherwise 15 min default
282
+ const isPersistent = !!profile_path;
283
+ const effectiveCleanupMinutes = cleanupMinutes > 0
284
+ ? cleanupMinutes
285
+ : (isPersistent ? null : 15);
286
+
287
+ // Run cleanup scan — skips the profile being launched
288
+ cleanUpProfiles(fullPath);
206
289
 
207
290
  let browserInstance;
208
291
 
@@ -223,6 +306,7 @@ export async function launchBrowser({
223
306
  CapSolver,
224
307
  humanize_options: effectiveHumanizeOptions,
225
308
  spoof_fingerprint,
309
+ cleanupMinutes: effectiveCleanupMinutes,
226
310
  });
227
311
  break;
228
312
  case "firefox":
@@ -232,6 +316,7 @@ export async function launchBrowser({
232
316
  timezoneId,
233
317
  humanize_options: effectiveHumanizeOptions,
234
318
  spoof_fingerprint,
319
+ cleanupMinutes: effectiveCleanupMinutes,
235
320
  });
236
321
  break;
237
322
  case "brave":
@@ -243,6 +328,7 @@ export async function launchBrowser({
243
328
  CapSolver,
244
329
  humanize_options: effectiveHumanizeOptions,
245
330
  spoof_fingerprint,
331
+ cleanupMinutes: effectiveCleanupMinutes,
246
332
  });
247
333
  break;
248
334
  case "camoufox":
@@ -252,6 +338,7 @@ export async function launchBrowser({
252
338
  proxy,
253
339
  timezoneId,
254
340
  camoufox_options,
341
+ cleanupMinutes: effectiveCleanupMinutes,
255
342
  // Spoof Fingerprint not needed for Camoufox
256
343
  });
257
344
  break;
@@ -269,7 +356,7 @@ export async function launchBrowser({
269
356
 
270
357
  return browserInstance;
271
358
  } catch (error) {
272
- console.error(" [LaunchBrowser] Critical Error:", error.message || error);
359
+ console.error("░░░░░ [LaunchBrowser] Critical Error:", error.message || error);
273
360
  return { browser: null, context: null, page: null, isBrowserRunning: () => false, closeBrowser: async () => false, launchError: error };
274
361
  }
275
362
  }
@@ -277,12 +364,14 @@ export async function launchBrowser({
277
364
  // ==========================================================================
278
365
  // 4. ENGINE: CHROMIUM
279
366
  // ==========================================================================
280
- async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, humanize_options, spoof_fingerprint }) {
367
+ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, humanize_options, spoof_fingerprint, cleanupMinutes }) {
281
368
  const isPersistent = !!profilePath;
282
369
 
283
370
  // 1. Determine Path (Temp needs it for fingerprint storage, Persistent needs it for data)
284
371
  const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
285
- console.log(`🚀 Starting Chromium [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
372
+ if (!fs.existsSync(activePath)) fs.mkdirSync(activePath, { recursive: true });
373
+ writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
374
+ if (_launchLogs) console.log(`░░░░░ Starting Chromium [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
286
375
 
287
376
  // 2. Define Args
288
377
  const args = [
@@ -403,12 +492,14 @@ async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, hum
403
492
  // ==========================================================================
404
493
  // 5. ENGINE: FIREFOX
405
494
  // ==========================================================================
406
- async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_options, spoof_fingerprint }) {
495
+ async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_options, spoof_fingerprint, cleanupMinutes }) {
407
496
  const isPersistent = !!profilePath;
408
497
 
409
498
  // 1. Determine Path
410
499
  const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
411
- console.log(`🚀 Starting Firefox [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
500
+ if (!fs.existsSync(activePath)) fs.mkdirSync(activePath, { recursive: true });
501
+ writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
502
+ if (_launchLogs) console.log(`░░░░░ Starting Firefox [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
412
503
 
413
504
  const proxyObj = formatProxy(proxy);
414
505
  const tz = timezoneId || undefined;
@@ -497,12 +588,13 @@ async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_option
497
588
  // ==========================================================================
498
589
  // 6. ENGINE: BRAVE
499
590
  // ==========================================================================
500
- async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humanize_options, spoof_fingerprint }) {
591
+ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humanize_options, spoof_fingerprint, cleanupMinutes }) {
501
592
  const isPersistent = !!profilePath;
502
593
  const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
503
594
 
504
- console.log(`🚀 Starting Brave: ${activePath}`);
595
+ if (_launchLogs) console.log(`░░░░░ Starting Brave: ${activePath}`);
505
596
  fs.mkdirSync(activePath, { recursive: true });
597
+ writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
506
598
 
507
599
  // ======================================================
508
600
  // Persist spoof_fingerprint setting for persistent profiles
@@ -516,12 +608,12 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
516
608
  // Load saved settings from first launch
517
609
  const savedSettings = JSON.parse(fs.readFileSync(settingsFilePath, "utf-8"));
518
610
  effectiveSpoofFingerprint = savedSettings.spoof_fingerprint;
519
- console.log(`📋 Using saved spoof_fingerprint: ${JSON.stringify(effectiveSpoofFingerprint)} (ignoring current: ${JSON.stringify(spoof_fingerprint)})`);
611
+ if (_launchLogs) console.log(`░░░░░ Using saved spoof_fingerprint: ${JSON.stringify(effectiveSpoofFingerprint)} (ignoring current: ${JSON.stringify(spoof_fingerprint)})`);
520
612
  } else {
521
613
  // First launch - save the current setting
522
614
  const settings = { spoof_fingerprint: spoof_fingerprint };
523
615
  fs.writeFileSync(settingsFilePath, JSON.stringify(settings, null, 2), "utf-8");
524
- console.log(`📝 Saved spoof_fingerprint setting: ${JSON.stringify(spoof_fingerprint)}`);
616
+ if (_launchLogs) console.log(`░░░░░ Saved spoof_fingerprint setting: ${JSON.stringify(spoof_fingerprint)}`);
525
617
  }
526
618
  }
527
619
 
@@ -545,7 +637,7 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
545
637
 
546
638
  if (isPersistent) {
547
639
  fs.writeFileSync(fingerprintFilePath, JSON.stringify(fingerprintData, null, 2), "utf-8");
548
- console.log("📝 Generated and saved new fingerprint for Brave");
640
+ if (_launchLogs) console.log("░░░░░ Generated and saved new fingerprint for Brave");
549
641
  }
550
642
  }
551
643
 
@@ -569,7 +661,7 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
569
661
  prefs.brave.sidebar.sidebar_show_option = 3;
570
662
  fs.writeFileSync(prefsFilePath, JSON.stringify(prefs, null, 2), "utf-8");
571
663
  } catch (e) {
572
- console.warn("⚠️ Could not modify Brave preferences:", e.message);
664
+ console.warn("░░░░░ Could not modify Brave preferences:", e.message);
573
665
  }
574
666
 
575
667
  const args = [
@@ -656,11 +748,11 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
656
748
 
657
749
 
658
750
  // ======================================================
659
- // 🛠️ FIX: Close Extra Tabs (Brave Dashboard / Restore)
751
+ // ░░░░░ FIX: Close Extra Tabs (Brave Dashboard / Restore)
660
752
  // ======================================================
661
753
  const pages = context.pages();
662
754
  if (pages.length > 1) {
663
- console.log(`🧹 Found ${pages.length} tabs open in Brave. Closing extras...`);
755
+ if (_launchLogs) console.log(`░░░░░ Found ${pages.length} tabs open in Brave. Closing extras...`);
664
756
  // Close all tabs except the last one (which is usually the fresh active one)
665
757
  for (let i = 0; i < pages.length - 1; i++) {
666
758
  await pages[i].close();
@@ -676,12 +768,13 @@ async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humani
676
768
  // ==========================================================================
677
769
  // 7. ENGINE: CAMOUFOX
678
770
  // ==========================================================================
679
- async function camoufoxLauncher({ profilePath, proxy, timezoneId, camoufox_options = {} }) {
771
+ async function camoufoxLauncher({ profilePath, proxy, timezoneId, camoufox_options = {}, cleanupMinutes }) {
680
772
  const isPersistent = !!profilePath;
681
773
  const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
682
774
 
683
- console.log(`🚀 Starting camoufoxJs: ${activePath}`);
775
+ if (_launchLogs) console.log(`░░░░░ Starting camoufoxJs: ${activePath}`);
684
776
  fs.mkdirSync(activePath, { recursive: true });
777
+ writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
685
778
 
686
779
  const proxyObj = formatProxy(proxy);
687
780
  const tz = timezoneId || undefined;
@@ -746,7 +839,7 @@ async function camoufoxLauncher({ profilePath, proxy, timezoneId, camoufox_optio
746
839
  // Save persistent config
747
840
  if (isPersistent) {
748
841
  fs.writeFileSync(fingerprintFilePath, JSON.stringify(launchConfig, null, 2), "utf-8");
749
- console.log("📝 Generated and saved new launch config for Camoufox");
842
+ if (_launchLogs) console.log("░░░░░ Generated and saved new launch config for Camoufox");
750
843
  }
751
844
  }
752
845
 
@@ -810,7 +903,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
810
903
  if (response.status === 400) {
811
904
  const errorJson = await response.json().catch(() => null);
812
905
  if (errorJson?.status?.error_code === "PROFILE_ALREADY_RUNNING") {
813
- console.log(`⚠️ Profile ${profileId} is running. Restarting...`);
906
+ if (_launchLogs) console.log(`░░░░░ Profile ${profileId} is running. Restarting...`);
814
907
  await stopMultiloginProfile(profileId);
815
908
  await sleep(5000);
816
909
  response = await fetch(startUrl, {
@@ -830,7 +923,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
830
923
 
831
924
  const data = await response.json();
832
925
  const port = data.data.port;
833
- console.log(`✅ Multilogin Profile ${profileId} started on port ${port}`);
926
+ if (_launchLogs) console.log(`░░░░░ Multilogin Profile ${profileId} started on port ${port}`);
834
927
 
835
928
  browser = await chromium.connectOverCDP(`http://localhost:${port}`);
836
929
  context = browser.contexts()[0];
@@ -904,7 +997,7 @@ async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, medi
904
997
 
905
998
  profileId = json.data.id;
906
999
  const port = json.data.port;
907
- console.log(`✅ Quick Profile Created: ${profileId} on port ${port}`);
1000
+ if (_launchLogs) console.log(`░░░░░ Quick Profile Created: ${profileId} on port ${port}`);
908
1001
 
909
1002
  browser = await chromium.connectOverCDP(`http://localhost:${port}`);
910
1003
  context = browser.contexts()[0];
@@ -944,7 +1037,7 @@ async function stopMultiloginProfile(profileId) {
944
1037
  }
945
1038
  throw new Error(txt);
946
1039
  }
947
- console.log(`🛑 Profile ${profileId} stopped.`);
1040
+ if (_launchLogs) console.log(`░░░░░ Profile ${profileId} stopped.`);
948
1041
  return { error: null };
949
1042
  } catch (err) {
950
1043
  console.error(`Error stopping ${profileId}:`, err);
@@ -999,7 +1092,7 @@ function createBrowserController(browser, context, page, dirToDelete = null, hum
999
1092
 
1000
1093
  const closeBrowser = async () => {
1001
1094
  try {
1002
- console.log("🔒 Closing browser session...");
1095
+ if (_launchLogs) console.log("░░░░░ Closing browser session...");
1003
1096
  if (context) await context.close();
1004
1097
  if (browser && typeof browser.close === "function" && browser !== context) {
1005
1098
  try {
@@ -1008,9 +1101,9 @@ function createBrowserController(browser, context, page, dirToDelete = null, hum
1008
1101
  }
1009
1102
  if (dirToDelete) {
1010
1103
  if (dirToDelete.includes("persistent")) {
1011
- console.warn(`⚠️ Safety Block: Attempted to delete persistent profile: ${dirToDelete}`);
1104
+ console.warn(`░░░░░ Safety Block: Attempted to delete persistent profile: ${dirToDelete}`);
1012
1105
  } else {
1013
- console.log(`🗑️ Deleting temp profile: ${dirToDelete}`);
1106
+ if (_launchLogs) console.log(`░░░░░ Deleting temp profile: ${dirToDelete}`);
1014
1107
  await deleteDirectoryWithRetries(dirToDelete);
1015
1108
  }
1016
1109
  }
@@ -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 = {
@@ -239,6 +239,9 @@ export async function startProxyServer({
239
239
  const connectionMap = {}; // Maps connectionId -> { type: "..." }
240
240
  let serverRunning = false;
241
241
 
242
+ // Track all open sockets so we can force-destroy them on close
243
+ const activeSockets = new Set();
244
+
242
245
  // 6. Server
243
246
  const server = new ProxyChain.Server({
244
247
  port: selectedPort,
@@ -327,13 +330,30 @@ export async function startProxyServer({
327
330
  try {
328
331
  await server.listen();
329
332
  serverRunning = true;
330
- console.log(`✅ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
333
+
334
+ // Track sockets so closeServer() can destroy them all
335
+ server.server.on('connection', (socket) => {
336
+ activeSockets.add(socket);
337
+ socket.once('close', () => activeSockets.delete(socket));
338
+
339
+ // Intercept pipe to catch the upstream target sockets created by proxy-chain
340
+ const originalPipe = socket.pipe;
341
+ socket.pipe = function(destination, options) {
342
+ if (destination && typeof destination.destroy === 'function') {
343
+ activeSockets.add(destination);
344
+ destination.once('close', () => activeSockets.delete(destination));
345
+ }
346
+ return originalPipe.apply(this, arguments);
347
+ };
348
+ });
349
+
350
+ console.log(`░░ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
331
351
  } catch (err) {
332
- console.error(" Failed to start proxy server:", err);
352
+ console.error("░░ Failed to start proxy server:", err);
333
353
  return null;
334
354
  }
335
355
 
336
- const formatBytes = (bytes) => (bytes / 1024 / 1024).toFixed(2);
356
+ const formatBytes = (bytes) => (bytes / 1024 / 1024).toFixed(3);
337
357
 
338
358
  const getProxyStatsFormatted = () => {
339
359
  const formatted = {};
@@ -397,15 +417,21 @@ export async function startProxyServer({
397
417
  await server.close(true);
398
418
  serverRunning = false;
399
419
 
420
+ // Force-destroy any lingering sockets so Node can exit
421
+ for (const socket of activeSockets) {
422
+ socket.destroy();
423
+ }
424
+ activeSockets.clear();
425
+
400
426
  // Auto console.log stats on close
401
427
  if (proxy_stats) {
402
- console.log("📊 Proxy Stats:", getProxyStatsFormatted());
428
+ console.log("░░ Proxy Stats:", getProxyStatsFormatted());
403
429
  }
404
430
  if (host_stats) {
405
- console.log("🌐 Host Stats:", getHostStatsFormatted());
431
+ console.log("░░ Host Stats:", getHostStatsFormatted());
406
432
  }
407
433
 
408
- console.log("🔒 Proxy server closed.");
434
+ console.log("░░ Proxy server closed.");
409
435
  },
410
436
 
411
437
  getProxyStats: getProxyStatsFormatted,