"use client"; import { useEffect, useRef, useState, useCallback } from "react"; import { useParams, useRouter } from "next/navigation"; import { ChevronRight, Send, User, Lock } from "lucide-react"; import { cn } from "@/lib/utils"; import { formatApiError } from "@/lib/hooks"; import { ErrorBanner } from "@/components/ui/ErrorBanner"; import { FullScreenLoader } from "@/components/ui/FullScreenLoader"; import { MessageView, MatchView, listMatches, listMessages, sendMessage, photoUrl, } from "@/lib/api"; import { ensureKeyPair, encryptMessage, decryptMessage } from "@/lib/e2ee"; const POLL_MS = 4000; const UNREADABLE = "🔒 این پیام رو نمی‌تونی روی این دستگاه بخونی"; // پیام رمزنگاری‌شده رو برای نمایش رمزگشایی می‌کنه؛ پیام قدیمیِ قبل از E2EE // همینجوری برگردونده می‌شه. function messageText( m: MessageView, partnerPublicKey: string | undefined, mySecretKey: Uint8Array | null ): string { if (m.cipher_text && m.nonce) { if (!mySecretKey || !partnerPublicKey) return UNREADABLE; return decryptMessage(m.cipher_text, m.nonce, partnerPublicKey, mySecretKey) ?? UNREADABLE; } return m.text ?? ""; } export default function ChatPage() { const params = useParams<{ id: string }>(); const router = useRouter(); const matchId = Number(params.id); const [match, setMatch] = useState(null); const [messages, setMessages] = useState(null); const [error, setError] = useState(null); const [draft, setDraft] = useState(""); const [sending, setSending] = useState(false); const [mySecretKey, setMySecretKey] = useState(null); const bottomRef = useRef(null); const pollRef = useRef | null>(null); const refreshMessages = useCallback(async () => { try { const msgs = await listMessages(matchId); setMessages(msgs); } catch (err) { setError(formatApiError(err)); } }, [matchId]); useEffect(() => { listMatches() .then((matches) => { const found = matches.find((m) => m.id === matchId); setMatch(found ?? null); }) .catch(() => {}); // eslint-disable-next-line react-hooks/set-state-in-effect refreshMessages(); ensureKeyPair() .then((kp) => setMySecretKey(kp.secretKey)) .catch(() => {}); pollRef.current = setInterval(refreshMessages, POLL_MS); return () => { if (pollRef.current) clearInterval(pollRef.current); }; }, [matchId, refreshMessages]); useEffect(() => { bottomRef.current?.scrollIntoView({ block: "end" }); }, [messages?.length]); const canEncrypt = !!mySecretKey && !!match?.partner_public_key; async function handleSend() { const text = draft.trim(); if (!text || sending || !mySecretKey || !match?.partner_public_key) return; setSending(true); setDraft(""); try { const { cipherText, nonce } = encryptMessage(text, match.partner_public_key, mySecretKey); const msg = await sendMessage(matchId, cipherText, nonce); setMessages((prev) => (prev ? [...prev, msg] : [msg])); } catch (err) { setError(formatApiError(err)); } finally { setSending(false); } } if (error && !messages) { return (
); } if (!messages) return ; return (

پیام‌ها با رمزنگاری سرتاسری محافظت می‌شن؛ هیچ‌کس جز شما دو نفر، حتی ادمین‌های دلدل، نمی‌تونه بخوندشون.

{messages.length === 0 ? (

هنوز پیامی نیست؛ اولین نفر باش که سلام می‌ده 👋

) : ( messages.map((m) => (
{messageText(m, match?.partner_public_key, mySecretKey)}
)) )}
{!canEncrypt && (

طرف مقابل هنوز رمزنگاری سرتاسری رو فعال نکرده؛ تا فعال نشه نمی‌تونی پیام بفرستی.

)}
setDraft(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSend()} placeholder="پیامت رو بنویس..." disabled={!canEncrypt} className="flex-1 h-11 rounded-(--radius-full) bg-(--color-bg-secondary) px-4 text-[14px] focus:outline-none focus:ring-2 focus:ring-(--color-brand-200) disabled:opacity-50" />
); }