1const CACHE_NAME = 'joy-johnston-v1'; 2const STATIC_CACHE_NAME = 'static-v1'; 3const DYNAMIC_CACHE_NAME = 'dynamic-v1'; 4 5// Assets to cache immediately 6const STATIC_ASSETS = [ 7 '/', 8 '/src/main.tsx', 9 '/src/index.css', 10 '/favicon.ico', 11 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap' 12]; 13 14// Assets to cache on demand 15const CACHEABLE_PATTERNS = [ 16 /^https:\/\/fonts\.googleapis\.com/, 17 /^https:\/\/fonts\.gstatic\.com/, 18 /\/lovable-uploads\//, 19 /\.(js|css|png|jpg|jpeg|webp|svg|ico)$/ 20]; 21 22// Install event - cache static assets 23self.addEventListener('install', event => { 24 console.log('[SW] Installing...'); 25 event.waitUntil( 26 caches.open(STATIC_CACHE_NAME) 27 .then(cache => { 28 console.log('[SW] Pre-caching static assets'); 29 return cache.addAll(STATIC_ASSETS); 30 }) 31 .then(() => self.skipWaiting()) 32 ); 33}); 34 35// Activate event - clean up old caches 36self.addEventListener('activate', event => { 37 console.log('[SW] Activating...'); 38 event.waitUntil( 39 caches.keys().then(cacheNames => { 40 return Promise.all( 41 cacheNames 42 .filter(cacheName => 43 cacheName !== STATIC_CACHE_NAME && 44 cacheName !== DYNAMIC_CACHE_NAME 45 ) 46 .map(cacheName => { 47 console.log('[SW] Deleting old cache:', cacheName); 48 return caches.delete(cacheName); 49 }) 50 ); 51 }).then(() => self.clients.claim()) 52 ); 53}); 54 55// Fetch event - serve from cache with network fallback 56self.addEventListener('fetch', event => { 57 const { request } = event; 58 const url = new URL(request.url); 59 60 // Skip non-GET requests 61 if (request.method !== 'GET') return; 62 63 // Skip chrome-extension and other non-http requests 64 if (!url.protocol.startsWith('http')) return; 65 66 // Network first for API calls 67 if (url.pathname.includes('/api/') || url.hostname.includes('supabase')) { 68 event.respondWith( 69 fetch(request) 70 .catch(() => new Response('Offline', { status: 503 })) 71 ); 72 return; 73 } 74 75 // Cache first for static assets 76 if (CACHEABLE_PATTERNS.some(pattern => pattern.test(request.url))) { 77 event.respondWith( 78 caches.match(request) 79 .then(cachedResponse => { 80 if (cachedResponse) { 81 // Update cache in background 82 fetch(request) 83 .then(response => { 84 if (response.ok) { 85 caches.open(DYNAMIC_CACHE_NAME) 86 .then(cache => cache.put(request, response.clone())); 87 } 88 }) 89 .catch(() => {}); 90 91 return cachedResponse; 92 } 93 94 // Not in cache, fetch and cache 95 return fetch(request) 96 .then(response => { 97 if (response.ok) { 98 const responseClone = response.clone(); 99 caches.open(DYNAMIC_CACHE_NAME) 100 .then(cache => cache.put(request, responseClone)); 101 } 102 return response; 103 }) 104 .catch(() => { 105 // Return offline fallback for images 106 if (request.destination === 'image') { 107 return new Response( 108 '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200"><rect width="200" height="200" fill="#f3f4f6"/><text x="100" y="100" text-anchor="middle" dy="0.3em" font-family="sans-serif" font-size="14" fill="#6b7280">Image unavailable</text></svg>', 109 { headers: { 'Content-Type': 'image/svg+xml' } } 110 ); 111 } 112 throw error; 113 }); 114 }) 115 ); 116 return; 117 } 118 119 // Network first for everything else 120 event.respondWith( 121 fetch(request) 122 .catch(() => 123 caches.match(request) 124 .then(cachedResponse => 125 cachedResponse || new Response('Offline', { status: 503 }) 126 ) 127 ) 128 ); 129}); 130 131// Background sync for failed requests 132self.addEventListener('sync', event => { 133 if (event.tag === 'background-sync') { 134 console.log('[SW] Background sync triggered'); 135 // Handle any queued requests here 136 } 137}); 138
139// Push notifications (if needed in future) 140self.addEventListener('push', event => { 141 if (event.data) { 142 const options = { 143 body: event.data.text(), 144 icon: '/favicon.ico', 145 badge: '/favicon.ico' 146 }; 147 148 event.waitUntil( 149 self.registration.showNotification('Joy Johnston', options) 150 ); 151 } 152});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.