1/** 2 * omniaTrack.js â first-party visitor tracking for Omnia sites (Phase 1) 3 * 4 * Sets a permanent OMNIA_VID cookie per browser and beacons events to 5 * /ajax/track.cfm on the same domain (no third parties, no config). 6 * 7 * Auto-tracked: 8 * - pageview on every page load (with referrer + utm params) 9 * - engaged time (visible, active seconds) sent as "leave" chunks 10 * - form_submit on any <form> submission 11 * - identity handoff: trkid/indvtrkid in email links link the browser 12 * to the maillist contact immediately 13 * 14 * Custom events from any page or snip: 15 * omniaTrack('product_view', { productid: 123, name: 'Widget' }); 16 */ 17(function () { 18 'use strict'; 19 20 if (navigator.webdriver) return; // skip headless browsers / test runners 21 22 var ENDPOINT = '/ajax/track.cfm'; 23 var COOKIE = 'OMNIA_VID'; 24 25 function makeUuid() { 26 if (window.crypto && crypto.randomUUID) return crypto.randomUUID(); 27 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 28 var r = (Math.random() * 16) | 0; 29 return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); 30 }); 31 } 32 33 function getCookie(name) { 34 var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); 35 return m ? decodeURIComponent(m[1]) : ''; 36 } 37 38 var vid = getCookie(COOKIE); 39 if (!vid) { 40 vid = makeUuid(); 41 document.cookie = COOKIE + '=' + vid + '; path=/; max-age=63072000; SameSite=Lax'; 42 } 43 44 var qs = new URLSearchParams(location.search); 45 46 function send(eventType, extra) { 47 var p = new URLSearchParams(); 48 p.set('e', eventType); 49 p.set('vid', vid); 50 p.set('u', (location.pathname + location.search).slice(0, 500)); 51 p.set('t', (document.title || '').slice(0, 300));
52 p.set('r', (document.referrer || '').slice(0, 500)); 53 ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach(function (k) { 54 var v = qs.get(k); 55 if (v) p.set(k, v.slice(0, 150)); 56 }); 57 var tid = qs.get('trkid') || qs.get('indvtrkid'); 58 if (tid) p.set('tid', tid.slice(0, 40)); 59 if (extra) { 60 Object.keys(extra).forEach(function (k) { p.set(k, extra[k]); }); 61 } 62 if (navigator.sendBeacon) { 63 navigator.sendBeacon(ENDPOINT, p); 64 } else { 65 fetch(ENDPOINT, { method: 'POST', body: p, keepalive: true, credentials: 'same-origin' }); 66 } 67 } 68 69 // ---- pageview ---- 70 send('pageview'); 71 72 // ---- engaged time: count seconds while tab is visible and user is active ---- 73 var engaged = 0; 74 var lastActive = Date.now(); 75 var ticker = null; 76 77 ['scroll', 'mousemove', 'keydown', 'touchstart', 'click'].forEach(function (evt) { 78 window.addEventListener(evt, function () { lastActive = Date.now(); }, { passive: true }); 79 }); 80 81 function startTicker() { 82 if (ticker) return; 83 ticker = setInterval(function () { 84 if (Date.now() - lastActive < 30000) engaged++; 85 }, 1000); 86 } 87 function stopTicker() { 88 clearInterval(ticker); 89 ticker = null; 90 } 91 92 function flushEngagement() { 93 if (engaged > 0) { 94 send('leave', { sec: Math.min(engaged, 1800) }); 95 engaged = 0; 96 } 97 } 98 99 document.addEventListener('visibilitychange', function () { 100 if (document.hidden) { 101 stopTicker(); 102 flushEngagement(); 103 } else { 104 startTicker(); 105 } 106 }); 107 window.addEventListener('pagehide', function () { flushEngagement(true); }); 108 if (!document.hidden) startTicker(); 109 110 // ---- form submissions (any form, including custom_forms output) ---- 111 document.addEventListener('submit', function (ev) { 112 var f = ev.target; 113 if (!f || f.nodeName !== 'FORM') return; 114 var label = f.getAttribute('id') || f.getAttribute('name') || f.getAttribute('action') || 'form'; 115 send('form_submit', { meta: JSON.stringify({ form: String(label).slice(0, 200) }) }); 116 }, true); 117 118 // ---- public API for snips / product pages / anything custom ---- 119 window.omniaTrack = function (eventType, meta) { 120 send(String(eventType || 'custom').replace(/[^a-zA-Z0-9_]/g, '').slice(0, 30) || 'custom', 121 meta ? { meta: JSON.stringify(meta).slice(0, 2000) } : null); 122 }; 123})();
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.