import { type NostrEvent, type NostrMetadata, NSchema as n } from '@nostrify/nostrify'; import { useNostr } from '@nostrify/react'; import { useQuery } from '@tanstack/react-query'; import { SKATE_RELAY } from '@/lib/types'; // Fallback relays for profile aggregation — global indexers. const FALLBACK_RELAYS = [ 'wss://purplepag.es', 'wss://relay.nostr.band', 'wss://nos.lol', 'wss://relay.damus.io', ]; export function useAuthor(pubkey: string | undefined) { const { nostr } = useNostr(); return useQuery<{ event?: NostrEvent; metadata?: NostrMetadata }>({ queryKey: ['author', pubkey ?? ''], queryFn: async ({ signal }) => { if (!pubkey) { return {}; } const querySignal = AbortSignal.any([signal, AbortSignal.timeout(15000)]); try { // Query the main pool, the global aggregators, AND the app's own // community relay — each INDEPENDENTLY. They must not be in one group: // a single flaky relay there would reject the whole group and drop a // profile that only lives on the community relay (the "generated name / // missing avatar" bug). const results = await Promise.allSettled([ nostr.query( [{ kinds: [0], authors: [pubkey], limit: 1 }], { signal: querySignal }, ), (async () => { const fallbackGroup = nostr.group(FALLBACK_RELAYS); return fallbackGroup.query( [{ kinds: [0], authors: [pubkey], limit: 1 }], { signal: AbortSignal.timeout(8000) }, ); })(), nostr.relay(SKATE_RELAY).query( [{ kinds: [0], authors: [pubkey], limit: 1 }], { signal: AbortSignal.timeout(8000) }, ), ]); // Collect every kind 0 event we found and pick the most recent one. const candidates: NostrEvent[] = []; for (const r of results) { if (r.status === 'fulfilled' && Array.isArray(r.value)) { candidates.push(...r.value); } } const event = candidates .filter((e) => e.kind === 0 && e.pubkey === pubkey) .sort((a, b) => b.created_at - a.created_at)[0]; if (!event) { // No profile found - this is okay, user may not have published one yet return {}; } try { const metadata = n.json().pipe(n.metadata()).parse(event.content); return { metadata, event }; } catch { return { event }; } } catch (error) { // Network error or timeout - return empty to avoid breaking the UI console.warn('[useAuthor] Failed to fetch profile for', pubkey?.slice(0, 8), error); return {}; } }, staleTime: 60 * 1000, // 1 minute — allows NostrSync to invalidate and refetch quickly retry: 5, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), }); }