zerorate-voip-sdk
v0.2.0
Published
VoIP Client for Zerorate
Readme
Zerorate VoIP SDK
[BEFORE YOU INSTALL: IMPORTANT NOTICE]: This SDK is customly built to be use by merchants on the freepass.africa platform.
Production-ready, framework-agnostic JavaScript/TypeScript SDK to add in-app VoIP calling to any web application. Handles WebSocket signaling, LiveKit WebRTC integration, call state management, and provides a simple API with optional pre-built UI and framework adapters.
Installation
npm install zerorate-voip-sdkQuick Start (ESM)
import { useEffect, useRef } from "react";
import ZerorateVoIPClient from "zerorate-voip-sdk";
import "zerorate-voip-sdk/dist/voip-sdk.css";
const clientRef = useRef<ZerorateVoIPClient | null>(null);
useEffect(() => {
const client = new ZerorateVoIPClient({
wsUrl: process.env.NEXT_PUBLIC_WS_URL!,
backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
authToken: /* your bearer token */,
userId: /* current user id */,
userName: /* current user name */,
enableUI: true,
autoReconnect: true,
debug: false,
});
clientRef.current = client;
// Connect WS once
client.connect();
// Optional UI
client.showUI();
//For Custom UI
client.on("incoming_call", (data) => {
// show incoming UI or auto accept
});
// Event hooks (optional)
client.on("connected", () => {});
client.on("state:changed", ({ newState }) => {});
client.on("call_ended", () => {});
return () => {
// Keep the instance if the page stays mounted; otherwise cleanup here
client.destroy();
clientRef.current = null;
};
}, []);
const startCall = async () => {
const client = clientRef.current;
if (!client) return;
// Ensure we are connected before initiating
if (!client.isConnected()) {
await client.connect();
}
await client.initiateCall(/* calleeId */, /* calleeName */, { isVideo: true });
};
const endCall = async () => {
const client = clientRef.current;
const active = client?.getActiveCall();
if (client && active?.callId) {
await client.endCall(active.callId);
// Do not call destroy() here; keep client/WS alive for next calls
}
};
Quick Start (UMD)
<script src="https://cdn.example.com/voip-sdk.min.js"></script>
<link rel="stylesheet" href="https://cdn.example.com/voip-sdk.css" />
<script>
const client = new ZerorateVoIPSDK.ZerorateVoIPClient({
backendUrl: "https://api.example.com",
wsUrl: "wss://api.example.com/ws",
userId: "user_123",
userName: "John Doe",
});
client.connect();
client.initiateCall("user_456", "Jane Smith", { isVideo: false });
</script>Configuration
- backendUrl: string
- wsUrl: string
- userId: string
- userName: string
- authToken?: string
- enableUI?: boolean
- theme?: 'light' | 'dark' | 'auto'
- ringTones?: { incoming?: string; outgoing?: string }
- autoReconnect?: boolean
- debug?: boolean
Public API
- connect(): Promise
- disconnect(): void
- isConnected(): boolean
- initiateCall(calleeId: string, calleeName: string, options?: { isVideo?: boolean; callType?: "oneOnOne" | "callCentre"; businessId?: string }): Promise
- acceptCall(callId: string): Promise
- declineCall(callId: string): Promise
- endCall(callId: string): Promise
- toggleMicrophone(): Promise
- toggleVideo(): Promise
- setMicrophoneEnabled(enabled: boolean): Promise
- setVideoEnabled(enabled: boolean): Promise
- getCallState(): 'idle' | 'calling' | 'ringing' | 'connecting' | 'connected' | 'ending'
- getActiveCall(): CallData | null
- isInCall(): boolean
- getCallDurationSeconds(): number
- getParticipants(): Array<{ sid: string; identity: string; isLocal: boolean }>
- getLocalTracks(): MediaStreamTrack[]
- getRemoteTracks(): MediaStreamTrack[]
- attachTrack(track: MediaStreamTrack, element: HTMLElement): void
- detachTrack(track: MediaStreamTrack, element: HTMLElement): void
- isMicrophoneEnabled(): boolean
- isVideoEnabled(): boolean
- on(event, callback): void
- off(event, callback): void
- once(event, callback): void
- showUI(): void
- hideUI(): void
- setTheme(theme): void
- destroy(): void
Events
- connected, disconnected, reconnecting, error
- incoming_call, call_taken, call_already_taken, call_accepted, call_declined, call_missed, call_ended, call:error
- media:microphone-changed, media:video-changed
- state:changed
Call Centre Mode
- Initiate with
callType: "callCentre"andbusinessId:
{
"callerId": "customer_01",
"callerName": "Alice Customer",
"businessId": "biz_789",
"callType": "callCentre",
"isVideo": true
}- One-on-One remains the default:
{
"callerId": "user_123",
"callerName": "John Doe",
"calleeId": "user_456",
"calleeName": "Jane Doe",
"callType": "oneOnOne",
"isVideo": true
}Validation rules:
oneOnOne:calleeIdandcalleeNameare required.callCentre:businessIdis required (callee fields ignored).
WebSocket events:
incoming_call: includesbusinessIdandcallType; broadcast to all online agents for callCentre.call_taken: emitted to other agents when one accepts; dismiss their incoming UI for thecallId.call_already_taken: returned to an agent who tries to accept after another already did; show notice and dismiss incoming UI.call_missed: sent if timeout/declined by everyone; dismiss UI and show “Missed Call”.
Accept logic:
- On 200 OK, proceed to join room.
- On 409 Conflict, dismiss incoming UI locally and show notice: “Call already picked by another agent.”
Decline logic:
oneOnOne: ends call immediately for both parties.callCentre: only removes that agent from ringing; call ends as missed only if all agents decline.
Auto Resume (Refresh/Interruption)
- The SDK persists active call details in
localStorageand automatically rejoins the room on initialization if token/URL are valid. - Persisted fields include
callId,livekitUrl,token,roomName,isVideo,participant, pluscallTypeandbusinessIdwhen present. - Storage is cleared upon call end/cleanup.
Next.js Client-Side Usage
- Ensure initialization happens client-side to avoid CORS/backend validation issues:
import { useEffect, useState } from 'react';
export default function CallComponent() {
const [client, setClient] = useState<any>(null);
useEffect(() => {
(async () => {
const { ZerorateVoIPClient } = await import('zerorate-voip-sdk');
const c = new ZerorateVoIPClient({
wsUrl: process.env.NEXT_PUBLIC_WS_URL!,
backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,
authToken: process.env.NEXT_PUBLIC_TOKEN!,
userId: 'user_123',
userName: 'John Doe',
enableUI: true
});
setClient(c);
await c.connect();
c.showUI();
})();
return () => client?.destroy();
}, []);
}Optional UI Components
- Include CSS:
import '@freepass/voip-sdk/dist/voip-sdk.css' - Use UIManager to render:
- showIncomingCall(callData)
- showOutgoingCall(callData)
- showActiveCall(callData)
- hideAllModals(), show(), hide(), setTheme(theme)
- showNotice(message) for transient messages (e.g., “Call already picked by another agent”)
React Adapter
import { useVoIP } from "@freepass/voip-sdk/src/adapters/react/useVoIP";
const { isConnected, callState, initiateCall } = useVoIP(config);Vue Adapter
import { useVoIP } from "@freepass/voip-sdk/src/adapters/vue/useVoIP";
const { state, initiateCall } = useVoIP(config);Examples
- Vanilla: see
examples/vanilla/index.html - React: see
examples/react/App.tsx
Custom UI (enableUI: false)
- Initialize with
enableUI: falseand bind to events:- incoming_call, call_accepted, call_declined, call_missed, call_ended
- state:changed for rendering call flow
- media:microphone-changed, media:video-changed for icon states
- Use API to render and control:
- getCallState(), getActiveCall(), isInCall(), getCallDurationSeconds()
- toggleMicrophone(), toggleVideo(), setMicrophoneEnabled(), setVideoEnabled()
- isMicrophoneEnabled(), isVideoEnabled()
- getParticipants() for active room participants
- getLocalTracks(), getRemoteTracks() with attachTrack()/detachTrack() for video elements
- initiateCall(), acceptCall(), declineCall(), endCall()
Build Scripts
- Build:
npm run build(UMD + ESM + CSS copy) - Typecheck:
npm run typecheck - Lint:
npm run lint
