import { decryptPayload, encryptPayload, type EncryptedPayload } from './cryptoHelpers' import { createIndexedDBHelper, type IndexedDBHelper } from '../helpers/indexedDBHelper' const DB_NAME = 'nostr_paywall' const DB_VERSION = 1 const STORE_NAME = 'article_content' interface DBData { id: string data: EncryptedPayload createdAt: number expiresAt?: number } /** * IndexedDB storage service for article content * More robust than localStorage and supports larger data sizes */ export class IndexedDBStorage { private readonly dbHelper: IndexedDBHelper constructor() { this.dbHelper = createIndexedDBHelper({ dbName: DB_NAME, version: DB_VERSION, storeName: STORE_NAME, keyPath: 'id', indexes: [ { name: 'createdAt', keyPath: 'createdAt', unique: false }, { name: 'expiresAt', keyPath: 'expiresAt', unique: false }, ], }) } /** * Store data in IndexedDB */ async set(key: string, value: unknown, secret: string, expiresIn?: number): Promise { try { const encrypted = await encryptPayload(secret, value) const now = Date.now() const data: DBData = { id: key, data: encrypted, createdAt: now, ...(expiresIn ? { expiresAt: now + expiresIn } : {}), } await this.dbHelper.put(data) } catch (error) { console.error('Error storing in IndexedDB:', error) throw error } } /** * Get data from IndexedDB */ async get(key: string, secret: string): Promise { try { const result = await this.dbHelper.get(key) if (!result) { return null } if (result.expiresAt && result.expiresAt < Date.now()) { await this.delete(key).catch(console.error) return null } try { return await decryptPayload(secret, result.data) } catch (decryptError) { console.error('Error decrypting from IndexedDB:', decryptError) return null } } catch (error) { console.error('Error getting from IndexedDB:', error) return null } } /** * Delete data from IndexedDB */ async delete(key: string): Promise { try { await this.dbHelper.delete(key) } catch (error) { console.error('Error deleting from IndexedDB:', error) throw error } } /** * Clear all expired entries */ async clearExpired(): Promise { try { const store = await this.dbHelper.getStoreWrite('readwrite') const index = store.index('expiresAt') return new Promise((resolve, reject) => { const request = index.openCursor(IDBKeyRange.upperBound(Date.now())) request.onsuccess = (event): void => { const cursor = (event.target as IDBRequest).result if (cursor) { cursor.delete() cursor.continue() } else { resolve() } } request.onerror = (): void => { if (request.error) { reject(new Error(`Failed to clear expired: ${request.error}`)) } else { reject(new Error('Unknown error clearing expired entries')) } } }) } 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()