{"version":3,"file":"registerSW.mjs","sources":["../../../src/lib/utils/registerSW.ts"],"sourcesContent":["/**\n * @file Service Worker Registration\n * @description Comprehensive service worker lifecycle management with update\n * detection, offline/online events, and user notification support.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Service worker registration configuration\n */\nexport interface ServiceWorkerConfig {\n  /** Path to service worker file */\n  swPath?: string;\n  /** Called when SW is successfully installed for first time */\n  onSuccess?: (registration: ServiceWorkerRegistration) => void;\n  /** Called when new SW content is available */\n  onUpdate?: (registration: ServiceWorkerRegistration) => void;\n  /** Called when app goes offline */\n  onOffline?: () => void;\n  /** Called when app comes back online */\n  onOnline?: () => void;\n  /** Called on registration error */\n  onError?: (error: Error) => void;\n  /** Called during SW registration */\n  onRegistering?: () => void;\n  /** Enable registration in development (default: false) */\n  enableInDev?: boolean;\n  /** Update check interval in ms (default: 60000 = 1 minute) */\n  updateCheckInterval?: number;\n}\n\n/**\n * Service worker state\n */\nexport interface ServiceWorkerState {\n  /** Whether SW is supported */\n  isSupported: boolean;\n  /** Whether SW is registered */\n  isRegistered: boolean;\n  /** Whether a new version is available */\n  hasUpdate: boolean;\n  /** Whether app is online */\n  isOnline: boolean;\n  /** Current registration */\n  registration: ServiceWorkerRegistration | null;\n  /** Any registration error */\n  error: Error | null;\n}\n\n/**\n * Service worker controller interface\n */\nexport interface ServiceWorkerController {\n  /** Current state */\n  state: ServiceWorkerState;\n  /** Update the service worker */\n  update: () => Promise<void>;\n  /** Skip waiting and activate new SW */\n  skipWaiting: () => void;\n  /** Unregister the service worker */\n  unregister: () => Promise<boolean>;\n  /** Get SW version */\n  getVersion: () => Promise<string | null>;\n  /** Cache specific URLs */\n  cacheUrls: (urls: string[]) => void;\n  /** Clear all caches */\n  clearCaches: () => void;\n  /** Add state change listener */\n  subscribe: (listener: (state: ServiceWorkerState) => void) => () => void;\n}\n\n// ============================================================================\n// State Management\n// ============================================================================\n\nlet currentState: ServiceWorkerState = {\n  isSupported: typeof navigator !== 'undefined' && 'serviceWorker' in navigator,\n  isRegistered: false,\n  hasUpdate: false,\n  isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,\n  registration: null,\n  error: null,\n};\n\nconst listeners = new Set<(state: ServiceWorkerState) => void>();\n\nfunction setState(updates: Partial<ServiceWorkerState>): void {\n  currentState = { ...currentState, ...updates };\n  listeners.forEach((listener) => listener(currentState));\n}\n\n// ============================================================================\n// Environment Detection\n// ============================================================================\n\n/**\n * Check if running on localhost\n */\nfunction isLocalhost(): boolean {\n  if (typeof window === 'undefined') return false;\n\n  return Boolean(\n    window.location.hostname === 'localhost' ||\n    window.location.hostname === '[::1]' ||\n    window.location.hostname.match(/^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)\n  );\n}\n\n/**\n * Check if SW should be enabled\n */\nfunction shouldEnableSW(enableInDev: boolean): boolean {\n  if (!currentState.isSupported) return false;\n  return !(!enableInDev && isLocalhost());\n}\n\n// ============================================================================\n// Registration Functions\n// ============================================================================\n\n/**\n * Register a valid service worker\n */\nasync function registerValidSW(\n  swPath: string,\n  config: ServiceWorkerConfig\n): Promise<ServiceWorkerRegistration | null> {\n  try {\n    config.onRegistering?.();\n\n    const registration = await navigator.serviceWorker.register(swPath, {\n      scope: '/',\n    });\n\n    setState({ registration, isRegistered: true, error: null });\n\n    // Handle updates\n    registration.onupdatefound = () => {\n      const installingWorker = registration.installing;\n      if (!installingWorker) return;\n\n      installingWorker.onstatechange = () => {\n        if (installingWorker.state === 'installed') {\n          if (navigator.serviceWorker.controller) {\n            // New content available\n            console.info('[SW] New content available; please refresh.');\n            setState({ hasUpdate: true });\n            config.onUpdate?.(registration);\n          } else {\n            // Content cached for first time\n            console.info('[SW] Content cached for offline use.');\n            config.onSuccess?.(registration);\n          }\n        }\n      };\n    };\n\n    // Setup periodic update checks\n    if (config.updateCheckInterval !== undefined && config.updateCheckInterval > 0) {\n      setInterval(() => {\n        registration.update().catch(console.error);\n      }, config.updateCheckInterval);\n    }\n\n    return registration;\n  } catch (error) {\n    console.error('[SW] Registration failed:', error);\n    const err = error instanceof Error ? error : new Error(String(error));\n    setState({ error: err, isRegistered: false });\n    config.onError?.(err);\n    return null;\n  }\n}\n\n/**\n * Check if service worker is valid (for localhost debugging)\n */\nasync function checkValidServiceWorker(\n  swPath: string,\n  config: ServiceWorkerConfig\n): Promise<ServiceWorkerRegistration | null> {\n  try {\n    const response = await fetch(swPath, {\n      headers: { 'Service-Worker': 'script' },\n    });\n\n    const contentType = response.headers.get('content-type');\n\n    if (\n      response.status === 404 ||\n      (contentType !== null && contentType !== '' && !contentType.includes('javascript'))\n    ) {\n      // No valid SW found - unregister existing\n      const registration = await navigator.serviceWorker.ready;\n      await registration.unregister();\n      window.location.reload();\n      return null;\n    }\n\n    // Valid SW, proceed with registration\n    return registerValidSW(swPath, config);\n  } catch {\n    console.info('[SW] No internet connection. Running offline.');\n    setState({ isOnline: false });\n    config.onOffline?.();\n    return null;\n  }\n}\n\n// ============================================================================\n// Main Registration Function\n// ============================================================================\n\n/**\n * Register service worker with lifecycle management\n *\n * @example\n * ```tsx\n * // In your app entry point\n * registerServiceWorker({\n *   onSuccess: () => console.log('App cached for offline'),\n *   onUpdate: () => {\n *     if (confirm('New version available! Reload?')) {\n *       window.location.reload();\n *     }\n *   },\n *   onOffline: () => toast.info('You are offline'),\n *   onOnline: () => toast.success('Back online'),\n * });\n * ```\n */\nexport function registerServiceWorker(config: ServiceWorkerConfig = {}): ServiceWorkerController {\n  const {\n    swPath = '/sw.js',\n    enableInDev = false,\n    updateCheckInterval = 60000,\n    ...callbacks\n  } = config;\n\n  // Setup network status listeners\n  if (typeof window !== 'undefined') {\n    window.addEventListener('online', () => {\n      setState({ isOnline: true });\n      callbacks.onOnline?.();\n    });\n\n    window.addEventListener('offline', () => {\n      setState({ isOnline: false });\n      callbacks.onOffline?.();\n    });\n  }\n\n  // Register SW if supported\n  if (shouldEnableSW(enableInDev)) {\n    window.addEventListener('load', () => {\n      if (isLocalhost()) {\n        // Localhost - check if SW is valid first\n        void checkValidServiceWorker(swPath, {\n          ...callbacks,\n          updateCheckInterval,\n        });\n      } else {\n        // Production - register directly\n        void registerValidSW(swPath, {\n          ...callbacks,\n          updateCheckInterval,\n        });\n      }\n    });\n\n    // Listen for controller changes (when new SW takes over)\n    navigator.serviceWorker.addEventListener('controllerchange', () => {\n      console.info('[SW] Controller changed - reloading');\n      window.location.reload();\n    });\n  }\n\n  // Return controller interface\n  return createController();\n}\n\n// ============================================================================\n// Controller Interface\n// ============================================================================\n\nfunction createController(): ServiceWorkerController {\n  return {\n    get state() {\n      return currentState;\n    },\n\n    async update(): Promise<void> {\n      if (currentState.registration) {\n        await currentState.registration.update();\n      }\n    },\n\n    skipWaiting(): void {\n      const waiting = currentState.registration?.waiting;\n      if (waiting) {\n        waiting.postMessage({ type: 'SKIP_WAITING' });\n      }\n    },\n\n    async unregister(): Promise<boolean> {\n      if (currentState.registration) {\n        const result = await currentState.registration.unregister();\n        if (result) {\n          setState({\n            isRegistered: false,\n            registration: null,\n            hasUpdate: false,\n          });\n        }\n        return result;\n      }\n      return false;\n    },\n\n    async getVersion(): Promise<string | null> {\n      if (!currentState.registration?.active) return null;\n\n      return new Promise((resolve) => {\n        const channel = new MessageChannel();\n        channel.port1.onmessage = (event: MessageEvent<{ version?: string } | null>) => {\n          resolve(event.data?.version ?? null);\n        };\n\n        const activeWorker = currentState.registration?.active;\n        if (activeWorker) {\n          activeWorker.postMessage({ type: 'GET_VERSION' }, [channel.port2]);\n        } else {\n          resolve(null);\n          return;\n        }\n\n        // Timeout after 1 second\n        setTimeout(() => resolve(null), 1000);\n      });\n    },\n\n    cacheUrls(urls: string[]): void {\n      currentState.registration?.active?.postMessage({\n        type: 'CACHE_URLS',\n        payload: { urls },\n      });\n    },\n\n    clearCaches(): void {\n      currentState.registration?.active?.postMessage({\n        type: 'CLEAR_CACHES',\n      });\n    },\n\n    subscribe(listener: (state: ServiceWorkerState) => void): () => void {\n      listeners.add(listener);\n      // Immediately call with current state\n      listener(currentState);\n      return () => listeners.delete(listener);\n    },\n  };\n}\n\n// ============================================================================\n// React Hook\n// ============================================================================\n\nimport { useState as useReactState, useEffect } from 'react';\n\n/**\n * React hook for service worker state\n *\n * @example\n * ```tsx\n * function App() {\n *   const { hasUpdate, isOnline, skipWaiting } = useServiceWorker();\n *\n *   return (\n *     <>\n *       {hasUpdate && (\n *         <Banner>\n *           Update available!\n *           <button onClick={skipWaiting}>Refresh</button>\n *         </Banner>\n *       )}\n *       {!isOnline && <Banner>You are offline</Banner>}\n *     </>\n *   );\n * }\n * ```\n */\nexport function useServiceWorker(controller?: ServiceWorkerController): {\n  isSupported: boolean;\n  isRegistered: boolean;\n  hasUpdate: boolean;\n  isOnline: boolean;\n  registration: ServiceWorkerRegistration | null;\n  error: Error | null;\n  update: () => Promise<void>;\n  skipWaiting: () => void;\n  unregister: () => Promise<boolean>;\n  getVersion: () => Promise<string | null>;\n  cacheUrls: (urls: string[]) => void;\n  clearCaches: () => void;\n} {\n  const [state, setLocalState] = useReactState<ServiceWorkerState>(currentState);\n\n  useEffect(() => {\n    const ctrl = controller ?? createController();\n    return ctrl.subscribe(setLocalState);\n  }, [controller, setLocalState]);\n\n  const ctrl = controller ?? createController();\n  return {\n    ...state,\n    update: async () => ctrl.update(),\n    skipWaiting: () => ctrl.skipWaiting(),\n    unregister: async () => ctrl.unregister(),\n    getVersion: async () => ctrl.getVersion(),\n    cacheUrls: (urls: string[]) => ctrl.cacheUrls(urls),\n    clearCaches: () => ctrl.clearCaches(),\n  };\n}\n\n// ============================================================================\n// Unregister Function\n// ============================================================================\n\n/**\n * Unregister all service workers\n *\n * @example\n * ```tsx\n * // Useful for debugging or resetting app state\n * await unregisterServiceWorker();\n * ```\n */\nexport async function unregisterServiceWorker(): Promise<boolean> {\n  if (!currentState.isSupported) return false;\n\n  try {\n    const registrations = await navigator.serviceWorker.getRegistrations();\n    await Promise.all(registrations.map(async (r) => r.unregister()));\n\n    setState({\n      isRegistered: false,\n      registration: null,\n      hasUpdate: false,\n    });\n\n    return true;\n  } catch (error) {\n    console.error('[SW] Unregister failed:', error);\n    return false;\n  }\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Check if service worker is controlling the page\n */\nexport function isServiceWorkerControlling(): boolean {\n  return currentState.isSupported && Boolean(navigator.serviceWorker.controller);\n}\n\n/**\n * Wait for service worker to be ready\n */\nexport async function waitForServiceWorker(): Promise<ServiceWorkerRegistration | null> {\n  if (!currentState.isSupported) return null;\n\n  try {\n    return await navigator.serviceWorker.ready;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Send message to service worker\n */\nexport function postMessageToSW(message: unknown): void {\n  currentState.registration?.active?.postMessage(message);\n}\n"],"names":["currentState","listeners","setState","updates","listener","isLocalhost","shouldEnableSW","enableInDev","registerValidSW","swPath","config","registration","installingWorker","error","err","checkValidServiceWorker","response","contentType","registerServiceWorker","updateCheckInterval","callbacks","createController","waiting","result","resolve","channel","event","activeWorker","urls","useServiceWorker","controller","state","setLocalState","useReactState","useEffect","ctrl","unregisterServiceWorker","registrations","r","isServiceWorkerControlling","waitForServiceWorker","postMessageToSW","message"],"mappings":"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"}