(ip)
| 104 | |
| 105 | // Race all providers, first successful result wins, abort the rest |
| 106 | const lookupGeo = async (ip) => { |
| 107 | const ac = new AbortController(); |
| 108 | const signal = AbortSignal.any([ac.signal, AbortSignal.timeout(TIMEOUT)]); |
| 109 | const tasks = providers.map((p) => |
| 110 | tryProvider(p, ip, signal).catch((e) => { |
| 111 | if (e.name !== 'AbortError') log.warn(`${p.name} failed for ${ip}`, e.message); |
| 112 | throw e; |
| 113 | }), |
| 114 | ); |
| 115 | try { |
| 116 | const result = await Promise.any(tasks); |
| 117 | ac.abort(); |
| 118 | return result; |
| 119 | } catch { |
| 120 | return null; |
| 121 | } |
| 122 | }; |
| 123 | |
| 124 | // Fetch country-level metadata to fill fields not provided by every geo source |
| 125 | const enrichCountry = async (code) => { |
no test coverage detected