diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx
index cb52b84..9338d8e 100644
--- a/src/components/ChatViewer.tsx
+++ b/src/components/ChatViewer.tsx
@@ -135,6 +135,24 @@ function isLiveActivityMetadata(value: unknown): value is LiveActivityMetadata {
);
}
+/**
+ * Get relay URLs for a conversation based on protocol
+ * Used for fetching protocol-specific data like reactions
+ */
+function getConversationRelays(conversation: Conversation): string[] {
+ // NIP-53 live chats: Use full relay list from liveActivity metadata
+ if (conversation.protocol === "nip-53") {
+ const liveActivity = conversation.metadata?.liveActivity;
+ if (isLiveActivityMetadata(liveActivity) && liveActivity.relays) {
+ return liveActivity.relays;
+ }
+ }
+
+ // NIP-29 groups and fallback: Use single relay URL
+ const relayUrl = conversation.metadata?.relayUrl;
+ return relayUrl ? [relayUrl] : [];
+}
+
/**
* Get the chat command identifier for a conversation
* Returns a string that can be passed to the `chat` command to open this conversation
@@ -249,6 +267,12 @@ const MessageItem = memo(function MessageItem({
canReply: boolean;
onScrollToMessage?: (messageId: string) => void;
}) {
+ // Get relays for this conversation (memoized to prevent unnecessary re-subscriptions)
+ const relays = useMemo(
+ () => getConversationRelays(conversation),
+ [conversation],
+ );
+
// System messages (join/leave) have special styling
if (message.type === "system") {
return (
@@ -334,10 +358,7 @@ const MessageItem = memo(function MessageItem({
{/* Reactions display - lazy loaded per message */}
-
+
);
}
@@ -380,10 +401,7 @@ const MessageItem = memo(function MessageItem({
)}
{/* Reactions display - lazy loaded per message */}
-
+
);
diff --git a/src/components/chat/MessageReactions.tsx b/src/components/chat/MessageReactions.tsx
index d0d6f06..28fdaf0 100644
--- a/src/components/chat/MessageReactions.tsx
+++ b/src/components/chat/MessageReactions.tsx
@@ -1,13 +1,14 @@
-import { useMemo } from "react";
+import { useMemo, useEffect } from "react";
import { use$ } from "applesauce-react/hooks";
import eventStore from "@/services/event-store";
+import pool from "@/services/relay-pool";
import { EMOJI_SHORTCODE_REGEX } from "@/lib/emoji-helpers";
import type { NostrEvent } from "@/types/nostr";
interface MessageReactionsProps {
messageId: string;
- /** Relay URL for fetching reactions (NIP-29 group relay) */
- relayUrl?: string;
+ /** Relay URLs for fetching reactions - protocol-specific */
+ relays: string[];
}
interface ReactionSummary {
@@ -26,14 +27,51 @@ interface ReactionSummary {
* Loads kind 7 (reaction) events that reference the messageId via e-tag.
* Aggregates by emoji and displays as tiny inline badges in bottom-right corner.
*
- * Uses EventStore timeline for reactive updates - new reactions appear automatically.
+ * Fetches reactions from protocol-specific relays and uses EventStore timeline
+ * for reactive updates - new reactions appear automatically.
*/
-export function MessageReactions({
- messageId,
- relayUrl,
-}: MessageReactionsProps) {
+export function MessageReactions({ messageId, relays }: MessageReactionsProps) {
+ // Start relay subscription to fetch reactions for this message
+ useEffect(() => {
+ if (relays.length === 0) return;
+
+ const filter = {
+ kinds: [7],
+ "#e": [messageId],
+ limit: 100, // Reasonable limit for reactions
+ };
+
+ // Subscribe to relays to fetch reactions
+ const subscription = pool
+ .subscription(relays, [filter], {
+ eventStore, // Automatically add reactions to EventStore
+ })
+ .subscribe({
+ next: (response) => {
+ if (typeof response !== "string") {
+ // Event received - it's automatically added to EventStore
+ console.log(
+ `[MessageReactions] Reaction received for ${messageId.slice(0, 8)}...`,
+ );
+ }
+ },
+ error: (err) => {
+ console.error(
+ `[MessageReactions] Subscription error for ${messageId.slice(0, 8)}...`,
+ err,
+ );
+ },
+ });
+
+ // Cleanup subscription when component unmounts or messageId changes
+ return () => {
+ subscription.unsubscribe();
+ };
+ }, [messageId, relays]);
+
// Load reactions for this message from EventStore
// Filter: kind 7, e-tag pointing to messageId
+ // This observable will update automatically as reactions arrive from the subscription above
const reactions = use$(
() =>
eventStore.timeline({