Source: iotdata.js

/*
 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 */

const { Buffer } = require('buffer');

const GreengrassCommon = require('aws-greengrass-common-js');
const Lambda = require('./lambda');
const Util = require('./util');

const { envVars } = GreengrassCommon;
const { MY_FUNCTION_ARN } = envVars;
const { SHADOW_FUNCTION_ARN } = envVars;
const { ROUTER_FUNCTION_ARN } = envVars;

/**
 * Constructs a service interface object. Each API operation is exposed as a function on service.
 *
 * @class
 * @memberOf aws-greengrass-core-sdk
 *
 * @example <caption>Sending a Request Using IotData</caption>
 * var iotdata = new GG.IotData();
 * iotdata.getThingShadow(params, function (err, data) {
 *   if (err) console.log(err, err.stack); // an error occurred
 *   else     console.log(data);           // successful response
 * });
 */
class IotData {
    /**
     * Constructs a service object. This object has one method for each API operation.
     *
     * @example <caption>Constructing a IotData object</caption>
     * var iotdata = new GG.IotData();
     */
    constructor() {
        this.lambda = new Lambda();
    }

    /**
     * Called when a response from the service is returned.
     *
     * @callback iotDataCallback
     * @param err {Error} The error object returned from the request. Set to <tt>null</tt> if the request is successful.
     * @param data {Object} The de-serialized data returned from the request. Set to <tt>null</tt> if a request error occurs.
     * @param data.payload {Buffer|TypedArray|Blob|String} The state information in JSON format
     */
    /**
     * Gets the thing shadow for the specified thing.
     *
     * @param params {Object}
     * @param params.thingName {String} The name of the thing.
     * @param callback {iotDataCallback} The callback.
     *
     * @example <caption>Calling the getThingShadow operation</caption>
     * var params = {
     *   thingName: 'STRING_VALUE' // required
     * };
     * iotdata.getThingShadow(params, function(err, data) {
     *   if (err) console.log(err, err.stack); // an error occurred
     *   else     console.log(data);           // successful response
     * });
     */
    getThingShadow(params, callback) {
        const thingName = Util.getParameter(params, 'thingName');
        if (thingName === undefined) {
            callback(new Error('"thingName" is a required parameter.'), null);
            return;
        }

        const payload = '';
        this._shadowOperation('get', thingName, payload, callback);
    }

    /**
     * Updates the thing shadow for the specified thing.
     *
     * @param params {Object}
     * @param params.thingName {String} The name of the thing.
     * @param params.payload {String} The state information as a JSON string.
     * @param callback {iotDataCallback} The callback.
     *
     * @example <caption>Calling the updateThingShadow operation</caption>
     * var params = {
     *   payload: 'Proper JSON data', // required
     *   thingName: 'STRING_VALUE' // required
     * };
     * iotdata.updateThingShadow(params, function(err, data) {
     *   if (err) console.log(err, err.stack); // an error occurred
     *   else     console.log(data);           // successful response
     * });
     */
    updateThingShadow(params, callback) {
        const thingName = Util.getParameter(params, 'thingName');
        if (thingName === undefined) {
            callback(new Error('"thingName" is a required parameter.'), null);
            return;
        }

        const payload = Util.getParameter(params, 'payload');
        if (payload === undefined) {
            callback(new Error('"payload" is a required parameter.'), null);
            return;
        }

        this._shadowOperation('update', thingName, payload, callback);
    }

    /**
     * Call shadow lambda to delete the shadow state.
     *
     * @param params {Object}
     * @param params.thingName {String} The name of the thing whose shadow should be deleted.
     * @param callback {iotDataCallback} The callback.
     */
    deleteThingShadow(params, callback) {
        const thingName = Util.getParameter(params, 'thingName');
        if (thingName === undefined) {
            callback(new Error('"thingName" is a required parameter.'), null);
            return;
        }

        const payload = '';
        this._shadowOperation('delete', thingName, payload, callback);
    }

    /**
     * Publishes a message within Greengrass.
     *
     * @param params {Object}
     * @param params.topic {String} The name of the MQTT topic.
     * @param params.payload {Buffer|TypedArray|Blob|String} The payload to be sent.
     * @param params.queueFullPolicy {'AllOrError'|'BestEffort'} Specify whether to enforce message delivery to all destinations. Options are 'AllOrError' and 'BestEffort'. Defaults to 'BestEffort' when omitted.
     * @param callback {iotDataCallback} The callback.
     *
     * @example <caption>Calling the publish operation</caption>
     * var params = {
     *   topic: 'STRING_VALUE', // required
     *   payload: new Buffer('...') || 'STRING_VALUE',
     *   queueFullPolicy: 'AllOrError' || 'BestEffort'
     * };
     * iotdata.publish(params, function(err, data) {
     *   if (err) console.log(err, err.stack); // an error occurred
     *   else     console.log(data);           // successful response
     * });
     */
    publish(params, callback) {
        const topic = Util.getParameter(params, 'topic');
        if (topic === undefined) {
            callback(new Error('"topic" is a required parameter'), null);
            return;
        }

        const payload = Util.getParameter(params, 'payload');
        if (payload === undefined) {
            callback(new Error('"payload" is a required parameter'), null);
            return;
        }

        // this is an optional parameter
        const queueFullPolicy = Util.getParameter(params, 'queueFullPolicy');

        const context = {
            custom: {
                source: MY_FUNCTION_ARN,
                subject: topic,
                queueFullPolicy: '',
            },
        };

        switch (queueFullPolicy) {
            case 'BestEffort':
            case 'AllOrError':
                context.custom.queueFullPolicy = queueFullPolicy;
                break;
            case '':
            case undefined:
            case null:
                break;
            default:
                callback(new Error(`queueFullPolicy "${queueFullPolicy}" is not supported`), null);
                break;
        }

        const buff = Buffer.from(JSON.stringify(context));
        const clientContext = buff.toString('base64');

        const invokeParams = {
            FunctionName: ROUTER_FUNCTION_ARN,
            InvocationType: 'Event',
            ClientContext: clientContext,
            Payload: payload,
        };

        console.log(`Publishing message on topic "${topic}" with Payload "${payload}"`);

        this.lambda.invoke(invokeParams, (err, data) => {
            if (err) {
                callback(err, null); // an error occurred
            } else {
                callback(null, data); // successful response
            }
        });
    }

    _shadowOperation(operation, thingName, payload, callback) {
        const topic = `$aws/things/${thingName}/shadow/${operation}`;
        const context = {
            custom: {
                subject: topic,
            },
        };

        const clientContext = Buffer.from(JSON.stringify(context)).toString('base64');
        const invokeParams = {
            FunctionName: SHADOW_FUNCTION_ARN,
            ClientContext: clientContext,
            Payload: payload,
        };

        console.log(`Calling shadow service on topic "${topic}" with payload "${payload}"`);
        this.lambda.invoke(invokeParams, (err, data) => {
            if (err) {
                callback(err, null);
            } else {
                callback(null, data);
            }
        });
    }
}

module.exports = IotData;