arn-browser 0.1.29 → 0.1.31
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
|
@@ -208,14 +208,7 @@ export async function startProxyServer({
|
|
|
208
208
|
proxy2: createHostMatcher([...PROXY_2_HOSTS, "proxy.multilogin.com", "multilogin.com"]),
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
-
// 2.
|
|
212
|
-
const selectedPort = await findAvailablePort(50001, 50010);
|
|
213
|
-
if (!selectedPort) {
|
|
214
|
-
console.error("░░ Critical Error: No available ports.");
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// 3. Build URLs
|
|
211
|
+
// 2. Build URLs
|
|
219
212
|
const buildURL = (data) => {
|
|
220
213
|
if (!data) return null; // Returns null if no config
|
|
221
214
|
const { type = "http", host, port, user, pass } = data;
|
|
@@ -230,7 +223,7 @@ export async function startProxyServer({
|
|
|
230
223
|
p2: buildURL(PROXY_2_DATA),
|
|
231
224
|
};
|
|
232
225
|
|
|
233
|
-
//
|
|
226
|
+
// 3. Fetch Details (Simplified Logic)
|
|
234
227
|
// We pass the URL (or null). The function handles the "Local" logic.
|
|
235
228
|
const [defaultDetails, p1Details, p2Details] = await Promise.all([
|
|
236
229
|
fetchProxyDetails(upstreamProxies.default, ip2LocationKey, retryDelayMs),
|
|
@@ -243,7 +236,7 @@ export async function startProxyServer({
|
|
|
243
236
|
if (upstreamProxies.p1 && !p1Details) { console.warn("░░ Warning: PROXY_1 configured but unreachable."); return null; }
|
|
244
237
|
if (upstreamProxies.p2 && !p2Details) { console.warn("░░ Warning: PROXY_2 configured but unreachable."); return null; }
|
|
245
238
|
|
|
246
|
-
//
|
|
239
|
+
// 4. Stats
|
|
247
240
|
const stats = {
|
|
248
241
|
DEFAULT_PROXY: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
|
|
249
242
|
NO_PROXY: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
|
|
@@ -261,72 +254,105 @@ export async function startProxyServer({
|
|
|
261
254
|
const connectionMap = {}; // Maps connectionId -> { type: "..." }
|
|
262
255
|
let serverRunning = false;
|
|
263
256
|
|
|
264
|
-
//
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
verbose: debug,
|
|
269
|
-
prepareRequestFunction: ({ hostname, connectionId }) => {
|
|
270
|
-
let proxyType = "DEFAULT_PROXY";
|
|
271
|
-
let upstreamUrl = upstreamProxies.default;
|
|
272
|
-
let isCustomResponse = false;
|
|
273
|
-
let customResponseData = null;
|
|
274
|
-
|
|
275
|
-
// Logic to determine Proxy Type
|
|
276
|
-
if (matchers.noProxy(hostname)) {
|
|
277
|
-
// A. Direct
|
|
278
|
-
proxyType = "NO_PROXY";
|
|
279
|
-
upstreamUrl = null;
|
|
280
|
-
} else if (matchers.proxy1(hostname) && upstreamProxies.p1) {
|
|
281
|
-
// C1. Proxy 1
|
|
282
|
-
proxyType = "PROXY_1";
|
|
283
|
-
upstreamUrl = upstreamProxies.p1;
|
|
284
|
-
} else if (matchers.proxy2(hostname) && upstreamProxies.p2) {
|
|
285
|
-
// C2. Proxy 2
|
|
286
|
-
proxyType = "PROXY_2";
|
|
287
|
-
upstreamUrl = upstreamProxies.p2;
|
|
288
|
-
}
|
|
257
|
+
// 5. Create server and bind with retry (handles race conditions between concurrent processes)
|
|
258
|
+
const PORT_RANGE_START = 50001;
|
|
259
|
+
const PORT_RANGE_END = 50200;
|
|
260
|
+
const MAX_BIND_RETRIES = 10;
|
|
289
261
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
else displayedIP = defaultDetails?.ip;
|
|
300
|
-
|
|
301
|
-
customResponseData = {
|
|
302
|
-
statusCode: 200,
|
|
303
|
-
headers: { "Content-Type": "text/plain", Connection: "close" },
|
|
304
|
-
body: displayedIP || "Unknown IP",
|
|
305
|
-
};
|
|
306
|
-
}
|
|
262
|
+
let selectedPort = null;
|
|
263
|
+
let server = null;
|
|
264
|
+
|
|
265
|
+
for (let attempt = 1; attempt <= MAX_BIND_RETRIES; attempt++) {
|
|
266
|
+
const candidatePort = await findAvailablePort(PORT_RANGE_START, PORT_RANGE_END);
|
|
267
|
+
if (!candidatePort) {
|
|
268
|
+
console.error("░░ Critical Error: No available ports in range " + PORT_RANGE_START + "-" + PORT_RANGE_END);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
307
271
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
272
|
+
server = new ProxyChain.Server({
|
|
273
|
+
port: candidatePort,
|
|
274
|
+
host: "127.0.0.1",
|
|
275
|
+
verbose: debug,
|
|
276
|
+
prepareRequestFunction: ({ hostname, connectionId }) => {
|
|
277
|
+
let proxyType = "DEFAULT_PROXY";
|
|
278
|
+
let upstreamUrl = upstreamProxies.default;
|
|
279
|
+
let isCustomResponse = false;
|
|
280
|
+
let customResponseData = null;
|
|
281
|
+
|
|
282
|
+
// Logic to determine Proxy Type
|
|
283
|
+
if (matchers.noProxy(hostname)) {
|
|
284
|
+
// A. Direct
|
|
285
|
+
proxyType = "NO_PROXY";
|
|
286
|
+
upstreamUrl = null;
|
|
287
|
+
} else if (matchers.proxy1(hostname) && upstreamProxies.p1) {
|
|
288
|
+
// C1. Proxy 1
|
|
289
|
+
proxyType = "PROXY_1";
|
|
290
|
+
upstreamUrl = upstreamProxies.p1;
|
|
291
|
+
} else if (matchers.proxy2(hostname) && upstreamProxies.p2) {
|
|
292
|
+
// C2. Proxy 2
|
|
293
|
+
proxyType = "PROXY_2";
|
|
294
|
+
upstreamUrl = upstreamProxies.p2;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// B. IP Check Interception (Overrules standard routing for specific domain)
|
|
298
|
+
if (hostname === "ip.bablosoft.com") {
|
|
299
|
+
isCustomResponse = true;
|
|
300
|
+
let displayedIP;
|
|
301
|
+
if (proxyType === "PROXY_1") displayedIP = p1Details?.ip;
|
|
302
|
+
else if (proxyType === "PROXY_2") displayedIP = p2Details?.ip;
|
|
303
|
+
else if (proxyType === "NO_PROXY") displayedIP = "127.0.0.1";
|
|
304
|
+
else displayedIP = defaultDetails?.ip;
|
|
305
|
+
|
|
306
|
+
customResponseData = {
|
|
307
|
+
statusCode: 200,
|
|
308
|
+
headers: { "Content-Type": "text/plain", Connection: "close" },
|
|
309
|
+
body: displayedIP || "Unknown IP",
|
|
310
|
+
};
|
|
315
311
|
}
|
|
316
|
-
hostStatsMap[proxyType][hostname].req++;
|
|
317
|
-
}
|
|
318
312
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
// Record Stats
|
|
314
|
+
connectionMap[connectionId] = { type: proxyType, hostname: hostname };
|
|
315
|
+
if (host_stats && hostname) {
|
|
316
|
+
if (!hostStatsMap[proxyType]) hostStatsMap[proxyType] = {};
|
|
317
|
+
if (!hostStatsMap[proxyType][hostname]) {
|
|
318
|
+
hostStatsMap[proxyType][hostname] = { req: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 };
|
|
319
|
+
}
|
|
320
|
+
hostStatsMap[proxyType][hostname].req++;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Return Decision
|
|
324
|
+
if (isCustomResponse) {
|
|
325
|
+
return { customResponseFunction: () => customResponseData };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
upstreamProxyUrl: upstreamUrl,
|
|
330
|
+
requestAuthentication: false,
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
await server.listen();
|
|
337
|
+
selectedPort = candidatePort;
|
|
338
|
+
serverRunning = true;
|
|
339
|
+
console.log(`░░ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
|
|
340
|
+
break; // Successfully bound — exit retry loop
|
|
341
|
+
} catch (err) {
|
|
342
|
+
if (err.code === "EADDRINUSE" && attempt < MAX_BIND_RETRIES) {
|
|
343
|
+
console.warn(`░░ Port ${candidatePort} taken (race), retrying... (${attempt}/${MAX_BIND_RETRIES})`);
|
|
344
|
+
await sleep(50 + Math.random() * 100); // Small random delay to de-sync concurrent processes
|
|
345
|
+
continue;
|
|
322
346
|
}
|
|
347
|
+
console.error("░░ Failed to start proxy server:", err);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
323
351
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
},
|
|
329
|
-
});
|
|
352
|
+
if (!selectedPort || !server) {
|
|
353
|
+
console.error("░░ Critical Error: Could not bind to any port after " + MAX_BIND_RETRIES + " attempts.");
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
330
356
|
|
|
331
357
|
server.on("connectionClosed", ({ connectionId, stats: connStats }) => {
|
|
332
358
|
const connectionInfo = connectionMap[connectionId];
|
|
@@ -355,15 +381,6 @@ export async function startProxyServer({
|
|
|
355
381
|
delete connectionMap[connectionId];
|
|
356
382
|
});
|
|
357
383
|
|
|
358
|
-
try {
|
|
359
|
-
await server.listen();
|
|
360
|
-
serverRunning = true;
|
|
361
|
-
console.log(`░░ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
|
|
362
|
-
} catch (err) {
|
|
363
|
-
console.error("░░ Failed to start proxy server:", err);
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
384
|
const formatBytes = (bytes) => {
|
|
368
385
|
if (bytes < 1024) return bytes + " B";
|
|
369
386
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
|
|
@@ -54,8 +54,9 @@ export async function get_multilogin_proxy({
|
|
|
54
54
|
// 1. Get Token
|
|
55
55
|
const token = await getMultiloginToken();
|
|
56
56
|
|
|
57
|
-
// 2. Prepare Data
|
|
58
|
-
const
|
|
57
|
+
// 2. Prepare Data (sanitize region: spaces → underscores)
|
|
58
|
+
const sanitizedRegion = region ? region.toLowerCase().replace(/ /g, '_') : region;
|
|
59
|
+
const data = { country, sessionType, protocol, region: sanitizedRegion, city, IPTTL, count: 1 };
|
|
59
60
|
Object.keys(data).forEach((k) => (data[k] === "" || data[k] === 0 || data[k] == null) && delete data[k]);
|
|
60
61
|
|
|
61
62
|
// 3. Setup Timeout
|
|
@@ -64,7 +64,18 @@ export interface PpLaunchOptions {
|
|
|
64
64
|
which_browser?: "chrome" | "chromium" | "brave" | "multilogin";
|
|
65
65
|
|
|
66
66
|
// ========================================================================
|
|
67
|
-
// 2.
|
|
67
|
+
// 2. GENERAL & NETWORK
|
|
68
|
+
// ========================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Timezone ID (IANA format, e.g., "America/New_York").
|
|
72
|
+
* Applied via CDP `Emulation.setTimezoneOverride` after connecting.
|
|
73
|
+
* Default: null (uses system timezone)
|
|
74
|
+
*/
|
|
75
|
+
timezoneId?: string | null;
|
|
76
|
+
|
|
77
|
+
// ========================================================================
|
|
78
|
+
// 3. STORAGE & PROFILES
|
|
68
79
|
// ========================================================================
|
|
69
80
|
|
|
70
81
|
/**
|
|
@@ -86,7 +97,7 @@ export interface PpLaunchOptions {
|
|
|
86
97
|
cleanupMinutes?: number;
|
|
87
98
|
|
|
88
99
|
// ========================================================================
|
|
89
|
-
//
|
|
100
|
+
// 4. NETWORK
|
|
90
101
|
// ========================================================================
|
|
91
102
|
|
|
92
103
|
/**
|
|
@@ -96,7 +107,7 @@ export interface PpLaunchOptions {
|
|
|
96
107
|
proxy?: ProxyConfig | string | null;
|
|
97
108
|
|
|
98
109
|
// ========================================================================
|
|
99
|
-
//
|
|
110
|
+
// 5. BROWSER ARGS
|
|
100
111
|
// ========================================================================
|
|
101
112
|
|
|
102
113
|
/**
|
|
@@ -108,7 +119,7 @@ export interface PpLaunchOptions {
|
|
|
108
119
|
extraArgs?: string[];
|
|
109
120
|
|
|
110
121
|
// ========================================================================
|
|
111
|
-
//
|
|
122
|
+
// 6. ENGINE SPECIFIC
|
|
112
123
|
// ========================================================================
|
|
113
124
|
|
|
114
125
|
/**
|
|
@@ -117,7 +128,7 @@ export interface PpLaunchOptions {
|
|
|
117
128
|
multilogin_options?: PpMultiloginOptions;
|
|
118
129
|
|
|
119
130
|
// ========================================================================
|
|
120
|
-
//
|
|
131
|
+
// 7. FINGERPRINT SPOOFING
|
|
121
132
|
// ========================================================================
|
|
122
133
|
|
|
123
134
|
/**
|
|
@@ -169,7 +180,7 @@ export interface PpLaunchOptions {
|
|
|
169
180
|
};
|
|
170
181
|
|
|
171
182
|
// ========================================================================
|
|
172
|
-
//
|
|
183
|
+
// 8. LOGGING
|
|
173
184
|
// ========================================================================
|
|
174
185
|
|
|
175
186
|
/**
|
|
@@ -265,6 +265,9 @@ export async function ppLaunch({
|
|
|
265
265
|
// Browser selection
|
|
266
266
|
which_browser = "chrome",
|
|
267
267
|
|
|
268
|
+
// Common
|
|
269
|
+
timezoneId = null,
|
|
270
|
+
|
|
268
271
|
// Path & Storage
|
|
269
272
|
profile_path = null,
|
|
270
273
|
cleanupMinutes = 0,
|
|
@@ -316,6 +319,7 @@ export async function ppLaunch({
|
|
|
316
319
|
result = await chromeLauncher({
|
|
317
320
|
profilePath: fullPath,
|
|
318
321
|
proxy,
|
|
322
|
+
timezoneId,
|
|
319
323
|
extraArgs,
|
|
320
324
|
spoof_fingerprint,
|
|
321
325
|
cleanupMinutes: effectiveCleanupMinutes,
|
|
@@ -325,6 +329,7 @@ export async function ppLaunch({
|
|
|
325
329
|
result = await braveLauncher({
|
|
326
330
|
profilePath: fullPath,
|
|
327
331
|
proxy,
|
|
332
|
+
timezoneId,
|
|
328
333
|
extraArgs,
|
|
329
334
|
spoof_fingerprint,
|
|
330
335
|
cleanupMinutes: effectiveCleanupMinutes,
|
|
@@ -351,7 +356,7 @@ export async function ppLaunch({
|
|
|
351
356
|
// 4. ENGINE: CHROME (CDP)
|
|
352
357
|
// ==========================================================================
|
|
353
358
|
|
|
354
|
-
async function chromeLauncher({ profilePath, proxy, extraArgs, spoof_fingerprint, cleanupMinutes }) {
|
|
359
|
+
async function chromeLauncher({ profilePath, proxy, timezoneId, extraArgs, spoof_fingerprint, cleanupMinutes }) {
|
|
355
360
|
const isPersistent = !!profilePath;
|
|
356
361
|
const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
|
|
357
362
|
|
|
@@ -365,6 +370,7 @@ async function chromeLauncher({ profilePath, proxy, extraArgs, spoof_fingerprint
|
|
|
365
370
|
profilePath: activePath,
|
|
366
371
|
isPersistent,
|
|
367
372
|
proxy,
|
|
373
|
+
timezoneId,
|
|
368
374
|
extraArgs,
|
|
369
375
|
spoof_fingerprint,
|
|
370
376
|
browserLabel: "Chrome",
|
|
@@ -375,7 +381,7 @@ async function chromeLauncher({ profilePath, proxy, extraArgs, spoof_fingerprint
|
|
|
375
381
|
// 5. ENGINE: BRAVE (CDP)
|
|
376
382
|
// ==========================================================================
|
|
377
383
|
|
|
378
|
-
async function braveLauncher({ profilePath, proxy, extraArgs, spoof_fingerprint, cleanupMinutes }) {
|
|
384
|
+
async function braveLauncher({ profilePath, proxy, timezoneId, extraArgs, spoof_fingerprint, cleanupMinutes }) {
|
|
379
385
|
const isPersistent = !!profilePath;
|
|
380
386
|
const activePath = isPersistent ? profilePath : path.join(TEMP_DIR, crypto.randomUUID());
|
|
381
387
|
|
|
@@ -445,6 +451,7 @@ async function braveLauncher({ profilePath, proxy, extraArgs, spoof_fingerprint,
|
|
|
445
451
|
profilePath: activePath,
|
|
446
452
|
isPersistent,
|
|
447
453
|
proxy,
|
|
454
|
+
timezoneId,
|
|
448
455
|
extraArgs: [...braveArgs, ...extraArgs],
|
|
449
456
|
spoof_fingerprint,
|
|
450
457
|
browserLabel: "Brave",
|
|
@@ -455,7 +462,7 @@ async function braveLauncher({ profilePath, proxy, extraArgs, spoof_fingerprint,
|
|
|
455
462
|
// 6. CDP SPAWN & CONNECT (Shared between Chrome & Brave)
|
|
456
463
|
// ==========================================================================
|
|
457
464
|
|
|
458
|
-
async function spawnAndConnect({ binaryPath, profilePath, isPersistent, proxy, extraArgs = [], spoof_fingerprint = false, browserLabel = "Browser" }) {
|
|
465
|
+
async function spawnAndConnect({ binaryPath, profilePath, isPersistent, proxy, timezoneId = null, extraArgs = [], spoof_fingerprint = false, browserLabel = "Browser" }) {
|
|
459
466
|
let browser;
|
|
460
467
|
let closing = false;
|
|
461
468
|
let signalHandler;
|
|
@@ -597,6 +604,13 @@ async function spawnAndConnect({ binaryPath, profilePath, isPersistent, proxy, e
|
|
|
597
604
|
const pages = await browser.pages();
|
|
598
605
|
const page = pages[0] ?? (await browser.newPage());
|
|
599
606
|
|
|
607
|
+
// Apply timezone emulation via CDP
|
|
608
|
+
const tz = timezoneId || undefined;
|
|
609
|
+
if (tz) {
|
|
610
|
+
await page.emulateTimezone(tz);
|
|
611
|
+
if (_launchLogs) console.log(`░░░░░ Timezone set to ${tz} for ${browserLabel}`);
|
|
612
|
+
}
|
|
613
|
+
|
|
600
614
|
// Fingerprint injection logic:
|
|
601
615
|
// 1. If persistent profile has a saved fingerprint.json → ALWAYS use it (even if spoof_fingerprint is false)
|
|
602
616
|
// 2. If spoof_fingerprint is truthy → generate new fingerprint (save it for persistent profiles)
|
|
@@ -142,6 +142,9 @@ function sanitizeResponseHeaders(headers, logger, url) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// Always ensure CORS header is present, even if server never sent it
|
|
146
|
+
cleaned["access-control-allow-origin"] = "*";
|
|
147
|
+
|
|
145
148
|
if (logger && stripped.length) console.log(`[stripGot] Response stripped ${stripped.length} headers: [${stripped.join(", ")}] → ${url}`);
|
|
146
149
|
return cleaned;
|
|
147
150
|
}
|