// SlideShare Service Worker // Enables offline functionality and app installation const CACHE_NAME = 'slideshare-v1'; const STATIC_CACHE = `${CACHE_NAME}-static`; const DYNAMIC_CACHE = `${CACHE_NAME}-dynamic`; // Resources to cache immediately when service worker installs const STATIC_ASSETS = [ '/', '/index.html', '/manifest.json', // Core app files will be added by build process ]; // Resources that should always be fetched from network first const NETWORK_FIRST = [ '/api/', '.json' ]; // Resources that can be cached and served stale while revalidating const STALE_WHILE_REVALIDATE = [ '/themes/', '.css', '.js', '.woff2', '.woff' ]; // Install event - cache core assets self.addEventListener('install', event => { console.log('[SW] Installing service worker...'); event.waitUntil( caches.open(STATIC_CACHE) .then(cache => { console.log('[SW] Caching static assets'); return cache.addAll(STATIC_ASSETS); }) .then(() => { console.log('[SW] Service worker installed successfully'); // Skip waiting to activate immediately return self.skipWaiting(); }) .catch(error => { console.error('[SW] Failed to cache static assets:', error); }) ); }); // Activate event - cleanup old caches self.addEventListener('activate', event => { console.log('[SW] Activating service worker...'); event.waitUntil( caches.keys() .then(cacheNames => { return Promise.all( cacheNames .filter(cacheName => cacheName.startsWith('slideshare-') && !cacheName.includes(CACHE_NAME) ) .map(cacheName => { console.log('[SW] Deleting old cache:', cacheName); return caches.delete(cacheName); }) ); }) .then(() => { console.log('[SW] Service worker activated'); // Take control of all clients immediately return self.clients.claim(); }) ); }); // Fetch event - handle network requests with caching strategies self.addEventListener('fetch', event => { const { request } = event; const url = new URL(request.url); // Only handle same-origin requests if (url.origin !== location.origin) { return; } // Determine caching strategy based on request if (shouldUseNetworkFirst(request)) { event.respondWith(networkFirst(request)); } else if (shouldUseStaleWhileRevalidate(request)) { event.respondWith(staleWhileRevalidate(request)); } else { event.respondWith(cacheFirst(request)); } }); // Network-first strategy (for dynamic content) async function networkFirst(request) { const cache = await caches.open(DYNAMIC_CACHE); try { const response = await fetch(request); if (response.status === 200) { cache.put(request, response.clone()); } return response; } catch (error) { console.log('[SW] Network failed, serving from cache:', request.url); const cachedResponse = await cache.match(request); if (cachedResponse) { return cachedResponse; } // Return offline page for navigation requests if (request.mode === 'navigate') { return caches.match('/'); } throw error; } } // Stale-while-revalidate strategy (for assets that can be updated in background) async function staleWhileRevalidate(request) { const cache = await caches.open(STATIC_CACHE); const cachedResponse = await cache.match(request); const fetchPromise = fetch(request) .then(response => { if (response.status === 200) { cache.put(request, response.clone()); } return response; }) .catch(() => cachedResponse); // Fallback to cache on network error // Return cached version immediately if available, otherwise wait for network return cachedResponse || fetchPromise; } // Cache-first strategy (for static assets) async function cacheFirst(request) { const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } try { const response = await fetch(request); if (response.status === 200) { const cache = await caches.open(STATIC_CACHE); cache.put(request, response.clone()); } return response; } catch (error) { console.log('[SW] Failed to fetch and cache:', request.url); throw error; } } // Helper functions to determine caching strategy function shouldUseNetworkFirst(request) { return NETWORK_FIRST.some(pattern => request.url.includes(pattern) || request.method !== 'GET' ); } function shouldUseStaleWhileRevalidate(request) { return STALE_WHILE_REVALIDATE.some(pattern => request.url.includes(pattern) ); } // Handle messages from the main thread self.addEventListener('message', event => { if (event.data && event.data.type) { switch (event.data.type) { case 'SKIP_WAITING': self.skipWaiting(); break; case 'GET_VERSION': event.ports[0].postMessage({ version: CACHE_NAME }); break; case 'CLEAR_CACHE': clearAllCaches().then(() => { event.ports[0].postMessage({ success: true }); }); break; } } }); // Clear all caches (useful for development) async function clearAllCaches() { const cacheNames = await caches.keys(); await Promise.all( cacheNames.map(cacheName => caches.delete(cacheName)) ); console.log('[SW] All caches cleared'); } // Background sync for when connection is restored self.addEventListener('sync', event => { console.log('[SW] Background sync triggered:', event.tag); if (event.tag === 'background-sync') { event.waitUntil( // Could implement background data sync here Promise.resolve() ); } }); // Push notifications (for future use) self.addEventListener('push', event => { if (!event.data) return; const data = event.data.json(); const options = { body: data.body, icon: '/icons/icon-192.png', badge: '/icons/badge-72.png', vibrate: [100, 50, 100], data: data.data, actions: data.actions }; event.waitUntil( self.registration.showNotification(data.title, options) ); }); // Handle notification clicks self.addEventListener('notificationclick', event => { event.notification.close(); if (event.action) { // Handle action button clicks console.log('[SW] Notification action clicked:', event.action); } else { // Handle notification body click event.waitUntil( clients.openWindow('/') ); } }); console.log('[SW] Service worker script loaded');