(function() { "use strict"; var TRACKING_ID = "{{ $trackingId }}"; var COLLECT_URL = "{{ $collectUrl }}"; var SESSION_TIMEOUT = 30 * 60 * 1000; var STORAGE_PREFIX = "_ta_"; // ---- Utilities ---- function generateId() { try { var arr = new Uint8Array(16); crypto.getRandomValues(arr); return Array.from(arr, function(b) { return b.toString(16).padStart(2, "0"); }).join(""); } catch(e) { return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0; return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); }); } } function getStore(key) { try { return localStorage.getItem(STORAGE_PREFIX + key); } catch(e) { return null; } } function setStore(key, value) { try { localStorage.setItem(STORAGE_PREFIX + key, value); } catch(e) {} } function now() { return Date.now(); } // ---- Visitor ID ---- function getVisitorId() { var vid = getStore("vid"); if (!vid) { vid = generateId(); setStore("vid", vid); } return vid; } // ---- Session ID ---- function getSessionId() { var sid = getStore("sid"); var ts = parseInt(getStore("sid_ts"), 10) || 0; if (!sid || (now() - ts) > SESSION_TIMEOUT) { sid = generateId(); setStore("sid", sid); setStore("is_new_session", "1"); } setStore("sid_ts", String(now())); return sid; } function isNewSession() { var val = getStore("is_new_session"); setStore("is_new_session", "0"); return val === "1"; } // ---- Device / Browser Detection ---- function detectDevice() { var ua = navigator.userAgent || ""; if (/Mobi|Android.*Mobile|iPhone|iPod/i.test(ua)) return "mobile"; if (/iPad|Android(?!.*Mobile)|Tablet/i.test(ua)) return "tablet"; return "desktop"; } function detectBrowser() { var ua = navigator.userAgent || ""; var m; if ((m = ua.match(/Edg(?:e|A|iOS)?\/(\S+)/))) return { name: "Edge", version: m[1] }; if ((m = ua.match(/OPR\/(\S+)/))) return { name: "Opera", version: m[1] }; if ((m = ua.match(/Chrome\/(\S+)/)) && !/Edg/.test(ua)) return { name: "Chrome", version: m[1] }; if ((m = ua.match(/Safari\/(\S+)/)) && !/Chrome/.test(ua)) { var v = ua.match(/Version\/(\S+)/); return { name: "Safari", version: v ? v[1] : m[1] }; } if ((m = ua.match(/Firefox\/(\S+)/))) return { name: "Firefox", version: m[1] }; return { name: "Other", version: "" }; } function detectOS() { var ua = navigator.userAgent || ""; var m; if ((m = ua.match(/Windows NT (\d+\.\d+)/))) return { name: "Windows", version: m[1] }; if ((m = ua.match(/Mac OS X (\d+[._]\d+[._]?\d*)/))) return { name: "macOS", version: m[1].replace(/_/g, ".") }; if ((m = ua.match(/Android (\d+(\.\d+)*)/))) return { name: "Android", version: m[1] }; if (/iPhone|iPad|iPod/.test(ua)) { m = ua.match(/OS (\d+[_]\d+[_]?\d*)/); return { name: "iOS", version: m ? m[1].replace(/_/g, ".") : "" }; } if (/Linux/.test(ua)) return { name: "Linux", version: "" }; return { name: "Other", version: "" }; } // ---- UTM Parameters ---- function getUtmParams() { var params = {}; try { var sp = new URLSearchParams(window.location.search); ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"].forEach(function(key) { var val = sp.get(key); if (val) params[key] = val; }); } catch(e) {} return params; } function getReferrerDomain() { try { if (!document.referrer) return ""; return new URL(document.referrer).hostname; } catch(e) { return ""; } } // ---- Send Beacon ---- function send(data) { var vid = getVisitorId(); var sid = getSessionId(); var browser = detectBrowser(); var os = detectOS(); var utm = getUtmParams(); var payload = Object.assign({ tid: TRACKING_ID, vid: vid, sid: sid, is_new_session: isNewSession(), url: window.location.href, path: window.location.pathname, title: document.title, referrer: document.referrer, ref_domain: getReferrerDomain(), sr: screen.width + "x" + screen.height, lang: (navigator.language || "").substring(0, 10), device: detectDevice(), browser: browser.name, browser_version: browser.version.substring(0, 20), os: os.name, os_version: os.version.substring(0, 20), }, utm, data); var blob = new Blob([JSON.stringify(payload)], { type: "application/json" }); if (navigator.sendBeacon) { navigator.sendBeacon(COLLECT_URL, blob); } else { var xhr = new XMLHttpRequest(); xhr.open("POST", COLLECT_URL, true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify(payload)); } } // ---- Time on Page ---- var pageEnteredAt = now(); function getTimeOnPage() { return Math.round((now() - pageEnteredAt) / 1000); } // ---- Track Page View ---- function trackPageView() { pageEnteredAt = now(); send({ type: "pageview" }); } // ---- Track Event ---- function trackEvent(name, params) { params = params || {}; send({ type: "event", event_name: name, event_category: params.category || null, event_action: params.action || null, event_label: params.label || null, event_value: params.value != null ? params.value : null, event_properties: params.properties || null, }); } // ---- Send time-on-page before leaving ---- function onPageLeave() { var top = getTimeOnPage(); if (top > 0) { send({ type: "event", event_name: "page_leave", event_properties: { time_on_page: top } }); } } document.addEventListener("visibilitychange", function() { if (document.visibilityState === "hidden") onPageLeave(); }); // ---- SPA Support: listen for pushState / popstate ---- var originalPushState = history.pushState; history.pushState = function() { onPageLeave(); originalPushState.apply(history, arguments); trackPageView(); }; window.addEventListener("popstate", function() { onPageLeave(); trackPageView(); }); // ---- Public API ---- window.ta = function(command, arg1, arg2) { switch(command) { case "event": trackEvent(arg1, arg2); break; case "pageview": trackPageView(); break; } }; // ---- Auto-track initial page view ---- trackPageView(); })();