Server Architecture

Folder Structure

server/
  room/           P2P room domain
  friends/        Friends and DM domain
  calls/          Voice/video call domain
  profile/        Local user profile domain
  identity/       Cryptographic identity domain
  messages/       Chat message domain

Each domain folder follows the same pattern:

File

Role

store.ts

Read and write data to disk

utils.ts

Pure utility functions

handler.ts

WebSocket message handlers

events.ts

P2P event handlers

watcher.ts

Live P2P subscriptions (room only)

tracker.ts

In-memory runtime state (calls only)

Domain Responsibilities

room/

Everything about P2P rooms - persisting joined rooms, watching for live updates, querying room history for ownership and moderation state, and handling all room-related WebSocket messages (create, join, leave, admin operations).

friends/

The friends system - persisting the friends list, pure lookup utilities, handling friend request/accept/remove WebSocket messages, and handling incoming P2P friend events (joining DM rooms on accept).

calls/

Voice/video calls - tracking which calls are currently active in memory, and relaying call signaling WebSocket messages (start, join, signal relay, end) through the room log. PeerJS runs in the browser; the server only forwards the opaque call-signal payloads, so it needs no PeerServer broker.

profile/

The local user’s display data - persisting name, avatar, and presence status to disk, utility functions for normalising presence and generating anonymous usernames, and handling profile update WebSocket messages.

identity/

Cryptographic identity operations - handling WebSocket messages for seed phrase backup, seed phrase import, and local database reset. The core identity logic lives in lib/identity.ts;

messages/

Chat message operations - handling all message-related WebSocket messages (send, edit, react, pin, file upload/download, channel management, custom emoji).

Server-Level Files

File

Role

server.ts

Entry point

ws-dispatch.ts

Routes incoming WebSocket messages to the correct handler

context.ts

Shared types: SessionContext and PipelineContext

broadcast.ts

Pushes messages to all connected WebSocket clients

http-handler.ts

Serves static client files and handles the reset endpoint

quibble-init.ts

Initialises the P2P node with retry logic for the storage lock

rtc.ts

Resolves the ICE server list pushed to clients (delegates to lib/rtc-config.ts)

net.ts

Finds an open TCP port on startup

ICE servers

On connect the server sends each client an rtc-config message with the ICE server list. Defaults come from lib/rtc-config.ts (Cloudflare STUN + shared Metered TURN). Override with environment variables:

  • QUIBBLE_ICE_SERVERS_JSON - a full JSON array of { urls, username?, credential? }, used verbatim.

  • TURN_URL, TURN_USERNAME, TURN_CREDENTIAL - point every client at your own TURN server.

Users can also set their own STUN/TURN server in Settings, which overrides the server defaults locally. See stun-turn.md.