1/** 2 * Lebanco PWA Service Worker 3 * Version: 1.0 4 * Provides offline support, caching strategy, and background sync 5 * 6 * Installation: Registered in index.php and key pages 7 * Cache Strategy: Network-first for APIs, Cache-first for static assets 8 */ 9 10const CACHE_NAME = 'lebanco-v1'; 11const RUNTIME_CACHE = 'lebanco-runtime-v1'; 12const API_CACHE = 'lebanco-api-v1'; 13 14// Assets to precache on installation 15const PRECACHE_URLS = [ 16 '/', 17 '/index.php', 18 '/css/style.min.css', 19 '/css/reset.css', 20 '/css/font-awesome.css', 21 '/js/jquery.js', 22 '/images/logo-lebanco.png', 23 '/images/banco-shortcut.jpg', 24 '/offline.html' // Offline fallback page (optional) 25]; 26 27/** 28 * Install Event: Precache static assets 29 */ 30self.addEventListener('install', (event) => { 31 console.log('[Service Worker] Installing...', CACHE_NAME); 32 33 event.waitUntil( 34 caches.open(CACHE_NAME).then((cache) => { 35 console.log('[Service Worker] Precaching assets'); 36 return cache.addAll(PRECACHE_URLS).catch((err) => { 37 console.warn('[Service Worker] Precache partially failed:', err); 38 // Continue even if some resources fail 39 }); 40 }).then(() => self.skipWaiting()) 41 ); 42}); 43 44/** 45 * Activate Event: Clean up old caches 46 */ 47self.addEventListener('activate', (event) => { 48 console.log('[Service Worker] Activating...'); 49 50 event.waitUntil( 51 caches.keys().then((cacheNames) => { 52 return Promise.all( 53 cacheNames.map((cacheName) => { 54 if (cacheName !== CACHE_NAME && 55 cacheName !== RUNTIME_CACHE && 56 cacheName !== API_CACHE) { 57 console.log('[Service Worker] Deleting old cache:', cacheName); 58 return caches.delete(cacheName); 59 } 60 }) 61 ); 62 }).then(() => self.clients.claim()) 63 ); 64}); 65 66/** 67 * Fetch Event: Network + Caching Strategy 68 * 69 * Strategy breakdown: 70 * 1. API calls (/myapi2/*) â Network-first with API cache fallback 71 * 2. HTML pages â Network-first, cache fallback 72 * 3. CSS/JS/Images â Cache-first with network update 73 * 4. Failed requests â Return offline fallback 74 */ 75self.addEventListener('fetch', (event) => { 76 const { request } = event; 77 const url = new URL(request.url); 78 79 // Skip cross-origin requests 80 if (url.origin !== location.origin) { 81 return; 82 } 83 84 // Skip non-GET requests 85 if (request.method !== 'GET') { 86 return; 87 } 88 89 /** 90 * API Calls: Network First (GET fresh data, use cache as fallback) 91 */ 92 if (url.pathname.includes('/myapi2/')) { 93 event.respondWith( 94 fetch(request) 95 .then((response) => { 96 // Cache successful responses 97 if (response.ok) { 98 const clonedResponse = response.clone(); 99 caches.open(API_CACHE).then((cache) => { 100 cache.put(request, clonedResponse); 101 }); 102 } 103 return response; 104 }) 105 .catch(() => { 106 // Fallback to cached API data 107 return caches.match(request).then((cachedResponse) => { 108 return cachedResponse || createOfflineResponse('API unavailable'); 109 }); 110 }) 111 ); 112 return; 113 } 114 115 /** 116 * HTML Pages: Network First (get latest content, cache fallback) 117 */ 118 if (request.headers.get('accept').includes('text/html') || 119 url.pathname.endsWith('.php') || 120 url.pathname === '/') { 121 event.respondWith( 122 fetch(request) 123 .then((response) => { 124 if (response.ok) { 125 const clonedResponse = response.clone(); 126 caches.open(RUNTIME_CACHE).then((cache) => { 127 cache.put(request, clonedResponse); 128 }); 129 } 130 return response; 131 }) 132 .catch(() => { 133 return caches.match(request).then((cachedResponse) => { 134 return cachedResponse || createOfflineResponse('Page offline'); 135 }); 136 }) 137 ); 138 return; 139 } 140 141 /** 142 * Static Assets (CSS, JS, Images): Cache First 143 * Serve from cache and update in background 144 */ 145 if (request.destination === 'style' || 146 request.destination === 'script' ||
147 request.destination === 'image') { 148 event.respondWith( 149 caches.match(request).then((cachedResponse) => { 150 // Return cached version immediately 151 if (cachedResponse) { 152 // Update cache in background 153 fetch(request).then((freshResponse) => { 154 if (freshResponse.ok) { 155 caches.open(CACHE_NAME).then((cache) => { 156 cache.put(request, freshResponse.clone()); 157 }); 158 } 159 }).catch(() => { 160 // Silently fail background update 161 }); 162 return cachedResponse; 163 } 164 165 // No cache, try network 166 return fetch(request) 167 .then((response) => { 168 if (response.ok) { 169 const clonedResponse = response.clone(); 170 caches.open(CACHE_NAME).then((cache) => { 171 cache.put(request, clonedResponse); 172 }); 173 } 174 return response; 175 }) 176 .catch(() => createOfflineResponse('Asset unavailable')); 177 }) 178 ); 179 return; 180 } 181 182 /** 183 * Default: Network First 184 */ 185 event.respondWith( 186 fetch(request) 187 .catch(() => caches.match(request)) 188 ); 189}); 190 191/** 192 * Background Sync: Sync failed requests when reconnected 193 * (Optional: requires user to have enabled periodic sync permission) 194 */ 195self.addEventListener('sync', (event) => { 196 console.log('[Service Worker] Background sync triggered:', event.tag); 197 198 if (event.tag === 'sync-articles') { 199 event.waitUntil(syncArticles()); 200 } 201}); 202 203/** 204 * Push Notifications: Handle push events 205 * (Optional: requires push subscription) 206 */ 207self.addEventListener('push', (event) => { 208 console.log('[Service Worker] Push notification received'); 209 210 if (!event.data) return; 211 212 const data = event.data.json(); 213 const options = { 214 body: data.body || 'Nouvelle actualité sur Lebanco.net', 215 icon: '/images/logo-lebanco.png', 216 badge: '/images/banco-shortcut.jpg', 217 tag: 'lebanco-news', 218 requireInteraction: false, 219 data: { 220 dateOfArrival: Date.now(), 221 primaryKey: 1, 222 url: data.url || '/' 223 } 224 }; 225 226 event.waitUntil( 227 self.registration.showNotification(data.title || 'Lebanco', options) 228 ); 229}); 230 231/** 232 * Notification Click: Navigate to article URL 233 */ 234self.addEventListener('notificationclick', (event) => { 235 console.log('[Service Worker] Notification clicked'); 236 237 event.notification.close(); 238 239 const urlToOpen = event.notification.data.url || '/'; 240 event.waitUntil( 241 clients.matchAll({ 242 type: 'window', 243 includeUncontrolled: true 244 }).then((clientList) => { 245 // Check if page already open 246 for (let client of clientList) { 247 if (client.url === urlToOpen && 'focus' in client) { 248 return client.focus(); 249 } 250 } 251 // Open new window 252 if (clients.openWindow) { 253 return clients.openWindow(urlToOpen); 254 } 255 }) 256 ); 257}); 258 259/** 260 * Sync articles when connection restored 261 * Helper function for background sync 262 */ 263function syncArticles() { 264 return fetch('/myapi2/article/home') 265 .then((response) => response.json()) 266 .then((data) => { 267 console.log('[Service Worker] Synced articles:', data.length); 268 return caches.open(API_CACHE).then((cache) => { 269 cache.put('/myapi2/article/home', new Response(JSON.stringify(data))); 270 }); 271 }) 272 .catch((err) => { 273 console.error('[Service Worker] Sync failed:', err); 274 throw err; // Retry sync 275 }); 276} 277 278/** 279 * Create offline response 280 * Returns a minimal HTML response for offline pages 281 */ 282function createOfflineResponse(message) { 283 return new Response( 284 `<!DOCTYPE html> 285 <html> 286 <head> 287 <meta charset="UTF-8"> 288 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 289 <title>Offline - Lebanco.net</title> 290 <style> 291 body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f5f5f5; } 292 h1 { color: #c80c00; } 293 p { color: #666; font-size: 16px; } 294 a { color: #c80c00; text-decoration: none; } 295 </style> 296 </head> 297 <body> 298 <h1>${message}</h1> 299 <p>Vous êtes actuellement hors ligne. Veuillez vérifier votre connexion internet.</p> 300 <a href="/">Retour à l'accueil</a> 301 </body> 302 </html>`, 303 { 304 headers: { 305 'Content-Type': 'text/html; charset=utf-8', 306 }, 307 status: 503, 308 statusText: 'Service Unavailable' 309 } 310 ); 311} 312 313console.log('[Service Worker] Loaded');
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.