Source

client/base.js

const axios = require('axios');

/**
 * Abstract class that provides the common bones for OSDU API clients.
 * - Provides the framework for managing authentication and appending required headers to API requests.
 * @class
 * @abstract
 * @category Clients
 */
class BaseOsduClient {
    /**
     * @constructor
     * @param {string} api_url - The url for the OSDU API, with or without a trailing `/`
     */
    constructor(api_url) {
        if (!api_url) {
            throw new Error(`Must provide an API url for the OSDU application`);
        }
        this.apiUrl = api_url;
        if (this.apiUrl[this.apiUrl.length-1] == '/') {
            this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length-1);
        }
        this.axiosClient = axios.create({
            baseURL: this.apiUrl
        });
    }

    // Auth
    /**
     * Refresh the access token used to authenticate API Requests
     * - Not implemented in the base class
     * @protected
     * @throws Error
     */
    _refreshAccessToken() {
        throw new Error(`_refreshAccessToken is not implemented in BaseOsduClient`);
    }

    // HTTP Requests
    /**
     * Generate the HTTP headers needed to interact with the OSDU API
     * @protected
     * @param {string} data_partition - The data partition against which the request is being made
     * @returns {Object} The common headers required to communicate with the OSDU API
     */
    _getHeaders(data_partition) {
        if (!this.accessToken) {
            throw new Error(`OSDU client does not have an access token`);
        }
        return {
            "Content-Type": "application/json",
            "data-partition-id": data_partition,
            "Authorization": `Bearer ${this.accessToken}`
        };
    }

    /**
     * Common logic to all HTTP requests made to the OSDU API
     * - For internal client use only
     * - Handles the automatic generation of common headers, authentication, and retries (up to 3) on error conditions
     * @protected
     * @param {string} data_partition - The data partition against which the request is being made
     * @param {Function} method - The axios method reference to call when sending the request (`get`, `post`, etc.)
     * @param {Object} args - The arguments used to invoke the API request
     * @param {string} args.path - The url path to reach out to on the OSDU API (I.E. `/path`)
     * @param {Object} [args.body] - The JSON HTTP body to include with the request
     * @param {Object} [args.config] - Any additional Axios config to provide with the request. Note that `Content-Type`, `data-partition-id`, and `Authorization` headers will be overwritten
     * @param {number} [iteration=1] - The iteration of this request. Used for internal recursion and should not be passed in by an external caller
     * @returns {Promise<AxiosResponse>} The response from the Axios client
     */
    async _makeRequest(data_partition, method, args, iteration = 1) {
        // Set up configuration such as headers
        if (!args.config) {
            args.config = { headers: {} };
        }
        if (!this.accessToken) {
            await this._refreshAccessToken();
        }
        args.config.headers = Object.assign(
            args.config.headers, 
            this._getHeaders(data_partition)
        );

        // Make axios request
        var response;
        try {
            if (args.body) {
                response = await method(args.path, args.body, args.config);
            }
            else {
                response = await method(args.path, args.config);
            }
        }
        catch (error) {
            if (error.response) {
                response = error.response;
            }
            else {
                throw error;
            }
        }

        // Handle response
        if (response.status >= 200 && response.status < 300) {
            if (response.data) {
                return response.data;
            }
            else {
                return true;
            }
        }
        if (response.status == 404) {
            return undefined;
        }
        if (response.status == 401 || response.status == 403) {
            await this._refreshAccessToken();
        }
        if ([401, 403, 500].includes(response.status)) {
            // Validate iteration and retry
            const maxAttempts = 3;
            if (iteration > maxAttempts) {
                const err = new Error(`Failed retrying method with the OSDU API: [${response.status}] ${response.statusText}`);
                err.response = response;
                throw err;
            }
            else {
                return this._makeRequest(data_partition, method, args, iteration+1);
            }
        }
        const err = new Error(`Invalid response code from the OSDU API: [${response.status}] ${response.statusText}`);
        err.response = response;
        throw err;
    }

    /**
     * Convenience method for invoking HTTP POST requests
     * @param {string} path - The url path for the HTTP POST request (I.E. `/path`)
     * @param {Object} body - The JSON body to send with the HTTP POST request
     * @param {string} data_partition - The data partition against which the request is being made
     * @returns {Promise<Object>} The response data from the Axios client
     */
    post(path, body, data_partition) {
        return this._makeRequest(data_partition, this.axiosClient.post, { path, body });
    }

    /**
     * Convenience method for invoking HTTP PUT requests
     * @param {string} path - The url path for the HTTP PUT request (I.E. `/path`)
     * @param {Object} body - The JSON body to send with the HTTP PUT request
     * @param {string} data_partition - The data partition against which the request is being made
     * @returns {Promise<Object>} The response data from the Axios client
     */
    put(path, body, data_partition) {
        return this._makeRequest(data_partition, this.axiosClient.put, { path, body });
    }

    /**
     * Convenience method for invoking HTTP GET requests
     * @param {string} path - The url path for the HTTP GET request (I.E. `/path`)
     * @param {string} data_partition - The data partition against which the request is being made
     * @returns {Promise<Object>} The response data from the Axios client
     */
    get(path, data_partition) {
        return this._makeRequest(data_partition, this.axiosClient.get, { path });
    }

    /**
     * Convenience method for invoking HTTP DELETE requests
     * @param {string} path - The url path for the HTTP DELETE request (I.E. `/path`)
     * @param {string} data_partition - The data partition against which the request is being made
     * @returns {Promise<Object>} The response data from the Axios client
     */
    delete(path, data_partition) {
        return this._makeRequest(data_partition, this.axiosClient.delete, { path });
    }
}

module.exports = BaseOsduClient;