diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fa7fd..5c82c33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Change Log +## [4.16.0](https://github.com/plivo/plivo-node/releases/tag/v4.16.0)(2021-04-19) +- Added SDK support for Voice MultiPartyCall APIs and XML + ## [4.15.0](https://github.com/plivo/plivo-node/releases/tag/v4.15.0)(2021-04-19) - Add support for Regulatory Compliance APIs. - Add "npanxx" and "local_calling_area" support for Search Phone Number. diff --git a/lib/base.js b/lib/base.js index 3ed6040..d6ba0d7 100644 --- a/lib/base.js +++ b/lib/base.js @@ -4,6 +4,9 @@ let actionKey = Symbol('api action'); let klassKey = Symbol('constructor'); let idKey = Symbol('id filed'); let clientKey = Symbol('make api call'); +let secondaryActionKey = Symbol('api action'); +let secondaryKlassKey = Symbol('constructor'); +let secondaryIdKey = Symbol('id filed'); export class PlivoGenericResponse { constructor(params, idString) { @@ -123,6 +126,36 @@ export class PlivoResource { } } +export class PlivoSecondaryResource { + constructor(action, klass, idField, secondaryAction, secondaryKlass, secondaryIdField, request) { + this[actionKey] = action; + this[klassKey] = klass; + this[idKey] = idField; + this[clientKey] = request; + this[secondaryActionKey] = secondaryAction; + this[secondaryKlassKey] = secondaryKlass; + this[secondaryIdKey] = secondaryIdField; + } + + executeAction(task = '', secondaryTask = '', method = 'GET', params = {}, action, secondaryAction) { + let client = this[clientKey]; + action = action == null ? this[actionKey] : action; + let idField = this[idKey]; + secondaryAction = secondaryAction == null ? this[secondaryActionKey] : secondaryAction; + let secondaryIdField = this[secondaryIdKey] + + return new Promise((resolve, reject) => { + client(method, action + task + '/' + secondaryAction + secondaryTask + '/', params) + .then(response => { + resolve(new PlivoGenericResponse(response.body, secondaryIdField)); + }) + .catch(error => { + reject(error); + }); + }); + } +} + export class PlivoResourceInterface { constructor(action, klass, idField, request) { this[actionKey] = action; diff --git a/lib/resources/applications.js b/lib/resources/applications.js index 7d653f5..8f01b3a 100644 --- a/lib/resources/applications.js +++ b/lib/resources/applications.js @@ -327,4 +327,4 @@ export class ApplicationInterface extends PlivoResourceInterface { id: id }).delete(params, id); } -} \ No newline at end of file +} diff --git a/lib/resources/multiPartyCall.js b/lib/resources/multiPartyCall.js new file mode 100644 index 0000000..0ffe665 --- /dev/null +++ b/lib/resources/multiPartyCall.js @@ -0,0 +1,592 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface, PlivoSecondaryResource} from '../base'; +import { + validSubAccount, + validUrl, + validParam, + validDateFormat, + validRange, + validMultipleDestinationNos, + isOneAmongStringUrl, multiValidParam +} from '../rest/utils.js' + +const clientKey = Symbol(); +const action = 'MultiPartyCall/'; +const idField = 'mpcUuid'; +const secondaryAction = 'Participant/'; +const secondaryIdField = 'participantUuid'; + +export class MPCError extends Error { } + +export class MultiPartyCall extends PlivoResource{ + constructor(client, data = {}) { + super(action, MultiPartyCall, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + this[clientKey] = client; + } + + get(params = {}){ + params.isVoiceRequest = 'true'; + return super.executeAction(this.id, 'GET', params); + } + + addParticipant(params){ + if((params.from && params.to) && (params.callUuid)){ + throw new MPCError('cannot specify callUuid when (from, to) is provided') + } + if((!params.from && !params.to) && !params.callUuid){ + throw new MPCError('specify either callUuid or (from, to)') + } + if((!params.callUuid) && (!params.from || !params.to)){ + throw new MPCError('specify (from, to) when not adding an existing callUuid to multi party participant') + } + + validParam('role', params.role.toLowerCase(), [String], true, ['agent', 'supervisor', 'customer']) + + if(params.from){ + validParam('from', params.from, [String], false) + } + + if(params.to){ + validParam('to', params.to, [String], false) + validMultipleDestinationNos('to', params.to, {role: params.role, delimiter: '<', agentLimit: 20}) + } + + if(params.callUuid){ + validParam('callUuid', params.callUuid, [String], false) + } + + if(params.callStatusCallbackUrl){ + validUrl('callStatusCallbackUrl', params.callStatusCallbackUrl, false) + } + + if(params.callStatusCallbackMethod){ + validParam('callStatusCallbackMethod', params.callStatusCallbackMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else{ + params.callStatusCallbackMethod = 'POST' + } + + if(params.sipHeaders){ + validParam('sipHeaders', params.sipHeaders, [String], false) + } + + if(params.confirmKey){ + validParam('confirmKey', params.confirmKey, [String], false, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*']) + } + + if(params.confirmKeySoundUrl){ + validUrl('confirmKeySoundUrl', params.confirmKeySoundUrl, false) + } + + if(params.confirmKeySoundMethod){ + validParam('confirmKeySoundMethod)', params.confirmKeySoundMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else{ + params.confirmKeySoundMethod = 'GET' + } + + if(params.dialMusic){ + isOneAmongStringUrl('dialMusic', params.dialMusic, false, ['real', 'none']) + } + else { + params.dialMusic = 'Real' + } + + if(params.ringTimeout || params.ringTimeout === 0){ + validRange('ringTimeout', params.ringTimeout, false, 15, 120) + } + else { + params.ringTimeout = 45 + } + + if(params.maxDuration || params.maxDuration === 0){ + validRange('maxDuration', params.maxDuration, false, 300, 28800) + } + else { + params.maxDuration = 14400 + } + + if(params.maxParticipants || params.maxParticipants === 0){ + validRange('maxParticipants', params.maxParticipants, false, 2, 10) + } + else { + params.maxParticipants = 10 + } + + if(params.waitMusicUrl){ + validUrl('waitMusicUrl', params.waitMusicUrl, false) + } + + if(params.waitMusicMethod){ + validParam('waitMusicMethod', params.waitMusicMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.waitMusicMethod = 'GET' + } + + if(params.agentHoldMusicUrl){ + validUrl('agentHoldMusicUrl', params.agentHoldMusicUrl, false) + } + + if(params.agentHoldMusicMethod){ + validParam('agentHoldMusicMethod', params.agentHoldMusicMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.agentHoldMusicMethod = 'GET' + } + + if(params.customerHoldMusicUrl){ + validUrl('customerHoldMusicUrl', params.customerHoldMusicUrl, false) + } + + if(params.customerHoldMusicMethod){ + validParam('customerHoldMusicMethod', params.customerHoldMusicMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.customerHoldMusicMethod = 'GET' + } + + if(params.recordingCallbackUrl){ + validUrl('recordingCallbackUrl', params.recordingCallbackUrl, false) + } + + if(params.recordingCallbackMethod){ + validParam('recordingCallbackMethod', params.recordingCallbackMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.recordingCallbackMethod = 'GET' + } + + if(params.statusCallbackUrl){ + validUrl('statusCallbackUrl', params.statusCallbackUrl, false) + } + + if(params.statusCallbackMethod){ + validParam('statusCallbackMethod', params.statusCallbackMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.statusCallbackMethod = 'GET' + } + + if(params.onExitActionUrl){ + validUrl('onExitActionUrl', params.onExitActionUrl, false) + } + + if(params.onExitActionMethod){ + validParam('statusCallbackMethod', params.statusCallbackMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.onExitActionMethod = 'POST' + } + + if(params.record){ + validParam('record', params.record, [Boolean, String], false) + } + else { + params.record = 'false' + } + + if(params.recordFileFormat){ + validParam('recordFileFormat', params.recordFileFormat.toLowerCase(), [String], false, ['mp3', 'wav']) + } + else { + params.recordFileFormat = 'mp3' + } + + if(params.statusCallbackEvents){ + multiValidParam('statusCallbackEvents', params.statusCallbackEvents.toLowerCase(), [String], false, ['mpc-state-changes', 'participant-state-changes', 'participant-speak-events', 'participant-digit-input-events', 'add-participant-api-events'], true,',') + } + else { + params.statusCallbackEvents = 'mpc-state-changes,participant-state-changes' + } + + if(params.stayAlone){ + validParam('stayAlone', params.stayAlone, [Boolean, String], false) + } + else { + params.stayAlone = 'false' + } + + if(params.coachMode){ + validParam('coachMode', params.coachMode, [Boolean, String], false) + } + else { + params.coachMode = 'true' + } + + if(params.mute){ + validParam('mute', params.mute, [Boolean, String], false) + } + else { + params.mute = 'false' + } + + if(params.hold){ + validParam('hold', params.hold, [Boolean, String], false) + } + else { + params.hold = 'false' + } + + if(params.startMpcOnEnter){ + validParam('startMpcOnEnter', params.startMpcOnEnter, [Boolean, String], false) + } + else { + params.startMpcOnEnter = 'true' + } + + if(params.endMpcOnExit){ + validParam('endMpcOnExit', params.endMpcOnExit, [Boolean, String], false) + } + else { + params.endMpcOnExit = 'false' + } + + if(params.relayDTMFInputs){ + validParam('relayDTMFInputs', params.relayDTMFInputs, [Boolean, String], false) + } + else { + params.relayDTMFInputs = 'false' + } + + if(params.enterSound){ + isOneAmongStringUrl('enterSound', params.enterSound, false, ['beep:1', 'beep:2', 'none']) + } + else { + params.enterSound = 'beep:1' + } + + if(params.enterSoundMethod){ + validParam('enterSoundMethod', params.enterSoundMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.enterSoundMethod = 'GET' + } + + if(params.exitSound){ + isOneAmongStringUrl('exitSound', params.exitSound, false, ['beep:1', 'beep:2', 'none']) + } + else { + params.exitSound = 'beep:2' + } + + if(params.exitSoundMethod){ + validParam('exitSoundMethod', params.exitSoundMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.exitSoundMethod = 'GET' + } + params.isVoiceRequest = 'true'; + return super.executeAction(this.id + '/Participant/', 'POST', params) + } + + start(){ + return super.executeAction(this.id + '/', 'POST', {'status' : 'active', 'isVoiceRequest' : 'true'}) + } + + stop(){ + return super.delete({'isVoiceRequest' : 'true'}) + } + + startRecording(params = {}){ + if(params.fileFormat){ + validParam('fileFormat', params.fileFormat, [String], false, ['mp3', 'wav']) + } + else { + params.fileFormat = 'mp3' + } + + if(params.statusCallbackUrl){ + validUrl('statusCallbackUrl', params.statusCallbackUrl, false) + } + + if(params.statusCallbackMethod){ + validParam('statusCallbackMethod', params.statusCallbackMethod.toUpperCase(), [String], false, ['GET', 'POST']) + } + else { + params.statusCallbackMethod = 'POST' + } + params.isVoiceRequest = 'true'; + return super.executeAction(this.id + '/Record/', 'POST', params) + } + + stopRecording(){ + return super.executeAction(this.id + '/Record/', 'DELETE',{'isVoiceRequest' : 'true'}) + } + + pauseRecording(){ + return super.executeAction(this.id + '/Record/Pause/', 'POST',{'isVoiceRequest' : 'true'}) + } + + resumeRecording(){ + return super.executeAction(this.id + '/Record/Resume/', 'POST',{'isVoiceRequest' : 'true'}) + } + + listParticipants(params = {}){ + if(params.callUuid){ + validParam('callUuid', params.callUuid, [String], false) + } + params.isVoiceRequest = 'true'; + return super.executeAction(this.id + '/Participant/', 'GET', params) + } +} + +export class MultiPartyCallParticipant extends PlivoSecondaryResource{ + constructor(client, data = {}) { + super(action, MultiPartyCall, idField, secondaryAction, MultiPartyCallParticipant, secondaryIdField, client); + + if (idField in data) { + this.id = data[idField]; + } + + if(secondaryIdField in data){ + this.secondaryId = data[secondaryIdField]; + } + + extend(this, data); + this[clientKey] = client; + } + + updateParticipant(params = {}){ + if(params.coachMode){ + validParam('coachMode', params.coachMode, [Boolean, String], false) + } + + if(params.mute){ + validParam('mute', params.mute, [Boolean, String], false) + } + + if(params.hold){ + validParam('hold', params.hold, [Boolean, String], false) + } + params.isVoiceRequest = 'true'; + return super.executeAction(this.id, this.secondaryId, 'POST', params) + } + + kickParticipant(){ + return super.executeAction(this.id, this.secondaryId, 'DELETE',{'isVoiceRequest' : 'true'}) + } + + getParticipant(){ + return super.executeAction(this.id, this.secondaryId, 'GET',{'isVoiceRequest' : 'true'}) + } + +} + +export class MultiPartyCallInterface extends PlivoResourceInterface{ + constructor(client, data = {}) { + super(action, MultiPartyCall, idField, client); + extend(this, data); + + this[clientKey] = client; + } + + makeMpcId(uuid = null, friendlyName = null){ + if(!uuid && !friendlyName){ + throw new MPCError('Specify either multi party call friendly name or uuid') + } + if(uuid && friendlyName){ + throw new MPCError('Cannot specify both multi party call friendly name or uuid') + } + let identifier = '' + if(uuid){ + identifier = ['uuid_', uuid] + } + else{ + identifier = ['name_', friendlyName] + } + return identifier; + } + + list(params={}) { + if(params.subAccount){ + validSubAccount(params.subAccount); + } + if(params.friendlyName){ + validParam('friendlyName', params.friendlyName, [String], false) + } + if(params.status){ + validParam('status', params.status.toLowerCase(), [String], false, ['initialized', 'active', 'ended']) + } + if(params.terminationCauseCode){ + validParam('terminationCauseCode', params.terminationCauseCode, [Number, String], false) + } + if(params.end_time__gt){ + validDateFormat('end_time__gt', params.end_time__gt, false) + } + if(params.end_time__gte){ + validDateFormat('end_time__gte', params.end_time__gte, false) + } + if(params.end_time__lt){ + validDateFormat('end_time__lt', params.end_time__lt, false) + } + if(params.end_time__lte){ + validDateFormat('end_time__lte', params.end_time__lte, false) + } + if(params.creation_time__gt){ + validDateFormat('creation_time__gt', params.creation_time__gt, false) + } + if(params.creation_time__gte){ + validDateFormat('creation_time__gte', params.creation_time__gte, false) + } + if(params.creation_time__lt){ + validDateFormat('creation_time__lt', params.creation_time__lt, false) + } + if(params.creation_time__lte){ + validDateFormat('creation_time__lte', params.creation_time__lte, false) + } + if(params.limit){ + validRange('limit', params.limit, false, 1, 20) + } + if(params.offset){ + validRange('offset', params.offset, false, 0) + } + params.isVoiceRequest = 'true'; + return super.list(params); + } + + get(uuid = null, friendlyName = null){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).get(); + } + + addParticipant(role, params = {}){ + if(params.uuid){ + validParam('uuid', params.uuid, [String], false) + } + if(params.friendlyName){ + validParam('friendlyName', params.friendlyName, [String], false) + } + let mpcId = this.makeMpcId(params.uuid, params.friendlyName) + delete params.uuid + delete params.friendlyName + params.role = role + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).addParticipant(params) + } + + start(uuid = null, friendlyName = null){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).start() + } + + stop(uuid = null, friendlyName = null){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).stop() + } + + startRecording(uuid = null, friendlyName = null, params){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).startRecording(params) + } + + stopRecording(uuid = null, friendlyName = null){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).stopRecording() + } + + pauseRecording(uuid = null, friendlyName = null){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).pauseRecording() + } + + resumeRecording(uuid = null, friendlyName = null){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).resumeRecording() + } + + listParticipants(uuid = null, friendlyName = null, params){ + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCall(this[clientKey], {id: mpcId[0] + mpcId[1]}).listParticipants(params) + } + + updateParticipant(participantId, uuid= null, friendlyName = null, params){ + validParam('participantId', participantId, [String, Number], true) + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCallParticipant(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).updateParticipant(params) + } + + kickParticipant(participantId, uuid = null, friendlyName = null){ + validParam('participantId', participantId, [String, Number], true) + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCallParticipant(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).kickParticipant() + } + + getParticipant(participantId, uuid = null, friendlyName = null){ + validParam('participantId', participantId, [String, Number], true) + if(uuid){ + validParam('uuid', uuid, [String], false) + } + if(friendlyName){ + validParam('friendlyName', friendlyName, [String], false) + } + let mpcId = this.makeMpcId(uuid, friendlyName) + return new MultiPartyCallParticipant(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).getParticipant() + } + +} diff --git a/lib/rest/client-test.js b/lib/rest/client-test.js index bf709ed..848c901 100644 --- a/lib/rest/client-test.js +++ b/lib/rest/client-test.js @@ -48,6 +48,7 @@ import { import { MediaInterface } from '../resources/media.js'; +import {MultiPartyCallInterface} from "../resources/multiPartyCall"; export class Client { constructor(authId, authToken, proxy) { @@ -95,6 +96,7 @@ export class Client { this.complianceDocuments = new ComplianceDocumentInterface(client); this.complianceRequirements = new ComplianceRequirementInterface(client); this.complianceApplications = new ComplianceApplicationInterface(client); + this.multiPartyCalls = new MultiPartyCallInterface(client); } } diff --git a/lib/rest/client.js b/lib/rest/client.js index 60a8fce..492c32d 100644 --- a/lib/rest/client.js +++ b/lib/rest/client.js @@ -25,6 +25,7 @@ import { ComplianceDocumentTypeInterface } from "../resources/complianceDocument import { ComplianceDocumentInterface} from "../resources/complianceDocuments"; import { ComplianceRequirementInterface } from "../resources/complianceRequirements"; import { ComplianceApplicationInterface } from "../resources/complianceApplications"; +import {MultiPartyCallInterface} from "../resources/multiPartyCall"; exports.Response = function() { return new Response(); @@ -92,6 +93,7 @@ export class Client { this.complianceDocuments = new ComplianceDocumentInterface(client); this.complianceRequirements = new ComplianceRequirementInterface(client); this.complianceApplications = new ComplianceApplicationInterface(client); + this.multiPartyCalls = new MultiPartyCallInterface(client); } toJSON() { diff --git a/lib/rest/request-test.js b/lib/rest/request-test.js index be2e21d..9f60e97 100644 --- a/lib/rest/request-test.js +++ b/lib/rest/request-test.js @@ -583,6 +583,306 @@ export function Request(config) { }); } + // ============= MultiPartyCalls =============== + else if (method === 'GET' && action === 'MultiPartyCall/'){ + resolve({ + response: {}, + body: { + "api_id": "d53ab14c-eddb-11ea-b02e-0242ac110003", + "meta": { + "count": 6, + "limit": 20, + "next": null, + "offset": 0, + "previous": null + }, + "objects": [ + { + "billed_amount": "0.00500", + "billed_duration": 60, + "creation_time": "2020-08-31 15:12:03+00:00", + "duration": 3, + "end_time": "2020-08-31 15:12:06+00:00", + "friendly_name": "TestMPC", + "mpc_uuid": "ca8e8a44-48e1-445d-afd5-1fcccdbccd9d", + "participants": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_ca8e8a44-48e1-445d-afd5-1fcccdbccd9d/Participant/", + "recording": null, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_ca8e8a44-48e1-445d-afd5-1fcccdbccd9d/", + "start_time": null, + "status": "Ended", + "stay_alone": false, + "sub_account": null, + "termination_cause": "No Active Participants", + "termination_cause_code": 1000 + }, + { + "billed_amount": "0.00500", + "billed_duration": 60, + "creation_time": "2020-08-31 14:32:40+00:00", + "duration": 5, + "end_time": "2020-08-31 14:32:45+00:00", + "friendly_name": "TestMPC", + "mpc_uuid": "9b531a1f-1692-4802-a7d6-3ef25bcfe3fc", + "participants": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_9b531a1f-1692-4802-a7d6-3ef25bcfe3fc/Participant/", + "recording": null, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_9b531a1f-1692-4802-a7d6-3ef25bcfe3fc/", + "start_time": null, + "status": "Ended", + "stay_alone": false, + "sub_account": null, + "termination_cause": "No Active Participants", + "termination_cause_code": 1000 + }, + { + "billed_amount": "0.01000", + "billed_duration": 120, + "creation_time": "2020-08-31 14:32:11+00:00", + "duration": 11, + "end_time": "2020-08-31 14:32:22+00:00", + "friendly_name": "TestMPC", + "mpc_uuid": "6f84d47c-ee82-4172-a155-c6e22f87d874", + "participants": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_6f84d47c-ee82-4172-a155-c6e22f87d874/Participant/", + "recording": null, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_6f84d47c-ee82-4172-a155-c6e22f87d874/", + "start_time": null, + "status": "Ended", + "stay_alone": false, + "sub_account": null, + "termination_cause": "Stay Alone Not Permitted", + "termination_cause_code": 1010 + }, + { + "billed_amount": "0.00500", + "billed_duration": 60, + "creation_time": "2020-08-31 14:31:20+00:00", + "duration": 3, + "end_time": "2020-08-31 14:31:23+00:00", + "friendly_name": "TestMPC", + "mpc_uuid": "0746f6c6-7447-4e0a-9013-186e4220aaf4", + "participants": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_0746f6c6-7447-4e0a-9013-186e4220aaf4/Participant/", + "recording": null, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_0746f6c6-7447-4e0a-9013-186e4220aaf4/", + "start_time": "2020-08-31 14:31:20+00:00", + "status": "Ended", + "stay_alone": false, + "sub_account": null, + "termination_cause": "No Active Participants", + "termination_cause_code": 1000 + }, + { + "billed_amount": "0.00500", + "billed_duration": 60, + "creation_time": "2020-08-31 06:42:50+00:00", + "duration": 36, + "end_time": "2020-08-31 06:43:26+00:00", + "friendly_name": "TestMPC", + "mpc_uuid": "b89150fd-0387-4bf8-bde7-a4fed39601ce", + "participants": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_b89150fd-0387-4bf8-bde7-a4fed39601ce/Participant/", + "recording": null, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_b89150fd-0387-4bf8-bde7-a4fed39601ce/", + "start_time": "2020-08-31 06:42:50+00:00", + "status": "Ended", + "stay_alone": false, + "sub_account": null, + "termination_cause": "No Active Participants", + "termination_cause_code": 1000 + }, + { + "billed_amount": "0.00500", + "billed_duration": 60, + "creation_time": "2020-08-28 17:30:10+00:00", + "duration": 2, + "end_time": "2020-08-28 17:30:12+00:00", + "friendly_name": "tank", + "mpc_uuid": "2999c70d-b635-420f-b6f2-2fd4421f0381", + "participants": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_2999c70d-b635-420f-b6f2-2fd4421f0381/Participant/", + "recording": null, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_2999c70d-b635-420f-b6f2-2fd4421f0381/", + "start_time": "2020-08-28 17:30:10+00:00", + "status": "Ended", + "stay_alone": true, + "sub_account": null, + "termination_cause": "No Active Participants", + "termination_cause_code": 1000 + } + ] + } + }); + } + + else if (method === 'GET' && action === 'MultiPartyCall/uuid_ca8e8a44-48e1-445d-afd5-1fcccdbccd9d'){ + resolve({ + response: {}, + body: { + "api_id": "8970c2b3-edfb-11ea-b02e-0242ac110003", + "billed_amount": "0.00500", + "billed_duration": 60, + "creation_time": "2020-08-31 15:12:03+00:00", + "duration": 3, + "end_time": "2020-08-31 15:12:06+00:00", + "friendly_name": "TestMPC", + "mpc_uuid": "ca8e8a44-48e1-445d-afd5-1fcccdbccd9d", + "participants": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_ca8e8a44-48e1-445d-afd5-1fcccdbccd9d/Participant/", + "recording": null, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_ca8e8a44-48e1-445d-afd5-1fcccdbccd9d/", + "start_time": null, + "status": "Ended", + "stay_alone": false, + "sub_account": null, + "termination_cause": "No Active Participants", + "termination_cause_code": 1000 + } + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_Voice/Participant/' && params.role === 'Agent' && params.from === '+919090909090' && params.to === '+918309866821'){ + resolve({ + response: {}, + body: { + "api_id": "1cebd713-ee00-11ea-b02e-0242ac110003", + "calls": [ + { + "to": "sip:koushikqa119062465586783372208@phone-qa.voice.plivodev.com", + "from": "918888888888", + "call_uuid": "c0267574-5c12-4861-8990-da9404c8cdf6" + } + ], + "message": "add participant action initiated", + "request_uuid": "c0267574-5c12-4861-8990-da9404c8cdf6" + } + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_Voice/' && params.status === 'active'){ + resolve({ + response: {}, + body: {} + }); + } + + else if (method === 'DELETE' && action === 'MultiPartyCall/name_Voice/'){ + resolve({ + response: {}, + body: {} + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Record/'){ + resolve({ + response: {}, + body: { + "api_id" : "e9b9b0cf-ee0a-11ea-b02e-0242ac110003", + "message" : "MPC: TestMPC record started", + "recording_id" : "e9bd7634-ee0a-11ea-9ddf-06feebbe3347", + "recording_url" : "https://media-qa.voice.plivodev.com/v1/Account/MAMDJMMTEZOWY0ZMQWM2/Recording/e9bd7634-ee0a-11ea-9ddf-06feebbe3347.mp3" + } + }); + } + + else if (method === 'DELETE' && action === 'MultiPartyCall/name_TestMPC/Record/'){ + resolve({ + response: {}, + body: { + } + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Record/Pause/'){ + resolve({ + response: {}, + body: { + } + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Record/Resume/'){ + resolve({ + response: {}, + body: { + } + }); + } + + else if (method === 'GET' && action === 'MultiPartyCall/uuid_12345678-90123456/Participant/'){ + resolve({ + response: {}, + body: { + "api_id": "d53e6c49-ee0e-11ea-b02e-0242ac110003", + "meta": { + "count": 1, + "limit": 20, + "next": null, + "offset": 0, + "previous": null + }, + "objects": [ + { + "billed_amount": null, + "billed_duration": null, + "call_uuid": "426c1fb3-8f47-46e5-a916-51faa85ca90e", + "coach_mode": false, + "duration": null, + "end_mpc_on_exit": false, + "exit_cause": null, + "exit_time": null, + "hold": false, + "join_time": "2020-09-03 17:24:12+00:00", + "member_id": "2132", + "mpc_uuid": "7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087", + "mute": false, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087/Participant/2132/", + "role": "agent", + "start_mpc_on_enter": true + } + ] + } + }); + } + + else if (method === 'POST' && action === 'MultiPartyCall/uuid_12345678-90123456/Participant/10/'){ + resolve({ + response: {}, + body: { + "api_id" : "be5a333a-ee0f-11ea-b02e-0242ac110003", + "hold" : "MPC: TestMPC hold/unhold member(s) succeded", + "mute" : "MPC: TestMPC mute/unmute member(s) succeded" + } + }); + } + + else if (method === 'DELETE' && action === 'MultiPartyCall/uuid_12345678-90123456/Participant/10/'){ + resolve({ + response: {}, + body: { + } + }); + } + + else if (method === 'GET' && action === 'MultiPartyCall/uuid_7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087/Participant/2132/'){ + resolve({ + response: {}, + body: { + "api_id": "7ca274bb-ee11-11ea-b02e-0242ac110003", + "billed_amount": null, + "billed_duration": null, + "call_uuid": "426c1fb3-8f47-46e5-a916-51faa85ca90e", + "coach_mode": false, + "duration": null, + "end_mpc_on_exit": false, + "exit_cause": null, + "exit_time": null, + "hold": false, + "join_time": "2020-09-03 17:24:12+00:00", + "member_id": "2132", + "mpc_uuid": "7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087", + "mute": false, + "resource_uri": "/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087/Participant/2132/", + "role": "agent", + "start_mpc_on_enter": true + } + }); + } + // ============= Numbers =================== else if (method == 'GET' && action == 'Number/+919999999990/') { resolve({ diff --git a/lib/rest/utils.js b/lib/rest/utils.js index a456cae..300fe5b 100644 --- a/lib/rest/utils.js +++ b/lib/rest/utils.js @@ -5,6 +5,8 @@ import _mapValues from 'lodash/mapValues'; import _map from 'lodash/map'; import { parseString } from 'xml2js'; +export class InvalidRequestError extends Error {} + function recursivelyRenameObject(object, renameFunc) { if (!(object instanceof Object)) { return object; @@ -79,3 +81,203 @@ export function validateSpeakAttributes(content, voice) { } } +export function validSubAccount(accountId){ + if(accountId.constructor !== String){ + throw new InvalidRequestError('Subaccount Id must be a string'); + } + + if(accountId.length !== 20){ + throw new InvalidRequestError('Subaccount Id should be of length 20'); + } + + if(accountId.substring(0,2) !== 'SA'){ + throw new InvalidRequestError("Subaccount Id should start with 'SA'"); + } + + return true; +} + +export function validMultipleDestinationNos(paramName, paramValue, options = {}){ + if(paramValue.split(options.delimiter).length > 1 && options.role.toLowerCase()!=='agent'){ + throw new InvalidRequestError('Multiple ' + paramName + ' values given for role ' + options.role) + } + else if (paramValue.split(options.delimiter).length >= options.agentLimit){ + throw new InvalidRequestError('No of ' + paramName + ' values provided should be lesser than ' + options.agentLimit) + } + else { + return true + } +} + +export function validParam(paramName, paramValue, expectedTypes = null, mandatory = false, expectedValues = null){ + if(mandatory && !paramValue){ + throw new InvalidRequestError(paramName + " is a required parameter"); + } + + if (!paramValue){ + return true; + } + + if(!expectedValues){ + return expectedType(paramName, expectedTypes, paramValue); + } + + if(expectedValue(paramName, expectedValues, paramValue)){ + return true; + } +} + +export function expectedType(paramName, expectedTypes, paramValue){ + if(!expectedTypes){ + return true; + } + + if(expectedTypes.indexOf(paramValue.constructor)===-1){ + throw new InvalidRequestError(paramName + ": Expected one of " + expectedTypes + " but received " + paramValue.constructor + " instead") + } + return true; +} + +export function expectedValue(paramName, expectedValues, paramValue){ + if(!expectedValues){ + return true; + } + + if(expectedValues.constructor === Array){ + if(expectedValues.indexOf(paramValue) === -1){ + throw new InvalidRequestError(paramName + ': Expected one of ' + expectedValues + ' but received ' + paramValue + ' instead'); + } + return true; + } + else{ + if(expectedValues !== paramValue){ + throw new InvalidRequestError(paramName + ': Expected ' + expectedValues + ' but received ' + paramValue + ' instead') + } + return true; + } +} + +export function multiValidParam(paramName, paramValue, expectedTypes = null, mandatory = false, expectedValues = null, makeLowerCase = false, seperator = ','){ + if(mandatory && !paramValue){ + throw new InvalidRequestError(paramName + 'is a required parameter'); + } + + if(!paramValue){ + return true; + } + + if(makeLowerCase){ + paramValue = paramValue.toLowerCase(); + } + else{ + paramValue = paramValue.toUpperCase(); + } + let values = paramValue.split(seperator) + if(expectedValues) { + for (let i = 0; i < values.length; i++) { + expectedValue(paramName, expectedValues, values[i].trim()); + } + } + return true; +} + +export function validUrl(paramName, paramValue, mandatory = false){ + if(mandatory && !paramValue){ + throw new InvalidRequestError(paramName + 'is a required parameter'); + } + + if(!paramValue){ + return true; + } + + let response = paramValue.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g); + if(response == null){ + throw new InvalidRequestError("Invalid URL : Doesn't satisfy the URL format") + } + else { + return true; + } +} + +export function isOneAmongStringUrl(paramName, paramValue, mandatory = false, expectedValues = null){ + if(mandatory && !paramValue){ + throw new InvalidRequestError(paramName + 'is a required parameter'); + } + + if(!paramValue){ + return true; + } + + if(!(expectedValues.indexOf(paramValue.toLowerCase()) === -1) || !(expectedValues.indexOf(paramValue.toUpperCase()) === -1)){ + return true; + } + else if (validUrl(paramName, paramValue)){ + return true; + } + else { + throw new InvalidRequestError(paramName + ' neither a valid URL nor in the expected values') + } +} + +export function validDateFormat(paramName, paramValue, mandatory = false){ + if(mandatory && !paramValue){ + throw new InvalidRequestError(paramName + " is a required parameter") + } + + if(!paramValue){ + return true; + } + + let response = paramValue.match(/^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?$/); + if(response == null){ + throw new InvalidRequestError("Invalid Date : Doesn't satisfy the date format") + } + else { + return true; + } +} + +export function validRange(paramName, paramValue, mandatory = false, lowerBound = null, upperBound = null){ + if(mandatory && !paramValue){ + throw new InvalidRequestError(paramName + " is a required parameter") + } + + if(!paramValue && paramValue !== 0){ + return true; + } + + if(!expectedType(paramName, [Number], paramValue)){ + throw new InvalidRequestError(paramName + ": Expected an Integer but received " + paramValue.constructor + " instead") + } + + if(lowerBound && upperBound){ + if(paramValue < lowerBound || paramValue > upperBound) { + throw new InvalidRequestError(paramName + " ranges between " + lowerBound + " and " + upperBound) + } + + if(paramValue >= lowerBound && paramValue <= upperBound){ + return true; + } + } + else if(lowerBound){ + if(paramValue < lowerBound){ + throw new InvalidRequestError(paramName + " should be greater than " + lowerBound) + } + + if(paramValue >= lowerBound){ + return true; + } + } + else if(upperBound){ + if(paramValue > upperBound){ + throw new InvalidRequestError(paramName + " should be lesser than " + upperBound) + } + + if(paramValue <= upperBound){ + return true; + } + } + else{ + throw new InvalidRequestError("Any one or both of lower and upper bound should be provided") + } +} diff --git a/lib/utils/plivoxml.js b/lib/utils/plivoxml.js index 577ce9f..f6a30b3 100644 --- a/lib/utils/plivoxml.js +++ b/lib/utils/plivoxml.js @@ -2,7 +2,6 @@ var qs = require('querystring'); var xmlBuilder = require('xmlbuilder'); var util = require('util'); var plivoUtils = require('./../rest/utils'); - import * as Exceptions from './exceptions'; var jsonStringifier = require('./jsonStrinfigier'); @@ -15,7 +14,7 @@ export class PlivoXMLError extends Error { } export function Response() { this.element = 'Response'; this.nestables = ['Speak', 'Play', 'GetDigits', 'GetInput', 'Record', 'Dial', 'Message', - 'Redirect', 'Wait', 'Hangup', 'PreAnswer', 'Conference', 'DTMF']; + 'Redirect', 'Wait', 'Hangup', 'PreAnswer', 'Conference', 'DTMF', 'MultiPartyCall']; this.valid_attributes = []; this.elem = xmlBuilder.begin().ele(this.element); } @@ -440,6 +439,234 @@ Response.prototype = { return this.add(new DTMF(Response), body, attributes); }, + /** + * Add a MultiPartyCall element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.role] + * @param {number} [attributes.maxDuration] + * @param {number} [attributes.maxParticipants] + * @param {string} [attributes.waitMusicMethod] + * @param {string} [attributes.agentHoldMusicMethod] + * @param {string} [attributes.customerHoldMusicMethod] + * @param {boolean} [attributes.record] + * @param {string} [attributes.recordFileFormat] + * @param {string} [attributes.recordingCallbackMethod] + * @param {string} [attributes.statusCallbackEvents] + * @param {string} [attributes.statusCallbackMethod] + * @param {boolean} [attributes.stayAlone] + * @param {boolean} [attributes.coachMode] + * @param {boolean} [attributes.mute] + * @param {boolean} [attributes.hold] + * @param {boolean} [attributes.startMpcOnEnter] + * @param {boolean} [attributes.endMpcOnExit] + * @param {string} [attributes.enterSound] + * @param {string} [attributes.enterSoundMethod] + * @param {string} [attributes.exitSound] + * @param {string} [attributes.exitSoundMethod] + * @param {string} [attributes.onExitActionMethod] + * @param {boolean} [attributes.relayDTMFInputs] + * @param {string} [attributes.waitMusicUrl] + * @param {string} [attributes.agentHoldMusicUrl] + * @param {string} [attributes.customerHoldMusicUrl] + * @param {string} [attributes.recordingCallbackUrl] + * @param {string} [attributes.statusCallbackUrl] + * @param {string} [attributes.customerHoldMusicUrl] + */ + addMultiPartyCall: function (body, attributes){ + const VALID_ROLE_VALUES = ['agent', 'supervisor', 'customer'] + const VALID_METHOD_VALUES = ['GET', 'POST'] + const VALID_BOOL_VALUES = [true, false] + const VALID_RECORD_FILE_FORMAT_VALUES = ['mp3', 'wav'] + + if(attributes.role && VALID_ROLE_VALUES.indexOf(attributes.role.toLowerCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.role + ' for role') + } + else if (!attributes.role){ + throw new PlivoXMLError('role not mentioned : possible values - Agent / Supervisor / Customer') + } + + if(attributes.maxDuration && (attributes.maxDuration<300 || attributes.maxDuration>28800)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.maxDuration + ' for maxDuration') + } + else if(!attributes.maxDuration){ + attributes.maxDuration = 14400 + } + + if(attributes.maxParticipants && (attributes.maxParticipants<2 || attributes.maxParticipants>10)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.maxParticipants + ' for maxParticipants') + } + else if(!attributes.maxParticipants){ + attributes.maxParticipants = 10 + } + + if(attributes.waitMusicMethod && VALID_METHOD_VALUES.indexOf(attributes.waitMusicMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.waitMusicMethod + ' for waitMusicMethod') + } + else if (!attributes.waitMusicMethod){ + attributes.waitMusicMethod = 'GET' + } + + if(attributes.agentHoldMusicMethod && VALID_METHOD_VALUES.indexOf(attributes.agentHoldMusicMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.agentHoldMusicMethod + ' for agentHoldMusicMethod') + } + else if (!attributes.agentHoldMusicMethod){ + attributes.agentHoldMusicMethod = 'GET' + } + + if(attributes.customerHoldMusicMethod && VALID_METHOD_VALUES.indexOf(attributes.customerHoldMusicMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.customerHoldMusicMethod + ' for customerHoldMusicMethod') + } + else if (!attributes.customerHoldMusicMethod){ + attributes.customerHoldMusicMethod = 'GET' + } + + if(attributes.record && VALID_BOOL_VALUES.indexOf(attributes.record)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.record + ' for record') + } + else if (!attributes.record){ + attributes.record = false + } + + if(attributes.recordFileFormat && VALID_RECORD_FILE_FORMAT_VALUES.indexOf(attributes.recordFileFormat.toLowerCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.recordFileFormat + ' for recordFileFormat') + } + else if (!attributes.recordFileFormat){ + attributes.recordFileFormat = 'mp3' + } + + if(attributes.recordingCallbackMethod && VALID_METHOD_VALUES.indexOf(attributes.recordingCallbackMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.recordingCallbackMethod + ' for recordingCallbackMethod') + } + else if (!attributes.recordingCallbackMethod){ + attributes.recordingCallbackMethod = 'GET' + } + + if(attributes.statusCallbackEvents && !plivoUtils.multiValidParam('statusCallbackEvents', attributes.statusCallbackEvents, String, false, ['mpc-state-changes', 'participant-state-changes', 'participant-speak-events', 'participant-digit-input-events', 'add-participant-api-events'], true, ',')){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.statusCallbackEvents + ' for statusCallbackEvents') + } + else if(!attributes.statusCallbackEvents){ + attributes.statusCallbackEvents = 'mpc-state-changes,participant-state-changes' + } + + if(attributes.statusCallbackMethod && VALID_METHOD_VALUES.indexOf(attributes.statusCallbackMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.statusCallbackMethod + ' for statusCallbackMethod') + } + else if (!attributes.statusCallbackMethod){ + attributes.statusCallbackMethod = 'POST' + } + + if(attributes.stayAlone && VALID_BOOL_VALUES.indexOf(attributes.stayAlone)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.stayAlone + ' for stayAlone') + } + else if (!attributes.stayAlone){ + attributes.stayAlone = false + } + + if(attributes.coachMode && VALID_BOOL_VALUES.indexOf(attributes.coachMode)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.coachMode + ' for coachMode') + } + else if (!attributes.coachMode){ + attributes.coachMode = true + } + + if(attributes.mute && VALID_BOOL_VALUES.indexOf(attributes.mute)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.mute + ' for mute') + } + else if (!attributes.mute){ + attributes.mute = false + } + + if(attributes.hold && VALID_BOOL_VALUES.indexOf(attributes.hold)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.hold + ' for hold') + } + else if (!attributes.hold){ + attributes.hold = false + } + + if(attributes.startMpcOnEnter && VALID_BOOL_VALUES.indexOf(attributes.startMpcOnEnter)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.startMpcOnEnter + ' for startMpcOnEnter') + } + else if (!attributes.startMpcOnEnter){ + attributes.startMpcOnEnter = true + } + + if(attributes.endMpcOnExit && VALID_BOOL_VALUES.indexOf(attributes.endMpcOnExit)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.endMpcOnExit + ' for endMpcOnExit') + } + else if (!attributes.endMpcOnExit){ + attributes.endMpcOnExit = false + } + + if(attributes.enterSound && !plivoUtils.isOneAmongStringUrl('enterSound', attributes.enterSound, false, ['beep:1', 'beep:2', 'none'])){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.enterSound + ' for enterSound') + } + else if(!attributes.enterSound){ + attributes.enterSound = 'beep:1' + } + + if(attributes.enterSoundMethod && VALID_METHOD_VALUES.indexOf(attributes.enterSoundMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.enterSoundMethod + ' for enterSoundMethod') + } + else if (!attributes.enterSoundMethod){ + attributes.enterSoundMethod = 'GET' + } + + if(attributes.exitSound && !plivoUtils.isOneAmongStringUrl('exitSound', attributes.exitSound, false, ['beep:1', 'beep:2', 'none'])){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.exitSound + ' for exitSound') + } + else if(!attributes.exitSound){ + attributes.exitSound = 'beep:2' + } + + if(attributes.exitSoundMethod && VALID_METHOD_VALUES.indexOf(attributes.exitSoundMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.exitSoundMethod + ' for exitSoundMethod') + } + else if (!attributes.exitSoundMethod){ + attributes.exitSoundMethod = 'GET' + } + + if(attributes.onExitActionMethod && VALID_METHOD_VALUES.indexOf(attributes.onExitActionMethod.toUpperCase())===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.onExitActionMethod + ' for onExitActionMethod') + } + else if (!attributes.onExitActionMethod){ + attributes.onExitActionMethod = 'POST' + } + + if(attributes.relayDTMFInputs && VALID_BOOL_VALUES.indexOf(attributes.relayDTMFInputs)===-1){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.relayDTMFInputs + ' for relayDTMFInputs') + } + else if (!attributes.relayDTMFInputs){ + attributes.relayDTMFInputs = false + } + + if(attributes.waitMusicUrl && !plivoUtils.validUrl('waitMusicUrl', attributes.waitMusicUrl, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.waitMusicUrl + ' for waitMusicUrl') + } + + if(attributes.agentHoldMusicUrl && !plivoUtils.validUrl('agentHoldMusicUrl', attributes.agentHoldMusicUrl, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.agentHoldMusicUrl + ' for agentHoldMusicUrl') + } + + if(attributes.customerHoldMusicUrl && !plivoUtils.validUrl('customerHoldMusicUrl', attributes.customerHoldMusicUrl, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.customerHoldMusicUrl + ' for customerHoldMusicUrl') + } + + if(attributes.recordingCallbackUrl && !plivoUtils.validUrl('recordingCallbackUrl', attributes.recordingCallbackUrl, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.recordingCallbackUrl + ' for recordingCallbackUrl') + } + + if(attributes.statusCallbackUrl && !plivoUtils.validUrl('statusCallbackUrl', attributes.statusCallbackUrl, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.statusCallbackUrl + ' for statusCallbackUrl') + } + + if(attributes.customerHoldMusicUrl && !plivoUtils.validUrl('customerHoldMusicUrl', attributes.customerHoldMusicUrl, false)){ + throw new PlivoXMLError('Invalid attribute value ' + attributes.customerHoldMusicUrl + ' for customerHoldMusicUrl') + } + return this.add(new MultiPartyCall(Response), body, attributes); + }, + toXML: function () { return this.elem.toString(); }, @@ -521,7 +748,7 @@ function GetInput(Response) { this.element = 'GetInput'; this.valid_attributes = ['action', 'method', 'inputType', 'executionTimeout', 'digitEndTimeout', 'speechEndTimeout', 'finishOnKey', 'numDigits', - 'speechModel', 'hints','language', 'interimSpeechResultsCallback', + 'speechModel', 'hints','language', 'interimSpeechResultsCallback', 'interimSpeechResultsCallbackMethod', 'log', 'redirect', 'profanityFilter']; this.nestables = ['Speak', 'Play', 'Wait']; } @@ -741,4 +968,22 @@ function DTMF(Response) { this.valid_attributes = ['digits', 'async']; } -util.inherits(DTMF, Response); \ No newline at end of file +util.inherits(DTMF, Response); + +/** + * MultiPartyCall element + * @constructor + */ +function MultiPartyCall(Response){ + this.element = 'MultiPartyCall'; + this.nestables = []; + this.valid_attributes = ['role', 'maxDuration', 'maxParticipants', 'waitMusicUrl', + 'waitMusicMethod', 'agentHoldMusicUrl', 'agentHoldMusicMethod', + 'customerHoldMusicUrl', 'customerHoldMusicMethod', 'record', + 'recordFileFormat', 'recordingCallbackUrl', 'recordingCallbackMethod', + 'statusCallbackEvents', 'statusCallbackUrl', 'statusCallbackMethod', + 'stayAlone', 'coachMode', 'mute', 'hold', 'startMpcOnEnter', 'endMpcOnExit', + 'enterSound', 'enterSoundMethod', 'exitSound', 'exitSoundMethod', + 'onExitActionUrl', 'onExitActionMethod', 'relayDTMFInputs']; +} +util.inherits(MultiPartyCall, Response); diff --git a/package.json b/package.json index ddd9b7b..7df55cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plivo", - "version": "4.15.0", + "version": "4.16.0", "description": "A Node.js SDK to make voice calls and send SMS using Plivo and to generate Plivo XML", "homepage": "https://github.com/plivo/plivo-node", "files": [ diff --git a/test/calls.js b/test/calls.js index f60efee..4e623a6 100644 --- a/test/calls.js +++ b/test/calls.js @@ -68,6 +68,7 @@ describe('calls', function () { client.calls.get(1) .then(function(call){ return call.transfer() + done() }) .then(function(call) { assert.equal(call.id, 5) diff --git a/test/multiPartyCalls.js b/test/multiPartyCalls.js new file mode 100644 index 0000000..c5887bc --- /dev/null +++ b/test/multiPartyCalls.js @@ -0,0 +1,95 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; +import {MultiPartyCall} from "../lib/resources/multiPartyCall"; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('multiPartyCalls', function (){ + it('should list all MultiPartyCalls', function (){ + return client.multiPartyCalls.list().then(function (response){ + for(let i=0; i< response.length;i++) { + assert(response[i] instanceof MultiPartyCall) + } + }) + }); + + it('should get details of a MultiPartyCall', function (){ + return client.multiPartyCalls.get('ca8e8a44-48e1-445d-afd5-1fcccdbccd9d').then(function (response){ + assert(response instanceof PlivoGenericResponse) + assert.equal(response.id, 'ca8e8a44-48e1-445d-afd5-1fcccdbccd9d') + assert.equal(response.resourceUri, '/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_ca8e8a44-48e1-445d-afd5-1fcccdbccd9d/') + }) + }); + + it('should add a Participant', function (){ + return client.multiPartyCalls.addParticipant('Agent', {'friendlyName' : 'Voice', 'from' : '+919090909090', 'to' : '+918309866821'}).then(function (response){ + assert(response instanceof PlivoGenericResponse) + assert.equal(response.message, 'add participant action initiated') + }) + }); + + it('should start an MPC', function (){ + return client.multiPartyCalls.start(null, 'Voice').then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should end an MPC', function (){ + return client.multiPartyCalls.stop(null, 'Voice').then(function (response){ + assert(response, true) + }) + }); + + it('should start MPC Recording', function (){ + return client.multiPartyCalls.startRecording(null, 'TestMPC').then(function (response){ + assert(response.message, "MPC: TestMPC record started") + }) + }); + + it('should stop MPC Recording', function (){ + return client.multiPartyCalls.stopRecording(null, 'TestMPC').then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should pause MPC Recording', function (){ + return client.multiPartyCalls.pauseRecording(null, 'TestMPC').then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should resume MPC Recording', function (){ + return client.multiPartyCalls.resumeRecording(null, 'TestMPC').then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should list MPC Participants', function (){ + return client.multiPartyCalls.listParticipants('12345678-90123456', null).then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should update MPC Participant', function (){ + return client.multiPartyCalls.updateParticipant(10, '12345678-90123456', null).then(function (response){ + assert(response instanceof PlivoGenericResponse) + assert.equal(response.hold, 'MPC: TestMPC hold/unhold member(s) succeded') + assert.equal(response.mute, 'MPC: TestMPC mute/unmute member(s) succeded') + }) + }); + + it('should kick MPC Participant', function (){ + return client.multiPartyCalls.kickParticipant(10, '12345678-90123456', null).then(function (response){ + assert(response instanceof PlivoGenericResponse) + }) + }); + + it('should get MPC Participant', function (){ + return client.multiPartyCalls.getParticipant(2132, '7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087', null).then(function (response){ + assert(response instanceof PlivoGenericResponse) + assert.equal(response.resourceUri, '/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087/Participant/2132/') + }) + }); +}) diff --git a/test/xml.js b/test/xml.js index efb2b60..b2ce224 100644 --- a/test/xml.js +++ b/test/xml.js @@ -29,4 +29,15 @@ describe('PlivoXML', function () { done("Failed to test Plivo Xml due to unknown error"); }); }); + + it('tests MultiPartyCall', function (done){ + const mpcResponse = new Response(); + mpcResponse.addMultiPartyCall('Nairobi',{ + role: 'Agent', + maxDuration: 1000, + statusCallbackEvents: 'participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes' + }); + assert.equal('Nairobi',mpcResponse.toXML()); + done(); + }) });