Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Preparing Ring content
Preparing Ring content
Preparing Ring content
Use Founder / Developer tabs in the docs sidebar to filter this page. Sidebar visibility is curated in lib/docs/audience-curated-docs.ts.
Ring messenger can place 1:1 audio and video calls inside a direct conversation. Signaling rides the existing Tunnel channel conversation:{id}. Media uses the browser RTCPeerConnection. NAT traversal uses STUNner (STUN/TURN) on the cluster, with ICE servers delivered by an authenticated API — credentials never ship in NEXT_PUBLIC_*.
| Layer | Role |
|---|---|
| UI | Phone / Video on conversation header → full-screen call overlay |
| Signaling | Tunnel events call:invite … call:hangup on conversation:{id} |
| ICE | GET /api/webrtc/ice-servers (session required) |
| TURN | STUNner Gateway TURN-UDP:3478 (e.g. turn.ring-platform.org) |
Related: Messaging · Peer Games · Tunnel Protocol · Real-time architecture
Peer Games share a call ↔ game mutex (features/peer-games/lib/peer-game-mutex.ts). MessagesShell publishes setPeerCallBusy from the WebRTC phase so /games banner and game_request widgets refuse accept while a call is live. Cross-tab sync ships via BroadcastChannel (optional navigator.locks). Peer Games also reuse GET /api/webrtc/ice-servers for optimistic DataChannel move hints — Tunnel + DB remain board SSOT.
Members close deals faster when they can talk without leaving your Ring. Calls stay inside the same conversation that already carries opportunity, entity, or store context.
Conversations, groups, and Tunnel live chat.
The realtime pipe calls reuse for invite / accept / hangup.
Where STUNner and env wiring live for k8s clones.
stunner-system) and Gateway programmed in namespace stunner.Depends-on: calls start from direct conversation headers in the messenger.
Same-workflow: shared call/game mutex (BroadcastChannel); ICE route reused for optimistic DataChannel move hints.
Depends-on: call:invite … call:hangup ride conversation:{id}.
Same-workflow: CALL_INVITE offline OS banner (data-only FCM, 90s TTL, RFC no-op on Chrome).
Use Founder / Developer tabs in the docs sidebar to filter this page. Sidebar visibility is curated in lib/docs/audience-curated-docs.ts.
Ring messenger can place 1:1 audio and video calls inside a direct conversation. Signaling rides the existing Tunnel channel conversation:{id}. Media uses the browser RTCPeerConnection. NAT traversal uses STUNner (STUN/TURN) on the cluster, with ICE servers delivered by an authenticated API — credentials never ship in NEXT_PUBLIC_*.
| Layer | Role |
|---|---|
| UI | Phone / Video on conversation header → full-screen call overlay |
| Signaling | Tunnel events call:invite … call:hangup on conversation:{id} |
| ICE | GET /api/webrtc/ice-servers (session required) |
| TURN | STUNner Gateway TURN-UDP:3478 (e.g. turn.ring-platform.org) |
Related: Messaging · Peer Games · Tunnel Protocol · Real-time architecture
Peer Games share a call ↔ game mutex (features/peer-games/lib/peer-game-mutex.ts). MessagesShell publishes setPeerCallBusy from the WebRTC phase so /games banner and game_request widgets refuse accept while a call is live. Cross-tab sync ships via BroadcastChannel (optional navigator.locks). Peer Games also reuse GET /api/webrtc/ice-servers for optimistic DataChannel move hints — Tunnel + DB remain board SSOT.
Members close deals faster when they can talk without leaving your Ring. Calls stay inside the same conversation that already carries opportunity, entity, or store context.
Conversations, groups, and Tunnel live chat.
The realtime pipe calls reuse for invite / accept / hangup.
Where STUNner and env wiring live for k8s clones.
stunner-system) and Gateway programmed in namespace stunner.Depends-on: calls start from direct conversation headers in the messenger.
Same-workflow: shared call/game mutex (BroadcastChannel); ICE route reused for optimistic DataChannel move hints.
Depends-on: call:invite … call:hangup ride conversation:{id}.
Same-workflow: CALL_INVITE offline OS banner (data-only FCM, 90s TTL, RFC no-op on Chrome).
Use Founder / Developer tabs in the docs sidebar to filter this page. Sidebar visibility is curated in lib/docs/audience-curated-docs.ts.
Ring messenger can place 1:1 audio and video calls inside a direct conversation. Signaling rides the existing Tunnel channel conversation:{id}. Media uses the browser RTCPeerConnection. NAT traversal uses STUNner (STUN/TURN) on the cluster, with ICE servers delivered by an authenticated API — credentials never ship in NEXT_PUBLIC_*.
| Layer | Role |
|---|---|
| UI | Phone / Video on conversation header → full-screen call overlay |
| Signaling | Tunnel events call:invite … call:hangup on conversation:{id} |
| ICE | GET /api/webrtc/ice-servers (session required) |
| TURN | STUNner Gateway TURN-UDP:3478 (e.g. turn.ring-platform.org) |
Related: Messaging · Peer Games · Tunnel Protocol · Real-time architecture
Peer Games share a call ↔ game mutex (features/peer-games/lib/peer-game-mutex.ts). MessagesShell publishes setPeerCallBusy from the WebRTC phase so /games banner and game_request widgets refuse accept while a call is live. Cross-tab sync ships via BroadcastChannel (optional navigator.locks). Peer Games also reuse GET /api/webrtc/ice-servers for optimistic DataChannel move hints — Tunnel + DB remain board SSOT.
Members close deals faster when they can talk without leaving your Ring. Calls stay inside the same conversation that already carries opportunity, entity, or store context.
Conversations, groups, and Tunnel live chat.
The realtime pipe calls reuse for invite / accept / hangup.
Where STUNner and env wiring live for k8s clones.
stunner-system) and Gateway programmed in namespace stunner.Depends-on: calls start from direct conversation headers in the messenger.
Same-workflow: shared call/game mutex (BroadcastChannel); ICE route reused for optimistic DataChannel move hints.
Depends-on: call:invite … call:hangup ride conversation:{id}.
Same-workflow: CALL_INVITE offline OS banner (data-only FCM, 90s TTL, RFC no-op on Chrome).
turn.ring-platform.org A/AAAA → node LB IP).WEBRTC_STUN_URL, WEBRTC_TURN_URL, WEBRTC_TURN_USERNAME, WEBRTC_TURN_CREDENTIAL.Calls require a live Tunnel session. If realtime is disconnected, the UI refuses to start a call — fix Tunnel first (Tunnel Protocol).
features/chat/components/conversation-header.tsx |
| Phone / Video actions (direct only) |
features/messages/components/messages-shell.tsx | Wires hook + overlay |
infrastructure/k3s-or/stunner/ | GatewayClass, GatewayConfig, Gateway manifests |
k8s/ENV-PROD-WIRING.md | Prod env table for WEBRTC_* |
GET /api/webrtc/ice-servers — requires Auth.js session (401 if anonymous).
Response shape (verified):
STUN falls back to stun:stun.l.google.com:19302 when WEBRTC_STUN_URL is unset. TURN is added only when all of WEBRTC_TURN_URL, WEBRTC_TURN_USERNAME, and WEBRTC_TURN_CREDENTIAL are set.
| Variable | Where | Notes |
|---|---|---|
WEBRTC_STUN_URL | ConfigMap | Optional; Google STUN default in route |
WEBRTC_TURN_URL | ConfigMap | e.g. turn:turn.ring-platform.org:3478?transport=udp |
WEBRTC_TURN_USERNAME | Secret ring-platform-org-secrets | Matches STUNner auth Secret |
WEBRTC_TURN_CREDENTIAL | Secret | Never NEXT_PUBLIC_* |
Examples: k8s/secrets.example.yaml, wiring notes: k8s/ENV-PROD-WIRING.md.
env.local.template does not yet list these keys — use the k8s examples for local/prod parity.
Published on channel conversation:{conversationId} (same channel as typing):
| Event | Purpose |
|---|---|
call:invite | Outgoing ring |
call:accept / call:reject | Callee decision |
call:offer / call:answer | SDP |
call:ice | ICE candidates |
call:hangup | Tear down |
Subscribe with useTunnelChannel — do not raw-subscribe in effects (Tunnel Protocol).
Install operator (pulls Gateway API + STUNner CRDs):
Create auth Secret (type=static, username, password) and apply infrastructure/k3s-or/stunner/gateway.yaml (GatewayClass + GatewayConfig + Gateway with protocol TURN-UDP).
Wire app Secret/ConfigMap + ensure Deployment env refs for WEBRTC_TURN_USERNAME / WEBRTC_TURN_CREDENTIAL (k8s/deployment.yaml). Restart the app Deployment.
Smoke: external STUN Binding to :3478; authenticated GET /api/webrtc/ice-servers; two browsers on the same direct conversation.
Listener protocol must be TURN-UDP, not bare UDP. Realm in GatewayConfig is alphanumeric + hyphen (e.g. ring-platform-org). Empty UDPRoute backendRefs are invalid — peer TURN does not need a UDPRoute until an SFU (LiveKit) exists.
Connect Platform’s RTVS / FastTransponder stack is not ported — only control UX patterns (mute / camera / end) were absorbed.
See-also: conversation and typing HTTP contracts.
{
"success": true,
"data": {
"iceServers": [
{ "urls": "stun:…" },
{ "urls": "turn:…", "username": "…", "credential": "…" }
],
"turnConfigured": true
}
}turn.ring-platform.org A/AAAA → node LB IP).WEBRTC_STUN_URL, WEBRTC_TURN_URL, WEBRTC_TURN_USERNAME, WEBRTC_TURN_CREDENTIAL.Calls require a live Tunnel session. If realtime is disconnected, the UI refuses to start a call — fix Tunnel first (Tunnel Protocol).
features/chat/components/conversation-header.tsx |
| Phone / Video actions (direct only) |
features/messages/components/messages-shell.tsx | Wires hook + overlay |
infrastructure/k3s-or/stunner/ | GatewayClass, GatewayConfig, Gateway manifests |
k8s/ENV-PROD-WIRING.md | Prod env table for WEBRTC_* |
GET /api/webrtc/ice-servers — requires Auth.js session (401 if anonymous).
Response shape (verified):
STUN falls back to stun:stun.l.google.com:19302 when WEBRTC_STUN_URL is unset. TURN is added only when all of WEBRTC_TURN_URL, WEBRTC_TURN_USERNAME, and WEBRTC_TURN_CREDENTIAL are set.
| Variable | Where | Notes |
|---|---|---|
WEBRTC_STUN_URL | ConfigMap | Optional; Google STUN default in route |
WEBRTC_TURN_URL | ConfigMap | e.g. turn:turn.ring-platform.org:3478?transport=udp |
WEBRTC_TURN_USERNAME | Secret ring-platform-org-secrets | Matches STUNner auth Secret |
WEBRTC_TURN_CREDENTIAL | Secret | Never NEXT_PUBLIC_* |
Examples: k8s/secrets.example.yaml, wiring notes: k8s/ENV-PROD-WIRING.md.
env.local.template does not yet list these keys — use the k8s examples for local/prod parity.
Published on channel conversation:{conversationId} (same channel as typing):
| Event | Purpose |
|---|---|
call:invite | Outgoing ring |
call:accept / call:reject | Callee decision |
call:offer / call:answer | SDP |
call:ice | ICE candidates |
call:hangup | Tear down |
Subscribe with useTunnelChannel — do not raw-subscribe in effects (Tunnel Protocol).
Install operator (pulls Gateway API + STUNner CRDs):
Create auth Secret (type=static, username, password) and apply infrastructure/k3s-or/stunner/gateway.yaml (GatewayClass + GatewayConfig + Gateway with protocol TURN-UDP).
Wire app Secret/ConfigMap + ensure Deployment env refs for WEBRTC_TURN_USERNAME / WEBRTC_TURN_CREDENTIAL (k8s/deployment.yaml). Restart the app Deployment.
Smoke: external STUN Binding to :3478; authenticated GET /api/webrtc/ice-servers; two browsers on the same direct conversation.
Listener protocol must be TURN-UDP, not bare UDP. Realm in GatewayConfig is alphanumeric + hyphen (e.g. ring-platform-org). Empty UDPRoute backendRefs are invalid — peer TURN does not need a UDPRoute until an SFU (LiveKit) exists.
Connect Platform’s RTVS / FastTransponder stack is not ported — only control UX patterns (mute / camera / end) were absorbed.
See-also: conversation and typing HTTP contracts.
{
"success": true,
"data": {
"iceServers": [
{ "urls": "stun:…" },
{ "urls": "turn:…", "username": "…", "credential": "…" }
],
"turnConfigured": true
}
}turn.ring-platform.org A/AAAA → node LB IP).WEBRTC_STUN_URL, WEBRTC_TURN_URL, WEBRTC_TURN_USERNAME, WEBRTC_TURN_CREDENTIAL.Calls require a live Tunnel session. If realtime is disconnected, the UI refuses to start a call — fix Tunnel first (Tunnel Protocol).
features/chat/components/conversation-header.tsx |
| Phone / Video actions (direct only) |
features/messages/components/messages-shell.tsx | Wires hook + overlay |
infrastructure/k3s-or/stunner/ | GatewayClass, GatewayConfig, Gateway manifests |
k8s/ENV-PROD-WIRING.md | Prod env table for WEBRTC_* |
GET /api/webrtc/ice-servers — requires Auth.js session (401 if anonymous).
Response shape (verified):
STUN falls back to stun:stun.l.google.com:19302 when WEBRTC_STUN_URL is unset. TURN is added only when all of WEBRTC_TURN_URL, WEBRTC_TURN_USERNAME, and WEBRTC_TURN_CREDENTIAL are set.
| Variable | Where | Notes |
|---|---|---|
WEBRTC_STUN_URL | ConfigMap | Optional; Google STUN default in route |
WEBRTC_TURN_URL | ConfigMap | e.g. turn:turn.ring-platform.org:3478?transport=udp |
WEBRTC_TURN_USERNAME | Secret ring-platform-org-secrets | Matches STUNner auth Secret |
WEBRTC_TURN_CREDENTIAL | Secret | Never NEXT_PUBLIC_* |
Examples: k8s/secrets.example.yaml, wiring notes: k8s/ENV-PROD-WIRING.md.
env.local.template does not yet list these keys — use the k8s examples for local/prod parity.
Published on channel conversation:{conversationId} (same channel as typing):
| Event | Purpose |
|---|---|
call:invite | Outgoing ring |
call:accept / call:reject | Callee decision |
call:offer / call:answer | SDP |
call:ice | ICE candidates |
call:hangup | Tear down |
Subscribe with useTunnelChannel — do not raw-subscribe in effects (Tunnel Protocol).
Install operator (pulls Gateway API + STUNner CRDs):
Create auth Secret (type=static, username, password) and apply infrastructure/k3s-or/stunner/gateway.yaml (GatewayClass + GatewayConfig + Gateway with protocol TURN-UDP).
Wire app Secret/ConfigMap + ensure Deployment env refs for WEBRTC_TURN_USERNAME / WEBRTC_TURN_CREDENTIAL (k8s/deployment.yaml). Restart the app Deployment.
Smoke: external STUN Binding to :3478; authenticated GET /api/webrtc/ice-servers; two browsers on the same direct conversation.
Listener protocol must be TURN-UDP, not bare UDP. Realm in GatewayConfig is alphanumeric + hyphen (e.g. ring-platform-org). Empty UDPRoute backendRefs are invalid — peer TURN does not need a UDPRoute until an SFU (LiveKit) exists.
Connect Platform’s RTVS / FastTransponder stack is not ported — only control UX patterns (mute / camera / end) were absorbed.
See-also: conversation and typing HTTP contracts.
{
"success": true,
"data": {
"iceServers": [
{ "urls": "stun:…" },
{ "urls": "turn:…", "username": "…", "credential": "…" }
],
"turnConfigured": true
}
}