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
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> |
|
) |
|
}
|
|
|