UNPKG

tranzak-node

Version:
176 lines (167 loc) 5.09 kB
"use strict"; 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;