const DB_NAME = 'nostr_paywall' const DB_VERSION = 1 const STORE_NAME = 'article_content' interface DBData { id: string data: any createdAt: number expiresAt?: number } /** * IndexedDB storage service for article content * More robust than localStorage and supports larger data sizes */ export class IndexedDBStorage { private db: IDBDatabase | null = null private initPromise: Promise | null = null /** * Initialize the IndexedDB database */ private async init(): Promise { if (this.db) { return } if (this.initPromise) { return this.initPromise } this.initPromise = new Promise((resolve, reject) => { if (typeof window === 'undefined' || !window.indexedDB) { reject(new Error('IndexedDB is not available. This application requires IndexedDB support.')) return } const request = indexedDB.open(DB_NAME, DB_VERSION) request.onerror = () => { reject(new Error(`Failed to open IndexedDB: ${request.error}`)) } request.onsuccess = () => { this.db = request.result resolve() } request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result // Create object store if it doesn't exist if (!db.objectStoreNames.contains(STORE_NAME)) { const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' }) store.createIndex('createdAt', 'createdAt', { unique: false }) store.createIndex('expiresAt', 'expiresAt', { unique: false }) } } }) try { await this.initPromise } catch (error) { this.initPromise = null throw error } } /** * Store data in IndexedDB */ async set(key: string, value: any, expiresIn?: number): Promise { try { await this.init() if (!this.db) { throw new Error('Database not initialized') } const now = Date.now() const data: DBData = { id: key, data: value, createdAt: now, expiresAt: expiresIn ? now + expiresIn : undefined, } return new Promise((resolve, reject) => { const transaction = this.db!.transaction([STORE_NAME], 'readwrite') const store = transaction.objectStore(STORE_NAME) const request = store.put(data) request.onsuccess = () => resolve() request.onerror = () => reject(new Error(`Failed to store data: ${request.error}`)) }) } catch (error) { console.error('Error storing in IndexedDB:', error) throw error } } /** * Get data from IndexedDB */ async get(key: string): Promise { try { await this.init() if (!this.db) { throw new Error('Database not initialized') } return new Promise((resolve, reject) => { const transaction = this.db!.transaction([STORE_NAME], 'readonly') const store = transaction.objectStore(STORE_NAME) const request = store.get(key) request.onsuccess = () => { const result = request.result as DBData | undefined if (!result) { resolve(null) return } // Check if expired if (result.expiresAt && result.expiresAt < Date.now()) { // Delete expired data this.delete(key).catch(console.error) resolve(null) return } resolve(result.data as T) } request.onerror = () => reject(new Error(`Failed to get data: ${request.error}`)) }) } catch (error) { console.error('Error getting from IndexedDB:', error) return null } } /** * Delete data from IndexedDB */ async delete(key: string): Promise { try { await this.init() if (!this.db) { throw new Error('Database not initialized') } return new Promise((resolve, reject) => { const transaction = this.db!.transaction([STORE_NAME], 'readwrite') const store = transaction.objectStore(STORE_NAME) const request = store.delete(key) request.onsuccess = () => resolve() request.onerror = () => reject(new Error(`Failed to delete data: ${request.error}`)) }) } catch (error) { console.error('Error deleting from IndexedDB:', error) throw error } } /** * Clear all expired entries */ async clearExpired(): Promise { try { await this.init() if (!this.db) { throw new Error('Database not initialized') } return new Promise((resolve, reject) => { const transaction = this.db!.transaction([STORE_NAME], 'readwrite') const store = transaction.objectStore(STORE_NAME) const index = store.index('expiresAt') const request = index.openCursor(IDBKeyRange.upperBound(Date.now())) request.onsuccess = (event) => { const cursor = (event.target as IDBRequest).result if (cursor) { cursor.delete() cursor.continue() } else { resolve() } } request.onerror = () => reject(new Error(`Failed to clear expired: ${request.error}`)) }) } catch (error) { console.error('Error clearing expired entries:', error) throw error } } /** * Check if IndexedDB is available */ static isAvailable(): boolean { return typeof window !== 'undefined' && typeof window.indexedDB !== 'undefined' } } export const storageService = new IndexedDBStorage()