# Error Handling Guide

This guide covers how to handle errors, connection issues, and edge cases when building applications with the Hiyve SDK. Each section includes the relevant hooks, error shapes, and practical patterns you can apply directly.

---

## 1. Connection Errors

The `useConnection()` hook exposes the current connection state, including any error that occurred during room creation or joining.

```tsx
import { useConnection } from '@hiyve/react';

function ConnectionStatus() {
  const { isConnected, isConnecting, error, createRoom } = useConnection();

  if (error) {
    return (
      <div>
        <p>Connection failed: {error}</p>
        <button onClick={() => createRoom({ room: 'my-room', userId: 'user-1' })}>
          Retry
        </button>
      </div>
    );
  }

  if (isConnecting) {
    return <p>Connecting...</p>;
  }

  return <p>{isConnected ? 'Connected' : 'Not connected'}</p>;
}
```

**Key details:**

- `error` is a `string | null`. When non-null, it contains the error message from the server or client.
- `isConnecting` is `true` while `createRoom()` or `joinRoom()` is in progress.
- To retry, call `createRoom()` or `joinRoom()` again with the same parameters. The SDK resets the error state on a new attempt.

---

## 2. Global Error Callback

You can capture all SDK errors in a single place by passing `onError` to the provider. This is useful for centralized logging or error reporting.

**React:**

```tsx
import { HiyveProvider } from '@hiyve/react';

function App() {
  return (
    <HiyveProvider
      onError={(err) => {
        console.error('[Hiyve SDK]', err.message);
        // Send to your error reporting service
        errorReporter.captureException(err);
      }}
    >
      <VideoRoom />
    </HiyveProvider>
  );
}
```

**Framework-agnostic:**

```ts
import { HiyveStore } from '@hiyve/core';

const store = new HiyveStore({
  onError: (err) => {
    console.error('[Hiyve SDK]', err.message);
  },
});
```

The `onError` callback receives an `Error` object and fires for connection failures, room errors, and server-side errors. It does not fire for errors that are already handled internally (such as duplicate-user retries).

---

## 3. Room Flow Error States

The `useRoomFlow()` hook consolidates connection, waiting room, and room state into a single `screen` value. Use it to drive your top-level routing instead of checking multiple boolean flags.

```tsx
import { useRoomFlow } from '@hiyve/react';

function App() {
  const { screen } = useRoomFlow();

  switch (screen) {
    case 'lobby':
      return <JoinForm />;
    case 'connecting':
      return <LoadingSpinner label="Connecting..." />;
    case 'waiting-for-host':
      return <WaitingForHostScreen />;
    case 'waiting-room':
      return <WaitingRoomScreen />;
    case 'waiting-room-rejected':
      return <RejectedScreen />;
    case 'in-room':
      return <VideoRoom />;
  }
}
```

**Screen states explained:**

| Screen | Meaning | What to show |
|---|---|---|
| `lobby` | Not connected (default state) | Join form or landing page |
| `connecting` | `createRoom` or `joinRoom` in progress | Loading spinner |
| `waiting-for-host` | Guest joined before the host started the room. The SDK polls every 10 seconds automatically and transitions to `in-room` when the host arrives. | "Waiting for host" message |
| `waiting-room` | Guest is waiting for the host to admit them | "Please wait" message |
| `waiting-room-rejected` | Host denied the guest entry | Rejection message with option to leave |
| `in-room` | Active room session | Video grid and controls |

`useRoomFlow()` also returns individual boolean flags (`isConnecting`, `isInRoom`, `isWaitingForHost`, `isWaitingForAdmission`, `wasRejected`) if you need them for conditional logic outside the switch.

---

## 4. Join Token Errors

The `useJoinToken()` hook handles the full lifecycle of token-based room joining and returns structured error objects.

```tsx
import { useJoinToken } from '@hiyve/react';
import {
  TOKEN_NOT_FOUND,
  TOKEN_EXPIRED,
  INVALID_PASSWORD,
  USER_NOT_AUTHORIZED,
  ROOM_NOT_ACTIVE,
} from '@hiyve/core';

function JoinWithToken({ token, region }: { token: string; region: string }) {
  const {
    roomName,
    isRoomActive,
    isValidating,
    isValidated,
    error,
    requiresPassword,
    validateToken,
    joinRoom,
    clearError,
  } = useJoinToken(token, region, { autoValidate: true });

  function getErrorMessage() {
    if (!error) return null;
    switch (error.code) {
      case TOKEN_NOT_FOUND:
        return 'This invite link is invalid. Please check the link and try again.';
      case TOKEN_EXPIRED:
        return 'This invite link has expired. Please ask for a new one.';
      case INVALID_PASSWORD:
        return 'Incorrect password. Please try again.';
      case USER_NOT_AUTHORIZED:
        return 'You are not authorized to join this room.';
      case ROOM_NOT_ACTIVE:
        return 'The host has not started the meeting yet. Please wait.';
      default:
        return error.message;
    }
  }

  return (
    <div>
      {error && (
        <div role="alert">
          <p>{getErrorMessage()}</p>
          {requiresPassword && (
            <PasswordInput onSubmit={(pw) => validateToken(pw)} />
          )}
        </div>
      )}

      {isValidated && !isRoomActive && (
        <p>Waiting for the host to start the meeting...</p>
      )}

      {isValidated && isRoomActive && (
        <button onClick={() => joinRoom('user-1')}>
          Join {roomName}
        </button>
      )}
    </div>
  );
}
```

**Error shape:**

```ts
interface JoinTokenError {
  code: 'TOKEN_NOT_FOUND' | 'TOKEN_EXPIRED' | 'INVALID_PASSWORD'
      | 'USER_NOT_AUTHORIZED' | 'ROOM_NOT_ACTIVE' | null;
  message: string;
  requiresPassword: boolean;
  httpStatus?: number;
}
```

**Automatic polling:** When a token validates successfully but the room is not yet active (`ROOM_NOT_ACTIVE`), the hook automatically polls the server every 10 seconds. When the room becomes active, `isRoomActive` flips to `true` and you can enable the join button.

---

## 5. Recording and Streaming Errors

Both `useRecording()` and `useStreaming()` expose an `error` field and a `clearError()` action. The `startRecording()` method returns a boolean indicating success or failure. The `startStreaming()` method returns void and throws on failure.

```tsx
import { useRecording } from '@hiyve/react';

function RecordingControls() {
  const {
    isRecording,
    isRecordingStarting,
    error,
    startRecording,
    stopRecording,
    clearError,
  } = useRecording();

  async function handleStartRecording() {
    const success = await startRecording({ transcribe: true });
    if (!success) {
      // error state is already set on the hook — show it in the UI
      showToast('Recording failed to start. See error details.');
    }
  }

  return (
    <div>
      {error && (
        <div role="alert">
          <p>{error}</p>
          <button onClick={clearError}>Dismiss</button>
        </div>
      )}
      <button
        onClick={isRecording ? stopRecording : handleStartRecording}
        disabled={isRecordingStarting}
      >
        {isRecording ? 'Stop Recording' : 'Start Recording'}
      </button>
    </div>
  );
}
```

The `useStreaming()` hook uses a different pattern -- `startStreaming()` throws on failure instead of returning a boolean:

```tsx
import { useStreaming } from '@hiyve/react';

function StreamingControls() {
  const { isStreaming, error, startStreaming, stopStreaming, clearError } = useStreaming();

  async function handleStartStreaming() {
    try {
      await startStreaming({ rtmpUrl: 'rtmp://...' });
    } catch {
      showToast('Streaming failed to start.');
    }
  }

  // ... same error UI pattern as recording
}
```

---

## 6. Audio Device Errors

The `useAudioProcessing()` hook exposes `feedbackDetected` and `audioValidation` to help you surface audio issues to users.

```tsx
import { useAudioProcessing } from '@hiyve/react';

function AudioWarnings() {
  const { feedbackDetected, audioValidation } = useAudioProcessing();

  return (
    <>
      {feedbackDetected && (
        <div role="alert">
          Audio feedback detected. Try using headphones or lowering your speaker volume.
        </div>
      )}

      {audioValidation && audioValidation.severity === 'error' && (
        <div role="alert">
          <p>Audio issue detected:</p>
          <ul>
            {audioValidation.issues.map((issue, i) => (
              <li key={i}>{issue}</li>
            ))}
          </ul>
          <p>Suggestions:</p>
          <ul>
            {audioValidation.recommendations.map((rec, i) => (
              <li key={i}>{rec}</li>
            ))}
          </ul>
        </div>
      )}
    </>
  );
}
```

**`audioValidation` shape:**

- `issues` -- array of detected problems (e.g., "No audio input detected")
- `recommendations` -- array of suggested fixes
- `severity` -- `'warning'` or `'error'`

Show warnings when `feedbackDetected` is `true` or when `audioValidation.severity` is `'error'`. For `'warning'` severity, consider a less intrusive indicator.

---

## 7. Media Permission Errors

The SDK handles `getUserMedia` errors internally with progressive constraint relaxation. If the browser denies camera or microphone access, the SDK automatically retries with relaxed constraints (for example, requesting a lower resolution). If all attempts fail, the `error` field on `useConnection()` will contain the failure message.

```tsx
function PermissionErrorUI() {
  const { error } = useConnection();

  if (error && error.includes('Permission denied')) {
    return (
      <div>
        <p>Camera or microphone access was denied.</p>
        <p>Please allow access in your browser settings and try again.</p>
      </div>
    );
  }

  return null;
}
```

**Screen sharing:** When a user cancels the screen share picker dialog, the SDK handles this gracefully -- it does not throw an error or update any error state. No special handling is needed.

---

## 8. Network Disconnection

When the WebRTC transport disconnects (network loss, server restart, etc.), the `isConnected` field from `useConnection()` goes to `false`. Use this alongside `isInRoom` from `useRoom()` to show a reconnecting overlay.

```tsx
import { useConnection } from '@hiyve/react';
import { useRoom } from '@hiyve/react';

function ReconnectingOverlay() {
  const { isConnected } = useConnection();
  const { isInRoom } = useRoom();

  if (!isConnected && isInRoom) {
    return (
      <div className="reconnecting-overlay">
        <p>Connection lost. Attempting to reconnect...</p>
      </div>
    );
  }

  return null;
}
```

**Duplicate user handling:** If a user opens a second tab or reconnects while a stale session still exists on the server, the SDK detects the "same name already connected" error and automatically retries after a brief delay. This is handled internally and not surfaced as an error to your application.

---

## 9. Error Boundary

The `@hiyve/utilities` package exports an `ErrorBoundary` component that catches React render errors in its subtree. Wrap your video room in an `ErrorBoundary` to prevent a component crash from taking down the entire application.

**Static fallback:**

```tsx
import { ErrorBoundary } from '@hiyve/utilities';

function App() {
  return (
    <ErrorBoundary fallback={<p>Something went wrong. Please refresh the page.</p>}>
      <VideoRoom />
    </ErrorBoundary>
  );
}
```

**Render function fallback with reset and error logging:**

```tsx
import { ErrorBoundary } from '@hiyve/utilities';

function App() {
  return (
    <ErrorBoundary
      fallback={(error, reset) => (
        <div>
          <p>An error occurred: {error.message}</p>
          <button onClick={reset}>Try Again</button>
          <button onClick={() => window.location.reload()}>Reload Page</button>
        </div>
      )}
      onError={(error, errorInfo) => {
        console.error('Render error:', error);
        errorReporter.captureException(error, { extra: errorInfo });
      }}
    >
      <VideoRoom />
    </ErrorBoundary>
  );
}
```

**Props:**

| Prop | Type | Description |
|---|---|---|
| `fallback` | `ReactNode \| (error: Error, reset: () => void) => ReactNode` | Content to render when an error is caught |
| `onError` | `(error: Error, errorInfo: ErrorInfo) => void` | Callback when an error is caught |
| `children` | `ReactNode` | Content to render when no error has occurred |

---

## 10. Component-Level onError

UI components like `ControlBar` accept an `onError` callback that fires when an internal operation fails. The callback receives the error and the name of the operation that failed.

```tsx
import { ControlBar } from '@hiyve/react-ui';

function VideoRoom() {
  return (
    <ControlBar
      onError={(error, operation) => {
        showToast(`${operation} failed: ${error.message}`);
        console.error(`[ControlBar] ${operation}:`, error);
      }}
    />
  );
}
```

**Common operation names passed to onError:**

- `toggleAudio` -- microphone toggle failed
- `toggleVideo` -- camera toggle failed
- `startScreenShare` / `stopScreenShare` -- screen share failed
- `startRecording` / `stopRecording` -- recording control failed
- `setVideoDevice` / `setAudioInputDevice` -- device switch failed

This callback is useful for showing user-facing toast notifications without needing to monitor every individual hook's error state.

---

## Summary

| Error Source | Hook / API | Error Shape | Recovery Pattern |
|---|---|---|---|
| Connection | `useConnection()` | `error: string \| null` | Show message, retry `createRoom`/`joinRoom` |
| Global | `HiyveProvider onError` | `Error` object | Log or report centrally |
| Room flow | `useRoomFlow()` | `screen: RoomFlowScreen` | Switch on `screen` value |
| Join token | `useJoinToken()` | `JoinTokenError { code, message, requiresPassword }` | Show message by `error.code` |
| Recording | `useRecording()` | `error: string \| null` | Check `startRecording()` return value, call `clearError()` |
| Streaming | `useStreaming()` | `error: string \| null` | Wrap `startStreaming()` in try/catch, call `clearError()` |
| Audio | `useAudioProcessing()` | `audioValidation: { issues, recommendations, severity }` | Show warnings to user |
| Permissions | `useConnection()` | `error: string \| null` | Prompt user to allow access |
| Network | `useConnection()` | `isConnected: boolean` | Show reconnecting overlay |
| Render crashes | `ErrorBoundary` | `Error` object | Show fallback UI, offer reset |
| Component ops | `ControlBar onError` | `(Error, string)` | Show toast notification |
