npm package: @frayit/sdk
Description: Official Frayit SDK for chat and voice services with behavioral risk detection.
Author: Frayit
Latest version: 1.2.0

# @frayit/sdk

Official TypeScript SDK for Frayit chat moderation and voice services.

Server-side only for credentialed APIs. Do not expose `clientId` and `clientSecret` in browser bundles or game clients.

## Install

```bash
npm install @frayit/sdk
```

## Initialize

```ts
import { FrayitClient } from "@frayit/sdk"

const client = new FrayitClient({
  clientId: process.env.FRAYIT_CLIENT_ID!,
  clientSecret: process.env.FRAYIT_CLIENT_SECRET!,
  baseUrl: process.env.FRAYIT_BASE_URL!,
  voiceTimeoutMs: 1500,
})

await client.initialize()
```

Call `initialize()` once at service startup.

## Chat APIs

### evaluateChat

```ts
const result = await client.evaluateChat({
  player_id: "player-123",
  session_id: "session-1",
  channel_id: "global",
  message: "hello world",
})
```

### sendMessage

```ts
const result = await client.sendMessage({
  player_id: "player-123",
  session_id: "session-1",
  channel_id: "global",
  message: "gg wp",
})
```

### connectChat (Managed mode only)

```ts
const channel = await client.connectChat("player-123", "global", {
  onMessage: (event) => console.log(event),
  onError: (error) => console.error(error),
})

await channel.disconnect()
```

## Voice Backend APIs

Use these from your studio backend to issue LiveKit access and manage room state.

### Join voice channel

```ts
const join = await client.joinVoiceChannel({
  channel_id: "team-red",
  player_id: "player-123",
  session_id: "session-1",
  max_participants: 8,
})

console.log(join.livekit_url, join.livekit_token)
```

### Server mute or unmute player

```ts
await client.setServerMute({
  channel_id: "team-red",
  player_id: "player-123",
  muted: true,
})
```

### Get room state

```ts
const room = await client.getVoiceRoomState("team-red")
console.log(room.participant_count, room.participants)
```

### Leave and close room

```ts
await client.leaveVoiceChannel({
  channel_id: "team-red",
  player_id: "player-123",
})

await client.closeVoiceChannel("team-red")
```

## Client-Side Voice State Helpers

These helpers are local state only. They do not call network APIs.

```ts
client.setSelfMuted(true)
client.setSelfDeafened(false)
client.setPeerMutedLocally("player-456", true)

const canPlay = client.shouldPlayPeerAudio("player-456")
const isSelfMuted = client.isSelfMuted
const muteMap = client.getPeerMuteMapSnapshot()
```

## Production Architecture for Voice

1. Your backend calls `joinVoiceChannel`.
2. Backend sends only `livekit_url` and `livekit_token` to the game client.
3. Game client connects directly to LiveKit.
4. Backend uses `setServerMute`, `getVoiceRoomState`, `leaveVoiceChannel`, and `closeVoiceChannel` for control and moderation.

## Config Options

| Option | Default | Description |
|---|---|---|
| `chatTimeoutMs` | `100` | Timeout for `evaluateChat` and `sendMessage`. |
| `voiceTimeoutMs` | `1500` | Timeout for voice APIs (`join`, `leave`, `close`, `room`, `mute`). |
| `tokenRequestTimeoutMs` | `5000` | Timeout for token endpoint. |
| `tokenPrefetchWindowMs` | `1800000` | Refresh token this many ms before expiry. |
| `idleRefreshCheckIntervalMs` | `30000` | Periodic background refresh check interval. |
| `tokenFailureBackoffMs` | `20000` | Backoff after failed refresh when token still valid. |
| `maxRetries` | `2` | Retry count for transient failures. |
| `retryBaseDelayMs` | `200` | Exponential backoff base delay. |
| `circuitBreakerFailureThreshold` | `5` | Consecutive failures before circuit opens (chat APIs). |
| `circuitBreakerOpenDurationMs` | `20000` | Open-state duration before half-open probe. |

## Fail-Open Behavior (Chat APIs)

When Frayit is unreachable, `evaluateChat()` and `sendMessage()` return fail-open results so gameplay is not blocked.

## Dispose

```ts
client.dispose()
```