Error Recovery Example
Handle all error cases gracefully with user-friendly messaging and recovery options.
Complete Example
components/RobustVoiceUI.tsx
'use client'
import { useState, useEffect, useCallback } from 'react'
import {
VoiceSessionProvider,
useVoiceSession,
useConnectionState,
useNetworkStatus,
VoiceAgentError,
VoiceAgentErrorCode
} from '@vocobase/voice-client-sdk'
export function RobustVoiceUI({ apiKey, agentName }: { apiKey: string; agentName: string }) {
return (
<VoiceSessionProvider apiKey={apiKey} agentName={agentName} maxReconnectAttempts={5}>
<VoiceInterface />
</VoiceSessionProvider>
)
}
function VoiceInterface() {
const { connect, disconnect, error } = useVoiceSession()
const { isConnected, isConnecting, isReconnecting } = useConnectionState()
const { isOnline } = useNetworkStatus()
const [retryCount, setRetryCount] = useState(0)
useEffect(() => {
if (isConnected) setRetryCount(0)
}, [isConnected])
const isRetryableError = (code: VoiceAgentErrorCode) => {
return ['NETWORK_ERROR', 'CONNECTION_FAILED', 'CONCURRENCY_LIMIT'].includes(code)
}
// Offline state
if (!isOnline) {
return <OfflineState />
}
// Reconnecting state
if (isReconnecting) {
return <ReconnectingState />
}
// Error state
if (error) {
return (
<ErrorState
error={error}
retryCount={retryCount}
onRetry={() => {
setRetryCount(prev => prev + 1)
connect()
}}
canRetry={isRetryableError(error.code)}
/>
)
}
// Normal state
return isConnected ? (
<ConnectedState onDisconnect={disconnect} />
) : (
<IdleState onConnect={connect} isConnecting={isConnecting} />
)
}
function ErrorState({ error, retryCount, onRetry, canRetry }) {
const configs = {
MIC_PERMISSION_DENIED: {
icon: '🎤',
title: 'Microphone Access Blocked',
message: 'We need microphone access to enable voice calls.',
action: { label: 'Refresh Page', onClick: () => window.location.reload() }
},
INSUFFICIENT_CREDITS: {
icon: '💳',
title: 'Out of Credits',
message: 'Your account needs more credits.',
action: { label: 'Purchase Credits', href: 'https://dashboard.vocobase.com/billing' }
},
CONCURRENCY_LIMIT: {
icon: '📞',
title: 'All Lines Busy',
message: 'Maximum concurrent calls reached.',
action: canRetry ? { label: 'Try Again', onClick: onRetry } : null
},
NETWORK_ERROR: {
icon: '🌐',
title: 'Network Error',
message: 'Could not reach our servers.',
action: canRetry ? { label: `Retry (${3 - retryCount} left)`, onClick: onRetry } : null
},
CONNECTION_FAILED: {
icon: '🔌',
title: 'Connection Failed',
message: 'Could not establish a voice connection.',
action: canRetry ? { label: 'Try Again', onClick: onRetry } : null
}
}
const config = configs[error.code] || {
icon: '❌',
title: 'Error',
message: error.message,
action: { label: 'Try Again', onClick: onRetry }
}
return (
<div className="error-state">
<div className="icon">{config.icon}</div>
<h3>{config.title}</h3>
<p>{config.message}</p>
{config.action && (
config.action.href ? (
<a href={config.action.href}>{config.action.label}</a>
) : (
<button onClick={config.action.onClick}>{config.action.label}</button>
)
)}
</div>
)
}
function OfflineState() {
return (
<div className="state-card">
<div>📡</div>
<h3>No Internet Connection</h3>
<p>Voice calls require an active internet connection.</p>
</div>
)
}
function ReconnectingState() {
return (
<div className="state-card">
<h3>Connection Lost</h3>
<p>Attempting to reconnect...</p>
</div>
)
}
function ConnectedState({ onDisconnect }) {
return (
<div className="state-card">
<h3>Connected</h3>
<button onClick={onDisconnect}>End Call</button>
</div>
)
}
function IdleState({ onConnect, isConnecting }) {
return (
<div className="state-card">
<h3>Ready to Talk</h3>
<button onClick={onConnect} disabled={isConnecting}>
{isConnecting ? 'Connecting...' : 'Start Call'}
</button>
</div>
)
}Key Patterns
1. Categorize errors by recoverability
const isRetryableError = (code) => {
return ['NETWORK_ERROR', 'CONNECTION_FAILED', 'CONCURRENCY_LIMIT'].includes(code)
}2. Limit retry attempts
if (retryCount >= 3) {
// Stop retrying, show different UI
}3. Provide actionable guidance
Each error type has specific instructions that help users resolve the issue.
Last updated on