import { BaseService } from '@/core/base/BaseService' /** * Centralized storage service providing user-scoped localStorage operations * * Features: * - Automatic user-scoped key generation * - Type-safe storage operations * - Consistent data isolation between users * - Fallback to global storage for anonymous users * - Error handling with graceful degradation */ export class StorageService extends BaseService { // Service metadata protected readonly metadata = { name: 'StorageService', version: '1.0.0', dependencies: ['AuthService'] // Depends on auth for user pubkey } /** * Service-specific initialization (called by BaseService) */ protected async onInitialize(): Promise { this.debug('StorageService initialized') } /** * Generate user-scoped storage key * @param baseKey - Base key to scope to current user * @returns User-scoped key or base key if no user */ private getUserStorageKey(baseKey: string): string { if (!this.authService?.user?.value?.pubkey) { // No user authenticated, use global key return baseKey } const userPubkey = this.authService.user.value.pubkey return `${baseKey}_${userPubkey}` } /** * Store user-scoped data in localStorage * @param key - Base storage key * @param data - Data to store (will be JSON serialized) */ setUserData(key: string, data: T): void { try { const storageKey = this.getUserStorageKey(key) const serializedData = JSON.stringify(data) localStorage.setItem(storageKey, serializedData) this.debug(`Stored user data: ${storageKey}`) } catch (error) { console.error(`Failed to store user data for key "${key}":`, error) } } /** * Retrieve user-scoped data from localStorage * @param key - Base storage key * @param defaultValue - Default value if not found * @returns Stored data or default value */ getUserData(key: string, defaultValue?: T): T | undefined { try { const storageKey = this.getUserStorageKey(key) const stored = localStorage.getItem(storageKey) if (stored === null) { this.debug(`No stored data found for key: ${storageKey}`) return defaultValue } const parsed = JSON.parse(stored) as T this.debug(`Retrieved user data: ${storageKey}`) return parsed } catch (error) { console.error(`Failed to retrieve user data for key "${key}":`, error) return defaultValue } } /** * Remove user-scoped data from localStorage * @param key - Base storage key */ clearUserData(key: string): void { try { const storageKey = this.getUserStorageKey(key) localStorage.removeItem(storageKey) this.debug(`Cleared user data: ${storageKey}`) } catch (error) { console.error(`Failed to clear user data for key "${key}":`, error) } } /** * Check if user-scoped data exists * @param key - Base storage key * @returns True if data exists */ hasUserData(key: string): boolean { try { const storageKey = this.getUserStorageKey(key) return localStorage.getItem(storageKey) !== null } catch (error) { console.error(`Failed to check user data for key "${key}":`, error) return false } } /** * Get all keys for the current user (useful for cleanup) * @returns Array of storage keys belonging to current user */ getUserKeys(): string[] { try { if (!this.authService?.user?.value?.pubkey) { return [] } const userPubkey = this.authService.user.value.pubkey const userKeys: string[] = [] // Scan localStorage for keys belonging to this user for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i) if (key && key.endsWith(`_${userPubkey}`)) { userKeys.push(key) } } return userKeys } catch (error) { console.error('Failed to get user keys:', error) return [] } } /** * Clear all data for the current user (useful on logout) */ clearAllUserData(): void { try { const userKeys = this.getUserKeys() for (const key of userKeys) { localStorage.removeItem(key) } this.debug(`Cleared all user data: ${userKeys.length} keys`) } catch (error) { console.error('Failed to clear all user data:', error) } } /** * Migrate data from old storage pattern to user-scoped pattern * @param oldKey - Old global key * @param newKey - New base key for user scoping */ migrateToUserScoped(oldKey: string, newKey: string): void { try { // Only migrate if we have a user and old data exists if (!this.authService?.user?.value?.pubkey) { return } const oldData = localStorage.getItem(oldKey) if (oldData) { const parsed = JSON.parse(oldData) as T this.setUserData(newKey, parsed) localStorage.removeItem(oldKey) this.debug(`Migrated data from "${oldKey}" to user-scoped "${newKey}"`) } } catch (error) { console.error(`Failed to migrate data from "${oldKey}" to "${newKey}":`, error) } } /** * Get current user pubkey (for debugging) */ getCurrentUserPubkey(): string | null { return this.authService?.user?.value?.pubkey || null } /** * Cleanup when service is disposed (called by BaseService) */ protected async onDispose(): Promise { this.debug('StorageService disposed') } } // Export singleton instance export const storageService = new StorageService()