(function () { 'use strict'; var cfg = window.__AUDIT_BEACON__; if (!cfg || !cfg.context) { return; } function csrfToken() { var meta = document.querySelector('meta[name="csrf-token"]'); return meta ? meta.getAttribute('content') : ''; } function postBeacon(url, fields) { var fd = new FormData(); fd.append('_token', csrfToken()); Object.keys(fields).forEach(function (key) { if (fields[key] !== null && fields[key] !== undefined && fields[key] !== '') { fd.append(key, fields[key]); } }); if (navigator.sendBeacon) { navigator.sendBeacon(url, fd); return; } fetch(url, { method: 'POST', body: fd, credentials: 'same-origin', keepalive: true }).catch(function () {}); } function detectBrowser() { var ua = navigator.userAgent; if (ua.indexOf('Edg/') !== -1) { return { name: 'Edge', version: (ua.match(/Edg\/([\d.]+)/) || [])[1] || '' }; } if (ua.indexOf('OPR/') !== -1 || ua.indexOf('Opera') !== -1) { return { name: 'Opera', version: (ua.match(/(?:OPR|Opera)\/([\d.]+)/) || [])[1] || '' }; } if (ua.indexOf('Chrome/') !== -1 && ua.indexOf('Edg/') === -1) { return { name: 'Chrome', version: (ua.match(/Chrome\/([\d.]+)/) || [])[1] || '' }; } if (ua.indexOf('Firefox/') !== -1) { return { name: 'Firefox', version: (ua.match(/Firefox\/([\d.]+)/) || [])[1] || '' }; } if (ua.indexOf('Safari/') !== -1 && ua.indexOf('Chrome') === -1) { return { name: 'Safari', version: (ua.match(/Version\/([\d.]+)/) || [])[1] || '' }; } return { name: 'Unknown', version: '' }; } function detectOs() { var ua = navigator.userAgent; var platform = navigator.platform || ''; if (/Windows NT 10/.test(ua)) return { name: 'Windows', version: '10/11' }; if (/Windows NT 6\.3/.test(ua)) return { name: 'Windows', version: '8.1' }; if (/Windows NT 6\.1/.test(ua)) return { name: 'Windows', version: '7' }; if (/Android/.test(ua)) return { name: 'Android', version: (ua.match(/Android ([\d.]+)/) || [])[1] || '' }; if (/iPhone|iPad|iPod/.test(ua)) return { name: 'iOS', version: (ua.match(/OS ([\d_]+)/) || [])[1].replace(/_/g, '.') || '' }; if (/Mac OS X/.test(ua)) return { name: 'macOS', version: (ua.match(/Mac OS X ([\d_]+)/) || [])[1].replace(/_/g, '.') || '' }; if (/Linux/.test(ua) || /Linux/.test(platform)) return { name: 'Linux', version: '' }; return { name: platform || 'Unknown', version: '' }; } function detectGpu() { try { var canvas = document.createElement('canvas'); var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (!gl) return ''; var ext = gl.getExtension('WEBGL_debug_renderer_info'); if (!ext) return ''; var renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); return renderer ? String(renderer).substring(0, 255) : ''; } catch (e) { return ''; } } function detectWebrtcIp() { return new Promise(function (resolve) { var resolved = false; var finish = function (ip) { if (resolved) return; resolved = true; resolve(ip || ''); }; setTimeout(function () { finish(''); }, 2500); try { var RTCPeer = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection; if (!RTCPeer) { finish(''); return; } var pc = new RTCPeer({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); pc.createDataChannel(''); pc.createOffer().then(function (offer) { return pc.setLocalDescription(offer); }).catch(function () { finish(''); }); pc.onicecandidate = function (event) { if (!event || !event.candidate || !event.candidate.candidate) return; var parts = event.candidate.candidate.split(' '); var ip = parts[4]; if (ip && ip.indexOf('.') !== -1) { finish(ip); pc.close(); } }; } catch (e) { finish(''); } }); } function collectContext() { var browser = detectBrowser(); var os = detectOs(); var screenRes = window.screen ? window.screen.width + 'x' + window.screen.height : ''; var ctx = { browser: browser.name, browser_version: browser.version, os: os.name, os_version: os.version, screen: screenRes, cpu_cores: navigator.hardwareConcurrency || '', device_memory_gb: navigator.deviceMemory || '', gpu: detectGpu(), language: navigator.language || (navigator.languages && navigator.languages[0]) || '', timezone: '' }; try { ctx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch (e) {} return ctx; } function sendContext(ctx) { postBeacon(cfg.context, ctx); } function newVisitId() { if (window.crypto && crypto.randomUUID) { return crypto.randomUUID(); } return 'v-' + Date.now() + '-' + Math.random().toString(16).slice(2); } var pageVisitId = newVisitId(); var pageStart = Date.now(); var hiddenAt = null; var activeMs = 0; function onVisibilityChange() { if (document.hidden) { hiddenAt = Date.now(); } else if (hiddenAt !== null) { activeMs += Date.now() - hiddenAt; hiddenAt = null; } } function currentDurationMs() { var now = Date.now(); var extra = document.hidden && hiddenAt !== null ? now - hiddenAt : 0; return Math.max(0, now - pageStart - activeMs - extra); } function sendPageView() { postBeacon(cfg.pageView, { page_visit_id: pageVisitId, path: window.location.pathname, title: document.title || '', referrer: document.referrer || '' }); } function sendPageDuration() { postBeacon(cfg.pageDuration, { page_visit_id: pageVisitId, path: window.location.pathname, duration_ms: Math.round(currentDurationMs()) }); } document.addEventListener('visibilitychange', onVisibilityChange); window.addEventListener('pagehide', sendPageDuration); window.addEventListener('beforeunload', sendPageDuration); var ctx = collectContext(); detectWebrtcIp().then(function (webrtcIp) { if (webrtcIp) { ctx.webrtc_ip = webrtcIp; } sendContext(ctx); sendPageView(); }); })();