1// ============================================================ 2// KIRA RESEARCH â /js/track.js 3// First-party funnel events for /en/admin/funnel. Privacy by construction: 4// - no cookies, no IP, no email: only a random per-tab id (sessionStorage) + path + coarse source 5// - respects Do Not Track; does nothing on admin, studio, previews and localhost 6// Events: pageview (on load), search (library search with 3+ letters, once per query), 7// request_email / waitlist / lead (a successful form POST) â all detected by watching 8// fetch, so no form on the site needs to be edited. 9// ============================================================ 10(function () { 11 'use strict'; 12 if (window.kiraTrack) return; 13 var noop = function () {}; 14 var loc = window.location; 15 var onProd = /^(www\.)?kiraresearch\.com$/.test(loc.hostname); 16 var dnt = navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1'; 17 if (!onProd || dnt || /^\/(en\/)?admin(\/|$)/.test(loc.pathname)) { window.kiraTrack = noop; return; } 18 19 var sid; 20 try { 21 sid = sessionStorage.getItem('kira_sid'); 22 if (!sid) { sid = Math.random().toString(36).slice(2, 12) + Date.now().toString(36); sessionStorage.setItem('kira_sid', sid); } 23 } catch (_e) { sid = Math.random().toString(36).slice(2, 12) + Date.now().toString(36); } 24 25 var refHost = ''; 26 try { refHost = document.referrer ? new URL(document.referrer).hostname : ''; } catch (_e) { /* ignore */ } 27 var utm = ''; 28 try { utm = (new URLSearchParams(loc.search).get('utm_source') || '').slice(0, 40); } catch (_e) { /* ignore */ } 29 30 function send(event, extra) { 31 var body = JSON.stringify(Object.assign({ e: event, s: sid, p: loc.pathname, r: refHost, u: utm }, extra || {})); 32 try { 33 if (!(navigator.sendBeacon && navigator.sendBeacon('/api/track', new Blob([body], { type: 'application/json' })))) { 34 fetch('/api/track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body, keepalive: true }).catch(noop); 35 } 36 } catch (_e) { /* tracking must never break the page */ } 37 } 38 window.kiraTrack = send; 39 40 // Watch fetch: successful signup POSTs and library searches. 41 var SIGNUP = { '/api/topic-request': 'request_email', '/api/waitlist': 'waitlist', '/api/leads': 'lead' }; 42 var seenQueries = {}; 43 var realFetch = window.fetch; 44 if (typeof realFetch === 'function') { 45 window.fetch = function (input, init) { 46 var p = realFetch.apply(this, arguments); 47 try { 48 var url = String(typeof input === 'string' ? input : (input && input.url) || ''); 49 var path = url.replace(/^https?:\/\/[^/]+/, '').split('?')[0]; 50 var method = String((init && init.method) || (input && input.method) || 'GET').toUpperCase(); 51 if (method === 'POST' && SIGNUP[path]) { 52 p.then(function (r) { if (r && r.ok) send(SIGNUP[path]); }, noop); 53 } else if (method === 'GET' && path === '/api/library-list') { 54 var q = (new URLSearchParams(url.split('?')[1] || '').get('q') || '').trim().toLowerCase(); 55 if (q.length >= 3 && !seenQueries[q]) { 56 seenQueries[q] = 1; 57 p.then(function (r) { 58 if (r && r.ok) r.clone().json().then(function (d) { send('search', { h: (d && d.total || 0) > 0 }); }, noop); 59 }, noop); 60 } 61 } 62 } catch (_e) { /* ignore */ } 63 return p; 64 }; 65 } 66 67 send('pageview'); 68})();
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.