76 lines
2.9 KiB
Go
76 lines
2.9 KiB
Go
|
|
package handlers
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
|
||
|
|
"veza-backend-api/internal/config"
|
||
|
|
|
||
|
|
"github.com/gin-gonic/gin"
|
||
|
|
)
|
||
|
|
|
||
|
|
// IceServer mirrors the WebRTC `RTCIceServer` dictionary. Username and
|
||
|
|
// credential are omitted (not zero-valued) when empty so STUN-only
|
||
|
|
// entries don't carry meaningless auth fields, and so frontends that
|
||
|
|
// validate the response don't have to special-case empty strings.
|
||
|
|
//
|
||
|
|
// Wire shape is intentionally lowercase to match the JS API the
|
||
|
|
// frontend feeds directly into `new RTCPeerConnection({ iceServers })`.
|
||
|
|
type IceServer struct {
|
||
|
|
URLs []string `json:"urls"`
|
||
|
|
Username string `json:"username,omitempty"`
|
||
|
|
Credential string `json:"credential,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// WebRTCConfigResponse is the body of GET /api/v1/config/webrtc. The
|
||
|
|
// frontend caches it for the lifetime of the page and uses it to build
|
||
|
|
// every RTCPeerConnection (1:1 calls today, screen-share / multi-party
|
||
|
|
// later). Public endpoint by design — TURN credentials returned here
|
||
|
|
// are short-lived rotation candidates, never long-lived secrets.
|
||
|
|
type WebRTCConfigResponse struct {
|
||
|
|
IceServers []IceServer `json:"iceServers"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetWebRTCConfig returns the ICE-server set the frontend should hand to
|
||
|
|
// `new RTCPeerConnection`. v1.0.9 item 1.2 — closes the
|
||
|
|
// FUNCTIONAL_AUDIT.md §4 #1 NAT-traversal gap. Before this endpoint the
|
||
|
|
// frontend hardcoded `stun:stun.l.google.com:19302`, which works on
|
||
|
|
// flat networks but fails behind symmetric NAT (corporate, mobile
|
||
|
|
// carrier, Incus container default networking). Returning the operator-
|
||
|
|
// configured TURN block lets the browser fall back to a relay when
|
||
|
|
// hole-punching fails, which is the only thing that makes WebRTC reach
|
||
|
|
// "actually works for end users" status.
|
||
|
|
//
|
||
|
|
// @Summary WebRTC ICE configuration
|
||
|
|
// @Description Public — returns the ICE-server set the SPA feeds to RTCPeerConnection. STUN-only when no TURN is configured. TURN credentials are always emitted as static (REST shared-secret rotation deferred to v1.1).
|
||
|
|
// @Tags Config
|
||
|
|
// @Produce json
|
||
|
|
// @Success 200 {object} handlers.WebRTCConfigResponse "ICE servers"
|
||
|
|
// @Router /config/webrtc [get]
|
||
|
|
func GetWebRTCConfig(cfg *config.Config) gin.HandlerFunc {
|
||
|
|
return func(c *gin.Context) {
|
||
|
|
resp := WebRTCConfigResponse{
|
||
|
|
IceServers: []IceServer{},
|
||
|
|
}
|
||
|
|
|
||
|
|
if cfg != nil && len(cfg.WebRTCStunURLs) > 0 {
|
||
|
|
resp.IceServers = append(resp.IceServers, IceServer{URLs: cfg.WebRTCStunURLs})
|
||
|
|
}
|
||
|
|
|
||
|
|
// TURN block is only emitted when fully configured. Half-configured
|
||
|
|
// is worse than missing — the browser would surface auth failures
|
||
|
|
// instead of falling back cleanly to STUN-only.
|
||
|
|
if cfg != nil &&
|
||
|
|
len(cfg.WebRTCTurnURLs) > 0 &&
|
||
|
|
cfg.WebRTCTurnUsername != "" &&
|
||
|
|
cfg.WebRTCTurnCredential != "" {
|
||
|
|
resp.IceServers = append(resp.IceServers, IceServer{
|
||
|
|
URLs: cfg.WebRTCTurnURLs,
|
||
|
|
Username: cfg.WebRTCTurnUsername,
|
||
|
|
Credential: cfg.WebRTCTurnCredential,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
c.JSON(http.StatusOK, resp)
|
||
|
|
}
|
||
|
|
}
|