Files
smesh/src/providers/FeedProvider.tsx
codytseng 6bc2fde314 fix: 🐛
2025-01-13 23:56:41 +08:00

151 lines
4.3 KiB
TypeScript

import { BIG_RELAY_URLS } from '@/constants'
import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
import storage from '@/services/storage.service'
import { TFeedType } from '@/types'
import { Filter } from 'nostr-tools'
import { createContext, useContext, useEffect, useState } from 'react'
import { useNostr } from './NostrProvider'
import { useRelaySets } from './RelaySetsProvider'
type TFeedContext = {
feedType: TFeedType
relayUrls: string[]
temporaryRelayUrls: string[]
filter: Filter
isReady: boolean
activeRelaySetId: string | null
switchFeed: (
feedType: TFeedType,
options?: { activeRelaySetId?: string; pubkey?: string }
) => Promise<void>
}
const FeedContext = createContext<TFeedContext | undefined>(undefined)
export const useFeed = () => {
const context = useContext(FeedContext)
if (!context) {
throw new Error('useFeed must be used within a FeedProvider')
}
return context
}
export function FeedProvider({ children }: { children: React.ReactNode }) {
const { pubkey, getRelayList, getFollowings } = useNostr()
const { relaySets } = useRelaySets()
const [feedType, setFeedType] = useState<TFeedType>(storage.getFeedType())
const [relayUrls, setRelayUrls] = useState<string[]>([])
const [temporaryRelayUrls, setTemporaryRelayUrls] = useState<string[]>([])
const [filter, setFilter] = useState<Filter>({})
const [isReady, setIsReady] = useState(false)
const [activeRelaySetId, setActiveRelaySetId] = useState<string | null>(
storage.getActiveRelaySetId()
)
useEffect(() => {
const init = async () => {
// temporary relay urls from query params
const searchParams = new URLSearchParams(window.location.search)
const temporaryRelayUrls = searchParams
.getAll('r')
.map((url) => normalizeUrl(url))
.filter((url) => isWebsocketUrl(url))
if (temporaryRelayUrls.length) {
return await switchFeed('temporary', { temporaryRelayUrls })
}
if (feedType === 'following') {
return await switchFeed('following', { pubkey })
} else {
console.log('activeRelaySetId', activeRelaySetId)
await switchFeed('relays', { activeRelaySetId })
}
}
init()
}, [])
useEffect(() => {
if (pubkey && feedType === 'following') {
switchFeed('following', { pubkey })
}
}, [feedType, pubkey])
const switchFeed = async (
feedType: TFeedType,
options: {
activeRelaySetId?: string | null
temporaryRelayUrls?: string[] | null
pubkey?: string | null
} = {}
) => {
setIsReady(false)
if (feedType === 'relays') {
const relaySetId = options.activeRelaySetId ?? (relaySets.length > 0 ? relaySets[0].id : null)
if (!relaySetId) {
return setIsReady(true)
}
const relaySet =
relaySets.find((set) => set.id === options.activeRelaySetId) ??
(relaySets.length > 0 ? relaySets[0] : null)
if (relaySet) {
setFeedType(feedType)
setRelayUrls(relaySet.relayUrls)
setActiveRelaySetId(relaySet.id)
setFilter({})
storage.setActiveRelaySetId(relaySet.id)
storage.setFeedType(feedType)
}
return setIsReady(true)
}
if (feedType === 'following') {
if (!options.pubkey) {
return setIsReady(true)
}
setFeedType(feedType)
setActiveRelaySetId(null)
const [relayList, followings] = await Promise.all([
getRelayList(options.pubkey),
getFollowings(options.pubkey)
])
setRelayUrls(relayList.read.concat(BIG_RELAY_URLS).slice(0, 4))
setFilter({
authors: followings.includes(options.pubkey) ? followings : [...followings, options.pubkey]
})
storage.setFeedType(feedType)
return setIsReady(true)
}
if (feedType === 'temporary') {
const urls = options.temporaryRelayUrls ?? temporaryRelayUrls
if (!urls.length) {
return setIsReady(true)
}
setFeedType(feedType)
setTemporaryRelayUrls(urls)
setRelayUrls(urls)
setActiveRelaySetId(null)
setFilter({})
return setIsReady(true)
}
setIsReady(true)
}
return (
<FeedContext.Provider
value={{
feedType,
relayUrls,
temporaryRelayUrls,
filter,
isReady,
activeRelaySetId,
switchFeed
}}
>
{children}
</FeedContext.Provider>
)
}