Fix Auth Errors

This commit is contained in:
Eric Gullickson
2025-09-22 10:27:10 -05:00
parent 3588372cef
commit 8fd7973656
19 changed files with 1342 additions and 174 deletions

View 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;
};