Compare commits

...

4 Commits

Author SHA1 Message Date
David Zhao
a5186108a6 changeset
Some checks failed
Generate / generate (push) Has been cancelled
2026-03-22 22:29:52 -07:00
David Zhao
32f84ffd84 Merge branch 'main' into video-to-rtc 2026-03-22 22:26:38 -07:00
github-actions
4f1c06741a generated protobuf 2026-01-22 22:57:19 +00:00
David Zhao
5ecdf3bc87 use rtc as the grant field, instead of video 2026-01-22 14:55:47 -08:00
3 changed files with 173 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
---
"github.com/livekit/protocol": patch
---
support parsing rtc as grant in addition to video

View File

@@ -15,6 +15,7 @@
package auth
import (
"encoding/json"
"errors"
"maps"
"strings"
@@ -245,6 +246,58 @@ func (c *ClaimGrants) MarshalLogObject(e zapcore.ObjectEncoder) error {
return nil
}
// claimGrantsJSON is used for JSON marshaling/unmarshaling to support both
// "video" (legacy) and "rtc" (new) field names for VideoGrant.
// This enables backwards compatibility during the migration from VideoGrant to RTCGrant.
type claimGrantsJSON struct {
Identity string `json:"identity,omitempty"`
Name string `json:"name,omitempty"`
Kind string `json:"kind,omitempty"`
KindDetails []string `json:"kindDetails,omitempty"`
Video *VideoGrant `json:"video,omitempty"`
RTC *VideoGrant `json:"rtc,omitempty"`
SIP *SIPGrant `json:"sip,omitempty"`
Agent *AgentGrant `json:"agent,omitempty"`
Inference *InferenceGrant `json:"inference,omitempty"`
Observability *ObservabilityGrant `json:"observability,omitempty"`
RoomConfig *RoomConfiguration `json:"roomConfig,omitempty"`
RoomPreset string `json:"roomPreset,omitempty"`
Sha256 string `json:"sha256,omitempty"`
Metadata string `json:"metadata,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
func (c *ClaimGrants) UnmarshalJSON(data []byte) error {
var j claimGrantsJSON
if err := json.Unmarshal(data, &j); err != nil {
return err
}
c.Identity = j.Identity
c.Name = j.Name
c.Kind = j.Kind
c.KindDetails = j.KindDetails
c.SIP = j.SIP
c.Agent = j.Agent
c.Inference = j.Inference
c.Observability = j.Observability
c.RoomConfig = j.RoomConfig
c.RoomPreset = j.RoomPreset
c.Sha256 = j.Sha256
c.Metadata = j.Metadata
c.Attributes = j.Attributes
// Support both "video" (legacy) and "rtc" (new) field names.
// If both are present, "rtc" takes precedence.
if j.RTC != nil {
c.Video = j.RTC
} else {
c.Video = j.Video
}
return nil
}
// -------------------------------------------------------------
type VideoGrant struct {

View File

@@ -15,6 +15,7 @@
package auth
import (
"encoding/json"
"reflect"
"strconv"
"testing"
@@ -159,6 +160,120 @@ func TestGrants(t *testing.T) {
})
}
func TestClaimGrantsVideoRTCCompat(t *testing.T) {
t.Parallel()
t.Run("unmarshal with video field", func(t *testing.T) {
jsonData := `{"identity":"user1","video":{"roomJoin":true,"room":"test-room","canPublish":true}}`
var grants ClaimGrants
err := json.Unmarshal([]byte(jsonData), &grants)
require.NoError(t, err)
require.Equal(t, "user1", grants.Identity)
require.NotNil(t, grants.Video)
require.True(t, grants.Video.RoomJoin)
require.Equal(t, "test-room", grants.Video.Room)
require.True(t, grants.Video.GetCanPublish())
})
t.Run("unmarshal with rtc field", func(t *testing.T) {
jsonData := `{"identity":"user2","rtc":{"roomJoin":true,"room":"rtc-room","canSubscribe":false}}`
var grants ClaimGrants
err := json.Unmarshal([]byte(jsonData), &grants)
require.NoError(t, err)
require.Equal(t, "user2", grants.Identity)
require.NotNil(t, grants.Video)
require.True(t, grants.Video.RoomJoin)
require.Equal(t, "rtc-room", grants.Video.Room)
require.False(t, grants.Video.GetCanSubscribe())
})
t.Run("unmarshal with both video and rtc fields prefers rtc", func(t *testing.T) {
jsonData := `{"identity":"user3","video":{"room":"video-room"},"rtc":{"room":"rtc-room"}}`
var grants ClaimGrants
err := json.Unmarshal([]byte(jsonData), &grants)
require.NoError(t, err)
require.NotNil(t, grants.Video)
require.Equal(t, "rtc-room", grants.Video.Room, "rtc field should take precedence over video")
})
t.Run("marshal outputs video field", func(t *testing.T) {
grants := &ClaimGrants{
Identity: "user4",
Video: &VideoGrant{
RoomJoin: true,
Room: "my-room",
},
}
data, err := json.Marshal(grants)
require.NoError(t, err)
// Verify the output contains "video" not "rtc"
var rawMap map[string]interface{}
err = json.Unmarshal(data, &rawMap)
require.NoError(t, err)
require.Contains(t, rawMap, "video", "marshaled JSON should contain 'video' field")
require.NotContains(t, rawMap, "rtc", "marshaled JSON should not contain 'rtc' field")
})
t.Run("roundtrip with video field preserves data", func(t *testing.T) {
original := &ClaimGrants{
Identity: "user5",
Name: "Test User",
Video: &VideoGrant{
RoomJoin: true,
Room: "test-room",
RoomCreate: true,
},
SIP: &SIPGrant{Admin: true},
}
data, err := json.Marshal(original)
require.NoError(t, err)
var decoded ClaimGrants
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
require.Equal(t, original.Identity, decoded.Identity)
require.Equal(t, original.Name, decoded.Name)
require.Equal(t, original.Video.RoomJoin, decoded.Video.RoomJoin)
require.Equal(t, original.Video.Room, decoded.Video.Room)
require.Equal(t, original.Video.RoomCreate, decoded.Video.RoomCreate)
require.Equal(t, original.SIP.Admin, decoded.SIP.Admin)
})
t.Run("unmarshal with all grant types via rtc", func(t *testing.T) {
jsonData := `{
"identity": "agent1",
"kind": "agent",
"rtc": {"roomJoin": true, "room": "agent-room", "agent": true},
"sip": {"admin": true},
"agent": {"admin": true},
"inference": {"perform": true}
}`
var grants ClaimGrants
err := json.Unmarshal([]byte(jsonData), &grants)
require.NoError(t, err)
require.Equal(t, "agent1", grants.Identity)
require.Equal(t, "agent", grants.Kind)
require.NotNil(t, grants.Video)
require.True(t, grants.Video.RoomJoin)
require.Equal(t, "agent-room", grants.Video.Room)
require.True(t, grants.Video.Agent)
require.NotNil(t, grants.SIP)
require.True(t, grants.SIP.Admin)
require.NotNil(t, grants.Agent)
require.True(t, grants.Agent.Admin)
require.NotNil(t, grants.Inference)
require.True(t, grants.Inference.Perform)
})
}
func TestParticipantKind(t *testing.T) {
const kindMin, kindMax = livekit.ParticipantInfo_STANDARD, livekit.ParticipantInfo_AGENT
for k := kindMin; k <= kindMax; k++ {