/** * Commission-system (Laravel middleware) reachability probe with anti-flap hysteresis. * * Probes Laravel 12's built-in liveness route at the ROOT (`/up`, NOT under /api) — * it returns 200 whenever Laravel is serving requests, independent of business state. * (Do NOT probe /api/monitoring/health: it returns 503 on failed order imports even * when Laravel itself is up.) * * Anti-flapping: a short blip (single failed probe) must NOT flip the indicator. * We only report OFFLINE after >= 3 consecutive failed probes spanning >= 60s of * continuous unreachability; the FIRST successful probe flips back to ONLINE. * Raw probes are rate-limited to one per 15s (module cache) so client polling can * never hammer Laravel. State is per-process (PM2 instances: 1). */ const PROBE_TIMEOUT_MS = 3000 const MIN_PROBE_INTERVAL_MS = 15_000 const OFFLINE_AFTER_FAILURES = 3 const OFFLINE_AFTER_MS = 60_000 const state = { reportedOnline: true, consecutiveFailures: 0, firstFailureAt: 0, checkedAt: 0 } // Share one in-flight probe between concurrent status requests let probing: Promise | null = null const markReachable = () => { state.consecutiveFailures = 0 state.firstFailureAt = 0 state.reportedOnline = true } const rawProbe = async () => { const config = useRuntimeConfig() const now = Date.now() state.checkedAt = now try { await $fetch(config.api.laravel + '/up', { timeout: PROBE_TIMEOUT_MS }) markReachable() } catch (err: any) { // Any real HTTP response that isn't a gateway/unavailable status proves the // backend stack answered (e.g. a deployment where /up 404s — dev-data does). // Only transport failures (timeout, refused, DNS) and 502/503/504 count as down. const status = Number(err?.status ?? err?.statusCode ?? err?.response?.status ?? 0) if (status > 0 && status !== 502 && status !== 503 && status !== 504) { markReachable() return } state.consecutiveFailures += 1 if (state.firstFailureAt === 0) state.firstFailureAt = now if ( state.consecutiveFailures >= OFFLINE_AFTER_FAILURES && now - state.firstFailureAt >= OFFLINE_AFTER_MS ) { state.reportedOnline = false } } } export const probeCommissionSystem = async (): Promise<{ online: boolean checkedAt: number degradedSince: number | null }> => { try { if (Date.now() - state.checkedAt >= MIN_PROBE_INTERVAL_MS) { if (!probing) { probing = rawProbe().finally(() => { probing = null }) } await probing } } catch { // fail-soft — keep the last reported state } return { online: state.reportedOnline, checkedAt: state.checkedAt, degradedSince: state.firstFailureAt > 0 ? state.firstFailureAt : null } }