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