import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { normalizeUrl, isLocalNetworkUrl } from '@/lib/url' import { getRelaysFromNip07Extension, verifyNip05 } from '@/lib/nip05' import { useNostr } from '@/providers/NostrProvider' import { TMailboxRelay } from '@/types' import { Loader2, Check, AlertCircle } from 'lucide-react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import RelayIcon from '../RelayIcon' import logger from '@/lib/logger' interface DiscoveredRelay { url: string source: 'nip05' | 'nip07' | 'bunker' selected: boolean } export default function DiscoveredRelays({ onAdd, localOnly = false }: { onAdd: (relays: TMailboxRelay[]) => void; localOnly?: boolean }) { const { t } = useTranslation() const { profile, account } = useNostr() const [discoveredRelays, setDiscoveredRelays] = useState([]) const [isLoading, setIsLoading] = useState(false) const [isAdding, setIsAdding] = useState(false) const [successMsg, setSuccessMsg] = useState('') const [errorMsg, setErrorMsg] = useState('') useEffect(() => { discoverRelays() }, [profile?.nip05, account?.pubkey, account?.signerType]) const discoverRelays = async () => { if (!account?.pubkey) return setIsLoading(true) setErrorMsg('') const discovered = new Map() try { // Try to get relays from NIP-05 if (profile?.nip05) { try { const nip05Result = await verifyNip05(profile.nip05, account.pubkey) if (nip05Result.isVerified && nip05Result.relays) { nip05Result.relays.forEach(url => { const normalized = normalizeUrl(url) if (normalized && !discovered.has(normalized)) { discovered.set(normalized, { url: normalized, source: 'nip05', selected: true }) } }) } } catch (error) { logger.warn('Could not fetch relays from NIP-05', error as Error) } } // Try to get relays from NIP-07 extension if (account.signerType === 'nip-07') { try { const extensionRelays = await getRelaysFromNip07Extension() extensionRelays.forEach(url => { const normalized = normalizeUrl(url) if (normalized && !discovered.has(normalized)) { discovered.set(normalized, { url: normalized, source: 'nip07', selected: true }) } }) } catch (error) { logger.warn('Could not fetch relays from NIP-07 extension', error as Error) } } // Note: Bunker relays are from the bunker connection URL itself // We could add logic here to extract relays from the bunker URL if needed // Filter to only local relays if localOnly is true let discoveredArray = Array.from(discovered.values()) if (localOnly) { discoveredArray = discoveredArray.filter(relay => isLocalNetworkUrl(relay.url)) } setDiscoveredRelays(discoveredArray) } catch (error) { logger.error('Error discovering relays', { error }) setErrorMsg(t('Failed to discover relays')) } finally { setIsLoading(false) } } const handleToggleRelay = (url: string) => { setDiscoveredRelays(prev => prev.map(relay => relay.url === url ? { ...relay, selected: !relay.selected } : relay ) ) } const handleSelectAll = () => { setDiscoveredRelays(prev => prev.map(relay => ({ ...relay, selected: true }))) } const handleClearAll = () => { setDiscoveredRelays(prev => prev.map(relay => ({ ...relay, selected: false }))) } const handleAddSelected = async () => { const selectedRelays = discoveredRelays.filter(r => r.selected) if (selectedRelays.length === 0) return setIsAdding(true) setErrorMsg('') setSuccessMsg('') try { const mailboxRelays: TMailboxRelay[] = selectedRelays.map(relay => ({ url: relay.url, scope: 'both' as const })) onAdd(mailboxRelays) setSuccessMsg(t('Added {{count}} relay(s)', { count: selectedRelays.length })) setTimeout(() => setSuccessMsg(''), 3000) // Clear discovered relays after adding setDiscoveredRelays([]) } catch (error) { logger.error('Failed to add relays', { error }) setErrorMsg(t('Failed to add relays')) } finally { setIsAdding(false) } } const getSourceLabel = (source: DiscoveredRelay['source']) => { switch (source) { case 'nip05': return t('from NIP-05') case 'nip07': return t('from Extension') case 'bunker': return t('from Bunker') } } if (!profile || !account) { return null } if (isLoading) { return (
{t('Discovered Relays')}
{t('Discovering relays...')}
) } if (discoveredRelays.length === 0) { return null } const selectedCount = discoveredRelays.filter(r => r.selected).length return (
{t('Discovered Relays')}
{t('These relays were found from your NIP-05 identifier and signer. You can add them to your relay list.')}
{discoveredRelays.map((relay) => (
handleToggleRelay(relay.url)} />
))}
{successMsg && (
{successMsg}
)} {errorMsg && (
{errorMsg}
)}
) }