/** * UTILITY: Service Worker Manager * * PURPOSE: Register and communicate with Service Worker * FEATURES: * - Register Service Worker on app load * - Send messages to SW * - Receive messages from SW * - Handle token refresh via SW */ export interface TokenCheckResult { needsRefresh: boolean; timeUntilExpiry: number; } export interface TokenRefreshResult { success: boolean; accessToken?: string; expiresIn?: number; error?: string; } /** * FUNCTION: registerServiceWorker * * Register the service worker on app load * Called once from App.svelte onMount */ export async function registerServiceWorker(): Promise { // Service Workers only available in production (HTTPS) or localhost if (!('serviceWorker' in navigator)) { console.log('[SW Manager] Service Workers not supported'); return null; } try { console.log('[SW Manager] Registering Service Worker...'); const registration = await navigator.serviceWorker.register('/service-worker.js', { scope: '/', }); console.log('[SW Manager] Service Worker registered:', registration); // Listen for messages from Service Worker navigator.serviceWorker.addEventListener('message', (event) => { console.log('[SW Manager] Message from Service Worker:', event.data); }); return registration; } catch (error) { console.error('[SW Manager] Failed to register Service Worker:', error); return null; } } /** * FUNCTION: checkTokenExpiry * * Ask Service Worker if token needs refresh * * PARAMS: * - tokenExpiry: number - Token expiry timestamp (ms since epoch) * * RETURNS: TokenCheckResult */ export async function checkTokenExpiry(tokenExpiry: number): Promise { if (!navigator.serviceWorker.controller) { console.log('[SW Manager] Service Worker not active'); return { needsRefresh: false, timeUntilExpiry: tokenExpiry - Date.now() }; } return new Promise((resolve, reject) => { const channel = new MessageChannel(); channel.port1.onmessage = (event) => { if (event.data.error) { reject(new Error(event.data.error)); } else { resolve(event.data); } }; navigator.serviceWorker.controller?.postMessage( { type: 'CHECK_TOKEN', payload: { tokenExpiry }, }, [channel.port2] ); // Timeout after 5 seconds setTimeout(() => reject(new Error('Token check timeout')), 5000); }); } /** * FUNCTION: refreshTokenViaServiceWorker * * Ask Service Worker to refresh access token * * PARAMS: * - refreshToken: string - Refresh token * - apiUrl: string - Backend API URL * * RETURNS: TokenRefreshResult */ export async function refreshTokenViaServiceWorker( refreshToken: string, apiUrl: string ): Promise { if (!navigator.serviceWorker.controller) { console.log('[SW Manager] Service Worker not active'); return { success: false, error: 'Service Worker not active' }; } return new Promise((resolve, reject) => { const channel = new MessageChannel(); channel.port1.onmessage = (event) => { if (event.data.error) { resolve({ success: false, error: event.data.error }); } else { resolve(event.data); } }; navigator.serviceWorker.controller?.postMessage( { type: 'REFRESH_TOKEN', payload: { refreshToken, apiUrl }, }, [channel.port2] ); // Timeout after 10 seconds setTimeout(() => reject(new Error('Token refresh timeout')), 10000); }); } /** * FUNCTION: getServiceWorkerStatus * * Get Service Worker status and version * * RETURNS: { active: boolean, version?: string } */ export async function getServiceWorkerStatus(): Promise<{ active: boolean; version?: string }> { if (!navigator.serviceWorker.controller) { return { active: false }; } return new Promise((resolve, reject) => { const channel = new MessageChannel(); channel.port1.onmessage = (event) => { if (event.data.error) { reject(new Error(event.data.error)); } else { resolve(event.data); } }; navigator.serviceWorker.controller?.postMessage( { type: 'GET_STATUS' }, [channel.port2] ); // Timeout after 5 seconds setTimeout(() => reject(new Error('Status check timeout')), 5000); }); } /** * FUNCTION: unregisterServiceWorker * * Unregister the service worker (for development) */ export async function unregisterServiceWorker(): Promise { if (!('serviceWorker' in navigator)) { return; } try { const registrations = await navigator.serviceWorker.getRegistrations(); for (const registration of registrations) { await registration.unregister(); } console.log('[SW Manager] Service Worker unregistered'); } catch (error) { console.error('[SW Manager] Failed to unregister Service Worker:', error); } }