From cd889d70cb54e6514c5873fa58285284742bd8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Wed, 18 Mar 2026 11:07:46 +0100 Subject: [PATCH] feat: add outbox relay resolution to spellbook loading Spellbook URLs only queried hardcoded aggregator relays, missing events published to other relays. Now fetches the author's kind:10002 relay list and includes their outbox relays when loading kind:30777 spellbook events. Extract useUserRelays hook from inline pattern and refactor useRepositoryRelays to use it. --- src/components/pages/SpellbookPage.tsx | 9 ++++++-- src/hooks/useRepositoryRelays.ts | 20 ++++------------- src/hooks/useUserRelays.ts | 30 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/hooks/useUserRelays.ts diff --git a/src/components/pages/SpellbookPage.tsx b/src/components/pages/SpellbookPage.tsx index 23dd2b4..bfc5bb1 100644 --- a/src/components/pages/SpellbookPage.tsx +++ b/src/components/pages/SpellbookPage.tsx @@ -3,6 +3,7 @@ import { useParams, useNavigate, useLocation } from "react-router"; import { useGrimoire } from "@/core/state"; import { useNostrEvent } from "@/hooks/useNostrEvent"; import { useProfile } from "@/hooks/useProfile"; +import { useUserRelays } from "@/hooks/useUserRelays"; import { resolveNip05, isNip05 } from "@/lib/nip05"; import { parseSpellbook } from "@/lib/spellbook-manager"; import { SpellbookEvent } from "@/types/spell"; @@ -84,15 +85,19 @@ export default function SpellbookPage() { resolve(); }, [actor]); - // 2. Fetch the spellbook event + // 2. Resolve author's outbox relays for better spellbook discovery + const { outboxRelays } = useUserRelays(resolvedPubkey ?? undefined); + + // 3. Fetch the spellbook event (re-fetches when outbox relays arrive) const pointer = useMemo(() => { if (!resolvedPubkey || !identifier) return undefined; return { kind: 30777, pubkey: resolvedPubkey, identifier: identifier, + relays: outboxRelays, }; - }, [resolvedPubkey, identifier]); + }, [resolvedPubkey, identifier, outboxRelays]); const spellbookEvent = useNostrEvent(pointer); const authorProfile = useProfile(resolvedPubkey || undefined); diff --git a/src/hooks/useRepositoryRelays.ts b/src/hooks/useRepositoryRelays.ts index be89ee3..1c87299 100644 --- a/src/hooks/useRepositoryRelays.ts +++ b/src/hooks/useRepositoryRelays.ts @@ -1,8 +1,8 @@ import { useMemo } from "react"; import { parseReplaceableAddress } from "applesauce-core/helpers/pointers"; -import { getOutboxes } from "applesauce-core/helpers"; import { getRepositoryRelays } from "@/lib/nip34-helpers"; import { useNostrEvent } from "@/hooks/useNostrEvent"; +import { useUserRelays } from "@/hooks/useUserRelays"; import { AGGREGATOR_RELAYS } from "@/services/loaders"; import type { NostrEvent } from "@/types/nostr"; @@ -29,28 +29,16 @@ export function useRepositoryRelays(repoAddress: string | undefined): { ); const repositoryEvent = useNostrEvent(repoPointer); - - const authorRelayListPointer = useMemo( - () => - repoPointer - ? { kind: 10002, pubkey: repoPointer.pubkey, identifier: "" } - : undefined, - [repoPointer], - ); - - const authorRelayList = useNostrEvent(authorRelayListPointer); + const { outboxRelays } = useUserRelays(repoPointer?.pubkey); const relays = useMemo(() => { if (repositoryEvent) { const repoRelays = getRepositoryRelays(repositoryEvent); if (repoRelays.length > 0) return repoRelays; } - if (authorRelayList) { - const outbox = getOutboxes(authorRelayList); - if (outbox.length > 0) return outbox; - } + if (outboxRelays && outboxRelays.length > 0) return outboxRelays; return AGGREGATOR_RELAYS; - }, [repositoryEvent, authorRelayList]); + }, [repositoryEvent, outboxRelays]); return { relays, repositoryEvent }; } diff --git a/src/hooks/useUserRelays.ts b/src/hooks/useUserRelays.ts new file mode 100644 index 0000000..92beecd --- /dev/null +++ b/src/hooks/useUserRelays.ts @@ -0,0 +1,30 @@ +import { useMemo } from "react"; +import { getInboxes, getOutboxes } from "applesauce-core/helpers"; +import { useNostrEvent } from "@/hooks/useNostrEvent"; + +/** + * Fetches a user's NIP-65 relay list (kind 10002) and returns + * parsed inbox (read) and outbox (write) relays. + */ +export function useUserRelays(pubkey: string | undefined) { + const pointer = useMemo( + () => (pubkey ? { kind: 10002, pubkey, identifier: "" } : undefined), + [pubkey], + ); + + const relayListEvent = useNostrEvent(pointer); + + const outboxRelays = useMemo(() => { + if (!relayListEvent) return undefined; + const relays = getOutboxes(relayListEvent); + return relays.length > 0 ? relays : undefined; + }, [relayListEvent]); + + const inboxRelays = useMemo(() => { + if (!relayListEvent) return undefined; + const relays = getInboxes(relayListEvent); + return relays.length > 0 ? relays : undefined; + }, [relayListEvent]); + + return { inboxRelays, outboxRelays, relayListEvent }; +}