(function() { 'use strict'; const TRACKER_URL = 'https://t.query.domains'; let sessionId = null; let pageViewId = null; // Generate session ID function generateSessionId() { const stored = sessionStorage.getItem('tracker_session_id'); if (stored) return stored; const newId = Date.now() + '-' + Math.random().toString(36).substr(2, 9); sessionStorage.setItem('tracker_session_id', newId); return newId; } // Collect metadata to help detect automation/bots async function collectMetadata() { function safeMatchMedia(query) { try { return typeof window.matchMedia === 'function' ? window.matchMedia(query).matches : null; } catch (error) { return null; } } function hashString(value) { let hash = 2166136261; for (let i = 0; i < value.length; i++) { hash ^= value.charCodeAt(i); hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); } return (hash >>> 0).toString(16); } function getCanvasFingerprint() { try { const canvas = document.createElement('canvas'); canvas.width = 240; canvas.height = 60; const ctx = canvas.getContext('2d'); if (!ctx) return null; ctx.textBaseline = 'top'; ctx.font = "16px 'Arial'"; ctx.fillStyle = '#f60'; ctx.fillRect(125, 1, 62, 20); ctx.fillStyle = '#069'; ctx.fillText('Cwm fjordbank glyphs vext quiz', 2, 15); ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; ctx.fillText('Cwm fjordbank glyphs vext quiz', 4, 17); const dataUrl = canvas.toDataURL(); return { hash: hashString(dataUrl), }; } catch (error) { return null; } } function getWebglFingerprint() { try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (!gl) return null; const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); const vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : null; const renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : null; return { vendor, renderer, version: gl.getParameter(gl.VERSION), shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION), maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE), maxViewportDims: gl.getParameter(gl.MAX_VIEWPORT_DIMS), maxCombinedTextureImageUnits: gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ), aliasedLineWidthRange: gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE), aliasedPointSizeRange: gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE), }; } catch (error) { return null; } } function getWebgl2Fingerprint() { try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl2'); if (!gl) return null; return { version: gl.getParameter(gl.VERSION), shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION), max3dTextureSize: gl.getParameter(gl.MAX_3D_TEXTURE_SIZE), maxArrayTextureLayers: gl.getParameter(gl.MAX_ARRAY_TEXTURE_LAYERS), maxColorAttachments: gl.getParameter(gl.MAX_COLOR_ATTACHMENTS), maxDrawBuffers: gl.getParameter(gl.MAX_DRAW_BUFFERS), maxSamples: gl.getParameter(gl.MAX_SAMPLES), maxTransformFeedbackInterleavedComponents: gl.getParameter( gl.MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS ), maxTransformFeedbackSeparateAttribs: gl.getParameter( gl.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS ), maxTransformFeedbackSeparateComponents: gl.getParameter( gl.MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS ), maxUniformBlockSize: gl.getParameter(gl.MAX_UNIFORM_BLOCK_SIZE), maxUniformBufferBindings: gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS), maxVertexUniformBlocks: gl.getParameter(gl.MAX_VERTEX_UNIFORM_BLOCKS), maxFragmentUniformBlocks: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_BLOCKS), maxVertexOutputComponents: gl.getParameter(gl.MAX_VERTEX_OUTPUT_COMPONENTS), maxFragmentInputComponents: gl.getParameter(gl.MAX_FRAGMENT_INPUT_COMPONENTS), maxVaryingComponents: gl.getParameter(gl.MAX_VARYING_COMPONENTS), maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS), maxElementsVertices: gl.getParameter(gl.MAX_ELEMENTS_VERTICES), maxElementsIndices: gl.getParameter(gl.MAX_ELEMENTS_INDICES), maxTextureLodBias: gl.getParameter(gl.MAX_TEXTURE_LOD_BIAS), maxProgramTexelOffset: gl.getParameter(gl.MAX_PROGRAM_TEXEL_OFFSET), minProgramTexelOffset: gl.getParameter(gl.MIN_PROGRAM_TEXEL_OFFSET), }; } catch (error) { return null; } } function getTimezoneInfo() { let timeZone = null; try { timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || null; } catch (error) { timeZone = null; } return { timeZone, offsetMinutes: new Date().getTimezoneOffset() }; } async function getUserAgentData() { if (!navigator.userAgentData) return null; const uaCh = { brands: navigator.userAgentData.brands || null, mobile: typeof navigator.userAgentData.mobile === 'boolean' ? navigator.userAgentData.mobile : null, platform: navigator.userAgentData.platform || null, architecture: null, bitness: null, model: null, platformVersion: null, uaFullVersion: null, fullVersionList: null, wow64: null }; if (typeof navigator.userAgentData.getHighEntropyValues === 'function') { try { const high = await navigator.userAgentData.getHighEntropyValues([ 'architecture', 'bitness', 'model', 'platformVersion', 'uaFullVersion', 'fullVersionList', 'wow64' ]); uaCh.architecture = high.architecture || null; uaCh.bitness = high.bitness || null; uaCh.model = high.model || null; uaCh.platformVersion = high.platformVersion || null; uaCh.uaFullVersion = high.uaFullVersion || null; uaCh.fullVersionList = high.fullVersionList || null; uaCh.wow64 = typeof high.wow64 === 'boolean' ? high.wow64 : null; } catch (error) { return uaCh; } } return uaCh; } function getDeviceCapabilities() { return { deviceMemory: typeof navigator.deviceMemory === 'number' ? navigator.deviceMemory : null, hardwareConcurrency: typeof navigator.hardwareConcurrency === 'number' ? navigator.hardwareConcurrency : null, maxTouchPoints: typeof navigator.maxTouchPoints === 'number' ? navigator.maxTouchPoints : null, pointerFine: safeMatchMedia('(pointer: fine)'), pointerCoarse: safeMatchMedia('(pointer: coarse)'), hoverSupported: safeMatchMedia('(hover: hover)'), hoverNone: safeMatchMedia('(hover: none)'), devicePixelRatio: typeof window.devicePixelRatio === 'number' ? window.devicePixelRatio : null, colorDepth: window.screen ? window.screen.colorDepth : null, pixelDepth: window.screen ? window.screen.pixelDepth : null }; } function testStorage(storage) { try { if (!storage) return { available: false, length: null }; const key = '__tracker_test__'; storage.setItem(key, '1'); storage.removeItem(key); return { available: true, length: storage.length }; } catch (error) { return { available: false, length: null }; } } async function getStorageState() { const localState = testStorage( typeof localStorage !== 'undefined' ? localStorage : null ); const sessionState = testStorage( typeof sessionStorage !== 'undefined' ? sessionStorage : null ); let storageEstimate = null; let storagePersisted = null; if (navigator.storage && navigator.storage.estimate) { try { const estimate = await navigator.storage.estimate(); storageEstimate = estimate ? { quota: typeof estimate.quota === 'number' ? estimate.quota : null, usage: typeof estimate.usage === 'number' ? estimate.usage : null } : null; } catch (error) { storageEstimate = null; } } if (navigator.storage && navigator.storage.persisted) { try { storagePersisted = await navigator.storage.persisted(); } catch (error) { storagePersisted = null; } } return { cookiesEnabled: typeof navigator.cookieEnabled === 'boolean' ? navigator.cookieEnabled : null, localStorage: localState, sessionStorage: sessionState, storageEstimate, storagePersisted }; } function getMediaFeatures() { let audioContextSupported = false; if (typeof AudioContext !== 'undefined') { try { const ctx = new AudioContext(); audioContextSupported = true; if (typeof ctx.close === 'function') { ctx.close(); } } catch (error) { audioContextSupported = false; } } const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; const connectionInfo = connection ? { downlink: typeof connection.downlink === 'number' ? connection.downlink : null, rtt: typeof connection.rtt === 'number' ? connection.rtt : null, effectiveType: connection.effectiveType || null, saveData: typeof connection.saveData === 'boolean' ? connection.saveData : null } : null; const speechSynthesisSupported = typeof speechSynthesis !== 'undefined'; return { audioContext: audioContextSupported, speechSynthesis: speechSynthesisSupported, mediaDevices: !!(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices), mediaCapabilities: !!navigator.mediaCapabilities, connection: connectionInfo }; } const canQueryPermissions = !!(navigator.permissions && typeof navigator.permissions.query === 'function'); const uaCh = await getUserAgentData(); const storageState = await getStorageState(); const jsEnv = { webdriver: typeof navigator.webdriver === 'boolean' ? navigator.webdriver : null, languages: Array.isArray(navigator.languages) ? navigator.languages : null, pluginsLength: typeof navigator.plugins !== 'undefined' ? navigator.plugins.length : null, mimeTypesLength: typeof navigator.mimeTypes !== 'undefined' ? navigator.mimeTypes.length : null, hasChrome: typeof window.chrome !== 'undefined', permissionsQuery: canQueryPermissions, notificationPermission: typeof Notification !== 'undefined' ? Notification.permission : null, permissionsState: null, canvas: getCanvasFingerprint(), webgl: getWebglFingerprint(), webgl2: getWebgl2Fingerprint(), timezone: getTimezoneInfo(), uaCh, device: getDeviceCapabilities(), storage: storageState, media: getMediaFeatures() }; if (canQueryPermissions) { try { const status = await navigator.permissions.query({ name: 'notifications' }); jsEnv.permissionsState = status && status.state ? status.state : null; } catch (error) { jsEnv.permissionsState = null; } } return { jsEnv }; } // Collect page view data async function trackPageView() { sessionId = generateSessionId(); const metadata = await collectMetadata(); const data = { sessionId, url: window.location.href, referrer: document.referrer || null, userAgent: navigator.userAgent, screenResolution: window.screen.width + 'x' + window.screen.height, language: navigator.language, metadata }; try { const response = await fetch(TRACKER_URL + '/track/pageview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); pageViewId = result.pageViewId; } catch (error) { console.error('Tracking error:', error); } } // Track user events function trackEvent(eventType, eventData = {}) { if (!sessionId || !pageViewId) { console.warn('Session not initialized'); return; } const data = { sessionId, pageViewId, eventType, eventData }; fetch(TRACKER_URL + '/track/event', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).catch(error => { console.error('Event tracking error:', error); }); } // Throttle function - limits event tracking to once per interval const eventThrottleTimers = {}; function throttleEvent(eventType, eventData, interval) { const now = Date.now(); const lastTime = eventThrottleTimers[eventType] || 0; if (now - lastTime >= interval) { eventThrottleTimers[eventType] = now; trackEvent(eventType, eventData); } } // Auto-track click events document.addEventListener('click', function(e) { const target = e.target; const tagName = target.tagName.toLowerCase(); trackEvent('click', { element: tagName, text: target.innerText?.substring(0, 100), href: target.href || null, id: target.id || null, class: target.className || null }); }, true); // Auto-track scroll events (throttled to once per 20 seconds) let scrollDepth = 0; window.addEventListener('scroll', function() { const windowHeight = window.innerHeight; const documentHeight = document.documentElement.scrollHeight; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollPercent = Math.round((scrollTop / (documentHeight - windowHeight)) * 100); if (scrollPercent > scrollDepth) { scrollDepth = scrollPercent; } throttleEvent('scroll', { scrollDepth: scrollDepth, scrollPercent: scrollPercent, scrollTop: scrollTop }, 20000); }); // Auto-track hover events (throttled to once per 20 seconds) document.addEventListener('mouseover', function(e) { const target = e.target; const tagName = target.tagName.toLowerCase(); throttleEvent('hover', { element: tagName, text: target.innerText?.substring(0, 100), id: target.id || null, class: target.className || null }, 20000); }, true); // Track page duration let startTime = Date.now(); window.addEventListener('beforeunload', function() { const duration = Math.round((Date.now() - startTime) / 1000); trackEvent('page_duration', { duration }); }); // Track visibility changes document.addEventListener('visibilitychange', function() { trackEvent('visibility', { state: document.visibilityState, hidden: document.hidden, sinceStartMs: Date.now() - startTime }); }); // Expose API for external use window.__tracker = { trackEvent: trackEvent }; // Initialize if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', trackPageView); } else { trackPageView(); } })();