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
JavaScript
;
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/LnVybDtcblxuICAgIGNvbnN0IGVyck1zZyA9IGVycm9yRGF0YS5tZXNzYWdlIHx8IGVyci5tZXNzYWdlO1xuICAgIGNvbnN0IGVyck1zZ0RlZmF1bHQgPSBlcnJvckRhdGEubWVzc2FnZSB8fCBlcnIuY29kZSB8fCBlcnIubWVzc2FnZTtcblxuICAgIHN3aXRjaCAoc3RhdHVzKSB7XG4gICAgY2FzZSA0MDA6XG4gICAgICByZXR1cm4gbmV3IFZhbGlkYXRpb25FcnJvcihlcnJNc2csIGVycm9yRGF0YS5kZXRhaWxzIHx8IGVyci5kZXRhaWxzLCB1cmwpO1xuICAgIGNhc2UgNDAxOlxuICAgICAgcmV0dXJuIG5ldyBVbmF1dGhvcml6ZWRFcnJvcihlcnJNc2csIHVybCk7XG4gICAgY2FzZSA0MDM6XG4gICAgICByZXR1cm4gbmV3IEZvcmJpZGRlbkVycm9yKGVyck1zZywgdXJsKTtcbiAgICBjYXNlIDQwNDpcbiAgICAgIHJldHVybiBuZXcgTm90Rm91bmRFcnJvcihlcnJNc2csIHVybCk7XG4gICAgY2FzZSA0MDk6XG4gICAgICByZXR1cm4gbmV3IENvbmZsaWN0RXJyb3IoZXJyTXNnLCB1cmwpO1xuICAgIGNhc2UgNDI5OlxuICAgICAgcmV0dXJuIG5ldyBUb29NYW55UmVxdWVzdHNFcnJvcihlcnJNc2csIGVycm9yRGF0YS5tZXRhZGF0YSB8fCBlcnIubWV0YWRhdGEsIHVybCk7XG4gICAgY2FzZSA1MDA6XG4gICAgICByZXR1cm4gbmV3IEludGVybmFsRXJyb3IoZXJyTXNnLCB1cmwpO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gbmV3IEFwaUVycm9yKEFwaUVycm9yLCBlcnJNc2dEZWZhdWx0LCBzdGF0dXMsIHVybCk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogSFRUUCBjbGllbnQgc2VydmljZSBtb2NrIGZvciB0ZXN0c1xuICovXG5leHBvcnQgY2xhc3MgSHR0cENsaWVudE1vY2sgZXh0ZW5kcyBIdHRwQ2xpZW50IHtcbiAgX3JlcXVlc3RGbjogYW55O1xuICAvKipcbiAgICogQ29uc3RydWN0cyBIVFRQIGNsaWVudCBtb2NrXG4gICAqIEBwYXJhbSB7RnVuY3Rpb24ob3B0aW9uczpPYmplY3QpOlByb21pc2V9IHJlcXVlc3RGbiBtb2NrZWQgcmVxdWVzdCBmdW5jdGlvblxuICAgKiBAcGFyYW0ge051bWJlcn0gdGltZW91dCByZXF1ZXN0IHRpbWVvdXQgaW4gc2Vjb25kc1xuICAgKiBAcGFyYW0ge1JldHJ5T3B0aW9uc30gcmV0cnlPcHRzIHJldHJ5IG9wdGlvbnNcbiAgICovXG4gIGNvbnN0cnVjdG9yKHJlcXVlc3RGbiwgdGltZW91dD8sIHJldHJ5T3B0cz8pIHtcbiAgICBzdXBlcih0aW1lb3V0LCByZXRyeU9wdHMpO1xuICAgIHRoaXMuX3JlcXVlc3RGbiA9IHJlcXVlc3RGbjtcbiAgfVxuXG4gIF9tYWtlUmVxdWVzdCguLi5hcmdzKSB7XG4gICAgcmV0dXJuIHRoaXMuX3JlcXVlc3RGbiguLi5hcmdzKTtcbiAgfVxuXG59XG5cbi8qKlxuICogcmV0cnkgb3B0aW9uc1xuICovXG5leHBvcnQgZGVjbGFyZSB0eXBlIFJldHJ5T3B0aW9ucyA9IHtcblxuICAvKipcbiAgICogdGhlIG51bWJlciBvZiBhdHRlbXB0cyB0byByZXRyeSBmYWlsZWQgcmVxdWVzdCwgZGVmYXVsdCA1XG4gICAqL1xuICByZXRyaWVzPzogbnVtYmVyLFxuXG4gIC8qKlxuICAgKiBtaW5pbXVtIGRlbGF5IGluIHNlY29uZHMgYmVmb3JlIHJldHJ5aW5nLCBkZWZhdWx0IDFcbiAgICovXG4gIG1pbkRlbGF5SW5TZWNvbmRzPzogbnVtYmVyLFxuICBcbiAgLyoqXG4gICAqIG1heGltdW0gZGVsYXkgaW4gc2Vjb25kcyBiZWZvcmUgcmV0cnlpbmcsIGRlZmF1bHQgMzBcbiAgICovXG4gIG1heERlbGF5SW5TZWNvbmRzPzogbnVtYmVyLFxuXG4gIC8qKlxuICAgKiB0aW1lb3V0IGluIG1pbnV0ZXMgZm9yIGxvbmcgcnVubmluZyByZXF1ZXN0cywgZGVmYXVsdCAxMFxuICAgKi9cbiAgbG9uZ1J1bm5pbmdSZXF1ZXN0VGltZW91dEluTWludXRlcz86IG51bWJlcixcblxuICAvKipcbiAgICogdGltZSB0byBkaXNhYmxlIG5ldyBzdWJzY3JpcHRpb25zIGZvclxuICAgKi9cbiAgc3Vic2NyaWJlQ29vbGRvd25JblNlY29uZHM/OiBudW1iZXJcbn1cbiJdLCJuYW1lcyI6WyJheGlvcyIsInJlcXVpcmUiLCJVbmF1dGhvcml6ZWRFcnJvciIsIkZvcmJpZGRlbkVycm9yIiwiQXBpRXJyb3IiLCJWYWxpZGF0aW9uRXJyb3IiLCJJbnRlcm5hbEVycm9yIiwiTm90Rm91bmRFcnJvciIsIlRvb01hbnlSZXF1ZXN0c0Vycm9yIiwiQ29uZmxpY3RFcnJvciIsIk9wdGlvbnNWYWxpZGF0b3IiLCJUaW1lb3V0RXJyb3IiLCJMb2dnZXJNYW5hZ2VyIiwiXyIsIkh0dHBDbGllbnQiLCJyZXF1ZXN0Iiwib3B0aW9ucyIsInR5cGUiLCJyZXRyeUNvdW50ZXIiLCJlbmRUaW1lIiwiRGF0ZSIsIm5vdyIsIl9tYXhSZXRyeURlbGF5IiwiX3JldHJpZXMiLCJpc0xvbmdSdW5uaW5nIiwidGltZW91dCIsIl90aW1lb3V0IiwicmV0cnlBZnRlclNlY29uZHMiLCJjYWxsYmFjayIsImUiLCJyZXMiLCJfbG9nZ2VyIiwiZGVidWciLCJzdGF0dXMiLCJoZWFkZXJzIiwiZGF0YSIsIm1ldGFkYXRhIiwicmVjb21tZW5kZWRSZXRyeVRpbWUiLCJpc05hTiIsIk1hdGgiLCJtYXgiLCJnZXRUaW1lIiwiX2xvbmdSdW5uaW5nUmVxdWVzdFRpbWVvdXQiLCJib2R5IiwicmVzcG9uc2UiLCJfbWFrZVJlcXVlc3QiLCJ1bmRlZmluZWQiLCJlcnIiLCJfaGFuZGxlRXJyb3IiLCJtZXNzYWdlIiwiaW5mbyIsImZsb29yIiwiX2hhbmRsZVJldHJ5Iiwib3B0aW9uc1RvTG9nIiwiY2xvbmVEZWVwIiwiSlNPTiIsInN0cmluZ2lmeSIsInRyYW5zaXRpb25hbCIsImNsYXJpZnlUaW1lb3V0RXJyb3IiLCJfd2FpdCIsInBhdXNlIiwiUHJvbWlzZSIsInNldFRpbWVvdXQiLCJyZXRyeUFmdGVyIiwiZXJyb3IiLCJfY29udmVydEVycm9yIiwiaW5jbHVkZXMiLCJuYW1lIiwibWluIiwicG93IiwiX21pblJldHJ5RGVsYXkiLCJyZXRyeVRpbWUiLCJjZWlsIiwiZXJyb3JSZXNwb25zZSIsImVycm9yRGF0YSIsInVybCIsImNvbmZpZyIsImVyck1zZyIsImVyck1zZ0RlZmF1bHQiLCJjb2RlIiwiZGV0YWlscyIsImNvbnN0cnVjdG9yIiwicmV0cnlPcHRzIiwidmFsaWRhdG9yIiwidmFsaWRhdGVOdW1iZXIiLCJyZXRyaWVzIiwidmFsaWRhdGVOb25aZXJvIiwibWluRGVsYXlJblNlY29uZHMiLCJtYXhEZWxheUluU2Vjb25kcyIsImxvbmdSdW5uaW5nUmVxdWVzdFRpbWVvdXRJbk1pbnV0ZXMiLCJnZXRMb2dnZXIiLCJIdHRwQ2xpZW50TW9jayIsImFyZ3MiLCJfcmVxdWVzdEZuIiwicmVxdWVzdEZuIl0sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUVBLE1BQU1BLFFBQVFDLFFBQVE7QUFFdEIsU0FDRUMsaUJBQWlCLEVBQUVDLGNBQWMsRUFBRUMsUUFBUSxFQUFFQyxlQUFlLEVBQUVDLGFBQWEsRUFDM0VDLGFBQWEsRUFBRUMsb0JBQW9CLEVBQUVDLGFBQWEsUUFDN0MsaUJBQWlCO0FBQ3hCLE9BQU9DLHNCQUFzQixxQkFBcUI7QUFDbEQsT0FBT0Msa0JBQWtCLGlCQUFpQjtBQUMxQyxPQUFPQyxtQkFBNkIsWUFBWTtBQUNoRCxPQUFPQyxPQUFPLFNBQVM7QUFLUixJQUFBLEFBQU1DLGFBQU4sTUFBTUE7SUE0Qm5COzs7O0dBSUMsR0FDRCxBQUFNQyxRQUNKQyxPQUFPLEVBQUVDLE9BQU8sRUFBRSxFQUFFQyxlQUFlLENBQUMsRUFBRUMsVUFBVUMs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