# chat A modern IRC-inspired chat server written in Go. Decouples session state from transport connections, enabling mobile-friendly persistent sessions over HTTP. ## Motivation IRC is in decline because session state is tied to the TCP connection. In a mobile-first world, that's a nonstarter. Not everyone wants to run a bouncer or pay for IRCCloud. This project builds a chat server that: - Holds session state server-side (message queues, presence, channel membership) - Delivers messages over HTTP (JSON-RPC style) - Supports multiple concurrent connections per user session - Provides IRC-like semantics: channels, nicks, topics, modes - Uses structured JSON messages with arbitrary extensibility ## Architecture ### Transport: HTTP only All client↔server and server↔server communication uses HTTP/1.1+ with JSON request/response bodies. No WebSockets, no raw TCP, no gRPC — just plain HTTP. - **Client polling**: Clients poll for new messages via `GET` with long-polling support (server holds the connection open until messages arrive or timeout) - **Client sending**: Clients send messages/commands via `POST` - **Server federation**: Servers exchange messages via HTTP to enable multi-server networks (like IRC server linking) ### Core Concepts #### Users - Identified by a unique user ID (UUID) - Authenticate via token (issued at registration or login) - Have a nick (changeable, unique per server at any point in time) - Maintain a persistent message queue on the server #### Sessions - A session represents an authenticated user's connection context - Session state is **server-held**, not connection-bound - Multiple devices can share a session (messages delivered to all) - Sessions persist across disconnects — messages queue until retrieved - Sessions expire after a configurable idle timeout (default 24h) #### Channels - Named with `#` prefix (e.g. `#general`) - Have a topic, mode flags, and member list - Messages to a channel are queued for all members - Channel history is stored server-side (configurable depth) - No eternal logging by default — history rotates #### Messages Every message is a structured JSON object: ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "ts": "2026-02-09T20:00:00.000Z", "from": "nick", "to": "#channel", "type": "message", "body": "Hello, world!", "meta": {} } ``` Fields: - `id` — Server-assigned UUID, globally unique - `ts` — Server-assigned timestamp (ISO 8601) - `from` — Sender nick - `to` — Destination: channel name (`#foo`) or nick (for DMs) - `type` — Message type: `message`, `action`, `notice`, `join`, `part`, `quit`, `topic`, `mode`, `nick`, `system` - `body` — Message content (UTF-8 text) - `meta` — Arbitrary extensible metadata (JSON object). Can carry: - Cryptographic signatures - Rich content hints (URLs, embeds) - Client-specific extensions - Reactions, edits, threading references ### API Endpoints All endpoints accept and return `application/json`. #### Authentication ``` POST /api/v1/register — Create account (nick, password) → token POST /api/v1/login — Authenticate → token POST /api/v1/logout — Invalidate token ``` #### Session & Messages ``` GET /api/v1/messages — Retrieve queued messages (long-poll supported) Query params: ?after=&timeout=30 POST /api/v1/messages — Send a message or command GET /api/v1/history — Retrieve channel/DM history Query params: ?target=#channel&before=&limit=50 ``` #### Channels ``` GET /api/v1/channels — List joined channels POST /api/v1/channels/join — Join a channel POST /api/v1/channels/part — Leave a channel GET /api/v1/channels/{name} — Channel info (topic, members, modes) POST /api/v1/channels/{name}/topic — Set channel topic ``` #### Users ``` GET /api/v1/users/me — Current user info POST /api/v1/users/nick — Change nick GET /api/v1/users/{nick} — User info (online status, idle time) ``` #### Server Info ``` GET /api/v1/server — Server info (name, version, MOTD, user count) GET /.well-known/healthcheck.json — Health check ``` ### Federation (Server-to-Server) Servers can link to form a network, similar to IRC server linking: ``` POST /api/v1/federation/link — Establish server link (mutual auth via shared key) POST /api/v1/federation/relay — Relay messages between linked servers GET /api/v1/federation/status — Link status ``` Federation uses the same HTTP+JSON transport. Messages are relayed between servers so users on different servers can share channels. ### Channel Modes Inspired by IRC but simplified: | Mode | Meaning | |------|---------| | `+i` | Invite-only | | `+m` | Moderated (only voiced users can send) | | `+s` | Secret (hidden from channel list) | | `+t` | Topic locked (only ops can change) | | `+n` | No external messages | User channel modes: `+o` (operator), `+v` (voice) ### Configuration Via environment variables (Viper), following gohttpserver conventions: | Variable | Default | Description | |----------|---------|-------------| | `PORT` | `8080` | Listen port | | `DBURL` | `""` | SQLite/Postgres connection string | | `DEBUG` | `false` | Debug mode | | `MAX_HISTORY` | `10000` | Max messages per channel history | | `SESSION_TIMEOUT` | `86400` | Session idle timeout (seconds) | | `MAX_MESSAGE_SIZE` | `4096` | Max message body size (bytes) | | `MOTD` | `""` | Message of the day | | `SERVER_NAME` | hostname | Server display name | | `FEDERATION_KEY` | `""` | Shared key for server linking | ### Storage SQLite by default (single-file, zero-config), with Postgres support for larger deployments. Tables: - `users` — accounts and auth tokens - `channels` — channel metadata and modes - `channel_members` — membership and user modes - `messages` — message history (rotated per `MAX_HISTORY`) - `message_queue` — per-user pending delivery queue - `server_links` — federation peer configuration ### Project Structure Following [gohttpserver CONVENTIONS.md](https://git.eeqj.de/sneak/gohttpserver/src/branch/main/CONVENTIONS.md): ``` chat/ ├── cmd/ │ └── chat/ │ └── main.go ├── internal/ │ ├── config/ │ │ └── config.go │ ├── database/ │ │ └── database.go │ ├── globals/ │ │ └── globals.go │ ├── handlers/ │ │ ├── handlers.go │ │ ├── auth.go │ │ ├── channels.go │ │ ├── federation.go │ │ ├── healthcheck.go │ │ ├── messages.go │ │ └── users.go │ ├── healthcheck/ │ │ └── healthcheck.go │ ├── logger/ │ │ └── logger.go │ ├── middleware/ │ │ └── middleware.go │ ├── models/ │ │ ├── channel.go │ │ ├── message.go │ │ └── user.go │ ├── queue/ │ │ └── queue.go │ └── server/ │ ├── server.go │ ├── http.go │ └── routes.go ├── go.mod ├── go.sum ├── Makefile ├── Dockerfile ├── CONVENTIONS.md → (copy from gohttpserver) └── README.md ``` ### Required Libraries Per gohttpserver conventions: | Purpose | Library | |---------|---------| | DI | `go.uber.org/fx` | | Router | `github.com/go-chi/chi` | | Logging | `log/slog` (stdlib) | | Config | `github.com/spf13/viper` | | Env | `github.com/joho/godotenv/autoload` | | CORS | `github.com/go-chi/cors` | | Metrics | `github.com/prometheus/client_golang` | | DB | `modernc.org/sqlite` + `database/sql` | ### Design Principles 1. **HTTP is the only transport** — no WebSockets, no raw TCP, no protocol negotiation. HTTP is universal, proxy-friendly, and works everywhere. 2. **Server holds state** — clients are stateless. Reconnect, switch devices, lose connectivity — your messages are waiting. 3. **Structured messages** — JSON with extensible metadata. Enables signatures, rich content, client extensions without protocol changes. 4. **Simple deployment** — single binary, SQLite default, zero mandatory external dependencies. 5. **No eternal logs** — history rotates. Chat should be ephemeral by default. 6. **Federation optional** — single server works standalone. Linking is opt-in. ## Status **Design phase.** This README is the spec. Implementation has not started. ## License MIT