Error Handling

Handle errors gracefully and provide a good user experience when things go wrong.

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

PTT Error Handling

The startTalking() method returns a result object instead of throwing errors:

TypeScript
async function handlePTT(channelId: string) {
  const result = await talker.startTalking(channelId);

  if (result.success) {
    // PTT started successfully
    updateUI('talking');
    return;
  }

  // Handle different error codes
  switch (result.code) {
    case 'not_connected':
      // SDK will auto-reconnect
      showToast('Connecting to server...');
      waitForConnection();
      break;

    case 'no_user_id':
      // Developer error - missing userId
      console.error('Configuration error:', result.message);
      break;

    case 'already_talking':
      // Not really an error - already in PTT session
      break;

    case 'channel_busy':
      // Someone else is talking on this channel
      showToast('Channel is busy. Please wait for the current speaker to finish.');
      break;

    case 'error':
      // Show error to user
      showError(result.message);
      break;
  }
}

Connection Error Recovery

TypeScript
class ConnectionManager {
  private pendingAction: (() => Promise) | null = null;

  constructor(private talker: TalkerClient) {
    talker.on('connection_change', ({ connected }) => {
      if (connected && this.pendingAction) {
        this.pendingAction();
        this.pendingAction = null;
      }
    });
  }

  async startTalkingWithRetry(channelId: string) {
    const result = await this.talker.startTalking(channelId);

    if (!result.success && result.code === 'not_connected') {
      // Queue the action to retry when connected
      this.pendingAction = () => this.talker.startTalking(channelId);
      showUI('connecting');
    }
  }
}

Microphone Permission Errors

TypeScript
async function requestMicrophoneAccess(): Promise {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    // Success - stop the stream
    stream.getTracks().forEach(track => track.stop());
    return true;
  } catch (error) {
    handleMicrophoneError(error);
    return false;
  }
}

function handleMicrophoneError(error: Error) {
  switch (error.name) {
    case 'NotAllowedError':
      showError(
        'Microphone access denied. Please allow microphone access in your browser settings.',
        { showSettingsButton: true }
      );
      break;

    case 'NotFoundError':
      showError(
        'No microphone found. Please connect a microphone and try again.'
      );
      break;

    case 'NotReadableError':
      showError(
        'Microphone is in use by another application. Please close other apps using the microphone.'
      );
      break;

    case 'OverconstrainedError':
      showError(
        'Could not find a suitable microphone. Please try a different device.'
      );
      break;

    default:
      showError(`Microphone error: ${error.message}`);
  }
}

Network Error Handling

TypeScript
// Monitor online/offline status
window.addEventListener('online', () => {
  showToast('Back online');
  talker.reconnect();
});

window.addEventListener('offline', () => {
  showToast('You are offline. PTT will resume when connected.');
});

// Check before important operations
async function performAction() {
  if (!navigator.onLine) {
    showError('No internet connection. Please check your network.');
    return;
  }

  // Proceed with action...
}

API Error Handling

TypeScript
async function createChannel(name: string, participants: string[]) {
  try {
    const channel = await talker.createChannel({
      name,
      participants,
      workspaceId: 'workspace-id',
    });
    return channel;
  } catch (error) {
    if (error.response) {
      // Server responded with error
      switch (error.response.status) {
        case 400:
          showError('Invalid channel configuration');
          break;
        case 401:
          showError('Session expired. Please log in again.');
          handleLogout();
          break;
        case 403:
          showError('You do not have permission to create channels');
          break;
        case 429:
          showError('Too many requests. Please wait and try again.');
          break;
        default:
          showError('Server error. Please try again later.');
      }
    } else if (error.request) {
      // Network error
      showError('Network error. Please check your connection.');
    } else {
      // Other error
      showError('An error occurred: ' + error.message);
    }
    throw error;
  }
}

Global Error Handler

TypeScript
class TalkerErrorHandler {
  private errorCallbacks: ((error: Error) => void)[] = [];

  onError(callback: (error: Error) => void) {
    this.errorCallbacks.push(callback);
    return () => {
      this.errorCallbacks = this.errorCallbacks.filter(cb => cb !== callback);
    };
  }

  handleError(error: Error, context?: string) {
    console.error(`[Talker Error${context ? ` - ${context}` : ''}]:`, error);

    // Log to error tracking service
    this.logToService(error, context);

    // Notify all listeners
    this.errorCallbacks.forEach(cb => cb(error));
  }

  private logToService(error: Error, context?: string) {
    // Send to Sentry, LogRocket, etc.
    if (typeof Sentry !== 'undefined') {
      Sentry.captureException(error, {
        extra: { context },
      });
    }
  }
}

// Usage
const errorHandler = new TalkerErrorHandler();

errorHandler.onError((error) => {
  showToast(`Error: ${error.message}`, 'error');
});

User-Friendly Error Messages

TypeScript
const ERROR_MESSAGES: Record = {
  // Connection errors
  'not_connected': 'Connecting to server. Please wait...',
  'connection_timeout': 'Connection timed out. Retrying...',
  'connection_failed': 'Could not connect to server. Please check your internet.',

  // PTT errors
  'no_user_id': 'User not authenticated. Please refresh the page.',
  'already_talking': 'Already in a call.',
  'channel_busy': 'Channel is busy. Someone else is talking.',
  'microphone_denied': 'Microphone access denied. Please enable in settings.',

  // General errors
  'network_error': 'Network error. Please check your connection.',
  'server_error': 'Server error. Please try again later.',
  'unknown_error': 'Something went wrong. Please try again.',
};

function getErrorMessage(code: string, fallback?: string): string {
  return ERROR_MESSAGES[code] || fallback || ERROR_MESSAGES['unknown_error'];
}

// Usage
const result = await talker.startTalking(channelId);
if (!result.success) {
  showToast(getErrorMessage(result.code, result.message));
}

Error UI Component

React Component
interface ErrorBannerProps {
  error: string | null;
  onRetry?: () => void;
  onDismiss?: () => void;
}

function ErrorBanner({ error, onRetry, onDismiss }: ErrorBannerProps) {
  if (!error) return null;

  return (
    <div className="error-banner">
      <span className="error-icon">⚠️</span>
      <span className="error-message">{error}</span>
      <div className="error-actions">
        {onRetry && (
          <button onClick={onRetry}>Retry</button>
        )}
        {onDismiss && (
          <button onClick={onDismiss}>Dismiss</button>
        )}
      </div>
    </div>
  );
}
Best Practices
  • Always provide actionable error messages
  • Include retry options when appropriate
  • Log errors for debugging but show user-friendly messages
  • Handle offline scenarios gracefully