This is the code referred to in this article about the TWC encouragement extension.
((sName, bDebug) => {
var storage,
SESSION_ID, USER_INPUT_DISABLED,
GLOBAL_ENCOURAGEMENTS, NODE_ENCOURAGEMENTS, ENCOURAGEMENT_INDEX, ENCOURAGEMENT_THREAD_START,
sessionId = null,
encouragementThreadId, globalEncouragements;
const isSessionOpen = () => (sessionId && window.TeneoWebChat.get('chat_history').length !== 0),
getBooleanFromString = s => {
if (s == null || (s = s.trim()).length === 0 || s == 0) return false;
s = s.toLowerCase();
return s!=='false' && s!=='null';
},
isValidEncouragementArray = xx => {
if (!Array.isArray(xx)) return false;
for (var i = 0; i !== xx.length; i++) {
if (!(xx[i].delaySeconds > 0 && (xx[i].text || xx[i].command))) {
console.warn(sName, 'Bad encouragement object', xx[i]);
return false;
}
}
return true;
},
toEncouragements = s => {
try {
const r = JSON.parse(s);
if (isValidEncouragementArray(r)) return r;
console.warn(sName, 'Skipping parsed encouragements value as it is either a non-array or has incorrect format', s);
} catch (err) {
console.error(sName, 'Bad encouragements value', s, err);
}
return null;
},
stopEncouragementThread = () => {
if (encouragementThreadId == null) return;
clearTimeout(encouragementThreadId);
encouragementThreadId = null;
if (bDebug) console.debug(sName, 'Stopping encouragements thread');
},
cancelAllEncouragements = () => {
stopEncouragementThread();
globalEncouragements = null;
storage.removeItem(GLOBAL_ENCOURAGEMENTS);
storage.removeItem(NODE_ENCOURAGEMENTS);
storage.removeItem(ENCOURAGEMENT_INDEX);
storage.removeItem(ENCOURAGEMENT_THREAD_START);
if (bDebug) console.debug(sName, 'Deleting encouragements');
},
isProvidedWithInput = m => {
if (m) {
var x = m.type;
if (x && (x === 'form' || x === 'buttons' || x === 'clickablelist' || x === 'quickreply' || x === 'form')) return true;
if (Array.isArray(m)) {
x = m.length;
while (--x >= 0) {
if (isProvidedWithInput(m[x])) return true;
}
} else if ('object' === typeof m) {
for (x in m) {
if (m.hasOwnProperty(x) && (x === 'postback' || x === 'parameters' || isProvidedWithInput(m[x]))) return true;
}
}
}
return false;
},
isLikelyCtaMessage = m => m != null && m.author !== 'user' && m.type !== 'system' && m.type !== 'text' && isProvidedWithInput(m),
/**
* Handles encouragements.
*
* @param {number} [nEncInd] the index of the currently displayed encouragement in the encouragement array.
* If its value is not provided or is null or undefined, then the encouragement count is resumed. Otherwise
* it is (re)started. This value should be provided as 0 on engine response and as null or not provided
* for page reloads.
* @param {string} [sGlobalEncouragements] a stringified array of global encouragement objects. If it is not provided,
* the existing array remains unchanged. If it provided as malformed, the existing array is deleted.
* @param {string} [sNodeEncouragements] a stringified array of node encouragement objects.
* @param {object} [nodeEncouragements] an array of a node encouragements objects. If sNodeEncouragements
* is provided, it has priority over nodeEncouragements.
*/
doEncouragementThread = (nEncInd, sGlobalEncouragements, sNodeEncouragements, nodeEncouragements) => {
if (nEncInd == null) {
if (sGlobalEncouragements == null) sGlobalEncouragements = storage.getItem(GLOBAL_ENCOURAGEMENTS);
if (sNodeEncouragements == null) sNodeEncouragements = storage.getItem(NODE_ENCOURAGEMENTS);
}
if (sGlobalEncouragements != null && (sGlobalEncouragements = sGlobalEncouragements.trim()).length !== 0) {
globalEncouragements = toEncouragements(sGlobalEncouragements);
if (globalEncouragements == null) storage.removeItem(GLOBAL_ENCOURAGEMENTS);
else if (globalEncouragements.length === 0) {
globalEncouragements = null;
storage.removeItem(GLOBAL_ENCOURAGEMENTS);
} else {
storage.setItem(GLOBAL_ENCOURAGEMENTS, sGlobalEncouragements);
}
} else {
if (globalEncouragements == null) storage.removeItem(GLOBAL_ENCOURAGEMENTS);
}
if (sNodeEncouragements != null && (sNodeEncouragements = sNodeEncouragements.trim()).length !== 0) {
nodeEncouragements = toEncouragements(sNodeEncouragements);
if (nodeEncouragements == null) storage.removeItem(NODE_ENCOURAGEMENTS);
else storage.setItem(NODE_ENCOURAGEMENTS, sNodeEncouragements);
} else {
if (nodeEncouragements == null) storage.removeItem(NODE_ENCOURAGEMENTS);
}
const encs = nodeEncouragements || globalEncouragements;
if (encs == null) {
cancelAllEncouragements();
return;
}
stopEncouragementThread();
if (encs.length === 0) return;
var x;
if (nEncInd != null) storage.setItem(ENCOURAGEMENT_INDEX, nEncInd);
else {
// Page is refreshed
x = storage.getItem(ENCOURAGEMENT_INDEX);
if (x == null || (x = x.trim()).length === 0) nEncInd = 0;
else if (Number.isNaN(nEncInd = Number.parseInt(x))) {
console.warn(sName, 'Bad [' + ENCOURAGEMENT_INDEX + '] value:', x);
return;
}
if (encs.length === nEncInd) {
// Page is refreshed after all the encouragements have been displayed
if (bDebug) console.debug(sName, 'encs.length==' + encs.length + ', nEncInd==' + nEncInd);
return;
}
x = storage.getItem(ENCOURAGEMENT_THREAD_START);
if (x != null) {
if ((x = x.trim()).length === 0) x = null;
else if (Number.isNaN(x = Number.parseInt(x))) {
console.warn(sName, 'Bad [' + ENCOURAGEMENT_THREAD_START + '] value:', storage.getItem(ENCOURAGEMENT_THREAD_START));
return;
}
}
}
// Here, x is the start time point for the countdown for the currently planned encouragement
// or null (undefined) if it has not started yet or is irrelevant. nEncInd is the integer
// index of the encouragement to be executed.
const enc = encs[nEncInd], nDelay = (x == null) ? enc.delaySeconds * 1000 : (enc.delaySeconds * 1000 - (Date.now() - x));
if (enc == null) {
console.warn(sName, 'Bad nEncInd value', nEncInd, 'for encs', encs);
return;
}
// If the encouragement countdown hasn't been set previously, set it to the current time point:
if (x == null) storage.setItem(ENCOURAGEMENT_THREAD_START, Date.now());
if (bDebug) console.debug(sName, 'Starting encouragement thread with calculated delay in milliseconds', nDelay, 'and value', enc);
x = sessionId;
encouragementThreadId = setTimeout(() => {
encouragementThreadId = null;
storage.removeItem(ENCOURAGEMENT_THREAD_START);
if (!isSessionOpen()) {
console.info(sName, 'Ignoring encouragements because the session closed');
cancelAllEncouragements();
return;
}
if (x !== sessionId) {
console.info(sName, 'Ignoring encouragements because the session changed from', x, 'to', sessionId);
cancelAllEncouragements();
return;
}
if (bDebug) console.debug(sName, 'Printing encouragement', enc);
if (enc.text) window.TeneoWebChat.call('add_message', { type: 'text', author: 'bot', data: { text: enc.text }});
nEncInd++;
if (enc.command) {
switch (enc.command) {
case 'reset':
if (nEncInd < encs.length) {
console.warn(sName, 'Command "reset" on a non-last item encouragements', encs);
}
window.TeneoWebChat.call('reset');
return;
case 'endSession':
if (nEncInd < encs.length) {
console.warn(sName, 'Command "endSession" on a non-last item for encouragements', encs);
}
window.TeneoWebChat.call('end_session');
resetData();
return;
case 'disableUserInput':
window.TeneoWebChat.call('disable_user_input');
storage.setItem(USER_INPUT_DISABLED, '1');
break;
case 'enableUserInput':
window.TeneoWebChat.call('enable_user_input');
storage.removeItem(USER_INPUT_DISABLED);
break;
default:
console.warn(sName, 'Unknown encouragement command', enc.command);
}
}
if (enc.text) {
// The enc text message has been added. So if the last output contained
// CTAs, they have been relegated to the history and thus deactivated.
// Get the last history item and repeat it if it is likely to contain CTAs:
let h = window.TeneoWebChat.get('chat_history');
h = h[h.length - 1];
if (isLikelyCtaMessage(h)) {
if (bDebug) console.debug(sName, 'Repeating a likely CTA message', h);
window.TeneoWebChat.call('add_message', h);
}
}
storage.setItem(ENCOURAGEMENT_INDEX, nEncInd);
if (nEncInd < encs.length) doEncouragementThread(nEncInd, null, null, nodeEncouragements);
}, nDelay > 0 ? nDelay : 0);
},
resetData = () => {
cancelAllEncouragements();
sessionId = null;
storage.removeItem(SESSION_ID);
storage.removeItem(USER_INPUT_DISABLED);
if (bDebug) console.debug(sName, 'Data reset');
};
window.TeneoWebChat.on('ready',() => {
storage = window.TeneoWebChat.get('storage');
SESSION_ID = sName + '_sessionId';
USER_INPUT_DISABLED = sName + '_userInputDisabled';
GLOBAL_ENCOURAGEMENTS = sName + '_globalEncouragements';
NODE_ENCOURAGEMENTS = sName + '_nodeEncouragements';
ENCOURAGEMENT_INDEX = sName + '_encouragementIndex';
ENCOURAGEMENT_THREAD_START = sName + '_encouragementThreadStart';
sessionId = storage.getItem(SESSION_ID);
if (isSessionOpen()) {
if (bDebug) console.debug(sName,'Session is open on reload');
doEncouragementThread();
} else {
cancelAllEncouragements();
if (bDebug) console.debug(sName,'Session is not open on reload');
}
if (getBooleanFromString(storage.getItem(USER_INPUT_DISABLED))) setTimeout(() => window.TeneoWebChat.call('disable_user_input'));
});
window.TeneoWebChat.on('engine_request', stopEncouragementThread);
window.TeneoWebChat.on('engine_response', ({responseDetails}) => {
storage.setItem(SESSION_ID, sessionId = responseDetails.sessionId);
var x = responseDetails.output.parameters;
if (x) doEncouragementThread(0, x.encouragements, x.nodeEncouragements);
else doEncouragementThread(0);
});
window.TeneoWebChat.on('reset', resetData);
})('TWCEncouragement');