arn-browser 0.1.2 → 0.1.4

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.2",
3
+ "version": "0.1.4",
4
4
  "description": "A lightweight, browser autmation helper.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -64,6 +64,8 @@ export interface ProxyServerOptions {
64
64
  debug?: boolean;
65
65
  /** Track proxy usage statistics (default: true) */
66
66
  proxy_stats?: boolean;
67
+ /** Track hostname usage statistics (default: true) */
68
+ host_stats?: boolean;
67
69
  }
68
70
 
69
71
  /**
@@ -102,11 +104,17 @@ export interface ProxyServerController {
102
104
  /** The public IP of Proxy 2 */
103
105
  PROXY_2_IP: string | null;
104
106
 
107
+ /** Check if the proxy server is currently running */
108
+ isServerRunning: () => boolean;
109
+
105
110
  /** Gracefully closes the proxy server */
106
111
  closeServer: () => Promise<void>;
107
112
 
108
113
  /** Returns formatted statistics for all proxy channels */
109
114
  getProxyStats: () => Record<string, TrafficStats>;
115
+
116
+ /** Returns formatted statistics for hostnames per proxy channel */
117
+ getHostStats: () => Record<string, Record<string, TrafficStats>>;
110
118
  }
111
119
 
112
120
  /**
@@ -178,10 +178,11 @@ export async function startProxyServer({
178
178
  ip2LocationKey = null,
179
179
  debug = false,
180
180
  proxy_stats = true,
181
+ host_stats = true,
181
182
  }) {
182
183
  // 1. Matchers
183
184
  const matchers = {
184
- noProxy: createHostMatcher([...NO_PROXY_HOSTS, "brave.com", "gvt1.com"]),
185
+ noProxy: createHostMatcher([...NO_PROXY_HOSTS, "%brave.com%", "%gvt1.com%"]),
185
186
  proxy1: createHostMatcher([...PROXY_1_HOSTS, "proxy.multilogin.com", "multilogin.com"]),
186
187
  proxy2: createHostMatcher([...PROXY_2_HOSTS, "proxy.multilogin.com", "multilogin.com"]),
187
188
  };
@@ -227,7 +228,16 @@ export async function startProxyServer({
227
228
  PROXY_1: { request: 0, Tx: 0, Rx: 0 },
228
229
  PROXY_2: { request: 0, Tx: 0, Rx: 0 },
229
230
  };
230
- const connectionMap = {};
231
+ // hostStatsMap is now categorized by proxy type
232
+ const hostStatsMap = {
233
+ DEFAULT_PROXY: {},
234
+ NO_PROXY: {},
235
+ PROXY_1: {},
236
+ PROXY_2: {},
237
+ };
238
+
239
+ const connectionMap = {}; // Maps connectionId -> { type: "..." }
240
+ let serverRunning = false;
231
241
 
232
242
  // 6. Server
233
243
  const server = new ProxyChain.Server({
@@ -235,60 +245,88 @@ export async function startProxyServer({
235
245
  host: "127.0.0.1",
236
246
  verbose: debug,
237
247
  prepareRequestFunction: ({ hostname, connectionId }) => {
238
- // A. Direct (Force NO_PROXY)
248
+ let proxyType = "DEFAULT_PROXY";
249
+ let upstreamUrl = upstreamProxies.default;
250
+ let isCustomResponse = false;
251
+ let customResponseData = null;
252
+
253
+ // Logic to determine Proxy Type
239
254
  if (matchers.noProxy(hostname)) {
240
- connectionMap[connectionId] = { type: "NO_PROXY" };
241
- return { upstreamProxyUrl: null, requestAuthentication: false };
255
+ // A. Direct
256
+ proxyType = "NO_PROXY";
257
+ upstreamUrl = null;
258
+ } else if (matchers.proxy1(hostname) && upstreamProxies.p1) {
259
+ // C1. Proxy 1
260
+ proxyType = "PROXY_1";
261
+ upstreamUrl = upstreamProxies.p1;
262
+ } else if (matchers.proxy2(hostname) && upstreamProxies.p2) {
263
+ // C2. Proxy 2
264
+ proxyType = "PROXY_2";
265
+ upstreamUrl = upstreamProxies.p2;
242
266
  }
243
267
 
244
- // B. IP Check Interception (ip.bablosoft.com)
268
+ // B. IP Check Interception (Overrules standard routing for specific domain)
245
269
  if (hostname === "ip.bablosoft.com") {
270
+ isCustomResponse = true;
271
+ // Inherit the proxyType determined above to fetch the correct IP details
272
+ // (e.g. if it matched PROXY_1 matchers, we show PROXY_1 IP)
246
273
  let displayedIP;
247
-
248
- if (matchers.proxy1(hostname)) displayedIP = p1Details?.ip;
249
- else if (matchers.proxy2(hostname)) displayedIP = p2Details?.ip;
250
- else displayedIP = defaultDetails?.ip; // Uses Default (or Local fallback)
251
-
252
- return {
253
- customResponseFunction: () => ({
254
- statusCode: 200,
255
- headers: { "Content-Type": "text/plain", Connection: "close" },
256
- body: displayedIP || "Unknown IP",
257
- }),
274
+ if (proxyType === "PROXY_1") displayedIP = p1Details?.ip;
275
+ else if (proxyType === "PROXY_2") displayedIP = p2Details?.ip;
276
+ else if (proxyType === "NO_PROXY") displayedIP = "127.0.0.1";
277
+ else displayedIP = defaultDetails?.ip;
278
+
279
+ customResponseData = {
280
+ statusCode: 200,
281
+ headers: { "Content-Type": "text/plain", Connection: "close" },
282
+ body: displayedIP || "Unknown IP",
258
283
  };
259
284
  }
260
285
 
261
- // C. Routing
262
- if (matchers.proxy1(hostname) && upstreamProxies.p1) {
263
- connectionMap[connectionId] = { type: "PROXY_1" };
264
- return { upstreamProxyUrl: upstreamProxies.p1 };
286
+ // Record Stats
287
+ connectionMap[connectionId] = { type: proxyType, hostname: hostname };
288
+ if (host_stats && hostname) {
289
+ // Ensure the type exists in map (it should, but safety first)
290
+ if (!hostStatsMap[proxyType]) hostStatsMap[proxyType] = {};
291
+ if (!hostStatsMap[proxyType][hostname]) {
292
+ hostStatsMap[proxyType][hostname] = { req: 0, Tx: 0, Rx: 0 };
293
+ }
294
+ hostStatsMap[proxyType][hostname].req++;
265
295
  }
266
- if (matchers.proxy2(hostname) && upstreamProxies.p2) {
267
- connectionMap[connectionId] = { type: "PROXY_2" };
268
- return { upstreamProxyUrl: upstreamProxies.p2 };
296
+
297
+ // Return Decision
298
+ if (isCustomResponse) {
299
+ return { customResponseFunction: () => customResponseData };
269
300
  }
270
301
 
271
- // D. Default
272
- connectionMap[connectionId] = { type: "DEFAULT_PROXY" };
273
- return { upstreamProxyUrl: upstreamProxies.default };
302
+ return {
303
+ upstreamProxyUrl: upstreamUrl,
304
+ requestAuthentication: false, // Auto-handle upstream auth via URL
305
+ };
274
306
  },
275
307
  });
276
308
 
277
309
  server.on("connectionClosed", ({ connectionId, stats: connStats }) => {
278
310
  const connectionInfo = connectionMap[connectionId];
279
- if (connectionInfo && proxy_stats) {
280
- const { type } = connectionInfo;
281
- if (stats[type]) {
311
+ if (connectionInfo) {
312
+ const { type, hostname } = connectionInfo;
313
+ if (proxy_stats && stats[type]) {
282
314
  stats[type].request++;
283
315
  stats[type].Tx += connStats.srcTxBytes;
284
316
  stats[type].Rx += connStats.srcRxBytes;
285
317
  }
318
+ // Update host stats with Tx/Rx on connection close
319
+ if (host_stats && hostname && hostStatsMap[type] && hostStatsMap[type][hostname]) {
320
+ hostStatsMap[type][hostname].Tx += connStats.srcTxBytes;
321
+ hostStatsMap[type][hostname].Rx += connStats.srcRxBytes;
322
+ }
286
323
  }
287
324
  delete connectionMap[connectionId];
288
325
  });
289
326
 
290
327
  try {
291
328
  await server.listen();
329
+ serverRunning = true;
292
330
  console.log(`✅ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
293
331
  } catch (err) {
294
332
  console.error("❌ Failed to start proxy server:", err);
@@ -297,6 +335,41 @@ export async function startProxyServer({
297
335
 
298
336
  const formatBytes = (bytes) => (bytes / 1024 / 1024).toFixed(2);
299
337
 
338
+ const getProxyStatsFormatted = () => {
339
+ const formatted = {};
340
+ for (const [key, val] of Object.entries(stats)) {
341
+ formatted[key] = {
342
+ req: val.request,
343
+ Tx: formatBytes(val.Tx) + " MB",
344
+ Rx: formatBytes(val.Rx) + " MB",
345
+ };
346
+ }
347
+ return formatted;
348
+ };
349
+
350
+ const getHostStatsFormatted = () => {
351
+ const result = {};
352
+ // Iterate over each proxy category
353
+ for (const [type, hosts] of Object.entries(hostStatsMap)) {
354
+ const sortedHosts = Object.entries(hosts)
355
+ .sort((a, b) => b[1].req - a[1].req) // Sort by request count descending
356
+ .reduce((acc, [host, hostData]) => {
357
+ acc[host] = {
358
+ req: hostData.req,
359
+ Tx: formatBytes(hostData.Tx) + " MB",
360
+ Rx: formatBytes(hostData.Rx) + " MB",
361
+ };
362
+ return acc;
363
+ }, {});
364
+
365
+ // Only include categories that have traffic
366
+ if (Object.keys(sortedHosts).length > 0) {
367
+ result[type] = sortedHosts;
368
+ }
369
+ }
370
+ return result;
371
+ };
372
+
300
373
  // 7. Return Controller
301
374
  return {
302
375
  port: selectedPort,
@@ -318,20 +391,24 @@ export async function startProxyServer({
318
391
  PROXY_1_IP: p1Details?.ip || null,
319
392
  PROXY_2_IP: p2Details?.ip || null,
320
393
 
394
+ isServerRunning: () => serverRunning,
395
+
321
396
  closeServer: async () => {
322
397
  await server.close(true);
323
- console.log("🔒 Proxy server closed.");
324
- },
325
- getProxyStats: () => {
326
- const formatted = {};
327
- for (const [key, val] of Object.entries(stats)) {
328
- formatted[key] = {
329
- req: val.request,
330
- Tx: formatBytes(val.Tx) + " MB",
331
- Rx: formatBytes(val.Rx) + " MB",
332
- };
398
+ serverRunning = false;
399
+
400
+ // Auto console.log stats on close
401
+ if (proxy_stats) {
402
+ console.log("📊 Proxy Stats:", getProxyStatsFormatted());
403
+ }
404
+ if (host_stats) {
405
+ console.log("🌐 Host Stats:", getHostStatsFormatted());
333
406
  }
334
- return formatted;
407
+
408
+ console.log("🔒 Proxy server closed.");
335
409
  },
410
+
411
+ getProxyStats: getProxyStatsFormatted,
412
+ getHostStats: getHostStatsFormatted,
336
413
  };
337
414
  }