Merge pull request #421 from strukturag/internal-incall

Allow internal clients to set / change the "inCall" flags.
This commit is contained in:
Joachim Bauch 2023-02-22 08:31:08 +01:00 committed by GitHub
commit 5757b9ca30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 521 additions and 39 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -141,6 +141,7 @@ Message format (Client -> Server):
"type": "hello",
"hello": {
"version": "the-protocol-version",
"features": ["optional", "list, "of", "client", "feature", "ids"],
"auth": {
"url": "the-url-to-the-auth-backend",
"params": {
@ -307,6 +308,7 @@ Message format (Client -> Server):
"type": "hello",
"hello": {
"version": "the-protocol-version",
"features": ["optional", "list, "of", "client", "feature", "ids"],
"auth": {
"type": "the-client-type",
...other attributes depending on the client type...
@ -869,6 +871,98 @@ Message format (Server -> Client):
}
## Internal clients
Internal clients can be used by third-party applications to perform tasks that
a regular client can not be used. Examples are adding virtual sessions or
sending media without a regular client connected. This is used for example by
the SIP bridge to publish mixed phone audio and show "virtual" sessions for the
individial phone calls.
See above for details on how to connect as internal client. By default, internal
clients have their "inCall" and the "publishing audio" flags set. Virtual
sessions have their "inCall" and the "publishing phone" flags set.
This can be changed by including the client feature flag `internal-incall`
which will require the client to set the flags as necessary.
### Add virtual session
Message format (Client -> Server):
{
"type": "internal",
"internal": {
"type": "addsession",
"addsession": {
"sessionid": "the-virtual-sessionid",
"roomid": "the-room-id-to-add-the-session",
"userid": "optional-user-id",
"user": {
...additional data of the user...
},
"flags": "optional-initial-flags",
"incall": "optional-initial-incall",
"options": {
"actorId": "optional-actor-id",
"actorType": "optional-actor-type",
}
}
}
}
### Update virtual session
Message format (Client -> Server):
{
"type": "internal",
"internal": {
"type": "updatesession",
"updatesession": {
"sessionid": "the-virtual-sessionid",
"roomid": "the-room-id-to-update-the-session",
"flags": "optional-updated-flags",
"incall": "optional-updated-incall"
}
}
}
### Remove virtual session
Message format (Client -> Server):
{
"type": "internal",
"internal": {
"type": "removesession",
"removesession": {
"sessionid": "the-virtual-sessionid",
"roomid": "the-room-id-to-add-the-session",
"userid": "optional-user-id"
}
}
}
### Change inCall flags of internal client
Message format (Client -> Server):
{
"type": "internal",
"internal": {
"type": "incall",
"incall": {
"incall": "the-incall-flags"
}
}
}
# Internal signaling server API
The signaling server provides an internal API that can be called from Nextcloud

22
hub.go
View file

@ -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

View file

@ -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)

60
room.go
View file

@ -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() {

View file

@ -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,

View file

@ -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
}

View file

@ -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)