RICADO Gen 4 API JS Client

RICADO Gen 4 API JS Client

source

RequestHelper.js

import nodeFetch, { Headers as nodeHeaders, RequestInit as nodeRequestInit, Request as nodeRequest, Response as nodeResponse, Body as nodeBody } from 'node-fetch';
import { isDefined, isDebugMode, BaseURL, JWT, NoAuthPaths } from './index';
import BadRequestError from './Errors/BadRequestError';
import ForbiddenError from './Errors/ForbiddenError';
import NotAllowedError from './Errors/NotAllowedError';
import NotFoundError from './Errors/NotFoundError';
import ServerError from './Errors/ServerError';
import UnauthorizedError from './Errors/UnauthorizedError';
import NetworkError from './Errors/NetworkError';

/**
 * A Helper Class to abstract away the Lower-Level Fetch API to suit calls to the RICADO Gen 4 API
 *
 * @public
 */
class RequestHelper
{
    /**
     * Perform a GET Request
     *
     * @static
     * @public
     * @param {string} url - The Relative URL (e.g. /sites/{siteId}/my-resource)
     * @param {Object<string, any>} [queryParameters] - Query Parameters (e.g. {myQuery: myValue})
     * @returns {Promise<any>}
     */
    static getRequest(url, queryParameters = {})
    {
        return RequestHelper.performRequest('GET', url, null, queryParameters);
    }

    /**
     * Perform a PUT Request
     *
     * @static
     * @public
     * @param {string} url - The Relative URL (e.g. /sites/{siteId}/my-resource)
     * @param {any} [data] - The Data to PUT
     * @returns {Promise<any>}
     */
    static putRequest(url, data = null)
    {
        return RequestHelper.performRequest('PUT', url, data);
    }

    /**
     * Perform a PATCH Request
     *
     * @static
     * @public
     * @param {string} url - The Relative URL (e.g. /sites/{siteId}/my-resource)
     * @param {any} [data] - The Data to PATCH
     * @returns {Promise<any>}
     */
    static patchRequest(url, data = null)
    {
        return RequestHelper.performRequest('PATCH', url, data);
    }

    /**
     * Perform a POST Request
     *
     * @static
     * @public
     * @param {string} url - The Relative URL (e.g. /sites/{siteId}/my-resource)
     * @param {any} [data] - The Data to POST
     * @returns {Promise<any>}
     */
    static postRequest(url, data = null)
    {
        return RequestHelper.performRequest('POST', url, data);
    }

    /**
     * Perform a DELETE Request
     *
     * @static
     * @public
     * @param {string} url - The Relative URL (e.g. /sites/{siteId}/my-resource)
     * @param {Object<string, any>} [queryParameters] - Query Parameters (e.g. {myQuery: myValue})
     * @returns {Promise<boolean>}
     */
    static deleteRequest(url, queryParameters = {})
    {
        return RequestHelper.performRequest('DELETE', url, null, queryParameters);
    }

    /**
     * Performs a Request (fetch)
     *
     * @static
     * @public
     * @param {string} method The Request Method
     * @param {string} url The Relative URL
     * @param {any} [data] The Data to send with this Request
     * @param {Object<string, any>} [queryParameters] - Query Parameters (e.g. {myQuery: myValue})
     * @returns {Promise<any>}
     */
    static performRequest(method, url, data = null, queryParameters = {})
    {
        if(url.length > 0 && url[0] != '/')
        {
            url = '/' + url;
        }

        if(isDebugMode() == true)
        {
            console.log('RequestHelper :: ' + method.toUpperCase() + " - " + url);
        }

        var query = '';

        if (isDefined(queryParameters) && method.toUpperCase() == 'GET')
        {
            for(const [key, value] of Object.entries(queryParameters))
            {
                if(value !== undefined)
                {
                    let parameter = value;

                    if(Array.isArray(value))
                    {
                        parameter = value.join(',')
                    }
                    else if(value instanceof Date)
                    {
                        parameter = value.toISOString();
                    }

                    query += (query.length == 0 ? '?' : '&') + key + '=' + encodeURIComponent(parameter);
                }
            }
        }

        if(RequestHelper.hasNativeFetch() === true)
        {
            return RequestHelper.performNativeFetchRequest(method, url, data, query);
        }
        else
        {
            return RequestHelper.performNodeFetchRequest(method, url, data, query);
        }
    }

    /**
     * Perform a Native Fetch Request
     * 
     * @private
     * @static
     * @param {string} method The Request Method
     * @param {string} url The Relative URL
     * @param {any} data The Data to Send with this Request
     * @param {string} query The Query String
     * @returns {Promise<any>}
     */
    static performNativeFetchRequest(method, url, data, query)
    {
        /**
         * @type {RequestInit}
         */
        const options = {
            method,
            mode: 'cors',
            credentials: 'omit',
        };

        const headers = new Headers();

        if (isDefined(JWT) && NoAuthPaths.some((path) => { return new RegExp(`^${path}(/.*)?$`).test(url); }) === false)
        {
            headers.set('Authorization', `Bearer ${JWT}`);
        }

        if (isDefined(data))
        {
            headers.set('Content-Type', 'application/json');
            options.body = JSON.stringify(data);
        }

        options.headers = headers;

        return new Promise((resolve, reject) => {
            fetch(BaseURL + url + query, options)
                .then((response) => {
                    if (response.ok) {
                        if (response.status === 200 || response.status === 201) {
                            response.json()
                                .then((json) => {
                                    resolve(json);
                                })
                                .catch((error) => {
                                    reject(error);
                                });

                            return;
                        }

                        resolve(true);
                        return;
                    }

                    response.json().then((json) => {
                        switch(response.status)
                        {
                            case 400:
                                reject(new BadRequestError(json.title));
                                break;
                            
                            case 401:
                                reject(new UnauthorizedError(json.title));
                                break;
                            
                            case 403:
                                reject(new ForbiddenError(json.title));
                                break;
                            
                            case 404:
                                reject(new NotFoundError(json.title));
                                break;
                            
                            case 405:
                                reject(new NotAllowedError(json.title));
                                break;
                            
                            case 500:
                                reject(new ServerError(json.title));
                                break;
                            
                            default:
                                reject(new Error("Unknown HTTP Response Code `" + response.status + "`"));
                                break;
                        }
                    }).catch(error => reject(error));
                })
                .catch(error => reject(new NetworkError(error.message)));
        });
    }

    /**
     * Perform a Node Fetch Request
     * 
     * @private
     * @static
     * @param {string} method The Request Method
     * @param {string} url The Relative URL
     * @param {any} data The Data to Send with this Request
     * @param {string} query The Query String
     * @returns {Promise<any>}
     */
    static performNodeFetchRequest(method, url, data, query)
    {
        /**
         * @type {nodeRequestInit}
         */
        const options = {
            method,
            compress: true,
        };
        
        const headers = new nodeHeaders();

        if (isDefined(JWT) && NoAuthPaths.some((path) => { return new RegExp(`^${path}(/.*)?$`).test(url); }) === false)
        {
            headers.set('Authorization', `Bearer ${JWT}`);
        }

        if (isDefined(data))
        {
            headers.set('Content-Type', 'application/json');
            options.body = JSON.stringify(data);
        }

        options.headers = headers;

        return new Promise((resolve, reject) => {
            nodeFetch(BaseURL + url + query, options)
                .then((response) => {
                    if (response.ok) {
                        if (response.status === 200 || response.status === 201) {
                            response.json()
                                .then((json) => {
                                    resolve(json);
                                })
                                .catch((error) => {
                                    reject(error);
                                });

                            return;
                        }

                        resolve(true);
                        return;
                    }

                    response.json().then((json) => {
                        switch(response.status)
                        {
                            case 400:
                                reject(new BadRequestError(json.title));
                                break;
                            
                            case 401:
                                reject(new UnauthorizedError(json.title));
                                break;
                            
                            case 403:
                                reject(new ForbiddenError(json.title));
                                break;
                            
                            case 404:
                                reject(new NotFoundError(json.title));
                                break;
                            
                            case 405:
                                reject(new NotAllowedError(json.title));
                                break;
                            
                            case 500:
                                reject(new ServerError(json.title));
                                break;
                            
                            default:
                                reject(new Error("Unknown HTTP Response Code `" + response.status + "`"));
                                break;
                        }
                    }).catch(error => reject(error));
                })
                .catch(error => reject(new NetworkError(error.message)));
        });
    }
    
    /**
     * Checks if a Native Version of Fetch is Available
     * 
     * @private
     * @static
     * @returns {boolean}
     */
    static hasNativeFetch()
    {
        return typeof window === 'object' && typeof window.fetch === 'function';
    }
}

export default RequestHelper;