mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
d1e024277f
Opening the installed PWA offline showed Chrome's "no internet" page instead of
the cached app. On boot the axios response interceptor reacts to a failed
request with no response by probing /api/health; the probe collapsed "genuinely
offline" and "edge-proxy auth wall" into a single reachable=false, so the
interceptor unregistered the service worker and reloaded — straight into a dead
network. navigator.onLine is true on mobile while offline, so the existing guard
didn't help. This also defeated the offline data layer (withOfflineFallback,
authStore's offline branch), which runs later in the chain.
Fix: connectivity.probe() now returns a discriminated state
('online' | 'offline' | 'proxy-wall'). A fetch that throws, or navigator.onLine
false, is 'offline'; a cross-origin redirect (CF Access, via redirect:'manual'
→ opaqueredirect) or an HTML auth wall (Pangolin) is 'proxy-wall'. The
interceptor only tears down the SW on 'proxy-wall'; on plain offline it lets the
request reject so the cached shell + IndexedDB serve the app. CF Access /
Pangolin reauth still works — the proxy always presents a reachable redirect or
HTML wall, which the probe now detects positively.
Regression dates to v3.0.16 (#964), surfaced by the 3.1.0 rewrite.
Tests: 6 new connectivity cases (offline/online/proxy-wall discrimination);
client tsc clean, full client suite green (2850).
56 lines
2.1 KiB
TypeScript
56 lines
2.1 KiB
TypeScript
// FE-SYNC-CONNECTIVITY: probeNow must tell a genuine offline (network error —
|
|
// never tear down the SW) apart from an edge-proxy auth wall (#1346).
|
|
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
import { probeNow } from '../../../src/sync/connectivity'
|
|
|
|
function setOnline(v: boolean): void {
|
|
Object.defineProperty(navigator, 'onLine', { value: v, configurable: true })
|
|
}
|
|
|
|
function fetchReturns(res: Partial<Response>): void {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(res as Response))
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
vi.unstubAllGlobals()
|
|
setOnline(true)
|
|
})
|
|
|
|
describe('FE-SYNC-CONNECTIVITY: probeNow offline vs proxy-wall (#1346)', () => {
|
|
it('FE-SYNC-CONNECTIVITY-001: navigator.onLine false → "offline"', async () => {
|
|
setOnline(false)
|
|
expect(await probeNow()).toBe('offline')
|
|
})
|
|
|
|
it('FE-SYNC-CONNECTIVITY-002: fetch throws (network error) → "offline" (must NOT unregister the SW)', async () => {
|
|
setOnline(true)
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new TypeError('Failed to fetch')))
|
|
expect(await probeNow()).toBe('offline')
|
|
})
|
|
|
|
it('FE-SYNC-CONNECTIVITY-003: ok JSON health → "online"', async () => {
|
|
setOnline(true)
|
|
fetchReturns({ type: 'basic', ok: true, headers: new Headers({ 'content-type': 'application/json' }) })
|
|
expect(await probeNow()).toBe('online')
|
|
})
|
|
|
|
it('FE-SYNC-CONNECTIVITY-004: cross-origin auth redirect (CF Access) → "proxy-wall"', async () => {
|
|
setOnline(true)
|
|
fetchReturns({ type: 'opaqueredirect', ok: false, headers: new Headers() })
|
|
expect(await probeNow()).toBe('proxy-wall')
|
|
})
|
|
|
|
it('FE-SYNC-CONNECTIVITY-005: HTML auth wall (Pangolin 200) → "proxy-wall"', async () => {
|
|
setOnline(true)
|
|
fetchReturns({ type: 'basic', ok: true, headers: new Headers({ 'content-type': 'text/html; charset=utf-8' }) })
|
|
expect(await probeNow()).toBe('proxy-wall')
|
|
})
|
|
|
|
it('FE-SYNC-CONNECTIVITY-006: edge 401/403 (non-JSON) → "proxy-wall"', async () => {
|
|
setOnline(true)
|
|
fetchReturns({ type: 'basic', ok: false, headers: new Headers({ 'content-type': 'text/html' }) })
|
|
expect(await probeNow()).toBe('proxy-wall')
|
|
})
|
|
})
|