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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arn-browser",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "A lightweight, browser autmation helper.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -72,44 +72,77 @@ function createProxyAgent(proxyUrl) {
72
72
  }
73
73
 
74
74
  /**
75
- * Strips sensitive headers from outgoing request headers.
76
- * Removes cookie and authorization to prevent session/IP correlation.
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
- const cleaned = { ...headers };
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
- // Remove known auth headers
82
- if (cleaned["cookie"]) { stripped.push("cookie"); delete cleaned["cookie"]; }
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 (lower.includes("token") || lower.includes("csrf") || lower.includes("auth")) {
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
- if (logger && stripped.length) console.log(`[stripGot] Request stripped: [${stripped.join(", ")}] → ${url}`);
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 for safe caching across origins.
98
- * - Replaces access-control-allow-origin with * (if present)
99
- * - Removes access-control-allow-credentials, set-cookie, CSP, HSTS
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
- const cleaned = { ...headers };
103
- const changes = [];
104
- if (cleaned["access-control-allow-origin"]) {
105
- changes.push(`access-control-allow-origin: ${cleaned["access-control-allow-origin"]} → *`);
106
- cleaned["access-control-allow-origin"] = "*";
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
- if (cleaned["access-control-allow-credentials"]) { changes.push("access-control-allow-credentials"); delete cleaned["access-control-allow-credentials"]; }
109
- if (cleaned["set-cookie"]) { changes.push("set-cookie"); delete cleaned["set-cookie"]; }
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 data = { country, sessionType, protocol, region, city, IPTTL, count: 1 };
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. STORAGE & PROFILES
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
- // 3. NETWORK
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
- // 4. BROWSER ARGS
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
- // 5. ENGINE SPECIFIC
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
- // 6. FINGERPRINT SPOOFING
131
+ // 7. FINGERPRINT SPOOFING
121
132
  // ========================================================================
122
133
 
123
134
  /**
@@ -169,7 +180,7 @@ export interface PpLaunchOptions {
169
180
  };
170
181
 
171
182
  // ========================================================================
172
- // 7. LOGGING
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)