# commonroom-turn A Cloudflare Worker that mints short-lived TURN credentials for CommonRoom. Most WebRTC connections pair up directly, but some cannot: symmetric NAT at both ends, or a firewall that only permits outbound 443. Those pairs need a relay, and CommonRoom uses [Cloudflare Realtime TURN](https://developers.cloudflare.com/realtime/turn/) for it. Relaying is metered, so the credentials are gated behind a token rather than handed to anyone who loads the page. The Worker exists because the browser cannot call Cloudflare's TURN API itself: doing so would put the long-lived API token in a static bundle, where anyone could mint unlimited credentials against the account. The Worker holds that token, checks a room token, and returns only a short-lived ICE configuration. ``` POST / Authorization: Bearer {"room": ""} -> 200 {"iceServers": [...], "expiresAt": } ``` ## Setup 1. **Create a TURN key.** In the Cloudflare dashboard, go to Realtime → TURN and create a key. You get a TURN key ID and an API token. 2. **Choose room tokens.** These are the strings people type into CommonRoom's landing form. Any hard-to-guess strings will do: ```sh openssl rand -base64 24 ``` Several can be configured at once (comma-separated), so different groups can have different tokens and one can be withdrawn without disturbing the others. 3. **Set the secrets and deploy.** ```sh cd worker npm install npx wrangler secret put TURN_KEY_ID npx wrangler secret put TURN_KEY_API_TOKEN npx wrangler secret put ROOM_TOKENS npx wrangler deploy ``` 4. **Point the app at it.** Edit `ALLOWED_ORIGINS` in `wrangler.jsonc` to the origin serving CommonRoom, then set `VITE_TURN_ENDPOINT` to the deployed Worker URL — in `.env.local` for local development, and as the repository variable `VITE_TURN_ENDPOINT` for the GitHub Pages build. That URL is not a secret; the room token is what protects the endpoint. ## Local development ```sh cp .dev.vars.example .dev.vars # then edit in your real key ID and API token npm run dev ``` `.dev.vars` is gitignored. With the example values left as they are, token checks work (`goodtoken` is accepted, anything else gets a 401) but the upstream call to Cloudflare returns 404, which the Worker reports as a 502 — enough to exercise everything except the credential itself. ## Cost Cloudflare Realtime TURN is $0.05/GB after a free tier of 1,000 GB per month, billed on data sent from the Cloudflare edge to the TURN client. A relayed participant in a four-person room at the default `medium` quality preset receives roughly 1 GB per hour, so the free tier covers on the order of 800 participant-hours per month. Only the connections that actually need a relay use one; everyone else pairs directly and costs nothing. Usage is visible per room in the Realtime analytics: the client sends a prefix of the hashed room topic as the credential's `customIdentifier`, which distinguishes rooms from each other without revealing any room name. ## Notes on the token A room token buys a credential, so treat it as a spending key. It is checked in constant time, and `CREDENTIAL_TTL` bounds how long an issued credential stays useful (Cloudflare's maximum is 48 hours). Credentials can also be revoked before they expire: ```sh curl --request POST \ https://rtc.live.cloudflare.com/v1/turn/keys/$TURN_KEY_ID/credentials/$USERNAME/revoke \ --header "Authorization: Bearer $TURN_KEY_API_TOKEN" ``` Note that a credential minted by one participant is shared with everyone else in that room (this is the point — only one person needs a token), so anyone in the room could in principle use it elsewhere until it expires. Keeping `CREDENTIAL_TTL` modest is the main defense; per-room analytics is how abuse would be noticed.