/** * Network Status Detection and Monitoring * * Provides utilities for detecting and responding to network connectivity changes. * Used throughout the app for graceful offline mode degradation. */ import NetInfo, { NetInfoState } from '@react-native-community/netinfo'; import { useEffect, useState, useCallback } from 'react'; /** * Network status type */ export type NetworkStatus = 'online' | 'offline' | 'unknown'; /** * Get current network status (sync) * Use this for one-time checks */ export async function getNetworkStatus(): Promise { try { const state = await NetInfo.fetch(); return state.isConnected ? 'online' : 'offline'; } catch { return 'unknown'; } } /** * Check if device is currently online * Use this before making API calls */ export async function isOnline(): Promise { const status = await getNetworkStatus(); return status === 'online'; } /** * React hook for network status monitoring * * @example * function MyComponent() { * const { isOnline, isOffline, status } = useNetworkStatus(); * * if (isOffline) { * return ; * } * // ... * } */ export function useNetworkStatus() { const [status, setStatus] = useState('unknown'); useEffect(() => { // Get initial status getNetworkStatus().then(setStatus); // Subscribe to network changes const unsubscribe = NetInfo.addEventListener((state: NetInfoState) => { setStatus(state.isConnected ? 'online' : 'offline'); }); return () => { unsubscribe(); }; }, []); return { status, isOnline: status === 'online', isOffline: status === 'offline', isUnknown: status === 'unknown', }; } /** * React hook for online-only callback * Executes callback only when online, otherwise shows error * * @example * function MyComponent() { * const executeOnline = useOnlineOnly(); * * const handleSave = () => { * executeOnline(() => { * // This only runs when online * api.saveBeneficiary(data); * }, 'Cannot save while offline'); * }; * } */ export function useOnlineOnly() { const { isOnline } = useNetworkStatus(); return useCallback( async (callback: () => void | Promise, offlineMessage?: string) => { if (!isOnline) { throw new Error(offlineMessage || 'This action requires an internet connection'); } return await callback(); }, [isOnline] ); } /** * Retry configuration for API calls */ export interface RetryConfig { maxAttempts: number; delayMs: number; backoffMultiplier: number; } /** * Default retry configuration */ export const DEFAULT_RETRY_CONFIG: RetryConfig = { maxAttempts: 3, delayMs: 1000, backoffMultiplier: 2, }; /** * Retry an async operation with exponential backoff * Only retries if network is available * * @param operation - Async function to retry * @param config - Retry configuration * @returns Promise with operation result * * @example * const data = await retryWithBackoff( * () => api.getBeneficiaries(), * { maxAttempts: 3, delayMs: 1000, backoffMultiplier: 2 } * ); */ export async function retryWithBackoff( operation: () => Promise, config: RetryConfig = DEFAULT_RETRY_CONFIG ): Promise { let lastError: Error | undefined; let delay = config.delayMs; for (let attempt = 1; attempt <= config.maxAttempts; attempt++) { try { // Check network before retry (after first attempt) if (attempt > 1) { const online = await isOnline(); if (!online) { throw new Error('Network unavailable'); } } return await operation(); } catch (error) { lastError = error instanceof Error ? error : new Error('Unknown error'); // Don't retry on last attempt if (attempt === config.maxAttempts) { break; } // Wait before retry with exponential backoff await new Promise(resolve => setTimeout(resolve, delay)); delay *= config.backoffMultiplier; } } throw lastError || new Error('Operation failed after retries'); } /** * React hook for retrying async operations * * @example * function MyComponent() { * const retry = useRetry(); * * const loadData = async () => { * const data = await retry(() => api.getBeneficiaries()); * setData(data); * }; * } */ export function useRetry(config: RetryConfig = DEFAULT_RETRY_CONFIG) { return useCallback( (operation: () => Promise) => retryWithBackoff(operation, config), // eslint-disable-next-line react-hooks/exhaustive-deps [config.maxAttempts, config.delayMs, config.backoffMultiplier] ); }