CoolFace
Apppublic

Leon4gr45/builder

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes
backend-status.ts95 linesDownload Raw Back to api
1export type BackendStatus = {2  backendDown: boolean;3  authExpired: boolean;4};5 6type Listener = (status: BackendStatus) => void;7 8const state: BackendStatus = { backendDown: false, authExpired: false };9const listeners = new Set<Listener>();10 11function notify() {12  const snapshot = { ...state };13  listeners.forEach((l) => l(snapshot));14}15 16export function markBackendDown(): void {17  if (!state.backendDown) {18    state.backendDown = true;19    notify();20  }21}22 23export function markBackendUp(): void {24  if (state.backendDown) {25    state.backendDown = false;26    notify();27  }28}29 30export function markAuthExpired(): void {31  if (!state.authExpired) {32    state.authExpired = true;33    notify();34  }35}36 37export function clearAuthExpired(): void {38  if (state.authExpired) {39    state.authExpired = false;40    notify();41  }42}43 44export function getBackendStatus(): BackendStatus {45  return { ...state };46}47 48export function subscribeBackendStatus(listener: Listener): () => void {49  listeners.add(listener);50  return () => {51    listeners.delete(listener);52  };53}54 55// Auth-gated paths that return 401 when the session cookie is missing or expired.56// Matches middleware.ts.57function isAuthGatedPath(url: string): boolean {58  try {59    const path = new URL(url, typeof window !== 'undefined' ? window.location.origin : 'http://localhost').pathname;60    return path.startsWith('/api/w/') || path.startsWith('/api/admin/');61  } catch {62    return false;63  }64}65 66// Wraps fetch() for calls to our own Next.js API routes.67// Flips the "backend down" banner on when the server is unreachable (network68// error or 5xx) and off when a request succeeds. A 401 from an auth-gated69// route flips the "auth expired" banner on; a 2xx from one flips it off.70export async function apiFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {71  const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;72  try {73    const res = await fetch(input, init);74    if (res.status >= 500) {75      markBackendDown();76    } else {77      markBackendUp();78    }79    if (isAuthGatedPath(url)) {80      if (res.status === 401) {81        markAuthExpired();82      } else if (res.ok) {83        clearAuthExpired();84      }85    }86    return res;87  } catch (err) {88    // Ignore user-initiated aborts — not a backend failure89    if (!(err instanceof DOMException && err.name === 'AbortError')) {90      markBackendDown();91    }92    throw err;93  }94}95