diff --git a/src/lib/chat/adapters/nip-10-adapter.ts b/src/lib/chat/adapters/nip-10-adapter.ts index ccca9f3..8cbe5d5 100644 --- a/src/lib/chat/adapters/nip-10-adapter.ts +++ b/src/lib/chat/adapters/nip-10-adapter.ts @@ -22,7 +22,8 @@ import pool from "@/services/relay-pool"; import { publishEventToRelays } from "@/services/hub"; import accountManager from "@/services/accounts"; import { AGGREGATOR_RELAYS } from "@/services/loaders"; -import { normalizeURL } from "applesauce-core/helpers"; +import { mergeRelaySets } from "applesauce-core/helpers"; +import { getOutboxes } from "applesauce-core/helpers/mailboxes"; import { getEventPointerFromETag } from "applesauce-core/helpers/pointers"; import { EventFactory } from "applesauce-core/event-factory"; import { @@ -516,16 +517,9 @@ export class Nip10Adapter extends ChatProtocolAdapter { return cachedEvent; } - // Build relay list: conversation relays + pointer relay hints (deduplicated) + // Build relay list: conversation relays + pointer relay hints (deduplicated and normalized) const conversationRelays = conversation.metadata?.relays || []; - const relays = [...conversationRelays]; - if (pointer.relays) { - for (const relay of pointer.relays) { - if (!relays.includes(relay)) { - relays.push(relay); - } - } - } + const relays = mergeRelaySets(conversationRelays, pointer.relays || []); if (relays.length === 0) { console.warn("[NIP-10] No relays for loading reply message"); @@ -635,52 +629,38 @@ export class Nip10Adapter extends ChatProtocolAdapter { providedEvent: NostrEvent, providedRelays: string[], ): Promise { - const relays = new Set(); + const relaySets: string[][] = []; - // 1. Provided relay hints - providedRelays.forEach((r) => relays.add(normalizeURL(r))); + // 1. Provided relay hints (highest priority) + relaySets.push(providedRelays); - // 2. Root author's outbox relays (NIP-65) - highest priority + // 2. Root author's outbox relays (NIP-65) try { const rootOutbox = await this.getOutboxRelays(rootEvent.pubkey); - rootOutbox.slice(0, 3).forEach((r) => relays.add(normalizeURL(r))); + relaySets.push(rootOutbox.slice(0, 3)); } catch (err) { console.warn("[NIP-10] Failed to get root author outbox:", err); } // 3. Collect unique participant pubkeys from both events' p-tags const participantPubkeys = new Set(); - - // Add p-tags from root event for (const tag of rootEvent.tags) { - if (tag[0] === "p" && tag[1]) { - participantPubkeys.add(tag[1]); - } + if (tag[0] === "p" && tag[1]) participantPubkeys.add(tag[1]); } - - // Add p-tags from provided event for (const tag of providedEvent.tags) { - if (tag[0] === "p" && tag[1]) { - participantPubkeys.add(tag[1]); - } + if (tag[0] === "p" && tag[1]) participantPubkeys.add(tag[1]); } - - // Add provided event author if different from root if (providedEvent.pubkey !== rootEvent.pubkey) { participantPubkeys.add(providedEvent.pubkey); } // 4. Fetch outbox relays from participant subset (limit to avoid slowdown) - // Take first 5 participants to get relay diversity without excessive fetching const participantsToCheck = Array.from(participantPubkeys).slice(0, 5); for (const pubkey of participantsToCheck) { try { const outbox = await this.getOutboxRelays(pubkey); - // Add 1 relay from each participant for diversity - if (outbox.length > 0) { - relays.add(normalizeURL(outbox[0])); - } - } catch (_err) { + if (outbox.length > 0) relaySets.push([outbox[0]]); + } catch { // Silently continue if participant has no relay list } } @@ -690,19 +670,22 @@ export class Nip10Adapter extends ChatProtocolAdapter { if (activePubkey && !participantPubkeys.has(activePubkey)) { try { const userOutbox = await this.getOutboxRelays(activePubkey); - userOutbox.slice(0, 2).forEach((r) => relays.add(normalizeURL(r))); + relaySets.push(userOutbox.slice(0, 2)); } catch (err) { console.warn("[NIP-10] Failed to get user outbox:", err); } } + // Merge all relay sets (handles deduplication and normalization) + let relays = mergeRelaySets(...relaySets); + // 6. Fallback to aggregator relays if we have too few - if (relays.size < 3) { - AGGREGATOR_RELAYS.forEach((r) => relays.add(r)); + if (relays.length < 3) { + relays = mergeRelaySets(relays, AGGREGATOR_RELAYS); } // Limit to 10 relays max for performance - return Array.from(relays).slice(0, 10); + return relays.slice(0, 10); } /** @@ -716,15 +699,8 @@ export class Nip10Adapter extends ChatProtocolAdapter { if (!relayList) return []; - // Extract write relays (r tags with "write" or no marker) - return relayList.tags - .filter((t) => { - if (t[0] !== "r") return false; - const marker = t[2]; - return !marker || marker === "write"; - }) - .map((t) => normalizeURL(t[1])) - .slice(0, 5); // Limit to 5 + // Use applesauce helper to extract write relays + return getOutboxes(relayList).slice(0, 5); } /** diff --git a/src/lib/chat/adapters/nip-29-adapter.ts b/src/lib/chat/adapters/nip-29-adapter.ts index 538ee43..1e57b3b 100644 --- a/src/lib/chat/adapters/nip-29-adapter.ts +++ b/src/lib/chat/adapters/nip-29-adapter.ts @@ -21,6 +21,7 @@ import { publishEventToRelays, publishEvent } from "@/services/hub"; import accountManager from "@/services/accounts"; import { getTagValues, getQuotePointer } from "@/lib/nostr-utils"; import { getEventPointerFromETag } from "applesauce-core/helpers/pointers"; +import { mergeRelaySets } from "applesauce-core/helpers"; import { normalizeRelayURL } from "@/lib/relay-url"; import { EventFactory } from "applesauce-core/event-factory"; import { @@ -803,20 +804,12 @@ export class Nip29Adapter extends ChatProtocolAdapter { return cachedEvent; } - // Build relay list: group relay + pointer relay hints (deduplicated) - const relays: string[] = []; + // Build relay list: group relay + pointer relay hints (deduplicated and normalized) const groupRelayUrl = conversation.metadata?.relayUrl; - if (groupRelayUrl) { - relays.push(groupRelayUrl); - } - // Add relay hints from the pointer (q-tag relay hint) - if (pointer.relays) { - for (const relay of pointer.relays) { - if (!relays.includes(relay)) { - relays.push(relay); - } - } - } + const relays = mergeRelaySets( + groupRelayUrl ? [groupRelayUrl] : [], + pointer.relays || [], + ); if (relays.length === 0) { console.warn("[NIP-29] No relays available for loading reply message"); diff --git a/src/lib/chat/adapters/nip-53-adapter.ts b/src/lib/chat/adapters/nip-53-adapter.ts index bca5275..de244cd 100644 --- a/src/lib/chat/adapters/nip-53-adapter.ts +++ b/src/lib/chat/adapters/nip-53-adapter.ts @@ -24,6 +24,7 @@ import { publishEventToRelays } from "@/services/hub"; import accountManager from "@/services/accounts"; import { AGGREGATOR_RELAYS } from "@/services/loaders"; import { getEventPointerFromETag } from "applesauce-core/helpers/pointers"; +import { mergeRelaySets } from "applesauce-core/helpers"; import { parseLiveActivity, getLiveStatus, @@ -666,20 +667,13 @@ export class Nip53Adapter extends ChatProtocolAdapter { // Get conversation relays const conversationRelays = liveActivity?.relays && liveActivity.relays.length > 0 - ? [...liveActivity.relays] + ? liveActivity.relays : conversation.metadata?.relayUrl ? [conversation.metadata.relayUrl] : []; - // Add pointer relay hints (deduplicated) - const relays = [...conversationRelays]; - if (pointer.relays) { - for (const relay of pointer.relays) { - if (!relays.includes(relay)) { - relays.push(relay); - } - } - } + // Merge conversation relays with pointer relay hints (deduplicated and normalized) + const relays = mergeRelaySets(conversationRelays, pointer.relays || []); if (relays.length === 0) { console.warn("[NIP-53] No relays for loading reply message");