arn-browser 0.1.1 → 0.1.3

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.1",
3
+ "version": "0.1.3",
4
4
  "description": "A lightweight, browser autmation helper.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -91,3 +91,38 @@ export function isProxyAlive(proxyHost: string, proxyPort: number): Promise<stri
91
91
  * @param timeout - Request timeout in ms.
92
92
  */
93
93
  export function nativeGet(url: string, agent?: any, timeout?: number): Promise<string>;
94
+
95
+ /**
96
+ * Proxy configuration object returned by fetchAwsProxy.
97
+ */
98
+ export interface ProxyConfig {
99
+ type: "socks5" | "http";
100
+ host: string;
101
+ port: number;
102
+ user: string | null;
103
+ pass: string | null;
104
+ }
105
+
106
+ /**
107
+ * Parameters for fetchAwsProxy function.
108
+ */
109
+ export interface FetchAwsProxyParams {
110
+ instance_name: string;
111
+ /**
112
+ * If true, uses getActiveProxyWithStartStop (hard reset).
113
+ * If false, uses getActiveProxy (IP cycling).
114
+ * @default true
115
+ */
116
+ useStartStop?: boolean;
117
+ /**
118
+ * The proxy type. Determines the port (socks5: 10001, http: 9001).
119
+ * @default 'socks5'
120
+ */
121
+ proxy_type?: "socks5" | "http";
122
+ }
123
+
124
+ /**
125
+ * Fetches an active AWS proxy and returns it in a standardized format.
126
+ * @returns The proxy configuration object or null if not found.
127
+ */
128
+ export function fetchAwsProxy(params: FetchAwsProxyParams): Promise<ProxyConfig | null>;
@@ -623,3 +623,43 @@ export async function isProxyAlive(proxyHost, proxyPort) {
623
623
 
624
624
  return null;
625
625
  }
626
+
627
+ // ==========================================
628
+ // SECTION 8: HIGH-LEVEL PROXY FETCH
629
+ // ==========================================
630
+
631
+ /**
632
+ * @typedef {Object} ProxyConfig
633
+ * @property {'socks5'|'http'} type - The proxy type.
634
+ * @property {string} host - The proxy host IP address.
635
+ * @property {number} port - The proxy port.
636
+ * @property {string|null} user - The proxy username (null if not required).
637
+ * @property {string|null} pass - The proxy password (null if not required).
638
+ */
639
+
640
+ /**
641
+ * Fetches an active AWS proxy and returns it in a standardized format.
642
+ *
643
+ * @param {Object} params - The parameters object.
644
+ * @param {string} params.instance_name - The name of the EC2 instance.
645
+ * @param {boolean} [params.useStartStop=true] - If true, uses getActiveProxyWithStartStop (hard reset).
646
+ * If false, uses getActiveProxy (IP cycling).
647
+ * @param {'socks5'|'http'} [params.proxy_type='socks5'] - The proxy type (socks5: port 10001, http: port 9001).
648
+ * @returns {Promise<ProxyConfig|null>} - The proxy configuration object or null if not found.
649
+ */
650
+ export const fetchAwsProxy = async ({ instance_name, useStartStop = true, proxy_type = "socks5" }) => {
651
+ let getawsproxy;
652
+ if (useStartStop) {
653
+ getawsproxy = await getActiveProxyWithStartStop({ instance_name: instance_name });
654
+ } else {
655
+ getawsproxy = await getActiveProxy({ instance_name: instance_name });
656
+ }
657
+ if (!getawsproxy) {
658
+ console.log("Aws proxy not found");
659
+ return null;
660
+ }
661
+ /** @type {ProxyConfig} */
662
+ const port = proxy_type === "socks5" ? 10001 : 9001;
663
+ let temp_proxy = { type: proxy_type, host: getawsproxy, port: port, user: null, pass: null };
664
+ return temp_proxy;
665
+ };
@@ -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, number>>;
110
118
  }
111
119
 
112
120
  /**
@@ -178,6 +178,7 @@ 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 = {
@@ -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,42 +245,61 @@ 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 };
288
+ if (host_stats && hostname) {
289
+ // Ensure the type exists in map (it should, but safety first)
290
+ if (!hostStatsMap[proxyType]) hostStatsMap[proxyType] = {};
291
+ hostStatsMap[proxyType][hostname] = (hostStatsMap[proxyType][hostname] || 0) + 1;
265
292
  }
266
- if (matchers.proxy2(hostname) && upstreamProxies.p2) {
267
- connectionMap[connectionId] = { type: "PROXY_2" };
268
- return { upstreamProxyUrl: upstreamProxies.p2 };
293
+
294
+ // Return Decision
295
+ if (isCustomResponse) {
296
+ return { customResponseFunction: () => customResponseData };
269
297
  }
270
298
 
271
- // D. Default
272
- connectionMap[connectionId] = { type: "DEFAULT_PROXY" };
273
- return { upstreamProxyUrl: upstreamProxies.default };
299
+ return {
300
+ upstreamProxyUrl: upstreamUrl,
301
+ requestAuthentication: false, // Auto-handle upstream auth via URL
302
+ };
274
303
  },
275
304
  });
276
305
 
@@ -289,6 +318,7 @@ export async function startProxyServer({
289
318
 
290
319
  try {
291
320
  await server.listen();
321
+ serverRunning = true;
292
322
  console.log(`✅ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
293
323
  } catch (err) {
294
324
  console.error("❌ Failed to start proxy server:", err);
@@ -297,6 +327,37 @@ export async function startProxyServer({
297
327
 
298
328
  const formatBytes = (bytes) => (bytes / 1024 / 1024).toFixed(2);
299
329
 
330
+ const getProxyStatsFormatted = () => {
331
+ const formatted = {};
332
+ for (const [key, val] of Object.entries(stats)) {
333
+ formatted[key] = {
334
+ req: val.request,
335
+ Tx: formatBytes(val.Tx) + " MB",
336
+ Rx: formatBytes(val.Rx) + " MB",
337
+ };
338
+ }
339
+ return formatted;
340
+ };
341
+
342
+ const getHostStatsFormatted = () => {
343
+ const result = {};
344
+ // Iterate over each proxy category
345
+ for (const [type, hosts] of Object.entries(hostStatsMap)) {
346
+ const sortedHosts = Object.entries(hosts)
347
+ .sort((a, b) => b[1] - a[1]) // Sort by count descending
348
+ .reduce((acc, [host, count]) => {
349
+ acc[host] = count;
350
+ return acc;
351
+ }, {});
352
+
353
+ // Only include categories that have traffic
354
+ if (Object.keys(sortedHosts).length > 0) {
355
+ result[type] = sortedHosts;
356
+ }
357
+ }
358
+ return result;
359
+ };
360
+
300
361
  // 7. Return Controller
301
362
  return {
302
363
  port: selectedPort,
@@ -318,20 +379,24 @@ export async function startProxyServer({
318
379
  PROXY_1_IP: p1Details?.ip || null,
319
380
  PROXY_2_IP: p2Details?.ip || null,
320
381
 
382
+ isServerRunning: () => serverRunning,
383
+
321
384
  closeServer: async () => {
322
385
  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
- };
386
+ serverRunning = false;
387
+
388
+ // Auto console.log stats on close
389
+ if (proxy_stats) {
390
+ console.log("📊 Proxy Stats:", getProxyStatsFormatted());
391
+ }
392
+ if (host_stats) {
393
+ console.log("🌐 Host Stats:", getHostStatsFormatted());
333
394
  }
334
- return formatted;
395
+
396
+ console.log("🔒 Proxy server closed.");
335
397
  },
398
+
399
+ getProxyStats: getProxyStatsFormatted,
400
+ getHostStats: getHostStatsFormatted,
336
401
  };
337
402
  }