| import { storageService } from './storage-service'; |
|
|
| const FINGERPRINT_UUID_KEY = 'device_fingerprint_uuid'; |
|
|
| |
| |
| |
| class FingerprintService { |
|
|
| private isBrowser: boolean; |
|
|
| constructor() { |
| this.isBrowser = typeof window !== 'undefined'; |
| } |
|
|
| |
| |
| |
| |
| |
| private async digest(text: string): Promise<string> { |
| if (!this.isBrowser || !window.crypto?.subtle) { |
| |
| let hash = 0; |
| for (let i = 0; i < text.length; i++) { |
| const char = text.charCodeAt(i); |
| hash = ((hash << 5) - hash) + char; |
| hash |= 0; |
| } |
| return Promise.resolve(hash.toString(16)); |
| } |
|
|
| const data = new TextEncoder().encode(text); |
| const hashBuffer = await window.crypto.subtle.digest('SHA-256', data); |
| const hashArray = Array.from(new Uint8Array(hashBuffer)); |
| const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); |
| return hashHex; |
| } |
|
|
| |
| |
| |
| |
| |
| private async getPersistentUUID(): Promise<string> { |
| let uuid = await storageService.getCachedData<string>(FINGERPRINT_UUID_KEY); |
| if (!uuid) { |
| |
| uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
| const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); |
| return v.toString(16); |
| }); |
| await storageService.saveCachedData(FINGERPRINT_UUID_KEY, uuid); |
| } |
| return uuid; |
| } |
|
|
| |
| |
| |
| |
| private async getComponentData(): Promise<Record<string, any>> { |
| if (!this.isBrowser) { |
| return { environment: 'non-browser' }; |
| } |
|
|
| const screen = window.screen; |
| const navigator = window.navigator; |
|
|
| return { |
| |
| platform: navigator.platform, |
| cpuCores: navigator.hardwareConcurrency, |
| deviceMemory: (navigator as any).deviceMemory || 'unknown', |
|
|
| |
| userAgent: navigator.userAgent, |
| vendor: navigator.vendor, |
| |
| |
| screenResolution: `${screen.width}x${screen.height}`, |
| colorDepth: screen.colorDepth, |
| pixelDepth: screen.pixelDepth, |
| |
| |
| timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone, |
| language: navigator.language, |
| languages: navigator.languages, |
| |
| |
| appUuid: await this.getPersistentUUID(), |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| public async generate(nativeComponents?: Record<string, any>): Promise<string> { |
| const webComponents = await this.getComponentData(); |
| |
| |
| const allComponents = { ...webComponents, ...nativeComponents }; |
|
|
| |
| const sortedKeys = Object.keys(allComponents).sort(); |
| const jsonString = JSON.stringify(sortedKeys.map(key => allComponents[key])); |
|
|
| return this.digest(jsonString); |
| } |
| } |
|
|
| |
| export const fingerprintService = new FingerprintService(); |
|
|