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,82 @@
# Cloudflare TURN Service
Expert guidance for implementing Cloudflare TURN Service in WebRTC applications.
## Overview
Cloudflare TURN (Traversal Using Relays around NAT) Service is a managed relay service for WebRTC applications. TURN acts as a relay point for traffic between WebRTC clients and SFUs, particularly when direct peer-to-peer communication is obstructed by NATs or firewalls. The service runs on Cloudflare's global anycast network across 310+ cities.
## Key Characteristics
- **Anycast Architecture**: Automatically connects clients to the closest Cloudflare location
- **Global Network**: Available across Cloudflare's entire network (excluding China Network)
- **Zero Configuration**: No need to manually select regions or servers
- **Protocol Support**: STUN/TURN over UDP, TCP, and TLS
- **Free Tier**: Free when used with Cloudflare Calls SFU, otherwise $0.05/GB outbound
## In This Reference
| File | Purpose |
|------|---------|
| [api.md](./api.md) | Credentials API, TURN key management, types, constraints |
| [configuration.md](./configuration.md) | Worker setup, wrangler.jsonc, env vars, IP allowlisting |
| [patterns.md](./patterns.md) | Implementation patterns, use cases, integration examples |
| [gotchas.md](./gotchas.md) | Troubleshooting, limits, security, common mistakes |
## Reading Order
| Task | Files to Read | Est. Tokens |
|------|---------------|-------------|
| Quick start | README only | ~500 |
| Generate credentials | README → api | ~1300 |
| Worker integration | README → configuration → patterns | ~2000 |
| Debug connection | gotchas | ~700 |
| Security review | api → gotchas | ~1500 |
| Enterprise firewall | configuration | ~600 |
## Service Addresses and Ports
### STUN over UDP
- **Primary**: `stun.cloudflare.com:3478/udp`
- **Alternate**: `stun.cloudflare.com:53/udp` (blocked by browsers, not recommended)
### TURN over UDP
- **Primary**: `turn.cloudflare.com:3478/udp`
- **Alternate**: `turn.cloudflare.com:53/udp` (blocked by browsers)
### TURN over TCP
- **Primary**: `turn.cloudflare.com:3478/tcp`
- **Alternate**: `turn.cloudflare.com:80/tcp`
### TURN over TLS
- **Primary**: `turn.cloudflare.com:5349/tcp`
- **Alternate**: `turn.cloudflare.com:443/tcp`
## Quick Start
1. **Create TURN key via API**: see [api.md#create-turn-key](./api.md#create-turn-key)
2. **Generate credentials**: see [api.md#generate-temporary-credentials](./api.md#generate-temporary-credentials)
3. **Configure Worker**: see [configuration.md#cloudflare-worker-integration](./configuration.md#cloudflare-worker-integration)
4. **Implement client**: see [patterns.md#basic-turn-configuration-browser](./patterns.md#basic-turn-configuration-browser)
## When to Use TURN
- **Restrictive NATs**: Symmetric NATs that block direct connections
- **Corporate firewalls**: Environments blocking WebRTC ports
- **Mobile networks**: Carrier-grade NAT scenarios
- **Predictable connectivity**: When reliability > efficiency
## Related Cloudflare Services
- **Cloudflare Calls SFU**: Managed Selective Forwarding Unit (TURN free when used with SFU)
- **Cloudflare Stream**: Video streaming with WHIP/WHEP support
- **Cloudflare Workers**: Backend for credential generation
- **Cloudflare KV**: Credential caching
- **Cloudflare Durable Objects**: Session state management
## Additional Resources
- [Cloudflare Calls Documentation](https://developers.cloudflare.com/calls/)
- [Cloudflare TURN Service Docs](https://developers.cloudflare.com/realtime/turn/)
- [Cloudflare API Reference](https://developers.cloudflare.com/api/resources/calls/subresources/turn/)
- [Orange Meets (Open Source Example)](https://github.com/cloudflare/orange)

View File

@@ -0,0 +1,239 @@
# TURN API Reference
Complete API documentation for Cloudflare TURN service credentials and key management.
## Authentication
All endpoints require Cloudflare API token with "Calls Write" permission.
Base URL: `https://api.cloudflare.com/client/v4`
## TURN Key Management
### List TURN Keys
```
GET /accounts/{account_id}/calls/turn_keys
```
### Get TURN Key Details
```
GET /accounts/{account_id}/calls/turn_keys/{key_id}
```
### Create TURN Key
```
POST /accounts/{account_id}/calls/turn_keys
Content-Type: application/json
{
"name": "my-turn-key"
}
```
**Response includes**:
- `uid`: Key identifier
- `key`: The actual secret key (only returned on creation—save immediately)
- `name`: Human-readable name
- `created`: ISO 8601 timestamp
- `modified`: ISO 8601 timestamp
### Update TURN Key
```
PUT /accounts/{account_id}/calls/turn_keys/{key_id}
Content-Type: application/json
{
"name": "updated-name"
}
```
### Delete TURN Key
```
DELETE /accounts/{account_id}/calls/turn_keys/{key_id}
```
## Generate Temporary Credentials
```
POST https://rtc.live.cloudflare.com/v1/turn/keys/{key_id}/credentials/generate
Authorization: Bearer {key_secret}
Content-Type: application/json
{
"ttl": 86400
}
```
### Credential Constraints
| Parameter | Min | Max | Default | Notes |
|-----------|-----|-----|---------|-------|
| ttl | 1 | 172800 (48hrs) | varies | API rejects values >172800 |
**CRITICAL**: Maximum TTL is 48 hours (172800 seconds). API will reject requests exceeding this limit.
### Response Schema
```json
{
"iceServers": {
"urls": [
"stun:stun.cloudflare.com:3478",
"turn:turn.cloudflare.com:3478?transport=udp",
"turn:turn.cloudflare.com:3478?transport=tcp",
"turn:turn.cloudflare.com:53?transport=udp",
"turn:turn.cloudflare.com:80?transport=tcp",
"turns:turn.cloudflare.com:5349?transport=tcp",
"turns:turn.cloudflare.com:443?transport=tcp"
],
"username": "1738035200:user123",
"credential": "base64encodedhmac=="
}
}
```
**Port 53 Warning**: Filter port 53 URLs for browser clients—blocked by Chrome/Firefox. See [gotchas.md](./gotchas.md#using-port-53-in-browsers).
## Revoke Credentials
```
POST https://rtc.live.cloudflare.com/v1/turn/keys/{key_id}/credentials/revoke
Authorization: Bearer {key_secret}
Content-Type: application/json
{
"username": "1738035200:user123"
}
```
**Response**: 204 No Content
Billing stops immediately. Active connection drops after short delay (~seconds).
## TypeScript Types
```typescript
interface CloudflareTURNConfig {
keyId: string;
keySecret: string;
ttl?: number; // Max 172800 (48 hours)
}
interface TURNCredentialsRequest {
ttl?: number; // Max 172800 seconds
}
interface TURNCredentialsResponse {
iceServers: {
urls: string[];
username: string;
credential: string;
};
}
interface RTCIceServer {
urls: string | string[];
username?: string;
credential?: string;
credentialType?: "password";
}
interface TURNKeyResponse {
uid: string;
key: string; // Only present on creation
name: string;
created: string;
modified: string;
}
```
## Validation Function
```typescript
function validateRTCIceServer(obj: unknown): obj is RTCIceServer {
if (!obj || typeof obj !== 'object') {
return false;
}
const server = obj as Record<string, unknown>;
if (typeof server.urls !== 'string' && !Array.isArray(server.urls)) {
return false;
}
if (server.username && typeof server.username !== 'string') {
return false;
}
if (server.credential && typeof server.credential !== 'string') {
return false;
}
return true;
}
```
## Type-Safe Credential Generation
```typescript
async function fetchTURNServers(
config: CloudflareTURNConfig
): Promise<RTCIceServer[]> {
// Validate TTL constraint
const ttl = config.ttl ?? 3600;
if (ttl > 172800) {
throw new Error('TTL cannot exceed 172800 seconds (48 hours)');
}
const response = await fetch(
`https://rtc.live.cloudflare.com/v1/turn/keys/${config.keyId}/credentials/generate`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${config.keySecret}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ttl })
}
);
if (!response.ok) {
throw new Error(`TURN credential generation failed: ${response.status}`);
}
const data = await response.json();
// Filter port 53 for browser clients
const filteredUrls = data.iceServers.urls.filter(
(url: string) => !url.includes(':53')
);
const iceServers = [
{ urls: 'stun:stun.cloudflare.com:3478' },
{
urls: filteredUrls,
username: data.iceServers.username,
credential: data.iceServers.credential,
credentialType: 'password' as const
}
];
// Validate before returning
if (!iceServers.every(validateRTCIceServer)) {
throw new Error('Invalid ICE server configuration received');
}
return iceServers;
}
```
## See Also
- [configuration.md](./configuration.md) - Worker setup, environment variables
- [patterns.md](./patterns.md) - Implementation examples using these APIs
- [gotchas.md](./gotchas.md) - Security best practices, common mistakes

View File

@@ -0,0 +1,179 @@
# TURN Configuration
Setup and configuration for Cloudflare TURN service in Workers and applications.
## Environment Variables
```bash
# .env
CLOUDFLARE_ACCOUNT_ID=your_account_id
CLOUDFLARE_API_TOKEN=your_api_token
TURN_KEY_ID=your_turn_key_id
TURN_KEY_SECRET=your_turn_key_secret
```
Validate with zod:
```typescript
import { z } from 'zod';
const envSchema = z.object({
CLOUDFLARE_ACCOUNT_ID: z.string().min(1),
CLOUDFLARE_API_TOKEN: z.string().min(1),
TURN_KEY_ID: z.string().min(1),
TURN_KEY_SECRET: z.string().min(1)
});
export const config = envSchema.parse(process.env);
```
## wrangler.jsonc
```jsonc
{
"name": "turn-credentials-api",
"main": "src/index.ts",
"compatibility_date": "2025-01-01",
"vars": {
"TURN_KEY_ID": "your-turn-key-id" // Non-sensitive, can be in vars
},
"env": {
"production": {
"kv_namespaces": [
{
"binding": "CREDENTIALS_CACHE",
"id": "your-kv-namespace-id"
}
]
}
}
}
```
**Store secrets separately**:
```bash
wrangler secret put TURN_KEY_SECRET
```
## Cloudflare Worker Integration
### Worker Binding Types
```typescript
interface Env {
TURN_KEY_ID: string;
TURN_KEY_SECRET: string;
CREDENTIALS_CACHE?: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// See patterns.md for implementation
}
}
```
### Basic Worker Example
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.url.endsWith('/turn-credentials')) {
// Validate client auth
const authHeader = request.headers.get('Authorization');
if (!authHeader) {
return new Response('Unauthorized', { status: 401 });
}
const response = await fetch(
`https://rtc.live.cloudflare.com/v1/turn/keys/${env.TURN_KEY_ID}/credentials/generate`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${env.TURN_KEY_SECRET}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ttl: 3600 })
}
);
if (!response.ok) {
return new Response('Failed to generate credentials', { status: 500 });
}
const data = await response.json();
// Filter port 53 for browser clients
const filteredUrls = data.iceServers.urls.filter(
(url: string) => !url.includes(':53')
);
return Response.json({
iceServers: [
{ urls: 'stun:stun.cloudflare.com:3478' },
{
urls: filteredUrls,
username: data.iceServers.username,
credential: data.iceServers.credential
}
]
});
}
return new Response('Not found', { status: 404 });
}
};
```
## IP Allowlisting (Enterprise/Firewall)
For strict firewalls, allowlist these IPs for `turn.cloudflare.com`:
| Type | Address | Protocol |
|------|---------|----------|
| IPv4 | 141.101.90.1/32 | All |
| IPv4 | 162.159.207.1/32 | All |
| IPv6 | 2a06:98c1:3200::1/128 | All |
| IPv6 | 2606:4700:48::1/128 | All |
**IMPORTANT**: These IPs may change with 14-day notice. Monitor DNS:
```bash
# Check A and AAAA records
dig turn.cloudflare.com A
dig turn.cloudflare.com AAAA
```
Set up automated monitoring to detect IP changes and update allowlists within 14 days.
## IPv6 Support
- **Client-to-TURN**: Both IPv4 and IPv6 supported
- **Relay addresses**: IPv4 only (no RFC 6156 support)
- **TCP relaying**: Not supported (RFC 6062)
Clients can connect via IPv6, but relayed traffic uses IPv4 addresses.
## TLS Configuration
### Supported TLS Versions
- TLS 1.1
- TLS 1.2
- TLS 1.3
### Recommended Ciphers (TLS 1.3)
- AEAD-AES128-GCM-SHA256
- AEAD-AES256-GCM-SHA384
- AEAD-CHACHA20-POLY1305-SHA256
### Recommended Ciphers (TLS 1.2)
- ECDHE-ECDSA-AES128-GCM-SHA256
- ECDHE-RSA-AES128-GCM-SHA256
- ECDHE-RSA-AES128-SHA (also TLS 1.1)
- AES128-GCM-SHA256
## See Also
- [api.md](./api.md) - TURN key creation, credential generation API
- [patterns.md](./patterns.md) - Full Worker implementation patterns
- [gotchas.md](./gotchas.md) - Security best practices, troubleshooting

View File

@@ -0,0 +1,231 @@
# TURN Gotchas & Troubleshooting
Common mistakes, security best practices, and troubleshooting for Cloudflare TURN.
## Quick Reference
| Issue | Solution | Details |
|-------|----------|---------|
| Credentials not working | Check TTL ≤ 48hrs | [See Troubleshooting](#issue-turn-credentials-not-working) |
| Connection drops after ~48hrs | Implement credential refresh | [See Connection Drops](#issue-connection-drops-after-48-hours) |
| Port 53 fails in browser | Filter server-side | [See Port 53](#using-port-53-in-browsers) |
| High packet loss | Check rate limits | [See Rate Limits](#limits-per-turn-allocation) |
| Connection fails after maintenance | Implement ICE restart | [See ICE Restart](#ice-restart-required-scenarios) |
## Critical Constraints
| Constraint | Value | Consequence if Violated |
|------------|-------|-------------------------|
| Max credential TTL | 48 hours (172800s) | API rejects request |
| Credential revocation delay | ~seconds | Billing stops immediately, connection drops shortly |
| IP allowlist update window | 14 days (if IPs change) | Connection fails if IPs change |
| Packet rate | 5-10k pps per allocation | Packet drops |
| Data rate | 50-100 Mbps per allocation | Packet drops |
| Unique IP rate | >5 new IPs/sec | Packet drops |
## Limits Per TURN Allocation
**Per user** (not account-wide):
- **IP addresses**: >5 new unique IPs per second
- **Packet rate**: 5-10k packets per second (inbound/outbound)
- **Data rate**: 50-100 Mbps (inbound/outbound)
- **MTU**: No specific limit
- **Burst rates**: Higher than documented
Exceeding limits results in **packet drops**.
## Common Mistakes
### Setting TTL > 48 hours
```typescript
// ❌ BAD: API will reject
const creds = await generate({ ttl: 604800 }); // 7 days
// ✅ GOOD:
const creds = await generate({ ttl: 86400 }); // 24 hours
```
### Hardcoding IPs without monitoring
```typescript
// ❌ BAD: IPs can change with 14-day notice
const iceServers = [{ urls: 'turn:141.101.90.1:3478' }];
// ✅ GOOD: Use DNS
const iceServers = [{ urls: 'turn:turn.cloudflare.com:3478' }];
```
### Using port 53 in browsers
```typescript
// ❌ BAD: Blocked by Chrome/Firefox
urls: ['turn:turn.cloudflare.com:53']
// ✅ GOOD: Filter port 53
urls: urls.filter(url => !url.includes(':53'))
```
### Not handling credential expiry
```typescript
// ❌ BAD: Credentials expire but call continues → connection drops
const creds = await fetchCreds();
const pc = new RTCPeerConnection({ iceServers: creds });
// ✅ GOOD: Refresh before expiry
setInterval(() => refreshCredentials(pc), 3000000); // 50 min
```
### Missing ICE restart support
```typescript
// ❌ BAD: No recovery from TURN maintenance
pc.addEventListener('iceconnectionstatechange', () => {
console.log('State changed:', pc.iceConnectionState);
});
// ✅ GOOD: Implement ICE restart
pc.addEventListener('iceconnectionstatechange', async () => {
if (pc.iceConnectionState === 'failed') {
await refreshCredentials(pc);
pc.restartIce();
}
});
```
### Exposing TURN key secret client-side
```typescript
// ❌ BAD: Secret exposed to client
const secret = 'your-turn-key-secret';
const response = await fetch(`https://rtc.live.cloudflare.com/v1/turn/...`, {
headers: { 'Authorization': `Bearer ${secret}` }
});
// ✅ GOOD: Generate credentials server-side
const response = await fetch('/api/turn-credentials');
```
## ICE Restart Required Scenarios
These events require ICE restart (see [patterns.md](./patterns.md#ice-restart-pattern)):
1. **TURN server maintenance** (occasional on Cloudflare's network)
2. **Network topology changes** (anycast routing changes)
3. **Credential refresh** during long sessions (>1 hour)
4. **Connection failure** (iceConnectionState === 'failed')
Implement in all production apps:
```typescript
pc.addEventListener('iceconnectionstatechange', async () => {
if (pc.iceConnectionState === 'failed' ||
pc.iceConnectionState === 'disconnected') {
await refreshTURNCredentials(pc);
pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
// Send offer to peer via signaling...
}
});
```
Reference: [RFC 8445 Section 2.4](https://datatracker.ietf.org/doc/html/rfc8445#section-2.4)
## Security Checklist
- [ ] Credentials generated server-side only (never client-side)
- [ ] TURN_KEY_SECRET in wrangler secrets, not vars
- [ ] TTL ≤ expected session duration (and ≤ 48 hours)
- [ ] Rate limiting on credential generation endpoint
- [ ] Client authentication before issuing credentials
- [ ] Credential revocation API for compromised sessions
- [ ] No hardcoded IPs (or DNS monitoring in place)
- [ ] Port 53 filtered for browser clients
## Troubleshooting
### Issue: TURN credentials not working
**Check:**
- Key ID and secret are correct
- Credentials haven't expired (check TTL)
- TTL doesn't exceed 172800 seconds (48 hours)
- Server can reach rtc.live.cloudflare.com
- Network allows outbound HTTPS
**Solution:**
```typescript
// Validate before using
if (ttl > 172800) {
throw new Error('TTL cannot exceed 48 hours');
}
```
### Issue: Slow connection establishment
**Solutions:**
- Ensure proper ICE candidate gathering
- Check network latency to Cloudflare edge
- Verify firewall allows WebRTC ports (3478, 5349, 443)
- Consider using TURN over TLS (port 443) for corporate networks
### Issue: High packet loss
**Check:**
- Not exceeding rate limits (5-10k pps)
- Not exceeding bandwidth limits (50-100 Mbps)
- Not connecting to too many unique IPs (>5/sec)
- Client network quality
### Issue: Connection drops after ~48 hours
**Cause**: Credentials expired (48hr max)
**Solution**:
- Set TTL to expected session duration
- Implement credential refresh with setConfiguration()
- Use ICE restart if connection fails
```typescript
// Refresh credentials before expiry
const refreshInterval = ttl * 1000 - 60000; // 1 min early
setInterval(async () => {
await refreshTURNCredentials(pc);
}, refreshInterval);
```
### Issue: Port 53 URLs in browser fail silently
**Cause**: Chrome/Firefox block port 53
**Solution**: Filter port 53 URLs server-side:
```typescript
const filtered = urls.filter(url => !url.includes(':53'));
```
### Issue: Hardcoded IPs stop working
**Cause**: Cloudflare changed IP addresses (14-day notice)
**Solution**:
- Use DNS hostnames (`turn.cloudflare.com`)
- Monitor DNS changes with automated alerts
- Update allowlists within 14 days if using IP allowlisting
## Cost Optimization
1. Use appropriate TTLs (don't over-provision)
2. Implement credential caching
3. Set `iceTransportPolicy: 'all'` to try direct first (use `'relay'` only when necessary)
4. Monitor bandwidth usage
5. Free when used with Cloudflare Calls SFU
## See Also
- [api.md](./api.md) - Credential generation API, revocation
- [configuration.md](./configuration.md) - IP allowlisting, monitoring
- [patterns.md](./patterns.md) - ICE restart, credential refresh patterns

View File

@@ -0,0 +1,213 @@
# TURN Implementation Patterns
Production-ready patterns for implementing Cloudflare TURN in WebRTC applications.
## Prerequisites
Before implementing these patterns, ensure you have:
- TURN key created: see [api.md#create-turn-key](./api.md#create-turn-key)
- Worker configured: see [configuration.md#cloudflare-worker-integration](./configuration.md#cloudflare-worker-integration)
## Basic TURN Configuration (Browser)
```typescript
interface RTCIceServer {
urls: string | string[];
username?: string;
credential?: string;
credentialType?: "password" | "oauth";
}
async function getTURNConfig(): Promise<RTCIceServer[]> {
const response = await fetch('/api/turn-credentials');
const data = await response.json();
return [
{
urls: 'stun:stun.cloudflare.com:3478'
},
{
urls: [
'turn:turn.cloudflare.com:3478?transport=udp',
'turn:turn.cloudflare.com:3478?transport=tcp',
'turns:turn.cloudflare.com:5349?transport=tcp',
'turns:turn.cloudflare.com:443?transport=tcp'
],
username: data.username,
credential: data.credential,
credentialType: 'password'
}
];
}
// Use in RTCPeerConnection
const iceServers = await getTURNConfig();
const peerConnection = new RTCPeerConnection({ iceServers });
```
## Port Selection Strategy
Recommended order for browser clients:
1. **3478/udp** (primary, lowest latency)
2. **3478/tcp** (fallback for UDP-blocked networks)
3. **5349/tls** (corporate firewalls, most reliable)
4. **443/tls** (alternate TLS port, firewall-friendly)
**Avoid port 53**—blocked by Chrome and Firefox.
```typescript
function filterICEServersForBrowser(urls: string[]): string[] {
return urls
.filter(url => !url.includes(':53')) // Remove port 53
.sort((a, b) => {
// Prioritize UDP over TCP over TLS
if (a.includes('transport=udp')) return -1;
if (b.includes('transport=udp')) return 1;
if (a.includes('transport=tcp') && !a.startsWith('turns:')) return -1;
if (b.includes('transport=tcp') && !b.startsWith('turns:')) return 1;
return 0;
});
}
```
## Credential Refresh (Mid-Session)
When credentials expire during long calls:
```typescript
async function refreshTURNCredentials(pc: RTCPeerConnection): Promise<void> {
const newCreds = await fetch('/turn-credentials').then(r => r.json());
const config = pc.getConfiguration();
config.iceServers = newCreds.iceServers;
pc.setConfiguration(config);
// Note: setConfiguration() does NOT trigger ICE restart
// Combine with restartIce() if connection fails
}
// Auto-refresh before expiry
setInterval(async () => {
await refreshTURNCredentials(peerConnection);
}, 3000000); // 50 minutes if TTL is 1 hour
```
## ICE Restart Pattern
After network change, TURN server maintenance, or credential expiry:
```typescript
pc.addEventListener('iceconnectionstatechange', async () => {
if (pc.iceConnectionState === 'failed') {
console.warn('ICE connection failed, restarting...');
// Refresh credentials
await refreshTURNCredentials(pc);
// Trigger ICE restart
pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
// Send offer to peer via signaling channel...
}
});
```
## Credentials Caching Pattern
```typescript
class TURNCredentialsManager {
private creds: { username: string; credential: string; urls: string[]; expiresAt: number; } | null = null;
async getCredentials(keyId: string, keySecret: string): Promise<RTCIceServer[]> {
const now = Date.now();
if (this.creds && this.creds.expiresAt > now) {
return this.buildIceServers(this.creds);
}
const ttl = 3600;
if (ttl > 172800) throw new Error('TTL max 48hrs');
const res = await fetch(
`https://rtc.live.cloudflare.com/v1/turn/keys/${keyId}/credentials/generate`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${keySecret}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ ttl })
}
);
const data = await res.json();
const filteredUrls = data.iceServers.urls.filter((url: string) => !url.includes(':53'));
this.creds = {
username: data.iceServers.username,
credential: data.iceServers.credential,
urls: filteredUrls,
expiresAt: now + (ttl * 1000) - 60000
};
return this.buildIceServers(this.creds);
}
private buildIceServers(c: { username: string; credential: string; urls: string[] }): RTCIceServer[] {
return [
{ urls: 'stun:stun.cloudflare.com:3478' },
{ urls: c.urls, username: c.username, credential: c.credential, credentialType: 'password' as const }
];
}
}
```
## Common Use Cases
```typescript
// Video conferencing: TURN as fallback
const config = { iceServers: await getTURNConfig(), iceTransportPolicy: 'all' };
// IoT/predictable connectivity: force TURN
const config = { iceServers: await getTURNConfig(), iceTransportPolicy: 'relay' };
// Screen sharing: reduce overhead
const pc = new RTCPeerConnection({ iceServers: await getTURNConfig(), bundlePolicy: 'max-bundle' });
```
## Integration with Cloudflare Calls SFU
```typescript
// TURN is automatically used when needed
// Cloudflare Calls handles TURN + SFU coordination
const session = await callsClient.createSession({
appId: 'your-app-id',
sessionId: 'meeting-123'
});
```
## Debugging ICE Connectivity
```typescript
pc.addEventListener('icecandidate', (event) => {
if (event.candidate) {
console.log('ICE candidate:', event.candidate.type, event.candidate.protocol);
}
});
pc.addEventListener('iceconnectionstatechange', () => {
console.log('ICE state:', pc.iceConnectionState);
});
// Check selected candidate pair
const stats = await pc.getStats();
stats.forEach(report => {
if (report.type === 'candidate-pair' && report.selected) {
console.log('Selected:', report);
}
});
```
## See Also
- [api.md](./api.md) - Credential generation API, types
- [configuration.md](./configuration.md) - Worker setup, environment variables
- [gotchas.md](./gotchas.md) - Common mistakes, troubleshooting