From b8eed72f961e8a4304724a24c1bdf382571d00ba Mon Sep 17 00:00:00 2001 From: senke Date: Sun, 26 Apr 2026 23:38:42 +0200 Subject: [PATCH] feat(webrtc): coturn ICE config endpoint + frontend wiring + ops template (v1.0.9 item 1.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes FUNCTIONAL_AUDIT.md §4 #1: WebRTC 1:1 calls had working signaling but no NAT traversal, so calls between two peers behind symmetric NAT (corporate firewalls, mobile carrier CGNAT, Incus container default networking) failed silently after the SDP exchange. Backend: - GET /api/v1/config/webrtc (public) returns {iceServers: [...]} built from WEBRTC_STUN_URLS / WEBRTC_TURN_URLS / *_USERNAME / *_CREDENTIAL env vars. Half-config (URLs without creds, or vice versa) deliberately omits the TURN block — a half-configured TURN surfaces auth errors at call time instead of falling back cleanly to STUN-only. - 4 handler tests cover the matrix. Frontend: - services/api/webrtcConfig.ts caches the config for the page lifetime and falls back to the historical hardcoded Google STUN if the fetch fails. - useWebRTC fetches at mount, hands iceServers synchronously to every RTCPeerConnection, exposes a {hasTurn, loaded} hint. - CallButton tooltip warns up-front when TURN isn't configured instead of letting calls time out silently. Ops: - infra/coturn/turnserver.conf — annotated template with the SSRF- safe denied-peer-ip ranges, prometheus exporter, TLS for TURNS, static lt-cred-mech (REST-secret rotation deferred to v1.1). - infra/coturn/README.md — Incus deploy walkthrough, smoke test via turnutils_uclient, capacity rules of thumb. - docs/ENV_VARIABLES.md gains a 13bis. WebRTC ICE servers section. Coturn deployment itself is a separate ops action — this commit lands the plumbing so the deploy can light up the path with zero code changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../features/chat/components/CallButton.tsx | 13 +- .../src/features/chat/components/ChatRoom.tsx | 1 + apps/web/src/features/chat/hooks/useWebRTC.ts | 56 ++++++- .../web/src/services/api/webrtcConfig.test.ts | 104 +++++++++++++ apps/web/src/services/api/webrtcConfig.ts | 83 ++++++++++ .../src/services/generated/config/config.ts | 146 ++++++++++++++++++ .../web/src/services/generated/model/index.ts | 2 + .../model/internalHandlersIceServer.ts | 13 ++ .../internalHandlersWebRTCConfigResponse.ts | 12 ++ docs/ENV_VARIABLES.md | 19 +++ infra/coturn/README.md | 117 ++++++++++++++ infra/coturn/turnserver.conf | 100 ++++++++++++ veza-backend-api/docs/docs.go | 48 ++++++ veza-backend-api/docs/swagger.json | 48 ++++++ veza-backend-api/docs/swagger.yaml | 33 ++++ veza-backend-api/internal/api/routes_core.go | 9 ++ veza-backend-api/internal/config/config.go | 28 ++++ .../handlers/webrtc_config_handler.go | 75 +++++++++ .../handlers/webrtc_config_handler_test.go | 117 ++++++++++++++ veza-backend-api/openapi.yaml | 33 ++++ 20 files changed, 1053 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/services/api/webrtcConfig.test.ts create mode 100644 apps/web/src/services/api/webrtcConfig.ts create mode 100644 apps/web/src/services/generated/config/config.ts create mode 100644 apps/web/src/services/generated/model/internalHandlersIceServer.ts create mode 100644 apps/web/src/services/generated/model/internalHandlersWebRTCConfigResponse.ts create mode 100644 infra/coturn/README.md create mode 100644 infra/coturn/turnserver.conf create mode 100644 veza-backend-api/internal/handlers/webrtc_config_handler.go create mode 100644 veza-backend-api/internal/handlers/webrtc_config_handler_test.go diff --git a/apps/web/src/features/chat/components/CallButton.tsx b/apps/web/src/features/chat/components/CallButton.tsx index 9d13e0403..887156dc0 100644 --- a/apps/web/src/features/chat/components/CallButton.tsx +++ b/apps/web/src/features/chat/components/CallButton.tsx @@ -8,6 +8,13 @@ interface CallButtonProps { targetUserId: string; onCall: () => void; disabled?: boolean; + /** + * v1.0.9 item 1.2 — operator-configured TURN coverage status + * (forwarded from `useWebRTC().nat`). When false, the tooltip warns + * the user up-front that calls may fail behind symmetric NAT, instead + * of letting the connection time out silently. + */ + hasTurn?: boolean; } export function CallButton({ @@ -15,9 +22,13 @@ export function CallButton({ targetUserId: _targetUserId, onCall, disabled = false, + hasTurn, }: CallButtonProps) { + const tooltip = hasTurn + ? 'Démarrer un appel' + : 'Fonctionne mieux sur le même réseau local (TURN non configuré)'; return ( - +