PageSourceSearch

https://thecookingrecipe.com/wp-content/plugins/snapiom-analytics/beacon.js?ver=1.8.4

js thecookingrecipe.com collected 2026-10-05 04:27:05 UTC 7,918 bytes, 188 lines download raw bytes

1/* Snapiom Analytics — beacon first-party (sortant uniquement).
2   Mesure : temps actif, scroll max, source, device, clics internes/sortants, quick-back.
3   Envoi d'UN résumé par page vue via sendBeacon (text/plain → pas de préflight CORS). */
4(function () {
5  if (!window.SnapiomAN || !SnapiomAN.pk) return;
6  var cfg = SnapiomAN;
7  var startTs = Date.now();
8  var activeMs = 0, lastTick = Date.now();
9  var visible = (document.visibilityState === 'visible');
10  var maxScroll = 0, internalClicks = 0, outboundClicks = 0, sent = false;
11
12  // --- Core Web Vitals (RUM) : mesures RÉELLES du chargement côté visiteur ---
13  // LCP = quand le plus gros élément (souvent l'image de recette) s'affiche ;
14  // CLS = stabilité visuelle ; INP = latence d'interaction ; FCP/TTFB = début.
15  // Collectés via PerformanceObserver (buffered) puis envoyés dans le MÊME beacon.
16  var vitals = { lcp: null, cls: null, inp: null, fcp: null, ttfb: null };
17  try {
18    var nav = performance.getEntriesByType && performance.getEntriesByType('navigation')[0];
19    if (nav && nav.responseStart) vitals.ttfb = Math.round(nav.responseStart);
20  } catch (e) {}
21  function vObserve(type, cb) {
22    try { var po = new PerformanceObserver(function (l) { cb(l.getEntries()); }); po.observe({ type: type, buffered: true }); return po; }
23    catch (e) { return null; }
24  }
25  vObserve('paint', function (es) { es.forEach(function (en) { if (en.name === 'first-contentful-paint') vitals.fcp = Math.round(en.startTime); }); });
26  vObserve('largest-contentful-paint', function (es) { var last = es[es.length - 1]; if (last) vitals.lcp = Math.round(last.startTime); });
27  var clsMax = 0, clsSess = 0, clsEntries = [];
28  vObserve('layout-shift', function (es) {
29    es.forEach(function (en) {
30      if (en.hadRecentInput) return;
31      var first = clsEntries[0], last = clsEntries[clsEntries.length - 1];
32      if (clsSess && en.startTime - last.startTime < 1000 && en.startTime - first.startTime < 5000) { clsSess += en.value; clsEntries.push(en); }
33      else { clsSess = en.value; clsEntries = [en]; }
34      if (clsSess > clsMax) { clsMax = clsSess; vitals.cls = Math.round(clsMax * 1000) / 1000; }
35    });
36  });
37  var inpMax = 0;
38  try {
39    new PerformanceObserver(function (l) { l.getEntries().forEach(function (en) { if (en.duration > inpMax) { inpMax = en.duration; vitals.inp = Math.round(inpMax); } }); })
40      .observe({ type: 'event', buffered: true, durationThreshold: 40 });
41  } catch (e) {}
42
43  function tick() {
44    var now = Date.now();
45    if (visible) activeMs += now - lastTick;
46    lastTick = now;
47  }
48  var iv = setInterval(tick, 4000);
49
50  document.addEventListener('visibilitychange', function () {
51    tick();
52    visible = (document.visibilityState === 'visible');
53    lastTick = Date.now();
54    if (document.visibilityState === 'hidden') send();
55  });
56
57  function onScroll() {
58    var h = document.documentElement;
59    var scrollable = (h.scrollHeight - h.clientHeight);
60    var top = (h.scrollTop || window.pageYOffset || 0);
61    var pct = scrollable > 0 ? Math.round((top / scrollable) * 100) : 100;
62    if (pct > maxScroll) maxScroll = Math.max(0, Math.min(100, pct));
63  }
64  window.addEventListener('scroll', onScroll, { passive: true });
65  onScroll();
66
67  document.addEventListener('click', function (e) {
68    var a = e.target && e.target.closest ? e.target.closest('a[href]') : null;
69    if (!a) return;
70    var href = a.getAttribute('href') || '';
71    if (href.indexOf('http') !== 0) { internalClicks++; return; } // relatif/ancre = interne
72    try {
73      var host = new URL(href, location.href).hostname.replace(/^www\./, '');
74      if (host === cfg.host) internalClicks++; else outboundClicks++;
75    } catch (err) {}
76  }, true);
77
78  function classifySource() {
79    try {
80      var utm = (new URLSearchParams(location.search).get('utm_source') || '').toLowerCase();
81      if (utm) {
82        if (utm.indexOf('pinterest') >= 0) return 'pinterest';
83        if (utm.indexOf('google') >= 0) return 'google';
84        if (['facebook', 'instagram', 'twitter', 'tiktok'].some(function (s) { return utm.indexOf(s) >= 0; })) return 'social';
85        return 'referral';
86      }
87      var ref = document.referrer;
88      if (!ref) return 'direct';
89      var rh = new URL(ref).hostname.replace(/^www\./, '');
90      if (rh === cfg.host) return 'direct';
91      if (rh.indexOf('pinterest.') >= 0 || rh.indexOf('pin.it') >= 0) return 'pinterest';
92      if (rh.indexOf('google.') >= 0) return 'google';
93      if (['facebook.com', 'instagram.com', 't.co', 'twitter.com', 'x.com', 'tiktok.com'].some(function (s) { return rh.indexOf(s) >= 0; })) return 'social';
94      return 'referral';
95    } catch (e) { return 'direct'; }
96  }
97
98  function refererHost() {
99    try { return document.referrer ? new URL(document.referrer).hostname.replace(/^www\./, '') : ''; } catch (e) { return ''; }
100  }
101
102  // UTM d'attribution (ex: pin Pinterest → utm_content = id du pin). Présents seulement
103  // sur la page d'atterrissage → on les persiste en sessionStorage pour les rattacher aux
104  // pages vues suivantes de la même session (attribution first-touch).
105  function utmParams() {
106    var keys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
107    var out = {};
108    try {
109      var qs = new URLSearchParams(location.search);
110      var hasFresh = false;
111      keys.forEach(function (k) { if (qs.get(k)) hasFresh = true; });
112      if (hasFresh) {
113        keys.forEach(function (k) {
114          var val = (qs.get(k) || '').slice(0, 190);
115          if (val) out[k] = val;
116        });
117        try { sessionStorage.setItem('snap_utm', JSON.stringify(out)); } catch (e) {}
118        return out;
119      }
120      // Pas d'UTM dans l'URL courante → réutilise ceux de l'atterrissage (même session).
121      var stored = sessionStorage.getItem('snap_utm');
122      if (stored) { var p = JSON.parse(stored); if (p && typeof p === 'object') return p; }
123    } catch (e) {}
124    return out;
125  }
126
127  function device() {
128    var w = window.innerWidth || screen.width || 0;
129    if (w && w < 768) return 'mobile';
130    if (w && w < 1024) return 'tablet';
131    return 'desktop';
132  }
133
134  function visitorHash() {
135    try {
136      var today = new Date().toISOString().slice(0, 10);
137      var v = localStorage.getItem('snap_vh');
138      if (!v || v.slice(0, 10) !== today) {
139        v = today + '.' + Math.random().toString(36).slice(2, 12);
140        localStorage.setItem('snap_vh', v);
141      }
142      return v.slice(0, 40);
143    } catch (e) { return ''; }
144  }
145
146  function send() {
147    if (sent) return;
148    tick();
149    var activeSeconds = Math.round(activeMs / 1000);
150    var quickback = (activeSeconds < 8 && maxScroll < 25) ? 1 : 0;
151    var utm = utmParams();
152    var payload = {
153      public_key: cfg.pk,
154      events: [{
155        post_id: cfg.postId,
156        path: location.pathname,
157        referer_host: refererHost(),
158        source: classifySource(),
159        utm_source: utm.utm_source,
160        utm_medium: utm.utm_medium,
161        utm_campaign: utm.utm_campaign,
162        utm_content: utm.utm_content,
163        utm_term: utm.utm_term,
164        device: device(),
165        scroll_pct: maxScroll,
166        active_seconds: activeSeconds,
167        internal_clicks: internalClicks,
168        outbound_clicks: outboundClicks,
169        is_quickback: quickback,
170        visitor_hash: visitorHash(),
171        lcp_ms: vitals.lcp,
172        cls: vitals.cls,
173        inp_ms: vitals.inp,
174        fcp_ms: vitals.fcp,
175        ttfb_ms: vitals.ttfb
176      }]
177    };
178    sent = true;
179    clearInterval(iv);
180    try {
181      var blob = new Blob([JSON.stringify(payload)], { type: 'text/plain' });
182      if (navigator.sendBeacon) navigator.sendBeacon(cfg.url, blob);
183      else fetch(cfg.url, { method: 'POST', body: JSON.stringify(payload), keepalive: true, headers: { 'Content-Type': 'text/plain' } });
184    } catch (e) {}
185  }
186
187  window.addEventListener('pagehide', send);
188})();

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.