From 901cec1820a2ad4348d848bc021e4c047e0947d7 Mon Sep 17 00:00:00 2001 From: ajvpot <553597+ajvpot@users.noreply.github.com> Date: Wed, 10 Sep 2025 01:41:42 +0200 Subject: [PATCH] cache decryption results --- src/components/ChatMessageItem.tsx | 72 ++++++++++----------------- src/hooks/useMessageDecryption.ts | 79 ++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 45 deletions(-) create mode 100644 src/hooks/useMessageDecryption.ts diff --git a/src/components/ChatMessageItem.tsx b/src/components/ChatMessageItem.tsx index b3eaa69..e05bfe9 100644 --- a/src/components/ChatMessageItem.tsx +++ b/src/components/ChatMessageItem.tsx @@ -1,7 +1,7 @@ "use client"; -import React, { useState, useEffect, useMemo, useCallback } from "react"; +import React, { useMemo } from "react"; import { useConfig } from "./ConfigContext"; -import { decryptMeshcoreGroupMessage } from "../lib/meshcore"; +import { useMessageDecryption } from "../hooks/useMessageDecryption"; import PathVisualization, { PathData } from "./PathVisualization"; import NodeLinkWithHover from "./NodeLinkWithHover"; @@ -57,39 +57,17 @@ function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessage, showErrorRow ...(config?.meshcoreKeys?.map((k: any) => k.privateKey) || []), "izOH6cXN6mrJ5e26oRXNcg==", // Always include public key ], [config?.meshcoreKeys]); - const knownKeysString = knownKeys.join(","); - const [parsed, setParsed] = useState(null); - const [error, setError] = useState(null); - useEffect(() => { - let cancelled = false; - (async () => { - try { - const result = await decryptMeshcoreGroupMessage({ - encrypted_message: msg.encrypted_message, - mac: msg.mac, - channel_hash: msg.channel_hash, - knownKeys, - parse: true, - }); - if (!cancelled) { - if (result === null) { - setParsed(null); - setError("Could not decrypt message with any known key."); - } else { - setParsed(result); - setError(null); - } - } - } catch (err) { - if (!cancelled) { - setParsed(null); - setError(err instanceof Error ? err.message : "Decryption error occurred."); - } - } - })(); - return () => { cancelled = true; }; - }, [msg.encrypted_message, msg.mac, msg.channel_hash, knownKeysString, knownKeys]); + const { data: decryptionResult, isLoading } = useMessageDecryption({ + encrypted_message: msg.encrypted_message, + mac: msg.mac, + channel_hash: msg.channel_hash, + knownKeys, + parse: true, + }); + + const parsed = decryptionResult?.decrypted || null; + const error = decryptionResult?.error || null; const originKeyPathArray = useMemo(() => msg.origin_key_path_array && msg.origin_key_path_array.length > 0 ? msg.origin_key_path_array : [], @@ -158,20 +136,24 @@ function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessage, showErrorRow } } - return ( -
-
+ if (isLoading) { + return ( +
+
{formatLocalTime(msg.ingest_timestamp)} channel: {msg.channel_hash}
-
- -
- ); +
+ +
+ ); + } + + return null; // This should not be reached now, but kept for safety } export default React.memo(ChatMessageItem, (prevProps, nextProps) => { diff --git a/src/hooks/useMessageDecryption.ts b/src/hooks/useMessageDecryption.ts new file mode 100644 index 0000000..ce2b6d3 --- /dev/null +++ b/src/hooks/useMessageDecryption.ts @@ -0,0 +1,79 @@ +import { useQuery } from '@tanstack/react-query'; +import { decryptMeshcoreGroupMessage } from '../lib/meshcore'; +import { useMemo } from 'react'; + +export interface MessageDecryptionParams { + encrypted_message: string; + mac: string; + channel_hash: string; + knownKeys: string[]; + parse?: boolean; +} + +export interface DecryptedMessage { + timestamp: number; + msgType: number; + sender: string; + text: string; + rawText: string; +} + +export interface MessageDecryptionResult { + decrypted: DecryptedMessage | null; + error: string | null; +} + +interface UseMessageDecryptionParams extends MessageDecryptionParams { + enabled?: boolean; +} + +export function useMessageDecryption({ + encrypted_message, + mac, + channel_hash, + knownKeys, + parse = true, + enabled = true +}: UseMessageDecryptionParams) { + // Stabilize the known keys to prevent unnecessary re-renders + const knownKeysString = useMemo(() => knownKeys.join(','), [knownKeys]); + + return useQuery({ + queryKey: ['message-decryption', encrypted_message, mac, channel_hash, knownKeysString, parse], + queryFn: async (): Promise => { + try { + const result = await decryptMeshcoreGroupMessage({ + encrypted_message, + mac, + channel_hash, + knownKeys, + parse, + }); + + if (result === null) { + return { + decrypted: null, + error: "Could not decrypt message with any known key." + }; + } + + return { + decrypted: result as DecryptedMessage, + error: null + }; + } catch (err) { + return { + decrypted: null, + error: err instanceof Error ? err.message : "Decryption error occurred." + }; + } + }, + enabled: enabled && !!encrypted_message && !!mac && !!channel_hash && knownKeys.length > 0, + staleTime: Infinity, // Never consider decrypted messages stale + gcTime: Infinity, // Never garbage collect decrypted messages + retry: false, // Don't retry decryption failures + refetchOnWindowFocus: false, // Don't refetch on focus - decryption is deterministic + refetchOnMount: false, // Don't refetch on mount if we have cached data + refetchOnReconnect: false, // Don't refetch on network reconnect + }); +}