'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 = {
getMessages,
getMessage,
sendMessage,
receiveMessage,
getReferences,
getSequence
};
/* Common internal functions and classes */
const arr = require('../_common/arrays');
const { ProcessingError } = require('../_common/errors');
const { createBody,
addRelatedResource,
addMessageData,
sendImperative,
sendIndicative } = require('./_common/response');
/* Whiteflag modules */
const wfRetrieve = require('../protocol/retrieve');
const wfRxEvent = require('../protocol/events').rxEvent;
const wfTxEvent = require('../protocol/events').txEvent;
/* Module constants */
const R_MESSAGE = 'message';
/* MAIN MODULE FUNCTIONS */
/**
* 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 = createBody(req, operationId, R_MESSAGE, req.query);
// Put parameters in internal 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;
}
// Message Header fields
case 'Version':
case 'EncryptionIndicator':
case 'MessageCode':
case 'DuressIndicator':
case 'ReferenceIndicator':
case 'ReferencedMessage': {
wfQuery[`MessageHeader.${parameter}`] = req.query[parameter];
break;
}
// Metaheader strings
default: {
wfQuery[`MetaHeader.${parameter}`] = req.query[parameter];
}
}
});
}
wfRetrieve.getQuery(wfQuery, function opsGetMessagesDbCb(err, wfMessages) {
return returnMessages(err, res, resBody, wfMessages, callback);
});
}
/**
* Transmits messages to the blockchain through the tx event chain
* @function sendMessage
* @alias module:lib/operations/messages.sendMessage
* @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 sendMessage(req, res, operationId, callback) {
const wfMessage = req.body;
if (wfMessage.MetaHeader?.autoGenerated) wfMessage.MetaHeader.autoGenerated = false;
wfTxEvent.emit('messageCommitted', wfMessage, function opsSendMessageCb(err, wfMessage) {
// Create response body and preserve information before responding
let resBody = createBody(req, operationId, R_MESSAGE);
if (!err) resBody.meta = addResourceData(wfMessage, resBody.meta.request.url, resBody.meta);
// Send response using common endpoint response function
return sendImperative(res, err, resBody, wfMessage, callback);
});
}
/**
* Gets message by transaction hash
* @function getMessage
* @alias module:lib/operations/messages.getMessage
* @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 getMessage(req, res, operationId, callback) {
const transactionHash = req.params.transactionHash;
wfRetrieve.getMessage(transactionHash, null, function opsGetMessageCb(err, wfMessages = []) {
// Create response body and process result
let resBody = createBody(req, operationId, R_MESSAGE);
let resData = wfMessages;
if (!err) {
if (wfMessages.length < 1) {
err = new ProcessingError(`Message could not be found`, null, 'WF_API_NO_RESOURCE');
} else if (wfMessages.length > 1) {
err = new ProcessingError(`Multiple messages with the same transaction hash found ${transactionHash}`, null, 'WF_API_RESOURCE_CONFLICT');
} else {
resBody.meta = addResourceData(wfMessages[0], resBody.meta.request.url, resBody.meta);
resData = wfMessages[0];
}
}
// Add message info to meta data and send response using common endpoint response function
return sendIndicative(res, err, resBody, resData, callback);
});
}
/**
* Receives messages by triggering the rx event chain
* @function receiveMessage
* @alias module:lib/operations/messages.receiveMessage
* @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 receiveMessage(req, res, operationId, callback) {
// Get information and create response body
const transactionHash = req.params.transactionHash;
let resBody = createBody(req, operationId, R_MESSAGE);
// Do not complain if plain metaheader provided
let wfMessage = {};
if (Object.hasOwn(req.body, 'MetaHeader')) {
wfMessage = req.body;
} else if (Object.hasOwn(req.body, 'MessageHeader') || Object.hasOwn(req.body, 'MessageBody')) {
wfMessage = req.body;
wfMessage.MetaHeader = {};
} else {
wfMessage.MetaHeader = req.body;
}
// Check transaction hash
if (wfMessage.MetaHeader.transactionHash) {
if (wfMessage.MetaHeader.transactionHash !== transactionHash) {
const err = new ProcessingError(`Provided message data has a different transaction hash in its metaheader: ${wfMessage.MetaHeader.transactionHash}`, null, 'WF_API_BAD_REQUEST');
return sendImperative(res, err, resBody, null, callback);
}
} else {
wfMessage.MetaHeader.transactionHash = transactionHash;
}
wfRxEvent.emit('messageReceived', wfMessage, function opsReceiveMessageCb(err, wfMessage) {
// Add message info to meta data and send response using common endpoint response function
if (!err) resBody.meta = addResourceData(wfMessage, resBody.meta.request.url, resBody.meta);
return sendImperative(res, err, resBody, wfMessage, 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 = createBody(req, operationId, R_MESSAGE, query);
// Retrieve references if transaction hash is specified as query parameter
if (query.transactionHash && query.blockchain) {
return wfRetrieve.getReferences(query.transactionHash, query.blockchain,
function opsGetReferencesBcCb(err, wfMessages) {
if (!err) resBody.meta.info = arr.addItem(resBody.meta.info, `Known Whiteflag messages on ${query.blockchain} referencing message ${query.transactionHash}`);
returnMessages(err, res, resBody, wfMessages, callback);
}
);
}
if (query.transactionHash) {
return wfRetrieve.getReferences(query.transactionHash, null,
function opsGetReferencesCb(err, wfMessages) {
if (!err) resBody.meta.info = arr.addItem(resBody.meta.info, `Known Whiteflag messages referencing message ${query.transactionHash}`);
returnMessages(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 returnMessages(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 = createBody(req, operationId, R_MESSAGE, query);
// Retrieve sequence if transaction hash is specified as query parameter
if (query.transactionHash && query.blockchain) {
return wfRetrieve.getSequence(query.transactionHash, query.blockchain,
function opsGetSequenceBcCb(err, wfMessages) {
if (!err) resBody.meta.info = arr.addItem(resBody.meta.info, `Known Whiteflag message sequence on ${query.blockchain} starting with message ${query.transactionHash}`);
returnMessages(err, res, resBody, wfMessages, callback);
}
);
}
if (query.transactionHash) {
return wfRetrieve.getSequence(query.transactionHash, null,
function opsGetSequenceCb(err, wfMessages) {
if (!err) resBody.meta.info = arr.addItem(resBody.meta.info, `Known Whiteflag message sequence starting with message ${query.transactionHash}`);
returnMessages(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 returnMessages(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 returnMessages(err, res, resBody, wfMessages, callback) {
let resData = [];
if (!err) {
// Ensure message are in an array
if (Array.isArray(wfMessages)) resData = wfMessages;
else resData = [ wfMessages ];
// Check query for related resources
if (resBody.meta?.query) {
let blockchain = null;
let originator = null;
let parameters = Object.keys(resBody.meta.query);
parameters.forEach(parameter => {
if (parameter === 'blockchain') blockchain = resBody.meta.query[parameter]
if (parameter === 'originatorAddress') originator = resBody.meta.query[parameter]
});
resBody.meta = addRelatedResource(blockchain, null, originator, resBody.meta);
}
}
// Send response using common endpoint response function
return sendIndicative(res, err, resBody, resData, callback);
}
/**
* Adds information of a single Whiteflag message to meta data
* @param {wfMessage} wfMessage
* @param {string} reqPath
* @param {Object} [resMeta] the meta data object to add message details to
* @returns {Object} the updated meta data object
*/
function addResourceData(wfMessage, reqPath, resMeta = {}) {
// Add resource data
if (!Object.hasOwn(resMeta, 'resource')) {
resMeta.resource = { type: R_MESSAGE };
}
if (wfMessage.MetaHeader?.transactionHash) {
resMeta.resource.id = wfMessage.MetaHeader.transactionHash;
if (reqPath) {
if (!reqPath.includes(wfMessage.MetaHeader.transactionHash)) {
reqPath += `/${wfMessage.MetaHeader.transactionHash}`
}
resMeta.resource.location = reqPath;
}
}
resMeta = addMessageData(wfMessage, resMeta);
// Add related resource data
let blockchain = wfMessage.MetaHeader?.blockchain || null;
let account = null;
let originator = null;
if (wfMessage.MetaHeader?.transceiveDirection === 'TX') {
account = wfMessage.MetaHeader?.originatorAddress || null;
} else {
originator = wfMessage.MetaHeader?.originatorAddress || null;
}
resMeta = addRelatedResource(blockchain, account, originator, resMeta);
// Return result
return resMeta;
}