PageSourceSearch

https://www.83-629.fr/api/pwa-bootstrap.js

js 83-629.fr collected 2026-10-02 11:58:48 UTC 12,641 bytes, 237 lines download raw bytes

1/* 83-629.fr PWA bootstrap - 83629-pwa-v21-2026-07-22 */
2(function(){
3  'use strict';
4  if (window.__m83629PwaBootstrapped) return;
5  window.__m83629PwaBootstrapped = true;
6
7  if ('serviceWorker' in navigator) {
8    window.addEventListener('load', function() {
9      navigator.serviceWorker.register('/sw.js', { scope: '/' }).then(function(reg) {
10        try { reg.update(); } catch (e) {}
11      }).catch(function() { /* silencieux */ });
12    });
13  }
14
15  // Audit PWA 2026-06 : tracker l'instant precis d'installation (listener inexistant
16  // jusqu'ici) + basculer install_status sur 'app' des l'install accepte.
17  window.addEventListener('appinstalled', function() {
18    gtag_safe('pwa_app_installed', { source: 'appinstalled' });
19    try { if (window.gtag) window.gtag('set', 'user_properties', { install_status: 'app' }); } catch (e) {}
20    try { if (window.clarity) window.clarity('set', 'install_status', 'app'); } catch (e) {}
21    snooze();
22  });
23
24  // --- Web Push (Track C audit) : abonnement cible. Le cohorte installe est loyal
25  //     (92% returning) et le produit est une veille JORF quotidienne -> levier de
26  //     re-engagement #1. On ne PROPOSE l'opt-in qu'aux utilisateurs en mode app ;
27  //     pour les autres deja autorises (granted), on resynchronise silencieusement.
28  function urlB64ToU8(s) {
29    s = (s + '='.repeat((4 - s.length % 4) % 4)).replace(/-/g, '+').replace(/_/g, '/');
30    var raw = atob(s), out = new Uint8Array(raw.length);
31    for (var i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
32    return out;
33  }
34  function isStandaloneNow() {
35    return (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches)
36      || window.navigator.standalone === true
37      || (document.referrer || '').indexOf('android-app://') === 0;
38  }
39  function postSub(sub) {
40    try {
41      fetch('/api/push/subscribe', {
42        method: 'POST', headers: { 'Content-Type': 'application/json' }, keepalive: true,
43        body: JSON.stringify({ subscription: sub.toJSON ? sub.toJSON() : sub, install_status: isStandaloneNow() ? 'app' : 'browser' })
44      }).catch(function(){});
45    } catch (e) {}
46  }
47  function subscribePush(reg) {
48    try {
49      reg.pushManager.getSubscription().then(function(sub) {
50        if (sub) { postSub(sub); return; }
51        reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlB64ToU8(window.__b83Vapid) })
52          .then(postSub).catch(function(){});
53      }).catch(function(){});
54    } catch (e) {}
55  }
56  function pushOptIn() {
57    if (!('serviceWorker' in navigator) || !('PushManager' in window) || !window.__b83Vapid) return;
58    if (typeof Notification === 'undefined') return;
59    navigator.serviceWorker.ready.then(function(reg) {
60      if (Notification.permission === 'granted') { subscribePush(reg); return; }
61      if (Notification.permission === 'denied') return;
62      if (!isStandaloneNow()) return;
63      var PKEY = 'm83629-push-snooze';
64      try { var ps = parseInt(localStorage.getItem(PKEY) || '0', 10); if (ps && Date.now() < ps) return; } catch (e) {}
65      var bar = document.createElement('div');
66      bar.setAttribute('role', 'dialog');
67      bar.style.cssText = 'position:fixed;left:12px;right:12px;bottom:12px;max-width:520px;margin:0 auto;background:#1e3a8a;color:#fff;border-radius:12px;padding:12px 14px;box-shadow:0 12px 32px rgba(15,30,80,.28);font:14px -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;z-index:99991;display:flex;align-items:center;gap:10px';
68      bar.innerHTML = '<span style="flex:1;line-height:1.35">\ud83d\udd14 Activer les alertes veille JORF\u202f? Un signal d\u00e8s qu\u2019un texte du recueil change.</span>'
69        + '<button type="button" class="ok" style="background:#fff;color:#1e3a8a;border:none;border-radius:8px;padding:9px 14px;font-weight:700;cursor:pointer">Activer</button>'
70        + '<button type="button" class="no" aria-label="Plus tard" style="background:none;border:none;color:#cdd7f5;font-size:22px;cursor:pointer;padding:0 4px">\u00d7</button>';
71      document.body.appendChild(bar);
72      function close(days) { bar.remove();
72 try { localStorage.setItem(PKEY, String(Date.now() + days * 86400000)); } catch (e) {} }
73      bar.querySelector('.no').addEventListener('click', function() { close(30); });
74      bar.querySelector('.ok').addEventListener('click', function() {
75        Notification.requestPermission().then(function(p) {
76          if (p === 'granted') { subscribePush(reg); gtag_safe('push_opt_in', { result: 'granted' }); }
77          else gtag_safe('push_opt_in', { result: p });
78          close(90);
79        });
80      });
81      gtag_safe('push_optin_shown', {});
82    }).catch(function(){});
83  }
84  pushOptIn();
85
86  var isStandalone = window.matchMedia('(display-mode: standalone)').matches
87    || window.navigator.standalone === true
88    || (document.referrer || '').indexOf('android-app://') === 0;
89  if (isStandalone) return;
90
91  // Audit PWA 2026-06 : ne pas montrer la banniere dans les navigateurs in-app (Facebook,
92  // Instagram, LinkedIn, TikTok...) ou l'installation / A2HS est impossible -> frustration.
93  if (/FBAN|FBAV|FB_IAB|Instagram|LinkedInApp|Twitter|Snapchat|Pinterest|musical_ly|TikTok|Line\/|GSA\//i.test(navigator.userAgent)) return;
94
95  // Snooze robuste : stocke une date d'EXPIRATION (localStorage + cookie de secours, car
96  // localStorage throw en navigation privee / contexte embarque -> 24 erreurs Clarity).
97  var SNOOZE_KEY = 'm83629-pwa-snoozed';
98  function snoozeUntil() {
99    var v = 0;
100    try { v = parseInt(localStorage.getItem(SNOOZE_KEY) || '0', 10) || 0; } catch (e) {}
101    if (!v) { var m = (document.cookie.match(/(?:^|;\s*)m83629ps=(\d+)/) || [])[1]; v = m ? parseInt(m, 10) : 0; }
102    return v;
103  }
104  if (Date.now() < snoozeUntil()) return;
105
106  // Audit PWA 2026-06 : ne pas solliciter des la 1re visite (fatigue : 2,5 impressions /
107  // utilisateur desktop). Montrer si visiteur connu OU >= 2 pages vues dans la session.
108  var __engaged = true;
109  try {
110    var __pv = (parseInt(sessionStorage.getItem('m83629-pv') || '0', 10) || 0) + 1;
111    sessionStorage.setItem('m83629-pv', String(__pv));
112    var __known = !!localStorage.getItem('m83629-seen');
113    localStorage.setItem('m83629-seen', '1');
114    __engaged = __known || __pv >= 2;
115  } catch (e) { __engaged = true; }
116  if (!__engaged) return;
117
118  var style = document.createElement('style');
119  style.textContent = [
120    '.m83629-pwa-ib{position:fixed;left:16px;right:16px;bottom:16px;max-width:520px;margin:0 auto;background:#fff;color:#0E1A2B;border:1px solid #e5e7eb;border-left:4px solid #0A1E3D;box-shadow:0 12px 32px rgba(15,30,80,.18);border-radius:12px;padding:14px 16px;font:14px -apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;z-index:99990;display:none}',
121    '.m83629-pwa-ib.show{display:flex;align-items:flex-start;gap:12px;animation:m83629PwaIn .25s ease-out}',
122    '@keyframes m83629PwaIn{from{transform:translateY(20px);opacity:0}to{transform:none;opacity:1}}',
123    '.m83629-pwa-ib .ico{flex:0 0 40px;width:40px;height:40px;border-radius:10px;background:#0A1E3D;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:14px;letter-spacing:1px}',
124    '.m83629-pwa-ib .txt{flex:1;line-height:1.4;min-width:0}',
125    '.m83629-pwa-ib .h{margin:0 0 2px;font-weight:700;font-size:14.5px;color:#0E1A2B}',
126    '.m83629-pwa-ib .d{margin:0;color:#475569;font-size:13px}',
127    '.m83629-pwa-ib .act{display:flex;gap:8px;flex-shrink:0;align-self:center}',
128    '.m83629-pwa-ib .b{background:#0A1E3D;color:#fff;border:none;border-radius:8px;padding:9px 14px;font-weight:700;font-size:13px;cursor:pointer}',
129    '.m83629-pwa-ib .b:hover{background:#14245a}',
130    '.m83629-pwa-ib .x{background:none;border:none;color:#94a3b8;font-size:22px;cursor:pointer;padding:0 4px;line-height:1}',
131    '.m83629-pwa-ib .x:hover{color:#475569}',
132    ':root[data-theme="dark"] .m83629-pwa-ib{background:#1e1e2e;color:#e8ecf6!important;border-color:#374151;border-left-color:#60a5fa}',
133    ':root[data-theme="dark"] .m83629-pwa-ib .h{color:#f1f5f9!important}',
134    ':root[data-theme="dark"] .m83629-pwa-ib .d{color:#94a3b8!important}',
135    ':root[data-theme="dark"] .m83629-pwa-ib .x{color:#94a3b8!important}',
136    ':root[data-theme="dark"] .m83629-pwa-ib .ico{background:#60a5fa;color:#0e1017!important}',
137    ':root[data-theme="dark"] .m83629-pwa-ib .b{background:#60a5fa;color:#0e1017!important}',
138    '@media(max-width:520px){.m83629-pwa-ib{flex-wrap:wrap}.m83629-pwa-ib .act{width:100%;justify-content:flex-end;margin-top:4px}}'
139  ].join('\n');
140  document.head.appendChild(style);
141
142  function snoozeFor(days) {
143    var until = Date.now() + days * 86400000;
144    try { localStorage.setItem(SNOOZE_KEY, String(until)); } catch (e) {}
145    try { document.cookie = 'm83629ps=' + until + '; Path=/; Max-Age=' + (days * 86400) + '; SameSite=Lax'; } catch (e) {}
146  }
147  // decision prise (fermeture / installation) : ne plus solliciter pendant 90 jours.
148  function snooze() { snoozeFor(90); }
149  function gtag_safe(name, params) {
150    if (typeof window.gtag_safe === 'function') window.gtag_safe(name, params);
151  }
152  // Audit PWA 2026-06 : ne pas concurrencer le bandeau de consentement Iubenda en bas
153  // d'écran (mobile). Tant qu'il est visible, on diffère la bannière d'install.
154  function consentPending() {
155    try {
156      var b = document.getElementById('iubenda-cs-banner');
157      return !!(b && b.offsetParent !== null);
158    } catch (e) { return false; }
159  }
160
161  function buildBanner(opts) {
162    var w = document.createElement('div');
163    w.className = 'm83629-pwa-ib';
164    w.setAttribute('role', 'dialog');
165    w.setAttribute('aria-label', 'Installer 83-629.fr');
166    w.innerHTML = ''
167      + '<div class="ico" aria-hidden="true">83</div>'
168      + '<div class="txt">'
169      +   '<p class="h">' + opts.title + '</p>'
170      +   '<p class="d">' + opts.desc + '</p>'
171      + '</div>'
172      + '<div class="act">'
173      +   (opts.actionLabel ? '<button class="b" type="button">' + opts.actionLabel + '</button>' : '')
174      +   '<button class="x" type="button" aria-label="Fermer">\u00d7</button>'
175      + '</div>';
176    document.body.appendChild(w);
177    requestAnimationFrame(function() { w.classList.add('show'); });
178    var bActive = w.querySelector('.b');
179    var bClose = w.querySelector('.x');
180    bClose.addEventListener('click', function() {
181      w.remove(); snooze();
182      gtag_safe('pwa_install_banner_dismissed', { source: opts.source || 'beforeinstallprompt' });
183    });
184    if (bActive && opts.onAction) bActive.addEventListener('click', function() { opts.onAction(w); });
185    gtag_safe('pwa_install_banner_seen', { source: opts.source || 'beforeinstallprompt' });
186    // Anti-fatigue : une fois vue, ne pas re-solliciter avant 14 j (snooze plein de 90 j
187    // si l'utilisateur ferme ou installe). Coupe les 2,5 impressions/utilisateur desktop.
188    snoozeFor(14);
189    return w;
190  }
191
192  var deferredPrompt = null;
193  window.addEventListener('beforeinstallprompt', function(e) {
194    e.preventDefault();
195    deferredPrompt = e;
196    var tries = 0;
197    function showWhenReady() {
198      if (consentPending() && tries++ < 12) { setTimeout(showWhenReady, 2000); return; }
199      buildBanner({
200        title: 'Installer l\u2019app 83-629.fr',
201        desc: 'Acc\u00e8s rapide au recueil, \u00e0 la veille et aux tests de veille, m\u00eame sans r\u00e9seau.',
202        actionLabel: 'Installer',
203        source: 'chromium',
204        onAction: function(w) {
205          if (!deferredPrompt) return;
206          deferredPrompt.prompt();
207          deferredPrompt.userChoice.then(function(choice) {
208            gtag_safe('pwa_install_prompt_result', { outcome: choice && choice.outcome });
209            deferredPrompt = null;
210            w.remove();
211            snooze();
212          });
213        }
214      });
215    }
216    setTimeout(showWhenReady, 3000);
217  });
218
219  var ua = navigator.userAgent;
220  var isiOS = /iPad|iPhone|iPod/.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
221  var isSafari = /Safari/.test(ua) && !/CriOS|FxiOS|Chrome|EdgiOS/.test(ua);
222  if (isiOS && isSafari) {
223    var iosTries = 0;
224    function showIosWhenReady() {
225      if (consentPending() && iosTries++ < 12) { setTimeout(showIosWhenReady, 2000); return; }
226      if (document.querySelector('.m83629-pwa-ib')) return;
227      buildBanner({
228        title: 'Ajouter 83-629.fr \u00e0 l\u2019\u00e9cran d\u2019accueil',
229        desc: 'Bouton Partager \u2192 \u00ab Sur l\u2019\u00e9cran d\u2019accueil \u00bb.',
230        source: 'ios-safari',
231        actionLabel: null,
232        onAction: null
233      });
234    }
235    setTimeout(showIosWhenReady, 5000);
236  }
237})();

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.