Fix Auth Errors
This commit is contained in:
211
frontend/src/core/utils/indexeddb-storage.ts
Normal file
211
frontend/src/core/utils/indexeddb-storage.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @ai-summary IndexedDB storage adapter for Auth0 and Zustand persistence
|
||||
* @ai-context Replaces localStorage with IndexedDB for mobile browser compatibility
|
||||
*/
|
||||
|
||||
interface StorageAdapter {
|
||||
getItem(key: string): string | null;
|
||||
setItem(key: string, value: string): void;
|
||||
removeItem(key: string): void;
|
||||
clear(): void;
|
||||
key(index: number): string | null;
|
||||
readonly length: number;
|
||||
}
|
||||
|
||||
interface Auth0Cache {
|
||||
get(key: string): Promise<any>;
|
||||
set(key: string, value: any): Promise<void>;
|
||||
remove(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
class IndexedDBStorage implements StorageAdapter, Auth0Cache {
|
||||
private dbName = 'motovaultpro-storage';
|
||||
private dbVersion = 1;
|
||||
private storeName = 'keyvalue';
|
||||
private db: IDBDatabase | null = null;
|
||||
private memoryCache = new Map<string, string>();
|
||||
private initPromise: Promise<void>;
|
||||
private isReady = false;
|
||||
|
||||
constructor() {
|
||||
this.initPromise = this.initialize();
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
try {
|
||||
this.db = await this.openDatabase();
|
||||
await this.loadCacheFromDB();
|
||||
this.isReady = true;
|
||||
console.log('[IndexedDB] Storage initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('[IndexedDB] Initialization failed, using memory only:', error);
|
||||
this.isReady = false;
|
||||
}
|
||||
}
|
||||
|
||||
private openDatabase(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve) => {
|
||||
const request = indexedDB.open(this.dbName, this.dbVersion);
|
||||
|
||||
request.onerror = () => {
|
||||
console.error(`IndexedDB open failed: ${request.error?.message}`);
|
||||
resolve(null as any); // Fallback to memory-only mode
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
db.createObjectStore(this.storeName);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async loadCacheFromDB(): Promise<void> {
|
||||
if (!this.db) return;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const transaction = this.db!.transaction([this.storeName], 'readonly');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const request = store.getAll();
|
||||
|
||||
request.onsuccess = () => {
|
||||
const results = request.result;
|
||||
this.memoryCache.clear();
|
||||
|
||||
for (const item of results) {
|
||||
if (item.key && typeof item.value === 'string') {
|
||||
this.memoryCache.set(item.key, item.value);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[IndexedDB] Loaded ${this.memoryCache.size} items into cache`);
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
console.warn('[IndexedDB] Failed to load cache from DB:', request.error);
|
||||
resolve(); // Don't fail initialization
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async persistToDB(key: string, value: string | null): Promise<void> {
|
||||
if (!this.db) return;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const transaction = this.db!.transaction([this.storeName], 'readwrite');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
|
||||
if (value === null) {
|
||||
const request = store.delete(key);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => {
|
||||
console.warn(`[IndexedDB] Failed to delete ${key}:`, request.error);
|
||||
resolve();
|
||||
};
|
||||
} else {
|
||||
const request = store.put(value, key);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => {
|
||||
console.warn(`[IndexedDB] Failed to persist ${key}:`, request.error);
|
||||
resolve();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Synchronous Storage interface (uses memory cache)
|
||||
getItem(key: string): string | null {
|
||||
return this.memoryCache.get(key) || null;
|
||||
}
|
||||
|
||||
setItem(key: string, value: string): void {
|
||||
this.memoryCache.set(key, value);
|
||||
|
||||
// Async persist to IndexedDB (non-blocking)
|
||||
if (this.isReady) {
|
||||
this.persistToDB(key, value).catch(error => {
|
||||
console.warn(`[IndexedDB] Background persist failed for ${key}:`, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
this.memoryCache.delete(key);
|
||||
|
||||
// Async remove from IndexedDB (non-blocking)
|
||||
if (this.isReady) {
|
||||
this.persistToDB(key, null).catch(error => {
|
||||
console.warn(`[IndexedDB] Background removal failed for ${key}:`, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.memoryCache.clear();
|
||||
|
||||
// Async clear IndexedDB (non-blocking)
|
||||
if (this.db) {
|
||||
const transaction = this.db.transaction([this.storeName], 'readwrite');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
store.clear();
|
||||
}
|
||||
}
|
||||
|
||||
key(index: number): string | null {
|
||||
const keys = Array.from(this.memoryCache.keys());
|
||||
return keys[index] || null;
|
||||
}
|
||||
|
||||
get length(): number {
|
||||
return this.memoryCache.size;
|
||||
}
|
||||
|
||||
// Auth0 Cache interface implementation
|
||||
async get(key: string): Promise<any> {
|
||||
await this.initPromise;
|
||||
const value = this.getItem(key);
|
||||
return value ? JSON.parse(value) : undefined;
|
||||
}
|
||||
|
||||
async set(key: string, value: any): Promise<void> {
|
||||
await this.initPromise;
|
||||
this.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
async remove(key: string): Promise<void> {
|
||||
await this.initPromise;
|
||||
this.removeItem(key);
|
||||
}
|
||||
|
||||
// Additional methods for enhanced functionality
|
||||
async waitForReady(): Promise<void> {
|
||||
return this.initPromise;
|
||||
}
|
||||
|
||||
get isInitialized(): boolean {
|
||||
return this.isReady;
|
||||
}
|
||||
|
||||
// For debugging
|
||||
getStats() {
|
||||
return {
|
||||
cacheSize: this.memoryCache.size,
|
||||
isReady: this.isReady,
|
||||
hasDB: !!this.db
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const indexedDBStorage = new IndexedDBStorage();
|
||||
|
||||
// For Auth0 compatibility - ensure storage is ready before use
|
||||
export const createIndexedDBAdapter = () => {
|
||||
return indexedDBStorage;
|
||||
};
|
||||
@@ -1,107 +1,9 @@
|
||||
/**
|
||||
* @ai-summary Safe localStorage wrapper for mobile browsers
|
||||
* @ai-context Prevents errors when localStorage is blocked in mobile browsers
|
||||
* @ai-summary IndexedDB storage wrapper for mobile browsers
|
||||
* @ai-context Replaces localStorage with IndexedDB for better mobile compatibility
|
||||
*/
|
||||
|
||||
// Safe localStorage wrapper that won't crash on mobile browsers
|
||||
const createSafeStorage = () => {
|
||||
let isAvailable = false;
|
||||
import { indexedDBStorage } from './indexeddb-storage';
|
||||
|
||||
// Test localStorage availability
|
||||
try {
|
||||
const testKey = '__motovaultpro_storage_test__';
|
||||
localStorage.setItem(testKey, 'test');
|
||||
localStorage.removeItem(testKey);
|
||||
isAvailable = true;
|
||||
} catch (error) {
|
||||
console.warn('[Storage] localStorage not available, using memory fallback:', error);
|
||||
isAvailable = false;
|
||||
}
|
||||
|
||||
// Memory fallback when localStorage is blocked
|
||||
const memoryStorage = new Map<string, string>();
|
||||
|
||||
return {
|
||||
getItem: (key: string): string | null => {
|
||||
try {
|
||||
if (isAvailable) {
|
||||
return localStorage.getItem(key);
|
||||
} else {
|
||||
return memoryStorage.get(key) || null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[Storage] getItem failed, using memory fallback:', error);
|
||||
return memoryStorage.get(key) || null;
|
||||
}
|
||||
},
|
||||
|
||||
setItem: (key: string, value: string): void => {
|
||||
try {
|
||||
if (isAvailable) {
|
||||
localStorage.setItem(key, value);
|
||||
} else {
|
||||
memoryStorage.set(key, value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[Storage] setItem failed, using memory fallback:', error);
|
||||
memoryStorage.set(key, value);
|
||||
}
|
||||
},
|
||||
|
||||
removeItem: (key: string): void => {
|
||||
try {
|
||||
if (isAvailable) {
|
||||
localStorage.removeItem(key);
|
||||
} else {
|
||||
memoryStorage.delete(key);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[Storage] removeItem failed, using memory fallback:', error);
|
||||
memoryStorage.delete(key);
|
||||
}
|
||||
},
|
||||
|
||||
// For zustand createJSONStorage compatibility
|
||||
key: (index: number): string | null => {
|
||||
try {
|
||||
if (isAvailable) {
|
||||
return localStorage.key(index);
|
||||
} else {
|
||||
const keys = Array.from(memoryStorage.keys());
|
||||
return keys[index] || null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[Storage] key access failed:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
get length(): number {
|
||||
try {
|
||||
if (isAvailable) {
|
||||
return localStorage.length;
|
||||
} else {
|
||||
return memoryStorage.size;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[Storage] length access failed:', error);
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
clear: (): void => {
|
||||
try {
|
||||
if (isAvailable) {
|
||||
localStorage.clear();
|
||||
} else {
|
||||
memoryStorage.clear();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[Storage] clear failed:', error);
|
||||
memoryStorage.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const safeStorage = createSafeStorage();
|
||||
// Export IndexedDB storage as the safe storage implementation
|
||||
export const safeStorage = indexedDBStorage;
|
||||
Reference in New Issue
Block a user