PageSourceSearch

https://buy.bangnbody.com/attribution.js?shop=bangn-body.myshopify.com

js bangnbody.com collected 2026-10-03 21:20:22 UTC 6,878 bytes, 234 lines download raw bytes

1(() => {
2  // Wide debug logging for staging/dev domains
3  var DEBUG = true;
4  var wideEvent = {
5    script: 'attribution.js',
6    hostname: location.hostname,
7    url: location.href,
8    debug: DEBUG
9  };
10
11  function emit(outcome) {
12    if (!DEBUG) return;
13    wideEvent.outcome = outcome;
14    wideEvent.duration_ms = Date.now() - wideEvent.start_time;
15    console.log('[GF Attribution]', JSON.stringify(wideEvent, null, 2));
16  }
17
18  wideEvent.start_time = Date.now();
19
20  // Bail out if running in a sandboxed iframe without cookie access
21  try {
22    var _ = document.cookie;
23  } catch (_e) {
24    wideEvent.sandboxed = true;
25    emit('sandboxed_exit');
26    return;
27  }
28
29  var el = document.currentScript || document.querySelector('[data-gf-subdomain], [data-gf-source="theme-app-embed"]');
30  // Extract context from data attributes or from the hosted script URL. Theme app extensions
31  // serve assets from Shopify CDN, so absence of a funnel subdomain should not block cart attribution.
32  var configuredSubdomain = el?.getAttribute('data-gf-subdomain');
33  var shopDomain = el?.getAttribute('data-gf-shop');
34  var isThemeAppEmbed = el?.getAttribute('data-gf-source') === 'theme-app-embed';
35  var scriptHost = null;
36  if (el?.src) {
37    try {
38      var srcHost = new URL(el.src).hostname;
39      if (srcHost && srcHost !== location.hostname) scriptHost = srcHost;
40    } catch (_e) {}
41  }
42  var subdomain = configuredSubdomain || scriptHost || null;
43  wideEvent.script_src = el ? el.src : null;
44  wideEvent.asset_host = scriptHost;
45  wideEvent.source = el?.getAttribute('data-gf-source') || null;
46  wideEvent.shop_domain = shopDomain;
47  wideEvent.subdomain = subdomain;
48  wideEvent.subdomain_source = configuredSubdomain ? 'data_attr' : scriptHost ? 'script_src' : 'none';
49  wideEvent.theme_app_embed = isThemeAppEmbed;
50  wideEvent.no_subdomain = !subdomain;
51
52  if (!subdomain && !isThemeAppEmbed) {
53    wideEvent.exit_reason = 'no_subdomain';
54    emit('no_subdomain');
55    return;
56  }
57
58  var TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
59
60  function getCookie(name) {
61    var match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
62    return match ? match[1] : null;
63  }
64
65  // The Shopify cart token is part of the hash so a NEW cart (e.g. after an order
66  // clears the old one) is re-stamped with the same attribution.
67  function payloadHash(p) {
68    return [p.pid, p.sid, p.fid, p.uid, p.vid, p.eid, getCookie('cart') || ''].join('|');
69  }
70
71  // External-URL experiment arms: the edge 302s the visitor here with
72  // ?gf_eid&gf_vid&gf_sid&gf_pid (EXPERIMENT_ATTRIBUTION_PARAMS in
73  // @getfunneled/classes/experiment-attribution). That landing attribution is persisted in
74  // localStorage (same _gf_attr shape, fid empty) so later pages in the visit
75  // still stamp the cart, and it competes with the parent-domain _gf_attr cookie
76  // on freshness (ts) — an old 30-day funnel cookie must not override it.
77  var LANDING_KEY = '_gf_landing_attr';
78
79  function landingFromUrl() {
80    try {
81      var params = new URLSearchParams(location.search);
82      var p = {
83        pid: params.get('gf_pid') || '',
84        sid: params.get('gf_sid') || '',
85        fid: '',
86        uid: '',
87        vid: params.get('gf_vid') || '',
88        eid: params.get('gf_eid') || '',
89        ts: Date.now()
90      };
91      if (!p.pid || !p.sid || !p.eid || !p.vid) return null;
92      return p;
93    } catch (_e) {
94      return null;
95    }
96  }
97
98  function parseAttr(raw) {
99    if (!raw) return null;
100    try {
101      var p = JSON.parse(decodeURIComponent(raw));
102      return p && typeof p === 'object' && p.pid ? p : null;
103    } catch (_e) {
104      return null;
105    }
106  }
107
108  function freshest(a, b) {
109    if (!a) return b;
110    if (!b) return a;
111    return (b.ts || 0) > (a.ts || 0) ? b : a;
112  }
113
114  function alreadyApplied(p) {
115    try {
116      var applied = JSON.parse(localStorage.getItem('_gf_applied') || '{}');
117      return applied.hash === payloadHash(p);
118    } catch (_e) {
119      return false;
120    }
121  }
122
123  function markApplied(p) {
124    try {
125      localStorage.setItem('_gf_applied', JSON.stringify({ hash: payloadHash(p) }));
126    } catch (_e) {}
127  }
128
129  function updateCart(p) {
130    var body = { attributes: {} };
131    if (p.pid) body.attributes._gf_pid = p.pid;
132    if (p.sid) body.attributes._gf_sid = p.sid;
133    if (p.fid) body.attributes._gf_fid = p.fid;
134    if (p.uid) body.attributes._gf_uid = p.uid;
135    if (p.vid) body.attributes._gf_vid = p.vid;
136    if (p.eid) body.attributes._gf_eid = p.eid;
137    body.attributes._gf_src = 'indirect';
138
139    wideEvent.cart_attributes = body.attributes;
140
141    fetch('/cart/update.js', {
142      method: 'POST',
143      headers: { 'Content-Type': 'application/json' },
144      body: JSON.stringify(body)
145    })
146      .then((res) => {
147        wideEvent.cart_update_status = res.status;
148        markApplied(p);
149        emit('cart_updated');
150      })
151      .catch((err) => {
152        wideEvent.cart_update_error = String(err);
153        emit('cart_update_failed');
154      });
155  }
156
157  function apply(p) {
158    if (window.requestIdleCallback) {
159      requestIdleCallback(() => {
160        updateCart(p);
161      });
162    } else {
163      setTimeout(() => {
164        updateCart(p);
165      }, 0);
166    }
167  }
168
169  // Read attribution: freshest of the _gf_attr cookie and the persisted landing attribution
170  try {
171    var allCookies = document.cookie;
172    wideEvent.cookie_count = (allCookies.match(/=/g) || []).length;
173    wideEvent.cookie_length = allCookies.length;
174
175    var raw = getCookie('_gf_attr');
176    wideEvent.has_gf_attr = !!raw;
177    wideEvent.gf_attr_raw = raw;
178
179    var landing = landingFromUrl();
180    wideEvent.has_landing_params = !!landing;
181    if (landing) {
182      try {
183        localStorage.setItem(LANDING_KEY, encodeURIComponent(JSON.stringify(landing)));
184      } catch (_e) {}
185    }
186    var stored = landing;
187    if (!stored) {
188      try {
189        stored = parseAttr(localStorage.getItem(LANDING_KEY));
190      } catch (_e) {
191        stored = null;
192      }
193    }
194    var cookieAttr = parseAttr(raw);
195    var p = freshest(cookieAttr, stored);
196    wideEvent.attribution_source = !p ? 'none' : p === cookieAttr ? 'cookie' : 'landing';
197
198    if (!raw && !stored) {
199      emit('no_cookie');
200      return;
201    }
202
203    wideEvent.payload = p;
204    wideEvent.has_pid = !!p?.pid;
205
206    if (!p || !p.pid) {
207      emit('no_pid');
208      return;
209    }
210
211    if (p.ts) {
212      var age_ms = Date.now() - p.ts;
213      wideEvent.cookie_age_ms = age_ms;
214      wideEvent.cookie_age_hours = Math.round(age_ms / 3600000);
215      if (age_ms > TTL_MS) {
216        emit('cookie_expired');
217        return;
218      }
219    }
220
221    var wasApplied = alreadyApplied(p);
222    wideEvent.already_applied = wasApplied;
223    if (wasApplied) {
224      emit('already_applied');
225      return;
226    }
227
228    emit('applying');
229    apply(p);
230  } catch (e) {
231    wideEvent.error = String(e);
232    emit('error');
233  }
234})();

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.