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