From a5838eed1debb001b815b503ac0fa02b2cad2278 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Mon, 19 Jan 2026 12:11:25 +0100 Subject: [PATCH 1/4] feat(signaling): Don't send all room userids in signaling server requests Signed-off-by: Joachim Bauch --- lib/Signaling/BackendNotifier.php | 47 ++++++------------- lib/Signaling/Listener.php | 5 +- .../features/callapi/recording.feature | 4 +- .../callapi/update-call-flags.feature | 4 +- .../features/chat-1/group-read-only.feature | 4 +- .../features/command/user-remove.feature | 8 ++-- .../conversation-1/add-participant.feature | 2 +- .../conversation-1/breakout-rooms.feature | 16 +++---- .../conversation-3/join-leave.feature | 6 +-- .../features/conversation-3/lobby.feature | 4 +- .../conversation-3/public-private.feature | 4 +- .../conversation-4/promotion-demotion.feature | 3 +- .../conversation-4/remove-participant.feature | 2 +- .../conversation-4/rename-room.feature | 2 +- .../conversation-5/delete-room.feature | 2 +- .../conversation-5/set-description.feature | 2 +- .../conversation-5/set-listable.feature | 4 +- .../conversation-5/set-password.feature | 2 +- tests/php/Signaling/ListenerTest.php | 6 +-- 19 files changed, 50 insertions(+), 77 deletions(-) diff --git a/lib/Signaling/BackendNotifier.php b/lib/Signaling/BackendNotifier.php index 181580b426b..e04ebea0741 100644 --- a/lib/Signaling/BackendNotifier.php +++ b/lib/Signaling/BackendNotifier.php @@ -151,14 +151,14 @@ public function roomInvited(Room $room, array $attendees): void { $userIds[] = $attendee->getActorId(); } } + if (empty($userIds)) { + return; + } $start = microtime(true); $this->backendRequest($room, [ 'type' => 'invite', 'invite' => [ 'userids' => $userIds, - // TODO(fancycode): We should try to get rid of 'alluserids' and - // find a better way to notify existing users to update the room. - 'alluserids' => $this->participantService->getParticipantUserIdsAndFederatedUserCloudIds($room), 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, '', false), ], ]); @@ -179,22 +179,20 @@ public function roomInvited(Room $room, array $attendees): void { * @throws \Exception */ public function roomsDisinvited(Room $room, array $attendees): void { - $allUserIds = $this->participantService->getParticipantUserIdsAndFederatedUserCloudIds($room); - sort($allUserIds); $userIds = []; foreach ($attendees as $attendee) { if ($attendee->getActorType() === Attendee::ACTOR_USERS) { $userIds[] = $attendee->getActorId(); } } + if (empty($userIds)) { + return; + } $start = microtime(true); $this->backendRequest($room, [ 'type' => 'disinvite', 'disinvite' => [ 'userids' => $userIds, - // TODO(fancycode): We should try to get rid of 'alluserids' and - // find a better way to notify existing users to update the room. - 'alluserids' => $allUserIds, 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, '', false), ], ]); @@ -215,16 +213,11 @@ public function roomsDisinvited(Room $room, array $attendees): void { * @throws \Exception */ public function roomSessionsRemoved(Room $room, array $sessionIds): void { - $allUserIds = $this->participantService->getParticipantUserIdsAndFederatedUserCloudIds($room); - sort($allUserIds); $start = microtime(true); $this->backendRequest($room, [ 'type' => 'disinvite', 'disinvite' => [ 'sessionids' => $sessionIds, - // TODO(fancycode): We should try to get rid of 'alluserids' and - // find a better way to notify existing users to update the room. - 'alluserids' => $allUserIds, 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, '', false), ], ]); @@ -248,10 +241,6 @@ public function roomModified(Room $room): void { $this->backendRequest($room, [ 'type' => 'update', 'update' => [ - // Message not sent for federated users, as they will receive - // the message from their federated Nextcloud server once the - // property change is propagated. - 'userids' => $this->participantService->getParticipantUserIds($room), 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, ''), ], ]); @@ -267,16 +256,13 @@ public function roomModified(Room $room): void { * The given room has been deleted. * * @param Room $room - * @param string[] $userIds * @throws \Exception */ - public function roomDeleted(Room $room, array $userIds): void { + public function roomDeleted(Room $room): void { $start = microtime(true); $this->backendRequest($room, [ 'type' => 'delete', - 'delete' => [ - 'userids' => $userIds, - ], + 'delete' => new \stdClass(), ]); $duration = microtime(true) - $start; $this->logger->debug('Room deleted: {token} ({duration})', [ @@ -322,7 +308,6 @@ public function switchToRoom(Room $room, string $switchToRoomToken, array $sessi */ public function participantsModified(Room $room, array $sessionIds): void { $changed = []; - $users = []; $participants = $this->participantService->getSessionsAndParticipantsForRoom($room); foreach ($participants as $participant) { $attendee = $participant->getAttendee(); @@ -353,7 +338,6 @@ public function participantsModified(Room $room, array $sessionIds): void { $data['lastPing'] = $session->getLastPing(); $data['sessionId'] = $session->getSessionId(); $data['participantPermissions'] = $participant->getPermissions(); - $users[] = $data; if (\in_array($session->getSessionId(), $sessionIds, true)) { $data['permissions'] = []; @@ -371,17 +355,17 @@ public function participantsModified(Room $room, array $sessionIds): void { } $changed[] = $data; } - } else { - $users[] = $data; } } + if (empty($changed)) { + return; + } $start = microtime(true); $this->backendRequest($room, [ 'type' => 'participants', 'participants' => [ 'changed' => $changed, - 'users' => $users ], ]); $duration = microtime(true) - $start; @@ -410,7 +394,6 @@ public function roomInCallChanged(Room $room, int $flags, array $sessionIds, boo ]; } else { $changed = []; - $users = []; $participants = $this->participantService->getParticipantsForAllSessions($room); foreach ($participants as $participant) { @@ -441,19 +424,17 @@ public function roomInCallChanged(Room $room, int $flags, array $sessionIds, boo $data['userId'] = $attendee->getActorId(); } - if ($session->getInCall() !== Participant::FLAG_DISCONNECTED) { - $users[] = $data; - } - if (\in_array($session->getSessionId(), $sessionIds, true)) { $changed[] = $data; } } + if (empty($changed)) { + return; + } $data = [ 'incall' => $flags, 'changed' => $changed, - 'users' => $users, ]; } diff --git a/lib/Signaling/Listener.php b/lib/Signaling/Listener.php index f4f552eee61..164735cf8bb 100644 --- a/lib/Signaling/Listener.php +++ b/lib/Signaling/Listener.php @@ -264,7 +264,7 @@ protected function notifyCallEndedForEveryone(CallEndedForEveryoneEvent $event): protected function notifyBeforeRoomDeleted(BeforeRoomDeletedEvent $event): void { $room = $event->getRoom(); - $this->externalSignaling->roomDeleted($room, $this->participantService->getParticipantUserIds($room)); + $this->externalSignaling->roomDeleted($room); } protected function notifyGuestsCleanedUp(GuestsCleanedUpEvent $event): void { @@ -311,9 +311,6 @@ protected function notifyParticipantNameModified(AParticipantModifiedEvent $even protected function notifyParticipantTypeOrPermissionsModified(AParticipantModifiedEvent $event): void { $sessionIds = []; - // If the participant is not active in the room the "participants" - // request will be sent anyway, although with an empty "changed" - // property. $sessions = $this->sessionService->getAllSessionsForAttendee($event->getParticipant()->getAttendee()); foreach ($sessions as $session) { $sessionIds[] = $session->getSessionId(); diff --git a/tests/integration/features/callapi/recording.feature b/tests/integration/features/callapi/recording.feature index 75e9c2c8374..d97fa1dce69 100644 --- a/tests/integration/features/callapi/recording.feature +++ b/tests/integration/features/callapi/recording.feature @@ -18,7 +18,7 @@ Feature: callapi/recording Then signaling server received the following requests | token | data | | room1 | {"type":"message","message":{"data":{"type":"recording","recording":{"status":3}}}} | - | room1 | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":{"date":"ACTIVE_SINCE()","timezone_type":3,"timezone":"UTC"},"sip-enabled":0,"description":""}}} | + | room1 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":{"date":"ACTIVE_SINCE()","timezone_type":3,"timezone":"UTC"},"sip-enabled":0,"description":""}}} | And user "participant1" is participant of the following unordered rooms (v4) | type | name | callRecording | | 2 | room1 | 3 | @@ -47,7 +47,7 @@ Feature: callapi/recording | token | data | | room1 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true,"comment":[]}}}} | | room1 | {"type":"message","message":{"data":{"type":"recording","recording":{"status":0}}}} | - | room1 | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":{"date":"ACTIVE_SINCE()","timezone_type":3,"timezone":"UTC"},"sip-enabled":0,"description":""}}} | + | room1 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":{"date":"ACTIVE_SINCE()","timezone_type":3,"timezone":"UTC"},"sip-enabled":0,"description":""}}} | Then user "participant1" sees the following system messages in room "room1" with 200 (v1) | room | actorType | actorId | actorDisplayName | systemMessage | | room1 | users | participant1 | participant1-displayname | recording_stopped | diff --git a/tests/integration/features/callapi/update-call-flags.feature b/tests/integration/features/callapi/update-call-flags.feature index 0a5f24337bf..8b4294284a1 100644 --- a/tests/integration/features/callapi/update-call-flags.feature +++ b/tests/integration/features/callapi/update-call-flags.feature @@ -26,12 +26,12 @@ Feature: callapi/update-call-flags Then signaling server received the following requests | token | data | | public room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true,"comment":[]}}}} | - | public room | {"type":"incall","incall":{"incall":7,"changed":[{"inCall":7,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":510,"actorType":"users","actorId":"owner","userId":"owner"}],"users":[{"inCall":7,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":510,"actorType":"users","actorId":"owner","userId":"owner"}]}} | + | public room | {"type":"incall","incall":{"incall":7,"changed":[{"inCall":7,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":510,"actorType":"users","actorId":"owner","userId":"owner"}]}} | And reset signaling server requests When user "owner" updates call flags in room "public room" to "1" with 200 (v4) Then signaling server received the following requests | token | data | - | public room | {"type":"incall","incall":{"incall":1,"changed":[{"inCall":1,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":510,"actorType":"users","actorId":"owner","userId":"owner"}],"users":[{"inCall":1,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":510,"actorType":"users","actorId":"owner","userId":"owner"}]}} | + | public room | {"type":"incall","incall":{"incall":1,"changed":[{"inCall":1,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":510,"actorType":"users","actorId":"owner","userId":"owner"}]}} | And user "moderator" joins call "public room" with 200 (v4) And user "invited user" joins call "public room" with 200 (v4) And user "not invited but joined user" joins call "public room" with 200 (v4) diff --git a/tests/integration/features/chat-1/group-read-only.feature b/tests/integration/features/chat-1/group-read-only.feature index 0278e5869cb..86e63890083 100644 --- a/tests/integration/features/chat-1/group-read-only.feature +++ b/tests/integration/features/chat-1/group-read-only.feature @@ -19,7 +19,7 @@ Feature: chat-1/group-read-only Then signaling server received the following requests | token | data | | group room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | group room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":1,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | group room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":1,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | When user "participant1" sends message "Message 2" to room "group room" with 403 Then user "participant1" sees the following messages in room "group room" with 200 | room | actorType | actorId | actorDisplayName | message | messageParameters | @@ -29,7 +29,7 @@ Feature: chat-1/group-read-only Then signaling server received the following requests | token | data | | group room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | group room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | group room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | When user "participant1" sends message "Message 3" to room "group room" with 201 Then user "participant1" sees the following messages in room "group room" with 200 | room | actorType | actorId | actorDisplayName | message | messageParameters | diff --git a/tests/integration/features/command/user-remove.feature b/tests/integration/features/command/user-remove.feature index e2497eb4231..e71638d53aa 100644 --- a/tests/integration/features/command/user-remove.feature +++ b/tests/integration/features/command/user-remove.feature @@ -85,14 +85,14 @@ Feature: command/user-remove And invoking occ with "talk:user:remove --user participant2" Then signaling server received the following requests | token | data | - | room | {"type":"disinvite","disinvite":{"userids":["participant2"],"alluserids":["participant1"],"properties":{"name":"Private conversation","type":1,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + | room | {"type":"disinvite","disinvite":{"userids":["participant2"],"properties":{"name":"Private conversation","type":1,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | # Type changed - | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | # Name changed - | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | # Read only changed - | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":1,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":1,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And the command output contains the text "Users successfully removed from all rooms" Then the command was successful And user "participant2" is participant of the following rooms (v4) diff --git a/tests/integration/features/conversation-1/add-participant.feature b/tests/integration/features/conversation-1/add-participant.feature index 338429dbbe2..a31b3fe69b3 100644 --- a/tests/integration/features/conversation-1/add-participant.feature +++ b/tests/integration/features/conversation-1/add-participant.feature @@ -33,7 +33,7 @@ Feature: conversation-1/add-participant And user "participant1" adds user "participant2" to room "room" with 200 (v4) Then signaling server received the following requests | token | data | - | room | {"type":"invite","invite":{"userids":["participant2"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + | room | {"type":"invite","invite":{"userids":["participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | And user "participant1" is participant of the following rooms (v4) | id | type | participantType | diff --git a/tests/integration/features/conversation-1/breakout-rooms.feature b/tests/integration/features/conversation-1/breakout-rooms.feature index ace2e182b86..c23827e2bda 100644 --- a/tests/integration/features/conversation-1/breakout-rooms.feature +++ b/tests/integration/features/conversation-1/breakout-rooms.feature @@ -70,13 +70,13 @@ Feature: conversation-1/breakout-rooms And user "participant1" starts breakout rooms in room "class room" with 200 (v1) Then signaling server received the following requests | token | data | - | Room 1 | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | - | Room 2 | {"type":"update","update":{"userids":["participant1","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | - | Room 3 | {"type":"update","update":{"userids":["participant1","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 1 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 2 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 3 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | | class room | {"type":"switchto","switchto":{"roomid":"ROOM(Room 1)","sessions":["SESSION(participant2)"]}} | | class room | {"type":"switchto","switchto":{"roomid":"ROOM(Room 2)","sessions":["SESSION(participant3)"]}} | | class room | {"type":"switchto","switchto":{"roomid":"ROOM(Room 3)","sessions":["SESSION(participant4)"]}} | - | class room | {"type":"update","update":{"userids":["participant1","participant2","participant3","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | class room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And user "participant2" leaves room "class room" with 200 (v4) And user "participant3" leaves room "class room" with 200 (v4) And user "participant4" leaves room "class room" with 200 (v4) @@ -90,13 +90,13 @@ Feature: conversation-1/breakout-rooms | Room 1 | {"type":"switchto","switchto":{"roomid":"ROOM(class room)","sessions":["SESSION(participant2)"]}} | | Room 2 | {"type":"switchto","switchto":{"roomid":"ROOM(class room)","sessions":["SESSION(participant3)"]}} | | Room 3 | {"type":"switchto","switchto":{"roomid":"ROOM(class room)","sessions":["SESSION(participant4)"]}} | - | class room | {"type":"update","update":{"userids":["participant1","participant2","participant3","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | class room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | | Room 1 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | Room 1 | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 1 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | | Room 2 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | Room 2 | {"type":"update","update":{"userids":["participant1","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 2 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | | Room 3 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | Room 3 | {"type":"update","update":{"userids":["participant1","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 3 | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Scenario: Teacher creates automatic breakout rooms Given user "participant3" exists diff --git a/tests/integration/features/conversation-3/join-leave.feature b/tests/integration/features/conversation-3/join-leave.feature index 6fb8cb0a666..081f8247b68 100644 --- a/tests/integration/features/conversation-3/join-leave.feature +++ b/tests/integration/features/conversation-3/join-leave.feature @@ -94,7 +94,7 @@ Feature: conversation-3/join-leave # Rejoining the same room disinvites the previous session Then signaling server received the following requests | token | data | - | room | {"type":"disinvite","disinvite":{"sessionids":["SESSION(participant1#old)"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + | room | {"type":"disinvite","disinvite":{"sessionids":["SESSION(participant1#old)"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | And user "participant2" joins room "room" with 200 (v4) And user "participant3" joins room "room" with 200 (v4) And user "guest" joins room "room" with 200 (v4) @@ -108,13 +108,13 @@ Feature: conversation-3/join-leave # Signaling message when a self-joined user leaves Then signaling server received the following requests | token | data | - | room | {"type":"disinvite","disinvite":{"userids":["participant3"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + | room | {"type":"disinvite","disinvite":{"userids":["participant3"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | And reset signaling server requests And user "guest" leaves room "room" with 200 (v4) # Signaling message when a guest leaves Then signaling server received the following requests | token | data | - | room | {"type":"disinvite","disinvite":{"sessionids":["SESSION(guest)"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + | room | {"type":"disinvite","disinvite":{"sessionids":["SESSION(guest)"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | Then user "participant1" is participant of room "room" (v4) And user "participant2" is participant of room "room" (v4) And user "participant3" is not participant of room "room" (v4) diff --git a/tests/integration/features/conversation-3/lobby.feature b/tests/integration/features/conversation-3/lobby.feature index 789ffa0d79c..800fb4822dc 100644 --- a/tests/integration/features/conversation-3/lobby.feature +++ b/tests/integration/features/conversation-3/lobby.feature @@ -19,13 +19,13 @@ Feature: conversation-3/lobby Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["participant1","participant2","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And reset signaling server requests And user "participant1" sets lobby state for room "room" to "no lobby" with 200 (v4) Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["participant1","participant2","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And user "participant2" sets lobby state for room "room" to "non moderators" with 200 (v4) And user "participant2" sets lobby state for room "room" to "no lobby" with 200 (v4) And user "participant3" sets lobby state for room "room" to "non moderators" with 403 (v4) diff --git a/tests/integration/features/conversation-3/public-private.feature b/tests/integration/features/conversation-3/public-private.feature index cd0aa08f1d9..39de2228c26 100644 --- a/tests/integration/features/conversation-3/public-private.feature +++ b/tests/integration/features/conversation-3/public-private.feature @@ -15,7 +15,7 @@ Feature: conversation-3/public-private Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Then user "participant1" is participant of the following rooms (v4) | id | type | participantType | | room | 2 | 1 | @@ -24,7 +24,7 @@ Feature: conversation-3/public-private Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Then user "participant1" is participant of the following rooms (v4) | id | type | participantType | | room | 3 | 1 | diff --git a/tests/integration/features/conversation-4/promotion-demotion.feature b/tests/integration/features/conversation-4/promotion-demotion.feature index 697422443d7..b7e6eb7f11f 100644 --- a/tests/integration/features/conversation-4/promotion-demotion.feature +++ b/tests/integration/features/conversation-4/promotion-demotion.feature @@ -21,8 +21,7 @@ Feature: conversation-4/promotion-demotion # TODO remove handler with "roomModified" in favour of handler with # "participantsModified" once the clients no longer expect a # "roomModified" message for participant type changes. - | room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | - | room | {"type":"participants","participants":{"changed":[],"users":[{"inCall":0,"lastPing":0,"sessionId":"0","participantType":1,"participantPermissions":1,"displayName":"participant1-displayname","actorType":"users","actorId":"participant1","userId":"participant1"},{"inCall":0,"lastPing":0,"sessionId":"0","participantType":2,"participantPermissions":1,"displayName":"participant2-displayname","actorType":"users","actorId":"participant2","userId":"participant2"}]}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And user "participant2" is participant of the following rooms (v4) | id | type | participantType | | room | 3 | 2 | diff --git a/tests/integration/features/conversation-4/remove-participant.feature b/tests/integration/features/conversation-4/remove-participant.feature index 9c208c4a130..a12e8f7ee13 100644 --- a/tests/integration/features/conversation-4/remove-participant.feature +++ b/tests/integration/features/conversation-4/remove-participant.feature @@ -103,7 +103,7 @@ Feature: conversation-4/remove-participant Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"disinvite","disinvite":{"userids":["participant1"],"alluserids":["participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + | room | {"type":"disinvite","disinvite":{"userids":["participant1"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | And user "participant1" is not participant of room "room" (v4) And user "participant2" adds user "participant3" to room "room" with 200 (v4) And user "participant2" promotes "participant3" in room "room" with 200 (v4) diff --git a/tests/integration/features/conversation-4/rename-room.feature b/tests/integration/features/conversation-4/rename-room.feature index 4b597b9e048..982085bc27c 100644 --- a/tests/integration/features/conversation-4/rename-room.feature +++ b/tests/integration/features/conversation-4/rename-room.feature @@ -25,7 +25,7 @@ Feature: conversation-4/rename-room Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Scenario: User renames Given user "participant2" exists diff --git a/tests/integration/features/conversation-5/delete-room.feature b/tests/integration/features/conversation-5/delete-room.feature index 61746a93d60..40222f7ebf5 100644 --- a/tests/integration/features/conversation-5/delete-room.feature +++ b/tests/integration/features/conversation-5/delete-room.feature @@ -18,7 +18,7 @@ Feature: conversation-5/delete-room When user "participant1" deletes room "room" with 200 (v4) Then signaling server received the following requests | token | data | - | room | {"type":"delete","delete":{"userids":["participant1"]}} | + | room | {"type":"delete","delete":{}} | Then user "participant1" is not participant of room "room" (v4) Scenario: Moderator deletes diff --git a/tests/integration/features/conversation-5/set-description.feature b/tests/integration/features/conversation-5/set-description.feature index 2069c18affa..8acf5ab665a 100644 --- a/tests/integration/features/conversation-5/set-description.feature +++ b/tests/integration/features/conversation-5/set-description.feature @@ -15,7 +15,7 @@ Feature: conversation-5/set-description Then signaling server received the following requests | token | data | | group room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | group room | {"type":"update","update":{"userids":["owner"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C"}}} | + | group room | {"type":"update","update":{"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C"}}} | Then user "owner" is participant of room "group room" (v4) | description | | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C | diff --git a/tests/integration/features/conversation-5/set-listable.feature b/tests/integration/features/conversation-5/set-listable.feature index 6b497e56cff..4431132f29a 100644 --- a/tests/integration/features/conversation-5/set-listable.feature +++ b/tests/integration/features/conversation-5/set-listable.feature @@ -39,7 +39,7 @@ Feature: conversation-5/set-listable Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["creator","moderator","regular-user"],"properties":{"name":"room","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":2,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"room","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":2,"active-since":null,"sip-enabled":0,"description":""}}} | When user "creator" promotes "moderator" in room "room" with 200 (v4) And user "user-guest@example.com" joins room "room" with 200 (v4) And user "guest" joins room "room" with 200 (v4) @@ -48,7 +48,7 @@ Feature: conversation-5/set-listable Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["creator","moderator","regular-user","user-guest@example.com"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And user "regular-user" allows listing room "room" for "users" with 403 (v4) And user "user-guest@example.com" allows listing room "room" for "users" with 403 (v4) And user "guest" allows listing room "room" for "users" with 401 (v4) diff --git a/tests/integration/features/conversation-5/set-password.feature b/tests/integration/features/conversation-5/set-password.feature index 65042280ca3..69e83e910c4 100644 --- a/tests/integration/features/conversation-5/set-password.feature +++ b/tests/integration/features/conversation-5/set-password.feature @@ -16,7 +16,7 @@ Feature: conversation-5/set-password Then signaling server received the following requests | token | data | | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | - | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"update","update":{"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Then user "participant3" joins room "room" with 403 (v4) Then user "participant3" joins room "room" with 200 (v4) | password | foobar | diff --git a/tests/php/Signaling/ListenerTest.php b/tests/php/Signaling/ListenerTest.php index 4a230eb2fbc..1591914ac1c 100644 --- a/tests/php/Signaling/ListenerTest.php +++ b/tests/php/Signaling/ListenerTest.php @@ -223,13 +223,9 @@ public function testRoomDelete(): void { $room ); - $this->participantService->method('getParticipantUserIds') - ->with($room) - ->willReturn(['user1', 'user2']); - $this->backendNotifier->expects($this->once()) ->method('roomDeleted') - ->with($room, ['user1', 'user2']); + ->with($room); $this->listener->handle($event); } From 92bbe8ba0443531f1c03a461f43809843bf9add0 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 14 Jul 2026 13:54:39 +0200 Subject: [PATCH 2/4] fix(hpb): Support HPB with and without "changed-users" feature flag Signed-off-by: Joas Schilling --- lib/ConfigLexicon.php | 2 + lib/Signaling/BackendNotifier.php | 130 +++++++++++++++++++++++++----- lib/Signaling/Manager.php | 1 + tests/integration/run.sh | 2 + 4 files changed, 113 insertions(+), 22 deletions(-) diff --git a/lib/ConfigLexicon.php b/lib/ConfigLexicon.php index 71850ad64e2..de30d2e5fbc 100644 --- a/lib/ConfigLexicon.php +++ b/lib/ConfigLexicon.php @@ -9,6 +9,7 @@ namespace OCA\Talk; use OCA\Talk\Settings\UserPreference; +use OCA\Talk\Signaling\Manager as SignalingManager; use OCP\Config\Lexicon\Entry; use OCP\Config\Lexicon\ILexicon; use OCP\Config\Lexicon\Strictness; @@ -26,6 +27,7 @@ public function getAppConfigs(): array { return [ new Entry(UserPreference::CONVERSATIONS_LIST_STYLE, ValueType::STRING, UserPreference::CONVERSATION_LIST_STYLE_TWO_LINES), new Entry(UserPreference::CHAT_STYLE, ValueType::STRING, UserPreference::CHAT_STYLE_SPLIT), + new Entry(SignalingManager::HAS_FEATURE_CHANGED_USERS, ValueType::BOOL, false), ]; } diff --git a/lib/Signaling/BackendNotifier.php b/lib/Signaling/BackendNotifier.php index e04ebea0741..621cd6902d0 100644 --- a/lib/Signaling/BackendNotifier.php +++ b/lib/Signaling/BackendNotifier.php @@ -17,6 +17,7 @@ use OCA\Talk\Participant; use OCA\Talk\Room; use OCA\Talk\Service\ParticipantService; +use OCP\AppFramework\Services\IAppConfig; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; use OCP\IURLGenerator; @@ -27,6 +28,7 @@ class BackendNotifier { public function __construct( private readonly Config $config, + private readonly IAppConfig $appConfig, private readonly LoggerInterface $logger, private readonly IClientService $clientService, private readonly ISecureRandom $secureRandom, @@ -145,23 +147,36 @@ private function backendRequest(Room $room, array $data): ?IResponse { * @throws \Exception */ public function roomInvited(Room $room, array $attendees): void { + // TODO Remove once a new HPB is required + $supportsChangedUsers = $this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS); + $userIds = []; foreach ($attendees as $attendee) { if ($attendee->getActorType() === Attendee::ACTOR_USERS) { $userIds[] = $attendee->getActorId(); } } - if (empty($userIds)) { + + if ($supportsChangedUsers && empty($userIds)) { return; } - $start = microtime(true); - $this->backendRequest($room, [ + + $data = [ 'type' => 'invite', 'invite' => [ 'userids' => $userIds, 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, '', false), ], - ]); + ]; + + if (!$supportsChangedUsers) { + $allUserIds = $this->participantService->getParticipantUserIdsAndFederatedUserCloudIds($room); + sort($allUserIds); + $data['invite']['alluserids'] = $allUserIds; + } + + $start = microtime(true); + $this->backendRequest($room, $data); $duration = microtime(true) - $start; $this->logger->debug('Now invited to {token}: {users} ({duration})', [ 'token' => $room->getToken(), @@ -179,23 +194,36 @@ public function roomInvited(Room $room, array $attendees): void { * @throws \Exception */ public function roomsDisinvited(Room $room, array $attendees): void { + // TODO Remove once a new HPB is required + $supportsChangedUsers = $this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS); + $userIds = []; foreach ($attendees as $attendee) { if ($attendee->getActorType() === Attendee::ACTOR_USERS) { $userIds[] = $attendee->getActorId(); } } - if (empty($userIds)) { + + if ($supportsChangedUsers && empty($userIds)) { return; } - $start = microtime(true); - $this->backendRequest($room, [ + + $data = [ 'type' => 'disinvite', 'disinvite' => [ 'userids' => $userIds, 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, '', false), ], - ]); + ]; + + if (!$supportsChangedUsers) { + $allUserIds = $this->participantService->getParticipantUserIdsAndFederatedUserCloudIds($room); + sort($allUserIds); + $data['disinvite']['alluserids'] = $allUserIds; + } + + $start = microtime(true); + $this->backendRequest($room, $data); $duration = microtime(true) - $start; $this->logger->debug('No longer invited to {token}: {users} ({duration})', [ 'token' => $room->getToken(), @@ -213,14 +241,25 @@ public function roomsDisinvited(Room $room, array $attendees): void { * @throws \Exception */ public function roomSessionsRemoved(Room $room, array $sessionIds): void { - $start = microtime(true); - $this->backendRequest($room, [ + // TODO Remove once a new HPB is required + $supportsChangedUsers = $this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS); + + $data = [ 'type' => 'disinvite', 'disinvite' => [ 'sessionids' => $sessionIds, 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, '', false), ], - ]); + ]; + + if (!$supportsChangedUsers) { + $allUserIds = $this->participantService->getParticipantUserIdsAndFederatedUserCloudIds($room); + sort($allUserIds); + $data['disinvite']['alluserids'] = $allUserIds; + } + + $start = microtime(true); + $this->backendRequest($room, $data); $duration = microtime(true) - $start; $this->logger->debug('Removed from {token}: {users} ({duration})', [ 'token' => $room->getToken(), @@ -237,13 +276,22 @@ public function roomSessionsRemoved(Room $room, array $sessionIds): void { * @throws \Exception */ public function roomModified(Room $room): void { - $start = microtime(true); - $this->backendRequest($room, [ + // TODO Remove once a new HPB is required + $supportsChangedUsers = $this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS); + + $data = [ 'type' => 'update', 'update' => [ 'properties' => $this->roomPropertiesHelper->getPropertiesForSignaling($room, ''), ], - ]); + ]; + + if (!$supportsChangedUsers) { + $data['update']['userids'] = $this->participantService->getParticipantUserIds($room); + } + + $start = microtime(true); + $this->backendRequest($room, $data); $duration = microtime(true) - $start; $this->logger->debug('Room modified: {token} ({duration})', [ 'token' => $room->getToken(), @@ -259,11 +307,22 @@ public function roomModified(Room $room): void { * @throws \Exception */ public function roomDeleted(Room $room): void { - $start = microtime(true); - $this->backendRequest($room, [ + // TODO Remove once a new HPB is required + $supportsChangedUsers = $this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS); + + $data = [ 'type' => 'delete', 'delete' => new \stdClass(), - ]); + ]; + + if (!$supportsChangedUsers) { + $data['delete'] = [ + 'userids' => $this->participantService->getParticipantUserIds($room), + ]; + } + + $start = microtime(true); + $this->backendRequest($room, $data); $duration = microtime(true) - $start; $this->logger->debug('Room deleted: {token} ({duration})', [ 'token' => $room->getToken(), @@ -307,7 +366,11 @@ public function switchToRoom(Room $room, string $switchToRoomToken, array $sessi * @throws \Exception */ public function participantsModified(Room $room, array $sessionIds): void { + // TODO Remove once a new HPB is required + $supportsChangedUsers = $this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS); + $changed = []; + $users = []; $participants = $this->participantService->getSessionsAndParticipantsForRoom($room); foreach ($participants as $participant) { $attendee = $participant->getAttendee(); @@ -338,6 +401,7 @@ public function participantsModified(Room $room, array $sessionIds): void { $data['lastPing'] = $session->getLastPing(); $data['sessionId'] = $session->getSessionId(); $data['participantPermissions'] = $participant->getPermissions(); + $users[] = $data; if (\in_array($session->getSessionId(), $sessionIds, true)) { $data['permissions'] = []; @@ -355,19 +419,28 @@ public function participantsModified(Room $room, array $sessionIds): void { } $changed[] = $data; } + } else { + $users[] = $data; } } - if (empty($changed)) { + if ($supportsChangedUsers && empty($changed)) { return; } - $start = microtime(true); - $this->backendRequest($room, [ + + $data = [ 'type' => 'participants', 'participants' => [ 'changed' => $changed, ], - ]); + ]; + + if (!$supportsChangedUsers) { + $data['participants']['users'] = $users; + } + + $start = microtime(true); + $this->backendRequest($room, $data); $duration = microtime(true) - $start; $this->logger->debug('Room participants modified: {token} {users} ({duration})', [ 'token' => $room->getToken(), @@ -387,6 +460,9 @@ public function participantsModified(Room $room, array $sessionIds): void { * @throws \Exception */ public function roomInCallChanged(Room $room, int $flags, array $sessionIds, bool $changeAll = false): void { + // TODO Remove once a new HPB is required + $supportsChangedUsers = $this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS); + if ($changeAll) { $data = [ 'incall' => $flags, @@ -394,6 +470,7 @@ public function roomInCallChanged(Room $room, int $flags, array $sessionIds, boo ]; } else { $changed = []; + $users = []; $participants = $this->participantService->getParticipantsForAllSessions($room); foreach ($participants as $participant) { @@ -424,18 +501,27 @@ public function roomInCallChanged(Room $room, int $flags, array $sessionIds, boo $data['userId'] = $attendee->getActorId(); } + if (!$supportsChangedUsers && $session->getInCall() !== Participant::FLAG_DISCONNECTED) { + $users[] = $data; + } + if (\in_array($session->getSessionId(), $sessionIds, true)) { $changed[] = $data; } } - if (empty($changed)) { + if ($supportsChangedUsers && empty($changed)) { return; } + $data = [ 'incall' => $flags, 'changed' => $changed, ]; + + if (!$supportsChangedUsers) { + $data['users'] = $users; + } } $start = microtime(true); diff --git a/lib/Signaling/Manager.php b/lib/Signaling/Manager.php index e845dd05712..38d723a3afc 100644 --- a/lib/Signaling/Manager.php +++ b/lib/Signaling/Manager.php @@ -24,6 +24,7 @@ class Manager { public const FEATURE_HEADER = 'X-Spreed-Signaling-Features'; + public const string HAS_FEATURE_CHANGED_USERS = 'has_feature_changed_users'; private readonly ICache $cache; diff --git a/tests/integration/run.sh b/tests/integration/run.sh index 23f041a3047..60c953ddb36 100755 --- a/tests/integration/run.sh +++ b/tests/integration/run.sh @@ -182,6 +182,8 @@ for OCC in occ_host occ_remote; do ${OCC} config:system:set hashing_default_password --value=true --type=bool # Change the default group notification level for more variate in default tests ${OCC} config:app:set spreed default_group_notification --value=2 --type=integer + # Claim that the HPB has the newest feature + ${OCC} config:app:set spreed has_feature_changed_users --value=true --type=boolean # Build skip list MAJOR_VERSION=$(${OCC} status | grep -Eo 'version: ([0-9]+).' | grep -Eo '[0-9]+') From 803fdc34b126d98432f7ad4de5464c79e8fa94e5 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Tue, 14 Jul 2026 14:17:24 +0200 Subject: [PATCH 3/4] fix(signaling): Recognize the changed-users feature on requests This allows Talk to switch the mode so the all user ids arrays are skipped from various requests to allow scaling. Signed-off-by: Joas Schilling --- lib/Signaling/BackendNotifier.php | 6 +++++ lib/Signaling/Manager.php | 39 ++++++++++++++++++++++++------- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/Signaling/BackendNotifier.php b/lib/Signaling/BackendNotifier.php index 621cd6902d0..8bf6f8aab8a 100644 --- a/lib/Signaling/BackendNotifier.php +++ b/lib/Signaling/BackendNotifier.php @@ -63,6 +63,12 @@ protected function doRequest(string $url, array $params, int $retries = 3): ?IRe throw new \RuntimeException('Signaling server needs to be updated to be compatible with this version of Talk'); } + if (!$this->appConfig->getAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS)) { + if ($this->signalingManager->hasFeature($response, 'changed-users')) { + $this->appConfig->setAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS, true); + } + } + return $response; } catch (ConnectException $e) { if ($retries > 1) { diff --git a/lib/Signaling/Manager.php b/lib/Signaling/Manager.php index 38d723a3afc..d9c39c8a9e7 100644 --- a/lib/Signaling/Manager.php +++ b/lib/Signaling/Manager.php @@ -15,6 +15,7 @@ use OCA\Talk\Service\CertificateService; use OCA\Talk\Service\RoomService; use OCP\AppFramework\Http; +use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; @@ -30,6 +31,7 @@ class Manager { public function __construct( private readonly IConfig $serverConfig, + private readonly IAppConfig $appConfig, private readonly Config $talkConfig, private readonly RoomService $roomService, private readonly ITimeFactory $timeFactory, @@ -110,7 +112,8 @@ public function checkServerCompatibility(int $serverId): array { ]; } - if (!$this->isCompatibleSignalingServer($response)) { + $features = $this->getFeatureArray($response); + if (!$this->hasRequiredFeatures($features)) { return [ 'status' => Http::STATUS_INTERNAL_SERVER_ERROR, 'data' => [ @@ -131,7 +134,7 @@ public function checkServerCompatibility(int $serverId): array { ]; } - $missingFeatures = $this->getSignalingServerMissingFeatures($response); + $missingFeatures = $this->getSignalingServerMissingFeatures($features); if (!empty($missingFeatures)) { return [ 'status' => Http::STATUS_OK, @@ -143,6 +146,14 @@ public function checkServerCompatibility(int $serverId): array { ]; } + /** + * Store appconfig for some optional features that require different + * behaviour on PHP backend side + */ + if (in_array('changed-users', $features, true)) { + $this->appConfig->setAppValueBool(Manager::HAS_FEATURE_CHANGED_USERS, true); + } + return [ 'status' => Http::STATUS_OK, 'data' => [ @@ -167,10 +178,23 @@ public function checkServerCompatibility(int $serverId): array { } - public function isCompatibleSignalingServer(IResponse $response): bool { + protected function getFeatureArray(IResponse $response): array { $featureHeader = $response->getHeader(self::FEATURE_HEADER); $features = explode(',', $featureHeader); - $features = array_map(trim(...), $features); + return array_map(trim(...), $features); + } + + public function isCompatibleSignalingServer(IResponse $response): bool { + $features = $this->getFeatureArray($response); + return $this->hasRequiredFeatures($features); + } + + public function hasFeature(IResponse $response, string $feature): bool { + $features = $this->getFeatureArray($response); + return in_array($feature, $features, true); + } + + protected function hasRequiredFeatures(array $features): bool { return in_array('audio-video-permissions', $features, true) && in_array('federation', $features, true) && in_array('incall-all', $features, true) @@ -181,15 +205,12 @@ public function isCompatibleSignalingServer(IResponse $response): bool { /** * @return list */ - public function getSignalingServerMissingFeatures(IResponse $response): array { - $featureHeader = $response->getHeader(self::FEATURE_HEADER); - $features = explode(',', $featureHeader); - $features = array_map(trim(...), $features); - + protected function getSignalingServerMissingFeatures(array $features): array { $optionFeatures = [ 'dialout', 'join-features', 'chat-relay', + 'changed-users', ]; return array_values(array_diff($optionFeatures, $features)); From 7e950d8d27a3d2cbd56c468e07f28807b02be8d8 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 15 Jul 2026 15:28:21 +0200 Subject: [PATCH 4/4] fix(signaling): Fix linting on PHP 8.2 Signed-off-by: Joas Schilling --- lib/Signaling/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Signaling/Manager.php b/lib/Signaling/Manager.php index d9c39c8a9e7..139709e12bb 100644 --- a/lib/Signaling/Manager.php +++ b/lib/Signaling/Manager.php @@ -25,7 +25,7 @@ class Manager { public const FEATURE_HEADER = 'X-Spreed-Signaling-Features'; - public const string HAS_FEATURE_CHANGED_USERS = 'has_feature_changed_users'; + public const HAS_FEATURE_CHANGED_USERS = 'has_feature_changed_users'; private readonly ICache $cache;