/** * Configuration storage service using IndexedDB * Stores Nostr relay URLs and NIP-95 API endpoints */ import type { ConfigData } from './configStorageTypes' import { DEFAULT_RELAYS, DEFAULT_NIP95_APIS, DEFAULT_PLATFORM_LIGHTNING_ADDRESS } from './configStorageTypes' import { getEnabledRelays, getPrimaryRelayFromConfig, addRelayToConfig, updateRelayInConfig, removeRelayFromConfig, } from './configStorageRelays' import { getEnabledNip95Apis, getPrimaryNip95ApiFromConfig, addNip95ApiToConfig, updateNip95ApiInConfig, removeNip95ApiFromConfig, } from './configStorageNip95' const DB_NAME = 'nostr_paywall_config' const DB_VERSION = 1 const STORE_NAME = 'config' /** * IndexedDB storage service for application configuration */ export class ConfigStorage { 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 = this.openDatabase() try { await this.initPromise } catch (error) { this.initPromise = null throw error } } private openDatabase(): Promise { return 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 if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME, { keyPath: 'key' }) } } }) } /** * Get all configuration from IndexedDB or return defaults */ async getConfig(): Promise { try { await this.init() if (!this.db) { return this.getDefaultConfig() } const db = this.db return new Promise((resolve) => { const transaction = db.transaction([STORE_NAME], 'readonly') const store = transaction.objectStore(STORE_NAME) const request = store.get('config') request.onsuccess = () => { const result = request.result as { key: string; value: ConfigData } | undefined if (!result?.value) { resolve(this.getDefaultConfig()) return } resolve(result.value) } request.onerror = () => { console.warn('Failed to read config from IndexedDB, using defaults') resolve(this.getDefaultConfig()) } }) } catch (error) { console.error('Error getting config from IndexedDB:', error) return this.getDefaultConfig() } } /** * Save configuration to IndexedDB */ async saveConfig(config: ConfigData): Promise { try { await this.init() if (!this.db) { throw new Error('Database not initialized') } const db = this.db return new Promise((resolve, reject) => { const transaction = db.transaction([STORE_NAME], 'readwrite') const store = transaction.objectStore(STORE_NAME) const request = store.put({ key: 'config', value: { ...config, updatedAt: Date.now(), }, }) request.onsuccess = () => resolve() request.onerror = () => reject(new Error(`Failed to save config: ${request.error}`)) }) } catch (error) { console.error('Error saving config to IndexedDB:', error) throw error } } /** * Get default configuration */ private getDefaultConfig(): ConfigData { return { relays: [...DEFAULT_RELAYS], nip95Apis: [...DEFAULT_NIP95_APIS], platformLightningAddress: DEFAULT_PLATFORM_LIGHTNING_ADDRESS, updatedAt: Date.now(), } } /** * Get enabled relays sorted by priority */ async getEnabledRelays(): Promise { const config = await this.getConfig() return getEnabledRelays(config) } /** * Get primary relay URL (first enabled relay) */ async getPrimaryRelay(): Promise { const config = await this.getConfig() return getPrimaryRelayFromConfig(config) } /** * Get enabled NIP-95 APIs sorted by priority */ async getEnabledNip95Apis(): Promise { const config = await this.getConfig() return getEnabledNip95Apis(config) } /** * Get primary NIP-95 API URL (first enabled API) */ async getPrimaryNip95Api(): Promise { const config = await this.getConfig() return getPrimaryNip95ApiFromConfig(config) } /** * Get platform Lightning address */ async getPlatformLightningAddress(): Promise { const config = await this.getConfig() return config.platformLightningAddress || DEFAULT_PLATFORM_LIGHTNING_ADDRESS } /** * Add a new relay */ async addRelay(url: string, enabled: boolean = true, priority?: number): Promise { const config = await this.getConfig() const updatedConfig = addRelayToConfig(config, url, enabled, priority) await this.saveConfig(updatedConfig) } /** * Add a new NIP-95 API */ async addNip95Api(url: string, enabled: boolean = true, priority?: number): Promise { const config = await this.getConfig() const updatedConfig = addNip95ApiToConfig(config, url, enabled, priority) await this.saveConfig(updatedConfig) } /** * Update relay configuration */ async updateRelay(id: string, updates: Partial>): Promise { const config = await this.getConfig() const updatedConfig = updateRelayInConfig(config, id, updates) await this.saveConfig(updatedConfig) } /** * Update NIP-95 API configuration */ async updateNip95Api(id: string, updates: Partial>): Promise { const config = await this.getConfig() const updatedConfig = updateNip95ApiInConfig(config, id, updates) await this.saveConfig(updatedConfig) } /** * Remove a relay */ async removeRelay(id: string): Promise { const config = await this.getConfig() const updatedConfig = removeRelayFromConfig(config, id) await this.saveConfig(updatedConfig) } /** * Remove a NIP-95 API */ async removeNip95Api(id: string): Promise { const config = await this.getConfig() const updatedConfig = removeNip95ApiFromConfig(config, id) await this.saveConfig(updatedConfig) } /** * Set platform Lightning address */ async setPlatformLightningAddress(address: string): Promise { const config = await this.getConfig() config.platformLightningAddress = address await this.saveConfig(config) } /** * Check if IndexedDB is available */ static isAvailable(): boolean { return typeof window !== 'undefined' && typeof window.indexedDB !== 'undefined' } } export const configStorage = new ConfigStorage() export { getPrimaryRelaySync, getPrimaryNip95ApiSync, getPlatformLightningAddressSync } from './configStorageSync'