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.
This commit is contained in:
Alejandro Gómez
2026-03-18 11:07:46 +01:00
parent 400e60107f
commit cd889d70cb
3 changed files with 41 additions and 18 deletions

View File

@@ -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);

View File

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

View File

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