import type { AvatarData } from './types'; const STORAGE_PREFIX = 'aisthetix'; const AVATAR_KEY = `${STORAGE_PREFIX}-avatar`; const VISITOR_ID_KEY = `${STORAGE_PREFIX}-visitor-id`; /** * Written by widget versions up to 0.5.0, which kept up to ten full-resolution * try-on results here — several megabytes — even though nothing ever read them. * It filled localStorage and made saveAvatar() fail, taking the persistent-photo * feature down with it. Nothing writes this key any more; we only clean it up. */ const LEGACY_HISTORY_KEY = `${STORAGE_PREFIX}-history`; /** Reclaim the space the old history left behind. Safe to call repeatedly. */ export function purgeLegacyHistory(): boolean { try { if (localStorage.getItem(LEGACY_HISTORY_KEY) === null) { return false; } localStorage.removeItem(LEGACY_HISTORY_KEY); return true; } catch { return false; } } // Avatar Storage export function getAvatar(): AvatarData | null { try { const data = localStorage.getItem(AVATAR_KEY); if (data) { return JSON.parse(data); } } catch (e) { console.error('Error reading avatar from storage:', e); } return null; } export function saveAvatar(imageData: string): void { try { const avatar: AvatarData = { imageData, createdAt: new Date().toISOString(), }; const serialized = JSON.stringify(avatar); // Estimate size (roughly 3/4 of base64 string size due to encoding overhead) const sizeInBytes = serialized.length * 2; // UTF-16 encoding const maxSize = 5 * 1024 * 1024; // 5MB limit if (sizeInBytes > maxSize) { throw new Error('Image too large to store locally (exceeds 5MB limit)'); } try { localStorage.setItem(AVATAR_KEY, serialized); } catch (storageError) { if (storageError instanceof Error && storageError.name === 'QuotaExceededError') { // A browser that used an older widget may still be carrying megabytes of // dead history. Reclaim it and try once more before giving up. if (purgeLegacyHistory()) { localStorage.setItem(AVATAR_KEY, serialized); return; } throw new Error('Storage quota exceeded. Please clear browser cache or try a smaller image.'); } throw storageError; } } catch (e) { console.error('Error saving avatar to storage:', e); // Re-throw with user-friendly message if (e instanceof Error) { throw e; } throw new Error('Failed to save avatar'); } } export function clearAvatar(): void { try { localStorage.removeItem(AVATAR_KEY); } catch (e) { console.error('Error clearing avatar from storage:', e); } } // Visitor ID for per-user rate limiting export function getVisitorId(): string { try { let visitorId = localStorage.getItem(VISITOR_ID_KEY); if (!visitorId) { visitorId = `anon_${crypto.randomUUID()}`; localStorage.setItem(VISITOR_ID_KEY, visitorId); } return visitorId; } catch { // If localStorage unavailable, generate ephemeral ID (resets on page reload) return `anon_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } } // Consent Storage — asked once per visitor before an AI try-on is generated const CONSENT_KEY = 'aisthetix_tryon_consent'; export function hasConsent(): boolean { try { return localStorage.getItem(CONSENT_KEY) === '1'; } catch { return false; } } export function setConsent(): void { try { localStorage.setItem(CONSENT_KEY, '1'); } catch { // If localStorage is unavailable, consent will simply be re-requested next visit. } } // Check if localStorage is available export function isStorageAvailable(): boolean { try { const test = '__storage_test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch (e) { return false; } }