( endpoint: string, label: string, timeoutMs = DEFAULT_TIMEOUT_MS, )
| 82 | * Returns a structured result. Never throws — failures become statuses. |
| 83 | */ |
| 84 | export async function probeEndpoint( |
| 85 | endpoint: string, |
| 86 | label: string, |
| 87 | timeoutMs = DEFAULT_TIMEOUT_MS, |
| 88 | ): Promise<ProbeResult> { |
| 89 | const start = Date.now(); |
| 90 | const controller = new AbortController(); |
| 91 | const timer = setTimeout(() => controller.abort(), timeoutMs); |
| 92 | |
| 93 | try { |
| 94 | // Try HEAD first (lighter on the server). Many CDNs reject HEAD on some |
| 95 | // paths, so on 405 fall back to GET. |
| 96 | let response: Response; |
| 97 | try { |
| 98 | response = await proxyFetch(endpoint, { method: 'HEAD', signal: controller.signal, redirect: 'follow' }); |
| 99 | if (response.status === 405) { |
| 100 | response = await proxyFetch(endpoint, { method: 'GET', signal: controller.signal, redirect: 'follow' }); |
| 101 | } |
| 102 | } catch (e: any) { |
| 103 | // Some servers don't speak HEAD at all (close the connection); try GET. |
| 104 | if (!controller.signal.aborted) { |
| 105 | response = await proxyFetch(endpoint, { method: 'GET', signal: controller.signal, redirect: 'follow' }); |
| 106 | } else { |
| 107 | throw e; |
| 108 | } |
| 109 | } |
| 110 | const latencyMs = Date.now() - start; |
| 111 | return { |
| 112 | endpoint, |
| 113 | label, |
| 114 | status: response.ok || (response.status >= 200 && response.status < 500) ? 'ok' : 'http_error', |
| 115 | httpCode: response.status, |
| 116 | latencyMs, |
| 117 | }; |
| 118 | } catch (e: any) { |
| 119 | const msg = e?.message ?? String(e); |
| 120 | const aborted = controller.signal.aborted; |
| 121 | let status: ProbeStatus = 'unknown_error'; |
| 122 | if (aborted) status = 'timeout'; |
| 123 | else if (/ENOTFOUND|EAI_AGAIN|getaddrinfo/i.test(msg)) status = 'dns_failed'; |
| 124 | else if (/ECONNREFUSED/i.test(msg)) status = 'connection_refused'; |
| 125 | return { |
| 126 | endpoint, |
| 127 | label, |
| 128 | status, |
| 129 | error: msg, |
| 130 | }; |
| 131 | } finally { |
| 132 | clearTimeout(timer); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Run probes against all public endpoints in parallel. |
no test coverage detected