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)

479 lines (478 loc) 76.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return _default; } }); const _convert = /*#__PURE__*/ _interop_require_wildcard(require("../../../../helpers/convert")); const _helpers = /*#__PURE__*/ _interop_require_wildcard(require("../../../../helpers/helpers")); const _random = /*#__PURE__*/ _interop_require_wildcard(require("../../../../helpers/random")); const _format = /*#__PURE__*/ _interop_require_wildcard(require("../../../../helpers/format")); const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash")); const _controlSignal = /*#__PURE__*/ _interop_require_default(require("../controlSignal")); const _usageCounter = /*#__PURE__*/ _interop_require_default(require("../../tools/usageCounter")); const _logger = /*#__PURE__*/ _interop_require_default(require("../../../../logger")); const _eventEmitter = /*#__PURE__*/ _interop_require_default(require("../../../../tools/eventEmitter")); const _timeoutError = /*#__PURE__*/ _interop_require_default(require("../../../../clients/timeoutError")); const _processContext = /*#__PURE__*/ _interop_require_default(require("./processContext")); 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 _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interop_require_wildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = { __proto__: null }; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for(var key in obj){ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * Starts, stops, restarts and provides long-running background processes (running functions within node process) * resolving race conditions and including process failovers until process canceled */ let AsyncProcessPool = class AsyncProcessPool { /** * Returns label for logging * @returns label */ get label() { return this._label; } /** * Event emitter related to this pool * @returns event emitter * @internal */ get events() { return this._events; } /** * Returns whether the scheduler was given command to stop * @returns whether stopped */ get stopped() { return this._stopped; } /** * Schedules a process. After the process has scheduled, it is immediately available in synchronous way. But it may * not be started fully yet (its `start` method not completed yet), or currently running process may be an old one, * which is going to stop. The latest actual and fully started process can be awaited with `waitProcess` method * @param id process ID. If a process with same ID is already scheduled, the call will be ignored * @param options process options * @throws if the pool is stopped and the `throwIfStopped` option is enabled */ scheduleProcess(id, options) { if (id.includes(":")) { throw new TypeError("Process ID must not contain any colons"); } const usage = options.usage || "default"; if (this._stopped) { var _options_throwIfStopped; if ((_options_throwIfStopped = options.throwIfStopped) !== null && _options_throwIfStopped !== void 0 ? _options_throwIfStopped : true) { throw new Error("Async pool stopped"); } else { this._logger.debug(`${this._label}: won't scheduled process ${id} because the pool is stopped`); return; } } this._logger.debug(`${this._label}: scheduling process ${id} by usage ${usage}`); this._usages.acquire(id, usage); if (this._scheduledProcesses[id]) { this._logger.debug(`${this._label}: won't schedule process ${id} because it is already scheduled`); return; } this._logger.info(`${this._label}: scheduling process ${id}`); this._scheduledProcesses[id] = { args: options.args, failoverThrottleDelay: options.failoverThrottleDelay }; this._run(id); } /** * Restarts a process if it is scheduled, otherwise the call will be ignored. If the process is waiting for throttle * delay to failover after error or unexpected stop, this method will force the failover * @param process process ID or process itself. If the process instance is specified, it will be restarted only if it * is still actual running process and not scheduled to be restarted yet */ restartProcess(process) { if (typeof process === "string") { if (this._runtimeProcesses[process]) { this._logger.info(`${this._label}: restarting process ${process}`); this._cancelProcess(this._runtimeProcesses[process]); } } else { let runtime = this._processRuntimes.get(process); if ((runtime === null || runtime === void 0 ? void 0 : runtime.process) === process) { this._logger.info(`${this._label}: restarting process ${runtime.id} instance`); this._cancelProcess(runtime); } } } /** * Cancels a process * @param id process ID * @param options additional options * @returns promise resolving when currently running process stopped */ async cancelProcess(id, options) { const usage = (options === null || options === void 0 ? void 0 : options.usage) || "default"; if (!this._scheduledProcesses[id]) { this._logger.debug(`${this._label}: won't cancel process ${id} because it is not scheduled`); return; } if (options === null || options === void 0 ? void 0 : options.allUsages) { this._logger.debug(`${this._label}: releasing process ${id} by all usages`); this._usages.releaseAll(id); } else { this._logger.debug(`${this._label}: releasing process ${id} by usage ${usage}`); this._usages.release(id, usage); } if (this._usages.isInUse(id)) { this._logger.debug(`${this._label}: won't cancel process ${id} yet as it is still in use`); return; } this._logger.info(`${this._label}: canceling process ${id}`); delete this._scheduledProcesses[id]; let runtime = this._runtimeProcesses[id]; if (runtime && !runtime.cancelPromise.completed) { this._cancelProcess(runtime); this._events.emit(`canceled:${id}`); } return runtime === null || runtime === void 0 ? void 0 : runtime.stopPromise; } _cancelProcess(runtime) { runtime.context.canceled = true; runtime.cancelPromise.resolve(); } /** * Returns scheduled process IDs * @returns process IDs */ getScheduledIds() { return Object.keys(this._scheduledProcesses); } /** * Returns running at this moment process IDs. It will always include scheduled IDs + some processes that was canceled * but may be still running or stopping * @returns process IDs */ getRunningIds() { return Object.keys(this._runtimeProcesses); } /** * Returns whether has scheduled process * @param id process ID * @returns true if scheduled */ hasScheduled(id) { return !!this._scheduledProcesses[id]; } /** * Returns whether has a process, scheduled by specific usage * @param id process ID * @param usage usage * @returns true if scheduled by */ hasScheduledBy(id, usage) { return this._usages.isAcquiredBy(id, usage); } /** * Returns process IDs which are scheduled by specific usage * @param usage usage * @returns process IDs */ getScheduledBy(usage) { return this._usages.getAcquiredBy(usage); } /** * Returns process schedulement * @param id process ID * @returns schedulement or undefined if not scheduled */ getSchedulement(id) { if (!this._scheduledProcesses[id]) { return; } return { options: this._scheduledProcesses[id], usages: this._usages.getUsages(id) }; } /** * Returns current process in its running state which can be not started yet, starting, running, stopping or stopped * @param id process ID * @returns process in its state or undefined if there is no such process scheduled */ getProcess(id) { var _this__runtimeProcesses_id; return (_this__runtimeProcesses_id = this._runtimeProcesses[id]) === null || _this__runtimeProcesses_id === void 0 ? void 0 : _this__runtimeProcesses_id.process; } /** * Waits for running started process instance. If the process was rescheduled/restarted and an old process instance is * still running, the method will wait for the new process instance to start only. Note, that a process can become * stopped or canceled till the method resolves after `await` in the calling code. So this method only increases * probability the returned process will be running * @param id process ID * @param options additional options * @returns promise resolving with process or `undefined`, depending on specified options * @throws if process is not scheduled or becomes unscheduled during waiting due to `throwIfNotScheduled` option * @throws `TimeoutError` if timed out waiting for the process due to timeout options */ async waitProcess(id, options) { if (!this._scheduledProcesses[id]) { if (options === null || options === void 0 ? void 0 : options.throwIfNotScheduled) { throw new Error("Process is not scheduled"); } return; } let result = this._runtimeProcesses[id]; if ((result === null || result === void 0 ? void 0 : result.context.stage) === _processContext.default.ProcessStage.RUNNING && !result.context.canceled) { return result.process; } return new Promise((_resolve, _reject)=>{ var _options_stopPromise; let cleanup = ()=>{ this._events.off(`started:${id}`, resolve); this._events.off(`canceled:${id}`, processCanceledListener); clearTimeout(timeout); return true; }; let resolve = (value)=>cleanup() && _resolve(value); let reject = (err)=>cleanup() && _reject(err); let processCanceledListener = ()=>setImmediate(()=>{ if (!this._scheduledProcesses[id]) { (options === null || options === void 0 ? void 0 : options.throwIfNotScheduled) ? reject(new Error("Process is not scheduled")) : resolve(undefined); } }); this._events.on(`started:${id}`, resolve); this._events.on(`canceled:${id}`, processCanceledListener); let timeout; if (!isNaN(options === null || options === void 0 ? void 0 : options.timeoutInMs)) { timeout = setTimeout(()=>{ options.throwOnTimeout ? reject(new _timeoutError.default("Timed out waiting for the process")) : resolve(undefined); }, options.timeoutInMs); } options === null || options === void 0 ? void 0 : (_options_stopPromise = options.stopPromise) === null || _options_stopPromise === void 0 ? void 0 : _options_stopPromise.then(()=>resolve(undefined)).catch((err)=>reject(err)); }); } /** * Cancels all currently scheduled processes and waits until they stop * @returns promise resolving when stopped */ async cancelAll() { await Promise.all(Object.keys(this._scheduledProcesses).map((id)=>this.cancelProcess(id, { allUsages: true }))); } /** * Stops all processes * @returns promise resolving when stopped */ async stop() { this._stopped = true; await this.cancelAll(); await Promise.all(Object.values(this._runtimeProcesses).map((runtime)=>runtime.stopPromise)); } async _run(id) { if (this._runtimeProcesses[id]) { return; } while(this._scheduledProcesses[id]){ try { let expected = this._scheduledProcesses[id]; let basicContext = { processId: id, pool: this, stage: _processContext.default.ProcessStage.STARTING }; let { process, context, args } = this._processProvider(basicContext, expected.args); let runtime = this._runtimeProcesses[id] = { id: id, process, context: basicContext, cancelPromise: _helpers.createHandlePromise(), stopPromise: _helpers.createHandlePromise(), startPromise: undefined }; process.inject(...this._dependencies); process.initialize(...args); context.initialize(process); this._processRuntimes.set(runtime.process, runtime); this._events.emit(`created:${id}`); try { this._logger.debug(`${this._label}: starting process ${id}`); await (runtime.startPromise = _helpers.wrapHandlePromise(_helpers.ensurePromise(()=>runtime.process.start(runtime.cancelPromise)).then(()=>{ var _expected_nextPostProcessThrottling; return (_expected_nextPostProcessThrottling = expected.nextPostProcessThrottling) === null || _expected_nextPostProcessThrottling === void 0 ? void 0 : _expected_nextPostProcessThrottling.call(expected, true); }).catch((err)=>{ var _expected_nextPostProcessThrottling; (_expected_nextPostProcessThrottling = expected.nextPostProcessThrottling) === null || _expected_nextPostProcessThrottling === void 0 ? void 0 : _expected_nextPostProcessThrottling.call(expected, false); throw err; }).finally(()=>delete expected.nextPostProcessThrottling))); if (!runtime.cancelPromise.completed) { basicContext.stage = _processContext.default.ProcessStage.RUNNING; this._logger.debug(`${this._label}: running process ${id}`); this._events.emit(`started:${id}`, runtime.process); this._events.emit("started", runtime.process); await runtime.process.run(runtime.cancelPromise); } basicContext.stage = _processContext.default.ProcessStage.STOPPING; await this._handleControlSignal(id, expected, runtime, runtime.cancelPromise.completed ? { action: "stop", message: "process ceased to run gracefully", severity: "debug" } : { action: "failover", message: "process ceased to run unexpectedly" }); } catch (err) { basicContext.stage = _processContext.default.ProcessStage.STOPPING; err instanceof _controlSignal.default ? await this._handleControlSignal(id, expected, runtime, err.options) : await this._handleControlSignal(id, expected, runtime, { error: err, message: "failed to run process" }); } finally{ basicContext.stage = _processContext.default.ProcessStage.STOPPED; context.release(); runtime.stopPromise.resolve(); delete this._runtimeProcesses[id]; this._processRuntimes.delete(runtime.process); } } catch (err) { this._logger.fatal(`${this._label}: failed to prepare process ${id} instance, it will be canceled`, err); this.cancelProcess(id, { allUsages: true }); } } } // eslint-disable-next-line complexity async _handleControlSignal(id, schedulement, runtime, signal = {}) { const action = signal.action || "failover"; const cancel = action === "cancel" && this._scheduledProcesses[id] === schedulement; const failover = action === "failover" && !!this._scheduledProcesses[id]; const restart = action === "stop" && !!this._scheduledProcesses[id]; const forceFailover = failover && runtime.cancelPromise.completed; const throttleDelayInMs = failover && !forceFailover ? this._getThrottleDelay(schedulement, runtime) : undefined; let message = _lodash.default.compact([ signal.message ? `${this._label}: process ${id}: ${signal.message}` : `${this._label}: process ${id} completed with a ${signal.action} control signal`, cancel && "The process will be canceled", failover && `The process will be failovered in ${_format.simplifyTimeAmount(throttleDelayInMs || 0, "ms").description}`, restart && "The process will be restarted" ]).join(". "); if (signal.severity) { signal.error ? this._logger[signal.severity](message + ".", signal.error) : this._logger[signal.severity](message); } else if (signal.error) { this._logger.error(message + ".", signal.error); } else if (action === "failover") { this._logger.warn(message); } else { this._logger.info(message); } if (cancel) { this.cancelProcess(id, { allUsages: true }); } await this._stopProcess(id, runtime, failover, throttleDelayInMs); } // eslint-disable-next-line complexity _getThrottleDelay(schedulement, runtime) { var _this__options, _schedulement_throttlings, _schedulement_throttlings1; var _this__options_processFailoverThrottleDelayInMs; let options = schedulement.failoverThrottleDelay || { mode: "fixed", delayInMs: (_this__options_processFailoverThrottleDelayInMs = (_this__options = this._options) === null || _this__options === void 0 ? void 0 : _this__options.processFailoverThrottleDelayInMs) !== null && _this__options_processFailoverThrottleDelayInMs !== void 0 ? _this__options_processFailoverThrottleDelayInMs : _convert.time.secondsToMs(10) }; if (options.mode === "fixed") { var _options_randomizationFactor; return _random.getIntegerAround(options.delayInMs, (_options_randomizationFactor = options.randomizationFactor) !== null && _options_randomizationFactor !== void 0 ? _options_randomizationFactor : 0); } var _options_resetDelayInMs; if (((_schedulement_throttlings = schedulement.throttlings) === null || _schedulement_throttlings === void 0 ? void 0 : _schedulement_throttlings.lastSuccessfulConnectTime) !== undefined && Date.now() - schedulement.throttlings.lastSuccessfulConnectTime >= ((_options_resetDelayInMs = options.resetDelayInMs) !== null && _options_resetDelayInMs !== void 0 ? _options_resetDelayInMs : 0)) { delete schedulement.throttlings; } schedulement.nextPostProcessThrottling = (successfulStart)=>{ var _schedulement_throttlings; return schedulement.throttlings = { counter: (((_schedulement_throttlings = schedulement.throttlings) === null || _schedulement_throttlings === void 0 ? void 0 : _schedulement_throttlings.counter) || 0) + (runtime.cancelPromise.completed ? 0 : 1), lastSuccessfulConnectTime: successfulStart ? Date.now() : undefined }; }; var _options_randomizationFactor1; return _random.getIntegerAround(_helpers.expBackoffDelay((((_schedulement_throttlings1 = schedulement.throttlings) === null || _schedulement_throttlings1 === void 0 ? void 0 : _schedulement_throttlings1.counter) || 0) + 1, Math.max(options.minDelayInMs, 1), options.maxDelayInMs), (_options_randomizationFactor1 = options.randomizationFactor) !== null && _options_randomizationFactor1 !== void 0 ? _options_randomizationFactor1 : 0); } async _stopProcess(id, runtime, failover, throttleDelayInMs) { this._logger.debug(`${this._label}: stopping process ${id}`); await runtime.process.stop().then(()=>this._logger.debug(`${this._label}: process ${id} stopped`)).catch((e)=>this._logger.warn(`${this._label}: failed to stop process ${id} properly`, e)); if (throttleDelayInMs) { let delay = _helpers.delay(throttleDelayInMs); await Promise.race([ delay, runtime.cancelPromise ]); delay.cancel(); } if (failover && runtime.cancelPromise.completed) { this._scheduledProcesses[id] ? this._logger.info(`${this._label}: forcing process ${id} failover`) : this._logger.info(`${this._label}: canceling process ${id} failover as it was canceled`); } } /** * Constructs instance * @param provider process provider * @param options additional options */ constructor(provider, options){ _define_property(this, "_logger", _logger.default.getLogger("AsyncProcessPool")); _define_property(this, "_processProvider", void 0); _define_property(this, "_scheduledProcesses", {}); _define_property(this, "_runtimeProcesses", {}); _define_property(this, "_processRuntimes", new Map()); _define_property(this, "_options", void 0); _define_property(this, "_events", new _eventEmitter.default()); _define_property(this, "_label", void 0); _define_property(this, "_stopped", false); _define_property(this, "_usages", new _usageCounter.default()); _define_property(this, "_dependencies", void 0); this._processProvider = provider; this._label = (options === null || options === void 0 ? void 0 : options.label) || "default"; this._options = options; this._dependencies = options.dependencies; } }; const _default = AsyncProcessPool; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCAqIGFzIGNvbnZlcnQgZnJvbSAnLi4vLi4vLi4vLi4vaGVscGVycy9jb252ZXJ0JztcbmltcG9ydCAqIGFzIGhlbHBlcnMgZnJvbSAnLi4vLi4vLi4vLi4vaGVscGVycy9oZWxwZXJzJztcbmltcG9ydCAqIGFzIHJhbmRvbSBmcm9tICcuLi8uLi8uLi8uLi9oZWxwZXJzL3JhbmRvbSc7XG5pbXBvcnQgKiBhcyBmb3JtYXQgZnJvbSAnLi4vLi4vLi4vLi4vaGVscGVycy9mb3JtYXQnO1xuaW1wb3J0IEFzeW5jUHJvY2VzcyBmcm9tICcuL2FzeW5jUHJvY2Vzcyc7XG5pbXBvcnQgXyBmcm9tICdsb2Rhc2gnO1xuaW1wb3J0IENvbnRyb2xTaWduYWwgZnJvbSAnLi4vY29udHJvbFNpZ25hbCc7XG5pbXBvcnQge0RpY3R9IGZyb20gJy4uLy4uLy4uLy4uL3R5cGVzL3V0aWwnO1xuaW1wb3J0IFVzYWdlQ291bnRlciBmcm9tICcuLi8uLi90b29scy91c2FnZUNvdW50ZXInO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vLi4vLi4vLi4vbG9nZ2VyJztcbmltcG9ydCBFdmVudEVtaXR0ZXIgZnJvbSAnLi4vLi4vLi4vLi4vdG9vbHMvZXZlbnRFbWl0dGVyJztcbmltcG9ydCBUaW1lb3V0RXJyb3IgZnJvbSAnLi4vLi4vLi4vLi4vY2xpZW50cy90aW1lb3V0RXJyb3InO1xuXG4vKipcbiAqIFN0YXJ0cywgc3RvcHMsIHJlc3RhcnRzIGFuZCBwcm92aWRlcyBsb25nLXJ1bm5pbmcgYmFja2dyb3VuZCBwcm9jZXNzZXMgKHJ1bm5pbmcgZnVuY3Rpb25zIHdpdGhpbiBub2RlIHByb2Nlc3MpXG4gKiByZXNvbHZpbmcgcmFjZSBjb25kaXRpb25zIGFuZCBpbmNsdWRpbmcgcHJvY2VzcyBmYWlsb3ZlcnMgdW50aWwgcHJvY2VzcyBjYW5jZWxlZFxuICovXG5jbGFzcyBBc3luY1Byb2Nlc3NQb29sPFxuICBQcm92aWRlciAvKiBleHRlbmRzIEFzeW5jUHJvY2Vzc1Bvb2wuUHJvY2Vzc1Byb3ZpZGVyPEFzeW5jUHJvY2Vzcz4gKi8sXG4gIFByb2Nlc3MgZXh0ZW5kcyBBc3luY1Byb2Nlc3Ncbj4ge1xuXG4gIHByb3RlY3RlZCBfbG9nZ2VyID0gTG9nZ2VyTWFuYWdlci5nZXRMb2dnZXIoJ0FzeW5jUHJvY2Vzc1Bvb2wnKTtcbiAgcHJpdmF0ZSBfcHJvY2Vzc1Byb3ZpZGVyOiBQcm92aWRlcjtcbiAgcHJpdmF0ZSBfc2NoZWR1bGVkUHJvY2Vzc2VzOiBEaWN0PEV4cGVjdGVkUHJvY2VzczxQcm92aWRlcj4+ID0ge307XG4gIHByaXZhdGUgX3J1bnRpbWVQcm9jZXNzZXM6IERpY3Q8UnVudGltZVByb2Nlc3M+ID0ge307XG4gIHByaXZhdGUgX3Byb2Nlc3NSdW50aW1lcyA9IG5ldyBNYXA8QXN5bmNQcm9jZXNzLCBSdW50aW1lUHJvY2Vzcz4oKTtcbiAgcHJpdmF0ZSBfb3B0aW9ucz86IEFzeW5jUHJvY2Vzc1Bvb2wuT3B0aW9uczxQcm9jZXNzPjtcbiAgcHJpdmF0ZSBfZXZlbnRzID0gbmV3IEV2ZW50RW1pdHRlcjxBc3luY1Byb2Nlc3NQb29sLkV2ZW50czxQcm9jZXNzPj4oKTtcbiAgcHJpdmF0ZSBfbGFiZWw6IHN0cmluZztcbiAgcHJpdmF0ZSBfc3RvcHBlZCA9IGZhbHNlO1xuICBwcml2YXRlIF91c2FnZXMgPSBuZXcgVXNhZ2VDb3VudGVyKCk7XG4gIHByaXZhdGUgX2RlcGVuZGVuY2llczogYW55W107XG5cbiAgLyoqXG4gICAqIENvbnN0cnVjdHMgaW5zdGFuY2VcbiAgICogQHBhcmFtIHByb3ZpZGVyIHByb2Nlc3MgcHJvdmlkZXJcbiAgICogQHBhcmFtIG9wdGlvbnMgYWRkaXRpb25hbCBvcHRpb25zXG4gICAqL1xuICBjb25zdHJ1Y3Rvcihwcm92aWRlcjogUHJvdmlkZXIsIG9wdGlvbnM6IEFzeW5jUHJvY2Vzc1Bvb2wuT3B0aW9uczxQcm9jZXNzPikge1xuICAgIHRoaXMuX3Byb2Nlc3NQcm92aWRlciA9IHByb3ZpZGVyO1xuICAgIHRoaXMuX2xhYmVsID0gb3B0aW9ucz8ubGFiZWwgfHwgJ2RlZmF1bHQnO1xuICAgIHRoaXMuX29wdGlvbnMgPSBvcHRpb25zO1xuICAgIHRoaXMuX2RlcGVuZGVuY2llcyA9IG9wdGlvbnMuZGVwZW5kZW5jaWVzO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgbGFiZWwgZm9yIGxvZ2dpbmdcbiAgICogQHJldHVybnMgbGFiZWxcbiAgICovXG4gIGdldCBsYWJlbCgpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLl9sYWJlbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBFdmVudCBlbWl0dGVyIHJlbGF0ZWQgdG8gdGhpcyBwb29sXG4gICAqIEByZXR1cm5zIGV2ZW50IGVtaXR0ZXJcbiAgICogQGludGVybmFsXG4gICAqL1xuICBnZXQgZXZlbnRzKCk6IEV2ZW50RW1pdHRlcjxBc3luY1Byb2Nlc3NQb29sLkV2ZW50czxQcm9jZXNzPj4ge1xuICAgIHJldHVybiB0aGlzLl9ldmVudHM7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB3aGV0aGVyIHRoZSBzY2hlZHVsZXIgd2FzIGdpdmVuIGNvbW1hbmQgdG8gc3RvcFxuICAgKiBAcmV0dXJucyB3aGV0aGVyIHN0b3BwZWRcbiAgICovXG4gIGdldCBzdG9wcGVkKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLl9zdG9wcGVkO1xuICB9XG5cbiAgLyoqXG4gICAqIFNjaGVkdWxlcyBhIHByb2Nlc3MuIEFmdGVyIHRoZSBwcm9jZXNzIGhhcyBzY2hlZHVsZWQsIGl0IGlzIGltbWVkaWF0ZWx5IGF2YWlsYWJsZSBpbiBzeW5jaHJvbm91cyB3YXkuIEJ1dCBpdCBtYXlcbiAgICogbm90IGJlIHN0YXJ0ZWQgZnVsbHkgeWV0IChpdHMgYHN0YXJ0YCBtZXRob2Qgbm90IGNvbXBsZXRlZCB5ZXQpLCBvciBjdXJyZW50bHkgcnVubmluZyBwcm9jZXNzIG1heSBiZSBhbiBvbGQgb25lLFxuICAgKiB3aGljaCBpcyBnb2luZyB0byBzdG9wLiBUaGUgbGF0ZXN0IGFjdHVhbCBhbmQgZnVsbHkgc3RhcnRlZCBwcm9jZXNzIGNhbiBiZSBhd2FpdGVkIHdpdGggYHdhaXRQcm9jZXNzYCBtZXRob2RcbiAgICogQHBhcmFtIGlkIHByb2Nlc3MgSUQuIElmIGEgcHJvY2VzcyB3aXRoIHNhbWUgSUQgaXMgYWxyZWFkeSBzY2hlZHVsZWQsIHRoZSBjYWxsIHdpbGwgYmUgaWdub3JlZFxuICAgKiBAcGFyYW0gb3B0aW9ucyBwcm9jZXNzIG9wdGlvbnNcbiAgICogQHRocm93cyBpZiB0aGUgcG9vbCBpcyBzdG9wcGVkIGFuZCB0aGUgYHRocm93SWZTdG9wcGVkYCBvcHRpb24gaXMgZW5hYmxlZFxuICAgKi9cbiAgc2NoZWR1bGVQcm9jZXNzKGlkOiBzdHJpbmcsIG9wdGlvbnM6IEFzeW5jUHJvY2Vzc1Bvb2wuU2NoZWR1bGVQcm9jZXNzT3B0aW9uczxQcm92aWRlcj4pIHtcbiAgICBpZiAoaWQuaW5jbHVkZXMoJzonKSkge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignUHJvY2VzcyBJRCBtdXN0IG5vdCBjb250YWluIGFueSBjb2xvbnMnKTtcbiAgICB9XG4gICAgY29uc3QgdXNhZ2UgPSBvcHRpb25zLnVzYWdlIHx8ICdkZWZhdWx0JztcbiAgICBpZiAodGhpcy5fc3RvcHBlZCkge1xuICAgICAgaWYgKG9wdGlvbnMudGhyb3dJZlN0b3BwZWQgPz8gdHJ1ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0FzeW5jIHBvb2wgc3RvcHBlZCcpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2xhYmVsfTogd29uJ3Qgc2NoZWR1bGVkIHByb2Nlc3MgJHtpZH0gYmVjYXVzZSB0aGUgcG9vbCBpcyBzdG9wcGVkYCk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2xhYmVsfTogc2NoZWR1bGluZyBwcm9jZXNzICR7aWR9IGJ5IHVzYWdlICR7dXNhZ2V9YCk7XG4gICAgdGhpcy5fdXNhZ2VzLmFjcXVpcmUoaWQsIHVzYWdlKTtcbiAgICBpZiAodGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXSkge1xuICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2xhYmVsfTogd29uJ3Qgc2NoZWR1bGUgcHJvY2VzcyAke2lkfSBiZWNhdXNlIGl0IGlzIGFscmVhZHkgc2NoZWR1bGVkYCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuX2xvZ2dlci5pbmZvKGAke3RoaXMuX2xhYmVsfTogc2NoZWR1bGluZyBwcm9jZXNzICR7aWR9YCk7XG4gICAgdGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXSA9IHtcbiAgICAgIGFyZ3M6IG9wdGlvbnMuYXJncyxcbiAgICAgIGZhaWxvdmVyVGhyb3R0bGVEZWxheTogb3B0aW9ucy5mYWlsb3ZlclRocm90dGxlRGVsYXlcbiAgICB9O1xuICAgIHRoaXMuX3J1bihpZCk7XG4gIH1cblxuICAvKipcbiAgICogUmVzdGFydHMgYSBwcm9jZXNzIGlmIGl0IGlzIHNjaGVkdWxlZCwgb3RoZXJ3aXNlIHRoZSBjYWxsIHdpbGwgYmUgaWdub3JlZC4gSWYgdGhlIHByb2Nlc3MgaXMgd2FpdGluZyBmb3IgdGhyb3R0bGVcbiAgICogZGVsYXkgdG8gZmFpbG92ZXIgYWZ0ZXIgZXJyb3Igb3IgdW5leHBlY3RlZCBzdG9wLCB0aGlzIG1ldGhvZCB3aWxsIGZvcmNlIHRoZSBmYWlsb3ZlclxuICAgKiBAcGFyYW0gcHJvY2VzcyBwcm9jZXNzIElEIG9yIHByb2Nlc3MgaXRzZWxmLiBJZiB0aGUgcHJvY2VzcyBpbnN0YW5jZSBpcyBzcGVjaWZpZWQsIGl0IHdpbGwgYmUgcmVzdGFydGVkIG9ubHkgaWYgaXRcbiAgICogaXMgc3RpbGwgYWN0dWFsIHJ1bm5pbmcgcHJvY2VzcyBhbmQgbm90IHNjaGVkdWxlZCB0byBiZSByZXN0YXJ0ZWQgeWV0XG4gICAqL1xuICByZXN0YXJ0UHJvY2Vzcyhwcm9jZXNzOiBzdHJpbmcgfCBQcm9jZXNzKSB7XG4gICAgaWYgKHR5cGVvZiBwcm9jZXNzID09PSAnc3RyaW5nJykge1xuICAgICAgaWYgKHRoaXMuX3J1bnRpbWVQcm9jZXNzZXNbcHJvY2Vzc10pIHtcbiAgICAgICAgdGhpcy5fbG9nZ2VyLmluZm8oYCR7dGhpcy5fbGFiZWx9OiByZXN0YXJ0aW5nIHByb2Nlc3MgJHtwcm9jZXNzfWApO1xuICAgICAgICB0aGlzLl9jYW5jZWxQcm9jZXNzKHRoaXMuX3J1bnRpbWVQcm9jZXNzZXNbcHJvY2Vzc10pO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBsZXQgcnVudGltZSA9IHRoaXMuX3Byb2Nlc3NSdW50aW1lcy5nZXQocHJvY2Vzcyk7XG4gICAgICBpZiAocnVudGltZT8ucHJvY2VzcyA9PT0gcHJvY2Vzcykge1xuICAgICAgICB0aGlzLl9sb2dnZXIuaW5mbyhgJHt0aGlzLl9sYWJlbH06IHJlc3RhcnRpbmcgcHJvY2VzcyAke3J1bnRpbWUuaWR9IGluc3RhbmNlYCk7XG4gICAgICAgIHRoaXMuX2NhbmNlbFByb2Nlc3MocnVudGltZSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENhbmNlbHMgYSBwcm9jZXNzXG4gICAqIEBwYXJhbSBpZCBwcm9jZXNzIElEXG4gICAqIEBwYXJhbSBvcHRpb25zIGFkZGl0aW9uYWwgb3B0aW9uc1xuICAgKiBAcmV0dXJucyBwcm9taXNlIHJlc29sdmluZyB3aGVuIGN1cnJlbnRseSBydW5uaW5nIHByb2Nlc3Mgc3RvcHBlZFxuICAgKi9cbiAgYXN5bmMgY2FuY2VsUHJvY2VzcyhpZDogc3RyaW5nLCBvcHRpb25zPzogQXN5bmNQcm9jZXNzUG9vbC5DYW5jZWxQcm9jZXNzT3B0aW9ucykge1xuICAgIGNvbnN0IHVzYWdlID0gb3B0aW9ucz8udXNhZ2UgfHwgJ2RlZmF1bHQnO1xuICAgIGlmICghdGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXSkge1xuICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2xhYmVsfTogd29uJ3QgY2FuY2VsIHByb2Nlc3MgJHtpZH0gYmVjYXVzZSBpdCBpcyBub3Qgc2NoZWR1bGVkYCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChvcHRpb25zPy5hbGxVc2FnZXMpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHt0aGlzLl9sYWJlbH06IHJlbGVhc2luZyBwcm9jZXNzICR7aWR9IGJ5IGFsbCB1c2FnZXNgKTtcbiAgICAgIHRoaXMuX3VzYWdlcy5yZWxlYXNlQWxsKGlkKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2xhYmVsfTogcmVsZWFzaW5nIHByb2Nlc3MgJHtpZH0gYnkgdXNhZ2UgJHt1c2FnZX1gKTtcbiAgICAgIHRoaXMuX3VzYWdlcy5yZWxlYXNlKGlkLCB1c2FnZSk7XG4gICAgfVxuICAgIGlmICh0aGlzLl91c2FnZXMuaXNJblVzZShpZCkpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHt0aGlzLl9sYWJlbH06IHdvbid0IGNhbmNlbCBwcm9jZXNzICR7aWR9IHlldCBhcyBpdCBpcyBzdGlsbCBpbiB1c2VgKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5fbG9nZ2VyLmluZm8oYCR7dGhpcy5fbGFiZWx9OiBjYW5jZWxpbmcgcHJvY2VzcyAke2lkfWApO1xuICAgIGRlbGV0ZSB0aGlzLl9zY2hlZHVsZWRQcm9jZXNzZXNbaWRdO1xuICAgIGxldCBydW50aW1lID0gdGhpcy5fcnVudGltZVByb2Nlc3Nlc1tpZF07XG4gICAgaWYgKHJ1bnRpbWUgJiYgIXJ1bnRpbWUuY2FuY2VsUHJvbWlzZS5jb21wbGV0ZWQpIHtcbiAgICAgIHRoaXMuX2NhbmNlbFByb2Nlc3MocnVudGltZSk7XG4gICAgICB0aGlzLl9ldmVudHMuZW1pdChgY2FuY2VsZWQ6JHtpZH1gKTtcbiAgICB9XG4gICAgcmV0dXJuIHJ1bnRpbWU/LnN0b3BQcm9taXNlO1xuICB9XG5cbiAgcHJpdmF0ZSBfY2FuY2VsUHJvY2VzcyhydW50aW1lOiBSdW50aW1lUHJvY2Vzcykge1xuICAgIHJ1bnRpbWUuY29udGV4dC5jYW5jZWxlZCA9IHRydWU7XG4gICAgcnVudGltZS5jYW5jZWxQcm9taXNlLnJlc29sdmUoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHNjaGVkdWxlZCBwcm9jZXNzIElEc1xuICAgKiBAcmV0dXJucyBwcm9jZXNzIElEc1xuICAgKi9cbiAgZ2V0U2NoZWR1bGVkSWRzKCk6IHN0cmluZ1tdIHtcbiAgICByZXR1cm4gT2JqZWN0LmtleXModGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHJ1bm5pbmcgYXQgdGhpcyBtb21lbnQgcHJvY2VzcyBJRHMuIEl0IHdpbGwgYWx3YXlzIGluY2x1ZGUgc2NoZWR1bGVkIElEcyArIHNvbWUgcHJvY2Vzc2VzIHRoYXQgd2FzIGNhbmNlbGVkXG4gICAqIGJ1dCBtYXkgYmUgc3RpbGwgcnVubmluZyBvciBzdG9wcGluZ1xuICAgKiBAcmV0dXJucyBwcm9jZXNzIElEc1xuICAgKi9cbiAgZ2V0UnVubmluZ0lkcygpOiBzdHJpbmdbXSB7XG4gICAgcmV0dXJuIE9iamVjdC5rZXlzKHRoaXMuX3J1bnRpbWVQcm9jZXNzZXMpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgd2hldGhlciBoYXMgc2NoZWR1bGVkIHByb2Nlc3NcbiAgICogQHBhcmFtIGlkIHByb2Nlc3MgSURcbiAgICogQHJldHVybnMgdHJ1ZSBpZiBzY2hlZHVsZWRcbiAgICovXG4gIGhhc1NjaGVkdWxlZChpZDogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICEhdGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHdoZXRoZXIgaGFzIGEgcHJvY2Vzcywgc2NoZWR1bGVkIGJ5IHNwZWNpZmljIHVzYWdlXG4gICAqIEBwYXJhbSBpZCBwcm9jZXNzIElEXG4gICAqIEBwYXJhbSB1c2FnZSB1c2FnZVxuICAgKiBAcmV0dXJucyB0cnVlIGlmIHNjaGVkdWxlZCBieVxuICAgKi9cbiAgaGFzU2NoZWR1bGVkQnkoaWQ6IHN0cmluZywgdXNhZ2U6IGFueSk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLl91c2FnZXMuaXNBY3F1aXJlZEJ5KGlkLCB1c2FnZSk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBwcm9jZXNzIElEcyB3aGljaCBhcmUgc2NoZWR1bGVkIGJ5IHNwZWNpZmljIHVzYWdlXG4gICAqIEBwYXJhbSB1c2FnZSB1c2FnZVxuICAgKiBAcmV0dXJucyBwcm9jZXNzIElEc1xuICAgKi9cbiAgZ2V0U2NoZWR1bGVkQnkodXNhZ2U6IGFueSk6IHN0cmluZ1tdIHtcbiAgICByZXR1cm4gdGhpcy5fdXNhZ2VzLmdldEFjcXVpcmVkQnkodXNhZ2UpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgcHJvY2VzcyBzY2hlZHVsZW1lbnRcbiAgICogQHBhcmFtIGlkIHByb2Nlc3MgSURcbiAgICogQHJldHVybnMgc2NoZWR1bGVtZW50IG9yIHVuZGVmaW5lZCBpZiBub3Qgc2NoZWR1bGVkXG4gICAqL1xuICBnZXRTY2hlZHVsZW1lbnQoaWQ6IHN0cmluZyk6IEFzeW5jUHJvY2Vzc1Bvb2wuU2NoZWR1bGVtZW50PFByb3ZpZGVyPiB8IHVuZGVmaW5lZCB7XG4gICAgaWYgKCF0aGlzLl9zY2hlZHVsZWRQcm9jZXNzZXNbaWRdKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBvcHRpb25zOiB0aGlzLl9zY2hlZHVsZWRQcm9jZXNzZXNbaWRdLFxuICAgICAgdXNhZ2VzOiB0aGlzLl91c2FnZXMuZ2V0VXNhZ2VzKGlkKVxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBjdXJyZW50IHByb2Nlc3MgaW4gaXRzIHJ1bm5pbmcgc3RhdGUgd2hpY2ggY2FuIGJlIG5vdCBzdGFydGVkIHlldCwgc3RhcnRpbmcsIHJ1bm5pbmcsIHN0b3BwaW5nIG9yIHN0b3BwZWRcbiAgICogQHBhcmFtIGlkIHByb2Nlc3MgSURcbiAgICogQHJldHVybnMgcHJvY2VzcyBpbiBpdHMgc3RhdGUgb3IgdW5kZWZpbmVkIGlmIHRoZXJlIGlzIG5vIHN1Y2ggcHJvY2VzcyBzY2hlZHVsZWRcbiAgICovXG4gIGdldFByb2Nlc3MoaWQ6IHN0cmluZyk6IFByb2Nlc3MgfCB1bmRlZmluZWQge1xuICAgIHJldHVybiB0aGlzLl9ydW50aW1lUHJvY2Vzc2VzW2lkXT8ucHJvY2VzcyBhcyBQcm9jZXNzO1xuICB9XG5cbiAgLyoqXG4gICAqIFdhaXRzIGZvciBydW5uaW5nIHN0YXJ0ZWQgcHJvY2VzcyBpbnN0YW5jZS4gSWYgdGhlIHByb2Nlc3Mgd2FzIHJlc2NoZWR1bGVkL3Jlc3RhcnRlZCBhbmQgYW4gb2xkIHByb2Nlc3MgaW5zdGFuY2UgaXNcbiAgICogc3RpbGwgcnVubmluZywgdGhlIG1ldGhvZCB3aWxsIHdhaXQgZm9yIHRoZSBuZXcgcHJvY2VzcyBpbnN0YW5jZSB0byBzdGFydCBvbmx5LiBOb3RlLCB0aGF0IGEgcHJvY2VzcyBjYW4gYmVjb21lXG4gICAqIHN0b3BwZWQgb3IgY2FuY2VsZWQgdGlsbCB0aGUgbWV0aG9kIHJlc29sdmVzIGFmdGVyIGBhd2FpdGAgaW4gdGhlIGNhbGxpbmcgY29kZS4gU28gdGhpcyBtZXRob2Qgb25seSBpbmNyZWFzZXNcbiAgICogcHJvYmFiaWxpdHkgdGhlIHJldHVybmVkIHByb2Nlc3Mgd2lsbCBiZSBydW5uaW5nXG4gICAqIEBwYXJhbSBpZCBwcm9jZXNzIElEXG4gICAqIEBwYXJhbSBvcHRpb25zIGFkZGl0aW9uYWwgb3B0aW9uc1xuICAgKiBAcmV0dXJucyBwcm9taXNlIHJlc29sdmluZyB3aXRoIHByb2Nlc3Mgb3IgYHVuZGVmaW5lZGAsIGRlcGVuZGluZyBvbiBzcGVjaWZpZWQgb3B0aW9uc1xuICAgKiBAdGhyb3dzIGlmIHByb2Nlc3MgaXMgbm90IHNjaGVkdWxlZCBvciBiZWNvbWVzIHVuc2NoZWR1bGVkIGR1cmluZyB3YWl0aW5nIGR1ZSB0byBgdGhyb3dJZk5vdFNjaGVkdWxlZGAgb3B0aW9uXG4gICAqIEB0aHJvd3MgYFRpbWVvdXRFcnJvcmAgaWYgdGltZWQgb3V0IHdhaXRpbmcgZm9yIHRoZSBwcm9jZXNzIGR1ZSB0byB0aW1lb3V0IG9wdGlvbnNcbiAgICovXG4gIGFzeW5jIHdhaXRQcm9jZXNzKGlkOiBzdHJpbmcsIG9wdGlvbnM/OiBBc3luY1Byb2Nlc3NQb29sLldhaXRQcm9jZXNzT3B0aW9ucyk6IFByb21pc2U8UHJvY2Vzcz4ge1xuICAgIGlmICghdGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXSkge1xuICAgICAgaWYgKG9wdGlvbnM/LnRocm93SWZOb3RTY2hlZHVsZWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdQcm9jZXNzIGlzIG5vdCBzY2hlZHVsZWQnKTtcbiAgICAgIH1cbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IHJlc3VsdCA9IHRoaXMuX3J1bnRpbWVQcm9jZXNzZXNbaWRdO1xuICAgIGlmIChyZXN1bHQ/LmNvbnRleHQuc3RhZ2UgPT09IFByb2Nlc3NDb250ZXh0LlByb2Nlc3NTdGFnZS5SVU5OSU5HICYmICFyZXN1bHQuY29udGV4dC5jYW5jZWxlZCkge1xuICAgICAgcmV0dXJuIHJlc3VsdC5wcm9jZXNzIGFzIFByb2Nlc3M7XG4gICAgfVxuICAgIHJldHVybiBuZXcgUHJvbWlzZTxQcm9jZXNzPigoX3Jlc29sdmUsIF9yZWplY3QpID0+IHtcbiAgICAgIGxldCBjbGVhbnVwID0gKCkgPT4ge1xuICAgICAgICB0aGlzLl9ldmVudHMub2ZmKGBzdGFydGVkOiR7aWR9YCwgcmVzb2x2ZSk7XG4gICAgICAgIHRoaXMuX2V2ZW50cy5vZmYoYGNhbmNlbGVkOiR7aWR9YCwgcHJvY2Vzc0NhbmNlbGVkTGlzdGVuZXIpO1xuICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfTtcbiAgICAgIGxldCByZXNvbHZlID0gKHZhbHVlOiBQcm9jZXNzKSA9PiBjbGVhbnVwKCkgJiYgX3Jlc29sdmUodmFsdWUpO1xuICAgICAgbGV0IHJlamVjdCA9IChlcnI6IEVycm9yKSA9PiBjbGVhbnVwKCkgJiYgX3JlamVjdChlcnIpO1xuICAgICAgbGV0IHByb2Nlc3NDYW5jZWxlZExpc3RlbmVyID0gKCkgPT4gc2V0SW1tZWRpYXRlKCgpID0+IHtcbiAgICAgICAgaWYgKCF0aGlzLl9zY2hlZHVsZWRQcm9jZXNzZXNbaWRdKSB7XG4gICAgICAgICAgb3B0aW9ucz8udGhyb3dJZk5vdFNjaGVkdWxlZCA/IHJlamVjdChuZXcgRXJyb3IoJ1Byb2Nlc3MgaXMgbm90IHNjaGVkdWxlZCcpKSA6IHJlc29sdmUodW5kZWZpbmVkKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgICB0aGlzLl9ldmVudHMub24oYHN0YXJ0ZWQ6JHtpZH1gLCByZXNvbHZlKTtcbiAgICAgIHRoaXMuX2V2ZW50cy5vbihgY2FuY2VsZWQ6JHtpZH1gLCBwcm9jZXNzQ2FuY2VsZWRMaXN0ZW5lcik7XG4gICAgICBsZXQgdGltZW91dDogTm9kZUpTLlRpbWVvdXQ7XG4gICAgICBpZiAoIWlzTmFOKG9wdGlvbnM/LnRpbWVvdXRJbk1zKSkge1xuICAgICAgICB0aW1lb3V0ID0gc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgb3B0aW9ucy50aHJvd09uVGltZW91dCA/IHJlamVjdChuZXcgVGltZW91dEVycm9yKCdUaW1lZCBvdXQgd2FpdGluZyBmb3IgdGhlIHByb2Nlc3MnKSkgOiByZXNvbHZlKHVuZGVmaW5lZCk7XG4gICAgICAgIH0sIG9wdGlvbnMudGltZW91dEluTXMpO1xuICAgICAgfVxuICAgICAgb3B0aW9ucz8uc3RvcFByb21pc2U/LnRoZW4oKCkgPT4gcmVzb2x2ZSh1bmRlZmluZWQpKS5jYXRjaChlcnIgPT4gcmVqZWN0KGVycikpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENhbmNlbHMgYWxsIGN1cnJlbnRseSBzY2hlZHVsZWQgcHJvY2Vzc2VzIGFuZCB3YWl0cyB1bnRpbCB0aGV5IHN0b3BcbiAgICogQHJldHVybnMgcHJvbWlzZSByZXNvbHZpbmcgd2hlbiBzdG9wcGVkXG4gICAqL1xuICBhc3luYyBjYW5jZWxBbGwoKSB7XG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoT2JqZWN0LmtleXModGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzKS5tYXAoaWQgPT4gdGhpcy5jYW5jZWxQcm9jZXNzKGlkLCB7YWxsVXNhZ2VzOiB0cnVlfSkpKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdG9wcyBhbGwgcHJvY2Vzc2VzXG4gICAqIEByZXR1cm5zIHByb21pc2UgcmVzb2x2aW5nIHdoZW4gc3RvcHBlZFxuICAgKi9cbiAgYXN5bmMgc3RvcCgpIHtcbiAgICB0aGlzLl9zdG9wcGVkID0gdHJ1ZTtcbiAgICBhd2FpdCB0aGlzLmNhbmNlbEFsbCgpO1xuICAgIGF3YWl0IFByb21pc2UuYWxsKE9iamVjdC52YWx1ZXModGhpcy5fcnVudGltZVByb2Nlc3NlcykubWFwKHJ1bnRpbWUgPT4gcnVudGltZS5zdG9wUHJvbWlzZSkpO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBfcnVuKGlkOiBzdHJpbmcpIHtcbiAgICBpZiAodGhpcy5fcnVudGltZVByb2Nlc3Nlc1tpZF0pIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgd2hpbGUgKHRoaXMuX3NjaGVkdWxlZFByb2Nlc3Nlc1tpZF0pIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGxldCBleHBlY3RlZCA9IHRoaXMuX3NjaGVkdWxlZFByb2Nlc3Nlc1tpZF07XG4gICAgICAgIGxldCBiYXNpY0NvbnRleHQ6IEFzeW5jUHJvY2Vzc1Bvb2wuQ29udGV4dCA9IHtcbiAgICAgICAgICBwcm9jZXNzSWQ6IGlkLFxuICAgICAgICAgIHBvb2w6IHRoaXMsXG4gICAgICAgICAgc3RhZ2U6IFByb2Nlc3NDb250ZXh0LlByb2Nlc3NTdGFnZS5TVEFSVElOR1xuICAgICAgICB9O1xuICAgICAgICBsZXQge3Byb2Nlc3MsIGNvbnRleHQsIGFyZ3N9ID0gKHRoaXMuX3Byb2Nlc3NQcm92aWRlciBhcyBBc3luY1Byb2Nlc3NQb29sLlByb2Nlc3NQcm92aWRlcjxBc3luY1Byb2Nlc3M+KShcbiAgICAgICAgICBiYXNpY0NvbnRleHQsIGV4cGVjdGVkLmFyZ3NcbiAgICAgICAgKTtcbiAgICAgICAgbGV0IHJ1bnRpbWU6IFJ1bnRpbWVQcm9jZXNzID0gdGhpcy5fcnVudGltZVByb2Nlc3Nlc1tpZF0gPSB7XG4gICAgICAgICAgaWQ6IGlkLFxuICAgICAgICAgIHByb2Nlc3MsXG4gICAgICAgICAgY29udGV4dDogYmFzaWNDb250ZXh0LFxuICAgICAgICAgIGNhbmNlbFByb21pc2U6IGhlbHBlcnMuY3JlYXRlSGFuZGxlUHJvbWlzZTx2b2lkPigpLFxuICAgICAgICAgIHN0b3BQcm9taXNlOiBoZWxwZXJzLmNyZWF0ZUhhbmRsZVByb21pc2UoKSxcbiAgICAgICAgICBzdGFydFByb21pc2U6IHVuZGVmaW5lZFxuICAgICAgICB9O1xuICAgICAgICBwcm9jZXNzLmluamVjdCguLi50aGlzLl9kZXBlbmRlbmNpZXMpO1xuICAgICAgICBwcm9jZXNzLmluaXRpYWxpemUoLi4uYXJncyk7XG4gICAgICAgIGNvbnRleHQuaW5pdGlhbGl6ZShwcm9jZXNzKTtcbiAgICAgICAgdGhpcy5fcHJvY2Vzc1J1bnRpbWVzLnNldChydW50aW1lLnByb2Nlc3MsIHJ1bnRpbWUpO1xuICAgICAgICB0aGlzLl9ldmVudHMuZW1pdChgY3JlYXRlZDoke2lkfWApO1xuICAgICAgICB0cnkge1xuICAgICAgICAgIHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHt0aGlzLl9sYWJlbH06IHN0YXJ0aW5nIHByb2Nlc3MgJHtpZH1gKTtcbiAgICAgICAgICBhd2FpdCAocnVudGltZS5zdGFydFByb21pc2UgPSBoZWxwZXJzLndyYXBIYW5kbGVQcm9taXNlKGhlbHBlcnNcbiAgICAgICAgICAgIC5lbnN1cmVQcm9taXNlKCgpID0+IHJ1bnRpbWUucHJvY2Vzcy5zdGFydChydW50aW1lLmNhbmNlbFByb21pc2UpKVxuICAgICAgICAgICAgLnRoZW4oKCkgPT4gZXhwZWN0ZWQubmV4dFBvc3RQcm9jZXNzVGhyb3R0bGluZz8uKHRydWUpKVxuICAgICAgICAgICAgLmNhdGNoKGVyciA9PiB7XG4gICAgICAgICAgICAgIGV4cGVjdGVkLm5leHRQb3N0UHJvY2Vzc1Rocm90dGxpbmc/LihmYWxzZSk7XG4gICAgICAgICAgICAgIHRocm93IGVycjtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAuZmluYWxseSgoKSA9PiBkZWxldGUgZXhwZWN0ZWQubmV4dFBvc3RQcm9jZXNzVGhyb3R0bGluZylcbiAgICAgICAgICApKTtcbiAgICAgICAgICBpZiAoIXJ1bnRpbWUuY2FuY2VsUHJvbWlzZS5jb21wbGV0ZWQpIHtcbiAgICAgICAgICAgIGJhc2ljQ29udGV4dC5zdGFnZSA9IFByb2Nlc3NDb250ZXh0LlByb2Nlc3NTdGFnZS5SVU5OSU5HO1xuICAgICAgICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2xhYmVsfTogcnVubmluZyBwcm9jZXNzICR7aWR9YCk7XG4gICAgICAgICAgICB0aGlzLl9ldmVudHMuZW1pdChgc3RhcnRlZDoke2lkfWAsIHJ1bnRpbWUucHJvY2VzcyBhcyBQcm9jZXNzKTtcbiAgICAgICAgICAgIHRoaXMuX2V2ZW50cy5lbWl0KCdzdGFydGVkJywgcnVudGltZS5wcm9jZXNzIGFzIFByb2Nlc3MpO1xuICAgICAgICAgICAgYXdhaXQgcnVudGltZS5wcm9jZXNzLnJ1bihydW50aW1lLmNhbmNlbFByb21pc2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBiYXNpY0NvbnRleHQuc3RhZ2UgPSBQcm9jZXNzQ29udGV4dC5Qcm9jZXNzU3RhZ2UuU1RPUFBJTkc7XG4gICAgICAgICAgYXdhaXQgdGhpcy5faGFuZGxlQ29udHJvbFNpZ25hbChpZCwgZXhwZWN0ZWQsIHJ1bnRpbWUsIHJ1bnRpbWUuY2FuY2VsUHJvbWlzZS5jb21wbGV0ZWQgP1xuICAgICAgICAgICAge2FjdGlvbjogJ3N0b3AnLCBtZXNzYWdlOiAncHJvY2VzcyBjZWFzZWQgdG8gcnVuIGdyYWNlZnVsbHknLCBzZXZlcml0eTogJ2RlYnVnJ30gOlxuICAgICAgICAgICAge2FjdGlvbjogJ2ZhaWxvdmVyJywgbWVzc2FnZTogJ3Byb2Nlc3MgY2Vhc2VkIHRvIHJ1biB1bmV4cGVjdGVkbHknfSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIGJhc2ljQ29udGV4dC5zdGFnZSA9IFByb2Nlc3NDb250ZXh0LlByb2Nlc3NTdGFnZS5TVE9QUElORztcbiAgICAgICAgICBlcnIgaW5zdGFuY2VvZiBDb250cm9sU2lnbmFsID9cbiAgICAgICAgICAgIGF3YWl0IHRoaXMuX2hhbmRsZUNvbnRyb2xTaWduYWwoaWQsIGV4cGVjdGVkLCBydW50aW1lLCBlcnIub3B0aW9ucykgOlxuICAgICAgICAgICAgYXdhaXQgdGhpcy5faGFuZGxlQ29udHJvbFNpZ25hbChpZCwgZXhwZWN0ZWQsIHJ1bnRpbWUsIHtlcnJvcjogZXJyLCBtZXNzYWdlOiAnZmFpbGVkIHRvIHJ1biBwcm9jZXNzJ30pO1xuICAgICAgICB9IGZpbmFsbHkge1xuICAgICAgICAgIGJhc2ljQ29udGV4dC5zdGFnZSA9IFByb2Nlc3NDb250ZXh0LlByb2Nlc3NTdGFnZS5TVE9QUEVEO1xuICAgICAgICAgIGNvbnRleHQucmVsZWFzZSgpO1xuICAgICAgICAgIHJ1bnRpbWUuc3RvcFByb21pc2UucmVzb2x2ZSgpO1xuICAgICAgICAgIGRlbGV0ZSB0aGlzLl9ydW50aW1lUHJvY2Vzc2VzW2lkXTtcbiAgICAgICAgICB0aGlzLl9wcm9jZXNzUnVudGltZXMuZGVsZXRlKHJ1bnRpbWUucHJvY2Vzcyk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICB0aGlzLl9sb2dnZXIuZmF0YWwoYCR7dGhpcy5fbGFiZWx9OiBmYWlsZWQgdG8gcHJlcGFyZSBwcm9jZXNzICR7aWR9IGluc3RhbmNlLCBpdCB3aWxsIGJlIGNhbmNlbGVkYCwgZXJyKTtcbiAgICAgICAgdGhpcy5jYW5jZWxQcm9jZXNzKGlkLCB7YWxsVXNhZ2VzOiB0cnVlfSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGNvbXBsZXhpdHlcbiAgcHJpdmF0ZSBhc3luYyBfaGFuZGxlQ29udHJvbFNpZ25hbChcbiAgICBpZDogc3RyaW5nLCBzY2hlZHVsZW1lbnQ6IEV4cGVjdGVkUHJvY2VzczxQcm92aWRlcj4sIHJ1bnRpbWU6IFJ1bnRpbWVQcm9jZXNzLFxuICAgIHNpZ25hbDogQ29udHJvbFNpZ25hbC5PcHRpb25zID0ge31cbiAgKSB7XG4gICAgY29uc3QgYWN0aW9uID0gc2lnbmFsLmFjdGlvbiB8fCAnZmFpbG92ZXInO1xuICAgIGNvbnN0IGNhbmNlbCA9IGFjdGlvbiA9PT0gJ2NhbmNlbCcgJiYgdGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXSA9PT0gc2NoZWR1bGVtZW50O1xuICAgIGNvbnN0IGZhaWxvdmVyID0gYWN0aW9uID09PSAnZmFpbG92ZXInICYmICEhdGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXTtcbiAgICBjb25zdCByZXN0YXJ0ID0gYWN0aW9uID09PSAnc3RvcCcgJiYgISF0aGlzLl9zY2hlZHVsZWRQcm9jZXNzZXNbaWRdO1xuICAgIGNvbnN0IGZvcmNlRmFpbG92ZXIgPSBmYWlsb3ZlciAmJiBydW50aW1lLmNhbmNlbFByb21pc2UuY29tcGxldGVkO1xuICAgIGNvbnN0IHRocm90dGxlRGVsYXlJbk1zID0gKGZhaWxvdmVyICYmICFmb3JjZUZhaWxvdmVyKSA/IHRoaXMuX2dldFRocm90dGxlRGVsYXkoc2NoZWR1bGVtZW50LCBydW50aW1lKSA6IHVuZGVmaW5lZDtcblxuICAgIGxldCBtZXNzYWdlID0gXy5jb21wYWN0KFtcbiAgICAgIHNpZ25hbC5tZXNzYWdlID9cbiAgICAgICAgYCR7dGhpcy5fbGFiZWx9OiBwcm9jZXNzICR7aWR9OiAke3NpZ25hbC5tZXNzYWdlfWAgOlxuICAgICAgICBgJHt0aGlzLl9sYWJlbH06IHByb2Nlc3MgJHtpZH0gY29tcGxldGVkIHdpdGggYSAke3NpZ25hbC5hY3Rpb259IGNvbnRyb2wgc2lnbmFsYCxcbiAgICAgIGNhbmNlbCAmJiAnVGhlIHByb2Nlc3Mgd2lsbCBiZSBjYW5jZWxlZCcsXG4gICAgICBmYWlsb3ZlciAmJiBgVGhlIHByb2Nlc3Mgd2lsbCBiZSBmYWlsb3ZlcmVkIGluICR7XG4gICAgICAgIGZvcm1hdC5zaW1wbGlmeVRpbWVBbW91bnQodGhyb3R0bGVEZWxheUluTXMgfHwgMCwgJ21zJykuZGVzY3JpcHRpb25cbiAgICAgIH1gLFxuICAgICAgcmVzdGFydCAmJiAnVGhlIHByb2Nlc3Mgd2lsbCBiZSByZXN0YXJ0ZWQnXG4gICAgXSkuam9pbignLiAnKTtcbiAgICBpZiAoc2lnbmFsLnNldmVyaXR5KSB7XG4gICAgICBzaWduYWwuZXJyb3IgP1xuICAgICAgICB0aGlzLl9sb2dnZXJbc2lnbmFsLnNldmVyaXR5XShtZXNzYWdlICsgJy4nLCBzaWduYWwuZXJyb3IpIDpcbiAgICAgICAgdGhpcy5fbG9nZ2VyW3NpZ25hbC5zZXZlcml0eV0obWVzc2FnZSk7XG4gICAgfSBlbHNlIGlmIChzaWduYWwuZXJyb3IpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihtZXNzYWdlICsgJy4nLCBzaWduYWwuZXJyb3IpO1xuICAgIH0gZWxzZSBpZiAoYWN0aW9uID09PSAnZmFpbG92ZXInKSB7XG4gICAgICB0aGlzLl9sb2dnZXIud2FybihtZXNzYWdlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5fbG9nZ2VyLmluZm8obWVzc2FnZSk7XG4gICAgfVxuICAgIGlmIChjYW5jZWwpIHtcbiAgICAgIHRoaXMuY2FuY2VsUHJvY2VzcyhpZCwge2FsbFVzYWdlczogdHJ1ZX0pO1xuICAgIH1cbiAgICBhd2FpdCB0aGlzLl9zdG9wUHJvY2VzcyhpZCwgcnVudGltZSwgZmFpbG92ZXIsIHRocm90dGxlRGVsYXlJbk1zKTtcbiAgfVxuXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjb21wbGV4aXR5XG4gIHByaXZhdGUgX2dldFRocm90dGxlRGVsYXkoc2NoZWR1bGVtZW50OiBFeHBlY3RlZFByb2Nlc3M8YW55PiwgcnVudGltZTogUnVudGltZVByb2Nlc3MpOiBudW1iZXIge1xuICAgIGxldCBvcHRpb25zID0gc2NoZWR1bGVtZW50LmZhaWxvdmVyVGhyb3R0bGVEZWxheSB8fCB7XG4gICAgICBtb2RlOiAnZml4ZWQnLFxuICAgICAgZGVsYXlJbk1zOiB0aGlzLl9vcHRpb25zPy5wcm9jZXNzRmFpbG92ZXJUaHJvdHRsZURlbGF5SW5NcyA/PyBjb252ZXJ0LnRpbWUuc2Vjb25kc1RvTXMoMTApXG4gICAgfTtcbiAgICBpZiAob3B0aW9ucy5tb2RlID09PSAnZml4ZWQnKSB7XG4gICAgICByZXR1cm4gcmFuZG9tLmdldEludGVnZXJBcm91bmQob3B0aW9ucy5kZWxheUluTXMsIG9wdGlvbnMucmFuZG9taXphdGlvbkZhY3RvciA/PyAwKTtcbiAgICB9XG4gICAgaWYgKFxuICAgICAgc2NoZWR1bGVtZW50LnRocm90dGxpbmdzPy5sYXN0U3VjY2Vzc2Z1bENvbm5lY3RUaW1lICE9PSB1bmRlZmluZWQgJiZcbiAgICAgIERhdGUubm93KCkgLSBzY2hlZHVsZW1lbnQudGhyb3R0bGluZ3MubGFzdFN1Y2Nlc3NmdWxDb25uZWN0VGltZSA+PSAob3B0aW9ucy5yZXNldERlbGF5SW5NcyA/PyAwKVxuICAgICkge1xuICAgICAgZGVsZXRlIHNjaGVkdWxlbWVudC50aHJvdHRsaW5ncztcbiAgICB9XG4gICAgc2NoZWR1bGVtZW50Lm5leHRQb3N0UHJvY2Vzc1Rocm90dGxpbmcgPSAoc3VjY2Vzc2Z1bFN0YXJ0KSA9PiBzY2hlZHVsZW1lbnQudGhyb3R0bGluZ3MgPSB7XG4gICAgICBjb3VudGVyOiAoc2NoZWR1bGVtZW50LnRocm90dGxpbmdzPy5jb3VudGVyIHx8IDApICsgKHJ1bnRpbWUuY2FuY2VsUHJvbWlzZS5jb21wbGV0ZWQgPyAwIDogMSksXG4gICAgICBsYXN0U3VjY2Vzc2Z1bENvbm5lY3RUaW1lOiBzdWNjZXNzZnVsU3RhcnQgPyBEYXRlLm5vdygpIDogdW5kZWZpbmVkXG4gICAgfTtcbiAgICByZXR1cm4gcmFuZG9tLmdldEludGVnZXJBcm91bmQoaGVscGVycy5leHBCYWNrb2ZmRGVsYXkoXG4gICAgICAoc2NoZWR1bGVtZW50LnRocm90dGxpbmdzPy5jb3VudGVyIHx8IDApICsgMSxcbiAgICAgIE1hdGgubWF4KG9wdGlvbnMubWluRGVsYXlJbk1zLCAxKSwgb3B0aW9ucy5tYXhEZWxheUluTXNcbiAgICApLCBvcHRpb25zLnJhbmRvbWl6YXRpb25GYWN0b3IgPz8gMCk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIF9zdG9wUHJvY2VzcyhpZDogc3RyaW5nLCBydW50aW1lOiBSdW50aW1lUHJvY2VzcywgZmFpbG92ZXI6IGJvb2xlYW4sIHRocm90dGxlRGVsYXlJbk1zPzogbnVtYmVyKSB7XG4gICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2xhYmVsfTogc3RvcHBpbmcgcHJvY2VzcyAke2lkfWApO1xuICAgIGF3YWl0IHJ1bnRpbWUucHJvY2Vzcy5zdG9wKClcbiAgICAgIC50aGVuKCgpID0+IHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHt0aGlzLl9sYWJlbH06IHByb2Nlc3MgJHtpZH0gc3RvcHBlZGApKVxuICAgICAgLmNhdGNoKGUgPT4gdGhpcy5fbG9nZ2VyLndhcm4oYCR7dGhpcy5fbGFiZWx9OiBmYWlsZWQgdG8gc3RvcCBwcm9jZXNzICR7aWR9IHByb3Blcmx5YCwgZSkpO1xuICAgIGlmICh0aHJvdHRsZURlbGF5SW5Ncykge1xuICAgICAgbGV0IGRlbGF5ID0gaGVscGVycy5kZWxheSh0aHJvdHRsZURlbGF5SW5Ncyk7XG4gICAgICBhd2FpdCBQcm9taXNlLnJhY2UoW2RlbGF5LCBydW50aW1lLmNhbmNlbFByb21pc2VdKTtcbiAgICAgIGRlbGF5LmNhbmNlbCgpO1xuICAgIH1cbiAgICBpZiAoZmFpbG92ZXIgJiYgcnVudGltZS5jYW5jZWxQcm9taXNlLmNvbXBsZXRlZCkge1xuICAgICAgdGhpcy5fc2NoZWR1bGVkUHJvY2Vzc2VzW2lkXSA/XG4gICAgICAgIHRoaXMuX2xvZ2dlci5pbmZvKGAke3RoaXMuX2xhYmVsfTogZm9yY2luZyBwcm9jZXNzICR7aWR9IGZhaWxvdmVyYCkgOlxuICAgICAgICB0aGlzLl9sb2dnZXIuaW5mbyhgJHt0aGlzLl9sYWJlbH06IGNhbmNlbGluZyBwcm9jZXNzICR7aWR9IGZhaWxvdmVyIGFzIGl0IHdhcyBjYW5jZWxlZGApO1xuICAgIH1cbiAgfVxufVxuXG5uYW1lc3BhY2UgQXN5bmNQcm9jZXNzUG9vbCB7XG5cbiAgLyoqIFByb2Nlc3MgcHJvdmlkZXIgKi9cbiAgZXhwb3J0IHR5cGUgUHJvY2Vzc1Byb3ZpZGVyPFByb2Nlc3MgZXh0ZW5kcyBBc3luY1Byb2Nlc3M+ID0gKFxuICAgIGNvbnRleHQ6IENvbnRleHQsIGFyZ3M6IGFueVtdXG4gICkgPT4gQ29uc3RydWN0ZWRQcm9jZXNzPFByb2Nlc3M+O1xuXG4gIC8qKiBDb25zdHJ1Y3RlZCBwcm9jZXNzICovXG4gIGV4cG9ydCB0eXBlIENvbnN0cnVjdGVkUHJvY2VzczxQcm9jZXNzIGV4dGVuZHMgQXN5bmNQcm9jZXNzPiA9IHtcbiAgICAvKiogUHJvY2VzcyAqL1xuICAgIHByb2Nlc3M6IFByb2Nlc3M7XG4gICAgLyoqIFByb2Nlc3MgY29udGV4dCAqL1xuICAgIGNvbnRleHQ6IFByb2Nlc3NDb250ZXh0O1xuICAgIC8qKiBNYXBwZWQgcHJvY2VzcyBhcmdzICovXG4gICAgYXJnczogYW55W107XG4gIH07XG5cbiAgLyoqIEJhc2ljIHByb2Nlc3MgY29udGV4dCAqL1xuICBleHBvcnQgdHlwZSBDb250ZXh0ID0ge1xuICAgIC8qKiBQcm9jZXNzIElEICovXG4gICAgcHJvY2Vzc0lkOiBzdHJpbmc7XG4gICAgLyoqIEN1cnJlbnQgcG9vbCAqL1xuICAgIHBvb2w6IEFzeW5jUHJvY2Vzc1Bvb2w8YW55LCBhbnk+O1xuICAgIC8qKiBQcm9jZXNzIHN0YWdlICovXG4gICAgc3RhZ2U6IFByb2Nlc3NDb250ZXh0LlByb2Nlc3NTdGFnZTtcbiAgICAvKiogV2hldGhlciB0aGUgcHJvY2VzcyB3YXMgc2NoZWR1bGVkIHRvIGNhbmNlbCBieSBleHRlcm5hbCBjb21tYW5kIChjYW5jZWwgb3IgcmVzdGFydCkgKi9cbiAgICBjYW5jZWxlZD86IGJvb2xlYW47XG4gIH07XG5cbiAgLyoqIENvbnN0cnVjdGluZyBvcHRpb25zICovXG4gIGV4cG9ydCB0eXBlIE9wdGlvbnM8UHJvY2Vzcz4gPSB7XG4gICAgLyoqIExvZ2dpbmcgbGFiZWwuIERlZmF1bHRzIHRvIGBkZWZhdWx0YCAqL1xuICAgIGxhYmVsPzogc3RyaW5nO1xuICAgIC8qKlxuICAgICAqIFByb2Nlc3MgcnVuIGF0dGVtcHQgdGhyb3R0bGUgZGVsYXkgaW4gY2FzZSBvZiBmYWlsb3Zlci4gTWF5IGJlIG92ZXJyaWRlbiBieSBwcm9jZXNzZXMgd2hlbiBzY2hlZHVsaW5nLFxuICAgICAqIERlZmF1bHRzIHRvIDMwIHNlY29uZHNcbiAgICAgKi9cbiAgICBwcm9jZXNzRmFpbG92ZXJUaHJvdHRsZURlbGF5SW5Ncz86IG51bWJlcjtcbiAgICAvKiogRGVwZW5kZW5jaWVzIHNoYXJlZCBhY3Jvc3MgYWxsIHByb2Nlc3NlcyAqL1xuICAgIGRlcGVuZGVu