Skip to content

Commit

Permalink
feat(calls): Support "Send call notification" for and with federated …
Browse files Browse the repository at this point in the history
…users

Signed-off-by: Joas Schilling <[email protected]>
  • Loading branch information
nickvergessen committed Aug 26, 2024
1 parent 1a9988b commit ece1dec
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/call.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
## Send call notification

* Required capability: `send-call-notification`
* Federation capability: `federation-v2`
* Method: `POST`
* Endpoint: `/call/{token}/ring/{attendeeId}`
* Data:
Expand Down
7 changes: 7 additions & 0 deletions lib/Controller/CallController.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,18 @@ public function joinFederatedCall(string $sessionId, ?int $flags = null, bool $s
* 400: Ringing attendee is not possible
* 404: Attendee could not be found
*/
#[FederationSupported]
#[PublicPage]
#[RequireCallEnabled]
#[RequireParticipant]
#[RequirePermission(permission: RequirePermission::START_CALL)]
public function ringAttendee(int $attendeeId): DataResponse {
if ($this->room->isFederatedConversation()) {
/** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\CallController $proxy */
$proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\CallController::class);
return $proxy->ringAttendee($this->room, $this->participant, $attendeeId);
}

if ($this->room->getCallFlag() === Participant::FLAG_DISCONNECTED) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
Expand Down
1 change: 1 addition & 0 deletions lib/Events/AParticipantModifiedEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ abstract class AParticipantModifiedEvent extends ARoomEvent {
public const PROPERTY_IN_CALL = 'inCall';
public const PROPERTY_NAME = 'name';
public const PROPERTY_PERMISSIONS = 'permissions';
public const PROPERTY_RESEND_CALL = 'resend_call_notification';
public const PROPERTY_TYPE = 'type';

public const DETAIL_IN_CALL_SILENT = 'silent';
Expand Down
4 changes: 2 additions & 2 deletions lib/Events/CallNotificationSendEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ class CallNotificationSendEvent extends ARoomEvent {

public function __construct(
Room $room,
protected Participant $actor,
protected ?Participant $actor,
protected Participant $target,
) {
parent::__construct($room);
}

public function getActor(): Participant {
public function getActor(): ?Participant {
return $this->actor;
}

Expand Down
4 changes: 4 additions & 0 deletions lib/Federation/CloudFederationProviderTalk.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use OCA\Talk\Events\AParticipantModifiedEvent;
use OCA\Talk\Events\ARoomModifiedEvent;
use OCA\Talk\Events\AttendeesAddedEvent;
use OCA\Talk\Events\CallNotificationSendEvent;
use OCA\Talk\Exceptions\CannotReachRemoteException;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
Expand Down Expand Up @@ -324,6 +325,9 @@ private function participantModified(int $remoteAttendeeId, array $notification)

if ($notification['changedProperty'] === AParticipantModifiedEvent::PROPERTY_PERMISSIONS) {
$this->participantService->updatePermissions($room, $participant, Attendee::PERMISSIONS_MODIFY_SET, $notification['newValue']);
} elseif ($notification['changedProperty'] === AParticipantModifiedEvent::PROPERTY_RESEND_CALL) {
$event = new CallNotificationSendEvent($room, null, $participant);
$this->dispatcher->dispatchTyped($event);
} else {
$this->logger->debug('Update of participant property "' . $notification['changedProperty'] . '" is not handled and should not be send via federation');
}
Expand Down
30 changes: 30 additions & 0 deletions lib/Federation/Proxy/TalkV1/Controller/CallController.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,36 @@ public function joinFederatedCall(Room $room, Participant $participant, int $fla
return new DataResponse([], $statusCode);
}

/**
* @see \OCA\Talk\Controller\RoomController::ringAttendee()
*
* @param int $attendeeId ID of the attendee to ring
* @return DataResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, array<empty>, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: string}, array{}>
* @throws CannotReachRemoteException
*
* 200: Attendee rang successfully
* 400: Ringing attendee is not possible
* 404: Attendee could not be found
*/
public function ringAttendee(Room $room, Participant $participant, int $attendeeId): DataResponse {
$proxy = $this->proxy->post(
$participant->getAttendee()->getInvitedCloudId(),
$participant->getAttendee()->getAccessToken(),
$room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v4/call/' . $room->getRemoteToken() . '/ring/' . $attendeeId,
);

$statusCode = $proxy->getStatusCode();
if (!in_array($statusCode, [Http::STATUS_OK, Http::STATUS_BAD_REQUEST, Http::STATUS_NOT_FOUND], true)) {
$this->proxy->logUnexpectedStatusCode(__METHOD__, $proxy->getStatusCode());
throw new CannotReachRemoteException();
}

/** @var array{error?: string} $data */
$data = $this->proxy->getOCSData($proxy);

return new DataResponse($data, $statusCode);
}

/**
* @see \OCA\Talk\Controller\RoomController::updateFederatedCallFlags()
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function handle(Event $event): void {

if (!in_array($event->getProperty(), [
AParticipantModifiedEvent::PROPERTY_PERMISSIONS,
AParticipantModifiedEvent::PROPERTY_RESEND_CALL,
], true)) {
return;
}
Expand Down
6 changes: 3 additions & 3 deletions lib/Notification/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function __construct(

public function handle(Event $event): void {
match (get_class($event)) {
CallNotificationSendEvent::class => $this->sendCallNotification($event->getRoom(), $event->getActor()->getAttendee(), $event->getTarget()->getAttendee()),
CallNotificationSendEvent::class => $this->sendCallNotification($event->getRoom(), $event->getActor()?->getAttendee(), $event->getTarget()->getAttendee()),
AttendeesAddedEvent::class => $this->generateInvitation($event->getRoom(), $event->getAttendees()),
UserJoinedRoomEvent::class => $this->handleUserJoinedRoomEvent($event),
BeforeCallStartedEvent::class => $this->checkCallNotifications($event),
Expand Down Expand Up @@ -335,7 +335,7 @@ protected function sendCallNotifications(Room $room): void {
/**
* Forced call notification when ringing a single participant again
*/
protected function sendCallNotification(Room $room, Attendee $actor, Attendee $target): void {
protected function sendCallNotification(Room $room, ?Attendee $actor, Attendee $target): void {
try {
// Remove previous call notifications
$notification = $this->notificationManager->createNotification();
Expand All @@ -346,7 +346,7 @@ protected function sendCallNotification(Room $room, Attendee $actor, Attendee $t

$dateTime = $this->timeFactory->getDateTime();
$notification->setSubject('call', [
'callee' => $actor->getActorId(),
'callee' => $actor?->getActorId(),
])
->setDateTime($dateTime);
$this->notificationManager->notify($notification);
Expand Down
2 changes: 1 addition & 1 deletion lib/Notification/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,7 @@ protected function parseCall(INotification $notification, Room $room, IL10N $l):
$roomName = $room->getDisplayName($notification->getUser());
if ($room->getType() === Room::TYPE_ONE_TO_ONE || $room->getType() === Room::TYPE_ONE_TO_ONE_FORMER) {
$parameters = $notification->getSubjectParameters();
$calleeId = $parameters['callee'];
$calleeId = $parameters['callee']; // TODO can be null on federated conversations, so needs to be changed once we have federated 1-1
$userDisplayName = $this->userManager->getDisplayName($calleeId);
if ($userDisplayName !== null) {
if ($this->notificationManager->isPreparingPushNotification() || $this->participantService->hasActiveSessionsInCall($room)) {
Expand Down
6 changes: 6 additions & 0 deletions lib/Service/ParticipantService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,12 @@ public function changeInCall(Room $room, Participant $participant, int $flags, b
*/
public function sendCallNotificationForAttendee(Room $room, Participant $currentParticipant, int $targetAttendeeId): void {
$attendee = $this->attendeeMapper->getById($targetAttendeeId);
if ($attendee->getActorType() === Attendee::ACTOR_FEDERATED_USERS) {
$target = new Participant($room, $attendee, null);
$event = new ParticipantModifiedEvent($room, $target, AParticipantModifiedEvent::PROPERTY_RESEND_CALL, 1);
$this->dispatcher->dispatchTyped($event);
return;
}
if ($attendee->getActorType() !== Attendee::ACTOR_USERS) {
throw new \InvalidArgumentException('actor-type');
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/RightSidebar/Participants/Participant.vue
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ export default {
},

canSendCallNotification() {
return this.isUserActor
return (this.isUserActor || this.isFederatedActor)
&& !this.isSelf
&& (this.currentParticipant.permissions & PARTICIPANT.PERMISSIONS.CALL_START) !== 0
// Can also be undefined, so have to check > than disconnect
Expand Down
17 changes: 15 additions & 2 deletions tests/integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ public static function getSessionIdForUser(string $user): string {
}

public function getAttendeeId(string $type, string $id, string $room, ?string $user = null) {
if ($type === 'federated_users') {
if (!str_contains($id, '@')) {
$id .= '@' . $this->localRemoteServerUrl;
} else {
$id = str_replace(
['LOCAL', 'REMOTE'],
[$this->localServerUrl, $this->remoteServerUrl],
$id
);
}
$id = rtrim($id, '/');
}

if (!isset(self::$userToAttendeeId[$room][$type][$id])) {
if ($user !== null) {
$this->userLoadsAttendeeIdsInRoom($user, $room, 'v4');
Expand Down Expand Up @@ -2114,7 +2127,7 @@ public function userUpdatesCallFlagsInRoomTo(string $user, string $identifier, s
}

/**
* @Then /^user "([^"]*)" pings (user|guest) "([^"]*)"( attendeeIdPlusOne)? to join call "([^"]*)" with (\d+) \((v4)\)$/
* @Then /^user "([^"]*)" pings (federated_user|user|guest) "([^"]*)"( attendeeIdPlusOne)? to join call "([^"]*)" with (\d+) \((v4)\)$/
*
* @param string $user
* @param string $actorType
Expand All @@ -2126,7 +2139,7 @@ public function userUpdatesCallFlagsInRoomTo(string $user, string $identifier, s
public function userPingsAttendeeInRoomTo(string $user, string $actorType, string $actorId, ?string $offset, string $identifier, int $statusCode, string $apiVersion): void {
$this->setCurrentUser($user);

$attendeeId = self::$userToAttendeeId[$identifier][$actorType . 's'][$actorId];
$attendeeId = $this->getAttendeeId($actorType . 's', $actorId, $identifier, $user);
if ($offset) {
$attendeeId++;
}
Expand Down
62 changes: 62 additions & 0 deletions tests/integration/features/federation/call.feature
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,65 @@ Feature: federation/call
| type | name | recordingConsent |
| 2 | room | 0 |
When user "participant1" leaves call "room" with 200 (v4)

Scenario: Resend call notification for federated user
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| roomName | room |
And user "participant1" adds federated_user "participant2@REMOTE" to room "room" with 200 (v4)
And using server "REMOTE"
And user "participant2" has the following invitations (v1)
| remoteServerUrl | remoteToken | state | inviterCloudId | inviterDisplayName |
| LOCAL | room | 0 | participant1@http://localhost:8080 | participant1-displayname |
And user "participant2" accepts invite to room "room" of server "LOCAL" with 200 (v1)
| id | name | type | remoteServer | remoteToken |
| LOCAL::room | room | 2 | LOCAL | room |
And user "participant2" joins room "LOCAL::room" with 200 (v4)
And using server "LOCAL"
And user "participant1" joins room "room" with 200 (v4)
When user "participant1" joins call "room" with 200 (v4)
Then using server "REMOTE"
And user "participant2" has the following notifications
| app | object_type | object_id | subject |
| spreed | call | LOCAL::room | A group call has started in room |
And user "participant2" joins room "LOCAL::room" with 200 (v4)
When user "participant2" joins call "LOCAL::room" with 200 (v4)
When user "participant2" leaves call "LOCAL::room" with 200 (v4)
And user "participant2" has the following notifications
And using server "LOCAL"
Then user "participant1" loads attendees attendee ids in room "room" (v4)
Then user "participant1" pings federated_user "participant2@REMOTE" to join call "room" with 200 (v4)
Then using server "REMOTE"
And user "participant2" has the following notifications
| app | object_type | object_id | subject |
| spreed | call | LOCAL::room | A group call has started in room |

Scenario: Resend call notification as a federated user
Given user "participant1" creates room "room" (v4)
| roomType | 2 |
| roomName | room |
And user "participant1" adds federated_user "participant2@REMOTE" to room "room" with 200 (v4)
And using server "REMOTE"
And user "participant2" has the following invitations (v1)
| remoteServerUrl | remoteToken | state | inviterCloudId | inviterDisplayName |
| LOCAL | room | 0 | participant1@http://localhost:8080 | participant1-displayname |
And user "participant2" accepts invite to room "room" of server "LOCAL" with 200 (v1)
| id | name | type | remoteServer | remoteToken |
| LOCAL::room | room | 2 | LOCAL | room |
And user "participant2" joins room "LOCAL::room" with 200 (v4)
When user "participant2" joins call "LOCAL::room" with 200 (v4)
And using server "LOCAL"
And user "participant1" has the following notifications
| app | object_type | object_id | subject |
| spreed | call | room | A group call has started in room |
And user "participant1" joins room "room" with 200 (v4)
When user "participant1" joins call "room" with 200 (v4)
When user "participant1" leaves call "room" with 200 (v4)
And user "participant1" has the following notifications
Then using server "REMOTE"
Then user "participant2" loads attendees attendee ids in room "LOCAL::room" (v4)
Then user "participant2" pings federated_user "participant1@LOCAL" to join call "LOCAL::room" with 200 (v4)
And using server "LOCAL"
And user "participant1" has the following notifications
| app | object_type | object_id | subject |
| spreed | call | room | A group call has started in room |

0 comments on commit ece1dec

Please sign in to comment.