1/* East Pass Excursions â first-party analytics beacon. 2 * 3 * Cookie-free, PII-free, and fully self-contained. On load it records a 4 * pageview (with referrer + UTM tags) and auto-wires clicks on booking/CTA, 5 * phone, email, and tour-card links. Anything else can be tracked manually via 6 * window.track(type, opts). 7 * 8 * Transport: navigator.sendBeacon to POST /api/track (falls back to fetch 9 * keepalive). The server responds 204 immediately, so this never blocks. 10 * 11 * Session id: a short random id kept only in memory for this page-session. It 12 * is NOT stored in a cookie or localStorage (those are blocked in some embeds 13 * and we want to stay cookie-free). It groups events within a single tab load. 14 */ 15(function () { 16 'use strict'; 17 if (window.__epxTrack) return; // guard against double-load 18 19 var ENDPOINT = '/api/track'; 20 21 // Cookie-free, in-memory session id (per tab load). 22 function rid() { 23 try { 24 if (window.crypto && window.crypto.getRandomValues) { 25 var a = new Uint8Array(9); 26 window.crypto.getRandomValues(a); 27 return Array.prototype.map.call(a, function (b) { return ('0' + b.toString(16)).slice(-2); }).join(''); 28 } 29 } catch (e) {} 30 return (Date.now().toString(36) + Math.random().toString(36).slice(2, 10)); 31 } 32 var SID = rid(); 33 34 function qp(name) { 35 try { 36 return new URLSearchParams(window.location.search).get(name) || undefined; 37 } catch (e) { return undefined; } 38 } 39 40 function base(extra) { 41 var o = { 42 sid: SID, 43 path: window.location.pathname + (window.location.search || ''), 44 ref: document.referrer || undefined, 45 utm_source: qp('utm_source'), 46 utm_medium: qp('utm_medium'), 47 utm_campaign: qp('utm_campaign'), 48 }; 49 if (extra) for (var k in extra) if (extra.hasOwnProperty(k)) o[k] = extra[k]; 50 return o; 51 } 52 53 function send(payload) { 54 try { 55 var body = JSON.stringify(payload); 56 if (navigator.sendBeacon) { 57 var blob = new Blob([body], { type: 'application/json' }); 58 if (navigator.sendBeacon(ENDPOINT, blob)) return; 59 } 60 // Fallback 61 fetch(ENDPOINT, { 62 method: 'POST', 63 headers: { 'Content-Type': 'application/json' }, 64 body: body, 65 keepalive: true, 66 credentials: 'same-origin', 67 }).catch(function () {}); 68 } catch (e) { /* swallow */ } 69 } 70 71 // Public API: track('cta_click', { label: 'Book now', boat: 'pontoon' }) 72 function track(type, opts) { 73 if (!type) return; 74 send(base(Object.assign({ type: type }, opts || {}))); 75 } 76 window.track = track; 77 window.__epxTrack = { sid: SID, track: track }; 78 79 // ---- auto pageview ---- 80 track('pageview'); 81 82 // ---- auto-wire common clicks (delegated, one listener) ---- 83 function labelFor(el) { 84 var l = el.getAttribute('data-track-label'); 85 if (l) return l; 86 var txt = (el.textContent || '').trim().replace(/\s+/g, ' '); 87 if (txt) return txt.slice(0, 80); 88 var aria = el.getAttribute('aria-label'); 89 return aria ? aria.slice(0, 80) : '(unlabeled)'; 90 } 91 92 document.addEventListener('click', function (e) { 93 var el = e.target && e.target.closest ? e.target.closest('a,button,[data-track]') : null; 94 if (!el) return; 95 96 // Explicit opt-in: data-track="event_type" [data-track-label] [data-boat] 97 var explicit = el.getAttribute('data-track'); 98 if (explicit) { 99 track(explicit, { label: labelFor(el), boat: el.getAttribute('data-boat') || undefined }); 100 return; 101 } 102 103 var href = (el.getAttribute('href') || '').trim(); 104 105 // Phone / email links 106 if (/^tel:/i.test(href)) { track('phone_click', { label: labelFor(el) }); return; } 107 if (/^mailto:/i.test(href)) { track('email_click', { label: labelFor(el) }); return; } 108 109 // Booking / CTA buttons. Only count genuine booking/CTA intent â the bare 110 // `btn` utility class is NOT enough (it's on every button, incl. waiver 111 // Continue/Back). Require a CTA-specific class or CTA-intent text. 112 var cls = (el.className || '').toString(); 113 var txt = (el.textContent || '').toLowerCase().replace(/\s+/g, ' ').trim(); 114 var isCta = /\b(cta|book(ing)?|reserve)\b/i.test(cls) || 115 /\bbook a trip\b|\bbook now\b|\bbook\b|\breserve\b|check availability|request a trip|inquire/.test(txt); 116 // Exclude waiver wizard navigation (Continue/Back/Submit) which happens to 117 // use the .btn class but is not a booking CTA. 118 var isWizardNav = el.hasAttribute('data-next') || el.hasAttribute('data-back') || 119 /^(continue|back|next|submit|previous)$/.test(txt); 120 if (isCta && !isWizardNav) { 121 track('cta_click', { label: labelFor(el), boat: el.getAttribute('data-boat') || undefined }); 122 return; 123 } 124 125 // Tour cards / tour links 126 if (/\/tours\//.test(href)) { 127 track('tour_click', { label: labelFor(el) }); 128 return; 129 } 130 }, true); 131})();
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.