UNPKG

@aeternity/aepp-sdk

Version:

SDK for the æternity blockchain

212 lines 8.96 kB
import { RestError } from '@azure/core-rest-pipeline'; import { createSerializer as createSerializerOrig } from '@azure/core-client'; import { pause } from './other.js'; import semverSatisfies from './semver-satisfies.js'; import { InternalError, UnexpectedTsError, UnsupportedVersionError } from './errors.js'; const bigIntPrefix = '_sdk-big-int-'; export const createSerializer = (...args) => { const serializer = createSerializerOrig(...args); const { serialize, deserialize } = serializer; return Object.assign(serializer, { serialize(...[mapper, object, objectName, options]) { // @ts-expect-error we are extending autorest with BigInt support if (mapper.type.name !== 'BigInt' || object == null) { return serialize.call(this, mapper, object, objectName, options); } if (typeof object !== 'bigint') { objectName !== null && objectName !== void 0 ? objectName : objectName = mapper.serializedName; throw new Error(`${objectName} with value ${object} must be of type bigint.`); } return object.toString(); }, deserialize(...[mapper, responseBody, objectName, options]) { // @ts-expect-error we are extending autorest with BigInt support if (mapper.type.name !== 'BigInt' || responseBody == null) { if (typeof responseBody === 'string' && responseBody.startsWith(bigIntPrefix)) { console.warn(`AeSdk internal error: BigInt value ${responseBody} handled incorrectly`); responseBody = +responseBody.replace(bigIntPrefix, ''); } const result = deserialize.call(this, mapper, responseBody, objectName, options); // TODO: remove after fixing https://github.com/aeternity/ae_mdw/issues/1891 // and https://github.com/aeternity/aeternity/issues/4386 if (result instanceof Date) return new Date(+result / 1000); return result; } if (typeof responseBody === 'number' && responseBody > Number.MAX_SAFE_INTEGER) { throw new InternalError(`Number ${responseBody} is not accurate to be converted to BigInt`); } return BigInt(responseBody.toString().replace(bigIntPrefix, '')); } }); }; const safeLength = Number.MAX_SAFE_INTEGER.toString().length; const bigIntPropertyRe = new RegExp(String.raw`("\w+":\s*)(\d{${safeLength},})(\s*[,}])`, 'm'); const bigIntArrayItemRe = new RegExp(String.raw`([[,]\s*)(\d{${safeLength},})\b`, 'm'); export const parseBigIntPolicy = { name: 'parse-big-int', async sendRequest(request, next) { const response = await next(request); if (response.bodyAsText == null) return response; // TODO: replace with https://caniuse.com/mdn-javascript_builtins_json_parse_reviver_parameter_context_argument when it gets support in FF and Safari response.bodyAsText = response.bodyAsText.replaceAll(new RegExp(bigIntPropertyRe, 'g'), matched => { const match = matched.match(bigIntPropertyRe); if (match == null) throw new UnexpectedTsError(); const [, name, value, end] = match; return [name, +value > Number.MAX_SAFE_INTEGER ? `"${bigIntPrefix}${value}"` : value, end].join(''); }); // FIXME: may break strings inside json response.bodyAsText = response.bodyAsText.replaceAll(new RegExp(bigIntArrayItemRe, 'g'), matched => { const match = matched.match(bigIntArrayItemRe); if (match == null) throw new UnexpectedTsError(); const [, prefix, value] = match; return `${prefix}"${bigIntPrefix}${value}"`; }); return response; } }; export const genRequestQueuesPolicy = () => { const requestQueues = new Map(); return { policy: { name: 'request-queues', async sendRequest(request, next) { var _requestQueues$get; const key = request.headers.get('__queue'); request.headers.delete('__queue'); const getResponse = async () => next(request); if (key == null) return getResponse(); const req = ((_requestQueues$get = requestQueues.get(key)) !== null && _requestQueues$get !== void 0 ? _requestQueues$get : Promise.resolve()).then(getResponse); requestQueues.set(key, req.catch(() => {})); return req; } }, position: 'perCall' }; }; export const genCombineGetRequestsPolicy = () => { const pendingGetRequests = new Map(); return { policy: { name: 'combine-get-requests', async sendRequest(request, next) { var _pendingGetRequests$g; if (request.method !== 'GET') return next(request); const key = JSON.stringify([request.url, request.body]); const response = (_pendingGetRequests$g = pendingGetRequests.get(key)) !== null && _pendingGetRequests$g !== void 0 ? _pendingGetRequests$g : next(request); pendingGetRequests.set(key, response); try { return await response; } finally { pendingGetRequests.delete(key); } } }, position: 'perCall' }; }; export const genAggressiveCacheGetResponsesPolicy = () => { const getRequests = new Map(); return { policy: { name: 'aggressive-cache-get-responses', async sendRequest(request, next) { var _getRequests$get; if (request.method !== 'GET') return next(request); const key = JSON.stringify([request.url, request.body]); const response = (_getRequests$get = getRequests.get(key)) !== null && _getRequests$get !== void 0 ? _getRequests$get : next(request); getRequests.set(key, response); return response; } }, position: 'perCall' }; }; export const genErrorFormatterPolicy = getMessage => ({ policy: { name: 'error-formatter', async sendRequest(request, next) { try { return await next(request); } catch (error) { if (!(error instanceof RestError) || error.request == null || error.message.startsWith('Error ')) throw error; const prefix = `${new URL(error.request.url).pathname.slice(1)} error`; if (error.response?.bodyAsText == null) { if (error.message === '') error.message = `${prefix}: ${error.code}`; throw error; } const body = error.response.parsedBody; error.message = prefix; const message = body == null ? ` ${error.response.status} status code` : getMessage(body); if (message !== '') error.message += `:${message}`; throw error; } } }, position: 'perCall' }); export const genVersionCheckPolicy = (name, versionCb, geVersion, ltVersion, ignoreVersion) => ({ policy: { name: 'version-check', async sendRequest(request, next) { if (request.headers.has('__version-check')) { request.headers.delete('__version-check'); return next(request); } const options = { requestOptions: { customHeaders: { '__version-check': 'true' } } }; const args = [await versionCb(options), geVersion, ltVersion]; if (!semverSatisfies(...args)) { const error = new UnsupportedVersionError(name, ...args); if (ignoreVersion) console.warn(error.message);else throw error; } return next(request); } }, position: 'perCall' }); export const genRetryOnFailurePolicy = (retryCount, retryOverallDelay) => ({ policy: { name: 'retry-on-failure', async sendRequest(request, next) { var _request$headers$get; if (request.headers.get('__no-retry') != null) { request.headers.delete('__no-retry'); return next(request); } const retryCode = (_request$headers$get = request.headers.get('__retry-code')) !== null && _request$headers$get !== void 0 ? _request$headers$get : NaN; request.headers.delete('__retry-code'); const statusesToNotRetry = [200, 400, 403, 410, 500].filter(c => c !== +retryCode); const intervals = new Array(retryCount).fill(0).map((_, idx) => ((idx + 1) / retryCount) ** 2); const intervalSum = intervals.reduce((a, b) => a + b, 0); const intervalsInMs = intervals.map(e => Math.floor(e / intervalSum * retryOverallDelay)); let error = new RestError('Not expected to be thrown'); for (let attempt = 0; attempt <= retryCount; attempt += 1) { if (attempt !== 0) { await pause(intervalsInMs[attempt - 1]); const urlParsed = new URL(request.url); urlParsed.searchParams.set('__sdk-retry', attempt.toString()); request.url = urlParsed.toString(); } try { return await next(request); } catch (e) { var _e$response$status; if (!(e instanceof RestError)) throw e; if (statusesToNotRetry.includes((_e$response$status = e.response?.status) !== null && _e$response$status !== void 0 ? _e$response$status : 0)) throw e; error = e; } } throw error; } }, position: 'perCall' }); //# sourceMappingURL=autorest.js.map