From 776b9af15231bf7edb5bbf917aea78725a0c0439 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Jan 2026 16:48:34 +0000 Subject: [PATCH] fix: resolve NIP-17 crashes and conversation key inconsistencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical Fixes: 1. Fixed crashes from non-DM gift wraps (reactions, private relays, etc.) - Only use getConversationIdentifierFromMessage() for kind 14 DMs - Manual conversation key generation for other kinds (7, 25050, etc.) - No more "Can only get participants from direct message event" errors 2. Fixed chat $me (saved messages) not loading any messages - Deduplicate pubkeys in getConversationKey() method - Self-conversations now correctly map to same key as stored messages - Conversation key "user:user" → "user" after deduplication 3. Added participant tooltip for group DMs - Shows all participant names when hovering over group title - Only displays for groups with 3+ participants - Deduplicates participant list properly Technical Details: - Kind 14 (DMs): Use applesauce getConversationIdentifierFromMessage() - Other kinds: Extract p-tags manually and sort - All conversation keys deduplicated and sorted for consistency - Matches storage format from gift-wrap service --- src/components/ChatViewer.tsx | 22 ++++++++++++++++++++++ src/lib/chat/adapters/nip-17-adapter.ts | 5 +++-- src/services/gift-wrap.ts | 19 ++++++++++++++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index ef3b89b..f65019b 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -940,6 +940,28 @@ export function ChatViewer({ {conversation.metadata.description}

)} + {/* Participants for NIP-17 group DMs */} + {protocol === "nip-17" && + conversation.participants.length > 2 && ( +
+ + Participants: + +
+ {[ + ...new Set( + conversation.participants.map((p) => p.pubkey), + ), + ].map((participantPubkey) => ( + + ))} +
+
+ )} {/* Protocol Type - Clickable */}
{(conversation.type === "group" || diff --git a/src/lib/chat/adapters/nip-17-adapter.ts b/src/lib/chat/adapters/nip-17-adapter.ts index d7af0a5..711812f 100644 --- a/src/lib/chat/adapters/nip-17-adapter.ts +++ b/src/lib/chat/adapters/nip-17-adapter.ts @@ -462,8 +462,9 @@ export class Nip17Adapter extends ChatProtocolAdapter { * Helper: Get conversation key from conversation */ private getConversationKey(conversation: Conversation): string { - const pubkeys = conversation.participants.map((p) => p.pubkey).sort(); - return pubkeys.join(":"); + const pubkeys = conversation.participants.map((p) => p.pubkey); + // Deduplicate and sort for consistency with gift wrap storage + return [...new Set(pubkeys)].sort().join(":"); } /** diff --git a/src/services/gift-wrap.ts b/src/services/gift-wrap.ts index 5d93238..434dd36 100644 --- a/src/services/gift-wrap.ts +++ b/src/services/gift-wrap.ts @@ -775,9 +775,22 @@ class GiftWrapManager { throw new Error(`Failed to unlock gift wrap: ${error}`); } - // Use applesauce helper to get the conversation identifier - // This properly handles all participants including group DMs - const conversationKey = getConversationIdentifierFromMessage(rumor); + // Generate conversation key based on rumor kind + // For kind 14 (DMs), use applesauce helper which handles groups properly + // For other kinds (reactions, etc.), extract participants from p-tags + let conversationKey: string; + if (rumor.kind === 14) { + // Use applesauce helper for DMs - it properly handles group DMs + conversationKey = getConversationIdentifierFromMessage(rumor); + } else { + // For non-DM events (reactions, private relays, etc.), + // create conversation key from sender + all p-tags + const recipientPubkeys = rumor.tags + .filter((t: string[]) => t[0] === "p" && t[1]) + .map((t: string[]) => t[1]); + const allParticipants = [rumor.pubkey, ...recipientPubkeys]; + conversationKey = [...new Set(allParticipants)].sort().join(":"); + } // Get seal ID from gift wrap tags (applesauce stores it there) const sealId = rumor.id.split(":")[1] || giftWrap.id;