api.js

/**
 * To work out which environment the site is on (dev/staging/live)
 * @module
 */

import axios from 'axios';
import axiosCancel from 'axios-cancel';

axiosCancel(axios, {
    debug: false, // default
});

/**
 * @private
 * @param {number} channel_id
 * @param {string} hash_path  String to the remote cfc to call CORE pluto.hash
 * @returns {object} JS api struct with headers
 */
async function getParams(skv_config) {
    // var channel_id = skv_config.channel_id;
    const { hash_path } = skv_config;
    let call_params = '';

    try {
        const response = await axios({
            url: hash_path,
            params: {
                skv_params: skv_config.data,
                channel_id: skv_config.channel_id,
                method: 'getHash',
                returnFormat: 'json',
            },
        });

        call_params = response;
    } catch (e) {
        console.error(e);
    }

    return call_params;
}

/**
 * work out whether the returned data is a cancelled ajax request
 * @private
 * @param {string} msg
 * @returns {boolean}
 */
function isCancelRequest(msg) {
    const regex = /cancelRequest\(.*\)/g;
    const search_num = msg.search(regex);

    if (search_num === -1) {
        return false;
    }

    return true;
}

/**
 * Generic ajax call
 * @param {struct} config
 * @returns {struct} Data returned from call
 */
const call = async (config) => {
    const skv_config = config;
    skv_config.data.returnFormat = 'json';
    // cancel any other calculations to stop stacking
    axios.cancel(config.request_id);

    // when calling pluto we need to call a small cfc to get a hash for
    // authentication. the hash is made up of key, secret, time and nonce
    // that will then be decoded by the api
    let is_hash_provided = false;
    // to be populated and passed in to axios if we are calling the api
    let headers = {};

    // see if we're calling pluto/api
    if (Object.prototype.hasOwnProperty.call(config, 'hash_path')) {
        is_hash_provided = true;
    }

    try {
        // if we're calling pluto/api we need to get a hash to authenticate
        // the call with
        if (is_hash_provided) {
            const rst_hash_response = await getParams(skv_config);

            const hash_data = rst_hash_response.data;

            if (
                hash_data.success
                && hash_data.data.length
            ) {
                headers = {
                    key: hash_data.data[0].api_key,
                    hash: hash_data.data[0].hash,
                    nonce: hash_data.data[0].nonce,
                    time: hash_data.data[0].time,
                };
            } else {
                throw new Error(`API is being called but no headers were retrieved: ${rst_hash_response.data.msg}`);
            }
        }

        const skv_axios_call_cfg = {
            url: skv_config.url,
            requestId: config.request_id,
            // params are the URL parameters to be sent with the request
            // use config for POST
            params: skv_config.data,
        };

        // if we're using the api we'll have headers to add to the axios call
        if (Object.keys(headers).length) {
            skv_axios_call_cfg.headers = headers;
        }

        const response = await axios(skv_axios_call_cfg);

        // only return the data if the call worked
        const { data } = response;

        // default the internal_status_code to failure if there is no entry
        // for one otherwise we'll have it as part of the data object
        if (
            !Object.prototype.hasOwnProperty.call(data, 'internal_status_code')
            || data.internal_status_code === ''
        ) {
            data.internal_status_code = 'FAILURE';
        }

        // we should always be returning a minimum of jsapi keys (there'll likely
        // be more for pluto calls)
        if (
            !Object.prototype.hasOwnProperty.call(data, 'data')
            || !Object.prototype.hasOwnProperty.call(data, 'msg')
            || !Object.prototype.hasOwnProperty.call(data, 'success')
        ) {
            data.internal_status_code = 'FAIL_MISSING_KEYS_IN_RETURNED_DATA';

            console.error(`Internal status code: ${data.internal_status_code} // Message: One of data, msg or success has not been included in the returned data struct`);
        }

        if (data.success) {
            data.internal_status_code = 'SUCCESS';
        } else {
            // check to see if we are cancelling an ajax call based on the
            // message provided by AxiosCancel
            if (isCancelRequest(data.msg)) {
                data.internal_status_code = 'AJAX_CANCEL';
            }

            console.error(`Internal status code: ${data.internal_status_code} // Message: ${data.msg}`);
        }

        // return whats hopefully a jsapi structured return data
        return data;
    } catch (err) {
        let internal_status_code = 'FAILURE';

        // if theres no cancelRequest(...) in the message record as an warning
        // rather than an error
        if (isCancelRequest(err.message)) {
            internal_status_code = 'AJAX_CANCEL';
            console.warn('Warning in service: ', err);
        } else {
            console.error('Error in service: ', err);
        }

        return {
            msg: err.message,
            data: [],
            success: false,
            internal_status_code,
        };
    }
};

export {
    call,
};