Skip to main content

Error Handling

Proper error handling is crucial for building reliable WebSocket integrations with BlackBox agents. This guide covers common error scenarios, handling strategies, and best practices for graceful degradation.

Error Types

BlackBox WebSocket connections can encounter several types of errors:

Connection Errors

Errors during WebSocket connection establishment or maintenance. Common Causes:
  • Network connectivity issues
  • Invalid token or authentication failure
  • Server unavailable or overloaded
  • Firewall/proxy blocking WebSocket connections
  • Origin restrictions (CORS)
Error Indicators:
  • Connection status changes to error
  • WebSocket onerror event fired
  • Connection closes unexpectedly

Message Errors

Errors in message format, validation, or processing. Common Causes:
  • Invalid message format
  • Missing required fields
  • Type mismatches
  • Malformed JSON
Error Indicators:
  • error message type received
  • Message parsing failures
  • Validation errors

Agent Errors

Errors from the agent or conversation processing. Common Causes:
  • Agent configuration issues
  • LLM provider errors
  • Tool execution failures
  • Conversation timeouts
Error Indicators:
  • error message type with agent error details
  • event message with failedOpen name
  • Conversation fails to start

WebRTC Errors

Errors specific to voice call WebRTC connections. Common Causes:
  • Microphone permission denied
  • WebRTC connection failure
  • SDP negotiation failure
  • ICE candidate issues
Error Indicators:
  • WebRTC connection state becomes failed
  • SDP handling errors
  • Audio stream failures

Error Handling Strategies

1. Connection Error Handling

Handle connection errors with retry logic:
class RobustConnection {
  private ws: WebSocket | null = null;
  private retryCount = 0;
  private maxRetries = 3;
  private retryDelay = 1000; // Start with 1 second

  connect() {
    const wsUrl = 'wss://blackbox.dasha.ai/api/v1/ws/webCall?token=YOUR_WEB_INTEGRATION_TOKEN';
    this.ws = new WebSocket(wsUrl);

    this.ws.onopen = () => {
      // Reset retry count on successful connection
      this.retryCount = 0;
      this.retryDelay = 1000;
      
      // Send initialization
      this.ws!.send(JSON.stringify({
        type: 'initialize',
        timestamp: new Date().toISOString(),
        request: {
          callType: 'chat',
          additionalData: {}
        }
      }));
    };

    this.ws.onerror = () => {
      this.handleConnectionError();
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.type === 'error') {
        this.handleError(message);
      }
    };
  }

  private handleConnectionError() {
    if (this.retryCount < this.maxRetries) {
      this.retryCount++;
      const delay = this.retryDelay * Math.pow(2, this.retryCount - 1); // Exponential backoff
      
      console.log(`Connection error. Retrying in ${delay}ms (attempt ${this.retryCount}/${this.maxRetries})...`);
      
      setTimeout(() => {
        this.ws?.close();
        this.connect();
      }, delay);
    } else {
      console.error('Max retries reached. Connection failed.');
      this.onMaxRetriesReached();
    }
  }

  private onMaxRetriesReached() {
    // Notify user, show error message, etc.
    alert('Unable to connect to agent. Please check your connection and try again.');
  }
}

2. Error Message Parsing

Parse and handle error messages from the server:
function parseErrorMessage(error: unknown): {
  message: string;
  code?: string;
  details?: unknown;
} {
  // Handle Error objects with parsedContent
  if (error instanceof Error && 'parsedContent' in error) {
    const parsed = (error as any).parsedContent;
    return {
      message: parsed.message || error.message || 'Unknown error',
      code: parsed.code,
      details: parsed.details
    };
  }
  
  // Handle error messages from WebSocket
  if (typeof error === 'object' && error !== null) {
    const err = error as any;
    if (err.type === 'error') {
      return {
        message: err.data?.message || err.message || 'Unknown error',
        code: err.data?.code,
        details: err.data?.details
      };
    }
  }
  
  // Handle string errors
  if (typeof error === 'string') {
    return { message: error };
  }
  
  // Fallback
  return {
    message: error instanceof Error ? error.message : 'Unknown error occurred'
  };
}

// Usage
const handlers: AgentConnectionHandlers = {
  onError: (error) => {
    const parsed = parseErrorMessage(error);
    
    console.error('Error:', parsed.message);
    if (parsed.code) {
      console.error('Error code:', parsed.code);
    }
    
    // Show user-friendly error message
    showErrorMessage(parsed.message);
    
    // Handle specific error codes
    switch (parsed.code) {
      case 'AUTH_ERROR':
        handleAuthError();
        break;
      case 'RATE_LIMIT':
        handleRateLimit();
        break;
      case 'AGENT_ERROR':
        handleAgentError(parsed.details);
        break;
    }
  }
};

3. Graceful Degradation

Implement fallback behavior when errors occur:
class ChatWithFallback {
  private connection: AgentConnection | null = null;
  private fallbackMode = false;

  connect() {
    try {
      this.connection = new AgentConnection(
        {
          serverUrl: 'https://blackbox.dasha.ai',
          token: 'YOUR_WEB_INTEGRATION_TOKEN', // Replace with your token
          callType: 'chat'
        },
        {
          onStatusChange: (status) => {
            if (status === 'error') {
              this.enableFallbackMode();
            } else if (status === 'open') {
              this.disableFallbackMode();
            }
          },
          onError: (error) => {
            this.handleError(error);
          }
        }
      );

      this.connection.start();
    } catch (error) {
      console.error('Failed to create connection:', error);
      this.enableFallbackMode();
    }
  }

  private enableFallbackMode() {
    this.fallbackMode = true;
    showMessage('Connection issue detected. Using fallback mode.');
    // Switch to REST API polling, show offline message, etc.
  }

  private disableFallbackMode() {
    this.fallbackMode = false;
    hideFallbackMessage();
  }

  sendMessage(text: string) {
    if (this.fallbackMode) {
      // Use REST API or show offline message
      this.sendViaRestAPI(text);
    } else {
      this.connection?.sendTextMessage(text);
    }
  }

  private async sendViaRestAPI(text: string) {
    // Fallback to REST API if WebSocket fails
    try {
      const response = await fetch('https://blackbox.dasha.ai/api/v1/chat', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ message: text })
      });
      
      const data = await response.json();
      this.onMessageReceived(data.response);
    } catch (error) {
      showErrorMessage('Unable to send message. Please try again later.');
    }
  }
}

4. Error Recovery

Implement recovery strategies for different error types:
class ErrorRecovery {
  private connection: AgentConnection | null = null;

  handleError(error: unknown) {
    const parsed = parseErrorMessage(error);
    
    // Categorize error
    const category = this.categorizeError(parsed);
    
    // Apply recovery strategy
    switch (category) {
      case 'network':
        this.recoverNetworkError();
        break;
      case 'authentication':
        this.recoverAuthError();
        break;
      case 'agent':
        this.recoverAgentError();
        break;
      case 'rate_limit':
        this.recoverRateLimit();
        break;
      default:
        this.recoverGenericError();
    }
  }

  private categorizeError(error: { code?: string; message: string }): string {
    if (error.code === 'AUTH_ERROR' || error.message.includes('token')) {
      return 'authentication';
    }
    if (error.code === 'RATE_LIMIT') {
      return 'rate_limit';
    }
    if (error.message.includes('network') || error.message.includes('connection')) {
      return 'network';
    }
    if (error.code?.startsWith('AGENT_')) {
      return 'agent';
    }
    return 'generic';
  }

  private recoverNetworkError() {
    // Retry with exponential backoff
    setTimeout(() => {
      this.connection?.start();
    }, this.getRetryDelay());
  }

  private recoverAuthError() {
    // Refresh token or prompt for re-authentication
    this.refreshToken().then(() => {
      this.connection?.stop();
      this.connect();
    });
  }

  private recoverAgentError() {
    // Notify user, log error, potentially switch to different agent
    showMessage('Agent error occurred. Please try again.');
    logError('Agent error', error);
  }

  private recoverRateLimit() {
    // Wait and retry after rate limit window
    const waitTime = 60000; // 1 minute
    showMessage(`Rate limit reached. Retrying in ${waitTime / 1000} seconds...`);
    setTimeout(() => {
      this.connection?.start();
    }, waitTime);
  }

  private recoverGenericError() {
    // Generic recovery: log and notify
    console.error('Generic error:', error);
    showMessage('An error occurred. Please try again.');
  }
}

Common Error Scenarios

Invalid Token

Error: AUTH_ERROR or “Invalid token” Handling:
onError: (error) => {
  const parsed = parseErrorMessage(error);
  if (parsed.code === 'AUTH_ERROR' || parsed.message.includes('token')) {
    // Token may be expired or invalid
    showError('Authentication failed. Please refresh your session.');
    // Redirect to login or refresh token
    refreshToken();
  }
}

Network Failure

Error: Connection fails or times out Handling:
onStatusChange: (status) => {
  if (status === 'error') {
    // Check if it's a network issue
    if (!navigator.onLine) {
      showError('No internet connection. Please check your network.');
    } else {
      showError('Connection failed. Retrying...');
      // Implement retry logic
    }
  }
}

Agent Unavailable

Error: Agent not responding or unavailable Handling:
onError: (error) => {
  const parsed = parseErrorMessage(error);
  if (parsed.code === 'AGENT_UNAVAILABLE' || parsed.message.includes('agent')) {
    showError('Agent is currently unavailable. Please try again later.');
    // Optionally queue message or switch to different agent
  }
}

Rate Limiting

Error: Too many requests Handling:
onError: (error) => {
  const parsed = parseErrorMessage(error);
  if (parsed.code === 'RATE_LIMIT') {
    const retryAfter = parsed.details?.retryAfter || 60;
    showError(`Rate limit reached. Please wait ${retryAfter} seconds.`);
    // Disable input for retryAfter seconds
    disableInput(retryAfter);
  }
}

WebRTC Errors

Error: Voice call connection failures Handling:
// Handle WebRTC connection errors
peerConnection.onconnectionstatechange = () => {
  if (peerConnection.connectionState === 'failed') {
    console.error('WebRTC connection failed');
    
    // Attempt to restart ICE
    try {
      peerConnection.restartIce();
    } catch (error) {
      console.error('Failed to restart ICE:', error);
      // Fallback: end call and show error
      endCall();
      showError('Voice connection failed. Please try again.');
    }
  }
};

// Handle SDP errors
try {
  await peerConnection.setRemoteDescription({ type: 'offer', sdp: sdpInvite });
} catch (error) {
  console.error('SDP error:', error);
  // Send error to server
  connection.send({
    type: 'error',
    message: 'Failed to process SDP offer',
    timestamp: new Date().toISOString()
  });
  // End call gracefully
  endCall();
}

Error Logging and Monitoring

Logging Errors

Log errors for debugging and monitoring:
function logError(error: unknown, context?: Record<string, unknown>) {
  const errorInfo = {
    timestamp: new Date().toISOString(),
    error: parseErrorMessage(error),
    context: context || {},
    userAgent: navigator.userAgent,
    url: window.location.href
  };
  
  // Log to console in development
  if (process.env.NODE_ENV === 'development') {
    console.error('Error logged:', errorInfo);
  }
  
  // Send to error tracking service (Sentry, LogRocket, etc.)
  if (window.Sentry) {
    window.Sentry.captureException(error, {
      contexts: { custom: context }
    });
  }
  
  // Or send to your own logging endpoint
  fetch('/api/errors', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(errorInfo)
  }).catch(err => {
    console.error('Failed to log error:', err);
  });
}

// Usage
const handlers: AgentConnectionHandlers = {
  onError: (error) => {
    logError(error, {
      connectionStatus: connection.ConnectionStatus,
      messageCount: connection.Messages.length
    });
    
    // Show user-friendly error
    showErrorMessage('An error occurred. Our team has been notified.');
  }
};

Error Monitoring

Monitor error rates and patterns:
class ErrorMonitor {
  private errors: Array<{ timestamp: number; type: string; message: string }> = [];
  private maxErrors = 100;

  recordError(error: unknown) {
    const parsed = parseErrorMessage(error);
    this.errors.push({
      timestamp: Date.now(),
      type: parsed.code || 'unknown',
      message: parsed.message
    });
    
    // Keep only recent errors
    if (this.errors.length > this.maxErrors) {
      this.errors.shift();
    }
    
    // Check error rate
    this.checkErrorRate();
  }

  private checkErrorRate() {
    const recentErrors = this.errors.filter(
      e => Date.now() - e.timestamp < 60000 // Last minute
    );
    
    if (recentErrors.length > 10) {
      console.warn('High error rate detected:', recentErrors.length);
      // Alert monitoring system, disable features, etc.
    }
  }

  getErrorStats() {
    const errorsByType = this.errors.reduce((acc, err) => {
      acc[err.type] = (acc[err.type] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
    
    return {
      total: this.errors.length,
      byType: errorsByType,
      recent: this.errors.filter(e => Date.now() - e.timestamp < 300000).length
    };
  }
}

User-Friendly Error Messages

Translate technical errors to user-friendly messages:
function getUserFriendlyMessage(error: unknown): string {
  const parsed = parseErrorMessage(error);
  
  // Map error codes to user-friendly messages
  const errorMessages: Record<string, string> = {
    'AUTH_ERROR': 'Your session has expired. Please refresh the page.',
    'RATE_LIMIT': 'Too many requests. Please wait a moment and try again.',
    'AGENT_UNAVAILABLE': 'The agent is currently unavailable. Please try again later.',
    'NETWORK_ERROR': 'Network connection issue. Please check your internet connection.',
    'TIMEOUT': 'Request timed out. Please try again.',
    'INVALID_MESSAGE': 'Invalid message format. Please try again.',
    'WEBRTC_ERROR': 'Voice call connection failed. Please try again or use text chat.'
  };
  
  // Return mapped message or generic fallback
  return errorMessages[parsed.code || ''] || 
         parsed.message || 
         'An unexpected error occurred. Please try again.';
}

// Usage
onError: (error) => {
  const message = getUserFriendlyMessage(error);
  showErrorMessage(message);
}

Best Practices

1. Always Handle Errors

Never leave error handlers empty:
// ❌ Bad
onError: (error) => {
  // Empty handler
}

// ✅ Good
onError: (error) => {
  logError(error);
  showErrorMessage(getUserFriendlyMessage(error));
  // Implement recovery if appropriate
}

2. Provide User Feedback

Always inform users when errors occur:
onError: (error) => {
  // Log for debugging
  console.error('Error:', error);
  
  // Show user-friendly message
  showNotification({
    type: 'error',
    message: getUserFriendlyMessage(error),
    duration: 5000
  });
}

3. Implement Retry Logic

Retry transient errors automatically:
async function sendWithRetry(
  fn: () => Promise<void>,
  maxRetries: number = 3
): Promise<void> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await fn();
      return; // Success
    } catch (error) {
      if (i === maxRetries - 1) throw error; // Last attempt failed
      
      // Wait before retry (exponential backoff)
      await new Promise(resolve => 
        setTimeout(resolve, Math.pow(2, i) * 1000)
      );
    }
  }
}

4. Validate Before Sending

Validate data before sending to prevent errors:
function sendMessage(text: string) {
  // Validate input
  if (!text || text.trim().length === 0) {
    showError('Please enter a message.');
    return;
  }
  
  if (text.length > 1000) {
    showError('Message is too long. Please keep it under 1000 characters.');
    return;
  }
  
  // Check connection status
  if (connection.ConnectionStatus !== 'open') {
    showError('Not connected. Please wait...');
    return;
  }
  
  // Send message
  connection.sendTextMessage(text);
}

Next Steps