update skills

This commit is contained in:
2026-03-17 16:53:22 -07:00
parent 0b0783ef8e
commit f9a530667e
389 changed files with 54512 additions and 1 deletions

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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