diff --git a/App.js b/App.js index 5d2e0b6..fd9e298 100755 --- a/App.js +++ b/App.js @@ -1,18 +1,4 @@ -const agentBot = require('./lib/agentBot'); -const readline = require('readline'); +const agentBot = require('./lib/agent'); -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -rl.question('Please enter account id: \n', (accountId) => { - rl.question('Please enter agent user name: \n', (userName) => { - rl.question('Please enter agent password: \n', (password) => { - const agent = new agentBot(accountId, userName, password); - - rl.close(); - agent.start(); - }); - }); -}); \ No newline at end of file +const agent = new agentBot(); +agent.start(); \ No newline at end of file diff --git a/config/config.js b/config/config.js index 6baefc5..98f9194 100755 --- a/config/config.js +++ b/config/config.js @@ -1,14 +1,25 @@ - "use strict"; -var config={ - "lesession" : { - "csdsDomain" : "https://adminlogin.liveperson.net", - "refreshDelay" : 5 * 60 * 1000 // 5 minutes - }, - "chat" : { - pingInterval : 2, // chat ping interval in seconds - minLineWaitTime : 1 //minimum time to type a line in ms +let config = { + "lesession" : { + "csdsDomain" : "https://adminlogin.liveperson.net", + "refreshDelay" : 5 * 60 * 1000 // 5 minutes + }, + "chat" : { + pingInterval : 2, // chat ping interval in seconds + minLineWaitTime : 1 //minimum time to type a line in ms + }, + "account" : "51007909", + "alpha" : "72740529", + "support" : "34466921", + "users" : { + "bastion" : { + "login_name" : "Bastion", + "email" : "bastion@yourmercymain.com", + "username" : "Bastion", + "name" : "Bastion Turret", + "password" : "Munitioned1157!" } + } }; module.exports = config; diff --git a/helpers/string.js b/helpers/string.js new file mode 100644 index 0000000..5ea15ed --- /dev/null +++ b/helpers/string.js @@ -0,0 +1,13 @@ +class String { + + static compare ( expected, received ) { + // + let expectedUpper = expected.toUpperCase(); + // + let receivedUpper = received.toUpperCase(); + // + return (expectedUpper.includes(receivedUpper)); + } +} + +module.exports = String; \ No newline at end of file diff --git a/lib/agent.js b/lib/agent.js new file mode 100755 index 0000000..f3c1fc6 --- /dev/null +++ b/lib/agent.js @@ -0,0 +1,266 @@ +'use strict'; +// Import Async Module +const async = require('async'); +// Import Request Module +const request = require('request'); +// Import Configuration +const config = require('../config/config'); +// Import LiveEngage Session Class +const LESession = require('./loginSession'); +// Import Chat Class +const Chat = require('./chat'); + +class Agent { + + /** + * Constructor + */ + constructor() { + // LE Account + this.account = config.support; + // Bot User + this.userName = config.users.bastion.username; + // Bot Password + this.password = config.users.bastion.password; + } + + /** + * Will make requests on behalf on the Agent (Change Availability/Check for Incoming Chats) + * @param method PUT/GET/POST + * @param command + * @param body + * @param callback + */ + makeRequest( method, command, body, callback ) { + // Define Request Options + const options = { + // Method + method : method, + // URL for Request + url : `${this.requestURL}/${command}.json?v=1&NC=true`, + headers : { + 'Authorization' : `Bearer ${this.session.getBearer()}`, + 'content-type' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, + json : true + }; + // Check if there is a Body to attach + if (body) { + // Attach Body to to options + options.body = body; + } + // Check if method should be POST + if (method === 'PUT' || method === 'DELETE') { + // Set Post Headers + options.headers['X-HTTP-Method-Override'] = method; + // Set Method + options.method = 'POST'; + } + // Make Request + request(options, ( error, response, body ) => { + // Check for Errors + if (error) { + // Trigger Callback + callback(`Agent Error in ${command}: ${JSON.stringify(error)}`); + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Trigger Callback + callback(`Agent Error in ${command} body: ${JSON.stringify(body)}`); + } + // Trigger Callback + callback(null, body, response); + }); + } + + /** + * Will Start Agent + */ + start() { + // Log Agent Username + console.log(`Login username = ${this.userName}`); + // Create new Session with Credentials + this.session = new LESession(this.account, this.userName, this.password); + // Set Callbacks + async.series([callback => { + // Start Session + this.session.start(callback); + }, callback => { + // Login Session + this.session.login(callback); + }, callback => { + // Login Agent + this._login(callback); + }, callback => { + // Check Availability + this._setAvailability('Online', callback); + }], err => { + if (err) { + // Log Error + console.log('Error:' + err); + } else { + // Log State + console.log('Agent checkForIncomingChats'); + // Start Checking for Incoming Chats + this.checkForIncomingChats(); + } + }); + } + + /** + * Will Start a new Chat for given URL + * @param chatURL + */ + startChat( chatURL ) { + // Create new Agent for Current Session and Chat + const chat = new Chat(this.session, chatURL, this.requestURL); + // Start new Chat + chat.start(( error ) => { + // Check if any Errors while starting Chat + if (error) { + // Log Error + console.log(`Chat failed to start ${JSON.stringify(error)}`); + } else { + console.log(`Chat started successfully`); + } + }); + } + + /** + * Will login an Agent + * @param callback - Callback Function + * @private + */ + _login( callback ) { + // Define Service URL + let url = `https://${this.session.getCSDSDomain('agentVep')}/api/account/${this.account}/agentSession.json?v=1&NC=true`; + // Define Request Body + let body = { + // Login Data JSON + loginData : { + userName : this.userName, // User Name + password : this.password // Password + } + }; + // Define Request Options + const options = { + // Method + method : 'POST', + url : url, // URL for Request + headers : { + // Set Authorization Bearer + AUTHORIZATION : `Bearer ${this.session.getBearer()}` + }, + json : true, // Body Type - JSON, if False needs to be XML + body : body // Attach Body to Request + }; + // Make Request + request(options, ( error, response, body ) => { + // Check if Errors while making request + if (error) { + // Error Message + const msg = `Agent login failed - ${JSON.stringify(error)}`; + // Trigger Callback + callback(msg) + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Error Message + const msg = `Agent login failed - ${JSON.stringify(body)}`; + // Trigger Callback + callback(msg) + } + // Request Succeed + this.requestURL = body.agentSessionLocation.link['@href']; + console.log(`Agent login successfully. requestURL= ${this.requestURL}`); + callback(); + }); + } + + /** + * Will Set/Change Agent Availability + * @param value - + * "Online" - Agents can accept chat requests, + * "Away" - Agents cannot accept chat requests, + * "Occupied" - Agents can receive chats that were transferred to them + * @param callback + * @private + */ + _setAvailability( value, callback ) { + // Make Request + this.makeRequest('PUT', 'availability', { 'availability' : { 'chat' : value } }, err => { + // Check if Errors + if (err) { + // Trigger Callback + callback(`Error Checking Agent Availability ${err.message}`); + } else { + // Trigger Callback + callback(); + } + }); + } + + /** + * Will return the Agent's Availability to Accept Chat Requests. + * @param callback ("chat": "Online/Away/Occupied", "voice": "Offline/Online") + * @private + */ + _getAvailability ( callback ){ + // Make Request to fetch Availability Status + this.makeRequest('GET','availability',null, (err,body) =>{ + // Check if any Errors + if (err) { + // Log Error + console.log(`Get Availability Error :: ${JSON.stringify(err)}`); + // Trigger Callback - Error + callback(err); + } else { + // Check if availability is on body + if (body.hasOwnProperty('availability')){ + // Trigger Callback - Availability + callback(body.availability); + } else { + // Trigger Callback - null + callback(); + } + } + }); + } + + /** + * Will check for Incoming Chats (New Chats) + */ + checkForIncomingChats() { + // Make Request + this.makeRequest('GET', 'incomingRequests', null, ( err, body ) => { + // LOG Error + console.log(`Error :: ${err}`); + // + console.log(`Body :: ${JSON.stringify(body)}`); + // Check if any Errors + if (err) { + // Log Error + console.log(`Check for incomingRequests error: ${JSON.stringify(err)}`); + } else { + // Check if there is an Incoming request or an Ring > 0 + if (body && body.incomingRequests && (body.incomingRequests.ringingCount > 0)) { + // Log - Incoming Chat + console.log('There is an incoming chat!'); + // Get Request URL + const chatURL = body.incomingRequests.link['@href']; + // URL exist + if (chatURL) { + // Start New Chat + this.startChat(chatURL); + } + } + // Set Timeout Function to Check for Incoming Chats Regularly + this.incomingTimer = setTimeout(() => { + // Set Function for as Timeout + this.checkForIncomingChats(); + }, // Set Interval + 2000); + } + }); + } + +} +// Expose Class +module.exports = Agent; diff --git a/lib/agentBot.js b/lib/agentBot.js deleted file mode 100755 index 8638d75..0000000 --- a/lib/agentBot.js +++ /dev/null @@ -1,159 +0,0 @@ -'use strict'; - -const async = require('async'); -const request = require('request'); - -const config = require('../config/config'); -const LESession = require('./loginSession'); -const AgentChat = require('./agentChat'); - - -class Agent { - constructor(account, userName, password) { - this.account = account; - this.userName = userName; - this.password = password; - } - - start() { - console.log(`Login username = ${this.userName}`); - this.session = new LESession(this.account, this.userName, this.password); - async.series([ - callback => { - this.session.start(callback); - }, - callback => { - this.session.login(callback); - }, - callback => { - this._loginAgent(callback); - }, - callback => { - this._setAvailability('Online', callback); - } - ], err => { - if (err) { - console.log('Error:' + err); - } - else { - console.log('Agent checkForIncomingChats'); - this.checkForIncomingChats(); - } - }); - } - - - _loginAgent(callback) { - const options = { - method: 'POST', - url: `https://${this.session.getCSDSDomain('agentVep')}/api/account/${this.account}/agentSession.json?v=1&NC=true`, - headers: { - AUTHORIZATION: `Bearer ${this.session.getBearer()}` - }, - json: true, - body: { - loginData: { - userName: this.userName, - password: this.password - } - } - }; - - request(options, (error, response, body) => { - if(error){ - const msg = `Agent login failed - ${JSON.stringify(error)}`; - console.log(msg); - callback(msg) - } - else if(response.statusCode < 200 || response.statusCode > 299){ - const msg = `Agent login failed - ${JSON.stringify(body)}`; - console.log(msg); - callback(msg) - } - this.requestURL = body.agentSessionLocation.link['@href']; - console.log(`Agent login successfully. requestURL= ${this.requestURL}`); - callback(); - - }); - } - - _setAvailability(value, callback) { - this.agentRequest(`PUT`, `availability`, {'availability': {'chat': value}}, err => { - if (err) { - callback(`Error Checking Agent Availability ${err.message}`); - } - else { - console.log(`Agent Availability has been set: ${value}`); - callback(); - } - }); - } - - agentRequest(method, command, body, callback) { - const options = { - method: method, - url: `${this.requestURL}/${command}.json?v=1&NC=true`, - headers: { - 'Authorization': `Bearer ${this.session.getBearer()}`, - 'content-type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - }, - json: true - }; - if (body) { - options.body = body; - } - if (method === 'PUT' || method === 'DELETE') { - options.headers['X-HTTP-Method-Override'] = method; - options.method = 'POST'; - } - - request(options, (error, response, body) => { - if (error) { - callback(`Agent Error in ${command}: ${JSON.stringify(error)}`); - } - else if(response.statusCode < 200 || response.statusCode > 299){ - callback(`Agent Error in ${command} body: ${JSON.stringify(body)}`); - } - - callback(null, body, response); - - }); - } - - checkForIncomingChats() { - this.agentRequest('GET', 'incomingRequests', null, (err, body) => { - if (err) { - console.log(`Check for incomingRequests error: ${JSON.stringify(err)}`); - } - else { - if (body && body.incomingRequests && (body.incomingRequests.ringingCount > 0)) { - console.log('There is an incoming chat!'); - const chatURL = body.incomingRequests.link['@href']; - - if (chatURL) { - this.startChat(chatURL); - } - } - - this.incomingTimer = setTimeout(() => { - this.checkForIncomingChats(); - }, 2000); - } - }); - } - - startChat(chatURL) { - const chat = new AgentChat(this.session, chatURL); - chat.start((err) => { - if (err) { - console.log(`Chat failed to start`); - } - else { - console.log(`Chat started successfully`); - } - }); - } -} - -module.exports = Agent; diff --git a/lib/agentChat.js b/lib/agentChat.js deleted file mode 100755 index cfcf0c5..0000000 --- a/lib/agentChat.js +++ /dev/null @@ -1,233 +0,0 @@ -'use strict'; - -const util = require('util'); -const request = require('request'); -const config = require('../config/config'); -const transcript = require('../chats/transcript.json'); - - - -function getNextPingURL(linkArr) { - for (let i = 0; i < linkArr.length; i++) { - const link = linkArr[i]; - if (link['@rel'] === 'next') { - return link['@href'].replace('/events', '/events.json'); - } - } -} - -class AgentChat { - constructor(session, chatURL) { - this.session = session; - this.chatURL = chatURL; - this.lineIndex = 0; - this.chatPingInterval = 2000; - } - - start(callback) { - this.startChatSession((err, data) => { - if (err) { - callback(err); - } - else { - callback(null); - this.chatLink = data.chatLink; - this.chatPolling(); - } - }); - } - - startChatSession(callback) { - console.log(`(startChatSession) In linkForNextChat: ${this.chatURL}`); - - const options = { - method: 'POST', - url: `${this.chatURL}.json?v=1&NC=true`, - headers: { - 'Authorization': `Bearer ${this.session.getBearer()}`, - 'content-type': 'application/json', - 'Accept': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - }, - json: true, - body: {'chat': 'start'} - }; - - request(options, (error, response, body) => { - if (error) { - callback(`Failed to start chat session with error: ${JSON.stringify(error)}`); - } - else if(response.statusCode < 200 || response.statusCode > 299){ - callback(`Failed o start chat session with error: ${JSON.stringify(body)}`); - } - console.log(`Start chat session - body: ${body.chatLocation.link['@href']}`); - callback(null, { - chatLink: body.chatLocation.link['@href'] - }); - }); - } - - chatPolling(url) { - if (!url) { - url = this.chatLink + '.json?v=1&NC=true' - } - - const options = { - method: 'GET', - url: url, - headers: { - 'Authorization': `Bearer ${this.session.getBearer()}`, - 'content-type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - }, - json:true - }; - - request(options, (error, response, body)=> { - if (error) { - console.error(`Agent polling failed. Error: ${JSON.stringify(error)}`); - return; - } - else if(response.statusCode < 200 || response.statusCode > 299){ - console.error(`Agent polling failed. body: ${JSON.stringify(body)}`); - return; - } - let events; - let nextURL; - - if (body.chat && body.chat.error) { - console.log(`Chat error: ${JSON.stringify(body.chat.error)}`); - return; - } - - if (body.chat && body.chat.events) { - nextURL = `${getNextPingURL(body.chat.events.link)}&v=1&NC=true`; - events = body.chat['events']['event']; - } - else { - try { - nextURL = `${getNextPingURL(body.events.link)}&v=1&NC=true`; - } - catch (e) { - console.log(`Error getting the next URL link: ${e.message}, body=${JSON.stringify(body)}`); - return; - } - events = body['events']['event']; - } - - if (events) { - if (!Array.isArray(events)) { // The API send an object and not an array if there is 1 event only - events = [events]; - } - for (let i = 0; i < events.length; i++) { - const ev = events[i]; - - if ((ev['@type'] === 'state') && (ev.state === 'ended')) { - return; - } - else if ((ev['@type'] === 'line') && (ev['source'] === 'visitor')) { - console.log(`(chatPolling) - line form visitor:${ev.text}`); - - this.sendLine(); - } - } - } - this.chatTimer = setTimeout(() => { - this.chatPolling(nextURL); - }, this.chatPingInterval); - }); - } - - sendLine() { - const line = transcript[this.lineIndex]; - - if (!line) { - this.stop(err => { - if (err) { - console.log(`Error stopping chat err: ${err.message}`); - } - }); - return; - } - - - console.log(`Sending line: ${line}`); - const options = { - method: 'POST', - url: `${this.chatLink}/events.json?v=1&NC=true`, - headers: { - 'Authorization': `Bearer ${this.session.getBearer()}`, - 'content-type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - }, - json: true, - body: { - event: { - '@type': 'line', - 'text': `
${line}
`, - 'textType': 'html' - } - } - }; - - setTimeout(() => { - request(options, (error, response, body) => { - this.lineIndex++; - if (error) { - console.log(`Error sending line. Error: ${JSON.stringify(error)}`); - } - else if(response.statusCode < 200 || response.statusCode > 299){ - console.log(`Error sending line. Body: ${JSON.stringify(body)}`); - - } - console.log(`Send line: ${JSON.stringify(body)}`); - }); - }, config.chat.minLineWaitTime); - } - - stop(callback) { - clearTimeout(this.chatTimer); - clearTimeout(this.incomingTimer); - - if (this.chatLink) { - const options = { - method: 'POST', - url: `${this.chatLink}/events.json?v=1&NC=true`, - headers: { - 'Authorization': `Bearer ${this.session.getBearer()}`, - 'content-type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - }, - json: true, - body: { - event: { - '@type': 'state', - 'state': 'ended' - } - } - }; - request(options, (error, response, body) => { - if (error) { - callback(`Error trying to end chat: ${JSON.stringify(error)}`); - } - else if(response.statusCode < 200 || response.statusCode > 299){ - callback(`Error trying to end chat: ${JSON.stringify(body)}`); - } - this.session.stop(err => { - if (err) { - console.log(`Error stopping session: ${err.message}`); - callback(err); - } - else { - callback(); - } - }); - }); - }else{ - callback(`Chat link is unavailable chatLink: ${this.chatLink}`); - } - } - -} - -module.exports = AgentChat; diff --git a/lib/chat.js b/lib/chat.js new file mode 100755 index 0000000..3103c3a --- /dev/null +++ b/lib/chat.js @@ -0,0 +1,639 @@ +'use strict'; + +const util = require('util'); +const request = require('request'); +const config = require('../config/config'); +const transcript = require('../chats/transcript.json'); +const StringHelper = require('../helpers/string'); +const StructureContent = require('../structure_content/content'); + + +class Chat { + + /** + * Constructor + * @param session - Session ID + * @param chatURL - Chat URL + * @param serviceURL - URL with Session Info - need for transfer Chat + */ + constructor( session, chatURL, serviceURL ) { + // Set Session + this.session = session; + // Set Chat URL + this.chatURL = chatURL; + // Set Service URL + this.serviceURL = serviceURL; + // Set Line + this.lineIndex = 0; + // Set Pinging Interval - Will Keep Agent In Chat + this.chatPingInterval = 2000; + } + + /** + * Will return Next Link for Chat + * @param linkArr + * @returns {string | void | *} + * @private + */ + _getNextPingURL( linkArr ) { + for (let i = 0; i < linkArr.length; i++) { + const link = linkArr[i]; + if (link['@rel'] === 'next') { + return link['@href'].replace('/events', '/events.json'); + } + } + } + + /** + * Will get NextURL and Events from Request + * @param data + * @returns {{events: *, url: *}} + * @private + */ + _getEvents( data ) { + // Events Holder + let events; + // URL Holder + let nextURL; + // Check if Chat is available and there are Events + if (data.chat && data.chat.events) { + // Set Next URL from Events + nextURL = `${this._getNextPingURL(data.chat.events.link)}&v=1&NC=true`; + // Set Events + events = data.chat['events']['event']; + } else { + // Try-Chat Block + try { + // Set Next URL from Events + nextURL = `${this._getNextPingURL(data.events.link)}&v=1&NC=true`; + } catch (e) { + // Log Error + console.log(`Error getting the next URL link: ${e.message}, body=${JSON.stringify(data)}`); + // Escape + return; + } + // Set Events + events = data['events']['event']; + } + // Events & Next URL for Chat + return { + events : events, + url : nextURL + } + } + + /** + * Will Start new Chat Session + * @param callback + */ + start( callback ) { + // Start Session + this.startSession(( err, data ) => { + // Check if any Errors while starting Session + if (err) { + // Trigger Callback + callback(err); + } else { + // Trigger Callback + callback(null); + // Set Chat Link + this.chatLink = data.chatLink; + // Start Polling Chat + this.polling(); + } + }); + } + + /** + * Will Stop Chat + * @param callback + */ + stop( callback ) { + // Cleat Timeout + clearTimeout(this.chatTimer); + // Clear Incoming Timer + clearTimeout(this.incomingTimer); + // URL for Chat + let url = `${this.chatLink}/events.json?v=1&NC=true`; + // Authentication Bearer + let bearer = `Bearer ${this.session.getBearer()}`; + // Check if Chat Link Exist + if (this.chatLink) { + // Define Request Options + const options = { + method : 'POST', + url : url, + headers : { + 'Authorization' : bearer, + 'content-type' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, + json : true, // Body Type - JSON, if False needs to be XML + body : { + event : { + '@type' : 'state', // 'state','line' + 'state' : 'ended' + } + } + }; + // Make Request + request(options, ( error, response, body ) => { + // Check if Errors while making request + if (error) { + // Error Message + let error = `Error trying to end chat: ${JSON.stringify(error)}`; + // Trigger Callback + callback(error); + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Error Message + let error = `Error trying to end chat: ${JSON.stringify(body)}`; + callback(error); + } else { + // Trigger Callback with Status Code + callback(null, response.statusCode); + } + /*// Stop Session + this.session.stop(err => { + // Check if Errors while stopping Chat + if (err) { + // Trigger Callback + callback(err); + } else { + // Trigger Callback - Success + callback(null, response.statusCode); + } + });*/ + }); + } else { + // Trigger Callback - Chat Link Unavailable + callback(`Chat link is unavailable chatLink: ${this.chatLink}`); + } + } + + /** + * Will calculate the Best Agent to Transfer Chat to + * @param agents - Array of Agents + * @returns {*} - Best Agent + * @private + */ + _calculateBestAvailableAgent( agents ) { + // + let agent = null; + // + let bestAvailability = 0; + // + console.log(`Number of Agents :: ${agents}`); + if (agents.length !== undefined) { + agents.forEach(( item ) => { + // if Agent doesn't have Chats Key means Agent doesn't have a current Chat + if (item.hasOwnProperty('chats')) { + let availability = parseInt(item['@maxChats']) - parseInt(item['chats']); + // + if (bestAvailability > availability) { + bestAvailability = availability; + agent = item; + } + } else { + bestAvailability = -1; + // Set Agent + agent = item; + } + }); + } + // + return agent; + } + + /** + * Will get list of Agents depending on Selected State ("Online", "Offline", "Occupied", "Away") + * @param callback + * @private + */ + _getAvailableAgents( callback ) { + // Service URL + let url = `${this.serviceURL}/availableAgents`; + // Define Chat State ("Online", "Offline", "Occupied", "Away") + let chatState = 'Online'; + // Define Request Options + const options = { + // Method + method : 'GET', + url : `${url}.json?v=1&NC=true&chatState=${chatState}`, // URL for Request + headers : { + 'Authorization' : `Bearer ${this.session.getBearer()}`, + 'content-type' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, + json : true, + }; + // Make Request + request(options, ( error, response, body ) => { + // Check for Errors + if (error) { + // Log Error + console.log(`Retrieving Available Agents Error: ${JSON.stringify(error)}`); + // Trigger Callback + callback(error,); + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Log Error + console.log(`Error Retrieving Available Agents Body: ${JSON.stringify(body)}`); + // Trigger Callback + callback(null, body); + } + // Trigger Callback + callback(null, body, response); + }); + } + + /** + * Will get Agent with Best Availability + * @param callback + * @private + */ + _getAgentWithBestAvailability( callback ) { + // Agents Holder + let pool = null; + // Get List of Online Agents + this._getAvailableAgents(( error, response, body ) => { + // Check for Errors + if (!error && response !== null) { + // Set Agents + pool = response; + } + callback(this._calculateBestAvailableAgent(pool.availableAgents.agents.agent)) + }); + } + + /** + * Will transfer a Chat to an Agent or Skill + * if both Agent & Skill are present Agent takes precedent + * @param agent - Agent Object + * @param skill - Skill ID + * @param callback - Callback Function + */ + transfer( agent, skill, callback ) { + // Define Body for Transfer + let body = { + transfer : {}, + text : "I think this person needs some help from you." + }; + // Check if Agent is available + if (agent) { + // Attach Agent ID to Body + body.transfer = { + agent : { + id : agent['@id'] + } + }; + // Check if Skill was pass + } else if (skill) { + // Attach Skill ID to Body + body.transfer = { + skill : { + id : skill + } + }; + } else if ( agent && skill){ + // Attach Agent ID to Body + body.transfer = { + agent : { + id : agent['@id'] + } + }; + } else { + // Trigger Callback - Chat Link Unavailable + callback(`An Agent or Specific Skill ID is needed to Transfer a Chat`); + } + console.log(`Body :: ${JSON.stringify(body)}`); + // URL for Chat + let url = `${this.chatLink}/transfer.json?v=1&NC=true`; + // Authentication Bearer + let bearer = `Bearer ${this.session.getBearer()}`; + // Check if Chat Link Exist + if (this.chatLink) { + // Define Request Options + const options = { + method : 'POST', + url : url, + headers : { + 'Authorization' : bearer, + 'content-type' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, + json : true, // Body Type - JSON, if False needs to be XML + body : body + }; + // Make Request + request(options, ( error, response, body ) => { + // Check if Errors while making request + if (error) { + // Error Message + let error = `Error trying to transfer chat: ${JSON.stringify(error)}`; + // Trigger Callback + callback(error); + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Error Message + let error = `Error trying to transfer chat: ${JSON.stringify(body)}`; + callback(error); + } + // + console.log(`Transfer Status :: ${response.statusCode}`); + // Trigger Callback - Chat Link Unavailable + callback(null, body); + }); + } else { + // Trigger Callback - Chat Link Unavailable + callback(`Chat link is unavailable chatLink: ${this.chatLink}`); + } + } + + /** + * (Wrapper) Will Start new Chat Session + * @param callback + */ + startSession( callback ) { + // URL + let url = `${this.chatURL}.json?v=1&NC=true`; + // Bearer + let bearer = `Bearer ${this.session.getBearer()}`; + // Define Request Options + const options = { + // Method + method : 'POST', // URL for Request + url : url, // Headers + headers : { + 'Authorization' : bearer, + 'content-type' : 'application/json', + 'Accept' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, // Body Type - JSON, if False needs to be XML + json : true, // Body + body : { 'chat' : 'start' } + }; + // Make Request + request(options, ( error, response, body ) => { + // Check if Errors + if (error) { + // Trigger Callback + callback(`Failed to start chat session with error: ${JSON.stringify(error)}`); + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Trigger Callback + callback(`Failed o start chat session with error: ${JSON.stringify(body)}`); + } + // Log + console.log(`Start chat session - body: ${body.chatLocation.link['@href']}`); + // Trigger Callback + callback(null, { + chatLink : body.chatLocation.link['@href'] + }); + }); + } + + /** + * Will start Polling Chat Events from given Chat URL + * @param url + */ + polling( url ) { + // Check if Chat URL is Available + if (!url) { + // Set Chat URL if no URL was set + url = this.chatLink + '.json?v=1&NC=true' + } + console.log(`URL :: ${url}`); + // Define Request Options + const options = { + method : 'GET', + url : url, + headers : { + 'Authorization' : `Bearer ${this.session.getBearer()}`, + 'content-type' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, + json : true // Body Type - JSON, if False needs to be XML + }; + // Make Request + request(options, ( error, response, body ) => { + // Check if any Errors while polling Chat + if (error) { + // Log Error + console.error(`Agent polling failed. Error: ${JSON.stringify(error)}`); + // Escape + return; + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Log - Status not expected + console.error(`Agent polling failed. body: ${JSON.stringify(body)}`); + // Escape + return; + } + // Check if Chat is available and there are no Errors + if (body.chat && body.chat.error) { + // Log Error + console.log(`Chat error: ${JSON.stringify(body.chat.error)}`); + // Escape + return; + } + // Get Events & URL from Body + let data = this._getEvents(body); + // Set Events + let events = data.events; + // Set Next URL from Ping + let nextURL = data.url; + // Manage Bot Responding to Chat + this.manage(events); + // Set new Chat Timeout Function + this.chatTimer = setTimeout(() => { + // Set Polling for Next URL + this.polling(nextURL); + }, this.chatPingInterval); + }); + } + + /** + * Will manage Bot responding chat if Events are valid. + * @param events - Chat Events + */ + manage( events ) { + // Check if any Events + if (events) { + // The API send an object and not an array if there is 1 event only + if (!Array.isArray(events)) { + // Get Events if not and Array + events = [events]; + } + // Iterate Events + for (let i = 0; i < events.length; i++) { + // Get Event + const ev = events[i]; + // Check if the Conversation is over + if ((ev['@type'] === 'state') && (ev.state === 'ended')) { + // Escape + return; + } else if ((ev['@type'] === 'line') && (ev['source'] === 'visitor')) { + // Log Visitor Line + console.log(`(chatPolling) - line form visitor: ${ev.text}`); + if (StringHelper.compare('appointment', ev.text)) { + // + this.sendContent(StructureContent.newAppointment); + } else if (StringHelper.compare('day', ev.text)) { + // + this.sendContent(StructureContent.day); + } else if (StringHelper.compare('Monday Tuesday Wednesday Thursday Friday', ev.text)) { + // + this.sendContent(StructureContent.hour); + } else if (StringHelper.compare('complex', ev.text)) { + // Send Example with Complex Structure Content Template + this.sendContent(StructureContent.complexContent); + } else if (StringHelper.compare('simple', ev.text)) { + // Send Example with Simple Structure Content Template + this.sendContent(StructureContent.simple); + } else if (StringHelper.compare('transfer', ev.text)) { + // Will Get Best Agent to Transfer + this._getAgentWithBestAvailability(( agent ) => { + if (agent){ + // Transfer Chat + this.transfer(agent, null,( info ) => { + // Log + console.log(`Status :: ${info}`); + }); + } else { + // Send No Available Agents + this.sendLine("Sorry for the moment there aren't any available Agents to transfer you") + } + }); + } else if(StringHelper.compare('stop', ev.text)) { + // Close Chat + this.stop((error, statusCode) => { + // Check for Errors + if (error){ + // Log Error + console.log(`Ending Chat Error :: ${error}`); + } else { + // Log Status + console.log(`Chat Status Code :: ${statusCode}`); + } + }); + } else { + // Send Regular Line + this.sendLine(); + } + } + } + } + } + + /** + * Will send a single line to the Chat + * @param newline + */ + sendLine( newline ) { + // Set Line - if there is no line, it will use line from transcript + let line = (newline == null) ? transcript[this.lineIndex] : newline; + // Check if Line is not null + if (!line) { + // Stop Chat + this.stop(err => { + // Check if any Errors stopping chat + if (err) { + // Log Error + console.log(`Error stopping chat err: ${err.message}`); + } + }); + // Escape + return; + } + // URL for Chat + let url = `${this.chatLink}/events.json?v=1&NC=true`; + // Authentication Bearer + let bearer = `Bearer ${this.session.getBearer()}`; + // Define Request Options + const options = { + // Method + method : 'POST', // URL for Request + url : url, // Set Headers + headers : { + 'Authorization' : bearer, + 'content-type' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, // Body Type - JSON, if False needs to be XML + json : true, // Attach Body to Request + body : { + event : { + '@type' : 'line', //'state','line' + 'text' : `${line}
`, + 'textType' : 'html' + } + } + }; + // Set Timeout to Make Request + setTimeout(() => { + // Make Request + request(options, ( error, response, body ) => { + // Increment Line Index + this.lineIndex++; + // Check if any errors while sending new Line + if (error) { + // Log Error + console.log(`Error sending line. Error: ${JSON.stringify(error)}`); + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Log - Not Expected Code + console.log(`Error sending line. Body: ${JSON.stringify(body)}`); + + } + // Log Body Request + console.log(`Send line: ${JSON.stringify(body)}`); + }); + }, config.chat.minLineWaitTime); + } + + /** + * Will send Structure Content to Chat + * @param content - JSON for SC + */ + sendContent( content ) { + // URL for Chat + let url = `${this.chatLink}/events.json?v=1&NC=true`; + // Authentication Bearer + let bearer = `Bearer ${this.session.getBearer()}`; + // Log Structure Content + console.log(`Structure Content: ${JSON.stringify(content)}`); + // TODO: @type value can be: 'state','line' + // Define Request Options + const options = { + method : 'POST', + url : url, + headers : { + 'Authorization' : bearer, + 'content-type' : 'application/json', + 'X-Requested-With' : 'XMLHttpRequest' + }, + json : true, + body : { + "event" : { + "@type" : "line", + "textType" : "rich-content", + "json" : content + } + } + }; + // Set Timeout to Make Request + setTimeout(() => { + // Make Request + request(options, ( error, response, body ) => { + // Increment Line Index + this.lineIndex++; + // Check if any errors while sending new Line + if (error) { + // Log Error + console.log(`Error sending structure content. Error: ${JSON.stringify(error)}`); + } else if (response.statusCode < 200 || response.statusCode > 299) { + // Log - Not Expected Code + console.log(`Error sending structure content. Body: ${JSON.stringify(body)}`); + } + // Log Body Request + console.log(`Send line: ${JSON.stringify(body)}`); + }); + }, config.chat.minLineWaitTime); + } +} + +module.exports = Chat; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ccf33c8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,542 @@ +{ + "name": "agent-sample-app", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", + "integrity": "sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw=", + "requires": { + "lodash": "4.17.4" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "body-parser": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.2.tgz", + "integrity": "sha1-11eM9PHRHV9uqATO813Hp/9trmc=", + "requires": { + "bytes": "2.4.0", + "content-type": "1.0.4", + "debug": "2.2.0", + "depd": "1.1.2", + "http-errors": "1.5.1", + "iconv-lite": "0.4.13", + "on-finished": "2.3.0", + "qs": "6.2.0", + "raw-body": "2.1.7", + "type-is": "1.6.15" + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.0" + } + }, + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, + "http-errors": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", + "integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=", + "requires": { + "inherits": "2.0.3", + "setprototypeof": "1.0.2", + "statuses": "1.4.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "iconv-lite": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", + "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz", + "integrity": "sha1-O3hIwDwt7OaalSKw+ujEEm10Xzs=" + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + } + }, + "readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" + }, + "request": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + }, + "dependencies": { + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "setprototypeof": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz", + "integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg=" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.0" + } + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.17" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + } + } +} diff --git a/package.json b/package.json index 7debc29..bea51a1 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-sample-app", - "description":"Agent sample app", + "description": "Agent sample app", "version": "0.0.1", "scripts": { "start": "node ./App.js" @@ -8,12 +8,14 @@ "dependencies": { "async": "~2.1.4", "body-parser": "~1.15.2", - "request": "~2.79.0", "cookie-parser": "~1.4.3", - "readline": "~1.3.0" + "readline": "~1.3.0", + "request": "^2.83.0" }, "engines": { "node": ">=6.9.1", "npm": ">=3.10.9" - } + }, + "license": "MIT", + "repository": "" } diff --git a/structure_content/content.js b/structure_content/content.js new file mode 100644 index 0000000..64c2d42 --- /dev/null +++ b/structure_content/content.js @@ -0,0 +1,284 @@ +let content = { + // + newAppointment : { + type : "vertical", + elements : [{ + type : "text", + text : "Dentist Appointment", + tooltip : "Create new appointment", + style : { + bold : true, + size : "large", + color : "#000" + } + }, { + type : "button", + title : "Create new appointment", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "day" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }] + }, + day : { + type : "vertical", + elements : [{ + type : "text", + text : "Select Day", + tooltip : "Create new appointment", + style : { + bold : true, + size : "large", + color : "#000" + } + }, { + type : "button", + title : "Monday", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "Monday" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "Tuesday", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "Tuesday" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "Wednesday", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "Wednesday" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "Thursday", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "Thursday" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "Friday", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "Friday" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }] + }, + hour : { + type : "vertical", + elements : [{ + type : "text", + text : "Select Hour", + tooltip : "Create new appointment", + style : { + bold : true, + size : "large", + color : "#000" + } + }, { + type : "button", + title : "12:00pm", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "12:00pm" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "1:00pm", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "1:00pm" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "2:00pm", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "2:00pm" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "3:00pm", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "3:00pm" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }, { + type : "button", + title : "4:00pm", + click : { + metadata : [{ + "type" : "ExternalId", + "id" : "12345678" + }], + actions : [{ + "type" : "publishText", + "text" : "4:00pm" + }] + }, + style : { + bold : false, + size : "medium", + color : "#0A0A0" + } + }] + }, + simple : { + "type" : "text", + "text" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis egestas dignissim arcu, quis facilisis augue pharetra quis. Phasellus convallis ullamcorper felis in ultrices. Morbi pharetra at urna ut laoreet. Ut mauris ipsum, imperdiet eu quam at, dignissim malesuada tellus. Mauris in eros tincidunt, ullamcorper enim quis, cursus elit. Nam eu dui sed nisi laoreet aliquam. Sed eget lorem ut neque dapibus placerat. Donec sapien leo, volutpat a quam eget, cursus tincidunt dolor. Sed suscipit, mauris at tincidunt luctus, neque leo mattis est, sed ornare elit felis et libero. Vestibulum blandit luctus quam. Integer et enim eleifend, semper justo vel, feugiat quam. Nullam eu ultrices lectus. Curabitur nibh dui, mattis vel purus sed, viverra interdum ante. Maecenas pellentesque dapibus dui, ac vulputate velit.", + "tooltip" : "text tooltip" + }, + complexContent : { + "type" : "vertical", + "elements" : [{ + "type" : "image", + "url" : "https://mobileimages.lowes.com/product/converted/885911/885911334792.jpg", + "tooltip" : "image tooltip", + }, { + "type" : "horizontal", + "elements" : [{ + "type" : "button", + "title" : "Add to cart", + "click" : { + "actions" : [{ + "type" : "publishText", + "text" : "" + }] + } + }, { + "type" : "button", + "title" : "Add to cart", + "click" : { + "actions" : [{ + "type" : "publishText", + "text" : "" + }] + } + }] + }, { + "type" : "button", + "title" : "Buy now", + "click" : { + "actions" : [{ + "type" : "publishText", + "text" : "" + }] + } + }] + } +}; +module.exports = content; \ No newline at end of file