UNPKG

gip

Version:

Dependency-free, TypeScript-friendly module and CLI tool that uses the Fetch API to retrieve your real public IPv4 address.

88 lines (71 loc) 2.98 kB
import process from "node:process"; parseInt(process.version.match(/(?:v?)([\d]+)(?:\.)/)[1]) < 21 && process.removeAllListeners("warning"); import services from "./services.mjs"; const IPv4_regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; const gip = async ({ services: customServices = [], ensure = 3, timeout = 10000, verbose = true } = {}) => { const formattedCustomServices = customServices.map(s => /^https?:\/\//.test(s) ? s : `https://${s.replace(/^\W+/g, "")}` ); const allServices = [...new Set([...services, ...formattedCustomServices])]; if (ensure > allServices.length) throw new Error(`Maximum ensure count is ${allServices.length}`); // Ensure count must be at least 1 ensure = Math.max(1, ensure); const controller = new AbortController(); const signal = controller.signal; // Global timeout const timeoutId = setTimeout(() => { controller.abort(); }, timeout); // Map to store IP addresses and their counts const ipCounts = new Map(); // Track the first IP to reach the ensure count (edge case handling) let firstValidIP = null; let isResolved = false; return new Promise((resolve, reject) => { const promises = allServices.map((url) => fetch(url, { signal }) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.text(); }) .then((ip) => { const trimmedIP = ip.trim(); if (IPv4_regex.test(trimmedIP) && !isResolved) { // Increment count for this IP const currentCount = (ipCounts.get(trimmedIP) || 0) + 1; ipCounts.set(trimmedIP, currentCount); // Check if this IP has reached the ensure count if (currentCount === ensure && !firstValidIP) { firstValidIP = trimmedIP; // Mark as the first valid IP isResolved = true; clearTimeout(timeoutId); // Clear timeout controller.abort(); // Stop further requests resolve(trimmedIP); // Resolve immediately! return trimmedIP; } } return null; }) .catch((err) => { if (err.name !== "AbortError" && verbose) { console.error("Fetch error:", err); } return null; }) ); Promise.allSettled(promises).then(() => { if (!isResolved) { clearTimeout(timeoutId); const ipResults = Array.from(ipCounts.entries()) .map(([ip, count]) => `${ip}(${count})`) .join(", "); const errorMessage = ipResults ? `Not enough IP addresses found to meet ensure count of ${ensure}. Found: ${ipResults}` : `No valid IP addresses found within ${timeout}ms timeout`; reject(new Error(errorMessage)); } }); }); }; export default gip;