diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index 6b26f8e..ade187f 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -1,6 +1,6 @@ import { useMemo, useState, memo, useCallback, useRef, useEffect } from "react"; import { use$ } from "applesauce-react/hooks"; -import { from, catchError, of, map, Observable } from "rxjs"; +import { from, catchError, of, map } from "rxjs"; import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; import { Loader2, Reply, Zap, AlertTriangle, RefreshCw } from "lucide-react"; import { getZapRequest } from "applesauce-common/helpers/zap"; diff --git a/src/lib/chat/adapters/nip-29-adapter.ts b/src/lib/chat/adapters/nip-29-adapter.ts index 7838651..1f1bc80 100644 --- a/src/lib/chat/adapters/nip-29-adapter.ts +++ b/src/lib/chat/adapters/nip-29-adapter.ts @@ -101,6 +101,12 @@ export class Nip29Adapter extends ChatProtocolAdapter { async resolveConversation( identifier: ProtocolIdentifier, ): Promise { + // This adapter only handles group identifiers + if (identifier.type !== "group") { + throw new Error( + `NIP-29 adapter cannot handle identifier type: ${identifier.type}`, + ); + } const groupId = identifier.value; const relayUrl = identifier.relays?.[0]; @@ -367,7 +373,10 @@ export class Nip29Adapter extends ChatProtocolAdapter { }); console.log(`[NIP-29] Timeline has ${messages.length} events`); - return messages.sort((a, b) => a.timestamp - b.timestamp); + // EventStore timeline returns events sorted by created_at desc, + // we need ascending order for chat. Since it's already sorted, + // just reverse instead of full sort (O(n) vs O(n log n)) + return messages.reverse(); }), ); } @@ -413,7 +422,9 @@ export class Nip29Adapter extends ChatProtocolAdapter { return this.eventToMessage(event, conversation.id); }); - return messages.sort((a, b) => a.timestamp - b.timestamp); + // loadMoreMessages returns events in desc order from relay, + // reverse for ascending chronological order + return messages.reverse(); } /** diff --git a/src/lib/chat/adapters/nip-53-adapter.ts b/src/lib/chat/adapters/nip-53-adapter.ts index 9a103b7..871fa86 100644 --- a/src/lib/chat/adapters/nip-53-adapter.ts +++ b/src/lib/chat/adapters/nip-53-adapter.ts @@ -84,11 +84,13 @@ export class Nip53Adapter extends ChatProtocolAdapter { async resolveConversation( identifier: ProtocolIdentifier, ): Promise { - const { pubkey, identifier: dTag } = identifier.value as { - kind: number; - pubkey: string; - identifier: string; - }; + // This adapter only handles live-activity identifiers + if (identifier.type !== "live-activity") { + throw new Error( + `NIP-53 adapter cannot handle identifier type: ${identifier.type}`, + ); + } + const { pubkey, identifier: dTag } = identifier.value; const relayHints = identifier.relays || []; const activePubkey = accountManager.active$.value?.pubkey; @@ -311,7 +313,10 @@ export class Nip53Adapter extends ChatProtocolAdapter { .filter((msg): msg is Message => msg !== null); console.log(`[NIP-53] Timeline has ${messages.length} events`); - return messages.sort((a, b) => a.timestamp - b.timestamp); + // EventStore timeline returns events sorted by created_at desc, + // we need ascending order for chat. Since it's already sorted, + // just reverse instead of full sort (O(n) vs O(n log n)) + return messages.reverse(); }), ); } @@ -380,7 +385,9 @@ export class Nip53Adapter extends ChatProtocolAdapter { }) .filter((msg): msg is Message => msg !== null); - return messages.sort((a, b) => a.timestamp - b.timestamp); + // loadMoreMessages returns events in desc order from relay, + // reverse for ascending chronological order + return messages.reverse(); } /** diff --git a/src/lib/chat/adapters/nip-c7-adapter.ts b/src/lib/chat/adapters/nip-c7-adapter.ts index 28aa3cc..4e375de 100644 --- a/src/lib/chat/adapters/nip-c7-adapter.ts +++ b/src/lib/chat/adapters/nip-c7-adapter.ts @@ -93,8 +93,15 @@ export class NipC7Adapter extends ChatProtocolAdapter { throw new Error(`Failed to resolve NIP-05: ${identifier.value}`); } pubkey = resolved; - } else { + } else if ( + identifier.type === "chat-partner" || + identifier.type === "dm-recipient" + ) { pubkey = identifier.value; + } else { + throw new Error( + `NIP-C7 adapter cannot handle identifier type: ${identifier.type}`, + ); } const activePubkey = accountManager.active$.value?.pubkey; diff --git a/src/types/chat.ts b/src/types/chat.ts index 711333d..6d77c7f 100644 --- a/src/types/chat.ts +++ b/src/types/chat.ts @@ -120,14 +120,74 @@ export interface Message { } /** - * Protocol-specific identifier + * NIP-29 group identifier + */ +export interface GroupIdentifier { + type: "group"; + /** Group ID (e.g., "bitcoin-dev") */ + value: string; + /** Relay URL where the group is hosted (required for NIP-29) */ + relays: string[]; +} + +/** + * NIP-53 live activity identifier + */ +export interface LiveActivityIdentifier { + type: "live-activity"; + /** Address pointer for the live activity */ + value: { + kind: 30311; + pubkey: string; + identifier: string; + }; + /** Relay hints from naddr encoding */ + relays?: string[]; +} + +/** + * NIP-C7/NIP-17 direct message identifier (resolved pubkey) + */ +export interface DMIdentifier { + type: "dm-recipient" | "chat-partner"; + /** Recipient pubkey (hex) */ + value: string; + /** Relay hints */ + relays?: string[]; +} + +/** + * NIP-C7 NIP-05 identifier (needs resolution) + */ +export interface NIP05Identifier { + type: "chat-partner-nip05"; + /** NIP-05 address to resolve */ + value: string; + /** Relay hints */ + relays?: string[]; +} + +/** + * NIP-28 channel identifier (future) + */ +export interface ChannelIdentifier { + type: "channel"; + /** Channel creation event ID or address */ + value: string; + /** Relay hints */ + relays?: string[]; +} + +/** + * Protocol-specific identifier - discriminated union * Returned by adapter parseIdentifier() */ -export interface ProtocolIdentifier { - type: string; // e.g., 'dm-recipient', 'channel-event', 'group-id' - value: any; // Protocol-specific value - relays?: string[]; // Relay hints from bech32 encoding -} +export type ProtocolIdentifier = + | GroupIdentifier + | LiveActivityIdentifier + | DMIdentifier + | NIP05Identifier + | ChannelIdentifier; /** * Chat command parsing result