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)

543 lines (542 loc) 74.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return MetatraderAccount; } }); const _timeoutError = /*#__PURE__*/ _interop_require_default(require("../clients/timeoutError")); const _rpcMetaApiConnectionInstance = /*#__PURE__*/ _interop_require_default(require("./rpcMetaApiConnectionInstance")); const _streamingMetaApiConnectionInstance = /*#__PURE__*/ _interop_require_default(require("./streamingMetaApiConnectionInstance")); const _index = /*#__PURE__*/ _interop_require_default(require("./historyDatabase/index")); const _expertAdvisor = /*#__PURE__*/ _interop_require_default(require("./expertAdvisor")); const _errorHandler = require("../clients/errorHandler"); const _metatraderAccountReplica = /*#__PURE__*/ _interop_require_default(require("./metatraderAccountReplica")); const _metatraderAccountclient = require("../clients/metaApi/metatraderAccount.client"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let MetatraderAccount = class MetatraderAccount { /** * Returns unique account id * @return {string} unique account id */ get id() { return this._data._id; } /** * Returns current account state. One of CREATED, DEPLOYING, DEPLOYED, DEPLOY_FAILED, UNDEPLOYING, * UNDEPLOYED, UNDEPLOY_FAILED, DELETING, DELETE_FAILED, REDEPLOY_FAILED, DRAFT * @return {State} current account state */ get state() { return this._data.state; } /** * Returns MetaTrader magic to place trades using * @return {number} MetaTrader magic to place trades using */ get magic() { return this._data.magic; } /** * Returns terminal & broker connection status, one of CONNECTED, DISCONNECTED, DISCONNECTED_FROM_BROKER * @return {ConnectionStatus} terminal & broker connection status */ get connectionStatus() { return this._data.connectionStatus; } /** * Returns quote streaming interval in seconds * @return {number} quote streaming interval in seconds */ get quoteStreamingIntervalInSeconds() { return this._data.quoteStreamingIntervalInSeconds; } /** * Returns symbol provided by broker * @return {string} any symbol provided by broker */ get symbol() { return this._data.symbol; } /** * Returns reliability value. Possible values are regular and high * @return {Reliability} account reliability value */ get reliability() { return this._data.reliability; } /** * Returns user-defined account tags * @return {Array<string>} user-defined account tags */ get tags() { return this._data.tags; } /** * Returns extra information which can be stored together with your account * @return {Object} extra information which can be stored together with your account */ get metadata() { return this._data.metadata; } /** * Returns number of resource slots to allocate to account. Allocating extra resource slots * results in better account performance under load which is useful for some applications. E.g. if you have many * accounts copying the same strategy via CopyFactory API, then you can increase resourceSlots to get a lower trade * copying latency. Please note that allocating extra resource slots is a paid option. Please note that high * reliability accounts use redundant infrastructure, so that each resource slot for a high reliability account * is billed as 2 standard resource slots. * @return {number} number of resource slots to allocate to account */ get resourceSlots() { return this._data.resourceSlots; } /** * Returns the number of CopyFactory 2 resource slots to allocate to account. * Allocating extra resource slots results in lower trade copying latency. Please note that allocating extra resource * slots is a paid option. Please also note that CopyFactory 2 uses redundant infrastructure so that * each CopyFactory resource slot is billed as 2 standard resource slots. You will be billed for CopyFactory 2 * resource slots only if you have added your account to CopyFactory 2 by specifying copyFactoryRoles field. * @return {number} number of CopyFactory 2 resource slots to allocate to account */ get copyFactoryResourceSlots() { return this._data.copyFactoryResourceSlots; } /** * Returns account region * @return {string} account region value */ get region() { return this._data.region; } /** * Returns the time account was created at, in ISO format * @returns {string} the time account was created at, in ISO format */ get createdAt() { return new Date(this._data.createdAt); } /** * Returns human-readable account name * @return {string} human-readable account name */ get name() { return this._data.name; } /** * Returns flag indicating if trades should be placed as manual trades on this account * @return {boolean} flag indicating if trades should be placed as manual trades on this account */ get manualTrades() { return this._data.manualTrades; } /** * Returns default trade slippage in points * @return {number} default trade slippage in points */ get slippage() { return this._data.slippage; } /** * Returns id of the account's provisioning profile * @return {string} id of the account's provisioning profile */ get provisioningProfileId() { return this._data.provisioningProfileId; } /** * Returns MetaTrader account login * @return {string} MetaTrader account number */ get login() { return this._data.login; } /** * Returns MetaTrader server name to connect to * @return {string} MetaTrader server name to connect to */ get server() { return this._data.server; } /** * Returns account type. Possible values are cloud-g1, cloud-g2 * @return {Type} account type */ get type() { return this._data.type; } /** * Returns MT version. Possible values are 4 and 5 * @return {Version} MT version */ get version() { return this._data.version; } /** * Returns hash-code of the account * @return {number} hash-code of the account */ get hash() { return this._data.hash; } /** * Returns 3-character ISO currency code of the account base currency. The setting is to be used * for copy trading accounts which use national currencies only, such as some Brazilian brokers. You should not alter * this setting unless you understand what you are doing. * @return {string} 3-character ISO currency code of the account base currency */ get baseCurrency() { return this._data.baseCurrency; } /** * Returns account roles for CopyFactory2 application. Possible values are `PROVIDER` and `SUBSCRIBER` * @return {Array<CopyFactoryRoles>} account roles for CopyFactory2 application */ get copyFactoryRoles() { return this._data.copyFactoryRoles; } /** * Returns flag indicating that risk management API is enabled on account * @return {boolean} flag indicating that risk management API is enabled on account */ get riskManagementApiEnabled() { return this._data.riskManagementApiEnabled; } /** * Returns flag indicating that MetaStats API is enabled on account * @return {boolean} flag indicating that MetaStats API is enabled on account */ get metastatsApiEnabled() { return this._data.metastatsApiEnabled; } /** * Returns configured dedicated IP protocol to connect to the trading account terminal * @return {DedicatedIp} */ get allocateDedicatedIp() { return this._data.allocateDedicatedIp; } /** * Returns active account connections * @return {Array<AccountConnection>} active account connections */ get connections() { return this._data.connections; } /** * Returns flag indicating that account is primary * @return {boolean} flag indicating that account is primary */ get primaryReplica() { return this._data.primaryReplica; } /** * Returns user id * @return {string} user id */ get userId() { return this._data.userId; } /** * Returns primary account id * @return {string} primary account id */ get primaryAccountId() { return this._data.primaryAccountId; } /** * Returns account replicas from DTO * @return {MetatraderAccountReplica[]} account replicas from DTO */ get accountReplicas() { return this._data.accountReplicas; } /** * Returns account replica instances * @return {MetatraderAccountReplica[]} account replica instances */ get replicas() { return this._replicas; } /** * Returns a dictionary with account's available regions and replicas * @returns {{[region: string]: string}} */ get accountRegions() { const regions = { [this.region]: this.id }; this.replicas.forEach((replica)=>regions[replica.region] = replica.id); return regions; } /** * Reloads MetaTrader account from API * @return {Promise} promise resolving when MetaTrader account is updated */ async reload() { this._data = await this._metatraderAccountClient.getAccount(this.id); const updatedReplicaData = this._data.accountReplicas || []; const regions = updatedReplicaData.map((replica)=>replica.region); const createdReplicaRegions = this._replicas.map((replica)=>replica.region); this._replicas = this._replicas.filter((replica)=>regions.includes(replica.region)); this._replicas.forEach((replica)=>{ const updatedData = updatedReplicaData.find((replicaData)=>replicaData.region === replica.region); replica.updateData(updatedData); }); updatedReplicaData.forEach((replica)=>{ if (!createdReplicaRegions.includes(replica.region)) { this._replicas.push(new _metatraderAccountReplica.default(replica, this, this._metatraderAccountClient)); } }); } /** * Removes a trading account and stops the API server serving the account. * The account state such as downloaded market data history will be removed as well when you remove the account. * @return {Promise} promise resolving when account is scheduled for deletion */ async remove() { this._connectionRegistry.remove(this.id); await this._metatraderAccountClient.deleteAccount(this.id); const fileManager = _index.default.getInstance(); await fileManager.clear(this.id, this._application); if (this.type !== "self-hosted") { try { await this.reload(); } catch (err) { if (err.name !== "NotFoundError") { throw err; } } } } /** * Starts API server and trading terminal for trading account. * This request will be ignored if the account is already deployed. * @returns {Promise} promise resolving when account is scheduled for deployment */ async deploy() { await this._metatraderAccountClient.deployAccount(this.id); await this.reload(); } /** * Stops API server and trading terminal for trading account. * This request will be ignored if trading account is already undeployed * @returns {Promise} promise resolving when account is scheduled for undeployment */ async undeploy() { this._connectionRegistry.remove(this.id); await this._metatraderAccountClient.undeployAccount(this.id); await this.reload(); } /** * Redeploys trading account. This is equivalent to undeploy immediately followed by deploy * @returns {Promise} promise resolving when account is scheduled for redeployment */ async redeploy() { await this._metatraderAccountClient.redeployAccount(this.id); await this.reload(); } /** * Increases trading account reliability in order to increase the expected account uptime. * The account will be temporary stopped to perform this action. * Note that increasing reliability is a paid option * @returns {Promise} promise resolving when account reliability is increased */ async increaseReliability() { await this._metatraderAccountClient.increaseReliability(this.id); await this.reload(); } /** * Enables risk management API for trading account. * The account will be temporary stopped to perform this action. * Note that risk management API is a paid option * @returns {Promise} promise resolving when account risk management is enabled */ async enableRiskManagementApi() { await this._metatraderAccountClient.enableRiskManagementApi(this.id); await this.reload(); } /** * Enables MetaStats API for trading account. * The account will be temporary stopped to perform this action. * Note that this is a paid option * @returns {Promise} promise resolving when account MetaStats API is enabled */ async enableMetaStatsApi() { await this._metatraderAccountClient.enableMetaStatsApi(this.id); await this.reload(); } /** * Waits until API server has finished deployment and account reached the DEPLOYED state * @param {number} timeoutInSeconds wait timeout in seconds, default is 5m * @param {number} intervalInMilliseconds interval between account reloads while waiting for a change, default is 1s * @return {Promise} promise which resolves when account is deployed * @throws {TimeoutError} if account have not reached the DEPLOYED state within timeout allowed */ async waitDeployed(timeoutInSeconds = 300, intervalInMilliseconds = 1000) { let startTime = Date.now(); await this.reload(); while(this.state !== "DEPLOYED" && startTime + timeoutInSeconds * 1000 > Date.now()){ await this._delay(intervalInMilliseconds); await this.reload(); } if (this.state !== "DEPLOYED") { throw new _timeoutError.default("Timed out waiting for account " + this.id + " to be deployed"); } } /** * Waits until API server has finished undeployment and account reached the UNDEPLOYED state * @param {number} timeoutInSeconds wait timeout in seconds, default is 5m * @param {number} intervalInMilliseconds interval between account reloads while waiting for a change, default is 1s * @return {Promise} promise which resolves when account is deployed * @throws {TimeoutError} if account have not reached the UNDEPLOYED state within timeout allowed */ async waitUndeployed(timeoutInSeconds = 300, intervalInMilliseconds = 1000) { let startTime = Date.now(); await this.reload(); while(this.state !== "UNDEPLOYED" && startTime + timeoutInSeconds * 1000 > Date.now()){ await this._delay(intervalInMilliseconds); await this.reload(); } if (this.state !== "UNDEPLOYED") { throw new _timeoutError.default("Timed out waiting for account " + this.id + " to be undeployed"); } } /** * Waits until account has been deleted * @param {number} timeoutInSeconds wait timeout in seconds, default is 5m * @param {number} intervalInMilliseconds interval between account reloads while waiting for a change, default is 1s * @return {Promise} promise which resolves when account is deleted * @throws {TimeoutError} if account was not deleted within timeout allowed */ async waitRemoved(timeoutInSeconds = 300, intervalInMilliseconds = 1000) { let startTime = Date.now(); try { await this.reload(); while(startTime + timeoutInSeconds * 1000 > Date.now()){ await this._delay(intervalInMilliseconds); await this.reload(); } throw new _timeoutError.default("Timed out waiting for account " + this.id + " to be deleted"); } catch (err) { if (err.name === "NotFoundError") { return; } else { throw err; } } } /** * Waits until API server has connected to the terminal and terminal has connected to the broker * @param {number} timeoutInSeconds wait timeout in seconds, default is 5m * @param {number} intervalInMilliseconds interval between account reloads while waiting for a change, default is 1s * @return {Promise} promise which resolves when API server is connected to the broker * @throws {TimeoutError} if account have not connected to the broker within timeout allowed */ async waitConnected(timeoutInSeconds = 300, intervalInMilliseconds = 1000) { const checkConnected = ()=>{ return [ this.connectionStatus ].concat(this.replicas.map((replica)=>replica.connectionStatus)).includes("CONNECTED"); }; let startTime = Date.now(); await this.reload(); while(!checkConnected() && startTime + timeoutInSeconds * 1000 > Date.now()){ await this._delay(intervalInMilliseconds); await this.reload(); } if (!checkConnected()) { throw new _timeoutError.default("Timed out waiting for account " + this.id + " to connect to the broker"); } } /** * Connects to MetaApi. There is only one connection per account. Subsequent calls to this method will return the same connection. * @param {HistoryStorage} historyStorage optional history storage * @param {Date} [historyStartTime] history start time. Used for tests * @return {StreamingMetaApiConnectionInstance} MetaApi connection instance */ getStreamingConnection(historyStorage, historyStartTime) { if (this._metaApiWebsocketClient.region && this._metaApiWebsocketClient.region !== this.region) { throw new _errorHandler.ValidationError(`Account ${this.id} is not on specified region ${this._metaApiWebsocketClient.region}`); } return this._connectionRegistry.connectStreaming(this, historyStorage, historyStartTime); } /** * Connects to MetaApi via RPC connection instance. * @returns {RpcMetaApiConnectionInstance} MetaApi connection instance */ getRPCConnection() { if (this._metaApiWebsocketClient.region && this._metaApiWebsocketClient.region !== this.region) { throw new _errorHandler.ValidationError(`Account ${this.id} is not on specified region ${this._metaApiWebsocketClient.region}`); } return this._connectionRegistry.connectRpc(this); } /** * Updates trading account. * Please redeploy the trading account in order for updated settings to take effect * @param {MetatraderAccountUpdateDto} account updated account information * @return {Promise} promise resolving when account is updated */ async update(account) { await this._metatraderAccountClient.updateAccount(this.id, account); await this.reload(); } /** * Creates a trading account replica in a region different from trading account region and starts a cloud API server for it * @param {NewMetaTraderAccountDto} account MetaTrader account data * @return {Promise<MetatraderAccountReplica>} promise resolving with created MetaTrader account replica entity */ async createReplica(account) { await this._metatraderAccountClient.createAccountReplica(this.id, account); await this.reload(); return this._replicas.find((r)=>r.region === account.region); } /** * Retrieves expert advisor of current account * @returns {Promise<ExpertAdvisor[]>} promise resolving with an array of expert advisor entities */ async getExpertAdvisors() { this._checkExpertAdvisorAllowed(); let expertAdvisors = await this._expertAdvisorClient.getExpertAdvisors(this.id); return expertAdvisors.map((e)=>new _expertAdvisor.default(e, this.id, this._expertAdvisorClient)); } /** * Retrieves a expert advisor of current account by id * @param {String} expertId expert advisor id * @returns {Promise<ExpertAdvisor>} promise resolving with expert advisor entity */ async getExpertAdvisor(expertId) { this._checkExpertAdvisorAllowed(); let expertAdvisor = await this._expertAdvisorClient.getExpertAdvisor(this.id, expertId); return new _expertAdvisor.default(expertAdvisor, this.id, this._expertAdvisorClient); } /** * Creates an expert advisor * @param {string} expertId expert advisor id * @param {NewExpertAdvisorDto} expert expert advisor data * @returns {Promise<ExpertAdvisor>} promise resolving with expert advisor entity */ async createExpertAdvisor(expertId, expert) { this._checkExpertAdvisorAllowed(); await this._expertAdvisorClient.updateExpertAdvisor(this.id, expertId, expert); return this.getExpertAdvisor(expertId); } /** * Returns historical candles for a specific symbol and timeframe from the MetaTrader account. * See https://metaapi.cloud/docs/client/restApi/api/retrieveMarketData/readHistoricalCandles/ * @param {string} symbol symbol to retrieve candles for (e.g. a currency pair or an index) * @param {string} timeframe defines the timeframe according to which the candles must be generated. Allowed values * for MT5 are 1m, 2m, 3m, 4m, 5m, 6m, 10m, 12m, 15m, 20m, 30m, 1h, 2h, 3h, 4h, 6h, 8h, 12h, 1d, 1w, 1mn. Allowed * values for MT4 are 1m, 5m, 15m 30m, 1h, 4h, 1d, 1w, 1mn * @param {Date} [startTime] time to start loading candles from. Note that candles are loaded in backwards direction, so * this should be the latest time. Leave empty to request latest candles. * @param {number} [limit] maximum number of candles to retrieve. Must be less or equal to 1000 * @return {Promise<Array<MetatraderCandle>>} promise resolving with historical candles downloaded */ getHistoricalCandles(symbol, timeframe, startTime, limit) { return this._historicalMarketDataClient.getHistoricalCandles(this.id, this.region, symbol, timeframe, startTime, limit); } /** * Returns historical ticks for a specific symbol from the MetaTrader account. This API is not supported by MT4 * accounts. * See https://metaapi.cloud/docs/client/restApi/api/retrieveMarketData/readHistoricalTicks/ * @param {string} symbol symbol to retrieve ticks for (e.g. a currency pair or an index) * @param {Date} [startTime] time to start loading ticks from. Note that candles are loaded in forward direction, so * this should be the earliest time. Leave empty to request latest candles. * @param {number} [offset] number of ticks to skip (you can use it to avoid requesting ticks from previous request * twice) * @param {number} [limit] maximum number of ticks to retrieve. Must be less or equal to 1000 * @return {Promise<Array<MetatraderTick>>} promise resolving with historical ticks downloaded */ getHistoricalTicks(symbol, startTime, offset, limit) { return this._historicalMarketDataClient.getHistoricalTicks(this.id, this.region, symbol, startTime, offset, limit); } /** * Generates trading account configuration link by account id. * @param {number} [ttlInDays] Lifetime of the link in days. Default is 7. * @return {Promise<ConfigurationLink>} promise resolving with configuration link */ async createConfigurationLink(ttlInDays) { const configurationLink = await this._metatraderAccountClient.createConfigurationLink(this.id, ttlInDays); return configurationLink; } _checkExpertAdvisorAllowed() { if (this.version !== 4 || this.type !== "cloud-g1") { throw new _errorHandler.ValidationError("Custom expert advisor is available only for MT4 G1 accounts"); } } _delay(timeoutInMilliseconds) { return new Promise((res)=>setTimeout(res, timeoutInMilliseconds)); } /** * Constructs a MetaTrader account entity * @param {MetatraderAccountDto} data MetaTrader account data * @param {MetatraderAccountClient} metatraderAccountClient MetaTrader account REST API client * @param {MetaApiWebsocketClient} metaApiWebsocketClient MetaApi websocket client * @param {ConnectionRegistry} connectionRegistry metatrader account connection registry * @param {ExpertAdvisorClient} expertAdvisorClient expert advisor REST API client * @param {HistoricalMarketDataClient} historicalMarketDataClient historical market data HTTP API client * @param {string} application application name */ constructor(data, metatraderAccountClient, metaApiWebsocketClient, connectionRegistry, expertAdvisorClient, historicalMarketDataClient, application){ this._data = data; this._metatraderAccountClient = metatraderAccountClient; this._metaApiWebsocketClient = metaApiWebsocketClient; this._connectionRegistry = connectionRegistry; this._expertAdvisorClient = expertAdvisorClient; this._historicalMarketDataClient = historicalMarketDataClient; this._application = application; this._replicas = (data.accountReplicas || []).map((replica)=>new _metatraderAccountReplica.default(replica, this, metatraderAccountClient)); } }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUaW1lb3V0RXJyb3IgZnJvbSAnLi4vY2xpZW50cy90aW1lb3V0RXJyb3InO1xuaW1wb3J0IFJwY01ldGFBcGlDb25uZWN0aW9uSW5zdGFuY2UgZnJvbSAnLi9ycGNNZXRhQXBpQ29ubmVjdGlvbkluc3RhbmNlJztcbmltcG9ydCBTdHJlYW1pbmdNZXRhQXBpQ29ubmVjdGlvbkluc3RhbmNlIGZyb20gJy4vc3RyZWFtaW5nTWV0YUFwaUNvbm5lY3Rpb25JbnN0YW5jZSc7XG5pbXBvcnQgSGlzdG9yeURhdGFiYXNlIGZyb20gJy4vaGlzdG9yeURhdGFiYXNlL2luZGV4JztcbmltcG9ydCBFeHBlcnRBZHZpc29yIGZyb20gJy4vZXhwZXJ0QWR2aXNvcic7XG5pbXBvcnQge1ZhbGlkYXRpb25FcnJvcn0gZnJvbSAnLi4vY2xpZW50cy9lcnJvckhhbmRsZXInO1xuaW1wb3J0IE1ldGF0cmFkZXJBY2NvdW50UmVwbGljYSBmcm9tICcuL21ldGF0cmFkZXJBY2NvdW50UmVwbGljYSc7XG4vL2VzbGludC1kaXNhYmxlLW5leHQtbGluZSBtYXgtbGVuXG5pbXBvcnQge1xuICBSZWxpYWJpbGl0eSwgU3RhdGUsIFZlcnNpb24sIENvbm5lY3Rpb25TdGF0dXMsIENvcHlGYWN0b3J5Um9sZXMsIFR5cGUsIEFjY291bnRDb25uZWN0aW9uLCBDb25maWd1cmF0aW9uTGluayxcbiAgTWV0YXRyYWRlckFjY291bnREdG8sIERlZGljYXRlZElwXG59IGZyb20gJy4uL2NsaWVudHMvbWV0YUFwaS9tZXRhdHJhZGVyQWNjb3VudC5jbGllbnQnO1xuXG4vKipcbiAqIEltcGxlbWVudHMgYSBNZXRhVHJhZGVyIGFjY291bnQgZW50aXR5XG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIE1ldGF0cmFkZXJBY2NvdW50IHtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyBhIE1ldGFUcmFkZXIgYWNjb3VudCBlbnRpdHlcbiAgICogQHBhcmFtIHtNZXRhdHJhZGVyQWNjb3VudER0b30gZGF0YSBNZXRhVHJhZGVyIGFjY291bnQgZGF0YVxuICAgKiBAcGFyYW0ge01ldGF0cmFkZXJBY2NvdW50Q2xpZW50fSBtZXRhdHJhZGVyQWNjb3VudENsaWVudCBNZXRhVHJhZGVyIGFjY291bnQgUkVTVCBBUEkgY2xpZW50XG4gICAqIEBwYXJhbSB7TWV0YUFwaVdlYnNvY2tldENsaWVudH0gbWV0YUFwaVdlYnNvY2tldENsaWVudCBNZXRhQXBpIHdlYnNvY2tldCBjbGllbnRcbiAgICogQHBhcmFtIHtDb25uZWN0aW9uUmVnaXN0cnl9IGNvbm5lY3Rpb25SZWdpc3RyeSBtZXRhdHJhZGVyIGFjY291bnQgY29ubmVjdGlvbiByZWdpc3RyeVxuICAgKiBAcGFyYW0ge0V4cGVydEFkdmlzb3JDbGllbnR9IGV4cGVydEFkdmlzb3JDbGllbnQgZXhwZXJ0IGFkdmlzb3IgUkVTVCBBUEkgY2xpZW50XG4gICAqIEBwYXJhbSB7SGlzdG9yaWNhbE1hcmtldERhdGFDbGllbnR9IGhpc3RvcmljYWxNYXJrZXREYXRhQ2xpZW50IGhpc3RvcmljYWwgbWFya2V0IGRhdGEgSFRUUCBBUEkgY2xpZW50XG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcHBsaWNhdGlvbiBhcHBsaWNhdGlvbiBuYW1lXG4gICAqL1xuICBjb25zdHJ1Y3RvcihkYXRhLCBtZXRhdHJhZGVyQWNjb3VudENsaWVudCwgbWV0YUFwaVdlYnNvY2tldENsaWVudCwgY29ubmVjdGlvblJlZ2lzdHJ5LCBleHBlcnRBZHZpc29yQ2xpZW50LCBcbiAgICBoaXN0b3JpY2FsTWFya2V0RGF0YUNsaWVudCwgYXBwbGljYXRpb24pIHtcbiAgICB0aGlzLl9kYXRhID0gZGF0YTtcbiAgICB0aGlzLl9tZXRhdHJhZGVyQWNjb3VudENsaWVudCA9IG1ldGF0cmFkZXJBY2NvdW50Q2xpZW50O1xuICAgIHRoaXMuX21ldGFBcGlXZWJzb2NrZXRDbGllbnQgPSBtZXRhQXBpV2Vic29ja2V0Q2xpZW50O1xuICAgIHRoaXMuX2Nvbm5lY3Rpb25SZWdpc3RyeSA9IGNvbm5lY3Rpb25SZWdpc3RyeTtcbiAgICB0aGlzLl9leHBlcnRBZHZpc29yQ2xpZW50ID0gZXhwZXJ0QWR2aXNvckNsaWVudDtcbiAgICB0aGlzLl9oaXN0b3JpY2FsTWFya2V0RGF0YUNsaWVudCA9IGhpc3RvcmljYWxNYXJrZXREYXRhQ2xpZW50O1xuICAgIHRoaXMuX2FwcGxpY2F0aW9uID0gYXBwbGljYXRpb247XG4gICAgdGhpcy5fcmVwbGljYXMgPSAoZGF0YS5hY2NvdW50UmVwbGljYXMgfHwgW10pXG4gICAgICAubWFwKHJlcGxpY2EgPT4gbmV3IE1ldGF0cmFkZXJBY2NvdW50UmVwbGljYShyZXBsaWNhLCB0aGlzLCBtZXRhdHJhZGVyQWNjb3VudENsaWVudCkpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdW5pcXVlIGFjY291bnQgaWRcbiAgICogQHJldHVybiB7c3RyaW5nfSB1bmlxdWUgYWNjb3VudCBpZFxuICAgKi9cbiAgZ2V0IGlkKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLl9pZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGN1cnJlbnQgYWNjb3VudCBzdGF0ZS4gT25lIG9mIENSRUFURUQsIERFUExPWUlORywgREVQTE9ZRUQsIERFUExPWV9GQUlMRUQsIFVOREVQTE9ZSU5HLFxuICAgKiBVTkRFUExPWUVELCBVTkRFUExPWV9GQUlMRUQsIERFTEVUSU5HLCBERUxFVEVfRkFJTEVELCBSRURFUExPWV9GQUlMRUQsIERSQUZUXG4gICAqIEByZXR1cm4ge1N0YXRlfSBjdXJyZW50IGFjY291bnQgc3RhdGVcbiAgICovXG4gIGdldCBzdGF0ZSgpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS5zdGF0ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIE1ldGFUcmFkZXIgbWFnaWMgdG8gcGxhY2UgdHJhZGVzIHVzaW5nXG4gICAqIEByZXR1cm4ge251bWJlcn0gTWV0YVRyYWRlciBtYWdpYyB0byBwbGFjZSB0cmFkZXMgdXNpbmdcbiAgICovXG4gIGdldCBtYWdpYygpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS5tYWdpYztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRlcm1pbmFsICYgYnJva2VyIGNvbm5lY3Rpb24gc3RhdHVzLCBvbmUgb2YgQ09OTkVDVEVELCBESVNDT05ORUNURUQsIERJU0NPTk5FQ1RFRF9GUk9NX0JST0tFUlxuICAgKiBAcmV0dXJuIHtDb25uZWN0aW9uU3RhdHVzfSB0ZXJtaW5hbCAmIGJyb2tlciBjb25uZWN0aW9uIHN0YXR1c1xuICAgKi9cbiAgZ2V0IGNvbm5lY3Rpb25TdGF0dXMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEuY29ubmVjdGlvblN0YXR1cztcbiAgfVxuICBcbiAgLyoqXG4gICAqIFJldHVybnMgcXVvdGUgc3RyZWFtaW5nIGludGVydmFsIGluIHNlY29uZHMgXG4gICAqIEByZXR1cm4ge251bWJlcn0gcXVvdGUgc3RyZWFtaW5nIGludGVydmFsIGluIHNlY29uZHNcbiAgICovXG4gIGdldCBxdW90ZVN0cmVhbWluZ0ludGVydmFsSW5TZWNvbmRzKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLnF1b3RlU3RyZWFtaW5nSW50ZXJ2YWxJblNlY29uZHM7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBSZXR1cm5zIHN5bWJvbCBwcm92aWRlZCBieSBicm9rZXIgXG4gICAqIEByZXR1cm4ge3N0cmluZ30gYW55IHN5bWJvbCBwcm92aWRlZCBieSBicm9rZXJcbiAgICovXG4gIGdldCBzeW1ib2woKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEuc3ltYm9sO1xuICB9XG4gIFxuICAvKipcbiAgICogUmV0dXJucyByZWxpYWJpbGl0eSB2YWx1ZS4gUG9zc2libGUgdmFsdWVzIGFyZSByZWd1bGFyIGFuZCBoaWdoXG4gICAqIEByZXR1cm4ge1JlbGlhYmlsaXR5fSBhY2NvdW50IHJlbGlhYmlsaXR5IHZhbHVlXG4gICAqL1xuICBnZXQgcmVsaWFiaWxpdHkoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEucmVsaWFiaWxpdHk7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBSZXR1cm5zIHVzZXItZGVmaW5lZCBhY2NvdW50IHRhZ3NcbiAgICogQHJldHVybiB7QXJyYXk8c3RyaW5nPn0gdXNlci1kZWZpbmVkIGFjY291bnQgdGFnc1xuICAgKi9cbiAgZ2V0IHRhZ3MoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEudGFncztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGV4dHJhIGluZm9ybWF0aW9uIHdoaWNoIGNhbiBiZSBzdG9yZWQgdG9nZXRoZXIgd2l0aCB5b3VyIGFjY291bnRcbiAgICogQHJldHVybiB7T2JqZWN0fSBleHRyYSBpbmZvcm1hdGlvbiB3aGljaCBjYW4gYmUgc3RvcmVkIHRvZ2V0aGVyIHdpdGggeW91ciBhY2NvdW50XG4gICAqL1xuICBnZXQgbWV0YWRhdGEoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEubWV0YWRhdGE7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBudW1iZXIgb2YgcmVzb3VyY2Ugc2xvdHMgdG8gYWxsb2NhdGUgdG8gYWNjb3VudC4gQWxsb2NhdGluZyBleHRyYSByZXNvdXJjZSBzbG90c1xuICAgKiByZXN1bHRzIGluIGJldHRlciBhY2NvdW50IHBlcmZvcm1hbmNlIHVuZGVyIGxvYWQgd2hpY2ggaXMgdXNlZnVsIGZvciBzb21lIGFwcGxpY2F0aW9ucy4gRS5nLiBpZiB5b3UgaGF2ZSBtYW55XG4gICAqIGFjY291bnRzIGNvcHlpbmcgdGhlIHNhbWUgc3RyYXRlZ3kgdmlhIENvcHlGYWN0b3J5IEFQSSwgdGhlbiB5b3UgY2FuIGluY3JlYXNlIHJlc291cmNlU2xvdHMgdG8gZ2V0IGEgbG93ZXIgdHJhZGVcbiAgICogY29weWluZyBsYXRlbmN5LiBQbGVhc2Ugbm90ZSB0aGF0IGFsbG9jYXRpbmcgZXh0cmEgcmVzb3VyY2Ugc2xvdHMgaXMgYSBwYWlkIG9wdGlvbi4gUGxlYXNlIG5vdGUgdGhhdCBoaWdoXG4gICAqIHJlbGlhYmlsaXR5IGFjY291bnRzIHVzZSByZWR1bmRhbnQgaW5mcmFzdHJ1Y3R1cmUsIHNvIHRoYXQgZWFjaCByZXNvdXJjZSBzbG90IGZvciBhIGhpZ2ggcmVsaWFiaWxpdHkgYWNjb3VudFxuICAgKiBpcyBiaWxsZWQgYXMgMiBzdGFuZGFyZCByZXNvdXJjZSBzbG90cy5cbiAgICogQHJldHVybiB7bnVtYmVyfSBudW1iZXIgb2YgcmVzb3VyY2Ugc2xvdHMgdG8gYWxsb2NhdGUgdG8gYWNjb3VudFxuICAgKi9cbiAgZ2V0IHJlc291cmNlU2xvdHMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEucmVzb3VyY2VTbG90cztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBudW1iZXIgb2YgQ29weUZhY3RvcnkgMiByZXNvdXJjZSBzbG90cyB0byBhbGxvY2F0ZSB0byBhY2NvdW50LlxuICAgKiBBbGxvY2F0aW5nIGV4dHJhIHJlc291cmNlIHNsb3RzIHJlc3VsdHMgaW4gbG93ZXIgdHJhZGUgY29weWluZyBsYXRlbmN5LiBQbGVhc2Ugbm90ZSB0aGF0IGFsbG9jYXRpbmcgZXh0cmEgcmVzb3VyY2VcbiAgICogc2xvdHMgaXMgYSBwYWlkIG9wdGlvbi4gUGxlYXNlIGFsc28gbm90ZSB0aGF0IENvcHlGYWN0b3J5IDIgdXNlcyByZWR1bmRhbnQgaW5mcmFzdHJ1Y3R1cmUgc28gdGhhdFxuICAgKiBlYWNoIENvcHlGYWN0b3J5IHJlc291cmNlIHNsb3QgaXMgYmlsbGVkIGFzIDIgc3RhbmRhcmQgcmVzb3VyY2Ugc2xvdHMuIFlvdSB3aWxsIGJlIGJpbGxlZCBmb3IgQ29weUZhY3RvcnkgMlxuICAgKiByZXNvdXJjZSBzbG90cyBvbmx5IGlmIHlvdSBoYXZlIGFkZGVkIHlvdXIgYWNjb3VudCB0byBDb3B5RmFjdG9yeSAyIGJ5IHNwZWNpZnlpbmcgY29weUZhY3RvcnlSb2xlcyBmaWVsZC5cbiAgICogQHJldHVybiB7bnVtYmVyfSBudW1iZXIgb2YgQ29weUZhY3RvcnkgMiByZXNvdXJjZSBzbG90cyB0byBhbGxvY2F0ZSB0byBhY2NvdW50XG4gICAqL1xuICBnZXQgY29weUZhY3RvcnlSZXNvdXJjZVNsb3RzKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLmNvcHlGYWN0b3J5UmVzb3VyY2VTbG90cztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGFjY291bnQgcmVnaW9uXG4gICAqIEByZXR1cm4ge3N0cmluZ30gYWNjb3VudCByZWdpb24gdmFsdWVcbiAgICovXG4gIGdldCByZWdpb24oKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEucmVnaW9uO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIHRpbWUgYWNjb3VudCB3YXMgY3JlYXRlZCBhdCwgaW4gSVNPIGZvcm1hdFxuICAgKiBAcmV0dXJucyB7c3RyaW5nfSB0aGUgdGltZSBhY2NvdW50IHdhcyBjcmVhdGVkIGF0LCBpbiBJU08gZm9ybWF0XG4gICAqL1xuICBnZXQgY3JlYXRlZEF0KCkge1xuICAgIHJldHVybiBuZXcgRGF0ZSh0aGlzLl9kYXRhLmNyZWF0ZWRBdCk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBodW1hbi1yZWFkYWJsZSBhY2NvdW50IG5hbWVcbiAgICogQHJldHVybiB7c3RyaW5nfSBodW1hbi1yZWFkYWJsZSBhY2NvdW50IG5hbWVcbiAgICovXG4gIGdldCBuYW1lKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLm5hbWU7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBSZXR1cm5zIGZsYWcgaW5kaWNhdGluZyBpZiB0cmFkZXMgc2hvdWxkIGJlIHBsYWNlZCBhcyBtYW51YWwgdHJhZGVzIG9uIHRoaXMgYWNjb3VudFxuICAgKiBAcmV0dXJuIHtib29sZWFufSBmbGFnIGluZGljYXRpbmcgaWYgdHJhZGVzIHNob3VsZCBiZSBwbGFjZWQgYXMgbWFudWFsIHRyYWRlcyBvbiB0aGlzIGFjY291bnRcbiAgICovXG4gIGdldCBtYW51YWxUcmFkZXMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEubWFudWFsVHJhZGVzO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgZGVmYXVsdCB0cmFkZSBzbGlwcGFnZSBpbiBwb2ludHNcbiAgICogQHJldHVybiB7bnVtYmVyfSBkZWZhdWx0IHRyYWRlIHNsaXBwYWdlIGluIHBvaW50c1xuICAgKi9cbiAgZ2V0IHNsaXBwYWdlKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLnNsaXBwYWdlO1xuICB9XG4gIFxuICAvKipcbiAgICogUmV0dXJucyBpZCBvZiB0aGUgYWNjb3VudCdzIHByb3Zpc2lvbmluZyBwcm9maWxlXG4gICAqIEByZXR1cm4ge3N0cmluZ30gaWQgb2YgdGhlIGFjY291bnQncyBwcm92aXNpb25pbmcgcHJvZmlsZVxuICAgKi9cbiAgZ2V0IHByb3Zpc2lvbmluZ1Byb2ZpbGVJZCgpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS5wcm92aXNpb25pbmdQcm9maWxlSWQ7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBSZXR1cm5zIE1ldGFUcmFkZXIgYWNjb3VudCBsb2dpblxuICAgKiBAcmV0dXJuIHtzdHJpbmd9IE1ldGFUcmFkZXIgYWNjb3VudCBudW1iZXJcbiAgICovXG4gIGdldCBsb2dpbigpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS5sb2dpbjtcbiAgfVxuICBcbiAgLyoqXG4gICAqIFJldHVybnMgTWV0YVRyYWRlciBzZXJ2ZXIgbmFtZSB0byBjb25uZWN0IHRvXG4gICAqIEByZXR1cm4ge3N0cmluZ30gTWV0YVRyYWRlciBzZXJ2ZXIgbmFtZSB0byBjb25uZWN0IHRvXG4gICAqL1xuICBnZXQgc2VydmVyKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLnNlcnZlcjtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGFjY291bnQgdHlwZS4gUG9zc2libGUgdmFsdWVzIGFyZSBjbG91ZC1nMSwgY2xvdWQtZzJcbiAgICogQHJldHVybiB7VHlwZX0gYWNjb3VudCB0eXBlXG4gICAqL1xuICBnZXQgdHlwZSgpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS50eXBlO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgTVQgdmVyc2lvbi4gUG9zc2libGUgdmFsdWVzIGFyZSA0IGFuZCA1XG4gICAqIEByZXR1cm4ge1ZlcnNpb259IE1UIHZlcnNpb25cbiAgICovXG4gIGdldCB2ZXJzaW9uKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLnZlcnNpb247XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBoYXNoLWNvZGUgb2YgdGhlIGFjY291bnRcbiAgICogQHJldHVybiB7bnVtYmVyfSBoYXNoLWNvZGUgb2YgdGhlIGFjY291bnRcbiAgICovXG4gIGdldCBoYXNoKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLmhhc2g7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyAzLWNoYXJhY3RlciBJU08gY3VycmVuY3kgY29kZSBvZiB0aGUgYWNjb3VudCBiYXNlIGN1cnJlbmN5LiBUaGUgc2V0dGluZyBpcyB0byBiZSB1c2VkXG4gICAqIGZvciBjb3B5IHRyYWRpbmcgYWNjb3VudHMgd2hpY2ggdXNlIG5hdGlvbmFsIGN1cnJlbmNpZXMgb25seSwgc3VjaCBhcyBzb21lIEJyYXppbGlhbiBicm9rZXJzLiBZb3Ugc2hvdWxkIG5vdCBhbHRlclxuICAgKiB0aGlzIHNldHRpbmcgdW5sZXNzIHlvdSB1bmRlcnN0YW5kIHdoYXQgeW91IGFyZSBkb2luZy5cbiAgICogQHJldHVybiB7c3RyaW5nfSAzLWNoYXJhY3RlciBJU08gY3VycmVuY3kgY29kZSBvZiB0aGUgYWNjb3VudCBiYXNlIGN1cnJlbmN5XG4gICAqL1xuICBnZXQgYmFzZUN1cnJlbmN5KCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLmJhc2VDdXJyZW5jeTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGFjY291bnQgcm9sZXMgZm9yIENvcHlGYWN0b3J5MiBhcHBsaWNhdGlvbi4gUG9zc2libGUgdmFsdWVzIGFyZSBgUFJPVklERVJgIGFuZCBgU1VCU0NSSUJFUmBcbiAgICogQHJldHVybiB7QXJyYXk8Q29weUZhY3RvcnlSb2xlcz59IGFjY291bnQgcm9sZXMgZm9yIENvcHlGYWN0b3J5MiBhcHBsaWNhdGlvblxuICAgKi9cbiAgZ2V0IGNvcHlGYWN0b3J5Um9sZXMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEuY29weUZhY3RvcnlSb2xlcztcbiAgfVxuICBcbiAgLyoqXG4gICAqIFJldHVybnMgZmxhZyBpbmRpY2F0aW5nIHRoYXQgcmlzayBtYW5hZ2VtZW50IEFQSSBpcyBlbmFibGVkIG9uIGFjY291bnRcbiAgICogQHJldHVybiB7Ym9vbGVhbn0gZmxhZyBpbmRpY2F0aW5nIHRoYXQgcmlzayBtYW5hZ2VtZW50IEFQSSBpcyBlbmFibGVkIG9uIGFjY291bnRcbiAgICovXG4gIGdldCByaXNrTWFuYWdlbWVudEFwaUVuYWJsZWQoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEucmlza01hbmFnZW1lbnRBcGlFbmFibGVkO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgZmxhZyBpbmRpY2F0aW5nIHRoYXQgTWV0YVN0YXRzIEFQSSBpcyBlbmFibGVkIG9uIGFjY291bnRcbiAgICogQHJldHVybiB7Ym9vbGVhbn0gZmxhZyBpbmRpY2F0aW5nIHRoYXQgTWV0YVN0YXRzIEFQSSBpcyBlbmFibGVkIG9uIGFjY291bnRcbiAgICovXG4gIGdldCBtZXRhc3RhdHNBcGlFbmFibGVkKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLm1ldGFzdGF0c0FwaUVuYWJsZWQ7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBjb25maWd1cmVkIGRlZGljYXRlZCBJUCBwcm90b2NvbCB0byBjb25uZWN0IHRvIHRoZSB0cmFkaW5nIGFjY291bnQgdGVybWluYWxcbiAgICogQHJldHVybiB7RGVkaWNhdGVkSXB9XG4gICAqL1xuICBnZXQgYWxsb2NhdGVEZWRpY2F0ZWRJcCgpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS5hbGxvY2F0ZURlZGljYXRlZElwO1xuICB9XG4gICAgXG4gIC8qKlxuICAgKiBSZXR1cm5zIGFjdGl2ZSBhY2NvdW50IGNvbm5lY3Rpb25zXG4gICAqIEByZXR1cm4ge0FycmF5PEFjY291bnRDb25uZWN0aW9uPn0gYWN0aXZlIGFjY291bnQgY29ubmVjdGlvbnNcbiAgICovXG4gIGdldCBjb25uZWN0aW9ucygpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS5jb25uZWN0aW9ucztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGZsYWcgaW5kaWNhdGluZyB0aGF0IGFjY291bnQgaXMgcHJpbWFyeVxuICAgKiBAcmV0dXJuIHtib29sZWFufSBmbGFnIGluZGljYXRpbmcgdGhhdCBhY2NvdW50IGlzIHByaW1hcnlcbiAgICovXG4gIGdldCBwcmltYXJ5UmVwbGljYSgpIHtcbiAgICByZXR1cm4gdGhpcy5fZGF0YS5wcmltYXJ5UmVwbGljYTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHVzZXIgaWRcbiAgICogQHJldHVybiB7c3RyaW5nfSB1c2VyIGlkXG4gICAqL1xuICBnZXQgdXNlcklkKCkge1xuICAgIHJldHVybiB0aGlzLl9kYXRhLnVzZXJJZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHByaW1hcnkgYWNjb3VudCBpZFxuICAgKiBAcmV0dXJuIHtzdHJpbmd9IHByaW1hcnkgYWNjb3VudCBpZFxuICAgKi9cbiAgZ2V0IHByaW1hcnlBY2NvdW50SWQoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEucHJpbWFyeUFjY291bnRJZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGFjY291bnQgcmVwbGljYXMgZnJvbSBEVE9cbiAgICogQHJldHVybiB7TWV0YXRyYWRlckFjY291bnRSZXBsaWNhW119IGFjY291bnQgcmVwbGljYXMgZnJvbSBEVE9cbiAgICovXG4gIGdldCBhY2NvdW50UmVwbGljYXMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RhdGEuYWNjb3VudFJlcGxpY2FzO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgYWNjb3VudCByZXBsaWNhIGluc3RhbmNlc1xuICAgKiBAcmV0dXJuIHtNZXRhdHJhZGVyQWNjb3VudFJlcGxpY2FbXX0gYWNjb3VudCByZXBsaWNhIGluc3RhbmNlc1xuICAgKi9cbiAgZ2V0IHJlcGxpY2FzKCkge1xuICAgIHJldHVybiB0aGlzLl9yZXBsaWNhcztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGEgZGljdGlvbmFyeSB3aXRoIGFjY291bnQncyBhdmFpbGFibGUgcmVnaW9ucyBhbmQgcmVwbGljYXNcbiAgICogQHJldHVybnMge3tbcmVnaW9uOiBzdHJpbmddOiBzdHJpbmd9fVxuICAgKi9cbiAgZ2V0IGFjY291bnRSZWdpb25zKCkge1xuICAgIGNvbnN0IHJlZ2lvbnMgPSB7W3RoaXMucmVnaW9uXTogdGhpcy5pZH07XG4gICAgdGhpcy5yZXBsaWNhcy5mb3JFYWNoKHJlcGxpY2EgPT4gcmVnaW9uc1tyZXBsaWNhLnJlZ2lvbl0gPSByZXBsaWNhLmlkKTtcbiAgICByZXR1cm4gcmVnaW9ucztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWxvYWRzIE1ldGFUcmFkZXIgYWNjb3VudCBmcm9tIEFQSVxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHJlc29sdmluZyB3aGVuIE1ldGFUcmFkZXIgYWNjb3VudCBpcyB1cGRhdGVkXG4gICAqL1xuICBhc3luYyByZWxvYWQoKSB7XG4gICAgdGhpcy5fZGF0YSA9IGF3YWl0IHRoaXMuX21ldGF0cmFkZXJBY2NvdW50Q2xpZW50LmdldEFjY291bnQodGhpcy5pZCk7XG4gICAgY29uc3QgdXBkYXRlZFJlcGxpY2FEYXRhID0gKHRoaXMuX2RhdGEuYWNjb3VudFJlcGxpY2FzIHx8IFtdKTtcbiAgICBjb25zdCByZWdpb25zID0gdXBkYXRlZFJlcGxpY2FEYXRhLm1hcChyZXBsaWNhID0+IHJlcGxpY2EucmVnaW9uKTtcbiAgICBjb25zdCBjcmVhdGVkUmVwbGljYVJlZ2lvbnMgPSB0aGlzLl9yZXBsaWNhcy5tYXAocmVwbGljYSA9PiByZXBsaWNhLnJlZ2lvbik7XG4gICAgdGhpcy5fcmVwbGljYXMgPSB0aGlzLl9yZXBsaWNhcy5maWx0ZXIocmVwbGljYSA9PiByZWdpb25zLmluY2x1ZGVzKHJlcGxpY2EucmVnaW9uKSk7XG4gICAgdGhpcy5fcmVwbGljYXMuZm9yRWFjaChyZXBsaWNhID0+IHtcbiAgICAgIGNvbnN0IHVwZGF0ZWREYXRhID0gdXBkYXRlZFJlcGxpY2FEYXRhLmZpbmQocmVwbGljYURhdGEgPT4gcmVwbGljYURhdGEucmVnaW9uID09PSByZXBsaWNhLnJlZ2lvbik7XG4gICAgICByZXBsaWNhLnVwZGF0ZURhdGEodXBkYXRlZERhdGEpO1xuICAgIH0pO1xuICAgIHVwZGF0ZWRSZXBsaWNhRGF0YS5mb3JFYWNoKHJlcGxpY2EgPT4ge1xuICAgICAgaWYoIWNyZWF0ZWRSZXBsaWNhUmVnaW9ucy5pbmNsdWRlcyhyZXBsaWNhLnJlZ2lvbikpIHtcbiAgICAgICAgdGhpcy5fcmVwbGljYXMucHVzaChuZXcgTWV0YXRyYWRlckFjY291bnRSZXBsaWNhKHJlcGxpY2EsIHRoaXMsIHRoaXMuX21ldGF0cmFkZXJBY2NvdW50Q2xpZW50KSk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyBhIHRyYWRpbmcgYWNjb3VudCBhbmQgc3RvcHMgdGhlIEFQSSBzZXJ2ZXIgc2VydmluZyB0aGUgYWNjb3VudC5cbiAgICogVGhlIGFjY291bnQgc3RhdGUgc3VjaCBhcyBkb3dubG9hZGVkIG1hcmtldCBkYXRhIGhpc3Rvcnkgd2lsbCBiZSByZW1vdmVkIGFzIHdlbGwgd2hlbiB5b3UgcmVtb3ZlIHRoZSBhY2NvdW50LlxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHJlc29sdmluZyB3aGVuIGFjY291bnQgaXMgc2NoZWR1bGVkIGZvciBkZWxldGlvblxuICAgKi9cbiAgYXN5bmMgcmVtb3ZlKCkge1xuICAgIHRoaXMuX2Nvbm5lY3Rpb25SZWdpc3RyeS5yZW1vdmUodGhpcy5pZCk7XG4gICAgYXdhaXQgdGhpcy5fbWV0YXRyYWRlckFjY291bnRDbGllbnQuZGVsZXRlQWNjb3VudCh0aGlzLmlkKTtcbiAgICBjb25zdCBmaWxlTWFuYWdlciA9IEhpc3RvcnlEYXRhYmFzZS5nZXRJbnN0YW5jZSgpO1xuICAgIGF3YWl0IGZpbGVNYW5hZ2VyLmNsZWFyKHRoaXMuaWQsIHRoaXMuX2FwcGxpY2F0aW9uKTtcbiAgICBpZiAodGhpcy50eXBlICE9PSAnc2VsZi1ob3N0ZWQnKSB7XG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB0aGlzLnJlbG9hZCgpO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGlmIChlcnIubmFtZSAhPT0gJ05vdEZvdW5kRXJyb3InKSB7XG4gICAgICAgICAgdGhyb3cgZXJyO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFN0YXJ0cyBBUEkgc2VydmVyIGFuZCB0cmFkaW5nIHRlcm1pbmFsIGZvciB0cmFkaW5nIGFjY291bnQuXG4gICAqIFRoaXMgcmVxdWVzdCB3aWxsIGJlIGlnbm9yZWQgaWYgdGhlIGFjY291bnQgaXMgYWxyZWFkeSBkZXBsb3llZC5cbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gYWNjb3VudCBpcyBzY2hlZHVsZWQgZm9yIGRlcGxveW1lbnRcbiAgICovXG4gIGFzeW5jIGRlcGxveSgpIHtcbiAgICBhd2FpdCB0aGlzLl9tZXRhdHJhZGVyQWNjb3VudENsaWVudC5kZXBsb3lBY2NvdW50KHRoaXMuaWQpO1xuICAgIGF3YWl0IHRoaXMucmVsb2FkKCk7XG4gIH1cblxuICAvKipcbiAgICogU3RvcHMgQVBJIHNlcnZlciBhbmQgdHJhZGluZyB0ZXJtaW5hbCBmb3IgdHJhZGluZyBhY2NvdW50LlxuICAgKiBUaGlzIHJlcXVlc3Qgd2lsbCBiZSBpZ25vcmVkIGlmIHRyYWRpbmcgYWNjb3VudCBpcyBhbHJlYWR5IHVuZGVwbG95ZWRcbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gYWNjb3VudCBpcyBzY2hlZHVsZWQgZm9yIHVuZGVwbG95bWVudFxuICAgKi9cbiAgYXN5bmMgdW5kZXBsb3koKSB7XG4gICAgdGhpcy5fY29ubmVjdGlvblJlZ2lzdHJ5LnJlbW92ZSh0aGlzLmlkKTtcbiAgICBhd2FpdCB0aGlzLl9tZXRhdHJhZGVyQWNjb3VudENsaWVudC51bmRlcGxveUFjY291bnQodGhpcy5pZCk7XG4gICAgYXdhaXQgdGhpcy5yZWxvYWQoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWRlcGxveXMgdHJhZGluZyBhY2NvdW50LiBUaGlzIGlzIGVxdWl2YWxlbnQgdG8gdW5kZXBsb3kgaW1tZWRpYXRlbHkgZm9sbG93ZWQgYnkgZGVwbG95XG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBwcm9taXNlIHJlc29sdmluZyB3aGVuIGFjY291bnQgaXMgc2NoZWR1bGVkIGZvciByZWRlcGxveW1lbnRcbiAgICovXG4gIGFzeW5jIHJlZGVwbG95KCkge1xuICAgIGF3YWl0IHRoaXMuX21ldGF0cmFkZXJBY2NvdW50Q2xpZW50LnJlZGVwbG95QWNjb3VudCh0aGlzLmlkKTtcbiAgICBhd2FpdCB0aGlzLnJlbG9hZCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIEluY3JlYXNlcyB0cmFkaW5nIGFjY291bnQgcmVsaWFiaWxpdHkgaW4gb3JkZXIgdG8gaW5jcmVhc2UgdGhlIGV4cGVjdGVkIGFjY291bnQgdXB0aW1lLlxuICAgKiBUaGUgYWNjb3VudCB3aWxsIGJlIHRlbXBvcmFyeSBzdG9wcGVkIHRvIHBlcmZvcm0gdGhpcyBhY3Rpb24uXG4gICAqIE5vdGUgdGhhdCBpbmNyZWFzaW5nIHJlbGlhYmlsaXR5IGlzIGEgcGFpZCBvcHRpb25cbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gYWNjb3VudCByZWxpYWJpbGl0eSBpcyBpbmNyZWFzZWRcbiAgICovXG4gIGFzeW5jIGluY3JlYXNlUmVsaWFiaWxpdHkoKSB7XG4gICAgYXdhaXQgdGhpcy5fbWV0YXRyYWRlckFjY291bnRDbGllbnQuaW5jcmVhc2VSZWxpYWJpbGl0eSh0aGlzLmlkKTtcbiAgICBhd2FpdCB0aGlzLnJlbG9hZCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIEVuYWJsZXMgcmlzayBtYW5hZ2VtZW50IEFQSSBmb3IgdHJhZGluZyBhY2NvdW50LlxuICAgKiBUaGUgYWNjb3VudCB3aWxsIGJlIHRlbXBvcmFyeSBzdG9wcGVkIHRvIHBlcmZvcm0gdGhpcyBhY3Rpb24uXG4gICAqIE5vdGUgdGhhdCByaXNrIG1hbmFnZW1lbnQgQVBJIGlzIGEgcGFpZCBvcHRpb25cbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gYWNjb3VudCByaXNrIG1hbmFnZW1lbnQgaXMgZW5hYmxlZFxuICAgKi9cbiAgYXN5bmMgZW5hYmxlUmlza01hbmFnZW1lbnRBcGkoKSB7XG4gICAgYXdhaXQgdGhpcy5fbWV0YXRyYWRlckFjY291bnRDbGllbnQuZW5hYmxlUmlza01hbmFnZW1lbnRBcGkodGhpcy5pZCk7XG4gICAgYXdhaXQgdGhpcy5yZWxvYWQoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFbmFibGVzIE1ldGFTdGF0cyBBUEkgZm9yIHRyYWRpbmcgYWNjb3VudC5cbiAgICogVGhlIGFjY291bnQgd2lsbCBiZSB0ZW1wb3Jhcnkgc3RvcHBlZCB0byBwZXJmb3JtIHRoaXMgYWN0aW9uLlxuICAgKiBOb3RlIHRoYXQgdGhpcyBpcyBhIHBhaWQgb3B0aW9uXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBwcm9taXNlIHJlc29sdmluZyB3aGVuIGFjY291bnQgTWV0YVN0YXRzIEFQSSBpcyBlbmFibGVkXG4gICAqL1xuICBhc3luYyBlbmFibGVNZXRhU3RhdHNBcGkoKSB7XG4gICAgYXdhaXQgdGhpcy5fbWV0YXRyYWRlckFjY291bnRDbGllbnQuZW5hYmxlTWV0YVN0YXRzQXBpKHRoaXMuaWQpO1xuICAgIGF3YWl0IHRoaXMucmVsb2FkKCk7XG4gIH1cblxuICAvKipcbiAgICogV2FpdHMgdW50aWwgQVBJIHNlcnZlciBoYXMgZmluaXNoZWQgZGVwbG95bWVudCBhbmQgYWNjb3VudCByZWFjaGVkIHRoZSBERVBMT1lFRCBzdGF0ZVxuICAgKiBAcGFyYW0ge251bWJlcn0gdGltZW91dEluU2Vjb25kcyB3YWl0IHRpbWVvdXQgaW4gc2Vjb25kcywgZGVmYXVsdCBpcyA1bVxuICAgKiBAcGFyYW0ge251bWJlcn0gaW50ZXJ2YWxJbk1pbGxpc2Vjb25kcyBpbnRlcnZhbCBiZXR3ZWVuIGFjY291bnQgcmVsb2FkcyB3aGlsZSB3YWl0aW5nIGZvciBhIGNoYW5nZSwgZGVmYXVsdCBpcyAxc1xuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gYWNjb3VudCBpcyBkZXBsb3llZFxuICAgKiBAdGhyb3dzIHtUaW1lb3V0RXJyb3J9IGlmIGFjY291bnQgaGF2ZSBub3QgcmVhY2hlZCB0aGUgREVQTE9ZRUQgc3RhdGUgd2l0aGluIHRpbWVvdXQgYWxsb3dlZFxuICAgKi9cbiAgYXN5bmMgd2FpdERlcGxveWVkKHRpbWVvdXRJblNlY29uZHMgPSAzMDAsIGludGVydmFsSW5NaWxsaXNlY29uZHMgPSAxMDAwKSB7XG4gICAgbGV0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG4gICAgYXdhaXQgdGhpcy5yZWxvYWQoKTtcbiAgICB3aGlsZSAodGhpcy5zdGF0ZSAhPT0gJ0RFUExPWUVEJyAmJiAoc3RhcnRUaW1lICsgdGltZW91dEluU2Vjb25kcyAqIDEwMDApID4gRGF0ZS5ub3coKSkge1xuICAgICAgYXdhaXQgdGhpcy5fZGVsYXkoaW50ZXJ2YWxJbk1pbGxpc2Vjb25kcyk7XG4gICAgICBhd2FpdCB0aGlzLnJlbG9hZCgpO1xuICAgIH1cbiAgICBpZiAodGhpcy5zdGF0ZSAhPT0gJ0RFUExPWUVEJykge1xuICAgICAgdGhyb3cgbmV3IFRpbWVvdXRFcnJvcignVGltZWQgb3V0IHdhaXRpbmcgZm9yIGFjY291bnQgJyArIHRoaXMuaWQgKyAnIHRvIGJlIGRlcGxveWVkJyk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFdhaXRzIHVudGlsIEFQSSBzZXJ2ZXIgaGFzIGZpbmlzaGVkIHVuZGVwbG95bWVudCBhbmQgYWNjb3VudCByZWFjaGVkIHRoZSBVTkRFUExPWUVEIHN0YXRlXG4gICAqIEBwYXJhbSB7bnVtYmVyfSB0aW1lb3V0SW5TZWNvbmRzIHdhaXQgdGltZW91dCBpbiBzZWNvbmRzLCBkZWZhdWx0IGlzIDVtXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBpbnRlcnZhbEluTWlsbGlzZWNvbmRzIGludGVydmFsIGJldHdlZW4gYWNjb3VudCByZWxvYWRzIHdoaWxlIHdhaXRpbmcgZm9yIGEgY2hhbmdlLCBkZWZhdWx0IGlzIDFzXG4gICAqIEByZXR1cm4ge1Byb21pc2V9IHByb21pc2Ugd2hpY2ggcmVzb2x2ZXMgd2hlbiBhY2NvdW50IGlzIGRlcGxveWVkXG4gICAqIEB0aHJvd3Mge1RpbWVvdXRFcnJvcn0gaWYgYWNjb3VudCBoYXZlIG5vdCByZWFjaGVkIHRoZSBVTkRFUExPWUVEIHN0YXRlIHdpdGhpbiB0aW1lb3V0IGFsbG93ZWRcbiAgICovXG4gIGFzeW5jIHdhaXRVbmRlcGxveWVkKHRpbWVvdXRJblNlY29uZHMgPSAzMDAsIGludGVydmFsSW5NaWxsaXNlY29uZHMgPSAxMDAwKSB7XG4gICAgbGV0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG4gICAgYXdhaXQgdGhpcy5yZWxvYWQoKTtcbiAgICB3aGlsZSAodGhpcy5zdGF0ZSAhPT0gJ1VOREVQTE9ZRUQnICYmIChzdGFydFRpbWUgKyB0aW1lb3V0SW5TZWNvbmRzICogMTAwMCkgPiBEYXRlLm5vdygpKSB7XG4gICAgICBhd2FpdCB0aGlzLl9kZWxheShpbnRlcnZhbEluTWlsbGlzZWNvbmRzKTtcbiAgICAgIGF3YWl0IHRoaXMucmVsb2FkKCk7XG4gICAgfVxuICAgIGlmICh0aGlzLnN0YXRlICE9PSAnVU5ERVBMT1lFRCcpIHtcbiAgICAgIHRocm93IG5ldyBUaW1lb3V0RXJyb3IoJ1RpbWVkIG91dCB3YWl0aW5nIGZvciBhY2NvdW50ICcgKyB0aGlzLmlkICsgJyB0byBiZSB1bmRlcGxveWVkJyk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFdhaXRzIHVudGlsIGFjY291bnQgaGFzIGJlZW4gZGVsZXRlZFxuICAgKiBAcGFyYW0ge251bWJlcn0gdGltZW91dEluU2Vjb25kcyB3YWl0IHRpbWVvdXQgaW4gc2Vjb25kcywgZGVmYXVsdCBpcyA1bVxuICAgKiBAcGFyYW0ge251bWJlcn0gaW50ZXJ2YWxJbk1pbGxpc2Vjb25kcyBpbnRlcnZhbCBiZXR3ZWVuIGFjY291bnQgcmVsb2FkcyB3aGlsZSB3YWl0aW5nIGZvciBhIGNoYW5nZSwgZGVmYXVsdCBpcyAxc1xuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gYWNjb3VudCBpcyBkZWxldGVkXG4gICAqIEB0aHJvd3Mge1RpbWVvdXRFcnJvcn0gaWYgYWNjb3VudCB3YXMgbm90IGRlbGV0ZWQgd2l0aGluIHRpbWVvdXQgYWxsb3dlZFxuICAgKi9cbiAgYXN5bmMgd2FpdFJlbW92ZWQodGltZW91dEluU2Vjb25kcyA9IDMwMCwgaW50ZXJ2YWxJbk1pbGxpc2Vjb25kcyA9IDEwMDApIHtcbiAgICBsZXQgc3RhcnRUaW1lID0gRGF0ZS5ub3coKTtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgdGhpcy5yZWxvYWQoKTtcbiAgICAgIHdoaWxlIChzdGFydFRpbWUgKyB0aW1lb3V0SW5TZWNvbmRzICogMTAwMCA+IERhdGUubm93KCkpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5fZGVsYXkoaW50ZXJ2YWxJbk1pbGxpc2Vjb25kcyk7XG4gICAgICAgIGF3YWl0IHRoaXMucmVsb2FkKCk7XG4gICAgICB9XG4gICAgICB0aHJvdyBuZXcgVGltZW91dEVycm9yKCdUaW1lZCBvdXQgd2FpdGluZyBmb3IgYWNjb3VudCAnICsgdGhpcy5pZCArICcgdG8gYmUgZGVsZXRlZCcpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgaWYgKGVyci5uYW1lID09PSAnTm90Rm91bmRFcnJvcicpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgZXJyO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBXYWl0cyB1bnRpbCBBUEkgc2VydmVyIGhhcyBjb25uZWN0ZWQgdG8gdGhlIHRlcm1pbmFsIGFuZCB0ZXJtaW5hbCBoYXMgY29ubmVjdGVkIHRvIHRoZSBicm9rZXJcbiAgICogQHBhcmFtIHtudW1iZXJ9IHRpbWVvdXRJblNlY29uZHMgd2FpdCB0aW1lb3V0IGluIHNlY29uZHMsIGRlZmF1bHQgaXMgNW1cbiAgICogQHBhcmFtIHtudW1iZXJ9IGludGVydmFsSW5NaWxsaXNlY29uZHMgaW50ZXJ2YWwgYmV0d2VlbiBhY2NvdW50IHJlbG9hZHMgd2hpbGUgd2FpdGluZyBmb3IgYSBjaGFuZ2UsIGRlZmF1bHQgaXMgMXNcbiAgICogQHJldHVybiB7UHJvbWlzZX0gcHJvbWlzZSB3aGljaCByZXNvbHZlcyB3aGVuIEFQSSBzZXJ2ZXIgaXMgY29ubmVjdGVkIHRvIHRoZSBicm9rZXJcbiAgICogQHRocm93cyB7VGltZW91dEVycm9yfSBpZiBhY2NvdW50IGhhdmUgbm90IGNvbm5lY3RlZCB0byB0aGUgYnJva2VyIHdpdGhpbiB0aW1lb3V0IGFsbG93ZWRcbiAgICovXG4gIGFzeW5jIHdhaXRDb25uZWN0ZWQodGltZW91dEluU2Vjb25kcyA9IDMwMCwgaW50ZXJ2YWxJbk1pbGxpc2Vjb25kcyA9IDEwMDApIHtcbiAgICBjb25zdCBjaGVja0Nvbm5lY3RlZCA9ICgpID0+IHtcbiAgICAgIHJldHVybiBbdGhpcy5jb25uZWN0aW9uU3RhdHVzXS5jb25jYXQodGhpcy5yZXBsaWNhcy5tYXAocmVwbGljYSA9PiBcbiAgICAgICAgcmVwbGljYS5jb25uZWN0aW9uU3RhdHVzKSkuaW5jbHVkZXMoJ0NPTk5FQ1RFRCcpO1xuICAgIH07XG5cbiAgICBsZXQgc3RhcnRUaW1lID0gRGF0ZS5ub3coKTtcbiAgICBhd2FpdCB0aGlzLnJlbG9hZCgpO1xuICAgIHdoaWxlICghY2hlY2tDb25uZWN0ZWQoKSAmJiAoc3RhcnRUaW1lICsgdGltZW91dEluU2Vjb25kcyAqIDEwMDApID4gRGF0ZS5ub3coKSkge1xuICAgICAgYXdhaXQgdGhpcy5fZGVsYXkoaW50ZXJ2YWxJbk1pbGxpc2Vjb25kcyk7XG4gICAgICBhd2FpdCB0aGlzLnJlbG9hZCgpO1xuICAgIH1cbiAgICBpZiAoIWNoZWNrQ29ubmVjdGVkKCkpIHtcbiAgICAgIHRoc