@talker-network/talker-sdk
v1.1.0
Published
Talker Web SDK for push-to-talk and real-time communication
Maintainers
Readme
Talker Web SDK
A JavaScript/TypeScript SDK for integrating Talker's push-to-talk (PTT) and real-time communication features into web applications.
Installation
npm install @talker-network/talker-sdkGetting Started
To use the Talker SDK, you need an SDK key. Contact [email protected] to obtain your SDK key.
Quick Start
The SDK provides two main classes:
TalkerAdmin- For backend/server-side operations (uses SDK key)TalkerClient- For frontend/client-side operations (uses user auth token)
Backend Setup (Node.js / Next.js API Route)
import { TalkerAdmin } from '@talker-network/talker-sdk';
// Initialize with your SDK key (keep secret on server!)
const admin = new TalkerAdmin({
sdkKey: process.env.TALKER_SDK_KEY!,
});
// Create a new user
const user = await admin.createUser({ name: 'John Doe' });
// Or get an existing user
const existingUser = await admin.setUser({ userId: 'existing-user-id' });
// Return credentials to frontend
return {
userAuthToken: user.userAuthToken,
userId: user.userId,
a_username: user.aUsername,
a_password: user.aPassword,
};Frontend Setup (Browser)
import { TalkerClient } from '@talker-network/talker-sdk';
// Get credentials from your backend
const { userAuthToken, userId, a_username, a_password } = await fetch('/api/auth').then(r => r.json());
// Initialize client - connection starts automatically
const talker = new TalkerClient({
userAuthToken,
userId,
a_username,
a_password,
});
// Monitor connection status
talker.on('connection_change', ({ connected, status }) => {
console.log(`Connection: ${status}`); // 'connected' | 'connecting' | 'disconnected'
});
// Listen to broadcast events
talker.on('broadcast_start', (data) => {
console.log(`${data.senderName} started speaking`);
});
talker.on('broadcast_end', (data) => {
console.log(`User ${data.senderId} stopped speaking`);
});
// PTT button handlers
const pttButton = document.getElementById('ptt-button');
pttButton.onmousedown = async () => {
try {
await talker.startTalking(channelId);
} catch (error) {
if (error.message === 'Establishing connection to servers') {
// Connection in progress, retry shortly
}
}
};
pttButton.onmouseup = () => talker.stopTalking();API Reference
TalkerAdmin (Backend)
Use this class on your backend server to manage users and channels.
Constructor
const admin = new TalkerAdmin({
sdkKey: string, // Required: Your SDK key (keep secret!)
baseUrl?: string, // Optional: API base URL
});User Management
| Method | Description | Returns |
|--------|-------------|---------|
| createUser(config) | Create a new SDK user | Promise<UserData> |
| setUser(config) | Get/switch to existing user | Promise<UserData> |
| getAllUsers() | Get all users in workspace | Promise<UserInfo[]> |
// Create user
const user = await admin.createUser({
name: 'John Doe', // Required: Display name
platform?: 'WEB', // Optional: 'WEB' | 'ANDROID' | 'IOS'
fcmToken?: 'token', // Optional: Firebase token for push notifications
});
// Set existing user
const user = await admin.setUser({
userId: 'user-id', // Required: Existing user ID
platform?: 'WEB',
fcmToken?: 'token',
});
// Returns UserData:
// {
// userId: string,
// name: string,
// userAuthToken: string, // Pass to TalkerClient
// aUsername: string, // Pass to TalkerClient
// aPassword: string, // Pass to TalkerClient
// }Channel Management
| Method | Description | Returns |
|--------|-------------|---------|
| createChannel(config) | Create a new channel | Promise<{ channelId, channelName }> |
| updateChannelName(channelId, name) | Rename a channel | Promise<void> |
| addParticipant(channelId, userId) | Add user to channel | Promise<void> |
| removeParticipant(channelId, userId) | Remove user from channel | Promise<void> |
| addAdmin(channelId, userId) | Make user a channel admin | Promise<void> |
| removeAdmin(channelId, userId) | Remove admin privileges | Promise<void> |
// Create channel
const channel = await admin.createChannel({
name: 'Team Chat', // Required: Channel name
participants: ['user1', 'user2'], // Required: User IDs
type?: 'group', // Optional: 'group' | 'direct'
});Credentials
| Method | Description | Returns |
|--------|-------------|---------|
| getCredentials() | Get SDK credentials | Promise<CredentialsData> |
| getWebRTCCredentials(aUsername, aPassword) | Get WebRTC credentials | Promise<WebRTCCredentials> |
TalkerClient (Frontend)
Use this class in your frontend application for real-time PTT communication.
Constructor
const client = new TalkerClient({
userAuthToken: string, // Required: From backend
userId: string, // Required: From backend (filters own broadcasts)
a_username: string, // Required: From backend
a_password: string, // Required: From backend
baseUrl?: string, // Optional: API base URL
socketUrl?: string, // Optional: Socket.IO URL
debug?: boolean, // Optional: Enable debug logging (deprecated)
loggerConfig?: { // Optional: Logger configuration
level?: LogLevel, // DEBUG, INFO, WARN, ERROR, OFF
enableConsole?: boolean,
customHandler?: (entry: LogEntry) => void,
},
});Note: Connection to servers starts automatically on initialization. Monitor status via getConnectionStatus() or the connection_change event.
Connection Methods
| Method | Description | Returns |
|--------|-------------|---------|
| getConnectionStatus() | Get connection status | { connected: boolean, status: 'connected' \| 'connecting' \| 'disconnected' } |
| isFullyConnected() | Check if ready for PTT | boolean |
| connect() | Manually trigger connection | void |
| reconnect() | Force reconnection | Promise<boolean> |
| disconnect() | Disconnect from all services | Promise<void> |
// Check connection
const { connected, status } = talker.getConnectionStatus();
// Manual reconnection
const success = await talker.reconnect();Push-to-Talk Methods
| Method | Description | Returns |
|--------|-------------|---------|
| startTalking(channelId) | Start recording and streaming | Promise<StartTalkingResult> |
| stopTalking(channelId?) | Stop recording | Promise<void> |
| isTalking() | Check if currently talking | boolean |
StartTalkingResult:
interface StartTalkingResult {
success: boolean;
code: 'success' | 'already_talking' | 'not_connected' | 'no_user_id' | 'channel_busy' | 'error';
message: string;
}| Code | Success | Description |
|------|---------|-------------|
| success | true | PTT started successfully |
| already_talking | true | Already in a PTT session (no action needed) |
| not_connected | false | Not connected, auto-reconnecting in background |
| no_user_id | false | User ID not set (pass in constructor or call setUserId) |
| channel_busy | false | Channel is busy, someone else is currently talking |
| error | false | Other error (check message) |
// PTT flow with deterministic handling
const result = await talker.startTalking('channel-id');
if (!result.success) {
switch (result.code) {
case 'not_connected':
// Show "Connecting..." UI, retry after connection_change event
break;
case 'no_user_id':
// Developer error - userId not provided
console.error(result.message);
break;
case 'channel_busy':
// Someone else is talking, show feedback to user
showToast('Channel is busy. Please wait...');
break;
case 'error':
// Show error to user
alert(result.message);
break;
}
}
// Release button
await talker.stopTalking();Audio Control Methods
| Method | Description | Returns |
|--------|-------------|---------|
| setSpeakerEnabled(enabled) | Enable/disable audio playback | void |
| setVolume(volume) | Set playback volume (0-1) | void |
| setMicrophoneEnabled(enabled) | Enable/disable microphone | void |
| getAudioLevel() | Get current mic input level (0-1) | number |
| unlockAudio() | Unlock audio context (required for iOS/Safari) | Promise<void> |
// Volume control
talker.setVolume(0.8);
// Mute speaker
talker.setSpeakerEnabled(false);
// Get mic level for UI visualization
const level = talker.getAudioLevel();Audio Device Selection
| Method | Description | Returns |
|--------|-------------|---------|
| getAudioInputDevices() | List available microphones | Promise<MediaDeviceInfo[]> |
| getAudioOutputDevices() | List available speakers | Promise<MediaDeviceInfo[]> |
| setAudioInputDevice(deviceId) | Select microphone | Promise<void> |
| setAudioOutputDevice(deviceId) | Select speaker | Promise<void> |
// List and select devices
const microphones = await talker.getAudioInputDevices();
await talker.setAudioInputDevice(microphones[1].deviceId);
const speakers = await talker.getAudioOutputDevices();
await talker.setAudioOutputDevice(speakers[0].deviceId);Playback Queue Methods
The SDK automatically queues incoming broadcasts and plays them in order.
| Method | Description | Returns |
|--------|-------------|---------|
| pauseQueue() | Pause queue (current playback continues) | void |
| resumeQueue() | Resume queue processing | void |
| stopPlaybackAndPause() | Stop current playback and pause queue | void |
| getPlaybackQueueLength() | Get number of pending broadcasts | number |
| getCurrentlyPlaying() | Get currently playing broadcast | BroadcastStartEvent \| null |
// Pause incoming broadcasts while user is busy
talker.pauseQueue();
// Later, resume
talker.resumeQueue();
// Need immediate silence
talker.stopPlaybackAndPause();
// Check queue status
console.log(`${talker.getPlaybackQueueLength()} broadcasts waiting`);Channel Methods
| Method | Description | Returns |
|--------|-------------|---------|
| getChannels() | Get channels list | Promise<Channel[]> |
| createChannel(config) | Create a channel | Promise<{ channelId, channelName }> |
| leaveChannel(channelId) | Leave a channel | Promise<void> |
| addParticipant(channelId, userId) | Add user to channel | Promise<void> |
| removeParticipant(channelId, userId) | Remove user | Promise<void> |
| addAdmin(channelId, userId) | Make user admin | Promise<void> |
| removeAdmin(channelId, userId) | Remove admin | Promise<void> |
| updateChannelName(channelId, name) | Rename channel | Promise<void> |
Example: Create Channel
// Create a group channel with participants
const channel = await talker.createChannel({
name: 'Field Team Alpha',
type: 'group',
participants: ['user-id-1', 'user-id-2', 'user-id-3'],
});
console.log(`Channel created: ${channel.channelName} (${channel.channelId})`);Example: Update Channel Members
// Add a participant
await talker.addParticipant('channel-id', 'new-user-id');
// Remove a participant
await talker.removeParticipant('channel-id', 'user-to-remove');
// Promote to admin
await talker.addAdmin('channel-id', 'user-id');
// Remove admin privileges
await talker.removeAdmin('channel-id', 'user-id');
// Listen for member changes
talker.on('channel_participant_added', (event) => {
console.log(`${event.userName} joined channel ${event.channelId}`);
});
talker.on('channel_participant_removed', (event) => {
console.log(`User ${event.userId} left channel ${event.channelId}`);
});Example: Join/Leave Channel
// Get available channels
const channels = await talker.getChannels();
console.log(`You are in ${channels.length} channels`);
channels.forEach((ch) => {
console.log(`- ${ch.name} (${ch.participants.length} members)`);
});
// Leave a channel
await talker.leaveChannel('channel-id');Live Status Methods
Live status indicates a user's availability for real-time audio in a channel. When live, users can send and receive audio instantly.
| Method | Description | Returns |
|--------|-------------|---------|
| goLive(channelId) | Set yourself as live in a channel | Promise<void> |
| dropFromLive(channelId) | Remove yourself from live | Promise<void> |
| isLive(channelId) | Check if you are live in a channel | boolean |
// Go live (available for real-time audio)
await talker.goLive('channel-id');
// Check live status
if (talker.isLive('channel-id')) {
console.log('You are live');
}
// Drop from live
await talker.dropFromLive('channel-id');
// Listen for live status changes
talker.on('channel_live_status', (event) => {
console.log(`Channel ${event.channelId} live: ${event.live}`);
});
talker.on('channel_participant_live_status', (event) => {
console.log(`User ${event.participantId} live: ${event.live}`);
});User Methods
| Method | Description | Returns |
|--------|-------------|---------|
| setUserId(userId) | Set current user ID | void |
| getUserId() | Get current user ID | string \| null |
| getAllUsers() | Get all workspace users | Promise<UserInfo[]> |
Stream Capturing
Access raw incoming audio for custom processing, visualization, speech-to-text, or recording.
| Event | Payload | Description |
|-------|---------|-------------|
| audio_received | ArrayBuffer | Raw PCM audio from incoming broadcast |
Audio Format: 16-bit signed PCM, 16kHz, mono
// Audio visualization
talker.on('audio_received', (pcmData: ArrayBuffer) => {
const samples = new Int16Array(pcmData);
const level = samples.reduce((sum, s) => sum + Math.abs(s), 0) / samples.length / 32768;
updateWaveform(level);
});
// Recording
const chunks: Int16Array[] = [];
talker.on('audio_received', (pcmData) => chunks.push(new Int16Array(pcmData)));
// Speech-to-text
talker.on('audio_received', (pcmData) => speechRecognizer.feedAudio(pcmData));Messaging
| Method | Description | Returns |
|--------|-------------|---------|
| sendMessage(channelId, content) | Send text/file message | Promise<void> |
// Send text message
await talker.sendMessage('channel-id', { text: 'Hello!' });
// Send file
await talker.sendMessage('channel-id', {
file: fileInput.files[0],
description: 'Photo from today',
});Events
Subscribe to events using client.on(event, handler) and unsubscribe with client.off(event, handler).
Connection Events
client.on('connection_change', (event: ConnectionChangeEvent) => {
// event.connected: boolean - true if fully connected
// event.status: 'connected' | 'connecting' | 'disconnected'
});Broadcast Events
client.on('broadcast_start', (event: BroadcastStartEvent) => {
// event.channelId: string
// event.senderId: string
// event.senderName: string
// event.messageId?: string
// event.timestamp: number
});
client.on('broadcast_end', (event: BroadcastEndEvent) => {
// event.channelId: string
// event.senderId: string
// event.timestamp: number
});
client.on('playback_progress', (event: PlaybackProgressEvent) => {
// event.duration: number (seconds elapsed)
});Channel Events
client.on('new_channel', (event: NewChannelEvent) => {
// event.channel: { channelId, channelName, channelType }
// event.timestamp: number
});
client.on('channel_name_update', (event: ChannelNameUpdateEvent) => {
// event.channelId: string
// event.newName: string
// event.timestamp: number
});
client.on('channel_participant_added', (event: ParticipantAddedEvent) => {
// event.channelId: string
// event.userId: string
// event.userName: string
// event.timestamp: number
});
client.on('channel_participant_removed', (event: ParticipantRemovedEvent) => {
// event.channelId: string
// event.userId: string
// event.timestamp: number
});
client.on('channel_admin_added', (event: AdminAddedEvent) => {
// event.channelId: string
// event.userId: string
// event.timestamp: number
});
client.on('channel_admin_removed', (event: AdminRemovedEvent) => {
// event.channelId: string
// event.userId: string
// event.timestamp: number
});User Events
client.on('new_sdk_user', (event: NewSdkUserEvent) => {
// event.userId: string
// event.userName: string
// event.timestamp: number
});Message Events
client.on('message', (event: MessageEvent) => {
// event.message: {
// messageId: string,
// channelId: string,
// senderId: string,
// senderName: string,
// type: string,
// text?: string,
// mediaUrl?: string,
// timestamp: string,
// }
});TypeScript Types
All types are exported from the package:
import type {
// User types
Platform, // 'WEB' | 'ANDROID' | 'IOS'
CreateUserConfig,
SetUserConfig,
UserData,
UserInfo,
// Channel types
ChannelType, // 'group' | 'direct'
CreateChannelConfig,
ChannelParticipant,
Channel,
// Event types
BroadcastStartEvent,
BroadcastEndEvent,
ConnectionChangeEvent,
ConnectionStatus,
NewChannelEvent,
ChannelNameUpdateEvent,
ParticipantAddedEvent,
ParticipantRemovedEvent,
AdminAddedEvent,
AdminRemovedEvent,
NewSdkUserEvent,
MessageEvent,
PlaybackProgressEvent,
// PTT result types
StartTalkingResult,
StartTalkingResultCode,
// Config types
TalkerAdminConfig,
TalkerClientConfig,
LoggerConfig,
// Credentials
CredentialsData,
WebRTCCredentials,
} from '@talker-network/talker-sdk';
// Logger utilities
import { LogLevel, logger } from '@talker-network/talker-sdk';Error Handling
Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| "Establishing connection to servers" | PTT called while reconnecting | Retry after connection_change event shows connected |
| "TalkerClient requires a userAuthToken" | Missing constructor parameter | Pass all required credentials from backend |
| "Failed to get credentials" | Invalid SDK key or network error | Check SDK key and network connectivity |
| "WebRTC data channel failed to open" | Connection timeout | Check network, retry connection |
Error Handling Pattern
// PTT with retry
async function startPTT(channelId: string) {
try {
await talker.startTalking(channelId);
} catch (error) {
if (error.message === 'Establishing connection to servers') {
// Wait for connection and retry
const unsubscribe = talker.on('connection_change', async ({ connected }) => {
if (connected) {
unsubscribe();
await talker.startTalking(channelId);
}
});
} else {
console.error('PTT error:', error);
}
}
}Connection Behavior
- Auto-connect on init:
TalkerClientautomatically connects when created - Auto-reconnect: Both Socket and WebRTC connections auto-reconnect if disconnected
- Credentials caching: WebRTC credentials are cached with 5-minute expiry buffer
- PTT auto-reconnect: If
startTalking()is called while disconnected, reconnection is triggered automatically
Connection States
| Status | Meaning |
|--------|---------|
| connected | Socket and WebRTC data channel both connected, ready for PTT |
| connecting | Connection is being established |
| disconnected | Not connected (will auto-reconnect) |
Logging
Default Behavior
The SDK automatically detects your environment:
| Environment | Console Output | Log Level | |-------------|---------------|-----------| | Development (localhost, ports 3000/5173/8080) | Enabled | DEBUG | | Production (npm package in prod) | Disabled | ERROR only |
This means no console spam in production by default.
Enable Logging in Production
To enable logging in production for debugging:
import { TalkerClient, LogLevel } from '@talker-network/talker-sdk';
const client = new TalkerClient({
userAuthToken,
userId,
a_username,
a_password,
loggerConfig: {
level: LogLevel.DEBUG, // TRACE, DEBUG, INFO, WARN, ERROR, OFF
enableConsole: true, // Force console output
},
});Custom Log Handler
Send logs to external services (works in both dev and production):
const client = new TalkerClient({
// ...credentials
loggerConfig: {
level: LogLevel.INFO,
customHandler: (entry) => {
if (entry.level >= LogLevel.ERROR) {
Sentry.captureMessage(entry.event, {
extra: { component: entry.component, data: entry.data },
});
}
},
},
});See LOGGING.md for detailed logging documentation.
React Example
import { useEffect, useState, useCallback, useRef } from 'react';
import { TalkerClient, LogLevel } from '@talker-network/talker-sdk';
function useTalker(credentials: Credentials | null) {
const [connectionStatus, setConnectionStatus] = useState<'disconnected' | 'connecting' | 'connected'>('disconnected');
const [isTalking, setIsTalking] = useState(false);
const clientRef = useRef<TalkerClient | null>(null);
useEffect(() => {
if (!credentials) return;
const client = new TalkerClient({
userAuthToken: credentials.userAuthToken,
userId: credentials.userId,
a_username: credentials.a_username,
a_password: credentials.a_password,
loggerConfig: { level: LogLevel.DEBUG, enableConsole: true },
});
clientRef.current = client;
client.on('connection_change', ({ status }) => {
setConnectionStatus(status);
});
client.on('broadcast_start', (data) => {
console.log(`${data.senderName} is speaking`);
});
return () => {
client.disconnect();
};
}, [credentials]);
const startTalking = useCallback(async (channelId: string) => {
if (!clientRef.current) return;
try {
await clientRef.current.startTalking(channelId);
setIsTalking(true);
} catch (error) {
console.error('Failed to start talking:', error);
}
}, []);
const stopTalking = useCallback(async () => {
if (!clientRef.current) return;
await clientRef.current.stopTalking();
setIsTalking(false);
}, []);
return { connectionStatus, isTalking, startTalking, stopTalking };
}Browser Support
| Browser | Minimum Version | |---------|-----------------| | Chrome | 80+ | | Firefox | 75+ | | Safari | 14+ | | Edge | 80+ |
iOS/Safari Notes
Call unlockAudio() on first user interaction to enable audio playback:
document.addEventListener('click', () => {
talker.unlockAudio();
}, { once: true });Troubleshooting
Connection Issues
- Check network connectivity
- Verify credentials are correct
- Enable DEBUG logging to see connection details
- Check browser console for errors
PTT Not Working
- Verify
isFullyConnected()returnstrue - Check microphone permissions
- Ensure
setUserId()was called - Enable DEBUG logging and check for errors
No Audio Playback
- Call
unlockAudio()on user interaction (iOS/Safari) - Check
setSpeakerEnabled(true) - Verify volume with
setVolume(1.0) - Check speaker device selection
Audio Permission Denied
try {
await navigator.mediaDevices.getUserMedia({ audio: true });
} catch (error) {
if (error.name === 'NotAllowedError') {
// User denied permission
}
}License
MIT
