/** * QA Click Tracker v4 * * No session id. Bubble always gives us a unique user id (including for * logged-out visitors), so that is the grouping key - ordered by timestamp * downstream. Nothing is stored in the browser, so remounts, reloads and * view transitions are all irrelevant by construction. * * Flow boundaries are decided during analysis (entry action -> terminal * action), not here. This file just reports what happened. */ (function () { const BUBBLE_USER_ID = "Current User's unique id"; const BUBBLE_USER_EMAIL = "Current User's email"; const CONFIG = { BASE_URL: 'https://app.arkhitect.net/version-REPLACE_WITH_VERSION/api/1.1/wf', WORKFLOW_NAME: 'log_qa_event', PRODUCT: 'Arkhitect', ID_PREFIX: 'ACT-', TRACK_FIELD_INPUT: true, TRACK_PAGE_VIEWS: true, }; const ENDPOINT = CONFIG.BASE_URL.replace(/\/+$/, '') + '/' + CONFIG.WORKFLOW_NAME; function findActionId(el) { while (el && el !== document.body) { if (el.id && el.id.indexOf(CONFIG.ID_PREFIX) === 0) return el.id; el = el.parentElement; } return null; } function send(actionId, eventType, value, useBeacon) { const body = JSON.stringify({ user_id: BUBBLE_USER_ID, email: BUBBLE_USER_EMAIL, action_id: actionId, event_type: eventType, page: window.location.pathname + window.location.search, ts: new Date().toISOString(), product: CONFIG.PRODUCT, value: value === undefined ? null : value, }); try { if (useBeacon && navigator.sendBeacon) { navigator.sendBeacon(ENDPOINT, new Blob([body], { type: 'application/json' })); return; } fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body, keepalive: true, }).catch(function () {}); } catch (e) { /* never break a real interaction */ } } document.addEventListener('click', function (evt) { const actionId = findActionId(evt.target); if (actionId) send(actionId, 'click', undefined, true); }, true); if (CONFIG.TRACK_FIELD_INPUT) { document.addEventListener('change', function (evt) { const el = evt.target; const tag = (el.tagName || '').toLowerCase(); if (tag !== 'input' && tag !== 'select' && tag !== 'textarea') return; const actionId = findActionId(el); if (!actionId) return; // Bounded choices drive UI branching and are safe to log. Free text is // never sent - it may contain PII on a shared endpoint. const type = (el.type || 'text').toLowerCase(); let value = null; if (tag === 'select') value = el.value; else if (type === 'checkbox') value = el.checked; else if (type === 'radio') { if (!el.checked) return; value = el.value; } send(actionId, 'input', value, false); }, true); } if (CONFIG.TRACK_PAGE_VIEWS) send(null, 'pageview', undefined, false); window.addEventListener('pagehide', function () { send(null, 'session_end', undefined, true); }); })();