From 2f6e2ba87c3be5e30e5b3a617c782c346bb6b3eb Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Wed, 15 Feb 2023 11:10:13 +0100 Subject: [PATCH] Allow internal clients to set / change the "inCall" flags. --- api_signaling.go | 27 ++++- clientsession.go | 26 ++++ clientsession_test.go | 2 +- hub.go | 22 +++- hub_test.go | 20 ++-- room.go | 60 ++++++++-- testclient_test.go | 17 ++- virtualsession.go | 29 +++++ virtualsession_test.go | 263 ++++++++++++++++++++++++++++++++++++++++- 9 files changed, 427 insertions(+), 39 deletions(-) diff --git a/api_signaling.go b/api_signaling.go index 8411fea..d9ca99a 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -423,7 +423,7 @@ func (m *HelloClientMessage) CheckValid() error { } const ( - // Features for all clients. + // Features to send to all clients. ServerFeatureMcu = "mcu" ServerFeatureSimulcast = "simulcast" ServerFeatureUpdateSdp = "update-sdp" @@ -434,8 +434,11 @@ const ( ServerFeatureHelloV2 = "hello-v2" ServerFeatureSwitchTo = "switchto" - // Features for internal clients only. + // Features to send to internal clients only. ServerFeatureInternalVirtualSessions = "virtual-sessions" + + // Possible client features from the "hello" request. + ClientFeatureInternalInCall = "internal-incall" ) var ( @@ -628,6 +631,7 @@ type AddSessionInternalClientMessage struct { UserId string `json:"userid,omitempty"` User *json.RawMessage `json:"user,omitempty"` Flags uint32 `json:"flags,omitempty"` + InCall *int `json:"incall,omitempty"` Options *AddSessionOptions `json:"options,omitempty"` } @@ -639,7 +643,8 @@ func (m *AddSessionInternalClientMessage) CheckValid() error { type UpdateSessionInternalClientMessage struct { CommonSessionInternalClientMessage - Flags *uint32 `json:"flags,omitempty"` + Flags *uint32 `json:"flags,omitempty"` + InCall *int `json:"incall,omitempty"` } func (m *UpdateSessionInternalClientMessage) CheckValid() error { @@ -656,6 +661,14 @@ func (m *RemoveSessionInternalClientMessage) CheckValid() error { return m.CommonSessionInternalClientMessage.CheckValid() } +type InCallInternalClientMessage struct { + InCall int `json:"incall"` +} + +func (m *InCallInternalClientMessage) CheckValid() error { + return nil +} + type InternalClientMessage struct { Type string `json:"type"` @@ -664,6 +677,8 @@ type InternalClientMessage struct { UpdateSession *UpdateSessionInternalClientMessage `json:"updatesession,omitempty"` RemoveSession *RemoveSessionInternalClientMessage `json:"removesession,omitempty"` + + InCall *InCallInternalClientMessage `json:"incall,omitempty"` } func (m *InternalClientMessage) CheckValid() error { @@ -686,6 +701,12 @@ func (m *InternalClientMessage) CheckValid() error { } else if err := m.RemoveSession.CheckValid(); err != nil { return err } + case "incall": + if m.InCall == nil { + return fmt.Errorf("incall missing") + } else if err := m.InCall.CheckValid(); err != nil { + return err + } } return nil } diff --git a/clientsession.go b/clientsession.go index 22814b4..d8b3a45 100644 --- a/clientsession.go +++ b/clientsession.go @@ -48,6 +48,7 @@ var ( type ClientSession struct { roomJoinTime int64 + inCall uint32 hub *Hub events AsyncEvents @@ -106,6 +107,9 @@ func NewClientSession(hub *Hub, privateId string, publicId string, data *Session if s.clientType == HelloClientTypeInternal { s.backendUrl = hello.Auth.internalParams.Backend s.parsedBackendUrl = hello.Auth.internalParams.parsedBackend + if !s.HasFeature(ClientFeatureInternalInCall) { + s.SetInCall(FlagInCall | FlagWithAudio) + } } else { s.backendUrl = hello.Auth.Url s.parsedBackendUrl = hello.Auth.parsedUrl @@ -155,6 +159,28 @@ func (s *ClientSession) ClientType() string { return s.clientType } +// GetInCall is only used for internal clients. +func (s *ClientSession) GetInCall() int { + return int(atomic.LoadUint32(&s.inCall)) +} + +func (s *ClientSession) SetInCall(inCall int) bool { + if inCall < 0 { + inCall = 0 + } + + for { + old := atomic.LoadUint32(&s.inCall) + if old == uint32(inCall) { + return false + } + + if atomic.CompareAndSwapUint32(&s.inCall, old, uint32(inCall)) { + return true + } + } +} + func (s *ClientSession) GetFeatures() []string { return s.features } diff --git a/clientsession_test.go b/clientsession_test.go index eb152dd..0066eea 100644 --- a/clientsession_test.go +++ b/clientsession_test.go @@ -238,7 +238,7 @@ func TestBandwidth_Backend(t *testing.T) { params := TestBackendClientAuthParams{ UserId: testDefaultUserId, } - if err := client.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", params); err != nil { + if err := client.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params); err != nil { t.Fatal(err) } diff --git a/hub.go b/hub.go index c7dc1af..5afb393 100644 --- a/hub.go +++ b/hub.go @@ -1797,7 +1797,7 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) { request := NewBackendClientRoomRequest(room.Id(), msg.UserId, publicSessionId) request.Room.ActorId = msg.Options.ActorId request.Room.ActorType = msg.Options.ActorType - request.Room.InCall = FlagInCall | FlagWithPhone + request.Room.InCall = sess.GetInCall() var response BackendClientResponse if err := h.backend.PerformJSONRequest(ctx, session.ParsedBackendUrl(), request, &response); err != nil { @@ -1856,18 +1856,23 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) { sess := h.sessions[sid] h.mu.Unlock() if sess != nil { - update := false + var changed SessionChangeFlag if virtualSession, ok := sess.(*VirtualSession); ok { if msg.Flags != nil { if virtualSession.SetFlags(*msg.Flags) { - update = true + changed |= SessionChangeFlags + } + } + if msg.InCall != nil { + if virtualSession.SetInCall(*msg.InCall) { + changed |= SessionChangeInCall } } } else { log.Printf("Ignore update request for non-virtual session %s", sess.PublicId()) } - if update { - room.NotifySessionChanged(sess) + if changed != 0 { + room.NotifySessionChanged(sess, changed) } } case "removesession": @@ -1898,6 +1903,13 @@ func (h *Hub) processInternalMsg(client *Client, message *ClientMessage) { sess.Close() } } + case "incall": + msg := msg.InCall + if session.SetInCall(msg.InCall) { + if room := session.GetRoom(); room != nil { + room.NotifySessionChanged(session, SessionChangeInCall) + } + } default: log.Printf("Ignore unsupported internal message %+v from %s", msg, session.PublicId()) return diff --git a/hub_test.go b/hub_test.go index 5ae07f6..6d70cc1 100644 --- a/hub_test.go +++ b/hub_test.go @@ -848,7 +848,7 @@ func TestExpectClientHelloUnsupportedVersion(t *testing.T) { params := TestBackendClientAuthParams{ UserId: testDefaultUserId, } - if err := client.SendHelloParams(server.URL, "0.0", "", params); err != nil { + if err := client.SendHelloParams(server.URL, "0.0", "", nil, params); err != nil { t.Fatal(err) } @@ -1254,7 +1254,7 @@ func TestClientHelloSessionLimit(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: testDefaultUserId, } - if err := client.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", params1); err != nil { + if err := client.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params1); err != nil { t.Fatal(err) } @@ -1279,7 +1279,7 @@ func TestClientHelloSessionLimit(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: testDefaultUserId + "2", } - if err := client2.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", params2); err != nil { + if err := client2.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params2); err != nil { t.Fatal(err) } @@ -1295,7 +1295,7 @@ func TestClientHelloSessionLimit(t *testing.T) { } // The client can connect to a different backend. - if err := client2.SendHelloParams(server1.URL+"/two", HelloVersionV1, "client", params2); err != nil { + if err := client2.SendHelloParams(server1.URL+"/two", HelloVersionV1, "client", nil, params2); err != nil { t.Fatal(err) } @@ -1322,7 +1322,7 @@ func TestClientHelloSessionLimit(t *testing.T) { params3 := TestBackendClientAuthParams{ UserId: testDefaultUserId + "3", } - if err := client3.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", params3); err != nil { + if err := client3.SendHelloParams(server1.URL+"/one", HelloVersionV1, "client", nil, params3); err != nil { t.Fatal(err) } @@ -1926,7 +1926,7 @@ func TestClientHelloClient_V3Api(t *testing.T) { } // The "/api/v1/signaling/" URL will be changed to use "v3" as the "signaling-v3" // feature is returned by the capabilities endpoint. - if err := client.SendHelloParams(server.URL+"/ocs/v2.php/apps/spreed/api/v1/signaling/backend", HelloVersionV1, "client", params); err != nil { + if err := client.SendHelloParams(server.URL+"/ocs/v2.php/apps/spreed/api/v1/signaling/backend", HelloVersionV1, "client", nil, params); err != nil { t.Fatal(err) } @@ -4269,7 +4269,7 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: "user1", } - if err := client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", params1); err != nil { + if err := client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1); err != nil { t.Fatal(err) } hello1, err := client1.RunUntilHello(ctx) @@ -4283,7 +4283,7 @@ func TestNoSendBetweenSessionsOnDifferentBackends(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: "user2", } - if err := client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", params2); err != nil { + if err := client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2); err != nil { t.Fatal(err) } hello2, err := client2.RunUntilHello(ctx) @@ -4339,7 +4339,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { params1 := TestBackendClientAuthParams{ UserId: "user1", } - if err := client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", params1); err != nil { + if err := client1.SendHelloParams(server.URL+"/one", HelloVersionV1, "client", nil, params1); err != nil { t.Fatal(err) } hello1, err := client1.RunUntilHello(ctx) @@ -4353,7 +4353,7 @@ func TestNoSameRoomOnDifferentBackends(t *testing.T) { params2 := TestBackendClientAuthParams{ UserId: "user2", } - if err := client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", params2); err != nil { + if err := client2.SendHelloParams(server.URL+"/two", HelloVersionV1, "client", nil, params2); err != nil { t.Fatal(err) } hello2, err := client2.RunUntilHello(ctx) diff --git a/room.go b/room.go index 94db3f3..a3f8e20 100644 --- a/room.go +++ b/room.go @@ -44,6 +44,13 @@ const ( FlagWithPhone = 8 ) +type SessionChangeFlag int + +const ( + SessionChangeFlags SessionChangeFlag = 1 + SessionChangeInCall SessionChangeFlag = 2 +) + var ( updateActiveSessionsInterval = 10 * time.Second ) @@ -571,7 +578,7 @@ func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string] } for session := range r.internalSessions { users = append(users, map[string]interface{}{ - "inCall": FlagInCall | FlagWithAudio, + "inCall": session.(*ClientSession).GetInCall(), "sessionId": session.PublicId(), "lastPing": now, "internal": true, @@ -579,7 +586,7 @@ func (r *Room) addInternalSessions(users []map[string]interface{}) []map[string] } for session := range r.virtualSessions { users = append(users, map[string]interface{}{ - "inCall": FlagInCall | FlagWithPhone, + "inCall": session.GetInCall(), "sessionId": session.PublicId(), "lastPing": now, "virtual": true, @@ -816,18 +823,51 @@ func (r *Room) NotifySessionResumed(session *ClientSession) { session.SendMessage(message) } -func (r *Room) NotifySessionChanged(session Session) { - if session.ClientType() != HelloClientTypeVirtual { +func (r *Room) NotifySessionChanged(session Session, flags SessionChangeFlag) { + if flags&SessionChangeFlags != 0 && session.ClientType() == HelloClientTypeVirtual { // Only notify if a virtual session has changed. - return + if virtual, ok := session.(*VirtualSession); ok { + r.publishSessionFlagsChanged(virtual) + } } - virtual, ok := session.(*VirtualSession) - if !ok { - return - } + if flags&SessionChangeInCall != 0 { + joinLeave := 0 + if clientSession, ok := session.(*ClientSession); ok { + if clientSession.GetInCall()&FlagInCall != 0 { + joinLeave = 1 + } else { + joinLeave = 2 + } + } else if virtual, ok := session.(*VirtualSession); ok { + if virtual.GetInCall()&FlagInCall != 0 { + joinLeave = 1 + } else { + joinLeave = 2 + } + } - r.publishSessionFlagsChanged(virtual) + if joinLeave != 0 { + if joinLeave == 1 { + r.mu.Lock() + if !r.inCallSessions[session] { + r.inCallSessions[session] = true + log.Printf("Session %s joined call %s", session.PublicId(), r.id) + } + r.mu.Unlock() + } else if joinLeave == 2 { + r.mu.Lock() + delete(r.inCallSessions, session) + r.mu.Unlock() + if clientSession, ok := session.(*ClientSession); ok { + clientSession.LeaveCall() + } + } + + // TODO: Check if we could send a smaller update message with only the changed session. + r.publishUsersChangedWithInternal() + } + } } func (r *Room) publishUsersChangedWithInternal() { diff --git a/testclient_test.go b/testclient_test.go index 6cb90c1..86560c0 100644 --- a/testclient_test.go +++ b/testclient_test.go @@ -385,7 +385,7 @@ func (c *TestClient) SendHelloV1(userid string) error { params := TestBackendClientAuthParams{ UserId: userid, } - return c.SendHelloParams(c.server.URL, HelloVersionV1, "", params) + return c.SendHelloParams(c.server.URL, HelloVersionV1, "", nil, params) } func (c *TestClient) SendHelloV2(userid string) error { @@ -434,7 +434,7 @@ func (c *TestClient) SendHelloV2WithTimes(userid string, issuedAt time.Time, exp params := HelloV2AuthParams{ Token: tokenString, } - return c.SendHelloParams(c.server.URL, HelloVersionV2, "", params) + return c.SendHelloParams(c.server.URL, HelloVersionV2, "", nil, params) } func (c *TestClient) SendHelloResume(resumeId string) error { @@ -453,10 +453,14 @@ func (c *TestClient) SendHelloClient(userid string) error { params := TestBackendClientAuthParams{ UserId: userid, } - return c.SendHelloParams(c.server.URL, HelloVersionV1, "client", params) + return c.SendHelloParams(c.server.URL, HelloVersionV1, "client", nil, params) } func (c *TestClient) SendHelloInternal() error { + return c.SendHelloInternalWithFeatures(nil) +} + +func (c *TestClient) SendHelloInternalWithFeatures(features []string) error { random := newRandomString(48) mac := hmac.New(sha256.New, testInternalSecret) mac.Write([]byte(random)) // nolint @@ -468,10 +472,10 @@ func (c *TestClient) SendHelloInternal() error { Token: token, Backend: backend, } - return c.SendHelloParams("", HelloVersionV1, "internal", params) + return c.SendHelloParams("", HelloVersionV1, "internal", features, params) } -func (c *TestClient) SendHelloParams(url string, version string, clientType string, params interface{}) error { +func (c *TestClient) SendHelloParams(url string, version string, clientType string, features []string, params interface{}) error { data, err := json.Marshal(params) if err != nil { c.t.Fatal(err) @@ -481,7 +485,8 @@ func (c *TestClient) SendHelloParams(url string, version string, clientType stri Id: "1234", Type: "hello", Hello: &HelloClientMessage{ - Version: version, + Version: version, + Features: features, Auth: HelloClientMessageAuth{ Type: clientType, Url: url, diff --git a/virtualsession.go b/virtualsession.go index 45ec832..1f16d59 100644 --- a/virtualsession.go +++ b/virtualsession.go @@ -38,6 +38,8 @@ const ( ) type VirtualSession struct { + inCall uint32 + hub *Hub session *ClientSession privateId string @@ -75,6 +77,12 @@ func NewVirtualSession(session *ClientSession, privateId string, publicId string return nil, err } + if msg.InCall != nil { + result.SetInCall(*msg.InCall) + } else if !session.HasFeature(ClientFeatureInternalInCall) { + result.SetInCall(FlagInCall | FlagWithPhone) + } + return result, nil } @@ -90,6 +98,27 @@ func (s *VirtualSession) ClientType() string { return HelloClientTypeVirtual } +func (s *VirtualSession) GetInCall() int { + return int(atomic.LoadUint32(&s.inCall)) +} + +func (s *VirtualSession) SetInCall(inCall int) bool { + if inCall < 0 { + inCall = 0 + } + + for { + old := atomic.LoadUint32(&s.inCall) + if old == uint32(inCall) { + return false + } + + if atomic.CompareAndSwapUint32(&s.inCall, old, uint32(inCall)) { + return true + } + } +} + func (s *VirtualSession) Data() *SessionIdData { return s.data } diff --git a/virtualsession_test.go b/virtualsession_test.go index d4651e4..7a986bf 100644 --- a/virtualsession_test.go +++ b/virtualsession_test.go @@ -25,6 +25,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "testing" ) @@ -143,8 +144,8 @@ func TestVirtualSession(t *testing.T) { t.Errorf("Expected session id %s, got %+v", sessionId, updateMsg.Users[0]) } else if virtual, ok := updateMsg.Users[0]["virtual"].(bool); !ok || !virtual { t.Errorf("Expected virtual user, got %+v", updateMsg.Users[0]) - } else if inCall, ok := updateMsg.Users[0]["inCall"].(float64); !ok || inCall == 0 { - t.Errorf("Expected user in call, got %+v", updateMsg.Users[0]) + } else if inCall, ok := updateMsg.Users[0]["inCall"].(float64); !ok || inCall != (FlagInCall|FlagWithPhone) { + t.Errorf("Expected user in call with phone, got %+v", updateMsg.Users[0]) } msg3, err := client.RunUntilMessage(ctx) @@ -313,6 +314,260 @@ func TestVirtualSession(t *testing.T) { } } +func checkHasEntryWithInCall(message *RoomEventServerMessage, sessionId string, entryType string, inCall int) error { + found := false + for _, entry := range message.Users { + if sid, ok := entry["sessionId"].(string); ok && sid == sessionId { + if value, ok := entry[entryType].(bool); !ok || !value { + return fmt.Errorf("Expected %s user, got %+v", entryType, entry) + } + + if value, ok := entry["inCall"].(float64); !ok || int(value) != inCall { + return fmt.Errorf("Expected in call %d, got %+v", inCall, entry) + } + found = true + break + } + } + + if !found { + return fmt.Errorf("No user with session id %s found, got %+v", sessionId, message) + } + + return nil +} + +func TestVirtualSessionCustomInCall(t *testing.T) { + hub, _, _, server := CreateHubForTest(t) + + roomId := "the-room-id" + emptyProperties := json.RawMessage("{}") + backend := &Backend{ + id: "compat", + compat: true, + } + room, err := hub.createRoom(roomId, &emptyProperties, backend) + if err != nil { + t.Fatalf("Could not create room: %s", err) + } + defer room.Close() + + clientInternal := NewTestClient(t, server, hub) + defer clientInternal.CloseWithBye() + features := []string{ + ClientFeatureInternalInCall, + } + if err := clientInternal.SendHelloInternalWithFeatures(features); err != nil { + t.Fatal(err) + } + + client := NewTestClient(t, server, hub) + defer client.CloseWithBye() + if err := client.SendHello(testDefaultUserId); err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + helloInternal, err := clientInternal.RunUntilHello(ctx) + if err != nil { + t.Error(err) + } else { + if helloInternal.Hello.UserId != "" { + t.Errorf("Expected empty user id, got %+v", helloInternal.Hello) + } + if helloInternal.Hello.SessionId == "" { + t.Errorf("Expected session id, got %+v", helloInternal.Hello) + } + if helloInternal.Hello.ResumeId == "" { + t.Errorf("Expected resume id, got %+v", helloInternal.Hello) + } + } + if room, err := clientInternal.JoinRoomWithRoomSession(ctx, roomId, ""); err != nil { + t.Fatal(err) + } else if room.Room.RoomId != roomId { + t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) + } + + hello, err := client.RunUntilHello(ctx) + if err != nil { + t.Error(err) + } + if room, err := client.JoinRoom(ctx, roomId); err != nil { + t.Fatal(err) + } else if room.Room.RoomId != roomId { + t.Fatalf("Expected room %s, got %s", roomId, room.Room.RoomId) + } + + if _, additional, err := clientInternal.RunUntilJoinedAndReturn(ctx, helloInternal.Hello, hello.Hello); err != nil { + t.Error(err) + } else if len(additional) != 1 { + t.Errorf("expected one additional message, got %+v", additional) + } else if additional[0].Type != "event" { + t.Errorf("expected event message, got %+v", additional[0]) + } else if additional[0].Event.Target != "participants" { + t.Errorf("expected event participants message, got %+v", additional[0]) + } else if additional[0].Event.Type != "update" { + t.Errorf("expected event participants update message, got %+v", additional[0]) + } else if additional[0].Event.Update.Users[0]["sessionId"].(string) != helloInternal.Hello.SessionId { + t.Errorf("expected event update message for internal session, got %+v", additional[0]) + } else if additional[0].Event.Update.Users[0]["inCall"].(float64) != 0 { + t.Errorf("expected event update message with session not in call, got %+v", additional[0]) + } + if err := client.RunUntilJoined(ctx, helloInternal.Hello, hello.Hello); err != nil { + t.Error(err) + } + + internalSessionId := "session1" + userId := "user1" + msgAdd := &ClientMessage{ + Type: "internal", + Internal: &InternalClientMessage{ + Type: "addsession", + AddSession: &AddSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: internalSessionId, + RoomId: roomId, + }, + UserId: userId, + Flags: FLAG_MUTED_SPEAKING, + }, + }, + } + if err := clientInternal.WriteJSON(msgAdd); err != nil { + t.Fatal(err) + } + + msg1, err := client.RunUntilMessage(ctx) + if err != nil { + t.Fatal(err) + } + // The public session id will be generated by the server, so don't check for it. + if err := client.checkMessageJoinedSession(msg1, "", userId); err != nil { + t.Fatal(err) + } + sessionId := msg1.Event.Join[0].SessionId + session := hub.GetSessionByPublicId(sessionId) + if session == nil { + t.Fatalf("Could not get virtual session %s", sessionId) + } + if session.ClientType() != HelloClientTypeVirtual { + t.Errorf("Expected client type %s, got %s", HelloClientTypeVirtual, session.ClientType()) + } + if sid := session.(*VirtualSession).SessionId(); sid != internalSessionId { + t.Errorf("Expected internal session id %s, got %s", internalSessionId, sid) + } + + // Also a participants update event will be triggered for the virtual user. + msg2, err := client.RunUntilMessage(ctx) + if err != nil { + t.Fatal(err) + } + updateMsg, err := checkMessageParticipantsInCall(msg2) + if err != nil { + t.Error(err) + } else if updateMsg.RoomId != roomId { + t.Errorf("Expected room %s, got %s", roomId, updateMsg.RoomId) + } else if len(updateMsg.Users) != 2 { + t.Errorf("Expected two users, got %+v", updateMsg.Users) + } + + if err := checkHasEntryWithInCall(updateMsg, sessionId, "virtual", 0); err != nil { + t.Error(err) + } + if err := checkHasEntryWithInCall(updateMsg, helloInternal.Hello.SessionId, "internal", 0); err != nil { + t.Error(err) + } + + msg3, err := client.RunUntilMessage(ctx) + if err != nil { + t.Fatal(err) + } + + flagsMsg, err := checkMessageParticipantFlags(msg3) + if err != nil { + t.Error(err) + } else if flagsMsg.RoomId != roomId { + t.Errorf("Expected room %s, got %s", roomId, flagsMsg.RoomId) + } else if flagsMsg.SessionId != sessionId { + t.Errorf("Expected session id %s, got %s", sessionId, flagsMsg.SessionId) + } else if flagsMsg.Flags != FLAG_MUTED_SPEAKING { + t.Errorf("Expected flags %d, got %+v", FLAG_MUTED_SPEAKING, flagsMsg.Flags) + } + + // The internal session can change its "inCall" flags + msgInCall := &ClientMessage{ + Type: "internal", + Internal: &InternalClientMessage{ + Type: "incall", + InCall: &InCallInternalClientMessage{ + InCall: FlagInCall | FlagWithAudio, + }, + }, + } + if err := clientInternal.WriteJSON(msgInCall); err != nil { + t.Fatal(err) + } + + msg4, err := client.RunUntilMessage(ctx) + if err != nil { + t.Fatal(err) + } + updateMsg2, err := checkMessageParticipantsInCall(msg4) + if err != nil { + t.Error(err) + } else if updateMsg2.RoomId != roomId { + t.Errorf("Expected room %s, got %s", roomId, updateMsg2.RoomId) + } else if len(updateMsg2.Users) != 2 { + t.Errorf("Expected two users, got %+v", updateMsg2.Users) + } + if err := checkHasEntryWithInCall(updateMsg2, sessionId, "virtual", 0); err != nil { + t.Error(err) + } + if err := checkHasEntryWithInCall(updateMsg2, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio); err != nil { + t.Error(err) + } + + // The internal session can change the "inCall" flags of a virtual session + newInCall := FlagInCall | FlagWithPhone + msgInCall2 := &ClientMessage{ + Type: "internal", + Internal: &InternalClientMessage{ + Type: "updatesession", + UpdateSession: &UpdateSessionInternalClientMessage{ + CommonSessionInternalClientMessage: CommonSessionInternalClientMessage{ + SessionId: internalSessionId, + RoomId: roomId, + }, + InCall: &newInCall, + }, + }, + } + if err := clientInternal.WriteJSON(msgInCall2); err != nil { + t.Fatal(err) + } + + msg5, err := client.RunUntilMessage(ctx) + if err != nil { + t.Fatal(err) + } + updateMsg3, err := checkMessageParticipantsInCall(msg5) + if err != nil { + t.Error(err) + } else if updateMsg3.RoomId != roomId { + t.Errorf("Expected room %s, got %s", roomId, updateMsg3.RoomId) + } else if len(updateMsg3.Users) != 2 { + t.Errorf("Expected two users, got %+v", updateMsg3.Users) + } + if err := checkHasEntryWithInCall(updateMsg3, sessionId, "virtual", newInCall); err != nil { + t.Error(err) + } + if err := checkHasEntryWithInCall(updateMsg3, helloInternal.Hello.SessionId, "internal", FlagInCall|FlagWithAudio); err != nil { + t.Error(err) + } +} + func TestVirtualSessionCleanup(t *testing.T) { hub, _, _, server := CreateHubForTest(t) @@ -427,8 +682,8 @@ func TestVirtualSessionCleanup(t *testing.T) { t.Errorf("Expected session id %s, got %+v", sessionId, updateMsg.Users[0]) } else if virtual, ok := updateMsg.Users[0]["virtual"].(bool); !ok || !virtual { t.Errorf("Expected virtual user, got %+v", updateMsg.Users[0]) - } else if inCall, ok := updateMsg.Users[0]["inCall"].(float64); !ok || inCall == 0 { - t.Errorf("Expected user in call, got %+v", updateMsg.Users[0]) + } else if inCall, ok := updateMsg.Users[0]["inCall"].(float64); !ok || inCall != (FlagInCall|FlagWithPhone) { + t.Errorf("Expected user in call with phone, got %+v", updateMsg.Users[0]) } msg3, err := client.RunUntilMessage(ctx)