# chat-app Private, end-to-end encrypted chat app for a small circle. Self-hosted Supabase backend, clients on iOS, Android, macOS, Windows, and Linux. Server is zero-knowledge: it only stores ciphertexts and metadata needed to route them. ## Milestone Roadmap 1. **Milestone 1 — Text Chat** (current): auth, identity keys, 1:1 encrypted messaging, local history, push. 2. **Milestone 2 — Voice Calls**: libsodium-secured WebRTC signaling via Supabase Realtime. 3. **Milestone 3 — Video Calls**: same stack, add video tracks + bandwidth handling. 4. **Milestone 4 — Desktop Polish**: feature parity with mobile, tray, notifications. 5. **Milestone 5 — Groups & Channels**: Discord-like group channels with shared ratchet keys. ## Repository Layout ``` apps/ mobile/ Expo + React Native (iOS / Android) desktop/ Tauri v2 + React + Vite + Tailwind (macOS / Windows / Linux) packages/ shared/ Business logic: Supabase client, libsodium crypto, auth, chat db-types/ Generated Supabase database types ui-web/ React web components shared by desktop (not by React Native) infra/ supabase/ Self-hosting docs, client env, SQL migrations .github/workflows/ CI + build placeholders ``` ## Tech Stack - **Mobile**: Expo SDK 52, Expo Router, expo-secure-store, expo-sqlite, expo-notifications, react-native-libsodium. - **Desktop**: Tauri v2, React 18, Vite, Tailwind, tauri-plugin-stronghold, tauri-plugin-sql, tauri-plugin-notification, Zustand. - **Backend**: Supabase self-hosted (Postgres + GoTrue + PostgREST + Realtime + Storage + Edge Functions) on Hetzner, Caddy reverse proxy. - **Crypto**: X25519 identity keys, XChaCha20-Poly1305 envelopes (libsodium / NaCl). - **Auth**: email magic-link, invite-only. ## Prerequisites - Node **22+** (see `.nvmrc`) - pnpm **9+** - Rust + Cargo (Tauri): https://rustup.rs - Docker + Docker Compose (for running Supabase locally or on the VPS) - Platform toolchain per target: - iOS: Xcode - Android: Android Studio + SDK - macOS/Linux/Windows Tauri: see `apps/desktop/README.md` ## Setup ```bash # 1. Node + pnpm (macOS) brew install pnpm corepack enable # 2. Clone + install git clone cd chat-app pnpm install # 3. Env cp infra/supabase/.env.example apps/mobile/.env cp infra/supabase/.env.example apps/desktop/.env # Fill in SUPABASE_URL and SUPABASE_ANON_KEY from your self-hosted stack. ``` See `infra/supabase/README.md` for spinning up the backend. ## Common Scripts (run from repo root) | Script | Purpose | |--------|---------| | `pnpm dev` | Start every workspace's dev task (Turborepo). | | `pnpm build` | Build every workspace. | | `pnpm lint` | ESLint across workspaces. | | `pnpm typecheck` | TypeScript project-wide type check. | | `pnpm test` | Vitest across workspaces. | | `pnpm format` | Prettier write. | | `pnpm format:check` | Prettier check. | | `pnpm mobile:dev` | `expo start` for the mobile app. | | `pnpm mobile:ios` | Native iOS run. | | `pnpm mobile:android` | Native Android run. | | `pnpm desktop:dev` | `tauri dev` for the desktop app. | | `pnpm desktop:build` | Platform-specific Tauri bundle. | | `pnpm db:types` | Regenerate `@chat-app/db-types` from the running Supabase. | ## Architecture Overview - **Shared-first**: Anything that can run in both React Native and the Tauri WebView lives in `packages/shared` and is imported via `@chat-app/shared/*`. Both hosts pass in adapters for platform-only concerns (secure storage, SQLite, libsodium backend). - **Zero-knowledge server**: Messages are encrypted client-side before insert. The server sees ciphertexts, a conversation id, a sender id, and a timestamp — nothing else. - **Key custody**: X25519 private keys live in platform secure stores only (Keychain/Keystore on mobile, Stronghold on desktop). Public keys live in the `profiles` table. - **Realtime**: Supabase Realtime delivers new ciphertext rows to subscribed clients. Push notifications are silent (data-only) — the client decrypts and composes the visible notification locally. ## Security Checklist - [ ] RLS enabled on every user-facing table - [ ] Invite-only enforced in SQL (invites table + policy) - [ ] No plaintext in push payloads - [ ] Service role key never shipped to a client - [ ] JWT secret rotated on first boot - [ ] TLS via Caddy at the edge