arn-browser 0.1.19 → 0.1.21

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.19",
3
+ "version": "0.1.21",
4
4
  "description": "A lightweight, browser autmation helper.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -32,9 +32,12 @@ export interface PwRouteOptions {
32
32
  /** Enable caching for requests */
33
33
  useCache?: boolean;
34
34
 
35
- /** Strip cookies/auth from outgoing requests and sanitize CORS/CSP/set-cookie from responses (default: false) */
35
+ /** Strip cookies/auth from outgoing requests and sanitize CORS/CSP/set-cookie from responses (default: true) */
36
36
  stripGotHeaders?: boolean;
37
37
 
38
+ /** Log stripped headers to console for debugging (default: false) */
39
+ stripGotLogger?: boolean;
40
+
38
41
  /**
39
42
  * Proxy for custom fetch requests (only used when useGot is true).
40
43
  * String: "http://host:port", "socks5://user:pass@host:port"
@@ -75,10 +75,21 @@ function createProxyAgent(proxyUrl) {
75
75
  * Strips sensitive headers from outgoing request headers.
76
76
  * Removes cookie and authorization to prevent session/IP correlation.
77
77
  */
78
- function sanitizeRequestHeaders(headers) {
78
+ function sanitizeRequestHeaders(headers, logger, url) {
79
79
  const cleaned = { ...headers };
80
- delete cleaned["cookie"];
81
- delete cleaned["authorization"];
80
+ 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)) {
86
+ const lower = key.toLowerCase();
87
+ if (lower.includes("token") || lower.includes("csrf") || lower.includes("auth")) {
88
+ stripped.push(key);
89
+ delete cleaned[key];
90
+ }
91
+ }
92
+ if (logger && stripped.length) console.log(`[stripGot] Request stripped: [${stripped.join(", ")}] → ${url}`);
82
93
  return cleaned;
83
94
  }
84
95
 
@@ -87,15 +98,18 @@ function sanitizeRequestHeaders(headers) {
87
98
  * - Replaces access-control-allow-origin with * (if present)
88
99
  * - Removes access-control-allow-credentials, set-cookie, CSP, HSTS
89
100
  */
90
- function sanitizeResponseHeaders(headers) {
101
+ function sanitizeResponseHeaders(headers, logger, url) {
91
102
  const cleaned = { ...headers };
103
+ const changes = [];
92
104
  if (cleaned["access-control-allow-origin"]) {
105
+ changes.push(`access-control-allow-origin: ${cleaned["access-control-allow-origin"]} → *`);
93
106
  cleaned["access-control-allow-origin"] = "*";
94
107
  }
95
- delete cleaned["access-control-allow-credentials"];
96
- delete cleaned["set-cookie"];
97
- delete cleaned["content-security-policy"];
98
- delete cleaned["strict-transport-security"];
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}`);
99
113
  return cleaned;
100
114
  }
101
115
 
@@ -110,9 +124,10 @@ function sanitizeResponseHeaders(headers) {
110
124
  * @param {string|false} logger - Log level: "info" (success+error), "error" (errors only), false (no logs)
111
125
  * @param {Object|null} proxyAgent - Proxy agent to use for the request
112
126
  * @param {boolean} stripHeaders - Whether to sanitize request/response headers
127
+ * @param {boolean} stripLogger - Whether to log stripped headers
113
128
  * @returns {Promise<Object>} - The response object containing status, headers, and body
114
129
  */
115
- async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl, logger, proxyAgent, stripHeaders) {
130
+ async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl, logger, proxyAgent, stripHeaders, stripLogger) {
116
131
  // Determine the cache key based on configuration
117
132
  let mainUrl = new URL(url).origin + new URL(url).pathname;
118
133
  if (useFullUrl) {
@@ -132,7 +147,7 @@ async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl
132
147
 
133
148
  try {
134
149
  // Sanitize outgoing request headers if stripHeaders is enabled
135
- const finalHeaders = stripHeaders ? sanitizeRequestHeaders(requestHeaders) : requestHeaders;
150
+ const finalHeaders = stripHeaders ? sanitizeRequestHeaders(requestHeaders, stripLogger, url) : requestHeaders;
136
151
 
137
152
  // Fetch the resource using superagent
138
153
  // buffer(true) ensures we get the raw binary data (essential for images/fonts)
@@ -149,7 +164,7 @@ async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl
149
164
  const responseBody = response.body instanceof Buffer ? response.body : response.text;
150
165
 
151
166
  // Sanitize response headers if stripHeaders is enabled
152
- const finalResponseHeaders = stripHeaders ? sanitizeResponseHeaders(response.headers) : response.headers;
167
+ const finalResponseHeaders = stripHeaders ? sanitizeResponseHeaders(response.headers, stripLogger, url) : response.headers;
153
168
 
154
169
  // Save to cache only when caching is enabled
155
170
  if (useCache) {
@@ -201,6 +216,7 @@ export async function pwRoute({
201
216
  useFullUrl = true,
202
217
  useCache = true,
203
218
  stripGotHeaders = true,
219
+ stripGotLogger = false,
204
220
  proxy = null,
205
221
  m4w_send_on_post = null,
206
222
  m4w_send_on_message = null,
@@ -394,7 +410,8 @@ export async function pwRoute({
394
410
  useFullUrl,
395
411
  logger,
396
412
  proxyAgent,
397
- stripGotHeaders
413
+ stripGotHeaders,
414
+ stripGotLogger
398
415
  );
399
416
 
400
417
  if (response) {
@@ -235,10 +235,10 @@ export async function startProxyServer({
235
235
 
236
236
  // 5. Stats
237
237
  const stats = {
238
- DEFAULT_PROXY: { request: 0, Tx: 0, Rx: 0 },
239
- NO_PROXY: { request: 0, Tx: 0, Rx: 0 },
240
- PROXY_1: { request: 0, Tx: 0, Rx: 0 },
241
- PROXY_2: { request: 0, Tx: 0, Rx: 0 },
238
+ DEFAULT_PROXY: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
239
+ NO_PROXY: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
240
+ PROXY_1: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
241
+ PROXY_2: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
242
242
  };
243
243
  // hostStatsMap is now categorized by proxy type
244
244
  const hostStatsMap = {
@@ -301,7 +301,7 @@ export async function startProxyServer({
301
301
  // Ensure the type exists in map (it should, but safety first)
302
302
  if (!hostStatsMap[proxyType]) hostStatsMap[proxyType] = {};
303
303
  if (!hostStatsMap[proxyType][hostname]) {
304
- hostStatsMap[proxyType][hostname] = { req: 0, Tx: 0, Rx: 0 };
304
+ hostStatsMap[proxyType][hostname] = { req: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 };
305
305
  }
306
306
  hostStatsMap[proxyType][hostname].req++;
307
307
  }
@@ -320,17 +320,26 @@ export async function startProxyServer({
320
320
 
321
321
  server.on("connectionClosed", ({ connectionId, stats: connStats }) => {
322
322
  const connectionInfo = connectionMap[connectionId];
323
- if (connectionInfo) {
323
+ if (connectionInfo && connStats) {
324
324
  const { type, hostname } = connectionInfo;
325
+ const srcTx = connStats.srcTxBytes || 0;
326
+ const srcRx = connStats.srcRxBytes || 0;
327
+ const trgTx = connStats.trgTxBytes || 0;
328
+ const trgRx = connStats.trgRxBytes || 0;
329
+
325
330
  if (proxy_stats && stats[type]) {
326
331
  stats[type].request++;
327
- stats[type].Tx += connStats.srcTxBytes;
328
- stats[type].Rx += connStats.srcRxBytes;
332
+ stats[type].srcTx += srcTx;
333
+ stats[type].srcRx += srcRx;
334
+ stats[type].trgTx += trgTx;
335
+ stats[type].trgRx += trgRx;
329
336
  }
330
- // Update host stats with Tx/Rx on connection close
337
+ // Update host stats
331
338
  if (host_stats && hostname && hostStatsMap[type] && hostStatsMap[type][hostname]) {
332
- hostStatsMap[type][hostname].Tx += connStats.srcTxBytes;
333
- hostStatsMap[type][hostname].Rx += connStats.srcRxBytes;
339
+ hostStatsMap[type][hostname].srcTx += srcTx;
340
+ hostStatsMap[type][hostname].srcRx += srcRx;
341
+ hostStatsMap[type][hostname].trgTx += trgTx;
342
+ hostStatsMap[type][hostname].trgRx += trgRx;
334
343
  }
335
344
  }
336
345
  delete connectionMap[connectionId];
@@ -345,15 +354,21 @@ export async function startProxyServer({
345
354
  return null;
346
355
  }
347
356
 
348
- const formatBytes = (bytes) => (bytes / 1024 / 1024).toFixed(3);
357
+ const formatBytes = (bytes) => {
358
+ if (bytes < 1024) return bytes + " B";
359
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
360
+ return (bytes / (1024 * 1024)).toFixed(3) + " MB";
361
+ };
349
362
 
350
363
  const getProxyStatsFormatted = () => {
351
364
  const formatted = {};
352
365
  for (const [key, val] of Object.entries(stats)) {
353
366
  formatted[key] = {
354
367
  req: val.request,
355
- Tx: formatBytes(val.Tx) + " MB",
356
- Rx: formatBytes(val.Rx) + " MB",
368
+ sTx: formatBytes(val.srcTx),
369
+ sRx: formatBytes(val.srcRx),
370
+ tTx: formatBytes(val.trgTx),
371
+ tRx: formatBytes(val.trgRx),
357
372
  };
358
373
  }
359
374
  return formatted;
@@ -361,20 +376,20 @@ export async function startProxyServer({
361
376
 
362
377
  const getHostStatsFormatted = () => {
363
378
  const result = {};
364
- // Iterate over each proxy category
365
379
  for (const [type, hosts] of Object.entries(hostStatsMap)) {
366
380
  const sortedHosts = Object.entries(hosts)
367
- .sort((a, b) => b[1].req - a[1].req) // Sort by request count descending
381
+ .sort((a, b) => b[1].req - a[1].req)
368
382
  .reduce((acc, [host, hostData]) => {
369
383
  acc[host] = {
370
384
  req: hostData.req,
371
- Tx: formatBytes(hostData.Tx) + " MB",
372
- Rx: formatBytes(hostData.Rx) + " MB",
385
+ sTx: formatBytes(hostData.srcTx),
386
+ sRx: formatBytes(hostData.srcRx),
387
+ tTx: formatBytes(hostData.trgTx),
388
+ tRx: formatBytes(hostData.trgRx),
373
389
  };
374
390
  return acc;
375
391
  }, {});
376
392
 
377
- // Only include categories that have traffic
378
393
  if (Object.keys(sortedHosts).length > 0) {
379
394
  result[type] = sortedHosts;
380
395
  }
@@ -406,16 +421,34 @@ export async function startProxyServer({
406
421
  isServerRunning: () => serverRunning,
407
422
 
408
423
  closeServer: async () => {
409
- // Close the server — triggers connectionClosed events which accumulate byte stats
424
+ // Close the server — triggers socket destroys
410
425
  await server.close(true);
411
426
  serverRunning = false;
412
427
 
413
- // Log stats AFTER close so all connectionClosed events have fired
428
+ // Wait up to 2000ms (2 seconds) for all async 'connectionClosed' events to fire
429
+ // When sockets are destroyed, their 'close' events happen asynchronously
430
+ let maxWait = 200; // 200 * 10ms = 2000ms
431
+ while (Object.keys(connectionMap).length > 0 && maxWait > 0) {
432
+ await new Promise((resolve) => setTimeout(resolve, 10));
433
+ maxWait--;
434
+ }
435
+
436
+ // Log stats AFTER close so all connectionClosed events have accumulated bytes
414
437
  if (proxy_stats) {
415
- console.log("░░ Proxy Stats:", getProxyStatsFormatted());
438
+ console.log("\n░░ Proxy Stats:");
439
+ for (const [type, data] of Object.entries(getProxyStatsFormatted())) {
440
+ console.log(` ${type}: { req: ${data.req}, sTx: '${data.sTx}', sRx: '${data.sRx}', tTx: '${data.tTx}', tRx: '${data.tRx}' }`);
441
+ }
416
442
  }
417
443
  if (host_stats) {
418
- console.log("░░ Host Stats:", getHostStatsFormatted());
444
+ console.log("░░ Host Stats:");
445
+ for (const [type, hosts] of Object.entries(getHostStatsFormatted())) {
446
+ console.log(` ${type}: {`);
447
+ for (const [host, data] of Object.entries(hosts)) {
448
+ console.log(` '${host}': { req: ${data.req}, sTx: '${data.sTx}', sRx: '${data.sRx}', tTx: '${data.tTx}', tRx: '${data.tRx}' }`);
449
+ }
450
+ console.log(` }`);
451
+ }
419
452
  }
420
453
 
421
454
  console.log("░░ Proxy server closed.");
@@ -29,9 +29,12 @@ export interface PpRouteOptions {
29
29
  /** Enable caching for requests */
30
30
  useCache?: boolean;
31
31
 
32
- /** Strip cookies/auth from outgoing requests and sanitize CORS/CSP/set-cookie from responses (default: false) */
32
+ /** Strip cookies/auth from outgoing requests and sanitize CORS/CSP/set-cookie from responses (default: true) */
33
33
  stripGotHeaders?: boolean;
34
34
 
35
+ /** Log stripped headers to console for debugging (default: false) */
36
+ stripGotLogger?: boolean;
37
+
35
38
  /**
36
39
  * Proxy for custom fetch requests (only used when useGot is true).
37
40
  * String: "http://host:port", "socks5://user:pass@host:port"
@@ -75,10 +75,21 @@ function createProxyAgent(proxyUrl) {
75
75
  * Strips sensitive headers from outgoing request headers.
76
76
  * Removes cookie and authorization to prevent session/IP correlation.
77
77
  */
78
- function sanitizeRequestHeaders(headers) {
78
+ function sanitizeRequestHeaders(headers, logger, url) {
79
79
  const cleaned = { ...headers };
80
- delete cleaned["cookie"];
81
- delete cleaned["authorization"];
80
+ 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)) {
86
+ const lower = key.toLowerCase();
87
+ if (lower.includes("token") || lower.includes("csrf") || lower.includes("auth")) {
88
+ stripped.push(key);
89
+ delete cleaned[key];
90
+ }
91
+ }
92
+ if (logger && stripped.length) console.log(`[stripGot] Request stripped: [${stripped.join(", ")}] → ${url}`);
82
93
  return cleaned;
83
94
  }
84
95
 
@@ -87,15 +98,18 @@ function sanitizeRequestHeaders(headers) {
87
98
  * - Replaces access-control-allow-origin with * (if present)
88
99
  * - Removes access-control-allow-credentials, set-cookie, CSP, HSTS
89
100
  */
90
- function sanitizeResponseHeaders(headers) {
101
+ function sanitizeResponseHeaders(headers, logger, url) {
91
102
  const cleaned = { ...headers };
103
+ const changes = [];
92
104
  if (cleaned["access-control-allow-origin"]) {
105
+ changes.push(`access-control-allow-origin: ${cleaned["access-control-allow-origin"]} → *`);
93
106
  cleaned["access-control-allow-origin"] = "*";
94
107
  }
95
- delete cleaned["access-control-allow-credentials"];
96
- delete cleaned["set-cookie"];
97
- delete cleaned["content-security-policy"];
98
- delete cleaned["strict-transport-security"];
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}`);
99
113
  return cleaned;
100
114
  }
101
115
 
@@ -110,9 +124,10 @@ function sanitizeResponseHeaders(headers) {
110
124
  * @param {string|false} logger - Log level: "info" (success+error), "error" (errors only), false (no logs)
111
125
  * @param {Object|null} proxyAgent - Proxy agent to use for the request
112
126
  * @param {boolean} stripHeaders - Whether to sanitize request/response headers
127
+ * @param {boolean} stripLogger - Whether to log stripped headers
113
128
  * @returns {Promise<Object>} - The response object containing status, headers, and body
114
129
  */
115
- async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl, logger, proxyAgent, stripHeaders) {
130
+ async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl, logger, proxyAgent, stripHeaders, stripLogger) {
116
131
  // Determine the cache key based on configuration
117
132
  let mainUrl = new URL(url).origin + new URL(url).pathname;
118
133
  if (useFullUrl) {
@@ -132,7 +147,7 @@ async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl
132
147
 
133
148
  try {
134
149
  // Sanitize outgoing request headers if stripHeaders is enabled
135
- const finalHeaders = stripHeaders ? sanitizeRequestHeaders(requestHeaders) : requestHeaders;
150
+ const finalHeaders = stripHeaders ? sanitizeRequestHeaders(requestHeaders, stripLogger, url) : requestHeaders;
136
151
 
137
152
  // Fetch the resource using superagent
138
153
  // buffer(true) ensures we get the raw binary data (essential for images/fonts)
@@ -149,7 +164,7 @@ async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl
149
164
  const responseBody = response.body instanceof Buffer ? response.body : response.text;
150
165
 
151
166
  // Sanitize response headers if stripHeaders is enabled
152
- const finalResponseHeaders = stripHeaders ? sanitizeResponseHeaders(response.headers) : response.headers;
167
+ const finalResponseHeaders = stripHeaders ? sanitizeResponseHeaders(response.headers, stripLogger, url) : response.headers;
153
168
 
154
169
  // Save to cache only when caching is enabled
155
170
  if (useCache) {
@@ -199,6 +214,7 @@ export async function ppRoute({
199
214
  useFullUrl = true,
200
215
  useCache = true,
201
216
  stripGotHeaders = true,
217
+ stripGotLogger = false,
202
218
  proxy = null,
203
219
  m4w_send_on_post = null,
204
220
  m4w_send_on_message = null,
@@ -397,7 +413,8 @@ export async function ppRoute({
397
413
  useFullUrl,
398
414
  logger,
399
415
  proxyAgent,
400
- stripGotHeaders
416
+ stripGotHeaders,
417
+ stripGotLogger
401
418
  );
402
419
 
403
420
  if (response) {