PageSourceSearch

https://positionstack.com/js/shared/gtm_tracking.js

js positionstack.com collected 2026-10-03 21:37:51 UTC 6,787 bytes, 204 lines download raw bytes

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.