From f8a43dbb79d929c75f945e00bf90f732214ba18f Mon Sep 17 00:00:00 2001 From: clawbot Date: Mon, 9 Feb 2026 12:05:48 -0800 Subject: [PATCH] Initial spec: HTTP-based IRC replacement --- README.md | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..57bb9d9 --- /dev/null +++ b/README.md @@ -0,0 +1,272 @@ +# 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