cctsa-wxccdemobot-commons
Version:
Common routines for building demo/reference bots with Webex Contact Center and Google Dialogflow ES using the codingforconvos NPM library.
347 lines (308 loc) • 15 kB
JavaScript
/**
* Copyright 2022 Justin Randall, Cisco Systems Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not,
* see <https://www.gnu.org/licenses/>.
*/
const { Intent, Sequence, fmtLog } = require("codingforconvos");
const { WebexConnectConnector } = require("../connectors/webexconnect");
const { RedmineConnector } = require("../connectors/redmine");
// Define Sequence Name Constants.
const SEQ_AUTH_NAME = 'authentication';
///////////////////////////////////////////////////////////
// Register Authentication Sequence and Intent Handlers. //
///////////////////////////////////////////////////////////
/**
* Reset and send a one-time-passcode (OTP) based on secondary channel configuration.
*
* @param {DialogContext} dialogContext The dialog context.
*/
function createAndSendOtp(dialogContext) {
if (dialogContext.params.secondChannel === 'sms') {
createAndSendOtpBySms(dialogContext);
} else {
createAndSendOtpByEmail(dialogContext);
}
}
/**
* Reset and send a one-time-passcode (OTP) via SMS.
*
* @param {DialogContext} dialogContext The dialog context.
*/
function createAndSendOtpBySms(dialogContext) {
let generatedOtp = WebexConnectConnector.createOtp();
dialogContext.setCurrentParam('generatedOtp', generatedOtp);
const wxConnectApi = dialogContext.connectorManager.get(WebexConnectConnector.name());
wxConnectApi.sendOtpBySms(
dialogContext.params.sessionId,
dialogContext.params.smsNumber,
generatedOtp,
dialogContext.params.customerFirstName,
dialogContext.params.companyName,
dialogContext.params.interactionSource
);
}
/**
* Reset and send a one-time-passcode (OTP) via Email.
*
* @param {DialogContext} dialogContext The dialog context.
*/
function createAndSendOtpByEmail(dialogContext) {
let generatedOtp = WebexConnectConnector.createOtp();
dialogContext.setCurrentParam('generatedOtp', generatedOtp);
const wxConnectApi = dialogContext.connectorManager.get(WebexConnectConnector.name());
wxConnectApi.sendOtpByEmail(
dialogContext.params.sessionId,
dialogContext.params.mail,
generatedOtp,
dialogContext.params.customerFirstName,
dialogContext.params.companyName,
dialogContext.params.interactionSource
);
}
/**
* Registers the sequences and intents for the authentication module.
*
* @param {ConvoClient} convoClient The convo client.
*/
function registerModuleAuthentication(convoClient) {
// Register Sequence.
convoClient.registerSequence(new Sequence({
name: SEQ_AUTH_NAME, // Sequence name, also used for Dialogflow context name.
activity: 'verifying your identity', // Activity description, used in course correction.
identityRequired: false,
authRequired: false,
params: {
validationComplete: '0',
validationStatus: '0',
validationReceived: '0',
validatedBy: '',
generatedOtp: '',
advisedAuthRequired: '0',
receivedAccountNumber: '',
accountNumberFound: '0',
accountNumberNotFound: '0',
phoneNumberFound: '',
phoneNumberNotFound: '',
emailFound: '',
emailNotFound: '',
usernameFound: '',
usernameNotFound: '',
receivedOtp: ''
},
navigate: (dialogContext) => { // Navigate the sequence forward.
let context = dialogContext.getOrCreateCtx(SEQ_AUTH_NAME);
if (context.parameters.advisedAuthRequired === '0') {
console.log(fmtLog('authentication.navigate', 'Sending Event AuthRequired', dialogContext));
let event = dialogContext.respondWithEvent('AuthRequired', dialogContext.params.lastFulfillmentText);
return;
}
if (dialogContext.params.customerIdentified === '1') {
if (context.parameters.validationComplete === '0' && context.parameters.validationReceived === '1' && context.parameters.validationStatus === '1') {
console.log(fmtLog('authentication.navigate', 'Sending Event AuthOtpSuccess', dialogContext));
let event = dialogContext.respondWithEvent('AuthOtpSuccess', dialogContext.params.lastFulfillmentText);
return;
}
if (context.parameters.validationComplete === '0' && context.parameters.validationReceived === '1' && context.parameters.validationStatus === '2') {
console.log(fmtLog('authentication.navigate', 'Sending Event AuthOtpFailure', dialogContext));
let event = dialogContext.respondWithEvent('AuthOtpFailure', dialogContext.params.lastFulfillmentText);
return;
}
if (context.parameters.validationComplete === '0' && context.parameters.validationReceived === '0') {
console.log(fmtLog('authentication.navigate', 'Sending Event AuthSendOtp', dialogContext));
let event = dialogContext.respondWithEvent('AuthSendOtp', dialogContext.params.lastFulfillmentText);
return;
}
}
if (context.parameters.validationComplete === '1') {
if (context.parameters.validationStatus === '2') {
if (dialogContext.params.offeredAgent === '0') {
let event = dialogContext.respondWithEvent('OfferSpeakToAgent', dialogContext.params.lastFulfillmentText);
return;
}
if (dialogContext.params.offeredAgentAccepted === '1') {
let event = dialogContext.respondWithEvent('EscalateToAgent', dialogContext.params.lastFulfillmentText);
return;
}
if (dialogContext.params.offeredAgentDeclined === '1') {
console.log(fmtLog('authentication.navigate', 'Calling popSequenceAndNavigate', dialogContext));
dialogContext.deleteCtx('skillresetpasswordsms-followup'); // TODO: I think I can remove these, but make sure.
dialogContext.popSequenceAndNavigate(SEQ_AUTH_NAME);
return;
}
}
console.log(fmtLog('authentication.navigate', 'Calling popSequenceAndNavigate', dialogContext));
dialogContext.deleteCtx('skillresetpasswordsms-followup'); // TODO: I think I can remove these, but make sure.
dialogContext.popSequenceAndNavigate(SEQ_AUTH_NAME);
}
if (dialogContext.params.customerIdentified === '0') {
if (context.parameters.receivedAccountNumber !== '' && context.parameters.accountNumberNotFound === '1') {
console.log(fmtLog('authentication.navigate', 'Sending Event AuthInvalidAccount', dialogContext));
let event = dialogContext.respondWithEvent('AuthInvalidAccount', dialogContext.params.lastFulfillmentText);
return;
}
console.log(fmtLog('authentication.navigate', 'Sending Event AuthGetAccount', dialogContext));
let event = dialogContext.respondWithEvent('AuthGetAccount', dialogContext.params.lastFulfillmentText);
return;
}
console.log(fmtLog('authentication.navigate', 'action: '+dialogContext.currentAction+', lastFulfillmentText: '+dialogContext.params.lastFulfillmentText, dialogContext));
dialogContext.respondWithText();
return;
}
}));
// Register Intent Handlers.
convoClient.registerIntent(new Intent({
action: 'auth.required',
sequenceName: SEQ_AUTH_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
dialogContext.setCurrentParam('advisedAuthRequired', '1');
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'auth.getaccount',
sequenceName: SEQ_AUTH_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
return;
}
}));
// FIXME: Most of this operation should be moved into the RedmineConnector, similar to phone and email account lookups.
convoClient.registerIntent(new Intent({
action: 'auth.getaccount.value',
sequenceName: SEQ_AUTH_NAME,
waitForReply: false,
handler: async (dialogContext) => {
const redmineApi = dialogContext.connectorManager.get(RedmineConnector.name());
dialogContext.setFulfillmentText();
dialogContext.setCurrentParam('receivedAccountNumber', dialogContext.inparams.accountnumber);
const redmineUsersByAccountFound = await redmineApi.findRedmineUsersByAccountId(dialogContext.inparams.accountnumber);
if (redmineUsersByAccountFound.length === 0) {
dialogContext.setCurrentParam('accountNumberNotFound', '1');
dialogContext.deleteCtx('Authgetaccount-followup');
return;
}
let resultUser = redmineUsersByAccountFound[0]; // TODO: Handle multiple user disambiguation in future.
if (resultUser.mobileNumber == null) {
console.error('Mobile Phone field not found for user: '+JSON.stringify(resultUser));
dialogContext.setCurrentParam('accountNumberNotFound', '1');
dialogContext.deleteCtx('Authgetaccount-followup');
return;
}
dialogContext.setCurrentParams({
'accountNumberFound': '1',
'validatedBy': 'Account Number'
});
dialogContext.setSessionParams({
'customerIdentified': '1',
'smsNumber': resultUser.mobileNumber,
'redmineUserId': Math.floor(resultUser.id).toString(), // Convert from float->int->string
'mail': resultUser.mail,
'identityPhoneNumber': resultUser.mobileNumber,
'identityEmail': resultUser.mail,
'customerFirstName': resultUser.firstName,
'customerLastName': resultUser.lastName,
'redmineOpenCaseId': resultUser.openCaseId,
'redmineOpenAppointmentId': resultUser.openAppointmentId,
'accountNumber': resultUser.accountNumber,
'accountTier': resultUser.accountTier,
'accountStatus': resultUser.accountStatus,
'preferredLanguage': resultUser.preferredLanguage
});
console.log('resultUser: ' + JSON.stringify(resultUser));
dialogContext.deleteCtx('Authgetaccount-followup');
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'auth.invalidaccount',
sequenceName: SEQ_AUTH_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParam('receivedAccountNumber', '');
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'auth.sendotp',
sequenceName: SEQ_AUTH_NAME,
waitForReply: true,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
createAndSendOtp(dialogContext);
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'auth.sendotp.confirmation.notreceived',
sequenceName: SEQ_AUTH_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setSessionParam('customerValidated', '0');
dialogContext.setCurrentParams ({
'validationComplete': '1',
'validationReceived': '1',
'validationStatus': '2'
});
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'auth.sendotp.value',
sequenceName: SEQ_AUTH_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.setFulfillmentText();
dialogContext.setCurrentParam('receivedOtp', dialogContext.inparams.otp);
if (dialogContext.ctxparams.generatedOtp === dialogContext.ctxparams.receivedOtp) {
dialogContext.setSessionParam('customerValidated', '1');
dialogContext.setCurrentParams ({
'validationReceived': '1',
'validationStatus': '1'
});
} else {
dialogContext.setSessionParam('customerValidated', '0');
dialogContext.setCurrentParams ({
'validationReceived': '1',
'validationStatus': '2'
});
}
dialogContext.deleteCtx('authenticationsendotp-followup');
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'auth.sendotp.success',
sequenceName: SEQ_AUTH_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
dialogContext.setCurrentParam('validationComplete', '1');
return;
}
}));
convoClient.registerIntent(new Intent({
action: 'auth.sendotp.failure',
sequenceName: SEQ_AUTH_NAME,
waitForReply: false,
handler: (dialogContext) => {
dialogContext.appendFulfillmentText();
dialogContext.setCurrentParam('validationComplete', '1');
return;
}
}));
}
module.exports = {registerModuleAuthentication};