Skip to content

Commit

Permalink
Voice Additions, DTMF + NCCO (#527)
Browse files Browse the repository at this point in the history
* added DTMF mode with logic

* added DTMF events subscribe and stop
  • Loading branch information
SecondeJK authored Nov 27, 2024
1 parent f4d0994 commit 945eec9
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 2 deletions.
16 changes: 16 additions & 0 deletions src/Voice/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ public function playDTMF(string $callId, string $digits): array
]);
}

public function subscribeToDtmfEventsById(string $id, array $payload): bool
{
$this->api->update($id . '/input/dtmf', [
'eventUrl' => $payload
]);

return true;
}

public function unsubscribeToDtmfEventsById(string $id): bool
{
$this->api->delete($id . '/input/dtmf');

return true;
}

/**
* @throws ClientExceptionInterface
* @throws \Vonage\Client\Exception\Exception
Expand Down
44 changes: 43 additions & 1 deletion src/Voice/NCCO/Action/Input.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Vonage\Voice\NCCO\Action;

use phpDocumentor\Reflection\Types\This;
use RuntimeException;
use Vonage\Voice\Webhook;

Expand All @@ -14,6 +15,13 @@

class Input implements ActionInterface
{
public const ASYNCHRONOUS_MODE = 'asynchronous';
public const SYNCHRONOUS_MODE = 'synchronous';

public array $allowedModes = [
self::SYNCHRONOUS_MODE,
self::ASYNCHRONOUS_MODE,
];
protected ?int $dtmfTimeout = null;

protected ?int $dtmfMaxDigits = null;
Expand All @@ -26,6 +34,8 @@ class Input implements ActionInterface

protected ?string $speechLanguage = null;

protected ?string $mode = null;

/**
* @var ?array<string>
*/
Expand Down Expand Up @@ -70,6 +80,10 @@ public static function factory(array $data): Input
}
}

if (array_key_exists('mode', $data)) {
$action->setMode($data['mode']);
}

if (array_key_exists('speech', $data)) {
$speech = $data['speech'];
$action->setEnableSpeech(true);
Expand Down Expand Up @@ -136,7 +150,10 @@ public function toNCCOArray(): array
'action' => 'input',
];

if ($this->getEnableDtmf() === false && $this->getEnableSpeech() === false) {
if (
$this->getEnableDtmf() === false && $this->getEnableSpeech() === false && $this->getMode() !==
self::ASYNCHRONOUS_MODE
) {
throw new RuntimeException('Input NCCO action must have either speech or DTMF enabled');
}

Expand Down Expand Up @@ -198,6 +215,10 @@ public function toNCCOArray(): array
$data['eventMethod'] = $eventWebhook->getMethod();
}

if ($this->getMode()) {
$data['mode'] = $this->getMode();
}

return $data;
}

Expand Down Expand Up @@ -365,4 +386,25 @@ public function setEnableDtmf(bool $enableDtmf): Input

return $this;
}

public function getMode(): ?string
{
return $this->mode;
}

public function setMode(?string $mode): self
{
if ($this->getEnableDtmf()) {
if ($mode == self::ASYNCHRONOUS_MODE) {
throw new \InvalidArgumentException('Cannot have DTMF input when using Asynchronous mode.');
}
}

if (!in_array($mode, $this->allowedModes)) {
throw new \InvalidArgumentException('Mode not a valid string');
}

$this->mode = $mode;
return $this;
}
}
44 changes: 44 additions & 0 deletions test/Voice/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,50 @@ public function testCanPlayTTSIntoCall(): void
$this->assertEquals('Talk started', $response['message']);
}

public function testCanSubscribeToDtmfEvents(): void
{
$id = '63f61863-4a51-4f6b-86e1-46edebcf9356';

$payload = [
'https://example.com/events'
];

$this->vonageClient->send(Argument::that(function (RequestInterface $request) use ($id) {
$uri = $request->getUri();
$uriString = $uri->__toString();
$this->assertEquals(
'https://api.nexmo.com/v1/calls/63f61863-4a51-4f6b-86e1-46edebcf9356/input/dtmf',
$uriString
);
$this->assertEquals('PUT', $request->getMethod());

$this->assertRequestJsonBodyContains('eventUrl', ['https://example.com/events'], $request);

return true;
}))->willReturn($this->getResponse('dtmf-subscribed'));

$this->voiceClient->subscribeToDtmfEventsById($id, $payload);
}

public function testCanUnsubscribeToDtmfEvents(): void
{
$id = '63f61863-4a51-4f6b-86e1-46edebcf9356';

$this->vonageClient->send(Argument::that(function (RequestInterface $request) use ($id) {
$uri = $request->getUri();
$uriString = $uri->__toString();
$this->assertEquals(
'https://api.nexmo.com/v1/calls/63f61863-4a51-4f6b-86e1-46edebcf9356/input/dtmf',
$uriString
);
$this->assertEquals('DELETE', $request->getMethod());

return true;
}))->willReturn($this->getResponse('dtmf-unsubscribed'));

$this->voiceClient->unsubscribeToDtmfEventsById($id);
}

/**
* @throws ClientExceptionInterface
* @throws Client\Exception\Exception
Expand Down
72 changes: 71 additions & 1 deletion test/Voice/NCCO/Action/InputTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,76 @@ public function testThrowsRuntimeExceptionIfNoInputDefined(): void
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Input NCCO action must have either speech or DTMF enabled');

(new Input())->toNCCOArray();
$input = new Input();
$array = $input->toNCCOArray();
}

public function testCanCreateInputSyncNCCOCorrectly(): void
{
$data = [
'action' => 'input',
'eventUrl' => ['https://test.domain/events'],
'dtmf' => [
'maxDigits' => 4,
],
'mode' => 'synchronous'
];

$action = Input::factory($data);
$ncco = $action->toNCCOArray();

$this->assertSame($data['dtmf']['maxDigits'], $action->getDtmfMaxDigits());
$this->assertSame($data['dtmf']['maxDigits'], $ncco['dtmf']->maxDigits);
$this->assertSame($data['mode'], $ncco['mode']);
$this->assertSame('POST', $action->getEventWebhook()->getMethod());
$this->assertSame($data['mode'], $action->getMode());
}

public function testCanCreateInputAsyncNCCOCorrectly(): void
{
$data = [
'action' => 'input',
'eventUrl' => ['https://test.domain/events'],
'mode' => 'asynchronous'
];

$action = Input::factory($data);
$ncco = $action->toNCCOArray();

$this->assertSame($data['mode'], $ncco['mode']);
$this->assertSame('POST', $action->getEventWebhook()->getMethod());
$this->assertSame($data['mode'], $action->getMode());
}

public function testCannotCreateInputNCCOWithDtmfAndAsyncMode(): void
{
$this->expectException(\InvalidArgumentException::class);

$data = [
'action' => 'input',
'eventUrl' => ['https://test.domain/events'],
'dtmf' => [
'maxDigits' => 4,
],
'mode' => 'asynchronous'
];

$action = Input::factory($data);
}

public function testErrorsOnInvalidInput(): void
{
$this->expectException(\InvalidArgumentException::class);

$data = [
'action' => 'input',
'eventUrl' => ['https://test.domain/events'],
'dtmf' => [
'maxDigits' => 4,
],
'mode' => 'syncronus'
];

$action = Input::factory($data);
}
}
Empty file.
Empty file.

0 comments on commit 945eec9

Please sign in to comment.