fix: resolve NIP-17 crashes and conversation key inconsistencies

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
This commit is contained in:
Claude
2026-01-20 16:48:34 +00:00
parent 42568ae29e
commit 776b9af152
3 changed files with 41 additions and 5 deletions

View File

@@ -940,6 +940,28 @@ export function ChatViewer({
{conversation.metadata.description}
</p>
)}
{/* Participants for NIP-17 group DMs */}
{protocol === "nip-17" &&
conversation.participants.length > 2 && (
<div className="flex flex-col gap-1">
<span className="text-xs text-primary-foreground/80">
Participants:
</span>
<div className="flex flex-col gap-0.5 text-xs">
{[
...new Set(
conversation.participants.map((p) => p.pubkey),
),
].map((participantPubkey) => (
<UserName
key={participantPubkey}
pubkey={participantPubkey}
className="text-xs text-primary-foreground"
/>
))}
</div>
</div>
)}
{/* Protocol Type - Clickable */}
<div className="flex items-center gap-1.5 text-xs">
{(conversation.type === "group" ||

View File

@@ -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(":");
}
/**

View File

@@ -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;