You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
4.3 KiB
140 lines
4.3 KiB
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<never>((_, 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 ( |
|
<NormalFeed |
|
ref={ref} |
|
subRequests={subRequests} |
|
areAlgoRelays={areAlgoRelays} |
|
isMainFeed |
|
setSubHeader={setSubHeader} |
|
onSubHeaderRefresh={onSubHeaderRefresh} |
|
preserveTimelineOnSubRequestsChange |
|
repliesSubRequests={repliesSubRequests} |
|
mainFeedGalleryRelayUrls={replyRelayUrls} |
|
widenMainGalleryRelays={false} |
|
feedSubscriptionKey="home-all-favorites" |
|
feedTimelineScopeKey="all-favorites" |
|
homeFeedSeenOnAllowlistOp={homeFeedSeenOnAllowlistOp} |
|
homeFeedSeenOnAllowlistReplies={homeFeedSeenOnAllowlistReplies} |
|
showFeedClientFilter |
|
hostPrimaryPageName="feed" |
|
/> |
|
) |
|
}) |
|
|
|
RelaysFeed.displayName = 'RelaysFeed' |
|
export default RelaysFeed
|
|
|