/** * Утилита для создания уникального отпечатка устройства (Device Fingerprinting) * Используется для блокировки на уровне железа */ class DeviceFingerprint { constructor() { this.components = new Map(); this.fingerprint = null; this.initialized = false; } /** * Инициализация и создание отпечатка устройства */ async initialize() { try { console.log('[DeviceFingerprint] Начинаем инициализацию отпечатка устройства...'); // Очищаем предыдущие компоненты this.components.clear(); // Собираем компоненты отпечатка await this.collectScreenInfo(); await this.collectNavigatorInfo(); await this.collectCanvasFingerprint(); await this.collectWebGLFingerprint(); await this.collectAudioFingerprint(); await this.collectTimezoneInfo(); await this.collectLanguageInfo(); await this.collectPlatformInfo(); await this.collectHardwareInfo(); await this.collectStorageInfo(); await this.collectNetworkInfo(); // Генерируем финальный отпечаток this.fingerprint = await this.generateFingerprint(); this.initialized = true; console.log('[DeviceFingerprint] Отпечаток устройства создан:', this.fingerprint.substring(0, 16) + '...'); return this.fingerprint; } catch (error) { console.error('[DeviceFingerprint] Ошибка при создании отпечатка:', error); // Создаем базовый отпечаток даже при ошибках this.fingerprint = await this.createFallbackFingerprint(); this.initialized = true; return this.fingerprint; } } /** * Информация об экране */ async collectScreenInfo() { try { this.components.set('screen', { width: screen.width, height: screen.height, availWidth: screen.availWidth, availHeight: screen.availHeight, colorDepth: screen.colorDepth, pixelDepth: screen.pixelDepth, devicePixelRatio: window.devicePixelRatio || 1 }); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить информацию об экране:', e); } } /** * Информация о навигаторе */ async collectNavigatorInfo() { try { this.components.set('navigator', { userAgent: navigator.userAgent, language: navigator.language, languages: navigator.languages ? Array.from(navigator.languages) : [], platform: navigator.platform, cookieEnabled: navigator.cookieEnabled, doNotTrack: navigator.doNotTrack, maxTouchPoints: navigator.maxTouchPoints || 0, hardwareConcurrency: navigator.hardwareConcurrency || 0, deviceMemory: navigator.deviceMemory || 0 }); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить информацию навигатора:', e); } } /** * Canvas fingerprinting */ async collectCanvasFingerprint() { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Рисуем уникальный паттерн ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillStyle = '#f60'; ctx.fillRect(125, 1, 62, 20); ctx.fillStyle = '#069'; ctx.fillText('Device fingerprint text 🔐', 2, 15); ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; ctx.fillText('Device fingerprint text 🔐', 4, 17); // Добавляем геометрические фигуры ctx.globalCompositeOperation = 'multiply'; ctx.fillStyle = 'rgb(255,0,255)'; ctx.beginPath(); ctx.arc(50, 50, 50, 0, Math.PI * 2, true); ctx.closePath(); ctx.fill(); const canvasData = canvas.toDataURL(); this.components.set('canvas', this.hashString(canvasData)); } catch (e) { console.warn('[DeviceFingerprint] Не удалось создать canvas fingerprint:', e); } } /** * WebGL fingerprinting */ async collectWebGLFingerprint() { try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (gl) { const webglInfo = { version: gl.getParameter(gl.VERSION), vendor: gl.getParameter(gl.VENDOR), renderer: gl.getParameter(gl.RENDERER), shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION), maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE), maxViewportDims: gl.getParameter(gl.MAX_VIEWPORT_DIMS) }; this.components.set('webgl', webglInfo); } } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить WebGL информацию:', e); } } /** * Audio fingerprinting */ async collectAudioFingerprint() { return new Promise((resolve) => { try { if (!window.AudioContext && !window.webkitAudioContext) { resolve(); return; } const AudioContext = window.AudioContext || window.webkitAudioContext; const context = new AudioContext(); const oscillator = context.createOscillator(); const analyser = context.createAnalyser(); const gainNode = context.createGain(); const scriptProcessor = context.createScriptProcessor(4096, 1, 1); oscillator.type = 'triangle'; oscillator.frequency.value = 10000; gainNode.gain.value = 0; oscillator.connect(analyser); analyser.connect(scriptProcessor); scriptProcessor.connect(gainNode); gainNode.connect(context.destination); scriptProcessor.onaudioprocess = (event) => { const bins = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData(bins); const audioHash = this.hashString(bins.slice(0, 50).join(',')); this.components.set('audio', audioHash); oscillator.disconnect(); context.close(); resolve(); }; oscillator.start(); // Таймаут на случай проблем setTimeout(() => { try { oscillator.disconnect(); context.close(); } catch (e) {} resolve(); }, 1000); } catch (e) { console.warn('[DeviceFingerprint] Не удалось создать audio fingerprint:', e); resolve(); } }); } /** * Информация о временной зоне */ async collectTimezoneInfo() { try { this.components.set('timezone', { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, timezoneOffset: new Date().getTimezoneOffset(), locale: Intl.DateTimeFormat().resolvedOptions().locale }); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить информацию о временной зоне:', e); } } /** * Языковые настройки */ async collectLanguageInfo() { try { this.components.set('language', { language: navigator.language, languages: navigator.languages ? Array.from(navigator.languages) : [] }); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить языковую информацию:', e); } } /** * Платформа и ОС */ async collectPlatformInfo() { try { this.components.set('platform', { platform: navigator.platform, oscpu: navigator.oscpu, appVersion: navigator.appVersion }); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить информацию о платформе:', e); } } /** * Аппаратная информация */ async collectHardwareInfo() { try { this.components.set('hardware', { hardwareConcurrency: navigator.hardwareConcurrency || 0, deviceMemory: navigator.deviceMemory || 0, maxTouchPoints: navigator.maxTouchPoints || 0 }); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить аппаратную информацию:', e); } } /** * Информация о хранилище */ async collectStorageInfo() { try { let storageQuota = 0; if ('storage' in navigator && 'estimate' in navigator.storage) { const estimate = await navigator.storage.estimate(); storageQuota = estimate.quota || 0; } this.components.set('storage', { localStorage: !!window.localStorage, sessionStorage: !!window.sessionStorage, indexedDB: !!window.indexedDB, storageQuota }); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить информацию о хранилище:', e); } } /** * Сетевая информация */ async collectNetworkInfo() { try { const networkInfo = {}; if ('connection' in navigator) { const conn = navigator.connection; networkInfo.effectiveType = conn.effectiveType; networkInfo.downlink = conn.downlink; networkInfo.rtt = conn.rtt; } this.components.set('network', networkInfo); } catch (e) { console.warn('[DeviceFingerprint] Не удалось получить сетевую информацию:', e); } } /** * Генерация финального отпечатка */ async generateFingerprint() { const componentsString = JSON.stringify(Array.from(this.components.entries()).sort()); return await this.hashString(componentsString); } /** * Создание резервного отпечатка при ошибках */ async createFallbackFingerprint() { const fallbackData = { userAgent: navigator.userAgent, screen: `${screen.width}x${screen.height}`, timezone: new Date().getTimezoneOffset(), language: navigator.language, timestamp: Date.now() }; return await this.hashString(JSON.stringify(fallbackData)); } /** * Хеширование строки */ async hashString(str) { if (crypto && crypto.subtle) { const encoder = new TextEncoder(); const data = encoder.encode(str); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } else { // Fallback для старых браузеров return this.simpleHash(str); } } /** * Простое хеширование для старых браузеров */ simpleHash(str) { let hash = 0; if (str.length === 0) return hash.toString(); for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Конвертируем в 32-битное целое } return Math.abs(hash).toString(16); } /** * Получение текущего отпечатка */ getFingerprint() { return this.fingerprint; } /** * Проверка инициализации */ isInitialized() { return this.initialized; } /** * Получение компонентов отпечатка (для отладки) */ getComponents() { return Object.fromEntries(this.components); } /** * Обновление отпечатка */ async refresh() { return await this.initialize(); } } // Экспортируем класс export default DeviceFingerprint;