arn-browser 0.1.5 → 0.1.7
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 +43 -0
- package/bin/install.js +412 -0
- package/package.json +8 -3
- package/src/index.d.ts +16 -6
- package/src/index.js +7 -4
- package/src/utility/deleteDirectory.js +2 -2
- package/src/utility/{multilogin_token_manager.js → mlx_token.js} +32 -43
- package/src/utility/{launchBrowser.d.ts → playwright/pwLaunch.d.ts} +25 -18
- package/src/utility/{launchBrowser.js → playwright/pwLaunch.js} +221 -137
- package/src/{all_routes/routeWithSuperagent.d.ts → utility/playwright/routes/pwRoute.d.ts} +4 -4
- package/src/{all_routes/routeWithSuperagent.js → utility/playwright/routes/pwRoute.js} +2 -2
- package/src/utility/proxy-utility/proxy-chain.js +30 -3
- package/src/utility/proxy-utility/proxy-helper.js +1 -1
- package/src/utility/puppeteer/ppLaunch.d.ts +199 -0
- package/src/utility/puppeteer/ppLaunch.js +723 -0
- package/src/utility/puppeteer/routes/ppRoute.d.ts +64 -0
- package/src/utility/puppeteer/routes/ppRoute.js +326 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/HumanCursor.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/bezier.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/index.d.ts +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/index.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/randomizer.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/tweening.js +0 -0
- /package/src/utility/{playwright-helper.d.ts → playwright/playwright-helper.d.ts} +0 -0
- /package/src/utility/{playwright-helper.js → playwright/playwright-helper.js} +0 -0
|
@@ -17,11 +17,11 @@ import { newInjectedContext, FingerprintInjector } from "fingerprint-injector";
|
|
|
17
17
|
import { FingerprintGenerator } from "fingerprint-generator";
|
|
18
18
|
|
|
19
19
|
// Internal Utilities
|
|
20
|
-
import { getMultiloginToken } from "
|
|
21
|
-
import { deleteDirectoryWithRetries } from "
|
|
20
|
+
import { getMultiloginToken } from "../mlx_token.js";
|
|
21
|
+
import { deleteDirectoryWithRetries } from "../deleteDirectory.js";
|
|
22
22
|
|
|
23
23
|
// Human Cursor - for human-like mouse movements
|
|
24
|
-
import { createCursor } from "
|
|
24
|
+
import { createCursor } from "./human-cursor/index.js";
|
|
25
25
|
|
|
26
26
|
// Camoufox Special
|
|
27
27
|
import { launchOptions } from "camoufox-js";
|
|
@@ -40,8 +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
|
-
|
|
43
|
+
const ARN_BROWSER_DIR = path.join(os.homedir(), ".arn-browser");
|
|
44
|
+
const BASE_PROFILE_DIR = path.join(ARN_BROWSER_DIR, "profiles", "pw");
|
|
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 });
|
|
50
|
+
|
|
51
|
+
// Module-level log flags (set per-launch via launchBrowser options)
|
|
52
|
+
let _launchLogs = false;
|
|
53
|
+
let _cleanupLogs = false;
|
|
45
54
|
|
|
46
55
|
// ==========================================================================
|
|
47
56
|
// 2. HELPER FUNCTIONS
|
|
@@ -62,42 +71,34 @@ function resolveProfilePath(nameOrPath, browserName) {
|
|
|
62
71
|
else if (prefix.includes("camoufox")) prefix = "camoufox";
|
|
63
72
|
|
|
64
73
|
const folderName = `${prefix}_${nameOrPath}`;
|
|
65
|
-
return path.join(
|
|
74
|
+
return path.join(PERSISTENT_DIR, folderName);
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
/**
|
|
69
78
|
* Locates binaries for manual browsers (Brave).
|
|
79
|
+
* Uses ~/.arn-browser/browsers/{browser}/{executable}
|
|
70
80
|
*/
|
|
71
81
|
function getBinaryPath(browserName) {
|
|
72
82
|
const isWindows = process.platform === "win32";
|
|
73
|
-
const
|
|
83
|
+
const browsersDir = path.join(ARN_BROWSER_DIR, "browsers");
|
|
74
84
|
|
|
75
|
-
|
|
85
|
+
const binaryMap = {
|
|
86
|
+
brave: isWindows
|
|
87
|
+
? path.join(browsersDir, "brave", "brave.exe")
|
|
88
|
+
: path.join(browsersDir, "brave", "brave"),
|
|
89
|
+
};
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// Linux: Primary check in .cache (common for script installs)
|
|
83
|
-
const cachePath = path.join(homeDir, ".cache", "brave", "brave");
|
|
84
|
-
const downloadsPath = path.join(homeDir, "Downloads", "brave", "brave");
|
|
85
|
-
|
|
86
|
-
if (fs.existsSync(cachePath)) {
|
|
87
|
-
binaryPath = cachePath;
|
|
88
|
-
} else if (fs.existsSync(downloadsPath)) {
|
|
89
|
-
binaryPath = downloadsPath;
|
|
90
|
-
} else {
|
|
91
|
-
// Default to cache path for the error message if neither is found
|
|
92
|
-
binaryPath = cachePath;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
91
|
+
const binaryPath = binaryMap[browserName];
|
|
92
|
+
if (!binaryPath) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`░░░░░ [LaunchBrowser] Unknown browser for getBinaryPath: ${browserName}`
|
|
95
|
+
);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
if (!
|
|
98
|
+
if (!fs.existsSync(binaryPath)) {
|
|
98
99
|
throw new Error(
|
|
99
|
-
|
|
100
|
-
`
|
|
100
|
+
`░░░░░ [LaunchBrowser] ${browserName} binary not found at: ${binaryPath}\n` +
|
|
101
|
+
` Run "node bin/cli.js install" to install browsers.`
|
|
101
102
|
);
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -105,60 +106,125 @@ function getBinaryPath(browserName) {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
109
|
+
* Gets the executable path for Playwright-managed browsers (Chromium, Firefox).
|
|
110
|
+
* Uses ~/.arn-browser/browsers/{browser}/{executable}
|
|
110
111
|
*/
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
const
|
|
112
|
+
function getPlaywrightExePath(browserName) {
|
|
113
|
+
const isWindows = process.platform === "win32";
|
|
114
|
+
const browsersDir = path.join(ARN_BROWSER_DIR, "browsers");
|
|
115
|
+
|
|
116
|
+
const binaryMap = {
|
|
117
|
+
chromium: isWindows
|
|
118
|
+
? path.join(browsersDir, "chromium", "chrome.exe")
|
|
119
|
+
: path.join(browsersDir, "chromium", "chrome"),
|
|
120
|
+
firefox: isWindows
|
|
121
|
+
? path.join(browsersDir, "firefox", "firefox.exe")
|
|
122
|
+
: path.join(browsersDir, "firefox", "firefox"),
|
|
123
|
+
};
|
|
114
124
|
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
const binaryPath = binaryMap[browserName];
|
|
126
|
+
if (!binaryPath) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`░░░░░ [LaunchBrowser] Unknown browser: ${browserName}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(binaryPath)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`░░░░░ [LaunchBrowser] ${browserName} binary not found at: ${binaryPath}\n` +
|
|
135
|
+
` Run "node bin/cli.js install" to install browsers.`
|
|
136
|
+
);
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
|
|
123
|
-
meta.delete_after_minutes = deleteAfterMinutes || null;
|
|
124
|
-
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
139
|
+
return binaryPath;
|
|
125
140
|
}
|
|
126
141
|
|
|
142
|
+
const PROFILE_META_FILE = "_profile_meta.json";
|
|
143
|
+
|
|
127
144
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
145
|
+
* Writes or updates the profile metadata file in a profile directory.
|
|
146
|
+
* On first creation: sets all fields.
|
|
147
|
+
* On subsequent launches: only updates `last_launched_at`.
|
|
130
148
|
*/
|
|
131
|
-
function
|
|
132
|
-
if (!fs.existsSync(BASE_PROFILE_DIR)) return;
|
|
133
|
-
|
|
134
|
-
const now = Date.now();
|
|
149
|
+
function writeProfileMeta(dirPath, type, cleanupMinutes) {
|
|
135
150
|
try {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
const metaPath = path.join(dirPath, PROFILE_META_FILE);
|
|
152
|
+
const now = new Date().toISOString();
|
|
153
|
+
|
|
154
|
+
if (fs.existsSync(metaPath)) {
|
|
155
|
+
// Update last_launched_at only
|
|
156
|
+
const existing = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
157
|
+
existing.last_launched_at = now;
|
|
158
|
+
fs.writeFileSync(metaPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
159
|
+
} else {
|
|
160
|
+
// Create new meta
|
|
161
|
+
const meta = {
|
|
162
|
+
type,
|
|
163
|
+
created_at: now,
|
|
164
|
+
last_launched_at: now,
|
|
165
|
+
cleanup_minutes: cleanupMinutes,
|
|
166
|
+
};
|
|
167
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {
|
|
170
|
+
// Non-critical — don't break the launch
|
|
171
|
+
}
|
|
172
|
+
}
|
|
147
173
|
|
|
148
|
-
|
|
149
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Scans both persistent and temp profile directories.
|
|
176
|
+
* Deletes profiles whose `_profile_meta.json` indicates they are expired.
|
|
177
|
+
* Skips the profile currently being launched (skipPath).
|
|
178
|
+
*/
|
|
179
|
+
function cleanUpProfiles(skipPath) {
|
|
180
|
+
const now = Date.now();
|
|
150
181
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
182
|
+
function scanDir(dir) {
|
|
183
|
+
if (!fs.existsSync(dir)) return;
|
|
184
|
+
try {
|
|
185
|
+
const folders = fs.readdirSync(dir);
|
|
186
|
+
for (const folder of folders) {
|
|
187
|
+
const folderPath = path.join(dir, folder);
|
|
188
|
+
try {
|
|
189
|
+
const stats = fs.statSync(folderPath);
|
|
190
|
+
if (!stats.isDirectory()) continue;
|
|
191
|
+
|
|
192
|
+
// Skip the profile being launched right now
|
|
193
|
+
if (skipPath && path.resolve(folderPath) === path.resolve(skipPath)) continue;
|
|
194
|
+
|
|
195
|
+
const metaPath = path.join(folderPath, PROFILE_META_FILE);
|
|
196
|
+
if (!fs.existsSync(metaPath)) {
|
|
197
|
+
// Legacy temp profile without meta — use mtime, default 15 min
|
|
198
|
+
if (dir === TEMP_DIR && now - stats.mtimeMs > 15 * 60 * 1000) {
|
|
199
|
+
if (_cleanupLogs) console.log(`░░░░░ [Cleanup] Deleting legacy temp profile: ${folder}`);
|
|
200
|
+
deleteDirectoryWithRetries(folderPath);
|
|
201
|
+
}
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
206
|
+
const cleanupMs = meta.cleanup_minutes;
|
|
207
|
+
|
|
208
|
+
// Skip if cleanup is disabled (null or 0)
|
|
209
|
+
if (!cleanupMs || cleanupMs <= 0) continue;
|
|
210
|
+
|
|
211
|
+
const lastLaunch = new Date(meta.last_launched_at).getTime();
|
|
212
|
+
if (now - lastLaunch > cleanupMs * 60 * 1000) {
|
|
213
|
+
if (_cleanupLogs) console.log(`░░░░░ [Cleanup] Deleting expired ${meta.type} profile: ${folder} (last launched ${Math.round((now - lastLaunch) / 60000)} mins ago)`);
|
|
214
|
+
deleteDirectoryWithRetries(folderPath);
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
// Ignore per-folder errors (locks, permission, corrupted meta)
|
|
154
218
|
}
|
|
155
|
-
} catch (e) {
|
|
156
|
-
// Ignore errors for individual profiles
|
|
157
219
|
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error("Cleanup scan error:", err);
|
|
158
222
|
}
|
|
159
|
-
} catch (err) {
|
|
160
|
-
console.error("Cleanup Error:", err);
|
|
161
223
|
}
|
|
224
|
+
|
|
225
|
+
if (_cleanupLogs) console.log("░░░░░ [Cleanup] Scanning for expired profiles...");
|
|
226
|
+
scanDir(PERSISTENT_DIR);
|
|
227
|
+
scanDir(TEMP_DIR);
|
|
162
228
|
}
|
|
163
229
|
|
|
164
230
|
/**
|
|
@@ -198,7 +264,7 @@ function getFingerprintConfig(browserType, screenOptions = null) {
|
|
|
198
264
|
// 3. MAIN LAUNCHER ENTRY POINT
|
|
199
265
|
// ==========================================================================
|
|
200
266
|
|
|
201
|
-
export async function
|
|
267
|
+
export async function pwLaunch({
|
|
202
268
|
// Common
|
|
203
269
|
timezoneId = null,
|
|
204
270
|
proxy,
|
|
@@ -230,21 +296,23 @@ export async function launchBrowser({
|
|
|
230
296
|
try {
|
|
231
297
|
if (custom_profile_path) throw new Error("Please use profile_path");
|
|
232
298
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const activePath = resolvedPath || path.join(BASE_PROFILE_DIR, crypto.randomUUID());
|
|
299
|
+
// Set module-level log flags
|
|
300
|
+
_launchLogs = launch_logs;
|
|
301
|
+
_cleanupLogs = cleanup_logs;
|
|
237
302
|
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
// (safety net — if process crashes before closeBrowser, cleanup catches it)
|
|
241
|
-
writeProfileMeta(activePath, isNamed ? cleanupMinutes : (cleanupMinutes || 15));
|
|
303
|
+
// Resolve path using the browser type to ensure prefixes
|
|
304
|
+
const fullPath = resolveProfilePath(profile_path, which_browser);
|
|
242
305
|
|
|
243
|
-
//
|
|
244
|
-
|
|
306
|
+
// Calculate effective cleanup minutes:
|
|
307
|
+
// Persistent: user value if > 0, otherwise null (never clean)
|
|
308
|
+
// Temp: user value if > 0, otherwise 15 min default
|
|
309
|
+
const isPersistent = !!profile_path;
|
|
310
|
+
const effectiveCleanupMinutes = cleanupMinutes > 0
|
|
311
|
+
? cleanupMinutes
|
|
312
|
+
: (isPersistent ? null : 15);
|
|
245
313
|
|
|
246
|
-
//
|
|
247
|
-
|
|
314
|
+
// Run cleanup scan — skips the profile being launched
|
|
315
|
+
cleanUpProfiles(fullPath);
|
|
248
316
|
|
|
249
317
|
let browserInstance;
|
|
250
318
|
|
|
@@ -259,53 +327,45 @@ export async function launchBrowser({
|
|
|
259
327
|
case "chromium":
|
|
260
328
|
case "chrome":
|
|
261
329
|
browserInstance = await chromiumLauncher({
|
|
262
|
-
|
|
263
|
-
isPersistent: isNamed,
|
|
264
|
-
dirToDelete,
|
|
265
|
-
launch_logs,
|
|
330
|
+
profilePath: fullPath,
|
|
266
331
|
proxy,
|
|
267
332
|
timezoneId,
|
|
268
333
|
CapSolver,
|
|
269
334
|
humanize_options: effectiveHumanizeOptions,
|
|
270
335
|
spoof_fingerprint,
|
|
336
|
+
cleanupMinutes: effectiveCleanupMinutes,
|
|
271
337
|
});
|
|
272
338
|
break;
|
|
273
339
|
case "firefox":
|
|
274
340
|
browserInstance = await firefoxLauncher({
|
|
275
|
-
|
|
276
|
-
isPersistent: isNamed,
|
|
277
|
-
dirToDelete,
|
|
278
|
-
launch_logs,
|
|
341
|
+
profilePath: fullPath,
|
|
279
342
|
proxy,
|
|
280
343
|
timezoneId,
|
|
281
344
|
humanize_options: effectiveHumanizeOptions,
|
|
282
345
|
spoof_fingerprint,
|
|
346
|
+
cleanupMinutes: effectiveCleanupMinutes,
|
|
283
347
|
});
|
|
284
348
|
break;
|
|
285
349
|
case "brave":
|
|
286
350
|
case "braveLauncher":
|
|
287
351
|
browserInstance = await braveLauncher({
|
|
288
|
-
|
|
289
|
-
isPersistent: isNamed,
|
|
290
|
-
dirToDelete,
|
|
291
|
-
launch_logs,
|
|
352
|
+
profilePath: fullPath,
|
|
292
353
|
proxy,
|
|
293
354
|
timezoneId,
|
|
294
355
|
CapSolver,
|
|
295
356
|
humanize_options: effectiveHumanizeOptions,
|
|
296
357
|
spoof_fingerprint,
|
|
358
|
+
cleanupMinutes: effectiveCleanupMinutes,
|
|
297
359
|
});
|
|
298
360
|
break;
|
|
299
361
|
case "camoufox":
|
|
300
362
|
// Camoufox already has its own humanize in camoufox_options
|
|
301
363
|
browserInstance = await camoufoxLauncher({
|
|
302
|
-
|
|
303
|
-
isPersistent: isNamed,
|
|
304
|
-
dirToDelete,
|
|
305
|
-
launch_logs,
|
|
364
|
+
profilePath: fullPath,
|
|
306
365
|
proxy,
|
|
307
366
|
timezoneId,
|
|
308
367
|
camoufox_options,
|
|
368
|
+
cleanupMinutes: effectiveCleanupMinutes,
|
|
309
369
|
// Spoof Fingerprint not needed for Camoufox
|
|
310
370
|
});
|
|
311
371
|
break;
|
|
@@ -314,7 +374,6 @@ export async function launchBrowser({
|
|
|
314
374
|
proxy,
|
|
315
375
|
multilogin_options,
|
|
316
376
|
humanize_options: effectiveHumanizeOptions,
|
|
317
|
-
launch_logs,
|
|
318
377
|
// Spoof Fingerprint not needed for Multilogin
|
|
319
378
|
});
|
|
320
379
|
break;
|
|
@@ -324,7 +383,7 @@ export async function launchBrowser({
|
|
|
324
383
|
|
|
325
384
|
return browserInstance;
|
|
326
385
|
} catch (error) {
|
|
327
|
-
console.error("
|
|
386
|
+
console.error("░░░░░ [LaunchBrowser] Critical Error:", error.message || error);
|
|
328
387
|
return { browser: null, context: null, page: null, isBrowserRunning: () => false, closeBrowser: async () => false, launchError: error };
|
|
329
388
|
}
|
|
330
389
|
}
|
|
@@ -332,9 +391,14 @@ export async function launchBrowser({
|
|
|
332
391
|
// ==========================================================================
|
|
333
392
|
// 4. ENGINE: CHROMIUM
|
|
334
393
|
// ==========================================================================
|
|
335
|
-
async function chromiumLauncher({
|
|
394
|
+
async function chromiumLauncher({ profilePath, proxy, timezoneId, CapSolver, humanize_options, spoof_fingerprint, cleanupMinutes }) {
|
|
395
|
+
const isPersistent = !!profilePath;
|
|
336
396
|
|
|
337
|
-
|
|
397
|
+
// 1. Determine Path (Temp needs it for fingerprint storage, Persistent needs it for data)
|
|
398
|
+
const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
|
|
399
|
+
if (!fs.existsSync(activePath)) fs.mkdirSync(activePath, { recursive: true });
|
|
400
|
+
writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
|
|
401
|
+
if (_launchLogs) console.log(`░░░░░ Starting Chromium [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
|
|
338
402
|
|
|
339
403
|
// 2. Define Args
|
|
340
404
|
const args = [
|
|
@@ -370,7 +434,7 @@ async function chromiumLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
370
434
|
const ignoreDefaultArgs = ["--enable-automation"];
|
|
371
435
|
|
|
372
436
|
if (CapSolver) {
|
|
373
|
-
const extPath = path.resolve(`./utility/browser-fingerprint/CapSolver`);
|
|
437
|
+
const extPath = path.resolve(`./src/utility/browser-fingerprint/CapSolver`);
|
|
374
438
|
args.push(`--disable-extensions-except=${extPath}`, `--load-extension=${extPath}`);
|
|
375
439
|
}
|
|
376
440
|
|
|
@@ -391,6 +455,7 @@ async function chromiumLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
391
455
|
// Launch standard browser (not persistent context)
|
|
392
456
|
const browser = await chromium.launch({
|
|
393
457
|
headless: false,
|
|
458
|
+
executablePath: getPlaywrightExePath("chromium"),
|
|
394
459
|
proxy: proxyObj,
|
|
395
460
|
args: args,
|
|
396
461
|
ignoreDefaultArgs: ignoreDefaultArgs,
|
|
@@ -414,8 +479,8 @@ async function chromiumLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
414
479
|
|
|
415
480
|
const page = context.pages()[0] || (await context.newPage());
|
|
416
481
|
|
|
417
|
-
// Pass
|
|
418
|
-
return createBrowserController(browser, context, page,
|
|
482
|
+
// Pass 'activePath' so the temp folder (containing fingerprint.json) gets deleted on close
|
|
483
|
+
return createBrowserController(browser, context, page, activePath, humanize_options);
|
|
419
484
|
} catch (err) {
|
|
420
485
|
console.error("Chromium Temp Launch Error:", err);
|
|
421
486
|
throw err;
|
|
@@ -432,6 +497,7 @@ async function chromiumLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
432
497
|
// Logic: Native Persistent Launch. No fingerprint-injector.
|
|
433
498
|
const context = await chromium.launchPersistentContext(activePath, {
|
|
434
499
|
headless: false,
|
|
500
|
+
executablePath: getPlaywrightExePath("chromium"),
|
|
435
501
|
proxy: proxyObj,
|
|
436
502
|
args: args,
|
|
437
503
|
timezoneId: tz,
|
|
@@ -444,7 +510,7 @@ async function chromiumLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
444
510
|
|
|
445
511
|
const page = context.pages()[0];
|
|
446
512
|
|
|
447
|
-
return createBrowserController(context, context, page,
|
|
513
|
+
return createBrowserController(context, context, page, null, humanize_options);
|
|
448
514
|
} catch (err) {
|
|
449
515
|
console.error("Chromium Persistent Launch Error:", err);
|
|
450
516
|
throw err;
|
|
@@ -455,9 +521,14 @@ async function chromiumLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
455
521
|
// ==========================================================================
|
|
456
522
|
// 5. ENGINE: FIREFOX
|
|
457
523
|
// ==========================================================================
|
|
458
|
-
async function firefoxLauncher({
|
|
524
|
+
async function firefoxLauncher({ profilePath, proxy, timezoneId, humanize_options, spoof_fingerprint, cleanupMinutes }) {
|
|
525
|
+
const isPersistent = !!profilePath;
|
|
459
526
|
|
|
460
|
-
|
|
527
|
+
// 1. Determine Path
|
|
528
|
+
const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
|
|
529
|
+
if (!fs.existsSync(activePath)) fs.mkdirSync(activePath, { recursive: true });
|
|
530
|
+
writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
|
|
531
|
+
if (_launchLogs) console.log(`░░░░░ Starting Firefox [${isPersistent ? "Persistent" : "Temp"}]: ${activePath}`);
|
|
461
532
|
|
|
462
533
|
const proxyObj = formatProxy(proxy);
|
|
463
534
|
const tz = timezoneId || undefined;
|
|
@@ -482,6 +553,7 @@ async function firefoxLauncher({ activePath, isPersistent, dirToDelete, launch_l
|
|
|
482
553
|
|
|
483
554
|
const browser = await firefox.launch({
|
|
484
555
|
headless: false,
|
|
556
|
+
executablePath: getPlaywrightExePath("firefox"),
|
|
485
557
|
proxy: proxyObj,
|
|
486
558
|
ignoreDefaultArgs: ["--enable-automation"],
|
|
487
559
|
firefoxUserPrefs: firefoxUserPrefs,
|
|
@@ -506,8 +578,8 @@ async function firefoxLauncher({ activePath, isPersistent, dirToDelete, launch_l
|
|
|
506
578
|
|
|
507
579
|
const page = context.pages()[0] || (await context.newPage());
|
|
508
580
|
|
|
509
|
-
// Pass
|
|
510
|
-
return createBrowserController(browser, context, page,
|
|
581
|
+
// Pass 'activePath' to delete temp folder later
|
|
582
|
+
return createBrowserController(browser, context, page, activePath, humanize_options);
|
|
511
583
|
} catch (err) {
|
|
512
584
|
console.error("Firefox Temp Launch Error:", err);
|
|
513
585
|
throw err;
|
|
@@ -523,6 +595,7 @@ async function firefoxLauncher({ activePath, isPersistent, dirToDelete, launch_l
|
|
|
523
595
|
|
|
524
596
|
const context = await firefox.launchPersistentContext(activePath, {
|
|
525
597
|
headless: false,
|
|
598
|
+
executablePath: getPlaywrightExePath("firefox"),
|
|
526
599
|
proxy: proxyObj,
|
|
527
600
|
timezoneId: tz,
|
|
528
601
|
ignoreDefaultArgs: ["--enable-automation"],
|
|
@@ -535,7 +608,7 @@ async function firefoxLauncher({ activePath, isPersistent, dirToDelete, launch_l
|
|
|
535
608
|
|
|
536
609
|
const page = context.pages()[0];
|
|
537
610
|
|
|
538
|
-
return createBrowserController(context, context, page,
|
|
611
|
+
return createBrowserController(context, context, page, null, humanize_options);
|
|
539
612
|
} catch (err) {
|
|
540
613
|
console.error("Firefox Persistent Launch Error:", err);
|
|
541
614
|
throw err;
|
|
@@ -546,10 +619,13 @@ async function firefoxLauncher({ activePath, isPersistent, dirToDelete, launch_l
|
|
|
546
619
|
// ==========================================================================
|
|
547
620
|
// 6. ENGINE: BRAVE
|
|
548
621
|
// ==========================================================================
|
|
549
|
-
async function braveLauncher({
|
|
622
|
+
async function braveLauncher({ profilePath, proxy, CapSolver, timezoneId, humanize_options, spoof_fingerprint, cleanupMinutes }) {
|
|
623
|
+
const isPersistent = !!profilePath;
|
|
624
|
+
const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
|
|
550
625
|
|
|
551
|
-
if (
|
|
626
|
+
if (_launchLogs) console.log(`░░░░░ Starting Brave: ${activePath}`);
|
|
552
627
|
fs.mkdirSync(activePath, { recursive: true });
|
|
628
|
+
writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
|
|
553
629
|
|
|
554
630
|
// ======================================================
|
|
555
631
|
// Persist spoof_fingerprint setting for persistent profiles
|
|
@@ -563,12 +639,12 @@ async function braveLauncher({ activePath, isPersistent, dirToDelete, launch_log
|
|
|
563
639
|
// Load saved settings from first launch
|
|
564
640
|
const savedSettings = JSON.parse(fs.readFileSync(settingsFilePath, "utf-8"));
|
|
565
641
|
effectiveSpoofFingerprint = savedSettings.spoof_fingerprint;
|
|
566
|
-
if (
|
|
642
|
+
if (_launchLogs) console.log(`░░░░░ Using saved spoof_fingerprint: ${JSON.stringify(effectiveSpoofFingerprint)} (ignoring current: ${JSON.stringify(spoof_fingerprint)})`);
|
|
567
643
|
} else {
|
|
568
644
|
// First launch - save the current setting
|
|
569
645
|
const settings = { spoof_fingerprint: spoof_fingerprint };
|
|
570
646
|
fs.writeFileSync(settingsFilePath, JSON.stringify(settings, null, 2), "utf-8");
|
|
571
|
-
if (
|
|
647
|
+
if (_launchLogs) console.log(`░░░░░ Saved spoof_fingerprint setting: ${JSON.stringify(spoof_fingerprint)}`);
|
|
572
648
|
}
|
|
573
649
|
}
|
|
574
650
|
|
|
@@ -592,7 +668,7 @@ async function braveLauncher({ activePath, isPersistent, dirToDelete, launch_log
|
|
|
592
668
|
|
|
593
669
|
if (isPersistent) {
|
|
594
670
|
fs.writeFileSync(fingerprintFilePath, JSON.stringify(fingerprintData, null, 2), "utf-8");
|
|
595
|
-
if (
|
|
671
|
+
if (_launchLogs) console.log("░░░░░ Generated and saved new fingerprint for Brave");
|
|
596
672
|
}
|
|
597
673
|
}
|
|
598
674
|
|
|
@@ -616,7 +692,7 @@ async function braveLauncher({ activePath, isPersistent, dirToDelete, launch_log
|
|
|
616
692
|
prefs.brave.sidebar.sidebar_show_option = 3;
|
|
617
693
|
fs.writeFileSync(prefsFilePath, JSON.stringify(prefs, null, 2), "utf-8");
|
|
618
694
|
} catch (e) {
|
|
619
|
-
|
|
695
|
+
console.warn("░░░░░ Could not modify Brave preferences:", e.message);
|
|
620
696
|
}
|
|
621
697
|
|
|
622
698
|
const args = [
|
|
@@ -656,7 +732,7 @@ async function braveLauncher({ activePath, isPersistent, dirToDelete, launch_log
|
|
|
656
732
|
];
|
|
657
733
|
const ignoreDefaultArgs = ["--enable-automation"];
|
|
658
734
|
if (CapSolver) {
|
|
659
|
-
const extPath = path.resolve(`./utility/browser-fingerprint/CapSolver`);
|
|
735
|
+
const extPath = path.resolve(`./src/utility/browser-fingerprint/CapSolver`);
|
|
660
736
|
args.push(`--load-extension=${extPath}`);
|
|
661
737
|
}
|
|
662
738
|
|
|
@@ -703,11 +779,11 @@ async function braveLauncher({ activePath, isPersistent, dirToDelete, launch_log
|
|
|
703
779
|
|
|
704
780
|
|
|
705
781
|
// ======================================================
|
|
706
|
-
// FIX: Close Extra Tabs (Brave Dashboard / Restore)
|
|
782
|
+
// ░░░░░ FIX: Close Extra Tabs (Brave Dashboard / Restore)
|
|
707
783
|
// ======================================================
|
|
708
784
|
const pages = context.pages();
|
|
709
785
|
if (pages.length > 1) {
|
|
710
|
-
if (
|
|
786
|
+
if (_launchLogs) console.log(`░░░░░ Found ${pages.length} tabs open in Brave. Closing extras...`);
|
|
711
787
|
// Close all tabs except the last one (which is usually the fresh active one)
|
|
712
788
|
for (let i = 0; i < pages.length - 1; i++) {
|
|
713
789
|
await pages[i].close();
|
|
@@ -715,17 +791,21 @@ async function braveLauncher({ activePath, isPersistent, dirToDelete, launch_log
|
|
|
715
791
|
}
|
|
716
792
|
const page = context.pages()[0];
|
|
717
793
|
|
|
794
|
+
const dirToDelete = isPersistent ? null : activePath;
|
|
718
795
|
// Return the remaining open page (context.pages() might change, so we grab index 0 after cleanup)
|
|
719
|
-
return createBrowserController(context, context, page, dirToDelete, humanize_options
|
|
796
|
+
return createBrowserController(context, context, page, dirToDelete, humanize_options);
|
|
720
797
|
}
|
|
721
798
|
|
|
722
799
|
// ==========================================================================
|
|
723
800
|
// 7. ENGINE: CAMOUFOX
|
|
724
801
|
// ==========================================================================
|
|
725
|
-
async function camoufoxLauncher({
|
|
802
|
+
async function camoufoxLauncher({ profilePath, proxy, timezoneId, camoufox_options = {}, cleanupMinutes }) {
|
|
803
|
+
const isPersistent = !!profilePath;
|
|
804
|
+
const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
|
|
726
805
|
|
|
727
|
-
if (
|
|
806
|
+
if (_launchLogs) console.log(`░░░░░ Starting camoufoxJs: ${activePath}`);
|
|
728
807
|
fs.mkdirSync(activePath, { recursive: true });
|
|
808
|
+
writeProfileMeta(activePath, isPersistent ? "persistent" : "temp", cleanupMinutes);
|
|
729
809
|
|
|
730
810
|
const proxyObj = formatProxy(proxy);
|
|
731
811
|
const tz = timezoneId || undefined;
|
|
@@ -790,7 +870,7 @@ async function camoufoxLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
790
870
|
// Save persistent config
|
|
791
871
|
if (isPersistent) {
|
|
792
872
|
fs.writeFileSync(fingerprintFilePath, JSON.stringify(launchConfig, null, 2), "utf-8");
|
|
793
|
-
if (
|
|
873
|
+
if (_launchLogs) console.log("░░░░░ Generated and saved new launch config for Camoufox");
|
|
794
874
|
}
|
|
795
875
|
}
|
|
796
876
|
|
|
@@ -808,13 +888,14 @@ async function camoufoxLauncher({ activePath, isPersistent, dirToDelete, launch_
|
|
|
808
888
|
// Removed viewport: null, handled by Camoufox args
|
|
809
889
|
});
|
|
810
890
|
|
|
811
|
-
|
|
891
|
+
const dirToDelete = isPersistent ? null : activePath;
|
|
892
|
+
return createBrowserController(context, context, context.pages()[0], dirToDelete);
|
|
812
893
|
}
|
|
813
894
|
|
|
814
895
|
// ==========================================================================
|
|
815
896
|
// 8. ENGINE: MULTILOGIN
|
|
816
897
|
// ==========================================================================
|
|
817
|
-
async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_options = null
|
|
898
|
+
async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_options = null }) {
|
|
818
899
|
// Destructure defaults from multilogin_options
|
|
819
900
|
const {
|
|
820
901
|
profileId = null,
|
|
@@ -826,7 +907,7 @@ async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_opt
|
|
|
826
907
|
} = multilogin_options;
|
|
827
908
|
|
|
828
909
|
if (profileId) {
|
|
829
|
-
return await launchExistingMultiloginProfile(profileId, humanize_options
|
|
910
|
+
return await launchExistingMultiloginProfile(profileId, humanize_options);
|
|
830
911
|
} else {
|
|
831
912
|
return await launchQuickMultiloginProfile({
|
|
832
913
|
os_type,
|
|
@@ -835,12 +916,11 @@ async function multiloginLauncher({ proxy, multilogin_options = {}, humanize_opt
|
|
|
835
916
|
media_masking,
|
|
836
917
|
audio_masking,
|
|
837
918
|
humanize_options,
|
|
838
|
-
launch_logs,
|
|
839
919
|
});
|
|
840
920
|
}
|
|
841
921
|
}
|
|
842
922
|
|
|
843
|
-
async function launchExistingMultiloginProfile(profileId, humanize_options = null
|
|
923
|
+
async function launchExistingMultiloginProfile(profileId, humanize_options = null) {
|
|
844
924
|
const startUrl = `${MULTILOGIN_LAUNCHER_URL}/api/v2/profile/f/${MULTILOGIN_FOLDER_ID}/p/${profileId}/start?automation_type=playwright&headless_mode=false`;
|
|
845
925
|
let browser, context, page;
|
|
846
926
|
|
|
@@ -854,7 +934,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
|
|
|
854
934
|
if (response.status === 400) {
|
|
855
935
|
const errorJson = await response.json().catch(() => null);
|
|
856
936
|
if (errorJson?.status?.error_code === "PROFILE_ALREADY_RUNNING") {
|
|
857
|
-
if (
|
|
937
|
+
if (_launchLogs) console.log(`░░░░░ Profile ${profileId} is running. Restarting...`);
|
|
858
938
|
await stopMultiloginProfile(profileId);
|
|
859
939
|
await sleep(5000);
|
|
860
940
|
response = await fetch(startUrl, {
|
|
@@ -874,7 +954,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
|
|
|
874
954
|
|
|
875
955
|
const data = await response.json();
|
|
876
956
|
const port = data.data.port;
|
|
877
|
-
if (
|
|
957
|
+
if (_launchLogs) console.log(`░░░░░ Multilogin Profile ${profileId} started on port ${port}`);
|
|
878
958
|
|
|
879
959
|
browser = await chromium.connectOverCDP(`http://localhost:${port}`);
|
|
880
960
|
context = browser.contexts()[0];
|
|
@@ -900,7 +980,7 @@ async function launchExistingMultiloginProfile(profileId, humanize_options = nul
|
|
|
900
980
|
}
|
|
901
981
|
}
|
|
902
982
|
|
|
903
|
-
async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, media_masking, audio_masking, humanize_options = null
|
|
983
|
+
async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, media_masking, audio_masking, humanize_options = null }) {
|
|
904
984
|
const createUrl = `${MULTILOGIN_LAUNCHER_URL}/api/v3/profile/quick`;
|
|
905
985
|
let browser, context, page, profileId;
|
|
906
986
|
|
|
@@ -948,7 +1028,7 @@ async function launchQuickMultiloginProfile({ os_type, proxy, canvas_noise, medi
|
|
|
948
1028
|
|
|
949
1029
|
profileId = json.data.id;
|
|
950
1030
|
const port = json.data.port;
|
|
951
|
-
if (
|
|
1031
|
+
if (_launchLogs) console.log(`░░░░░ Quick Profile Created: ${profileId} on port ${port}`);
|
|
952
1032
|
|
|
953
1033
|
browser = await chromium.connectOverCDP(`http://localhost:${port}`);
|
|
954
1034
|
context = browser.contexts()[0];
|
|
@@ -988,7 +1068,7 @@ async function stopMultiloginProfile(profileId) {
|
|
|
988
1068
|
}
|
|
989
1069
|
throw new Error(txt);
|
|
990
1070
|
}
|
|
991
|
-
if (
|
|
1071
|
+
if (_launchLogs) console.log(`░░░░░ Profile ${profileId} stopped.`);
|
|
992
1072
|
return { error: null };
|
|
993
1073
|
} catch (err) {
|
|
994
1074
|
console.error(`Error stopping ${profileId}:`, err);
|
|
@@ -1034,7 +1114,7 @@ function formatProxy(proxy) {
|
|
|
1034
1114
|
return p;
|
|
1035
1115
|
}
|
|
1036
1116
|
|
|
1037
|
-
function createBrowserController(browser, context, page, dirToDelete = null, humanize_options = null
|
|
1117
|
+
function createBrowserController(browser, context, page, dirToDelete = null, humanize_options = null) {
|
|
1038
1118
|
// Apply human cursor if options provided
|
|
1039
1119
|
let humanPage = page;
|
|
1040
1120
|
if (humanize_options && page) {
|
|
@@ -1043,7 +1123,7 @@ function createBrowserController(browser, context, page, dirToDelete = null, hum
|
|
|
1043
1123
|
|
|
1044
1124
|
const closeBrowser = async () => {
|
|
1045
1125
|
try {
|
|
1046
|
-
if (
|
|
1126
|
+
if (_launchLogs) console.log("░░░░░ Closing browser session...");
|
|
1047
1127
|
if (context) await context.close();
|
|
1048
1128
|
if (browser && typeof browser.close === "function" && browser !== context) {
|
|
1049
1129
|
try {
|
|
@@ -1051,8 +1131,12 @@ function createBrowserController(browser, context, page, dirToDelete = null, hum
|
|
|
1051
1131
|
} catch (e) { }
|
|
1052
1132
|
}
|
|
1053
1133
|
if (dirToDelete) {
|
|
1054
|
-
if (
|
|
1055
|
-
|
|
1134
|
+
if (dirToDelete.includes("persistent")) {
|
|
1135
|
+
console.warn(`░░░░░ Safety Block: Attempted to delete persistent profile: ${dirToDelete}`);
|
|
1136
|
+
} else {
|
|
1137
|
+
if (_launchLogs) console.log(`░░░░░ Deleting temp profile: ${dirToDelete}`);
|
|
1138
|
+
await deleteDirectoryWithRetries(dirToDelete);
|
|
1139
|
+
}
|
|
1056
1140
|
}
|
|
1057
1141
|
return true;
|
|
1058
1142
|
} catch (err) {
|