API overview
The TypeScript, Rust, and Tauri APIs at a glance
Start with the public client
import { OpenRTC } from 'openrtc';
const rtc = OpenRTC({
apiKey: import.meta.env.VITE_OPENRTC_API_KEY,
});
Creating the client does not start a connection or load the browser runtime. Network work begins when you start or join an avenue.
The complete option shape is
OpenRTC({ apiKey, auth, trust, usage, transports }). Only apiKey is
required; add the other options when the application needs them.
| Namespace | Method | Result |
|---|---|---|
devices | start(options) | Persistent device-mesh handle |
spaces | join(id, options) | Live-only space handle |
rooms | join(id, options) | Ephemeral or durable room handle |
tickets | issue(id, options) | Bounded ticket-session handle |
channels | register(descriptor) | Registers a channel for subsequently active handles |
usage | estimate(input) | Local usage-credit estimate |
offline | support() / createEnrollmentRequest(options) | Native edge capability and target-signed enrollment request |
Choose the avenue that matches who should meet. Avoid starting a device mesh, space, and room when the feature needs only one of them.
Avenue handles
Every avenue exposes the same core tools:
const room = await rtc.rooms.join('call-123');
room.peers.watch((peers) => renderParticipants(peers));
const chat = room.channel<{ text: string }>('chat');
chat.onMessage(({ peerId, message }) => {
addMessage(peerId, message.text);
});
await chat.send({ text: 'hello' });
room.onConnection((connection) => {
console.log('connected to', connection.peerId);
});
await room.leave();
All avenue handles provide close(). Spaces and rooms also provide leave()
as a more natural alias. Closing the root client closes every remaining handle.
The current ticket API does not expose accept() or server-side revoke().
Connections
A Connection is a stable, logical relationship with one peer. It is not a
WebRTC peer connection, MoQ session, or QUIC socket. OpenRTC may replace the
physical route while keeping the public connection alive.
room.onConnection((connection) => {
connection.onMessage((message) => handleMessage(message));
connection.onClose(() => markPeerOffline(connection.peerId));
void connection.send({ type: 'ready' });
});
Each connection provides:
| API | Use it for |
|---|---|
send() / onMessage() | Small structured application messages |
streams | Long-lived or large byte flows |
streams.datagrams | Fresh values that may be lost, such as live input |
media | Audio and video tracks |
OpenRTC keeps avenue scope, admission, application encryption, route recovery, and carrier switching below this API.
Streams
Streams use familiar Web Streams and WebTransport terms. A protocol name lets the receiving application route the stream safely.
const stream = await connection.streams.createBidirectionalStream({
protocol: 'com.example.chat/1',
});
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode('hello'));
await writer.close();
Media
Browser applications pass ordinary MediaStreamTrack objects. OpenRTC owns
the encoded media protocol and keeps it attached to the logical connection.
const local = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
const remote = new MediaStream();
remoteVideo.srcObject = remote;
room.onConnection(async (connection) => {
for (const track of local.getTracks()) {
await connection.media.addTrack(track);
}
connection.media.onTrack(({ track }) => {
for (const current of remote.getTracks()) {
if (current.kind === track.kind) remote.removeTrack(current);
}
remote.addTrack(track);
});
});
Your application does not create SDP, exchange ICE candidates, operate a MoQ session, frame encoded chunks, or restart tracks after an OpenRTC route change.
Native Rust
The openrtc crate works without TypeScript. ControlPlane obtains and renews
avenue capabilities. Client owns the native Iroh endpoint, peer lifecycle,
messages, streams, datagrams, and media.
use openrtc::{
Client,
native::{CapabilityOptions, ControlPlane, DeviceSigner},
};
use std::sync::Arc;
const API_KEY: &str = "pk_live_...";
let signer: Arc<dyn DeviceSigner> = app_secure_signer();
let control = ControlPlane::anonymous(API_KEY, signer)?;
let room = control.join_room(
"call-123",
"desktop",
CapabilityOptions::default(),
).await?;
let client = Arc::new(
Client::builder(API_KEY.to_owned(), Box::new(|| None))?
.signaling_backend(room.signaling())
.build(),
);
client.send_peer(peer_id, br#"{"type":"hello"}"#).await?;
let (_connection_id, _node_id, send, receive) =
client.open_peer_bi(peer_id, Some(10_000)).await?;
room.close().await;
The host supplies secure key storage, platform capture and codecs, and product authorization. OpenRTC supplies connection and transport infrastructure. See Avenues for all four Rust activation examples.
Tauri without WASM
Tauri WebViews should import the native-only entrypoint. It returns the same
TypeScript Client, but sends commands to the native Rust owner over IPC.
import { OpenRTC } from 'openrtc/native';
import { createBridge } from 'openrtc-tauri/ipc';
const rtc = OpenRTC(
{ apiKey, auth },
createBridge(),
);
The Rust host installs openrtc-tauri-plugin. Product Rust code and the WebView
can share that plugin's one Arc<Client>. The frontend does not load WASM,
dial peers, retry routes, or select carriers.
Advanced runtime
Import openrtc/runtime only for a runtime adapter or bounded diagnostic tool. Endpoint overrides belong to openrtc/testing and should not enter production bundles.