281 lines
7.4 KiB
TypeScript
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'
|