diff --git a/src/hooks/useDirectMessages.ts b/src/hooks/useDirectMessages.ts index 3ad87728..6257b641 100644 --- a/src/hooks/useDirectMessages.ts +++ b/src/hooks/useDirectMessages.ts @@ -212,6 +212,63 @@ function mergeConversationPages(data: { pages: DirectMessagesPage[] } | undefine return [...byPeer.values()].sort((a, b) => b.latest.createdAt - a.latest.createdAt); } +function appendMessage(messages: DirectMessage[] | undefined, message: DirectMessage): DirectMessage[] | undefined { + if (!messages) return messages; + if (messages.some((existing) => existing.id === message.id)) return messages; + return [...messages, message].sort((a, b) => a.createdAt - b.createdAt); +} + +function addSentMessageToPages( + data: InfiniteData | undefined, + message: DirectMessage, +): InfiniteData | undefined { + if (!data || data.pages.length === 0) return data; + + const pages = data.pages.map((page, index) => { + if (index !== 0) return page; + + const conversations = [...page.conversations]; + const existingIndex = conversations.findIndex((conversation) => conversation.peer === message.peer); + if (existingIndex === -1) { + conversations.unshift({ + peer: message.peer, + events: [message.event], + messageCount: 1, + latest: message, + }); + } else { + const conversation = conversations[existingIndex]; + const events = conversation.events.some((event) => event.id === message.id) + ? conversation.events + : [...conversation.events, message.event].sort((a, b) => a.created_at - b.created_at); + + conversations[existingIndex] = { + ...conversation, + events, + messageCount: events.length, + latest: message.createdAt >= conversation.latest.createdAt ? message : conversation.latest, + }; + } + + conversations.sort((a, b) => b.latest.createdAt - a.latest.createdAt); + return { ...page, conversations }; + }); + + return { ...data, pages }; +} + +function directMessageFromPlaintext(event: NostrEvent, peer: string, self: string, content: string): DirectMessage { + return { + id: event.id, + pubkey: event.pubkey, + peer, + outgoing: event.pubkey === self, + createdAt: event.created_at, + content, + event, + }; +} + async function decryptMessage({ event, peer, @@ -258,6 +315,7 @@ export function useDirectMessageThread(conversation: Conversation | null) { conversation?.messageCount, ], enabled: !!self && !!nip04 && !!conversation, + placeholderData: (previousData) => previousData, queryFn: async () => { if (!self || !nip04 || !conversation) return []; const messages: DirectMessage[] = []; @@ -272,7 +330,8 @@ export function useDirectMessageThread(conversation: Conversation | null) { /** * Send a NIP-04 encrypted direct message to a peer. Encrypts the plaintext with * the signer's `nip04.encrypt`, then publishes a kind-4 event tagging the - * recipient. Invalidates the DM query so the new message shows up. + * recipient. Seeds the DM caches with the signed event so the active thread can + * append smoothly without waiting for relays to return the message. */ export function useSendDirectMessage() { const { user } = useCurrentUser(); @@ -289,8 +348,35 @@ export function useSendDirectMessage() { const content = await user.signer.nip04.encrypt(peer, trimmed); return publish({ kind: DM_KIND, content, tags: [['p', peer]] }); }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['direct-messages', user?.pubkey] }); + onSuccess: (event, { peer, text }) => { + if (!user) return; + + const messagesKey = ['direct-messages', user.pubkey] as const; + const existingData = queryClient.getQueryData>(messagesKey); + const existingConversation = mergeConversationPages(existingData)?.find((conversation) => conversation.peer === peer); + const messageCount = existingConversation?.events.some((existing) => existing.id === event.id) + ? existingConversation.messageCount + : (existingConversation?.messageCount ?? 0) + 1; + const message = directMessageFromPlaintext(event, peer, user.pubkey, text.trim()); + const existingThread = queryClient + .getQueriesData({ queryKey: ['direct-message-thread', user.pubkey, peer] }) + .find(([, messages]) => messages !== undefined)?.[1]; + + queryClient.setQueryData>(messagesKey, (data) => ( + addSentMessageToPages(data, message) + )); + queryClient.setQueryData([ + 'direct-message-thread', + user.pubkey, + peer, + event.id, + messageCount, + ], appendMessage(existingThread ?? [], message)); + queryClient.setQueriesData({ + queryKey: ['direct-message-thread', user.pubkey, peer], + }, (messages) => appendMessage(messages, message)); + + queryClient.invalidateQueries({ queryKey: messagesKey, refetchType: 'inactive' }); }, }); }