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 |
|---|---|
|
Read and write data to disk |
|
Pure utility functions |
|
WebSocket message handlers |
|
P2P event handlers |
|
Live P2P subscriptions (room only) |
|
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 |
|---|---|
|
Entry point |
|
Routes incoming WebSocket messages to the correct handler |
|
Shared types: |
|
Pushes messages to all connected WebSocket clients |
|
Serves static client files and handles the reset endpoint |
|
Initialises the P2P node with retry logic for the storage lock |
|
Resolves the ICE server list pushed to clients (delegates to |
|
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.