PageSourceSearch

https://kiraresearch.com/js/track.js

js kiraresearch.com collected 2026-10-09 18:23:59 UTC 3,453 bytes, 68 lines download raw bytes

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.