(ip: string | undefined)
| 196 | // Idempotent: kicks off (or reuses) a lookup. No-op when disabled or the IP |
| 197 | // isn't resolvable. |
| 198 | function lookup(ip: string | undefined): void { |
| 199 | if (!configStore.resolveClientHostname) return |
| 200 | if (!ip || !isResolvableIP(ip)) return |
| 201 | |
| 202 | const now = Date.now() |
| 203 | const cached = memoryCache.get(ip) |
| 204 | if (cached) { |
| 205 | if (!isExpired(cached, now)) { |
| 206 | if (cached.name) promote(ip, cached.name) |
| 207 | return |
| 208 | } |
| 209 | // Expired: drop from both caches so a stale name can't linger. |
| 210 | memoryCache.delete(ip) |
| 211 | if (hostnameState[ip]) delete hostnameState[ip] |
| 212 | } |
| 213 | |
| 214 | if (inflight.has(ip)) return |
| 215 | |
| 216 | // Cache before freeing the inflight slot so a concurrent call can't race in. |
| 217 | const promise = fetchHostname(ip) |
| 218 | inflight.set(ip, promise) |
| 219 | promise |
| 220 | .then((name) => { |
| 221 | const entry: HostnameEntry = { name, ts: Date.now() } |
| 222 | memoryCache.set(ip, entry) |
| 223 | if (name) { |
| 224 | hostnameState[ip] = name |
| 225 | persistPositive(ip, entry) |
| 226 | } |
| 227 | }) |
| 228 | .finally(() => inflight.delete(ip)) |
| 229 | } |
| 230 | |
| 231 | // Reactive getter — triggers a lookup and returns the resolved hostname, or |
| 232 | // undefined until resolved (or for skipped / negative IPs). |
no test coverage detected