fix(federation): Sync room properties on join

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2024-08-21 17:21:02 +02:00
parent 5e850a1b47
commit 1fda7a31e2
No known key found for this signature in database
GPG key ID: 74434EFE0D2E2205
15 changed files with 330 additions and 29 deletions

View file

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

View file

@ -41,6 +41,7 @@ use OCA\Talk\Events\BeforeGuestJoinedRoomEvent;
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\ParticipantModifiedEvent;
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 @@ class Application extends App implements IBootstrap {
$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);

View file

@ -1660,6 +1660,10 @@ class RoomController extends AEnvironmentAwareController {
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 @@ class RoomController extends AEnvironmentAwareController {
* 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<Http::STATUS_OK, array<empty>, array{X-Nextcloud-Talk-Hash: string}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}>
* @param ?string $sessionId Federated session id to join with
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{X-Nextcloud-Talk-Hash: string}>|DataResponse<Http::STATUS_NOT_FOUND, null, array{}>
*
* 200: Federated user joined the room
* 404: Room not found
@ -1711,14 +1715,16 @@ class RoomController extends AEnvironmentAwareController {
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;

View file

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

View file

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
abstract class ARoomSyncedEvent extends ARoomEvent {
public const PROPERTY_LAST_ACTIVITY = 'lastActivity';
}

View file

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
class BeforeRoomSyncedEvent extends ARoomSyncedEvent {
}

View file

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Events;
use OCA\Talk\Room;
class RoomSyncedEvent extends ARoomSyncedEvent {
/**
* @param array<array-key, ARoomModifiedEvent::PROPERTY_*|ARoomSyncedEvent::PROPERTY_*> $properties
*/
public function __construct(
Room $room,
protected array $properties,
) {
parent::__construct($room);
}
/**
* @return array<array-key, ARoomModifiedEvent::PROPERTY_*|ARoomSyncedEvent::PROPERTY_*>
*/
public function getProperties(): array {
return $this->properties;
}
}

View file

@ -322,6 +322,8 @@ class CloudFederationProviderTalk implements ICloudFederationProvider {
}
} 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 @@ class CloudFederationProviderTalk implements ICloudFederationProvider {
} 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 @@ class CloudFederationProviderTalk implements ICloudFederationProvider {
} 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 {

View file

@ -91,7 +91,12 @@ class RoomController {
$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);
}
/**

View file

@ -49,17 +49,25 @@ class RoomModifiedListener implements IEventListener {
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());

View file

@ -11,6 +11,7 @@ namespace OCA\Talk\Recording;
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 @@ class Listener implements IEventListener {
return;
}
if ($event instanceof ARoomEvent && $event->getRoom()->isFederatedConversation()) {
return;
}
match (get_class($event)) {
RoomDeletedEvent::class => $this->roomDeleted($event),
CallEndedEvent::class,

View file

@ -12,22 +12,26 @@ use InvalidArgumentException;
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\Log\Audit\CriticalActionPerformedEvent;
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 @@ class RoomService {
protected IHasher $hasher,
protected IEventDispatcher $dispatcher,
protected IJobList $jobList,
protected LoggerInterface $logger,
) {
}
@ -435,7 +444,8 @@ class RoomService {
* @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 @@ class RoomService {
$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<array-key, ARoomModifiedEvent::PROPERTY_*> $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);

View file

@ -11,6 +11,7 @@ namespace OCA\Talk\Signaling;
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\AttendeesAddedEvent;
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\GuestsCleanedUpEvent;
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 @@ use OCP\Server;
* @template-implements IEventListener<Event>
*/
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 @@ class Listener implements IEventListener {
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 @@ class Listener implements IEventListener {
};
}
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 @@ class Listener implements IEventListener {
$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 @@ class Listener implements IEventListener {
$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 @@ class Listener implements IEventListener {
}
}
protected function notifyBreakoutRoomStatusModified(ARoomModifiedEvent $event): void {
protected function notifyBreakoutRoomStatusModified(ARoomEvent $event): void {
$room = $event->getRoom();
if ($room->getBreakoutRoomStatus() === BreakoutRoom::STATUS_STARTED) {
$this->notifyBreakoutRoomStarted($room);

View file

@ -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 @@ class RoomServiceTest extends TestCase {
$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 @@ class RoomServiceTest extends TestCase {
$this->config,
$this->hasher,
$this->dispatcher,
$this->jobList
$this->jobList,
$this->logger,
);
}
@ -327,7 +330,8 @@ class RoomServiceTest extends TestCase {
$this->config,
$this->hasher,
$dispatcher,
$this->jobList
$this->jobList,
$this->logger,
);
$room = new Room(

View file

@ -117,6 +117,8 @@ class ListenerTest extends TestCase {
public function testRecordingStatusChanged(): void {
$room = $this->createMock(Room::class);
$room->method('getCallRecording')
->willReturn(Room::RECORDING_VIDEO);
$event = new RoomModifiedEvent(
$room,
@ -131,7 +133,7 @@ class ListenerTest extends TestCase {
->with($room, [
'type' => 'recording',
'recording' => [
'status' => $event->getNewValue(),
'status' => Room::RECORDING_VIDEO,
],
]);
$this->listener->handle($event);