PageSourceSearch

https://friends.se/app/plugins/cvp-analytics/assets/cvp-analytics.js?ver=1790846138

js friends.se collected 2026-10-02 03:19:02 UTC 4,521 bytes, 114 lines download raw bytes

1/**
2 * Bridge CVP (CVCheckout) analytics -> dataLayer, gated on consent.
3 *
4 * Everything is decided client-side at runtime, so it is page-cache safe: the
5 * markup is static, but consent is read live from the browser (Complianz /
6 * Google Consent Mode) on every visit. No PHP determines consent.
7 *
8 * The embedded checkout iframe never touches the dataLayer. It postMessages
9 * generic { id, name, ecommerce } events to the host, where the CVCheckout
10 * embed shim re-emits them on `window.CVCheckout`. We map them into GTM/GA4,
11 * but only after everything else is in place so our pushes always come last:
12 *   1. GTM has loaded            -> { event: 'gtm.load' }
13 *   2. Consent Mode was updated  -> ['consent', 'update', {...}]  (Complianz)
14 *   3. Statistics consent given  -> cmplz_has_consent('statistics')
15 */
16(function () {
17  window.dataLayer = window.dataLayer || [];
18  var queue = [];        // analytics events waiting to be pushed
19  var seenIds = {};      // exact-replay dedupe (same SDK id)
20  var firedOnce = {};    // singleton events already pushed this page
21  var ONCE = { view_item: 1, begin_checkout: 1, purchase: 1 };
22  var attached = false;  // analytics listener registered?
23  var timer = null;      // pending flush re-check, if any
24  var tries = 0;         // re-checks left in the current budget
25  var MAX_TRIES = 50;    // ~10s at 200ms, then give up until consent changes
26
27  // Debug logging, opt-in via the CVP_ANALYTICS_DEBUG env var (set in PHP).
28  function log() {
29    if (window.CVP_ANALYTICS_DEBUG && window.console) {
30      console.log.apply(console, arguments);
31    }
32  }
33
34  // Statistics consent granted right now (handles late accept/revoke).
35  function granted() {
36    try { return !!window.cmplz_has_consent('statistics'); } catch (e) { return false; }
37  }
38
39  // Does the dataLayer already contain a row matching test()?
40  function inDataLayer(test) {
41    var dl = window.dataLayer;
42    for (var i = 0; i < dl.length; i++) {
43      if (dl[i] && test(dl[i])) return true;
44    }
45    return false;
46  }
47
48  // We only push once everything else is in place, so our events come
49  // last regardless of page load vs. late consent:
50  //  1. GTM has loaded            -> { event: 'gtm.load' }
51  //  2. Consent Mode was updated  -> ['consent', 'update', {...}]  (Complianz)
52  //  3. Statistics consent given.
53  function ready() {
54    return granted()
55      && inDataLayer(function (r) { return r.event === 'gtm.load'; })
56      && inDataLayer(function (r) { return r[0] === 'consent' && r[1] === 'update'; });
57  }
58
59  // Drain the queue when ready, otherwise re-check until the budget runs
60  // out (e.g. consent never accepted). A consent change re-arms it.
61  function flush() {
62    timer = null;
63    if (!queue.length) return;
64    if (!ready()) {
65      if (tries-- > 0) timer = setTimeout(flush, 200);
66      return;
67    }
68    while (queue.length) {
69      var e = queue.shift();
70      log('[CVP analytics]', e.name, e.ecommerce);
71      window.dataLayer.push({ ecommerce: null });
72      window.dataLayer.push({ event: e.name, ecommerce: e.ecommerce });
73    }
74  }
75
76  // Start (or restart) checking with a fresh budget.
77  function schedule() {
78    tries = MAX_TRIES;
79    if (!timer) flush();
80  }
81
82  function onReady() {
83    var api = window.CVCheckout;
84    if (attached || !api || typeof api.on !== 'function') return;
85    attached = true;
86
87    // view_item, begin_checkout, add_payment_info, purchase, etc.
88    // Buffer and let flush() gate the push.
89    api.on('analytics', function (e) {
90      // The SDK buffers + replays events, and a consent flip (deny -> allow)
91      // can re-init the checkout, re-emitting events with *new* ids.
92      //  - Singletons (view_item/begin_checkout/purchase) fire once per page,
93      //    so dedupe by name to absorb replays and re-inits.
94      //  - Everything else (e.g. add_payment_info) dedupes by id only, so a
95      //    genuine payment-method switch back to A still pushes again.
96      if (ONCE[e.name]) {
97        if (firedOnce[e.name]) return;
98        firedOnce[e.name] = true;
99      } else {
100        if (seenIds[e.id]) return;
101        seenIds[e.id] = true;
102      }
103      queue.push(e);
104      schedule();
105    });
106  }
107
108  window.addEventListener('cv:checkout:ready', onReady);
109  window.addEventListener('cv:thankyou:ready', onReady);
110
111  // Re-arm checking when consent changes (e.g. late accept after giving up).
112  document.addEventListener('cmplz_status_change', schedule);
113  document.addEventListener('cmplz_enable_category', schedule);
114})();

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.