UNPKG

dprintjs

Version:

Generate unique device fingerprints using browser characteristics

193 lines (191 loc) 7.15 kB
class dPrintId { constructor() { } static getInstance() { if (!dPrintId._instance) { dPrintId._instance = new dPrintId(); } return dPrintId._instance; } async generateFingerprint(options = {}) { const components = await this.collectComponents(options); if (options.format === 'long') { return this.generateLongFingerprint(components, options.salt); } return this.generateShortFingerprint(components, options.salt); } async collectComponents(options) { const components = []; try { // Basic components (always included) components.push(navigator.userAgent); components.push(`${window.screen.height}x${window.screen.width}x${window.screen.colorDepth}`); components.push(Intl.DateTimeFormat().resolvedOptions().timeZone); components.push(navigator.language); components.push(navigator.hardwareConcurrency?.toString() || ''); components.push(navigator.deviceMemory?.toString() || ''); components.push(navigator.platform); // Optional components if (options.includeCanvas !== false) { const canvasData = await this.getCanvasFingerprint(); components.push(canvasData); } if (options.includeAudio !== false) { const audioData = await this.getAudioFingerprint(); components.push(audioData); } if (options.includeWebGL !== false) { const webglData = this.getWebGLFingerprint(); components.push(webglData); } // Add some additional entropy components.push(new Date().getTimezoneOffset().toString()); components.push(this.getPlugins()); components.push(this.getTouchSupport()); } catch (error) { console.warn('Error collecting fingerprint components:', error); } return components; } async getCanvasFingerprint() { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return ''; canvas.width = 200; canvas.height = 50; // Add a background ctx.fillStyle = '#f60'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#069'; ctx.fillRect(2, 2, 50, 50); // Add text ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillStyle = '#069'; ctx.fillText('👋 Hello, world!', 4, 15); // Add some shapes ctx.strokeStyle = '#069'; ctx.beginPath(); ctx.moveTo(100, 10); ctx.bezierCurveTo(120, 20, 140, 0, 160, 30); ctx.stroke(); return canvas.toDataURL(); } catch { return ''; } } async getAudioFingerprint() { try { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const analyser = audioContext.createAnalyser(); const gainNode = audioContext.createGain(); const scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1); gainNode.gain.value = 0; oscillator.type = 'triangle'; oscillator.connect(analyser); analyser.connect(scriptProcessor); scriptProcessor.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(0); const audioTimeData = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData(audioTimeData); oscillator.stop(); audioContext.close(); return Array.from(audioTimeData) .slice(0, 5) .map(x => x.toString()) .join(','); } catch { return ''; } } getWebGLFingerprint() { try { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (!gl) return ''; const webgl = gl; const data = [ webgl.getParameter(webgl.VENDOR), webgl.getParameter(webgl.RENDERER), webgl.getParameter(webgl.VERSION), webgl.getParameter(webgl.SHADING_LANGUAGE_VERSION), this.getWebGLExtensions(webgl), ].join('~'); return data; } catch { return ''; } } getWebGLExtensions(gl) { try { const extensions = gl.getSupportedExtensions(); return extensions ? extensions.join(',') : ''; } catch { return ''; } } getPlugins() { try { const plugins = Array.from(navigator.plugins || []) .map(p => [p.name, p.description, Array.from(p)]) .flat() .join(','); return plugins; } catch { return ''; } } getTouchSupport() { try { const touchPoints = navigator.maxTouchPoints; const touchEvent = 'ontouchstart' in window; const touchList = 'TouchEvent' in window; return `${touchPoints},${touchEvent},${touchList}`; } catch { return ''; } } async generateLongFingerprint(components, salt) { try { const data = components.join('|||') + (salt || ''); const msgBuffer = new TextEncoder().encode(data); const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } catch { // Fallback to short fingerprint if crypto API is not available return this.generateShortFingerprint(components, salt); } } generateShortFingerprint(components, salt) { const data = components.join('|||') + (salt || ''); let h1 = 0xdeadbeef; let h2 = 0x41c6ce57; for (let i = 0; i < data.length; i++) { const ch = data.charCodeAt(i); h1 = Math.imul(h1 ^ ch, 2654435761); h2 = Math.imul(h2 ^ ch, 1597334677); } h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); // Generate 8-character string return (h2 >>> 0).toString(36).padStart(6, '0') + (h1 >>> 0).toString(36).padStart(6, '0').slice(0, 2); } } export { dPrintId }; //# sourceMappingURL=index.js.map