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.
 
 
 
 

222 lines
8.5 KiB

import { DEFAULT_FAVORITE_RELAYS } from '@/constants'
import { getFavoritesFeedRelayUrls } from '@/lib/favorites-feed-relays'
import { getRelaySetFromEvent } from '@/lib/event-metadata'
import logger from '@/lib/logger'
import { isHttpRelayUrl, isWebsocketUrl, normalizeAnyRelayUrl } from '@/lib/url'
import indexedDb from '@/services/indexed-db.service'
import storage from '@/services/local-storage.service'
import { TFeedInfo, TFeedType } from '@/types'
import { kinds } from 'nostr-tools'
import { useEffect, useRef, useState, useCallback } from 'react'
import { FeedContext } from './feed-context'
import { useFavoriteRelays } from './FavoriteRelaysProvider'
import { useNostr } from './NostrProvider'
export { useFeed } from './feed-context'
export type { TFeedContext } from './feed-context'
export function FeedProvider({ children }: { children: React.ReactNode }) {
const { pubkey, isInitialized } = useNostr()
const { relaySets, favoriteRelays, blockedRelays } = useFavoriteRelays()
const [relayUrls, setRelayUrls] = useState<string[]>([])
const [isReady, setIsReady] = useState(false)
const [feedInfo, setFeedInfo] = useState<TFeedInfo>({
feedType: 'relay',
id: DEFAULT_FAVORITE_RELAYS[0]
})
const feedInfoRef = useRef<TFeedInfo>(feedInfo)
const loggedWaitingForNostrInitRef = useRef(false)
const switchFeed = useCallback(async (
feedType: TFeedType,
options: {
activeRelaySetId?: string | null
pubkey?: string | null
relay?: string | null
} = {}
) => {
logger.debug('switchFeed called:', { feedType, options })
setIsReady(false)
if (feedType === 'relay') {
const normalizedUrl = normalizeAnyRelayUrl(options.relay ?? '')
const isRelayFeedUrl =
!!normalizedUrl && (isHttpRelayUrl(normalizedUrl) || isWebsocketUrl(normalizedUrl))
logger.debug('Relay switchFeed:', { normalizedUrl, isRelayFeedUrl, blockedRelays })
if (!isRelayFeedUrl) {
logger.debug('Invalid relay URL, setting isReady to true')
setIsReady(true)
return
}
// Don't allow selecting a blocked relay as feed
if (blockedRelays.includes(normalizedUrl)) {
logger.warn('Cannot select blocked relay as feed:', normalizedUrl)
setIsReady(true)
return
}
const newFeedInfo = { feedType, id: normalizedUrl }
logger.component('FeedProvider', 'Setting relay feed info', newFeedInfo)
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
setRelayUrls([normalizedUrl])
logger.component('FeedProvider', 'Set relayUrls', { relayUrls: [normalizedUrl] })
storage.setFeedInfo(newFeedInfo, pubkey)
// Reset note list mode to 'posts' when switching to relay feed to ensure main content is shown
storage.setNoteListMode('posts')
setIsReady(true)
logger.component('FeedProvider', 'Relay feed setup complete, isReady set to true')
return
}
if (feedType === 'relays') {
const relaySetId = options.activeRelaySetId ?? (relaySets.length > 0 ? relaySets[0].id : null)
if (!relaySetId || !pubkey) {
setIsReady(true)
return
}
let relaySet =
relaySets.find((set) => set.id === relaySetId) ??
(relaySets.length > 0 ? relaySets[0] : null)
if (!relaySet) {
const storedRelaySetEvent = await indexedDb.getReplaceableEvent(
pubkey,
kinds.Relaysets,
relaySetId
)
if (storedRelaySetEvent) {
relaySet = getRelaySetFromEvent(storedRelaySetEvent, blockedRelays)
}
}
if (relaySet) {
const newFeedInfo = { feedType, id: relaySet.id }
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
setRelayUrls(relaySet.relayUrls)
storage.setFeedInfo(newFeedInfo, pubkey)
// Reset note list mode to 'posts' when switching to relay set to ensure main content is shown
storage.setNoteListMode('posts')
setIsReady(true)
}
setIsReady(true)
return
}
if (feedType === 'all-favorites') {
const finalRelays = getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays)
logger.debug('Switching to all-favorites, finalRelays:', finalRelays)
const newFeedInfo = { feedType }
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
setRelayUrls(finalRelays)
storage.setFeedInfo(newFeedInfo, pubkey)
// Reset note list mode to 'posts' when switching to all-favorites to ensure main content is shown
storage.setNoteListMode('posts')
setIsReady(true)
return
}
setIsReady(true)
}, [pubkey, favoriteRelays, blockedRelays, relaySets])
useEffect(() => {
const init = async () => {
logger.debug('FeedProvider init:', { isInitialized, pubkey, favoriteRelays: favoriteRelays.length, blockedRelays: blockedRelays.length })
if (!isInitialized) {
if (!loggedWaitingForNostrInitRef.current) {
loggedWaitingForNostrInitRef.current = true
logger.info(
'[FeedProvider] Waiting for Nostr session restore before attaching feeds (home may show a loading state)'
)
}
return
}
loggedWaitingForNostrInitRef.current = false
// Wait for favoriteRelays to be initialized (should have at least default relays)
// If favoriteRelays is empty, it might not be initialized yet, so wait
if (favoriteRelays.length === 0 && !pubkey) {
// For anonymous users, favoriteRelays should be initialized from FAST_READ_RELAY_URLS
// If it's still empty, something is wrong, but we'll use defaults
logger.debug('FeedProvider: favoriteRelays is empty, using defaults')
}
const favoritesFeedRelays = getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays)
let feedInfo: TFeedInfo = {
feedType: 'relay',
id: favoritesFeedRelays[0] ?? DEFAULT_FAVORITE_RELAYS[0]
}
// Ensure we always have a valid relay ID
if (!feedInfo.id) {
feedInfo.id = DEFAULT_FAVORITE_RELAYS[0]
}
logger.debug('Initial feedInfo setup:', { favoritesFeedRelays, favoriteRelays, blockedRelays, feedInfo })
if (pubkey) {
const storedFeedInfo = storage.getFeedInfo(pubkey)
logger.debug('Stored feed info:', storedFeedInfo)
if (storedFeedInfo) {
feedInfo = storedFeedInfo
}
}
// Pre-rewrite main feeds (`following`, `bookmarks`) are no longer supported; migrate persisted state.
const storedFeedType = (feedInfo as { feedType?: string }).feedType
const deprecatedMainFeed = storedFeedType === 'following' || storedFeedType === 'bookmarks'
if (deprecatedMainFeed) {
const previousMainFeed = storedFeedType
const migrated: TFeedInfo = { feedType: 'all-favorites' }
feedInfo = migrated
if (pubkey) {
storage.setFeedInfo(migrated, pubkey)
}
logger.info('[FeedProvider] Migrated deprecated feed type to all-favorites', {
previous: previousMainFeed
})
return await switchFeed('all-favorites')
}
if (feedInfo.feedType === 'relays') {
return await switchFeed('relays', { activeRelaySetId: feedInfo.id })
}
if (feedInfo.feedType === 'relay') {
// Check if the stored relay is blocked, if so use first visible relay instead
if (feedInfo.id && blockedRelays.includes(feedInfo.id)) {
logger.component('FeedProvider', 'Stored relay is blocked, using first visible relay instead')
feedInfo.id = favoritesFeedRelays[0] ?? DEFAULT_FAVORITE_RELAYS[0]
}
logger.component('FeedProvider', 'Initial relay setup, calling switchFeed', { relayId: feedInfo.id })
return await switchFeed('relay', { relay: feedInfo.id })
}
if (feedInfo.feedType === 'all-favorites') {
logger.debug('Initializing all-favorites feed')
return await switchFeed('all-favorites')
}
}
init()
}, [pubkey, isInitialized, favoriteRelays, blockedRelays, switchFeed])
// Update relay URLs when favoriteRelays change and we're in all-favorites mode
useEffect(() => {
if (feedInfo.feedType !== 'all-favorites') return
const finalRelays = getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays)
logger.debug('Updating relay URLs for all-favorites:', finalRelays)
setRelayUrls(finalRelays)
}, [feedInfo.feedType, favoriteRelays, blockedRelays])
return (
<FeedContext.Provider
value={{
feedInfo,
relayUrls,
isReady,
switchFeed
}}
>
{children}
</FeedContext.Provider>
)
}