From 9d569e2fef610dc6f64998e45e3b4fbe1eb83b77 Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Mon, 16 Jun 2025 21:01:15 -0700 Subject: [PATCH] Take ClientInfo from request. (#3738) This will allow WHIP client to pass in client info which can provide more information about WHIP clients for telemetry. --- pkg/service/rtcrestservice.go | 12 ++++-- pkg/service/rtcservice.go | 76 +---------------------------------- pkg/service/utils.go | 73 +++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 79 deletions(-) diff --git a/pkg/service/rtcrestservice.go b/pkg/service/rtcrestservice.go index 10b536203..23db4d6ad 100644 --- a/pkg/service/rtcrestservice.go +++ b/pkg/service/rtcrestservice.go @@ -169,14 +169,18 @@ func (s *RTCRestService) validateCreate(r *http.Request) (*createRequest, int, e return nil, http.StatusBadRequest, fmt.Errorf("malformed SDP offer: %s", err) } + ci := ParseClientInfo(r) + if ci.Protocol == 0 { + // if no client info available (which will be mostly the case with WHIP clients), at least set protocol + ci.Protocol = types.CurrentProtocol + } + pi := routing.ParticipantInit{ Identity: livekit.ParticipantIdentity(claims.Identity), Name: livekit.ParticipantName(claims.Name), AutoSubscribe: true, - Client: &livekit.ClientInfo{ - Protocol: types.CurrentProtocol, - }, - Grants: claims, + Client: ci, + Grants: claims, CreateRoom: &livekit.CreateRoomRequest{ Name: string(roomName), RoomPreset: claims.RoomPreset, diff --git a/pkg/service/rtcservice.go b/pkg/service/rtcservice.go index 909463ed6..711cf21b6 100644 --- a/pkg/service/rtcservice.go +++ b/pkg/service/rtcservice.go @@ -28,7 +28,6 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/ua-parser/uap-go/uaparser" "go.uber.org/atomic" "golang.org/x/exp/maps" @@ -52,7 +51,6 @@ type RTCService struct { config *config.Config isDev bool limits config.LimitConfig - parser *uaparser.Parser telemetry telemetry.TelemetryService mu sync.Mutex @@ -71,7 +69,6 @@ func NewRTCService( config: conf, isDev: conf.Development, limits: conf.Limit, - parser: uaparser.NewFromSaved(), telemetry: telemetry, connections: map[*websocket.Conn]struct{}{}, } @@ -229,7 +226,7 @@ func (s *RTCService) validateInternal(log logger.Logger, r *http.Request, strict Identity: livekit.ParticipantIdentity(claims.Identity), Name: livekit.ParticipantName(claims.Name), AutoSubscribe: true, - Client: s.ParseClientInfo(r), + Client: ParseClientInfo(r), Grants: claims, Region: region, CreateRoom: createRequest, @@ -549,77 +546,6 @@ func (s *RTCService) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -func (s *RTCService) ParseClientInfo(r *http.Request) *livekit.ClientInfo { - values := r.Form - ci := &livekit.ClientInfo{} - if pv, err := strconv.Atoi(values.Get("protocol")); err == nil { - ci.Protocol = int32(pv) - } - sdkString := values.Get("sdk") - switch sdkString { - case "js": - ci.Sdk = livekit.ClientInfo_JS - case "ios", "swift": - ci.Sdk = livekit.ClientInfo_SWIFT - case "android": - ci.Sdk = livekit.ClientInfo_ANDROID - case "flutter": - ci.Sdk = livekit.ClientInfo_FLUTTER - case "go": - ci.Sdk = livekit.ClientInfo_GO - case "unity": - ci.Sdk = livekit.ClientInfo_UNITY - case "reactnative": - ci.Sdk = livekit.ClientInfo_REACT_NATIVE - case "rust": - ci.Sdk = livekit.ClientInfo_RUST - case "python": - ci.Sdk = livekit.ClientInfo_PYTHON - case "cpp": - ci.Sdk = livekit.ClientInfo_CPP - case "unityweb": - ci.Sdk = livekit.ClientInfo_UNITY_WEB - case "node": - ci.Sdk = livekit.ClientInfo_NODE - } - - ci.Version = values.Get("version") - ci.Os = values.Get("os") - ci.OsVersion = values.Get("os_version") - ci.Browser = values.Get("browser") - ci.BrowserVersion = values.Get("browser_version") - ci.DeviceModel = values.Get("device_model") - ci.Network = values.Get("network") - // get real address (forwarded http header) - check Cloudflare headers first, fall back to X-Forwarded-For - ci.Address = GetClientIP(r) - - // attempt to parse types for SDKs that support browser as a platform - if ci.Sdk == livekit.ClientInfo_JS || - ci.Sdk == livekit.ClientInfo_REACT_NATIVE || - ci.Sdk == livekit.ClientInfo_FLUTTER || - ci.Sdk == livekit.ClientInfo_UNITY { - client := s.parser.Parse(r.UserAgent()) - if ci.Browser == "" { - ci.Browser = client.UserAgent.Family - ci.BrowserVersion = client.UserAgent.ToVersionString() - } - if ci.Os == "" { - ci.Os = client.Os.Family - ci.OsVersion = client.Os.ToVersionString() - } - if ci.DeviceModel == "" { - model := client.Device.Family - if model != "" && client.Device.Model != "" && model != client.Device.Model { - model += " " + client.Device.Model - } - - ci.DeviceModel = model - } - } - - return ci -} - func (s *RTCService) DrainConnections(interval time.Duration) { s.mu.Lock() conns := maps.Clone(s.connections) diff --git a/pkg/service/utils.go b/pkg/service/utils.go index bd930875c..66d7b8e30 100644 --- a/pkg/service/utils.go +++ b/pkg/service/utils.go @@ -20,10 +20,12 @@ import ( "net" "net/http" "regexp" + "strconv" "strings" "github.com/livekit/livekit-server/pkg/utils" "github.com/livekit/protocol/livekit" + "github.com/ua-parser/uap-go/uaparser" ) func HandleError(w http.ResponseWriter, r *http.Request, status int, err error, keysAndValues ...interface{}) { @@ -82,3 +84,74 @@ func SetRoomConfiguration(createRequest *livekit.CreateRoomRequest, conf *liveki createRequest.MaxPlayoutDelay = conf.MaxPlayoutDelay createRequest.SyncStreams = conf.SyncStreams } + +func ParseClientInfo(r *http.Request) *livekit.ClientInfo { + values := r.Form + ci := &livekit.ClientInfo{} + if pv, err := strconv.Atoi(values.Get("protocol")); err == nil { + ci.Protocol = int32(pv) + } + sdkString := values.Get("sdk") + switch sdkString { + case "js": + ci.Sdk = livekit.ClientInfo_JS + case "ios", "swift": + ci.Sdk = livekit.ClientInfo_SWIFT + case "android": + ci.Sdk = livekit.ClientInfo_ANDROID + case "flutter": + ci.Sdk = livekit.ClientInfo_FLUTTER + case "go": + ci.Sdk = livekit.ClientInfo_GO + case "unity": + ci.Sdk = livekit.ClientInfo_UNITY + case "reactnative": + ci.Sdk = livekit.ClientInfo_REACT_NATIVE + case "rust": + ci.Sdk = livekit.ClientInfo_RUST + case "python": + ci.Sdk = livekit.ClientInfo_PYTHON + case "cpp": + ci.Sdk = livekit.ClientInfo_CPP + case "unityweb": + ci.Sdk = livekit.ClientInfo_UNITY_WEB + case "node": + ci.Sdk = livekit.ClientInfo_NODE + } + + ci.Version = values.Get("version") + ci.Os = values.Get("os") + ci.OsVersion = values.Get("os_version") + ci.Browser = values.Get("browser") + ci.BrowserVersion = values.Get("browser_version") + ci.DeviceModel = values.Get("device_model") + ci.Network = values.Get("network") + // get real address (forwarded http header) - check Cloudflare headers first, fall back to X-Forwarded-For + ci.Address = GetClientIP(r) + + // attempt to parse types for SDKs that support browser as a platform + if ci.Sdk == livekit.ClientInfo_JS || + ci.Sdk == livekit.ClientInfo_REACT_NATIVE || + ci.Sdk == livekit.ClientInfo_FLUTTER || + ci.Sdk == livekit.ClientInfo_UNITY { + client := uaparser.NewFromSaved().Parse(r.UserAgent()) + if ci.Browser == "" { + ci.Browser = client.UserAgent.Family + ci.BrowserVersion = client.UserAgent.ToVersionString() + } + if ci.Os == "" { + ci.Os = client.Os.Family + ci.OsVersion = client.Os.ToVersionString() + } + if ci.DeviceModel == "" { + model := client.Device.Family + if model != "" && client.Device.Model != "" && model != client.Device.Model { + model += " " + client.Device.Model + } + + ci.DeviceModel = model + } + } + + return ci +}