UNPKG

ipflare

Version:

IP Geolocation API, our API enables you to effortlessly obtain precise geolocation data for any IP address through a single endpoint. Benefit from ultra-fast responses—typically between 50-100ms—and enjoy reliable performance with 99.9% uptime.

378 lines (377 loc) 15.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IPFlare = void 0; exports.isSuccess = isSuccess; exports.isError = isError; exports.isIPGeolocationError = isIPGeolocationError; exports.isIPGeolocationSuccess = isIPGeolocationSuccess; const axios_1 = __importDefault(require("axios")); // Type guards for new result types function isSuccess(result) { return result.ok === true; } function isError(result) { return result.ok === false; } // Type guard for error responses function isIPGeolocationError(response) { return response.status === "error"; } // Type guard for success responses function isIPGeolocationSuccess(response) { return response.status === "success"; } // IP validation regex patterns const IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; const IPV6_REGEX = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; class IPFlare { constructor(options) { if (!options.apiKey) { throw new Error("API key is required"); } if (typeof options.apiKey !== "string") { throw new TypeError("API key must be a string"); } if (options.apiKey.trim().length === 0) { throw new Error("API key cannot be empty"); } this.apiKey = options.apiKey; this.client = axios_1.default.create({ baseURL: options.baseURL || "https://api.ipflare.io", timeout: options.timeout || 10000, headers: { "X-API-Key": this.apiKey, "Content-Type": "application/json", }, }); } /** * Validates if a string is a valid IP address (IPv4 or IPv6) * @param ip - IP address to validate * @returns true if valid IP address */ isValidIP(ip) { return IPV4_REGEX.test(ip) || IPV6_REGEX.test(ip); } /** * Get geolocation data for a single IP address * @param ip - IP address to lookup * @param options - Additional options for the lookup * @returns Promise with Result containing geolocation data or error */ async lookup(ip, options = {}) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; // Validation checks if (!ip) { return { ok: false, error: { type: "INVALID_INPUT", message: "IP address is required", }, }; } if (typeof ip !== "string") { return { ok: false, error: { type: "INVALID_INPUT", message: "IP address must be a string", }, }; } const trimmedIP = ip.trim(); // Check if the IP contains control characters or non-space whitespace if (ip.includes("\n") || ip.includes("\r") || ip.includes("\t") || ip.includes("\0")) { return { ok: false, error: { type: "INVALID_IP_ADDRESS", message: `Invalid IP address format: ${ip}`, }, }; } if (!this.isValidIP(trimmedIP)) { return { ok: false, error: { type: "INVALID_IP_ADDRESS", message: `Invalid IP address format: ${ip}`, }, }; } try { const params = {}; const fields = []; if ((_a = options.include) === null || _a === void 0 ? void 0 : _a.asn) fields.push("asn"); if ((_b = options.include) === null || _b === void 0 ? void 0 : _b.isp) fields.push("isp"); if (fields.length > 0) { params.fields = fields.join(","); } const response = await this.client.get(`/${trimmedIP}`, { params }); return { ok: true, data: response.data, }; } catch (error) { if (axios_1.default.isAxiosError(error)) { if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) === 401) { return { ok: false, error: { type: "UNAUTHORIZED", message: "Invalid API key", details: error.response.data, }, }; } if (((_d = error.response) === null || _d === void 0 ? void 0 : _d.status) === 429) { return { ok: false, error: { type: "QUOTA_EXCEEDED", message: "Quota exceeded", details: error.response.data, }, }; } if (((_e = error.response) === null || _e === void 0 ? void 0 : _e.status) === 500) { return { ok: false, error: { type: "INTERNAL_SERVER_ERROR", message: "Internal server error", details: error.response.data, }, }; } if ((_g = (_f = error.response) === null || _f === void 0 ? void 0 : _f.data) === null || _g === void 0 ? void 0 : _g.error) { // Map API error messages to appropriate error types const apiError = error.response.data.error; let errorType = "UNKNOWN_ERROR"; if (apiError.includes("invalid") && apiError.includes("ip")) { errorType = "INVALID_IP_ADDRESS"; } else if (apiError.includes("reserved")) { errorType = "RESERVED_IP_ADDRESS"; } else if (apiError.includes("geolocation") || apiError.includes("not found")) { errorType = "GEOLOCATION_NOT_FOUND"; } else if (apiError.includes("api key")) { errorType = "NO_API_KEY_PROVIDED"; } else if (apiError.includes("input")) { errorType = "INVALID_INPUT"; } return { ok: false, error: { type: errorType, message: apiError, details: error.response.data, }, }; } return { ok: false, error: { type: "NETWORK_ERROR", message: "Network error occurred", details: { status: (_h = error.response) === null || _h === void 0 ? void 0 : _h.status, statusText: (_j = error.response) === null || _j === void 0 ? void 0 : _j.statusText, }, }, }; } return { ok: false, error: { type: "UNKNOWN_ERROR", message: "An unexpected error occurred", details: error, }, }; } } /** * Get geolocation data for multiple IP addresses * @param options - Options for bulk lookup including IPs array and additional fields * @returns Promise with Result containing array of geolocation data or error */ async bulkLookup(options) { var _a, _b, _c, _d, _e, _f, _g; const { ips, include } = options; // Validation checks if (!Array.isArray(ips)) { return { ok: false, error: { type: "INVALID_INPUT", message: "IPs must be an array", }, }; } if (!ips.length) { return { ok: false, error: { type: "INVALID_INPUT", message: "At least one IP address is required", }, }; } if (ips.length > 500) { return { ok: false, error: { type: "INVALID_INPUT", message: "Maximum of 500 IPs per request allowed", }, }; } // Validate all IPs const invalidIPs = ips.filter((ip) => { if (typeof ip !== "string") return true; const trimmedIP = ip.trim(); // Check if IP contains control characters or is invalid format const hasControlChars = ip.includes("\n") || ip.includes("\r") || ip.includes("\t") || ip.includes("\0"); return hasControlChars || !this.isValidIP(trimmedIP); }); if (invalidIPs.length > 0) { return { ok: false, error: { type: "INVALID_IP_ADDRESS", message: `Invalid IP addresses found: ${invalidIPs.join(", ")}`, details: { invalidIPs }, }, }; } try { const params = {}; const fields = []; if (include === null || include === void 0 ? void 0 : include.asn) fields.push("asn"); if (include === null || include === void 0 ? void 0 : include.isp) fields.push("isp"); if (fields.length > 0) { params.fields = fields.join(","); } const trimmedIPs = ips.map((ip) => ip.trim()); const response = await this.client.post("/bulk-lookup", { ips: trimmedIPs }, { params }); if (!response.data.results || !Array.isArray(response.data.results)) { return { ok: false, error: { type: "INTERNAL_SERVER_ERROR", message: "Invalid response format from API", details: response.data, }, }; } return { ok: true, data: response.data.results, }; } catch (error) { if (axios_1.default.isAxiosError(error)) { if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) { return { ok: false, error: { type: "UNAUTHORIZED", message: "Invalid API key", details: error.response.data, }, }; } if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 429) { return { ok: false, error: { type: "QUOTA_EXCEEDED", message: "Quota exceeded", details: error.response.data, }, }; } if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) === 500) { return { ok: false, error: { type: "INTERNAL_SERVER_ERROR", message: "Internal server error", details: error.response.data, }, }; } if ((_e = (_d = error.response) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e.error) { // Map API error messages to appropriate error types const apiError = error.response.data.error; let errorType = "UNKNOWN_ERROR"; if (apiError.includes("invalid") && apiError.includes("ip")) { errorType = "INVALID_IP_ADDRESS"; } else if (apiError.includes("reserved")) { errorType = "RESERVED_IP_ADDRESS"; } else if (apiError.includes("geolocation") || apiError.includes("not found")) { errorType = "GEOLOCATION_NOT_FOUND"; } else if (apiError.includes("api key")) { errorType = "NO_API_KEY_PROVIDED"; } else if (apiError.includes("input")) { errorType = "INVALID_INPUT"; } return { ok: false, error: { type: errorType, message: apiError, details: error.response.data, }, }; } return { ok: false, error: { type: "NETWORK_ERROR", message: "Network error occurred", details: { status: (_f = error.response) === null || _f === void 0 ? void 0 : _f.status, statusText: (_g = error.response) === null || _g === void 0 ? void 0 : _g.statusText, }, }, }; } return { ok: false, error: { type: "UNKNOWN_ERROR", message: "An unexpected error occurred", details: error, }, }; } } } exports.IPFlare = IPFlare;