import React, { useState, useEffect, useContext, useRef } from 'react' import { toast } from 'react-toastify' import { finalizeEvent } from 'nostr-tools/pure' import { hexToBytes } from 'nostr-tools/utils' import Loading from '../components/Loading' import MainContext from '../contexts/MainContext' import { getSessionVault } from '../common' import { fetchRelayList, createRelayListEvent, publishRelayList, normalizeRelayUrl, getDefaultRelayList, DISCOVERY_RELAYS } from '../helpers/outbox' const RelaysPage = () => { const { defaultAccount } = useContext(MainContext) const [relays, setRelays] = useState([]) const [newRelay, setNewRelay] = useState('') const [loading, setLoading] = useState(true) const [refreshingRelays, setRefreshingRelays] = useState(false) const [publishing, setPublishing] = useState(false) const [hasChanges, setHasChanges] = useState(false) const [usingDefaults, setUsingDefaults] = useState(false) // Track previous pubkey to detect account switch const prevPubKeyRef = useRef(null) useEffect(() => { if (defaultAccount?.pubKey) { const isAccountSwitch = prevPubKeyRef.current !== null && prevPubKeyRef.current !== defaultAccount.pubKey prevPubKeyRef.current = defaultAccount.pubKey // Use cache for initial load and account switches // Only force refresh on explicit user action loadRelays(defaultAccount.pubKey, false) // Reset changes state on account switch if (isAccountSwitch) { setHasChanges(false) } } }, [defaultAccount?.pubKey]) const loadRelays = async (pubkey, forceRefresh = false) => { if (forceRefresh) { setRefreshingRelays(true) } else { setLoading(true) } try { const { relays: fetchedRelays, fromCache } = await fetchRelayList(pubkey, forceRefresh) if (fetchedRelays.length > 0) { setRelays(fetchedRelays) setUsingDefaults(false) // Don't mark as having changes just because we loaded from cache if (!forceRefresh) { setHasChanges(false) } } else { // No relay list found, use defaults setRelays(getDefaultRelayList()) setUsingDefaults(true) setHasChanges(true) // Mark as needing publish } if (forceRefresh && !fromCache) { toast.success('Relay list refreshed') } } catch (error) { console.error('Failed to load relays:', error) setRelays(getDefaultRelayList()) setUsingDefaults(true) setHasChanges(true) } setLoading(false) setRefreshingRelays(false) } const handleRefresh = () => { if (defaultAccount?.pubKey) { loadRelays(defaultAccount.pubKey, true) } } const addRelay = (e) => { e.preventDefault() const url = normalizeRelayUrl(newRelay) if (relays.some(r => r.url === url)) { toast.error('Relay already exists') return } setRelays([...relays, { url, read: true, write: true }]) setNewRelay('') setHasChanges(true) toast.success('Relay added') } const removeRelay = (index) => { const updated = [...relays] updated.splice(index, 1) setRelays(updated) setHasChanges(true) toast.success('Relay removed') } const toggleRead = (index) => { const updated = [...relays] updated[index].read = !updated[index].read // Ensure at least one is enabled if (!updated[index].read && !updated[index].write) { updated[index].write = true } setRelays(updated) setHasChanges(true) } const toggleWrite = (index) => { const updated = [...relays] updated[index].write = !updated[index].write // Ensure at least one is enabled if (!updated[index].read && !updated[index].write) { updated[index].read = true } setRelays(updated) setHasChanges(true) } const publishChanges = async () => { setPublishing(true) try { const vault = await getSessionVault() if (!vault?.accountDefault) { toast.error('No account found') return } const event = createRelayListEvent(relays) const signedEvent = finalizeEvent(event, hexToBytes(vault.accountDefault)) // Publish to current write relays + all discovery relays for visibility const writeRelays = relays.filter(r => r.write).map(r => r.url) const publishTo = [...new Set([...writeRelays, ...DISCOVERY_RELAYS])] await publishRelayList(signedEvent, publishTo) setHasChanges(false) setUsingDefaults(false) toast.success('Relay list published') } catch (error) { console.error('Failed to publish:', error) toast.error('Failed to publish relay list') } setPublishing(false) } if (loading) { return (
Manage your NIP-65 relay list
No relay list (kind 10002) was found for this account. Publish your relay list so other users can find your content.