Sunday, July 26, 2020

Salesforce Object Query via REST Call


Summary

This post explains how to execute a Salesforce Object Query Language (SOQL) command from a REST call.  The approach and code here are by no means production grade.  This is simply a method to get testing jump-started.

SFDC-side Set Up

You will need to create a 'Connected App' that can be accessed via a 'password' OAuth grant type.  Instructions for that here.  Screen-shot below of the critical areas that need to be set for the authentication to work correctly.

As mentioned in the Summary, there's little regard for security in the config below.  These settings are just to get things working.  You can lock it down after that.  You would not use a password OAuth grant type in a production setting.



 

Fetch Access Token

This step was actually the most painful of the entire exercise.  The 'connected app' and HTTP POST have to be configured just right.

function formEncode(data) {
    return Object.keys(data)
    .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
    .join('&');  
}

async function getToken() {
    const body = {
        grant_type: 'password',
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        username: USERNAME,
        password: PASSWORD
    };

    const response = await fetch(AUTH_URL, {
        method: 'POST',
        body: formEncode(body),
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': 'application/json'
        }
    })
    if (response.ok) {
        const json = await response.json();
        return json.access_token;
    }
    else {
        const msg = `getToken() response status: ${response.status} ${response.statusText}`;
  throw new Error(msg);
    }
}

SOQL Command via REST

Once the access token is obtained, a SOQL command can be URI encoded and sent as a query parameter in a HTTP GET to the URL of your SFDC instance.

async function sendQuery(query, token) {

    const response = await fetch(QUERY_URL + encodeURIComponent(query), {
        method: 'GET',
        headers: {
            'Authorization': 'Bearer ' + token
        }
    })
    if (response.ok) {
        return await response.json();
    }
    else {
        const msg = `sendQuery() response status: ${response.status} ${response.statusText}`;
  throw new Error(msg);
    }

}

Execution

Example of the two functions above being used in a promise chain to execute a SOQL command:
const QUERY='SELECT Name,Phone FROM Account ORDER BY Name';
(() => {
    getToken()
    .then((token) => {
        return sendQuery(QUERY, token);
    })
    .then((data) => {
        console.log(JSON.stringify(data, null, 4));
    })
    .catch((err) => {
        console.error(err);
    });
})();

{
    "totalSize": 12,
    "done": true,
    "records": [
        {
            "attributes": {
                "type": "Account",
                "url": "/services/data/v20.0/sobjects/Account/0013t00001Xq9bnAAB"
            },
            "Name": "Burlington Textiles Corp of America",
            "Phone": "(336) 222-7000"
        },
        {
            "attributes": {
                "type": "Account",
                "url": "/services/data/v20.0/sobjects/Account/0013t00001Xq9bpAAB"
            },
            "Name": "Dickenson plc",
            "Phone": "(785) 241-6200"
        },

Source

https://github.com/joeywhelan/soql

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

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.

Friday, April 10, 2020

NiceIncontact API Authentication


Summary

I've authored several posts on the usage of the NiceIncontact (NiC) APIs, but never covered the authentication steps.  This post will show how to do that for both the legacy and new Userhub configuration interfaces.  I'll show a Typescript implementation of the necessary API calls for both API interfaces.

API Access Keys

Legacy Interface

Below is a screenshot of the legacy admin interface of the 3 pieces of information necessary to generate an API bearer token.


Userhub Interface

The newer Userhub interface requires the two pieces of info below to generate the API bearer token.  This access key has been deleted, so there's no security concern here.



API Authentication Class Design

Below is a diagram depicting the Typescript classes that will be used for generating API bearer tokens for the two access types mentioned above.

Code

 

Authenticator Interface

export interface Authenticator {
    getToken():Promise<string>;
};

Legacy Nic OAuth Class (getToken function)

    async getToken():Promise<string> {
        let body;
        const username = this.credentials ? this.credentials.username : '';
        const password = this.credentials ? this.credentials.password: '';

        switch (this.grant) {
            case GRANT.CLIENT : {
                body = {
                    'grant_type' : 'client_credentials'
                };
                break;
            };
            case GRANT.PASSWORD : {
                body = {
                    'grant_type' : 'password',
                    'username' : username,  
                    'password' : password
                }
                break;
            };
            default : {
                throw new Error('unknown grant type');
            }
        };

        const response = await fetch(this.tokenURL, {
            method: 'POST',
            headers: {
                'Content-Type' : 'application/json', 
                'Authorization' : 'basic ' + this.key
            },
            body: JSON.stringify(body)
        });
    
        if (response.ok) {
            const json = await response.json();
            return json.access_token;
        }
        else {
            throw new Error(`response status: ${response.status} ${response.statusText}`);
        }
    }

Userhub Access Key Class (getToken function)

    async getToken():Promise<string> {
        const body:object = {
            accessKeyId: this.key,
            accessKeySecret: this.secret
        } 
        const response = await fetch(this.url, {
            method: 'POST',
            headers: {
                'Content-Type' : 'application/json'
            },
            body: JSON.stringify(body)
        });
    
        if (response.ok) {
            const json = await response.json();
            return json.access_token;
        }
        else {
            throw new Error(`getToken() response status: ${response.status} ${response.statusText}`);
        }
    
    }

Demo

async function demo():Promise {
    dotenv.config();
    const app:any = process.env.NIC_APP;
    const vendor:any = process.env.NIC_VENDOR;
    const secret:any = process.env.NIC_SECRET;
    const username:any = process.env.NIC_USERNAME;
    const password:any = process.env.NIC_PASSWORD;
    const accessSecret:any = process.env.NIC_ACCESS_SECRET;
    const accessKey:any = process.env.NIC_ACCESS_KEY;
 
    
    let url:string =  'https://api.incontact.com/InContactAuthorizationServer/Token';
    const clientAuth = new NicOAuth(app, vendor, secret, GRANT.CLIENT, url);
    let token:string = await clientAuth.getToken();
    console.log(`client auth token: ${token}`);
    console.log('');

    const credentials = new Credentials(username, password);
    const passwordAuth = new NicOAuth(app, vendor, secret, GRANT.PASSWORD, url, credentials);
    token = await passwordAuth.getToken();
    console.log(`password auth token: ${token}`);
    console.log('');

    url = 'https://na1.nice-incontact.com/authentication/v1/token/access-key';
    const nicAccess = new NicAccess(accessKey, accessSecret, url);
    token = await nicAccess.getToken();
    console.log(`access token: ${token}`);
}

Results

$ npm run start

> authdemo@1.0.0 start nicapiauth
> node authdemo.js

client auth token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpY0JVSWQiOjQ1OTM0NDMsIm5hbWUiOiIiLCJpc3...

password auth token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpY0JVSWQiOjQ1OTM...

access token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ey...

Source

https://github.com/joeywhelan/NiCAuthentication

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

Sunday, March 29, 2020

Priority Queue with Typescript



Summary

This post covers development of a Priority Queue with Typescript.  The queue is implemented via a Binary Heap.  Typescript features such as static type-checking and object-oriented concepts such as classes, interfaces and inheritance are utilized.

Design

Implementation

Queue Item Class

 export class Item {
    priority:number;
    value:object;

    constructor(priority:number, value:object) {
        this.priority = priority;
        this.value = value;
    }
 }

Heap Interface

import {Item} from './item';

export enum Order {MIN, MAX};
export interface Heap {
    insert(item:Item):void;
    extract():Item;
    peek():Item;
    show():void;
    size():number;
};

Binary Heap Class - snippets

export class BinaryHeap implements Heap {
    private order:Order;
    private heap:Item[];

    insert(item:Item):void {
        this.heap.push(item);
        this.siftUp(this.heap.length-1);
    };

    private siftUp(idx:number):void {
        let parent:number;
        let sorted:boolean = false;

        while (!sorted) {
          parent = this.getParent(idx)
          switch (this.order) {
              case Order.MIN: {
                if (this.heap[idx].priority < this.heap[parent].priority) {
                    this.swap(idx, parent);
                    idx = parent;
                }
                else {
                    sorted = true;
                }
                break;
              }
              case Order.MAX: {
                if (this.heap[idx].priority > this.heap[parent].priority) {
                    this.swap(idx, parent);
                    idx = parent;
                }
                else {
                    sorted = true;
                }
                break;
              }
              default: {
                  sorted = true;
                  break;
              }
          }  
        }
    }

Priority Queue Class

export class PriorityQueue {
    heap:BinaryHeap;

    constructor(items:Item[]){
        this.heap = new BinaryHeap(items);
    }

    insert(item:Item):void {
        this.heap.insert(item);
    }

    isEmpty():boolean {
        return this.heap.size() == 0;
    }

    peek():Item {
        return this.heap.peek();
    }

    pull():Item {
        return this.heap.extract();
    }

    show():void {
        this.heap.show();
    }
}

Example

 


Source

https://github.com/joeywhelan/PriorityQueue

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

Sunday, December 15, 2019

Workflow for moving an existing VS Code project to Github

Summary

This post is a step by step instruction on how to push an existing Visual Studio Code project to Github.  I'll demonstrate steps from both the GUI and command line to accomplish this.

Step 1:  Create the Github Repository

Screen-shot below of a basic repo.  Note that I use Github's stock Node.js .gitignore file.


Resulting repo:


Step 2:  Create a Local Git Repository

Command-line and GUI methods below:
$ git init .

 

Step 3:  Add Remote (Github) Repository

git remote add origin https://github.com/joeywhelan/containertest

Step 4:  Pull from Github

This step will pull down the existing files in the start-up repo - most importantly, the .gitignore file.

Command-line + GUI methods:

$ git pull origin main

Result below.  Note all the untracked files from the node_modules directory are now hidden via the .gitignore from Github.


Step 5:  Add all of the local files to the local Git Staging Area

$ git add .

Results:

Step 6:  Commit all to the local Git repo.

$ git commit -m "first commit" .

Results:

Step 7:  Rename the local 'master' branch to main and push local files to Github. 

$ git branch -m master main
$ git push -u origin main

Results:

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

Monday, December 9, 2019

Google Cloud Run Quickstart

Summary

This post is a continuation of the previous quick start on containerizing a Node.js app.  In this post, I'll use the same simple app + Dockerfile and deploy the app to Google's Cloud Run platform.

Cloud Run is an alternate method for server-less app deployments.  Google Cloud Functions is other method.  Whereas Cloud Functions has very specific language requirements, Cloud Run has none.  Whatever you build in a container is fair game.  The only requirement is the app needs to respond to HTTP requests as a trigger.  Cloud Run provides similar auto-scaling capabilities as Cloud Functions making it an excellent choice for a self-managing app deployment.

Step 1:  Create a Google Cloud Project

 

 

Step 2:  Enable Cloud Build and Cloud Run APIs

 

Step 3:  Build the Container

$ gcloud config set project cloudrun-quickstart-261521 
Updated property [core/project].
$ gcloud builds submit --tag gcr.io/cloudrun-quickstart-261521/cloudrun-quickstartCreating temporary tarball archive of 331 file(s) totalling 1.6 MiB before compression.
Uploading tarball of [.] to [gs://cloudrun-quickstart-261521_cloudbuild/source/1575934972.69-7637137b7675474ab861a2f4185529c6.tgz]
Created [https://cloudbuild.googleapis.com/v1/projects/cloudrun-quickstart-261521/builds/7591715a-3e15-4323-b570-0c2dc191fb3a].
Logs are available at [https://console.cloud.google.com/gcr/builds/7591715a-3e15-4323-b570-0c2dc191fb3a?project=945345104488].
...
DONE
--------------------------------------------------------------------------------------------------------------------------------------------

ID                                    CREATE_TIME                DURATION  SOURCE                                                                                                IMAGES                                                           STATUS
7591715a-3e15-4323-b570-0c2dc191fb3a  2019-12-09T23:42:55+00:00  57S       gs://cloudrun-quickstart-261521_cloudbuild/source/1575934972.69-7637137b7675474ab861a2f4185529c6.tgz  gcr.io/cloudrun-quickstart-261521/cloudrun-quickstart (+1 more)  SUCCESS

Step 4:  Deploy to Cloud Run

Screen shots below of the Cloud Run console.  Note in the second screen shot the concurrency controls available.  You can specify the number of requests per container and the max container auto-scale growth.




Step 5:  Execute

$ curl -i https://cloudrun-quickstart-6saiqefrtq-uc.a.run.app 
HTTP/2 200 
x-powered-by: Express
content-type: text/html; charset=utf-8
etag: W/"b-Kq5sNclPz7QV2+lfQIuc6R7oRu0"
x-cloud-trace-context: 4f70f43d41086a3d8ea2320a166a5f89;o=1
date: Mon, 09 Dec 2019 23:58:10 GMT
server: Google Frontend
content-length: 11
alt-svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000

hello world

Step 6:  Clean up

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