Getting started

Configuration

One options object controls everything: where the client connects, which signaling transport it uses, how it authenticates, and how aggressively it falls back when networks misbehave.

Passing options

The same options object is accepted by new Dendri(options), new Dendri(id, options), and createDendriStore(options). The simplest form is a single url:

import { Dendri, createDendriStore } from "@afterrealism/dendri-client";

// Store (recommended for UI apps)
const store = createDendriStore({ url: "wss://signal.example.com" });

// Low-level peer, with an explicit peer ID
const peer = new Dendri("alice-laptop", { url: "wss://signal.example.com" });

url is shorthand for host / port / secure / path. If you pass any of those explicitly, the explicit value wins over the url-derived one.

Option reference

OptionTypeDefaultDescription
urlstringFull server URL, e.g. wss://signal.example.com or http://127.0.0.1:9876. Shorthand for host/port/secure/path.
hoststringServer hostname, e.g. signal.example.com.
portnumberServer port, e.g. 443 or 9876.
pathstring"/"URL path prefix; must match the server's --path.
securebooleantrue for wss/https, false for ws/http.
apiKeystringAPI key for hosted / multi-tenant deployments. Sent with every signaling and REST request.
keystring"dendri"Connection key namespace; must match the server's --key.
jwtstringJWT for authenticated connections (server started with --jwt_secret).
tokenstringrandomSession token used to authenticate reconnects for the same peer ID.
signalingTransport"websocket" | "sse" | "polling" | "auto""websocket"Signaling transport. "auto" tries WebSocket → SSE → long-poll.
fetchTurnCredentialsbooleanfalseAuto-fetch ephemeral TURN credentials from the server's /turn-credentials endpoint.
enableRelaybooleanfalseEnable the tier-4 encrypted WebSocket relay when WebRTC is unavailable.
configRTCConfigurationCustom ICE configuration passed to every RTCPeerConnection (your own STUN/TURN servers).
validateMetadata(meta: unknown) => booleanValidate incoming connection metadata before accepting a connection.
referrerPolicyReferrerPolicyReferrer policy for the SDK's HTTP requests.
debugnumber0Log verbosity, 0 (silent) to 3 (verbose).

Configuring from environment variables

Apps should never hardcode a server URL. The pattern used by every official example reads a single env var and derives the parts, so the same build runs against local, staging, and production servers:

// .env.local →  VITE_DENDRI_URL=https://signal.example.com
//               VITE_DENDRI_API_KEY=dk_your_key   (hosted tier only)
const serverUrl = new URL(import.meta.env.VITE_DENDRI_URL ?? "http://127.0.0.1:9876");

export const DENDRI_OPTIONS = {
	host: serverUrl.hostname,
	port: Number(serverUrl.port) || (serverUrl.protocol === "https:" ? 443 : 80),
	secure: serverUrl.protocol === "https:",
	path: serverUrl.pathname,
	apiKey: import.meta.env.VITE_DENDRI_API_KEY,
	fetchTurnCredentials: true,
	enableRelay: true,
	debug: 0,
};
No hidden defaults. If no URL is configured, fail fast with a clear error. The SDK will never fall back to a vendor-operated endpoint on its own.

Signaling transports

Signaling (the handshake channel) is separate from the data path. Dendri supports three transports against the same server, selected with signalingTransport:

ValueMechanismUse when
"websocket"Persistent WebSocket (default)Normal networks. Lowest latency.
"sse"Server-Sent Events + HTTP POSTProxies that terminate WebSockets.
"polling"HTTP long-pollThe most restrictive corporate networks.
"auto"WS → SSE → pollingYou don't control your users' networks. Recommended for public apps.

Connectivity & fallback

TURN credentials

For peers behind symmetric NAT, WebRTC needs a TURN relay. If your server is configured with --turn_secret, set fetchTurnCredentials: true and the client automatically fetches short-lived credentials before connecting. Alternatively, pass your own ICE servers:

const store = createDendriStore({
	url: "wss://signal.example.com",
	config: {
		iceServers: [
			{ urls: "stun:stun.example.com:3478" },
			{
				urls: "turn:turn.example.com:3478",
				username: "user",
				credential: "pass",
			},
		],
	},
});

Encrypted TLS relay

With enableRelay: true, room broadcasts also flow through the signaling server as a last-resort delivery path (tier 4), necessary on networks where ICE reports success but packets silently vanish (VPNs, UDP-blocked networks). Relayed payloads are end-to-end encrypted with ECDH-derived AES-256-GCM keys, so the server relays ciphertext it cannot read. Duplicate deliveries are de-duplicated on the receiving side.

Connection lifecycle

The client runs a connection state machine, exposed as peer.connectionState / store.connectionState and via the connectionStateChanged event:

StateMeaning
initializedCreated, not yet connected.
connectingAttempting to reach the signaling server.
connectedConnected and ready.
disconnectedTemporarily offline; auto-reconnect with exponential backoff and jitter.
suspendedExtended offline (5+ consecutive failures); slower retry schedule.
closedExplicitly closed by your code.
failedUnrecoverable error.

After a successful reconnect the SDK re-sends room membership and presence automatically, so a server restart or a laptop lid-close doesn't leave peers in a ghost state.

Room and store options

createDendriStore() also accepts an advanced shape when you need room-level settings or want to inject a custom client class in tests:

const store = createDendriStore({
	dendriOptions: { url: "wss://signal.example.com", enableRelay: true },
	roomOptions: {
		// How long to wait for a new host to become reachable
		// during host migration (default 10 000 ms).
		migrationTimeout: 5000,
	},
});

Multi-tab behaviour

When two tabs use the same peer_id against the same server:

For multi-tab apps, either coordinate a single connection with BroadcastChannel, or let each tab be its own peer and communicate through a room.

Debugging

Set debug: 3 for verbose logs of signaling, ICE negotiation, and transport fallbacks. peer.diagnostics() returns a snapshot of connection internals suitable for bug reports.