Using MQTT in JavaScript with AWS IoT
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:
- Getting Started with AWS and HERE in the Supply Chain
- Using JavaScript to Simulate Transport in Supply Chain Scenario
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!
Have your say
Sign up for our newsletter
Why sign up:
- Latest offers and discounts
- Tailored content delivered weekly
- Exclusive events
- One click to unsubscribe