diff --git a/endpoint/avayaVoiceChannel/README.MD b/endpoint/avayaVoiceChannel/README.MD
index f043826..a8a0c8e 100644
--- a/endpoint/avayaVoiceChannel/README.MD
+++ b/endpoint/avayaVoiceChannel/README.MD
@@ -29,8 +29,12 @@ While creating Cognigy flow, you can use Avaya Extension nodes such as play,prom
**sms** - sends the sms messages in the voice channel flow. It's not needed for sms channel.
**redirect** - redirects current Cognigy flow to another flow and won't return. It requires the endpoint url of the target flow.
**locale** - sets the locale language and the voice type on CPaaS so that the intended language and the voice will be spoken to the customers.
+**hours** - get the business hours with the settgins of holiday and hours of operations
# Deploy #
-A Cognigy flow should be created and deployed with the endpoint type of Avaya voice channel and the ``Enable Input Transformer``, ``Enable Output Transformer`` and ``Enable Execution Finished Transformer`` have to be ``enabled``.
-
+A Cognigy flow should be created and deployed with the endpoint type of Avaya voice channel with the optional parameter ``CpaaS Token`` which authenticates CPaaS's signature, and the ``Enable Input Transformer``, ``Enable Output Transformer`` and ``Enable Execution Finished Transformer`` have to be ``enabled``.
+```
+ CPaaS Token
+ 90b196e676074f52xb4de0a2d13af4f3
+```
Next copy the endpoint url and paste it into the ``Voice Configuration`` -> ``WebLink`` of the phone number in Avaya CPaaS. Now it's ready for you to call the number to experience the Cognigy's flow.
diff --git a/endpoint/avayaVoiceChannel/icon.png b/endpoint/avayaVoiceChannel/icon.png
index 2d155bc..d9062fc 100644
Binary files a/endpoint/avayaVoiceChannel/icon.png and b/endpoint/avayaVoiceChannel/icon.png differ
diff --git a/endpoint/avayaVoiceChannel/transformer.ts b/endpoint/avayaVoiceChannel/transformer.ts
index e9632f4..bd0fcea 100644
--- a/endpoint/avayaVoiceChannel/transformer.ts
+++ b/endpoint/avayaVoiceChannel/transformer.ts
@@ -21,17 +21,58 @@ interface CPaaSBody {
UrlBase: string;
Digits: string;
SpeechResult: string;
+ SpeechResultError: string;
Body: string;
SmsSid: string;
+ SessionId: string;
}
-const COGNIGY_BASE_URL = 'https://endpoint-trial.cognigy.ai/';
const DEBUG_MODE = false;
const MAX_CONF_PARTIES = 2;
const DEFAULT_NUM_DIGITS = 1;
const DEFAULT_LANGUAGE = 'en-US';
-const DEFAULT_GATHER_TIMEOUT = 3;
+const DEFAULT_GATHER_TIMEOUT = 10;
const DEFAULT_VOICE = 'woman';
+const DEFAULT_CALLER_ID = '18004567890';
+const DEFAULT_API_VERSION = 'v2';
+const HTTPS = 'https://';
+const REDIRECT_PARAMS = '?PlayStatus=completed&SpeechResult=&SpeechResultError=redirect&Confidence=0';
+/**
+ * creates CPaaS signature
+ */
+function getSignature(authToken, url, params) {
+ const data = Object.keys(params)
+ .sort()
+ .reduce((acc, key) => acc + key + params[key], url);
+ return crypto
+ .createHmac('sha1', authToken)
+ .update(data)
+ .digest('base64');
+}
+/**
+ * validate the signature
+*/
+function validSignature(endpoint, request, url) {
+ const requestSignature = request['headers']['x-zang-signature'];
+ if (requestSignature &&
+ (requestSignature == getSignature(endpoint.settings.cpaasToken,url,request.body)) ||
+ (requestSignature == getSignature(endpoint.settings.cpaasToken,url+REDIRECT_PARAMS,request.body))) {
+ return true;
+ } else {
+ return false;
+ }
+}
+/**
+ * get current timestamp
+ */
+function getTimestamp() {
+ const timestamp = new Date();
+ return (timestamp.getFullYear().toString() +
+ (timestamp.getMonth()+1).toString() +
+ timestamp.getDate().toString() +
+ timestamp.getHours().toString() +
+ timestamp.getMinutes().toString());
+}
createRestTransformer({
@@ -58,28 +99,45 @@ createRestTransformer({
* every Endpoint, and the example above needs to be adjusted
* accordingly.
*/
- const { body } = request as CPaaSRequest;
+ const { headers, body } = request;
+ const { host } = headers;
if (DEBUG_MODE) {
console.log('body='.concat(JSON.stringify(body)));
}
- const { From, To, CallSid, Digits, SpeechResult, SmsSid, Body } = body;
+ const { AccountSid, ApiVersion, Digits, CallSid, From, SpeechResult, SpeechResultError, To, UrlBase, Body, SessionId } = body;
+ if (CallSid && endpoint?.settings?.cpaasToken && !validSignature(endpoint,request,UrlBase)) {
+ response.status(401).send('Unauthorized');
+ console.log('Unauthorized')
+ return null;
+ } else if (DEBUG_MODE) {
+ console.log('valid signature or turned off');
+ }
+ if (ApiVersion != DEFAULT_API_VERSION) {
+ throw Error('wrong CPaaS API version '.concat(ApiVersion));
+ }
+ if (SpeechResultError == 'redirect') {
+ return null;
+ }
const userId = From;
- const sessionId = CallSid ? CallSid : SmsSid;
+ const sessionId = CallSid ? CallSid : (SessionId ? SessionId : (userId+getTimestamp()));
let sessionStorage = await getSessionStorage(userId,sessionId);
+ if (!sessionStorage.urlbase) {
+ sessionStorage.urlbase = host;
+ }
sessionStorage.From = From;
sessionStorage.To = To;
- sessionStorage.cpaas_channel = CallSid ? 'call' : 'sms';
+ sessionStorage.cpaasChannel = CallSid ? 'call' : 'sms';
sessionStorage.numberOfDigits = DEFAULT_NUM_DIGITS;
- let data = {"call": false,"sms":false, "phone":From};
+ let data = {"accountSid":AccountSid, "apiVersion":ApiVersion, "call": false,"sms":false, "phone":From};
const menu = sessionStorage['menu'] ? sessionStorage['menu'] : {};
let text = '';
- switch (sessionStorage.cpaas_channel) {
+ switch (sessionStorage.cpaasChannel) {
case 'call':
text = Digits ? (menu[Digits] ? menu[Digits] : Digits.replace(/\s+/g, '')) : SpeechResult;
data.call = true;
break;
case 'sms':
- text = Body;
+ text = Body.match(/^\d$/) ? (menu[Body] ? menu[Body] : Body) : Body;
data.sms = true;
break;
default:
@@ -127,12 +185,16 @@ createRestTransformer({
activities.forEach( (activity) => {
switch (activity.name) {
case 'handover':
+ let callerId = 'callerId="' + ((activity.activityParams.from != null && activity.activityParams.from != '') ? activity.activityParams.from : '{{To}}') + '"';
const handoverType = activity.activityParams.handoverType;
if (handoverType === "phone") {
const dest = activity.activityParams.destination;
+ if (dest == userId && callerId == '') {
+ callerId = 'callerId="' + DEFAULT_CALLER_ID + '"';
+ }
const cbUrl = (activity.activityParams.callbackUrl != "") ?
(' callbackUrl="' + activity.activityParams.callbackUrl + '"') : '';
- let dial = '' + dest + '';
+ let dial = '' + dest + '';
output.text = (output.text != null) ? (output.text + dial) : dial;
sessionStorage.dial = true;
} else if (handoverType === "sip") {
@@ -140,10 +202,10 @@ createRestTransformer({
(' callbackUrl="' + activity.activityParams.callbackUrl + '"') : '';
const user = activity.activityParams.user;
const domain = activity.activityParams.domain;
- const userName = activity.activityParams.connection.userName;
+ const username = activity.activityParams.connection.username;
const password = activity.activityParams.connection.password;
let sipUrl = user.concat("@".concat(domain));
- let dial ='' + sipUrl + ';transport=tcp';
+ let dial ='' + sipUrl + ';transport=tcp';
output.text = (output.text != null) ? (output.text + dial) : dial;
sessionStorage.dial = true;
}
@@ -155,12 +217,12 @@ createRestTransformer({
sessionStorage['menu'] = menu;
sessionStorage.numberOfDigits = DEFAULT_NUM_DIGITS;
if (activity.activityParams.menuText) {
- output.text = (sessionStorage.cpaas_channel == 'sms') ? activity.activityParams.menuText : (say + activity.activityParams.menuText + '');
+ output.text = (sessionStorage.cpaasChannel == 'sms') ? activity.activityParams.menuText : (say + activity.activityParams.menuText + '');
}
} else if (promptType === 'number') {
sessionStorage.numberOfDigits = activity.activityParams.numberOfDigits;
if (activity.activityParams.numberText) {
- output.text = (sessionStorage.cpaas_channel == 'sms') ? activity.activityParams.numberText : (say + activity.activityParams.numberText + '');
+ output.text = (sessionStorage.cpaasChannel == 'sms') ? activity.activityParams.numberText : (say + activity.activityParams.numberText + '');
}
}
break;
@@ -168,7 +230,7 @@ createRestTransformer({
sessionStorage.hangup = true;
break;
case 'play':
- if (sessionStorage.cpaas_channel == 'call') {
+ if (sessionStorage.cpaasChannel == 'call') {
output.text += activity.activityParams.url ? ('' + activity.activityParams.url + '') : '';
} else {
output.text = activity.activityParams.text;
@@ -190,16 +252,17 @@ createRestTransformer({
break;
case 'sms':
sessionStorage.sms = true;
- const to = activity.activityParams.to ? activity.activityParams.to : '';
- if (sessionStorage.cpaas_channel == 'call') {
- output.data.sms = '' + activity.activityParams.text + '';
+ const from = ' from="' + ((activity.activityParams.from != null && activity.activityParams.from != '') ? activity.activityParams.from : '{{To}}') + '"';
+ const to = ' to="' + (activity.activityParams.to ? activity.activityParams.to : '') + '"';
+ if (sessionStorage.cpaasChannel == 'call') {
+ output.data.sms = '' + activity.activityParams.text + '';
} else {
output.text = activity.activityParams.text;
}
break;
case 'redirect':
sessionStorage.redirect = true;
- output.text += activity.activityParams.url ? (''+ activity.activityParams.url + '') : '';
+ output.text += activity.activityParams.url ? (''+ activity.activityParams.url + '') : '';
break;
case 'locale':
sessionStorage.language = activity.activityParams.language;
@@ -209,7 +272,7 @@ createRestTransformer({
break;
}
});
- } else if (output.text != null && (sessionStorage.cpaas_channel == 'call')) {
+ } else if (output.text != null && (sessionStorage.cpaasChannel == 'call')) {
output.text = say + output.text + '';
}
return output;
@@ -236,15 +299,15 @@ createRestTransformer({
* correct format according to the documentation of the specific Endpoint channel.
*/
handleExecutionFinished: async ({ processedOutput, outputs, userId, sessionId, endpoint, response }) => {
- let url = COGNIGY_BASE_URL + endpoint.URLToken;
const sessionStorage = await getSessionStorage(userId, sessionId);
+ const url = HTTPS + sessionStorage.urlbase + '/' + endpoint.URLToken;
let cpaasResponse = 'default';
- switch (sessionStorage.cpaas_channel) {
+ switch (sessionStorage.cpaasChannel) {
case 'call':
cpaasResponse = getCPaaSCallCmd(sessionStorage, url, outputs);
break;
case 'sms':
- cpaasResponse = getCPaaSSmsCmd(sessionStorage, url, outputs);
+ cpaasResponse = getCPaaSSmsCmd(sessionStorage, url, sessionId, outputs);
break;
default:
break;
@@ -277,16 +340,18 @@ const getCPaaSCallCmd = (sessionStorage, url, outputs) => {
let smsCmds = sms ? outputs.map((t) => {return t.data.sms}).join('\n') : '';
let prompt = outputs.map((t) => {return t.text}).join('\n');
let numDigits = 'numDigits="' + numberOfDigits + '"';
- let gather = ''+prompt+'';
+ let gather = ''+prompt+''
+ +
+ '' + url + REDIRECT_PARAMS + '';
let cpaasResponse = '' + (record) + (smsCmds) + (ctrlcmd ? prompt : gather) + '';
return (cpaasResponse);
};
-const getCPaaSSmsCmd = (sessionStorage, url, outputs) => {
+const getCPaaSSmsCmd = (sessionStorage, url, sessionId, outputs) => {
let text = outputs.map((t) => {return t.text}).join('\n');
let From = sessionStorage.To;
let To = sessionStorage.From;
let FromTo = ' From="'+From+'" To="'+To+'"';
- let cpaasResponse = ''+text+'';
return (cpaasResponse);
}
\ No newline at end of file