/** * RetroS Fingerprint Collector v1.2 * Embed this script on any website to collect and log device fingerprints. */ (function() { 'use strict'; if (window.retroSCollectorHasRun) return; window.retroSCollectorHasRun = true; const getFingerprintData = async () => { const getCanvasFp = () => { try { const canvas = document.createElement('canvas'); canvas.width = 200; canvas.height = 50; const ctx = canvas.getContext('2d'); ctx.textBaseline = "top"; ctx.font = "14px 'Arial'"; ctx.textBaseline = "alphabetic"; ctx.fillStyle = "#f60"; ctx.fillRect(125, 1, 62, 20); ctx.fillStyle = "#069"; ctx.fillText("Browser Fingerprint 1.0", 2, 15); return canvas.toDataURL(); } catch (e) { return 'error'; } }; const getAudioFp = async () => { try { const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioCtx.createOscillator(); const analyser = audioCtx.createAnalyser(); oscillator.type = 'triangle'; oscillator.frequency.value = 10000; oscillator.connect(analyser); analyser.connect(audioCtx.destination); oscillator.start(0); return new Promise(resolve => { setTimeout(() => { analyser.disconnect(); oscillator.disconnect(); const freqData = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData(freqData); const sum = freqData.reduce((a, b) => a + b, 0); resolve(String(sum)); }, 200); }); } catch (e) { return 'error'; } }; const getWebGLFp = () => { try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (!gl) return 'unsupported'; const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL); } catch (e) { return 'error'; } }; const getFonts = () => { const fontList = ["Arial", "Verdana", "Helvetica", "Times New Roman", "Courier New", "Georgia", "Menlo", "Consolas", "Roboto", "Calibri"]; return fontList.filter(font => document.fonts.check(`12px "${font}"`)).join(','); }; const getBattery = async () => (navigator.getBattery ? navigator.getBattery() : Promise.resolve(null)); const [audio, battery] = await Promise.all([getAudioFp(), getBattery()]); return { platform: navigator.platform, cookies_enabled: navigator.cookieEnabled, do_not_track: navigator.doNotTrack || 'unspecified', screen_resolution: `${screen.width}x${screen.height}`, viewport_size: `${window.innerWidth}x${window.innerHeight}`, color_depth: screen.colorDepth, hardware_concurrency: navigator.hardwareConcurrency || 0, device_memory: navigator.deviceMemory || 0, user_agent: navigator.userAgent, language: navigator.language, plugins: Array.from(navigator.plugins || []).map(p => p.name).join(','), canvas_fingerprint: getCanvasFp(), webgl_fingerprint: getWebGLFp(), fonts: getFonts(), audio_fingerprint: audio, battery_level: battery ? battery.level * 100 : -1, is_charging: battery ? battery.charging : null, }; }; const simpleHash = (str) => { let hash = 0; for (let i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash |= 0; } return String(hash); }; const generateFingerprintId = (data) => { const stableComponents = [ data.platform, data.screen_resolution, data.color_depth, data.webgl_fingerprint, data.audio_fingerprint, data.fonts, data.hardware_concurrency, ].join('||'); return simpleHash(stableComponents); }; const collectAndSend = async () => { const currentScript = document.currentScript; const siteKey = currentScript.getAttribute('data-site-key'); const apiHost = currentScript.getAttribute('data-api-host'); if (!siteKey || !apiHost) { console.error('RetroS Collector: data-site-key or data-api-host is missing.'); return; } try { const impressionData = await getFingerprintData(); const fingerprintId = generateFingerprintId(impressionData); fetch(`${apiHost}/api/functions/logImpression`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ siteKey, fingerprintId, impressionData }) }).catch(err => console.error('RetroS Collector: Network error during impression logging.', err)); } catch (err) { console.error('RetroS Collector: Error during data collection.', err); } }; if (document.readyState === 'complete') { collectAndSend(); } else { window.addEventListener('load', collectAndSend, { once: true }); } })();