/** * QA Click Tracker — 8Count * Drop into a Bubble HTML element on every page. Captures clicks (and * optionally field fill-ins) on any element whose ID starts with ID_PREFIX * (your existing Action ID convention, e.g. "ACT-SIGNUP-GOOGLE"), grouped * into per-browser-tab sessions. */ (function () { // ---- Replace these two with Bubble's dynamic data before pasting ---- // e.g. var BUBBLE_USER_TYPE = "Current User's Type"; // var BUBBLE_USER_EMAIL = "Current User's email"; var BUBBLE_USER_TYPE = ''; var BUBBLE_USER_EMAIL = ''; // ---- Config: this is the only other block you should need to touch ---- var CONFIG = { // Swap this to point tracking at a different app entirely, e.g. when // moving off the shared collector or standing up a dedicated one. BASE_URL: '', // Name of the backend workflow API endpoint on that app. WORKFLOW_NAME: 'log_qa_event', // Which product this page belongs to — every event is tagged with this, // so one shared collector app can hold data from 8Count, Arkhitect, etc. // without the rows getting mixed up. PRODUCT: '8Count', // Only elements whose ID starts with this get tracked. ID_PREFIX: 'ACT-', // Also log when a tagged input/select/textarea is filled in (not just // clicked). Never sends the actual value — only that it happened — // since this endpoint is shared across products and may see fields // like payment or contact info. TRACK_FIELD_INPUT: true, }; var SESSION_KEY = 'qa_session_id'; var ID_PREFIX = CONFIG.ID_PREFIX; var ENDPOINT = CONFIG.BASE_URL.replace(/\/+$/, '') + '/' + CONFIG.WORKFLOW_NAME; function getSessionId() { var id = sessionStorage.getItem(SESSION_KEY); if (!id) { id = 'sess-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10); sessionStorage.setItem(SESSION_KEY, id); } return id; } function findActionId(el) { while (el && el !== document.body) { if (el.id && el.id.indexOf(ID_PREFIX) === 0) return el.id; el = el.parentElement; } return null; } function send(actionId, eventType, page) { var payload = { session_id: getSessionId(), action_id: actionId, event_type: eventType, page: page, ts: new Date().toISOString(), product: CONFIG.PRODUCT, user_type: BUBBLE_USER_TYPE, email: BUBBLE_USER_EMAIL, }; try { fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), keepalive: true, // survives the page navigation the click may cause }).catch(function () {}); } catch (e) { /* never let tracking break a real user interaction */ } } document.addEventListener( 'click', function (evt) { var actionId = findActionId(evt.target); if (!actionId) return; // untagged element — ignore, don't touch Bubble's own logic send(actionId, 'click', window.location.pathname); }, true ); if (CONFIG.TRACK_FIELD_INPUT) { document.addEventListener( 'change', // fires once the field loses focus with a new value — not per keystroke function (evt) { var tag = (evt.target.tagName || '').toLowerCase(); if (tag !== 'input' && tag !== 'select' && tag !== 'textarea') return; var actionId = findActionId(evt.target); if (!actionId) return; send(actionId, 'input', window.location.pathname); }, true ); } })();