UNPKG

@agnostack/requestd

Version:

Please contact agnoStack via info@agnostack.com for any questions

261 lines 13.8 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbstractClient = void 0; const utils_1 = require("./utils"); const Adapters_1 = require("./Adapters"); const ClientError_1 = require("./ClientError"); // eslint-disable-next-line @typescript-eslint/no-var-requires const sdk_version = require('../package.json').version; const DEFAULT_DURATION = 15000; // 15 seconds const MAX_DURATION = 5184000000; // 2 months // NOTE: this function handles calling authenticate and storage const authorize = async (options, _fetch, callback, crypto, storage, authorizeConfig) => { const { storage_key, sdk_version } = authorizeConfig; let authentication; const { custom, URIs, auth: { expiration, }, } = options; const authOptions = Object.assign({ URIs }, custom); if (storage) { try { const now = Date.now(); const _storedCredentials = storage.getItem(storage_key); const storedCredentials = (0, utils_1.ensureObject)((0, utils_1.safeParse)(_storedCredentials)); const credentials = Object.assign(Object.assign({}, storedCredentials), { isExpired: () => ((storedCredentials.sdk_version == undefined) || (storedCredentials.expires == undefined) || (storedCredentials.previous == undefined) || (storedCredentials.sdk_version !== sdk_version) || ((storedCredentials.expires !== 0) && (now >= storedCredentials.expires)) || (storedCredentials.expires >= (now + MAX_DURATION)) || (storedCredentials.previous <= (now - MAX_DURATION))) }); authentication = (!credentials.authentication || credentials.isExpired()) // NOTE: this exposes this.fetch to the implementation of this.authenticate ? await callback.bind({ fetch: _fetch })(authOptions) : await crypto.decrypt(credentials.authentication); const expirationDuration = expiration !== null && expiration !== void 0 ? expiration : DEFAULT_DURATION; if (authentication) { const updatedCredentials = { authentication: await crypto.encrypt(authentication), expires: (expiration === 0) ? expiration : (now + expirationDuration), previous: now, sdk_version, }; await storage.setItem(storage_key, JSON.stringify(updatedCredentials)); } } catch (error) { throw new ClientError_1.ClientError('Authentication Error', { code: 500, error }); } } return authentication; }; class AbstractClient { constructor(options) { const { URI, URIs = { rest: URI, query: URI, }, shared_secret, storage_key, application, application: { id: applicationId, version: applicationVersion, installation: installationId, } = {}, headers: _headers, expiration: _expiration, storage: _storage, crypto: _crypto, disallow: _disallow = [], fetch: _fetch } = options, _custom = __rest(options, ["URI", "URIs", "shared_secret", "storage_key", "application", "application", "headers", "expiration", "storage", "crypto", "disallow", "fetch"]); this.fetch = _fetch || globalThis.fetch; this.clientType = this.constructor.name; this.application = application; this.sharedSecret = shared_secret; this.storageKey = storage_key || [this.clientType, applicationId, applicationVersion, installationId].filter(utils_1.stringNotEmpty).join('-'); this.storage = _storage || Adapters_1.FactoryStorage.getInstance(); this.crypto = _crypto; const custom = _custom; // eslint-disable-next-line @typescript-eslint/no-unused-vars const expiration = _expiration !== null && _expiration !== void 0 ? _expiration : DEFAULT_DURATION; const _a = (0, utils_1.ensureObject)(_headers), { rest: restHeaders = {}, query: queryHeaders = {} } = _a, otherHeaders = __rest(_a, ["rest", "query"]); const headers = Object.entries((0, utils_1.ensureObject)(otherHeaders)).reduce((_a, _b) => { var { rest: previousRest, query: previousQuery } = _a, previousHeaders = __rest(_a, ["rest", "query"]); var _c = __read(_b, 2), key = _c[0], value = _c[1]; return (Object.assign(Object.assign({}, previousHeaders), URIs[key] ? { [key]: Object.assign(Object.assign({}, previousHeaders[key]), (0, utils_1.ensureObject)(value)), rest: previousRest, query: previousQuery, } : { rest: Object.assign(Object.assign({}, previousRest), { [key]: value }), query: Object.assign(Object.assign({}, previousQuery), { [key]: value }) })); }, { rest: restHeaders, query: queryHeaders }); const verbs = utils_1.DEFAULT_VERBS.filter((defaultVerb) => !_disallow.includes(defaultVerb)); this.checkVerb = (verb) => verbs.includes(verb); this.options = { custom, URIs, verbs, auth: { expiration, headers, }, }; } initializeCrypto(config) { return Promise.resolve(Adapters_1.FactoryCrypto.getInstance(config)); } unAuthenticate() { if (this.storage) { this.storage.removeItem(this.storageKey); } } async request({ path, wrapped, authorization, basePath: _basePath, method: _method, headers: requestHeaders = {}, data: requestData = undefined, options: { uriType, } = {}, }) { var _a, _b; const { clientType, options: { URIs, auth: { headers: _replacementHeaders = {}, } }, application: { name: applicationName, version: applicationVersion, } = {}, } = this; const isQuery = _method === 'QUERY'; const method = isQuery ? 'POST' : _method; const defaultType = isQuery ? 'query' : 'rest'; const basePath = (_a = _basePath !== null && _basePath !== void 0 ? _basePath : URIs[uriType]) !== null && _a !== void 0 ? _a : URIs[defaultType]; const replacementHeaders = (_b = _replacementHeaders[uriType]) !== null && _b !== void 0 ? _b : _replacementHeaders[defaultType]; const url = path ? `${(0, utils_1.removeTrailingSlash)(basePath)}/${(0, utils_1.removeLeadingSlash)(path)}` : basePath; const body = requestHeaders['Content-Type'] ? requestData : { body: JSON.stringify(requestData) }; const headers = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ Accept: 'application/json', 'Content-Type': 'application/json', 'X-SDK-Language': 'javascript', 'X-SDK-Client': clientType }, sdk_version && { 'X-SDK-Version': sdk_version }), applicationName && { 'X-Application-Name': (0, utils_1.ensureString)(applicationName) }), applicationVersion && { 'X-Application-Version': (0, utils_1.ensureString)(applicationVersion) }), (0, utils_1.replaceHeaders)(replacementHeaders, authorization)), requestHeaders); const fetchOptions = Object.assign({ method, headers }, requestData && Object.assign({}, body)); const response = await this.fetch(url, fetchOptions); if (!response) { throw new ClientError_1.ClientError('No Response', { code: 500, }); } const responseHeaders = (0, utils_1.convertHeaders)(response.headers); let data; let text; try { data = await response.clone() .json() .catch(async () => { text = (await response.text()); return (0, utils_1.ensureObject)((0, utils_1.safeParse)(text)); }); // eslint-disable-next-line no-empty } catch (_ignore) { } if (response.status === 204) { return wrapped ? { data, headers: responseHeaders, } : text; } if (response.status >= 400) { throw new ClientError_1.ClientError(response.statusText, { code: response.status, response: { data, headers: responseHeaders, } }); } let errors; let extensions; if (isQuery) { const { data: graphData, errors: graphErrors, extensions: graphExtensions } = (0, utils_1.ensureObject)(data); data = graphData; if (graphErrors) { errors = graphErrors; } if (graphExtensions) { extensions = graphExtensions; } } if (!response.ok) { throw new ClientError_1.ClientError('Failed Request', { code: response.status, response: Object.assign(Object.assign({ data, headers: responseHeaders }, errors && { errors }), extensions && { extensions }) }); } return wrapped ? Object.assign(Object.assign({ data, headers: responseHeaders }, errors && { errors }), extensions && { extensions }) : data; } async authorizedRequest(requestOptions) { const { method } = requestOptions; if (!this.checkVerb(method)) { throw new ClientError_1.ClientError(`${method} method not allowed`, { code: 405 }); } const { storage, storageKey, options: authOptions, } = this; let cryptoImplementation = this.crypto; if (!cryptoImplementation) { cryptoImplementation = await this.initializeCrypto({ shared_secret: this.sharedSecret, }); if (!cryptoImplementation) { cryptoImplementation = Adapters_1.FactoryCrypto.getInstance({ shared_secret: this.sharedSecret, }); } else { this.crypto = cryptoImplementation; } } const authorization = await authorize(authOptions, this.fetch, this.authenticate, cryptoImplementation, storage, { storage_key: storageKey, sdk_version }); return this.request(Object.assign(Object.assign({}, requestOptions), { authorization })); } // #region Verbs head(path, wrapped, headers, options) { var _a; return this.authorizedRequest(Object.assign({ method: 'HEAD', path, wrapped, options: Object.assign(Object.assign({}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.custom), options) }, (headers && { headers }))); } get(path, wrapped, headers, options) { var _a; return this.authorizedRequest(Object.assign({ method: 'GET', path, wrapped, options: Object.assign(Object.assign({}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.custom), options) }, (headers && { headers }))); } put(path, data, wrapped, headers, options) { var _a; return this.authorizedRequest(Object.assign({ method: 'PUT', path, data, wrapped, options: Object.assign(Object.assign({}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.custom), options) }, (headers && { headers }))); } post(path, data, wrapped, headers, options) { var _a; return this.authorizedRequest(Object.assign({ method: 'POST', path, data, wrapped, options: Object.assign(Object.assign({}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.custom), options) }, (headers && { headers }))); } patch(path, data, wrapped, headers, options) { var _a; return this.authorizedRequest(Object.assign({ method: 'PATCH', path, data, wrapped, options: Object.assign(Object.assign({}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.custom), options) }, (headers && { headers }))); } delete(path, data, wrapped, headers, options) { var _a; return this.authorizedRequest(Object.assign(Object.assign({ method: 'DELETE', path, wrapped, options: Object.assign(Object.assign({}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.custom), options) }, (data && { data })), (headers && { headers }))); } query(data, wrapped, headers, options) { var _a; return this.authorizedRequest(Object.assign({ method: 'QUERY', data, wrapped, options: Object.assign(Object.assign({}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.custom), options) }, (headers && { headers }))); } } exports.AbstractClient = AbstractClient; //# sourceMappingURL=AbstractClient.js.map