Signaling and Relay Servers
Relevant source files
The following files were used as context for generating this wiki page:
Signaling and Relay Servers are a critical bootstrap component in the Sovereign Communications (SC) project. Their primary purpose is to facilitate the initial discovery and connection between peers before a direct, end-to-end encrypted WebRTC connection is established. These servers do not handle or have access to any message content; they only relay the metadata (SDP offers/answers and ICE candidates) required for peers to find and connect to each other.
The project provides two distinct signaling mechanisms: a stateful WebSocket-based server and a stateless serverless HTTP-based function. This dual approach offers flexibility in deployment, from a persistent Docker container to a scalable, on-demand serverless environment. Once the WebRTC connection is established, the signaling server is no longer involved in the communication between those peers.
Architecture Overview
The signaling process involves two or more peers using a shared "room" on a signaling server to exchange connection details.
The following diagram illustrates the high-level signaling flow.
This diagram shows how two clients use the signaling server to exchange WebRTC offer, answer, and ICE candidate messages to establish a direct peer-to-peer connection.
Sources: server/relay.ts, netlify/functions/room.ts
Signaling Transports (Client-Side)
The @sc/core library provides client-side implementations for communicating with the signaling servers.
WebSocket Signaling
The WebSocketSignaling class provides a persistent connection to a signaling server for real-time message exchange. It is designed to work with the Node.js WebSocket relay server.
Sources: core/src/transport/websocket-signaling.ts
Key Components
WebSocketSignalingclass: Implements theISignalinginterface.constructor(url: string): Initializes the WebSocket connection to the specified server URL.join(roomId: string): Joins a specific room on the server by sending ajoinmessage.signal(payload: any): Sends a signaling payload (like an SDP offer or ICE candidate) to the other peers in the room.onSignalevent: An event emitter that fires when a signal is received from the server.
Sequence Diagram
The diagram below shows the sequence of events when a peer connects using WebSocket signaling.
HTTP Signaling
The HttpSignaling class uses an HTTP long-polling mechanism to simulate a real-time connection. This is suitable for serverless environments where persistent WebSocket connections are not feasible.
Sources: core/src/transport/http-signaling.ts
Key Components
HttpSignalingclass: Implements theISignalinginterface.constructor(url: string): Sets the base URL for the HTTP signaling endpoint.join(roomId: string): Initiates the long-polling process by making a GET request to the room endpoint.signal(payload: any): Sends a signaling payload via an HTTP POST request to the room endpoint.longPoll(): A private method that continuously makes GET requests to poll for new signals. If a request times out or completes, it immediately initiates another one.
Sequence Diagram
This diagram illustrates the HTTP long-polling flow for exchanging signals.
Server Implementations
The repository contains two server-side implementations for signaling.
Node.js WebSocket Relay Server
A stateful server built with Node.js and the ws library. It is designed to be run as a persistent service, for example, within a Docker container.
Sources: server/relay.ts, docker-compose.yml
Logic Flow
The server maintains a Map where keys are roomIds and values are Sets of connected WebSocket clients.
This flowchart shows how the WebSocket server handles join and signal messages to manage rooms and relay data.
Sources: server/relay.ts:11-37
Deployment
The server is configured to run as a Docker container using the provided docker-compose.yml file.
| Configuration | Value | Description |
|---|---|---|
image | sc-relay | The name of the Docker image. |
build.context | . | The build context is the root of the repository. |
build.dockerfile | server/Dockerfile | The path to the Dockerfile for the server. |
ports | - "8080:8080" | Maps port 8080 on the host to port 8080 in the container. |
restart | unless-stopped | The container will restart automatically unless manually stopped. |
Sources: docker-compose.yml:1-8
Netlify Serverless Function
A stateless HTTP-based signaling server implemented as a single Netlify Function. It leverages Netlify's streaming response capabilities to handle long-polling efficiently.
Sources: netlify/functions/room.ts
API Endpoints
The function exposes a single endpoint that behaves differently based on the HTTP method.
Endpoint: /.netlify/functions/room
| Method | Path | Description |
|---|---|---|
GET | ?roomId=<ID> | Establishes a long-poll connection to listen for signals in the specified room. The connection is kept open and streams events. |
POST | ?roomId=<ID> | Sends a signal to all clients currently polling the specified room. The request body contains the signal payload. |
Internal Logic
- A global
Map(rooms) stores active polling clients for eachroomId. - On GET: A new client is added to the room's list. The response stream is kept open. When a signal is posted to that room, the data is written to the stream.
- On POST: The server iterates through all clients polling that room and sends the POST body's payload to each of them, then closes their respective connections to force a re-poll.
- A
cleanupmechanism removes clients that have disconnected.
Sources: netlify/functions/room.ts:10-70
Conclusion
The signaling and relay servers in SC are lightweight, special-purpose components designed solely for bootstrapping peer-to-peer connections. The provision of both WebSocket and HTTP-based implementations offers significant deployment flexibility. The servers are architected to be simple relays, ensuring they do not become a central point of failure or a privacy risk, as all application-level data is exchanged directly between peers after the initial handshake.