import ClientSelect from '@/components/ClientSelect' import { Button } from '@/components/ui/button' import { FAST_READ_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' import { normalizeUrl } from '@/lib/url' import client from '@/services/client.service' import { AlertCircle, Search } from 'lucide-react' import { nip19 } from 'nostr-tools' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import logger from '@/lib/logger' export default function NotFound({ bech32Id, onEventFound }: { bech32Id?: string onEventFound?: (event: any) => void }) { const { t } = useTranslation() const [isSearchingExternal, setIsSearchingExternal] = useState(false) const [triedExternal, setTriedExternal] = useState(false) const [externalRelays, setExternalRelays] = useState([]) const [hexEventId, setHexEventId] = useState(null) // Calculate which external relays would be tried (excluding already-tried relays) useEffect(() => { if (!bech32Id) return const getExternalRelays = async () => { // Get all relays that have already been tried (FAST_READ_RELAY_URLS) // These are the relays used in the initial fetch const alreadyTriedRelaysSet = new Set() ;[...FAST_READ_RELAY_URLS].forEach(url => { const normalized = normalizeUrl(url) if (normalized) alreadyTriedRelaysSet.add(normalized) }) let hintRelays: string[] = [] let extractedHexEventId: string | null = null // Parse relay hints and author from bech32 ID if (!/^[0-9a-f]{64}$/.test(bech32Id)) { try { const { type, data } = nip19.decode(bech32Id) if (type === 'nevent') { extractedHexEventId = data.id if (data.relays) hintRelays.push(...data.relays) if (data.author) { const authorRelayList = await client.fetchRelayList(data.author).catch(() => ({ read: [] as string[], write: [] as string[] })) hintRelays.push(...(authorRelayList.read ?? []).slice(0, 4), ...(authorRelayList.write ?? []).slice(0, 4)) } } else if (type === 'naddr') { if (data.relays) hintRelays.push(...data.relays) const authorRelayList = await client.fetchRelayList(data.pubkey).catch(() => ({ read: [] as string[], write: [] as string[] })) hintRelays.push(...(authorRelayList.read ?? []).slice(0, 4), ...(authorRelayList.write ?? []).slice(0, 4)) } else if (type === 'note') { extractedHexEventId = data } } catch (err) { logger.error('Failed to parse external relays', { error: err, bech32Id }) } } else { extractedHexEventId = bech32Id } setHexEventId(extractedHexEventId) // Get relays where this event was seen const seenOn = extractedHexEventId ? client.getSeenEventRelayUrls(extractedHexEventId) : [] hintRelays.push(...seenOn) // Normalize all hint relays const normalizedHints = hintRelays .map(url => normalizeUrl(url)) .filter((url): url is string => Boolean(url)) // Combine hints with SEARCHABLE_RELAY_URLS (always include as fallback) // Normalize SEARCHABLE_RELAY_URLS for comparison const normalizedSearchableRelays = SEARCHABLE_RELAY_URLS .map(url => normalizeUrl(url)) .filter((url): url is string => Boolean(url)) // Combine all potential relays (hints + searchable) const allPotentialRelays = new Set([...normalizedHints, ...normalizedSearchableRelays]) // Filter out relays that were already tried const externalRelays = Array.from(allPotentialRelays).filter( relay => !alreadyTriedRelaysSet.has(relay) ) // Deduplicate final relay list setExternalRelays(externalRelays) logger.debug('External relays calculated (NotFound)', { bech32Id, hintRelaysCount: normalizedHints.length, searchableRelaysCount: normalizedSearchableRelays.length, alreadyTriedCount: alreadyTriedRelaysSet.size, externalRelaysCount: externalRelays.length, externalRelays: externalRelays.slice(0, 10) // Log first 10 }) } getExternalRelays() }, [bech32Id]) const handleTryExternalRelays = async () => { if (!hexEventId || isSearchingExternal) return if (externalRelays.length === 0) { logger.warn('No external relays to search (NotFound)', { bech32Id, hexEventId }) setTriedExternal(true) return } setIsSearchingExternal(true) try { logger.info('Searching external relays (NotFound)', { bech32Id, hexEventId, relayCount: externalRelays.length, relays: externalRelays.slice(0, 5) // Log first 5 relays }) const event = await client.fetchEventWithExternalRelays(hexEventId, externalRelays) if (event) { logger.info('Event found on external relay (NotFound)', { bech32Id, hexEventId }) if (onEventFound) { onEventFound(event) } } else { logger.info('Event not found on external relays (NotFound)', { bech32Id, hexEventId, relayCount: externalRelays.length }) } } catch (error) { logger.error('External relay fetch failed (NotFound)', { error, bech32Id, hexEventId, externalRelays }) } finally { setIsSearchingExternal(false) setTriedExternal(true) } } const hasExternalRelays = externalRelays.length > 0 return (
{t('Note not found')}
{bech32Id && !triedExternal && hasExternalRelays && (
{t('The note was not found on your relays or default relays.')}
{t('Show relays')} ({externalRelays.length})
{externalRelays.map((relay, i) => (
{relay}
))}
)} {bech32Id && !triedExternal && !hasExternalRelays && (
{t('No external relay hints available')}
)} {triedExternal && (
{t('Note could not be found anywhere')}
)}
) }