import type { DeviceHardware, Fingerprint } from '../../../domain/device.js' /** * Recursively sorts the keys of an object in alphabetical order * to produce a consistent (canonical) representation. */ function canonicalize(obj: T): T { // Return immediately if obj is null, undefined, or not an object if (obj === null || typeof obj !== 'object') { return obj } // Handle arrays by recursively sorting each element if (Array.isArray(obj)) { return obj.map((item) => canonicalize(item)) as unknown as T } // Get all keys and sort them const sortedKeys = Object.keys(obj).sort() // Create a new object with sorted keys // biome-ignore lint/suspicious/noExplicitAny: const result = {} as Record // Add each key-value pair to the result object in sorted order for (const key of sortedKeys) { // Recursively sort nested objects // biome-ignore lint/suspicious/noExplicitAny: result[key] = canonicalize((obj as Record)[key]) } return result as T } /** * Generates a hash from the hardware object using the Web Crypto API * */ async function generateHardwareHash(hardware: DeviceHardware) { // First, create a canonical version of the object const canonicalHardware = canonicalize(hardware) // Convert the object to a JSON string const jsonString = JSON.stringify(canonicalHardware) // Encode the string as UTF-8 const encoder = new TextEncoder() const data = encoder.encode(jsonString) // Calculate the SHA-256 digest const hashBuffer = await crypto.subtle.digest('SHA-256', data) // Convert the ArrayBuffer to a hex string const hashArray = new Uint8Array(hashBuffer) const hashHex = Array.from(hashArray) .map((byte) => byte.toString(16).padStart(2, '0')) .join('') return hashHex } export async function createFingerprint( hardware: DeviceHardware, ): Promise { const fingerprint = await generateHardwareHash(hardware) return fingerprint as Fingerprint }