matrix-builder / web /src /lib /capabilities.ts
matrix-builder-bot
deploy: matrix-builder@ae902df6985a
66d0c75
Raw
History Blame Contribute Delete
1.62 kB
// C5 — Capability probe: is a control plane actually reachable?
//
// The Blueprint workspace is fully client-side (C0–C4). The server is an OPTIONAL sync target:
// callers gate every `/api/v1/blueprints/*` request behind `getCapabilities()`, so when no backend
// is present (static hosting, offline) the app makes zero per-interaction requests and the UX is
// identical. The probe runs at most once (cached), is timeout-bounded, and never throws.
import { apiBaseUrl } from "@/lib/api-client";
export interface Capabilities {
/** A control plane responded to /health — server sync/enhancement is available. */
server: boolean;
}
let cache: Capabilities | null = null;
let inflight: Promise<Capabilities> | null = null;
export async function getCapabilities(opts: { timeoutMs?: number } = {}): Promise<Capabilities> {
if (cache) return cache;
if (inflight) return inflight;
if (typeof window === "undefined" || typeof fetch === "undefined") return { server: false };
inflight = (async () => {
let server = false;
try {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 1500);
const res = await fetch(`${apiBaseUrl}/health`, { signal: ctrl.signal });
clearTimeout(t);
server = res.ok;
} catch {
server = false; // unreachable / blocked / refused — stay fully local
}
cache = { server };
inflight = null;
return cache;
})();
return inflight;
}
/** Test/runtime hook to forget the cached probe. */
export function resetCapabilities(): void {
cache = null;
inflight = null;
}