1const PREFIX = "V1.10"; 2const CACHED_FILES = ['favicon.ico','./css_generic/fonts/Roboto/Roboto.woff2','./offline.html']; 3// Optimisation : Set pour recherche O(1) au lieu de Array O(n) si la liste grandit 4const MAP_CACHED_FILES = ['GandG_efoodage', 'GandG_ecommerce', 'GandG_simple', 'global--gestion-commande']; 5 6let unreadNotif = 0; 7 8/** Gestion des notifications **/ 9self.addEventListener('push', (event)=> { 10 if (!(self.Notification && self.Notification.permission === 'granted')) return; 11 12 if (event.data) { 13 const message = event.data.text(); 14 const objectToDisplay = JSON.parse(message); 15 16 // 2FA number matching : 3 boutons d'action (le choix est POSTé par notificationclick). 17 // Clic sur le corps (iOS/Firefox sans boutons) â data.link = page d'approbation (repli). 18 if (objectToDisplay.type === '2fa') { 19 const actions = (objectToDisplay.numbers || []).map(n => ({ action: 'gg2fa_' + n, title: String(n) })); 20 event.waitUntil(self.registration.showNotification(objectToDisplay.title, { 21 body: objectToDisplay.msg, 22 tag: 'gg2fa_' + objectToDisplay.pushId, // un seul challenge affiché à la fois 23 renotify: true, 24 requireInteraction: true, // ne disparaît pas tout seul pendant la saisie 25 actions, 26 data: { 27 gg2fa: { 28 pushId: objectToDisplay.pushId, 29 token: objectToDisplay.token, 30 apiUrl: objectToDisplay.apiUrl, 31 msgOk: objectToDisplay.msgOk, 32 msgKo: objectToDisplay.msgKo 33 }, 34 link: objectToDisplay.link 35 }, 36 icon: 'apple-touch-icon.png' 37 })); 38 return; 39 } 40 41 const promiseChain = self.registration.showNotification(objectToDisplay.title, { 42 body: objectToDisplay.msg, 43 data: { link: objectToDisplay.link }, 44 icon: 'apple-touch-icon.png' 45 }); 46 47 event.waitUntil(promiseChain); 48 49 // Gestion badge 50 if ('setAppBadge' in self.navigator) { 51 // Idéalement, récupérer le vrai compte depuis le serveur, ici on incrémente juste localement 52 // unreadNotif est défini globalement ? Attention au redémarrage du SW qui remet à 0. 53 // Pour l'instant on laisse tel quel. 54 event.waitUntil(self.navigator.setAppBadge(unreadNotif)); 55 } 56 } 57}); 58 59self.addEventListener('notificationclick', (event) => { 60 event.notification.close(); 61 const data = event.notification.data || {}; 62 63 // 2FA : clic sur un des 3 nombres â POST du choix (jeton usage unique = preuve) 64 if (data.gg2fa && event.action && event.action.indexOf('gg2fa_') === 0) { 65 const choice = event.action.slice(6); 66 event.waitUntil((async () => { 67 try { 68 const apiUrl = new URL(data.gg2fa.apiUrl); 69 if (apiUrl.origin !== self.location.origin) return; // jamais de POST hors du site 70 const resp = await fetch(apiUrl.href, { 71 method: 'POST', 72 body: new URLSearchParams({ pushId: data.gg2fa.pushId, token: data.gg2fa.token, choice }) 73 }); 74 await self.registration.showNotification(event.notification.title, { 75 body: resp.ok ? (data.gg2fa.msgOk || 'OK') : (data.gg2fa.msgKo || 'KO'), 76 tag: 'gg2fa_result', 77 icon: 'apple-touch-icon.png' 78 }); 79 } catch (e) { 80 console.warn('2FA push approve failed', e); 81 } 82 })()); 83 return; 84 } 85 86 const link = data.link; 87 88 event.waitUntil((async () => { 89 try { 90 const urlObj = new URL(link); 91 if (['http:', 'https:'].includes(urlObj.protocol)) { 92 await clients.openWindow(urlObj.href); 93 } else { 94 console.warn('Protocole invalide:', link); 95 } 96 } catch (e) { 97 await clients.openWindow('/'); 98 } 99 })()); 100}); 101 102self.addEventListener("install", (event) => {
103 self.skipWaiting(); 104 console.log(`${PREFIX} Install`); 105 event.waitUntil((async ()=>{ 106 const cache = await caches.open(PREFIX); 107 // Robustesse : on essaie de tout mettre en cache, mais on ne plante pas si un fichier manque 108 await Promise.all( 109 CACHED_FILES.map(url => cache.add(url).catch(e => console.error(`Echec cache ${url}`, e))) 110 ); 111 })()); 112}); 113 114self.addEventListener('activate', (event) => { 115 event.waitUntil(self.clients.claim()); 116 console.log(`${PREFIX} Activate`); 117 118 event.waitUntil((async () => { 119 if (self.registration.navigationPreload) { 120 await self.registration.navigationPreload.enable(); 121 } 122 const keys = await caches.keys(); 123 await Promise.all( 124 keys.map(key => { 125 if (!key.includes(PREFIX)) return caches.delete(key); 126 }) 127 ); 128 })()); 129}); 130 131self.addEventListener("fetch", (event) => { 132 if (event.request.mode === "navigate") { 133 event.respondWith((async () => { 134 try { 135 const preloadResponse = await event.preloadResponse; 136 if (preloadResponse) return preloadResponse; 137 return await fetch(event.request); 138 } catch (error) { 139 const cache = await caches.open(PREFIX); 140 return await cache.match('./offline.html'); 141 } 142 })()); 143 } else { 144 const shouldCache = MAP_CACHED_FILES.some(file => event.request.url.includes(file)); 145 if (shouldCache) { 146 event.respondWith((async () => { 147 const cache = await caches.open(PREFIX); 148 try { 149 const response = await fetch(event.request); 150 cache.put(event.request, response.clone()); 151 return response; 152 } catch (e) { 153 return await cache.match(event.request); 154 } 155 })()); 156 } 157 } 158});
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.