arn-browser 0.1.30 → 0.1.31

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.30",
3
+ "version": "0.1.31",
4
4
  "description": "A lightweight, browser autmation helper.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -208,14 +208,7 @@ export async function startProxyServer({
208
208
  proxy2: createHostMatcher([...PROXY_2_HOSTS, "proxy.multilogin.com", "multilogin.com"]),
209
209
  };
210
210
 
211
- // 2. Port
212
- const selectedPort = await findAvailablePort(50001, 50010);
213
- if (!selectedPort) {
214
- console.error("░░ Critical Error: No available ports.");
215
- return null;
216
- }
217
-
218
- // 3. Build URLs
211
+ // 2. Build URLs
219
212
  const buildURL = (data) => {
220
213
  if (!data) return null; // Returns null if no config
221
214
  const { type = "http", host, port, user, pass } = data;
@@ -230,7 +223,7 @@ export async function startProxyServer({
230
223
  p2: buildURL(PROXY_2_DATA),
231
224
  };
232
225
 
233
- // 4. Fetch Details (Simplified Logic)
226
+ // 3. Fetch Details (Simplified Logic)
234
227
  // We pass the URL (or null). The function handles the "Local" logic.
235
228
  const [defaultDetails, p1Details, p2Details] = await Promise.all([
236
229
  fetchProxyDetails(upstreamProxies.default, ip2LocationKey, retryDelayMs),
@@ -243,7 +236,7 @@ export async function startProxyServer({
243
236
  if (upstreamProxies.p1 && !p1Details) { console.warn("░░ Warning: PROXY_1 configured but unreachable."); return null; }
244
237
  if (upstreamProxies.p2 && !p2Details) { console.warn("░░ Warning: PROXY_2 configured but unreachable."); return null; }
245
238
 
246
- // 5. Stats
239
+ // 4. Stats
247
240
  const stats = {
248
241
  DEFAULT_PROXY: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
249
242
  NO_PROXY: { request: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 },
@@ -261,72 +254,105 @@ export async function startProxyServer({
261
254
  const connectionMap = {}; // Maps connectionId -> { type: "..." }
262
255
  let serverRunning = false;
263
256
 
264
- // 6. Server
265
- const server = new ProxyChain.Server({
266
- port: selectedPort,
267
- host: "127.0.0.1",
268
- verbose: debug,
269
- prepareRequestFunction: ({ hostname, connectionId }) => {
270
- let proxyType = "DEFAULT_PROXY";
271
- let upstreamUrl = upstreamProxies.default;
272
- let isCustomResponse = false;
273
- let customResponseData = null;
274
-
275
- // Logic to determine Proxy Type
276
- if (matchers.noProxy(hostname)) {
277
- // A. Direct
278
- proxyType = "NO_PROXY";
279
- upstreamUrl = null;
280
- } else if (matchers.proxy1(hostname) && upstreamProxies.p1) {
281
- // C1. Proxy 1
282
- proxyType = "PROXY_1";
283
- upstreamUrl = upstreamProxies.p1;
284
- } else if (matchers.proxy2(hostname) && upstreamProxies.p2) {
285
- // C2. Proxy 2
286
- proxyType = "PROXY_2";
287
- upstreamUrl = upstreamProxies.p2;
288
- }
257
+ // 5. Create server and bind with retry (handles race conditions between concurrent processes)
258
+ const PORT_RANGE_START = 50001;
259
+ const PORT_RANGE_END = 50200;
260
+ const MAX_BIND_RETRIES = 10;
289
261
 
290
- // B. IP Check Interception (Overrules standard routing for specific domain)
291
- if (hostname === "ip.bablosoft.com") {
292
- isCustomResponse = true;
293
- // Inherit the proxyType determined above to fetch the correct IP details
294
- // (e.g. if it matched PROXY_1 matchers, we show PROXY_1 IP)
295
- let displayedIP;
296
- if (proxyType === "PROXY_1") displayedIP = p1Details?.ip;
297
- else if (proxyType === "PROXY_2") displayedIP = p2Details?.ip;
298
- else if (proxyType === "NO_PROXY") displayedIP = "127.0.0.1";
299
- else displayedIP = defaultDetails?.ip;
300
-
301
- customResponseData = {
302
- statusCode: 200,
303
- headers: { "Content-Type": "text/plain", Connection: "close" },
304
- body: displayedIP || "Unknown IP",
305
- };
306
- }
262
+ let selectedPort = null;
263
+ let server = null;
264
+
265
+ for (let attempt = 1; attempt <= MAX_BIND_RETRIES; attempt++) {
266
+ const candidatePort = await findAvailablePort(PORT_RANGE_START, PORT_RANGE_END);
267
+ if (!candidatePort) {
268
+ console.error("░░ Critical Error: No available ports in range " + PORT_RANGE_START + "-" + PORT_RANGE_END);
269
+ return null;
270
+ }
307
271
 
308
- // Record Stats
309
- connectionMap[connectionId] = { type: proxyType, hostname: hostname };
310
- if (host_stats && hostname) {
311
- // Ensure the type exists in map (it should, but safety first)
312
- if (!hostStatsMap[proxyType]) hostStatsMap[proxyType] = {};
313
- if (!hostStatsMap[proxyType][hostname]) {
314
- hostStatsMap[proxyType][hostname] = { req: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 };
272
+ server = new ProxyChain.Server({
273
+ port: candidatePort,
274
+ host: "127.0.0.1",
275
+ verbose: debug,
276
+ prepareRequestFunction: ({ hostname, connectionId }) => {
277
+ let proxyType = "DEFAULT_PROXY";
278
+ let upstreamUrl = upstreamProxies.default;
279
+ let isCustomResponse = false;
280
+ let customResponseData = null;
281
+
282
+ // Logic to determine Proxy Type
283
+ if (matchers.noProxy(hostname)) {
284
+ // A. Direct
285
+ proxyType = "NO_PROXY";
286
+ upstreamUrl = null;
287
+ } else if (matchers.proxy1(hostname) && upstreamProxies.p1) {
288
+ // C1. Proxy 1
289
+ proxyType = "PROXY_1";
290
+ upstreamUrl = upstreamProxies.p1;
291
+ } else if (matchers.proxy2(hostname) && upstreamProxies.p2) {
292
+ // C2. Proxy 2
293
+ proxyType = "PROXY_2";
294
+ upstreamUrl = upstreamProxies.p2;
295
+ }
296
+
297
+ // B. IP Check Interception (Overrules standard routing for specific domain)
298
+ if (hostname === "ip.bablosoft.com") {
299
+ isCustomResponse = true;
300
+ let displayedIP;
301
+ if (proxyType === "PROXY_1") displayedIP = p1Details?.ip;
302
+ else if (proxyType === "PROXY_2") displayedIP = p2Details?.ip;
303
+ else if (proxyType === "NO_PROXY") displayedIP = "127.0.0.1";
304
+ else displayedIP = defaultDetails?.ip;
305
+
306
+ customResponseData = {
307
+ statusCode: 200,
308
+ headers: { "Content-Type": "text/plain", Connection: "close" },
309
+ body: displayedIP || "Unknown IP",
310
+ };
315
311
  }
316
- hostStatsMap[proxyType][hostname].req++;
317
- }
318
312
 
319
- // Return Decision
320
- if (isCustomResponse) {
321
- return { customResponseFunction: () => customResponseData };
313
+ // Record Stats
314
+ connectionMap[connectionId] = { type: proxyType, hostname: hostname };
315
+ if (host_stats && hostname) {
316
+ if (!hostStatsMap[proxyType]) hostStatsMap[proxyType] = {};
317
+ if (!hostStatsMap[proxyType][hostname]) {
318
+ hostStatsMap[proxyType][hostname] = { req: 0, srcTx: 0, srcRx: 0, trgTx: 0, trgRx: 0 };
319
+ }
320
+ hostStatsMap[proxyType][hostname].req++;
321
+ }
322
+
323
+ // Return Decision
324
+ if (isCustomResponse) {
325
+ return { customResponseFunction: () => customResponseData };
326
+ }
327
+
328
+ return {
329
+ upstreamProxyUrl: upstreamUrl,
330
+ requestAuthentication: false,
331
+ };
332
+ },
333
+ });
334
+
335
+ try {
336
+ await server.listen();
337
+ selectedPort = candidatePort;
338
+ serverRunning = true;
339
+ console.log(`░░ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
340
+ break; // Successfully bound — exit retry loop
341
+ } catch (err) {
342
+ if (err.code === "EADDRINUSE" && attempt < MAX_BIND_RETRIES) {
343
+ console.warn(`░░ Port ${candidatePort} taken (race), retrying... (${attempt}/${MAX_BIND_RETRIES})`);
344
+ await sleep(50 + Math.random() * 100); // Small random delay to de-sync concurrent processes
345
+ continue;
322
346
  }
347
+ console.error("░░ Failed to start proxy server:", err);
348
+ return null;
349
+ }
350
+ }
323
351
 
324
- return {
325
- upstreamProxyUrl: upstreamUrl,
326
- requestAuthentication: false, // Auto-handle upstream auth via URL
327
- };
328
- },
329
- });
352
+ if (!selectedPort || !server) {
353
+ console.error("░░ Critical Error: Could not bind to any port after " + MAX_BIND_RETRIES + " attempts.");
354
+ return null;
355
+ }
330
356
 
331
357
  server.on("connectionClosed", ({ connectionId, stats: connStats }) => {
332
358
  const connectionInfo = connectionMap[connectionId];
@@ -355,15 +381,6 @@ export async function startProxyServer({
355
381
  delete connectionMap[connectionId];
356
382
  });
357
383
 
358
- try {
359
- await server.listen();
360
- serverRunning = true;
361
- console.log(`░░ Local Proxy Started: http://127.0.0.1:${selectedPort}`);
362
- } catch (err) {
363
- console.error("░░ Failed to start proxy server:", err);
364
- return null;
365
- }
366
-
367
384
  const formatBytes = (bytes) => {
368
385
  if (bytes < 1024) return bytes + " B";
369
386
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
@@ -142,6 +142,9 @@ function sanitizeResponseHeaders(headers, logger, url) {
142
142
  }
143
143
  }
144
144
 
145
+ // Always ensure CORS header is present, even if server never sent it
146
+ cleaned["access-control-allow-origin"] = "*";
147
+
145
148
  if (logger && stripped.length) console.log(`[stripGot] Response stripped ${stripped.length} headers: [${stripped.join(", ")}] → ${url}`);
146
149
  return cleaned;
147
150
  }