(feat): finish chat crypto migration
Some checks failed
/ build-web (push) Failing after 4m36s
/ build-desktop (linux) (push) Failing after 4m47s
/ build-mobile (push) Failing after 6m34s
/ release (push) Has been skipped

(wip): prep for full ECDH repo migration
This commit is contained in:
Alois 2026-07-06 19:23:37 +02:00
commit 2cd326d0b1
10 changed files with 195 additions and 339 deletions

View file

@ -1,16 +1,16 @@
import { useCrypto } from "@tensamin/crypto/context";
import { useStorage } from "@tensamin/storage/context";
import { useUser } from "@tensamin/user/context";
import { useChat } from "@tensamin/chat/context";
import { useMTP } from "@tensamin/mtp";
import { createContext, useEffect, useContext } from "react";
import z from "zod";
import { toast as sonnerToast } from "sonner";
import { Message as MessageSchema } from "@tensamin/shared/data";
import { Avatar, AvatarFallback, AvatarImage } from "@tensamin/ui";
import { isTauri } from "@tauri-apps/api/core";
import { useSession } from "@tensamin/storage/session";
import { useNavigate } from "@tanstack/react-router";
import { decryptChatText } from "@tensamin/crypto/chatSecret";
import { log } from "@tensamin/shared/log";
import { type RawMessage } from "@tensamin/chat/values";
export const context = createContext<contextType | undefined>(undefined);
@ -27,108 +27,113 @@ export default function Provider(props: { children: React.ReactNode }) {
const { subscribePush, send } = useMTP();
const { load } = useStorage();
const { get } = useUser();
const { decryptText, getSharedSecret } = useCrypto();
const { addLiveMessage, userId } = useChat();
const { addLiveMessage, getChatSecret, userId } = useChat();
const { moveUserIdToTop } = useSession();
const navigate = useNavigate();
useEffect(() => {
return subscribePush(async (ttpMessage) => {
if (ttpMessage.type === "MessageLive") {
const { message, SenderId } = ttpMessage.data as {
message: z.infer<typeof MessageSchema>;
SenderId: number;
return subscribePush(async (message) => {
if (message.type === "MessageLive") {
const data = message.data as {
Message?: RawMessage;
SenderId?: number;
};
const user = await get(SenderId);
if (!data.SenderId) return;
const decryptedContent = await decryptText(
await getSharedSecret(
await load("mtp_keyring"),
await get(await load("user_id")).then((data) => data.PublicKey),
user.PublicKey,
),
message.Content,
);
const chatSecret = await getChatSecret(data.SenderId);
// Update message state
if (userId === SenderId) {
addLiveMessage({
...message,
Content: decryptedContent,
SentBySelf: false,
});
return;
}
if (!data.Message || !chatSecret) return;
const user = await get(data.SenderId);
// todo: add notification symbol to conversation cards (incl. message start)
moveUserIdToTop(SenderId);
if (await load("settings.receive_confirmations")) {
void send(
"MessageState",
{
MessageState: "received",
},
{
id: ttpMessage.id,
},
);
}
if (isTauri()) {
console.log("weewoo");
} else {
const hasPermissions = await requestNotificationPermission();
if (hasPermissions) {
const notification = new Notification(user.Display, {
body: decryptedContent,
icon: user.Avatar || user.Display.slice(0, 2).toUpperCase(),
badge: user.Avatar || user.Display.slice(0, 2).toUpperCase(),
tag: `message-${user.UserId}`,
silent: true,
void decryptChatText(chatSecret, data.Message.Content)
.catch((err) => {
log(1, "chat", "red", "Failed to decrypt live message", err, {
SendTime: data.Message?.SendTime,
});
return null;
})
.then(async (content) => {
if (!data.Message || !content || !data.SenderId) return;
notification.onclick = () => {
window.focus();
navigate({
to: `/chat?id=${user.UserId}`,
if (userId === data.SenderId) {
addLiveMessage({
...data.Message,
Content: content ?? "Failed to decrypt message",
decryptionFailed: content === null,
SentBySelf: false,
});
return;
}
notification.close();
};
} else {
sonnerToast(user.Display, {
classNames: {
content: "pl-4",
},
description: decryptedContent,
icon: (
<Avatar>
<AvatarImage src={user.Avatar} />
<AvatarFallback>
{user.Display.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
),
});
}
}
// todo: add notification symbol to conversation cards (incl. message start)
moveUserIdToTop(data.SenderId);
if (await load("settings.receive_confirmations")) {
void send(
"MessageState",
{
MessageState: "received",
},
{
id: data.Message.SendTime,
},
);
}
if (isTauri()) {
console.log("weewoo");
} else {
const hasPermissions = await requestNotificationPermission();
if (hasPermissions) {
const notification = new Notification(user.Display, {
body: content,
icon: user.Avatar || user.Display.slice(0, 2).toUpperCase(),
badge: user.Avatar || user.Display.slice(0, 2).toUpperCase(),
tag: `message-${user.UserId}`,
silent: true,
});
notification.onclick = () => {
window.focus();
navigate({
to: `/chat?id=${user.UserId}`,
});
notification.close();
};
} else {
sonnerToast(user.Display, {
classNames: {
content: "pl-4",
},
description: content,
icon: (
<Avatar>
<AvatarImage src={user.Avatar} />
<AvatarFallback>
{user.Display.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
),
});
}
}
});
return;
}
});
}, [
subscribePush,
decryptText,
getSharedSecret,
load,
get,
addLiveMessage,
userId,
moveUserIdToTop,
send,
get,
load,
navigate,
send,
moveUserIdToTop,
subscribePush,
getChatSecret,
userId,
]);
return (