Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 120 additions & 19 deletions lib/Signaling/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@
use OCA\Talk\Events\UserJoinedRoomEvent;
use OCA\Talk\Manager;
use OCA\Talk\Model\BreakoutRoom;
use OCA\Talk\Model\Poll;
use OCA\Talk\Model\Session;
use OCA\Talk\Model\Vote;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\PollService;
use OCA\Talk\Service\SessionService;
use OCA\Talk\Service\ThreadService;
use OCP\AppFramework\Db\DoesNotExistException;
Expand Down Expand Up @@ -74,6 +77,29 @@ class Listener implements IEventListener {
ARoomModifiedEvent::PROPERTY_TYPE,
];

public const SYSTEM_MESSAGE_TYPE_RELAY = [
'call_started',
'call_joined',
'call_left',
'call_ended',
'call_ended_everyone',
'thread_created',
'thread_renamed',
'message_deleted',
'message_edited',
'moderator_promoted',
'moderator_demoted',
'guest_moderator_promoted',
'guest_moderator_demoted',
'file_shared',
'object_shared',
'history_cleared',
'poll_voted',
'poll_closed',
'recording_started',
'recording_stopped',
];

protected bool $pauseRoomModifiedListener = false;

public function __construct(
Expand All @@ -87,6 +113,7 @@ public function __construct(
protected MessageParser $messageParser,
protected ThreadService $threadService,
protected IFactory $l10nFactory,
protected PollService $pollService,
) {
}

Expand Down Expand Up @@ -153,9 +180,9 @@ protected function handleExternalSignaling(Event $event): void {
AttendeesRemovedEvent::class => $this->notifyAttendeesRemoved($event),
ParticipantModifiedEvent::class => $this->notifyParticipantModified($event),
SessionLeftRoomEvent::class => $this->notifySessionLeftRoom($event),
ChatMessageSentEvent::class,
ChatMessageSentEvent::class => $this->notifyMessageSent($event),
SystemMessageSentEvent::class,
SystemMessagesMultipleSentEvent::class => $this->notifyMessageSent($event),
SystemMessagesMultipleSentEvent::class => $this->notifySystemMessageSent($event),
ReactionAddedEvent::class,
ReactionRemovedEvent::class => $this->notifyReactionSent($event),
default => null, // Ignoring events subscribed by the internal signaling
Expand Down Expand Up @@ -489,14 +516,6 @@ protected function notifyMessageSent(AMessageSentEvent $event): void {
}

$comment = $event->getComment();
if ($event instanceof ASystemMessageSentEvent && $event->shouldSkipLastActivityUpdate()) {
$messageDecoded = json_decode($comment->getMessage(), true);
$messageType = $messageDecoded['message'] ?? '';
if ($messageType !== 'message_deleted' && $messageType !== 'message_edited') {
return;
}
}

$room = $event->getRoom();
$data = [
'type' => 'chat',
Expand All @@ -505,32 +524,114 @@ protected function notifyMessageSent(AMessageSentEvent $event): void {
],
];

if ($event instanceof ASystemMessageSentEvent && $comment->getVerb() === ChatManager::VERB_SYSTEM && $event->shouldSkipLastActivityUpdate() === false) {
$this->externalSignaling->sendRoomMessage($room, $data);
return;
}

$l10n = $this->l10nFactory->get(Application::APP_ID, 'en');
$message = $this->messageParser->createMessage($event->getRoom(), null, $comment, $l10n);
$this->messageParser->parseMessage($message);

if ($message->getVisibility() === false) {
$this->externalSignaling->sendRoomMessage($room, $data);
return;
}

$threadId = (int)$comment->getTopmostParentId() ?: $comment->getId();
try {
$thread = $this->threadService->findByThreadId($room->getId(), (int)$threadId);
} catch (DoesNotExistException) {
$thread = null;
}
$data['chat']['comment'] = $message->toArray('json', $thread);

$parent = $event->getParent();
if ($parent !== null) {
$parentMessage = $this->messageParser->createMessage($event->getRoom(), null, $parent, $l10n);
$this->messageParser->parseMessage($parentMessage);
$data['chat']['comment']['parent'] = $parentMessage->toArray('json', $thread);
}

$this->externalSignaling->sendRoomMessage($room, $data);
}

protected function notifySystemMessageSent(ASystemMessageSentEvent $event): void {
$comment = $event->getComment();
$messageDecoded = json_decode($comment->getMessage(), true);
$params = $messageDecoded['parameters'] ?? [];
$messageType = $messageDecoded['message'] ?? '';

if ($event->shouldSkipLastActivityUpdate() === true
&& !in_array($messageType, ['message_deleted', 'message_edited', 'thread_created', 'thread_renamed'], true)
) {
return;
}

$room = $event->getRoom();
$data = [
'type' => 'chat',
'chat' => [
'refresh' => true,
],
];

if (!in_array($messageType, self::SYSTEM_MESSAGE_TYPE_RELAY, true)) {
$this->externalSignaling->sendRoomMessage($room, $data);
return;
}

$thread = null;
if (!isset($messageType)) {
if ($messageType === 'thread_created' || $messageType === 'thread_renamed') {
$threadId = (int)$comment->getTopmostParentId() ?: $comment->getId();
try {
$thread = $this->threadService->findByThreadId($room->getId(), (int)$threadId);
} catch (DoesNotExistException) {
}
}

$l10n = $this->l10nFactory->get(Application::APP_ID, 'en');
$message = $this->messageParser->createMessage($event->getRoom(), null, $comment, $l10n);
$this->messageParser->parseMessage($message);
if ($message->getVisibility() === false) {
$this->externalSignaling->sendRoomMessage($room, $data);
return;
}

$data['chat']['comment'] = $message->toArray('json', $thread);

if ($event instanceof ASystemMessageSentEvent && $event->getParent() !== null) {
$parent = $event->getParent();
if ($messageType === 'object_shared' && isset($params['objectType']) && $params['objectType'] === 'talk-poll') {
$poll = $this->pollService->getPoll($event->getRoom()->getId(), $params['objectId']);
$data['chat']['comment']['poll'] = $poll->renderAsPoll();
}

if ($messageType === 'poll_voted' && isset($params['poll']['id'])) {
$poll = $this->pollService->getPoll($event->getRoom()->getId(), $params['poll']['id']);
$data['chat']['comment']['poll'] = $poll->renderAsPoll();
}

if ($messageType === 'poll_closed' && isset($params['poll']['id'])) {
$poll = $this->pollService->getPoll($event->getRoom()->getId(), $params['poll']['id']);
$data['chat']['comment']['poll'] = $poll->renderAsPoll();
if ($poll->getResultMode() !== Poll::MODE_HIDDEN) {
$votes = $this->pollService->getVotes($poll);
$data['chat']['comment']['poll']['votes'] = array_map(fn (Vote $vote) => $vote->asArray(), $votes);
}
}

if ($messageType === 'thread_created' || $messageType === 'thread_renamed') {
$data['chat']['comment']['threadInfo']['thread'] = [
'id' => $thread->getId(),
'roomToken' => $room->getToken(),
'title' => $thread->getName(),
'lastMessageId' => $thread->getLastMessageId(),
'lastActivity' => $thread->getLastActivity()->getTimestamp(),
'numReplies' => $thread->getNumReplies(),
];
$data['chat']['comment']['threadInfo']['attendee'] = ['notificationLevel' => 0];
$data['chat']['comment']['threadInfo']['first'] = $thread->toArray($room);
$data['chat']['comment']['threadInfo']['last'] = null;
$this->externalSignaling->sendRoomMessage($room, $data);
return;
}

$parent = $event->getParent();
if ($parent !== null) {
$parentMessage = $this->messageParser->createMessage($event->getRoom(), null, $parent, $l10n);
$this->messageParser->parseMessage($parentMessage);
$data['chat']['comment']['parent'] = $parentMessage->toArray('json', $thread);
Expand Down Expand Up @@ -597,7 +698,7 @@ protected function notifyReactionSent(AReactionEvent $event): void {
'referenceId' => '',
'reactions' => [],
'markdown' => false ,
'expirationTimestamp' => $message->getExpirationDateTime()?->getTimestamp(), // base on parent post timestamp + room expiration
'expirationTimestamp' => $message->getExpirationDateTime()?->getTimestamp(),
'threadId' => $threadId,
];

Expand Down
4 changes: 4 additions & 0 deletions tests/integration/features/bootstrap/RecordingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ public function fakeServerReceivedTheFollowingRequests(string $server, ?TableNod
usort($actualDataJson['participants']['users'], static fn (array $u1, array $u2) => $u1['userId'] <=> $u2['userId']);
$write = true;
}
if (isset($actualDataJson['message']['data']['chat']['comment'])) {
$actualDataJson['message']['data']['chat']['comment'] = [];
$write = true;
}

if ($write) {
$actual['data'] = json_encode($actualDataJson);
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/features/callapi/recording.feature
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Feature: callapi/recording
And recording server sent stopped request for recording in room "room1" as "participant1" with 200
Then signaling server received the following requests
| token | data |
| room1 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} |
| 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":""}}} |
Then user "participant1" sees the following system messages in room "room1" with 200 (v1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Feature: callapi/update-call-flags
And user "owner" joins call "public room" with 200 (v4)
Then signaling server received the following requests
| token | data |
| public room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} |
| 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":254,"actorType":"users","actorId":"owner","userId":"owner"}],"users":[{"inCall":7,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":254,"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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Feature: conversation-2/promotion-demotion
When user "participant1" promotes "participant2" in room "room" with 200 (v4)
Then signaling server received the following requests
| token | data |
| room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} |
| room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true,"comment":[]}}}} |
# TODO remove handler with "roomModified" in favour of handler with
# "participantsModified" once the clients no longer expect a
# "roomModified" message for participant type changes.
Expand Down
8 changes: 8 additions & 0 deletions tests/php/Signaling/ListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use OCA\Talk\Model\Thread;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\PollService;
use OCA\Talk\Service\SessionService;
use OCA\Talk\Service\ThreadService;
use OCA\Talk\Signaling\BackendNotifier;
Expand Down Expand Up @@ -51,6 +52,9 @@ class ListenerTest extends TestCase {
protected ThreadService&MockObject $threadService;
protected Config&MockObject $config;
protected IFactory $l10nFactory;
protected MockObject&PollService $pollService;



public function setUp(): void {
parent::setUp();
Expand All @@ -76,6 +80,7 @@ public function setUp(): void {
$this->messageParser,
$this->threadService,
$this->l10nFactory,
$this->pollService,
);
}

Expand Down Expand Up @@ -383,6 +388,7 @@ public function testSystemMessageSentEvent(): void {
$room = $this->createMock(Room::class);
$comment = $this->createMock(IComment::class);
$comment->method('getVerb')->willReturn(ChatManager::VERB_SYSTEM);
$comment->method('getMessage')->willReturn(json_encode(['message' => 'test']));

$event = new SystemMessageSentEvent(
$room,
Expand All @@ -402,6 +408,7 @@ public function testSystemMessageSentEvent(): void {
$this->listener->handle($event);
}


public function testSystemMessageSentEventSkippingUpdate(): void {
$room = $this->createMock(Room::class);
$comment = $this->createMock(IComment::class);
Expand All @@ -423,6 +430,7 @@ public function testSystemMessagesMultipleSentEvent(): void {
$room = $this->createMock(Room::class);
$comment = $this->createMock(IComment::class);
$comment->method('getVerb')->willReturn(ChatManager::VERB_SYSTEM);
$comment->method('getMessage')->willReturn(json_encode(['message' => 'test']));

$event = new SystemMessagesMultipleSentEvent(
$room,
Expand Down