diff --git a/src/lib/chat/adapters/nip-29-adapter.ts b/src/lib/chat/adapters/nip-29-adapter.ts index 4cb4646..a6c04d4 100644 --- a/src/lib/chat/adapters/nip-29-adapter.ts +++ b/src/lib/chat/adapters/nip-29-adapter.ts @@ -28,7 +28,10 @@ import { GroupMessageBlueprint, ReactionBlueprint, } from "applesauce-common/blueprints"; -import { resolveGroupMetadata } from "@/lib/chat/group-metadata-helpers"; +import { + resolveGroupMetadata, + extractMetadataFromEvent, +} from "@/lib/chat/group-metadata-helpers"; /** * NIP-29 Adapter - Relay-Based Groups @@ -130,72 +133,90 @@ export class Nip29Adapter extends ChatProtocolAdapter { } console.log( - `[NIP-29] Fetching group metadata for ${groupId} from ${relayUrl}`, + `[NIP-29] Resolving group metadata for ${groupId} from ${relayUrl}`, ); - // Fetch group metadata from the specific relay (kind 39000) - const metadataFilter: Filter = { - kinds: [39000], - "#d": [groupId], - limit: 1, - }; + // Check eventStore first for cached kind 39000 event + // Use timeline query since we don't know the relay's pubkey + const cachedMetadata = await firstValueFrom( + eventStore + .timeline([{ kinds: [39000], "#d": [groupId], limit: 1 }]) + .pipe(first()), + { defaultValue: [] }, + ); + let metadataEvent: NostrEvent | undefined = cachedMetadata[0]; - // Use pool.subscription to fetch from the relay - const metadataEvents: NostrEvent[] = []; - const metadataObs = pool.subscription([relayUrl], [metadataFilter], { - eventStore, // Automatically add to store - }); + // If not in store, fetch from relay + if (!metadataEvent) { + console.log(`[NIP-29] No cached metadata, fetching from relay`); - // Subscribe and wait for EOSE - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - console.log("[NIP-29] Metadata fetch timeout"); - resolve(); - }, 5000); + const metadataFilter: Filter = { + kinds: [39000], + "#d": [groupId], + limit: 1, + }; - const sub = metadataObs.subscribe({ - next: (response) => { - if (typeof response === "string") { - // EOSE received - clearTimeout(timeout); - console.log( - `[NIP-29] Got ${metadataEvents.length} metadata events`, - ); - sub.unsubscribe(); - resolve(); - } else { - // Event received - metadataEvents.push(response); - } - }, - error: (err) => { - clearTimeout(timeout); - console.error("[NIP-29] Metadata fetch error:", err); - sub.unsubscribe(); - reject(err); - }, + const metadataEvents: NostrEvent[] = []; + const metadataObs = pool.subscription([relayUrl], [metadataFilter], { + eventStore, }); - }); - const metadataEvent = metadataEvents[0]; + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + console.log("[NIP-29] Metadata fetch timeout"); + resolve(); + }, 5000); - // Debug: Log metadata event tags - if (metadataEvent) { - console.log(`[NIP-29] Metadata event tags:`, metadataEvent.tags); + const sub = metadataObs.subscribe({ + next: (response) => { + if (typeof response === "string") { + clearTimeout(timeout); + console.log( + `[NIP-29] Got ${metadataEvents.length} metadata events`, + ); + sub.unsubscribe(); + resolve(); + } else { + metadataEvents.push(response); + } + }, + error: (err) => { + clearTimeout(timeout); + console.error("[NIP-29] Metadata fetch error:", err); + sub.unsubscribe(); + reject(err); + }, + }); + }); + + metadataEvent = metadataEvents[0]; + } else { + console.log(`[NIP-29] Using cached metadata event`); } - // Resolve group metadata with profile fallback - const resolved = await resolveGroupMetadata( - groupId, - relayUrl, - metadataEvent, - ); + // Extract metadata - use sync extraction if we have the event + let title: string; + let description: string | undefined; + let icon: string | undefined; + let source: string; - const title = resolved.name || groupId; - const description = resolved.description; - const icon = resolved.icon; + if (metadataEvent && metadataEvent.kind === 39000) { + // Fast path: sync extraction from kind 39000 event + const resolved = extractMetadataFromEvent(groupId, metadataEvent); + title = resolved.name || groupId; + description = resolved.description; + icon = resolved.icon; + source = resolved.source; + } else { + // Slow path: async resolution with profile fallback (for pubkey-based groupIds) + const resolved = await resolveGroupMetadata(groupId, relayUrl, undefined); + title = resolved.name || groupId; + description = resolved.description; + icon = resolved.icon; + source = resolved.source; + } - console.log(`[NIP-29] Group title: ${title} (source: ${resolved.source})`); + console.log(`[NIP-29] Group title: ${title} (source: ${source})`); // Fetch admins (kind 39001) and members (kind 39002) in parallel // Both use d tag (addressable events signed by relay)