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
|
||||
|
||||
* Required capability: `send-call-notification`
|
||||
* Federation capability: `federation-v2`
|
||||
* Method: `POST`
|
||||
* Endpoint: `/call/{token}/ring/{attendeeId}`
|
||||
* Data:
|
||||
|
|
|
|||
|
|
@ -248,11 +248,18 @@ class CallController extends AEnvironmentAwareController {
|
|||
* 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use OCA\Talk\Events\AAttendeeRemovedEvent;
|
|||
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;
|
||||
|
|
@ -324,6 +325,9 @@ class CloudFederationProviderTalk implements ICloudFederationProvider {
|
|||
|
||||
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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,36 @@ class CallController {
|
|||
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()
|
||||
*
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class ParticipantModifiedListener implements IEventListener {
|
|||
|
||||
if (!in_array($event->getProperty(), [
|
||||
AParticipantModifiedEvent::PROPERTY_PERMISSIONS,
|
||||
AParticipantModifiedEvent::PROPERTY_RESEND_CALL,
|
||||
], true)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class Listener implements IEventListener {
|
|||
|
||||
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),
|
||||
|
|
@ -335,7 +335,7 @@ class Listener implements IEventListener {
|
|||
/**
|
||||
* 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();
|
||||
|
|
@ -346,7 +346,7 @@ class Listener implements IEventListener {
|
|||
|
||||
$dateTime = $this->timeFactory->getDateTime();
|
||||
$notification->setSubject('call', [
|
||||
'callee' => $actor->getActorId(),
|
||||
'callee' => $actor?->getActorId(),
|
||||
])
|
||||
->setDateTime($dateTime);
|
||||
$this->notificationManager->notify($notification);
|
||||
|
|
|
|||
|
|
@ -999,7 +999,7 @@ class Notifier implements INotifier {
|
|||
$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)) {
|
||||
|
|
|
|||
|
|
@ -1223,6 +1223,12 @@ class ParticipantService {
|
|||
*/
|
||||
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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -144,6 +144,19 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
}
|
||||
|
||||
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');
|
||||
|
|
@ -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 $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 {
|
||||
$this->setCurrentUser($user);
|
||||
|
||||
$attendeeId = self::$userToAttendeeId[$identifier][$actorType . 's'][$actorId];
|
||||
$attendeeId = $this->getAttendeeId($actorType . 's', $actorId, $identifier, $user);
|
||||
if ($offset) {
|
||||
$attendeeId++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue