// Service Worker for PWA and Push Notifications // Handles offline support, caching, and VAPID Web Push const CACHE_NAME = 'logship-erp-v1' const OFFLINE_URL = '/' self.addEventListener('install', (event) => { console.log('[SW] Service Worker installed') event.waitUntil( caches.open(CACHE_NAME).then((cache) => { console.log('[SW] Opened cache') // Pre-cache essential assets return cache.addAll([ OFFLINE_URL, '/favicon.ico', '/manifest.json' ]).catch((error) => { console.warn('[SW] Pre-caching failed:', error) }) }).then(() => self.skipWaiting()) ) }) self.addEventListener('activate', (event) => { console.log('[SW] Service Worker activated') event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== CACHE_NAME) { console.log('[SW] Deleting old cache:', cacheName) return caches.delete(cacheName) } }) ) }).then(() => clients.claim()) ) }) // Keep service worker alive and responsive // This helps ensure push notifications work in background self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting() } // Respond to keepalive pings if (event.data && event.data.type === 'PING') { event.ports[0].postMessage({ type: 'PONG' }) } }) // Handle incoming push notifications from server // CRITICAL: This must show a notification synchronously for Android to work in background self.addEventListener('push', (event) => { console.log('[SW] Push event received') // Parse notification data let data try { data = event.data ? event.data.json() : {} } catch (e) { console.error('[SW] Failed to parse push data:', e) data = {} } // Ensure we have required fields const title = data.title || '🔔 New Order' const body = data.body || 'You have a new update' // Minimal notification options for maximum compatibility const options = { body: body, icon: '/favicon.ico', badge: '/favicon.ico', tag: 'order-' + Date.now(), data: { url: data.url || '/sales/orders' } } // MUST call showNotification synchronously within push event // This is critical for Android background delivery event.waitUntil( self.registration.showNotification(title, options) ) }) // Handle notification click self.addEventListener('notificationclick', (event) => { console.log('[SW] Notification clicked', event.action) event.notification.close() // Handle different actions const action = event.action const url = event.notification.data?.url || '/sales/orders' if (action === 'dismiss') { // Just close notification return } // Focus the window or open a new one event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { // Check if there's already a window open for (let i = 0; i < clientList.length; i++) { const client = clientList[i] if (client.url.includes(url) && 'focus' in client) { return client.focus() } } // If no window is open, open a new one if (clients.openWindow) { return clients.openWindow(url) } }) ) })