/** * Service Worker for background synchronization * Handles platform sync, user content sync, and publish worker */ const CACHE_NAME = 'zapwall-sync-v1' const SYNC_INTERVAL_MS = 60000 // 1 minute const REPUBLISH_INTERVAL_MS = 30000 // 30 seconds let syncInProgress = false let publishWorkerInterval = null let notificationDetectorInterval = null // Install event - cache resources self.addEventListener('install', (event) => { console.log('[SW] Service Worker installing') self.skipWaiting() // Activate immediately }) // Activate event - clean up old caches self.addEventListener('activate', (event) => { console.log('[SW] Service Worker activating') event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== CACHE_NAME) .map((name) => caches.delete(name)) ) }) ) return self.clients.claim() // Take control of all pages immediately }) // Message handler - communication with main thread self.addEventListener('message', (event) => { const { type, data } = event.data switch (type) { case 'START_PLATFORM_SYNC': handleStartPlatformSync() break case 'STOP_PLATFORM_SYNC': handleStopPlatformSync() break case 'START_USER_SYNC': handleStartUserSync(data?.userPubkey) break case 'START_PUBLISH_WORKER': handleStartPublishWorker() break case 'STOP_PUBLISH_WORKER': handleStopPublishWorker() break case 'PUBLISH_EVENT': handlePublishEvent(data?.event, data?.relays) break case 'START_NOTIFICATION_DETECTOR': handleStartNotificationDetector(data?.userPubkey) break case 'STOP_NOTIFICATION_DETECTOR': handleStopNotificationDetector() break case 'PING': event.ports[0]?.postMessage({ type: 'PONG' }) break default: console.warn('[SW] Unknown message type:', type) } }) // Periodic sync event (if supported) self.addEventListener('periodicsync', (event) => { if (event.tag === 'platform-sync') { event.waitUntil(handleStartPlatformSync()) } else if (event.tag === 'publish-worker') { event.waitUntil(handlePublishWorker()) } }) /** * Start platform sync */ async function handleStartPlatformSync() { if (syncInProgress) { console.log('[SW] Platform sync already in progress') return } syncInProgress = true console.log('[SW] Starting platform sync') try { // Request sync from main thread (we need access to nostrService) broadcastToClients({ type: 'SYNC_REQUEST', data: { syncType: 'platform' } }) // Set up periodic sync if ('periodicSync' in self.registration) { try { await self.registration.periodicSync.register('platform-sync', { minInterval: SYNC_INTERVAL_MS, }) } catch (error) { console.warn('[SW] Periodic sync not supported:', error) } } } catch (error) { console.error('[SW] Error starting platform sync:', error) } finally { syncInProgress = false } } /** * Stop platform sync */ function handleStopPlatformSync() { console.log('[SW] Stopping platform sync') if ('periodicSync' in self.registration) { self.registration.periodicSync .unregister('platform-sync') .catch((error) => { console.warn('[SW] Error unregistering periodic sync:', error) }) } } /** * Start user content sync */ async function handleStartUserSync(userPubkey) { if (!userPubkey) { console.warn('[SW] User sync requires pubkey') return } console.log('[SW] Starting user content sync for:', userPubkey) try { // Request sync from main thread broadcastToClients({ type: 'SYNC_REQUEST', data: { syncType: 'user', userPubkey }, }) } catch (error) { console.error('[SW] Error starting user sync:', error) } } /** * Start publish worker */ function handleStartPublishWorker() { console.log('[SW] Starting publish worker') // Clear existing interval if (publishWorkerInterval) { clearInterval(publishWorkerInterval) } // Process unpublished objects periodically publishWorkerInterval = setInterval(() => { handlePublishWorker() }, REPUBLISH_INTERVAL_MS) // Process immediately handlePublishWorker() } /** * Stop publish worker */ function handleStopPublishWorker() { console.log('[SW] Stopping publish worker') if (publishWorkerInterval) { clearInterval(publishWorkerInterval) publishWorkerInterval = null } } /** * Process unpublished objects */ async function handlePublishWorker() { console.log('[SW] Processing unpublished objects') try { // Request publish worker execution from main thread broadcastToClients({ type: 'PUBLISH_WORKER_REQUEST' }) } catch (error) { console.error('[SW] Error in publish worker:', error) } } /** * Publish event to relays */ async function handlePublishEvent(event, relays) { if (!event || !relays || relays.length === 0) { console.warn('[SW] Invalid publish event data') return } console.log('[SW] Publishing event:', event.id, 'to', relays.length, 'relays') try { // Request publish from main thread (we need access to nostrService) broadcastToClients({ type: 'PUBLISH_REQUEST', data: { event, relays }, }) } catch (error) { console.error('[SW] Error publishing event:', error) } } /** * Broadcast message to all clients */ function broadcastToClients(message) { self.clients.matchAll().then((clients) => { clients.forEach((client) => { client.postMessage(message) }) }) } // Background sync event (fallback for browsers without periodic sync) self.addEventListener('sync', (event) => { if (event.tag === 'platform-sync') { event.waitUntil(handleStartPlatformSync()) } else if (event.tag === 'publish-worker') { event.waitUntil(handlePublishWorker()) } }) /** * Start notification detector * Service Worker directly populates notifications table */ function handleStartNotificationDetector(userPubkey) { console.log('[SW] Starting notification detector for:', userPubkey) // Clear existing interval if (notificationDetectorInterval) { clearInterval(notificationDetectorInterval) } // Service Worker directly scans IndexedDB and creates notifications // This runs in the Service Worker context notificationDetectorInterval = setInterval(() => { void scanAndCreateNotifications(userPubkey) }, 30000) // Every 30 seconds // Process immediately void scanAndCreateNotifications(userPubkey) } /** * Scan IndexedDB and create notifications * Service Worker détecte les changements de manière indépendante * Il n'est pas dépendant du service d'écriture */ async function scanAndCreateNotifications(userPubkey) { try { // Le Service Worker détecte les changements indépendamment // Il scanne IndexedDB directement pour détecter les nouveaux événements // et crée les notifications via le Web Worker d'écriture // Pour l'instant, on demande au thread principal de scanner // car le Service Worker n'a pas accès direct à objectCache // TODO: Implémenter la détection directe dans le Service Worker si possible broadcastToClients({ type: 'NOTIFICATION_DETECT_REQUEST', data: { userPubkey }, }) } catch (error) { console.error('[SW] Error in notification detection:', error) } } /** * Stop notification detector */ function handleStopNotificationDetector() { console.log('[SW] Stopping notification detector') if (notificationDetectorInterval) { clearInterval(notificationDetectorInterval) notificationDetectorInterval = null } } console.log('[SW] Service Worker loaded')