Spaces:
Running
Running
| // Service Worker for Zenith Fraud Detection Platform | |
| // Provides offline capability and performance optimization | |
| const CACHE_VERSION = 'v1.0.0'; | |
| const CACHE_NAME = `zenith-cache-${CACHE_VERSION}`; | |
| const OFFLINE_URL = '/offline.html'; | |
| // Assets to cache on install | |
| const STATIC_ASSETS = [ | |
| '/', | |
| '/offline.html', | |
| '/index.html', | |
| '/manifest.json', | |
| '/favicon.ico', | |
| ]; | |
| // Install event - cache static assets | |
| self.addEventListener('install', (event) => { | |
| console.log('[Service Worker] Installing...'); | |
| event.waitUntil( | |
| caches.open(CACHE_NAME) | |
| .then((cache) => { | |
| console.log('[Service Worker] Caching static assets'); | |
| return cache.addAll(STATIC_ASSETS); | |
| }) | |
| .then(() => self.skipWaiting()) | |
| ); | |
| }); | |
| // Activate event - clean up old caches | |
| self.addEventListener('activate', (event) => { | |
| console.log('[Service Worker] Activating...'); | |
| event.waitUntil( | |
| caches.keys() | |
| .then((cacheNames) => { | |
| return Promise.all( | |
| cacheNames.map((cacheName) => { | |
| if (cacheName !== CACHE_NAME) { | |
| console.log('[Service Worker] Deleting old cache:', cacheName); | |
| return caches.delete(cacheName); | |
| } | |
| }) | |
| ); | |
| }) | |
| .then(() => self.clients.claim()) | |
| ); | |
| }); | |
| // Fetch event - network first, fallback to cache | |
| self.addEventListener('fetch', (event) => { | |
| const { request } = event; | |
| const url = new URL(request.url); | |
| // Skip cross-origin requests | |
| if (url.origin !== location.origin) { | |
| return; | |
| } | |
| // API requests - network only (no cache) | |
| if (url.pathname.startsWith('/api/')) { | |
| event.respondWith( | |
| fetch(request).catch(() => { | |
| return new Response( | |
| JSON.stringify({ error: 'Offline - API unavailable' }), | |
| { | |
| status: 503, | |
| headers: { 'Content-Type': 'application/json' } | |
| } | |
| ); | |
| }) | |
| ); | |
| return; | |
| } | |
| // Static assets - cache first, fallback to network | |
| if (request.destination === 'script' || | |
| request.destination === 'style' || | |
| request.destination === 'font' || | |
| request.destination === 'image') { | |
| event.respondWith( | |
| caches.match(request) | |
| .then((cachedResponse) => { | |
| if (cachedResponse) { | |
| return cachedResponse; | |
| } | |
| return fetch(request).then((response) => { | |
| // Cache successful responses | |
| if (response.status === 200) { | |
| const responseToCache = response.clone(); | |
| caches.open(CACHE_NAME).then((cache) => { | |
| cache.put(request, responseToCache); | |
| }); | |
| } | |
| return response; | |
| }); | |
| }) | |
| ); | |
| return; | |
| } | |
| // HTML pages - network first, fallback to cache, then offline page | |
| event.respondWith( | |
| fetch(request) | |
| .then((response) => { | |
| // Cache successful HTML responses | |
| if (response.status === 200 && request.destination === 'document') { | |
| const responseToCache = response.clone(); | |
| caches.open(CACHE_NAME).then((cache) => { | |
| cache.put(request, responseToCache); | |
| }); | |
| } | |
| return response; | |
| }) | |
| .catch(() => { | |
| return caches.match(request) | |
| .then((cachedResponse) => { | |
| if (cachedResponse) { | |
| return cachedResponse; | |
| } | |
| // Return offline page for navigation requests | |
| if (request.destination === 'document') { | |
| return caches.match(OFFLINE_URL); | |
| } | |
| }); | |
| }) | |
| ); | |
| }); | |
| // Background sync for offline actions | |
| self.addEventListener('sync', (event) => { | |
| console.log('[Service Worker] Background sync:', event.tag); | |
| if (event.tag === 'sync-offline-actions') { | |
| event.waitUntil(syncOfflineActions()); | |
| } | |
| }); | |
| async function syncOfflineActions() { | |
| // Get offline actions from IndexedDB (handled by main app) | |
| // This is a placeholder - actual implementation in useOfflineQueue | |
| console.log('[Service Worker] Syncing offline actions...'); | |
| } | |
| // Push notifications (future enhancement) | |
| self.addEventListener('push', (event) => { | |
| const data = event.data ? event.data.json() : {}; | |
| const options = { | |
| body: data.body || 'New fraud alert detected', | |
| icon: '/icon-192.png', | |
| badge: '/badge-72.png', | |
| vibrate: [200, 100, 200], | |
| data: data, | |
| actions: [ | |
| { action: 'view', title: 'View Alert' }, | |
| { action: 'dismiss', title: 'Dismiss' } | |
| ] | |
| }; | |
| event.waitUntil( | |
| self.registration.showNotification(data.title || 'Zenith Alert', options) | |
| ); | |
| }); | |
| // Notification click handler | |
| self.addEventListener('notificationclick', (event) => { | |
| event.notification.close(); | |
| if (event.action === 'view') { | |
| event.waitUntil( | |
| clients.openWindow(event.notification.data.url || '/') | |
| ); | |
| } | |
| }); | |
| // Message handler for cache updates | |
| self.addEventListener('message', (event) => { | |
| if (event.data && event.data.type === 'SKIP_WAITING') { | |
| self.skipWaiting(); | |
| } | |
| if (event.data && event.data.type === 'CACHE_URLS') { | |
| event.waitUntil( | |
| caches.open(CACHE_NAME).then((cache) => { | |
| return cache.addAll(event.data.urls); | |
| }) | |
| ); | |
| } | |
| }); | |
| console.log('[Service Worker] Loaded successfully'); | |