(function () { 'use strict'; var config = window.WPGearedCWVMonitor || {}; var endpoint = config.endpoint; var siteId = config.siteId; var siteKey = config.siteKey; var sampleRate = Math.max(0, Math.min(1, Number(config.sampleRate || 1))); var queue = []; var flushTimer = null; if (!endpoint || !siteId || !siteKey || sampleRate <= 0) { debug('Tracker disabled: missing endpoint/site credentials or sample rate is zero.'); return; } if (!isSampled()) { debug('Visitor skipped by sample rate.'); return; } loadWebVitals() .then(function (webVitals) { if (!webVitals || !webVitals.onLCP || !webVitals.onINP || !webVitals.onCLS) { throw new Error('web-vitals attribution build did not expose onLCP/onINP/onCLS.'); } var options = { reportAllChanges: false }; webVitals.onLCP(handleMetric, options); webVitals.onINP(handleMetric, options); webVitals.onCLS(handleMetric, options); debug('Core Web Vitals observers registered.'); }) .catch(function (error) { debug('Unable to load web-vitals.', error); }); document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'hidden') { flush(true); } }); window.addEventListener('pagehide', function () { flush(true); }); function handleMetric(metric) { var event = { metric: metric.name, value: round(metric.value), delta: round(metric.delta || 0), rating: metric.rating, webVitalsId: metric.id, navigationType: metric.navigationType || '', debugTarget: extractDebugTarget(metric), attribution: config.includeAttribution ? sanitizeAttribution(metric.attribution || {}) : {}, url: getPageUrl(), pageTitle: config.pageTitle || document.title || '', sessionId: getSessionId(), visitorId: getVisitorId(), device: getDevice(), viewport: getViewport(), connection: getConnection(), referrerHost: getReferrerHost(), visibilityState: document.visibilityState, occurredAt: new Date().toISOString(), }; queue.push(event); if (metric.rating === 'poor') { flush(false); return; } clearTimeout(flushTimer); flushTimer = setTimeout(function () { flush(false); }, 2500); } function flush(useBeacon) { if (!queue.length) { return; } var events = queue.splice(0, queue.length); var body = JSON.stringify({ siteId: siteId, siteKey: siteKey, events: events, }); if (useBeacon && navigator.sendBeacon) { var blob = new Blob([body], { type: 'application/json' }); if (navigator.sendBeacon(endpoint, blob)) { debug('Events sent with beacon.', events.length); return; } } fetch(endpoint, { method: 'POST', mode: 'cors', keepalive: body.length < 60000, headers: { 'Content-Type': 'application/json' }, body: body, }).catch(function (error) { debug('Event send failed.', error); }); } function loadWebVitals() { if (window.webVitals) { return Promise.resolve(window.webVitals); } return new Promise(function (resolve, reject) { var script = document.createElement('script'); script.src = config.webVitalsUrl || 'https://unpkg.com/web-vitals@5.3.0/dist/web-vitals.attribution.iife.js'; script.async = true; script.crossOrigin = 'anonymous'; script.onload = function () { resolve(window.webVitals); }; script.onerror = function () { reject(new Error('Failed to load ' + script.src)); }; document.head.appendChild(script); }); } function isSampled() { var key = 'wpgeared_cwv_sampled'; var existing = sessionStorage.getItem(key); if (existing !== null) { return existing === '1'; } var sampled = Math.random() <= sampleRate; sessionStorage.setItem(key, sampled ? '1' : '0'); return sampled; } function extractDebugTarget(metric) { var attribution = metric.attribution || {}; return clean( attribution.element || attribution.eventTarget || attribution.interactionTarget || attribution.largestShiftTarget || attribution.largestShiftSource || attribution.url || '' ); } function sanitizeAttribution(input) { var output = {}; Object.keys(input || {}).slice(0, 35).forEach(function (key) { var value = input[key]; if (value && value.nodeType === 1) { output[key] = selector(value); } else if (Array.isArray(value)) { output[key] = value.slice(0, 8).map(function (item) { return item && item.nodeType === 1 ? selector(item) : clean(item); }); } else if (value && typeof value === 'object') { output[key] = sanitizeAttribution(value); } else if (typeof value === 'number') { output[key] = round(value); } else if (typeof value === 'boolean') { output[key] = value; } else { output[key] = clean(value); } }); return output; } function selector(element) { if (!element || element.nodeType !== 1) { return ''; } var tag = element.tagName.toLowerCase(); if (element.id) { return tag + '#' + element.id; } var className = String(element.className || '') .split(/\s+/) .filter(Boolean) .slice(0, 3) .join('.'); return clean(tag + (className ? '.' + className : '')); } function getPageUrl() { var url = new URL(window.location.href); url.hash = ''; if (!config.includeQueryString) { url.search = ''; } return url.toString(); } function getViewport() { return { width: window.innerWidth, height: window.innerHeight, dpr: window.devicePixelRatio || 1, }; } function getDevice() { var width = window.innerWidth || 0; if (width < 768) { return 'mobile'; } if (width < 1100) { return 'tablet'; } return 'desktop'; } function getConnection() { var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; if (!connection) { return {}; } return { effectiveType: clean(connection.effectiveType), rtt: round(connection.rtt || 0), downlink: round(connection.downlink || 0), saveData: Boolean(connection.saveData), }; } function getReferrerHost() { if (!document.referrer) { return ''; } try { return new URL(document.referrer).host; } catch (error) { return ''; } } function getVisitorId() { return getStoredId(localStorage, 'wpgeared_cwv_visitor_id'); } function getSessionId() { return getStoredId(sessionStorage, 'wpgeared_cwv_session_id'); } function getStoredId(storage, key) { try { var existing = storage.getItem(key); if (existing) { return existing; } var id = 'wpg_' + Math.random().toString(36).slice(2) + Date.now().toString(36); storage.setItem(key, id); return id; } catch (error) { return 'wpg_' + Math.random().toString(36).slice(2); } } function clean(value) { return String(value == null ? '' : value) .replace(/[\u0000-\u001f\u007f]/g, ' ') .replace(/\s+/g, ' ') .trim() .slice(0, 500); } function round(value) { var number = Number(value); if (!Number.isFinite(number)) { return 0; } return Math.round(number * 1000) / 1000; } function debug() { if (config.debug && window.console) { console.log.apply(console, ['[WPGeared CWV]'].concat(Array.prototype.slice.call(arguments))); } } })();