File size: 1,479 Bytes
cd99321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const PROBE_INTERVAL_MS = 30_000
const PROBE_TIMEOUT_MS = 1_500

let reachable = true
const listeners = new Set<(v: boolean) => void>()

function setReachable(v: boolean): void {
  if (reachable === v) return
  reachable = v
  listeners.forEach(fn => fn(v))
}

async function probe(): Promise<void> {
  if (!navigator.onLine) { setReachable(false); return }
  try {
    const ctrl = new AbortController()
    const t = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS)
    const res = await fetch('/api/health', {
      method: 'GET',
      credentials: 'include',
      cache: 'no-store',
      signal: ctrl.signal,
    })
    clearTimeout(t)
    // /api/health returns JSON. CF Access / Pangolin will either return HTML
    // (Pangolin 200 auth wall) or trigger a cross-origin redirect that throws
    // below. Both proxy-auth scenarios resolve to reachable = false.
    const ct = res.headers.get('content-type') || ''
    setReachable(res.ok && ct.includes('application/json'))
  } catch {
    setReachable(false)
  }
}

export function startConnectivityProbe(): void {
  probe()
  setInterval(probe, PROBE_INTERVAL_MS)
  window.addEventListener('online', probe)
  window.addEventListener('offline', () => setReachable(false))
}

export function isReachable(): boolean { return reachable }
export function probeNow(): Promise<void> { return probe() }
export function onChange(fn: (v: boolean) => void): () => void {
  listeners.add(fn)
  return () => listeners.delete(fn)
}