mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-17 21:12:20 +01:00
feat(calls): Support "Send call notification" for and with federated users
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
parent
1a9988be19
commit
ece1decade
13 changed files with 134 additions and 9 deletions
|
|
@ -60,6 +60,7 @@
|
||||||
## Send call notification
|
## Send call notification
|
||||||
|
|
||||||
* Required capability: `send-call-notification`
|
* Required capability: `send-call-notification`
|
||||||
|
* Federation capability: `federation-v2`
|
||||||
* Method: `POST`
|
* Method: `POST`
|
||||||
* Endpoint: `/call/{token}/ring/{attendeeId}`
|
* Endpoint: `/call/{token}/ring/{attendeeId}`
|
||||||
* Data:
|
* Data:
|
||||||
|
|
|
||||||
|
|
@ -248,11 +248,18 @@ class CallController extends AEnvironmentAwareController {
|
||||||
* 400: Ringing attendee is not possible
|
* 400: Ringing attendee is not possible
|
||||||
* 404: Attendee could not be found
|
* 404: Attendee could not be found
|
||||||
*/
|
*/
|
||||||
|
#[FederationSupported]
|
||||||
#[PublicPage]
|
#[PublicPage]
|
||||||
#[RequireCallEnabled]
|
#[RequireCallEnabled]
|
||||||
#[RequireParticipant]
|
#[RequireParticipant]
|
||||||
#[RequirePermission(permission: RequirePermission::START_CALL)]
|
#[RequirePermission(permission: RequirePermission::START_CALL)]
|
||||||
public function ringAttendee(int $attendeeId): DataResponse {
|
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) {
|
if ($this->room->getCallFlag() === Participant::FLAG_DISCONNECTED) {
|
||||||
return new DataResponse([], Http::STATUS_BAD_REQUEST);
|
return new DataResponse([], Http::STATUS_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ abstract class AParticipantModifiedEvent extends ARoomEvent {
|
||||||
public const PROPERTY_IN_CALL = 'inCall';
|
public const PROPERTY_IN_CALL = 'inCall';
|
||||||
public const PROPERTY_NAME = 'name';
|
public const PROPERTY_NAME = 'name';
|
||||||
public const PROPERTY_PERMISSIONS = 'permissions';
|
public const PROPERTY_PERMISSIONS = 'permissions';
|
||||||
|
public const PROPERTY_RESEND_CALL = 'resend_call_notification';
|
||||||
public const PROPERTY_TYPE = 'type';
|
public const PROPERTY_TYPE = 'type';
|
||||||
|
|
||||||
public const DETAIL_IN_CALL_SILENT = 'silent';
|
public const DETAIL_IN_CALL_SILENT = 'silent';
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ class CallNotificationSendEvent extends ARoomEvent {
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Room $room,
|
Room $room,
|
||||||
protected Participant $actor,
|
protected ?Participant $actor,
|
||||||
protected Participant $target,
|
protected Participant $target,
|
||||||
) {
|
) {
|
||||||
parent::__construct($room);
|
parent::__construct($room);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getActor(): Participant {
|
public function getActor(): ?Participant {
|
||||||
return $this->actor;
|
return $this->actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use OCA\Talk\Events\AAttendeeRemovedEvent;
|
||||||
use OCA\Talk\Events\AParticipantModifiedEvent;
|
use OCA\Talk\Events\AParticipantModifiedEvent;
|
||||||
use OCA\Talk\Events\ARoomModifiedEvent;
|
use OCA\Talk\Events\ARoomModifiedEvent;
|
||||||
use OCA\Talk\Events\AttendeesAddedEvent;
|
use OCA\Talk\Events\AttendeesAddedEvent;
|
||||||
|
use OCA\Talk\Events\CallNotificationSendEvent;
|
||||||
use OCA\Talk\Exceptions\CannotReachRemoteException;
|
use OCA\Talk\Exceptions\CannotReachRemoteException;
|
||||||
use OCA\Talk\Exceptions\ParticipantNotFoundException;
|
use OCA\Talk\Exceptions\ParticipantNotFoundException;
|
||||||
use OCA\Talk\Exceptions\RoomNotFoundException;
|
use OCA\Talk\Exceptions\RoomNotFoundException;
|
||||||
|
|
@ -324,6 +325,9 @@ class CloudFederationProviderTalk implements ICloudFederationProvider {
|
||||||
|
|
||||||
if ($notification['changedProperty'] === AParticipantModifiedEvent::PROPERTY_PERMISSIONS) {
|
if ($notification['changedProperty'] === AParticipantModifiedEvent::PROPERTY_PERMISSIONS) {
|
||||||
$this->participantService->updatePermissions($room, $participant, Attendee::PERMISSIONS_MODIFY_SET, $notification['newValue']);
|
$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 {
|
} else {
|
||||||
$this->logger->debug('Update of participant property "' . $notification['changedProperty'] . '" is not handled and should not be send via federation');
|
$this->logger->debug('Update of participant property "' . $notification['changedProperty'] . '" is not handled and should not be send via federation');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,36 @@ class CallController {
|
||||||
return new DataResponse([], $statusCode);
|
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()
|
* @see \OCA\Talk\Controller\RoomController::updateFederatedCallFlags()
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ class ParticipantModifiedListener implements IEventListener {
|
||||||
|
|
||||||
if (!in_array($event->getProperty(), [
|
if (!in_array($event->getProperty(), [
|
||||||
AParticipantModifiedEvent::PROPERTY_PERMISSIONS,
|
AParticipantModifiedEvent::PROPERTY_PERMISSIONS,
|
||||||
|
AParticipantModifiedEvent::PROPERTY_RESEND_CALL,
|
||||||
], true)) {
|
], true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class Listener implements IEventListener {
|
||||||
|
|
||||||
public function handle(Event $event): void {
|
public function handle(Event $event): void {
|
||||||
match (get_class($event)) {
|
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()),
|
AttendeesAddedEvent::class => $this->generateInvitation($event->getRoom(), $event->getAttendees()),
|
||||||
UserJoinedRoomEvent::class => $this->handleUserJoinedRoomEvent($event),
|
UserJoinedRoomEvent::class => $this->handleUserJoinedRoomEvent($event),
|
||||||
BeforeCallStartedEvent::class => $this->checkCallNotifications($event),
|
BeforeCallStartedEvent::class => $this->checkCallNotifications($event),
|
||||||
|
|
@ -335,7 +335,7 @@ class Listener implements IEventListener {
|
||||||
/**
|
/**
|
||||||
* Forced call notification when ringing a single participant again
|
* 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 {
|
try {
|
||||||
// Remove previous call notifications
|
// Remove previous call notifications
|
||||||
$notification = $this->notificationManager->createNotification();
|
$notification = $this->notificationManager->createNotification();
|
||||||
|
|
@ -346,7 +346,7 @@ class Listener implements IEventListener {
|
||||||
|
|
||||||
$dateTime = $this->timeFactory->getDateTime();
|
$dateTime = $this->timeFactory->getDateTime();
|
||||||
$notification->setSubject('call', [
|
$notification->setSubject('call', [
|
||||||
'callee' => $actor->getActorId(),
|
'callee' => $actor?->getActorId(),
|
||||||
])
|
])
|
||||||
->setDateTime($dateTime);
|
->setDateTime($dateTime);
|
||||||
$this->notificationManager->notify($notification);
|
$this->notificationManager->notify($notification);
|
||||||
|
|
|
||||||
|
|
@ -999,7 +999,7 @@ class Notifier implements INotifier {
|
||||||
$roomName = $room->getDisplayName($notification->getUser());
|
$roomName = $room->getDisplayName($notification->getUser());
|
||||||
if ($room->getType() === Room::TYPE_ONE_TO_ONE || $room->getType() === Room::TYPE_ONE_TO_ONE_FORMER) {
|
if ($room->getType() === Room::TYPE_ONE_TO_ONE || $room->getType() === Room::TYPE_ONE_TO_ONE_FORMER) {
|
||||||
$parameters = $notification->getSubjectParameters();
|
$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);
|
$userDisplayName = $this->userManager->getDisplayName($calleeId);
|
||||||
if ($userDisplayName !== null) {
|
if ($userDisplayName !== null) {
|
||||||
if ($this->notificationManager->isPreparingPushNotification() || $this->participantService->hasActiveSessionsInCall($room)) {
|
if ($this->notificationManager->isPreparingPushNotification() || $this->participantService->hasActiveSessionsInCall($room)) {
|
||||||
|
|
|
||||||
|
|
@ -1223,6 +1223,12 @@ class ParticipantService {
|
||||||
*/
|
*/
|
||||||
public function sendCallNotificationForAttendee(Room $room, Participant $currentParticipant, int $targetAttendeeId): void {
|
public function sendCallNotificationForAttendee(Room $room, Participant $currentParticipant, int $targetAttendeeId): void {
|
||||||
$attendee = $this->attendeeMapper->getById($targetAttendeeId);
|
$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) {
|
if ($attendee->getActorType() !== Attendee::ACTOR_USERS) {
|
||||||
throw new \InvalidArgumentException('actor-type');
|
throw new \InvalidArgumentException('actor-type');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -610,7 +610,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
canSendCallNotification() {
|
canSendCallNotification() {
|
||||||
return this.isUserActor
|
return (this.isUserActor || this.isFederatedActor)
|
||||||
&& !this.isSelf
|
&& !this.isSelf
|
||||||
&& (this.currentParticipant.permissions & PARTICIPANT.PERMISSIONS.CALL_START) !== 0
|
&& (this.currentParticipant.permissions & PARTICIPANT.PERMISSIONS.CALL_START) !== 0
|
||||||
// Can also be undefined, so have to check > than disconnect
|
// Can also be undefined, so have to check > than disconnect
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,19 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAttendeeId(string $type, string $id, string $room, ?string $user = null) {
|
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 (!isset(self::$userToAttendeeId[$room][$type][$id])) {
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
$this->userLoadsAttendeeIdsInRoom($user, $room, 'v4');
|
$this->userLoadsAttendeeIdsInRoom($user, $room, 'v4');
|
||||||
|
|
@ -2114,7 +2127,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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 $user
|
||||||
* @param string $actorType
|
* @param string $actorType
|
||||||
|
|
@ -2126,7 +2139,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
||||||
public function userPingsAttendeeInRoomTo(string $user, string $actorType, string $actorId, ?string $offset, string $identifier, int $statusCode, string $apiVersion): void {
|
public function userPingsAttendeeInRoomTo(string $user, string $actorType, string $actorId, ?string $offset, string $identifier, int $statusCode, string $apiVersion): void {
|
||||||
$this->setCurrentUser($user);
|
$this->setCurrentUser($user);
|
||||||
|
|
||||||
$attendeeId = self::$userToAttendeeId[$identifier][$actorType . 's'][$actorId];
|
$attendeeId = $this->getAttendeeId($actorType . 's', $actorId, $identifier, $user);
|
||||||
if ($offset) {
|
if ($offset) {
|
||||||
$attendeeId++;
|
$attendeeId++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -430,3 +430,65 @@ Feature: federation/call
|
||||||
| type | name | recordingConsent |
|
| type | name | recordingConsent |
|
||||||
| 2 | room | 0 |
|
| 2 | room | 0 |
|
||||||
When user "participant1" leaves call "room" with 200 (v4)
|
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 |
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue