VoIP Client for Zerorate
npm install zerorate-voip-sdkProduction-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.
``bash`
npm install zerorate-voip-sdk
`ts
import { useEffect, useRef } from "react";
import ZerorateVoIPClient from "zerorate-voip-sdk";
import "zerorate-voip-sdk/dist/voip-sdk.css";
const clientRef = useRef
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
}
};
`
`html`
- 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
- 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
- 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
- Initiate with callType: "callCentre" and businessId:
`json`
{
"callerId": "customer_01",
"callerName": "Alice Customer",
"businessId": "biz_789",
"callType": "callCentre",
"isVideo": true
}
- One-on-One remains the default:
`json`
{
"callerId": "user_123",
"callerName": "John Doe",
"calleeId": "user_456",
"calleeName": "Jane Doe",
"callType": "oneOnOne",
"isVideo": true
}
- Validation rules:
- oneOnOne: calleeId and calleeName are required.callCentre
- : businessId is required (callee fields ignored).
- WebSocket events:
- incoming_call: includes businessId and callType; broadcast to all online agents for callCentre.call_taken
- : emitted to other agents when one accepts; dismiss their incoming UI for the callId.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.
- The SDK persists active call details in localStorage and automatically rejoins the room on initialization if token/URL are valid.callId
- Persisted fields include , livekitUrl, token, roomName, isVideo, participant, plus callType and businessId when present.
- Storage is cleared upon call end/cleanup.
- Ensure initialization happens client-side to avoid CORS/backend validation issues:
`ts
import { useEffect, useState } from 'react';
export default function CallComponent() {
const [client, setClient] = useState
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();
}, []);
}
`
- 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”)
`tsx`
import { useVoIP } from "@freepass/voip-sdk/src/adapters/react/useVoIP";
const { isConnected, callState, initiateCall } = useVoIP(config);
`js`
import { useVoIP } from "@freepass/voip-sdk/src/adapters/vue/useVoIP";
const { state, initiateCall } = useVoIP(config);
- Vanilla: see examples/vanilla/index.htmlexamples/react/App.tsx
- React: see
- Initialize with enableUI: false and 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: npm run build (UMD + ESM + CSS copy)npm run typecheck
- Typecheck: npm run lint`
- Lint: