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.
 
 
 
 

101 lines
3.4 KiB

import { FETCH_RELAY_LIST_HOOK_MAX_MS } from '@/constants'
import logger from '@/lib/logger'
import client from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
import { TRelayList } from '@/types'
import { kinds } from 'nostr-tools'
import { useEffect, useState } from 'react'
const emptyRelayList = (): TRelayList => ({
write: [],
read: [],
originalRelays: [],
httpRead: [],
httpWrite: [],
httpOriginalRelays: []
})
export function useFetchRelayList(pubkey?: string | null) {
const [relayList, setRelayList] = useState<TRelayList>(emptyRelayList)
const [isFetching, setIsFetching] = useState(true)
/** True when IndexedDB has this author's kind 10002 (even if `originalRelays` is empty after merge). */
const [hasKind10002InStorage, setHasKind10002InStorage] = useState(false)
useEffect(() => {
let cancelled = false
const targetPk = pubkey?.trim() || null
const fetchRelayList = async () => {
setIsFetching(true)
setHasKind10002InStorage(false)
if (!targetPk) {
setRelayList(emptyRelayList())
setIsFetching(false)
return
}
setRelayList(emptyRelayList())
try {
const [fromStorage, k10002] = await Promise.all([
client.peekRelayListFromStorage(targetPk),
indexedDb.getReplaceableEvent(targetPk, kinds.RelayList).catch(() => null)
])
if (cancelled) return
setHasKind10002InStorage(!!k10002)
setRelayList(fromStorage)
const merged = await Promise.race([
client.fetchRelayList(targetPk),
new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('relay-list hook max wait')), FETCH_RELAY_LIST_HOOK_MAX_MS)
})
]).catch(async (err: unknown) => {
const isMaxWait = err instanceof Error && err.message === 'relay-list hook max wait'
if (isMaxWait) {
logger.warn('[useFetchRelayList] fetchRelayList exceeded max wait; clearing dedupe cache', {
pubkeyPrefix: targetPk.slice(0, 12)
})
client.clearRelayListCache(targetPk)
return client.peekRelayListFromStorage(targetPk)
}
throw err
})
if (cancelled) return
setRelayList(merged)
const k10002After = await indexedDb.getReplaceableEvent(targetPk, kinds.RelayList).catch(() => null)
if (!cancelled) {
setHasKind10002InStorage(!!k10002After)
}
} catch (err) {
logger.error('Failed to fetch relay list', { error: err, pubkey: targetPk })
try {
const fallback = await client.peekRelayListFromStorage(targetPk)
const k10002 = await indexedDb.getReplaceableEvent(targetPk, kinds.RelayList).catch(() => null)
if (!cancelled) {
setRelayList(fallback)
setHasKind10002InStorage(!!k10002)
}
} catch {
if (!cancelled) {
setRelayList(emptyRelayList())
}
}
} finally {
if (!cancelled) {
setIsFetching(false)
}
}
}
void fetchRelayList()
return () => {
cancelled = true
}
}, [pubkey])
/** True when no kind 10002 for this author in IDB — UI may show default discovery relays with a disclaimer. */
const showingRelayListFallback = !isFetching && !hasKind10002InStorage
return { relayList, isFetching, hasKind10002InStorage, showingRelayListFallback }
}