import NormalFeed from '@/components/NormalFeed' import type { TNoteListRef } from '@/components/NoteList' import { checkAlgoRelay } from '@/lib/relay' import { normalizeUrl } from '@/lib/url' import { useFeed } from '@/providers/feed-context' import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider' import relayInfoService from '@/services/relay-info.service' import { kinds } from 'nostr-tools' import React, { forwardRef, useEffect, useMemo, useState } from 'react' const RelaysFeed = forwardRef< TNoteListRef, { setSubHeader?: (node: React.ReactNode) => void onSubHeaderRefresh?: () => void /** When set, subscription kinds (fixed list); otherwise uses KindFilterProvider. */ kindsOverride?: number[] } >(function RelaysFeed({ setSubHeader, onSubHeaderRefresh, kindsOverride }, ref) { const { relayUrls, replyRelayUrls } = useFeed() const { showKinds } = useKindFilterOrDefaults() const [areAlgoRelays, setAreAlgoRelays] = useState(false) const relayUrlsKey = useMemo( () => [...relayUrls] .map((u) => normalizeUrl(u) || u) .filter(Boolean) .sort() .join('|'), [relayUrls] ) const replyRelayUrlsKey = useMemo( () => [...replyRelayUrls] .map((u) => normalizeUrl(u) || u) .filter(Boolean) .sort() .join('|'), [replyRelayUrls] ) const homeFeedSeenOnAllowlistOp = useMemo(() => relayUrls, [relayUrlsKey]) const homeFeedSeenOnAllowlistReplies = useMemo(() => replyRelayUrls, [replyRelayUrlsKey]) useEffect(() => { if (relayUrls.length === 0) { setAreAlgoRelays(false) return } let cancelled = false const init = async () => { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error('getRelayInfos timeout after 8 seconds')) }, 8000) }) try { const relayInfos = await Promise.race([ relayInfoService.getRelayInfos(relayUrls), timeoutPromise ]) if (cancelled) return const areAlgo = relayInfos.every((relayInfo) => checkAlgoRelay(relayInfo)) setAreAlgoRelays(areAlgo) } catch { if (!cancelled) setAreAlgoRelays(false) } } void init() return () => { cancelled = true } }, [relayUrlsKey, relayUrls.length]) /** Stable identity when kind filter is empty so `subRequests` does not invalidate every render. */ const fallbackNoteKinds = useMemo(() => [kinds.ShortTextNote], []) const defaultKinds = useMemo(() => { if (kindsOverride && kindsOverride.length > 0) return kindsOverride if (showKinds.length > 0) return showKinds return fallbackNoteKinds }, [kindsOverride, showKinds, fallbackNoteKinds]) const canRenderFeed = relayUrls.length > 0 // Hooks must run every render — never place useMemo after conditional returns. const subRequests = useMemo(() => { if (!canRenderFeed) return [] return [ { urls: relayUrls, filter: { kinds: defaultKinds } } ] }, [canRenderFeed, relayUrlsKey, relayUrls, defaultKinds]) const repliesSubRequests = useMemo(() => { if (!canRenderFeed) return [] return [ { urls: replyRelayUrls.length > 0 ? replyRelayUrls : relayUrls, filter: { kinds: defaultKinds } } ] }, [canRenderFeed, replyRelayUrlsKey, replyRelayUrls, relayUrlsKey, relayUrls, defaultKinds]) if (!canRenderFeed) { return null } // preserveTimeline: merge when relay list grows (e.g. all-favorites list fills in). return ( ) }) RelaysFeed.displayName = 'RelaysFeed' export default RelaysFeed