@toruslabs/http-helpers
Version:
303 lines (299 loc) • 10.9 kB
JavaScript
;
var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
var merge = require('deepmerge');
var logLevel = require('loglevel');
const log = logLevel.getLogger("http-helpers");
log.setLevel(logLevel.levels.INFO);
let apiKey = "torus-default";
let embedHost = "";
// #region API Keys
const gatewayAuthHeader = "x-api-key";
const gatewayEmbedHostHeader = "x-embed-host";
let sentry = null;
const tracingOrigins = [];
const tracingPaths = [];
function enableSentryTracing(_sentry, _tracingOrigins, _tracingPaths) {
sentry = _sentry;
tracingOrigins.push(..._tracingOrigins);
tracingPaths.push(..._tracingPaths);
}
function setEmbedHost(embedHost_) {
embedHost = embedHost_;
}
function clearEmbedHost() {
embedHost = "";
}
function getEmbedHost() {
return embedHost;
}
function setAPIKey(apiKey_) {
apiKey = apiKey_;
}
function clearAPIKey() {
apiKey = "torus-default";
}
function getAPIKey() {
return apiKey;
}
// #endregion
function setLogLevel(level) {
log.setLevel(level);
}
async function fetchAndTrace(url, init) {
let _url = null;
try {
_url = new URL(url);
} catch {
// ignore
}
if (sentry && _url && (tracingOrigins.includes(_url.origin) || tracingPaths.includes(_url.pathname))) {
const result = await sentry.startSpan({
name: url,
op: "http.client"
}, async () => {
const response = await fetch(url, init);
return response;
});
return result;
}
return fetch(url, init);
}
function getApiKeyHeaders() {
const headers = {};
if (apiKey) headers[gatewayAuthHeader] = apiKey;
if (embedHost) headers[gatewayEmbedHostHeader] = embedHost;
return headers;
}
function debugLogResponse(response) {
log.info(`Response: ${response.status} ${response.statusText}`);
log.info(`Url: ${response.url}`);
}
function logTracingHeader(response) {
const tracingHeader = response.headers.get("x-web3-correlation-id");
if (tracingHeader) log.info(`Request tracing with traceID = ${tracingHeader}`);
}
const promiseTimeout = async (ms, promise) => {
let timeoutFunc = null;
try {
const timeout = new Promise((_resolve, reject) => {
timeoutFunc = setTimeout(() => {
reject(new Error(`Timed out in ${ms}ms`));
}, ms);
});
const result = await Promise.race([promise, timeout]);
// promise.race will return the first resolved promise
// then we clear the timeout
if (timeoutFunc != null) {
clearTimeout(timeoutFunc);
}
return result;
} catch (err) {
// clear the timeout
if (timeoutFunc != null) {
clearTimeout(timeoutFunc);
}
// rethrow the original error
throw err;
}
};
const get = async (url, options_ = {}, customOptions = {}) => {
const defaultOptions = {
mode: "cors",
headers: {}
};
if (customOptions.useAPIKey) {
defaultOptions.headers = _objectSpread(_objectSpread({}, defaultOptions.headers), getApiKeyHeaders());
}
options_.method = "GET";
const options = merge(defaultOptions, options_);
const response = await fetchAndTrace(url, options);
if (response.ok) {
const responseContentType = response.headers.get("content-type");
if (responseContentType !== null && responseContentType !== void 0 && responseContentType.includes("application/json")) {
return response.json();
}
return response.text();
}
debugLogResponse(response);
throw response;
};
const post = (url, data = {}, options_ = {}, customOptions = {}) => {
const defaultOptions = {
mode: "cors",
headers: {
"Content-Type": "application/json; charset=utf-8"
}
};
if (customOptions.useAPIKey) {
defaultOptions.headers = _objectSpread(_objectSpread({}, defaultOptions.headers), getApiKeyHeaders());
}
options_.method = "POST";
const options = merge(defaultOptions, options_);
// deep merge changes the structure of form data and url encoded data ,
// so we should not deepmerge body data
if (customOptions.isUrlEncodedData) {
// for multipart request browser/client will add multipart content type
// along with multipart boundary , so for multipart request send
// content-type: undefined or send with multipart boundary if already known
options.body = data;
// If url encoded data, this must not be the content type
if (options.headers["Content-Type"] === "application/json; charset=utf-8") delete options.headers["Content-Type"];
} else {
options.body = JSON.stringify(data);
}
return promiseTimeout(customOptions.timeout || 60000, fetchAndTrace(url, options).then(response => {
if (customOptions.logTracingHeader) {
logTracingHeader(response);
}
if (response.ok) {
const responseContentType = response.headers.get("content-type");
if (responseContentType !== null && responseContentType !== void 0 && responseContentType.includes("application/json")) {
return response.json();
}
return response.text();
}
debugLogResponse(response);
throw response;
}));
};
const patch = async (url, data = {}, options_ = {}, customOptions = {}) => {
const defaultOptions = {
mode: "cors",
headers: {
"Content-Type": "application/json; charset=utf-8"
}
};
// for multipart request browser/client will add multipart content type
// along with multipart boundary , so for multipart request send
// content-type: undefined or send with multipart boundary if already known
if (customOptions.useAPIKey) {
defaultOptions.headers = _objectSpread(_objectSpread({}, defaultOptions.headers), getApiKeyHeaders());
}
options_.method = "PATCH";
const options = merge(defaultOptions, options_);
// deep merge changes the structure of form data and url encoded data ,
// so we should not deepmerge body data
if (customOptions.isUrlEncodedData) {
// for multipart request browser/client will add multipart content type
// along with multipart boundary , so for multipart request send
// content-type: undefined or send with multipart boundary if already known
options.body = data;
// If url encoded data, this must not be the content type
if (options.headers["Content-Type"] === "application/json; charset=utf-8") delete options.headers["Content-Type"];
} else {
options.body = JSON.stringify(data);
}
const response = await fetchAndTrace(url, options);
if (response.ok) {
const responseContentType = response.headers.get("content-type");
if (responseContentType !== null && responseContentType !== void 0 && responseContentType.includes("application/json")) {
return response.json();
}
return response.text();
}
debugLogResponse(response);
throw response;
};
const put = async (url, data = {}, options_ = {}, customOptions = {}) => {
const defaultOptions = {
mode: "cors",
headers: {
"Content-Type": "application/json; charset=utf-8"
}
};
// for multipart request browser/client will add multipart content type
// along with multipart boundary , so for multipart request send
// content-type: undefined or send with multipart boundary if already known
if (customOptions.useAPIKey) {
defaultOptions.headers = _objectSpread(_objectSpread({}, defaultOptions.headers), getApiKeyHeaders());
}
options_.method = "PUT";
const options = merge(defaultOptions, options_);
// deep merge changes the structure of form data and url encoded data ,
// so we should not deepmerge body data
if (customOptions.isUrlEncodedData) {
// for multipart request browser/client will add multipart content type
// along with multipart boundary , so for multipart request send
// content-type: undefined or send with multipart boundary if already known
options.body = data;
// If url encoded data, this must not be the content type
if (options.headers["Content-Type"] === "application/json; charset=utf-8") delete options.headers["Content-Type"];
} else {
options.body = JSON.stringify(data);
}
const response = await fetchAndTrace(url, options);
if (response.ok) {
const responseContentType = response.headers.get("content-type");
if (responseContentType !== null && responseContentType !== void 0 && responseContentType.includes("application/json")) {
return response.json();
}
return response.text();
}
debugLogResponse(response);
throw response;
};
const remove = async (url, data = {}, options_ = {}, customOptions = {}) => {
const defaultOptions = {
mode: "cors",
headers: {
"Content-Type": "application/json; charset=utf-8"
}
};
// for multipart request browser/client will add multipart content type
// along with multipart boundary , so for multipart request send
// content-type: undefined or send with multipart boundary if already known
if (customOptions.useAPIKey) {
defaultOptions.headers = _objectSpread(_objectSpread({}, defaultOptions.headers), getApiKeyHeaders());
}
options_.method = "DELETE";
const options = merge(defaultOptions, options_);
if (customOptions.isUrlEncodedData) {
// for multipart request browser/client will add multipart content type
// along with multipart boundary , so for multipart request send
// content-type: undefined or send with multipart boundary if already known
options.body = data;
// If url encoded data, this must not be the content type
if (options.headers["Content-Type"] === "application/json; charset=utf-8") delete options.headers["Content-Type"];
} else {
options.body = JSON.stringify(data);
}
const response = await fetchAndTrace(url, options);
if (response.ok) {
const responseContentType = response.headers.get("content-type");
if (responseContentType !== null && responseContentType !== void 0 && responseContentType.includes("application/json")) {
return response.json();
}
return response.text();
}
debugLogResponse(response);
throw response;
};
const generateJsonRPCObject = (method, parameters) => ({
jsonrpc: "2.0",
method,
id: 10,
params: parameters
});
const promiseRace = (url, options, timeout = 60000) => Promise.race([get(url, options), new Promise((_resolve, reject) => {
setTimeout(() => {
reject(new Error("timed out"));
}, timeout);
})]);
exports.clearAPIKey = clearAPIKey;
exports.clearEmbedHost = clearEmbedHost;
exports.enableSentryTracing = enableSentryTracing;
exports.gatewayAuthHeader = gatewayAuthHeader;
exports.gatewayEmbedHostHeader = gatewayEmbedHostHeader;
exports.generateJsonRPCObject = generateJsonRPCObject;
exports.get = get;
exports.getAPIKey = getAPIKey;
exports.getEmbedHost = getEmbedHost;
exports.patch = patch;
exports.post = post;
exports.promiseRace = promiseRace;
exports.promiseTimeout = promiseTimeout;
exports.put = put;
exports.remove = remove;
exports.setAPIKey = setAPIKey;
exports.setEmbedHost = setEmbedHost;
exports.setLogLevel = setLogLevel;