# 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
