(feat): move decryption to getMessages instead of letting it get handled by the message component

This commit is contained in:
Alois 2026-05-01 11:16:23 +02:00
commit de63b554c1
4 changed files with 31 additions and 230 deletions

View file

@ -8,7 +8,6 @@ import { Plus, Laugh, Clapperboard } from "lucide-react";
import { useChat } from "../context";
import { useTTP } from "@tensamin/ttp";
import { log, toast } from "@tensamin/shared/log";
import Message from "./message";
import { cn, useIsMobile } from "@tensamin/ui";
import { encryptText } from "@tensamin/crypto/worker";
@ -20,7 +19,6 @@ export default function InputComponent({
setValue: (value: string) => void;
}) {
const [invertEnterBehavior, setInvertEnterBehavior] = React.useState(false);
const measurementRef = React.useRef<HTMLDivElement | null>(null);
const { send } = useTTP();
const { addLiveMessage, sharedSecret, userId } = useChat();
@ -32,21 +30,6 @@ export default function InputComponent({
});
}, [load]);
const getRenderedHeight = React.useCallback(() => {
const measuredHeight =
measurementRef.current?.getBoundingClientRect().height;
if (
typeof measuredHeight === "number" &&
Number.isFinite(measuredHeight) &&
measuredHeight > 0
) {
return Math.ceil(measuredHeight);
}
return 40;
}, []);
/**
* Executes handleSubmit.
* @param none This function has no parameters.
@ -68,10 +51,8 @@ export default function InputComponent({
return;
}
const height = getRenderedHeight();
const reference = addLiveMessage({
height,
height: 0,
not_encrypted: true,
send_time: time,
content: currentValue,
@ -82,7 +63,7 @@ export default function InputComponent({
const encryptedContext = await encryptText(sharedSecret, currentValue);
send("message_send", {
height,
height: 0,
content: encryptedContext,
receiver_id: userId,
send_time: time,
@ -112,24 +93,6 @@ export default function InputComponent({
)}
>
<CardHeader className="relative p-0 flex flex-col">
<div
aria-hidden="true"
className="pointer-events-none absolute left-0 top-0 w-full px-2.5 opacity-0"
style={{ visibility: "hidden" }}
>
<Message
measureRef={measurementRef}
message={{
height: 0,
not_encrypted: true,
send_time: 0,
content: value,
sent_by_self: true,
message_state: "awaiting",
}}
notEncrypted
/>
</div>
<Input
placeholder="Send a message..."
value={value}

View file

@ -1,8 +1,5 @@
import { useCrypto } from "@tensamin/crypto/context";
import * as React from "react";
import { useChat } from "../context";
import type { RawMessage } from "../values";
import { log } from "@tensamin/shared/log";
import Text from "@tensamin/markdown/text";
import { AlertTriangle, Clock } from "lucide-react";
@ -14,92 +11,18 @@ import {
ContextMenuItem,
ContextMenuTrigger,
} from "@tensamin/ui";
import { decryptText } from "@tensamin/crypto/worker";
function generateFixedLoadingSize(size: number, height: number) {
return size * 3 - (height / 20) * 11;
}
function getSafeMessageHeight(height: number | undefined) {
if (typeof height === "number" && Number.isFinite(height) && height > 0) {
return Math.ceil(height);
}
return 40;
}
/**
* Executes Message.
* @param props Parameter props.
* @returns unknown.
*/
function MessageComponent({
message,
notEncrypted,
measureRef,
}: {
message: RawMessage & {
failed?: boolean;
};
notEncrypted?: boolean;
measureRef?: React.Ref<HTMLDivElement>;
}) {
const { sharedSecret } = useChat();
const { decrypt } = useCrypto();
const isMobile = useIsMobile();
const [decodedContent, setDecodedContent] = React.useState("");
const [isReady, setIsReady] = React.useState(false);
const safeMessageHeight = getSafeMessageHeight(message.height);
React.useEffect(() => {
if (notEncrypted) {
setDecodedContent(message.content);
setIsReady(true);
return;
}
const content = message.content;
const secret = sharedSecret;
let active = true;
if (!secret) {
setIsReady(false);
return;
}
decryptText(secret, content)
.then((value) => {
if (!active) {
return;
}
setDecodedContent(value);
setIsReady(true);
})
.catch((e) => {
if (!active) {
return;
}
log(0, "Chat", "red", "Failed to decrypt message", e, {
content,
secret,
});
setDecodedContent("Failed to decrypt");
setIsReady(true);
});
return () => {
active = false;
};
}, [decrypt, message.content, notEncrypted, sharedSecret]);
return (
<div
ref={measureRef}
className={`w-full flex justify-start transition-opacity duration-150 ${(message.failed || message.message_state === "awaiting") && "opacity-50"}`}
>
<ContextMenu>
@ -112,7 +35,7 @@ function MessageComponent({
? "bg-destructive/75 text-destructive-foreground"
: "bg-primary text-primary-foreground"
: "bg-muted"
} ${!isReady && "animate-pulse"} ${isMobile && "select-none"}`}
} ${isMobile && "select-none"}`}
>
{message.failed && message.message_state === "awaiting" && (
<Tooltip>
@ -125,26 +48,13 @@ function MessageComponent({
{!message.failed && message.message_state === "awaiting" && (
<Clock size={17} />
)}
{isReady ? (
<Text value={decodedContent} />
) : (
<div
className="block rounded-sm"
style={{
width: generateFixedLoadingSize(
message.content.length,
safeMessageHeight,
),
minHeight: safeMessageHeight,
}}
/>
)}
<Text value={message.content} />
</div>
}
/>
<ContextMenuContent>
<ContextMenuItem>
<Text value={decodedContent} />
<Text value={message.content} />
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
@ -154,8 +64,6 @@ function MessageComponent({
export default React.memo(MessageComponent, (prev, next) => {
return (
prev.notEncrypted === next.notEncrypted &&
prev.measureRef === next.measureRef &&
prev.message.send_time === next.message.send_time &&
prev.message.content === next.message.content &&
prev.message.height === next.message.height &&