Skip to content

API reference

The complete public surface of @silt/client. This is the locked API — every example on this site uses exactly these names.

import { joinRoom } from "@silt/client";
function joinRoom(url: string, options: JoinOptions): Promise<Room>;

Connects to a room and resolves a Room. The promise resolves only after the join snapshot has arrived, so room.peers is already populated with everyone else in the room the instant joinRoom returns.

const room = await joinRoom("https://demo.silt.run/room/graveyard", {
id: "alice",
});

url is the WebTransport URL of the room (an https:// origin plus the room path).

interface JoinOptions {
id: string;
certHash?: string;
reconnect?: boolean;
snapshotTimeoutMs?: number;
}
Option Type Default Description
id string (required) Stable, client-supplied identity. Reconnecting = calling joinRoom again with the same id.
certHash string SHA-256 cert hash (hex) to pin via serverCertificateHashes, for local/dev secure contexts. See Run your own.
reconnect boolean true Auto-reconnect with the same id on transport loss.
snapshotTimeoutMs number 5000 How long to wait for the join snapshot before joinRoom rejects.

The object returned by joinRoom.

room.url; // readonly string — the room URL you connected to
room.id; // readonly string — your stable identity
room.peers; // Peer[] — the OTHER peers in the room, populated on join

room.peers holds the other peers and their latest presence. It’s kept in sync as peers join, leave, and update presence. It does not include yourself.

room.presence.set(state: PeerState): void;

Publishes your latest presence on the unreliable, latest-wins datagram lane. Call it as often as you like. If the connection isn’t ready (before connect or between reconnects) the call is a harmless no-op — the last value you set is re-published automatically on reconnect. Write failures are ignored (the lane is droppable by design).

room.presence.set({ x: 10, y: 20, heading: 90 });
room.events.send(event: RoomEvent): Promise<void>;

Sends a discrete message on the reliable, ordered lane. Returns the write promise. Throws "room not connected" if the room isn’t currently connected, so a critical message can’t be silently dropped.

room.events.send({ type: "absorb", target: "bob" });
room.on(type, callback): () => void;

Subscribes to a room event. Returns an unsubscribe function — call it to remove the listener.

const off = room.on("presence", (peerId, state) => { /* ... */ });
off(); // stop listening
Event Callback signature Fires when
"presence" (peerId: string, state: PeerState) => void another peer publishes presence.
"event" (peerId: string, event: RoomEvent) => void another peer sends a reliable event.
"join" (peer: Peer) => void a new peer joins the room.
"leave" (peerId: string, reason: LeaveReason) => void a peer leaves. reason is "left" (clean) or "timeout".
room.close(): Promise<void>;

Cleanly leaves the room: sends a bye (so others see leave with reason "left") and closes the transport. Disables auto-reconnect.

await room.close();
type PeerState = unknown;
type RoomEvent = unknown;
type LeaveReason = "left" | "timeout";
interface Peer {
id: string;
state: PeerState | null; // null until the peer has sent presence
}

PeerState and RoomEvent are unknown — they’re whatever you put in them. Define your own shapes and narrow on the way out:

interface Cursor { x: number; y: number; heading: number }
room.presence.set({ x, y, heading } satisfies Cursor); // your own shape
room.on("presence", (peerId, state) => {
const cursor = state as Cursor;
});