/** * Whether the installation this page talks to is reachable right now. * * Transport state, kept apart from `liveStore` on purpose: that one holds what * the engine is doing, this one holds whether we can hear it at all. Only * meaningful under a portal — a local install talks to itself. * * Same hand-rolled external store as liveStore, for the same reason: a * `useSyncExternalStore` snapshot is the whole requirement. */ type Connection = { offline: boolean /** When the portal last heard from the installation, epoch ms. */ lastSeen: number | null } let state: Connection = { offline: false, lastSeen: null } /** * Whether the live socket is up right now. * * The socket is the continuous signal; a failed request is only a lagging one. * Latching `offline` on a single refused request is what used to leave the * banner up for good: the socket had never dropped, so no reconnect was coming * to clear it, and nothing else on a quiet page refetches. Reloading was the * only way out. */ let socketOpen = false const listeners = new Set<() => void>() function emit(next: Connection) { // Same values, same object: React re-renders on identity, and a poll that // keeps confirming "still offline" should not repaint the banner. if (next.offline === state.offline && next.lastSeen === state.lastSeen) return state = next for (const listener of listeners) listener() } export const connectionStore = { setOffline(lastSeen: string | number | null) { // The socket is up, so the installation is reachable: this was one bad // answer rather than a lost tunnel, and the banner would have nothing to // take it back down again. if (socketOpen) return const at = typeof lastSeen === "string" ? Date.parse(lastSeen) : typeof lastSeen === "number" ? lastSeen : null emit({ offline: true, lastSeen: Number.isNaN(at) ? null : at }) }, setOnline() { emit({ offline: false, lastSeen: null }) }, /** * The live socket opened or closed. * * An open socket is proof the installation can be heard, so it also clears * the banner — and since the socket reconnects on its own, that is the one * release the offline state can always count on. */ setSocketOpen(open: boolean) { socketOpen = open if (open) emit({ offline: false, lastSeen: null }) }, subscribe(listener: () => void) { listeners.add(listener) return () => listeners.delete(listener) }, snapshot(): Connection { return state }, } /** * The proxy's offline answer: a 503 carrying `{offline, last_seen}`. * * Told apart from every other 503 by that body, so an installation that is * merely busy is not reported as unreachable. */ export function offlineDetail( error: unknown, ): { lastSeen: string | null } | null { const body = (error as { body?: unknown; status?: number })?.body const status = (error as { status?: number })?.status if ((status !== 503 && status !== 502) || typeof body !== "object" || !body) { return null } const detail = body as { offline?: boolean; last_seen?: string | null } if (detail.offline !== true) return null return { lastSeen: detail.last_seen ?? null } }