Wednesday, June 17, 2020

AWS Connect Chat/Lex Bot - Web Client via API-Gateway, Lambda


Summary

This post is a continuation my previous on the topic web chat client integration to AWS Connect.  In this post, I utilize more of the AWS suite to implement the same chat client.  Specifically, I put together a pure cloud architecture with CloudFront providing CDN services, S3 providing static web hosting, API-Gateway providing REST API call proxying to Lambda, and finally Lambda providing the direct SDK integration with Connect.

Architecture

Below is a diagram depicting what was discussed above.  A static HTML/Javascript site is hosted on S3.  That site is front-ended by CloudFront.  The Javascript client application makes REST calls to API-Gateway which proxies those calls to a Lambda function.  The Lambda function in turn is proxying those calls to the appropriate AWS SDK calls to Connect.

 

Web Client Architecture

The diagram below depicts the client architecture.  All SDK calls to AWS Connect are abstracted to REST calls into API-Gateway + Lambda.

 

Code Snippets

Client POST/connect call

 async _connect() {  
  try {
   const body = {
    DisplayName: this.displayName,
    ParticipantToken: this.participantToken
   };
   const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
     'Content-Type': 'application/json'
    },
    body: JSON.stringify(body)
   });
   
   const json = await response.json();
   if (response.ok) {
    this.participantToken = json.ParticipantToken;
    const diff = Math.abs(new Date() - Date.parse(json.Expiration));
    this.refreshTimer = setTimeout(this._connect, diff - 5000); //refresh the websocket
    this.connectionToken = json.ConnectionToken;
    this._subscribe(json.Url);
   }
   else {
    throw new Error(JSON.stringify(json));
   }
  }
  catch(err) {
   console.log(err);
  }
 }

Corresponding Lambda Proxy

exports.handler = async (event) => {
 let resp, body;
 try {
  AWS.config.region = process.env.REGION; 
  AWS.config.credentials = new AWS.Credentials(process.env.ACCESS_KEY_ID, 
   process.env.SECRET_ACCESS_KEY);

  switch (event.path) {
   case '/connectChat': 
    switch (event.httpMethod) {
     case 'POST':
      body = JSON.parse(event.body);
      resp = await connect(body.DisplayName, body.ParticipantToken);
      return {
       headers: {'Access-Control-Allow-Origin': '*'}, 
       statusCode : 200,
       body : JSON.stringify(resp)
      } 
async function connect(displayName, token) {
 let sdk, params, response, participantToken;

 if (token) {
  participantToken = token;
 } 
 else {
  sdk = new AWS.Connect();
  params = {
    ContactFlowId: process.env.FLOW_ID,
    InstanceId: process.env.INSTANCE_ID,
    ParticipantDetails: {DisplayName: displayName}
  };
  response = await sdk.startChatContact(params).promise();
  participantToken = response.ParticipantToken;
 }

 sdk = new AWS.ConnectParticipant();
 params = {
  ParticipantToken: participantToken,
  Type: ['WEBSOCKET', 'CONNECTION_CREDENTIALS']
 };  
 response = await sdk.createParticipantConnection(params).promise();
 const expiration = response.Websocket.ConnectionExpiry;
 const connectionToken = response.ConnectionCredentials.ConnectionToken;
 const url = response.Websocket.Url;

 const retVal = {
  ParticipantToken : participantToken,
  Expiration : expiration,
  ConnectionToken : connectionToken,
  Url : url
 };

 return retVal;
}

Source

https://github.com/joeywhelan/awsConnectAPIGwyClient

Copyright ©1993-2024 Joey E Whelan, All rights reserved.

AWS Connect Chat/Lex Bot - Web Client via SDK


Summary

In this post, I cover development of a demo-grade chat web client against AWS Connect via direct integration with the Javascript SDK.  The Connect application utilizes a Lex chat-bot initially and allows escalation to an agent if self-service is not possible.

The code in this post was an interim step to a full AWS cloud integration that will be covered in a future posting.

Architecture

Diagram below of the overall architecture.  The AWS Javascript SDK is utilized for a customer-facing client application.  Agents use the out-of-box Contact Control Panel (CCP) application.  The overall interaction is managed via a AWS Connect flow that calls a Lex bot application.  The Lex bot application provides dialog and fulfillment validations via AWS Lambda function calls.

 

 

Call Flow

AWS Connect flow below.  This flow sends a chat interaction into a Lex bot that services intents for either self-service orders for firewood or a request for an agent.  If the agent intent is triggered, the interaction is sent to a queue for an agent.

 

Lex Bot

AWS Lex console screen-shot below of this simple firewood ordering bot.




AWS SDK Build 

The standard AWS Javascript SDK doesn't include the Connect and ConnectParticipant services, so you have to build your own browser include file.  Below are the steps to do that:

git clone git://github.com/aws/aws-sdk-js
cd aws-sdk-js
npm install
node dist-tools/browser-builder.js connect,connectparticipant > aws-connect.js

 

Web Client

Diagram and screen-shot below of the client app.  Its composition is a HTML page with vanilla Javascript.  The AWS Connect chat flow has multiple API calls to establish connectivity.  Once connectivity is established, Connect agents/Lex transmit chat messages over a Websocket to the client app.  The client app transmits chat messages via API calls to Connect.




Code Snippets


Main UI Driver

window.addEventListener('DOMContentLoaded', function() {
 const chat = new Chat();
    UIHelper.show(UIHelper.id('start'));
    UIHelper.hide(UIHelper.id('started'));
    UIHelper.id('startButton').onclick = function() {
        chat.start(UIHelper.id('firstName').value, UIHelper.id('lastName').value);
    }.bind(chat);
    UIHelper.id('sendButton').onclick = chat.send.bind(chat);
    UIHelper.id('leaveButton').onclick = chat.leave.bind(chat);
    UIHelper.id('firstName').autocomplete = 'off';
    UIHelper.id('firstName').focus();
    UIHelper.id('lastName').autocomplete = 'off';
    UIHelper.id('phrase').autocomplete = 'off';
    UIHelper.id('phrase').onkeyup = function(e) {
        if (e.keyCode === 13) {
            chat.send();
        }
    }.bind(chat);
        
    window.onunload = function() {
  if (chat) {
   chat.disconnect();
  }
    }.bind(chat); 
});

AWS SDK Driver Snippets

 async start(firstName, lastName) {
  if (!firstName || !lastName) {
   alert('Please enter a first and last name');
   return;
  } 
  else {
   this.firstName = firstName;
   this.lastName = lastName;
   await this._getToken();
   await this._connect();
   UIHelper.displayText('System:', 'Connecting...');
  }
 }

async _connect() {  
  try {
   const connectPart = new AWS.ConnectParticipant();
   const params = {
    ParticipantToken: this.partToken,
    Type: ['WEBSOCKET', 'CONNECTION_CREDENTIALS']
   };  
   const response = await connectPart.createParticipantConnection(params).promise();
   const diff = Math.abs(new Date() - Date.parse(response.Websocket.ConnectionExpiry));
   this.refreshTimer = setTimeout(this._connect, diff - 5000); //refresh the websocket
   this.connToken = response.ConnectionCredentials.ConnectionToken;
   this._subscribe(response.Websocket.Url);
  }
  catch (err) {
   console.log(err);
  }
 }

 async _getToken() {  
  try {
   const connect = new AWS.Connect();
   const partDetails = {
    DisplayName: this.firstName + ' ' + this.lastName
   }
   const params = {
    ContactFlowId: FLOW_ID,
    InstanceId: INSTANCE_ID,
    ParticipantDetails: partDetails
   };
   const response = await connect.startChatContact(params).promise();
   this.partToken = response.ParticipantToken;
  }
  catch (err) {
   console.error(err)
  }
 }

Source

https://github.com/joeywhelan/awsConnectSDKClient

Copyright ©1993-2024 Joey E Whelan, All rights reserved.