Start integrating the onboarding flow with backend 5

This commit is contained in:
Urtzi Alfaro
2025-09-05 12:58:05 +02:00
parent 3fe1f17610
commit 236c3a32ae

View File

@@ -1,397 +0,0 @@
/**
* Storage Service - Provides secure and consistent local/session storage management
* with encryption, expiration, and type safety
*/
interface StorageOptions {
encrypt?: boolean;
expiresIn?: number; // milliseconds
storage?: 'local' | 'session';
}
interface StorageItem<T = any> {
value: T;
encrypted?: boolean;
expiresAt?: number;
createdAt: number;
}
class StorageService {
private readonly encryptionKey = 'bakery-app-key'; // In production, use proper key management
/**
* Store data in browser storage
*/
setItem<T>(key: string, value: T, options: StorageOptions = {}): boolean {
try {
const {
encrypt = false,
expiresIn,
storage = 'local'
} = options;
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
const item: StorageItem = {
value: encrypt ? this.encrypt(JSON.stringify(value)) : value,
encrypted: encrypt,
createdAt: Date.now(),
...(expiresIn && { expiresAt: Date.now() + expiresIn })
};
storageInstance.setItem(key, JSON.stringify(item));
return true;
} catch (error) {
console.error(`Storage error setting item "${key}":`, error);
return false;
}
}
/**
* Retrieve data from browser storage
*/
getItem<T>(key: string, storage: 'local' | 'session' = 'local'): T | null {
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
const itemStr = storageInstance.getItem(key);
if (!itemStr) {
return null;
}
const item: StorageItem<T> = JSON.parse(itemStr);
// Check expiration
if (item.expiresAt && Date.now() > item.expiresAt) {
this.removeItem(key, storage);
return null;
}
// Handle encrypted data
if (item.encrypted && typeof item.value === 'string') {
try {
const decrypted = this.decrypt(item.value);
return JSON.parse(decrypted);
} catch (error) {
console.error(`Failed to decrypt item "${key}":`, error);
return null;
}
}
return item.value;
} catch (error) {
console.error(`Storage error getting item "${key}":`, error);
return null;
}
}
/**
* Remove item from storage
*/
removeItem(key: string, storage: 'local' | 'session' = 'local'): boolean {
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
storageInstance.removeItem(key);
return true;
} catch (error) {
console.error(`Storage error removing item "${key}":`, error);
return false;
}
}
/**
* Check if item exists and is not expired
*/
hasItem(key: string, storage: 'local' | 'session' = 'local'): boolean {
return this.getItem(key, storage) !== null;
}
/**
* Clear all items from storage
*/
clear(storage: 'local' | 'session' = 'local'): boolean {
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
storageInstance.clear();
return true;
} catch (error) {
console.error('Storage error clearing storage:', error);
return false;
}
}
/**
* Get all keys from storage with optional prefix filter
*/
getKeys(prefix?: string, storage: 'local' | 'session' = 'local'): string[] {
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
const keys: string[] = [];
for (let i = 0; i < storageInstance.length; i++) {
const key = storageInstance.key(i);
if (key && (!prefix || key.startsWith(prefix))) {
keys.push(key);
}
}
return keys;
} catch (error) {
console.error('Storage error getting keys:', error);
return [];
}
}
/**
* Get storage usage information
*/
getStorageInfo(storage: 'local' | 'session' = 'local'): {
used: number;
total: number;
available: number;
itemCount: number;
} {
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
// Calculate used space (approximate)
let used = 0;
for (let i = 0; i < storageInstance.length; i++) {
const key = storageInstance.key(i);
if (key) {
const value = storageInstance.getItem(key);
used += key.length + (value?.length || 0);
}
}
// Most browsers have ~5-10MB limit for localStorage
const estimated_total = 5 * 1024 * 1024; // 5MB in bytes
return {
used,
total: estimated_total,
available: estimated_total - used,
itemCount: storageInstance.length
};
} catch (error) {
console.error('Storage error getting storage info:', error);
return { used: 0, total: 0, available: 0, itemCount: 0 };
}
}
/**
* Clean expired items from storage
*/
cleanExpired(storage: 'local' | 'session' = 'local'): number {
let cleanedCount = 0;
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
const keysToRemove: string[] = [];
for (let i = 0; i < storageInstance.length; i++) {
const key = storageInstance.key(i);
if (key) {
try {
const itemStr = storageInstance.getItem(key);
if (itemStr) {
const item: StorageItem = JSON.parse(itemStr);
if (item.expiresAt && Date.now() > item.expiresAt) {
keysToRemove.push(key);
}
}
} catch (error) {
// If we can't parse the item, it might be corrupted
keysToRemove.push(key);
}
}
}
keysToRemove.forEach(key => {
storageInstance.removeItem(key);
cleanedCount++;
});
} catch (error) {
console.error('Storage error cleaning expired items:', error);
}
return cleanedCount;
}
/**
* Backup storage to JSON
*/
backup(storage: 'local' | 'session' = 'local'): string {
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
const backup: Record<string, any> = {};
for (let i = 0; i < storageInstance.length; i++) {
const key = storageInstance.key(i);
if (key) {
const value = storageInstance.getItem(key);
if (value) {
backup[key] = value;
}
}
}
return JSON.stringify({
timestamp: new Date().toISOString(),
storage: storage,
data: backup
}, null, 2);
} catch (error) {
console.error('Storage error creating backup:', error);
return '{}';
}
}
/**
* Restore storage from JSON backup
*/
restore(backupData: string, storage: 'local' | 'session' = 'local'): boolean {
try {
const backup = JSON.parse(backupData);
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
if (backup.data) {
Object.entries(backup.data).forEach(([key, value]) => {
if (typeof value === 'string') {
storageInstance.setItem(key, value);
}
});
return true;
}
return false;
} catch (error) {
console.error('Storage error restoring backup:', error);
return false;
}
}
// Encryption utilities (basic implementation - use proper crypto in production)
private encrypt(text: string): string {
try {
// This is a simple XOR cipher - replace with proper encryption in production
let result = '';
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(
text.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
);
}
return btoa(result);
} catch (error) {
console.error('Encryption error:', error);
return text;
}
}
private decrypt(encryptedText: string): string {
try {
const text = atob(encryptedText);
let result = '';
for (let i = 0; i < text.length; i++) {
result += String.fromCharCode(
text.charCodeAt(i) ^ this.encryptionKey.charCodeAt(i % this.encryptionKey.length)
);
}
return result;
} catch (error) {
console.error('Decryption error:', error);
return encryptedText;
}
}
// Convenience methods for common operations
/**
* Store user authentication data
*/
setAuthData(data: {
access_token: string;
refresh_token?: string;
user_data?: any;
tenant_id?: string;
}): boolean {
const success = [
this.setItem('access_token', data.access_token, { encrypt: true }),
data.refresh_token ? this.setItem('refresh_token', data.refresh_token, { encrypt: true }) : true,
data.user_data ? this.setItem('user_data', data.user_data) : true,
data.tenant_id ? this.setItem('tenant_id', data.tenant_id) : true,
].every(Boolean);
return success;
}
/**
* Clear all authentication data
*/
clearAuthData(): boolean {
return [
this.removeItem('access_token'),
this.removeItem('refresh_token'),
this.removeItem('user_data'),
this.removeItem('tenant_id'),
].every(Boolean);
}
/**
* Store app preferences
*/
setPreferences(preferences: Record<string, any>): boolean {
return this.setItem('app_preferences', preferences);
}
/**
* Get app preferences
*/
getPreferences<T = Record<string, any>>(): T | null {
return this.getItem<T>('app_preferences');
}
/**
* Store temporary session data with automatic expiration
*/
setSessionData(key: string, data: any, expiresInMinutes: number = 30): boolean {
return this.setItem(key, data, {
storage: 'session',
expiresIn: expiresInMinutes * 60 * 1000
});
}
/**
* Get temporary session data
*/
getSessionData<T>(key: string): T | null {
return this.getItem<T>(key, 'session');
}
/**
* Check storage availability
*/
isStorageAvailable(storage: 'local' | 'session' = 'local'): boolean {
try {
const storageInstance = storage === 'session' ? sessionStorage : localStorage;
const test = '__storage_test__';
storageInstance.setItem(test, test);
storageInstance.removeItem(test);
return true;
} catch (error) {
return false;
}
}
}
// Export singleton instance
export const storageService = new StorageService();
// Export class for testing or multiple instances
export { StorageService };
// Legacy compatibility functions
export const getStorageItem = <T>(key: string): T | null => storageService.getItem<T>(key);
export const setStorageItem = <T>(key: string, value: T, options?: StorageOptions): boolean =>
storageService.setItem(key, value, options);
export const removeStorageItem = (key: string): boolean => storageService.removeItem(key);