import { useState, useEffect } from 'react' import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faSpinner } from '@fortawesome/free-solid-svg-icons' import { EventStoreProvider, AccountsProvider, Hooks } from 'applesauce-react' import { EventStore } from 'applesauce-core' import { AccountManager } from 'applesauce-accounts' import { registerCommonAccountTypes } from 'applesauce-accounts/accounts' import { RelayPool } from 'applesauce-relay' import { createAddressLoader } from 'applesauce-loaders/loaders' import Bookmarks from './components/Bookmarks' import Toast from './components/Toast' import { useToast } from './hooks/useToast' import { useOnlineStatus } from './hooks/useOnlineStatus' import { RELAYS } from './config/relays' const DEFAULT_ARTICLE = import.meta.env.VITE_DEFAULT_ARTICLE_NADDR || 'naddr1qvzqqqr4gupzqmjxss3dld622uu8q25gywum9qtg4w4cv4064jmg20xsac2aam5nqqxnzd3cxqmrzv3exgmr2wfesgsmew' // AppRoutes component that has access to hooks function AppRoutes({ relayPool, showToast }: { relayPool: RelayPool showToast: (message: string) => void }) { const accountManager = Hooks.useAccountManager() const handleLogout = () => { accountManager.clearActive() showToast('Logged out successfully') } return ( } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ) } function App() { const [eventStore, setEventStore] = useState(null) const [accountManager, setAccountManager] = useState(null) const [relayPool, setRelayPool] = useState(null) const { toastMessage, toastType, showToast, clearToast } = useToast() const isOnline = useOnlineStatus() useEffect(() => { const initializeApp = async () => { // Initialize event store, account manager, and relay pool const store = new EventStore() const accounts = new AccountManager() // Register common account types (needed for deserialization) registerCommonAccountTypes(accounts) // Load persisted accounts from localStorage try { const json = JSON.parse(localStorage.getItem('accounts') || '[]') await accounts.fromJSON(json) console.log('Loaded', accounts.accounts.length, 'accounts from storage') // Load active account from storage const activeId = localStorage.getItem('active') if (activeId && accounts.getAccount(activeId)) { accounts.setActive(activeId) console.log('Restored active account:', activeId) } } catch (err) { console.error('Failed to load accounts from storage:', err) } // Subscribe to accounts changes and persist to localStorage const accountsSub = accounts.accounts$.subscribe(() => { localStorage.setItem('accounts', JSON.stringify(accounts.toJSON())) }) // Subscribe to active account changes and persist to localStorage const activeSub = accounts.active$.subscribe((account) => { if (account) { localStorage.setItem('active', account.id) } else { localStorage.removeItem('active') } }) const pool = new RelayPool() // Create a relay group for better event deduplication and management pool.group(RELAYS) console.log('Created relay group with', RELAYS.length, 'relays (including local)') console.log('Relay URLs:', RELAYS) // Keep all relay connections alive indefinitely by creating a persistent subscription // This prevents disconnection when no other subscriptions are active // Create a minimal subscription that never completes to keep connections alive const keepAliveSub = pool.subscription(RELAYS, { kinds: [0], limit: 0 }).subscribe({ next: () => {}, // No-op, we don't care about events error: (err) => console.warn('Keep-alive subscription error:', err) }) console.log('🔗 Created keep-alive subscription for', RELAYS.length, 'relay(s)') // Store subscription for cleanup // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(pool as any)._keepAliveSubscription = keepAliveSub // Attach address/replaceable loaders so ProfileModel can fetch profiles const addressLoader = createAddressLoader(pool, { eventStore: store, lookupRelays: RELAYS }) store.addressableLoader = addressLoader store.replaceableLoader = addressLoader setEventStore(store) setAccountManager(accounts) setRelayPool(pool) // Cleanup function return () => { accountsSub.unsubscribe() activeSub.unsubscribe() // Clean up keep-alive subscription if it exists // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((pool as any)._keepAliveSubscription) { // eslint-disable-next-line @typescript-eslint/no-explicit-any (pool as any)._keepAliveSubscription.unsubscribe() } } } let cleanup: (() => void) | undefined initializeApp().then((fn) => { cleanup = fn }) return () => { if (cleanup) cleanup() } }, []) // Monitor online/offline status useEffect(() => { if (!isOnline) { showToast('You are offline. Some features may be limited.') } }, [isOnline, showToast]) // Listen for service worker updates useEffect(() => { const handleSWUpdate = () => { showToast('New version available! Refresh to update.') } window.addEventListener('sw-update-available', handleSWUpdate) return () => { window.removeEventListener('sw-update-available', handleSWUpdate) } }, [showToast]) if (!eventStore || !accountManager || !relayPool) { return (
) } return (
{toastMessage && ( )}
) } export default App