Untitled

 avatar
unknown
plain_text
9 months ago
6.6 kB
12
Indexable
async function sha256Hex(str) {
    const enc = new TextEncoder();
    const data = enc.encode(str);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const bytes = new Uint8Array(hashBuffer);
    return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}

function getOrCreateClientId() {
    try {
        const key = 'my_app_client_id_v1';
        let id = localStorage.getItem(key);
        if (!id) {
            // simple UUID v4
            id = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
                (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
            );
            localStorage.setItem(key, id);
        }
        return id;
    } catch (e) {
        return 'volatile-' + Math.random().toString(36).slice(2);
    }
}

function getCanvasFingerprint() {
    try {
        const cvs = document.createElement('canvas');
        cvs.width = 200;
        cvs.height = 50;
        const ctx = cvs.getContext('2d');
        ctx.textBaseline = 'top';
        ctx.font = "16px 'Arial'";
        ctx.fillStyle = '#f60';
        ctx.fillRect(0, 0, 200, 50);
        ctx.fillStyle = '#069';
        ctx.fillText('Fingerprint test — åßç≈', 2, 2);
        ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
        ctx.fillText('Fingerprint test — åßç≈', 4, 20);
        const data = cvs.toDataURL();
        return data;
    } catch (e) {
        return 'canvas-error';
    }
}

function getWebGLFingerprint() {
    try {
        const canvas = document.createElement('canvas');
        const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
        if (!gl) return 'no-webgl';
        const dbgRenderInfo = gl.getExtension('WEBGL_debug_renderer_info');
        const vendor = dbgRenderInfo ? gl.getParameter(dbgRenderInfo.UNMASKED_VENDOR_WEBGL) : 'unknown';
        const renderer = dbgRenderInfo ? gl.getParameter(dbgRenderInfo.UNMASKED_RENDERER_WEBGL) : 'unknown';
        return vendor + '|' + renderer;
    } catch (e) {
        return 'webgl-error';
    }
}

function getAudioFingerprint() {
    try {
        const AudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;
        if (!AudioContext) return 'no-audio';
        const ctx = new AudioContext(1, 44100, 44100);
        const oscillator = ctx.createOscillator();
        const analyser = ctx.createAnalyser();
        oscillator.type = 'triangle';
        oscillator.frequency.value = 10000;
        oscillator.connect(analyser);
        analyser.connect(ctx.destination);
        if (ctx.startRendering) {
            return new Promise((res) => {
                try {
                    const offline = new AudioContext(1, 44100, 44100);
                    res('audio-' + (offline.sampleRate || 0) + '-' + (offline.numberOfChannels || 0));
                } catch (e) {
                    res('audio-error');
                }
            });
        } else {
            return Promise.resolve('audio-basic-' + (ctx.sampleRate || 0));
        }
    } catch (e) {
        return Promise.resolve('audio-error');
    }
}

function probeFonts() {
    try {
        const baseFonts = ['monospace', 'sans-serif', 'serif'];
        const testFonts = ['Arial', 'Times New Roman', 'Courier New', 'Segoe UI', 'Roboto', 'Noto Sans'];
        const span = document.createElement('span');
        span.style.fontSize = '72px';
        span.innerHTML = 'mmmmmmmmmwwwwwww';
        const defaultWidth = {};
        document.body.appendChild(span);
        baseFonts.forEach(bf => {
            span.style.fontFamily = bf;
            defaultWidth[bf] = span.getBoundingClientRect().width;
        });
        const found = [];
        for (const f of testFonts) {
            span.style.fontFamily = `${f}, monospace`;
            const w = span.getBoundingClientRect().width;
            if (w !== defaultWidth['monospace']) found.push(f);
        }
        document.body.removeChild(span);
        return found.join(',');
    } catch (e) {
        return 'font-probe-error';
    }
}

async function generateFingerprintEnhanced() {
    const nav = window.navigator || {};
    const screenInfo = window.screen || {};
    const components = [
        nav.userAgent || '',
        nav.vendor || '',
        navigator.language || '',
        navigator.languages ? navigator.languages.join(',') : '',
        screenInfo.width + 'x' + screenInfo.height + 'x' + (window.devicePixelRatio || 1),
        'colorDepth:' + (screenInfo.colorDepth || 0),
        'pixelDepth:' + (screenInfo.pixelDepth || 0),
        'platform:' + (nav.platform || ''),
        'hardwareConcurrency:' + (nav.hardwareConcurrency || ''),
        'deviceMemory:' + (navigator.deviceMemory || ''),
        'touchPoints:' + (navigator.maxTouchPoints || navigator.msMaxTouchPoints || 0),
        'cookieEnabled:' + (navigator.cookieEnabled ? 1 : 0),
        'doNotTrack:' + (navigator.doNotTrack || ''),
        'timezoneOffset:' + new Date().getTimezoneOffset(),
        'timezoneName:' + (Intl.DateTimeFormat && Intl.DateTimeFormat().resolvedOptions().timeZone ? Intl.DateTimeFormat().resolvedOptions().timeZone : ''),
        'plugins:' + (navigator.plugins ? Array.from(navigator.plugins).map(p => p.name + '/' + p.filename).join(';') : ''),
        'mimeTypes:' + (navigator.mimeTypes ? Array.from(navigator.mimeTypes).map(m => m.type).join(',') : '')
    ];
    components.push('canvas:' + getCanvasFingerprint());
    components.push('webgl:' + getWebGLFingerprint());
    try {
        const fonts = probeFonts();
        components.push('fonts:' + fonts);
    } catch (e) {
        components.push('fonts:error');
    }
    try {
        const audioFp = await getAudioFingerprint();
        components.push('audio:' + audioFp);
    } catch (e) {
        components.push('audio:error');
    }
    try {
        if (performance && performance.timing) {
            components.push('pt:' + (performance.timing.navigationStart || 0));
        }
    } catch (e) {
    }
    const seed = (nav.userAgent || '') + '|' + (nav.vendor || '') + '|' + (navigator.language || '');
    const seedHash = await sha256Hex(seed);
    components.push('seed:' + seedHash.slice(0, 8));
    const canon = components.join('||');
    const fingerprint = await sha256Hex(canon);
    const clientId = getOrCreateClientId();
    return {fingerprint, clientId, raw: canon};
}
Editor is loading...
Leave a Comment