AWS

Using MQTT in JavaScript with AWS IoT

By Michael Palermo | 02 October 2020

Try HERE Maps

Create a free API key to build location-aware apps and services.

Get Started

This post is the third in a series dealing with supply chain solutions using HERE Location Services  and AWS IoT Core. Need caught up? Here are the links to the first two posts:

Although the series has made mention of AWS IoT, this post will dig deeper into how it is all connected. Lets consider all the steps needed to make it work, starting with required sources.

Add Scripts to HTML

It is important to note - this post demonstrates how to get MQTT working in the browser client-side JavaScript, not a server-side approach. If you want information on doing something similar for server-side, please visit the AWS developer documentation. For our needs, you will need the following scripts added to the head section of your HTML page:

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/core-min.js" type="text/javascript"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/hmac-min.js" type="text/javascript"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/sha256-min.js" type="text/javascript"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script> 

The first three scripts contain cryptography functions we will need to help build our AWS IoT endpoint. The last script brings in the Paho.MQTT.Client object that will manage our MQTT communications. It is also noteworthy that it is not required to add a script to reference the AWS SDK.

Initialize the MQTT Client

In the client-side JavaScript attached to the HTML file with scripts above, create a function to initialize the MQTT client object as shown here:

// gets MQTT client 
function initClient() {    
    const clientId = Math.random().toString(36).substring(7); 
    const _client = new Paho.MQTT.Client(getEndpoint(), clientId);
 
    // publish method added to simplify messaging 
    _client.publish = function(topic, payload) { 
        let payloadText = JSON.stringify(payload); 
        let message = new Paho.MQTT.Message(payloadText); 
        message.destinationName = topic; 
        message.qos = 0; 
        _client.send(message); 
    } 
    return _client; 
} 

The first line of code creates a unique client ID for communications. The next line creates an instance of the Paho.MQTT.Client object, passing in the endpoint (discussed next) and the unique ID created in the first line of code. The remainder of the code attaches a publish function to the client object to simplify use elsewhere in the code. The last line of code returns the adjusted instance to the calling code.

Configure the Endpoint

In the previous code snippet, a method called getEndpoint() was passed into the constructor of the Paho.MQTT.Client object. This is where the major work is done to properly configure and connect to AWS IoT. Here is the complete code for the method:

function getEndpoint() { 
// WARNING!!! It is not recommended to expose 
// sensitive credential information in code. 
// Consider setting the following AWS values 
// from a secure source. 

    // example: us-east-1 
    const REGION = "your-aws-region";   

    // example: blahblahblah-ats.iot.your-region.amazonaws.com 
    const IOT_ENDPOINT = "your-iot-endpoint";  

    // your AWS access key ID 
    const KEY_ID = "your-key-id"; 

    // your AWS secret access key 
    const SECRET_KEY = "your-secret-key"; 

    // date & time 
    const dt = (new Date()).toISOString().replace(/[^0-9]/g, ""); 
    const ymd = dt.slice(0,8); 
    const fdt = `${ymd}T${dt.slice(8,14)}Z` 
    
  const scope = `${ymd}/${REGION}/iotdevicegateway/aws4_request`; 
    const ks = encodeURIComponent(`${KEY_ID}/${scope}`); 
    let qs = `X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${ks}&X-Amz-Date=${fdt}&X-Amz-SignedHeaders=host`; 
    const req = `GET\n/mqtt\n${qs}\nhost:${IOT_ENDPOINT}\n\nhost\n${p4.sha256('')}`; 
    qs += '&X-Amz-Signature=' + p4.sign( 
        p4.getSignatureKey( SECRET_KEY, ymd, REGION, 'iotdevicegateway'), 
        `AWS4-HMAC-SHA256\n${fdt}\n${scope}\n${p4.sha256(req)}`
    ); 
    return `wss://${IOT_ENDPOINT}/mqtt?${qs}`; 
}  

Take a moment to ponder each line of code. Note right away the warning regarding placing your AWS credential information directly in the code. Consider using options that either allow “hiding” the values in files/storage not directly accessible, or by obtaining via a secured serverless API call. The code shown above is for demonstration purposes.

The entire function results in the complete AWS IoT Endpoint needed for MQTT communications. Each occurrence of a p4 object is a wrapper to the cryptography scripts added to the HTML. Here is the p4 object defined:

function p4(){} 
p4.sign = function(key, msg) { 
    const hash = CryptoJS.HmacSHA256(msg, key); 
    return hash.toString(CryptoJS.enc.Hex); 
}; 
p4.sha256 = function(msg) { 
    const hash = CryptoJS.SHA256(msg); 
    return hash.toString(CryptoJS.enc.Hex); 
}; 
p4.getSignatureKey = function(key, dateStamp, regionName, serviceName) { 
    const kDate = CryptoJS.HmacSHA256(dateStamp, 'AWS4' + key); 
    const kRegion = CryptoJS.HmacSHA256(regionName, kDate); 
    const kService = CryptoJS.HmacSHA256(serviceName, kRegion); 
    const kSigning = CryptoJS.HmacSHA256('aws4_request', kService); 
    return kSigning; 
};  

Creating the Client to Connect

To use the code created so far, elsewhere it is recommended to create another function to prepare for connection. The following does just that:

function getClient(success) { 
    if (!success) success = ()=> console.log("connected"); 
    const _client = initClient(); 
    const connectOptions = { 
      useSSL: true, 
      timeout: 3, 
      mqttVersion: 4, 
      onSuccess: success 
    }; 
    _client.connect(connectOptions); 
    return _client;  
}  

The getClient function takes in an optional parameter (named success) which is another function to call if the connection succeeded. If no function is provided, an arrow function is created to exhibit a default behavior to log “connected” to console. The _client object is initialized from the initClient function defined earlier in this post. The connection options are defined in JSON and passed into the connect function. The client is then returned to the calling code. Let’s see how it is used!

Using the MQTT Client

The following init function sets the global client object to the return value of the getClient function:

let client = {}; 
function init() { 
    client = getClient(); 
    client.onMessageArrived = processMessage; 
    client.onConnectionLost = function(e) { 
        console.log(e); 
    }  
}  

The client is also configured to call on the processMessage function if it receives any communications. This will only happen if the client subscribes to a topic somewhere else in code, like this:

client.subscribe(“sc/orders/”); 

The above line of code subscribes to any messages published to the “sc/orders” topic.Let’s now look a possible use of the processMessage function when it receives a message:

function processMessage(message) { 
    let info = JSON.parse(message.payloadString); 
    const publishData = { 
        retailer: retailData, 
        order: info.order 
    }; 
    client.publish("sc/delivery", publishData); 
} 

If a message is published to the “sc/orders” topic, it is parsed, and used with other information to now publish to the “sc/delivery” topic. In other words, once an order is received, it is now forwarded to the delivery company with all the details needed to get a product to the requesting consumer. This scenario is explained in much more detail in the video below!