arn-browser 0.1.28 → 0.1.30
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
|
@@ -72,44 +72,77 @@ function createProxyAgent(proxyUrl) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
75
|
+
* Sanitizes outgoing request headers using an allowlist approach.
|
|
76
|
+
* Only keeps essential headers needed for the server to respond correctly.
|
|
77
|
+
* Everything else (cookies, auth, fingerprint hints, HTTP/2 pseudo-headers, etc.) is stripped.
|
|
77
78
|
*/
|
|
78
79
|
function sanitizeRequestHeaders(headers, logger, url) {
|
|
79
|
-
|
|
80
|
+
// Allowlist of essential request headers to keep
|
|
81
|
+
const allowedHeaders = new Set([
|
|
82
|
+
"accept",
|
|
83
|
+
"accept-encoding",
|
|
84
|
+
"accept-language",
|
|
85
|
+
"referer",
|
|
86
|
+
"user-agent",
|
|
87
|
+
"sec-ch-ua",
|
|
88
|
+
"sec-ch-ua-mobile",
|
|
89
|
+
"sec-ch-ua-platform",
|
|
90
|
+
"sec-ch-ua-platform-version",
|
|
91
|
+
"sec-fetch-dest",
|
|
92
|
+
"sec-fetch-mode",
|
|
93
|
+
"sec-fetch-site",
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const cleaned = {};
|
|
80
97
|
const stripped = [];
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (cleaned["authorization"]) { stripped.push("authorization"); delete cleaned["authorization"]; }
|
|
84
|
-
// Remove any header containing auth/token/csrf keywords
|
|
85
|
-
for (const key of Object.keys(cleaned)) {
|
|
98
|
+
|
|
99
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
86
100
|
const lower = key.toLowerCase();
|
|
87
|
-
if (
|
|
101
|
+
if (allowedHeaders.has(lower)) {
|
|
102
|
+
cleaned[lower] = value;
|
|
103
|
+
} else {
|
|
88
104
|
stripped.push(key);
|
|
89
|
-
delete cleaned[key];
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
|
-
|
|
107
|
+
|
|
108
|
+
if (logger && stripped.length) console.log(`[stripGot] Request stripped ${stripped.length} headers: [${stripped.join(", ")}] → ${url}`);
|
|
93
109
|
return cleaned;
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
/**
|
|
97
|
-
* Sanitizes response headers
|
|
98
|
-
*
|
|
99
|
-
*
|
|
113
|
+
* Sanitizes response headers using an allowlist approach.
|
|
114
|
+
* Only keeps essential headers needed for correct content delivery.
|
|
115
|
+
* Everything else (tracking, fingerprint, security policy, cookies, etc.) is stripped.
|
|
100
116
|
*/
|
|
101
117
|
function sanitizeResponseHeaders(headers, logger, url) {
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
// Allowlist of essential response headers to keep
|
|
119
|
+
const allowedHeaders = new Set([
|
|
120
|
+
"content-type",
|
|
121
|
+
"content-length",
|
|
122
|
+
"content-encoding",
|
|
123
|
+
"cache-control",
|
|
124
|
+
"etag",
|
|
125
|
+
"last-modified",
|
|
126
|
+
"location",
|
|
127
|
+
"access-control-allow-origin",
|
|
128
|
+
"accept-ranges",
|
|
129
|
+
"vary",
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const cleaned = {};
|
|
133
|
+
const stripped = [];
|
|
134
|
+
|
|
135
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
136
|
+
const lower = key.toLowerCase();
|
|
137
|
+
if (allowedHeaders.has(lower)) {
|
|
138
|
+
// Force access-control-allow-origin to wildcard for cross-origin safety
|
|
139
|
+
cleaned[lower] = lower === "access-control-allow-origin" ? "*" : value;
|
|
140
|
+
} else {
|
|
141
|
+
stripped.push(key);
|
|
142
|
+
}
|
|
107
143
|
}
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
if (cleaned["content-security-policy"]) { changes.push("content-security-policy"); delete cleaned["content-security-policy"]; }
|
|
111
|
-
if (cleaned["strict-transport-security"]) { changes.push("strict-transport-security"); delete cleaned["strict-transport-security"]; }
|
|
112
|
-
if (logger && changes.length) console.log(`[stripGot] Response stripped: [${changes.join(", ")}] → ${url}`);
|
|
144
|
+
|
|
145
|
+
if (logger && stripped.length) console.log(`[stripGot] Response stripped ${stripped.length} headers: [${stripped.join(", ")}] → ${url}`);
|
|
113
146
|
return cleaned;
|
|
114
147
|
}
|
|
115
148
|
|
|
@@ -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)
|