This commit is contained in:
Joachim Bauch 2026-01-23 19:47:35 +03:00 committed by GitHub
commit 6c50345f13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 636 additions and 825 deletions

View file

@ -26,6 +26,7 @@ import (
"errors"
"fmt"
"log"
"maps"
"net"
"net/url"
"slices"
@ -1177,13 +1178,62 @@ type InternalServerMessage struct {
// Type "event"
type UserData = StringMap
func (d UserData) Clone() UserData {
return maps.Clone(d)
}
func (d UserData) SessionId() (PublicSessionId, bool) {
return GetStringMapString[PublicSessionId](StringMap(d), "sessionId")
}
type UserDataMap map[PublicSessionId]UserData
func (m UserDataMap) Users() UserDataList {
if len(m) == 0 {
return nil
}
return slices.Collect(maps.Values(m))
}
type UserDataList []UserData
func (l UserDataList) Clone() UserDataList {
if len(l) == 0 {
return nil
}
clone := make(UserDataList, len(l))
for idx, u := range l {
clone[idx] = u.Clone()
}
return clone
}
func (l UserDataList) Map() UserDataMap {
if len(l) == 0 {
return nil
}
result := make(UserDataMap, len(l))
for _, d := range l {
sid, found := d.SessionId()
if found {
result[sid] = d
}
}
return result
}
type RoomEventServerMessage struct {
RoomId string `json:"roomid"`
Properties json.RawMessage `json:"properties,omitempty"`
// TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk.
InCall json.RawMessage `json:"incall,omitempty"`
Changed []StringMap `json:"changed,omitempty"`
Users []StringMap `json:"users,omitempty"`
Changed UserDataList `json:"changed,omitempty"`
Users UserDataList `json:"users,omitempty"`
All bool `json:"all,omitempty"`
}

View file

@ -1144,9 +1144,9 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(in *jl
in.Delim('[')
if out.Changed == nil {
if !in.IsDelim(']') {
out.Changed = make([]StringMap, 0, 8)
out.Changed = make(UserDataList, 0, 8)
} else {
out.Changed = []StringMap{}
out.Changed = UserDataList{}
}
} else {
out.Changed = (out.Changed)[:0]
@ -1191,9 +1191,9 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi8(in *jl
in.Delim('[')
if out.Users == nil {
if !in.IsDelim(']') {
out.Users = make([]StringMap, 0, 8)
out.Users = make(UserDataList, 0, 8)
} else {
out.Users = []StringMap{}
out.Users = UserDataList{}
}
} else {
out.Users = (out.Users)[:0]
@ -1741,9 +1741,9 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(in *j
in.Delim('[')
if out.Changed == nil {
if !in.IsDelim(']') {
out.Changed = make([]StringMap, 0, 8)
out.Changed = make(UserDataList, 0, 8)
} else {
out.Changed = []StringMap{}
out.Changed = UserDataList{}
}
} else {
out.Changed = (out.Changed)[:0]
@ -1788,9 +1788,9 @@ func easyjson6128dd2DecodeGithubComStrukturagNextcloudSpreedSignalingApi13(in *j
in.Delim('[')
if out.Users == nil {
if !in.IsDelim(']') {
out.Users = make([]StringMap, 0, 8)
out.Users = make(UserDataList, 0, 8)
} else {
out.Users = []StringMap{}
out.Users = UserDataList{}
}
} else {
out.Users = (out.Users)[:0]

View file

@ -603,3 +603,77 @@ func TestNoFilterSDPCandidates(t *testing.T) {
assert.Equal(mock.MockSdpOfferAudioOnlyNoFilter, strings.ReplaceAll(string(encoded), "\r\n", "\n"))
}
}
func TestUserData(t *testing.T) {
t.Parallel()
assert := assert.New(t)
type s struct {
value any
}
d := UserData{
"foo": "bar",
"baz": &s{
value: 1234,
},
}
// Clone is *not* a deep copy.
d2 := d.Clone()
assert.Equal(d, d2)
d["baz"].(*s).value = 2345
assert.Equal(UserData{
"foo": "bar",
"baz": &s{
value: 2345,
},
}, d2)
delete(d2, "foo")
assert.Equal(UserData{
"baz": &s{
value: 2345,
},
}, d2)
sid, found := d2.SessionId()
assert.False(found, "expected no session id, got %s", sid)
d2["sessionid"] = "not-found"
sid, found = d2.SessionId()
assert.False(found, "expected no session id, got %s", sid)
d2["sessionId"] = "session-id"
if sid, found = d2.SessionId(); assert.True(found) {
assert.EqualValues("session-id", sid)
}
}
func TestUserDataList(t *testing.T) {
t.Parallel()
assert := assert.New(t)
l := UserDataList{
{
"foo": "bar",
},
}
// Clone is a deep clone.
l2 := l.Clone()
assert.Equal(l, l2)
l[0]["bar"] = "baz"
assert.NotEqual(l, l2)
m := l.Map()
assert.Empty(m)
l[0]["sessionId"] = "session-id"
if m := l.Map(); assert.Len(m, 1) {
l3 := m.Users()
assert.Equal(l, l3)
}
}

View file

@ -1688,7 +1688,9 @@ sent as `POST` request with proper checksum headers as described above.
### New users invited to room
This can be used to notify users that they are now invited to a room.
This can be used to notify users that they are now invited to a room. Only
sessions currently connected to the room (in addition to the invited users)
will receive an event that they need to update the participant list.
Message format (Backend -> Server)
@ -1698,9 +1700,6 @@ Message format (Backend -> Server)
"userids": [
...list of user ids that are now invited to the room...
],
"alluserids": [
...list of all user ids that invited to the room...
],
"properties": [
...additional room properties...
]
@ -1711,6 +1710,8 @@ Message format (Backend -> Server)
### Users no longer invited to room
This can be used to notify users that they are no longer invited to a room.
Only sessions currently connected to the room (in addition to the disinvited
users) will receive an event that they need to update the participant list.
Message format (Backend -> Server)
@ -1719,9 +1720,6 @@ Message format (Backend -> Server)
"disinvite" {
"userids": [
...list of user ids that are no longer invited to the room...
],
"alluserids": [
...list of all user ids that still invited to the room...
]
}
}
@ -1730,16 +1728,14 @@ Message format (Backend -> Server)
### Room updated
This can be used to notify about changes to a room. The room properties are the
same as described in section "Join room" above.
same as described in section "Join room" above. Only sessions currently
connected to the room will receive an event about the update.
Message format (Backend -> Server)
{
"type": "update"
"update" {
"userids": [
...list of user ids that are invited to the room...
],
"properties": [
...additional room properties...
]
@ -1749,18 +1745,15 @@ Message format (Backend -> Server)
### Room deleted
This can be used to notify about a deleted room. All sessions currently
connected to the room will leave the room.
This can be used to notify about a deleted room. Only sessions currently
connected to the room will get notified about the deletion and also leave
the room.
Message format (Backend -> Server)
{
"type": "delete"
"delete" {
"userids": [
...list of user ids that were invited to the room...
]
}
"delete" {}
}
@ -1775,9 +1768,6 @@ Message format (Backend -> Server)
"participants" {
"changed": [
...list of users that were changed...
],
"users": [
...list of users in the room...
]
}
}
@ -1795,9 +1785,6 @@ Message format (Backend -> Server)
"incall": new-incall-state,
"changed": [
...list of users that were changed...
],
"users": [
...list of users in the room...
]
}
}

View file

@ -380,27 +380,20 @@ func (b *BackendServer) sendRoomDisinvite(roomid string, backend *talk.Backend,
defer cancel()
var wg sync.WaitGroup
for _, sessionid := range sessionids {
if sessionid == sessionIdNotInMeeting {
// Ignore entries that are no longer in the meeting.
continue
}
wg.Add(1)
go func(sessionid api.RoomSessionId) {
defer wg.Done()
if sid, err := b.lookupByRoomSessionId(ctx, sessionid, nil); err != nil {
b.logger.Printf("Could not lookup by room session %s: %s", sessionid, err)
} else if sid != "" {
if err := b.events.PublishSessionMessage(sid, backend, msg); err != nil {
b.logger.Printf("Could not publish room disinvite for session %s: %s", sid, err)
}
b.asyncLookupByRoomSessionId(ctx, sessionid, &wg, func(sid api.PublicSessionId) {
if sid == "" {
return
}
}(sessionid)
if err := b.events.PublishSessionMessage(sid, backend, msg); err != nil {
b.logger.Printf("Could not publish room disinvite for session %s: %s", sid, err)
}
})
}
wg.Wait()
}
func (b *BackendServer) sendRoomUpdate(roomid string, backend *talk.Backend, notified_userids []string, all_userids []string, properties json.RawMessage) {
func (b *BackendServer) sendRoomUpdate(roomid string, backend *talk.Backend, properties json.RawMessage) {
msg := &events.AsyncMessage{
Type: "message",
Message: &api.ServerMessage{
@ -415,34 +408,17 @@ func (b *BackendServer) sendRoomUpdate(roomid string, backend *talk.Backend, not
},
},
}
notified := make(map[string]bool)
for _, userid := range notified_userids {
notified[userid] = true
}
// Only send to users not notified otherwise.
for _, userid := range all_userids {
if notified[userid] {
continue
}
if err := b.events.PublishUserMessage(userid, backend, msg); err != nil {
b.logger.Printf("Could not publish room update for user %s in backend %s: %s", userid, backend.Id(), err)
}
if err := b.events.PublishRoomMessage(roomid, backend, msg); err != nil {
b.logger.Printf("Could not publish room update for %s in backend %s: %s", roomid, backend.Id(), err)
}
}
func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId api.RoomSessionId, cache *container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId]) (api.PublicSessionId, error) {
func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId api.RoomSessionId) (api.PublicSessionId, error) {
if roomSessionId == sessionIdNotInMeeting {
b.logger.Printf("Trying to lookup empty room session id: %s", roomSessionId)
return "", nil
}
if cache != nil {
if result, found := cache.Get(roomSessionId); found {
return result, nil
}
}
sid, err := b.roomSessions.LookupSessionId(ctx, roomSessionId, "")
if err == ErrNoSuchRoomSession {
return "", nil
@ -450,54 +426,43 @@ func (b *BackendServer) lookupByRoomSessionId(ctx context.Context, roomSessionId
return "", err
}
if cache != nil {
cache.Set(roomSessionId, sid)
}
return sid, nil
}
func (b *BackendServer) fixupUserSessions(ctx context.Context, cache *container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId], users []api.StringMap) []api.StringMap {
func (b *BackendServer) fixupUserSessions(ctx context.Context, users api.UserDataList) api.UserDataList {
if len(users) == 0 {
return users
}
var wg sync.WaitGroup
for _, user := range users {
roomSessionId, found := api.GetStringMapString[api.RoomSessionId](user, "sessionId")
roomSessionId, found := user.SessionId()
if !found {
b.logger.Printf("User %+v has invalid room session id, ignoring", user)
delete(user, "sessionId")
continue
}
if roomSessionId == sessionIdNotInMeeting {
if api.RoomSessionId(roomSessionId) == sessionIdNotInMeeting {
b.logger.Printf("User %+v is not in the meeting, ignoring", user)
delete(user, "sessionId")
continue
}
wg.Add(1)
go func(roomSessionId api.RoomSessionId, u api.StringMap) {
defer wg.Done()
if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, cache); err != nil {
b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err)
delete(u, "sessionId")
} else if sessionId != "" {
u["sessionId"] = sessionId
b.asyncLookupByRoomSessionId(ctx, api.RoomSessionId(roomSessionId), &wg, func(sessionId api.PublicSessionId) {
if sessionId == "" {
delete(user, "sessionId")
} else {
// sessionId == ""
delete(u, "sessionId")
user["sessionId"] = sessionId
}
}(roomSessionId, user)
})
}
wg.Wait()
result := make([]api.StringMap, 0, len(users))
for _, user := range users {
if _, found := user["sessionId"]; found {
result = append(result, user)
}
}
result := slices.DeleteFunc(users, func(user api.UserData) bool {
_, found := user["sessionId"]
return !found
})
return result
}
@ -508,13 +473,10 @@ func (b *BackendServer) sendRoomIncall(roomid string, backend *talk.Backend, req
ctx := log.NewLoggerContext(context.Background(), b.logger)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
var cache container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId]
// Convert (Nextcloud) session ids to signaling session ids.
request.InCall.Users = b.fixupUserSessions(ctx, &cache, request.InCall.Users)
// Entries in "Changed" are most likely already fetched through the "Users" list.
request.InCall.Changed = b.fixupUserSessions(ctx, &cache, request.InCall.Changed)
if len(request.InCall.Users) == 0 && len(request.InCall.Changed) == 0 {
// Convert (Nextcloud) session ids to signaling session ids.
request.InCall.Changed = b.fixupUserSessions(ctx, request.InCall.Changed)
if len(request.InCall.Changed) == 0 {
return nil
}
}
@ -532,11 +494,9 @@ func (b *BackendServer) sendRoomParticipantsUpdate(ctx context.Context, roomid s
// Convert (Nextcloud) session ids to signaling session ids.
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
var cache container.ConcurrentMap[api.RoomSessionId, api.PublicSessionId]
request.Participants.Users = b.fixupUserSessions(ctx, &cache, request.Participants.Users)
request.Participants.Changed = b.fixupUserSessions(ctx, &cache, request.Participants.Changed)
if len(request.Participants.Users) == 0 && len(request.Participants.Changed) == 0 {
request.Participants.Changed = b.fixupUserSessions(ctx, request.Participants.Changed)
if len(request.Participants.Changed) == 0 {
return nil
}
@ -548,7 +508,7 @@ loop:
continue
}
sessionId, found := api.GetStringMapString[api.PublicSessionId](user, "sessionId")
sessionId, found := user.SessionId()
if !found {
b.logger.Printf("User entry has no session id: %+v", user)
continue
@ -598,6 +558,24 @@ func (b *BackendServer) sendRoomMessage(roomid string, backend *talk.Backend, re
return b.events.PublishBackendRoomMessage(roomid, backend, message)
}
func (b *BackendServer) asyncLookupByRoomSessionId(ctx context.Context, roomSessionId api.RoomSessionId, wg *sync.WaitGroup, callback func(sessionId api.PublicSessionId)) {
if roomSessionId == sessionIdNotInMeeting {
return
}
wg.Add(1)
go func() {
defer wg.Done()
if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId); err != nil {
b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err)
callback("")
} else {
callback(sessionId)
}
}()
}
func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, backend *talk.Backend, request *talk.BackendServerRoomRequest) error {
timeout := time.Second
@ -621,21 +599,15 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac
var internalSessionsList talk.BackendRoomSwitchToPublicSessionsList
for _, roomSessionId := range sessionsList {
if roomSessionId == sessionIdNotInMeeting {
continue
}
wg.Add(1)
go func(roomSessionId api.RoomSessionId) {
defer wg.Done()
if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil {
b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err)
} else if sessionId != "" {
mu.Lock()
defer mu.Unlock()
internalSessionsList = append(internalSessionsList, sessionId)
b.asyncLookupByRoomSessionId(ctx, roomSessionId, &wg, func(sessionId api.PublicSessionId) {
if sessionId == "" {
return
}
}(roomSessionId)
mu.Lock()
defer mu.Unlock()
internalSessionsList = append(internalSessionsList, sessionId)
})
}
wg.Wait()
@ -659,21 +631,15 @@ func (b *BackendServer) sendRoomSwitchTo(ctx context.Context, roomid string, bac
internalSessionsMap := make(talk.BackendRoomSwitchToPublicSessionsMap)
for roomSessionId, details := range sessionsMap {
if roomSessionId == sessionIdNotInMeeting {
continue
}
wg.Add(1)
go func(roomSessionId api.RoomSessionId, details json.RawMessage) {
defer wg.Done()
if sessionId, err := b.lookupByRoomSessionId(ctx, roomSessionId, nil); err != nil {
b.logger.Printf("Could not lookup by room session %s: %s", roomSessionId, err)
} else if sessionId != "" {
mu.Lock()
defer mu.Unlock()
internalSessionsMap[sessionId] = details
b.asyncLookupByRoomSessionId(ctx, roomSessionId, &wg, func(sessionId api.PublicSessionId) {
if sessionId == "" {
return
}
}(roomSessionId, details)
mu.Lock()
defer mu.Unlock()
internalSessionsMap[sessionId] = details
})
}
wg.Wait()
@ -918,24 +884,24 @@ func (b *BackendServer) roomHandler(ctx context.Context, w http.ResponseWriter,
switch request.Type {
case "invite":
b.sendRoomInvite(roomid, backend, request.Invite.UserIds, request.Invite.Properties)
b.sendRoomUpdate(roomid, backend, request.Invite.UserIds, request.Invite.AllUserIds, request.Invite.Properties)
b.sendRoomUpdate(roomid, backend, request.Invite.Properties)
case "disinvite":
b.sendRoomUpdate(roomid, backend, request.Disinvite.Properties)
b.sendRoomDisinvite(roomid, backend, api.DisinviteReasonDisinvited, request.Disinvite.UserIds, request.Disinvite.SessionIds)
b.sendRoomUpdate(roomid, backend, request.Disinvite.UserIds, request.Disinvite.AllUserIds, request.Disinvite.Properties)
case "update":
message := &events.AsyncMessage{
Type: "room",
Room: &request,
}
err = b.events.PublishBackendRoomMessage(roomid, backend, message)
b.sendRoomUpdate(roomid, backend, nil, request.Update.UserIds, request.Update.Properties)
b.sendRoomUpdate(roomid, backend, request.Update.Properties)
case "delete":
// Notify the backend about the room deletion.
message := &events.AsyncMessage{
Type: "room",
Room: &request,
}
err = b.events.PublishBackendRoomMessage(roomid, backend, message)
b.sendRoomDisinvite(roomid, backend, api.DisinviteReasonDeleted, request.Delete.UserIds, nil)
case "incall":
err = b.sendRoomIncall(roomid, backend, &request)
case "participants":

View file

@ -249,34 +249,44 @@ func performBackendRequest(requestUrl string, body []byte) (*http.Response, erro
return client.Do(request)
}
func expectRoomlistEvent(t *testing.T, ch events.AsyncChannel, msgType string) (*api.EventServerMessage, bool) {
func expectAsyncMessage(t *testing.T, ch events.AsyncChannel) (*events.AsyncMessage, bool) {
assert := assert.New(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
select {
case natsMsg := <-ch:
var message events.AsyncMessage
if !assert.NoError(nats.Decode(natsMsg, &message)) ||
!assert.Equal("message", message.Type, "invalid message type, got %+v", message) ||
!assert.NotNil(message.Message, "message missing, got %+v", message) {
if !assert.NoError(nats.Decode(natsMsg, &message)) {
return nil, false
}
msg := message.Message
if !assert.Equal("event", msg.Type, "invalid message type, got %+v", msg) ||
!assert.NotNil(msg.Event, "event missing, got %+v", msg) ||
!assert.Equal("roomlist", msg.Event.Target, "invalid event target, got %+v", msg.Event) ||
!assert.Equal(msgType, msg.Event.Type, "invalid event type, got %+v", msg.Event) {
return nil, false
}
return msg.Event, true
return &message, true
case <-ctx.Done():
assert.NoError(ctx.Err())
return nil, false
}
}
func expectRoomlistEvent(t *testing.T, ch events.AsyncChannel, msgType string) (*api.EventServerMessage, bool) {
assert := assert.New(t)
message, ok := expectAsyncMessage(t, ch)
if !ok ||
!assert.Equal("message", message.Type, "invalid message type, got %+v", message) ||
!assert.NotNil(message.Message, "message missing, got %+v", message) {
return nil, false
}
msg := message.Message
if !assert.Equal("event", msg.Type, "invalid message type, got %+v", msg) ||
!assert.NotNil(msg.Event, "event missing, got %+v", msg) ||
!assert.Equal("roomlist", msg.Event.Target, "invalid event target, got %+v", msg.Event) ||
!assert.Equal(msgType, msg.Event.Type, "invalid event type, got %+v", msg.Event) {
return nil, false
}
return msg.Event, true
}
func TestBackendServer_NoAuth(t *testing.T) {
t.Parallel()
require := require.New(t)
@ -336,9 +346,6 @@ func TestBackendServer_OldCompatAuth(t *testing.T) {
UserIds: []string{
userid,
},
AllUserIds: []string{
userid,
},
Properties: roomProperties,
},
}
@ -447,9 +454,6 @@ func RunTestBackendServer_RoomInvite(ctx context.Context, t *testing.T) {
UserIds: []string{
userid,
},
AllUserIds: []string{
userid,
},
Properties: roomProperties,
},
}
@ -525,9 +529,8 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) {
testDefaultUserId,
},
SessionIds: []api.RoomSessionId{
api.RoomSessionId(fmt.Sprintf("%s-%s"+roomId, hello.Hello.SessionId)),
api.RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId)),
},
AllUserIds: []string{},
Properties: roomProperties,
},
}
@ -547,11 +550,21 @@ func RunTestBackendServer_RoomDisinvite(ctx context.Context, t *testing.T) {
assert.Empty(string(event.Disinvite.Properties))
}
if message, ok := client.RunUntilRoomlistDisinvite(ctx); ok {
if message, ok := client.RunUntilRoomlistUpdate(ctx); ok {
assert.Equal(roomId, message.RoomId)
}
client.RunUntilClosed(ctx)
if message, ok := client.RunUntilRoomlistDisinvite(ctx); ok {
assert.Equal(roomId, message.RoomId)
assert.Equal(api.DisinviteReasonDisinvited, message.Reason)
}
if message, ok := client.RunUntilMessageOrClosed(ctx); ok && message != nil {
// The client might receive a second disinvite message as both the userid and the session id were disinvited.
if message, ok := checkMessageRoomlistDisinvite(t, message); ok {
assert.Equal(roomId, message.RoomId)
assert.Equal(api.DisinviteReasonDisinvited, message.Reason)
}
client.RunUntilClosed(ctx)
}
}
func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) {
@ -585,9 +598,8 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) {
testDefaultUserId,
},
SessionIds: []api.RoomSessionId{
api.RoomSessionId(fmt.Sprintf("%s-%s"+roomId1, hello1.Hello.SessionId)),
api.RoomSessionId(fmt.Sprintf("%s-%s", roomId1, hello1.Hello.SessionId)),
},
AllUserIds: []string{},
},
}
@ -600,22 +612,30 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) {
assert.NoError(err)
assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body))
if message, ok := client1.RunUntilRoomlistDisinvite(ctx); ok {
if message, ok := client1.RunUntilRoomlistUpdate(ctx); ok {
assert.Equal(roomId1, message.RoomId)
}
client1.RunUntilClosed(ctx)
if message, ok := client1.RunUntilRoomlistDisinvite(ctx); ok {
assert.Equal(roomId1, message.RoomId)
assert.Equal(api.DisinviteReasonDisinvited, message.Reason)
}
if message, ok := client1.RunUntilMessageOrClosed(ctx); ok && message != nil {
// The client might receive a second disinvite message as both the userid and the session id were disinvited.
if message, ok := checkMessageRoomlistDisinvite(t, message); ok {
assert.Equal(roomId1, message.RoomId)
assert.Equal(api.DisinviteReasonDisinvited, message.Reason)
}
client1.RunUntilClosed(ctx)
}
if message, ok := client2.RunUntilRoomlistDisinvite(ctx); ok {
assert.Equal(roomId1, message.RoomId)
assert.Equal(api.DisinviteReasonDisinvited, message.Reason)
}
msg = &talk.BackendServerRoomRequest{
Type: "update",
Update: &talk.BackendRoomUpdateRequest{
UserIds: []string{
testDefaultUserId,
},
Properties: testRoomProperties,
},
}
@ -634,6 +654,62 @@ func TestBackendServer_RoomDisinviteDifferentRooms(t *testing.T) {
}
}
func TestBackendServer_RoomDisinviteClustered(t *testing.T) {
t.Parallel()
logger := logtest.NewLoggerForTest(t)
ctx := log.NewLoggerContext(t.Context(), logger)
require := require.New(t)
assert := assert.New(t)
_, _, hub1, hub2, server1, server2 := CreateBackendServerWithClusteringForTest(t)
ctx, cancel := context.WithTimeout(ctx, testTimeout)
defer cancel()
client1, hello1 := NewTestClientWithHello(ctx, t, server1, hub1, testDefaultUserId+"1")
defer client1.CloseWithBye()
client2, hello2 := NewTestClientWithHello(ctx, t, server2, hub2, testDefaultUserId+"2")
defer client2.CloseWithBye()
// Join room by id.
roomId := "test-room1"
MustSucceed2(t, client1.JoinRoom, ctx, roomId)
MustSucceed2(t, client2.JoinRoom, ctx, roomId)
WaitForUsersJoined(ctx, t, client1, hello1, client2, hello2)
msg := &talk.BackendServerRoomRequest{
Type: "disinvite",
Disinvite: &talk.BackendRoomDisinviteRequest{
SessionIds: []api.RoomSessionId{
api.RoomSessionId(fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId)),
},
},
}
data, err := json.Marshal(msg)
require.NoError(err)
res, err := performBackendRequest(server1.URL+"/api/v1/room/"+roomId, data)
require.NoError(err)
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
assert.NoError(err)
assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body))
if message, ok := client1.RunUntilRoomlistUpdate(ctx); ok {
assert.Equal(roomId, message.RoomId)
}
if message, ok := client2.RunUntilRoomlistUpdate(ctx); ok {
assert.Equal(roomId, message.RoomId)
}
if message, ok := client2.RunUntilRoomlistDisinvite(ctx); ok {
assert.Equal(roomId, message.RoomId)
assert.Equal(api.DisinviteReasonDisinvited, message.Reason)
}
client2.RunUntilClosed(ctx)
client1.RunUntilLeft(ctx, hello2.Hello)
}
func TestBackendServer_RoomUpdate(t *testing.T) {
t.Parallel()
for _, backend := range eventstest.EventBackendsForTest {
@ -662,24 +738,20 @@ func RunTestBackendServer_RoomUpdate(ctx context.Context, t *testing.T) {
require.NoError(err, "Could not create room")
defer room.Close()
userid := "test-userid"
roomProperties := json.RawMessage("{\"foo\":\"bar\"}")
eventsChan := make(events.AsyncChannel, 1)
listener := &channelEventListener{
ch: eventsChan,
}
require.NoError(asyncEvents.RegisterUserListener(userid, backend, listener))
require.NoError(asyncEvents.RegisterRoomListener(roomId, backend, listener))
defer func() {
assert.NoError(asyncEvents.UnregisterUserListener(userid, backend, listener))
assert.NoError(asyncEvents.UnregisterRoomListener(roomId, backend, listener))
}()
msg := &talk.BackendServerRoomRequest{
Type: "update",
Update: &talk.BackendRoomUpdateRequest{
UserIds: []string{
userid,
},
Properties: roomProperties,
},
}
@ -733,23 +805,18 @@ func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) {
_, err = hub.CreateRoom(roomId, emptyProperties, backend)
require.NoError(err)
userid := "test-userid"
eventsChan := make(events.AsyncChannel, 1)
listener := &channelEventListener{
ch: eventsChan,
}
require.NoError(asyncEvents.RegisterUserListener(userid, backend, listener))
require.NoError(asyncEvents.RegisterBackendRoomListener(roomId, backend, listener))
defer func() {
assert.NoError(asyncEvents.UnregisterUserListener(userid, backend, listener))
assert.NoError(asyncEvents.UnregisterBackendRoomListener(roomId, backend, listener))
}()
msg := &talk.BackendServerRoomRequest{
Type: "delete",
Delete: &talk.BackendRoomDeleteRequest{
UserIds: []string{
userid,
},
},
Type: "delete",
Delete: &talk.BackendRoomDeleteRequest{},
}
data, err := json.Marshal(msg)
@ -761,11 +828,12 @@ func RunTestBackendServer_RoomDelete(ctx context.Context, t *testing.T) {
assert.NoError(err)
assert.Equal(http.StatusOK, res.StatusCode, "Expected successful request, got %s", string(body))
// A deleted room is signalled as a "disinvite" event.
if event, ok := expectRoomlistEvent(t, eventsChan, "disinvite"); ok && assert.NotNil(event.Disinvite) {
assert.Equal(roomId, event.Disinvite.RoomId)
assert.Empty(event.Disinvite.Properties)
assert.Equal("deleted", event.Disinvite.Reason)
// A deleted room is signalled as a backend room event.
if message, ok := expectAsyncMessage(t, eventsChan); ok {
assert.Equal("room", message.Type)
if assert.NotNil(message.Room) {
assert.Equal("delete", message.Room.Type)
}
}
// TODO: Use event to wait for asynchronous messages.
@ -832,17 +900,7 @@ func TestBackendServer_ParticipantsUpdatePermissions(t *testing.T) {
msg := &talk.BackendServerRoomRequest{
Type: "participants",
Participants: &talk.BackendRoomParticipantsRequest{
Changed: []api.StringMap{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId),
"permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA},
},
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId),
"permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_SCREEN},
},
},
Users: []api.StringMap{
Changed: api.UserDataList{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId),
"permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA},
@ -910,13 +968,7 @@ func TestBackendServer_ParticipantsUpdateEmptyPermissions(t *testing.T) {
msg := &talk.BackendServerRoomRequest{
Type: "participants",
Participants: &talk.BackendRoomParticipantsRequest{
Changed: []api.StringMap{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId),
"permissions": []api.Permission{},
},
},
Users: []api.StringMap{
Changed: api.UserDataList{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId),
"permissions": []api.Permission{},
@ -979,17 +1031,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) {
Type: "incall",
InCall: &talk.BackendRoomInCallRequest{
InCall: json.RawMessage("7"),
Changed: []api.StringMap{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId),
"inCall": 7,
},
{
"sessionId": "unknown-room-session-id",
"inCall": 3,
},
},
Users: []api.StringMap{
Changed: api.UserDataList{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId),
"inCall": 7,
@ -1026,17 +1068,7 @@ func TestBackendServer_ParticipantsUpdateTimeout(t *testing.T) {
Type: "incall",
InCall: &talk.BackendRoomInCallRequest{
InCall: json.RawMessage("7"),
Changed: []api.StringMap{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId),
"inCall": 7,
},
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello2.Hello.SessionId),
"inCall": 3,
},
},
Users: []api.StringMap{
Changed: api.UserDataList{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello1.Hello.SessionId),
"inCall": 7,

View file

@ -650,7 +650,7 @@ func (c *FederationClient) joinRoom() error {
})
}
func (c *FederationClient) updateActor(u api.StringMap, actorIdKey, actorTypeKey string, localCloudUrl string, remoteCloudUrl string) (changed bool) {
func (c *FederationClient) updateActor(u api.UserData, actorIdKey, actorTypeKey string, localCloudUrl string, remoteCloudUrl string) (changed bool) {
if actorType, found := api.GetStringMapEntry[string](u, actorTypeKey); found {
if actorId, found := api.GetStringMapEntry[string](u, actorIdKey); found {
switch actorType {
@ -726,7 +726,7 @@ func (c *FederationClient) updateComment(comment api.StringMap, localCloudUrl st
return changed
}
func (c *FederationClient) updateEventUsers(users []api.StringMap, localSessionId api.PublicSessionId, remoteSessionId api.PublicSessionId) {
func (c *FederationClient) updateEventUsers(users api.UserDataList, localSessionId api.PublicSessionId, remoteSessionId api.PublicSessionId) {
localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl())
remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl)
checkSessionId := true

View file

@ -359,21 +359,25 @@ func Test_Federation(t *testing.T) {
"actorType": "federated_users",
},
}
room.PublishUsersInCallChanged(users, users)
room.PublishUsersInCallChanged(users)
var event *api.EventServerMessage
// For the local user, it's a federated user on server 2 that joined.
if checkReceiveClientEvent(ctx, t, client1, "update", &event) {
assert.EqualValues(remoteSessionId, event.Update.Users[0]["sessionId"])
assert.Equal("remoteUser@"+strings.TrimPrefix(server2.URL, "http://"), event.Update.Users[0]["actorId"])
assert.Equal("federated_users", event.Update.Users[0]["actorType"])
assert.Equal(roomId, event.Update.RoomId)
if assert.Len(event.Update.Users, 1) {
assert.EqualValues(remoteSessionId, event.Update.Users[0]["sessionId"])
assert.Equal("remoteUser@"+strings.TrimPrefix(server2.URL, "http://"), event.Update.Users[0]["actorId"])
assert.Equal("federated_users", event.Update.Users[0]["actorType"])
}
}
// For the federated user, it's a local user that joined.
if checkReceiveClientEvent(ctx, t, client2, "update", &event) {
assert.EqualValues(hello2.Hello.SessionId, event.Update.Users[0]["sessionId"])
assert.Equal("remoteUser", event.Update.Users[0]["actorId"])
assert.Equal("users", event.Update.Users[0]["actorType"])
assert.Equal(federatedRoomId, event.Update.RoomId)
if assert.Len(event.Update.Users, 1) {
assert.EqualValues(hello2.Hello.SessionId, event.Update.Users[0]["sessionId"])
assert.Equal("remoteUser", event.Update.Users[0]["actorId"])
assert.Equal("users", event.Update.Users[0]["actorType"])
}
}
// Simulate request from the backend that a local user joined the call.
@ -385,20 +389,42 @@ func Test_Federation(t *testing.T) {
"actorType": "users",
},
}
room.PublishUsersInCallChanged(users, users)
room.PublishUsersInCallChanged(users)
// For the local user, it's a local user that joined.
if checkReceiveClientEvent(ctx, t, client1, "update", &event) {
assert.EqualValues(hello1.Hello.SessionId, event.Update.Users[0]["sessionId"])
assert.Equal("localUser", event.Update.Users[0]["actorId"])
assert.Equal("users", event.Update.Users[0]["actorType"])
assert.Equal(roomId, event.Update.RoomId)
if assert.Len(event.Update.Users, 2) {
users := event.Update.Users.Map()
if user := users[hello1.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello1.Hello.SessionId, event) {
assert.EqualValues(hello1.Hello.SessionId, user["sessionId"])
assert.Equal("localUser", user["actorId"])
assert.Equal("users", user["actorType"])
}
if user := users[remoteSessionId]; assert.NotNil(user, "expected %s, got %+v", remoteSessionId, event) {
assert.EqualValues(remoteSessionId, user["sessionId"])
assert.Equal("remoteUser@"+strings.TrimPrefix(server2.URL, "http://"), user["actorId"])
assert.Equal("federated_users", user["actorType"])
}
}
}
// For the federated user, it's a federated user on server 1 that joined.
if checkReceiveClientEvent(ctx, t, client2, "update", &event) {
assert.EqualValues(hello1.Hello.SessionId, event.Update.Users[0]["sessionId"])
assert.Equal("localUser@"+strings.TrimPrefix(server1.URL, "http://"), event.Update.Users[0]["actorId"])
assert.Equal("federated_users", event.Update.Users[0]["actorType"])
assert.Equal(federatedRoomId, event.Update.RoomId)
if assert.Len(event.Update.Users, 2) {
users := event.Update.Users.Map()
if user := users[hello1.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello1.Hello.SessionId, event) {
assert.EqualValues(hello1.Hello.SessionId, user["sessionId"])
assert.Equal("localUser@"+strings.TrimPrefix(server1.URL, "http://"), user["actorId"])
assert.Equal("federated_users", user["actorType"])
}
if user := users[hello2.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello2.Hello.SessionId, event) {
assert.EqualValues(hello2.Hello.SessionId, user["sessionId"])
assert.Equal("remoteUser", user["actorId"])
assert.Equal("users", user["actorType"])
}
}
}
// Joining another "direct" session will trigger correct events.

View file

@ -3101,8 +3101,26 @@ func (h *Hub) processRoomDeleted(message *talk.BackendServerRoomRequest) {
}
sessions := room.Close()
if len(sessions) == 0 {
return
}
msg := &api.ServerMessage{
Type: "event",
Event: &api.EventServerMessage{
Target: "roomlist",
Type: "disinvite",
Disinvite: &api.RoomDisinviteEventServerMessage{
RoomEventServerMessage: api.RoomEventServerMessage{
RoomId: message.RoomId,
},
Reason: api.DisinviteReasonDeleted,
},
},
}
for _, session := range sessions {
// The session is no longer in the room
session.SendMessage(msg)
session.LeaveRoom(true)
switch sess := session.(type) {
case *ClientSession:
@ -3135,7 +3153,7 @@ func (h *Hub) processRoomInCallChanged(message *talk.BackendServerRoomRequest) {
room.PublishUsersInCallChangedAll(flags)
} else {
room.PublishUsersInCallChanged(message.InCall.Changed, message.InCall.Users)
room.PublishUsersInCallChanged(message.InCall.Changed)
}
}
@ -3145,7 +3163,7 @@ func (h *Hub) processRoomParticipants(message *talk.BackendServerRoomRequest) {
return
}
room.PublishUsersChanged(message.Participants.Changed, message.Participants.Users)
room.PublishUsersChanged(message.Participants.Changed)
}
func (h *Hub) GetStats() api.StringMap {

View file

@ -173,7 +173,7 @@ func Test_JanusSubscriberNoSuchRoom(t *testing.T) {
}
room := hub.getRoom(roomId)
require.NotNil(room, "Could not find room %s", roomId)
room.PublishUsersInCallChanged(users1, users1)
room.PublishUsersInCallChanged(users1)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -275,7 +275,7 @@ func test_JanusSubscriberAlreadyJoined(t *testing.T) {
}
room := hub.getRoom(roomId)
require.NotNil(room, "Could not find room %s", roomId)
room.PublishUsersInCallChanged(users1, users1)
room.PublishUsersInCallChanged(users1)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -390,7 +390,7 @@ func Test_JanusSubscriberTimeout(t *testing.T) {
}
room := hub.getRoom(roomId)
require.NotNil(room, "Could not find room %s", roomId)
room.PublishUsersInCallChanged(users1, users1)
room.PublishUsersInCallChanged(users1)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -498,7 +498,7 @@ func Test_JanusSubscriberCloseEmptyStreams(t *testing.T) {
}
room := hub.getRoom(roomId)
require.NotNil(room, "Could not find room %s", roomId)
room.PublishUsersInCallChanged(users1, users1)
room.PublishUsersInCallChanged(users1)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -612,7 +612,7 @@ func Test_JanusSubscriberRoomDestroyed(t *testing.T) {
}
room := hub.getRoom(roomId)
require.NotNil(room, "Could not find room %s", roomId)
room.PublishUsersInCallChanged(users1, users1)
room.PublishUsersInCallChanged(users1)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -726,7 +726,7 @@ func Test_JanusSubscriberUpdateOffer(t *testing.T) {
}
room := hub.getRoom(roomId)
require.NotNil(room, "Could not find room %s", roomId)
room.PublishUsersInCallChanged(users1, users1)
room.PublishUsersInCallChanged(users1)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)

View file

@ -1820,7 +1820,7 @@ func TestClientHelloResumeProxy(t *testing.T) { // nolint:paralleltest
"inCall": 1,
},
}
room.PublishUsersInCallChanged(users, users)
room.PublishUsersInCallChanged(users)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
})
})
@ -2447,7 +2447,7 @@ func TestClientMessageToCall(t *testing.T) {
}
room1 := hub1.getRoom(roomId)
require.NotNil(room1, "Could not find room %s", roomId)
room1.PublishUsersInCallChanged(users, users)
room1.PublishUsersInCallChanged(users)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -2484,7 +2484,7 @@ func TestClientMessageToCall(t *testing.T) {
}
room2 := hub2.getRoom(roomId)
require.NotNil(room2, "Could not find room %s", roomId)
room2.PublishUsersInCallChanged(users, users)
room2.PublishUsersInCallChanged(users)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -2552,7 +2552,7 @@ func TestClientControlToCall(t *testing.T) {
}
room1 := hub1.getRoom(roomId)
require.NotNil(room1, "Could not find room %s", roomId)
room1.PublishUsersInCallChanged(users, users)
room1.PublishUsersInCallChanged(users)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -2589,7 +2589,7 @@ func TestClientControlToCall(t *testing.T) {
}
room2 := hub2.getRoom(roomId)
require.NotNil(room2, "Could not find room %s", roomId)
room2.PublishUsersInCallChanged(users, users)
room2.PublishUsersInCallChanged(users)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -3239,13 +3239,13 @@ func TestRoomParticipantsListUpdateWhileDisconnected(t *testing.T) {
}
room := hub.getRoom(roomId)
require.NotNil(room, "Could not find room %s", roomId)
room.PublishUsersInCallChanged(users, users)
room.PublishUsersInCallChanged(users)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
client2.Close()
assert.NoError(client2.WaitForClientRemoved(ctx))
room.PublishUsersInCallChanged(users, users)
room.PublishUsersInCallChanged(users)
// Give asynchronous events some time to be processed.
time.Sleep(100 * time.Millisecond)
@ -3579,13 +3579,7 @@ func TestClientSendOfferPermissionsAudioVideo(t *testing.T) {
msg := &talk.BackendServerRoomRequest{
Type: "participants",
Participants: &talk.BackendRoomParticipantsRequest{
Changed: []api.StringMap{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId),
"permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_AUDIO},
},
},
Users: []api.StringMap{
Changed: api.UserDataList{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId),
"permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_AUDIO},
@ -3676,13 +3670,7 @@ func TestClientSendOfferPermissionsAudioVideoMedia(t *testing.T) {
msg := &talk.BackendServerRoomRequest{
Type: "participants",
Participants: &talk.BackendRoomParticipantsRequest{
Changed: []api.StringMap{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId),
"permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA, api.PERMISSION_MAY_CONTROL},
},
},
Users: []api.StringMap{
Changed: api.UserDataList{
{
"sessionId": fmt.Sprintf("%s-%s", roomId, hello.Hello.SessionId),
"permissions": []api.Permission{api.PERMISSION_MAY_PUBLISH_MEDIA, api.PERMISSION_MAY_CONTROL},
@ -3822,7 +3810,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) {
}
room2 := hub2.getRoom(roomId)
require.NotNil(room2, "Could not find room %s", roomId)
room2.PublishUsersInCallChanged(users1, users1)
room2.PublishUsersInCallChanged(users1)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -3848,7 +3836,7 @@ func TestClientRequestOfferNotInRoom(t *testing.T) {
}
room1 := hub1.getRoom(roomId)
require.NotNil(room1, "Could not find room %s", roomId)
room1.PublishUsersInCallChanged(users2, users2)
room1.PublishUsersInCallChanged(users2)
checkReceiveClientEvent(ctx, t, client1, "update", nil)
checkReceiveClientEvent(ctx, t, client2, "update", nil)
@ -4676,7 +4664,7 @@ func TestDuplicateVirtualSessions(t *testing.T) {
Type: "incall",
InCall: &talk.BackendRoomInCallRequest{
InCall: []byte("0"),
Users: []api.StringMap{
Changed: api.UserDataList{
{
"sessionId": virtualSession.PublicId(),
"participantPermissions": 246,
@ -4706,20 +4694,27 @@ func TestDuplicateVirtualSessions(t *testing.T) {
if msg, ok := client1.RunUntilMessage(ctx); ok {
if msg, ok := checkMessageParticipantsInCall(t, msg); ok {
if assert.Len(msg.Users, 3) {
assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg)
assert.EqualValues(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[0]["inCall"], "%+v", msg)
assert.EqualValues(246, msg.Users[0]["participantPermissions"], "%+v", msg)
assert.EqualValues(4, msg.Users[0]["participantType"], "%+v", msg)
users := msg.Users.Map()
if user := users[virtualSession.PublicId()]; assert.NotNil(user, "expected %s, got %+v", virtualSession.PublicId(), msg) {
assert.Equal(true, user["virtual"], "%+v", msg)
assert.EqualValues(virtualSession.PublicId(), user["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithPhone, user["inCall"], "%+v", msg)
assert.EqualValues(246, user["participantPermissions"], "%+v", msg)
assert.EqualValues(4, user["participantType"], "%+v", msg)
}
assert.EqualValues(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg)
assert.Nil(msg.Users[1]["inCall"], "%+v", msg)
assert.EqualValues(254, msg.Users[1]["participantPermissions"], "%+v", msg)
assert.EqualValues(1, msg.Users[1]["participantType"], "%+v", msg)
if user := users[hello1.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello1.Hello.SessionId, msg) {
assert.EqualValues(hello1.Hello.SessionId, user["sessionId"], "%+v", msg)
assert.Nil(user["inCall"], "%+v", msg)
assert.EqualValues(254, user["participantPermissions"], "%+v", msg)
assert.EqualValues(1, user["participantType"], "%+v", msg)
}
assert.Equal(true, msg.Users[2]["internal"], "%+v", msg)
assert.EqualValues(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[2]["inCall"], "%+v", msg)
if user := users[hello2.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello2.Hello.SessionId, msg) {
assert.Equal(true, user["internal"], "%+v", msg)
assert.EqualValues(hello2.Hello.SessionId, user["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithAudio, user["inCall"], "%+v", msg)
}
}
}
}
@ -4727,20 +4722,27 @@ func TestDuplicateVirtualSessions(t *testing.T) {
if msg, ok := client2.RunUntilMessage(ctx); ok {
if msg, ok := checkMessageParticipantsInCall(t, msg); ok {
if assert.Len(msg.Users, 3) {
assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg)
assert.EqualValues(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[0]["inCall"], "%+v", msg)
assert.EqualValues(246, msg.Users[0]["participantPermissions"], "%+v", msg)
assert.EqualValues(4, msg.Users[0]["participantType"], "%+v", msg)
users := msg.Users.Map()
if user := users[virtualSession.PublicId()]; assert.NotNil(user, "expected %s, got %+v", virtualSession.PublicId(), msg) {
assert.Equal(true, user["virtual"], "%+v", msg)
assert.EqualValues(virtualSession.PublicId(), user["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithPhone, user["inCall"], "%+v", msg)
assert.EqualValues(246, user["participantPermissions"], "%+v", msg)
assert.EqualValues(4, user["participantType"], "%+v", msg)
}
assert.EqualValues(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg)
assert.Nil(msg.Users[1]["inCall"], "%+v", msg)
assert.EqualValues(254, msg.Users[1]["participantPermissions"], "%+v", msg)
assert.EqualValues(1, msg.Users[1]["participantType"], "%+v", msg)
if user := users[hello1.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello1.Hello.SessionId, msg) {
assert.EqualValues(hello1.Hello.SessionId, user["sessionId"], "%+v", msg)
assert.Nil(user["inCall"], "%+v", msg)
assert.EqualValues(254, user["participantPermissions"], "%+v", msg)
assert.EqualValues(1, user["participantType"], "%+v", msg)
}
assert.Equal(true, msg.Users[2]["internal"], "%+v", msg)
assert.EqualValues(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[2]["inCall"], "%+v", msg)
if user := users[hello2.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello2.Hello.SessionId, msg) {
assert.Equal(true, user["internal"], "%+v", msg)
assert.EqualValues(hello2.Hello.SessionId, user["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithAudio, user["inCall"], "%+v", msg)
}
}
}
}
@ -4761,20 +4763,27 @@ func TestDuplicateVirtualSessions(t *testing.T) {
if msg, ok := client3.RunUntilMessage(ctx); ok {
if msg, ok := checkMessageParticipantsInCall(t, msg); ok {
if assert.Len(msg.Users, 3) {
assert.Equal(true, msg.Users[0]["virtual"], "%+v", msg)
assert.EqualValues(virtualSession.PublicId(), msg.Users[0]["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithPhone, msg.Users[0]["inCall"], "%+v", msg)
assert.EqualValues(246, msg.Users[0]["participantPermissions"], "%+v", msg)
assert.EqualValues(4, msg.Users[0]["participantType"], "%+v", msg)
users := msg.Users.Map()
if user := users[virtualSession.PublicId()]; assert.NotNil(user, "expected %s, got %+v", virtualSession.PublicId(), msg) {
assert.Equal(true, user["virtual"], "%+v", msg)
assert.EqualValues(virtualSession.PublicId(), user["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithPhone, user["inCall"], "%+v", msg)
assert.EqualValues(246, user["participantPermissions"], "%+v", msg)
assert.EqualValues(4, user["participantType"], "%+v", msg)
}
assert.EqualValues(hello1.Hello.SessionId, msg.Users[1]["sessionId"], "%+v", msg)
assert.Nil(msg.Users[1]["inCall"], "%+v", msg)
assert.EqualValues(254, msg.Users[1]["participantPermissions"], "%+v", msg)
assert.EqualValues(1, msg.Users[1]["participantType"], "%+v", msg)
if user := users[hello1.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello1.Hello.SessionId, msg) {
assert.EqualValues(hello1.Hello.SessionId, user["sessionId"], "%+v", msg)
assert.Nil(user["inCall"], "%+v", msg)
assert.EqualValues(254, user["participantPermissions"], "%+v", msg)
assert.EqualValues(1, user["participantType"], "%+v", msg)
}
assert.Equal(true, msg.Users[2]["internal"], "%+v", msg)
assert.EqualValues(hello2.Hello.SessionId, msg.Users[2]["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithAudio, msg.Users[2]["inCall"], "%+v", msg)
if user := users[hello2.Hello.SessionId]; assert.NotNil(user, "expected %s, got %+v", hello2.Hello.SessionId, msg) {
assert.Equal(true, user["internal"], "%+v", msg)
assert.EqualValues(hello2.Hello.SessionId, user["sessionId"], "%+v", msg)
assert.EqualValues(FlagInCall|FlagWithAudio, user["inCall"], "%+v", msg)
}
}
}
}

View file

@ -96,7 +96,8 @@ type Room struct {
statsRoomSessionsCurrent *prometheus.GaugeVec
// Users currently in the room
users []api.StringMap
// +checklocks:mu
users api.UserDataMap
// Timestamps of last backend requests for the different types.
lastRoomRequests map[string]int64
@ -136,6 +137,7 @@ func NewRoom(roomId string, properties json.RawMessage, hub *Hub, asyncEvents ev
"backend": backend.Id(),
"room": roomId,
}),
users: make(api.UserDataMap),
lastRoomRequests: make(map[string]int64),
@ -503,18 +505,9 @@ func (r *Room) RemoveSession(session Session) bool {
sid := session.PublicId()
r.statsRoomSessionsCurrent.With(prometheus.Labels{"clienttype": string(session.ClientType())}).Dec()
delete(r.sessions, sid)
delete(r.users, sid)
if virtualSession, ok := session.(*VirtualSession); ok {
delete(r.virtualSessions, virtualSession)
// Handle case where virtual session was also sent by Nextcloud.
users := make([]api.StringMap, 0, len(r.users))
for _, u := range r.users {
if value, found := api.GetStringMapString[api.PublicSessionId](u, "sessionId"); !found || value != sid {
users = append(users, u)
}
}
if len(users) != len(r.users) {
r.users = users
}
}
if clientSession, ok := session.(*ClientSession); ok {
delete(r.internalSessions, clientSession)
@ -683,7 +676,7 @@ func (r *Room) getClusteredInternalSessionsRLocked() (internal map[api.PublicSes
return
}
func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap {
func (r *Room) addInternalSessions(users api.UserDataList) api.UserDataList {
now := time.Now().Unix()
r.mu.RLock()
defer r.mu.RUnlock()
@ -700,7 +693,7 @@ func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap {
skipSession := make(map[api.PublicSessionId]bool)
for _, user := range users {
sessionid, found := api.GetStringMapString[api.PublicSessionId](user, "sessionId")
sessionid, found := user.SessionId()
if !found || sessionid == "" {
continue
}
@ -725,7 +718,7 @@ func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap {
}
}
for session := range r.internalSessions {
u := api.StringMap{
u := api.UserData{
"inCall": session.GetInCall(),
"sessionId": session.PublicId(),
"lastPing": now,
@ -737,7 +730,7 @@ func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap {
users = append(users, u)
}
for _, session := range clusteredInternalSessions {
u := api.StringMap{
u := api.UserData{
"inCall": session.GetInCall(),
"sessionId": session.GetSessionId(),
"lastPing": now,
@ -754,7 +747,7 @@ func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap {
continue
}
skipSession[sid] = true
users = append(users, api.StringMap{
users = append(users, api.UserData{
"inCall": session.GetInCall(),
"sessionId": sid,
"lastPing": now,
@ -766,7 +759,7 @@ func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap {
continue
}
users = append(users, api.StringMap{
users = append(users, api.UserData{
"inCall": session.GetInCall(),
"sessionId": sid,
"lastPing": now,
@ -776,7 +769,7 @@ func (r *Room) addInternalSessions(users []api.StringMap) []api.StringMap {
return users
}
func (r *Room) filterPermissions(users []api.StringMap) []api.StringMap {
func (r *Room) filterPermissions(users api.UserDataList) api.UserDataList {
for _, user := range users {
delete(user, "permissions")
}
@ -803,9 +796,25 @@ func IsInCall(value any) (bool, bool) {
}
}
func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.StringMap) {
r.users = users
func (r *Room) addUser(sessionId api.PublicSessionId, user api.UserData) {
r.mu.Lock()
defer r.mu.Unlock()
r.users[sessionId] = user
}
func (r *Room) PublishUsersInCallChanged(changed api.UserDataList) {
for _, user := range changed {
sessionId, found := user.SessionId()
if !found {
// TODO: Do we still need this fallback?
sessionId, found = api.GetStringMapString[api.PublicSessionId](user, "sessionid")
if !found {
continue
}
}
r.addUser(sessionId, user)
inCallInterface, found := user["inCall"]
if !found {
continue
@ -815,14 +824,6 @@ func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.St
continue
}
sessionId, found := api.GetStringMapString[api.PublicSessionId](user, "sessionId")
if !found {
sessionId, found = api.GetStringMapString[api.PublicSessionId](user, "sessionid")
if !found {
continue
}
}
session := r.hub.GetSessionByPublicId(sessionId)
if session == nil {
continue
@ -846,7 +847,8 @@ func (r *Room) PublishUsersInCallChanged(changed []api.StringMap, users []api.St
}
changed = r.filterPermissions(changed)
users = r.filterPermissions(users)
// TODO: Do we still need the whole list of users to send to all participants?
users := r.filterPermissions(r.getUsers())
message := &api.ServerMessage{
Type: "event",
@ -953,9 +955,10 @@ func (r *Room) PublishUsersInCallChangedAll(inCall int) {
}
}
func (r *Room) PublishUsersChanged(changed []api.StringMap, users []api.StringMap) {
func (r *Room) PublishUsersChanged(changed api.UserDataList) {
changed = r.filterPermissions(changed)
users = r.filterPermissions(users)
// TODO: Do we still need the whole list of users to send to all participants?
users := r.filterPermissions(r.getUsers())
message := &api.ServerMessage{
Type: "event",
@ -974,8 +977,15 @@ func (r *Room) PublishUsersChanged(changed []api.StringMap, users []api.StringMa
}
}
func (r *Room) getParticipantsUpdateMessage(users []api.StringMap) *api.ServerMessage {
users = r.filterPermissions(users)
func (r *Room) getUsers() api.UserDataList {
r.mu.RLock()
defer r.mu.RUnlock()
return r.users.Users().Clone()
}
func (r *Room) getParticipantsUpdateMessage() *api.ServerMessage {
users := r.filterPermissions(r.getUsers())
message := &api.ServerMessage{
Type: "event",
@ -992,7 +1002,7 @@ func (r *Room) getParticipantsUpdateMessage(users []api.StringMap) *api.ServerMe
}
func (r *Room) NotifySessionResumed(session *ClientSession) {
message := r.getParticipantsUpdateMessage(r.users)
message := r.getParticipantsUpdateMessage()
if len(message.Event.Update.Users) == 0 {
return
}
@ -1043,7 +1053,7 @@ func (r *Room) NotifySessionChanged(session Session, flags SessionChangeFlag) {
}
func (r *Room) publishUsersChangedWithInternal() {
message := r.getParticipantsUpdateMessage(r.users)
message := r.getParticipantsUpdateMessage()
if len(message.Event.Update.Users) == 0 {
return
}

View file

@ -35,6 +35,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/strukturag/nextcloud-spreed-signaling/api"
"github.com/strukturag/nextcloud-spreed-signaling/log"
logtest "github.com/strukturag/nextcloud-spreed-signaling/log/test"
"github.com/strukturag/nextcloud-spreed-signaling/talk"
@ -111,9 +112,6 @@ func TestRoom_Update(t *testing.T) {
msg := &talk.BackendServerRoomRequest{
Type: "update",
Update: &talk.BackendRoomUpdateRequest{
UserIds: []string{
testDefaultUserId,
},
Properties: roomProperties,
},
}
@ -203,12 +201,8 @@ func TestRoom_Delete(t *testing.T) {
// Simulate backend request from Nextcloud to update the room.
msg := &talk.BackendServerRoomRequest{
Type: "delete",
Delete: &talk.BackendRoomDeleteRequest{
UserIds: []string{
testDefaultUserId,
},
},
Type: "delete",
Delete: &talk.BackendRoomDeleteRequest{},
}
data, err := json.Marshal(msg)
@ -227,16 +221,27 @@ func TestRoom_Delete(t *testing.T) {
// Ordering should be "leave room", "disinvited".
checkMessageRoomId(t, message1, "")
if message2, ok := client.RunUntilMessage(ctx); ok {
checkMessageRoomlistDisinvite(t, message2)
if msg, ok := checkMessageRoomlistDisinvite(t, message2); ok {
assert.Equal(roomId, msg.RoomId)
assert.Equal(api.DisinviteReasonDeleted, msg.Reason)
}
}
if !client.RunUntilClosed(ctx) {
return
}
} else {
// Ordering should be "disinvited", "leave room".
checkMessageRoomlistDisinvite(t, message1)
if msg, ok := checkMessageRoomlistDisinvite(t, message1); ok {
assert.Equal(roomId, msg.RoomId)
assert.Equal(api.DisinviteReasonDeleted, msg.Reason)
}
// The connection should get closed after the "disinvited".
// However due to the asynchronous processing, the "leave room" message might be received before.
if message2, ok := client.RunUntilMessageOrClosed(ctx); ok && message2 != nil {
checkMessageRoomId(t, message2, "")
client.RunUntilClosed(ctx)
if !client.RunUntilClosed(ctx) {
return
}
}
}

View file

@ -110,42 +110,32 @@ type BackendServerRoomRequest struct {
}
type BackendRoomInviteRequest struct {
UserIds []string `json:"userids,omitempty"`
// TODO(jojo): We should get rid of "AllUserIds" and find a better way to
// notify existing users the room has changed and they need to update it.
AllUserIds []string `json:"alluserids,omitempty"`
UserIds []string `json:"userids,omitempty"`
Properties json.RawMessage `json:"properties,omitempty"`
}
type BackendRoomDisinviteRequest struct {
UserIds []string `json:"userids,omitempty"`
SessionIds []api.RoomSessionId `json:"sessionids,omitempty"`
// TODO(jojo): We should get rid of "AllUserIds" and find a better way to
// notify existing users the room has changed and they need to update it.
AllUserIds []string `json:"alluserids,omitempty"`
Properties json.RawMessage `json:"properties,omitempty"`
Properties json.RawMessage `json:"properties,omitempty"`
}
type BackendRoomUpdateRequest struct {
UserIds []string `json:"userids,omitempty"`
Properties json.RawMessage `json:"properties,omitempty"`
}
type BackendRoomDeleteRequest struct {
UserIds []string `json:"userids,omitempty"`
}
type BackendRoomInCallRequest struct {
// TODO(jojo): Change "InCall" to "int" when #914 has landed in NC Talk.
InCall json.RawMessage `json:"incall,omitempty"`
All bool `json:"all,omitempty"`
Changed []api.StringMap `json:"changed,omitempty"`
Users []api.StringMap `json:"users,omitempty"`
InCall json.RawMessage `json:"incall,omitempty"`
All bool `json:"all,omitempty"`
Changed api.UserDataList `json:"changed,omitempty"`
}
type BackendRoomParticipantsRequest struct {
Changed []api.StringMap `json:"changed,omitempty"`
Users []api.StringMap `json:"users,omitempty"`
Changed api.UserDataList `json:"changed,omitempty"`
}
type BackendRoomMessageRequest struct {

View file

@ -2021,33 +2021,6 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk13(in
key := in.UnsafeFieldName(false)
in.WantColon()
switch key {
case "userids":
if in.IsNull() {
in.Skip()
out.UserIds = nil
} else {
in.Delim('[')
if out.UserIds == nil {
if !in.IsDelim(']') {
out.UserIds = make([]string, 0, 4)
} else {
out.UserIds = []string{}
}
} else {
out.UserIds = (out.UserIds)[:0]
}
for !in.IsDelim(']') {
var v25 string
if in.IsNull() {
in.Skip()
} else {
v25 = string(in.String())
}
out.UserIds = append(out.UserIds, v25)
in.WantComma()
}
in.Delim(']')
}
case "properties":
if in.IsNull() {
in.Skip()
@ -2070,29 +2043,10 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk13(out
out.RawByte('{')
first := true
_ = first
if len(in.UserIds) != 0 {
const prefix string = ",\"userids\":"
first = false
out.RawString(prefix[1:])
{
out.RawByte('[')
for v26, v27 := range in.UserIds {
if v26 > 0 {
out.RawByte(',')
}
out.String(string(v27))
}
out.RawByte(']')
}
}
if len(in.Properties) != 0 {
const prefix string = ",\"properties\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
first = false
out.RawString(prefix[1:])
out.Raw((in.Properties).MarshalJSON())
}
out.RawByte('}')
@ -2271,13 +2225,13 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(in
out.SessionsList = (out.SessionsList)[:0]
}
for !in.IsDelim(']') {
var v28 api.PublicSessionId
var v25 api.PublicSessionId
if in.IsNull() {
in.Skip()
} else {
v28 = api.PublicSessionId(in.String())
v25 = api.PublicSessionId(in.String())
}
out.SessionsList = append(out.SessionsList, v28)
out.SessionsList = append(out.SessionsList, v25)
in.WantComma()
}
in.Delim(']')
@ -2295,15 +2249,15 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk15(in
for !in.IsDelim('}') {
key := api.PublicSessionId(in.String())
in.WantColon()
var v29 json.RawMessage
var v26 json.RawMessage
if in.IsNull() {
in.Skip()
} else {
if data := in.Raw(); in.Ok() {
in.AddError((v29).UnmarshalJSON(data))
in.AddError((v26).UnmarshalJSON(data))
}
}
(out.SessionsMap)[key] = v29
(out.SessionsMap)[key] = v26
in.WantComma()
}
in.Delim('}')
@ -2337,11 +2291,11 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(out
out.RawString(prefix)
{
out.RawByte('[')
for v30, v31 := range in.SessionsList {
if v30 > 0 {
for v27, v28 := range in.SessionsList {
if v27 > 0 {
out.RawByte(',')
}
out.String(string(v31))
out.String(string(v28))
}
out.RawByte(']')
}
@ -2351,16 +2305,16 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk15(out
out.RawString(prefix)
{
out.RawByte('{')
v32First := true
for v32Name, v32Value := range in.SessionsMap {
if v32First {
v32First = false
v29First := true
for v29Name, v29Value := range in.SessionsMap {
if v29First {
v29First = false
} else {
out.RawByte(',')
}
out.String(string(v32Name))
out.String(string(v29Name))
out.RawByte(':')
out.Raw((v32Value).MarshalJSON())
out.Raw((v29Value).MarshalJSON())
}
out.RawByte('}')
}
@ -2413,88 +2367,41 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk16(in
in.Delim('[')
if out.Changed == nil {
if !in.IsDelim(']') {
out.Changed = make([]api.StringMap, 0, 8)
out.Changed = make(api.UserDataList, 0, 8)
} else {
out.Changed = []api.StringMap{}
out.Changed = api.UserDataList{}
}
} else {
out.Changed = (out.Changed)[:0]
}
for !in.IsDelim(']') {
var v33 api.StringMap
var v30 api.StringMap
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
if !in.IsDelim('}') {
v33 = make(api.StringMap)
v30 = make(api.StringMap)
} else {
v33 = nil
v30 = nil
}
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v34 interface{}
if m, ok := v34.(easyjson.Unmarshaler); ok {
var v31 interface{}
if m, ok := v31.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := v34.(json.Unmarshaler); ok {
} else if m, ok := v31.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
v34 = in.Interface()
v31 = in.Interface()
}
(v33)[key] = v34
(v30)[key] = v31
in.WantComma()
}
in.Delim('}')
}
out.Changed = append(out.Changed, v33)
in.WantComma()
}
in.Delim(']')
}
case "users":
if in.IsNull() {
in.Skip()
out.Users = nil
} else {
in.Delim('[')
if out.Users == nil {
if !in.IsDelim(']') {
out.Users = make([]api.StringMap, 0, 8)
} else {
out.Users = []api.StringMap{}
}
} else {
out.Users = (out.Users)[:0]
}
for !in.IsDelim(']') {
var v35 api.StringMap
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
if !in.IsDelim('}') {
v35 = make(api.StringMap)
} else {
v35 = nil
}
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v36 interface{}
if m, ok := v36.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := v36.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
v36 = in.Interface()
}
(v35)[key] = v36
in.WantComma()
}
in.Delim('}')
}
out.Users = append(out.Users, v35)
out.Changed = append(out.Changed, v30)
in.WantComma()
}
in.Delim(']')
@ -2519,70 +2426,29 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk16(out
out.RawString(prefix[1:])
{
out.RawByte('[')
for v37, v38 := range in.Changed {
if v37 > 0 {
for v32, v33 := range in.Changed {
if v32 > 0 {
out.RawByte(',')
}
if v38 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
if v33 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v39First := true
for v39Name, v39Value := range v38 {
if v39First {
v39First = false
v34First := true
for v34Name, v34Value := range v33 {
if v34First {
v34First = false
} else {
out.RawByte(',')
}
out.String(string(v39Name))
out.String(string(v34Name))
out.RawByte(':')
if m, ok := v39Value.(easyjson.Marshaler); ok {
if m, ok := v34Value.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := v39Value.(json.Marshaler); ok {
} else if m, ok := v34Value.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(v39Value))
}
}
out.RawByte('}')
}
}
out.RawByte(']')
}
}
if len(in.Users) != 0 {
const prefix string = ",\"users\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('[')
for v40, v41 := range in.Users {
if v40 > 0 {
out.RawByte(',')
}
if v41 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v42First := true
for v42Name, v42Value := range v41 {
if v42First {
v42First = false
} else {
out.RawByte(',')
}
out.String(string(v42Name))
out.RawByte(':')
if m, ok := v42Value.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := v42Value.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(v42Value))
out.Raw(json.Marshal(v34Value))
}
}
out.RawByte('}')
@ -2715,40 +2581,13 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk18(in
out.UserIds = (out.UserIds)[:0]
}
for !in.IsDelim(']') {
var v43 string
var v35 string
if in.IsNull() {
in.Skip()
} else {
v43 = string(in.String())
v35 = string(in.String())
}
out.UserIds = append(out.UserIds, v43)
in.WantComma()
}
in.Delim(']')
}
case "alluserids":
if in.IsNull() {
in.Skip()
out.AllUserIds = nil
} else {
in.Delim('[')
if out.AllUserIds == nil {
if !in.IsDelim(']') {
out.AllUserIds = make([]string, 0, 4)
} else {
out.AllUserIds = []string{}
}
} else {
out.AllUserIds = (out.AllUserIds)[:0]
}
for !in.IsDelim(']') {
var v44 string
if in.IsNull() {
in.Skip()
} else {
v44 = string(in.String())
}
out.AllUserIds = append(out.AllUserIds, v44)
out.UserIds = append(out.UserIds, v35)
in.WantComma()
}
in.Delim(']')
@ -2781,30 +2620,11 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk18(out
out.RawString(prefix[1:])
{
out.RawByte('[')
for v45, v46 := range in.UserIds {
if v45 > 0 {
for v36, v37 := range in.UserIds {
if v36 > 0 {
out.RawByte(',')
}
out.String(string(v46))
}
out.RawByte(']')
}
}
if len(in.AllUserIds) != 0 {
const prefix string = ",\"alluserids\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('[')
for v47, v48 := range in.AllUserIds {
if v47 > 0 {
out.RawByte(',')
}
out.String(string(v48))
out.String(string(v37))
}
out.RawByte(']')
}
@ -2881,88 +2701,41 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk19(in
in.Delim('[')
if out.Changed == nil {
if !in.IsDelim(']') {
out.Changed = make([]api.StringMap, 0, 8)
out.Changed = make(api.UserDataList, 0, 8)
} else {
out.Changed = []api.StringMap{}
out.Changed = api.UserDataList{}
}
} else {
out.Changed = (out.Changed)[:0]
}
for !in.IsDelim(']') {
var v49 api.StringMap
var v38 api.StringMap
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
if !in.IsDelim('}') {
v49 = make(api.StringMap)
v38 = make(api.StringMap)
} else {
v49 = nil
v38 = nil
}
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v50 interface{}
if m, ok := v50.(easyjson.Unmarshaler); ok {
var v39 interface{}
if m, ok := v39.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := v50.(json.Unmarshaler); ok {
} else if m, ok := v39.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
v50 = in.Interface()
v39 = in.Interface()
}
(v49)[key] = v50
(v38)[key] = v39
in.WantComma()
}
in.Delim('}')
}
out.Changed = append(out.Changed, v49)
in.WantComma()
}
in.Delim(']')
}
case "users":
if in.IsNull() {
in.Skip()
out.Users = nil
} else {
in.Delim('[')
if out.Users == nil {
if !in.IsDelim(']') {
out.Users = make([]api.StringMap, 0, 8)
} else {
out.Users = []api.StringMap{}
}
} else {
out.Users = (out.Users)[:0]
}
for !in.IsDelim(']') {
var v51 api.StringMap
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
if !in.IsDelim('}') {
v51 = make(api.StringMap)
} else {
v51 = nil
}
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v52 interface{}
if m, ok := v52.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := v52.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
v52 = in.Interface()
}
(v51)[key] = v52
in.WantComma()
}
in.Delim('}')
}
out.Users = append(out.Users, v51)
out.Changed = append(out.Changed, v38)
in.WantComma()
}
in.Delim(']')
@ -3007,70 +2780,29 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk19(out
}
{
out.RawByte('[')
for v53, v54 := range in.Changed {
if v53 > 0 {
for v40, v41 := range in.Changed {
if v40 > 0 {
out.RawByte(',')
}
if v54 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
if v41 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v55First := true
for v55Name, v55Value := range v54 {
if v55First {
v55First = false
v42First := true
for v42Name, v42Value := range v41 {
if v42First {
v42First = false
} else {
out.RawByte(',')
}
out.String(string(v55Name))
out.String(string(v42Name))
out.RawByte(':')
if m, ok := v55Value.(easyjson.Marshaler); ok {
if m, ok := v42Value.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := v55Value.(json.Marshaler); ok {
} else if m, ok := v42Value.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(v55Value))
}
}
out.RawByte('}')
}
}
out.RawByte(']')
}
}
if len(in.Users) != 0 {
const prefix string = ",\"users\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('[')
for v56, v57 := range in.Users {
if v56 > 0 {
out.RawByte(',')
}
if v57 == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v58First := true
for v58Name, v58Value := range v57 {
if v58First {
v58First = false
} else {
out.RawByte(',')
}
out.String(string(v58Name))
out.RawByte(':')
if m, ok := v58Value.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := v58Value.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(v58Value))
out.Raw(json.Marshal(v42Value))
}
}
out.RawByte('}')
@ -3135,13 +2867,13 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(in
out.UserIds = (out.UserIds)[:0]
}
for !in.IsDelim(']') {
var v59 string
var v43 string
if in.IsNull() {
in.Skip()
} else {
v59 = string(in.String())
v43 = string(in.String())
}
out.UserIds = append(out.UserIds, v59)
out.UserIds = append(out.UserIds, v43)
in.WantComma()
}
in.Delim(']')
@ -3162,40 +2894,13 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk20(in
out.SessionIds = (out.SessionIds)[:0]
}
for !in.IsDelim(']') {
var v60 api.RoomSessionId
var v44 api.RoomSessionId
if in.IsNull() {
in.Skip()
} else {
v60 = api.RoomSessionId(in.String())
v44 = api.RoomSessionId(in.String())
}
out.SessionIds = append(out.SessionIds, v60)
in.WantComma()
}
in.Delim(']')
}
case "alluserids":
if in.IsNull() {
in.Skip()
out.AllUserIds = nil
} else {
in.Delim('[')
if out.AllUserIds == nil {
if !in.IsDelim(']') {
out.AllUserIds = make([]string, 0, 4)
} else {
out.AllUserIds = []string{}
}
} else {
out.AllUserIds = (out.AllUserIds)[:0]
}
for !in.IsDelim(']') {
var v61 string
if in.IsNull() {
in.Skip()
} else {
v61 = string(in.String())
}
out.AllUserIds = append(out.AllUserIds, v61)
out.SessionIds = append(out.SessionIds, v44)
in.WantComma()
}
in.Delim(']')
@ -3228,11 +2933,11 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(out
out.RawString(prefix[1:])
{
out.RawByte('[')
for v62, v63 := range in.UserIds {
if v62 > 0 {
for v45, v46 := range in.UserIds {
if v45 > 0 {
out.RawByte(',')
}
out.String(string(v63))
out.String(string(v46))
}
out.RawByte(']')
}
@ -3247,30 +2952,11 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk20(out
}
{
out.RawByte('[')
for v64, v65 := range in.SessionIds {
if v64 > 0 {
for v47, v48 := range in.SessionIds {
if v47 > 0 {
out.RawByte(',')
}
out.String(string(v65))
}
out.RawByte(']')
}
}
if len(in.AllUserIds) != 0 {
const prefix string = ",\"alluserids\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('[')
for v66, v67 := range in.AllUserIds {
if v66 > 0 {
out.RawByte(',')
}
out.String(string(v67))
out.String(string(v48))
}
out.RawByte(']')
}
@ -3569,33 +3255,6 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk24(in
key := in.UnsafeFieldName(false)
in.WantColon()
switch key {
case "userids":
if in.IsNull() {
in.Skip()
out.UserIds = nil
} else {
in.Delim('[')
if out.UserIds == nil {
if !in.IsDelim(']') {
out.UserIds = make([]string, 0, 4)
} else {
out.UserIds = []string{}
}
} else {
out.UserIds = (out.UserIds)[:0]
}
for !in.IsDelim(']') {
var v68 string
if in.IsNull() {
in.Skip()
} else {
v68 = string(in.String())
}
out.UserIds = append(out.UserIds, v68)
in.WantComma()
}
in.Delim(']')
}
default:
in.SkipRecursive()
}
@ -3610,21 +3269,6 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk24(out
out.RawByte('{')
first := true
_ = first
if len(in.UserIds) != 0 {
const prefix string = ",\"userids\":"
first = false
out.RawString(prefix[1:])
{
out.RawByte('[')
for v69, v70 := range in.UserIds {
if v69 > 0 {
out.RawByte(',')
}
out.String(string(v70))
}
out.RawByte(']')
}
}
out.RawByte('}')
}
@ -3996,13 +3640,13 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk28(in
*out.Permissions = (*out.Permissions)[:0]
}
for !in.IsDelim(']') {
var v71 api.Permission
var v49 api.Permission
if in.IsNull() {
in.Skip()
} else {
v71 = api.Permission(in.String())
v49 = api.Permission(in.String())
}
*out.Permissions = append(*out.Permissions, v71)
*out.Permissions = append(*out.Permissions, v49)
in.WantComma()
}
in.Delim(']')
@ -4049,11 +3693,11 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk28(out
out.RawString("null")
} else {
out.RawByte('[')
for v72, v73 := range *in.Permissions {
if v72 > 0 {
for v50, v51 := range *in.Permissions {
if v50 > 0 {
out.RawByte(',')
}
out.String(string(v73))
out.String(string(v51))
}
out.RawByte(']')
}
@ -4645,13 +4289,13 @@ func easyjsonC1cedd36DecodeGithubComStrukturagNextcloudSpreedSignalingTalk33(in
out.Entries = (out.Entries)[:0]
}
for !in.IsDelim(']') {
var v74 BackendPingEntry
var v52 BackendPingEntry
if in.IsNull() {
in.Skip()
} else {
(v74).UnmarshalEasyJSON(in)
(v52).UnmarshalEasyJSON(in)
}
out.Entries = append(out.Entries, v74)
out.Entries = append(out.Entries, v52)
in.WantComma()
}
in.Delim(']')
@ -4687,11 +4331,11 @@ func easyjsonC1cedd36EncodeGithubComStrukturagNextcloudSpreedSignalingTalk33(out
out.RawString("null")
} else {
out.RawByte('[')
for v75, v76 := range in.Entries {
if v75 > 0 {
for v53, v54 := range in.Entries {
if v53 > 0 {
out.RawByte(',')
}
(v76).MarshalEasyJSON(out)
(v54).MarshalEasyJSON(out)
}
out.RawByte(']')
}