From 1fda7a31e288bf7abc2b30f634ae736feae72715 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 21 Aug 2024 17:21:02 +0200 Subject: [PATCH 1/2] fix(federation): Sync room properties on join Signed-off-by: Joas Schilling --- docs/events.md | 11 ++ lib/AppInfo/Application.php | 4 + lib/Controller/RoomController.php | 14 +- lib/Events/ARoomModifiedEvent.php | 2 +- lib/Events/ARoomSyncedEvent.php | 13 ++ lib/Events/BeforeRoomSyncedEvent.php | 12 ++ lib/Events/RoomSyncedEvent.php | 30 ++++ .../CloudFederationProviderTalk.php | 8 + .../TalkV1/Controller/RoomController.php | 7 +- .../TalkV1/Notifier/RoomModifiedListener.php | 8 + lib/Recording/Listener.php | 5 + lib/Service/RoomService.php | 160 +++++++++++++++++- lib/Signaling/Listener.php | 73 +++++--- tests/php/Service/RoomServiceTest.php | 8 +- tests/php/Signaling/ListenerTest.php | 4 +- 15 files changed, 330 insertions(+), 29 deletions(-) create mode 100644 lib/Events/ARoomSyncedEvent.php create mode 100644 lib/Events/BeforeRoomSyncedEvent.php create mode 100644 lib/Events/RoomSyncedEvent.php diff --git a/docs/events.md b/docs/events.md index e09e956b1ea..f69b75630f7 100644 --- a/docs/events.md +++ b/docs/events.md @@ -34,6 +34,17 @@ See the general [Nextcloud Developers - Events](https://docs.nextcloud.com/serve * After event: `OCA\Talk\Events\LobbyModifiedEvent` * Since: 18.0.0 +### Federated conversation synced + +When multiple properties of a federated conversation are synced, the individual +"Conversation modified" and "Lobby modified" events are still triggered, but a +listener could decide to not follow up individual but only after all properties +where modified. + +* Before event: `OCA\Talk\Events\BeforeRoomSyncedEvent` +* After event: `OCA\Talk\Events\RoomSyncedEvent` +* Since: 20.0.0 + ### Call started * Before event: `OCA\Talk\Events\BeforeCallStartedEvent` diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index f145a74053b..6d6b295fe0f 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -41,6 +41,7 @@ use OCA\Talk\Events\BeforeParticipantModifiedEvent; use OCA\Talk\Events\BeforeRoomDeletedEvent; use OCA\Talk\Events\BeforeRoomsFetchEvent; +use OCA\Talk\Events\BeforeRoomSyncedEvent; use OCA\Talk\Events\BeforeSessionLeftRoomEvent; use OCA\Talk\Events\BeforeUserJoinedRoomEvent; use OCA\Talk\Events\BotDisabledEvent; @@ -61,6 +62,7 @@ use OCA\Talk\Events\RoomCreatedEvent; use OCA\Talk\Events\RoomDeletedEvent; use OCA\Talk\Events\RoomModifiedEvent; +use OCA\Talk\Events\RoomSyncedEvent; use OCA\Talk\Events\SessionLeftRoomEvent; use OCA\Talk\Events\SystemMessageSentEvent; use OCA\Talk\Events\SystemMessagesMultipleSentEvent; @@ -286,6 +288,8 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(CallEndedForEveryoneEvent::class, SignalingListener::class); $context->registerEventListener(GuestsCleanedUpEvent::class, SignalingListener::class); $context->registerEventListener(LobbyModifiedEvent::class, SignalingListener::class); + $context->registerEventListener(BeforeRoomSyncedEvent::class, SignalingListener::class); + $context->registerEventListener(RoomSyncedEvent::class, SignalingListener::class); $context->registerEventListener(ChatMessageSentEvent::class, SignalingListener::class); $context->registerEventListener(SystemMessageSentEvent::class, SignalingListener::class); diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index f44371f8ccb..0f20d346946 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -1660,6 +1660,10 @@ public function joinRoom(string $token, string $password = '', bool $force = tru return new DataResponse([], Http::STATUS_NOT_FOUND); } + /** @var TalkRoom $data */ + $data = $response->getData(); + $this->roomService->syncPropertiesFromHostRoom($room, $data); + $proxyHeaders = $response->getHeaders(); if (isset($proxyHeaders['X-Nextcloud-Talk-Proxy-Hash'])) { $headers['X-Nextcloud-Talk-Proxy-Hash'] = $proxyHeaders['X-Nextcloud-Talk-Proxy-Hash']; @@ -1675,8 +1679,8 @@ public function joinRoom(string $token, string $password = '', bool $force = tru * The session id can be null only for requests from Talk < 20. * * @param string $token Token of the room - * @param string $sessionId Federated session id to join with - * @return DataResponse, array{X-Nextcloud-Talk-Hash: string}>|DataResponse + * @param ?string $sessionId Federated session id to join with + * @return DataResponse|DataResponse * * 200: Federated user joined the room * 404: Room not found @@ -1711,14 +1715,16 @@ public function joinFederatedRoom(string $token, ?string $sessionId): DataRespon if ($session instanceof Session) { $this->sessionService->updateLastPing($session, $this->timeFactory->getTime()); } + } else { + $participant = $this->participantService->getParticipantByActor($room, Attendee::ACTOR_FEDERATED_USERS, $this->federationAuthenticator->getCloudId()); } // Let the clients know if they need to reload capabilities $capabilities = $this->capabilities->getCapabilities(); - return new DataResponse([], Http::STATUS_OK, [ + return new DataResponse($this->formatRoom($room, $participant), Http::STATUS_OK, [ 'X-Nextcloud-Talk-Hash' => sha1(json_encode($capabilities)), ]); - } catch (RoomNotFoundException|UnauthorizedException) { + } catch (RoomNotFoundException|ParticipantNotFoundException|UnauthorizedException) { $response = new DataResponse(null, Http::STATUS_NOT_FOUND); $response->throttle(['token' => $token, 'action' => 'talkFederationAccess']); return $response; diff --git a/lib/Events/ARoomModifiedEvent.php b/lib/Events/ARoomModifiedEvent.php index 4488e6e868c..dd20d45e266 100644 --- a/lib/Events/ARoomModifiedEvent.php +++ b/lib/Events/ARoomModifiedEvent.php @@ -24,13 +24,13 @@ abstract class ARoomModifiedEvent extends ARoomEvent { public const PROPERTY_LISTABLE = 'listable'; public const PROPERTY_LOBBY = 'lobby'; public const PROPERTY_MESSAGE_EXPIRATION = 'messageExpiration'; + public const PROPERTY_MENTION_PERMISSIONS = 'mentionPermissions'; public const PROPERTY_NAME = 'name'; public const PROPERTY_PASSWORD = 'password'; public const PROPERTY_READ_ONLY = 'readOnly'; public const PROPERTY_RECORDING_CONSENT = 'recordingConsent'; public const PROPERTY_SIP_ENABLED = 'sipEnabled'; public const PROPERTY_TYPE = 'type'; - public const PROPERTY_MENTION_PERMISSIONS = 'mentionPermissions'; /** * @param self::PROPERTY_* $property diff --git a/lib/Events/ARoomSyncedEvent.php b/lib/Events/ARoomSyncedEvent.php new file mode 100644 index 00000000000..97cd3a6b56e --- /dev/null +++ b/lib/Events/ARoomSyncedEvent.php @@ -0,0 +1,13 @@ + $properties + */ + public function __construct( + Room $room, + protected array $properties, + ) { + parent::__construct($room); + } + + /** + * @return array + */ + public function getProperties(): array { + return $this->properties; + } +} diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php index aa845ec9759..49d65b65823 100644 --- a/lib/Federation/CloudFederationProviderTalk.php +++ b/lib/Federation/CloudFederationProviderTalk.php @@ -322,6 +322,8 @@ private function roomModified(int $remoteAttendeeId, array $notification): array } } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_AVATAR) { $this->roomService->setAvatar($room, $notification['newValue']); + } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_CALL_RECORDING) { + $this->roomService->setCallRecording($room, $notification['newValue']); } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_DESCRIPTION) { $this->roomService->setDescription($room, $notification['newValue']); } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_IN_CALL) { @@ -329,6 +331,10 @@ private function roomModified(int $remoteAttendeeId, array $notification): array } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_LOBBY) { $dateTime = !empty($notification['dateTime']) ? \DateTime::createFromFormat('U', $notification['dateTime']) : null; $this->roomService->setLobby($room, $notification['newValue'], $dateTime, $notification['timerReached'] ?? false); + } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_MENTION_PERMISSIONS) { + $this->roomService->setMentionPermissions($room, $notification['newValue']); + } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_MESSAGE_EXPIRATION) { + $this->roomService->setMessageExpiration($room, $notification['newValue']); } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_NAME) { $this->roomService->setName($room, $notification['newValue'], $notification['oldValue']); } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_READ_ONLY) { @@ -336,6 +342,8 @@ private function roomModified(int $remoteAttendeeId, array $notification): array } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_RECORDING_CONSENT) { /** @psalm-suppress InvalidArgument */ $this->roomService->setRecordingConsent($room, $notification['newValue']); + } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_SIP_ENABLED) { + $this->roomService->setSIPEnabled($room, $notification['newValue']); } elseif ($notification['changedProperty'] === ARoomModifiedEvent::PROPERTY_TYPE) { $this->roomService->setType($room, $notification['newValue']); } else { diff --git a/lib/Federation/Proxy/TalkV1/Controller/RoomController.php b/lib/Federation/Proxy/TalkV1/Controller/RoomController.php index 963e3eb2da2..651719f7aac 100644 --- a/lib/Federation/Proxy/TalkV1/Controller/RoomController.php +++ b/lib/Federation/Proxy/TalkV1/Controller/RoomController.php @@ -91,7 +91,12 @@ public function joinFederatedRoom(Room $room, Participant $participant): DataRes $headers = ['X-Nextcloud-Talk-Proxy-Hash' => $this->proxy->overwrittenRemoteTalkHash($proxy->getHeader('X-Nextcloud-Talk-Hash'))]; - return new DataResponse([], $statusCode, $headers); + /** @var TalkRoom[] $data */ + $data = $this->proxy->getOCSData($proxy); + + $data = $this->userConverter->convertAttendee($room, $data, 'actorType', 'actorId', 'displayName'); + + return new DataResponse($data, $statusCode, $headers); } /** diff --git a/lib/Federation/Proxy/TalkV1/Notifier/RoomModifiedListener.php b/lib/Federation/Proxy/TalkV1/Notifier/RoomModifiedListener.php index 90d7dc5024f..a81fedd34f9 100644 --- a/lib/Federation/Proxy/TalkV1/Notifier/RoomModifiedListener.php +++ b/lib/Federation/Proxy/TalkV1/Notifier/RoomModifiedListener.php @@ -49,17 +49,25 @@ public function handle(Event $event): void { if (!in_array($event->getProperty(), [ ARoomModifiedEvent::PROPERTY_ACTIVE_SINCE, ARoomModifiedEvent::PROPERTY_AVATAR, + ARoomModifiedEvent::PROPERTY_CALL_RECORDING, ARoomModifiedEvent::PROPERTY_DESCRIPTION, ARoomModifiedEvent::PROPERTY_IN_CALL, ARoomModifiedEvent::PROPERTY_LOBBY, + ARoomModifiedEvent::PROPERTY_MENTION_PERMISSIONS, + ARoomModifiedEvent::PROPERTY_MESSAGE_EXPIRATION, ARoomModifiedEvent::PROPERTY_NAME, ARoomModifiedEvent::PROPERTY_READ_ONLY, ARoomModifiedEvent::PROPERTY_RECORDING_CONSENT, + ARoomModifiedEvent::PROPERTY_SIP_ENABLED, ARoomModifiedEvent::PROPERTY_TYPE, ], true)) { return; } + if ($event->getRoom()->isFederatedConversation()) { + return; + } + $participants = $this->participantService->getParticipantsByActorType($event->getRoom(), Attendee::ACTOR_FEDERATED_USERS); foreach ($participants as $participant) { $cloudId = $this->cloudIdManager->resolveCloudId($participant->getAttendee()->getActorId()); diff --git a/lib/Recording/Listener.php b/lib/Recording/Listener.php index 94c8037f3a1..75650197ce1 100644 --- a/lib/Recording/Listener.php +++ b/lib/Recording/Listener.php @@ -11,6 +11,7 @@ use OCA\Talk\AppInfo\Application; use OCA\Talk\Events\ACallEndedEvent; +use OCA\Talk\Events\ARoomEvent; use OCA\Talk\Events\CallEndedEvent; use OCA\Talk\Events\CallEndedForEveryoneEvent; use OCA\Talk\Events\RoomDeletedEvent; @@ -42,6 +43,10 @@ public function handle(Event $event): void { return; } + if ($event instanceof ARoomEvent && $event->getRoom()->isFederatedConversation()) { + return; + } + match (get_class($event)) { RoomDeletedEvent::class => $this->roomDeleted($event), CallEndedEvent::class, diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index dde9ca761c7..1ecbd529bec 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -12,22 +12,26 @@ use OCA\Talk\Config; use OCA\Talk\Events\AParticipantModifiedEvent; use OCA\Talk\Events\ARoomModifiedEvent; +use OCA\Talk\Events\ARoomSyncedEvent; use OCA\Talk\Events\BeforeCallEndedEvent; use OCA\Talk\Events\BeforeCallStartedEvent; use OCA\Talk\Events\BeforeLobbyModifiedEvent; use OCA\Talk\Events\BeforeRoomDeletedEvent; use OCA\Talk\Events\BeforeRoomModifiedEvent; +use OCA\Talk\Events\BeforeRoomSyncedEvent; use OCA\Talk\Events\CallEndedEvent; use OCA\Talk\Events\CallStartedEvent; use OCA\Talk\Events\LobbyModifiedEvent; use OCA\Talk\Events\RoomDeletedEvent; use OCA\Talk\Events\RoomModifiedEvent; use OCA\Talk\Events\RoomPasswordVerifyEvent; +use OCA\Talk\Events\RoomSyncedEvent; use OCA\Talk\Exceptions\RoomNotFoundException; use OCA\Talk\Manager; use OCA\Talk\Model\Attendee; use OCA\Talk\Model\BreakoutRoom; use OCA\Talk\Participant; +use OCA\Talk\ResponseDefinitions; use OCA\Talk\Room; use OCA\Talk\Webinary; use OCP\AppFramework\Utility\ITimeFactory; @@ -42,7 +46,11 @@ use OCP\Security\Events\ValidatePasswordPolicyEvent; use OCP\Security\IHasher; use OCP\Share\IManager as IShareManager; +use Psr\Log\LoggerInterface; +/** + * @psalm-import-type TalkRoom from ResponseDefinitions + */ class RoomService { public function __construct( @@ -55,6 +63,7 @@ public function __construct( protected IHasher $hasher, protected IEventDispatcher $dispatcher, protected IJobList $jobList, + protected LoggerInterface $logger, ) { } @@ -435,7 +444,8 @@ public function setAvatar(Room $room, string $avatar): bool { * @throws InvalidArgumentException When trying to start */ public function setCallRecording(Room $room, int $status = Room::RECORDING_NONE, ?Participant $participant = null): void { - if (!$this->config->isRecordingEnabled() && $status !== Room::RECORDING_NONE) { + $syncFederatedRoom = $room->getRemoteServer() && $room->getRemoteToken(); + if (!$syncFederatedRoom && !$this->config->isRecordingEnabled() && $status !== Room::RECORDING_NONE) { throw new InvalidArgumentException('config'); } @@ -999,6 +1009,154 @@ public function setLastActivity(Room $room, \DateTime $now): void { $room->setLastActivity($now); } + /** + * @psalm-param TalkRoom $host + */ + public function syncPropertiesFromHostRoom(Room $local, array $host): void { + $event = new BeforeRoomSyncedEvent($local); + $this->dispatcher->dispatchTyped($event); + + /** @var array $changed */ + $changed = []; + if (isset($host['type']) && $host['type'] !== $local->getType()) { + $success = $this->setType($local, $host['type']); + if (!$success) { + $this->logger->error('An error occurred while trying to sync type of ' . $local->getId() . ' to ' . $host['type']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_TYPE; + } + } + if (isset($host['name']) && $host['name'] !== $local->getName()) { + $success = $this->setName($local, $host['name'], $local->getName()); + if (!$success) { + $this->logger->error('An error occurred while trying to sync name of ' . $local->getId() . ' to ' . $host['name']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_NAME; + } + } + if (isset($host['description']) && $host['description'] !== $local->getDescription()) { + try { + $success = $this->setDescription($local, $host['description']); + if (!$success) { + $this->logger->error('An error occurred while trying to sync description of ' . $local->getId() . ' to ' . $host['description']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_DESCRIPTION; + } + } catch (\LengthException $e) { + $this->logger->error('A \LengthException occurred while trying to sync description of ' . $local->getId() . ' to ' . $host['description'], ['exception' => $e]); + } + } + if (isset($host['callRecording']) && $host['callRecording'] !== $local->getCallRecording()) { + try { + $this->setCallRecording($local, $host['callRecording']); + $changed[] = ARoomModifiedEvent::PROPERTY_CALL_RECORDING; + } catch (\InvalidArgumentException $e) { + $this->logger->error('An error (' . $e->getMessage() . ') occurred while trying to sync callRecording of ' . $local->getId() . ' to ' . $host['callRecording'], ['exception' => $e]); + } + } + if (isset($host['defaultPermissions']) && $host['defaultPermissions'] !== $local->getDefaultPermissions()) { + $success = $this->setPermissions($local, 'default', Attendee::PERMISSIONS_MODIFY_SET, $host['defaultPermissions'], false); + if (!$success) { + $this->logger->error('An error occurred while trying to sync defaultPermissions of ' . $local->getId() . ' to ' . $host['defaultPermissions']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS; + } + } + if (isset($host['avatarVersion']) && $host['avatarVersion'] !== $local->getAvatar()) { + $hostAvatar = $host['avatarVersion']; + if ($hostAvatar) { + // Add a fake suffix as we explode by the dot in the AvatarService, but the version doesn't have one. + $hostAvatar .= '.fed'; + } + $success = $this->setAvatar($local, $hostAvatar); + if (!$success) { + $this->logger->error('An error occurred while trying to sync avatarVersion of ' . $local->getId() . ' to ' . $host['avatarVersion']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_AVATAR; + } + } + if (isset($host['lastActivity']) && $host['lastActivity'] !== 0 && $host['lastActivity'] !== ((int) $local->getLastActivity()?->getTimestamp())) { + $lastActivity = $this->timeFactory->getDateTime('@' . $host['lastActivity']); + $this->setLastActivity($local, $lastActivity); + $changed[] = ARoomSyncedEvent::PROPERTY_LAST_ACTIVITY; + } + if (isset($host['lobbyState'], $host['lobbyTimer']) && ($host['lobbyState'] !== $local->getLobbyState(false) || $host['lobbyTimer'] !== ((int) $local->getLobbyTimer(false)?->getTimestamp()))) { + $hostTimer = $host['lobbyTimer'] === 0 ? null : $this->timeFactory->getDateTime('@' . $host['lobbyTimer']); + $success = $this->setLobby($local, $host['lobbyState'], $hostTimer); + if (!$success) { + $this->logger->error('An error occurred while trying to sync lobby of ' . $local->getId() . ' to ' . $host['lobbyState'] . ' with timer to ' . $host['lobbyTimer']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_LOBBY; + } + } + if (isset($host['callStartTime'], $host['callFlag'])) { + $localCallStartTime = (int) $local->getActiveSince()?->getTimestamp(); + if ($host['callStartTime'] === 0 && ($host['callStartTime'] !== $localCallStartTime || $host['callFlag'] !== $local->getCallFlag())) { + $this->resetActiveSince($local, null); + $changed[] = ARoomModifiedEvent::PROPERTY_ACTIVE_SINCE; + $changed[] = ARoomModifiedEvent::PROPERTY_IN_CALL; + } elseif ($host['callStartTime'] !== 0 && ($host['callStartTime'] !== $localCallStartTime || $host['callFlag'] !== $local->getCallFlag())) { + $startDateTime = $this->timeFactory->getDateTime('@' . $host['callStartTime']); + $this->setActiveSince($local, null, $startDateTime, $host['callFlag'], true); + $changed[] = ARoomModifiedEvent::PROPERTY_ACTIVE_SINCE; + $changed[] = ARoomModifiedEvent::PROPERTY_IN_CALL; + } + } + if (isset($host['mentionPermissions']) && $host['mentionPermissions'] !== $local->getMentionPermissions()) { + try { + $this->setMentionPermissions($local, $host['mentionPermissions']); + $changed[] = ARoomModifiedEvent::PROPERTY_MENTION_PERMISSIONS; + } catch (\InvalidArgumentException $e) { + $this->logger->error('An error (' . $e->getMessage() . ') occurred while trying to sync mentionPermissions of ' . $local->getId() . ' to ' . $host['mentionPermissions'], ['exception' => $e]); + } + } + if (isset($host['messageExpiration']) && $host['messageExpiration'] !== $local->getMessageExpiration()) { + try { + $this->setMessageExpiration($local, $host['messageExpiration']); + $changed[] = ARoomModifiedEvent::PROPERTY_MESSAGE_EXPIRATION; + } catch (\InvalidArgumentException $e) { + $this->logger->error('An error (' . $e->getMessage() . ') occurred while trying to sync messageExpiration of ' . $local->getId() . ' to ' . $host['messageExpiration'], ['exception' => $e]); + } + } + if (isset($host['readOnly']) && $host['readOnly'] !== $local->getReadOnly()) { + $success = $this->setReadOnly($local, $host['readOnly']); + if (!$success) { + $this->logger->error('An error occurred while trying to sync readOnly of ' . $local->getId() . ' to ' . $host['readOnly']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_READ_ONLY; + } + } + if (isset($host['recordingConsent']) && $host['recordingConsent'] !== $local->getRecordingConsent()) { + try { + $this->setRecordingConsent($local, $host['recordingConsent'], true); + $changed[] = ARoomModifiedEvent::PROPERTY_RECORDING_CONSENT; + } catch (\InvalidArgumentException $e) { + $this->logger->error('An error (' . $e->getMessage() . ') occurred while trying to sync recordingConsent of ' . $local->getId() . ' to ' . $host['recordingConsent'], ['exception' => $e]); + } + } + if (isset($host['sipEnabled']) && $host['sipEnabled'] !== $local->getSIPEnabled()) { + $success = $this->setSIPEnabled($local, $host['sipEnabled']); + if (!$success) { + $this->logger->error('An error occurred while trying to sync sipEnabled of ' . $local->getId() . ' to ' . $host['sipEnabled']); + } else { + $changed[] = ARoomModifiedEvent::PROPERTY_SIP_ENABLED; + } + } + + // Ignore for now, so the conversation is not found by other users on this federated participants server + // if (isset($host['listable']) && $host['listable'] !== $local->getListable()) { + // $success = $this->setListable($local, $host['listable']); + // if (!$success) { + // $this->logger->error('An error occurred while trying to sync listable of ' . $local->getId() . ' to ' . $host['listable']); + // } else { + // $changed[] = ARoomModifiedEvent::PROPERTY_LISTABLE; + // } + // } + + $event = new RoomSyncedEvent($local, $changed); + $this->dispatcher->dispatchTyped($event); + } + public function deleteRoom(Room $room): void { $event = new BeforeRoomDeletedEvent($room); $this->dispatcher->dispatchTyped($event); diff --git a/lib/Signaling/Listener.php b/lib/Signaling/Listener.php index 8d3446a75d1..6d4a9fa2d73 100644 --- a/lib/Signaling/Listener.php +++ b/lib/Signaling/Listener.php @@ -11,6 +11,7 @@ use OCA\Talk\Config; use OCA\Talk\Events\AMessageSentEvent; use OCA\Talk\Events\AParticipantModifiedEvent; +use OCA\Talk\Events\ARoomEvent; use OCA\Talk\Events\ARoomModifiedEvent; use OCA\Talk\Events\ASystemMessageSentEvent; use OCA\Talk\Events\AttendeeRemovedEvent; @@ -18,6 +19,7 @@ use OCA\Talk\Events\AttendeesRemovedEvent; use OCA\Talk\Events\BeforeAttendeeRemovedEvent; use OCA\Talk\Events\BeforeRoomDeletedEvent; +use OCA\Talk\Events\BeforeRoomSyncedEvent; use OCA\Talk\Events\BeforeSessionLeftRoomEvent; use OCA\Talk\Events\CallEndedForEveryoneEvent; use OCA\Talk\Events\ChatMessageSentEvent; @@ -26,6 +28,7 @@ use OCA\Talk\Events\LobbyModifiedEvent; use OCA\Talk\Events\ParticipantModifiedEvent; use OCA\Talk\Events\RoomModifiedEvent; +use OCA\Talk\Events\RoomSyncedEvent; use OCA\Talk\Events\SessionLeftRoomEvent; use OCA\Talk\Events\SystemMessageSentEvent; use OCA\Talk\Events\SystemMessagesMultipleSentEvent; @@ -44,6 +47,24 @@ * @template-implements IEventListener */ class Listener implements IEventListener { + public const EXTERNAL_SIGNALING_PROPERTIES = [ + ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_MODE, + ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_STATUS, + ARoomModifiedEvent::PROPERTY_CALL_RECORDING, + ARoomModifiedEvent::PROPERTY_CALL_PERMISSIONS, + ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS, + ARoomModifiedEvent::PROPERTY_DESCRIPTION, + ARoomModifiedEvent::PROPERTY_LISTABLE, + ARoomModifiedEvent::PROPERTY_LOBBY, + ARoomModifiedEvent::PROPERTY_NAME, + ARoomModifiedEvent::PROPERTY_PASSWORD, + ARoomModifiedEvent::PROPERTY_READ_ONLY, + ARoomModifiedEvent::PROPERTY_SIP_ENABLED, + ARoomModifiedEvent::PROPERTY_TYPE, + ]; + + protected bool $pauseRoomModifiedListener = false; + public function __construct( protected Config $talkConfig, protected Messages $internalSignaling, @@ -106,6 +127,8 @@ protected function handleExternalSignaling(Event $event): void { match (get_class($event)) { RoomModifiedEvent::class, LobbyModifiedEvent::class => $this->notifyRoomModified($event), + BeforeRoomSyncedEvent::class => $this->pauseRoomModifiedListener(), + RoomSyncedEvent::class => $this->notifyRoomSynced($event), BeforeRoomDeletedEvent::class => $this->notifyBeforeRoomDeleted($event), CallEndedForEveryoneEvent::class => $this->notifyCallEndedForEveryone($event), GuestsCleanedUpEvent::class => $this->notifyGuestsCleanedUp($event), @@ -121,22 +144,12 @@ protected function handleExternalSignaling(Event $event): void { }; } + protected function pauseRoomModifiedListener(): void { + $this->pauseRoomModifiedListener = true; + } + protected function notifyRoomModified(ARoomModifiedEvent $event): void { - if (!in_array($event->getProperty(), [ - ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_MODE, - ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_STATUS, - ARoomModifiedEvent::PROPERTY_CALL_RECORDING, - ARoomModifiedEvent::PROPERTY_CALL_PERMISSIONS, - ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS, - ARoomModifiedEvent::PROPERTY_DESCRIPTION, - ARoomModifiedEvent::PROPERTY_LISTABLE, - ARoomModifiedEvent::PROPERTY_LOBBY, - ARoomModifiedEvent::PROPERTY_NAME, - ARoomModifiedEvent::PROPERTY_PASSWORD, - ARoomModifiedEvent::PROPERTY_READ_ONLY, - ARoomModifiedEvent::PROPERTY_SIP_ENABLED, - ARoomModifiedEvent::PROPERTY_TYPE, - ], true)) { + if (!in_array($event->getProperty(), self::EXTERNAL_SIGNALING_PROPERTIES, true)) { return; } @@ -156,12 +169,34 @@ protected function notifyRoomModified(ARoomModifiedEvent $event): void { $this->externalSignaling->roomModified($event->getRoom()); } - protected function notifyRoomRecordingModified(ARoomModifiedEvent $event): void { + protected function notifyRoomSynced(RoomSyncedEvent $event): void { + $this->pauseRoomModifiedListener = false; + if (empty(array_intersect($event->getProperties(), self::EXTERNAL_SIGNALING_PROPERTIES))) { + return; + } + + if (in_array(ARoomModifiedEvent::PROPERTY_CALL_PERMISSIONS, $event->getProperties(), true) + || in_array(ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS, $event->getProperties(), true)) { + $this->notifyRoomPermissionsModified($event); + } + + if (in_array(ARoomModifiedEvent::PROPERTY_CALL_RECORDING, $event->getProperties(), true)) { + $this->notifyRoomRecordingModified($event); + } + + if (in_array(ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_STATUS, $event->getProperties(), true)) { + $this->notifyBreakoutRoomStatusModified($event); + } + + $this->externalSignaling->roomModified($event->getRoom()); + } + + protected function notifyRoomRecordingModified(ARoomEvent $event): void { $room = $event->getRoom(); $message = [ 'type' => 'recording', 'recording' => [ - 'status' => $event->getNewValue(), + 'status' => $room->getCallRecording(), ], ]; @@ -243,7 +278,7 @@ protected function notifyParticipantTypeOrPermissionsModified(AParticipantModifi $this->externalSignaling->participantsModified($event->getRoom(), $sessionIds); } - protected function notifyRoomPermissionsModified(ARoomModifiedEvent $event): void { + protected function notifyRoomPermissionsModified(ARoomEvent $event): void { $sessionIds = []; // Setting the room permissions resets the permissions of all @@ -327,7 +362,7 @@ protected function notifyParticipantInCallModified(AParticipantModifiedEvent $ev } } - protected function notifyBreakoutRoomStatusModified(ARoomModifiedEvent $event): void { + protected function notifyBreakoutRoomStatusModified(ARoomEvent $event): void { $room = $event->getRoom(); if ($room->getBreakoutRoomStatus() === BreakoutRoom::STATUS_STARTED) { $this->notifyBreakoutRoomStarted($room); diff --git a/tests/php/Service/RoomServiceTest.php b/tests/php/Service/RoomServiceTest.php index 776c0356514..d998ec59c0d 100644 --- a/tests/php/Service/RoomServiceTest.php +++ b/tests/php/Service/RoomServiceTest.php @@ -45,6 +45,7 @@ class RoomServiceTest extends TestCase { protected IHasher&MockObject $hasher; protected IEventDispatcher&MockObject $dispatcher; protected IJobList&MockObject $jobList; + protected LoggerInterface&MockObject $logger; protected ?RoomService $service = null; public function setUp(): void { @@ -58,6 +59,7 @@ public function setUp(): void { $this->hasher = $this->createMock(IHasher::class); $this->dispatcher = $this->createMock(IEventDispatcher::class); $this->jobList = $this->createMock(IJobList::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->service = new RoomService( $this->manager, $this->participantService, @@ -67,7 +69,8 @@ public function setUp(): void { $this->config, $this->hasher, $this->dispatcher, - $this->jobList + $this->jobList, + $this->logger, ); } @@ -327,7 +330,8 @@ public function testVerifyPassword(): void { $this->config, $this->hasher, $dispatcher, - $this->jobList + $this->jobList, + $this->logger, ); $room = new Room( diff --git a/tests/php/Signaling/ListenerTest.php b/tests/php/Signaling/ListenerTest.php index ec3c9126b17..444474de902 100644 --- a/tests/php/Signaling/ListenerTest.php +++ b/tests/php/Signaling/ListenerTest.php @@ -117,6 +117,8 @@ public function testRoomModified(string $property, mixed $newValue, mixed $oldVa public function testRecordingStatusChanged(): void { $room = $this->createMock(Room::class); + $room->method('getCallRecording') + ->willReturn(Room::RECORDING_VIDEO); $event = new RoomModifiedEvent( $room, @@ -131,7 +133,7 @@ public function testRecordingStatusChanged(): void { ->with($room, [ 'type' => 'recording', 'recording' => [ - 'status' => $event->getNewValue(), + 'status' => Room::RECORDING_VIDEO, ], ]); $this->listener->handle($event); From 9dda21cfb850c64a1e7baeefd280feaeabaf0e8f Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 21 Aug 2024 21:43:54 +0200 Subject: [PATCH 2/2] chore(assets): Recompile assets Signed-off-by: Joas Schilling --- openapi-federation.json | 10 +++++----- openapi-full.json | 10 +++++----- src/types/openapi/openapi-federation.ts | 6 +++--- src/types/openapi/openapi-full.ts | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/openapi-federation.json b/openapi-federation.json index 5c3e18c724c..7e9364ada09 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -1635,17 +1635,15 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "sessionId" - ], "properties": { "sessionId": { "type": "string", + "nullable": true, "description": "Federated session id to join with" } } @@ -1715,7 +1713,9 @@ "meta": { "$ref": "#/components/schemas/OCSMeta" }, - "data": {} + "data": { + "$ref": "#/components/schemas/Room" + } } } } diff --git a/openapi-full.json b/openapi-full.json index 97341c1211f..28f02fa4ee3 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -17314,17 +17314,15 @@ } ], "requestBody": { - "required": true, + "required": false, "content": { "application/json": { "schema": { "type": "object", - "required": [ - "sessionId" - ], "properties": { "sessionId": { "type": "string", + "nullable": true, "description": "Federated session id to join with" } } @@ -17394,7 +17392,9 @@ "meta": { "$ref": "#/components/schemas/OCSMeta" }, - "data": {} + "data": { + "$ref": "#/components/schemas/Room" + } } } } diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index 08d561a035b..980015022a6 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -704,11 +704,11 @@ export interface operations { }; cookie?: never; }; - requestBody: { + requestBody?: { content: { "application/json": { /** @description Federated session id to join with */ - sessionId: string; + sessionId?: string | null; }; }; }; @@ -723,7 +723,7 @@ export interface operations { "application/json": { ocs: { meta: components["schemas"]["OCSMeta"]; - data: unknown; + data: components["schemas"]["Room"]; }; }; }; diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index d6d96f8aa00..2f5a9b881db 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -8632,11 +8632,11 @@ export interface operations { }; cookie?: never; }; - requestBody: { + requestBody?: { content: { "application/json": { /** @description Federated session id to join with */ - sessionId: string; + sessionId?: string | null; }; }; }; @@ -8651,7 +8651,7 @@ export interface operations { "application/json": { ocs: { meta: components["schemas"]["OCSMeta"]; - data: unknown; + data: components["schemas"]["Room"]; }; }; };