From a46be4be6801c32f8d19cd1b9f2bb2f2d889cfaf Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Sat, 25 Jul 2026 14:01:55 +0700 Subject: [PATCH] Fix generic npub names: query profile aggregators in parallel in useAuthor Previously useAuthor only hit the global profile-aggregator relays as a fallback when the main pool returned zero kind 0 events. On the curated skate relay many followed users have no profile, so names fell back to genUserName. Now query the main pool and aggregators (purplepag.es, nostr.band, nos.lol, damus) in parallel and keep the newest kind 0, so real display names resolve. Co-authored-by: shakespeare.diy --- src/hooks/useAuthor.ts | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/hooks/useAuthor.ts b/src/hooks/useAuthor.ts index e0aea66..c5da199 100644 --- a/src/hooks/useAuthor.ts +++ b/src/hooks/useAuthor.ts @@ -23,25 +23,38 @@ export function useAuthor(pubkey: string | undefined) { const querySignal = AbortSignal.any([signal, AbortSignal.timeout(15000)]); try { - // Try main relays first - let [event] = await nostr.query( - [{ kinds: [0], authors: [pubkey], limit: 1 }], - { signal: querySignal }, - ); - - // If not found, try fallback profile aggregator relays - if (!event) { - try { + // Query the main pool AND the global profile aggregators IN PARALLEL, + // then keep the NEWEST kind 0 across all of them. Doing both at once + // (rather than only falling back when the main pool is empty) means a + // followed user's real name resolves even when their profile lives on a + // big indexer but not on the app's curated skate relay — the previous + // "generic npub name" bug. + const results = await Promise.allSettled([ + nostr.query( + [{ kinds: [0], authors: [pubkey], limit: 1 }], + { signal: querySignal }, + ), + (async () => { const fallbackGroup = nostr.group(FALLBACK_RELAYS); - [event] = await fallbackGroup.query( + return fallbackGroup.query( [{ kinds: [0], authors: [pubkey], limit: 1 }], - { signal: AbortSignal.timeout(5000) }, + { signal: AbortSignal.timeout(8000) }, ); - } catch { - // Fallback failed, continue with no event + })(), + ]); + + // 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 {};