Source: operations/messages.js

'use strict';
/**
 * @module lib/operations/messages
 * @summary Whiteflag API messages endpoints handler module
 * @description Module with api messages endpoint handlers
 * @tutorial modules
 * @tutorial openapi
 */
module.exports = {
    // Endpoint handler functions
    send,
    receive,
    validate,
    encode,
    decode,
    getMessages,
    getReferences,
    getSequence
};

// Whiteflag common functions and classes //
const response = require('../common/httpres');
const array = require('../common/arrays');
const { ProcessingError } = require('../common/errors');

// Whiteflag modules //
const wfCodec = require('../protocol/codec');
const wfRetrieve = require('../protocol/retrieve');
const wfReference = require('../protocol/references');

// Whiteflag event emitters //
const wfRxEvent = require('../protocol/events').rxEvent;
const wfTxEvent = require('../protocol/events').txEvent;

// MAIN MODULE FUNCTIONS //
/**
 * Transmits messages to the blockchain through the tx event chain
 * @function send
 * @alias module:lib/operations/messages.send
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function send(req, res, operationId, callback) {
    const wfMessage = req.body;
    if (wfMessage.MetaHeader && wfMessage.MetaHeader.autoGenerated) wfMessage.MetaHeader.autoGenerated = false;
    wfTxEvent.emit('messageCommitted', wfMessage, function endpointSendCb(err, wfMessage) {
        // Create response body and preserve information before responding
        let resBody = response.createBody(req, operationId);

        // Send response using common endpoint response function
        return response.sendImperative(res, err, resBody, wfMessage, callback);
    });
}

/**
 * Receives messages by triggering the rx event chain
 * @function receive
 * @alias module:lib/operations/messages.receive
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function receive(req, res, operationId, callback) {
    const wfMessage = req.body;
    wfRxEvent.emit('messageReceived', wfMessage, function endpointReceiveCb(err, wfMessage) {
        // Create response body and preserve information before responding
        let resBody = response.createBody(req, operationId);

        // Send response using common endpoint response function
        return response.sendImperative(res, err, resBody, wfMessage, callback);
    });
}

/**
 * Checks whether a Whiteflag message is valid
 * @function validate
 * @alias module:lib/operations/messages.validate
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function validate(req, res, operationId, callback) {
    const wfMessage = req.body;
    wfCodec.verifyFormat(wfMessage, function endpointVerifyFormatCb(err, wfMessage) {
        // Create response body and preserve information before responding
        let resBody = response.createBody(req, operationId);

        // Return response if format is not valid
        if (err) return response.sendIndicative(res, err, resBody, wfMessage, callback);

        // Add info message if format is valid
        if (wfMessage.MetaHeader.formatValid) {
            resBody.meta.info = array.addItem(resBody.meta.info, 'Message format is valid');
        }
        // Verify message reference
        wfReference.verify(wfMessage, function endpointVerifyReferenceCb(err, wfMessage) {
            if (!err && wfMessage.MetaHeader.referenceValid) {
                resBody.meta.info = array.addItem(resBody.meta.info, 'Message reference is valid');
            }
            return response.sendIndicative(res, err, resBody, wfMessage, callback);
        });
    });
}

/**
 * Encodes a Whiteflag message
 * @function encode
 * @alias module:lib/operations/messages.encode
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function encode(req, res, operationId, callback) {
    const wfMessage = req.body;
    wfCodec.encode(wfMessage, function endpointEncodeCb(err, wfMessage) {
        // Create response body and preserve information before responding
        let resBody = response.createBody(req, operationId);

        // Send response using common endpoint response function
        return response.sendImperative(res, err, resBody, wfMessage, callback);
    });
}

/**
 * Decodes a Whiteflag message
 * @function decode
 * @alias module:lib/operations/messages.decode
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function decode(req, res, operationId, callback) {
    const wfMessage = req.body;
    wfCodec.decode(wfMessage, function endpointDecodeCb(err, wfMessage, ivMissing) {
        // Create response body and preserve information before responding
        let resBody = response.createBody(req, operationId);

        // Check for missing decryption key
        if (err && err.code === 'WF_ENCRYPTION_ERROR'
            && !wfMessage.MetaHeader.encryptionKeyInput
        ) {
            err = new ProcessingError('Could not decrypt message', [ 'Encryption key input is missing', err.message ], 'WF_API_BAD_REQUEST');
        }
        // Check for missing intitialisation vector
        if (!err && ivMissing) {
            err = new ProcessingError('Could not decrypt message', [ 'Initialisation vector is missing' ], 'WF_API_BAD_REQUEST');
        }
        // Send response using common endpoint response function
        return response.sendImperative(res, err, resBody, wfMessage, callback);
    });
}

/**
 * Retrieves all messages from the database
 * @function getMessages
 * @alias module:lib/operations/messages.getMessages
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function getMessages(req, res, operationId, callback) {
    let wfQuery = {};
    let resBody = response.createBody(req, operationId);
    resBody.meta.query = req.query;

    let parameters = Object.keys(req.query);
    if (parameters.length !== 0) {
        parameters.forEach(parameter => {
            switch (parameter) {
                // Integers
                case 'blockNumber':
                case 'blockDepth': {
                    wfQuery[`MetaHeader.${parameter}`] = parseInt(req.query[parameter]);
                    break;
                }
                // Booleans
                case 'autoGenerated':
                case 'transmissionSuccess':
                case 'confirmed':
                case 'originatorValid':
                case 'referenceValid':
                case 'formatValid': {
                    wfQuery[`MetaHeader.${parameter}`] = (req.query[parameter] === 'true');
                    break;
                }
                // Strings
                default: {
                    wfQuery[`MetaHeader.${parameter}`] = req.query[parameter];
                }
            }
        });
    }
    wfRetrieve.getQuery(wfQuery, function endpointGetMessagesDbCb(err, wfMessages) {
        return processMessages(err, res, resBody, wfMessages, callback);
    });
}

/**
 * Retrieves message references from the database
 * @function getReferences
 * @alias module:lib/operations/messages.getReferences
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function getReferences(req, res, operationId, callback) {
    const query = req.query || {};
    let resBody = response.createBody(req, operationId);
    resBody.meta.query = query;

    // Retrieve references if transaction hash is specified as query parameter
    if (query.transactionHash && query.blockchain) {
        return wfRetrieve.getReferences(query.transactionHash, query.blockchain,
            function endpointGetReferencesBcCb(err, wfMessages) {
                processMessages(err, res, resBody, wfMessages, callback);
            }
        );
    }
    if (query.transactionHash) {
        return wfRetrieve.getReferences(query.transactionHash, null,
            function endpointGetReferencesCb(err, wfMessages) {
                processMessages(err, res, resBody, wfMessages, callback);
            }
        );
    }
    // Cannot retrieve message references without transaction hash
    let err = new ProcessingError('Query does not contain transactionHash of referencing message', null, 'WF_API_BAD_REQUEST');
    return processMessages(err, res, resBody, null, callback);
}

/**
 * Retrieves message sequences from the database
 * @function getSequence
 * @alias module:lib/operations/messages.getSequence
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {string} operationId the operation id as defined in the openapi definition
 * @param {logEndpointEventCb} callback
 */
function getSequence(req, res, operationId, callback) {
    const query = req.query || {};
    let resBody = response.createBody(req, operationId);
    resBody.meta.query = query;

    // Retrieve sequence if transaction hash is specified as query parameter
    if (query.transactionHash && query.blockchain) {
        return wfRetrieve.getSequence(query.transactionHash, query.blockchain,
            function endpointGetSequenceBcCb(err, wfMessages) {
                processMessages(err, res, resBody, wfMessages, callback);
            }
        );
    }
    if (query.transactionHash) {
        return wfRetrieve.getSequence(query.transactionHash, null,
            function endpointGetSequenceCb(err, wfMessages) {
                processMessages(err, res, resBody, wfMessages, callback);
            }
        );
    }
    // Cannot retrieve message sequence without trabsaction hash
    let err = new ProcessingError('Query does not contain transactionHash of first message in sequence', null, 'WF_API_BAD_REQUEST');
    return processMessages(err, res, resBody, null, callback);
}

// PRIVATE MODULE FUNCTIONS //
/**
 * Returns messages from queries on resource endpoints
 * @private
 * @param {Object} req the http request
 * @param {Object} res the http response
 * @param {Object} resBody the response body
 * @param {Array} wfMessages Whiteflag messages
 * @param {logEndpointEventCb} callback
 */
function processMessages(err, res, resBody, wfMessages, callback) {
    // Ensure message are in an array
    let resData = [];
    if (!err) {
        if (Array.isArray(wfMessages)) resData = wfMessages;
            else resData = [ wfMessages ];
    }
    // Send response using common endpoint response function
    return response.sendIndicative(res, err, resBody, resData, callback);
}