Skip to content

Commit

Permalink
Merge pull request #223 from plivo/VT-3652
Browse files Browse the repository at this point in the history
Added SDK changes for MPC Enhancements
  • Loading branch information
huzaif-plivo authored Nov 30, 2021
2 parents 84e6f8f + dc7deab commit 6ccd99f
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## [v4.24.0](https://github.com/plivo/plivo-node/tree/v4.24.0) (2021-11-30)
**Features - Voice: Multiparty calls**
- The [Add Multiparty Call API](https://www.plivo.com/docs/voice/api/multiparty-call/participants#add-a-participant) allows for greater functionality by accepting options like `start recording audio`, `stop recording audio`, and their HTTP methods.
- [Multiparty Calls](https://www.plivo.com/docs/voice/api/multiparty-call/) now has new APIs to `stop` and `play` audio.

## [v4.23.1](https://github.com/plivo/plivo-node/tree/v4.23.1) (2021-10-13)
**Bug Fix**
- LiveCallInterface.
Expand Down
79 changes: 79 additions & 0 deletions lib/resources/multiPartyCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const clientKey = Symbol();
const action = 'MultiPartyCall/';
const idField = 'mpcUuid';
const secondaryAction = 'Participant/';
const secondaryMemberAction = 'Member/';
const secondaryIdField = 'participantUuid';

export class MPCError extends Error { }
Expand Down Expand Up @@ -306,6 +307,29 @@ export class MultiPartyCall extends PlivoResource{
else {
params.exitSoundMethod = 'GET'
}

if(params.startRecordingAudio){
validUrl('startRecordingAudio', params.startRecordingAudio, false)
}

if(params.startRecordingAudioMethod){
validParam('startRecordingAudioMethod', params.startRecordingAudioMethod.toUpperCase(), [String], false, ['GET', 'POST'])
}
else {
params.startRecordingAudioMethod = 'GET'
}

if(params.stopRecordingAudio){
validUrl('stopRecordingAudio', params.stopRecordingAudio, false)
}

if(params.stopRecordingAudioMethod){
validParam('stopRecordingAudioMethod', params.stopRecordingAudioMethod.toUpperCase(), [String], false, ['GET', 'POST'])
}
else {
params.stopRecordingAudioMethod = 'GET'
}

if(params.to && (String(params.ringTimeout).split('<').length > params.to.split('<').length)){
throw new MPCError("RingTimeout:number of ring_timeout(s) should be same as number of destination(s)")
}
Expand Down Expand Up @@ -449,6 +473,32 @@ export class MultiPartyCallParticipant extends PlivoSecondaryResource{

}

export class MultiPartyCallMember extends PlivoSecondaryResource{
constructor(client, data= {}) {
super(action, MultiPartyCall, idField, secondaryMemberAction, MultiPartyCallMember, secondaryIdField, client);

if (idField in data) {
this.id = data[idField];
}

if(secondaryIdField in data){
this.secondaryId = data[secondaryIdField];
}

extend(this, data);
this[clientKey] = client;
}

startPlayAudio(params){
params.isVoiceRequest = 'true';
return super.executeAction(this.id, this.secondaryId + '/Play', 'POST', params)
}

stopPlayAudio(){
return super.executeAction(this.id, this.secondaryId +'/Play', 'DELETE',{'isVoiceRequest' : 'true'})
}
}

export class MultiPartyCallInterface extends PlivoResourceInterface{
constructor(client, data = {}) {
super(action, MultiPartyCall, idField, client);
Expand Down Expand Up @@ -737,4 +787,33 @@ export class MultiPartyCallInterface extends PlivoResourceInterface{
return new MultiPartyCallParticipant(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).getParticipant()
}

startPlayAudio(participantId, url, params = {}){
validParam('participantId', participantId, [String, Number], true)
validUrl('url', url, true)
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.url = url
return new MultiPartyCallMember(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).startPlayAudio(params)
}

stopPlayAudio(participantId, params = {}){
validParam('participantId', participantId, [String, Number], true)
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
return new MultiPartyCallMember(this[clientKey], {id: mpcId[0] + mpcId[1], secondaryId: participantId}).stopPlayAudio(params)
}
}
54 changes: 54 additions & 0 deletions lib/rest/request-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,60 @@ export function Request(config) {
});
}

else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/'){
resolve({
response: {},
body: {
"api_id": "036c80f3-3721-11ec-a678-0242ac110002",
"message": "MPC: TestMPC participant record started",
"recording_id": "24670db8-c723-4ba2-8521-f10ec41ddf8b",
"recording_url": "https://media-qa.voice.plivodev.com/v1/Account/MAXXXXXXXXXXXX/Recording/XXXXX-XXXX-XXXX-XXXXX.mp3"
}
});
}

else if (method === 'DELETE' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/'){
resolve({
response: {},
body: {}
});
}

else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/Pause/'){
resolve({
response: {},
body: {}
});
}

else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Participant/10/Record/Resume/'){
resolve({
response: {},
body: {}
});
}

else if (method === 'POST' && action === 'MultiPartyCall/name_TestMPC/Member/10/Play/'){
resolve({
response: {},
body: {
"api_id": "c07db813-3721-11ec-8bcd-0242ac110008",
"message": "play queued into MPC",
"mpcMemberId": [
"1003"
],
"mpcName": "TestMPC"
}
});
}

else if (method === 'DELETE' && action === 'MultiPartyCall/name_TestMPC/Member/10/Play/'){
resolve({
response: {},
body: {}
});
}

// ============= Numbers ===================
else if (method == 'GET' && action == 'Number/+919999999990/') {
resolve({
Expand Down
29 changes: 28 additions & 1 deletion lib/utils/plivoxml.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ Response.prototype = {
* @param {string} [attributes.recordingCallbackUrl]
* @param {string} [attributes.statusCallbackUrl]
* @param {string} [attributes.customerHoldMusicUrl]
* @param {string} [attributes.startRecordingAudio]
* @param {string} [attributes.stopRecordingAudio]
* @param {string} [attributes.startRecordingAudioMethod]
* @param {string} [attributes.stopRecordingAudioMethod]
*/
addMultiPartyCall: function (body, attributes){
const VALID_ROLE_VALUES = ['agent', 'supervisor', 'customer']
Expand Down Expand Up @@ -664,6 +668,28 @@ Response.prototype = {
if(attributes.customerHoldMusicUrl && !plivoUtils.validUrl('customerHoldMusicUrl', attributes.customerHoldMusicUrl, false)){
throw new PlivoXMLError('Invalid attribute value ' + attributes.customerHoldMusicUrl + ' for customerHoldMusicUrl')
}

if(attributes.startRecordingAudio && !plivoUtils.validUrl('startRecordingAudio', attributes.startRecordingAudio, false)){
throw new PlivoXMLError('Invalid attribute value ' + attributes.startRecordingAudio + ' for startRecordingAudio')
}

if(attributes.stopRecordingAudio && !plivoUtils.validUrl('stopRecordingAudio', attributes.stopRecordingAudio, false)){
throw new PlivoXMLError('Invalid attribute value ' + attributes.stopRecordingAudio + ' for stopRecordingAudio')
}

if(attributes.startRecordingAudioMethod && VALID_METHOD_VALUES.indexOf(attributes.startRecordingAudioMethod.toUpperCase())===-1){
throw new PlivoXMLError('Invalid attribute value ' + attributes.startRecordingAudioMethod + ' for startRecordingAudioMethod')
}
else if (!attributes.startRecordingAudioMethod){
attributes.startRecordingAudioMethod = 'GET'
}

if(attributes.stopRecordingAudioMethod && VALID_METHOD_VALUES.indexOf(attributes.stopRecordingAudioMethod.toUpperCase())===-1){
throw new PlivoXMLError('Invalid attribute value ' + attributes.stopRecordingAudioMethod + ' for stopRecordingAudioMethod')
}
else if (!attributes.stopRecordingAudioMethod){
attributes.stopRecordingAudioMethod = 'GET'
}
return this.add(new MultiPartyCall(Response), body, attributes);
},

Expand Down Expand Up @@ -984,6 +1010,7 @@ function MultiPartyCall(Response){
'statusCallbackEvents', 'statusCallbackUrl', 'statusCallbackMethod',
'stayAlone', 'coachMode', 'mute', 'hold', 'startMpcOnEnter', 'endMpcOnExit',
'enterSound', 'enterSoundMethod', 'exitSound', 'exitSoundMethod',
'onExitActionUrl', 'onExitActionMethod', 'relayDTMFInputs'];
'onExitActionUrl', 'onExitActionMethod', 'relayDTMFInputs',
'startRecordingAudio', 'startRecordingAudioMethod', 'stopRecordingAudio', 'stopRecordingAudioMethod'];
}
util.inherits(MultiPartyCall, Response);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "plivo",
"version": "4.23.1",
"version": "4.24.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": [
Expand Down
37 changes: 37 additions & 0 deletions test/multiPartyCalls.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,41 @@ describe('multiPartyCalls', function (){
assert.equal(response.resourceUri, '/v1/Account/MAMDJMMTEZOWY0ZMQWM2/MultiPartyCall/uuid_7503f05f-2d6e-4ab3-b9e6-3b0d81ae9087/Participant/2132/')
})
});

it('should start MPC Participant Recording', function (){
return client.multiPartyCalls.startParticipantRecording(10, {friendlyName: 'TestMPC'}).then(function (response){
assert(response.message, "MPC: TestMPC participant record started")
})
});

it('should stop MPC Participant Recording', function (){
return client.multiPartyCalls.stopParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){
assert(response instanceof PlivoGenericResponse)
})
});

it('should pause MPC Participant Recording', function (){
return client.multiPartyCalls.pauseParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){
assert(response instanceof PlivoGenericResponse)
})
});

it('should resume MPC Participant Recording', function (){
return client.multiPartyCalls.resumeParticipantRecording(10,{friendlyName: 'TestMPC'}).then(function (response){
assert(response instanceof PlivoGenericResponse)
})
});

it('should start MPC Play Audio Member', function (){
return client.multiPartyCalls.startPlayAudio(10,'https://s3.amazonaws.com/XXX/XXX.mp3',
{friendlyName: 'TestMPC'}).then(function (response){
assert(response.message, "play queued into MPC")
})
});

it('should stop MPC Play Audio Member', function (){
return client.multiPartyCalls.stopPlayAudio(10,{friendlyName: 'TestMPC'}).then(function (response){
assert(response instanceof PlivoGenericResponse)
})
});
})
2 changes: 1 addition & 1 deletion test/xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('PlivoXML', function () {
maxDuration: 1000,
statusCallbackEvents: 'participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes'
});
assert.equal('<Response><MultiPartyCall role="Agent" maxDuration="1000" statusCallbackEvents="participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes" maxParticipants="10" waitMusicMethod="GET" agentHoldMusicMethod="GET" customerHoldMusicMethod="GET" record="false" recordFileFormat="mp3" recordingCallbackMethod="GET" statusCallbackMethod="POST" stayAlone="false" coachMode="true" mute="false" hold="false" startMpcOnEnter="true" endMpcOnExit="false" enterSound="beep:1" enterSoundMethod="GET" exitSound="beep:2" exitSoundMethod="GET" onExitActionMethod="POST" relayDTMFInputs="false">Nairobi</MultiPartyCall></Response>',mpcResponse.toXML());
assert.equal('<Response><MultiPartyCall role="Agent" maxDuration="1000" statusCallbackEvents="participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes" maxParticipants="10" waitMusicMethod="GET" agentHoldMusicMethod="GET" customerHoldMusicMethod="GET" record="false" recordFileFormat="mp3" recordingCallbackMethod="GET" statusCallbackMethod="POST" stayAlone="false" coachMode="true" mute="false" hold="false" startMpcOnEnter="true" endMpcOnExit="false" enterSound="beep:1" enterSoundMethod="GET" exitSound="beep:2" exitSoundMethod="GET" onExitActionMethod="POST" relayDTMFInputs="false" startRecordingAudioMethod="GET" stopRecordingAudioMethod="GET">Nairobi</MultiPartyCall></Response>',mpcResponse.toXML());
done();
});
});

0 comments on commit 6ccd99f

Please sign in to comment.