Add embedded web chat client (closes #7) #8

Merged
clawbot merged 22 commits from feature/web-client into main 2026-02-11 03:02:42 +01:00
Showing only changes of commit 6483670dc7 - Show all commits

View File

@@ -3,6 +3,11 @@
A modern IRC-inspired chat server written in Go. Decouples session state from A modern IRC-inspired chat server written in Go. Decouples session state from
transport connections, enabling mobile-friendly persistent sessions over HTTP. transport connections, enabling mobile-friendly persistent sessions over HTTP.
The **HTTP API is the primary interface**. It's designed to be simple enough
that writing a terminal IRC-style client against it is straightforward — just
`curl` and `jq` get you surprisingly far. The server also ships an embedded
web client as a convenience/reference implementation, but the API comes first.
## Motivation ## Motivation
IRC is in decline because session state is tied to the TCP connection. In a IRC is in decline because session state is tied to the TCP connection. In a
@@ -12,7 +17,7 @@ or pay for IRCCloud.
This project builds a chat server that: This project builds a chat server that:
- Holds session state server-side (message queues, presence, channel membership) - Holds session state server-side (message queues, presence, channel membership)
- Delivers messages over HTTP (JSON-RPC style) - Exposes a minimal, clean HTTP+JSON API — easy to build clients against
- Supports multiple concurrent connections per user session - Supports multiple concurrent connections per user session
- Provides IRC-like semantics: channels, nicks, topics, modes - Provides IRC-like semantics: channels, nicks, topics, modes
- Uses structured JSON messages with arbitrary extensibility - Uses structured JSON messages with arbitrary extensibility
@@ -24,12 +29,15 @@ This project builds a chat server that:
All client↔server and server↔server communication uses HTTP/1.1+ with JSON 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. 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 - **Client polling**: Clients long-poll `GET /api/v1/messages` — server holds
support (server holds the connection open until messages arrive or timeout) the connection until messages arrive or timeout. One endpoint for everything.
- **Client sending**: Clients send messages/commands via `POST` - **Client sending**: `POST /api/v1/messages` with a `to` field. That's it.
- **Server federation**: Servers exchange messages via HTTP to enable multi-server - **Server federation**: Servers exchange messages via HTTP to enable multi-server
networks (like IRC server linking) networks (like IRC server linking)
The entire read/write loop for a client is two endpoints. Everything else is
channel management and history.
### Core Concepts ### Core Concepts
#### Users #### Users
@@ -90,9 +98,37 @@ Fields:
All endpoints accept and return `application/json`. Authenticated endpoints All endpoints accept and return `application/json`. Authenticated endpoints
require `Authorization: Bearer <token>` header. require `Authorization: Bearer <token>` header.
The API follows an IRC-inspired design: one unified message stream for all The API is the primary interface — designed for IRC-style clients. The entire
message types (channel messages, DMs, server notices), with a simple `to` field client loop is:
for addressing.
1. `POST /api/v1/register` — get a token
2. `GET /api/v1/state` — see who you are and what channels you're in
3. `GET /api/v1/messages?after=0` — long-poll for all messages (channel, DM, system)
4. `POST /api/v1/messages` — send to `"#channel"` or `"nick"`
That's the core. Everything else (join, part, history, members) is ancillary.
#### Quick example (curl)
```bash
# Register
TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/register \
-d '{"nick":"alice"}' | jq -r .token)
# Join a channel
curl -s -X POST http://localhost:8080/api/v1/channels/join \
-H "Authorization: Bearer $TOKEN" \
-d '{"channel":"#general"}'
# Send a message
curl -s -X POST http://localhost:8080/api/v1/messages \
-H "Authorization: Bearer $TOKEN" \
-d '{"to":"#general","content":"hello world"}'
# Poll for messages (long-poll)
curl -s http://localhost:8080/api/v1/messages?after=0 \
-H "Authorization: Bearer $TOKEN"
```
#### Registration #### Registration
@@ -259,18 +295,29 @@ Per gohttpserver conventions:
| Metrics | `github.com/prometheus/client_golang` | | Metrics | `github.com/prometheus/client_golang` |
| DB | `modernc.org/sqlite` + `database/sql` | | DB | `modernc.org/sqlite` + `database/sql` |
### Web Client
The server embeds a single-page web client (Preact) served at `/`. This is a
**convenience/reference implementation** — not the primary interface. It
demonstrates the API and provides a quick way to test the server in a browser.
The primary intended clients are IRC-style terminal applications and bots
talking directly to the HTTP API.
### Design Principles ### Design Principles
1. **HTTP is the only transport** — no WebSockets, no raw TCP, no protocol 1. **API-first** — the HTTP API is the product. Clients are thin. If you can't
build a working IRC-style TUI client in an afternoon, the API is too complex.
2. **HTTP is the only transport** — no WebSockets, no raw TCP, no protocol
negotiation. HTTP is universal, proxy-friendly, and works everywhere. negotiation. HTTP is universal, proxy-friendly, and works everywhere.
2. **Server holds state** — clients are stateless. Reconnect, switch devices, 3. **Server holds state** — clients are stateless. Reconnect, switch devices,
lose connectivity — your messages are waiting. lose connectivity — your messages are waiting.
3. **Structured messages** — JSON with extensible metadata. Enables signatures, 4. **Structured messages** — JSON with extensible metadata. Enables signatures,
rich content, client extensions without protocol changes. rich content, client extensions without protocol changes.
4. **Simple deployment** — single binary, SQLite default, zero mandatory 5. **Simple deployment** — single binary, SQLite default, zero mandatory
external dependencies. external dependencies.
5. **No eternal logs** — history rotates. Chat should be ephemeral by default. 6. **No eternal logs** — history rotates. Chat should be ephemeral by default.
6. **Federation optional** — single server works standalone. Linking is opt-in. 7. **Federation optional** — single server works standalone. Linking is opt-in.
## Status ## Status