/// /// import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching'; import { NavigationRoute, registerRoute } from 'workbox-routing'; declare let self: ServiceWorkerGlobalScope; // PWA Workbox caching precacheAndRoute(self.__WB_MANIFEST); cleanupOutdatedCaches(); // Handle navigation requests const handler = createHandlerBoundToURL('/'); const navigationRoute = new NavigationRoute(handler, { denylist: [/^\/api/] }); registerRoute(navigationRoute); // Push notification handling self.addEventListener('push', (event) => { console.log('[SW] Push event received:', event); if (!event.data) { console.log('[SW] Push event but no data'); return; } let data; try { data = event.data.json(); } catch (e) { console.error('[SW] Failed to parse push data:', e); return; } console.log('[SW] Push data:', data); const options: NotificationOptions = { body: data.body || 'Recipe processing update', icon: '/favicon.png', badge: '/favicon.png', data: data, requireInteraction: data.requireInteraction || false, silent: false, tag: data.tag || 'recipe-update', timestamp: Date.now(), actions: [] }; // Add actions based on notification type if (data.type === 'success' && data.itemId) { options.actions = [ { action: 'view', title: 'View Recipe', icon: '/favicon.png' }, { action: 'dismiss', title: 'Dismiss' } ]; } else if (data.type === 'error' && data.itemId) { options.actions = [ { action: 'retry', title: 'Retry', icon: '/favicon.png' }, { action: 'view', title: 'View Details' } ]; } const title = data.title || getNotificationTitle(data.type, data); event.waitUntil( self.registration.showNotification(title, options) ); }); // Handle notification clicks self.addEventListener('notificationclick', (event) => { console.log('[SW] Notification click received:', event); event.notification.close(); const data = event.notification.data; const action = event.action; let url = '/'; if (action === 'view' && data?.itemId) { url = `/?highlight=${data.itemId}`; } else if (action === 'retry' && data?.itemId) { // Navigate to dashboard and trigger retry via postMessage url = `/?highlight=${data.itemId}&action=retry`; } else if (data?.itemId) { url = `/?highlight=${data.itemId}`; } event.waitUntil( clients.matchAll({ type: 'window', includeUncontrolled: true }) .then((clientsList) => { // Check if there's already a window/tab open for (const client of clientsList) { if (client.url.includes(self.location.origin) && 'focus' in client) { return client.focus().then(() => { // Send message to the client about the action return client.postMessage({ type: 'notification-action', action: action, data: data }); }); } } // If no window is open, open a new one if (clients.openWindow) { return clients.openWindow(url); } }) ); }); // Handle notification close self.addEventListener('notificationclose', (event) => { console.log('[SW] Notification closed:', event); // Track notification dismissals if needed const data = event.notification.data; if (data?.analytics) { // Could send analytics event here console.log('[SW] Notification dismissed:', data); } }); // Background sync for retry operations self.addEventListener('sync', (event) => { console.log('[SW] Background sync:', event.tag); if (event.tag === 'retry-queue-item') { event.waitUntil(handleRetrySync()); } }); // Helper functions function getNotificationTitle(type: string, data: any): string { switch (type) { case 'success': return data.recipeName ? `✅ Recipe Ready: ${data.recipeName}` : '✅ Recipe extraction complete'; case 'error': return '❌ Recipe extraction failed'; case 'progress': return `🔄 Processing recipe...`; default: return '📱 InstaRecipe Update'; } } async function handleRetrySync() { try { // Get retry items from IndexedDB or localStorage if needed console.log('[SW] Handling retry sync'); // This could implement background retry logic // For now, we'll let the main app handle retries return Promise.resolve(); } catch (error) { console.error('[SW] Retry sync failed:', error); throw error; } } // Message handling for communication with main app self.addEventListener('message', (event) => { console.log('[SW] Message received:', event.data); const { type, data } = event.data; switch (type) { case 'SKIP_WAITING': self.skipWaiting(); break; case 'GET_VERSION': event.ports[0].postMessage({ version: '1.0.0' }); break; case 'QUEUE_RETRY': // Queue a background sync for retry self.registration.sync.register('retry-queue-item'); break; default: console.log('[SW] Unknown message type:', type); } });