Online/offline detection — navigator.onLine + heuristic ping via fetch.
Module network | Source packages/front/fw/src/dom/net/network.js | Deps none | Worker-safe partial
Helpers around navigator.onLine and the online/offline events. Includes an active ping to verify real connectivity (the native flag is notoriously imprecise), a connection() snapshot of the Network Information API, and onConnectionChange() for connection-quality changes.
Resolve
const network = runtime.resolve('network');
// Returns: { isOnline, onOnline, onOffline, onChange, connection, onConnectionChange, saveData, ping, off }
API
| Method | Signature | Returns |
|---|---|---|
isOnline |
() => boolean |
Current navigator.onLine |
onOnline |
(callback: () => void) => () => void |
Unsubscribe |
onOffline |
(callback: () => void) => () => void |
Unsubscribe |
onChange |
(callback: (online: boolean) => void, options?) => () => void |
Unsubscribe |
connection |
() => ConnectionSnapshot | null |
navigator.connection snapshot |
onConnectionChange |
(callback: (snap) => void, opts?) => () => void |
Unsubscribe |
saveData |
() => boolean |
true if Data Saver is enabled |
ping |
(url: string, options?) => Promise<boolean> |
true if reachable |
off |
() => void |
Removes all listeners |
onChange / onConnectionChange options
| Option | Type | Default | Description |
|---|---|---|---|
immediate |
boolean |
true |
Calls the callback immediately with the current state |
ping options
| Option | Type | Default | Description |
|---|---|---|---|
timeout |
number |
3000 |
Timeout in ms (AbortController) |
method |
string |
'HEAD' |
HTTP method |
connection() → ConnectionSnapshot | null
Returns null if the navigator.connection API is absent. Frozen snapshot:
{
effectiveType: '4g' | '3g' | '2g' | 'slow-2g' | null,
downlink: number | null, // Mbps
rtt: number | null, // ms
saveData: boolean,
type: 'wifi' | 'cellular' | ... | null,
}
Examples
Reacting to online/offline transitions
const network = runtime.resolve('network');
const unsub = network.onChange((online) => {
document.body.classList.toggle('offline', !online);
if (online) syncPendingData();
});
window.addEventListener('unload', unsub);
Active check before a critical operation
async function submitForm(data) {
const alive = await network.ping('/api/healthz', { timeout: 2000 });
if (!alive) {
showError('No connection — please try again.');
return;
}
await api.post('/submit', data);
}
Adapting to connection quality
const network = runtime.resolve('network');
const unsub = network.onConnectionChange((snap) => {
if (!snap) return;
const isSlowConnection = snap.effectiveType === 'slow-2g' || snap.effectiveType === '2g';
document.body.classList.toggle('low-quality', isSlowConnection);
if (snap.saveData) loadLowResImages();
});
One-shot snapshot
const snap = network.connection();
if (snap) {
console.log(`${snap.effectiveType} — downlink: ${snap.downlink} Mbps, RTT: ${snap.rtt} ms`);
}
if (network.saveData()) {
console.log('Data Saver enabled — reduce transfers');
}
Global cleanup
// In an SPA, cleanup on navigation
router.on('beforeLeave', () => network.off());
Notes
isOnline()is unreliable — the system can reporttruewithout real Internet access (local network without a gateway, captive portal). Always validate withpingfor critical decisions.worker-safe: partial—navigator.onLineandnavigator.connectionare accessible in a Worker, butonline/offlineevents must be listened onself(notwindow).connection()returnsnullifnavigator.connectionis absent (Firefox 140+, Safari). Test the value before reading its fields.pingusesmode: 'no-cors'— cross-origin requests are possible without CORS but the body is not readable. A non-5xxstatusis sufficient.- Recommended
pingtarget: a lightweight controlled endpoint (e.g./healthz), or a CDN asset.