diff options
author | Joas Schilling <coding@schilljs.com> | 2023-10-10 17:37:34 +0200 |
---|---|---|
committer | Joas Schilling <coding@schilljs.com> | 2023-10-23 15:47:59 +0200 |
commit | fb363778aa59e5bf70cc6348a30ffbb55065820a (patch) | |
tree | 55c5e47a4c9101b147b7da2a93e08c8e4efde72a | |
parent | 50b8f4eb389feb3f47c3519e340dbcc54a483442 (diff) |
feat(call): Add endpoint to send the dial-out request to the HPB
Signed-off-by: Joas Schilling <coding@schilljs.com>
-rw-r--r-- | appinfo/routes/routesCallController.php | 2 | ||||
-rw-r--r-- | docs/call.md | 22 | ||||
-rw-r--r-- | lib/Capabilities.php | 1 | ||||
-rw-r--r-- | lib/Controller/CallController.php | 36 | ||||
-rw-r--r-- | lib/Service/ParticipantService.php | 31 | ||||
-rw-r--r-- | lib/Service/SIPDialOutService.php | 29 | ||||
-rw-r--r-- | tests/php/Service/SIPDialOutServiceTest.php | 8 |
7 files changed, 112 insertions, 17 deletions
diff --git a/appinfo/routes/routesCallController.php b/appinfo/routes/routesCallController.php index 9a5f9042c..07f7c781c 100644 --- a/appinfo/routes/routesCallController.php +++ b/appinfo/routes/routesCallController.php @@ -36,6 +36,8 @@ return [ ['name' => 'Call#joinCall', 'url' => '/api/{apiVersion}/call/{token}', 'verb' => 'POST', 'requirements' => $requirements], /** @see \OCA\Talk\Controller\CallController::ringAttendee() */ ['name' => 'Call#ringAttendee', 'url' => '/api/{apiVersion}/call/{token}/ring/{attendeeId}', 'verb' => 'POST', 'requirements' => $requirements], + /** @see \OCA\Talk\Controller\CallController::sipDialOut() */ + ['name' => 'Call#sipDialOut', 'url' => '/api/{apiVersion}/call/{token}/dialout/{attendeeId}', 'verb' => 'POST', 'requirements' => $requirements], /** @see \OCA\Talk\Controller\CallController::updateCallFlags() */ ['name' => 'Call#updateCallFlags', 'url' => '/api/{apiVersion}/call/{token}', 'verb' => 'PUT', 'requirements' => $requirements], /** @see \OCA\Talk\Controller\CallController::leaveCall() */ diff --git a/docs/call.md b/docs/call.md index 97bf5deac..e95fadd2f 100644 --- a/docs/call.md +++ b/docs/call.md @@ -77,6 +77,28 @@ + `404 Not Found` When the conversation could not be found for the participant + `412 Precondition Failed` When the lobby is active and the user is not a moderator +## Send SIP dial-out request + +* Required capability: `sip-support-dialout` +* Method: `POST` +* Endpoint: `/call/{token}/dialout/{attendeeId}` +* Data: + +| field | type | Description | +|--------------|------|-------------------------| +| `attendeeId` | int | The participant to call | + +* Response: + - Status code: + + `200 OK` + + `400 Bad Request` When the room has no call in process + + `400 Bad Request` When the actor is not in the call + + `403 Forbidden` When the current user does not have the "Start call" permission + + `404 Not Found` When the conversation could not be found for the participant + + `404 Not Found` When the target participant could not be found or is not a phone number (Guest, group, etc.) + + `412 Precondition Failed` When the lobby is active and the user is not a moderator + + `501 Not Implemented` When the SIP functionality is not configured + ## Update call flags * Method: `PUT` diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 6e22aa1eb..53996f09e 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -166,6 +166,7 @@ class Capabilities implements IPublicCapability { 'session-state', 'note-to-self', 'recording-consent', + 'sip-support-dialout', ], 'config' => [ 'attachments' => [ diff --git a/lib/Controller/CallController.php b/lib/Controller/CallController.php index 2e378de33..be76507c6 100644 --- a/lib/Controller/CallController.php +++ b/lib/Controller/CallController.php @@ -29,6 +29,7 @@ declare(strict_types=1); namespace OCA\Talk\Controller; use OCA\Talk\Config; +use OCA\Talk\Exceptions\ParticipantNotFoundException; use OCA\Talk\Middleware\Attribute\RequireCallEnabled; use OCA\Talk\Middleware\Attribute\RequireModeratorOrNoLobby; use OCA\Talk\Middleware\Attribute\RequireParticipant; @@ -201,6 +202,41 @@ class CallController extends AEnvironmentAwareController { } /** + * Call a SIP dial-out attendee + * + * @param int $attendeeId ID of the attendee to call + * @return DataResponse<Http::STATUS_CREATED|Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_NOT_IMPLEMENTED, array<empty>, array{}> + * + * 201: Dial-out initiated successfully + * 400: SIP dial-out not possible + * 404: Participant could not be found or is a wrong type + * 501: SIP dial-out is not configured on the server + */ + #[PublicPage] + #[RequireCallEnabled] + #[RequireParticipant] + #[RequirePermission(permission: RequirePermission::START_CALL)] + public function sipDialOut(int $attendeeId): DataResponse { + if ($this->room->getCallFlag() === Participant::FLAG_DISCONNECTED) { + return new DataResponse([], Http::STATUS_BAD_REQUEST); + } + + if ($this->participant->getSession() && $this->participant->getSession()->getInCall() === Participant::FLAG_DISCONNECTED) { + return new DataResponse([], Http::STATUS_BAD_REQUEST); + } + + try { + $this->participantService->startDialOutRequest($this->room, $attendeeId); + } catch (ParticipantNotFoundException) { + return new DataResponse([], Http::STATUS_NOT_FOUND); + } catch (\InvalidArgumentException) { + return new DataResponse([], Http::STATUS_NOT_IMPLEMENTED); + } + + return new DataResponse([], Http::STATUS_CREATED); + } + + /** * Update the in-call flags * * @param int $flags New flags diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index 11a2d941a..49b63b64c 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -82,6 +82,7 @@ use OCA\Talk\Participant; use OCA\Talk\Room; use OCA\Talk\Webinary; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Comments\IComment; use OCP\DB\Exception; @@ -119,6 +120,7 @@ class ParticipantService { private BackendNotifier $backendNotifier, private ITimeFactory $timeFactory, private ICacheFactory $cacheFactory, + protected SIPDialOutService $dialOutService, ) { } @@ -1234,6 +1236,35 @@ class ParticipantService { return true; } + /** + * @throws \InvalidArgumentException + * @throws ParticipantNotFoundException + */ + public function startDialOutRequest(Room $room, int $targetAttendeeId): void { + try { + $attendee = $this->attendeeMapper->getById($targetAttendeeId); + } catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) { + throw new ParticipantNotFoundException(); + } + + if ($attendee->getRoomId() !== $room->getId()) { + throw new ParticipantNotFoundException(); + } + + if ($attendee->getActorType() !== Attendee::ACTOR_PHONES) { + throw new ParticipantNotFoundException(); + } + + $dialOutResponse = $this->dialOutService->sendDialOutRequestToBackend($room, $attendee); + + if (!$dialOutResponse) { + throw new \InvalidArgumentException('backend'); + } + + $attendee->setCallId($dialOutResponse->dialOut->callId); + $this->attendeeMapper->update($attendee); + } + public function updateCallFlags(Room $room, Participant $participant, int $flags): void { $session = $participant->getSession(); if (!$session instanceof Session) { diff --git a/lib/Service/SIPDialOutService.php b/lib/Service/SIPDialOutService.php index 76e1df0bc..34c252304 100644 --- a/lib/Service/SIPDialOutService.php +++ b/lib/Service/SIPDialOutService.php @@ -33,24 +33,27 @@ use OCA\Talk\Signaling\Responses\Response; use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\MappingError; use OCA\Talk\Vendor\CuyZ\Valinor\Mapper\Source\Source; use OCA\Talk\Vendor\CuyZ\Valinor\MapperBuilder; +use Psr\Log\LoggerInterface; class SIPDialOutService { public function __construct( - public ParticipantService $participantService, - public BackendNotifier $backendNotifier, + protected BackendNotifier $backendNotifier, + protected LoggerInterface $logger, ) { } - public function dialOut(Room $room): void { - $attendees = $this->participantService->getActorsByType($room, Attendee::ACTOR_PHONES); - foreach ($attendees as $attendee) { - if ($attendee->getActorType() !== Attendee::ACTOR_PHONES) { - continue; - } + public function sendDialOutRequestToBackend(Room $room, Attendee $attendee): ?Response { + if ($attendee->getActorType() !== Attendee::ACTOR_PHONES) { + return null; + } - $response = $this->backendNotifier->dialOutToAttendee($room, $attendee); - $dialOutResponse = $this->validateDialOutResponse($response); + $response = $this->backendNotifier->dialOutToAttendee($room, $attendee); + try { + return $this->validateDialOutResponse($response); + } catch (\InvalidArgumentException $e) { + $this->logger->error($e->getMessage(), ['exception' => $e]); + return null; } } @@ -71,12 +74,12 @@ class SIPDialOutService { 'dialout.callid' => 'callId', ]) ); - } catch (MappingError) { - throw new \InvalidArgumentException('Not a valid dial-out response'); + } catch (MappingError $e) { + throw new \InvalidArgumentException('Not a valid dial-out response', 0, $e); } if ($dialOutResponse->dialOut === null) { - throw new \InvalidArgumentException('Not a valid dial-out response'); + throw new \InvalidArgumentException('Not a valid dial-out response', 1); } return $dialOutResponse; diff --git a/tests/php/Service/SIPDialOutServiceTest.php b/tests/php/Service/SIPDialOutServiceTest.php index 7c6f58896..5f0f709bd 100644 --- a/tests/php/Service/SIPDialOutServiceTest.php +++ b/tests/php/Service/SIPDialOutServiceTest.php @@ -23,28 +23,28 @@ declare(strict_types=1); namespace OCA\Talk\Tests\php\Service; -use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\SIPDialOutService; use OCA\Talk\Signaling\BackendNotifier; use OCA\Talk\Signaling\Responses\DialOut; use OCA\Talk\Signaling\Responses\DialOutError; use OCA\Talk\Signaling\Responses\Response; use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; use Test\TestCase; class SIPDialOutServiceTest extends TestCase { - protected ParticipantService|MockObject $participantService; protected BackendNotifier|MockObject $backendNotifier; + protected LoggerInterface|MockObject $logger; private ?SIPDialOutService $service = null; public function setUp(): void { parent::setUp(); - $this->participantService = $this->createMock(ParticipantService::class); $this->backendNotifier = $this->createMock(BackendNotifier::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->service = new SIPDialOutService( - $this->participantService, $this->backendNotifier, + $this->logger, ); } |