2025-11-23 10:55:04 +08:00
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
|
2026-01-10 23:08:56 +08:00
|
|
|
type Status = "idle" | "ok" | "error" | "disabled";
|
2025-11-23 10:55:04 +08:00
|
|
|
|
2026-04-17 23:36:24 +08:00
|
|
|
const HEALTH_CACHE_TTL_MS = 15_000;
|
|
|
|
|
|
|
|
|
|
let cachedStatus: Status = "idle";
|
|
|
|
|
let cachedAt = 0;
|
|
|
|
|
let inflightCheck: Promise<Status> | null = null;
|
|
|
|
|
const listeners = new Set<(status: Status) => void>();
|
|
|
|
|
|
|
|
|
|
function publishStatus(status: Status) {
|
|
|
|
|
cachedStatus = status;
|
|
|
|
|
cachedAt = Date.now();
|
|
|
|
|
listeners.forEach((listener) => listener(status));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function requestBackendHealth(): Promise<Status> {
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch("/api/backend/health", { signal: controller.signal });
|
|
|
|
|
const payload = (await response.json().catch(() => null)) as { status?: Status } | null;
|
|
|
|
|
return payload?.status ?? "error";
|
|
|
|
|
} catch {
|
|
|
|
|
return "error";
|
|
|
|
|
} finally {
|
|
|
|
|
controller.abort();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function ensureBackendHealthFresh(force = false): Promise<Status> {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
if (!force && cachedStatus !== "idle" && now - cachedAt < HEALTH_CACHE_TTL_MS) {
|
|
|
|
|
return cachedStatus;
|
|
|
|
|
}
|
|
|
|
|
if (!inflightCheck) {
|
|
|
|
|
inflightCheck = requestBackendHealth()
|
|
|
|
|
.then((status) => {
|
|
|
|
|
publishStatus(status);
|
|
|
|
|
return status;
|
|
|
|
|
})
|
|
|
|
|
.finally(() => {
|
|
|
|
|
inflightCheck = null;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return inflightCheck;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-23 10:55:04 +08:00
|
|
|
export function useBackendHealth() {
|
2026-04-17 23:36:24 +08:00
|
|
|
const [status, setStatus] = useState<Status>(() => cachedStatus);
|
2025-11-23 10:55:04 +08:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let destroyed = false;
|
2026-04-17 23:36:24 +08:00
|
|
|
const listener = (nextStatus: Status) => {
|
|
|
|
|
if (!destroyed) {
|
|
|
|
|
setStatus(nextStatus);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
listeners.add(listener);
|
|
|
|
|
void ensureBackendHealthFresh().then((nextStatus) => {
|
|
|
|
|
if (!destroyed) {
|
|
|
|
|
setStatus(nextStatus);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return () => {
|
|
|
|
|
destroyed = true;
|
|
|
|
|
listeners.delete(listener);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-11-23 10:55:04 +08:00
|
|
|
return status;
|
|
|
|
|
}
|