1; (function (window) { 2 // Thin GTM dataLayer push helper. Server templates and dashboard.js call 3 // `pushTrackingEvent(eventName, payload)`; we attach the universal 4 // bootstrap (portal/user) and current attribution (UTM/referrer/fpr/gclid 5 // via window.AplReferral) and push. GA4 handles event_id-based dedupe 6 // natively â no server SHA1 or per-event builder framework here. 7 8 function getSafeReferrer() { 9 var referrer = document.referrer || ""; 10 if (!referrer) { 11 return ""; 12 } 13 try { 14 var refHost = new window.URL(referrer).hostname; 15 var curHost = (window.location && window.location.hostname) || ""; 16 if (refHost && refHost === curHost) { 17 return ""; 18 } 19 } catch (e) { } 20 return referrer; 21 } 22 23 function getReferralParams() { 24 // window.AplReferral is set up by utmreftracking.min.js, loaded before 25 // this script in gtm_runtime_head.php. Owns URL parsing, 60-day 26 // localStorage persistence, and the firstpromoter override. 27 if (window.AplReferral && typeof window.AplReferral.getParams === "function") { 28 try { 29 return window.AplReferral.getParams() || {}; 30 } catch (e) { } 31 } 32 return {}; 33 } 34 35 function getAttributionData() { 36 var p = getReferralParams(); 37 return { 38 utm_source: p.utm_source || "", 39 utm_medium: p.utm_medium || "", 40 utm_campaign: p.utm_campaign || "", 41 utm_term: p.utm_term || "", 42 utm_content: p.utm_content || "", 43 gclid: p.gclid || "", 44 fpr: p.fpr || "", 45 referrer: getSafeReferrer() || p.referrer || "", 46 }; 47 } 48 49 function getUniversalContext() { 50 return { 51 portal_id: window.portalId || "", 52 portal_name: window.portalName || "", 53 user_type: window.userType || "anon", 54 user_id: window.userId || "", 55 customer_type: window.customerType || "", 56 current_plan: window.currentPlan || "", 57 billing_cycle: window.billingCycle || "", 58 country: window.userCountry || "", 59 }; 60 } 61 62 function pushTrackingEvent(eventName, payload) { 63 if (!eventName) { 64 return; 65 } 66 payload = payload || {}; 67 window.dataLayer = window.dataLayer || []; 68 // GA4 built-in ecommerce reads ONLY from a nested `ecommerce` object. When 69 // an event carries one, clear the previous ecommerce object first so items 70 // / transaction data never bleed from one event into the next (GTM merges 71 // the dataLayer across pushes otherwise). 72 if (payload.ecommerce) { 73 window.dataLayer.push({ ecommerce: null }); 74 } 75 var event = Object.assign( 76 { event: eventName }, 77 getUniversalContext(), 78 getAttributionData(), 79 payload 80 ); 81 window.dataLayer.push(event); 82 // Return the fully-assembled event so callers (e.g. the purchase site) 83 // can stash it verbatim for a later, faithful replay on another page. 84 return event; 85 } 86 87 window.pushTrackingEvent = pushTrackingEvent; 88 89 // --- Thank-you page purchase replay ------------------------------------- 90 // The `purchase` event fires on the checkout/signup page, right before the 91 // redirect. To also fire it on the /thank-you-{plan} page, the purchase site 92 // stashes the fully-assembled event in sessionStorage; the thank-you page 93 // reads it back and replays it verbatim as `purchase_thank_you`. sessionStorage 94 // survives the same-tab (incl. /signin) navigation to the thank-you page. 95 var TY_STASH_KEY = "apl_ty_purchase"; // sessionStorage: pending replay 96 var TY_LEDGER_KEY = "apl_ty_purchase_fired"; // localStorage: fired nonces 97 var TY_LEDGER_CAP = 10; 98 99 function stashThankYouPurchase(assembledEvent, explicitNonce) { 100 if (!assembledEvent) { 101 return; 102 } 103 try { 104 // Free signups have no payment_intent_id/transaction_id, so callers pass 105 // a synthetic nonce (e.g. signup_<user_id>) for once-only dedup. 106 var nonce = 107 explicitNonce || 108 assembledEvent.payment_intent_id || 109 (assembledEvent.ecommerce && assembledEvent.ecommerce.transaction_id) || 110 ""; 111 window.sessionStorage.setItem( 112 TY_STASH_KEY, 113 JSON.stringify({ nonce: nonce, event: assembledEvent }) 114 ); 115 } catch (e) { /* private mode / storage disabled â never block checkout */ } 116 } 117 118 function readLedger() { 119 try { 120 var raw = window.localStorage.getItem(TY_LEDGER_KEY); 121 var parsed = raw ? JSON.parse(raw) : []; 122 return Array.isArray(parsed) ? parsed : []; 123 } catch (e) { 124 return []; 125 } 126 } 127 128 function fireThankYouPurchaseIfStashed() { 129 var raw; 130 try { 131 raw = window.sessionStorage.getItem(TY_STASH_KEY); 132 } catch (e) { 133 return; 134 } 135 if (!raw) { 136 return; 137 } 138 139 var stash; 140 try { 141 stash = JSON.parse(raw); 142 } catch (e) { 143 stash = null; 144 } 145 if (!stash || !stash.event) {
146 try { window.sessionStorage.removeItem(TY_STASH_KEY); } catch (e) {} 147 return; 148 } 149 150 var nonce = stash.nonce || ""; 151 var ledger = readLedger(); 152 // Belt-and-suspenders dedupe: the server flash gate already stops the page 153 // rendering twice, but never let the same purchase replay twice. 154 if (nonce && ledger.indexOf(nonce) !== -1) { 155 try { window.sessionStorage.removeItem(TY_STASH_KEY); } catch (e) {} 156 return; 157 } 158 159 var event = stash.event; 160 event.event = "purchase_thank_you"; 161 window.dataLayer = window.dataLayer || []; 162 // Same ecommerce reset pushTrackingEvent does, so items/transaction data 163 // don't bleed in from a prior dataLayer push. 164 if (event.ecommerce) { 165 window.dataLayer.push({ ecommerce: null }); 166 } 167 window.dataLayer.push(event); 168 169 if (nonce) { 170 ledger.push(nonce); 171 while (ledger.length > TY_LEDGER_CAP) { 172 ledger.shift(); 173 } 174 try { 175 window.localStorage.setItem(TY_LEDGER_KEY, JSON.stringify(ledger)); 176 } catch (e) {} 177 } 178 try { window.sessionStorage.removeItem(TY_STASH_KEY); } catch (e) {} 179 } 180 181 window.stashThankYouPurchase = stashThankYouPurchase; 182 window.fireThankYouPurchaseIfStashed = fireThankYouPurchaseIfStashed; 183 184 // Listen for cross-origin iframe tracking events (e.g. contact_us iframe). 185 window.addEventListener("message", function (e) { 186 if (e.data && e.data.type === "generate_lead" && e.data.payload) { 187 pushTrackingEvent("generate_lead", e.data.payload); 188 } 189 }); 190 191 function autoTrackPageView() { 192 var pathName = (window.location && window.location.pathname) || ""; 193 pushTrackingEvent("page_view", { 194 page_path: pathName, 195 page_name: pathName.replace(/^\/+|\/+$/g, "") || "home", 196 }); 197 } 198 199 if (document.readyState === "loading") { 200 document.addEventListener("DOMContentLoaded", autoTrackPageView); 201 } else { 202 autoTrackPageView(); 203 } 204})(window);
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.