summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2023-10-10 17:37:34 +0200
committerJoas Schilling <coding@schilljs.com>2023-10-23 15:47:59 +0200
commitfb363778aa59e5bf70cc6348a30ffbb55065820a (patch)
tree55c5e47a4c9101b147b7da2a93e08c8e4efde72a
parent50b8f4eb389feb3f47c3519e340dbcc54a483442 (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.php2
-rw-r--r--docs/call.md22
-rw-r--r--lib/Capabilities.php1
-rw-r--r--lib/Controller/CallController.php36
-rw-r--r--lib/Service/ParticipantService.php31
-rw-r--r--lib/Service/SIPDialOutService.php29
-rw-r--r--tests/php/Service/SIPDialOutServiceTest.php8
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,
);
}