tranzak-node
Version:
TRANZAK API client for Nodejs
176 lines (167 loc) • 5.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _section = require("./section.js");
var _simpleCache = _interopRequireDefault(require("./simple-cache.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2023 HolyCorn Software
* The tranzak-node library
* This module (transport) is the heart of communication in the library.
* It is responsible for making requests, and sending responses
*/
let realFetch;
/**
*
* @param { URL | RequestInfo} url
* @param {RequestInit} init
*/
async function fetch(url, init) {
realFetch ||= (await import('node-fetch')).default;
return await realFetch(...arguments);
}
const credentials = Symbol();
const authCache = Symbol();
class Transport {
/**
*
* @param {tranzak_node.Credentials} _credentials
*/
constructor(_credentials) {
this[credentials] = _credentials;
}
get apiUrl() {
return this[credentials].apiUrl || (this[credentials].mode ?? 'live') === 'sandbox' ? `https://sandbox.dsapi.tranzak.me` : `https://dsapi.tranzak.me`;
}
/**
* This method authenticates to the server's backend, and returns an authentication token.
*
* This method already incoporates caching.
*/
async getAuthToken() {
if (!this[authCache]) {
this[authCache] = new _simpleCache.default({
get: async () => {
const response = await (await fetch(`${this.apiUrl}/auth/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
appKey: this[credentials].appKey,
appId: this[credentials].appId
})
})).json();
if (!response.success) {
const error = Error(`Could not authenticate with server: ${response.errorMsg}`);
error.fatal = false;
throw error;
}
/** @type {string} */
const token = response.data.token;
const tokenLife = response.data.expires * 1000;
// In case the token's lifespan changes without our knowledge
if (tokenLife < this[authCache].timeout) {
this[authCache].timeout = tokenLife * 0.9;
}
return token;
},
timeout: 1.8 * 60 * 60 * 1000 // The token is valid for 1 hour and 50 minutes
});
}
return await this[authCache].get();
}
/**
* This method makes an authenticated request to the server.
* The method will throw an error, if the server responds with an error, even if request went through
* @param {object} param0
* @param {string} param0.path
* @param {object} param0.body
* @param {'GET'|"POST"} param0.method
* @param {object} param0.headers
* @returns {Promise<object>}
*/
async request({
path,
body,
method,
headers
}) {
method ??= typeof body === 'undefined' ? 'GET' : 'POST';
try {
const reply = await (await fetch(new URL(path, `${this.apiUrl}/xp021/`).href, {
method,
body: method.toUpperCase() == "POST" ? JSON.stringify(body) : undefined,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer: ${await this.getAuthToken()}`,
...headers
}
})).json();
if (!reply.success) {
const error = new Error(reply.errorMsg);
error.fatal = false;
error.payload = body;
error.endPoint = path;
if (/not found/gi.test(reply.errorMsg)) {
// Things like transaction not found, cannot be retried, as it suggests wrong input
error.fatal = true;
}
error.generated = true;
error.code = reply.errorCode;
throw error;
}
return reply.data;
} catch (e) {
e.fatal = false;
throw e;
}
}
/**
* @template Input
* @template Output
* This method makes an authenticated request to the server, in the knowledge that the servers response will be {@link tranzak_node.PaginatedResponse paginated}
* @param {object} param0
* @param {string} param0.path
* @param {object} param0.body
* @param {'GET'|"POST"} param0.method
* @param {(input: Input)=> Output} param0.transform
* @param {object} param0.headers
*/
async *paginatedRequest({
path,
body,
method,
headers,
transform
}) {
let pageIndex = 1;
let done;
const fetchNextBatch = async () => {
/**
* @type {tranzak_node.PaginatedResponse<Output>}
*/
const data = await this.request({
path: `${path}?page=${pageIndex}`,
body,
method,
headers
});
done = !data.hasMore;
return data.list;
};
while (!done) {
const set = await fetchNextBatch();
if (set.length == 0) {
break;
}
for (const item of set) {
yield transform(item, this[_section.transport]);
}
pageIndex += 1;
}
}
}
exports.default = Transport;