PageSourceSearch

https://winscore.ai/sw.js

js winscore.ai collected 2026-10-05 14:01:06 UTC 3,655 bytes, 72 lines download raw bytes

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.