de431386ea
Messages: - Reply-to: hover action, composer chip with cancel, quote bubble inside the replying message with tap-to-jump + amber highlight ring - Search: header search button toggles in-conversation search bar with prev/next + match counter, auto-jump to active match - Forward: multi-select conversation picker. Attachments are now carried over: download + decrypt source, re-encrypt under each target conv-key, re-upload with fresh per-attachment keys, insert new attachment rows Conversations: - Archive + mute per member. New migration 20260420000001 adds `archived` + `muted_until` on conversation_members. Shared helpers: setConversationArchived / setConversationMutedUntil / isConversationMuted - ChatsPage: archive toggle in header with unread badge for archived bucket, split active/archived lists, muted indicator (BellOff icon, dimmed unread badge) - ConversationRowMenu via createPortal (escapes sidebar overflow clip), forwardRef-based MenuItem so submenu positioning refs survive React 18 - ConversationsContext: suppresses notification sound + OS notif when target conversation is muted - Refresh on `profiles UPDATE` realtime so peer avatar / displayName changes flow to conversation.members without manual refresh Resilience: - ErrorBoundary (Discord-style): centred spinner + escalating copy, no manual reload button. Backoff retry schedule [2s, 4s, 8s, 15s, 30s]. Uses a keyed Fragment (not a div wrapper) so flex h-full chains survive - App wrapped root + per-route RouteBoundary, conversation-level boundary - AuthContext: flip `ready` immediately on cached session read; validate getUser in background so a stalled/offline Supabase doesn't freeze the app on the loading spinner Crypto: - Swap libsodium-wrappers -> libsodium-wrappers-sumo (compact build was missing crypto_pwhash so Argon2id vault KDF threw, falling back to plaintext localStorage on every launch) - Shim d.ts for sumo types (sumo is API superset, no official types ship) - vite optimizeDeps includes sumo with the "require" condition - secureFileStore: exists(dir) check before mkdir; surface genuine permission errors instead of silent catch Tauri: - fs scope adds `$APPLOCALDATA` direct entry (not just /**) so the app data directory itself can be mkdir'd on first launch Chat layout: - Skip call_event messages when computing avatar run boundaries so a regular bubble followed by a call event from the same sender still shows its avatar
30 lines
1.4 KiB
TypeScript
30 lines
1.4 KiB
TypeScript
import type { CryptoBackend } from '@chat-app/shared/crypto';
|
|
import _sodium from 'libsodium-wrappers-sumo';
|
|
|
|
// Build the libsodium-backed CryptoBackend. Awaits the WASM ready-gate once,
|
|
// then returns a synchronous implementation of the CryptoBackend contract.
|
|
export async function createLibsodiumBackend(): Promise<CryptoBackend> {
|
|
await _sodium.ready;
|
|
const s = _sodium;
|
|
|
|
return {
|
|
name: 'libsodium-wrappers',
|
|
nonceLength: s.crypto_box_NONCEBYTES,
|
|
publicKeyLength: s.crypto_box_PUBLICKEYBYTES,
|
|
privateKeyLength: s.crypto_box_SECRETKEYBYTES,
|
|
secretboxKeyLength: s.crypto_secretbox_KEYBYTES,
|
|
secretboxNonceLength: s.crypto_secretbox_NONCEBYTES,
|
|
randomBytes: (n: number) => s.randombytes_buf(n),
|
|
generateKeyPair: () => {
|
|
const kp = s.crypto_box_keypair();
|
|
return { publicKey: kp.publicKey, privateKey: kp.privateKey };
|
|
},
|
|
box: (plaintext, nonce, recipientPublicKey, senderPrivateKey) =>
|
|
s.crypto_box_easy(plaintext, nonce, recipientPublicKey, senderPrivateKey),
|
|
boxOpen: (ciphertext, nonce, senderPublicKey, recipientPrivateKey) =>
|
|
s.crypto_box_open_easy(ciphertext, nonce, senderPublicKey, recipientPrivateKey),
|
|
secretbox: (plaintext, nonce, key) => s.crypto_secretbox_easy(plaintext, nonce, key),
|
|
secretboxOpen: (ciphertext, nonce, key) => s.crypto_secretbox_open_easy(ciphertext, nonce, key),
|
|
};
|
|
}
|