UNPKG

offline-detector

Version:

A lightweight TypeScript library for detecting online/offline status in browsers with modern bundler support. Minimal dependencies, tree-shakable, and works with any bundler.

222 lines (216 loc) 6.35 kB
function createDebounce(options) { const { delay, callback } = options; let timer = null; return { call() { if (timer) { clearTimeout(timer); } timer = setTimeout(callback, delay); }, cancel() { if (timer) { clearTimeout(timer); timer = null; } }, }; } function createNativeEventHandlers(handlers) { const { onOnline, onOffline } = handlers; const handleOnline = () => { onOnline(); }; const handleOffline = () => { onOffline(); }; return { addListeners() { window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); }, removeListeners() { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }, }; } async function testConnectivity(options) { const { testUrl, timeout } = options; try { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Timeout')), timeout); }); const fetchPromise = fetch(testUrl, { method: 'HEAD', mode: 'no-cors', cache: 'no-cache', }); await Promise.race([fetchPromise, timeoutPromise]); return true; } catch { return false; } } function createPolling(options) { const { interval, callback } = options; let timer = null; return { start() { if (timer) return; timer = setInterval(callback, interval); }, stop() { if (timer) { clearInterval(timer); timer = null; } }, }; } function createOfflineDetector(options = {}) { const { onOnline, onOffline, stateChangeDebounceDelay = 1000, networkVerification = {}, nativeEvents: nativeEventsConfig = {}, } = options; const { enabled: useNetworkTest = true, url: testUrl = 'https://www.google.com/favicon.ico', requestTimeout: timeout = 5000, interval: checkInterval = 60000, maxFailures: failureThreshold = 3, } = networkVerification; const { enabled: useNativeEvents = true } = nativeEventsConfig; let isListening = false; let isOnline = true; let consecutiveFailures = 0; const isBrowser = typeof window !== 'undefined' && typeof navigator !== 'undefined'; if (!isBrowser) { throw new Error('OfflineDetector can only be used in browser environments'); } const debounceAction = createDebounce({ delay: stateChangeDebounceDelay, callback: () => { if (isOnline) { onOnline?.(); } else { onOffline?.(); } }, }); const handleStateChange = (newState) => { if (isOnline === newState) return; const wasOnline = isOnline; isOnline = newState; if (useNetworkTest && isListening) { if (newState && !wasOnline) { offlinePolling.stop(); polling.start(); } else if (!newState && wasOnline) { polling.stop(); offlinePolling.start(); } } debounceAction.call(); }; const verifyConnectivity = async () => { if (!useNetworkTest) { return navigator.onLine; } try { const isConnected = await testConnectivity({ testUrl, timeout, }); return isConnected; } catch { return false; } }; const performConnectivityCheck = async () => { if (!isListening) return; const isConnected = await verifyConnectivity(); if (isConnected) { consecutiveFailures = 0; if (!isOnline) { handleStateChange(true); } } else { if (isOnline) { consecutiveFailures++; if (consecutiveFailures >= failureThreshold) { handleStateChange(false); } } } }; const nativeEvents = createNativeEventHandlers({ onOnline: () => { if (!useNativeEvents) return; if (useNetworkTest) { verifyConnectivity().then(isActuallyOnline => { if (isActuallyOnline) { consecutiveFailures = 0; handleStateChange(true); } }); } else { consecutiveFailures = 0; handleStateChange(true); } }, onOffline: () => { if (!useNativeEvents) return; handleStateChange(false); }, }); const polling = createPolling({ interval: checkInterval, callback: performConnectivityCheck, }); const offlinePolling = createPolling({ interval: Math.min(checkInterval / 4, 15000), callback: performConnectivityCheck, }); return { start() { if (isListening) return; isListening = true; isOnline = navigator.onLine; if (useNativeEvents) { nativeEvents.addListeners(); } if (useNetworkTest) { if (isOnline) { polling.start(); } else { offlinePolling.start(); } performConnectivityCheck(); } }, stop() { if (!isListening) return; isListening = false; if (useNativeEvents) { nativeEvents.removeListeners(); } polling.stop(); offlinePolling.stop(); debounceAction.cancel(); }, isOnline() { return isOnline; }, destroy() { this.stop(); }, }; } export { createOfflineDetector }; //# sourceMappingURL=index.js.map