mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-23 03:25:07 +03:00
Refactor the code
This commit is contained in:
+71
-53
@@ -1,21 +1,14 @@
|
||||
import { API_BASE_URL } from "../core/config";
|
||||
import { getAuthHeaders } from "../auth/api";
|
||||
import { DmPanel, ChatPanelController } from "./panel";
|
||||
import { authToken, getAuthHeaders } from "../auth/api";
|
||||
import { DmPanel } from "./panel";
|
||||
import { ecdhSharedSecret, deriveWrappingKey } from "../crypto/asymmetric";
|
||||
import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../crypto/symmetric";
|
||||
import { randomBytes } from "../crypto/kdf";
|
||||
import { getCurrentKeys } from "../auth/crypto";
|
||||
import { websocket } from "../websocket";
|
||||
import type { WebSocketMessage } from "../core/types";
|
||||
import { request, websocket } from "../websocket";
|
||||
import type { FetchDMResponse, SendDMRequest, WebSocketMessage } from "../core/types";
|
||||
import type { Tabs } from "mdui/components/tabs";
|
||||
|
||||
function b64(a: Uint8Array): string { return btoa(String.fromCharCode(...a)); }
|
||||
function ub64(s: string): Uint8Array {
|
||||
const bin = atob(s);
|
||||
const arr = new Uint8Array(bin.length);
|
||||
for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
||||
return arr;
|
||||
}
|
||||
import { b64, ub64 } from "../utils/utils";
|
||||
|
||||
export async function sendDm(recipientId: number, recipientPublicKeyB64: string, plaintext: string): Promise<void> {
|
||||
const keys = getCurrentKeys();
|
||||
@@ -27,11 +20,12 @@ export async function sendDm(recipientId: number, recipientPublicKeyB64: string,
|
||||
const wk = await importAesGcmKey(wkRaw);
|
||||
const encMsg = await aesGcmEncrypt(await importAesGcmKey(mk), new TextEncoder().encode(plaintext));
|
||||
const wrap = await aesGcmEncrypt(wk, mk);
|
||||
|
||||
await fetch(`${API_BASE_URL}/dm/send`, {
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(true),
|
||||
body: JSON.stringify({
|
||||
recipientId,
|
||||
recipientId: recipientId,
|
||||
iv: b64(encMsg.iv),
|
||||
ciphertext: b64(encMsg.ciphertext),
|
||||
salt: b64(wkSalt),
|
||||
@@ -56,19 +50,30 @@ export interface DmEnvelope {
|
||||
export async function fetchDm(since?: number): Promise<DmEnvelope[]> {
|
||||
const url = new URL(`${API_BASE_URL}/dm/fetch`);
|
||||
if (since) url.searchParams.set("since", String(since));
|
||||
const res = await fetch(url, { headers: getAuthHeaders(true) });
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.messages ?? [];
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: getAuthHeaders(true)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data: FetchDMResponse = await response.json();
|
||||
return data.messages ?? [];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function decryptDm(envelope: DmEnvelope, senderPublicKeyB64: string): Promise<string> {
|
||||
const keys = getCurrentKeys();
|
||||
if (!keys) throw new Error("Keys not initialized");
|
||||
|
||||
// Obtain the key
|
||||
const shared = ecdhSharedSecret(keys.privateKey, ub64(senderPublicKeyB64));
|
||||
const wkRaw = await deriveWrappingKey(shared, ub64(envelope.salt), new Uint8Array([1]));
|
||||
const wk = await importAesGcmKey(wkRaw);
|
||||
const mk = await aesGcmDecrypt(wk, ub64(envelope.iv2), ub64(envelope.wrappedMk));
|
||||
|
||||
// Decrypt
|
||||
const msg = await aesGcmDecrypt(await importAesGcmKey(mk), ub64(envelope.iv), ub64(envelope.ciphertext));
|
||||
return new TextDecoder().decode(msg);
|
||||
}
|
||||
@@ -95,54 +100,66 @@ async function loadUsers() {
|
||||
// WebSocket realtime send
|
||||
const keys = getCurrentKeys();
|
||||
if (!keys) return;
|
||||
|
||||
// Encryption key
|
||||
const mk = randomBytes(32);
|
||||
const wkSalt = randomBytes(16);
|
||||
const shared = ecdhSharedSecret(keys.privateKey, ub64(activeDm.publicKey));
|
||||
const wkRaw = await deriveWrappingKey(shared, wkSalt, new Uint8Array([1]));
|
||||
const wk = await importAesGcmKey(wkRaw);
|
||||
|
||||
// Encrypt the message
|
||||
const encMsg = await aesGcmEncrypt(await importAesGcmKey(mk), new TextEncoder().encode(text));
|
||||
const wrap = await aesGcmEncrypt(wk, mk);
|
||||
const payload: WebSocketMessage = {
|
||||
|
||||
const payload: SendDMRequest = {
|
||||
recipientId: activeDm.userId,
|
||||
iv: b64(encMsg.iv),
|
||||
ciphertext: b64(encMsg.ciphertext),
|
||||
salt: b64(wkSalt),
|
||||
iv2: b64(wrap.iv),
|
||||
wrappedMk: b64(wrap.ciphertext)
|
||||
}
|
||||
|
||||
request({
|
||||
type: "dmSend",
|
||||
credentials: { scheme: "Bearer", credentials: (await import("../auth/api")).authToken! },
|
||||
data: {
|
||||
recipientId: activeDm.userId,
|
||||
iv: b64(encMsg.iv),
|
||||
ciphertext: b64(encMsg.ciphertext),
|
||||
salt: b64(wkSalt),
|
||||
iv2: b64(wrap.iv),
|
||||
wrappedMk: b64(wrap.ciphertext)
|
||||
}
|
||||
};
|
||||
websocket.send(JSON.stringify(payload));
|
||||
credentials: {
|
||||
scheme: "Bearer",
|
||||
credentials: authToken!
|
||||
},
|
||||
data: payload
|
||||
});
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
// Load DM history for the active conversation
|
||||
if (!activeDm?.publicKey) return;
|
||||
const res = await fetch(`${API_BASE_URL}/dm/history/${activeDm.userId}`, { headers: getAuthHeaders(true) });
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const messages: DmEnvelope[] = data.messages || [];
|
||||
const container = document.getElementById("chat-messages")!;
|
||||
container.innerHTML = "";
|
||||
for (const env of messages) {
|
||||
try {
|
||||
const text = await decryptDm(env, activeDm.publicKey);
|
||||
const div = document.createElement("div");
|
||||
const isAuthor = env.senderId !== activeDm.userId;
|
||||
div.className = `message ${isAuthor ? "sent" : "received"}`;
|
||||
const inner = document.createElement("div");
|
||||
inner.className = "message-inner";
|
||||
const content = document.createElement("div");
|
||||
content.className = "message-content";
|
||||
content.textContent = text;
|
||||
inner.appendChild(content);
|
||||
div.appendChild(inner);
|
||||
container.appendChild(div);
|
||||
} catch {}
|
||||
const response = await fetch(`${API_BASE_URL}/dm/history/${activeDm.userId}`, {
|
||||
headers: getAuthHeaders(true)
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const messages: DmEnvelope[] = data.messages || [];
|
||||
const container = document.getElementById("chat-messages")!;
|
||||
container.innerHTML = "";
|
||||
for (const env of messages) {
|
||||
try {
|
||||
const text = await decryptDm(env, activeDm.publicKey);
|
||||
const div = document.createElement("div");
|
||||
const isAuthor = env.senderId !== activeDm.userId;
|
||||
div.className = `message ${isAuthor ? "sent" : "received"}`;
|
||||
const inner = document.createElement("div");
|
||||
inner.className = "message-inner";
|
||||
const content = document.createElement("div");
|
||||
content.className = "message-content";
|
||||
content.textContent = text;
|
||||
inner.appendChild(content);
|
||||
div.appendChild(inner);
|
||||
container.appendChild(div);
|
||||
} catch {}
|
||||
}
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -182,9 +199,10 @@ init();
|
||||
// realtime incoming DMs
|
||||
websocket.addEventListener("message", async (e) => {
|
||||
try {
|
||||
const msg = JSON.parse((e as MessageEvent).data);
|
||||
if (msg?.type === "dmNew" && activeDm && (msg.data.senderId === activeDm.userId || msg.data.recipientId === activeDm.userId)) {
|
||||
const msg: WebSocketMessage = JSON.parse((e as MessageEvent).data);
|
||||
if (msg.type === "dmNew" && activeDm && (msg.data.senderId === activeDm.userId || msg.data.recipientId === activeDm.userId)) {
|
||||
const plaintext = await decryptDm(msg.data, activeDm.publicKey!);
|
||||
|
||||
const container = document.getElementById("chat-messages")!;
|
||||
const div = document.createElement("div");
|
||||
const isAuthor = msg.data.senderId !== activeDm.userId;
|
||||
|
||||
Reference in New Issue
Block a user