import net from 'node:net'; const SLOW_MS = 3000; export function classify({ ok, reachable, latency, error }) { if (ok) return { status: latency > SLOW_MS ? 'warn' : 'ok', latency_ms: latency, detail: `${latency}ms` }; if (reachable) return { status: 'warn', latency_ms: latency ?? null, detail: 'degraded' }; return { status: 'down', latency_ms: null, detail: error || 'unreachable' }; } // Default probe: HTTP (status 2xx/3xx) or TCP connect. Only called with // operator-configured URLs from the registry — never user input. export async function probe(svc) { const started = Date.now(); const type = svc.check?.type || 'http'; try { if (type === 'tcp') { const u = new URL(svc.url); await new Promise((resolve, reject) => { const sock = net.connect({ host: u.hostname, port: Number(u.port) }, () => { sock.end(); resolve(); }); sock.setTimeout(5000); sock.on('timeout', () => { sock.destroy(); reject(new Error('timeout')); }); sock.on('error', reject); }); return { ok: true, latency: Date.now() - started }; } const base = svc.url.replace(/\/$/, ''); const url = base + (svc.check?.path || ''); const res = await fetch(url, { redirect: 'manual', signal: AbortSignal.timeout(6000) }); const reachable = true; const ok = res.status >= 200 && res.status < 400; return { ok, reachable, latency: Date.now() - started }; } catch (e) { return { ok: false, reachable: false, latency: Date.now() - started, error: e.code || e.message }; } } export async function checkAll(services, probeFn = probe) { return Promise.all(services.map(async svc => { const c = classify(await probeFn(svc)); return { service_id: svc.id, ...c }; })); }