1(function(){ 2 if (typeof window.LEPIXEL_WOO === 'undefined') { return; } 3 var cfg = window.LEPIXEL_WOO; 4 5 var HOST = cfg.TRACKING_SERVER; 6 function nowTs(){ return Date.now(); } 7 function newEventId(){ return (''+Date.now()) + '' + Math.floor(Math.random() * 1e16); } 8 9 function headers(){ 10 var h = { 'Content-Type': 'application/json' }; 11 try { 12 if (cfg && cfg.nonce) { h['X-WP-Nonce'] = cfg.nonce; } 13 } catch(e){} 14 return h; 15 } 16 17 async function getVisitorId(){ 18 try { 19 var id = localStorage.getItem('lePixelVisitorId'); 20 if(!id){ 21 id = 'v-' + Date.now() + '-' + Math.floor(Math.random()*1000000); 22 localStorage.setItem('lePixelVisitorId', id); 23 } 24 return id; 25 } catch(e){} 26 } 27 28 async function getSessionId(){ 29 try { 30 var id = sessionStorage.getItem('lePixelSessionId'); 31 if(!id){ 32 id = 's-' + Date.now() + '-' + Math.floor(Math.random()*1000000); 33 sessionStorage.setItem('lePixelSessionId', id); 34 } 35 return id; 36 } catch(e){} 37 } 38 39 function sendTrackingData(payload, retries){ 40 retries = (typeof retries === 'number') ? retries : 3; 41 var body = JSON.stringify(payload); 42 try{ 43 fetch(HOST, { 44 method: 'POST', 45 headers: { 'Content-Type': 'application/json' }, 46 body: body 47 }).catch(function(){ 48 try{ 49 if (navigator && typeof navigator.sendBeacon === 'function') { 50 var blob = new Blob([body], { type: 'application/json' }); 51 navigator.sendBeacon(HOST, blob); 52 } 53 }catch(e){} 54 if(retries > 0){ sendTrackingData(payload, retries-1); } 55 }); 56 }catch(e){} 57 } 58 59 function baseEvent(evtName, extra){ 60 return Promise.all([getVisitorId(), getSessionId()]).then(function(ids){ 61 var visitorId = ids[0], sessionId = ids[1]; 62 var base = { 63 event: evtName, 64 eventId: newEventId(), 65 visitorId: visitorId, 66 url: window.location.href, 67 referrer: document.referrer, 68 timestamp: nowTs(), 69 sessionId: sessionId, 70 shop: cfg.shop, 71 customerId: cfg.user ? cfg.user.id : undefined, 72 customerEmail: cfg.user ? cfg.user.email : undefined, 73 firstName: cfg.user ? cfg.user.firstName : undefined, 74 lastName: cfg.user ? cfg.user.lastName : undefined 75 }; 76 if (extra) { for (var k in extra) { base[k] = extra[k]; } } 77 return base; 78 }); 79 } 80 81 function sendPageView(){ baseEvent('page_view').then(sendTrackingData); } 82 83 // jQuery helpers 84 function onJq(eventName, handler){ 85 if (typeof jQuery === 'undefined') { return; } 86 jQuery(function($){ $(document.body).on(eventName, handler); }); 87 } 88 function onDoc(eventName, selector, handler){ 89 if (typeof jQuery === 'undefined') { return; } 90 jQuery(function($){ $(document).on(eventName, selector, handler); }); 91 } 92 93 // REST helpers 94 function restGet(path){ 95 var url = cfg.restRoot.replace(/\/$/, '') + path; 96 return fetch(url, { headers: headers() }).then(function(r){ return r.json(); }); 97 } 98 function enrichProduct(productId, variationId, quantity){ 99 var qs = '?id=' + encodeURIComponent(productId) + 100 (variationId ? ('&variation_id=' + encodeURIComponent(variationId)) : '') + 101 '&quantity=' + encodeURIComponent(quantity || 1); 102 return restGet('/product' + qs); 103 } 104 function fetchCart(){ 105 return restGet('/cart'); 106 } 107 108 // ---- CART DIFFING ---- 109 var cartSnapshot = {}; // key -> { quantity, item } 110 var suppressDiffUntil = 0; 111 var snapshotReady = false; 112 113 function updateSnapshot(items){ 114 cartSnapshot = {}; 115 (items || []).forEach(function(it){ 116 cartSnapshot[it.key] = { quantity: it.quantity, item: it }; 117 }); 118 snapshotReady = true; 119 } 120 121 function buildCartPayload(item, deltaQty){ 122 var unit = (item && item.variant && typeof item.variant.price === 'number') ? item.variant.price : undefined; 123 return { 124 quantity: deltaQty, 125 totalPrice: (unit ? { amount: unit * deltaQty, currency: item.variant.currency || cfg.currency } : undefined), 126 variant: item.variant 127 }; 128 } 129 130 function diffEmit(prevMap, nextItems){ 131 var nextMap = {};
132 nextItems.forEach(function(it){ nextMap[it.key] = { quantity: it.quantity, item: it }; }); 133 134 // Detect increases/decreases 135 var keys = {}; 136 for (var k in prevMap) { keys[k] = true; } 137 for (var k in nextMap) { keys[k] = true; } 138 139 var emits = []; 140 for (var k in keys) { 141 var prev = prevMap[k] ? prevMap[k].quantity : 0; 142 var next = nextMap[k] ? nextMap[k].quantity : 0; 143 var item = nextMap[k] ? nextMap[k].item : (prevMap[k] ? prevMap[k].item : null); 144 if (!item) { continue; } 145 if (next > prev) { 146 emits.push({ type: 'add_to_cart', payload: buildCartPayload(item, next - prev) }); 147 } else if (next < prev) { 148 emits.push({ type: 'remove_from_cart', payload: buildCartPayload(item, prev - next) }); 149 } 150 } 151 152 emits.sort(function(a,b){ return a.type === 'remove_from_cart' ? -1 : 1; }); 153 var proms = emits.map(function(evt){ 154 return baseEvent(evt.type, evt.type === 'add_to_cart' ? { addedItem: evt.payload } : { removedItem: evt.payload }) 155 .then(function(data){ sendTrackingData(data, 0); }); 156 }); 157 158 updateSnapshot(nextItems); 159 return Promise.all(proms); 160 } 161 162 function refreshAndDiff(reason){ 163 if (Date.now() < suppressDiffUntil) { return; } 164 if (!snapshotReady) { return; } // avoid false positives on first load 165 fetchCart().then(function(res){ 166 var items = (res && res.items) ? res.items : []; 167 diffEmit(cartSnapshot, items); 168 }).catch(function(){ /* ignore */ }); 169 } 170 function suppressFor(ms){ suppressDiffUntil = Date.now() + (ms||1200); } 171 172 // ------------- EVENT WIRING ------------- 173 174 // 1) Page view 175 try { sendPageView(); } catch(e){} 176 177 // Initialize initial snapshot, then allow diffing 178 setTimeout(function(){ fetchCart().then(function(res){ updateSnapshot((res && res.items) || []); }); }, 150); 179 180 // 2) Add to cart (AJAX standard) 181 onJq('added_to_cart', function(ev, fragments, cart_hash, $button){ 182 try{ 183 var $btn = (typeof jQuery !== 'undefined' && $button && $button.length) ? $button : null; 184 var productId = $btn ? ($btn.data('product_id') || $btn.attr('data-product_id') || $btn.data('product') || $btn.attr('data-product')) : null; 185 var variationId = $btn ? ($btn.data('variation_id') || $btn.attr('data-variation_id')) : null; 186 var qty = 1; 187 try{ 188 qty = ($btn && ($btn.data('quantity') || $btn.attr('data-quantity'))) || 1; 189 var $form = $btn && $btn.closest('form'); 190 if ($form && !variationId) { 191 var vid = $form.find('input[name=variation_id]').val(); 192 if (vid) variationId = parseInt(vid, 10); 193 } 194 if ($form) { 195 var qv = $form.find('input[name=quantity]').val(); 196 if (qv) qty = parseInt(qv, 10); 197 } 198 }catch(e){} 199 200 if (!productId) { return; } 201 202 enrichProduct(productId, variationId, qty).then(function(data){ 203 baseEvent('add_to_cart', { addedItem: data.addedItem }).then(function(evt){ 204 sendTrackingData(evt, 0); 205 suppressFor(1200); 206 }); 207 }).catch(function(){ 208 baseEvent('add_to_cart', { 209 addedItem: { 210 quantity: qty, 211 variant: { 212 id: variationId || productId, 213 product: { id: productId } 214 } 215 } 216 }).then(function(evt){ sendTrackingData(evt, 0); suppressFor(1200); }); 217 }); 218 }catch(e){} 219 }); 220 221 // 3) Remove from cart (AJAX standard) 222 onJq('removed_from_cart', function(){ setTimeout(function(){ refreshAndDiff('removed_from_cart'); }, 200); suppressFor(1200); }); 223 224 // 4) Non-AJAX fallbacks via server-snapshotted items (already cleared server-side) 225 try{ 226 if (cfg.lastAddedItem) { baseEvent('add_to_cart', { addedItem: cfg.lastAddedItem }).then(sendTrackingData); } 227 if (cfg.lastRemovedItem) { baseEvent('remove_from_cart', { removedItem: cfg.lastRemovedItem }).then(sendTrackingData); } 228 }catch(e){} 229 230 // 5) Initiate checkout (on checkout page load snapshot) 231 try{ 232 if (cfg.context && cfg.context.isCheckout && cfg.checkout) { 233 baseEvent('initiate_checkout', { 234 totalPrice: cfg.checkout.totalPrice, 235 lineItems: cfg.checkout.lineItems, 236 discountsAmount: cfg.checkout.discountsAmount || undefined, 237 totalTax: cfg.checkout.totalTax, 238 shippingPrice: cfg.checkout.shippingPrice 239 }).then(function(evt){ sendTrackingData(evt, 0); }); 240 } 241 }catch(e){} 242 243// 6) Purchase (thank-you page) 244try { 245 if (cfg.purchase) { 246 var orderKey = cfg.purchase.orderId; 247 var dedupKey = 'lePixelPurchase' + orderKey; 248 // Skip if already fired this session 249 if (!sessionStorage.getItem(dedupKey)) { 250 sessionStorage.setItem(dedupKey, '1'); 251 baseEvent('purchase').then(function(base){ 252 var p = cfg.purchase; 253 p.eventId = base.eventId; 254 p.visitorId = base.visitorId; 255 p.sessionId = base.sessionId; 256 p.url = base.url; 257 p.referrer = base.referrer; 258 p.timestamp = base.timestamp; 259 p.shop = p.shop || base.shop; 260 261 sendTrackingData(p, 0); 262 }); 263 } 264 } 265}catch (e) {} 266 267 268 // 7) Broad coverage: listen to cart & fragments refreshes and diff. 269 onJq('updated_wc_div', function(){ refreshAndDiff('updated_wc_div'); }); 270 onJq('cart_page_refreshed', function(){ refreshAndDiff('cart_page_refreshed'); }); 271 onJq('wc_cart_emptied', function(){ refreshAndDiff('wc_cart_emptied'); }); 272 onJq('wc_fragments_refreshed', function(){ refreshAndDiff('wc_fragments_refreshed'); }); 273 274 // Extra: clicks that are known to remove items in various templates 275 onDoc('click', 'a.remove, a.remove_from_cart_button, .wc-block-cart-item__remove-link', function(){ setTimeout(function(){ refreshAndDiff('click_remove'); }, 200); }); 276 277 // Fallback: observe jQuery AJAX calls to Woo endpoints in case a theme/plugin skips standard events 278 if (typeof jQuery !== 'undefined') { 279 jQuery(document).ajaxSuccess(function(event, xhr, settings){
280 try{ 281 if (!settings || !settings.url) return; 282 var url = settings.url; 283 var dataStr = settings.data || ''; 284 if (url.indexOf('wc-ajax=add_to_cart') !== -1) { 285 var p = new URLSearchParams(dataStr || ''); 286 var productId = p.get('product_id') || p.get('add-to-cart'); 287 var variationId = p.get('variation_id'); 288 var qty = parseInt(p.get('quantity') || '1', 10); 289 if (productId) { 290 enrichProduct(productId, variationId, qty).then(function(data){ 291 baseEvent('add_to_cart', { addedItem: data.addedItem }).then(function(evt){ 292 sendTrackingData(evt, 0); 293 suppressFor(1200); 294 }); 295 }); 296 } 297 } else if (url.indexOf('wc-ajax=remove_from_cart') !== -1) { 298 setTimeout(function(){ refreshAndDiff('ajax_remove'); }, 200); 299 suppressFor(1200); 300 } else if (url.indexOf('wc-ajax=update_cart') !== -1 || url.indexOf('wc-ajax=apply_coupon') !== -1) { 301 setTimeout(function(){ refreshAndDiff('ajax_update'); }, 300); 302 } 303 }catch(e){} 304 }); 305 } 306 307 // Intercept fetch used by Blocks / modern themes to detect cart changes (Store API). 308 (function(){ 309 if (!window.fetch) { return; } 310 var origFetch = window.fetch; 311 window.fetch = function(input, init){ 312 var url = (typeof input === 'string') ? input : (input && input.url) || ''; 313 var method = (init && init.method) ? String(init.method).toUpperCase() : 'GET'; 314 var p = origFetch.apply(this, arguments); 315 try { 316 if (/wc-ajax=(add_to_cart|remove_from_cart|update_cart)|\/wc\/store\/|\/wp-json\/wc\/store\//.test(url)) { 317 p.then(function(){ setTimeout(function(){ refreshAndDiff('fetch:'+method); }, 220); }); 318 } 319 } catch(e){} 320 return p; 321 }; 322 })(); 323 324 // DOM observer for mini-cart/cart mutations (covers pure DOM-based updates) 325 (function(){ 326 if (typeof MutationObserver === 'undefined') { return; } 327 var sel = ['.widget_shopping_cart', '.woocommerce-mini-cart', '.wc-block-mini-cart', '.wc-block-cart-items'].join(','); 328 function observe(el){ 329 try { 330 var mo = new MutationObserver(function(){ setTimeout(function(){ refreshAndDiff('mo'); }, 300); }); 331 mo.observe(el, { childList: true, subtree: true, characterData: true }); 332 } catch(e){} 333 } 334 function scan(){ 335 var nodes = document.querySelectorAll(sel); 336 for (var i=0; i<nodes.length; i++){ observe(nodes[i]); } 337 } 338 if (document.readyState === 'loading') { 339 document.addEventListener('DOMContentLoaded', scan); 340 } else { scan(); } 341 })(); 342 343 // Optional: for themes that auto-update cart on quantity input change without events 344 if (typeof jQuery !== 'undefined') { 345 jQuery(function($){ 346 $(document).on('change', '.woocommerce-cart-form input.qty', function(){ 347 setTimeout(function(){ refreshAndDiff('qty_change'); }, 500); 348 }); 349 }); 350 } 351 352})();
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.