Files
dotfiles/.agents/skills/cloudflare-deploy/references/realtime-sfu/configuration.md
2026-03-17 16:53:22 -07:00

3.4 KiB

Configuration & Deployment

Dashboard Setup

  1. Navigate to https://dash.cloudflare.com/?to=/:account/calls
  2. Click "Create Application" (or use existing app)
  3. Copy CALLS_APP_ID from dashboard
  4. Generate and copy CALLS_APP_SECRET (treat as sensitive credential)
  5. Use credentials in Wrangler config or environment variables below

Dependencies

Backend (Workers): Built-in fetch API, no additional packages required

Client (PartyTracks):

npm install partytracks @cloudflare/calls

Client (React + PartyTracks):

npm install partytracks @cloudflare/calls observable-hooks
# Observable hooks: useObservableAsValue, useValueAsObservable

Client (Raw API): Native browser WebRTC API only

Wrangler Setup

{
  "name": "my-calls-app",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-01", // Use current date for new projects
  "vars": {
    "CALLS_APP_ID": "your-app-id",
    "MAX_WEBCAM_BITRATE": "1200000",
    "MAX_WEBCAM_FRAMERATE": "24",
    "MAX_WEBCAM_QUALITY_LEVEL": "1080"
  },
  // Set secret: wrangler secret put CALLS_APP_SECRET
  "durable_objects": {
    "bindings": [
      {
        "name": "ROOM",
        "class_name": "Room"
      }
    ]
  }
}

Deploy

wrangler login
wrangler secret put CALLS_APP_SECRET
wrangler deploy

Environment Variables

Required:

  • CALLS_APP_ID: From dashboard
  • CALLS_APP_SECRET: From dashboard (secret)

Optional:

  • MAX_WEBCAM_BITRATE (default: 1200000)
  • MAX_WEBCAM_FRAMERATE (default: 24)
  • MAX_WEBCAM_QUALITY_LEVEL (default: 1080)
  • TURN_SERVICE_ID: TURN service
  • TURN_SERVICE_TOKEN: TURN auth (secret)

TURN Configuration

const pc = new RTCPeerConnection({
  iceServers: [
    { 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'
      ],
      username: turnUsername,
      credential: turnCredential
    }
  ],
  bundlePolicy: 'max-bundle', // Recommended: reduces overhead
  iceTransportPolicy: 'all'    // Use 'relay' to force TURN (testing only)
});

Ports: 3478 (UDP/TCP), 53 (UDP), 80 (TCP), 443 (TLS), 5349 (TLS)

When to use TURN: Required for restrictive corporate firewalls/networks that block UDP. ~5-10% of connections fallback to TURN. STUN works for most users.

ICE candidate filtering: Cloudflare handles candidate filtering automatically. No need to manually filter candidates.

Durable Object Boilerplate

Minimal presence system:

export class Room {
  private sessions = new Map<string, {userId: string, tracks: string[]}>();

  async fetch(req: Request) {
    const {pathname} = new URL(req.url);
    const body = await req.json();
    
    if (pathname === '/join') {
      this.sessions.set(body.sessionId, {userId: body.userId, tracks: []});
      return Response.json({participants: this.sessions.size});
    }
    
    if (pathname === '/publish') {
      this.sessions.get(body.sessionId)?.tracks.push(...body.tracks);
      // Broadcast to others via WebSocket (not shown)
      return new Response('OK');
    }
    
    return new Response('Not found', {status: 404});
  }
}

Environment Validation

Check credentials before first API call:

if (!env.CALLS_APP_ID || !env.CALLS_APP_SECRET) {
  throw new Error('CALLS_APP_ID and CALLS_APP_SECRET required');
}