1// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2// Service worker WinScore â NOTIFICATIONS PUSH, ET RIEN D'AUTRE. 3// 4// Pas de cache, pas d'interception de `fetch`, pas de mode hors-ligne. Un 5// service worker qui met en cache prend la main sur TOUTES les requêtes du 6// site, y compris les payloads RSC : sur un site dont le contenu payant se 7// décide au rendu serveur, servir une réponse mise en cache par un worker 8// reviendrait à rejouer une page à quelqu'un qui n'y a plus droit. Le champ 9// d'action est donc volontairement réduit aux trois événements ci-dessous. 10// 11// â ï¸ Fichier servi tel quel depuis `public/` : pas de bundler, pas de 12// TypeScript, pas d'import. Syntaxe compatible navigateur, un point. 13// ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 14 15// Prendre la main immédiatement : sans ces deux lignes, un worker fraîchement 16// installé attend la fermeture de tous les onglets ouverts avant de servir â 17// l'utilisateur qui vient d'accepter les notifications n'en recevrait aucune 18// tant qu'il n'a pas fermé le site. 19self.addEventListener("install", () => self.skipWaiting()) 20self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())) 21 22self.addEventListener("push", (event) => { 23 // Charge utile écrite par `convex/webPushNode.ts` (WebPushPayload). 24 // Un push SANS données est légitime (RFC 8030) et sert de test côté 25 // navigateur : on affiche alors un texte neutre plutôt que de ne rien 26 // montrer â sur la plupart des plateformes, un push reçu et non affiché 27 // finit par faire révoquer la permission. 28 let data = {} 29 try { 30 data = event.data ? event.data.json() : {} 31 } catch { 32 data = {} 33 } 34 35 const title = data.title || "WinScore" 36 const options = { 37 body: data.body || "", 38 // `tag` : deux notifications de même tag se REMPLACENT. Un match qui 39 // prend trois buts ne doit pas laisser trois bannières empilées. 40 tag: data.tag || "winscore", 41 // Sans `renotify`, un remplacement est silencieux : le deuxième but 42 // n'attirerait pas l'attention. 43 renotify: Boolean(data.tag), 44 // Le logo existant, pas un `icon-192.png` qu'on aurait déclaré sans le 45 // produire : un chemin d'icône mort ne lève rien, il affiche juste une 46 // notification nue, et personne ne s'en aperçoit avant un utilisateur. 47 // Pas de `badge` pour la même raison â Android retombe alors sur l'icône 48 // du site, ce qui est correct, là où un fichier absent ne l'est pas. 49 icon: "/brand/logo.png", 50 data: { url: data.url || "/" }, 51 } 52 event.waitUntil(self.registration.showNotification(title, options)) 53}) 54 55self.addEventListener("notificationclick", (event) => { 56 event.notification.close() 57 const url = (event.notification.data && event.notification.data.url) || "/" 58 59 // Un onglet WinScore déjà ouvert est RÃUTILISà plutôt que doublé : ouvrir 60 // une nouvelle fenêtre à chaque but transformerait une soirée de football 61 // en dizaine d'onglets. 62 event.waitUntil( 63 self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => { 64 for (const client of clients) { 65 if ("focus" in client && "navigate" in client) { 66 return client.focus().then((c) => c.navigate(url)) 67 } 68 } 69 return self.clients.openWindow(url) 70 }), 71 ) 72})
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.