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;