Skip to content

The web SDK ​

@talkline/core is the whole client: the connection to the signaling server, rooms and calls, the WebRTC media, and recovery from network trouble. It has no UI and no framework. @talkline/react adds hooks over it for React apps (React).

Install ​

@talkline/core, with @talkline/protocol, which it depends on, and @talkline/react in a React app, come from the tarballs of a Talkline release, which your app keeps in its own repository: Installing the SDKs.

@talkline/core runs in any current browser with WebRTC. Camera and microphone access needs a secure context: serve your app over https (or from localhost while developing).

Create a client ​

One TalklineClient per signed-in user, created once and kept for as long as the page is open:

ts
import { TalklineClient } from '@talkline/core';

const client = new TalklineClient({
  url: 'wss://signal.talkline.example',
  token: async () => {
    const res = await fetch('/api/talkline-token', { method: 'POST', credentials: 'include' });
    if (!res.ok) throw new Error(`token endpoint answered ${res.status}`);
    return (await res.json()).token;
  },
});

url is your deployment's signaling address; the SDK adds the path and protocol version it needs.

The token is a function ​

token is a provider: a function the SDK calls whenever it needs a token — for the first connect, and again for every reconnect. Tokens are single-use, and a client that reconnects with the token it first connected with is refused token_replayed at exactly the moment it cannot ask the user for anything.

The provider is passed { reason: 'initial' | 'reconnect', identity }, for logging. It may return a string or a promise of one. A plain string also works, for a test or a one-shot page — it simply cannot survive a reconnect, and the SDK raises token_required rather than looping.

When the provider throws or rejects:

  • On the first connect, connect() — or the call() or join() that connected — rejects with exactly what your provider threw, not wrapped, and the client is closed. Catch it there: a signed-out user is your app's to handle.
  • On a reconnect, the attempt counts as failed and is tried again with backoff, calling your provider again each time, until the session's 15 minutes are spent and the client gives up with session_expired. A provider that will never succeed again — the user signed out — should be answered with client.close(), which stops the retries.

Connect ​

ts
await client.connect();

connect() resolves once the server has accepted the token. It is safe to call more than once — a second call joins the first — and call() and join() connect on their own if needed. A client that should be reachable for incoming calls connects as soon as the user is signed in, and stays connected.

After connecting, client.grants holds what the server actually granted, which can be narrower than the token asked for. Draw your controls from it: no camera button when canPublishSources leaves out camera.

Events ​

The client is an event emitter. on returns a function that removes the listener:

ts
const off = client.on('incomingCall', (call) => showRinger(call));
// later
off();
EventArgumentsWhen
connectionStatestate, { cause, attempt }The connection changed state — Connection and recovery
incomingCallcallSomeone is calling this identity
callUpdatedcallAnything about the current call changed — read it again
callStartedcallThe call was answered and is up
callEnded{ reason, call }The call ended, however it ended — once per call. reason is from this side: hangup when this client hung up, remote_hangup when the other side did; call.endReason keeps it
roomJoinedroomThis client took a seat — by join(), by placing a call, or by answering one
roomLeft{ reason }This client left its room, or was removed
participantJoined / participantLeft / participantUpdatedparticipant / { identity } / participantSomeone else arrived, left or changed
trackSubscribed / trackUnsubscribedtrack, participantSomeone else's audio or video arrived or went away
localTracksChangedtracksThis client's own tracks changed
activeSpeakersidentitiesWho is speaking now, loudest first
recordingChangedrecordingThe room's recording started, stopped or failed, or this user acknowledged it; also on joining a room being recorded. Show it — Recording a room
dataReceivedmessageA data message arrived
qualitylevel, identityA connection quality grade: this client's own, or, for a room admin, someone else's
audioOnlyFallbackactiveThe media server sends this client audio only, its connection too slow for video (true), or video is back (false); room.audioOnlyFallback keeps it — A group room
statssamplesThis client's own media numbers, every 5 seconds while in a room — for a diagnostics panel
mediaRecovery{ step, of }Media stalled and the SDK is recovering it, step by step — Connection and recovery
presenceinfoA watched identity's presence changed
needsUserGesturetrackThe browser blocked audio playback until the user interacts — show a button that calls client.resumeAudio()
updateDueprotocolThis build of the SDK is getting old for the deployment; ship an update
disconnected{ reason, reconnectAllowed }The server ended the session
errorerrorSomething failed that no promise was waiting for

Every event and its arguments are in the ClientEvents reference.

Showing media ​

A Track is one stream of audio or video. Attach it to an element:

ts
client.on('trackSubscribed', (track, participant) => {
  const el = track.attach(); // creates an <audio> or <video> element, or pass your own
  document.querySelector('#remote')!.append(el);
});
client.on('trackUnsubscribed', (track) => track.detach());

Use separate elements for a participant's audio and video, so that hiding or muting a video element can never silence the call. Your own camera is in client.localParticipant.tracks; show it in a muted video element.

The words are yours ​

The SDK ships no user-facing text — not a word, in any language. It emits states and error codes, and your app chooses what to say about each: "Reconnecting…", "Your camera is blocked", "They declined". Branch on error.code, never on error.message. Handling errors.

Closing ​

ts
await client.close();

when the user signs out. It leaves the room it is in — which hangs up a one-to-one call — and closes the connection. Create a new client for the next user rather than reusing a closed one.

Talkline documentation