243 lines
7.1 KiB
PHP
243 lines
7.1 KiB
PHP
(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 Data ----
|
|
|
|
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);
|
|
|
|
try {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("POST", COLLECT_URL, true);
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
|
xhr.send(JSON.stringify(payload));
|
|
} catch(e) {
|
|
// Silent fail
|
|
}
|
|
}
|
|
|
|
// ---- 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();
|
|
})();
|