@shivamprasad99/web-sdk
v0.1.5
Published
Simple browser SDK for integrating the voice service into external websites.
Readme
@shivamprasad99/web-sdk
Browser SDK for adding the voice service to an external website.
The SDK hides the low-level protocol:
- Session creation
- WebSocket token flow
- Microphone capture
- VAD
- User audio streaming
- Local assistant audio playback
- Text send interruption behavior
- State reduction
- Asset/audio/text events
The website owns the UI.
Basic Integration
import { createVoiceClient } from '@shivamprasad99/web-sdk';
const voice = createVoiceClient({
sessionEndpoint: 'https://voice.example.com/api/voice/session',
siteId: 'rocketaeo',
metadata: {
source: 'landing-chatbot'
}
});
voice.onStatus((status) => {
console.log(status.session, status.turn);
});
voice.onTranscript(({ text, isFinal }) => {
renderTranscript(text, { isFinal });
});
voice.onAssistantText((text) => {
renderAssistantText(text);
});
voice.onAssets((assets) => {
renderAssets(assets);
});
voice.onError((error) => {
showVoiceError(error.message);
});Optionally warm the text session before the first message:
openChatButton.addEventListener('click', () => {
void voice.warmSession();
});Start and stop the call from UI controls:
startButton.addEventListener('click', async () => {
await voice.startCall();
});
stopButton.addEventListener('click', async () => {
await voice.endCall();
});Send typed text:
form.addEventListener('submit', async (event) => {
event.preventDefault();
await voice.sendText(input.value);
});Typed text is treated like a manual interruption for playback. If the assistant is speaking, current audio is stopped before the new text turn is sent.
Manual interruption:
interruptButton.addEventListener('click', async () => {
await voice.interrupt();
});Cleanup when unmounting/removing the widget:
await voice.destroy();Configuration
const voice = createVoiceClient({
sessionEndpoint: 'https://voice.example.com/api/voice/session',
siteId: 'rocketaeo',
metadata: {
source: 'landing-chatbot',
page_name: 'home'
},
playbackRate: 1.12
});Required options:
sessionEndpoint: voice service session endpoint.siteId: website/platform identifier, for examplerocketaeo.
Optional options:
metadata: extra values sent with session creation.playbackRate: assistant audio playback speed. Defaults to1.12.vad: advanced VAD override.audio: advanced audio mode override.fetchImpl: custom fetch implementation for tests.WebSocketImpl: custom websocket implementation for tests.
Public API
warmSession()
Opens the voice service session and WebSocket without starting microphone capture.
Use this when the chat UI opens or the text input receives focus so the first typed message can be sent over an already-open session.
await voice.warmSession();closeSession(reason?)
Closes an idle text session without destroying the client or removing event handlers.
Use this after an unused warm session has been idle for a while.
await voice.closeSession('warm_session_idle');startCall()
Starts the voice session and microphone capture.
Safe to call more than once. Duplicate calls reuse the active start operation.
await voice.startCall();endCall(reason?)
Stops microphone capture, assistant playback, websocket session, and call state.
Safe to call when the call is not active.
await voice.endCall();sendText(text)
Sends a typed user message.
This also stops current assistant playback locally before sending.
await voice.sendText('Show me what RocketAEO can do.');interrupt()
Manual interrupt.
await voice.interrupt();destroy(reason?)
Stops the call and removes all registered handlers.
Use this when the widget/page is unmounted.
await voice.destroy();isActive()
Returns whether a call is currently active.
if (voice.isActive()) {
await voice.endCall();
}getStatus()
Returns the current high-level SDK status.
const status = voice.getStatus();Shape:
{
transport: 'idle' | 'connecting' | 'open' | 'closing' | 'closed' | 'error';
session: 'idle' | 'connecting' | 'ready' | 'active' | 'recovering' | 'ended' | 'error';
turn: 'idle' | 'user_speaking' | 'transcribing' | 'assistant_thinking' | 'assistant_speaking' | 'interrupted' | 'cancelled' | 'completed' | 'error';
isListening: boolean;
isAssistantSpeaking: boolean;
isActive: boolean;
isDestroyed: boolean;
errorCode?: string;
}getState()
Returns the lower-level client state. Most websites should prefer getStatus().
const state = voice.getState();Event Callbacks
All callback registration methods return an unsubscribe function.
const unsubscribe = voice.onAssistantText((text) => {
renderAssistantText(text);
});
unsubscribe();SDK event callbacks are isolated. If one UI callback throws, the SDK continues processing future events.
onStatus(handler)
Fires whenever transport/session/turn status changes.
voice.onStatus((status) => {
setCallOpen(status.isActive);
setListening(status.isListening);
setSpeaking(status.isAssistantSpeaking);
});onTranscript(handler)
Fires for partial and final speech transcripts.
voice.onTranscript(({ text, isFinal }) => {
if (isFinal) {
setFinalTranscript(text);
} else {
setLiveTranscript(text);
}
});onAssistantText(handler)
Fires as assistant text streams.
voice.onAssistantText((text) => {
setAssistantMessage(text);
});onAssets(handler)
Fires when BofuBot returns images, videos, HTML, or slides.
voice.onAssets((assets) => {
renderAssets(assets);
});Asset shape:
{
kind: 'image' | 'video' | 'html' | 'slide';
url: string;
title?: string;
alt?: string;
metadata?: Record<string, unknown>;
}Recommended rendering:
function renderAssets(assets) {
assets.forEach((asset) => {
if (asset.kind === 'image') {
renderImage(asset.url, asset.alt);
}
if (asset.kind === 'video') {
renderVideo(asset.url, asset.title);
}
if (asset.kind === 'html' || asset.kind === 'slide') {
renderLink(asset.url, asset.title);
}
});
}onError(handler)
Fires for SDK, transport, playback, or service errors.
voice.onError((error) => {
showVoiceError(error.message);
});onEvent(handler)
Low-level protocol event access for debugging.
Most website integrations should not need this.
voice.onEvent((event) => {
console.debug(event.type, event);
});UI Responsibilities
The website should provide:
- Start/end call controls.
- Optional manual interrupt control.
- Optional text input.
- Transcript display.
- Assistant text display.
- Asset rendering.
- Loading/error states.
The SDK does not render UI.
Server Requirements
The voice service must be configured with allowed origins for the website:
VOICE_PUBLIC_BASE_URL=https://voice.example.com
VOICE_PUBLIC_WS_URL=wss://voice.example.com/session
VOICE_ALLOWED_ORIGINS=https://www.rocketaeo.comLocal development:
VOICE_PUBLIC_BASE_URL=http://127.0.0.1:7071
VOICE_PUBLIC_WS_URL=ws://127.0.0.1:7071/session
VOICE_ALLOWED_ORIGINS=http://127.0.0.1:7080The service owns provider keys:
BOFUBOT_BASE_URL=...
BOFUBOT_API_KEY=...
AZURE_SPEECH_KEY=...Do not put provider keys in the browser.
Local Test Page
The repo includes an external-site simulation that uses this SDK:
npm run service:realtimeIn another terminal:
npm run example:external-site-directOpen:
http://127.0.0.1:7080