Files
2026-04-18 23:11:35 +02:00

102 lines
4.2 KiB
Markdown

# 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 <this repo>
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