diff --git a/docs/brain/06-Specs/CHATSERVER.md b/docs/brain/06-Specs/CHATSERVER.md new file mode 100644 index 00000000..e166653f --- /dev/null +++ b/docs/brain/06-Specs/CHATSERVER.md @@ -0,0 +1,230 @@ +# ChatServer — Self-Hosted Windows Chat Platform + +Native Windows desktop client + self-hosted server. +Two executables: `chatserver.exe` (server) and +`OwnCord.exe` (Tauri v2 client). Server operator runs +the server, friends install the client. + +## Tech Stack + +### Server (`chatserver.exe`) + +- **Go** — Single exe, no dependencies. Embeds admin web UI via `go embed`. +- **SQLite** — Single `.db` file. WAL mode. Zero config. +- **LiveKit** — Voice/video media server. Runs as a companion + process alongside `chatserver.exe`. Token-based auth. +- **Admin panel** — Web-based, served at `/admin`. + Browser access, not part of the client. + +### Client (`OwnCord.exe`) + +**Tauri v2** (Rust backend + TypeScript/HTML/CSS frontend). +See LANGUAGE-REVIEW.md for the evaluation that led to this +choice, and CLIENT-ARCHITECTURE.md for the full design. + +- Tauri v2 desktop app using system WebView2 (NOT Electron) +- ~10-15 MB install size, ~30-50 MB RAM idle +- TypeScript frontend with CSS from HTML mockups +- WebSocket client for real-time chat (browser `WebSocket` API) +- LiveKit for voice/video (LiveKit JS SDK in webview) +- Global keyboard hooks via `tauri-plugin-global-shortcut` +- System tray via Tauri's built-in tray support +- Windows toast notifications via `tauri-plugin-notification` +- Windows Credential Manager via `windows-rs` Rust crate +- NSIS installer via Tauri bundler + +## Architecture + +```text +SERVER (chatserver.exe) — runs on the host machine +├── REST API (Go net/http) +├── WebSocket Hub (real-time messages, presence, typing) +├── LiveKit Companion Process (voice/video media) +├── SQLite Database (data/chatserver.db) +├── File Storage (data/uploads/) +├── Admin Web UI (embedded, browser-based, /admin) +└── config.yaml + +CLIENT (OwnCord.exe) — installed by each friend +├── Native Windows UI +├── WebSocket Client (chat connection) +├── LiveKit Client (voice/video via livekitSession.ts) +├── Audio Engine (device management, noise suppression) +├── Local Settings (connection profiles, keybinds, audio config) +└── System Tray Integration +``` + +### How It Works + +1. Server operator runs `chatserver.exe` on their PC/home server +2. Friends download and install `OwnCord.exe` +3. Client connects to the server via IP/domain + port +4. All chat, voice, video, and file transfers go through the server +5. Admin manages the server through a browser at `https://server-ip:port/admin` + +--- + +## Phase 1: Protocol & Server Core (2–3 weeks) + +- [ ] Define client-server protocol over WebSocket + (JSON messages with type/payload structure) +- [ ] Message types: auth, chat, typing, presence, + channel_update, voice_signal, file_transfer +- [ ] Server: Go project with `go embed` for admin + panel static files only +- [ ] SQLite setup with migrations on startup (users, + channels, messages, sessions, roles, invites) +- [ ] config.yaml generation on first run (port, name, + max upload size, voice quality, TLS mode) +- [ ] Server systray icon (getlantern/systray) — minimize to tray, status + indicator, open admin panel, quit +- [ ] Windows Firewall handling on first launch +- [ ] Optional: register as Windows Service for headless operation + +## Phase 2: Auth & Security (2–3 weeks) + +- [ ] Invite-only registration — server generates + invite codes, client has "Redeem Invite" flow +- [ ] bcrypt (cost 12+) passwords, server-side session tokens (256-bit random) +- [ ] Client stores auth token securely via Windows Credential Manager / DPAPI +- [ ] Login rate limiting: 5 attempts/min/IP, lockout after 10 failures +- [ ] Optional TOTP 2FA (`pquerna/otp`) — QR code + during setup, prompts on login +- [ ] Roles: Owner, Admin, Moderator, Member + custom roles with bitfield permissions +- [ ] Per-channel permission overrides, enforced server-side on every action +- [ ] TLS modes: self-signed (default), Let's Encrypt, + manual cert, off (Tailscale) +- [ ] Client: certificate pinning or trust-on-first-use (TOFU) for self-signed certs + +## Phase 3: Client App — Core UI (3–4 weeks) + +- [ ] Connection dialog: server address, port, login/register, invite code entry +- [ ] Save server profiles (connect to multiple + servers like TeamSpeak) +- [ ] Main window layout: server list → channel list → message area → member list +- [ ] Channel tree view with categories, text channels, voice channels +- [ ] Message rendering: markdown, code blocks, timestamps, avatars, replies, reactions +- [ ] Message input: multi-line, markdown preview, emoji picker, file drag-and-drop +- [ ] Unread indicators, @mention badges per channel +- [ ] System tray: minimize to tray, notification popups, badge count +- [ ] Keyboard shortcuts: Ctrl+K quick switcher, + Escape to close panels, customizable PTT key +- [ ] Settings: account, appearance (light/dark), + notifications, audio devices, keybinds + +## Phase 4: Real-Time Chat Features (2–3 weeks) + +- [ ] WebSocket client with auto-reconnect, exponential + backoff, message replay on reconnect +- [ ] Send/receive messages in real-time, append to scrollback +- [ ] Message history: paginated from server on channel switch, scroll-to-load-more +- [ ] Threads, replies (inline preview), reactions (emoji), edit, delete +- [ ] Typing indicators ("X is typing..." below input) +- [ ] Online/offline/idle/DnD presence with status icons in member list +- [ ] File uploads: drag-and-drop or clipboard paste, + progress bar, inline image previews +- [ ] Client-side file validation before upload (size check, warn on large files) +- [ ] Search: query server FTS5 endpoint, display results with jump-to-message +- [ ] Windows toast notifications with action buttons (reply, mark read) +- [ ] Notification sounds (configurable, per-channel mute/override) + +## Phase 5: Voice & Video (3–5 weeks) + +- [ ] LiveKit integration in native client for voice/video + (client connects to LiveKit directly using token from server) +- [ ] Audio device selection: input/output dropdowns in settings, live preview +- [ ] Voice channels: click to join/leave, show connected users with speaking indicators +- [ ] Voice controls: mute (button + keybind), deafen, per-user volume sliders +- [ ] Push-to-talk: configurable global hotkey that works in fullscreen games +- [ ] Voice activity detection with configurable sensitivity +- [ ] Noise suppression (RNNoise or equivalent, bundled with client) +- [ ] Server-side: LiveKit companion process with token-based auth + and webhook sync for voice state updates +- [ ] Voice quality: low (32kbps) / medium (64kbps) / high (128kbps Opus) +- [ ] Screen sharing via LiveKit screen share track +- [ ] Video calls: camera capture, displayed in voice channel panel + +## Phase 6: Admin Panel — Web-Based (1–2 weeks) + +- [ ] Served by server at `/admin`, browser-only access +- [ ] Auth: admin credentials, session-based +- [ ] Dashboard: connected users, message count, disk usage, CPU/RAM, uptime +- [ ] User management: list all, edit roles, ban/unban, reset password, force disconnect +- [ ] Channel management: create, rename, reorder, set permissions, archive +- [ ] Invite management: generate, view active, set expiry/use limit, revoke +- [ ] Server settings: name, icon, MOTD, max upload size, voice quality, TLS config +- [ ] Moderation: kick, ban, temp ban, slow mode, mute, word filter, audit log +- [ ] Backup: trigger manual backup, configure + schedule, view/restore from admin panel +- [ ] Built with simple HTML/CSS/JS embedded in the server binary + +## Phase 7: Distribution & Updates (1–2 weeks) + +- [ ] **Server:** GitHub Actions builds + `chatserver.exe` (amd64), SHA256, GitHub Release +- [ ] **Client:** Tauri bundler (NSIS) installer — + Program Files, Start Menu, auto-start, protocol + handler for `chatserver://` invite links +- [ ] Client auto-update: check GitHub releases on + launch, prompt to download + install +- [ ] Server update: admin panel shows available update, one-click download + restart +- [ ] Docs: Quick Start, Port Forwarding, Tailscale, + Client install guide +- [ ] Security hardening checklist for server operators +- [ ] SECURITY.md, README.md, CONTRIBUTING.md + +--- + +## Windows-Specific Details + +### Client (Tauri v2) + +- **Installer:** Tauri bundler (NSIS, ~10-15 MB). + Registers `chatserver://` protocol handler. +- **Auto-start:** Registry key + `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`. +- **Credentials:** Auth tokens stored in Windows + Credential Manager via `windows-rs` Rust crate. +- **Push-to-talk:** Global hotkey via + `tauri-plugin-global-shortcut`. +- **Audio:** LiveKit SDK via `livekitSession.ts`. +- **Screen capture:** LiveKit screen share track. +- **Notifications:** `tauri-plugin-notification` + (Windows toast). +- **Tray:** Tauri built-in system tray with badge. +- See CLIENT-ARCHITECTURE.md for full design. + +### Server + +- **Firewall:** Prompt on first run. Installer can pre-register firewall rule. +- **SmartScreen:** Unsigned exe shows warning. Code signing cert resolves this. +- **Data path:** `data/` next to exe. Installer version uses `%APPDATA%/ChatServer/`. +- **Logs:** `data/logs/` with daily rotation, viewable from admin panel. +- **Service mode:** `chatserver.exe --service install` to register as Windows Service. + +## Security Priorities + +**Critical:** Invite-only registration, bcrypt auth, +TLS (self-signed minimum), file upload validation +(magic bytes, block executables), input sanitization +server-side, credential storage via DPAPI, backups. + +**High:** Rate limiting, TOTP 2FA, role permissions, +WebSocket auth, LiveKit token auth, cert pinning/TOFU, +update integrity (SHA256). + +## Server Libraries (Go) + +| Purpose | Library | +| --- | --- | +| HTTP/routing | `net/http` + `chi` | +| WebSocket | `nhooyr.io/websocket` | +| LiveKit | `livekit/server-sdk-go` (token generation, webhook validation) | +| SQLite | `modernc.org/sqlite` (pure Go) | +| Auth | `golang.org/x/crypto/bcrypt` | +| TOTP | `pquerna/otp` | +| Sanitization | `bluemonday` | +| TLS | `golang.org/x/crypto/acme/autocert` | +| Systray | `getlantern/systray` | +| Config | `koanf` | +| Logging | `log/slog` | diff --git a/docs/brain/06-Specs/PROTOCOL.md b/docs/brain/06-Specs/PROTOCOL.md new file mode 100644 index 00000000..c25b0ec3 --- /dev/null +++ b/docs/brain/06-Specs/PROTOCOL.md @@ -0,0 +1,475 @@ +# WebSocket Protocol Spec + +All client-server communication (except file uploads and +admin panel) happens over a single WebSocket connection. +Messages are JSON with a `type` and `payload`. + +## Message Format + +```json +{ + "type": "message_type", + "id": "unique-request-id", + "payload": { } +} +``` + +- `type` — string, required. Determines how payload is interpreted. +- `id` — string, optional. Client-generated UUID for request/response correlation. +- `payload` — object, required. Contents vary by type. + +Server responses to client requests include the same `id` for correlation. + +--- + +## Authentication + +### Client → Server + +```json +{ "type": "auth", "payload": { "token": "session-token-here" } } +``` + +### Server → Client (success) + +```json +{ + "type": "auth_ok", + "payload": { + "user": { + "id": 1, "username": "alex", + "avatar": "uuid.png", "role": "admin" + }, + "server_name": "My Server", + "motd": "Welcome!" + } +} +``` + +### Server → Client (failure) + +```json +{ "type": "auth_error", "payload": { "message": "Invalid or expired token" } } +``` + +Connection is closed by server after auth_error. + +--- + +## Chat Messages + +### Send Message (Client → Server) + +```json +{ + "type": "chat_send", + "id": "req-uuid", + "payload": { + "channel_id": 5, + "content": "Hello everyone!", + "reply_to": null, + "attachments": ["upload-uuid-1"] + } +} +``` + +### Message Broadcast (Server → Client) + +```json +{ + "type": "chat_message", + "payload": { + "id": 1042, "channel_id": 5, + "user": { + "id": 1, "username": "alex", + "avatar": "uuid.png" + }, + "content": "Hello everyone!", + "reply_to": null, + "attachments": [{ + "id": "upload-uuid-1", + "filename": "photo.jpg", + "size": 204800, + "mime": "image/jpeg", + "url": "/files/upload-uuid-1" + }], + "timestamp": "2026-03-14T10:30:00Z" + } +} +``` + +### Send Ack (Server → Client) + +```json +{ + "type": "chat_send_ok", + "id": "req-uuid", + "payload": { + "message_id": 1042, + "timestamp": "2026-03-14T10:30:00Z" + } +} +``` + +### Edit Message (Client → Server) + +```json +{ + "type": "chat_edit", + "id": "req-uuid", + "payload": { + "message_id": 1042, + "content": "Hello everyone! (edited)" + } +} +``` + +### Edit Broadcast (Server → Client) + +```json +{ + "type": "chat_edited", + "payload": { + "message_id": 1042, + "channel_id": 5, + "content": "Hello everyone! (edited)", + "edited_at": "2026-03-14T10:31:00Z" + } +} +``` + +### Delete Message (Client → Server) + +```json +{ "type": "chat_delete", "id": "req-uuid", "payload": { "message_id": 1042 } } +``` + +### Delete Broadcast (Server → Client) + +```json +{ "type": "chat_deleted", "payload": { "message_id": 1042, "channel_id": 5 } } +``` + +### Reaction Add/Remove (Client → Server) + +```json +{ "type": "reaction_add", "payload": { "message_id": 1042, "emoji": "👍" } } +{ "type": "reaction_remove", "payload": { "message_id": 1042, "emoji": "👍" } } +``` + +### Reaction Broadcast (Server → Client) + +```json +{ + "type": "reaction_update", + "payload": { + "message_id": 1042, + "channel_id": 5, + "emoji": "👍", + "user_id": 1, + "action": "add" + } +} +``` + +--- + +## Typing Indicators + +### Client → Server (throttle to 1 per 3 seconds) + +```json +{ "type": "typing_start", "payload": { "channel_id": 5 } } +``` + +### Server → Client (broadcast to channel members) + +```json +{ + "type": "typing", + "payload": { + "channel_id": 5, + "user_id": 1, + "username": "alex" + } +} +``` + +Client-side: show indicator for 5 seconds, reset on new typing event from same user. + +--- + +## Presence + +### Presence Client → Server + +```json +{ "type": "presence_update", "payload": { "status": "online" } } +``` + +Status values: `online`, `idle`, `dnd`, `offline` + +### Presence Server → Client (broadcast) + +```json +{ "type": "presence", "payload": { "user_id": 1, "status": "online" } } +``` + +Server auto-sets `idle` after 10 minutes of no WebSocket activity. + +--- + +## Channel Updates + +### Server → Client (on channel created/edited/deleted/reordered) + +```json +{ + "type": "channel_create", + "payload": { + "id": 8, "name": "gaming", + "type": "text", + "category": "Hangout", "position": 3 + } +} +{ + "type": "channel_update", + "payload": { + "id": 8, "name": "gaming-talk", + "position": 4 + } +} +{ "type": "channel_delete", "payload": { "id": 8 } } +``` + +Channel types: `text`, `voice`, `announcement` + +--- + +## Voice Signaling + +### Join Voice Channel (Client → Server) + +```json +{ "type": "voice_join", "payload": { "channel_id": 10 } } +``` + +### Server → Client (voice state updates, broadcast to channel) + +```json +{ + "type": "voice_state", + "payload": { + "channel_id": 10, "user_id": 1, + "username": "alex", + "muted": false, "deafened": false, + "speaking": false, + "camera": false, "screenshare": false + } +} +``` + +### Voice User Left (Server → Client) + +```json +{ "type": "voice_leave", "payload": { "channel_id": 10, "user_id": 1 } } +``` + +### voice_token (Server → Client) + +Sent after a successful `voice_join`. Contains the LiveKit access token +and server URL for the client to connect directly to LiveKit. + +```json +{ + "type": "voice_token", + "payload": { + "channel_id": 10, + "token": "eyJhbGciOi...", + "url": "ws://localhost:7880" + } +} +``` + +- `channel_id` (number) — the voice channel joined +- `token` (string) — LiveKit JWT access token +- `url` (string) — LiveKit WebSocket URL (e.g. `ws://localhost:7880`) + +### Voice Config (Server → Client, sent after voice_join acceptance) + +```json +{ + "type": "voice_config", + "payload": { + "channel_id": 10, "quality": "medium", "bitrate": 64000, + "max_users": 50 + } +} +``` + +Client uses `bitrate` to configure the Opus encoder. Other fields are +informational for UI. (`threshold_mode` and `top_speakers` fields have +been removed — LiveKit handles audio mixing and speaker selection +internally.) + +### Voice Control (Client → Server) + +```json +{ "type": "voice_mute", "payload": { "muted": true } } +{ "type": "voice_deafen", "payload": { "deafened": true } } +``` + +### Voice Camera / Screenshare (Client → Server) + +```json +{ "type": "voice_camera", "payload": { "enabled": true } } +{ "type": "voice_screenshare", "payload": { "enabled": true } } +``` + +Requires `USE_VIDEO` (bit 11) or `SHARE_SCREEN` (bit 12) permission. +Rate limit: 2/sec per user. + +**Note:** Active speaker detection (`voice_speakers`) is no longer a +server→client WebSocket message. Speaker detection is handled client-side +via LiveKit SDK events (`ParticipantEvent.IsSpeakingChanged`). + +--- + +## Member Updates + +### Server → Client + +```json +{ + "type": "member_join", + "payload": { + "user": { + "id": 5, "username": "newuser", + "avatar": null, "role": "member" + } + } +} +{ "type": "member_leave", "payload": { "user_id": 5 } } +{ "type": "member_update", "payload": { "user_id": 5, "role": "moderator" } } +{ "type": "member_ban", "payload": { "user_id": 5 } } +``` + +--- + +## Server Restart + +### Restart Server → Client + +```json +{ + "type": "server_restart", + "payload": { + "reason": "update", + "delay_seconds": 5 + } +} +``` + +- `reason` (string): Why the server is restarting. Currently only `"update"`. +- `delay_seconds` (integer): How many seconds until the server shuts down. + +Client behavior: Display a banner ("Server restarting..."), +then auto-reconnect after the delay expires. + +--- + +## Initial State (sent after auth_ok) + +### Ready Server → Client + +```json +{ + "type": "ready", + "payload": { + "channels": [ + { + "id": 1, "name": "general", + "type": "text", "category": "Main", + "position": 0, "unread_count": 3, + "last_message_id": 1040 + }, + { + "id": 10, "name": "voice-chat", + "type": "voice", "category": "Main", + "position": 1 + } + ], + "members": [ + { + "id": 1, "username": "alex", + "avatar": "uuid.png", + "role": "admin", "status": "online" + }, + { + "id": 2, "username": "jordan", + "avatar": null, + "role": "member", "status": "idle" + } + ], + "voice_states": [ + { "channel_id": 10, "user_id": 2, "muted": false, "deafened": false } + ], + "roles": [ + { + "id": 1, "name": "Owner", + "color": "#E74C3C", + "permissions": 2147483647 + }, + { + "id": 2, "name": "Admin", + "color": "#F39C12", + "permissions": 1073741823 + }, + { "id": 3, "name": "Member", "color": null, "permissions": 1049601 } + ] + } +} +``` + +--- + +## Message History (REST, not WebSocket) + +Fetched via REST API, not WebSocket, to keep the WS connection lean. + +```text +GET /api/channels/{id}/messages?before={msg_id}&limit=50 +``` + +--- + +## Error Format + +Any request that fails returns: + +```json +{ + "type": "error", + "id": "original-req-uuid", + "payload": { + "code": "FORBIDDEN", + "message": "No permission to post here" + } +} +``` + +Error codes: `FORBIDDEN`, `NOT_FOUND`, `RATE_LIMITED`, `INVALID_INPUT`, +`SERVER_ERROR`, `CHANNEL_FULL`, `VOICE_ERROR` + +--- + +## Rate Limits + +- Chat messages: 10/sec per user +- Typing events: 1/3sec per user per channel +- Presence updates: 1/10sec per user +- Reactions: 5/sec per user +- Voice signaling: 20/sec per user +- Voice camera/screenshare: 2/sec per user + +Server sends `rate_limited` error with `retry_after` in seconds.