{"version":3,"file":"network-utils.mjs","sources":["../../../src/lib/shared/network-utils.ts"],"sourcesContent":["/**\n * @file Unified Network Utilities\n * @description Standardized network information access, connection quality\n * detection, and online/offline status utilities.\n *\n * This module unifies patterns from:\n * - hooks/shared/networkUtils.ts\n * - utils/networkStatus.ts\n *\n * @module shared/network-utils\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Connection type from Network Information API.\n */\nexport type ConnectionType =\n  | 'wifi'\n  | '4g'\n  | '3g'\n  | '2g'\n  | 'slow-2g'\n  | 'ethernet'\n  | 'bluetooth'\n  | 'cellular'\n  | 'none'\n  | 'unknown';\n\n/**\n * Effective connection type (speed-based).\n */\nexport type EffectiveType = 'slow-2g' | '2g' | '3g' | '4g';\n\n/**\n * Network quality tier.\n */\nexport type NetworkQuality = 'excellent' | 'good' | 'fair' | 'poor' | 'offline';\n\n/**\n * Network information from the Network Information API.\n */\nexport interface NetworkInfo {\n  /** Whether the browser reports online status */\n  online: boolean;\n  /** Connection type (wifi, cellular, etc.) */\n  type: ConnectionType;\n  /** Effective connection type based on measured speed */\n  effectiveType: EffectiveType;\n  /** Estimated downlink speed in Mbps */\n  downlinkMbps: number;\n  /** Estimated round-trip time in milliseconds */\n  rttMs: number;\n  /** Whether data saver mode is enabled */\n  saveData: boolean;\n  /** Calculated quality tier */\n  quality: NetworkQuality;\n  /** Timestamp of measurement */\n  timestamp: number;\n}\n\n/**\n * Network Information API types (non-standard).\n */\ninterface NavigatorNetworkInfo extends EventTarget {\n  type?: ConnectionType;\n  effectiveType?: EffectiveType;\n  downlink?: number;\n  rtt?: number;\n  saveData?: boolean;\n  onchange?: ((this: NavigatorNetworkInfo, ev: Event) => void) | null;\n}\n\ninterface NavigatorWithConnection extends Navigator {\n  connection?: NavigatorNetworkInfo;\n  mozConnection?: NavigatorNetworkInfo;\n  webkitConnection?: NavigatorNetworkInfo;\n}\n\n// =============================================================================\n// Quality Assessment\n// =============================================================================\n\n/**\n * Quality ranking for connection types (higher is better).\n */\nconst QUALITY_RANK: Record<EffectiveType, number> = {\n  '4g': 4,\n  '3g': 3,\n  '2g': 2,\n  'slow-2g': 1,\n};\n\n/**\n * Calculate network quality tier from metrics.\n */\nfunction calculateQuality(\n  online: boolean,\n  effectiveType: EffectiveType,\n  downlinkMbps: number,\n  rttMs: number\n): NetworkQuality {\n  if (!online) return 'offline';\n\n  // Use effective type as primary indicator\n  if (effectiveType === 'slow-2g' || effectiveType === '2g') {\n    return 'poor';\n  }\n\n  // Use measured values for finer granularity\n  if (downlinkMbps >= 5 && rttMs <= 100) return 'excellent';\n  if (downlinkMbps >= 1.5 && rttMs <= 300) return 'good';\n  if (effectiveType === '3g' || downlinkMbps >= 0.5) return 'fair';\n\n  return 'poor';\n}\n\n// =============================================================================\n// Network Information Access\n// =============================================================================\n\n/**\n * Get the Network Information API connection object.\n */\nfunction getConnection(): NavigatorNetworkInfo | undefined {\n  if (typeof navigator === 'undefined') return undefined;\n\n  const nav = navigator as NavigatorWithConnection;\n  return nav.connection ?? nav.mozConnection ?? nav.webkitConnection;\n}\n\n/**\n * Get current network information.\n *\n * @returns Current network information with quality assessment\n *\n * @example\n * ```ts\n * const network = getNetworkInfo();\n * if (network.quality === 'poor') {\n *   // Show low-quality content\n * }\n * ```\n */\nexport function getNetworkInfo(): NetworkInfo {\n  const online = typeof navigator !== 'undefined' ? navigator.onLine : true;\n  const connection = getConnection();\n\n  const type = (connection?.type as ConnectionType) ?? 'unknown';\n  const effectiveType = connection?.effectiveType ?? '4g';\n  const downlinkMbps = connection?.downlink ?? 10;\n  const rttMs = connection?.rtt ?? 50;\n  const saveData = connection?.saveData ?? false;\n\n  return {\n    online,\n    type,\n    effectiveType,\n    downlinkMbps,\n    rttMs,\n    saveData,\n    quality: calculateQuality(online, effectiveType, downlinkMbps, rttMs),\n    timestamp: Date.now(),\n  };\n}\n\n/**\n * Check if browser is currently online.\n */\nexport function isOnline(): boolean {\n  return typeof navigator !== 'undefined' ? navigator.onLine : true;\n}\n\n/**\n * Check if current connection meets minimum quality requirement.\n *\n * @param minimum - Minimum required effective type\n * @returns True if current connection meets requirement\n */\nexport function meetsMinimumQuality(minimum: EffectiveType): boolean {\n  const network = getNetworkInfo();\n  return QUALITY_RANK[network.effectiveType] >= QUALITY_RANK[minimum];\n}\n\n/**\n * Check if connection is considered slow.\n *\n * @param thresholds - Custom thresholds for slow detection\n */\nexport function isSlowConnection(thresholds?: {\n  maxDownlinkMbps?: number;\n  maxRttMs?: number;\n}): boolean {\n  const network = getNetworkInfo();\n  const { maxDownlinkMbps = 1.5, maxRttMs = 300 } = thresholds ?? {};\n\n  return (\n    network.saveData ||\n    network.effectiveType === '2g' ||\n    network.effectiveType === 'slow-2g' ||\n    network.downlinkMbps < maxDownlinkMbps ||\n    network.rttMs > maxRttMs\n  );\n}\n\n/**\n * Get human-readable quality label.\n */\nexport function getQualityLabel(quality?: NetworkQuality): string {\n  const q = quality ?? getNetworkInfo().quality;\n\n  const labels: Record<NetworkQuality, string> = {\n    excellent: 'Excellent',\n    good: 'Good',\n    fair: 'Fair',\n    poor: 'Poor',\n    offline: 'Offline',\n  };\n\n  return labels[q];\n}\n\n// =============================================================================\n// Network Change Monitoring\n// =============================================================================\n\n/**\n * Callback for network status changes.\n */\nexport type NetworkChangeCallback = (info: NetworkInfo) => void;\n\n/**\n * Monitor network quality changes.\n *\n * @param callback - Function called when network quality changes\n * @returns Cleanup function to stop monitoring\n *\n * @example\n * ```ts\n * const unsubscribe = onNetworkChange((network) => {\n *   console.log('Network changed:', network.quality);\n * });\n *\n * // Later, cleanup\n * unsubscribe();\n * ```\n */\nexport function onNetworkChange(callback: NetworkChangeCallback): () => void {\n  if (typeof window === 'undefined') {\n    return () => {};\n  }\n\n  const connection = getConnection();\n  const cleanups: (() => void)[] = [];\n\n  // Listen to Network Information API changes\n  if (connection) {\n    const handleChange = (): void => {\n      callback(getNetworkInfo());\n    };\n\n    connection.addEventListener('change', handleChange);\n    cleanups.push(() => connection.removeEventListener('change', handleChange));\n  }\n\n  // Listen to online/offline events\n  const handleOnline = (): void => callback(getNetworkInfo());\n  const handleOffline = (): void => callback(getNetworkInfo());\n\n  window.addEventListener('online', handleOnline);\n  window.addEventListener('offline', handleOffline);\n  cleanups.push(() => window.removeEventListener('online', handleOnline));\n  cleanups.push(() => window.removeEventListener('offline', handleOffline));\n\n  return () => cleanups.forEach((cleanup) => cleanup());\n}\n\n/**\n * Listen for online status changes only.\n */\nexport function onOnlineChange(callback: (online: boolean) => void): () => void {\n  if (typeof window === 'undefined') {\n    return () => {};\n  }\n\n  const handleOnline = (): void => callback(true);\n  const handleOffline = (): void => callback(false);\n\n  window.addEventListener('online', handleOnline);\n  window.addEventListener('offline', handleOffline);\n\n  return () => {\n    window.removeEventListener('online', handleOnline);\n    window.removeEventListener('offline', handleOffline);\n  };\n}\n\n// =============================================================================\n// Prefetch Decision Utilities\n// =============================================================================\n\n/**\n * Check if prefetching should be allowed based on network conditions.\n *\n * @param options - Prefetch configuration\n * @returns True if prefetching should proceed\n *\n * @example\n * ```ts\n * if (shouldAllowPrefetch({ minQuality: '3g', respectDataSaver: true })) {\n *   prefetchNextPage();\n * }\n * ```\n */\nexport function shouldAllowPrefetch(\n  options: {\n    /** Minimum required effective connection type */\n    minQuality?: EffectiveType;\n    /** Skip prefetch if data saver is enabled */\n    respectDataSaver?: boolean;\n    /** Skip prefetch if offline */\n    requireOnline?: boolean;\n  } = {}\n): boolean {\n  const { minQuality = '2g', respectDataSaver = true, requireOnline = true } = options;\n\n  const network = getNetworkInfo();\n\n  if (requireOnline && !network.online) {\n    return false;\n  }\n\n  if (respectDataSaver && network.saveData) {\n    return false;\n  }\n\n  return meetsMinimumQuality(minQuality);\n}\n\n/**\n * Get recommended resource loading strategy based on network.\n */\nexport function getLoadingStrategy(): 'eager' | 'lazy' | 'none' {\n  const network = getNetworkInfo();\n\n  if (!network.online) return 'none';\n  if (network.quality === 'excellent' || network.quality === 'good') {\n    return 'eager';\n  }\n  return 'lazy';\n}\n\n/**\n * Get recommended image quality based on network.\n */\nexport function getRecommendedImageQuality(): 'high' | 'medium' | 'low' {\n  const network = getNetworkInfo();\n\n  if (network.saveData) return 'low';\n\n  switch (network.quality) {\n    case 'excellent':\n      return 'high';\n    case 'good':\n    case 'fair':\n      return 'medium';\n    default:\n      return 'low';\n  }\n}\n\n// =============================================================================\n// Connection Ping Utilities\n// =============================================================================\n\n/**\n * Ping an endpoint to check actual connectivity.\n *\n * Note: This function intentionally uses raw fetch() rather than apiClient because:\n * 1. It uses 'no-cors' mode which is only available with raw fetch\n * 2. Ping is a low-level network diagnostic, not an API call\n * 3. It must work with any URL, not just API endpoints\n *\n * @see {@link @/lib/api/api-client} for the main API client\n *\n * @param url - URL to ping\n * @param timeoutMs - Timeout in milliseconds\n * @returns True if ping succeeded\n */\nexport async function pingEndpoint(url: string, timeoutMs = 5000): Promise<boolean> {\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n  try {\n    // Raw fetch is intentional - uses 'no-cors' mode for cross-origin pings\n    const response = await fetch(url, {\n      method: 'HEAD',\n      mode: 'no-cors',\n      cache: 'no-cache',\n      signal: controller.signal,\n    });\n    clearTimeout(timeoutId);\n    return response.ok || response.type === 'opaque';\n  } catch {\n    clearTimeout(timeoutId);\n    return false;\n  }\n}\n\n/**\n * Measure actual round-trip time to an endpoint.\n *\n * @param url - URL to measure\n * @param samples - Number of samples to average\n * @returns Average RTT in milliseconds, or null if failed\n */\nexport async function measureRtt(url: string, samples = 3): Promise<number | null> {\n  const times: number[] = [];\n\n  for (let i = 0; i < samples; i++) {\n    const start = performance.now();\n    const success = await pingEndpoint(url, 3000);\n    const end = performance.now();\n\n    if (success) {\n      times.push(end - start);\n    }\n\n    // Small delay between samples\n    if (i < samples - 1) {\n      await new Promise((resolve) => setTimeout(resolve, 100));\n    }\n  }\n\n  if (times.length === 0) return null;\n  return times.reduce((a, b) => a + b, 0) / times.length;\n}\n"],"names":["QUALITY_RANK","calculateQuality","online","effectiveType","downlinkMbps","rttMs","getConnection","nav","getNetworkInfo","connection","type","saveData","isOnline","meetsMinimumQuality","minimum","network","isSlowConnection","thresholds","maxDownlinkMbps","maxRttMs","getQualityLabel","quality","q","onNetworkChange","callback","cleanups","handleChange","handleOnline","handleOffline","cleanup","onOnlineChange","shouldAllowPrefetch","options","minQuality","respectDataSaver","requireOnline","getLoadingStrategy","getRecommendedImageQuality","pingEndpoint","url","timeoutMs","controller","timeoutId","response","measureRtt","samples","times","i","start","success","end","resolve","a","b"],"mappings":"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"}