import { precacheAndRoute } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'; import { BackgroundSyncPlugin } from 'workbox-background-sync'; import { ExpirationPlugin } from 'workbox-expiration'; declare const self: ServiceWorkerGlobalScope; // Precache all build assets precacheAndRoute(self.__WB_MANIFEST); // API requests - Network first, cache fallback registerRoute( ({ url }) => url.pathname.startsWith('/api/'), new NetworkFirst({ cacheName: 'api-cache', plugins: [ new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 5 * 60, // 5 minutes }), ], }) ); // Images - Cache first registerRoute( ({ request }) => request.destination === 'image', new CacheFirst({ cacheName: 'images', plugins: [ new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days }), ], }) ); // Fonts - Cache first registerRoute( ({ request }) => request.destination === 'font', new CacheFirst({ cacheName: 'fonts', plugins: [ new ExpirationPlugin({ maxEntries: 20, maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year }), ], }) ); // Background sync for failed POST requests const bgSyncPlugin = new BackgroundSyncPlugin('message-queue', { maxRetentionTime: 24 * 60, // Retry for 24 hours }); registerRoute( ({ url }) => url.pathname.startsWith('/api/messages'), new NetworkFirst({ plugins: [bgSyncPlugin], }), 'POST' ); // Push notifications self.addEventListener('push', (event) => { const data = event.data?.json() || {}; const options: NotificationOptions = { body: data.body || 'You have a new message', icon: data.icon || '/icon-192.png', badge: '/badge-96.png', tag: data.tag || 'notification', data: data.data || {}, actions: data.actions || [ { action: 'open', title: 'Open' }, { action: 'dismiss', title: 'Dismiss' }, ], vibrate: [200, 100, 200], requireInteraction: data.requireInteraction || false, }; event.waitUntil( self.registration.showNotification(data.title || 'AeThex Connect', options) ); }); // Notification click handler self.addEventListener('notificationclick', (event) => { event.notification.close(); if (event.action === 'open' || !event.action) { const urlToOpen = event.notification.data?.url || '/'; event.waitUntil( self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { // Check if there's already a window open for (const client of clientList) { if (client.url === urlToOpen && 'focus' in client) { return client.focus(); } } // Open new window if none exists if (self.clients.openWindow) { return self.clients.openWindow(urlToOpen); } }) ); } }); // Handle notification actions (reply, etc.) self.addEventListener('notificationclick', (event) => { if (event.action === 'reply' && event.reply) { // Send reply via background sync event.waitUntil( fetch('/api/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ conversationId: event.notification.data.conversationId, content: event.reply, contentType: 'text', }), }) ); } }); // Periodic background sync (for checking new messages when offline) self.addEventListener('periodicsync', (event: any) => { if (event.tag === 'check-messages') { event.waitUntil(checkForNewMessages()); } }); async function checkForNewMessages() { try { const response = await fetch('/api/messages/unread'); const data = await response.json(); if (data.count > 0) { self.registration.showNotification('New Messages', { body: `You have ${data.count} unread messages`, icon: '/icon-192.png', badge: '/badge-96.png', tag: 'unread-messages', }); } } catch (error) { console.error('Error checking messages:', error); } } // Skip waiting and claim clients immediately self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } }); self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); }); // Log service worker version console.log('AeThex Connect Service Worker v1.0.0');