Files
chat-server/ARCHITECTURE.md
T
2026-02-20 16:37:30 +03:00

4.6 KiB

Reliquary Architecture & Protocol Reference

1. Project Overview

Reliquary is a self-hosted, high-performance chat platform operating on the Reliquary Protocol.

  • Philosophy: Desktop-first, native performance, decentralized identity.
  • Routing: No server-to-server federation. The Client handles all routing.
  • Identity: Cryptographic keypairs (secp256k1). User ID: user@home.server.
  • Push Notifications: Explicitly out of scope for MVP. Desktop relies on background WebSockets; Android relies on Foreground Services.

2. Current System State

The following features are implemented or have a fixed specification.

✅ Core Features

  • Auth: JWT-based. Register/Login returns encrypted keypair.
  • Join Flow: Cryptographic signature verification.
  • REST API: Basic CRUD for Channels and Text-only Messages.
  • Gateway: WebSocket server for event fanout (Specification defined in Section 4).

💾 Database Models (ObjectBox)

  • User: userId, publicKey, passwordHash (home only).
  • Channel: id, name, createdAt.
  • Message: id, channelId, authorUserId, content, createdAt.

3. Active Implementation Target: Component & File System

⚠️ THIS SECTION DESCRIBES NEW FEATURES TO BE CODED ⚠️

We are migrating from plain text to Reliquary Components. This allows rich media, embeds, and collapsible UIs.

3.1 UX Flow: Staged Uploads (Discord-Style)

To ensure instant UI feedback and save server bandwidth:

  1. Stage (Client): User picks files. Client shows local previews. User reorders/adds spoilers. No network request yet.
  2. Commit (Client): User hits "Send". Client adds a "Dummy Message" to the UI with a progress bar.
  3. Upload (Network): Client POSTs files to /api/uploads.
  4. Publish (Network): Client gets FileIds, builds the componentsJson, and POSTs to /api/messages.
  5. Resolve (Network): Server broadcasts the new message via Gateway. Client swaps "Dummy" for "Real".

3.2 New/Modified Database Models

@Entity()
class Message {
  // ... existing fields ...
  
  // Search Index (Server concatenates text from all components)
  String content; 

  // Source of Truth (JSON List<Component>)
  String componentsJson;
}

@Entity()
class File {
  int id;
  int uploaderId;
  String path;       // /uploads/uuid.jpg
  String mimeType;
  int width;         // CRITICAL for layout
  int height;
  int sizeBytes;
  
  bool isAttached;   // False until linked to a Message
  DateTime createdAt;
}

3.3 Component JSON Schema

All items in componentsJson must follow this structure.

Base Wrapper:

{
  "type": "text | gallery | collapsible | embed",
  "modifiers":, // "spoiler" applies blur
  "fallback": "String for push notifs",
  "data": { ... }
}

Gallery Component (Images/Video):

{
  "type": "gallery",
  "modifiers": [],
  "fallback": "Image",
  "data": {
    "items":
  }
}

4. Gateway Protocol Specification (WebSockets)

The server implements a WebSocket Gateway at /gateway.

Connection & Auth

  • URL: ws://host:port/gateway
  • Auth: ?token=<jwt> (Query param) or Authorization: Bearer <jwt> (Header).
  • OpCode: Ready (Server -> Client on connect):
    { "op": "ready", "connectionId": "gw_1", "userId": "alice@home" }
    

Client -> Server Ops

Subscribe:

{ "op": "subscribe", "channelIds": [12, 13] }

Unsubscribe:

{ "op": "unsubscribe", "channelIds": [12] }

Heartbeat:

{ "op": "ping" }

Server -> Client Ops (Events)

Message Created:

{
  "op": "event",
  "event": "message.created",
  "channelId": 12,
  "message": {
    "id": 101,
    "content": "Legacy text or fallback",
    "componentsJson": "[...]", 
    "authorId": "alice@home",
    "createdAt": "..."
  }
}

(Also supports message.updated and message.deleted)


5. API Reference: Pagination & Uploads

GET /api/messages

Used for initial load and history backfilling.

  • Params:

    • channelId (required)
    • limit (default 50)
    • before (Message ID) -> Load older messages.
    • after (Message ID) -> Load newer messages (catch-up).
  • Response:

    {
      "messages": [],
      "hasMore": true,
      "nextBefore": 51, // Cursor for older
      "nextAfter": 100  // Cursor for newer
    }
    

POST /api/uploads (New)

  • Format: multipart/form-data
  • Response:
    {
      "id": 505,
      "url": "/uploads/505.png",
      "width": 1920,
      "height": 1080,
      "mimeType": "image/png"
    }