Skip to content

Repository files navigation

@firetell/firetell-client-sdk

npm version TypeScript License: MIT

Official TypeScript/JavaScript SDK for building WebRTC Voice & Video Communications applications on the Firetell Platform (CPaaS, Virtual PBX, Call Center, Voice AI, SIP Trunking).

🎮 Live Interactive Demo: https://developers.firetell.com/firetell-client-sdk/example/


🌟 Key Features

  • 📞 WebRTC Outbound & Inbound Calls — High-quality audio/video calls using Native WebSockets per call session.
  • 🎵 Early Media & PSTN Ringback Audio (call.sdp / 183 Session Progress) — Full support for early audio playback so callers hear PSTN ringback tones or early IVR prompts before the callee answers.
  • 🌐 Native WebSockets & SSE — Lightweight, zero external dependencies (no Socket.IO or heavy WS wrappers).
  • 🔒 Per-Call Security Tokens — Short-lived call_token JWT authentication per WebRTC session.
  • 🎧 Call Center Supervision — Built-in listen (silent monitor), whisper (coach agent), and barge (3-way call) supervision modes.
  • 📥 Instant Incoming Call Alerts — Real-time ring notifications via SSE (call.ring) and WebRTC offer payloads (call.offer).
  • ⏸️ In-Call Control — Re-INVITE based Hold/Unhold, client-side Mute/Unmute, and DTMF tones (SIP INFO).
  • 👥 Real-time Agent Presence — Track team availability via Server-Sent Events (agent.state).
  • 📦 Multi-Format Distribution — Distributed as ESM (.mjs), CommonJS (.js), and Browser Bundle (.global.js).

📦 Installation

npm install @firetell/firetell-client-sdk
# or
yarn add @firetell/firetell-client-sdk
# or
pnpm add @firetell/firetell-client-sdk

Browser CDN Usage

<script src="https://cdn.jsdelivr.net/npm/@firetell/firetell-client-sdk/dist/index.global.js"></script>
<script>
  const token = "YOUR_AGENT_JWT_TOKEN";
  const domain = "your-workspace.firetell.app";
  const client = new Firetell.FiretellClient(token, domain);
</script>

🚀 Quick Start Guide

1. Initialize the Client

import {
  FiretellClient,
  Call,
  ECallState,
  EClientEventName,
  ECallEventName,
} from "@firetell/firetell-client-sdk";

// Initialize client with agent JWT and workspace domain/URL
const client = new FiretellClient(
  "YOUR_AGENT_JWT_TOKEN",
  "https://your-workspace.firetell.app"
);

// Wait for connection initialization
const session = await client.ready;
console.log(`Connected as Agent: ${session.username} (${session.workspace_id})`);

2. Make an Outbound Call (with Early Media Support)

// HTML audio elements for WebRTC streams
const remoteAudio = document.getElementById("remote-audio") as HTMLAudioElement;

// Create Call instance
const call = new Call(client, {
  to: "+84901234567",     // Extension, agent username, or phone number
  from: "+842871000000",   // Optional outbound Caller ID
  isVideo: false,
});

// 1. Listen for remote audio stream (Works for Early Media & Answered states!)
call.on(ECallEventName.REMOTE_STREAM, (remoteStream) => {
  console.log("Received Remote Stream (Early Media / Audio Answer):", remoteStream);
  remoteAudio.srcObject = remoteStream;
  remoteAudio.play().catch(console.error);
});

// 2. Listen to call state changes
call.on(ECallEventName.STATE, (payload) => {
  console.log("Call State Changed:", payload.state, payload.reason);
  // States: INITIATED -> TRYING -> RINGING -> ACTIVE (ANSWERED) -> ENDED
  switch (payload.state) {
    case ECallState.RINGING:
      console.log("Ringing / Early Media established...");
      break;
    case ECallState.ACTIVE:
      console.log("Call Connected & Active!");
      break;
    case ECallState.ENDED:
      console.log("Call Ended.");
      break;
  }
});

// 3. Listen to Real-time Speech-to-Text Transcription & Dialogues
call.on(ECallEventName.TRANSCRIPTION_DIALOGUE, (dialogue) => {
  console.log(`[${dialogue.speaker}]: ${dialogue.text} (final: ${dialogue.speech_final})`);
  // Render real-time live chat bubble / speech subtitles...
});

call.on(ECallEventName.TRANSCRIPTION_COMPLETED, (summaryData) => {
  console.log("Call Transcription Completed:", summaryData.full_text);
  console.log("AI Summary:", summaryData.summary);
  console.log("Sentiment:", summaryData.sentiment);
});

// 4. Listen to Call Recording Events
call.on(ECallEventName.RECORDING_STARTED, (event) => {
  console.log("Call Recording Started:", event);
});

call.on(ECallEventName.RECORDING_COMPLETED, (event) => {
  console.log("Call Recording Stopped:", event);
});

// Or listen to all recording events:
call.onRecording((event) => {
  console.log(`Recording Event [${event.type}]:`, event.data);
});

// 5. Initiate the call
await call.start();

3. Handle Incoming Calls

// 1. Instant Ring Alert (Trigger Ringing Popup & Play Ringtone)
client.events.on("call.ring", (ringData) => {
  console.log("🔔 Incoming Call Alert!", ringData.call_id);
  console.log("Caller:", ringData.from?.name || ringData.from?.number);
  console.log("Hotline:", ringData.to?.name || ringData.to?.number);
  // Show incoming call modal UI & play ringtone...
});

// 2. Incoming Call WebRTC Offer Ready
client.events.on("call.offer", (incomingCall) => {
  console.log("Call Object Ready:", incomingCall.callId);

  // Bind Remote Stream
  incomingCall.on(ECallEventName.REMOTE_STREAM, (stream) => {
    remoteAudio.srcObject = stream;
    remoteAudio.play();
  });

  // Example: Accept button click handler
  document.getElementById("btn-accept")?.addEventListener("click", async () => {
    await incomingCall.accept();
    console.log("Call Answered!");
  });

  // Example: Reject button click handler
  document.getElementById("btn-reject")?.addEventListener("click", async () => {
    await incomingCall.reject();
  });
});

🎛️ In-Call Operations

Once a call is active (call object), you can perform the following controls:

Mute & Unmute Audio

// Mute microphone (stops sending audio)
call.mute();
console.log("Microphone Muted:", call.isMuted);

// Unmute microphone
call.unmute();
console.log("Microphone Muted:", call.isMuted);

// Toggle mute state
const isMutedNow = call.toggleMute();

Hold & Unhold Call

// Hold call (sends SDP renegotiation to server)
await call.hold();

// Unhold call
await call.unhold();

Transfer Call

// Transfer to another agent by username
await call.transfer("agent.jane");

// Transfer to an extension number
await call.transfer("100");

// Transfer to a team
await call.transfer("te_support_team_id");

// Transfer to a SIP account
await call.transfer("si_sip_account_id");

// Transfer with a reason
await call.transfer("agent.jane", "Customer needs billing support");

Send DTMF Tones

// Send DTMF keypad digit ('0'-'9', '*', '#') via SIP INFO
await call.sendDTMF("1");

End Call

// Hangup / Terminate Call
await call.hangup();

🎧 Call Supervision (Supervisor / Monitor)

Supervisors and team leaders can monitor ongoing active calls in 3 supervision modes:

// Mode 1: "listen" — Silent Monitoring (Supervisor hears both agent & customer)
const call = await client.startSupervision("cl_123456789", "listen");

// Mode 2: "whisper" — Whisper / Coach (Only the agent hears the supervisor)
const call = await client.startSupervision("cl_123456789", "whisper");

// Mode 3: "barge" — 3-Way Barge-In (Both agent & customer hear supervisor)
const call = await client.startSupervision("cl_123456789", "barge");

// Bind remote stream to audio element
call.on(ECallEventName.REMOTE_STREAM, (stream) => {
  remoteAudio.srcObject = stream;
  remoteAudio.play().catch(console.error);
});

// Stop supervision session
await call.hangup();

Advanced Usage: You can also use client.superviseCall(callId, mode) to fetch session credentials ({ call_token, ws_url }) and initialize the session manually via call.joinSession(res.ws_url, res.call_token).


👥 Real-Time Agent Presence & Events

Track team presence and status changes in real-time via Server-Sent Events (SSE):

// Listen for agent status changes
client.events.on("agent.state", ({ username, state }) => {
  // States: "online" | "available" | "incall" | "busy" | "offline"
  console.log(`Agent ${username} is now ${state}`);
});

// Listen for forced state changes by supervisors
client.events.on("agent.state.forced", ({ target_username, new_state, forced_by, reason }) => {
  console.log(`Agent ${target_username} was forced ${new_state} by ${forced_by}: ${reason}`);
});

Agent Presence States:

State Description
online Agent is actively connected via SSE event stream
available Agent is reachable via VoIP push (has registered device) but not actively streaming SSE
incall Agent is in an active call
busy Agent is busy (set manually by agent — Do Not Disturb)
offline Agent has no active connections or registered push devices

Set Own Presence

// Set "Do Not Disturb" — agent won't receive incoming calls
await client.setPresence("busy");

// Return to active state (system determines: "online" or "available")
await client.setPresence("ready");

📊 Real-Time Call Lifecycle Events

In addition to WebRTC signaling, the SDK emits real-time call lifecycle state events over the persistent SSE stream for live dashboard monitoring, active call logs, and call telemetry:

// 1. New Call Session Created in Workspace
client.events.on("call.created", ({ data }) => {
  console.log("Call Created:", data.call_id, data.direction, data.number);
});

// 2. Call Ringing / Started
client.events.on("call.started", ({ data }) => {
  console.log("Call Ringing:", data.call_id, "From:", data.from?.number, "To:", data.to?.number);
});

// 3. Call Answered & Bridge Established
client.events.on("call.answered", ({ data }) => {
  console.log("Call Answered:", data.call_id, "Answered at:", data.answer_time);
});

// 4. Call Ended & Final Metrics Available
client.events.on("call.ended", ({ data }) => {
  console.log("Call Ended:", data.call_id, "Duration:", data.duration, "Cause:", data.hangup_cause);
});

📖 API & Event Reference

EClientEventName (Client Events)

Event Name Payload Type Description
call.ring ICallRingParams Triggered instantly when an incoming call starts ringing (popup alert)
call.offer Call Triggered when the WebRTC call object is ready to answer
call.created { event, workspace_id, data, timestamp } Real-time event when a new call is initialized
call.started { event, workspace_id, data, timestamp } Real-time event when a call begins ringing/progressing
call.answered { event, workspace_id, data, timestamp } Real-time event when a call is answered
call.ended { event, workspace_id, data, timestamp } Real-time event when a call finishes (includes duration, hangup_cause, cost)
call.canceled { event, workspace_id, data, timestamp } Triggered when ringing is canceled (call answered elsewhere or timed out)
connection.state 'connected' | 'connecting' | 'disconnected' Real-time connection status updates (handles background drops and reconnects without logging out)
agent.state { username, state } Real-time presence updates (online, available, incall, busy, offline)
agent.state.forced { target_username, new_state, forced_by, reason } Fired when a supervisor forces an agent's state to offline
agent.updated Agent Fired when an agent profile (name, avatar, email) is updated
agent.created Agent Fired when a new agent account is created
agent.deleted { id, username } Fired when an agent account is deleted
team.created Team Fired when a workspace team is created
team.updated Team Fired when team details/name are updated
team.deleted { id } Fired when a team is deleted
team.assigned { team_id, agent_id } Fired when an agent joins a team
team.unassigned { team_id, agent_id } Fired when an agent leaves a team
contact.created Contact Fired when a contact is created
contact.updated Contact Fired when contact info is updated
contact.deleted { id } Fired when a contact is deleted
call.recording.ready ICallRecordingReadyEvent Fired on workspace stream when call recording file is fully processed and ready to play/download
error { code, message } General client errors or authentication failures

ECallEventName (Call Instance Events)

Event Name Payload Type Description
state ISIPCallState Call state changes (INITIATED, TRYING, RINGING, ACTIVE, ONHOLD, ENDED)
remoteStream MediaStream Fired when remote audio/video stream is available (including Early Media / Ringback)
localStream MediaStream Fired when local microphone/camera stream is captured
mediaState RTCIceConnectionState WebRTC ICE connection status updates (connecting, connected, failed)
mute { muted: boolean } Fired when local microphone is muted or unmuted
transcription TranscriptionEvent Consolidated real-time speech transcription lifecycle events
transcription.started ITranscriptionStartedEvent Fired when real-time speech-to-text session begins
transcription.dialogue ITranscriptionDialogueEvent Fired on each live speech dialogue chunk / subtitle
transcription.completed ITranscriptionCompletedEvent Fired when full call transcription, summary, and sentiment analysis finish
recording CallRecordingEvent Consolidated call recording lifecycle events
recording.started ICallRecordingStartedEvent Fired when audio recording starts
recording.completed ICallRecordingCompletedEvent Fired when audio recording finishes

📄 License

This SDK is released under the MIT License.

About

Official TypeScript/JavaScript SDK for building WebRTC Voice & Video Communications applications on the Firetell Platform (CPaaS, Virtual PBX, Call Center, Voice AI, SIP Trunking).

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages