import { usePlatform } from "@/context/platform" import type { ServerConnection } from "@/context/server" import { createSdkForServer } from "./server" export type ServerHealth = { healthy: boolean; version?: string } interface CheckServerHealthOptions { timeoutMs?: number signal?: AbortSignal retryCount?: number retryDelayMs?: number } const defaultTimeoutMs = 3000 const defaultRetryCount = 2 const defaultRetryDelayMs = 100 function timeoutSignal(timeoutMs: number) { const timeout = (AbortSignal as unknown as { timeout?: (ms: number) => AbortSignal }).timeout if (timeout) { try { return { signal: timeout.call(AbortSignal, timeoutMs), clear: undefined as (() => void) | undefined, } } catch {} } const controller = new AbortController() const timer = setTimeout(() => controller.abort(), timeoutMs) return { signal: controller.signal, clear: () => clearTimeout(timer) } } function wait(ms: number, signal?: AbortSignal) { return new Promise((resolve, reject) => { if (signal?.aborted) { reject(new DOMException("Aborted", "AbortError")) return } const timer = setTimeout(() => { signal?.removeEventListener("abort", onAbort) resolve() }, ms) const onAbort = () => { clearTimeout(timer) reject(new DOMException("Aborted", "AbortError")) } signal?.addEventListener("abort", onAbort, { once: true }) }) } function retryable(error: unknown, signal?: AbortSignal) { if (signal?.aborted) return false if (!(error instanceof Error)) return false if (error.name === "AbortError" || error.name === "TimeoutError") return false if (error instanceof TypeError) return true return /network|fetch|econnreset|econnrefused|enotfound|timedout/i.test(error.message) } export async function checkServerHealth( server: ServerConnection.HttpBase, fetch: typeof globalThis.fetch, opts?: CheckServerHealthOptions, ): Promise { const timeout = opts?.signal ? undefined : timeoutSignal(opts?.timeoutMs ?? defaultTimeoutMs) const signal = opts?.signal ?? timeout?.signal const retryCount = opts?.retryCount ?? defaultRetryCount const retryDelayMs = opts?.retryDelayMs ?? defaultRetryDelayMs const next = (count: number, error: unknown) => { if (count >= retryCount || !retryable(error, signal)) return Promise.resolve({ healthy: false } as const) return wait(retryDelayMs * (count + 1), signal) .then(() => attempt(count + 1)) .catch(() => ({ healthy: false })) } const attempt = (count: number): Promise => createSdkForServer({ server, fetch, signal, }) .global.health() .then((x) => (x.error ? next(count, x.error) : { healthy: x.data?.healthy === true, version: x.data?.version })) .catch((error) => next(count, error)) return attempt(0).finally(() => timeout?.clear?.()) } export function useCheckServerHealth() { const platform = usePlatform() const fetcher = platform.fetch ?? globalThis.fetch return (http: ServerConnection.HttpBase) => checkServerHealth(http, fetcher) }