diff --git a/api_signaling.go b/api_signaling.go index 2edb10aa..84851f73 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -41,6 +41,9 @@ const ( // Version 2.0 validates auth params encoded as JWT. HelloVersionV2 = "2.0" + + ActorTypeUsers = "users" + ActorTypeFederatedUsers = "federated_users" ) var ( @@ -52,6 +55,17 @@ func makePtr[T any](v T) *T { return &v } +func getStringMapEntry[T any](m map[string]interface{}, key string) (s T, ok bool) { + var defaultValue T + v, found := m[key] + if !found { + return defaultValue, false + } + + s, ok = v.(T) + return +} + // ClientMessage is a message that is sent from a client to the server. type ClientMessage struct { json.Marshaler diff --git a/federation.go b/federation.go index ca012134..b93dacc3 100644 --- a/federation.go +++ b/federation.go @@ -55,6 +55,18 @@ func isClosedError(err error) bool { strings.Contains(err.Error(), net.ErrClosed.Error()) } +func getCloudUrl(s string) string { + if strings.HasPrefix(s, "https://") { + s = s[8:] + } else { + s = strings.TrimPrefix(s, "http://") + } + if pos := strings.Index(s, "/ocs/v"); pos != -1 { + s = s[:pos] + } + return s +} + type FederationClient struct { hub *Hub session *ClientSession @@ -577,17 +589,36 @@ func (c *FederationClient) joinRoom() error { } func (c *FederationClient) updateEventUsers(users []map[string]interface{}, localSessionId string, remoteSessionId string) { + localCloudUrl := "@" + getCloudUrl(c.session.BackendUrl()) + localCloudUrlLen := len(localCloudUrl) + remoteCloudUrl := "@" + getCloudUrl(c.federation.Load().NextcloudUrl) + checkSessionId := true for _, u := range users { - key := "sessionId" - sid, found := u[key] - if !found { - key := "sessionid" - sid, found = u[key] + if actorType, found := getStringMapEntry[string](u, "actorType"); found { + if actorId, found := getStringMapEntry[string](u, "actorId"); found { + switch actorType { + case ActorTypeFederatedUsers: + if strings.HasSuffix(actorId, localCloudUrl) { + u["actorId"] = actorId[:len(actorId)-localCloudUrlLen] + u["actorType"] = ActorTypeUsers + } + case ActorTypeUsers: + u["actorId"] = actorId + remoteCloudUrl + u["actorType"] = ActorTypeFederatedUsers + } + } } - if found { - if sid, ok := sid.(string); ok && sid == remoteSessionId { + + if checkSessionId { + key := "sessionId" + sid, found := getStringMapEntry[string](u, key) + if !found { + key := "sessionid" + sid, found = getStringMapEntry[string](u, key) + } + if found && sid == remoteSessionId { u[key] = localSessionId - break + checkSessionId = false } } } diff --git a/federation_test.go b/federation_test.go index 0e31f155..a56edae2 100644 --- a/federation_test.go +++ b/federation_test.go @@ -24,6 +24,7 @@ package signaling import ( "context" "encoding/json" + "strings" "testing" "time" @@ -286,23 +287,53 @@ func Test_Federation(t *testing.T) { } } - // Simulate request from the backend that somebody joined the call. + // Simulate request from the backend that a federated user joined the call. users := []map[string]interface{}{ { "sessionId": remoteSessionId, "inCall": 1, + "actorId": "remoteUser@" + strings.TrimPrefix(server2.URL, "http://"), + "actorType": "federated_users", }, } room := hub1.getRoom(roomId) require.NotNil(room) room.PublishUsersInCallChanged(users, users) var event *EventServerMessage + // For the local user, it's a federated user on server 2 that joined. assert.NoError(checkReceiveClientEvent(ctx, client1, "update", &event)) assert.Equal(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) - + // For the federated user, it's a local user that joined. assert.NoError(checkReceiveClientEvent(ctx, client2, "update", &event)) assert.Equal(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) + + // Simulate request from the backend that a local user joined the call. + users = []map[string]interface{}{ + { + "sessionId": hello1.Hello.SessionId, + "inCall": 1, + "actorId": "localUser", + "actorType": "users", + }, + } + room.PublishUsersInCallChanged(users, users) + // For the local user, it's a local user that joined. + assert.NoError(checkReceiveClientEvent(ctx, client1, "update", &event)) + assert.Equal(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) + // For the federated user, it's a federated user on server 1 that joined. + assert.NoError(checkReceiveClientEvent(ctx, client2, "update", &event)) + assert.Equal(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) // Joining another "direct" session will trigger correct events.