import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { ReaderModel } from '@maxmind/geoip2-node'; import { Reader } from '@maxmind/geoip2-node'; import { LRUCache } from 'lru-cache'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const filename = 'GeoLite2-City.mmdb'; // From api or worker package const dbPath = path.join(__dirname, `../../../packages/geo/${filename}`); // From local package const dbPathLocal = path.join(__dirname, `../${filename}`); // Singleton promise - initialized once, awaited on every call let readerPromise: Promise | null = null; async function loadDatabase(): Promise { try { const dbBuffer = await readFile(dbPath); console.log('GeoLite2-City.mmdb loaded (dist)', dbPath); return Reader.openBuffer(dbBuffer); } catch { try { const dbBuffer = await readFile(dbPathLocal); console.log('GeoLite2-City.mmdb loaded (local)', dbPathLocal); return Reader.openBuffer(dbBuffer); } catch { console.error('GeoLite2-City.mmdb not found', { dbPath, dbPathLocal }); return null; } } } function getReader(): Promise { if (!readerPromise) { readerPromise = loadDatabase(); } return readerPromise; } export interface GeoLocation { country: string | undefined; city: string | undefined; region: string | undefined; longitude: number | undefined; latitude: number | undefined; } const DEFAULT_GEO: GeoLocation = { country: undefined, city: undefined, region: undefined, longitude: undefined, latitude: undefined, }; const ignore = ['127.0.0.1', '::1']; const cache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5, ttlAutopurge: true, }); export async function getGeoLocation(ip?: string): Promise { if (!ip || ignore.includes(ip)) { return DEFAULT_GEO; } const cached = cache.get(ip); if (cached) { return cached; } const reader = await getReader(); try { const response = reader?.city(ip); const res = { city: response?.city?.names.en, country: response?.country?.isoCode, region: response?.subdivisions?.[0]?.names.en, longitude: response?.location?.longitude, latitude: response?.location?.latitude, }; cache.set(ip, res); return res; } catch (error) { return DEFAULT_GEO; } }