UNPKG

metaapi.cloud-sdk

Version:

SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)

231 lines (230 loc) 28.2 kB
'use strict'; function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _async_to_generator(fn) { return function() { var self = this, args = arguments; return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _object_spread(target) { for(var i = 1; i < arguments.length; i++){ var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _define_property(target, key, source[key]); }); } return target; } const axios = require('axios'); import { UnauthorizedError, ForbiddenError, ApiError, ValidationError, InternalError, NotFoundError, TooManyRequestsError, ConflictError } from './errorHandler'; import OptionsValidator from './optionsValidator'; import TimeoutError from './timeoutError'; import LoggerManager from '../logger'; import _ from 'lodash'; let HttpClient = class HttpClient { /** * Performs a request. Response errors are returned as ApiError or subclasses. * @param {Object} options request options * @returns {Object|String|any} request result */ request(options, type = '', retryCounter = 0, endTime = Date.now() + this._maxRetryDelay * this._retries, isLongRunning = false) { var _this = this; return _async_to_generator(function*() { options.timeout = _this._timeout; let retryAfterSeconds = 0; options.callback = (e, res)=>{ _this._logger.debug(`${type}: received request response with status ${res === null || res === void 0 ? void 0 : res.status}`); if ((res === null || res === void 0 ? void 0 : res.status) === 202) { var _res_data_metadata, _res_data; var _res_headers_retryafter; retryAfterSeconds = (_res_headers_retryafter = res.headers['retry-after']) !== null && _res_headers_retryafter !== void 0 ? _res_headers_retryafter : (_res_data = res.data) === null || _res_data === void 0 ? void 0 : (_res_data_metadata = _res_data.metadata) === null || _res_data_metadata === void 0 ? void 0 : _res_data_metadata.recommendedRetryTime; _this._logger.debug(`${type}: retry after value is ${retryAfterSeconds}`); if (isNaN(retryAfterSeconds)) { retryAfterSeconds = Math.max((new Date(retryAfterSeconds).getTime() - Date.now()) / 1000, 1); } if (!isLongRunning) { endTime = Date.now() + _this._longRunningRequestTimeout; isLongRunning = true; } } }; let body; try { const response = yield _this._makeRequest(options, type); options.callback(null, response); body = response && response.data || undefined; } catch (err) { retryCounter = yield _this._handleError(err, type, retryCounter, endTime); return _this.request(options, type, retryCounter, endTime); } if (retryAfterSeconds) { if (body && body.message) { _this._logger.info(`Retrying request in ${Math.floor(retryAfterSeconds)} seconds because request ` + 'returned message:', body.message); } yield _this._handleRetry(endTime, retryAfterSeconds * 1000); body = yield _this.request(options, type, retryCounter, endTime, isLongRunning); } return body; })(); } _makeRequest(options, type) { var _optionsToLog_headers; let optionsToLog = _.cloneDeep(options); if ((_optionsToLog_headers = optionsToLog.headers) === null || _optionsToLog_headers === void 0 ? void 0 : _optionsToLog_headers['auth-token']) { optionsToLog.headers['auth-token'] = '...'; } this._logger.debug(`${type}: sending a request with options`, JSON.stringify(optionsToLog)); return axios(_object_spread({ transitional: { clarifyTimeoutError: true } }, options)); } _wait(pause) { return _async_to_generator(function*() { yield new Promise((res)=>setTimeout(res, pause)); })(); } _handleRetry(endTime, retryAfter) { var _this = this; return _async_to_generator(function*() { if (endTime > Date.now() + retryAfter) { yield _this._wait(retryAfter); } else { throw new TimeoutError('Timed out waiting for the response'); } })(); } _handleError(err, type, retryCounter, endTime) { var _this = this; return _async_to_generator(function*() { const error = _this._convertError(err); if ([ 'ConflictError', 'InternalError', 'ApiError', 'TimeoutError' ].includes(error.name) && retryCounter < _this._retries) { const pause = Math.min(Math.pow(2, retryCounter) * _this._minRetryDelay, _this._maxRetryDelay); yield _this._wait(pause); return retryCounter + 1; } else if (error.name === 'TooManyRequestsError') { const retryTime = new Date(error.metadata.recommendedRetryTime).getTime(); if (retryTime < endTime) { _this._logger.debug(`${type} request has failed with TooManyRequestsError (HTTP status code 429). ` + `Will retry request in ${Math.ceil((retryTime - Date.now()) / 1000)} seconds`); yield _this._wait(retryTime - Date.now()); return retryCounter; } } throw error; })(); } // eslint-disable-next-line complexity _convertError(err) { var _err_config; const errorResponse = err.response || {}; const errorData = errorResponse.data || {}; const status = errorResponse.status || err.status; const url = err === null || err === void 0 ? void 0 : (_err_config = err.config) === null || _err_config === void 0 ? void 0 : _err_config.url; const errMsg = errorData.message || err.message; const errMsgDefault = errorData.message || err.code || err.message; switch(status){ case 400: return new ValidationError(errMsg, errorData.details || err.details, url); case 401: return new UnauthorizedError(errMsg, url); case 403: return new ForbiddenError(errMsg, url); case 404: return new NotFoundError(errMsg, url); case 409: return new ConflictError(errMsg, url); case 429: return new TooManyRequestsError(errMsg, errorData.metadata || err.metadata, url); case 500: return new InternalError(errMsg, url); default: return new ApiError(ApiError, errMsgDefault, status, url); } } /** * Constructs HttpClient class instance * @param {Number} timeout request timeout in seconds * @param {RetryOptions} [retryOpts] retry options */ constructor(timeout = 60, retryOpts = {}){ _define_property(this, "_timeout", void 0); _define_property(this, "_retries", void 0); _define_property(this, "_minRetryDelay", void 0); _define_property(this, "_maxRetryDelay", void 0); _define_property(this, "_longRunningRequestTimeout", void 0); _define_property(this, "_logger", void 0); const validator = new OptionsValidator(); this._timeout = timeout * 1000; this._retries = validator.validateNumber(retryOpts.retries, 5, 'retryOpts.retries'); this._minRetryDelay = validator.validateNonZero(retryOpts.minDelayInSeconds, 1, 'retryOpts.minDelayInSeconds') * 1000; this._maxRetryDelay = validator.validateNonZero(retryOpts.maxDelayInSeconds, 30, 'retryOpts.maxDelayInSeconds') * 1000; this._longRunningRequestTimeout = validator.validateNumber(retryOpts.longRunningRequestTimeoutInMinutes, 10, 'retryOpts.longRunningRequestTimeoutInMinutes') * 60 * 1000; this._logger = LoggerManager.getLogger('HttpClient'); } }; /** * HTTP client library based on axios */ export { HttpClient as default }; /** * HTTP client service mock for tests */ export class HttpClientMock extends HttpClient { _makeRequest(...args) { return this._requestFn(...args); } /** * Constructs HTTP client mock * @param {Function(options:Object):Promise} requestFn mocked request function * @param {Number} timeout request timeout in seconds * @param {RetryOptions} retryOpts retry options */ constructor(requestFn, timeout, retryOpts){ super(timeout, retryOpts); _define_property(this, "_requestFn", void 0); this._requestFn = requestFn; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmNvbnN0IGF4aW9zID0gcmVxdWlyZSgnYXhpb3MnKTtcblxuaW1wb3J0IHtcbiAgVW5hdXRob3JpemVkRXJyb3IsIEZvcmJpZGRlbkVycm9yLCBBcGlFcnJvciwgVmFsaWRhdGlvbkVycm9yLCBJbnRlcm5hbEVycm9yLCBcbiAgTm90Rm91bmRFcnJvciwgVG9vTWFueVJlcXVlc3RzRXJyb3IsIENvbmZsaWN0RXJyb3Jcbn0gZnJvbSAnLi9lcnJvckhhbmRsZXInO1xuaW1wb3J0IE9wdGlvbnNWYWxpZGF0b3IgZnJvbSAnLi9vcHRpb25zVmFsaWRhdG9yJztcbmltcG9ydCBUaW1lb3V0RXJyb3IgZnJvbSAnLi90aW1lb3V0RXJyb3InO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIsIHtMb2dnZXJ9IGZyb20gJy4uL2xvZ2dlcic7XG5pbXBvcnQgXyBmcm9tICdsb2Rhc2gnO1xuXG4vKipcbiAqIEhUVFAgY2xpZW50IGxpYnJhcnkgYmFzZWQgb24gYXhpb3NcbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgSHR0cENsaWVudCB7XG4gIFxuICBwcml2YXRlIF90aW1lb3V0OiBudW1iZXI7XG4gIHByaXZhdGUgX3JldHJpZXM6IGFueTtcbiAgcHJpdmF0ZSBfbWluUmV0cnlEZWxheTogbnVtYmVyO1xuICBwcml2YXRlIF9tYXhSZXRyeURlbGF5OiBudW1iZXI7XG4gIHByaXZhdGUgX2xvbmdSdW5uaW5nUmVxdWVzdFRpbWVvdXQ6IG51bWJlcjtcbiAgcHJpdmF0ZSBfbG9nZ2VyOiBMb2dnZXI7XG4gIFxuICAvKipcbiAgICogQ29uc3RydWN0cyBIdHRwQ2xpZW50IGNsYXNzIGluc3RhbmNlXG4gICAqIEBwYXJhbSB7TnVtYmVyfSB0aW1lb3V0IHJlcXVlc3QgdGltZW91dCBpbiBzZWNvbmRzXG4gICAqIEBwYXJhbSB7UmV0cnlPcHRpb25zfSBbcmV0cnlPcHRzXSByZXRyeSBvcHRpb25zXG4gICAqL1xuICBjb25zdHJ1Y3Rvcih0aW1lb3V0ID0gNjAsIHJldHJ5T3B0czogUmV0cnlPcHRpb25zID0ge30pIHtcbiAgICBjb25zdCB2YWxpZGF0b3IgPSBuZXcgT3B0aW9uc1ZhbGlkYXRvcigpO1xuXG4gICAgdGhpcy5fdGltZW91dCA9IHRpbWVvdXQgKiAxMDAwO1xuICAgIHRoaXMuX3JldHJpZXMgPSB2YWxpZGF0b3IudmFsaWRhdGVOdW1iZXIocmV0cnlPcHRzLnJldHJpZXMsIDUsICdyZXRyeU9wdHMucmV0cmllcycpO1xuICAgIHRoaXMuX21pblJldHJ5RGVsYXkgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKHJldHJ5T3B0cy5taW5EZWxheUluU2Vjb25kcywgMSxcbiAgICAgICdyZXRyeU9wdHMubWluRGVsYXlJblNlY29uZHMnKSAqIDEwMDA7XG4gICAgdGhpcy5fbWF4UmV0cnlEZWxheSA9IHZhbGlkYXRvci52YWxpZGF0ZU5vblplcm8ocmV0cnlPcHRzLm1heERlbGF5SW5TZWNvbmRzLCAzMCxcbiAgICAgICdyZXRyeU9wdHMubWF4RGVsYXlJblNlY29uZHMnKSAqIDEwMDA7XG4gICAgdGhpcy5fbG9uZ1J1bm5pbmdSZXF1ZXN0VGltZW91dCA9IHZhbGlkYXRvci52YWxpZGF0ZU51bWJlcihyZXRyeU9wdHMubG9uZ1J1bm5pbmdSZXF1ZXN0VGltZW91dEluTWludXRlcywgMTAsXG4gICAgICAncmV0cnlPcHRzLmxvbmdSdW5uaW5nUmVxdWVzdFRpbWVvdXRJbk1pbnV0ZXMnKSAqIDYwICogMTAwMDtcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignSHR0cENsaWVudCcpO1xuICB9XG5cbiAgLyoqXG4gICAqIFBlcmZvcm1zIGEgcmVxdWVzdC4gUmVzcG9uc2UgZXJyb3JzIGFyZSByZXR1cm5lZCBhcyBBcGlFcnJvciBvciBzdWJjbGFzc2VzLlxuICAgKiBAcGFyYW0ge09iamVjdH0gb3B0aW9ucyByZXF1ZXN0IG9wdGlvbnNcbiAgICogQHJldHVybnMge09iamVjdHxTdHJpbmd8YW55fSByZXF1ZXN0IHJlc3VsdFxuICAgKi9cbiAgYXN5bmMgcmVxdWVzdDxUID0gYW55PihcbiAgICBvcHRpb25zLCB0eXBlID0gJycsIHJldHJ5Q291bnRlciA9IDAsIGVuZFRpbWUgPSBEYXRlLm5vdygpICsgdGhpcy5fbWF4UmV0cnlEZWxheSAqIHRoaXMuX3JldHJpZXMsXG4gICAgaXNMb25nUnVubmluZyA9IGZhbHNlXG4gICk6IFByb21pc2U8VD4ge1xuICAgIG9wdGlvbnMudGltZW91dCA9IHRoaXMuX3RpbWVvdXQ7XG4gICAgXG4gICAgbGV0IHJldHJ5QWZ0ZXJTZWNvbmRzID0gMDtcbiAgICBvcHRpb25zLmNhbGxiYWNrID0gKGUsIHJlcykgPT4ge1xuICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3R5cGV9OiByZWNlaXZlZCByZXF1ZXN0IHJlc3BvbnNlIHdpdGggc3RhdHVzICR7cmVzPy5zdGF0dXN9YCk7XG4gICAgICBpZiAocmVzPy5zdGF0dXMgPT09IDIwMikge1xuICAgICAgICByZXRyeUFmdGVyU2Vjb25kcyA9IHJlcy5oZWFkZXJzWydyZXRyeS1hZnRlciddID8/IHJlcy5kYXRhPy5tZXRhZGF0YT8ucmVjb21tZW5kZWRSZXRyeVRpbWU7XG4gICAgICAgIHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHt0eXBlfTogcmV0cnkgYWZ0ZXIgdmFsdWUgaXMgJHtyZXRyeUFmdGVyU2Vjb25kc31gKTtcblxuICAgICAgICBpZiAoaXNOYU4ocmV0cnlBZnRlclNlY29uZHMpKSB7XG4gICAgICAgICAgcmV0cnlBZnRlclNlY29uZHMgPSBNYXRoLm1heCgobmV3IERhdGUocmV0cnlBZnRlclNlY29uZHMpLmdldFRpbWUoKSAtIERhdGUubm93KCkpIC8gMTAwMCwgMSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFpc0xvbmdSdW5uaW5nKSB7XG4gICAgICAgICAgZW5kVGltZSA9IERhdGUubm93KCkgKyB0aGlzLl9sb25nUnVubmluZ1JlcXVlc3RUaW1lb3V0O1xuICAgICAgICAgIGlzTG9uZ1J1bm5pbmcgPSB0cnVlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAgIGxldCBib2R5O1xuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGhpcy5fbWFrZVJlcXVlc3Qob3B0aW9ucywgdHlwZSk7XG4gICAgICBvcHRpb25zLmNhbGxiYWNrKG51bGwsIHJlc3BvbnNlKTtcbiAgICAgIGJvZHkgPSAocmVzcG9uc2UgJiYgcmVzcG9uc2UuZGF0YSkgfHwgdW5kZWZpbmVkO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgcmV0cnlDb3VudGVyID0gYXdhaXQgdGhpcy5faGFuZGxlRXJyb3IoZXJyLCB0eXBlLCByZXRyeUNvdW50ZXIsIGVuZFRpbWUpO1xuICAgICAgcmV0dXJuIHRoaXMucmVxdWVzdChvcHRpb25zLCB0eXBlLCByZXRyeUNvdW50ZXIsIGVuZFRpbWUpO1xuICAgIH1cblxuICAgIGlmIChyZXRyeUFmdGVyU2Vjb25kcykge1xuICAgICAgaWYgKGJvZHkgJiYgYm9keS5tZXNzYWdlKSB7XG4gICAgICAgIHRoaXMuX2xvZ2dlci5pbmZvKGBSZXRyeWluZyByZXF1ZXN0IGluICR7TWF0aC5mbG9vcihyZXRyeUFmdGVyU2Vjb25kcyl9IHNlY29uZHMgYmVjYXVzZSByZXF1ZXN0IGAgK1xuICAgICAgICAgICdyZXR1cm5lZCBtZXNzYWdlOicsIGJvZHkubWVzc2FnZSk7XG4gICAgICB9XG4gICAgICBhd2FpdCB0aGlzLl9oYW5kbGVSZXRyeShlbmRUaW1lLCByZXRyeUFmdGVyU2Vjb25kcyAqIDEwMDApO1xuICAgICAgYm9keSA9IGF3YWl0IHRoaXMucmVxdWVzdChvcHRpb25zLCB0eXBlLCByZXRyeUNvdW50ZXIsIGVuZFRpbWUsIGlzTG9uZ1J1bm5pbmcpO1xuICAgIH1cblxuICAgIHJldHVybiBib2R5O1xuICB9XG5cbiAgX21ha2VSZXF1ZXN0KG9wdGlvbnMsIHR5cGUpIHtcbiAgICBsZXQgb3B0aW9uc1RvTG9nID0gXy5jbG9uZURlZXAob3B0aW9ucyk7XG4gICAgaWYgKG9wdGlvbnNUb0xvZy5oZWFkZXJzPy5bJ2F1dGgtdG9rZW4nXSkge1xuICAgICAgb3B0aW9uc1RvTG9nLmhlYWRlcnNbJ2F1dGgtdG9rZW4nXSA9ICcuLi4nO1xuICAgIH1cbiAgICB0aGlzLl9sb2dnZXIuZGVidWcoYCR7dHlwZX06IHNlbmRpbmcgYSByZXF1ZXN0IHdpdGggb3B0aW9uc2AsIEpTT04uc3RyaW5naWZ5KG9wdGlvbnNUb0xvZykpO1xuICAgIHJldHVybiBheGlvcyh7XG4gICAgICB0cmFuc2l0aW9uYWw6IHtcbiAgICAgICAgY2xhcmlmeVRpbWVvdXRFcnJvcjogdHJ1ZVxuICAgICAgfSxcbiAgICAgIC4uLm9wdGlvbnNcbiAgICB9KTtcbiAgfVxuICBcbiAgYXN5bmMgX3dhaXQocGF1c2UpIHtcbiAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXMgPT4gc2V0VGltZW91dChyZXMsIHBhdXNlKSk7XG4gIH1cbiAgXG4gIGFzeW5jIF9oYW5kbGVSZXRyeShlbmRUaW1lLCByZXRyeUFmdGVyKSB7XG4gICAgaWYgKGVuZFRpbWUgPiBEYXRlLm5vdygpICsgcmV0cnlBZnRlcikge1xuICAgICAgYXdhaXQgdGhpcy5fd2FpdChyZXRyeUFmdGVyKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IFRpbWVvdXRFcnJvcignVGltZWQgb3V0IHdhaXRpbmcgZm9yIHRoZSByZXNwb25zZScpO1xuICAgIH1cbiAgfVxuICBcbiAgYXN5bmMgX2hhbmRsZUVycm9yKGVyciwgdHlwZSwgcmV0cnlDb3VudGVyLCBlbmRUaW1lKSB7XG4gICAgY29uc3QgZXJyb3IgPSB0aGlzLl9jb252ZXJ0RXJyb3IoZXJyKTtcblxuICAgIGlmIChcbiAgICAgIFsnQ29uZmxpY3RFcnJvcicsICdJbnRlcm5hbEVycm9yJywgJ0FwaUVycm9yJywgJ1RpbWVvdXRFcnJvciddLmluY2x1ZGVzKGVycm9yLm5hbWUpICYmIFxuICAgICAgcmV0cnlDb3VudGVyIDwgdGhpcy5fcmV0cmllc1xuICAgICkge1xuICAgICAgY29uc3QgcGF1c2UgPSBNYXRoLm1pbihNYXRoLnBvdygyLCByZXRyeUNvdW50ZXIpICogdGhpcy5fbWluUmV0cnlEZWxheSwgdGhpcy5fbWF4UmV0cnlEZWxheSk7XG4gICAgICBhd2FpdCB0aGlzLl93YWl0KHBhdXNlKTtcblxuICAgICAgcmV0dXJuIHJldHJ5Q291bnRlciArIDE7XG4gICAgfSBlbHNlIGlmIChlcnJvci5uYW1lID09PSAnVG9vTWFueVJlcXVlc3RzRXJyb3InKSB7XG4gICAgICBjb25zdCByZXRyeVRpbWUgPSBuZXcgRGF0ZSgoZXJyb3IgYXMgVG9vTWFueVJlcXVlc3RzRXJyb3IpLm1ldGFkYXRhLnJlY29tbWVuZGVkUmV0cnlUaW1lKS5nZXRUaW1lKCk7XG4gICAgICBpZiAocmV0cnlUaW1lIDwgZW5kVGltZSkge1xuICAgICAgICB0aGlzLl9sb2dnZXIuZGVidWcoYCR7dHlwZX0gcmVxdWVzdCBoYXMgZmFpbGVkIHdpdGggVG9vTWFueVJlcXVlc3RzRXJyb3IgKEhUVFAgc3RhdHVzIGNvZGUgNDI5KS4gYCArXG4gICAgICAgICAgYFdpbGwgcmV0cnkgcmVxdWVzdCBpbiAke01hdGguY2VpbCgocmV0cnlUaW1lIC0gRGF0ZS5ub3coKSkgLyAxMDAwKX0gc2Vjb25kc2ApO1xuICAgICAgICBhd2FpdCB0aGlzLl93YWl0KHJldHJ5VGltZSAtIERhdGUubm93KCkpO1xuXG4gICAgICAgIHJldHVybiByZXRyeUNvdW50ZXI7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhyb3cgZXJyb3I7XG4gIH1cblxuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY29tcGxleGl0eVxuICBfY29udmVydEVycm9yKGVycikge1xuICAgIGNvbnN0IGVycm9yUmVzcG9uc2UgPSBlcnIucmVzcG9uc2UgfHwge307XG4gICAgY29uc3QgZXJyb3JEYXRhID0gZXJyb3JSZXNwb25zZS5kYXRhIHx8IHt9O1xuICAgIGNvbnN0IHN0YXR1cyA9IGVycm9yUmVzcG9uc2Uuc3RhdHVzIHx8IGVyci5zdGF0dXM7XG4gICAgY29uc3QgdXJsID0gZXJyPy5jb25maWc/LnVybDtcblxuICAgIGNvbnN0IGVyck1zZyA9IGVycm9yRGF0YS5tZXNzYWdlIHx8IGVyci5tZXNzYWdlO1xuICAgIGNvbnN0IGVyck1zZ0RlZmF1bHQgPSBlcnJvckRhdGEubWVzc2FnZSB8fCBlcnIuY29kZSB8fCBlcnIubWVzc2FnZTtcblxuICAgIHN3aXRjaCAoc3RhdHVzKSB7XG4gICAgY2FzZSA0MDA6XG4gICAgICByZXR1cm4gbmV3IFZhbGlkYXRpb25FcnJvcihlcnJNc2csIGVycm9yRGF0YS5kZXRhaWxzIHx8IGVyci5kZXRhaWxzLCB1cmwpO1xuICAgIGNhc2UgNDAxOlxuICAgICAgcmV0dXJuIG5ldyBVbmF1dGhvcml6ZWRFcnJvcihlcnJNc2csIHVybCk7XG4gICAgY2FzZSA0MDM6XG4gICAgICByZXR1cm4gbmV3IEZvcmJpZGRlbkVycm9yKGVyck1zZywgdXJsKTtcbiAgICBjYXNlIDQwNDpcbiAgICAgIHJldHVybiBuZXcgTm90Rm91bmRFcnJvcihlcnJNc2csIHVybCk7XG4gICAgY2FzZSA0MDk6XG4gICAgICByZXR1cm4gbmV3IENvbmZsaWN0RXJyb3IoZXJyTXNnLCB1cmwpO1xuICAgIGNhc2UgNDI5OlxuICAgICAgcmV0dXJuIG5ldyBUb29NYW55UmVxdWVzdHNFcnJvcihlcnJNc2csIGVycm9yRGF0YS5tZXRhZGF0YSB8fCBlcnIubWV0YWRhdGEsIHVybCk7XG4gICAgY2FzZSA1MDA6XG4gICAgICByZXR1cm4gbmV3IEludGVybmFsRXJyb3IoZXJyTXNnLCB1cmwpO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gbmV3IEFwaUVycm9yKEFwaUVycm9yLCBlcnJNc2dEZWZhdWx0LCBzdGF0dXMsIHVybCk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogSFRUUCBjbGllbnQgc2VydmljZSBtb2NrIGZvciB0ZXN0c1xuICovXG5leHBvcnQgY2xhc3MgSHR0cENsaWVudE1vY2sgZXh0ZW5kcyBIdHRwQ2xpZW50IHtcbiAgX3JlcXVlc3RGbjogYW55O1xuICAvKipcbiAgICogQ29uc3RydWN0cyBIVFRQIGNsaWVudCBtb2NrXG4gICAqIEBwYXJhbSB7RnVuY3Rpb24ob3B0aW9uczpPYmplY3QpOlByb21pc2V9IHJlcXVlc3RGbiBtb2NrZWQgcmVxdWVzdCBmdW5jdGlvblxuICAgKiBAcGFyYW0ge051bWJlcn0gdGltZW91dCByZXF1ZXN0IHRpbWVvdXQgaW4gc2Vjb25kc1xuICAgKiBAcGFyYW0ge1JldHJ5T3B0aW9uc30gcmV0cnlPcHRzIHJldHJ5IG9wdGlvbnNcbiAgICovXG4gIGNvbnN0cnVjdG9yKHJlcXVlc3RGbiwgdGltZW91dCwgcmV0cnlPcHRzKSB7XG4gICAgc3VwZXIodGltZW91dCwgcmV0cnlPcHRzKTtcbiAgICB0aGlzLl9yZXF1ZXN0Rm4gPSByZXF1ZXN0Rm47XG4gIH1cblxuICBfbWFrZVJlcXVlc3QoLi4uYXJncykge1xuICAgIHJldHVybiB0aGlzLl9yZXF1ZXN0Rm4oLi4uYXJncyk7XG4gIH1cblxufVxuXG4vKipcbiAqIHJldHJ5IG9wdGlvbnNcbiAqL1xuZXhwb3J0IGRlY2xhcmUgdHlwZSBSZXRyeU9wdGlvbnMgPSB7XG5cbiAgLyoqXG4gICAqIHRoZSBudW1iZXIgb2YgYXR0ZW1wdHMgdG8gcmV0cnkgZmFpbGVkIHJlcXVlc3QsIGRlZmF1bHQgNVxuICAgKi9cbiAgcmV0cmllcz86IG51bWJlcixcblxuICAvKipcbiAgICogbWluaW11bSBkZWxheSBpbiBzZWNvbmRzIGJlZm9yZSByZXRyeWluZywgZGVmYXVsdCAxXG4gICAqL1xuICBtaW5EZWxheUluU2Vjb25kcz86IG51bWJlcixcbiAgXG4gIC8qKlxuICAgKiBtYXhpbXVtIGRlbGF5IGluIHNlY29uZHMgYmVmb3JlIHJldHJ5aW5nLCBkZWZhdWx0IDMwXG4gICAqL1xuICBtYXhEZWxheUluU2Vjb25kcz86IG51bWJlcixcblxuICAvKipcbiAgICogdGltZW91dCBpbiBtaW51dGVzIGZvciBsb25nIHJ1bm5pbmcgcmVxdWVzdHMsIGRlZmF1bHQgMTBcbiAgICovXG4gIGxvbmdSdW5uaW5nUmVxdWVzdFRpbWVvdXRJbk1pbnV0ZXM/OiBudW1iZXIsXG5cbiAgLyoqXG4gICAqIHRpbWUgdG8gZGlzYWJsZSBuZXcgc3Vic2NyaXB0aW9ucyBmb3JcbiAgICovXG4gIHN1YnNjcmliZUNvb2xkb3duSW5TZWNvbmRzPzogbnVtYmVyXG59XG4iXSwibmFtZXMiOlsiYXhpb3MiLCJyZXF1aXJlIiwiVW5hdXRob3JpemVkRXJyb3IiLCJGb3JiaWRkZW5FcnJvciIsIkFwaUVycm9yIiwiVmFsaWRhdGlvbkVycm9yIiwiSW50ZXJuYWxFcnJvciIsIk5vdEZvdW5kRXJyb3IiLCJUb29NYW55UmVxdWVzdHNFcnJvciIsIkNvbmZsaWN0RXJyb3IiLCJPcHRpb25zVmFsaWRhdG9yIiwiVGltZW91dEVycm9yIiwiTG9nZ2VyTWFuYWdlciIsIl8iLCJIdHRwQ2xpZW50IiwicmVxdWVzdCIsIm9wdGlvbnMiLCJ0eXBlIiwicmV0cnlDb3VudGVyIiwiZW5kVGltZSIsIkRhdGUiLCJub3ciLCJfbWF4UmV0cnlEZWxheSIsIl9yZXRyaWVzIiwiaXNMb25nUnVubmluZyIsInRpbWVvdXQiLCJfdGltZW91dCIsInJldHJ5QWZ0ZXJTZWNvbmRzIiwiY2FsbGJhY2siLCJlIiwicmVzIiwiX2xvZ2dlciIsImRlYnVnIiwic3RhdHVzIiwiaGVhZGVycyIsImRhdGEiLCJtZXRhZGF0YSIsInJlY29tbWVuZGVkUmV0cnlUaW1lIiwiaXNOYU4iLCJNYXRoIiwibWF4IiwiZ2V0VGltZSIsIl9sb25nUnVubmluZ1JlcXVlc3RUaW1lb3V0IiwiYm9keSIsInJlc3BvbnNlIiwiX21ha2VSZXF1ZXN0IiwidW5kZWZpbmVkIiwiZXJyIiwiX2hhbmRsZUVycm9yIiwibWVzc2FnZSIsImluZm8iLCJmbG9vciIsIl9oYW5kbGVSZXRyeSIsIm9wdGlvbnNUb0xvZyIsImNsb25lRGVlcCIsIkpTT04iLCJzdHJpbmdpZnkiLCJ0cmFuc2l0aW9uYWwiLCJjbGFyaWZ5VGltZW91dEVycm9yIiwiX3dhaXQiLCJwYXVzZSIsIlByb21pc2UiLCJzZXRUaW1lb3V0IiwicmV0cnlBZnRlciIsImVycm9yIiwiX2NvbnZlcnRFcnJvciIsImluY2x1ZGVzIiwibmFtZSIsIm1pbiIsInBvdyIsIl9taW5SZXRyeURlbGF5IiwicmV0cnlUaW1lIiwiY2VpbCIsImVycm9yUmVzcG9uc2UiLCJlcnJvckRhdGEiLCJ1cmwiLCJjb25maWciLCJlcnJNc2ciLCJlcnJNc2dEZWZhdWx0IiwiY29kZSIsImRldGFpbHMiLCJjb25zdHJ1Y3RvciIsInJldHJ5T3B0cyIsInZhbGlkYXRvciIsInZhbGlkYXRlTnVtYmVyIiwicmV0cmllcyIsInZhbGlkYXRlTm9uWmVybyIsIm1pbkRlbGF5SW5TZWNvbmRzIiwibWF4RGVsYXlJblNlY29uZHMiLCJsb25nUnVubmluZ1JlcXVlc3RUaW1lb3V0SW5NaW51dGVzIiwiZ2V0TG9nZ2VyIiwiSHR0cENsaWVudE1vY2siLCJhcmdzIiwiX3JlcXVlc3RGbiIsInJlcXVlc3RGbiJdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFQSxNQUFNQSxRQUFRQyxRQUFRO0FBRXRCLFNBQ0VDLGlCQUFpQixFQUFFQyxjQUFjLEVBQUVDLFFBQVEsRUFBRUMsZUFBZSxFQUFFQyxhQUFhLEVBQzNFQyxhQUFhLEVBQUVDLG9CQUFvQixFQUFFQyxhQUFhLFFBQzdDLGlCQUFpQjtBQUN4QixPQUFPQyxzQkFBc0IscUJBQXFCO0FBQ2xELE9BQU9DLGtCQUFrQixpQkFBaUI7QUFDMUMsT0FBT0MsbUJBQTZCLFlBQVk7QUFDaEQsT0FBT0MsT0FBTyxTQUFTO0FBS1IsSUFBQSxBQUFNQyxhQUFOLE1BQU1BO0lBNEJuQjs7OztHQUlDLEdBQ0QsQUFBTUMsUUFDSkMsT0FBTyxFQUFFQyxPQUFPLEVBQUUsRUFBRUMsZUFBZSxDQUFDLEVBQUVDLFVBQVVDLEtB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