// Service Worker for Talk2Me PWA const CACHE_NAME = 'voice-translator-v1'; const ASSETS_TO_CACHE = [ '/', '/static/css/styles.css', '/static/js/dist/app.js', '/static/icons/icon-192x192.png', '/static/icons/icon-512x512.png', '/static/icons/favicon.ico', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css' ]; // Install event - cache essential assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => { console.log('Service Worker: Caching files'); return cache.addAll(ASSETS_TO_CACHE); }) .then(() => self.skipWaiting()) ); }); // Activate event - clean up old caches self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((name) => { if (name !== CACHE_NAME) { console.log('Service Worker: Clearing old cache'); return caches.delete(name); } }) ); }) ); }); // Fetch event - serve cached content when offline self.addEventListener('fetch', (event) => { // Skip cross-origin requests if (!event.request.url.startsWith(self.location.origin)) { return; } // Skip API calls - we don't want to cache those if (event.request.url.includes('/transcribe') || event.request.url.includes('/translate') || event.request.url.includes('/speak') || event.request.url.includes('/get_audio/')) { return; } event.respondWith( caches.match(event.request) .then((cachedResponse) => { // Return cached response if available if (cachedResponse) { return cachedResponse; } // Otherwise fetch from network return fetch(event.request) .then((response) => { // Don't cache if response is not valid if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // Clone the response since it can only be consumed once const responseToCache = response.clone(); caches.open(CACHE_NAME) .then((cache) => { cache.put(event.request, responseToCache); }); return response; }) .catch(() => { // If network fetch fails and it's a document request, return fallback if (event.request.mode === 'navigate') { return caches.match('/'); } }); }) ); }); // Handle push notifications self.addEventListener('push', (event) => { if (!event.data) { return; } const data = event.data.json(); const options = { body: data.body || 'New translation available', icon: data.icon || '/static/icons/icon-192x192.png', badge: data.badge || '/static/icons/icon-192x192.png', vibrate: [100, 50, 100], tag: data.tag || 'talk2me-notification', requireInteraction: false, silent: false, data: { url: data.url || '/', ...data.data }, actions: [ { action: 'view', title: 'View', icon: '/static/icons/icon-192x192.png' }, { action: 'close', title: 'Close' } ] }; event.waitUntil( self.registration.showNotification(data.title || 'Voice Translator', options) ); }); // Handle notification click self.addEventListener('notificationclick', (event) => { event.notification.close(); if (event.action === 'close') { return; } const urlToOpen = event.notification.data.url || '/'; event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => { // Check if there's already a window/tab with the app open for (let client of windowClients) { if (client.url === urlToOpen && 'focus' in client) { return client.focus(); } } // If not, open a new window/tab if (clients.openWindow) { return clients.openWindow(urlToOpen); } }) ); }); // Handle periodic background sync self.addEventListener('periodicsync', (event) => { if (event.tag === 'translation-updates') { event.waitUntil(checkForUpdates()); } }); async function checkForUpdates() { // Check for app updates or send usage statistics try { const response = await fetch('/api/check-updates'); if (response.ok) { const data = await response.json(); if (data.hasUpdate) { self.registration.showNotification('Update Available', { body: 'A new version of Voice Translator is available!', icon: '/static/icons/icon-192x192.png', badge: '/static/icons/icon-192x192.png', tag: 'update-notification' }); } } } catch (error) { console.error('Failed to check for updates:', error); } }