import { Analysis, getGpuBrand } from '../utils/helpers' import { patch, html, HTMLNote } from '../utils/html' import { WORKER_NAME } from '../worker' // get GCD Samples export async function getSamples() { const samples = window.sessionStorage && sessionStorage.getItem('samples') if (samples) { return { samples: JSON.parse(samples), samplesDidLoadFromSession: true, } } const url = ( /\.github\.io/.test(location.origin) ? './data/samples.json' : '../docs/data/samples.json' ) const cloudSamples = await fetch(url).then((res) => res.json()).catch((error) => { console.error(error) return }) if (cloudSamples && window.sessionStorage) { sessionStorage.setItem('samples', JSON.stringify(cloudSamples)) } return { samples: cloudSamples, samplesDidLoadFromSession: false, } } export default async function renderSamples(samples, { fp, styleSystemHash }) { if (!samples) { return } const { window: windowSamples, math: mathSamples, error: errorSamples, html: htmlSamples, style: styleSamples, } = samples || {} const computeData = (hash, data) => { let systems = [] let poolTotal = 0 const metricTotal = Object.keys(data).reduce((acc, item) => acc+= data[item].length, 0) const decryption = Object.keys(data).find((key) => data[key].find((item) => { if (!(item.id == hash)) { return false } systems = item.systems poolTotal = data[key].length return true })) return { systems, poolTotal, metricTotal, decryption, } } const decryptHash = (hash, data) => { const { systems, poolTotal, metricTotal, decryption } = computeData(hash, data) const getIcon = (name) => `` const browserIcon = ( !decryption ? '' : /edgios|edge/i.test(decryption) ? getIcon('edge') : /brave/i.test(decryption) ? getIcon('brave') : /vivaldi/i.test(decryption) ? getIcon('vivaldi') : /duckduckgo/i.test(decryption) ? getIcon('duckduckgo') : /yandex/i.test(decryption) ? getIcon('yandex') : /opera/i.test(decryption) ? getIcon('opera') : /crios|chrome/i.test(decryption) ? getIcon('chrome') : /tor browser/i.test(decryption) ? getIcon('tor') : /palemoon/i.test(decryption) ? getIcon('palemoon') : /fxios|firefox/i.test(decryption) ? getIcon('firefox') : /safari/i.test(decryption) ? getIcon('safari') : '' ) const icon = { blink: '', v8: '', webkit: '', gecko: '', goanna: '', tor: '', firefox: '', cros: '', linux: '', apple: '', windows: '', android: '', } const engineIcon = ( !decryption ? '' : /SpiderMonkey/.test(decryption) ? icon.firefox : /JavaScriptCore/.test(decryption) ? icon.webkit : /V8/.test(decryption) ? icon.v8 : '' ) const engineRendererIcon = ( !decryption ? '' : /Gecko/.test(decryption) ? icon.gecko : /WebKit/.test(decryption) ? icon.webkit : /Blink/.test(decryption) ? icon.blink : /Goanna/.test(decryption) ? icon.goanna : '' ) const systemIcon = ( (!decryption || (systems.length != 1)) ? '' : /windows/i.test(systems[0]) ? icon.windows : /linux/i.test(systems[0]) ? icon.linux : /ipad|iphone|ipod|ios|mac/i.test(systems[0]) ? icon.apple : /android/i.test(systems[0]) ? icon.android : /chrome os/i.test(systems[0]) ? icon.cros : '' ) const formatPercent = (n) => n.toFixed(2).replace('.00', '') return { decryption: decryption || 'unknown', browserHTML: ( !decryption ? undefined : `${browserIcon}${decryption}` ), engineHTML: ( !decryption ? undefined : `${engineIcon}${decryption}` ), engineRendererHTML: ( !decryption ? undefined : `${engineRendererIcon}${decryption}` ), engineRendererSystemHTML: ( !decryption ? undefined : `${engineRendererIcon}${systemIcon}${decryption}${systems.length != 1 ? '' : ` on ${systems[0]}`}` ), engineSystem: ( !decryption ? undefined : `${engineIcon}${systemIcon}${decryption}${systems.length != 1 ? '' : ` on ${systems[0]}`}` ), uniqueMetric: !decryption ? '0' : formatPercent(1/metricTotal*100), uniqueEngine: !decryption ? '0' : formatPercent(1/poolTotal*100), } } const renderWindowSamples = (fp) => { const id = document.getElementById(`window-features-samples`) if (!fp.windowFeatures || !id) { return } const { windowFeatures: { $hash } } = fp const { browserHTML, uniqueEngine } = decryptHash($hash, windowSamples) return patch(id, html`
${uniqueEngine}% of ${browserHTML || HTMLNote.UNKNOWN}
`) } const renderMathSamples = (fp) => { const id = document.getElementById(`math-samples`) if (!fp.maths || !id) { return } const { maths: { $hash } } = fp const { engineHTML, uniqueEngine } = decryptHash($hash, mathSamples) return patch(id, html`
${uniqueEngine}% of ${engineHTML || HTMLNote.UNKNOWN}
`) } const renderErrorSamples = (fp) => { const id = document.getElementById(`error-samples`) if (!fp.consoleErrors || !id) { return } const { consoleErrors: { $hash } } = fp const { engineHTML, uniqueEngine } = decryptHash($hash, errorSamples) return patch(id, html`
${uniqueEngine}% of ${engineHTML || HTMLNote.UNKNOWN}
`) } const renderHTMLElementSamples = (fp) => { const id = document.getElementById(`html-element-samples`) if (!fp.htmlElementVersion || !id) { return } const { htmlElementVersion: { $hash } } = fp const { engineRendererHTML, uniqueEngine } = decryptHash($hash, htmlSamples) return patch(id, html`
${uniqueEngine}% of ${engineRendererHTML || HTMLNote.UNKNOWN}
`) } const renderSystemStylesSamples = (fp, styleSystemHash) => { const id = document.getElementById(`system-style-samples`) if (!fp.css || !id) { return } const { engineRendererSystemHTML } = decryptHash(styleSystemHash, styleSamples) return patch(id, html`
${engineRendererSystemHTML || HTMLNote.UNKNOWN}
`) } renderWindowSamples(fp) renderMathSamples(fp) renderErrorSamples(fp) renderHTMLElementSamples(fp) renderSystemStylesSamples(fp, styleSystemHash) return } export function getRawFingerprint(fp) { try { const { canvas2d, canvasWebgl, capturedErrors, clientRects, cssMedia, features, fonts, headless, intl, lies, navigator: nav, offlineAudioContext, resistance, screen: screenFp, svg, timezone, trash, voices, workerScope: wkr, } = fp || {} const analysisFP = { device: (() => { const { width, height } = cssMedia?.screenQuery || {} return [ wkr?.gpu?.compressedGPU || canvasWebgl?.gpu?.compressedGPU || null, wkr?.deviceMemory || nav?.deviceMemory || null, wkr?.hardwareConcurrency || nav?.hardwareConcurrency || null, fonts?.platformVersion || null, width || null, height || null, typeof screenFp?.touch == 'boolean' ? screenFp.touch : null, nav?.maxTouchPoints !== undefined ? nav.maxTouchPoints : null, typeof nav?.bluetoothAvailability == 'boolean' ? nav.bluetoothAvailability : null, ] })(), voices: voices?.local?.slice(0, 3), voicesDefault: voices?.defaultVoiceName ? [ voices?.defaultVoiceName, voices?.defaultVoiceLang || null, ] : undefined, headless: headless?.$hash?.slice(0, 16), ...(() => { if (!headless) return {} const { headless: hl, likeHeadless: ldl, stealth: s } = headless const data = { ...hl, ...ldl, ...s } return Object.keys(data).reduce((acc, key) => { acc[`headless${key[0].toUpperCase()}${key.slice(1)}`] = data[key] return acc }, {} as Record) })(), headlessRating: headless?.headlessRating, headlessLikeRating: headless?.likeHeadlessRating, headlessStealthRating: headless?.stealthRating, headlessPlatformEstimate: headless?.platformEstimate?.[0], headlessSystemFont: headless?.systemFonts, engine: resistance?.engine, resistance: resistance?.$hash.slice(0, 16), resistanceExt: resistance?.extension || null, audio: offlineAudioContext?.$hash?.slice(0, 16), canvas: canvas2d?.$hash?.slice(0, 16), webgl: canvasWebgl?.$hash?.slice(0, 16), errors: capturedErrors?.data.length !== 0 ? capturedErrors?.data.slice(0, 6) : undefined, emojiDOMRect: clientRects?.domrectSystemSum, emojiSetDOMRect: clientRects?.emojiSet?.join(''), emojiSVGRect: svg?.svgrectSystemSum, emojiSetSVGRect: svg?.emojiSet?.join(''), emojiPixels: fonts?.pixelSizeSystemSum, emojiSetPixels: fonts?.emojiSet?.join(''), emojiTextMetrics: canvas2d?.textMetricsSystemSum, emojiSetTextMetrics: canvas2d?.emojiSet?.join(''), features: features?.version, ...(() => { const vendor = ( wkr?.webglVendor || canvasWebgl?.parameters.UNMASKED_VENDOR_WEBGL ) const renderer = ( wkr?.webglRenderer || canvasWebgl?.parameters.UNMASKED_RENDERER_WEBGL ) const gpu = [vendor || null, renderer || null] const gpuBrand = getGpuBrand(renderer) return { gpu, gpuBrand } })(), ...(() => { const { ['any-hover']: cssAnyHover, ['any-pointer']: cssAnyPointer, ['color-gamut']: cssColorGamut, ['device-aspect-ratio']: cssDeviceAspectRatio, ['device-screen']: cssDeviceScreen, ['display-mode']: cssDisplayMode, ['forced-colors']: cssForcedColors, ['hover']: cssHover, ['inverted-colors']: cssInvertedColors, ['monochrome']: cssMonochrome, ['orientation']: cssOrientation, ['pointer']: cssPointer, ['prefers-color-scheme']: cssColorScheme, ['prefers-reduced-motion']: cssReducedMotion, } = cssMedia?.mediaCSS || {} return { cssMedia: cssMedia?.$hash?.slice(0, 16), cssAnyHover, cssAnyPointer, cssColorGamut, cssDeviceAspectRatio, cssDeviceScreen, cssDisplayMode, cssForcedColors, cssHover, cssInvertedColors, cssMonochrome, cssOrientation, cssPointer, cssColorScheme, cssReducedMotion, } })(), fonts: fonts?.$hash?.slice(0, 16), fontList: fonts?.fontFaceLoadFonts, fontPlatformVersion: fonts?.platformVersion, userAgent: wkr?.userAgent || nav?.userAgent, userAgentDevice: [ wkr?.device || nav?.device || null, wkr?.platform || nav?.platform || null, wkr?.system || nav?.system || null, ], userAgentData: (() => { const data = wkr?.userAgentData || nav?.userAgentData if (!data) return const { platform, platformVersion, bitness, architecture, model, mobile, } = data || {} return [ platform || null, platformVersion || null, bitness || null, architecture || null, model || null, typeof mobile == 'boolean' ? mobile : null, ] })(), lies: lies?.totalLies !== 0 ? lies?.$hash?.slice(0, 16) : undefined, lieKeys: lies?.totalLies !== 0 ? Object.keys(lies.data || {}) : undefined, trash: ( trash?.trashBin.length !== 0 ? trash?.trashBin .map((x: { name: string, value: string }) => [x.name, x.value].join(': ')).slice(0, 10): undefined ), timezone: (() => { if (!timezone) return const { location, zone, locationEpoch, offset, offsetComputed, } = timezone || {} const { locale, language, languages, timezoneLocation, timezoneOffset, localeEntropyIsTrusty, localeIntlEntropyIsTrusty, } = wkr || {} return [ timezoneLocation || location || null, zone || null, locationEpoch || null, timezoneOffset || offsetComputed || null, offset || null, locale || intl?.locale || null, language || null, languages || null, typeof localeEntropyIsTrusty == 'boolean' ? localeEntropyIsTrusty : null, typeof localeIntlEntropyIsTrusty == 'boolean' ? localeIntlEntropyIsTrusty : null, ] })(), screen: (() => { if (!screenFp) return const { availHeight, availWidth, colorDepth, height, pixelDepth, touch, width, } = screenFp || {} return [ width, height, availWidth, availHeight, colorDepth, pixelDepth, touch, nav?.maxTouchPoints !== undefined ? nav.maxTouchPoints : null, window.devicePixelRatio || null, ] })(), permDenied: nav?.permissions?.denied, permGranted: nav?.permissions?.granted, workerEnabled: WORKER_NAME, ...Analysis, } return analysisFP } catch (err) { console.error(err) return { fpErr: err } } }