// rMaps Service Worker for Push Notifications & Background Location const CACHE_NAME = 'rmaps-v1'; const SYNC_TAG = 'rmaps-location-sync'; // Install event - cache essential assets self.addEventListener('install', (event) => { console.log('[SW] Installing service worker...'); self.skipWaiting(); }); // Activate event - clean up old caches self.addEventListener('activate', (event) => { console.log('[SW] Activating service worker...'); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== CACHE_NAME) .map((name) => caches.delete(name)) ); }) ); self.clients.claim(); }); // Background Sync - triggered when device comes back online self.addEventListener('sync', (event) => { console.log('[SW] Sync event:', event.tag); if (event.tag === SYNC_TAG) { event.waitUntil(syncLocation()); } }); // Sync location to server when coming back online async function syncLocation() { try { // Get stored location data from IndexedDB or localStorage via client const clients = await self.clients.matchAll({ type: 'window' }); for (const client of clients) { // Ask the client to send its current location client.postMessage({ type: 'REQUEST_LOCATION_SYNC' }); } console.log('[SW] Location sync requested from clients'); } catch (error) { console.error('[SW] Location sync failed:', error); } } // Request location from any available client async function requestLocationFromClient() { const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); if (clients.length > 0) { // Send message to first available client to get location clients[0].postMessage({ type: 'REQUEST_LOCATION_UPDATE' }); return true; } return false; } // Push event - handle incoming push notifications self.addEventListener('push', (event) => { console.log('[SW] Push received:', event); let data = { title: 'rMaps', body: 'You have a new notification', icon: '/icon-192.png', badge: '/icon-192.png', tag: 'rmaps-notification', data: {}, silent: false, }; if (event.data) { try { const payload = event.data.json(); data = { ...data, ...payload }; } catch (e) { data.body = event.data.text(); } } // Handle silent push - request location update without showing notification if (data.silent || data.data?.type === 'location_request') { event.waitUntil( requestLocationFromClient().then((sent) => { console.log('[SW] Silent push - location request sent:', sent); }) ); return; } const options = { body: data.body, icon: data.icon || '/icon-192.png', badge: data.badge || '/icon-192.png', tag: data.tag || 'rmaps-notification', data: data.data || {}, // Strong vibration pattern: buzz-pause-buzz-pause-long buzz vibrate: [200, 100, 200, 100, 400], actions: data.actions || [], requireInteraction: data.requireInteraction || false, // Use default system notification sound silent: false, // Renotify even if same tag (so user hears it again) renotify: true, }; event.waitUntil( self.registration.showNotification(data.title, options) ); }); // Notification click event - handle user interaction self.addEventListener('notificationclick', (event) => { console.log('[SW] Notification clicked:', event); event.notification.close(); const data = event.notification.data || {}; let targetUrl = '/'; // Determine URL based on notification type if (data.roomSlug) { targetUrl = `/${data.roomSlug}`; } else if (data.url) { targetUrl = data.url; } // Handle action buttons if (event.action === 'view') { targetUrl = data.url || targetUrl; } else if (event.action === 'dismiss') { return; } event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { // Try to focus an existing window for (const client of clientList) { if (client.url.includes(targetUrl) && 'focus' in client) { return client.focus(); } } // Open a new window if none exists if (clients.openWindow) { return clients.openWindow(targetUrl); } }) ); }); // Handle notification close self.addEventListener('notificationclose', (event) => { console.log('[SW] Notification closed:', event); }); // Handle messages from the main thread self.addEventListener('message', (event) => { console.log('[SW] Message received:', event.data); if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } });