pr_01m47d15m3e54sn21z27rpy5n9/apps/web/app/lib/status.server.ts

115 lines4,738 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1/**
2 * The checks behind /status and the footer's dot. Each is one cheap call,
3 * the same a visitor's request would make, with a short timeout, all at
4 * once. The report is kept in the data centre's cache for a minute, so
5 * however many people look, each part is checked at most once a minute
6 * per data centre.
7 *
8 * The public hosts (api, mcp, docs, models, g1t.page) are fetched over the
9 * internet, as their users reach them; the rest through service bindings.
10 */
11import { billing, identity, repos } from "./services.server";
12import { type ComponentKey, type ProbeResult, type StatusReport, report } from "./status";
13
14/** No check waits longer than this. */
15const TIMEOUT_MS = 3000;
16/** How long one report is served before the parts are checked again. */
17const CACHE_SECONDS = 60;
18/** Where the report is kept: a key in the cache, never a page anyone fetches. */
19const CACHE_KEY = "https://g1t.sh/__status/report/v1";
20/** A public repository that has always existed, to look up. */
21const PROBE_REPO = { namespace: "flagon-io", name: "g1t" };
22/** A session token that cannot exist: the lookup answers "nobody". */
23const NO_SESSION = "0".repeat(64);
24
25class Timeout extends Error {}
26
27async function timed(run: (signal: AbortSignal) => Promise<boolean | string>): Promise<ProbeResult> {
28 const started = Date.now();
29 const controller = new AbortController();
30 let timer: ReturnType<typeof setTimeout> | undefined;
31 const deadline = new Promise<never>((_, reject) => {
32 timer = setTimeout(() => {
33 controller.abort();
34 reject(new Timeout());
35 }, TIMEOUT_MS);
36 });
37 try {
38 const outcome = await Promise.race([run(controller.signal), deadline]);
39 const ms = Date.now() - started;
40 return outcome === true ? { ok: true, ms } : { ok: false, ms, error: typeof outcome === "string" ? outcome : "unexpected answer" };
41 } catch (error) {
42 const ms = Date.now() - started;
43 return { ok: false, ms, error: error instanceof Timeout ? "timed out" : "could not connect" };
44 } finally {
45 clearTimeout(timer);
46 }
47}
48
49/** A public address answering 2xx. The body is not read. */
50function reachable(url: string, headers: Record<string, string> = {}) {
51 return timed(async (signal) => {
52 const response = await fetch(url, {
53 signal,
54 redirect: "manual",
55 headers: { "user-agent": "g1t-status (+https://g1t.sh/status)", ...headers },
56 });
57 await response.body?.cancel().catch(() => undefined);
58 return response.ok || `HTTP ${response.status}`;
59 });
60}
61
62async function probeAll(): Promise<Partial<Record<ComponentKey, ProbeResult | null>>> {
63 const checks: [ComponentKey, Promise<ProbeResult> | null][] = [
64 // Every page asks identity who is signed in; a token nobody holds
65 // answers "nobody" after a database read.
66 ["site", timed(async () => (await identity.userForSession(NO_SESSION), true))],
67 ["api", reachable("https://api.g1t.sh/")],
68 // A lookup answers, found or not, once the repository service and its
69 // database are up.
70 ["git", timed(async () => (await repos.get(PROBE_REPO, null), true))],
71 ["mcp", reachable("https://mcp.g1t.sh/", { accept: "application/json" })],
72 ["docs", reachable("https://docs.g1t.sh/")],
73 ["deployments", reachable("https://g1t.page/")],
74 ["agents", reachable("https://models.g1t.sh/")],
75 // Not checked yet: see COMPONENTS in status.ts.
76 ["sandboxes", null],
77 ["billing", timed(async () => Boolean(await billing.prices()) || "no price book")],
78 ];
79 const results = await Promise.all(checks.map(async ([key, check]) => [key, check ? await check : null] as const));
80 return Object.fromEntries(results);
81}
82
83function edgeCache(): Cache | null {
84 // The Workers runtime's own cache, which the DOM types do not know.
85 const store = (globalThis as { caches?: { default?: Cache } }).caches;
86 return store?.default ?? null;
87}
88
89/** The current report: from the cache when it is under a minute old, else checked now. */
90export async function currentStatus(): Promise<StatusReport> {
91 const cache = edgeCache();
92 if (cache) {
93 try {
94 const hit = await cache.match(CACHE_KEY);
95 if (hit) return (await hit.json()) as StatusReport;
96 } catch {
97 // A cache that fails is only slower.
98 }
99 }
100 const fresh = report(await probeAll(), new Date());
101 if (cache) {
102 try {
103 await cache.put(
104 CACHE_KEY,
105 Response.json(fresh, { headers: { "cache-control": `public, max-age=${CACHE_SECONDS}` } }),
106 );
107 } catch {
108 // Checked again next time.
109 }
110 }
111 return fresh;
112}
113
114/** Headers for a response carrying the report: browsers and the edge may keep it a minute. */
115export const STATUS_HEADERS = { "cache-control": `public, max-age=${CACHE_SECONDS}` };