# @hiyve/core

Framework-agnostic state store for Hiyve video conferencing applications. Use with any UI framework (React, Vue, Svelte) or vanilla JavaScript.

## Installation

```bash
npm install @hiyve/core @hiyve/rtc-client
```

## Quick Start

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

// Zero-config -- defaults to POST /api/generate-room-token
const store = new HiyveStore();

// Subscribe to state changes
store.subscribe(() => {
  console.log('State changed:', store.getState());
});

// Subscribe to a specific slice
store.subscribeToSlice('room', () => {
  const room = store.getSlice('room');
  console.log('Room:', room.room?.name, 'In room:', room.isInRoom);
});

// Create or join a room
await store.createRoom('my-room', 'user-123');

// Control media
await store.toggleAudio();
await store.toggleVideo();

// Clean up
store.destroy();
```

## API

### HiyveStore

The main class. Create one instance per session.

#### Constructor Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `generateRoomToken` | `() => Promise<string>` | auto | Returns a Hiyve room token. Defaults to `POST /api/generate-room-token` when using `@hiyve/admin` server middleware |
| `localVideoElementId` | `string` | `'local-video'` | ID of the HTML video element for local preview |
| `connectOptions` | `{ videoDeviceId?, audioDeviceId? }` | `{}` | Initial device IDs |
| `persistDeviceChanges` | `boolean` | `false` | Persist device selections across sessions |
| `onError` | `(error: Error) => void` | - | Error callback |

#### Subscription Methods

| Method | Returns | Description |
|--------|---------|-------------|
| `subscribe(listener)` | `() => void` | Subscribe to all state changes. Returns unsubscribe function. |
| `subscribeToSlice(name, listener)` | `() => void` | Subscribe to a specific slice. Returns unsubscribe function. |
| `getState()` | `HiyveStoreState` | Get full state snapshot |
| `getSlice(name)` | Slice value | Get a specific slice snapshot |
| `getClient()` | `Client \| null` | Get the raw rtc-client instance |

#### State Slices

| Slice | Key Fields |
|-------|------------|
| `connection` | `isConnected`, `isConnecting`, `error` |
| `room` | `room`, `isOwner`, `isInRoom` |
| `client` | `client` (Client instance) |
| `participants` | `participants` (Map), `localUserId` |
| `localMedia` | `isAudioMuted`, `isVideoMuted`, `isScreenSharing` |
| `recording` | `isRecording`, `recordingId`, `recordingStartTime` |
| `streaming` | `isStreaming`, `streamingId`, `streamingUrl` |
| `transcription` | `isTranscribing`, `transcriptions` (array) |
| `chat` | `messages` (array), `unreadCount` |
| `waitingRoom` | `waitingUsers`, `isWaitingForAdmission`, `wasRejected` |
| `waitForHost` | `isWaiting`, `roomName`, `timeout` |
| `audioProcessing` | `feedbackDetected`, `audioValidation`, `gainLevel` |
| `layout` | `dominantSpeaker` |
| `handRaise` | `raisedHands` (Map) |
| `aiChat` | `messages` (array) |
| `intelligence` | `isActive`, `coachingEnabled`, `coachingVariant`, `realtimeHints`, `talkRatio`, `currentTopic`, `topicShifts` |
| `storedRooms` | `rooms`, `isLoading`, `error`, `lastFetchedAt` |
| `activeRooms` | `rooms`, `isConnected`, `isLoading`, `error` |
| `userFiles` | `files`, `isLoading`, `error`, `lastFetchedAt` |

#### Action Methods

**Room Management:** `createRoom()`, `joinRoom()`, `joinRoomWithToken()`, `getRoomInfoFromToken()`, `leaveRoom()`

**Media Controls:** `toggleAudio()`, `toggleVideo()`, `toggleOutputMute()`, `startScreenShare()`, `stopScreenShare()`

**Devices:** `setVideoDevice()`, `setAudioInputDevice()`, `setAudioOutputDevice()`

**Recording:** `startRecording()`, `stopRecording()`, `clearRecordingError()`

**Streaming:** `startStreaming()`, `stopStreaming()`, `switchStreamingUser()`, `getStreamingUrls()`, `clearStreamingError()`

**Transcription:** `startTranscription()`, `stopTranscription()`, `enrichTranscription()`

**Chat:** `sendMessage()`, `sendDataMessage()`, `clearUnread()`, `loadChatHistory()`

**Waiting Room:** `admitUser()`, `rejectUser()`

**Remote Mute:** `remoteMuteAudio()`, `remoteMuteVideo()`, `muteRemoteOutput()`

**Layout:** `setDominant()`, `toggleHandRaised()`, `lowerAllHands()`, `setGain()`

**Intelligence:** `enableCoaching()`, `disableCoaching()`, `updateCoachingData()`, `dismissHint()`

**Lifecycle:** `destroy()`

#### Stored Rooms

Persistent rooms that can be created, listed, edited, and deleted outside of a live session (dashboard/lobby context).

| Method | Description |
|--------|-------------|
| `fetchStoredRooms(userId, options?)` | Fetch all stored rooms for a user. Options: `{ consumerOnly?, offlineOnly? }` |
| `addStoredRoom(options, userId)` | Create a new stored room |
| `updateStoredRoom(alias, updates, userId)` | Update an existing stored room by alias |
| `deleteStoredRoom(alias, userId)` | Delete a stored room by alias |
| `getStoredRoom(alias, userId)` | Fetch a single stored room by alias |
| `clearStoredRooms()` | Reset stored rooms to initial state (e.g. on user change or logout) |
| `clearStoredRoomsError()` | Clear the stored rooms error |

#### Active Rooms

Live room discovery -- owners advertise rooms so other users can find and join them in real time.

| Method | Description |
|--------|-------------|
| `advertiseRoom(cloudClient, options)` | Advertise a room for discovery |
| `removeAdvertisedRoom(cloudClient, roomName)` | Stop advertising a room |
| `connectActiveRooms(streamUrl)` | Open an SSE stream for real-time active room updates |
| `disconnectActiveRooms()` | Close the SSE stream |
| `fetchActiveRooms(cloudClient, userId?)` | One-shot fetch of active rooms |

#### File Operations

Manage user files across all rooms without requiring an active room connection.

| Method | Description |
|--------|-------------|
| `fetchUserFiles(userId)` | Fetch all files for a user across all rooms |
| `deleteUserFile(fileId)` | Delete a file by ID |
| `renameUserFile(fileId, newFilename)` | Rename a file |
| `moveUserFile(fileId, newLocation)` | Move a file to a different folder |
| `shareUserFile(fileId, userIds, sharedRoom?)` | Share a file with other users |
| `createUserFolder(location, roomName?)` | Create a folder |
| `deleteUserFolder(location)` | Delete a folder |
| `getUserFileUrl(fileId)` | Get a download URL for a file |
| `uploadUserFile(file, location, resourceType?)` | Upload a new file |
| `clearUserFiles()` | Reset user files to initial state (e.g. on user change or logout) |
| `clearUserFilesError()` | Clear the user files error |

#### Constants

Join token error codes for handling `useJoinToken` / `getRoomInfoFromToken` failures:

| Constant | Value | Description |
|----------|-------|-------------|
| `TOKEN_NOT_FOUND` | `'TOKEN_NOT_FOUND'` | Token does not exist (HTTP 404) |
| `TOKEN_EXPIRED` | `'TOKEN_EXPIRED'` | Token has expired (HTTP 410) |
| `INVALID_PASSWORD` | `'INVALID_PASSWORD'` | Incorrect room password (HTTP 401) |
| `USER_NOT_AUTHORIZED` | `'USER_NOT_AUTHORIZED'` | User is not allowed to join via this token (HTTP 403) |
| `ROOM_NOT_ACTIVE` | `'ROOM_NOT_ACTIVE'` | Room is not active yet (HTTP 503) |

#### Utilities

| Function | Description |
|----------|-------------|
| `resolveTabsConfig(config, mode)` | Resolves a `StoredRoomTabsConfig` into a flat `TabsFeatureConfig` for `'offline'` or `'live'` mode. Handles structured, legacy flat, and null configs. |
| `cleanUserId(userId)` | Sanitizes a user ID string for use with the signaling server. Re-exported from `@hiyve/rtc-client`. |

## Type Exports

All types are exported from the package entry point. Key types by category:

| Type | Description |
|------|-------------|
| `HiyveStoreOptions` | Constructor options for `HiyveStore` |
| `HiyveStoreState` | Full state snapshot returned by `getState()` |
| `SliceName` | Union of all valid slice names for `subscribeToSlice()` / `getSlice()` |
| `RoomInfo` | Room metadata (name, owner, participants count, etc.) |
| `ConnectionState` | Connection status fields (`isConnected`, `isConnecting`, `error`) |
| `RoomState` | Room state fields (`room`, `isOwner`, `isInRoom`) |
| `Participant` | Remote participant with streams and mute status |
| `ParticipantsState` | Participants map and local user ID |
| `LocalMediaState` | Local audio/video mute and screen share status |
| `RecordingState` | Recording status, ID, start time, and error |
| `StreamingState` | Streaming status, ID, URL, and error |
| `TranscriptionEntry` | Single transcription segment with speaker and timestamp |
| `TranscriptionState` | Transcription status and entries array |
| `ChatMessage` | Chat message with sender, text, and timestamp |
| `ChatState` | Chat messages array and unread count |
| `AiChatMessage` | AI assistant chat message |
| `WaitingRoomUser` | User waiting for admission |
| `WaitingRoomState` | Waiting room users and admission status |
| `WaitForHostState` | Wait-for-host screen state |
| `StoredRoom` | Persistent room configuration |
| `StoredRoomUpdates` | Fields for updating a stored room |
| `CreateStoredRoomOptions` | Options for creating a stored room |
| `StoredRoomsState` | Stored rooms list with loading/error state |
| `StoredRoomTabsConfig` | Tab configuration for stored rooms |
| `ActiveRoom` | Discovered active room |
| `ActiveRoomsState` | Active rooms list with connection and loading state |
| `AdvertiseRoomOptions` | Options for advertising a room for discovery |
| `FileUploadOptions` | Options for uploading a file |
| `FileUploadResult` | Result of a file upload (ID, URL) |
| `FileModifyOptions` | Options for renaming or moving a file |
| `FileClient` | File operations interface used by components |
| `FileShareEntry` | File sharing recipient entry |
| `UserFile` | User file metadata |
| `UserFilesState` | User files list with loading/error state |
| `JoinTokenErrorCode` | Union of join token error code strings |
| `JoinTokenError` | Error object for join token failures |
| `GetRoomInfoFromTokenOptions` | Options for `getRoomInfoFromToken()` |
| `RoomInfoFromToken` | Room info returned from a join token |
| `ClientState` | Client instance state |
| `ClientActions` | Available client actions |
| `ClientContextValue` | Combined state and actions for context consumers |

### Ghost Camera Utilities

Utilities for working with additional camera streams (ghost cameras):

| Export | Description |
|--------|-------------|
| `GHOST_CAMERA_DELIMITER` | Delimiter string used in ghost camera user IDs |
| `isGhostCamera(userId)` | Check if a user ID represents a ghost camera |
| `getGhostCameraLabel(userId)` | Extract the display label from a ghost camera user ID |
| `getGhostCameraOwner(userId)` | Extract the owner's user ID from a ghost camera user ID |
| `buildGhostCameraUserId(ownerId, label)` | Build a ghost camera user ID from an owner ID and label |

## Framework Integration

The `subscribe()` + `getSnapshot()` contract is the universal primitive for framework integration:

**React** (via `@hiyve/react`):
```typescript
const room = useSyncExternalStore(
  cb => store.subscribeToSlice('room', cb),
  () => store.getSlice('room'),
);
```

**Vue**:
```typescript
const room = ref(store.getSlice('room'));
const unsub = store.subscribeToSlice('room', () => {
  room.value = store.getSlice('room');
});
onUnmounted(unsub);
```

**Svelte**:
```typescript
const room = readable(store.getSlice('room'), set =>
  store.subscribeToSlice('room', () => set(store.getSlice('room')))
);
```

### Debug Logging

The `debug` export provides opt-in logging for troubleshooting. Logging is disabled by default.

```typescript
import { debug } from '@hiyve/core';

// Enable at runtime
debug.enable();

// Check if enabled
debug.isEnabled(); // true

// Disable
debug.disable();
```

Debug mode can also be enabled via:
- Environment variable: `HIYVE_DEBUG=true` (build time)
- Browser console: `window.__HIYVE_DEBUG__ = true` (runtime)
- LocalStorage: `localStorage.setItem('HIYVE_DEBUG', 'true')` (persistent)

## Requirements

- `@hiyve/rtc-client` as a peer dependency

## Related Packages

- `@hiyve/react` - React bindings with hooks and ClientProvider
