fix(messages): gate on server key state, not just local unlock
The Messages UI trusted only the per-device IndexedDB key, so if the server's key record was gone (deleted, DB-restored, admin-reset) while a stale private key lingered in the browser, the app looked 'unlocked' but no one could be messaged and no setup was offered. Add useKeyState (server 'configured' AND local unlock): show setup when the server has no key (setup overwrites the stale local key), unlock when it has one this device hasn't opened, ready otherwise. ChatThread is now self-contained. Also fix the header title on the Messages and Notifications pages (was falling through to 'Channel manager').
This commit is contained in:
parent
8392037e5f
commit
963afa33a6
5 changed files with 42 additions and 29 deletions
|
|
@ -5,7 +5,7 @@ import { Send } from "lucide-react";
|
|||
import { api, type Message, type MessageUser } from "../lib/api";
|
||||
import { useLiveQuery } from "../lib/useLiveQuery";
|
||||
import { relativeTime } from "../lib/format";
|
||||
import { partnerPub } from "../lib/messaging";
|
||||
import { partnerPub, useKeyState } from "../lib/messaging";
|
||||
import * as e2ee from "../lib/e2ee";
|
||||
import KeyGate from "./KeyGate";
|
||||
|
||||
|
|
@ -13,24 +13,24 @@ const POLL_MS = 20000; // safety net; live updates arrive over the WebSocket
|
|||
|
||||
// The message list + composer for one conversation, shared by the full Messages page and the
|
||||
// floating dock window. Handles client-side decryption and encrypt-on-send; the surrounding
|
||||
// chrome (back button / minimize / close) is the caller's.
|
||||
// chrome (back button / minimize / close) is the caller's. Self-contained key gating: prompts
|
||||
// setup/unlock (server-truth) for a user conversation until messaging is ready.
|
||||
export default function ChatThread({
|
||||
meId,
|
||||
partnerId,
|
||||
isSystem,
|
||||
unlocked,
|
||||
}: {
|
||||
meId: number;
|
||||
partnerId: number;
|
||||
isSystem: boolean;
|
||||
unlocked: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const qc = useQueryClient();
|
||||
const [draft, setDraft] = useState("");
|
||||
const [plain, setPlain] = useState<Record<number, string>>({});
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
const needsKey = !isSystem && !unlocked;
|
||||
const ready = useKeyState() === "ready";
|
||||
const needsKey = !isSystem && !ready;
|
||||
|
||||
const q = useLiveQuery<{ partner: MessageUser; items: Message[] }>(
|
||||
["thread", partnerId],
|
||||
|
|
@ -40,7 +40,7 @@ export default function ChatThread({
|
|||
const items = q.data?.items ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
if (isSystem || !unlocked) return;
|
||||
if (isSystem || !ready) return;
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
const out: Record<number, string> = {};
|
||||
|
|
@ -59,7 +59,7 @@ export default function ChatThread({
|
|||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [items, unlocked, isSystem, partnerId, t]);
|
||||
}, [items, ready, isSystem, partnerId, t]);
|
||||
|
||||
// Opening / refreshing the thread marks incoming messages read, so clear the badges.
|
||||
useEffect(() => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue