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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
|
156
|
-
* - If null: Creates a temporary, random profile in
|
|
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
|
-
*
|
|
162
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
if (!ageLimitMinutes || ageLimitMinutes < 10) return;
|
|
115
|
+
const PROFILE_META_FILE = "_profile_meta.json";
|
|
114
116
|
|
|
115
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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("
|
|
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("
|
|
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
|
-
//
|
|
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(
|
|
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(
|
|
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("
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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("
|
|
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(
|
|
1104
|
+
console.warn(`░░░░░ Safety Block: Attempted to delete persistent profile: ${dirToDelete}`);
|
|
1012
1105
|
} else {
|
|
1013
|
-
console.log(
|
|
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("
|
|
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("
|
|
222
|
-
if (upstreamProxies.p2 && !p2Details) console.warn("
|
|
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
|
-
|
|
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("
|
|
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(
|
|
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("
|
|
428
|
+
console.log("░░ Proxy Stats:", getProxyStatsFormatted());
|
|
403
429
|
}
|
|
404
430
|
if (host_stats) {
|
|
405
|
-
console.log("
|
|
431
|
+
console.log("░░ Host Stats:", getHostStatsFormatted());
|
|
406
432
|
}
|
|
407
433
|
|
|
408
|
-
console.log("
|
|
434
|
+
console.log("░░ Proxy server closed.");
|
|
409
435
|
},
|
|
410
436
|
|
|
411
437
|
getProxyStats: getProxyStatsFormatted,
|