From de9c951eafbcc67ccb31b8b5639088ae75a9f980 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Wed, 19 Nov 2025 13:48:31 +0100 Subject: [PATCH] =?UTF-8?q?```=20=E2=9C=A8=20feat(sw.js/app.js):=20G=C3=A8?= =?UTF-8?q?re=20les=20notifications=20push=20et=20l'abonnement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute la gestion des notifications push avec abonnement via le service worker et enregistre l'abonnement sur le serveur. Gère l'affichage d'une bannière pour demander la permission. ``` --- .env | 2 + ansible/templates/caddy.j2 | 2 +- assets/app.js | 221 +++++++++++++++++++++++++- migrations/Version20251119124756.php | 33 ++++ public/assets/notif.png | Bin 0 -> 20007 bytes public/sw.js | 62 ++++++-- src/Controller/SecurityController.php | 18 +++ src/Entity/Sub.php | 51 ++++++ src/Repository/SubRepository.php | 43 +++++ 9 files changed, 410 insertions(+), 22 deletions(-) create mode 100644 migrations/Version20251119124756.php create mode 100644 public/assets/notif.png create mode 100644 src/Entity/Sub.php create mode 100644 src/Repository/SubRepository.php diff --git a/.env b/.env index b691114..63c6af5 100644 --- a/.env +++ b/.env @@ -56,3 +56,5 @@ STRIPE_PK=pk_test_51SUA22173W4aeFB1nO6oFfDZ12HOTffDKtCshhZ8rkUg6kUO2ZaQC0tK72rhE STRIPE_SK=sk_test_51SUA22173W4aeFB16EB2LxGI0hNvNJzFshDI98zRImWBIhSfzqOGAz5TlPxSpUWbj3x4COm6kmSsaal9FpQR1A7M0022DvjbbR STRIPE_WEBHOOKS_SIGN=whsec_0DOZJAwgMwkcHl2RWXI8h8YItj9q7v3A DEV_URL=https://3ea1cf1b1555.ngrok-free.app +VAPID_PK=DsOg7jToRSD-VpNSV1Gt3YAhSwz4l-nqeu7yFvzbSxg +VAPID_PC=BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo diff --git a/ansible/templates/caddy.j2 b/ansible/templates/caddy.j2 index 48eb368..f8e8517 100644 --- a/ansible/templates/caddy.j2 +++ b/ansible/templates/caddy.j2 @@ -14,7 +14,7 @@ www.e-cosplay.fr { header { -X-Robots-Tag - Permissions-Policy "accelerometer=(), autoplay=(), camera=(), clipboard-write=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), publickey-credentials-get=(), usb=(), vr=(), screen-wake-lock=(), xr-spatial-tracking=(), bluetooth=(), ambient-light-sensor=(), battery=(), gamepad=(), notifications=(), push=()" + Permissions-Policy "accelerometer=(), autoplay=(), camera=(), clipboard-write=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), publickey-credentials-get=(), usb=(), screen-wake-lock=(), xr-spatial-tracking=(), bluetooth=(), gamepad=()" Content-Security-Policy "base-uri 'self'; default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' https://datas.e-cosplay.fr https://*.cloudflareinsights.com https://storage.googleapis.com https://*.trustpilot.com; font-src 'self' https://fonts.gstatic.com;connect-src https://*.e-cosplay.fr https://*.cloudflareinsights.com https://fonts.googleapis.com https://widget.trustpilot.com/ https://challenges.cloudflare.com; frame-src 'self' https://*.trustpilot.com;" } diff --git a/assets/app.js b/assets/app.js index 8161da6..0c73a18 100644 --- a/assets/app.js +++ b/assets/app.js @@ -2,6 +2,34 @@ import './app.scss' import * as Turbo from "@hotwired/turbo" import {PaymentForm} from './PaymentForm' + +// --- CLÉ VAPID PUBLIQUE DU SERVEUR --- +// Cette clé est nécessaire pour identifier notre application auprès du service push. +const VAPID_PUBLIC_KEY = "BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo"; + + +/** + * Convertit une chaîne Base64 URL Safe en Uint8Array. + * Nécessaire pour passer la clé VAPID publique à pushManager.subscribe(). + * @param {string} base64String + * @returns {Uint8Array} + */ +function urlBase64ToUint8Array(base64String) { + const padding = '='.repeat((4 - base64String.length % 4) % 4); + const base64 = (base64String + padding) + .replace(/\-/g, '+') + .replace(/_/g, '/'); + + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; +} + + /** * Fonction générique pour basculer la visibilité d'un menu déroulant. * @param {HTMLElement} button - Le bouton qui déclenche l'action. @@ -76,19 +104,202 @@ function initializeUI() { // Simuler un panier non-vide au chargement (Mettre 0 pour un panier vide réel) updateCartDisplay(0); + + // --- 4. Vérification de l'abonnement push au chargement (Logique demandée) --- + // Si la permission est déjà accordée, nous vérifions si l'abonnement est enregistré. + if ('Notification' in window && Notification.permission === 'granted') { + subscribeAndSave(); + } } +/** + * Tente d'abonner l'utilisateur aux notifications push via le Service Worker + * et envoie l'objet d'abonnement au backend (/notificationSub). + */ +async function subscribeAndSave() { + if (!('Notification' in window) || Notification.permission !== 'granted') { + console.log("Les notifications ne sont pas supportées ou la permission n'est pas accordée."); + return; + } + + if (!('serviceWorker' in navigator) || !('PushManager' in window)) { + console.error("Le Service Worker ou PushManager n'est pas disponible pour l'abonnement."); + return; + } + + try { + const registration = await navigator.serviceWorker.ready; + let subscription = await registration.pushManager.getSubscription(); + + // 1. Si aucun abonnement n'existe, on essaie d'en créer un. + if (!subscription) { + console.log("Aucun abonnement existant trouvé. Tentative de nouvel abonnement..."); + + const applicationServerKey = urlBase64ToUint8Array(VAPID_PUBLIC_KEY); + + subscription = await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: applicationServerKey + }); + + if (!subscription) { + console.error("Échec de la création de l'abonnement. Le Service Worker est-il correctement enregistré et la clé VAPID valide ?"); + return; + } + } else { + console.log("Abonnement push existant trouvé. Vérification/Mise à jour auprès du serveur."); + } + + // 2. Envoi (ou mise à jour) de l'abonnement au backend + const payload = { subscription: subscription.toJSON() }; + + const response = await fetch('/notificationSub', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + + if (response.ok) { + console.log("-> Abonnement aux notifications sauvegardé (ou vérifié) avec succès sur le serveur. <-"); + } else { + console.error("-> Erreur lors de la sauvegarde de l'abonnement:", response.status, response.statusText); + } + + } catch (error) { + console.error("Erreur lors de l'obtention de l'abonnement ou de l'enregistrement:", error); + } +} + +/** + * Tente de demander la permission si nécessaire et d'appeler l'abonnement. + * (Utilisée par le clic du bandeau) + */ +async function promptForPermissionAndSubscribe() { + if (!('Notification' in window)) { + console.error("Les Notifications ne sont pas supportées par ce navigateur."); + return; + } + + // Demander la permission + try { + const permission = await Notification.requestPermission(); + + if (permission === 'granted') { + console.log("-> Permission de notification accordée. Lancement de l'abonnement. <-"); + await subscribeAndSave(); + } else { + console.log(`-> Permission de notification refusée ou ignorée (${permission}). <-`); + } + } catch (error) { + console.error("Erreur lors de la demande de permission:", error); + } +} + + +/** + * Affiche une petite carte de notification push temporaire en bas à gauche. + * N'affiche QUE si la permission n'est PAS accordée. + */ +function handleNotificationBanner() { + // Clé pour éviter de ré-afficher le bandeau si l'utilisateur vient de le fermer/cliquer. + const BANNER_ID = 'notification-prompt-banner'; + const DURATION_MS = 15000; // 15 secondes d'affichage + + // 1. NE PAS AFFICHER si la permission est déjà accordée (la vérification silencieuse est dans initializeUI) + if ('Notification' in window && Notification.permission === 'granted') { + return; + } + + // 2. Si le bandeau existe déjà (e.g. navigation rapide), on quitte. + if (document.getElementById(BANNER_ID)) { + return; + } + + // --- LA LOGIQUE DE CONTRÔLE DE TEMPS (LOCAL STORAGE) A ÉTÉ RETIRÉE ICI --- + + // 3. Créer le conteneur du message + const banner = document.createElement('div'); + banner.id = BANNER_ID; + banner.className = `fixed bottom-4 left-4 z-50 p-4 max-w-xs + bg-indigo-600 text-white rounded-xl shadow-2xl + transition-all duration-500 transform + opacity-0 translate-y-full + md:left-8 md:bottom-8`; // Style initial (masqué) + + banner.innerHTML = ` +
+

+ 🔔 Activer les notifications +

+ +
+

+ Recevez les nouvelles, les promotions et les événements de l'association. +

+ + `; + + document.body.appendChild(banner); + + // 4. Fonctions d'animation et de gestion + const hideBanner = () => { + // Déclenche l'animation de disparition + banner.classList.remove('opacity-100', 'translate-y-0'); + banner.classList.add('opacity-0', 'translate-y-full'); + // Supprime après l'animation pour nettoyer le DOM + setTimeout(() => { + if (document.body.contains(banner)) { + document.body.removeChild(banner); + } + }, 600); + }; + + // Clic sur le bouton de fermeture + document.getElementById('closeNotificationBanner').addEventListener('click', () => { + hideBanner(); + }); + + // Clic sur le bouton d'activation -> Logique Push + document.getElementById('activateNotifications').addEventListener('click', async () => { + await promptForPermissionAndSubscribe(); + // Fermer le bandeau après l'interaction (que ce soit accordé ou refusé) + hideBanner(); + }); + + // 5. Affichage et Timer + + // Montre le bandeau (déclencher l'animation) + setTimeout(() => { + banner.classList.remove('opacity-0', 'translate-y-full'); + banner.classList.add('opacity-100', 'translate-y-0'); + }, 100); + + // Cache le bandeau automatiquement après la durée définie + setTimeout(hideBanner, DURATION_MS); +} + + // --- INITIALISATION DES COMPOSANTS APRÈS TURBO/CHARGEMENT --- document.addEventListener('DOMContentLoaded', ()=>{ customElements.define('payment-don',PaymentForm,{extends:'form'}) + // initializeUI appelle subscribeAndSave si la permission est accordée initializeUI() - const env = document.querySelector('meta[name="env"]') - if(env.getAttribute('content') == "prod") { - if (typeof navigator.serviceWorker !== 'undefined') { - navigator.serviceWorker.register('sw.js') - } + if (typeof navigator.serviceWorker !== 'undefined') { + // Assurez-vous que le Service Worker est bien enregistré en mode prod + navigator.serviceWorker.register('sw.js') } + // handleNotificationBanner n'affiche que si la permission n'est PAS accordée + handleNotificationBanner() + }); document.addEventListener('turbo:load', initializeUI); diff --git a/migrations/Version20251119124756.php b/migrations/Version20251119124756.php new file mode 100644 index 0000000..e76e71b --- /dev/null +++ b/migrations/Version20251119124756.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE sub (id SERIAL NOT NULL, subcriber TEXT NOT NULL, sub_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('COMMENT ON COLUMN sub.subcriber IS \'(DC2Type:array)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP TABLE sub'); + } +} diff --git a/public/assets/notif.png b/public/assets/notif.png new file mode 100644 index 0000000000000000000000000000000000000000..50844d3177cfba5256063789199bb4a812255f45 GIT binary patch literal 20007 zcmXtARahKNl!f3nI1KJiaCdiicXxuj3=-TOf?Ehqa33VN2lwFa4$Hsa?!$EVL&?3j zs^-c$-BBt^(kO`dh!7AED6%pVYM=Ms|9F(uz&!}SceWArsK}WZpEq&O~KVJWM zvAR(Fa?$lsNKMx3w6DNBH#MHc>t^*Zbl{yJ9a5F~Md<(T#83u`H=%A+&cZ`~sBzdx|Q#6wro( zVLB}yhQL)DKr+_+uggz-r`kqbwO;dJB)XmOT6#opWEV}8mc?Zxx-Hh;aIC9-ZETnt z@=pPW7EVqj|7#dSnVNAr`oDwkxB7Oq%ly|TDeOAgFzde&M+voSwQ*%36CjzARWdx- zYOg-hKBausp7lomoc~i-e5vJ=-JsG>nLg4uj0P4&KHJtrsNO$68vi$G5LFzmE0xMt z&|RN-@P{NB^LupJf12E0`sTeD|2IXD>YtdNqW{`r3w9bEyyJe3`e=-Gy;Ez0XwZl3 zBAJl0*oIZ~*@;dM6oah z?LqIjHk<#nnJKx(F#6AsTXKY&waU1P|7nx0Vbss?pTP~HijVJ({&#-Klu>zaUX`ud z<{9(g9ywO#H*CrOw5iy><-5`TPeS9;%#8Qw|76jgck#`8s`_u#TD5WcAW79NM6+N9 zJA=;267)|EWGKY^7D|-=6Gmuw&|VW?_0!lK*HLGz_Lu+6l2d%hGr2i_WGSzVC4-CXa=OGy$mb4k zJ@q4e^Fo>Xc7`$`CI+0vZte2#5BllKZzKY4r{0~x7#7=E34!%08likaB)JSH{D3sh zfBBx=hAh3u3so~!*2=f%a|F&^cR1aTJ4nBbdjJQMdGd23rBS(wiNSgNUS@rvFqu5A zl`?}p>=$?DE7jA6na7LS7UG~BfH{rOtfX39%f?Nk3?T3kH5sP{Lx z0#&dSJPHDKbWylDP5xv~ed?gs*#?NY`x#MLc%4AAY9kDv^XG1oG;!Z|bc)NFVUq2| z!I*l#2c`iD>DAYf>ZK)}Uwce!mZKK#N6(Z_m7Yl1oc1BQjl#p-0WaH2jgb;~k$3n& zN%|KDy_qfMz}Jiym&#s`v!@>qYWB0Pz(Jmh7Cld7HE5|a~c)XiD(j%mIMDr zQdsQf`GNeN$Qwk$fQz0FVrrL`YyltgGUc4;EOy~RZR5cBr<`6BafSs}VC@&gAu*=l)n^v8A9 zxBc;`ZE$3v{}Wth`4~NOYAJET=l0WRciuG> z!-mBG)ai%$HaJfEzx{>+yBf8D{7&sr)&-+!s@?r4v|ev_t>^9EXys`Qn~~+x>BH4Z zA1MA$WVrNjoN&%iw^S!xfFO**{5rVw7EDir~hb27?RT;Aj^Lvy;u zM~J)HGzuD@Qmg+peiT$Roqk_z6z#IoW`lP*CH zxl+hU+=?U>Vj{+XG&eJIjreI+9jL0Bb@h6~OzO7J_;aTLBCp5H+{|TX0HLGUalJjQ zV^fdu?Tp+1W@1qz)PLMHh(OuH4-u8iulL8(_5MU8A~r+e5olt%WfQ`mV;7V0-2(x) zLr|t*N$z^LrsI64hVZV%CXoqGM6gn$665nY(rPv7%nqnSi>63oG7VVM(nAgbA-;&j z`;#MEC+B2fBKs4@K#;RPs{A&izCBDty|`tLK!w8vmyiu!stt$Q_^Mkutb5>)D69{U zSj!`%_I*jl)2AvXawKv`VZfQJGmW^@=|sC7Tl%G7ZKwyBn}Jk-kc}MryEjcCvSrG$ z1+oiCg*EK(?5oy52AaE667vHbDhw3Qz3Gw7c>1Ptg&Z#w{{GShkpwZg}sa z+@36E9MskvP8WhS8Cq;taVGSedZ8w=3l&#A4=*3hHNqeI9ALN+bTG4xD52!f+HJhM5Q)OSAXus<;4fxxrFi;QbLZaR-9#)rJj85Ty%M_Ut}T84m25QNYQDzLT+Er zhay2`iY~<*Gd(Kj-g?WBFVqAPwsvb!A|4<~PZFB`$)$r{ptl~|XA~P+SdQ zCKTJwm9_51ueutxov5@c(EU)S*FiDJnGxlj6k<0MxU(V!gdi(@#$Jg%T-68b!vSCWaVJ_Nyp~8HGCO$jx>wT!{OH z6~`i&{y~G7fHaSF)Zggzx$??)pMW^l(9r?vsrt=5WLcvXI>wVj4Qs1@k8xu;F9rd* zAK($p&<0y~@KEqGroOkd;|z7S$eHgmP+grDn0f!UHdP4dhK{xXz_gM>!J@Gwz)FH4 zgvgL@USo360oUIo5HS$L@vs2c5Rw$Z$rfd^y5cHi*0fIazt_<6b<$Oj|b zsAo&Ig7@EHYUmZp{%;uQt+fu6`0A&29`aTKswepFG;{a1xkykX;~;DO{k|`J zZ5)6t9x0SSCJtL1^znZ9x0-;@<5=cuSGCD;6?P(rTYQ%~&ilx#cAJDrC#g{nHxEYz>o7wGh%hX8f#}My1baKxE-*2^BDBGjwK3RZ0-cBfK z(zJ80;hd-yHRB{_7owO7L7`17OA|NxVJ!SDDY7e@2@l(jW`9nYXt&I~X*=+-Ff=QV ziHAhSm>!q%tIoh?-K-!~g|glmvEQjH@|H+Mctqc&vUlIn90HKlle_HAdo5X7)c_dm zEi7xTLT=4-PD>D8)?z7{j<&xA9jPeN6EUhbmskT`m<*DfDN-diM<#;O=BOO-q-{k+Hif37%?HiF*n@tRQNJC+EDhq>ol9e{H@*$Ue|!T5O$d!U0LDO$XK!ba@U~Ou{!#jXB)~S781oFGBv57MrvjP z6*N0Pz6MKz?-uIi-hV~TwnNl!)6YVKA(#g*AZnv*a~xKP)9;dk8M+fD!1A5kW;8}9 zhqql?hWR&Wa1K~SRP_gn4Jv5$@!fN>CyAAeEQGu3DhNqTT)eQ)X|5exjJ88Z4A_Ex zz+-C!?0}1T7l4!H;F^rv*@6~}hTjQwBJCl-sytHX%aKg_5(yR&Ak@V%$anBMYn0Jr zqOO!HDi|l^ChwEpn~~*+0NY)HkLQ4_R=AUcDiWk1Sei5s&EQ$~lVx`6dglwHQ?k=y z!0Vqvu+}_}?>}HXiE#X!^`r>7vV%kH0)Es+Ah8UzU8O3CPJDEVcFm<-ZX9N4f$uVM z*euDUk}PdZ5rO22&E2F_kZE%jK8@E&n2NhJv=JvBO9ZALV)+?#3c|%~07?>2#5;CJ z9tt!5B&!Gh#=;s#SI1L}O-8D(5^weYYXQJ>C>dYiutl?v)9PBfqdSI6whs7~6li|Q zlVywu`3j;@#M=U+3imS9cE)DVfxBO?&unDyK4OEZkZE}2>1C7(UORT(qT*C&#^ zj$T>)F<{@49FX&CO%F9eFAyk~}C12Lu zl`#6tG%;wlQZ{{4^hc7M#FogDcl#o@9fx;VzU>$h3XO`lNc}i8!5QPRqntx`jDa6Q za7@AMJEs8J-k?QpiA#|3YkzwNN`r0Tl~hS-kmv@YbXKkk5zBX~;ll^wq^OY{d2IPz zuH_hyB9XlZCCp-@`Yrms_XFcoI=;@$vlY?~YIdLzB!!1d*DIl~nl6V}h8Lj%?cr6h zk_E;ggd%K)^R68#+NDX8Rm%_cL!eiXr{Qy$osF7oW(bF9CH`%4*bYJPeMceWfS*Do zias#*hMYpBqHGsuO?@w9Mh5K|0q;Wt7OfsHw}I)%Qn7>~s6vm^$4Qcu^62;Z?ofwL zt3CjJz;GM6P$(+GI8X8-wKkt{lxGBmTQ-z?_Dnf^`H`?BkK$_<^cLZRA=97lg~Acm z5j;bKtcIPzl2ypUgM^WO-AEF%s6Vhtxp`&YN}3!Z!RK&UexZOe+W!}@ zJn|jg9gYmj7qeXz>nOxU1}7-fi9MR?jxJq8oR*#(8io+$z?kq`r0F}Aq%3jojs;i< zKp+Y=;cK3`GED#x>GVZ(OykS$4gxFV0W#bb)|)lZE4ztLMDLXHkD!znK}ZQ)X1c74 zbC>UYsHMeYeinx*#7Jrk?nkKka6EWw4xgYvUk zKQ8BJWB$iE*xsdaYlgGp{*iC(e!y4bqKUZ0Ya_U$-9hB6oU2GFA+EJ$G2dqTK$*ke z3KRc+Jyzm!3fu2wc(OHD!usQRE#PFhL zp=`dQ&f^Ik&#`zx9v}xTMZlQSL@TbI@f_&+j@bq`d4)NOPxQ zj8&$Fl_dro#P(y+z98;Kwo#yNO;hadqZS)ctnOOVk?3+ z1CSd{AFY+MS%R~9{O%oAO(Ka@fesyf5^yC9v8*m1SoT{KS8=Ya?!hKP7{QRel46fC zc6RRze5k}Vc|^W`Au{%_uuGn_IOYc=gJ%iVV8OFRv)chEou*qX)S&vM9$~->Dy7Nh zyZ7lTZbFwIf<>}m7*U5=y`?pB^Xd1&vT9bHjw3=XM}Zh@`zg~S66-qeL0d*WPkdo! z!KnQ7T+-bBsoZhluR`G@L1gC{n2)aYNJxi)J%>EqPe(l6dM;S(J-pF)Zt+_Wn&pu3 z!C+NRyZYy$z_viRzt}%`7FkvF?s$PhldTH|IQ#rpz!)8Y7#sI(O8dd6h4CO*6yAD# z^lX<|!Cr)gwcPNvp;+fXQC`C9&$;e*Smhc4V4K1&oX2x!T9jqYNtNkaeWkWa{_Q$ z>X+z(7{Q2j_>~BM2;V?_XDwFcb-@eU>0{ad+&`w zEj0@|0?Op5m$;y(GHGU)Kh3(!7iv_hLAFrJrBSjl=8~$HjZ+4LJ30Q=aaOOqyTiKe*6`LxElh6*OH(#wQN_YPnB*gIknSePQ0QP_ z`%j@x)E4A+r)!-W_kv$VdQ};>YAgV+A#6nL4gwuA#w-ZICM{SKWh~<|>C#XyEFs&m zJ&}uTNi(^Yv`|A!c<>SK3k>Ev0S8u;|o)?ADV5p)g2w zi3JR9ZAgd4GFGmpA#ph$6aEIqHH3{xY)!JPGoQP6Er;l1r&L>NWG!T;n69UX>juKN zs|l0gZ=R&WuZUDUJY{jz#*{kJq;-ha-9Oe4aiN59fJ0N*-Rbaao6{5@FR&M$t%H8 zhbn~FlT!_+651tdGnNcr1%VbAmPO`J!O|Vm;t?a8_Y9!l3pV zfx8y`EVH==mfd1ETsw&vqCk-WBG%0ZZhauw_vd5Ne(Mnj4-68HM4?!CB9%N2l5V4# zGRY;JtLOvrf?r1^R7;y|%U0Mg37;P87=eOE=VFf4Nh%2E09W|hpgL6RDK-;XWEsCy z25lydUn-Ew?TFK*EHd$(3R~>iX!Tz@syWtVS?Voi zJvT!dmk0SlJ1NRmTk-toBG5Oygd(eG*Ey?FUuZE>O=xbEr25K{1Ls+{*+y zdz?0Ydq2yxj%wLCqdLz19Zq*NK2wD?PmDB?T=6 zMYcVv2y}^W$oVW9#rYPFonanNcZhq5gAF9E#@FkdPv_dha6G-&7&^ShMk1 z8@Hn?Orz$2K3t(kjlM2o$Rs0%-R%*I)!tZ*S<*?)lAO_jwO=j@4{#0w&`ihx6u_`3 zE(eBQ+mrkk5{gQ%G#vs_xD{FSDg_X+s!ZlL9_RB2>{jE!9BSO_?wGf7Qdrt#AwaoJ zCWUMsbma%%>?$?d-d|3h&B=<>+UD(Pd4l#_&K57rxJ%c26V_uR>0b%H9Zbe0V?7-I zt&VBg^hNUdcA$z*YuJwUb4_QkdorLaK=PUIc}(>EdT*>4gE`o#)|DR>^k4@3ZPjQU z5b>EaljmaM_z!fV3S0)i{c5tB=x`*t=zej+Vm2b2VZ};b;=(U0spguaG1uuBiUq{7 z(i#;(f0p7ShT;f{O~F#Rmnbu>s^%QFYhm0YiYm!?|gOslXv8HB2V_ZYM zD(9tf0dV)bkFLpid$vXPv#3SK%?`^A@CanXtjg1w!NWjMF0We=Kg~$M-32Hn!%?MD zO|mA;e5Sz%N$J*rWk?6!;*I;@FsXPcXbU0}-fJxoj>e4#gJky^Irb#Uu?tpDrK$Ly z5~|5|9p9Ht!b;+|epMfkitR7wkcjc%;NUPTGVsvV(Nh=lOMrqkmb_}on>ebjYXW-z zA6aPed{zM(7mMzXb3ePMc=QyD`5J1)Z0P-3(?DE1jamR}B(SfHOK!(xJ3-j&c;0rp zFszeXt)de_j=w{1Q%^;p%+LoAk`3^3787m|caJtX{IrxwL&5d)>gBP9!| z8#l2|tao#?Bio1d<~lurg4*Fa++X?g-?CMZk5tgM*J^DcW_N*U$L2yp-@(#5jykR5 zS!~;&hYjhM`!lF2Sj-(^0f+R=Z>xW+c><2H#46>E9|tgPCPq?O&4)e`MWJv+*lXqD z3gG9v{Jak9YTAFdqvCU97AeC3YZ61|Yl@L;m`AL5RAQ48`OW>FTyIY6ehV_ zg&Q%^h=JVAakVK?Qguj)H$vDF1@;tm?PUQ{zQrLSVPqIhali@ zAX6peR4Q-d7*ELUX;U8bCy>$ia?zulTV#)p1qMV zuhjhpFGe^+zT>DQcAFb9+TkY8l zs|_&;9mNY1a|isH)W)tl*P7lys~<@t!UFKWbJ{NkSBv^#Mt~h|agI|&p7EYNaAra+ zV%10oe`r`jj3no9JNMP^4ll#}UUuvR#352e3g&9el`9XIM5VB}_DdXc`%tSi<2XHR ztaDY)yDwUxNdC54uG{-Xk~8&GPqosA;{?F$;S1fR*@4vBrbs$(zhP=B_P0ewm+_sa z4p6VUaHq;S=`bSVdN^H=SqSXh)QhUR^_t`uP^|3X_Px2&H9x zqUyiV?5{a@a9__vXNJe~jbL^mHabuVcG3 z3A~x$UR4Rd#y$-ZBqs-=PUp6=#Y|c)wVmQY3YUe;ecA{>5sw+3`FWQL&(&bqKis+epvAR*AA8 zU@@*@#Rl8S?iTJRSIMdgwosm5Scw@%_V_%lpv1Cf11~5_D&^xdiWpk|ZC4};iU7)> zm*RRw!J^<7u6$jna~Nb}$T>F2&W!r;85gdIUmnbtoi24lZK|a-I-Ru-JZBEzn<|~N7+Q84W3>1q?oO|kv}JVV8)N`4q>=zLnsW+4B!{m~HH zc}mNk{cIB~4&om5j=Pl;=cPRD@?U;*FT*KH^4?D{=lF6CTOw-U@ZF-;AjAXpr~GtJ z(f7kc;AB>Z9`MCn*+#34XXf?uQMY@?Cj3IQRtkX`qRD*D<_Mus+OK!U5s zzz1H)YDaFn4$WMGcxVPqepR&Xq((drpHn9?hAerH=P@ag@eC0I>XnlI6cOsNl{25$ z5B3>Wx1ohqN*BYFVxcwrHIR5imw#t>w=j*cId;IJCK7b;K(i!!NS^Fqj{KYJ&ULlA zg`C_KTf;D3AEOI>d;9*9aA(dP{$DA@mAo|>HyJ5P>ij^g90%aUsO?)R_8`{q4#w{M z->>@3dU*Plb#zXIHx7h1Oi5z)65LRBt$lcSg#4-MFg0kn;X4HUIcPeB*n~Z_6p^8P zHp`!-G@z(5_oG>jLN1948`y^{HyWRZu7`rWDbwA4crP+6AKBM@G}JCoN&9yxfZeiF z*BuuS)23WsB6_KE2;z8tBfziTddjG!@EE~Qqh{^yyNtox8m&3haP1 zv*M2}*2u?4=y++$77p$Qlj(ArOO3vV&<=_9^jBc>8*c% zIQx%KV?8=-)I4haYYcR;zUpCBUX|cORn!Zs{+s3%$y3oGR~(GSuG|zd?kM63 zVTBz+Gr2q{o0$Fy7hwv4@&}CiTN;U&DW}CIK3!w#X!r6H4I%rHBFuxZ)t*?#yPRwQWR&sEe1fGUm5-m{J>4iZ!O<@6m zyA#on33<>k)bX06s(PsJK7Daw3`6|@1_4=&Prxr_qY&`5l1khw`;;!dHc!Z?b!u$r zQg;A9aXAKQy0v)Z*$kzF;)Jr*P|d+)U}#OX^j^xy$wJrSPtPbN*Wz~dP8Dogri|#R zI#3;i{E}L!+{#Go$CbmDCo@V&&bUsO>lKE8PWyKWem*|@p4W!VTO2wRr}j$vl5klC zk*RLMm#xt(WMC+=;q0O#-kYjLHb&Qtz%$2dOvhX@mAp>6@!?G`5VTr}=6yRO?ej(wfu_{UhK0*^^$HTPbiltgm|8U{ zwV$s*$D{60i7LpGuBACl*MFm(Sm$6>Bli3}h?{K%GK%AXr3SphewgdouF7M@I$UjJ zxHyfaoBegD}h`j%zffTprV)BlE?Yypk%Y zWQa7nhfUcDRPo2W3}eP@I@4Y93af?qcnx2*ILLX`0EM%PZA!$YDuK>>=09Jx0t-Q( zBm-F+KjGe-C8$oc8H+c|E9UPB@>>J-Cs;MReOe%baZGnq5+Mg|EGlG#tIyihY-`=? zob?oGwjEzuiKd*RT1u~UAPY!K!)u2Q`;>y(NLfmuA8jHKi&=}1*9}(NcE=`F>tg>f zc)Wm6)^2Vg`GrK?5lKk_^#b$DJ{Zp%2@6TjRAsSn-CmD%$mk0-q?DRvv;lj)E zuPGLXM;3`248@%kqrc`ECXh}i2SG1iUp3fRWOhX`6Nsf6MAxvXo;{APDaqL+f`Q!8 z5_(NG=;>dLt-$D=QHl{0&Rm;5)Ce957YI-0@>|b^vpA!K71e={ zruqRYue>7ECMAl_v3(Q?0$|)iv>09>G&68~^ym_NJRcfKMsCaGGyV2B8yHe&r?Xoip zQ>f=VBVsef?hMMeQrK&hb;c`NEPY0I{gspEWH`7@$bz~RNE!0XaiIRSzFRRZmo!g# z*Vfq}b1&A6bwfsW=vYu@rrRf3ig7^Y9EH>{GCalzQAXbk7ak_LPmirzqxI7h476+p z#2{eO6d3meR_NCY#e~c{?b;vrpg`4p+JE7w zD74QKw7p!;mns)tEb>W6Cy9)`sqv?$~xywfakACLQ_ z(+ea7)ob=dF&eF>hRXBDMY{cc3gvTe+*k+vKhZ$a=lg)6z%W6dYYQ1S^H_Ya z_kuiAKDRS*IV*(by`|!warwJC#J@Z%TYM`Vw-NRS{%4CXuj-NO30DdO;W*z!Bizuyrzg440B*rpR0Nbh$Chir)>sZ$5g2S5aqm8baHaF`vpMyLe_e(S- zwScy73-mT=e9V|o*>!Kv_h4y~fH1#Pf@}$D_RobK;xFE)H~T)?)BO1RkA&4KMScLR1FF(TpRnIz>^A)NN-@|#6HD3vJCO?f7ftJtT7Cz_wItglGmHGnEP`3@+!jHgaCSDl;U$`>4Ac7E@ zVl;kgAdrO^j_j!S$zj9lG+LpQEQk2^Gud;teRRK|O+ryJdEajQM{zqlb7>B)g4+8$ zK$&WkWfbD{vPo{QDvU*~v(i+LNl7m1M=~{?aw0!xKDp3<64{~$;m`lQSk?kdNye_o zuw;s#|6dEBAbPG;FE8{%GPq=+O2O}-z$-?>Ajwm|Wj`NF|JuT{K90jI%By>nIS(|y z!8dn!4NvzRtT!Jvg=DUc#-Wp&P@B9-w%M_vS%|(+B9h|Q6SkT0E3DLSVR}i`SPW9y z+fQEdxIIKaw8msMu=F}#iy{;M5snir6!2Uy#U3!-F~zR)+m1?lZi1P=UJ)4xToJL6 zsSx~5bJ4I;%e#Hxoh8y9K}~oS0vj=thnlw=`&C>ksb8|dw;%P^2At%E5?L3}zkCO%e41xs4gu^IxxAnxLkc9Tt9Sp7JXy zWdEcd#1mnt-0%$Z?fX22dfRvOzgWc(-5|fyi_I)J6*9b~U=^;-ORV&Zu!0jT2`c8w z5%DDEf`=h*19^l?iuR`)us~BikA%$KkL0)S3Hco`Nq=d(?w}dF_CJxC9G*6wa2~o6 zwFS?hPkwz25+(oCjHzZ-!@S#Pbe}Hx*uIA^bBch10?GtC%ByKrx^vF_=07UaC`#(i zT6keEG&N22a&E*g0uonnUcp5A4A+O76#@>2TIXHHJnS`&0Ji4vzI`e?q1U=~djr?+ z6cNaBb*AkC4)U0#LNbK&%krj3vHT8I7L{lCk&zx!w1RvI@5OiRbP_s9(MC8bZPh{`mD=Fs zlqyWXR3N)8s;PHoofaoS_rsXDIXUJp@N3B&85uQ#aUhx4| zk3&YU{6(W6A|gKb-W=yn+fDQ3<^Y2D8$RP&66cLhJmJ?%s82K^5`}=PJ-m@cQ6(UC zu3lZ*|FREGr`xP8;KJBWVx4z9mlyLnWTV+})eHx9+JYcwu~)}y;ghECUTfqXc&Jj5 z%UV3V)%CFPoB968vZ4F!d1pIvwb-&MeL63s>#GL@(c|5Ey++PTSLnIhlpx%hO0$bz z{JJJ`z!$z>i+8-!3GuS}^Fh53wx7o2YLucP*xAcwvxla3U>p?f>u#ZsjOU zy36we@qLft|H&{Ka@v|c|C>`rV-$A^D*JfcO{k3c8$F;A4_f!T7rA>-_t99OpE#-Q zi3jUxWJ>I+N3`?59YcXsCMD)7JUp{{&zHo{KCreRZQu|>~hXLhK%p*JJZXEYv} zjF#qWjW+{l%Rr{zDgvei(V$&Dj{k-Tg}T0$L`sDehV~K4&dpjRUZ?~9sCFaOk{I|^SstZVw!(;x>QAX*l)yXRqOAjX&Vo!Mhj&B zY4(S}P~0ESlFw@4KJV#)WbXQjX(#f5-Jo8s1mq4B;Cg?e(GCOxiBBhF2nGD`frWxa8Myp>A>q8^!t@i#xT*^npO9KL{h+Lwcy*v2NHiZw z{b|lP1v@949S`o@GGkkmkudSqlF9Lab&z!0=%l;+y`)nSTaPvItLhrweAy|DVS2Ti z@5M%JmE}Y&;Ahav?NJC(?C(0|CQSh%IOD)Os-a_T5(nMgg6p2QcmBsW1Sfo`F&!?H zlP1dq0;GUL%wecpJ;IUH#p@mU{Knas;?tDCFX)8qm*`MyI;Cu!Zclh}0Vzeu1qeO9 zcl5d2A+x5DvGr;UO|wLW&Ek4EC$r4jq+h=ek_bjqKtC0?8K&&~O)S$QG*#syRMzX^ z^dPxi{gEyJa1=`-H1+P%R!u!9GX-J5;0QE*r?gz z&d9q&0*Lwx!I3Vh4@kZ=#&Fu4VVZ1R-)3ndQBo#+&PnDtJhAK3u3wJ>wtV6J&fdtS;APqT z7rn$Qw}98!$m?E`%PJ+2y=O6GF@M>un~;vQUROgUBPohhEg9Om@W(ZOSQbW99KztY zJ66R^)IVo8g}}Lx?L;)Vj*2(lKJQX9uCnkq+L26?KSgR2W+`gT4gozUoXk2Iz;>Yw+W$JO&DAcy$Ym7pP>8dOn<>5{`ELB5g1-Ji!-H%!7Ad}fksR|aUri9 zo)xni-vV)wv!2q1=ejz01e4gmBzj*@-jV zMTyx*E<%k6LIXqyvNfR`;D?amQO~`iY{i~f`%e6}kHH`H->gt2YX}QzjVQlxYex4wwpd7^D6N!v)b{ldPsTXqAr?7jgnIkQxcc+` zM7B!k0vN~B#kB2?noT~kWwJLjcKerO2?agrjRW<*kf1O{96`mHJO}q@E|s$kxiftC z$_&z26a(IAh{hgEwMdh7%Dz@9nefo8v22qRf7HJMuXIF_ zt(U?{nS>)Sg~hI731$i*=FqL*zNIOlU9t!Aj1h!P65&ch`pRJo?9 zaPowIV(B=`^#TGR{0Xqoo(|`&Z}%VG#-SR%Jo4%_|9FTEJRaw&+SGmQ`5ce2mWYaOEK0LjATf_fxSbf!J3az8dN)uh?awM317p3t zP}7}-aZP~tAU8aNEcUQ3rIo~I>nyl|0kwqbQx(C?(uUR_L5q*L(dKK@W|^02vI;?s zSY=&W`1zfx_`(Xbk;YaU13O3DCJQv&CcCF{OO%y+LnIeBowKU5)pepps}!J@cVZEID>8p)f@JF#s5hy2qGNAuJdux0q}&7Ww?H#I}w&w!4QSj z8LZ8MQP31()W8w0C0cIuC66IQS-qux4A3ovXetJ5j7oq43hp6G$H3r|yfQf-zC%n1 zWW~c&I3o7wT!!aR#;hiuxrxH4Xk9?K5BuXWLoKOSjX|?N+>n&xIv%K8>QtmwwWIzB zzC0LqeTb(C!HMk|a&p@h%|ubLlqiGr41n@nqh9fi-ocojMD2_q)rVmp^9G6kDq<0 z7D*doaMkYMw0XOw7FY>l_w8=NWbX2Ua8}|8R-FTw%;r*LKCQEkKi~6MO$f&?ZxVxX zk(9RN``7Z7O*hE#cKdO|9YSTDJn2beIysD)e@x=w0LR~Q9v>Nds^vfqNCK{W?1(Lw4Ac;(Ah-ST z5to&$v1rJCIts2*8;uefG8+-bU{q^I4JnfLCWhdmWMd$8dc=Hregu1lC>{#*%=OAE zVPb)Bh${xCl9~gbH{0i%YXc@wG=tGy^^dOV99FNMh|J@Bv6IdhVkz(XE;ry4mBK;0 zwRwL|yCx$L?1x@L%j^rGi(+{3;3#sf{A_meFlUve<}0i~qv8zm34)@WS+wcZym z*veT>*5rL`f|2IRv%b8s=0$21!UCRE*5=jcBl^=i(iR2bF)!~wSP+5K*Nx|y@oFg5 zpWhMq^}pW>0D*@w>f9G}{ly{(t|o~EW`YQgg_K!L zb8wx<4gH{S<&jinhxFIL%@H+DHqF$e46jy}X)QwUGI@*GSsq|UZ7_rvfK8eg`mEa+ zo~9rI3g&v-egQ=?^i#Zpn|nraNXu+W#C0)$qUg{6u|qZKrwUPoj|?i{*>UlRxxynX z7U=RcX2ek`AfD{01|qU5~NB~ zngkW3i-07dNG}qaGzIApT4;Cg^W6I{&(6&3Z|2`#^Q{-*m&gqc%Tf zA44d(fbtdL`t8R`_dI!Bz$wuz{hHWLnLU@|!036DgkHI8usVVL!mIzJX$rRhX32!Q z>Kjf0Wb*@G$Dy*%Pdo8@dkUF2gH{jSyT}$vMf4JieY}KEfnd+fF`1n7_o(Y;cY?iF zkh8=Kc&AzA?hB+tJ>UMRBRzR+ zTtBycy~hG@l6hz(9}*0^hB`-0o)A-~zXVW4&(b@mYRRs#p<5F4}abe_!gKRmfKN?~);M_O^N#i!0b^x<8G` z^Yh*A_MNK`USaF7UFc~q>C~Gk*@5&N{JENrSBag1krQ(%^9v4eM9N>;8oza%nuM8p z?71&hq9IgFa;vc|3X&;JcaN9VszmNDs^~bBS3FtfkvXgF!-d}qGRPA4qMB>I)+)ng zW*6d1nO4ZfEV&`4DouOjawMB!*zM$FpVM0h@o~+z;~vtFzd*Z#AKSHHQS}}S=9QK& z&BD^8KEzbnc6d~vKD31e3;2$2v|u{67kg?2+VGYiIHg{@sJH_;F7Uy_MI0hS?Rmn9 zFk_)kbNF*cwtH9|p?l2%yGEsR=Re0YY%gMOZzsldMO1CdACNLuh2kniqU33{>3m~8 z4JYNO=Y%>p&dWQGa#SyWVXCdRjt%tx%)7Tmd;D2b_it-?Zi zYX|8fgaNDs?M*05NQm`sREj`^X%J*O^LfEF)#trddpmsvh}c=`N_SzvE?C)I2l}mzSZuB)wJw29L$|ZPf%~vx%x>H< zHaP`p2K+>CU2YVO@GSIU&K735OVRIjqXYeV<#&j)*3AC?Y`bfsEd#GuH_pUMLH2Sv&T{NZG83x&Yb0c&%fsG>_ANWv#Z#Z8kBx&Ppa@gxxEDFt!#~o7xjK zbZJ5Q#bZmO^1;t%=ITV5!}5*9dD7NjJcYxoPgw*W#V|!Ia>kKt*g5C4kc2oN2E0{0 zr^Om%X$CJxNaV)?CwY7UrH(fxc2Xrx*J6QN&Qv}Kn^2g`R6}8Vp&(sK0~bZcvDBnk zIoZ<)+DU^!Vp(+ecrbvuwa+o`gY&sjn>c>1`K9NK9hYiejDTL-&VIuuS6!!xal9vheL?S)>g)Sf7Y+7v4nj z4R3tlnD2y2-O625C20TDdWUXqsM{KONq62WH84eS+5ZLP7j_I%)@ZT!^e zwP@+_xKLn<9f7#^Q0*aL(e=x{`c)7Qb7?tYRR>T9OETj($}Q%PHomkQ#dvtH*JB;F zJ!=b=U|f)-pG8U(o=w9ijLO_=#M?p{IJ_G6cZukP_Iu6&{YbZ8<=s=AJeYO|=flB< zwic7TD|3o{>fsoj#0jT6oX`5W5*}pry;(0@*&2;FQFc?#UAUsxDX(*e7 z@e?QWi15IRY1ONnhLKeuH2Ay!&@Y*DUj)l)tdvx}E^)z)z5S_9bhRe7N;7&$YR0J1 z&}`w&%PA+9vBs(0y9Vs!!A4^wur(0s!r^J&&%Mg33E;m#f^O8Kn0YW)nO}-=_aSPh z%mo3=M%&T=W|dAcO|0Owlhh1z{`j98O2sC4#QS7-EaizuXP};l^>s}r zCuwxRAwqYJ>Am2k%;JZk_B49Bp1B6ua^G|mij^Sl#klmR1fpVp18@Khm|Vu|iuo~$ zqEi|cn&EK@dlt96HY?H1s;WM#+TVvHel&RV-~LH^luNEz#n136&=K{m(+_|JhS^d0 zIp)XAy1r16`jAn;f#6Km(M>|XLb~f(sV*=uZj%LDX=}&X%XcdMv>3ep9Q=jIa@`y< zgwHy>P-TW)B~=sbb}9_CXfe19za@4pjYn)osa<6sP@01j*vUO4d!ux--JhAO7@@3|kkI}z1X~Yqr zfo>`GlAOqP;n9RYyaDWOD`u+xVSI!wuVZ^%>VB0VbqajPVyMHCDC&gCpBdu(uPoOO zXZse{ixj3`gh)#C!%HVJ4B$S?VYC!A)x2P8bn~5&qS5@_S*-9|)Q*^|B)lVn4X)iD z8DHW~LJ;aG+D8)M>x&wBbU4*iSsoi;m5$7Na1U^4$osAf1W`LB_C~^d)`UJ@FQ9DW zGL6tD7FQSs8R(aA^LD+*P}iodx6N^basn$FJea1D)2?iv<#!gq+dUS$o_UkRVil%~ zxCPa9`I(DYAi7zV0X2TG17Cnnw7H?QC2zsOAS+65g+T5#WV!%!B+UjvKKv7hI{>n1 zFsutLxSS3Ujfy7nxA@Yz08WyKOv&mJF>!k$-emC+vUk?K@N+w2A<%&~A z04B*x5r)>1WdMLjbui@qpZmAK>?vr+$UqWh)Bs4Di1J(0GX-=cy8nSjWwqHF%mZ9{5cp$+kc8aXp?Vr8$!q|}>DfkOWe zEG5yDl3nm_iFA^80WBsGacEJT6e!wCr`ORp2AmH}w9taI2uhV5OWq;ph=KhhNPc%j z(VHLhfnIRmjr`9)I1dhp54qdbNgXYD7qpEYl_B#6MgH5Hr zDX<_sGXR^K$lu}1 { - if (event.request.mode === 'navigate') { - event.respondWith((async () => { - try { - const preloadResp = await event.preloadResponse; +// --- GESTION DES NOTIFICATIONS PUSH (Réception) --- +self.addEventListener('push', (event) => { + if (event.data) { + // Assurez-vous que le payload JSON envoyé par votre serveur contient + // 'title', 'message' et 'link'. + const data = event.data.json(); - if (preloadResp) { - return preloadResp; - } + const title = data.title || 'Nouvelle Notification'; + const message = data.message || 'Contenu mis à jour.'; + const link = data.link || '/'; // Lien par défaut vers la racine - const networkResp = await fetch(event.request); - return networkResp; - } catch (error) { - - const cache = await caches.open(CACHE); - const cachedResp = await cache.match(offlineFallbackPage); - return cachedResp; + const options = { + body: message, + // PATH MIS À JOUR ICI + icon: data.icon || '/assets/notif.png', + data: { + link: link // On stocke le lien pour le réutiliser au clic } - })()); + }; + + // Affiche la notification + event.waitUntil( + self.registration.showNotification(title, options) + ); } }); + +// --- GESTION DES CLICS SUR LA NOTIFICATION --- +self.addEventListener('notificationclick', (event) => { + // Récupère le lien stocké dans la notification + const urlToOpen = event.notification.data.link || '/'; + + // Ferme la notification après le clic + event.notification.close(); + + // Ouvre l'URL associée, soit dans un onglet existant, soit dans un nouvel onglet + event.waitUntil( + clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { + + // Tente de trouver un client existant pour naviguer + for (const client of clientList) { + if (client.url.endsWith(urlToOpen) && 'focus' in client) { + return client.focus(); + } + } + + // Sinon, ouvre une nouvelle fenêtre/onglet + return clients.openWindow(urlToOpen); + }) + ); +}); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 1a1d296..20ba4a0 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -4,8 +4,10 @@ namespace App\Controller; use App\Entity\Account; use App\Entity\AccountResetPasswordRequest; +use App\Entity\Sub; use App\Form\RequestPasswordConfirmType; use App\Form\RequestPasswordRequestType; +use App\Repository\SubRepository; use App\Service\ResetPassword\Event\ResetPasswordConfirmEvent; use App\Service\ResetPassword\Event\ResetPasswordEvent; use Doctrine\ORM\EntityManagerInterface; @@ -23,6 +25,22 @@ use Twig\Environment; class SecurityController extends AbstractController { + #[Route(path: '/notificationSub', name: 'app_notificationSub', options: ['sitemap' => false], methods: ['POST'])] + public function notificationSub(Request $request,SubRepository $subRepository,EntityManagerInterface $entityManager): Response + { + $content = json_decode($request->getContent(),true); + + $sub = $subRepository->findOneBy(['subId'=>$content['subscription']['endpoint']]); + if(!$sub instanceof Sub){ + $sub = new Sub(); + $sub->setSubId($content['subscription']['endpoint']); + $sub->setSubcriber($content); + $entityManager->persist($sub); + } + $entityManager->flush(); + return $this->json([]); + } + #[Route(path: '/connexion', name: 'app_login', options: ['sitemap' => false], methods: ['GET','POST'])] public function login(AuthenticationUtils $authenticationUtils): Response { diff --git a/src/Entity/Sub.php b/src/Entity/Sub.php new file mode 100644 index 0000000..5fdebab --- /dev/null +++ b/src/Entity/Sub.php @@ -0,0 +1,51 @@ +id; + } + + public function getSubcriber(): array + { + return $this->subcriber; + } + + public function setSubcriber(array $subcriber): static + { + $this->subcriber = $subcriber; + + return $this; + } + + public function getSubId(): ?string + { + return $this->subId; + } + + public function setSubId(?string $subId): static + { + $this->subId = $subId; + + return $this; + } +} diff --git a/src/Repository/SubRepository.php b/src/Repository/SubRepository.php new file mode 100644 index 0000000..d232455 --- /dev/null +++ b/src/Repository/SubRepository.php @@ -0,0 +1,43 @@ + + */ +class SubRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Sub::class); + } + + // /** + // * @return Sub[] Returns an array of Sub objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('s') + // ->andWhere('s.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('s.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Sub + // { + // return $this->createQueryBuilder('s') + // ->andWhere('s.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +}