@trap_stevo/geotide
Version:
Fuses IP intelligence, reverse geocoding, and radio/Wi-Fi triangulation into a single, real-time, precision-crafted API. Trace a single packet’s origin, map a million connections, or power real-time location-aware apps with elegance, accuracy, and streami
620 lines (619 loc) • 19.2 kB
JavaScript
"use strict";
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
const {
EventEmitter
} = require("events");
const net = require("net");
const {
getClientIP
} = require("./HUDManagers/IPUtilitiesManager.cjs");
function withTimeout(ms) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), ms);
return {
signal: controller.signal,
cancel: () => clearTimeout(id)
};
}
;
function isPrivateIP(ip) {
if (net.isIP(ip) === 4) {
if (ip.startsWith("10.") || ip.startsWith("192.168.")) {
return true;
}
if (ip.startsWith("172.")) {
const n = parseInt(ip.split(".")[1], 10);
if (n >= 16 && n <= 31) {
return true;
}
}
}
if (net.isIP(ip) === 6) {
const s = ip.toLowerCase();
if (s.startsWith("fc") || s.startsWith("fd") || s.startsWith("fe80")) {
return true;
}
}
return false;
}
;
function maskIP(ip) {
if (net.isIP(ip) === 4) {
return ip.replace(/\.\d+\.\d+$/, ".x.x");
}
if (net.isIP(ip) === 6) {
return ip.slice(0, 4) + "::xxxx";
}
return ip;
}
;
function nn(v) {
return v === undefined || v === null || v === "" ? null : v;
}
;
var _limit = /*#__PURE__*/new WeakMap();
var _map = /*#__PURE__*/new WeakMap();
class LRU {
constructor(limit = 500) {
_classPrivateFieldInitSpec(this, _limit, void 0);
_classPrivateFieldInitSpec(this, _map, void 0);
_classPrivateFieldSet(_limit, this, limit);
_classPrivateFieldSet(_map, this, new Map());
}
get(k) {
if (!_classPrivateFieldGet(_map, this).has(k)) {
return null;
}
const v = _classPrivateFieldGet(_map, this).get(k);
_classPrivateFieldGet(_map, this).delete(k);
_classPrivateFieldGet(_map, this).set(k, v);
return v;
}
set(k, v) {
if (_classPrivateFieldGet(_map, this).has(k)) {
_classPrivateFieldGet(_map, this).delete(k);
}
_classPrivateFieldGet(_map, this).set(k, v);
if (_classPrivateFieldGet(_map, this).size > _classPrivateFieldGet(_limit, this)) {
_classPrivateFieldGet(_map, this).delete(_classPrivateFieldGet(_map, this).keys().next().value);
}
}
}
;
var _maxConcurrency = /*#__PURE__*/new WeakMap();
var _enableDebug = /*#__PURE__*/new WeakMap();
var _cacheTtlMs = /*#__PURE__*/new WeakMap();
var _deadlineMs = /*#__PURE__*/new WeakMap();
var _timeoutMs = /*#__PURE__*/new WeakMap();
var _providers = /*#__PURE__*/new WeakMap();
var _scoreOk = /*#__PURE__*/new WeakMap();
var _cache = /*#__PURE__*/new WeakMap();
var _nominatimUA = /*#__PURE__*/new WeakMap();
var _googleMapsKey = /*#__PURE__*/new WeakMap();
var _mapboxToken = /*#__PURE__*/new WeakMap();
var _revCache = /*#__PURE__*/new WeakMap();
var _GeoTide_brand = /*#__PURE__*/new WeakSet();
class GeoTide extends EventEmitter {
constructor(_options = {}) {
super();
_classPrivateMethodInitSpec(this, _GeoTide_brand);
_classPrivateFieldInitSpec(this, _maxConcurrency, void 0);
_classPrivateFieldInitSpec(this, _enableDebug, void 0);
_classPrivateFieldInitSpec(this, _cacheTtlMs, void 0);
_classPrivateFieldInitSpec(this, _deadlineMs, void 0);
_classPrivateFieldInitSpec(this, _timeoutMs, void 0);
_classPrivateFieldInitSpec(this, _providers, void 0);
_classPrivateFieldInitSpec(this, _scoreOk, void 0);
_classPrivateFieldInitSpec(this, _cache, void 0);
_classPrivateFieldInitSpec(this, _nominatimUA, void 0);
_classPrivateFieldInitSpec(this, _googleMapsKey, void 0);
_classPrivateFieldInitSpec(this, _mapboxToken, void 0);
_classPrivateFieldInitSpec(this, _revCache, void 0);
_classPrivateFieldSet(_cacheTtlMs, this, _options.cacheTtlMs || 10 * 60 * 1000);
_classPrivateFieldSet(_deadlineMs, this, _options.deadlineMs || 3000);
_classPrivateFieldSet(_timeoutMs, this, _options.timeoutMs || 2500);
_classPrivateFieldSet(_enableDebug, this, !!_options.enableDebug);
_classPrivateFieldSet(_scoreOk, this, Number.isFinite(_options.scoreOk) ? _options.scoreOk : 5);
_classPrivateFieldSet(_maxConcurrency, this, _options.maxConcurrency || 8);
_classPrivateFieldSet(_revCache, this, new LRU(_options.reverseCacheSize || 1000));
_classPrivateFieldSet(_cache, this, new LRU(_options.cacheSize || 1000));
_classPrivateFieldSet(_providers, this, []);
_classPrivateFieldSet(_nominatimUA, this, _options.nominatimUserAgent || "GeoTide/1.0 (geotide@sclpowerful.com)");
_classPrivateFieldSet(_googleMapsKey, this, _options.googleMapsKey || null);
_classPrivateFieldSet(_mapboxToken, this, _options.mapboxToken || null);
const ipgeoKey = _options.ipgeolocationKey || null;
const ipinfoToken = _options.ipinfoToken || null;
this.register("ipwhois", _assertClassBrand(_GeoTide_brand, this, _ipwhois).bind(this));
this.register("ipapi", _assertClassBrand(_GeoTide_brand, this, _ipapi).bind(this));
if (ipgeoKey) {
this.register("ipgeolocation", (ip, o) => _assertClassBrand(_GeoTide_brand, this, _ipgeolocation).call(this, ipgeoKey, ip, o));
}
if (ipinfoToken) {
this.register("ipinfo", (ip, o) => _assertClassBrand(_GeoTide_brand, this, _ipinfo).call(this, ipinfoToken, ip, o));
}
if (Array.isArray(_options.providers)) {
for (const p of _options.providers) {
if (p && typeof p.name === "string" && typeof p.fn === "function") {
this.register(p.name, p.fn);
}
}
}
}
register(name, fn) {
_classPrivateFieldGet(_providers, this).push({
name,
fn,
healthyUntil: 0
});
}
async lookup(ip) {
if (!net.isIP(ip)) {
throw new Error("Invalid IP");
}
this.emit("lookup:start", {
ip,
ts: Date.now()
});
if (isPrivateIP(ip)) {
const v = {
source: "local",
ip,
city: null,
region: null,
country: null,
org: null,
isp: null,
loc: null,
timezone: null,
postal: null,
flag: null,
continent: null,
confidence: 0
};
this.emit("lookup:result", {
ip,
result: v,
cached: false,
ts: Date.now()
});
return v;
}
const ck = `geo:${ip}`;
const cached = _classPrivateFieldGet(_cache, this).get(ck);
if (cached && Date.now() - cached.t < _classPrivateFieldGet(_cacheTtlMs, this)) {
this.emit("lookup:cache_hit", {
ip,
ts: Date.now()
});
this.emit("lookup:result", {
ip,
result: cached.v,
cached: true,
ts: Date.now()
});
return cached.v;
}
const start = Date.now();
const deadline = start + _classPrivateFieldGet(_deadlineMs, this);
const best = await _assertClassBrand(_GeoTide_brand, this, _raceUntil).call(this, ip, deadline, _classPrivateFieldGet(_scoreOk, this));
if (!best) {
return null;
}
const normalized = {
source: best.source,
ip,
city: nn(best.city),
region: nn(best.region),
country: nn(best.country),
org: nn(best.org),
isp: nn(best.isp),
loc: nn(best.loc),
timezone: nn(best.timezone),
postal: nn(best.postal),
flag: nn(best.flag),
continent: nn(best.continent),
confidence: Math.min(1, best._score / 9)
};
_classPrivateFieldGet(_cache, this).set(ck, {
v: normalized,
t: Date.now()
});
this.emit("lookup:result", {
ip,
result: normalized,
cached: false,
ts: Date.now()
});
return normalized;
}
async lookupMany(ips, options = {}) {
const out = new Array(ips.length);
const concurrency = options.concurrency || _classPrivateFieldGet(_maxConcurrency, this);
let i = 0;
const worker = async () => {
while (i < ips.length) {
const index = i++;
const ip = ips[index];
try {
out[index] = await this.lookup(ip);
} catch (error) {
out[index] = null;
}
}
};
const n = Math.max(1, Math.min(concurrency, ips.length));
await Promise.all(Array.from({
length: n
}, () => worker()));
return out;
}
async reverse(lat, lon, options = {}) {
const latNum = Number(lat),
lonNum = Number(lon);
if (!Number.isFinite(latNum) || !Number.isFinite(lonNum)) {
console.log("[GeoTide] ~ Invalid lat/lon");
this.emit("reverse:error", {
error: "invalid_lat_lon",
lat,
lon,
ts: Date.now()
});
return null;
}
this.emit("reverse:start", {
lat: latNum,
lon: lonNum,
ts: Date.now()
});
const ttl = options.cacheTtlMs ?? _classPrivateFieldGet(_cacheTtlMs, this);
const ck = `rev:${latNum.toFixed(6)},${lonNum.toFixed(6)}`;
const cached = _classPrivateFieldGet(_revCache, this).get(ck);
if (cached && Date.now() - cached.t < ttl) {
this.emit("reverse:cache_hit", {
lat: latNum,
lon: lonNum,
ts: Date.now()
});
this.emit("reverse:result", {
lat: latNum,
lon: lonNum,
provider: cached.v.provider,
result: cached.v,
cached: true,
ts: Date.now()
});
return cached.v;
}
const start = Date.now();
const deadline = start + (options.deadlineMs ?? _classPrivateFieldGet(_deadlineMs, this));
const revFns = [];
if (_classPrivateFieldGet(_googleMapsKey, this)) {
revFns.push({
name: "google",
fn: o => _assertClassBrand(_GeoTide_brand, this, _revGoogle).call(this, latNum, lonNum, o)
});
}
if (_classPrivateFieldGet(_mapboxToken, this)) {
revFns.push({
name: "mapbox",
fn: o => _assertClassBrand(_GeoTide_brand, this, _revMapbox).call(this, latNum, lonNum, o)
});
}
revFns.push({
name: "nominatim",
fn: o => _assertClassBrand(_GeoTide_brand, this, _revNominatim).call(this, latNum, lonNum, o)
});
const inflight = new Set();
const startCall = p => {
const promise = _assertClassBrand(_GeoTide_brand, this, _callReverseBounded).call(this, p, deadline).catch(() => null).finally(() => inflight.delete(promise));
inflight.add(promise);
return promise;
};
const tasks = revFns.map(startCall);
let best = null;
while (inflight.size) {
const val = await Promise.race(inflight);
if (val && val.formatted) {
best = val;
break;
}
}
if (!best) {
const settled = await Promise.allSettled(tasks);
for (const s of settled) {
if (s.status === "fulfilled" && s.value?.formatted) {
best = s.value;
break;
}
}
}
if (!best) {
return null;
}
const normalized = {
provider: best.provider,
formatted: best.formatted,
components: best.components || {},
lat: latNum,
lon: lonNum
};
_classPrivateFieldGet(_revCache, this).set(ck, {
v: normalized,
t: Date.now()
});
this.emit("reverse:result", {
lat: latNum,
lon: lonNum,
provider: normalized.provider,
result: normalized,
cached: false,
ts: Date.now()
});
return normalized;
}
getClientIP(req) {
return getClientIP(req);
}
}
async function _raceUntil(ip, deadlineTs, scoreOk) {
const healthy = _classPrivateFieldGet(_providers, this).filter(p => Date.now() >= p.healthyUntil);
if (!healthy.length) {
return null;
}
const inflight = new Set();
const results = [];
const startCall = p => {
const promise = _assertClassBrand(_GeoTide_brand, this, _callBounded).call(this, p, ip, deadlineTs).then(val => {
if (val) {
results.push(val);
}
return val;
}).catch(() => null).finally(() => inflight.delete(promise));
inflight.add(promise);
return promise;
};
healthy.forEach(startCall);
while (inflight.size) {
const val = await Promise.race(inflight);
if (val && val._score >= scoreOk) {
return val;
}
}
if (!results.length) {
return null;
}
results.sort((a, b) => b._score - a._score || a._latency - b._latency);
return results[0];
}
async function _callBounded(p, ip, deadlineTs) {
const remaining = Math.max(1, deadlineTs - Date.now());
const budget = Math.min(_classPrivateFieldGet(_timeoutMs, this), remaining);
const t0 = Date.now();
const timer = withTimeout(budget);
try {
const v = await p.fn(ip, {
signal: timer.signal
});
timer.cancel();
if (!v) {
throw new Error("no-data");
}
v._latency = Date.now() - t0;
v._score = _assertClassBrand(_GeoTide_brand, this, _score).call(this, v);
if (_classPrivateFieldGet(_enableDebug, this)) {
console.log(`[GeoTide] ${p.name} ${v._latency}ms score=${v._score} ip=${maskIP(ip)}`);
}
this.emit("provider:success", {
ip,
provider: p.name,
latencyMs: v._latency,
score: v._score,
ts: Date.now()
});
return v;
} catch (error) {
timer.cancel();
p.healthyUntil = Date.now() + 30_000;
if (_classPrivateFieldGet(_enableDebug, this)) {
console.warn(`[GeoTide] ${p.name} fail: ${error.message} ip=${maskIP(ip)}`);
}
this.emit("provider:error", {
ip,
provider: p.name,
error: error.message,
ts: Date.now()
});
this.emit("provider:unhealthy", {
provider: p.name,
healthyUntil: p.healthyUntil,
ts: Date.now()
});
throw error;
}
}
async function _callReverseBounded(p, deadlineTs) {
const remaining = Math.max(1, deadlineTs - Date.now());
const budget = Math.min(_classPrivateFieldGet(_timeoutMs, this), remaining);
const timer = withTimeout(budget);
try {
const val = await p.fn({
signal: timer.signal
});
timer.cancel();
if (_classPrivateFieldGet(_enableDebug, this) && val) {
console.log(`[GeoTide] ~ reverse:${p.name} ok`);
}
return val;
} catch (error) {
timer.cancel();
if (_classPrivateFieldGet(_enableDebug, this)) {
console.warn(`[GeoTide] ~ reverse:${p.name} fail: ${error.message}`);
}
throw error;
}
}
function _score(r) {
let s = 0;
["city", "region", "country", "loc", "timezone", "postal", "org", "isp", "continent"].forEach(f => {
if (r && nn(r[f])) {
s++;
}
});
return s;
}
async function _ipwhois(ip, options = {}) {
const res = await fetch(`https://ipwho.is/${ip}`, {
signal: options.signal
});
const data = await res.json();
if (!res.ok || !data?.success) {
return null;
}
const lon = nn(data.longitude);
const lat = nn(data.latitude);
const loc = lat != null && lon != null ? `${lat},${lon}` : null;
return {
source: "ipwhois",
timezone: nn(data.timezone?.id),
continent: nn(data.continent),
country: nn(data.country),
region: nn(data.region),
postal: nn(data.postal),
city: nn(data.city),
loc,
org: nn(data.connection?.org),
isp: nn(data.connection?.isp),
flag: nn(data.flag?.emoji)
};
}
async function _ipapi(ip, options = {}) {
const res = await fetch(`https://ipapi.co/${ip}/json/`, {
signal: options.signal
});
const data = await res.json();
if (!res.ok || data?.error) {
return null;
}
const lon = nn(data.longitude);
const lat = nn(data.latitude);
const loc = lat != null && lon != null ? `${lat},${lon}` : null;
return {
source: "ipapi",
timezone: nn(data.timezone),
continent: nn(data.continent_code),
country: nn(data.country_name),
region: nn(data.region),
postal: nn(data.postal),
city: nn(data.city),
loc,
org: nn(data.org),
isp: nn(data.org),
flag: null
};
}
async function _ipinfo(token, ip, options = {}) {
const res = await fetch(`https://ipinfo.io/${ip}?token=${token}`, {
signal: options.signal
});
const data = await res.json();
if (!res.ok || data?.error) {
return null;
}
const [lat, lon] = (nn(data.loc) || "").split(",");
const loc = nn(lat) && nn(lon) ? `${lat},${lon}` : null;
return {
source: "ipinfo",
timezone: nn(data.timezone),
continent: null,
country: nn(data.country),
region: nn(data.region),
postal: nn(data.postal),
city: nn(data.city),
loc,
org: nn(data.org),
isp: nn(data.org),
flag: null
};
}
async function _ipgeolocation(key, ip, options = {}) {
const res = await fetch(`https://api.ipgeolocation.io/ipgeo?apiKey=${key}&ip=${ip}`, {
signal: options.signal
});
const data = await res.json();
if (!res.ok || data?.message) {
return null;
}
const lon = nn(data.longitude);
const lat = nn(data.latitude);
const loc = lat != null && lon != null ? `${lat},${lon}` : null;
return {
source: "ipgeolocation",
timezone: nn(data.time_zone?.name),
continent: nn(data.continent_name),
country: nn(data.country_name),
region: nn(data.state_prov),
postal: nn(data.zipcode),
city: nn(data.city),
loc,
org: nn(data.organization),
isp: nn(data.isp),
flag: nn(data.country_flag_emoji)
};
}
async function _revGoogle(lat, lon, options = {}) {
const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lon}&key=${_classPrivateFieldGet(_googleMapsKey, this)}`;
const res = await fetch(url, {
signal: options.signal
});
const j = await res.json();
if (j.status !== "OK" || !j.results?.length) {
return null;
}
const top = j.results[0];
return {
components: top.address_components,
formatted: top.formatted_address,
provider: "google"
};
}
async function _revMapbox(lat, lon, options = {}) {
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${lon},${lat}.json?access_token=${_classPrivateFieldGet(_mapboxToken, this)}&limit=1`;
const res = await fetch(url, {
signal: options.signal
});
const j = await res.json();
const f = j.features?.[0];
if (!f) {
return null;
}
return {
formatted: f.place_name,
components: f.context,
provider: "mapbox"
};
}
async function _revNominatim(lat, lon, options = {}) {
const url = `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lon}&addressdetails=1`;
const res = await fetch(url, {
headers: {
"User-Agent": _classPrivateFieldGet(_nominatimUA, this)
},
signal: options.signal
});
const j = await res.json();
if (!j?.display_name) {
return null;
}
return {
formatted: j.display_name,
components: j.address,
provider: "nominatim"
};
}
;
module.exports = GeoTide;