Try a call

@rill/client

Browser SDK: connect, media, labeled data. Hides SDP and ICE.

Browser SDK. SDP, ICE, and RTCDataChannel stay inside the package. Mint token with @rill/server. Walkthrough: Your app.

sh
npm i @rill/client

Until a v* tag exists, use this repo’s sdk/ workspace.

ts
import { Rill } from "@rill/client";

const video = document.querySelector("video")!;

async function mintFreshToken(): Promise<string> {
  return "paste-jwt";
}

const call = await Rill.connect({
  url: "https://rill.example.com",
  token: "paste-jwt",
  onToken: () => mintFreshToken(),
});

call.on("remote", (remote) => {
  video.srcObject = remote?.camera ?? null;
});
call.on("error", ({ code }) => console.error(code));
call.on("ended", () => console.log("call ended"));

await call.microphone.enable();
await call.camera.enable();

Connect

ts
Rill.connect(opts: ConnectOptions): Promise<Call>

type ConnectOptions = {
  url: string;
  token: string;
  onToken?: () => string | Promise<string>; // Called once on `token_expired`. Return a freshly minted JWT.
  speaker?: boolean; // Hidden speaker for `remote.microphone`. Default on. Pass `false` to opt out.
};
  • Rill.connect — Join with a short-lived JWT. Resolves after the signaling welcome + first offer/answer. speaker defaults to on: a hidden <audio> plays remote.microphone. Pass onToken to rotate once on token_expired.
  • ConnectOptionsRill.connect options. url is the public origin (HTTPS or the site that proxies /v1/signal).

Media

ts
call.microphone: LocalDevice
call.camera: LocalDevice
call.screen: LocalDevice
call.remote: Remote | null
call.speaker: Speaker | null

type LocalDevice = {
  enable: (deviceId?: string) => Promise<void>; // Capture this kind. Pass `deviceId` to switch if already on.
  disable: () => Promise<void>;
  readonly kind: TrackKind;
  readonly enabled: boolean;
  readonly stream: MediaStream | null; // Local MediaStream when enabled; SDK still hides the PeerConnection.
};

type Remote = {
  id: string;
  name?: string;
  microphone: MediaStream | null;
  camera: MediaStream | null;
  screen: MediaStream | null;
};
  • call.microphone — Local microphone. enable() is getUserMedia + replaceTrack.
  • call.screen — Local screen share (getDisplayMedia). Browser “stop sharing” calls disable().
  • call.remote — Other participant, or null while waiting / after they leave.
  • call.speaker — Hidden speaker for remote.microphone, or null when speaker: false.
  • Remote — Other participant. Bind camera / screen to <video>. Do not attach microphone to a second <audio>.

Data

ts
call.send(label: string, data: string | Uint8Array): Promise<void>
  • call.send — Send on a labeled data channel. Queues while the local channel is connecting. Resolves once RTCDataChannel.send accepts the payload (not after remote delivery). A missing peer is not an error — the SFU drops live relay like unpublished camera. Max payload 128 KiB (str0m write buffer); chunk larger snapshots in the app.

Events and hang-up

ts
call.on<K extends keyof CallEvents>(type: K, fn: (payload: CallEvents[K]) => void): () => void
call.state: CallState
call.disconnect(): Promise<void>

type CallEvents = {
  remote: Remote | null;
  local: LocalSnapshot;
  state: CallState;
  ended: Record<string, never>;
  error: CallError;
  data: DataMessage;
};

type DataMessage = {
  label: string;
  data: string | Uint8Array;
};
  • call.on — Subscribe. Returns unsubscribe.
    • remote — other participant’s streams, or null when they leave
    • local{ microphone, camera, screen } enabled flags
    • stateconnecting | connected | reconnecting | disconnected
    • ended — slot destroyed (call_ended or server_shutdown)
    • error{ code, message }
    • data — labeled message { label, data }. Do not subscribe from useCall.
  • call.state — Signaling + ICE lifecycle.
  • call.disconnect — Leave this browser. Does not destroy the call; the other person stays.
  • CallEvents — Event map for call.on. useCall does not subscribe to data.
  • DataMessage — Payload from call.on("data"). data is a string or a copy of the binary frame.

Errors

ts
type ErrorCode =
  | "unauthorized"
  | "forbidden"
  | "call_not_found"
  | "call_full"
  | "call_ended"
  | "participant_invalid"
  | "token_expired"
  | "overloaded"
  | "connection_failed"
  | "permission_denied"
  | "replaced"
  | "server_shutdown"
  | "unsupported"
  | "internal";
  • ErrorCode — Public error code strings. call_full is a third join; replaced is last-writer-wins.

Type to search…

↑↓ navigate open esc close