/* ============================================================================ * fingerprint.js — stable browser identifier for server-side playlist sync * * Combines canvas rendering, UA, screen geometry, and hardware hints into a * short hex string. The result is stored in localStorage so the same ID * survives page reloads, and only regenerated when localStorage is cleared. * * Not for tracking — used exclusively so the server can associate playlist * and history rows with this browser without requiring a login. * ========================================================================== */ (function () { 'use strict'; // djb2 hash over a string → 32-bit unsigned int function djb2(str) { let h = 5381; for (let i = 0; i < str.length; i++) { h = (((h << 5) + h) + str.charCodeAt(i)) >>> 0; } return h; } // Render a small canvas to capture GPU/font rasterisation differences, // then hash the pixel data. Falls back to empty string if canvas is blocked. function canvasHash() { try { const c = document.createElement('canvas'); c.width = 200; c.height = 40; const ctx = c.getContext('2d'); if (!ctx) return ''; ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillStyle = '#f60'; ctx.fillRect(125, 1, 62, 20); ctx.fillStyle = '#069'; ctx.fillText('YTPlayer🎵', 2, 15); ctx.fillStyle = 'rgba(102,204,0,0.7)'; ctx.fillText('YTPlayer🎵', 4, 17); return djb2(c.toDataURL()).toString(16); } catch { return ''; } } function generateFingerprint() { const parts = [ navigator.userAgent || '', String(screen.width) + 'x' + String(screen.height), String(screen.colorDepth), Intl.DateTimeFormat().resolvedOptions().timeZone || '', navigator.language || '', String(navigator.hardwareConcurrency || 0), String(navigator.deviceMemory || 0), canvasHash(), ]; // Combine all component hashes into one 16-char hex fingerprint const combined = parts.reduce((acc, p) => acc + '|' + p, ''); const h1 = djb2(combined); const h2 = djb2(combined.split('').reverse().join('')); return h1.toString(16).padStart(8, '0') + h2.toString(16).padStart(8, '0'); } window.getFingerprint = function getFingerprint() { try { let fp = localStorage.getItem('_ytpfp'); if (!fp || fp.length < 8) { fp = generateFingerprint(); localStorage.setItem('_ytpfp', fp); } return fp; } catch { // localStorage blocked (e.g. private mode on some browsers) — generate // ephemeral fingerprint that survives the page session via a closure. if (!window._ytpfpEphemeral) { window._ytpfpEphemeral = generateFingerprint(); } return window._ytpfpEphemeral; } }; }());