PageSourceSearch

https://lebanco.net/service-worker.js

js lebanco.net collected 2026-10-02 12:06:02 UTC 8,874 bytes, 313 lines download raw bytes

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.