mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 18:11:27 -07:00
update skills
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# Cloudflare RealtimeKit
|
||||
|
||||
Expert guidance for building real-time video and audio applications using **Cloudflare RealtimeKit** - a comprehensive SDK suite for adding customizable live video and voice to web or mobile applications.
|
||||
|
||||
## Overview
|
||||
|
||||
RealtimeKit is Cloudflare's SDK suite built on Realtime SFU, abstracting WebRTC complexity with fast integration, pre-built UI components, global performance (300+ cities), and production features (recording, transcription, chat, polls).
|
||||
|
||||
**Use cases**: Team meetings, webinars, social video, audio calls, interactive plugins
|
||||
|
||||
## Core Concepts
|
||||
|
||||
- **App**: Workspace grouping meetings, participants, presets, recordings. Use separate Apps for staging/production
|
||||
- **Meeting**: Re-usable virtual room. Each join creates new **Session**
|
||||
- **Session**: Live meeting instance. Created on first join, ends after last leave
|
||||
- **Participant**: User added via REST API. Returns `authToken` for client SDK. **Do not reuse tokens**
|
||||
- **Preset**: Reusable permission/UI template (permissions, meeting type, theme). Applied at participant creation
|
||||
- **Peer ID** (`id`): Unique per session, changes on rejoin
|
||||
- **Participant ID** (`userId`): Persistent across sessions
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create App & Meeting (Backend)
|
||||
|
||||
```bash
|
||||
# Create app
|
||||
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<account_id>/realtime/kit/apps' \
|
||||
-H 'Authorization: Bearer <api_token>' \
|
||||
-d '{"name": "My RealtimeKit App"}'
|
||||
|
||||
# Create meeting
|
||||
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<account_id>/realtime/kit/<app_id>/meetings' \
|
||||
-H 'Authorization: Bearer <api_token>' \
|
||||
-d '{"title": "Team Standup"}'
|
||||
|
||||
# Add participant
|
||||
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<account_id>/realtime/kit/<app_id>/meetings/<meeting_id>/participants' \
|
||||
-H 'Authorization: Bearer <api_token>' \
|
||||
-d '{"name": "Alice", "preset_name": "host"}'
|
||||
# Returns: { authToken }
|
||||
```
|
||||
|
||||
### 2. Client Integration
|
||||
|
||||
**React**:
|
||||
```tsx
|
||||
import { RtkMeeting } from '@cloudflare/realtimekit-react-ui';
|
||||
|
||||
function App() {
|
||||
return <RtkMeeting authToken="<participant_auth_token>" onLeave={() => {}} />;
|
||||
}
|
||||
```
|
||||
|
||||
**Core SDK**:
|
||||
```typescript
|
||||
import RealtimeKitClient from '@cloudflare/realtimekit';
|
||||
|
||||
const meeting = new RealtimeKitClient({ authToken: '<token>', video: true, audio: true });
|
||||
await meeting.join();
|
||||
```
|
||||
|
||||
## Reading Order
|
||||
|
||||
| Task | Files |
|
||||
|------|-------|
|
||||
| Quick integration | README only |
|
||||
| Custom UI | README → patterns → api |
|
||||
| Backend setup | README → configuration |
|
||||
| Debug issues | gotchas |
|
||||
| Advanced features | patterns → api |
|
||||
|
||||
## RealtimeKit vs Realtime SFU
|
||||
|
||||
| Choose | When |
|
||||
|--------|------|
|
||||
| **RealtimeKit** | Need pre-built UI, fast integration, React/Angular/HTML |
|
||||
| **Realtime SFU** | Building from scratch, custom WebRTC, full control |
|
||||
|
||||
RealtimeKit is built on Realtime SFU but abstracts WebRTC complexity with UI components and SDKs.
|
||||
|
||||
## Which Package?
|
||||
|
||||
Need pre-built meeting UI?
|
||||
- React → `@cloudflare/realtimekit-react-ui` (`<RtkMeeting>`)
|
||||
- Angular → `@cloudflare/realtimekit-angular-ui`
|
||||
- HTML/Vanilla → `@cloudflare/realtimekit-ui`
|
||||
|
||||
Need custom UI?
|
||||
- Core SDK → `@cloudflare/realtimekit` (RealtimeKitClient) - full control
|
||||
|
||||
Need raw WebRTC control?
|
||||
- See `realtime-sfu/` reference
|
||||
|
||||
## In This Reference
|
||||
|
||||
- [Configuration](./configuration.md) - Setup, installation, wrangler config
|
||||
- [API](./api.md) - Meeting object, REST API, SDK methods
|
||||
- [Patterns](./patterns.md) - Common workflows, code examples
|
||||
- [Gotchas](./gotchas.md) - Common issues, troubleshooting
|
||||
|
||||
## See Also
|
||||
|
||||
- [Workers](../workers/) - Backend integration
|
||||
- [D1](../d1/) - Meeting metadata storage
|
||||
- [R2](../r2/) - Recording storage
|
||||
- [KV](../kv/) - Session management
|
||||
|
||||
## Reference Links
|
||||
|
||||
- **Official Docs**: https://developers.cloudflare.com/realtime/realtimekit/
|
||||
- **API Reference**: https://developers.cloudflare.com/api/resources/realtime_kit/
|
||||
- **Examples**: https://github.com/cloudflare/realtimekit-web-examples
|
||||
- **Dashboard**: https://dash.cloudflare.com/?to=/:account/realtime/kit
|
||||
212
.agents/skills/cloudflare-deploy/references/realtimekit/api.md
Normal file
212
.agents/skills/cloudflare-deploy/references/realtimekit/api.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# RealtimeKit API Reference
|
||||
|
||||
Complete API reference for Meeting object, REST endpoints, and SDK methods.
|
||||
|
||||
## Meeting Object API
|
||||
|
||||
### `meeting.self` - Local Participant
|
||||
|
||||
```typescript
|
||||
// Properties: id, userId, name, audioEnabled, videoEnabled, screenShareEnabled, audioTrack, videoTrack, screenShareTracks, roomJoined, roomState
|
||||
// Methods
|
||||
await meeting.self.enableAudio() / disableAudio() / enableVideo() / disableVideo() / enableScreenShare() / disableScreenShare()
|
||||
await meeting.self.setName("Name") // Before join only
|
||||
await meeting.self.setDevice(device)
|
||||
const devices = await meeting.self.getAllDevices() / getAudioDevices() / getVideoDevices() / getSpeakerDevices()
|
||||
// Events: 'roomJoined', 'audioUpdate', 'videoUpdate', 'screenShareUpdate', 'deviceUpdate', 'deviceListUpdate'
|
||||
meeting.self.on('roomJoined', () => {})
|
||||
meeting.self.on('audioUpdate', ({ audioEnabled, audioTrack }) => {})
|
||||
```
|
||||
|
||||
### `meeting.participants` - Remote Participants
|
||||
|
||||
**Collections**:
|
||||
```typescript
|
||||
meeting.participants.joined / active / waitlisted / pinned // Maps
|
||||
const participants = meeting.participants.joined.toArray()
|
||||
const count = meeting.participants.joined.size()
|
||||
const p = meeting.participants.joined.get('peer-id')
|
||||
```
|
||||
|
||||
**Participant Properties**:
|
||||
```typescript
|
||||
participant.id / userId / name
|
||||
participant.audioEnabled / videoEnabled / screenShareEnabled
|
||||
participant.audioTrack / videoTrack / screenShareTracks
|
||||
```
|
||||
|
||||
**Events**:
|
||||
```typescript
|
||||
meeting.participants.joined.on('participantJoined', (participant) => {})
|
||||
meeting.participants.joined.on('participantLeft', (participant) => {})
|
||||
```
|
||||
|
||||
### `meeting.meta` - Metadata
|
||||
```typescript
|
||||
meeting.meta.meetingId / meetingTitle / meetingStartedTimestamp
|
||||
```
|
||||
|
||||
### `meeting.chat` - Chat
|
||||
```typescript
|
||||
meeting.chat.messages // Array
|
||||
await meeting.chat.sendTextMessage("Hello") / sendImageMessage(file)
|
||||
meeting.chat.on('chatUpdate', ({ message, messages }) => {})
|
||||
```
|
||||
|
||||
### `meeting.polls` - Polling
|
||||
```typescript
|
||||
meeting.polls.items // Array
|
||||
await meeting.polls.create(question, options, anonymous, hideVotes)
|
||||
await meeting.polls.vote(pollId, optionIndex)
|
||||
```
|
||||
|
||||
### `meeting.plugins` - Collaborative Apps
|
||||
```typescript
|
||||
meeting.plugins.all // Array
|
||||
await meeting.plugins.activate(pluginId) / deactivate()
|
||||
```
|
||||
|
||||
### `meeting.ai` - AI Features
|
||||
```typescript
|
||||
meeting.ai.transcripts // Live transcriptions (when enabled in Preset)
|
||||
```
|
||||
|
||||
### Core Methods
|
||||
```typescript
|
||||
await meeting.join() // Emits 'roomJoined' on meeting.self
|
||||
await meeting.leave()
|
||||
```
|
||||
|
||||
## TypeScript Types
|
||||
|
||||
```typescript
|
||||
import type { RealtimeKitClient, States, UIConfig, Participant } from '@cloudflare/realtimekit';
|
||||
|
||||
// Main interface
|
||||
interface RealtimeKitClient {
|
||||
self: SelfState; // Local participant (id, userId, name, audioEnabled, videoEnabled, roomJoined, roomState)
|
||||
participants: { joined, active, waitlisted, pinned }; // Reactive Maps
|
||||
chat: ChatNamespace; // messages[], sendTextMessage(), sendImageMessage()
|
||||
polls: PollsNamespace; // items[], create(), vote()
|
||||
plugins: PluginsNamespace; // all[], activate(), deactivate()
|
||||
ai: AINamespace; // transcripts[]
|
||||
meta: MetaState; // meetingId, meetingTitle, meetingStartedTimestamp
|
||||
join(): Promise<void>;
|
||||
leave(): Promise<void>;
|
||||
}
|
||||
|
||||
// Participant (self & remote share same shape)
|
||||
interface Participant {
|
||||
id: string; // Peer ID (changes on rejoin)
|
||||
userId: string; // Persistent participant ID
|
||||
name: string;
|
||||
audioEnabled: boolean;
|
||||
videoEnabled: boolean;
|
||||
screenShareEnabled: boolean;
|
||||
audioTrack: MediaStreamTrack | null;
|
||||
videoTrack: MediaStreamTrack | null;
|
||||
screenShareTracks: MediaStreamTrack[];
|
||||
}
|
||||
```
|
||||
|
||||
## Store Architecture
|
||||
|
||||
RealtimeKit uses reactive store (event-driven updates, live Maps):
|
||||
|
||||
```typescript
|
||||
// Subscribe to state changes
|
||||
meeting.self.on('audioUpdate', ({ audioEnabled, audioTrack }) => {});
|
||||
meeting.participants.joined.on('participantJoined', (p) => {});
|
||||
|
||||
// Access current state synchronously
|
||||
const isAudioOn = meeting.self.audioEnabled;
|
||||
const count = meeting.participants.joined.size();
|
||||
```
|
||||
|
||||
**Key principles:** State updates emit events after changes. Use `.toArray()` sparingly. Collections are live Maps.
|
||||
|
||||
## REST API
|
||||
|
||||
Base: `https://api.cloudflare.com/client/v4/accounts/{account_id}/realtime/kit/{app_id}`
|
||||
|
||||
### Meetings
|
||||
```bash
|
||||
GET /meetings # List all
|
||||
GET /meetings/{meeting_id} # Get details
|
||||
POST /meetings # Create: {"title": "..."}
|
||||
PATCH /meetings/{meeting_id} # Update: {"title": "...", "record_on_start": true}
|
||||
```
|
||||
|
||||
### Participants
|
||||
```bash
|
||||
GET /meetings/{meeting_id}/participants # List all
|
||||
GET /meetings/{meeting_id}/participants/{participant_id} # Get details
|
||||
POST /meetings/{meeting_id}/participants # Add: {"name": "...", "preset_name": "...", "custom_participant_id": "..."}
|
||||
PATCH /meetings/{meeting_id}/participants/{participant_id} # Update: {"name": "...", "preset_name": "..."}
|
||||
DELETE /meetings/{meeting_id}/participants/{participant_id} # Delete
|
||||
POST /meetings/{meeting_id}/participants/{participant_id}/token # Refresh token
|
||||
```
|
||||
|
||||
### Active Session
|
||||
```bash
|
||||
GET /meetings/{meeting_id}/active-session # Get active session
|
||||
POST /meetings/{meeting_id}/active-session/kick # Kick users: {"user_ids": ["id1", "id2"]}
|
||||
POST /meetings/{meeting_id}/active-session/kick-all # Kick all
|
||||
POST /meetings/{meeting_id}/active-session/poll # Create poll: {"question": "...", "options": [...], "anonymous": false}
|
||||
```
|
||||
|
||||
### Recording
|
||||
```bash
|
||||
GET /recordings?meeting_id={meeting_id} # List recordings
|
||||
GET /recordings/active-recording/{meeting_id} # Get active recording
|
||||
POST /recordings # Start: {"meeting_id": "...", "type": "composite"} (or "track")
|
||||
PUT /recordings/{recording_id} # Control: {"action": "pause"} (or "resume", "stop")
|
||||
POST /recordings/track # Track recording: {"meeting_id": "...", "layers": [...]}
|
||||
```
|
||||
|
||||
### Livestreaming
|
||||
```bash
|
||||
GET /livestreams?exclude_meetings=false # List all
|
||||
GET /livestreams/{livestream_id} # Get details
|
||||
POST /meetings/{meeting_id}/livestreams # Start for meeting
|
||||
POST /meetings/{meeting_id}/active-livestream/stop # Stop
|
||||
POST /livestreams # Create independent: returns {ingest_server, stream_key, playback_url}
|
||||
```
|
||||
|
||||
### Sessions & Analytics
|
||||
```bash
|
||||
GET /sessions # List all
|
||||
GET /sessions/{session_id} # Get details
|
||||
GET /sessions/{session_id}/participants # List participants
|
||||
GET /sessions/{session_id}/participants/{participant_id} # Call stats
|
||||
GET /sessions/{session_id}/chat # Download chat CSV
|
||||
GET /sessions/{session_id}/transcript # Download transcript CSV
|
||||
GET /sessions/{session_id}/summary # Get summary
|
||||
POST /sessions/{session_id}/summary # Generate summary
|
||||
GET /analytics/daywise?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD # Day-wise analytics
|
||||
GET /analytics/livestreams/overall # Livestream analytics
|
||||
```
|
||||
|
||||
### Webhooks
|
||||
```bash
|
||||
GET /webhooks # List all
|
||||
POST /webhooks # Create: {"url": "https://...", "events": ["session.started", "session.ended"]}
|
||||
PATCH /webhooks/{webhook_id} # Update
|
||||
DELETE /webhooks/{webhook_id} # Delete
|
||||
```
|
||||
|
||||
## Session Lifecycle
|
||||
|
||||
```
|
||||
Initialization → Join Intent → [Waitlist?] → Meeting Screen (Stage) → Ended
|
||||
↓ Approved
|
||||
[Rejected → Ended]
|
||||
```
|
||||
|
||||
UI Kit handles state transitions automatically.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Configuration](./configuration.md) - Setup and installation
|
||||
- [Patterns](./patterns.md) - Usage examples
|
||||
- [README](./README.md) - Overview and quick start
|
||||
@@ -0,0 +1,203 @@
|
||||
# RealtimeKit Configuration
|
||||
|
||||
Configuration guide for RealtimeKit setup, client SDKs, and wrangler integration.
|
||||
|
||||
## Installation
|
||||
|
||||
### React
|
||||
```bash
|
||||
npm install @cloudflare/realtimekit @cloudflare/realtimekit-react-ui
|
||||
```
|
||||
|
||||
### Angular
|
||||
```bash
|
||||
npm install @cloudflare/realtimekit @cloudflare/realtimekit-angular-ui
|
||||
```
|
||||
|
||||
### Web Components/HTML
|
||||
```bash
|
||||
npm install @cloudflare/realtimekit @cloudflare/realtimekit-ui
|
||||
```
|
||||
|
||||
## Client SDK Configuration
|
||||
|
||||
### React UI Kit
|
||||
```tsx
|
||||
import { RtkMeeting } from '@cloudflare/realtimekit-react-ui';
|
||||
<RtkMeeting authToken="<token>" onLeave={() => {}} />
|
||||
```
|
||||
|
||||
### Angular UI Kit
|
||||
```typescript
|
||||
@Component({ template: `<rtk-meeting [authToken]="authToken" (rtkLeave)="onLeave($event)"></rtk-meeting>` })
|
||||
export class AppComponent { authToken = '<token>'; onLeave() {} }
|
||||
```
|
||||
|
||||
### Web Components
|
||||
```html
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit-ui/dist/realtimekit-ui/realtimekit-ui.esm.js"></script>
|
||||
<rtk-meeting id="meeting"></rtk-meeting>
|
||||
<script>
|
||||
document.getElementById('meeting').authToken = '<token>';
|
||||
</script>
|
||||
```
|
||||
|
||||
### Core SDK Configuration
|
||||
```typescript
|
||||
import RealtimeKitClient from '@cloudflare/realtimekit';
|
||||
|
||||
const meeting = new RealtimeKitClient({
|
||||
authToken: '<token>',
|
||||
video: true, audio: true, autoSwitchAudioDevice: true,
|
||||
mediaConfiguration: {
|
||||
video: { width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: { ideal: 30 } },
|
||||
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
|
||||
screenshare: { width: { max: 1920 }, height: { max: 1080 }, frameRate: { ideal: 15 } }
|
||||
}
|
||||
});
|
||||
await meeting.join();
|
||||
```
|
||||
|
||||
## Backend Setup
|
||||
|
||||
### Create App & Credentials
|
||||
|
||||
**Dashboard**: https://dash.cloudflare.com/?to=/:account/realtime/kit
|
||||
|
||||
**API**:
|
||||
```bash
|
||||
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<account_id>/realtime/kit/apps' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer <api_token>' \
|
||||
-d '{"name": "My RealtimeKit App"}'
|
||||
```
|
||||
|
||||
**Required Permissions**: API token with **Realtime / Realtime Admin** permissions
|
||||
|
||||
### Create Presets
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<account_id>/realtime/kit/<app_id>/presets' \
|
||||
-H 'Authorization: Bearer <api_token>' \
|
||||
-d '{
|
||||
"name": "host",
|
||||
"permissions": {
|
||||
"canShareAudio": true,
|
||||
"canShareVideo": true,
|
||||
"canRecord": true,
|
||||
"canLivestream": true,
|
||||
"canStartStopRecording": true
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Wrangler Configuration
|
||||
|
||||
### Basic Configuration
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"name": "realtimekit-app",
|
||||
"main": "src/index.ts",
|
||||
"compatibility_date": "2025-01-01", // Use current date
|
||||
"vars": {
|
||||
"CLOUDFLARE_ACCOUNT_ID": "abc123",
|
||||
"REALTIMEKIT_APP_ID": "xyz789"
|
||||
}
|
||||
// Secrets: wrangler secret put CLOUDFLARE_API_TOKEN
|
||||
}
|
||||
```
|
||||
|
||||
### With Database & Storage
|
||||
```jsonc
|
||||
{
|
||||
"d1_databases": [{ "binding": "DB", "database_name": "meetings", "database_id": "d1-id" }],
|
||||
"r2_buckets": [{ "binding": "RECORDINGS", "bucket_name": "recordings" }],
|
||||
"kv_namespaces": [{ "binding": "SESSIONS", "id": "kv-id" }]
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Environment
|
||||
```bash
|
||||
# Deploy to environments
|
||||
wrangler deploy --env staging
|
||||
wrangler deploy --env production
|
||||
```
|
||||
|
||||
## TURN Service Configuration
|
||||
|
||||
RealtimeKit can use Cloudflare's TURN service for connectivity through restrictive networks:
|
||||
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"vars": {
|
||||
"TURN_SERVICE_ID": "your_turn_service_id"
|
||||
}
|
||||
// Set secret: wrangler secret put TURN_SERVICE_TOKEN
|
||||
}
|
||||
```
|
||||
|
||||
TURN automatically configured when enabled in account - no client-side changes needed.
|
||||
|
||||
## Theming & Design Tokens
|
||||
|
||||
```typescript
|
||||
import type { UIConfig } from '@cloudflare/realtimekit';
|
||||
|
||||
const uiConfig: UIConfig = {
|
||||
designTokens: {
|
||||
colors: {
|
||||
brand: { 500: '#0066ff', 600: '#0052cc' },
|
||||
background: { 1000: '#1A1A1A', 900: '#2D2D2D' },
|
||||
text: { 1000: '#FFFFFF', 900: '#E0E0E0' }
|
||||
},
|
||||
borderRadius: 'extra-rounded', // 'rounded' | 'extra-rounded' | 'sharp'
|
||||
theme: 'dark' // 'light' | 'dark'
|
||||
},
|
||||
logo: { url: 'https://example.com/logo.png', altText: 'Company' }
|
||||
};
|
||||
|
||||
// Apply to React
|
||||
<RtkMeeting authToken={token} config={uiConfig} onLeave={() => {}} />
|
||||
|
||||
// Or use CSS variables
|
||||
// :root { --rtk-color-brand-500: #0066ff; --rtk-border-radius: 12px; }
|
||||
```
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
### Custom Language Strings
|
||||
```typescript
|
||||
import { useLanguage } from '@cloudflare/realtimekit-ui';
|
||||
|
||||
const customLanguage = {
|
||||
'join': 'Entrar',
|
||||
'leave': 'Salir',
|
||||
'mute': 'Silenciar',
|
||||
'unmute': 'Activar audio',
|
||||
'turn_on_camera': 'Encender cámara',
|
||||
'turn_off_camera': 'Apagar cámara',
|
||||
'share_screen': 'Compartir pantalla',
|
||||
'stop_sharing': 'Dejar de compartir'
|
||||
};
|
||||
|
||||
const t = useLanguage(customLanguage);
|
||||
|
||||
// React usage
|
||||
<RtkMeeting authToken={token} t={t} onLeave={() => {}} />
|
||||
```
|
||||
|
||||
### Supported Locales
|
||||
Default locales available: `en`, `es`, `fr`, `de`, `pt`, `ja`, `zh`
|
||||
|
||||
```typescript
|
||||
import { setLocale } from '@cloudflare/realtimekit-ui';
|
||||
setLocale('es'); // Switch to Spanish
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [API](./api.md) - Meeting APIs, REST endpoints
|
||||
- [Patterns](./patterns.md) - Backend integration examples
|
||||
- [README](./README.md) - Overview and quick start
|
||||
@@ -0,0 +1,169 @@
|
||||
# RealtimeKit Gotchas & Troubleshooting
|
||||
|
||||
## Common Errors
|
||||
|
||||
### "Cannot connect to meeting"
|
||||
|
||||
**Cause:** Auth token invalid/expired, API credentials lack permissions, or network blocks WebRTC
|
||||
**Solution:**
|
||||
Verify token validity, check API token has **Realtime / Realtime Admin** permissions, enable TURN service for restrictive networks
|
||||
|
||||
### "No video/audio tracks"
|
||||
|
||||
**Cause:** Browser permissions not granted, video/audio not enabled, device in use, or device unavailable
|
||||
**Solution:**
|
||||
Request browser permissions explicitly, verify initialization config, use `meeting.self.getAllDevices()` to debug, close other apps using device
|
||||
|
||||
### "Participant count mismatched"
|
||||
|
||||
**Cause:** `meeting.participants` doesn't include `meeting.self`
|
||||
**Solution:** Total count = `meeting.participants.joined.size() + 1`
|
||||
|
||||
### "Events not firing"
|
||||
|
||||
**Cause:** Listeners registered after actions, incorrect event name, or wrong namespace
|
||||
**Solution:**
|
||||
Register listeners before calling `meeting.join()`, check event names against docs, verify correct namespace
|
||||
|
||||
### "CORS errors in API calls"
|
||||
|
||||
**Cause:** Making REST API calls from client-side
|
||||
**Solution:** All REST API calls **must** be server-side (Workers, backend). Never expose API tokens to clients.
|
||||
|
||||
### "Preset not applying"
|
||||
|
||||
**Cause:** Preset doesn't exist, name mismatch (case-sensitive), or participant created before preset
|
||||
**Solution:**
|
||||
Verify preset exists via Dashboard or API, check exact spelling and case, create preset before adding participants
|
||||
|
||||
### "Token reuse error"
|
||||
|
||||
**Cause:** Reusing participant tokens across sessions
|
||||
**Solution:** Generate fresh token per session. Use refresh endpoint if token expires during session.
|
||||
|
||||
### "Video quality poor"
|
||||
|
||||
**Cause:** Insufficient bandwidth, resolution/bitrate too high, or CPU overload
|
||||
**Solution:**
|
||||
Lower `mediaConfiguration.video` resolution/frameRate, monitor network conditions, reduce participant count or grid size
|
||||
|
||||
### "Echo or audio feedback"
|
||||
|
||||
**Cause:** Multiple devices picking up same audio source
|
||||
**Solution:**
|
||||
- Lower `mediaConfiguration.video` resolution/frameRate
|
||||
- Monitor network conditions
|
||||
- Reduce participant count or grid size
|
||||
|
||||
### Issue: Echo or audio feedback
|
||||
**Cause**: Multiple devices picking up same audio source
|
||||
|
||||
**Solutions**:
|
||||
Enable `echoCancellation: true` in `mediaConfiguration.audio`, use headphones, mute when not speaking
|
||||
|
||||
### "Screen share not working"
|
||||
|
||||
**Cause:** Browser doesn't support screen sharing API, permission denied, or wrong `displaySurface` config
|
||||
**Solution:**
|
||||
Use Chrome/Edge/Firefox (Safari limited support), check browser permissions, try different `displaySurface` values ('window', 'monitor', 'browser')
|
||||
|
||||
### "How do I schedule meetings?"
|
||||
|
||||
**Cause:** RealtimeKit has no built-in scheduling system
|
||||
**Solution:**
|
||||
Store meeting IDs in your database with timestamps. Generate participant tokens only when user should join. Example:
|
||||
```typescript
|
||||
// Store in DB
|
||||
{ meetingId: 'abc123', scheduledFor: '2026-02-15T10:00:00Z', userId: 'user456' }
|
||||
|
||||
// Generate token when user clicks "Join" near scheduled time
|
||||
const response = await fetch('/api/join-meeting', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ meetingId: 'abc123' })
|
||||
});
|
||||
const { authToken } = await response.json();
|
||||
```
|
||||
|
||||
### "Recording not starting"
|
||||
|
||||
**Cause:** Preset lacks recording permissions, no active session, or API call from client
|
||||
**Solution:**
|
||||
Verify preset has `canRecord: true` and `canStartStopRecording: true`, ensure session is active (at least one participant), make recording API calls server-side only
|
||||
|
||||
## Limits
|
||||
|
||||
| Resource | Limit |
|
||||
|----------|-------|
|
||||
| Max participants per session | 100 |
|
||||
| Max concurrent sessions per App | 1000 |
|
||||
| Max recording duration | 6 hours |
|
||||
| Max meeting duration | 24 hours |
|
||||
| Max chat message length | 4000 characters |
|
||||
| Max preset name length | 64 characters |
|
||||
| Max meeting title length | 256 characters |
|
||||
| Max participant name length | 256 characters |
|
||||
| Token expiration | 24 hours (default) |
|
||||
| WebRTC ports required | UDP 1024-65535 |
|
||||
|
||||
## Network Requirements
|
||||
|
||||
### Firewall Rules
|
||||
Allow outbound UDP/TCP to:
|
||||
- `*.cloudflare.com` ports 443, 80
|
||||
- UDP ports 1024-65535 (WebRTC media)
|
||||
|
||||
### TURN Service
|
||||
Enable for users behind restrictive firewalls/proxies:
|
||||
```jsonc
|
||||
// wrangler.jsonc
|
||||
{
|
||||
"vars": {
|
||||
"TURN_SERVICE_ID": "your_turn_service_id"
|
||||
}
|
||||
// Set secret: wrangler secret put TURN_SERVICE_TOKEN
|
||||
}
|
||||
```
|
||||
|
||||
TURN automatically configured in SDK when enabled in account.
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
```typescript
|
||||
// Check devices
|
||||
const devices = await meeting.self.getAllDevices();
|
||||
meeting.self.on('deviceListUpdate', ({ added, removed, devices }) => console.log('Devices:', { added, removed, devices }));
|
||||
|
||||
// Monitor participants
|
||||
meeting.participants.joined.on('participantJoined', (p) => console.log(`${p.name} joined:`, { id: p.id, userId: p.userId, audioEnabled: p.audioEnabled, videoEnabled: p.videoEnabled }));
|
||||
|
||||
// Check room state
|
||||
meeting.self.on('roomJoined', () => console.log('Room:', { meetingId: meeting.meta.meetingId, meetingTitle: meeting.meta.meetingTitle, participantCount: meeting.participants.joined.size() + 1, audioEnabled: meeting.self.audioEnabled, videoEnabled: meeting.self.videoEnabled }));
|
||||
|
||||
// Log all events
|
||||
['roomJoined', 'audioUpdate', 'videoUpdate', 'screenShareUpdate', 'deviceUpdate', 'deviceListUpdate'].forEach(event => meeting.self.on(event, (data) => console.log(`[self] ${event}:`, data)));
|
||||
['participantJoined', 'participantLeft'].forEach(event => meeting.participants.joined.on(event, (data) => console.log(`[participants] ${event}:`, data)));
|
||||
meeting.chat.on('chatUpdate', (data) => console.log('[chat] chatUpdate:', data));
|
||||
```
|
||||
|
||||
## Security & Performance
|
||||
|
||||
### Security: Do NOT
|
||||
- Expose `CLOUDFLARE_API_TOKEN` in client code, hardcode credentials in frontend
|
||||
- Reuse participant tokens, store tokens in localStorage without encryption
|
||||
- Allow client-side meeting creation
|
||||
|
||||
### Security: DO
|
||||
- Generate tokens server-side only, use HTTPS, implement rate limiting
|
||||
- Validate user auth before generating tokens, use `custom_participant_id` to map to your user system
|
||||
- Set appropriate preset permissions per user role, rotate API tokens regularly
|
||||
|
||||
### Performance
|
||||
- **CPU**: Lower video resolution/frameRate, disable video for audio-only, use `meeting.participants.active` for large meetings, implement virtual scrolling
|
||||
- **Bandwidth**: Set max resolution in `mediaConfiguration`, disable screenshare audio if unneeded, use audio-only mode, implement adaptive bitrate
|
||||
- **Memory**: Clean up event listeners on unmount, call `meeting.leave()` when done, don't store large participant arrays
|
||||
|
||||
## In This Reference
|
||||
- [README.md](README.md) - Overview, core concepts, quick start
|
||||
- [configuration.md](configuration.md) - SDK config, presets, wrangler setup
|
||||
- [api.md](api.md) - Client SDK APIs, REST endpoints
|
||||
- [patterns.md](patterns.md) - Common patterns, React hooks, backend integration
|
||||
@@ -0,0 +1,223 @@
|
||||
# RealtimeKit Patterns
|
||||
|
||||
## UI Kit (Minimal Code)
|
||||
|
||||
```tsx
|
||||
// React
|
||||
import { RtkMeeting } from '@cloudflare/realtimekit-react-ui';
|
||||
<RtkMeeting authToken="<token>" onLeave={() => console.log('Left')} />
|
||||
|
||||
// Angular
|
||||
@Component({ template: `<rtk-meeting [authToken]="authToken" (rtkLeave)="onLeave($event)"></rtk-meeting>` })
|
||||
export class AppComponent { authToken = '<token>'; onLeave(event: unknown) {} }
|
||||
|
||||
// HTML/Web Components
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@cloudflare/realtimekit-ui/dist/realtimekit-ui/realtimekit-ui.esm.js"></script>
|
||||
<rtk-meeting id="meeting"></rtk-meeting>
|
||||
<script>document.getElementById('meeting').authToken = '<token>';</script>
|
||||
```
|
||||
|
||||
## UI Components
|
||||
|
||||
RealtimeKit provides 133+ pre-built Stencil.js Web Components with framework wrappers:
|
||||
|
||||
### Layout Components
|
||||
- `<RtkMeeting>` - Full meeting UI (all-in-one)
|
||||
- `<RtkHeader>`, `<RtkStage>`, `<RtkControlbar>` - Layout sections
|
||||
- `<RtkSidebar>` - Chat/participants sidebar
|
||||
- `<RtkGrid>` - Adaptive video grid
|
||||
|
||||
### Control Components
|
||||
- `<RtkMicToggle>`, `<RtkCameraToggle>` - Media controls
|
||||
- `<RtkScreenShareToggle>` - Screen sharing
|
||||
- `<RtkLeaveButton>` - Leave meeting
|
||||
- `<RtkSettingsModal>` - Device settings
|
||||
|
||||
### Grid Variants
|
||||
- `<RtkSpotlightGrid>` - Active speaker focus
|
||||
- `<RtkAudioGrid>` - Audio-only mode
|
||||
- `<RtkPaginatedGrid>` - Paginated layout
|
||||
|
||||
**See full catalog**: https://docs.realtime.cloudflare.com/ui-kit
|
||||
|
||||
## Core SDK Patterns
|
||||
|
||||
### Basic Setup
|
||||
```typescript
|
||||
import RealtimeKitClient from '@cloudflare/realtimekit';
|
||||
|
||||
const meeting = new RealtimeKitClient({ authToken, video: true, audio: true });
|
||||
meeting.self.on('roomJoined', () => console.log('Joined:', meeting.meta.meetingTitle));
|
||||
meeting.participants.joined.on('participantJoined', (p) => console.log(`${p.name} joined`));
|
||||
await meeting.join();
|
||||
```
|
||||
|
||||
### Video Grid & Device Selection
|
||||
```typescript
|
||||
// Video grid
|
||||
function VideoGrid({ meeting }) {
|
||||
const [participants, setParticipants] = useState([]);
|
||||
useEffect(() => {
|
||||
const update = () => setParticipants(meeting.participants.joined.toArray());
|
||||
meeting.participants.joined.on('participantJoined', update);
|
||||
meeting.participants.joined.on('participantLeft', update);
|
||||
update();
|
||||
return () => { meeting.participants.joined.off('participantJoined', update); meeting.participants.joined.off('participantLeft', update); };
|
||||
}, [meeting]);
|
||||
return <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))' }}>
|
||||
{participants.map(p => <VideoTile key={p.id} participant={p} />)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function VideoTile({ participant }) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
useEffect(() => {
|
||||
if (videoRef.current && participant.videoTrack) videoRef.current.srcObject = new MediaStream([participant.videoTrack]);
|
||||
}, [participant.videoTrack]);
|
||||
return <div><video ref={videoRef} autoPlay playsInline muted /><div>{participant.name}</div></div>;
|
||||
}
|
||||
|
||||
// Device selection
|
||||
const devices = await meeting.self.getAllDevices();
|
||||
const switchCamera = (deviceId: string) => {
|
||||
const device = devices.find(d => d.deviceId === deviceId);
|
||||
if (device) await meeting.self.setDevice(device);
|
||||
};
|
||||
```
|
||||
|
||||
## React Hooks (Official)
|
||||
|
||||
```typescript
|
||||
import { useRealtimeKitClient, useRealtimeKitSelector } from '@cloudflare/realtimekit-react-ui';
|
||||
|
||||
function MyComponent() {
|
||||
const [meeting, initMeeting] = useRealtimeKitClient();
|
||||
const audioEnabled = useRealtimeKitSelector(m => m.self.audioEnabled);
|
||||
const participantCount = useRealtimeKitSelector(m => m.participants.joined.size());
|
||||
|
||||
useEffect(() => { initMeeting({ authToken: '<token>' }); }, []);
|
||||
|
||||
return <div>
|
||||
<button onClick={() => meeting?.self.enableAudio()}>{audioEnabled ? 'Mute' : 'Unmute'}</button>
|
||||
<span>{participantCount} participants</span>
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:** Automatic re-renders, memoized selectors, type-safe
|
||||
|
||||
## Waitlist Handling
|
||||
|
||||
```typescript
|
||||
// Monitor waitlist
|
||||
meeting.participants.waitlisted.on('participantJoined', (participant) => {
|
||||
console.log(`${participant.name} is waiting`);
|
||||
// Show admin UI to approve/reject
|
||||
});
|
||||
|
||||
// Approve from waitlist (backend only)
|
||||
await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${accountId}/realtime/kit/${appId}/meetings/${meetingId}/active-session/waitlist/approve`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${apiToken}` },
|
||||
body: JSON.stringify({ user_ids: [participant.userId] })
|
||||
}
|
||||
);
|
||||
|
||||
// Client receives automatic transition when approved
|
||||
meeting.self.on('roomJoined', () => console.log('Approved and joined'));
|
||||
```
|
||||
|
||||
## Audio-Only Mode
|
||||
|
||||
```typescript
|
||||
const meeting = new RealtimeKitClient({
|
||||
authToken: '<token>',
|
||||
video: false, // Disable video
|
||||
audio: true,
|
||||
mediaConfiguration: {
|
||||
audio: {
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true,
|
||||
autoGainControl: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Use audio grid component
|
||||
import { RtkAudioGrid } from '@cloudflare/realtimekit-react-ui';
|
||||
<RtkAudioGrid meeting={meeting} />
|
||||
```
|
||||
|
||||
## Addon System
|
||||
|
||||
```typescript
|
||||
// List available addons
|
||||
meeting.plugins.all.forEach(plugin => {
|
||||
console.log(plugin.id, plugin.name, plugin.active);
|
||||
});
|
||||
|
||||
// Activate collaborative app
|
||||
await meeting.plugins.activate('whiteboard-addon-id');
|
||||
|
||||
// Listen for activations
|
||||
meeting.plugins.on('pluginActivated', ({ plugin }) => {
|
||||
console.log(`${plugin.name} activated`);
|
||||
});
|
||||
|
||||
// Deactivate
|
||||
await meeting.plugins.deactivate();
|
||||
```
|
||||
|
||||
## Backend Integration
|
||||
|
||||
### Token Generation (Workers)
|
||||
```typescript
|
||||
export interface Env { CLOUDFLARE_API_TOKEN: string; CLOUDFLARE_ACCOUNT_ID: string; REALTIMEKIT_APP_ID: string; }
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === '/api/join-meeting') {
|
||||
const { meetingId, userName, presetName } = await request.json();
|
||||
const response = await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/realtime/kit/${env.REALTIMEKIT_APP_ID}/meetings/${meetingId}/participants`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${env.CLOUDFLARE_API_TOKEN}` },
|
||||
body: JSON.stringify({ name: userName, preset_name: presetName })
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
return Response.json({ authToken: data.result.authToken });
|
||||
}
|
||||
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
1. **Never expose API tokens client-side** - Generate participant tokens server-side only
|
||||
2. **Don't reuse participant tokens** - Generate fresh token per session, use refresh endpoint if expired
|
||||
3. **Use custom participant IDs** - Map to your user system for cross-session tracking
|
||||
|
||||
### Performance
|
||||
1. **Event-driven updates** - Listen to events, don't poll. Use `toArray()` only when needed
|
||||
2. **Media quality constraints** - Set appropriate resolution/bitrate limits based on network conditions
|
||||
3. **Device management** - Enable `autoSwitchAudioDevice` for better UX, handle device list updates
|
||||
|
||||
### Architecture
|
||||
1. **Separate Apps for environments** - staging vs production to prevent data mixing
|
||||
2. **Preset strategy** - Create presets at App level, reuse across meetings
|
||||
3. **Token management** - Backend generates tokens, frontend receives via authenticated endpoint
|
||||
|
||||
## In This Reference
|
||||
- [README.md](README.md) - Overview, core concepts, quick start
|
||||
- [configuration.md](configuration.md) - SDK config, presets, wrangler setup
|
||||
- [api.md](api.md) - Client SDK APIs, REST endpoints
|
||||
- [gotchas.md](gotchas.md) - Common issues, troubleshooting, limits
|
||||
Reference in New Issue
Block a user