From 72f9472ee5d5d8ec336546427fc2d4f8d2847e35 Mon Sep 17 00:00:00 2001 From: Chuck MANCHUCK Reeves Date: Mon, 6 Jan 2025 16:48:21 -0500 Subject: [PATCH] feat: added new DTMF endpoints --- .../voice/__tests__/__dataSets__/delete.ts | 19 ++++++ .../voice/__tests__/__dataSets__/index.ts | 5 ++ .../voice/__tests__/__dataSets__/update.ts | 59 +++++++++++++++++++ packages/voice/lib/classes/NCCO/Input.ts | 17 +++++- .../voice/lib/classes/NCCO/NCCOBuilder.ts | 2 +- packages/voice/lib/types/NCCO/InputAction.ts | 12 +++- packages/voice/lib/voice.ts | 49 ++++++++++++++- 7 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 packages/voice/__tests__/__dataSets__/delete.ts diff --git a/packages/voice/__tests__/__dataSets__/delete.ts b/packages/voice/__tests__/__dataSets__/delete.ts new file mode 100644 index 00000000..05677ca2 --- /dev/null +++ b/packages/voice/__tests__/__dataSets__/delete.ts @@ -0,0 +1,19 @@ +import { callPhone } from '../common'; + +export default [ + { + label: 'unsubscribe to DTMF events', + requests: [ + [ + `/v1/calls/${callPhone.uuid}/input/dtmf`, + 'DELETE', + ], + ], + responses: [[200]], + clientMethod: 'unsubscribeDTMF', + parameters: [callPhone.uuid], + generator: false, + error: false, + expected: undefined, + }, +]; diff --git a/packages/voice/__tests__/__dataSets__/index.ts b/packages/voice/__tests__/__dataSets__/index.ts index dc5cff4b..6fc5cfa5 100644 --- a/packages/voice/__tests__/__dataSets__/index.ts +++ b/packages/voice/__tests__/__dataSets__/index.ts @@ -1,6 +1,7 @@ import callTests from './calls'; import createTests from './create'; import updateTests from './update'; +import deleteTests from './delete'; export default [ { @@ -15,4 +16,8 @@ export default [ label: 'Update call', tests: updateTests, }, + { + label: 'Delete call', + tests: deleteTests, + }, ]; diff --git a/packages/voice/__tests__/__dataSets__/update.ts b/packages/voice/__tests__/__dataSets__/update.ts index 26cd646e..8d5b1a94 100644 --- a/packages/voice/__tests__/__dataSets__/update.ts +++ b/packages/voice/__tests__/__dataSets__/update.ts @@ -333,6 +333,47 @@ export default [ error: false, expected: undefined, }, + { + label: 'transfer call with NCCO Input DTMF Action', + requests: [ + [ + `/v1/calls/${callPhone.uuid}`, + 'PUT', + { + action: 'transfer', + destination: { + type: 'ncco', + ncco: [ + { + action: NCCOActions.INPUT, + dtmf: { + digits: '1234', + }, + mode: 'asynchronous', + }, + ], + }, + }, + ], + ], + responses: [[204]], + clientMethod: 'transferCallWithNCCO', + parameters: [ + callPhone.uuid, + [ + { + action: NCCOActions.INPUT, + dtmf: { + digits: '1234', + }, + mode: 'asynchronous', + }, + ], + ], + generator: false, + error: false, + expected: undefined, + }, { label: 'hangup call', requests: [ @@ -405,4 +446,22 @@ export default [ error: false, expected: undefined, }, + { + label: 'subscribe to DTMF events', + requests: [ + [ + `/v1/calls/${callPhone.uuid}/input/dtmf`, + 'PUT', + { + event_url: ['https://example.com/dtmf'], + }, + ], + ], + responses: [[200]], + clientMethod: 'subscribeDTMF', + parameters: [callPhone.uuid, 'https://example.com/dtmf'], + generator: false, + error: false, + expected: undefined, + }, ]; diff --git a/packages/voice/lib/classes/NCCO/Input.ts b/packages/voice/lib/classes/NCCO/Input.ts index bb454a5f..3c841841 100644 --- a/packages/voice/lib/classes/NCCO/Input.ts +++ b/packages/voice/lib/classes/NCCO/Input.ts @@ -7,7 +7,7 @@ import { Serializable } from '../../ncco'; /** * Represents an Input action in the Nexmo Call Control Object (NCCO) for gathering user input. */ -export class Input implements InputAction, Serializable { +export class Input implements Pick, Serializable { /** * The action type, which is always 'input'. */ @@ -38,6 +38,16 @@ export class Input implements InputAction, Serializable { */ eventMethod?: string; + /** + * Input processing mode, currently only applicable to DTMF. Valid values are + * synchronous (the default) and asynchronous. If set to asynchronous, all + * DTMF settings must be left blank. In asynchronous mode, digits are sent one + * at a time to the event webhook in real time. In the default synchronous + * mode, this is controlled by the DTMF settings instead and the inputs are + * sent in batch. + */ + mode?: 'asynchronous' | 'syncronous'; + /** * Create a new Input instance. * @@ -51,6 +61,7 @@ export class Input implements InputAction, Serializable { speech?: SpeechSettings, eventUrl?: string, eventMethod?: string, + mode?: 'asynchronous' | 'synchronous', ) { if (dtmf) { this.type.push('dtmf'); @@ -70,6 +81,10 @@ export class Input implements InputAction, Serializable { this.eventMethod = eventMethod; } + if (mode) { + this.mode = mode; + } + if (this.type.length === 0) { throw new TypeError( 'Input action must have at least either DTMF or Speech settings', diff --git a/packages/voice/lib/classes/NCCO/NCCOBuilder.ts b/packages/voice/lib/classes/NCCO/NCCOBuilder.ts index e4d7c817..e72a7be1 100644 --- a/packages/voice/lib/classes/NCCO/NCCOBuilder.ts +++ b/packages/voice/lib/classes/NCCO/NCCOBuilder.ts @@ -16,7 +16,7 @@ export class NCCOBuilder { public addAction(action: NCCOAction): NCCOBuilder { this.actions.push( ('serializeToNCCO' in action - ? (action as Serializable).serializeToNCCO() + ? (action as unknown as Serializable).serializeToNCCO() : action ) as NCCOAction, ); diff --git a/packages/voice/lib/types/NCCO/InputAction.ts b/packages/voice/lib/types/NCCO/InputAction.ts index ee703be9..927f5fe4 100644 --- a/packages/voice/lib/types/NCCO/InputAction.ts +++ b/packages/voice/lib/types/NCCO/InputAction.ts @@ -42,4 +42,14 @@ export type InputAction = { * Valid values are 'GET' and 'POST'. */ eventMethod?: string; -}; + + /** + * Input processing mode, currently only applicable to DTMF. Valid values are + * synchronous (the default) and asynchronous. If set to asynchronous, all + * DTMF settings must be left blank. In asynchronous mode, digits are sent one + * at a time to the event webhook in real time. In the default synchronous + * mode, this is controlled by the DTMF settings instead and the inputs are + * sent in batch. + */ + mode?: 'asynchronous' | 'synchronous'; +} | Record; diff --git a/packages/voice/lib/voice.ts b/packages/voice/lib/voice.ts index 9d0333a8..e169dfbc 100644 --- a/packages/voice/lib/voice.ts +++ b/packages/voice/lib/voice.ts @@ -13,6 +13,7 @@ import { OutboundCall, CallWithNCCO, SIPEndpoint, + CallEndpoint, } from './types'; import { ResponseTypes } from '@vonage/vetch'; @@ -37,7 +38,7 @@ const NCCOToApiCalls = (ncco: Action[]): Array => ncco.map((action) => { case NCCOActions.CONNECT: return { ...action, - endpoint: action.endpoint.map((endpoint) => { + endpoint: (action.endpoint as Array)?.map((endpoint) => { switch (endpoint.type) { case 'sip': return { @@ -331,6 +332,48 @@ export class Voice extends Client { ) as CallUpdateResult; } + /** + * Register a listener to receive asynchronous DTMF inputs from a call + * + * This is only applicable to Input NCCO events with the mode set to + * asynchronous. The payload delivered to this URL will be an Input webhook + * event with a single DTMF digit every time the callee enters DTMF into the + * call. + * + * @param {string} uuid - The UUID of the call leg + * @param {string} eventUrl - The The URL to send DTMF events to, as a POST request. + * @return {Promise} A promise that resolves to the result + * + * @example + * ```ts + * const result = await voiceClient.subscribeDTMF('CALL_UUID', 'https://example.com/dtmf'); + * console.log(result.status); + * ``` + */ + async subscribeDTMF(uuid: string, eventUrl: string): Promise { + await this.sendPutRequest( + `${this.config.apiHost}/v1/calls/${uuid}/input/dtmf`, + { event_url: [eventUrl]}, + ); + } + + /** + * Removes the registered DTMF listener + * @param {string} uuid - The UUID of the call leg + * @return {Promise} A promise that resolves to the result + * + * @example + * ```ts + * const result = await voiceClient.subscribeDTMF('CALL_UUID', 'https://example.com/dtmf'); + * console.log(result.status); + * ``` + */ + async unsubscribeDTMF(uuid: string): Promise { + await this.sendDeleteRequest( + `${this.config.apiHost}/v1/calls/${uuid}/input/dtmf`, + ); + } + /** * Plays text-to-speech (TTS) audio on an active call. * @@ -582,7 +625,7 @@ export class Voice extends Client { /** * Download the recording of a call to the specified file path. * - * @param {string} file - The name of the recording file to download. + * @param {string} file - The name or recording id of the recording file to download. * @param {string} path - The local file path where the recording will be saved. * @return {Promise} A promise that resolves when the recording has been successfully downloaded. * @@ -602,7 +645,7 @@ export class Voice extends Client { /** * Download the transcription of a call to the specified file path. * - * @param {string} file - The name of the transcription file to download. + * @param {string} file - The name or transcription id of the recording file to download. * @param {string} path - The local file path where the transcription will be saved. * @return {Promise} A promise that resolves when the transcription has been successfully downloaded. *