diff --git a/docs/scripts/main.js b/docs/scripts/main.js index 6269cde..bb98ed2 100644 --- a/docs/scripts/main.js +++ b/docs/scripts/main.js @@ -25,44 +25,37 @@ let messageIds = []; /** * Callback function for 'message' and 'typing-indicator' events. - * - * @param {Object} data the event data + * + * @param {Object} data the event data */ -let onMessage = (data) => { +const onMessage = data => { console.log(JSON.stringify(data)); - if(messageType === 'chat'){ - switch(data.metadata.type){ - case 'typing-indicator': - break; - case 'message': - // Values from the event - let eventBody = data.eventBody; - let message = eventBody.body; - let senderId = eventBody.sender.id; - - // Conversation values for cross reference - let participant = currentConversation.participants.find(p => p.chats[0].id == senderId); - let name = ''; - let purpose = ''; - - if(participant.name != null) { - name = participant.name; - purpose = participant.purpose; - } else { - name = 'BOT'; - purpose = 'agent'; - } - - // Wait for translate to finish before calling addChatMessage - translate.translateText(message, genesysCloudLanguage, function(translatedData) { - view.addChatMessage(name, translatedData.translated_text, purpose); - translationData = translatedData; - }); - - break; + if(messageType === 'chat' && data.metadata.type === 'message') { + // Values from the event + let eventBody = data.eventBody; + let message = eventBody.body; + let senderId = eventBody.sender.id; + + // Conversation values for cross reference + let participant = currentConversation.participants.find(p => p.chats[0].id === senderId); + let name = ''; + let purpose = ''; + + if(participant.name != null) { + name = participant.name; + purpose = participant.purpose; + } else { + name = 'BOT'; + purpose = 'agent'; } - } else if (messageType === 'message') { + + // Wait for translate to finish before calling addChatMessage + translate.translateText(message, genesysCloudLanguage, function(translatedData) { + view.addChatMessage(name, translatedData.translated_text, purpose); + translationData = translatedData; + }); + } else if(messageType === 'message') { let messageId = ''; let purpose = ''; let name = ''; @@ -73,27 +66,27 @@ let onMessage = (data) => { var mostRecentMessageTime = ''; // Discard unwanted notifications - if (data.topicName.toLowerCase() === 'channel.metadata') { + if(data.topicName.toLowerCase() === 'channel.metadata') { // Heartbeat - //console.info('Ignoring metadata: ', notification); + // console.info('Ignoring metadata: ', notification); return; - } else if (data.eventBody.id != currentConversationId) { + } else if(data.eventBody.id !== currentConversationId) { // Conversation event not related to the current conversationId (in this frame) // Ignore return; - } else if (data.eventBody.participants.find(p => p.purpose == 'customer').endTime) { + } else if(data.eventBody.participants.find(p => p.purpose === 'customer').endTime) { console.log('ending conversation'); } else { - data.eventBody.participants.forEach((participant) => { + data.eventBody.participants.forEach(participant => { if(!participant.endTime && Array.isArray(participant.messages[0].messages)) { - messages.push(participant.messages[0].messages[participant.messages[0].messages.length-1]); + messages.push(participant.messages[0].messages[participant.messages[0].messages.length - 1]); participantPurposes.push(participant.purpose); } }); - for (var x=0; x mostRecentMessageTime) { + if(messages[x].messageTime > mostRecentMessageTime) { mostRecentMessageTime = messages[x].messageTime; messageId = messages[x].messageId; purpose = participantPurposes[x]; @@ -101,51 +94,49 @@ let onMessage = (data) => { } } - name = (purpose === 'customer') ? customerName : agentAlias + name = (purpose === 'customer') ? customerName : agentAlias; - if (publish) { + if(publish && !messageIds.includes(messageId)) { // Make sure message is published only once conversationsApi.getConversationsMessageMessage(data.eventBody.id, messageId) .then((messageDetail => { - // Make sure message is published only once - if(!messageIds.includes(messageId)){ - // Ignore messages without text (e.g. Presence/Disconect Event) - if (null != messageDetail.textBody) { - messageIds.push(messageId); - - // Wait for translate to finish before calling addChatMessage - translate.translateText(messageDetail.textBody, genesysCloudLanguage, function(translatedData) { - view.addChatMessage(name, translatedData.translated_text, purpose); - translationData = translatedData; - }); - } - } - })); - } + // Ignore messages without text (e.g. Presence/Disconnect Event) + if(messageDetail.textBody == null) { + return; + } + messageIds.push(messageId); + + // Wait for translate to finish before calling addChatMessage + translate.translateText(messageDetail.textBody, genesysCloudLanguage, function(translatedData) { + view.addChatMessage(name, translatedData.translated_text, purpose); + translationData = translatedData; + }); + })); + } } - } + } }; /** * Translate then send message to the customer */ -function sendChat(){ +function sendChat() { let message = document.getElementById('message-textarea').value; // Get the last agent participant, this also fixes an issue when an agent // gets reconnected and reassigned a new participant id. - let agentsArr = currentConversation.participants.filter(p => p.purpose == 'agent'); + let agentsArr = currentConversation.participants.filter(p => p.purpose === 'agent'); let agent = agentsArr[agentsArr.length - 1]; let communicationId = ''; if(messageType === 'chat') { communicationId = agent.chats[0].id; - } else if (messageType === 'message') { + } else if(messageType === 'message') { communicationId = agent.messages[0].id; } let sourceLang; - // Default language to english if no source_language available + // Default language to english if no source_language available if(translationData === null) { sourceLang = 'en'; } else { @@ -155,7 +146,7 @@ function sendChat(){ // Translate text to customer's local language translate.translateText(message, sourceLang, function(translatedData) { // Wait for translate to finish before calling sendMessage - sendMessage(translatedData.translated_text, currentConversationId, communicationId); + sendMessage(translatedData.translated_text, currentConversationId, communicationId, message); }); document.getElementById('message-textarea').value = ''; @@ -164,49 +155,55 @@ function sendChat(){ /** * Send message to the customer */ -function sendMessage(message, conversationId, communicationId){ +function sendMessage(message, conversationId, communicationId, originalMessage = '') { console.log(message); if(messageType === 'chat') { conversationsApi.postConversationsChatCommunicationMessages( conversationId, communicationId, { - 'body': message, - 'bodyType': 'standard' + body: message, + bodyType: 'standard' } - ) - } else if (messageType === 'message') { + ); + } else if(messageType === 'message') { conversationsApi.postConversationsMessageCommunicationMessages( conversationId, communicationId, { - 'textBody': message + textBody: message } - ) - } + ).then(result => { + if(originalMessage) { + // Do not double-translate message - show as agent entered it + messageIds.push(result.id); + view.addChatMessage(agentAlias, originalMessage, 'agent'); + } + }); + } } /** * Show the chat messages for a conversation - * @param {String} conversationId - * @returns {Promise} + * @param {String} conversationId + * @returns {Promise} */ -function showChatTranscript(conversationId){ - if(messageType === 'chat'){ +function showChatTranscript(conversationId) { + if(messageType === 'chat') { return conversationsApi.getConversationsChatMessages(conversationId) - .then((data) => { + .then(data => { // Show each message - data.entities.forEach((msg) => { + data.entities.forEach(msg => { if(msg.hasOwnProperty('body')) { let message = msg.body; - // Determine the name by cross referencing sender id + // Determine the name by cross referencing sender id // with the participant.chats.id from the conversation parameter let senderId = msg.sender.id; let name = currentConversation - .participants.find(p => p.chats[0].id == senderId) + .participants.find(p => p.chats[0].id === senderId) .name; let purpose = currentConversation - .participants.find(p => p.chats[0].id == senderId) + .participants.find(p => p.chats[0].id === senderId) .purpose; // Wait for translate to finish before calling addChatMessage @@ -217,21 +214,21 @@ function showChatTranscript(conversationId){ } }); }); - } else if (messageType === 'message') { + } else if(messageType === 'message') { return conversationsApi.getConversation(conversationId) - .then((data) => { - data.participants.forEach((participant) => { + .then(data => { + data.participants.forEach(participant => { if(participant.purpose === 'customer' || participant.purpose === 'agent') { - participant.messages.forEach((message) => { - message.messages.forEach((msg) => { + participant.messages.forEach(message => { + message.messages.forEach(msg => { messageIds.push(msg.messageId); - }) - }) + }); + }); } }); - return conversationsApi.postConversationsMessageMessagesBulk(conversationId, { 'body': messageIds }) - }).then((data) => { + return conversationsApi.postConversationsMessageMessagesBulk(conversationId, { body: messageIds }); + }).then(data => { data.entities.reverse(); const translationResults = []; @@ -243,7 +240,7 @@ function showChatTranscript(conversationId){ return; } - translationResults.push(new Promise((resolve, reject) => { + translationResults.push(new Promise(resolve => { translate.translateText(msg.textBody, genesysCloudLanguage, translatedData => { translationData = translatedData; resolve({ @@ -270,17 +267,17 @@ function showChatTranscript(conversationId){ /** * Set-up the channel for chat conversations - * @param {String} conversationId + * @param {String} conversationId * @returns {Promise} */ -function setupChatChannel(conversationId){ +function setupChatChannel(conversationId) { return controller.createChannel() - .then(data => { + .then(() => { // Subscribe to all incoming messages return controller.addSubscription( `v2.users.${userId}.conversations`, onMessage) - .then(data => { + .then(() => { return controller.addSubscription( `v2.conversations.chats.${conversationId}.messages`, onMessage); @@ -288,24 +285,24 @@ function setupChatChannel(conversationId){ }); } -/** - * This toggles between translator and canned response iframe - */ -function toggleIframe(){ - let label = document.getElementById('toggle-iframe').textContent; +/** + * This toggles between translator and canned response iframe + */ +function toggleIframe() { + let label = document.getElementById('toggle-iframe').textContent; - if(label === 'Open Chat Translator'){ + if(label === 'Open Chat Translator') { document.getElementById('toggle-iframe').textContent = 'Open Canned Responses'; document.getElementById('agent-assist').style.display = 'block'; document.getElementById('canned-response-container').style.display = 'none'; - } else { + } else { document.getElementById('toggle-iframe').textContent = 'Open Chat Translator'; document.getElementById('agent-assist').style.display = 'none'; document.getElementById('canned-response-container').style.display = 'block'; - + // Only call getLibraries function if element does not have a child - if(document.getElementById('libraries-container').childNodes.length == 0) getLibraries(); - } + if(document.getElementById('libraries-container').childNodes.length === 0) getLibraries(); + } } /** -------------------------- @@ -314,10 +311,10 @@ function toggleIframe(){ /** * Get all libraries in the org */ - function getLibraries(){ +function getLibraries() { return responseManagementApi.getResponsemanagementLibraries() - .then((libraries) => { - libraries.entities.forEach((library) => { + .then(libraries => { + libraries.entities.forEach(library => { getResponses(library.id, library.name); }); }); @@ -325,15 +322,15 @@ function toggleIframe(){ /** * Get all responses of each library - * @param {String} libraryId - * @param {String} libraryName + * @param {String} libraryId + * @param {String} libraryName */ -function getResponses(libraryId, libraryName){ +function getResponses(libraryId, libraryName) { return responseManagementApi.getResponsemanagementResponses(libraryId) - .then((responses) => { + .then(responses => { view.displayLibraries(libraryId, libraryName); - responses.entities.forEach((response) => { + responses.entities.forEach(response => { view.displayResponses(response, doResponseSubstitution); }); }); @@ -341,12 +338,12 @@ function getResponses(libraryId, libraryName){ /** * Search all responses in the org - * @param {String} query + * @param {String} query */ -function searchResponse(query){ - return responseManagementApi.postResponsemanagementResponsesQuery({'queryPhrase': query}) - .then((responses) => { - responses.results.entities.forEach((response) => { +function searchResponse(query) { + return responseManagementApi.postResponsemanagementResponsesQuery({ queryPhrase: query }) + .then(responses => { + responses.results.entities.forEach(response => { view.toggleDIVs(); view.displaySearchResults(response, doResponseSubstitution); }); @@ -356,24 +353,23 @@ function searchResponse(query){ /** * Replaces the dynamic variables in canned responses with appropriate * values. This function is used in the view when an agent clicks a response. - * @param {String} text - * @param {String} responseId + * @param {String} text + * @param {String} responseId */ -function doResponseSubstitution(text, responseId){ +function doResponseSubstitution(text, responseId) { let finalText = text; // Do the default substitutions first finalText = finalText.replace(/{{AGENT_NAME}}/g, agentName); finalText = finalText.replace(/{{CUSTOMER_NAME}}/g, customerName); finalText = finalText.replace(/{{AGENT_ALIAS}}/g, agentAlias); - let participantData = currentConversation.participants - .find(p => p.purpose == 'customer').attributes; + .find(p => p.purpose === 'customer').attributes; // Do the custom substitutions return responseManagementApi.getResponsemanagementResponse(responseId) - .then((responseData) => { + .then(responseData => { let subs = responseData.substitutions; subs.forEach(sub => { let subRegex = new RegExp(`{{${sub.id}}}`, 'g'); @@ -381,7 +377,7 @@ function doResponseSubstitution(text, responseId){ // Check if substitution exists on the participant data, if not // use default value - if(participantData[sub.id]){ + if(participantData[sub.id]) { val = participantData[sub.id]; } else { val = sub.defaultValue ? sub.defaultValue : val; @@ -393,12 +389,12 @@ function doResponseSubstitution(text, responseId){ return finalText; }) .catch(e => console.error(e)); -} +} /** -------------------------------------------------------------- * EVENT HANDLERS * -------------------------------------------------------------- */ -document.getElementById('toggle-iframe') +document.getElementById('toggle-iframe') .addEventListener('click', () => toggleIframe()); document.getElementById('chat-form') @@ -408,8 +404,8 @@ document.getElementById('btn-send-message') .addEventListener('click', () => sendChat()); document.getElementById('message-textarea') - .addEventListener('keypress', function (e) { - if (e.key === 'Enter') { + .addEventListener('keypress', function(e) { + if(e.key === 'Enter') { sendChat(); if(e.preventDefault) e.preventDefault(); // prevent new line return false; // Just a workaround for old browsers @@ -417,14 +413,14 @@ document.getElementById('message-textarea') }); document.getElementById('find-response-btn') - .addEventListener('click', function(){ + .addEventListener('click', function() { let query = document.getElementById('find-response').value; searchResponse(query); }); document.getElementById('find-response') - .addEventListener('keypress', function (e) { - if (e.key === 'Enter') { + .addEventListener('keypress', function(e) { + if(e.key === 'Enter') { let query = document.getElementById('find-response').value; searchResponse(query); } @@ -456,7 +452,7 @@ client.loginImplicitGrant( let stateData = JSON.parse(data.state); currentConversationId = stateData.conversationId; genesysCloudLanguage = stateData.language; - + // Get Details of current User return usersApi.getUsersMe(); }).then(userMe => { @@ -466,27 +462,26 @@ client.loginImplicitGrant( // Get current conversation return conversationsApi.getConversation(currentConversationId); -}).then((conv) => { +}).then(conv => { currentConversation = conv; - let customer = conv.participants.find(p => p.purpose == 'customer') + let customer = conv.participants.find(p => p.purpose === 'customer'); if(null != customer.chats && customer.chats.length > 0) { messageType = 'chat'; customerName = customer.name; - } else if (null != customer.messages && customer.messages.length > 0){ + } else if(null != customer.messages && customer.messages.length > 0) { messageType = 'message'; customerName = 'customer'; - if (null != customer.attributes && customer.attributes.hasOwnProperty('name')) { + if(null != customer.attributes && customer.attributes.hasOwnProperty('name')) { customerName = customer.attributes['name']; } } return setupChatChannel(currentConversationId); -}).then(data => { +}).then(() => { // Get current chat conversations return showChatTranscript(currentConversationId); -}).then(data => { +}).then(() => { console.log('Finished Setup'); - // Error Handling }).catch(e => console.log(e)); diff --git a/docs/scripts/notifications-controller.js b/docs/scripts/notifications-controller.js index bfa7949..b79c5fe 100644 --- a/docs/scripts/notifications-controller.js +++ b/docs/scripts/notifications-controller.js @@ -18,11 +18,10 @@ let subscriptionMap = { /** * Callback function for notications event-handling. * It will reference the subscriptionMap to determine what function to run - * @param {Object} event + * @param {Object} event */ -function onSocketMessage(event){ +function onSocketMessage(event) { let data = JSON.parse(event.data); - subscriptionMap[data.topicName](data); } @@ -31,7 +30,7 @@ export default { * Creation of the channel. If called multiple times, * the last one will be the active one. */ - createChannel(){ + createChannel() { return notificationsApi.postNotificationsChannels() .then(data => { console.log('---- Created Notifications Channel ----'); @@ -48,14 +47,14 @@ export default { * @param {String} topic PureCloud notification topic string * @param {Function} callback callback function to fire when the event occurs */ - addSubscription(topic, callback){ - let body = [{'id': topic}] + addSubscription(topic, callback) { + let body = [{ id: topic }]; return notificationsApi.postNotificationsChannelSubscriptions( channel.id, body) - .then((data) => { + .then(() => { subscriptionMap[topic] = callback; console.log(`Added subscription to ${topic}`); }); } -} \ No newline at end of file +}; diff --git a/docs/scripts/translate-service.js b/docs/scripts/translate-service.js index ed7ca07..66520f9 100644 --- a/docs/scripts/translate-service.js +++ b/docs/scripts/translate-service.js @@ -20,10 +20,10 @@ const languageCodeMapping = { 'ja': 'ja', 'zh-cn': 'zh', 'zh-tw': 'zh-TW' -} +}; export default { - translateText(text, language, callback){ + translateText(text, language, callback) { let language_code = languageCodeMapping[language] ? languageCodeMapping[language] : language; @@ -31,7 +31,7 @@ export default { raw_text: text, source_language: 'auto', target_language: language_code - } + }; fetch(config.translateServiceURI, { @@ -50,4 +50,4 @@ export default { }) .catch(e => console.error(e)); } -} +}; diff --git a/docs/scripts/view.js b/docs/scripts/view.js index 06e07dc..85e6dcb 100644 --- a/docs/scripts/view.js +++ b/docs/scripts/view.js @@ -1,7 +1,7 @@ /** * This script is focused on the HTML / displaying of data to the page */ -function updateScroll(){ +function updateScroll() { let div = document.getElementById('agent-assist'); div.scrollTop = div.scrollHeight; } @@ -9,10 +9,10 @@ function updateScroll(){ /** * Clear DIV of previous search results */ -function clearSearchResults(){ - let searchContainer = document.getElementById("search-result-container"); +function clearSearchResults() { + let searchContainer = document.getElementById('search-result-container'); - while (searchContainer.childNodes.length > 1) { + while(searchContainer.childNodes.length > 1) { searchContainer.removeChild(searchContainer.lastChild); } } @@ -20,14 +20,14 @@ function clearSearchResults(){ /** * Convert the html content of the canned response to plain text */ -function htmlToPlain(rawHtml){ +function htmlToPlain(rawHtml) { let finalText = rawHtml; finalText = finalText.replace(/<\/div>/ig, '\n'); finalText = finalText.replace(/<\/li>/ig, '\n'); finalText = finalText.replace(/
  • /ig, ' * '); finalText = finalText.replace(/<\/ul>/ig, '\n'); finalText = finalText.replace(/<\/p>/ig, '\n'); - finalText = finalText.replace(//gi, "\n"); + finalText = finalText.replace(//gi, '\n'); finalText = finalText.replace(/<[^>]+>/ig, ''); return finalText; @@ -39,7 +39,7 @@ export default { * @param {String} sender sender name to be displayed * @param {String} message chat message to be displayed */ - addChatMessage(sender, message, purpose){ + addChatMessage(sender, message, purpose) { let chatMsg = document.createElement('p'); chatMsg.textContent = sender + ': ' + message; @@ -53,22 +53,22 @@ export default { /** * Display list of libraries - * @param {String} libraryId - * @param {String} libraryName + * @param {String} libraryId + * @param {String} libraryName */ - displayLibraries(libraryId, libraryName){ + displayLibraries(libraryId, libraryName) { let libContainer = document.createElement('button'); libContainer.textContent = libraryName; libContainer.id = 'library-' + libraryId; libContainer.className = 'collapsible'; libContainer.addEventListener('click', function() { this.classList.toggle('active'); - let content = this.nextElementSibling; - if (content.style.display === 'block') { - content.style.display = 'none'; - } else { - content.style.display = 'block'; - } + let content = this.nextElementSibling; + if(content.style.display === 'block') { + content.style.display = 'none'; + } else { + content.style.display = 'block'; + } }); document.getElementById('libraries-container').appendChild(libContainer); @@ -80,9 +80,9 @@ export default { /** * Display responses and group by libraries - * @param {Object} response + * @param {Object} response */ - displayResponses(response, doResponseSubstitution){ + displayResponses(response, doResponseSubstitution) { let responseId = response.id; // Collapsible response name @@ -92,12 +92,12 @@ export default { responseButton.className = 'collapsible'; responseButton.addEventListener('click', function() { this.classList.toggle('active'); - let content = this.nextElementSibling; - if (content.style.display === 'block') { - content.style.display = 'none'; - } else { - content.style.display = 'block'; - } + let content = this.nextElementSibling; + if(content.style.display === 'block') { + content.style.display = 'none'; + } else { + content.style.display = 'block'; + } }); document.getElementById('responses-container-' + response.libraries[0].id).appendChild(responseButton); @@ -109,7 +109,7 @@ export default { responseText.addEventListener('click', function() { let text = htmlToPlain(response.texts[0].content); doResponseSubstitution(text, responseId) - .then((finalText) => { + .then(finalText => { document.getElementById('message-textarea').value = finalText; }) .catch(e => console.error(e)); @@ -119,9 +119,9 @@ export default { /** * Displays all search results in a DIV - * @param {object} results + * @param {object} results */ - displaySearchResults(results, doResponseSubstitution){ + displaySearchResults(results, doResponseSubstitution) { let responseId = results.id; // Collapsible response name @@ -131,12 +131,12 @@ export default { responseButton.className = 'collapsible'; responseButton.addEventListener('click', function() { this.classList.toggle('active'); - let content = this.nextElementSibling; - if (content.style.display === 'block') { - content.style.display = 'none'; - } else { - content.style.display = 'block'; - } + let content = this.nextElementSibling; + if(content.style.display === 'block') { + content.style.display = 'none'; + } else { + content.style.display = 'block'; + } }); document.getElementById('search-result-container').appendChild(responseButton); @@ -148,7 +148,7 @@ export default { responseText.addEventListener('click', function() { let text = htmlToPlain(results.texts[0].content); doResponseSubstitution(text, responseId) - .then((finalText) => { + .then(finalText => { document.getElementById('message-textarea').value = finalText; }) .catch(e => console.error(e)); @@ -159,11 +159,11 @@ export default { /** * This toggles between showing Canned Responses or Search Results */ - toggleDIVs(){ + toggleDIVs() { let cannedDIV = document.getElementById('libraries-container'); let searchDIV = document.getElementById('search-result-container'); - if(cannedDIV.style.display === 'block'){ + if(cannedDIV.style.display === 'block') { cannedDIV.style.display = 'none'; searchDIV.style.display = 'block'; } else { @@ -172,7 +172,7 @@ export default { } // Clear DIV of previous search results - let searchContainer = document.getElementById("search-result-container"); + let searchContainer = document.getElementById('search-result-container'); if(searchContainer.children.length > 1) clearSearchResults(); } -} \ No newline at end of file +};