// lib/stores/relayStore.ts import { create } from 'zustand'; import type { RelayWithStatus } from '@/lib/db/services/RelayService'; import { RelayService } from '@/lib/db/services/RelayService'; import { useNDKStore } from './ndk'; import { NDKCommon } from '@/types/ndk-common'; // Create a singleton instance of RelayService let relayServiceInstance: RelayService | null = null; const getRelayService = (): RelayService => { if (!relayServiceInstance) { relayServiceInstance = new RelayService(); console.log('[RelayStore] Created RelayService instance'); } return relayServiceInstance; }; // Define state interface interface RelayStoreState { relays: RelayWithStatus[]; isLoading: boolean; isRefreshing: boolean; isSaving: boolean; error: Error | null; } // Define actions interface interface RelayStoreActions { loadRelays: () => Promise; addRelay: (url: string, read?: boolean, write?: boolean) => Promise; removeRelay: (url: string) => Promise; updateRelay: (url: string, changes: Partial) => Promise; applyChanges: () => Promise; resetToDefaults: () => Promise; importFromMetadata: (pubkey: string) => Promise; publishRelayList: () => Promise; } // Create the relay store export const useRelayStore = create((set, get) => { return { // Initial state relays: [], isLoading: true, isRefreshing: false, isSaving: false, error: null, // Action implementations loadRelays: async () => { try { console.log('[RelayStore] Loading relays...'); set({ isRefreshing: true, error: null }); const relayService = getRelayService(); const ndkState = useNDKStore.getState(); const ndk = ndkState.ndk as unknown as NDKCommon; if (ndk) { // Use type assertion to ensure compatibility relayService.setNDK(ndk as any); } const relays = await relayService.getAllRelaysWithStatus(); console.log(`[RelayStore] Loaded ${relays.length} relays with status`); set({ relays, isLoading: false, isRefreshing: false }); } catch (error) { console.error('[RelayStore] Error loading relays:', error); set({ error: error instanceof Error ? error : new Error('Failed to load relays'), isLoading: false, isRefreshing: false }); } }, addRelay: async (url, read = true, write = true) => { try { console.log(`[RelayStore] Adding relay: ${url}`); const relayService = getRelayService(); await relayService.addRelay(url, read, write); // Reload relays to get the updated list with status await get().loadRelays(); console.log(`[RelayStore] Successfully added relay: ${url}`); } catch (error) { console.error('[RelayStore] Error adding relay:', error); set({ error: error instanceof Error ? error : new Error('Failed to add relay') }); throw error; } }, removeRelay: async (url) => { try { console.log(`[RelayStore] Removing relay: ${url}`); const relayService = getRelayService(); await relayService.removeRelay(url); // Update local state without reload to avoid flicker set(state => ({ relays: state.relays.filter(relay => relay.url !== url) })); console.log(`[RelayStore] Successfully removed relay: ${url}`); } catch (error) { console.error('[RelayStore] Error removing relay:', error); set({ error: error instanceof Error ? error : new Error('Failed to remove relay') }); throw error; } }, updateRelay: async (url, changes) => { try { console.log(`[RelayStore] Updating relay: ${url}`, changes); const relayService = getRelayService(); // Extract only valid properties for the service const validChanges: Partial = {}; if (changes.read !== undefined) validChanges.read = changes.read; if (changes.write !== undefined) validChanges.write = changes.write; if (changes.priority !== undefined) validChanges.priority = changes.priority; await relayService.updateRelay(url, validChanges); // Update local state to reflect the changes set(state => ({ relays: state.relays.map(relay => relay.url === url ? { ...relay, ...validChanges } : relay ) })); console.log(`[RelayStore] Successfully updated relay: ${url}`); } catch (error) { console.error('[RelayStore] Error updating relay:', error); set({ error: error instanceof Error ? error : new Error('Failed to update relay') }); throw error; } }, applyChanges: async () => { // Prevent multiple simultaneous calls if (get().isSaving) return false; try { console.log('[RelayStore] Applying relay changes...'); set({ isSaving: true, error: null }); const relayService = getRelayService(); const ndkState = useNDKStore.getState(); const ndk = ndkState.ndk as unknown as NDKCommon; if (!ndk) { throw new Error('NDK not initialized'); } // Apply relay config changes to NDK const relays = get().relays; const success = await relayService.applyRelayConfig(relays); // Wait a moment before reloading to give connections time to establish await new Promise(resolve => setTimeout(resolve, 2000)); // Reload relays to reflect updated connection status await get().loadRelays(); set({ isSaving: false }); console.log('[RelayStore] Successfully applied relay changes'); return success; } catch (error) { console.error('[RelayStore] Error applying changes:', error); set({ error: error instanceof Error ? error : new Error('Failed to apply changes'), isSaving: false }); return false; } }, resetToDefaults: async () => { try { console.log('[RelayStore] Resetting relays to defaults...'); const relayService = getRelayService(); await relayService.resetToDefaults(); // Reload relays to get the updated list await get().loadRelays(); console.log('[RelayStore] Successfully reset relays to defaults'); } catch (error) { console.error('[RelayStore] Error resetting relays:', error); set({ error: error instanceof Error ? error : new Error('Failed to reset relays') }); throw error; } }, importFromMetadata: async (pubkey) => { try { console.log('[RelayStore] Importing relays from user metadata...'); set({ isRefreshing: true, error: null }); const relayService = getRelayService(); const ndkState = useNDKStore.getState(); const ndk = ndkState.ndk; if (!ndk) { throw new Error('NDK not initialized'); } // Import relays from the user's metadata await relayService.importFromUserMetadata(pubkey, ndk); // Reload relays to get the updated list await get().loadRelays(); console.log('[RelayStore] Successfully imported relays from metadata'); } catch (error) { console.error('[RelayStore] Error importing from metadata:', error); set({ error: error instanceof Error ? error : new Error('Failed to import from metadata'), isRefreshing: false }); throw error; } }, publishRelayList: async () => { try { console.log('[RelayStore] Publishing relay list...'); const relayService = getRelayService(); const ndkState = useNDKStore.getState(); const ndk = ndkState.ndk; if (!ndk) { throw new Error('NDK not initialized'); } // Publish relay list to the network const result = await relayService.publishRelayList(ndk); console.log('[RelayStore] Successfully published relay list'); return result; } catch (error) { console.error('[RelayStore] Error publishing relay list:', error); set({ error: error instanceof Error ? error : new Error('Failed to publish relay list') }); throw error; } } }; }); // Export individual hooks for specific use cases export function useLoadRelays() { return { loadRelays: useRelayStore(state => state.loadRelays), isLoading: useRelayStore(state => state.isLoading), isRefreshing: useRelayStore(state => state.isRefreshing) }; }