Skip to content

v0.2: Network heartbeat for unreliable navigator.onLine #5

@mayrang

Description

@mayrang

Problem

`navigator.onLine === true` lies in two common scenarios:

  • Captive portals (coffee shop wifi, airport, hotel) — connected to wifi, no internet
  • Airplane mode partial disable — wifi shows connected but data blocked

formdraft currently consults `navigator.onLine` for sync gating. When it lies, sync retries fire and fail, exhausting the retry budget without the user understanding why.

Proposed

A pluggable network detector option. Library default stays `navigator.onLine`; consumers can plug in a heartbeat-based detector.

API sketch

```tsx
import { createHeartbeatDetector } from 'formdraft';

const detector = createHeartbeatDetector({
url: '/api/health',
intervalMs: 30000,
timeoutMs: 5000,
});

useFormDraft({
// ...
onlineDetector: detector,
});
```

Or simpler — just a function:

```tsx
useFormDraft({
isOnline: async () => {
const r = await fetch('/health', { signal: AbortSignal.timeout(5000) }).catch(() => null);
return r?.ok ?? false;
},
});
```

Implementation notes

  • `createHeartbeatDetector` returns an object that wraps internal interval + cached online state
  • Sync queue consults the detector instead of `navigator.onLine` directly
  • Default behavior unchanged when no detector provided
  • Should respect `visibilitychange` (don't heartbeat in background tabs)

Acceptance

  • New `createHeartbeatDetector` exported from main
  • `useFormDraft` accepts `onlineDetector` option
  • Sync queue uses detector when provided
  • 3+ unit tests: heartbeat probes, detects captive portal failure, recovery on real-online
  • README new section "Reliable online detection"

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions