@algodex/algodex-sdk
Version:
API calls for interacting with the Algorand blockchain
153 lines (146 loc) • 4.47 kB
JavaScript
/*
* Copyright (C) 2021-2022 Algodex VASP (BVI) Corp.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
const axios = require('axios');
/**
* @typedef import('axios').AxiosResponse AxiosResponse
*/
/**
* # 🕸 Basic HTTP API Client
*
* HTTP interfaces
*
* Interfaces are based on Axios. Provides useful features like etag caching
*
* @example
* // importing a client
* const HTTPClient = require('@algodex/algodex-sdk/lib/http/HTTPClient')
* const client = new HTTPClient('https://www.google.com', false)
* client.get('/somepath').then(console.log)
*
* @example
* // Creating a custom client
* const HTTPClient = require('../HTTPClient');
* // Declare any defaults
* const BASE_URL = 'https://httpbin.org/get';
*
* // Static Function
* function mapResponse({status, ...rest}){
* return Object.create({status: `mapped-${status}`, ...rest})
* }
*
* // Custom Client Constructor
* function CustomClient(baseUrl=BASE_URL, etags=false) {
* // Apply client
* const client = new HTTPClient(baseUrl, etags);
* Object.keys(client).forEach((key)=>{
* this[key] = client[key];
* });
* }
*
* // Statics
* CustomClient.mapResponse = mapResponse;
*
* // Extend from HTTPClient
* CustomClient.prototype = Object.create(HTTPClient.prototype);
* // Override/Assign prototypes
* Object.assign(ExplorerClient.prototype, {
* async fetchHTTPBinGet() {
* return await this.get(`${this.baseUrl}`);
* },
* });
*
* module.exports = CustomClient;
*
* @TODO: Generate from OAS/Swagger
* @param {string} baseUrl Base URL to use for API requests
* @param {boolean} [etags] Flag to enable Etag on GET Requests
* @param {Config} [config] Optional configuration for services
* @see https://axios-http.com/docs/intro
* @ignore
*/
function HTTPClient(baseUrl, etags = false, config) {
// TODO: Check for valid URL
if (typeof baseUrl === 'undefined') throw new TypeError('Must have a valid URL');
if (typeof etags !== 'boolean') throw new TypeError('etags must be a boolean!');
/**
* Algodex Config
* @type {Config}
*/
this.config = config;
/**
* Base URL
* @type {string}
*/
this.baseUrl = baseUrl;
/**
* Etag State
* @type {boolean}
*/
this.etags = etags;
if (etags) {
/**
* Cache for ETags
* @type {{res: {}, etag: {}}}
*/
this.cache = {
etag: {},
res: {},
};
}
}
// Extend from axios
HTTPClient.prototype = Object.create(axios);
// Override Axios with instance properties
Object.assign(HTTPClient.prototype, {
/**
*
* Optionally check for etag changes in the API and update the local cache
* when it is stale. Used to reduce request load from react-query
* to the backend.
*
* @param {string} url Request URL
* @param {object} options HTTPClient/Axios Options
* @return {Promise<AxiosResponse>} Response or Cached Result
* @memberOf http.HTTPClient.prototype
*/
async get(url, options={}) {
if (typeof url !== 'string') throw new TypeError('Must be a valid URL');
if (typeof options !== 'object') throw new TypeError('Options must be an Object!');
if (this.etags && typeof this.cache.etag[url] !== 'undefined') {
if (typeof options.headers === 'undefined') {
options.headers = {};
}
options.headers['if-none-match'] = this.cache.etag[url];
}
return !this.etags ?
await axios.get(url, options) :
await axios.get(url, options)
.then((res) => {
if (res && res.status === 200) {
const etag = res.headers.etag;
res.data.etag = etag;
this.cache.res[url] = res;
this.cache.etag[url] = etag;
// console.log('NOT returning cache for ' + url);
return res;
} else {
throw new Error('Unexpected Status');
}
})
.catch((error) => {
const errorResp = error.response;
if (errorResp && errorResp.status === 304) {
// console.log('returning cache for ' + url);
return this.cache.res[url];
} else {
throw new Error(`Invalid response for ${url} : ${error.message}`);
}
});
},
});
module.exports = HTTPClient;