story-research-zapwall/lib/configStorage.ts

281 lines
7.4 KiB
TypeScript

/**
* 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<void> | null = null
/**
* Initialize the IndexedDB database
*/
private async init(): Promise<void> {
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<void> {
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<ConfigData> {
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<void> {
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<string[]> {
const config = await this.getConfig()
return getEnabledRelays(config)
}
/**
* Get primary relay URL (first enabled relay)
*/
async getPrimaryRelay(): Promise<string> {
const config = await this.getConfig()
return getPrimaryRelayFromConfig(config)
}
/**
* Get enabled NIP-95 APIs sorted by priority
*/
async getEnabledNip95Apis(): Promise<string[]> {
const config = await this.getConfig()
return getEnabledNip95Apis(config)
}
/**
* Get primary NIP-95 API URL (first enabled API)
*/
async getPrimaryNip95Api(): Promise<string> {
const config = await this.getConfig()
return getPrimaryNip95ApiFromConfig(config)
}
/**
* Get platform Lightning address
*/
async getPlatformLightningAddress(): Promise<string> {
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<void> {
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<void> {
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<Omit<import('./configStorageTypes').RelayConfig, 'id' | 'createdAt'>>): Promise<void> {
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<Omit<import('./configStorageTypes').Nip95Config, 'id' | 'createdAt'>>): Promise<void> {
const config = await this.getConfig()
const updatedConfig = updateNip95ApiInConfig(config, id, updates)
await this.saveConfig(updatedConfig)
}
/**
* Remove a relay
*/
async removeRelay(id: string): Promise<void> {
const config = await this.getConfig()
const updatedConfig = removeRelayFromConfig(config, id)
await this.saveConfig(updatedConfig)
}
/**
* Remove a NIP-95 API
*/
async removeNip95Api(id: string): Promise<void> {
const config = await this.getConfig()
const updatedConfig = removeNip95ApiFromConfig(config, id)
await this.saveConfig(updatedConfig)
}
/**
* Set platform Lightning address
*/
async setPlatformLightningAddress(address: string): Promise<void> {
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'