UNPKG

@synerty/vortexjs

Version:

Custom observable data serialisation and routing based on Angular 2+

433 lines 62.8 kB
import { Inject, Injectable } from "@angular/core"; import { BehaviorSubject, firstValueFrom } from "rxjs"; import { filter, takeUntil } from "rxjs/operators"; import { VortexService } from "../VortexService"; import { TupleSelector } from "../TupleSelector"; import { Payload } from "../Payload"; import { PayloadEndpoint } from "../PayloadEndpoint"; import { NgLifeCycleEvents } from "../../util/NgLifeCycleEvents"; import { dateStr, dictKeysFromObject } from "../UtilMisc"; import { VortexStatusService } from "../VortexStatusService"; import { PayloadResponse } from "../PayloadResponse"; import { TupleOfflineStorageService } from "../storage/TupleOfflineStorageService"; import * as i0 from "@angular/core"; export class TupleDataObservableNameService { name; additionalFilt; constructor(name, additionalFilt = {}) { this.name = name; this.additionalFilt = additionalFilt; } equals(other) { if (other == null) return false; if (this.name != other.name) return false; return (JSON.stringify(this.additionalFilt) == JSON.stringify(other.additionalFilt)); } toString() { return `${this.name}:${JSON.stringify(this.additionalFilt)}`; } } export class CachedSubscribedData { tupleSelector; subject$ = new BehaviorSubject(null); get observable() { return this.subject$.pipe(filter((v) => v != null)); } /** Last Server Payload Date * If the server has responded with a payload, this is the date in the payload * @type {Date | null} */ lastServerPayloadDate = null; lastServerAskDate = null; cacheEnabled = true; storageEnabled = true; askServerEnabled = true; constructor(tupleSelector) { this.tupleSelector = tupleSelector; this.touch(); } // The date the cache is scheduled to be torn down. // This will be X time after we notice that it has no subscribers tearDownDate = null; TEARDOWN_WAIT = 30 * 1000; // 30 seconds, in milliseconds markForTearDown() { if (this.tearDownDate == null) this.tearDownDate = Date.now(); } resetTearDown() { this.tearDownDate = null; this.touch(); } isReadyForTearDown() { return (this.tearDownDate != null && this.tearDownDate + this.TEARDOWN_WAIT <= Date.now()); } get tuples() { return this.subject$.getValue(); } set tuples(tuples) { this.touch(); this.subject$.next(tuples); } /** Last Touched * * The last date that this cache was touched (subscribed or updated) * @type {Date | null} */ FLUSH_WAIT = 120 * 1000; // 2 minutes, in milliseconds _lastTouched = null; touch() { this._lastTouched = Date.now(); } isReadyForFlush() { return (this._lastTouched != null && this._lastTouched + this.FLUSH_WAIT <= Date.now()); } flush() { this.lastServerAskDate = null; this.lastServerPayloadDate = null; this.subject$.next(null); } } export class TupleDataOfflineObserverService extends NgLifeCycleEvents { vortexService; vortexStatusService; tupleDataObservableName; tupleOfflineStorageService; endpoint; filt; cacheByTupleSelector = {}; constructor(vortexService, vortexStatusService, tupleDataObservableName, tupleOfflineStorageService) { super(); this.vortexService = vortexService; this.vortexStatusService = vortexStatusService; this.tupleDataObservableName = tupleDataObservableName; this.tupleOfflineStorageService = tupleOfflineStorageService; this.filt = Object.assign({ name: tupleDataObservableName.name, key: "tupleDataObservable", }, tupleDataObservableName.additionalFilt); this.endpoint = new PayloadEndpoint(this, this.filt); this.endpoint.observable.subscribe((payloadEnvelope) => { payloadEnvelope .decodePayload() .then((payload) => { this.receivePayload(payloadEnvelope, payload, payloadEnvelope.encodedPayload); }) .catch((e) => { console.log(`${dateStr()} TupleDataOfflineObserverService:Error decoding payload ${e}`); }); }); vortexStatusService.isOnline .pipe(takeUntil(this.onDestroyEvent), filter((online) => online === true)) .subscribe((online) => this.vortexOnlineChanged()); // Cleanup dead subscribers every 30 seconds. let cleanupTimer = setInterval(() => this.cleanupDeadCaches(), 2000); this.onDestroyEvent.subscribe(() => clearInterval(cleanupTimer)); } _nameService() { return this.tupleDataObservableName; } pollForTuples(tupleSelector, useCache = true) { // --- If the data exists in the cache, then return it let tsStr = tupleSelector.toOrderedJsonStr(); if (useCache && this.cacheByTupleSelector.hasOwnProperty(tsStr)) { let cachedData = this.cacheByTupleSelector[tsStr]; cachedData.resetTearDown(); if (cachedData.cacheEnabled && cachedData.lastServerPayloadDate != null) { return Promise.resolve(cachedData.tuples); } } // --- Else, we want the data from the server // The PayloadEndpoint for this observable should also pickup and process // the response. So that will take care of the cache update and notify of // subscribers. let startFilt = Object.assign({ subscribe: false, useCache: useCache }, this.filt, { tupleSelector: tupleSelector, }); // Optionally typed, No need to worry about the fact that we convert this // and then TypeScript doesn't recognise that data type change let promise = new Payload(startFilt).makePayloadEnvelope(); promise = promise.then((payloadEnvelope) => { return new PayloadResponse(this.vortexService, payloadEnvelope); }); promise = promise.then((payloadEnvelope) => { return payloadEnvelope.decodePayload(); }); promise = promise.then((payload) => payload.tuples); return promise; } /** Flush Cache * * The Data Offine Observer can be used to offline cache data by observing a large * amounts of data, more data then the user would normally look at. * * If it's being used like this then the cache should be flushed during the process * to ensure it's not all being kept in memory. * * @param {TupleSelector} tupleSelector The tuple selector to flush the cache for */ flushCache(tupleSelector) { let tsStr = tupleSelector.toOrderedJsonStr(); if (this.cacheByTupleSelector.hasOwnProperty(tsStr)) { let cachedData = this.cacheByTupleSelector[tsStr]; cachedData.flush(); } this.cleanupDeadCaches(); } /** Promise from to Tuple Selector * * See the subscribeToTupleSelector method for more details. * The promise will fire on the first emit of data. * * Do not use this when there will be no data present, * or it may cause a memory leak. * * @param {TupleSelector} tupleSelector * @param {boolean} disableCache * @param {boolean} disableStorage * @param {boolean} disableAskServer Use this to store and observe data completely * within the angular app. * @returns {Subject<Tuple[]>} */ promiseFromTupleSelector(tupleSelector, disableCache = false, disableStorage = false, disableAskServer = false) { return firstValueFrom(this.subscribeToTupleSelector(tupleSelector, disableCache, disableStorage, disableAskServer)); } /** Subscribe to Tuple Selector * * Get an observable that will be fired when any new data updates are available * Data is loaded from the local db cache, while it waits for the server to respond. * * either from the server, or if they are locally updated with updateOfflineState() * * @param {TupleSelector} tupleSelector * @param {boolean} disableCache * @param {boolean} disableStorage * @param {boolean} disableAskServer Use this to store and observe data completely * within the angular app. * @returns {Subject<Tuple[]>} */ subscribeToTupleSelector(tupleSelector, disableCache = false, disableStorage = false, disableAskServer = false) { let tsStr = tupleSelector.toOrderedJsonStr(); let cachedData = null; // If the cache exists, use it if (this.cacheByTupleSelector.hasOwnProperty(tsStr)) { cachedData = this.cacheByTupleSelector[tsStr]; // Maybe disable cache cachedData.cacheEnabled = cachedData.cacheEnabled && !disableCache; // Maybe disable storage cachedData.storageEnabled = cachedData.storageEnabled && !disableStorage; // Maybe disable ask server cachedData.askServerEnabled = cachedData.askServerEnabled && !disableAskServer; // If the cache is enabled, and we have tuple data, then notify if (cachedData.cacheEnabled && cachedData.tuples != null) { return cachedData.observable; } // ELSE, Create the cache } else { cachedData = new CachedSubscribedData(tupleSelector); cachedData.cacheEnabled = !disableCache; cachedData.storageEnabled = !disableStorage; cachedData.askServerEnabled = !disableAskServer; this.cacheByTupleSelector[tsStr] = cachedData; } // If asking the server is enabled and we have not asked the server, then ask if (cachedData.askServerEnabled && cachedData.lastServerAskDate == null) { this.tellServerWeWantData([tupleSelector], disableCache); } // If the tuples are null (because it's new or been flushed), // Then ask the local DB again for it. if (cachedData.storageEnabled && cachedData.tuples == null) { this.tupleOfflineStorageService .loadTuplesEncoded(tupleSelector) .then((vortexMsgOrNull) => { // There is no data, return if (vortexMsgOrNull == null) { // Only return if we're expecting data from the server if (cachedData.askServerEnabled) { return; } // Otherwise return an empty array this.notifyObservers(cachedData, tupleSelector, []); return; } return Payload.fromEncodedPayload(vortexMsgOrNull).then((payload) => { // If the server has responded before we loaded the data, then just // ignore the cached data. if (cachedData.lastServerPayloadDate != null) return; // Update the tuples, and notify if them cachedData.tuples = payload.tuples; this.notifyObservers(cachedData, tupleSelector, payload.tuples); }); }) .catch((err) => { this.vortexStatusService.logError(`loadTuples failed : ${err}`); throw new Error(err); }); } cachedData.resetTearDown(); return cachedData.observable; } /** Update Offline State * * This method updates the offline stored data, which will be used until the next * update from the server comes along. * @param tupleSelector The tuple selector to update tuples for * @param tuples The new data to store */ async updateOfflineState(tupleSelector, tuples) { let tsStr = tupleSelector.toOrderedJsonStr(); let cachedData = null; // Make note of the last server update date let lastServerDate = null; if (this.cacheByTupleSelector.hasOwnProperty(tsStr)) { cachedData = this.cacheByTupleSelector[tsStr]; lastServerDate = cachedData.lastServerPayloadDate; } // AND store the data locally await this.storeDataLocally(tupleSelector, tuples); if (cachedData == null) { return; } if (lastServerDate != cachedData.lastServerPayloadDate) return; cachedData.tuples = tuples; this.notifyObservers(cachedData, tupleSelector, tuples); } cleanupDeadCaches() { for (let key of dictKeysFromObject(this.cacheByTupleSelector)) { let cachedData = this.cacheByTupleSelector[key]; // If no activity has occured on the cache, then flush it if (cachedData.isReadyForFlush()) { console.log(`${dateStr()} Flushing cache due to expiry ${key}`); cachedData.flush(); } // Tear down happens 30s after the last subscriber unsubscribes // If there are subscribers, then reset the teardown clock if (cachedData.subject$.observers.length !== 0) { cachedData.resetTearDown(); // Tear down the cahce, including telling the server we're no longer // observing the data } else if (cachedData.isReadyForTearDown()) { console.log(`${dateStr()} Tearing down cache ${key}`); cachedData.flush(); delete this.cacheByTupleSelector[key]; this.tellServerWeWantData([cachedData.tupleSelector], null, true); // if there are no subscribers, then mark it for tear down (30s time) } else { cachedData.markForTearDown(); } } } vortexOnlineChanged() { this.cleanupDeadCaches(); let tupleSelectors = []; for (let key of dictKeysFromObject(this.cacheByTupleSelector)) { let cache = this.cacheByTupleSelector[key]; if (cache.askServerEnabled) tupleSelectors.push(TupleSelector.fromJsonStr(key)); } this.tellServerWeWantData(tupleSelectors); } receivePayload(payloadEnvelope, payload, encodedPayload) { const tupleSelector = payloadEnvelope.filt["tupleSelector"]; const tsStr = tupleSelector.toOrderedJsonStr(); if (!this.cacheByTupleSelector.hasOwnProperty(tsStr)) return; const cachedData = this.cacheByTupleSelector[tsStr]; const lastDate = cachedData.lastServerPayloadDate; if (payloadEnvelope.date == null) { throw new Error("payload.date can not be null"); } const thisDate = new Date(payloadEnvelope.date); // If the data is old, then disregard it. if (lastDate != null && // Newer dates have higher numbers // If this date is less than the last date thisDate.getTime() < lastDate.getTime()) { return; } cachedData.lastServerPayloadDate = thisDate; cachedData.tuples = payload.tuples; this.notifyObserversAndStore(cachedData, tupleSelector, payload.tuples, encodedPayload); } tellServerWeWantData(tupleSelectors, disableCache = false, unsubscribe = false) { if (!this.vortexStatusService.snapshot.isOnline) return; let startFilt = Object.assign({ subscribe: true }, this.filt); let payloads = []; for (let tupleSelector of tupleSelectors) { let tsStr = tupleSelector.toOrderedJsonStr(); if (this.cacheByTupleSelector.hasOwnProperty(tsStr)) this.cacheByTupleSelector[tsStr].lastServerAskDate = new Date(); let filt = Object.assign({}, startFilt, { tupleSelector: tupleSelector, unsubscribe: unsubscribe, }); if (disableCache != null) filt["disableCache"] = disableCache; payloads.push(new Payload(filt)); } this.vortexService.sendPayload(payloads); } notifyObservers(cachedData, tupleSelector, tuples) { // Notify Observers try { cachedData.tuples = tuples; } catch (e) { // NOTE: Observables automatically remove observers when the raise exceptions. console.log(`${dateStr()} ERROR: TupleDataObserverService.notifyObservers, observable has been removed ${e.toString()} ${tupleSelector.toOrderedJsonStr()}`); } } notifyObserversAndStore(cachedData, tupleSelector, tuples, encodedPayload = null) { this.notifyObservers(cachedData, tupleSelector, tuples); // AND store the data locally if (cachedData.storageEnabled) this.storeDataLocally(tupleSelector, tuples, encodedPayload); } storeDataLocally(tupleSelector, tuples, encodedPayload = null) { let errFunc = (err) => { this.vortexStatusService.logError(`saveTuples failed : ${err}`); throw new Error(err); }; if (encodedPayload == null) { return this.tupleOfflineStorageService .saveTuples(tupleSelector, tuples) .catch(errFunc); } return this.tupleOfflineStorageService .saveTuplesEncoded(tupleSelector, encodedPayload) .catch(errFunc); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TupleDataOfflineObserverService, deps: [{ token: VortexService }, { token: VortexStatusService }, { token: TupleDataObservableNameService }, { token: TupleOfflineStorageService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TupleDataOfflineObserverService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: TupleDataOfflineObserverService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [VortexService] }] }, { type: undefined, decorators: [{ type: Inject, args: [VortexStatusService] }] }, { type: undefined, decorators: [{ type: Inject, args: [TupleDataObservableNameService] }] }, { type: undefined, decorators: [{ type: Inject, args: [TupleOfflineStorageService] }] }]; } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVHVwbGVEYXRhT2ZmbGluZU9ic2VydmVyU2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy92b3J0ZXgvb2JzZXJ2YWJsZS1zZXJ2aWNlL1R1cGxlRGF0YU9mZmxpbmVPYnNlcnZlclNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkQsT0FBTyxFQUFFLGVBQWUsRUFBRSxjQUFjLEVBQXVCLE1BQU0sTUFBTSxDQUFDO0FBQzVFLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDbkQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRWpELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNqRCxPQUFPLEVBQWdCLE9BQU8sRUFBRSxNQUFNLFlBQVksQ0FBQztBQUNuRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDakUsT0FBTyxFQUFFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUMxRCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFckQsT0FBTyxFQUFFLDBCQUEwQixFQUFFLE1BQU0sdUNBQXVDLENBQUM7O0FBRW5GLE1BQU0sT0FBTyw4QkFBOEI7SUFFNUI7SUFDQTtJQUZYLFlBQ1csSUFBWSxFQUNaLGlCQUFzQixFQUFFO1FBRHhCLFNBQUksR0FBSixJQUFJLENBQVE7UUFDWixtQkFBYyxHQUFkLGNBQWMsQ0FBVTtJQUNoQyxDQUFDO0lBRUosTUFBTSxDQUFDLEtBQXFDO1FB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QUFJLEVBQUU7WUFDeEIsT0FBTyxJQUFJLENBQUMsMEJBQTBCO2lCQUNqQyxVQUFVLENBQUMsYUFBYSxFQUFFLE1BQU0sQ0FBQztpQkFDakMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1NBQ3ZCO1FBRUQsT0FBTyxJQUFJLENBQUMsMEJBQTBCO2FBQ2pDLGlCQUFpQixDQUFDLGFBQWEsRUFBRSxjQUFjLENBQUM7YUFDaEQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3hCLENBQUM7d0dBdmNRLCtCQUErQixrQkFRNUIsYUFBYSxhQUNiLG1CQUFtQixhQUNuQiw4QkFBOEIsYUFDOUIsMEJBQTBCOzRHQVg3QiwrQkFBK0I7OzRGQUEvQiwrQkFBK0I7a0JBRDNDLFVBQVU7OzBCQVNGLE1BQU07MkJBQUMsYUFBYTs7MEJBQ3BCLE1BQU07MkJBQUMsbUJBQW1COzswQkFDMUIsTUFBTTsyQkFBQyw4QkFBOEI7OzBCQUNyQyxNQUFNOzJCQUFDLDBCQUEwQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdCwgSW5qZWN0YWJsZSB9IGZyb20gXCJAYW5ndWxhci9jb3JlXCI7XG5pbXBvcnQgeyBCZWhhdmlvclN1YmplY3QsIGZpcnN0VmFsdWVGcm9tLCBPYnNlcnZhYmxlLCBTdWJqZWN0IH0gZnJvbSBcInJ4anNcIjtcbmltcG9ydCB7IGZpbHRlciwgdGFrZVVudGlsIH0gZnJvbSBcInJ4anMvb3BlcmF0b3JzXCI7XG5pbXBvcnQgeyBWb3J0ZXhTZXJ2aWNlIH0gZnJvbSBcIi4uL1ZvcnRleFNlcnZpY2VcIjtcbmltcG9ydCB7IFR1cGxlIH0gZnJvbSBcIi4uL2V4cG9ydHNcIjtcbmltcG9ydCB7IFR1cGxlU2VsZWN0b3IgfSBmcm9tIFwiLi4vVHVwbGVTZWxlY3RvclwiO1xuaW1wb3J0IHsgSVBheWxvYWRGaWx0LCBQYXlsb2FkIH0gZnJvbSBcIi4uL1BheWxvYWRcIjtcbmltcG9ydCB7IFBheWxvYWRFbmRwb2ludCB9IGZyb20gXCIuLi9QYXlsb2FkRW5kcG9pbnRcIjtcbmltcG9ydCB7IE5nTGlmZUN5Y2xlRXZlbnRzIH0gZnJvbSBcIi4uLy4uL3V0aWwvTmdMaWZlQ3ljbGVFdmVudHNcIjtcbmltcG9ydCB7IGRhdGVTdHIsIGRpY3RLZXlzRnJvbU9iamVjdCB9IGZyb20gXCIuLi9VdGlsTWlzY1wiO1xuaW1wb3J0IHsgVm9ydGV4U3RhdHVzU2VydmljZSB9IGZyb20gXCIuLi9Wb3J0ZXhTdGF0dXNTZXJ2aWNlXCI7XG5pbXBvcnQgeyBQYXlsb2FkUmVzcG9uc2UgfSBmcm9tIFwiLi4vUGF5bG9hZFJlc3BvbnNlXCI7XG5pbXBvcnQgeyBQYXlsb2FkRW52ZWxvcGUgfSBmcm9tIFwiLi4vUGF5bG9hZEVudmVsb3BlXCI7XG5pbXBvcnQgeyBUdXBsZU9mZmxpbmVTdG9yYWdlU2VydmljZSB9IGZyb20gXCIuLi9zdG9yYWdlL1R1cGxlT2ZmbGluZVN0b3JhZ2VTZXJ2aWNlXCI7XG5cbmV4cG9ydCBjbGFzcyBUdXBsZURhdGFPYnNlcnZhYmxlTmFtZVNlcnZpY2Uge1xuICAgIGNvbnN0cnVjdG9yKFxuICAgICAgICBwdWJsaWMgbmFtZTogc3RyaW5nLFxuICAgICAgICBwdWJsaWMgYWRkaXRpb25hbEZpbHQ6IGFueSA9IHt9LFxuICAgICkge31cblxuICAgIGVxdWFscyhvdGhlcjogVHVwbGVEYXRhT2JzZXJ2YWJsZU5hbWVTZXJ2aWNlKTogYm9vbGVhbiB7XG4gICAgICAgIGlmIChvdGhlciA9PSBudWxsKSByZXR1cm4gZmFsc2U7XG4gICAgICAgIGlmICh0aGlzLm5hbWUgIT0gb3RoZXIubmFtZSkgcmV0dXJuIGZhbHNlO1xuICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgSlNPTi5zdHJpbmdpZnkodGhpcy5hZGRpdGlvbmFsRmlsdCkgPT1cbiAgICAgICAgICAgIEpTT04uc3RyaW5naWZ5KG90aGVyLmFkZGl0aW9uYWxGaWx0KVxuICAgICAgICApO1xuICAgIH1cblxuICAgIHRvU3RyaW5nKCk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiBgJHt0aGlzLm5hbWV9OiR7SlNPTi5zdHJpbmdpZnkodGhpcy5hZGRpdGlvbmFsRmlsdCl9YDtcbiAgICB9XG59XG5cbmV4cG9ydCBjbGFzcyBDYWNoZWRTdWJzY3JpYmVkRGF0YSB7XG4gICAgc3ViamVjdCQ6IEJlaGF2aW9yU3ViamVjdDxUdXBsZVtdPiA9IG5ldyBCZWhhdmlvclN1YmplY3Q8VHVwbGVbXSB8IG51bGw+KFxuICAgICAgICBudWxsLFxuICAgICk7XG5cbiAgICBnZXQgb2JzZXJ2YWJsZSgpOiBPYnNlcnZhYmxlPFR1cGxlW10+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc3ViamVjdCQucGlwZShmaWx0ZXIoKHYpID0+IHYgIT0gbnVsbCkpO1xuICAgIH1cblxuICAgIC8qKiBMYXN0IFNlcnZlciBQYXlsb2FkIERhdGVcbiAgICAgKiBJZiB0aGUgc2VydmVyIGhhcyByZXNwb25kZWQgd2l0aCBhIHBheWxvYWQsIHRoaXMgaXMgdGhlIGRhdGUgaW4gdGhlIHBheWxvYWRcbiAgICAgKiBAdHlwZSB7RGF0ZSB8IG51bGx9XG4gICAgICovXG4gICAgbGFzdFNlcnZlclBheWxvYWREYXRlOiBEYXRlIHwgbnVsbCA9IG51bGw7XG4gICAgbGFzdFNlcnZlckFza0RhdGU6IERhdGUgfCBudWxsID0gbnVsbDtcblxuICAgIGNhY2hlRW5hYmxlZCA9IHRydWU7XG4gICAgc3RvcmFnZUVuYWJsZWQgPSB0cnVlO1xuICAgIGFza1NlcnZlckVuYWJsZWQgPSB0cnVlO1xuXG4gICAgY29uc3RydWN0b3IocHVibGljIHR1cGxlU2VsZWN0b3I6IFR1cGxlU2VsZWN0b3IpIHtcbiAgICAgICAgdGhpcy50b3VjaCgpO1xuICAgIH1cblxuICAgIC8vIFRoZSBkYXRlIHRoZSBjYWNoZSBpcyBzY2hlZHVsZWQgdG8gYmUgdG9ybiBkb3duLlxuICAgIC8vIFRoaXMgd2lsbCBiZSBYIHRpbWUgYWZ0ZXIgd2Ugbm90aWNlIHRoYXQgaXQgaGFzIG5vIHN1YnNjcmliZXJzXG4gICAgcHJpdmF0ZSB0ZWFyRG93bkRhdGU6IG51bWJlciB8IG51bGwgPSBudWxsO1xuICAgIHByaXZhdGUgVEVBUkRPV05fV0FJVCA9IDMwICogMTAwMDsgLy8gMzAgc2Vjb25kcywgaW4gbWlsbGlzZWNvbmRzXG5cbiAgICBtYXJrRm9yVGVhckRvd24oKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLnRlYXJEb3duRGF0ZSA9PSBudWxsKSB0aGlzLnRlYXJEb3duRGF0ZSA9IERhdGUubm93KCk7XG4gICAgfVxuXG4gICAgcmVzZXRUZWFyRG93bigpOiB2b2lkIHtcbiAgICAgICAgdGhpcy50ZWFyRG93bkRhdGUgPSBudWxsO1xuICAgICAgICB0aGlzLnRvdWNoKCk7XG4gICAgfVxuXG4gICAgaXNSZWFkeUZvclRlYXJEb3duKCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgdGhpcy50ZWFyRG93bkRhdGUgIT0gbnVsbCAmJlxuICAgICAgICAgICAgdGhpcy50ZWFyRG93bkRhdGUgKyB0aGlzLlRFQVJET1dOX1dBSVQgPD0gRGF0ZS5ub3coKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIGdldCB0dXBsZXMoKTogVHVwbGVbXSB8IG51bGwge1xuICAgICAgICByZXR1cm4gdGhpcy5zdWJqZWN0JC5nZXRWYWx1ZSgpO1xuICAgIH1cblxuICAgIHNldCB0dXBsZXModHVwbGVzOiBUdXBsZVtdKSB7XG4gICAgICAgIHRoaXMudG91Y2goKTtcbiAgICAgICAgdGhpcy5zdWJqZWN0JC5uZXh0KHR1cGxlcyk7XG4gICAgfVxuXG4gICAgLyoqIExhc3QgVG91Y2hlZFxuICAgICAqXG4gICAgICogVGhlIGxhc3QgZGF0ZSB0aGF0IHRoaXMgY2FjaGUgd2FzIHRvdWNoZWQgKHN1YnNjcmliZWQgb3IgdXBkYXRlZClcbiAgICAgKiBAdHlwZSB7RGF0ZSB8IG51bGx9XG4gICAgICovXG4gICAgcHJpdmF0ZSBGTFVTSF9XQUlUID0gMTIwICogMTAwMDsgLy8gMiBtaW51dGVzLCBpbiBtaWxsaXNlY29uZHNcbiAgICBwcml2YXRlIF9sYXN0VG91Y2hlZDogbnVtYmVyIHwgbnVsbCA9IG51bGw7XG5cbiAgICB0b3VjaCgpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5fbGFzdFRvdWNoZWQgPSBEYXRlLm5vdygpO1xuICAgIH1cblxuICAgIGlzUmVhZHlGb3JGbHVzaCgpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICAgIHRoaXMuX2xhc3RUb3VjaGVkICE9IG51bGwgJiZcbiAgICAgICAgICAgIHRoaXMuX2xhc3RUb3VjaGVkICsgdGhpcy5GTFVTSF9XQUlUIDw9IERhdGUubm93KClcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBmbHVzaCgpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5sYXN0U2VydmVyQXNrRGF0ZSA9IG51bGw7XG4gICAgICAgIHRoaXMubGFzdFNlcnZlclBheWxvYWREYXRlID0gbnVsbDtcbiAgICAgICAgdGhpcy5zdWJqZWN0JC5uZXh0KG51bGwpO1xuICAgIH1cbn1cblxuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIFR1cGxlRGF0YU9mZmxpbmVPYnNlcnZlclNlcnZpY2UgZXh0ZW5kcyBOZ0xpZmVDeWNsZUV2ZW50cyB7XG4gICAgcHJpdmF0ZSBlbmRwb2ludDogUGF5bG9hZEVuZHBvaW50O1xuICAgIHByaXZhdGUgZmlsdDogSVBheWxvYWRGaWx0O1xuICAgIHByaXZhdGUgY2FjaGVCeVR1cGxlU2VsZWN0b3I6IHtcbiAgICAgICAgW3R1cGxlU2VsZWN0b3I6IHN0cmluZ106IENhY2hlZFN1YnNjcmliZWREYXRhO1xuICAgIH0gPSB7fTtcblxuICAgIGNvbnN0cnVjdG9yKFxuICAgICAgICBASW5qZWN0KFZvcnRleFNlcnZpY2UpIHByaXZhdGUgdm9ydGV4U2VydmljZSxcbiAgICAgICAgQEluamVjdChWb3J0ZXhTdGF0dXNTZXJ2aWNlKSBwcml2YXRlIHZvcnRleFN0YXR1c1NlcnZpY2UsXG4gICAgICAgIEBJbmplY3QoVHVwbGVEYXRhT2JzZXJ2YWJsZU5hbWVTZXJ2aWNlKSBwcml2YXRlIHR1cGxlRGF0YU9ic2VydmFibGVOYW1lLFxuICAgICAgICBASW5qZWN0KFR1cGxlT2ZmbGluZVN0b3JhZ2VTZXJ2aWNlKSBwcml2YXRlIHR1cGxlT2ZmbGluZVN0b3JhZ2VTZXJ2aWNlLFxuICAgICkge1xuICAgICAgICBzdXBlcigpO1xuXG4gICAgICAgIHRoaXMuZmlsdCA9IE9iamVjdC5hc3NpZ24oXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgbmFtZTogdHVwbGVEYXRhT2JzZXJ2YWJsZU5hbWUubmFtZSxcbiAgICAgICAgICAgICAgICBrZXk6IFwidHVwbGVEYXRhT2JzZXJ2YWJsZVwiLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHR1cGxlRGF0YU9ic2VydmFibGVOYW1lLmFkZGl0aW9uYWxGaWx0LFxuICAgICAgICApO1xuXG4gICAgICAgIHRoaXMuZW5kcG9pbnQgPSBuZXcgUGF5bG9hZEVuZHBvaW50KHRoaXMsIHRoaXMuZmlsdCk7XG4gICAgICAgIHRoaXMuZW5kcG9pbnQub2JzZXJ2YWJsZS5zdWJzY3JpYmUoXG4gICAgICAgICAgICAocGF5bG9hZEVudmVsb3BlOiBQYXlsb2FkRW52ZWxvcGUpID0+IHtcbiAgICAgICAgICAgICAgICBwYXlsb2FkRW52ZWxvcGVcbiAgICAgICAgICAgICAgICAgICAgLmRlY29kZVBheWxvYWQoKVxuICAgICAgICAgICAgICAgICAgICAudGhlbigocGF5bG9hZDogUGF5bG9hZCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5yZWNlaXZlUGF5bG9hZChcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXlsb2FkRW52ZWxvcGUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF5bG9hZCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXlsb2FkRW52ZWxvcGUuZW5jb2RlZFBheWxvYWQsXG4gICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgICAuY2F0Y2goKGUpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGAke2RhdGVTdHIoKX0gVHVwbGVEYXRhT2ZmbGluZU9ic2VydmVyU2VydmljZTpFcnJvciBkZWNvZGluZyBwYXlsb2FkICR7ZX1gLFxuICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9LFxuICAgICAgICApO1xuXG4gICAgICAgIHZvcnRleFN0YXR1c1NlcnZpY2UuaXNPbmxpbmVcbiAgICAgICAgICAgIC5waXBlKFxuICAgICAgICAgICAgICAgIHRha2VVbnRpbCh0aGlzLm9uRGVzdHJveUV2ZW50KSxcbiAgICAgICAgICAgICAgICBmaWx0ZXIoKG9ubGluZSkgPT4gb25saW5lID09PSB0cnVlKSxcbiAgICAgICAgICAgIClcbiAgICAgICAgICAgIC5zdWJzY3JpYmUoKG9ubGluZSkgPT4gdGhpcy52b3J0ZXhPbmxpbmVDaGFuZ2VkKCkpO1xuXG4gICAgICAgIC8vIENsZWFudXAgZGVhZCBzdWJzY3JpYmVycyBldmVyeSAzMCBzZWNvbmRzLlxuICAgICAgICBsZXQgY2xlYW51cFRpbWVyID0gc2V0SW50ZXJ2YWwoKCkgPT4gdGhpcy5jbGVhbnVwRGVhZENhY2hlcygpLCAyMDAwKTtcbiAgICAgICAgdGhpcy5vbkRlc3Ryb3lFdmVudC5zdWJzY3JpYmUoKCkgPT4gY2xlYXJJbnRlcnZhbChjbGVhbnVwVGltZXIpKTtcbiAgICB9XG5cbiAgICBfbmFtZVNlcnZpY2UoKTogVHVwbGVEYXRhT2JzZXJ2YWJsZU5hbWVTZXJ2aWNlIHtcbiAgICAgICAgcmV0dXJuIHRoaXMudHVwbGVEYXRhT2JzZXJ2YWJsZU5hbWU7XG4gICAgfVxuXG4gICAgcG9sbEZvclR1cGxlcyhcbiAgICAgICAgdHVwbGVTZWxlY3RvcjogVHVwbGVTZWxlY3RvcixcbiAgICAgICAgdXNlQ2FjaGU6IGJvb2xlYW4gPSB0cnVlLFxuICAgICk6IFByb21pc2U8VHVwbGVbXT4ge1xuICAgICAgICAvLyAtLS0gSWYgdGhlIGRhdGEgZXhpc3RzIGluIHRoZSBjYWNoZSwgdGhlbiByZXR1cm4gaXRcbiAgICAgICAgbGV0IHRzU3RyID0gdHVwbGVTZWxlY3Rvci50b09yZGVyZWRKc29uU3RyKCk7XG5cbiAgICAgICAgaWYgKHVzZUNhY2hlICYmIHRoaXMuY2FjaGVCeVR1cGxlU2VsZWN0b3IuaGFzT3duUHJvcGVydHkodHNTdHIpKSB7XG4gICAgICAgICAgICBsZXQgY2FjaGVkRGF0YSA9IHRoaXMuY2FjaGVCeVR1cGxlU2VsZWN0b3JbdHNTdHJdO1xuICAgICAgICAgICAgY2FjaGVkRGF0YS5yZXNldFRlYXJEb3duKCk7XG5cbiAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICBjYWNoZWREYXRhLmNhY2hlRW5hYmxlZCAmJlxuICAgICAgICAgICAgICAgIGNhY2hlZERhdGEubGFzdFNlcnZlclBheWxvYWREYXRlICE9IG51bGxcbiAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoY2FjaGVkRGF0YS50dXBsZXMpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gLS0tIEVsc2UsIHdlIHdhbnQgdGhlIGRhdGEgZnJvbSB0aGUgc2VydmVyXG4gICAgICAgIC8vIFRoZSBQYXlsb2FkRW5kcG9pbnQgZm9yIHRoaXMgb2JzZXJ2YWJsZSBzaG91bGQgYWxzbyBwaWNrdXAgYW5kIHByb2Nlc3NcbiAgICAgICAgLy8gdGhlIHJlc3BvbnNlLiBTbyB0aGF0IHdpbGwgdGFrZSBjYXJlIG9mIHRoZSBjYWNoZSB1cGRhdGUgYW5kIG5vdGlmeSBvZlxuICAgICAgICAvLyBzdWJzY3JpYmVycy5cblxuICAgICAgICBsZXQgc3RhcnRGaWx0ID0gT2JqZWN0LmFzc2lnbihcbiAgICAgICAgICAgIHsgc3Vic2NyaWJlOiBmYWxzZSwgdXNlQ2FjaGU6IHVzZUNhY2hlIH0sXG4gICAgICAgICAgICB0aGlzLmZpbHQsXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgdHVwbGVTZWxlY3RvcjogdHVwbGVTZWxlY3RvcixcbiAgICAgICAgICAgIH0sXG4gICAgICAgICk7XG5cbiAgICAgICAgLy8gT3B0aW9uYWxseSB0eXBlZCwgTm8gbmVlZCB0byB3b3JyeSBhYm91dCB0aGUgZmFjdCB0aGF0IHdlIGNvbnZlcnQgdGhpc1xuICAgICAgICAvLyBhbmQgdGhlbiBUeXBlU2NyaXB0IGRvZXNuJ3QgcmVjb2duaXNlIHRoYXQgZGF0YSB0eXBlIGNoYW5nZVxuICAgICAgICBsZXQgcHJvbWlzZTogYW55ID0gbmV3IFBheWxvYWQoc3RhcnRGaWx0KS5tYWtlUGF5bG9hZEVudmVsb3BlKCk7XG5cbiAgICAgICAgcHJvbWlzZSA9IHByb21pc2UudGhlbigocGF5bG9hZEVudmVsb3BlOiBQYXlsb2FkRW52ZWxvcGUpID0+IHtcbiAgICAgICAgICAgIHJldHVybiBuZXcgUGF5bG9hZFJlc3BvbnNlKHRoaXMudm9ydGV4U2VydmljZSwgcGF5bG9hZEVudmVsb3BlKTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgcHJvbWlzZSA9IHByb21pc2UudGhlbigocGF5bG9hZEVudmVsb3BlOiBQYXlsb2FkRW52ZWxvcGUpID0+IHtcbiAgICAgICAgICAgIHJldHVybiBwYXlsb2FkRW52ZWxvcGUuZGVjb2RlUGF5bG9hZCgpO1xuICAgICAgICB9KTtcblxuICAgICAgICBwcm9taXNlID0gcHJvbWlzZS50aGVuKChwYXlsb2FkOiBQYXlsb2FkKSA9PiBwYXlsb2FkLnR1cGxlcyk7XG5cbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuXG4gICAgLyoqIEZsdXNoIENhY2hlXG4gICAgICpcbiAgICAgKiBUaGUgRGF0YSBPZmZpbmUgT2JzZXJ2ZXIgY2FuIGJlIHVzZWQgdG8gb2ZmbGluZSBjYWNoZSBkYXRhIGJ5IG9ic2VydmluZyBhIGxhcmdlXG4gICAgICogYW1vdW50cyBvZiBkYXRhLCBtb3JlIGRhdGEgdGhlbiB0aGUgdXNlciB3b3VsZCBub3JtYWxseSBsb29rIGF0LlxuICAgICAqXG4gICAgICogSWYgaXQncyBiZWluZyB1c2VkIGxpa2UgdGhpcyB0aGVuIHRoZSBjYWNoZSBzaG91bGQgYmUgZmx1c2hlZCBkdXJpbmcgdGhlIHByb2Nlc3NcbiAgICAgKiB0byBlbnN1cmUgaXQncyBub3QgYWxsIGJlaW5nIGtlcHQgaW4gbWVtb3J5LlxuICAgICAqXG4gICAgICogQHBhcmFtIHtUdXBsZVNlbGVjdG9yfSB0dXBsZVNlbGVjdG9yIFRoZSB0dXBsZSBzZWxlY3RvciB0byBmbHVzaCB0aGUgY2FjaGUgZm9yXG4gICAgICovXG4gICAgZmx1c2hDYWNoZSh0dXBsZVNlbGVjdG9yOiBUdXBsZVNlbGVjdG9yKTogdm9pZCB7XG4gICAgICAgIGxldCB0c1N0ciA9IHR1cGxlU2VsZWN0b3IudG9PcmRlcmVkSnNvblN0cigpO1xuICAgICAgICBpZiAodGhpcy5jYWNoZUJ5VHVwbGVTZWxlY3Rvci5oYXNPd25Qcm9wZXJ0eSh0c1N0cikpIHtcbiAgICAgICAgICAgIGxldCBjYWNoZWREYXRhID0gdGhpcy5jYWNoZUJ5VHVwbGVTZWxlY3Rvclt0c1N0cl07XG4gICAgICAgICAgICBjYWNoZWREYXRhLmZsdXNoKCk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5jbGVhbnVwRGVhZENhY2hlcygpO1xuICAgIH1cblxuICAgIC8qKiBQcm9taXNlIGZyb20gdG8gVHVwbGUgU2VsZWN0b3JcbiAgICAgKlxuICAgICAqIFNlZSB0aGUgc3Vic2NyaWJlVG9UdXBsZVNlbGVjdG9yIG1ldGhvZCBmb3IgbW9yZSBkZXRhaWxzLlxuICAgICAqIFRoZSBwcm9taXNlIHdpbGwgZmlyZSBvbiB0aGUgZmlyc3QgZW1pdCBvZiBkYXRhLlxuICAgICAqXG4gICAgICogRG8gbm90IHVzZSB0aGlzIHdoZW4gdGhlcmUgd2lsbCBiZSBubyBkYXRhIHByZXNlbnQsXG4gICAgICogb3IgaXQgbWF5IGNhdXNlIGEgbWVtb3J5IGxlYWsuXG4gICAgICpcbiAgICAgKiBAcGFyYW0ge1R1cGxlU2VsZWN0b3J9IHR1cGxlU2VsZWN0b3JcbiAgICAgKiBAcGFyYW0ge2Jvb2xlYW59IGRpc2FibGVDYWNoZVxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gZGlzYWJsZVN0b3JhZ2VcbiAgICAgKiBAcGFyYW0ge2Jvb2xlYW59IGRpc2FibGVBc2tTZXJ2ZXIgVXNlIHRoaXMgdG8gc3RvcmUgYW5kIG9ic2VydmUgZGF0YSBjb21wbGV0ZWx5XG4gICAgICogICAgICB3aXRoaW4gdGhlIGFuZ3VsYXIgYXBwLlxuICAgICAqIEByZXR1cm5zIHtTdWJqZWN0PFR1cGxlW10+fVxuICAgICAqL1xuICAgIHByb21pc2VGcm9tVHVwbGVTZWxlY3RvcihcbiAgICAgICAgdHVwbGVTZWxlY3RvcjogVHVwbGVTZWxlY3RvcixcbiAgICAgICAgZGlzYWJsZUNhY2hlOiBib29sZWFuID0gZmFsc2UsXG4gICAgICAgIGRpc2FibGVTdG9yYWdlOiBib29sZWFuID0gZmFsc2UsXG4gICAgICAgIGRpc2FibGVBc2tTZXJ2ZXI6IGJvb2xlYW4gPSBmYWxzZSxcbiAgICApOiBQcm9taXNlPFR1cGxlW10+IHtcbiAgICAgICAgcmV0dXJuIGZpcnN0VmFsdWVGcm9tKFxuICAgICAgICAgICAgdGhpcy5zdWJzY3JpYmVUb1R1cGxlU2VsZWN0b3IoXG4gICAgICAgICAgICAgICAgdHVwbGVTZWxlY3RvcixcbiAgICAgICAgICAgICAgICBkaXNhYmxlQ2FjaGUsXG4gICAgICAgICAgICAgICAgZGlzYWJsZVN0b3JhZ2UsXG4gICAgICAgICAgICAgICAgZGlzYWJsZUFza1NlcnZlcixcbiAgICAgICAgICAgICksXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqIFN1YnNjcmliZSB0byBUdXBsZSBTZWxlY3RvclxuICAgICAqXG4gICAgICogR2V0IGFuIG9ic2VydmFibGUgdGhhdCB3aWxsIGJlIGZpcmVkIHdoZW4gYW55IG5ldyBkYXRhIHVwZGF0ZXMgYXJlIGF2YWlsYWJsZVxuICAgICAqIERhdGEgaXMgbG9hZGVkIGZyb20gdGhlIGxvY2FsIGRiIGNhY2hlLCB3aGlsZSBpdCB3YWl0cyBmb3IgdGhlIHNlcnZlciB0byByZXNwb25kLlxuICAgICAqICogZWl0aGVyIGZyb20gdGhlIHNlcnZlciwgb3IgaWYgdGhleSBhcmUgbG9jYWxseSB1cGRhdGVkIHdpdGggdXBkYXRlT2ZmbGluZVN0YXRlKClcbiAgICAgKlxuICAgICAqIEBwYXJhbSB7VHVwbGVTZWxlY3Rvcn0gdHVwbGVTZWxlY3RvclxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gZGlzYWJsZUNhY2hlXG4gICAgICogQHBhcmFtIHtib29sZWFufSBkaXNhYmxlU3RvcmFnZVxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gZGlzYWJsZUFza1NlcnZlciBVc2UgdGhpcyB0byBzdG9yZSBhbmQgb2JzZXJ2ZSBkYXRhIGNvbXBsZXRlbHlcbiAgICAgKiAgICAgIHdpdGhpbiB0aGUgYW5ndWxhciBhcHAuXG4gICAgICogQHJldHVybnMge1N1YmplY3Q8VHVwbGVbXT59XG4gICAgICovXG4gICAgc3Vic2NyaWJlVG9UdXBsZVNlbGVjdG9yKFxuICAgICAgICB0dXBsZVNlbGVjdG9yOiBUdXBsZVNlbGVjdG9yLFxuICAgICAgICBkaXNhYmxlQ2FjaGU6IGJvb2xlYW4gPSBmYWxzZSxcbiAgICAgICAgZGlzYWJsZVN0b3JhZ2U6IGJvb2xlYW4gPSBmYWxzZSxcbiAgICAgICAgZGlzYWJsZUFza1NlcnZlcjogYm9vbGVhbiA9IGZhbHNlLFxuICAgICk6IE9ic2VydmFibGU8VHVwbGVbXT4ge1xuICAgICAgICBsZXQgdHNTdHIgPSB0dXBsZVNlbGVjdG9yLnRvT3JkZXJlZEpzb25TdHIoKTtcbiAgICAgICAgbGV0IGNhY2hlZERhdGE6IENhY2hlZFN1YnNjcmliZWREYXRhIHwgbnVsbCA9IG51bGw7XG5cbiAgICAgICAgLy8gSWYgdGhlIGNhY2hlIGV4aXN0cywgdXNlIGl0XG4gICAgICAgIGlmICh0aGlzLmNhY2hlQnlUdXBsZVNlbGVjdG9yLmhhc093blByb3BlcnR5KHRzU3RyKSkge1xuICAgICAgICAgICAgY2FjaGVkRGF0YSA9IHRoaXMuY2FjaGVCeVR1cGxlU2VsZWN0b3JbdHNTdHJdO1xuICAgICAgICAgICAgLy8gTWF5YmUgZGlzYWJsZSBjYWNoZVxuICAgICAgICAgICAgY2FjaGVkRGF0YS5jYWNoZUVuYWJsZWQgPSBjYWNoZWREYXRhLmNhY2hlRW5hYmxlZCAmJiAhZGlzYWJsZUNhY2hlO1xuICAgICAgICAgICAgLy8gTWF5YmUgZGlzYWJsZSBzdG9yYWdlXG4gICAgICAgICAgICBjYWNoZWREYXRhLnN0b3JhZ2VFbmFibGVkID1cbiAgICAgICAgICAgICAgICBjYWNoZWREYXRhLnN0b3JhZ2VFbmFibGVkICYmICFkaXNhYmxlU3RvcmFnZTtcbiAgICAgICAgICAgIC8vIE1heWJlIGRpc2FibGUgYXNrIHNlcnZlclxuICAgICAgICAgICAgY2FjaGVkRGF0YS5hc2tTZXJ2ZXJFbmFibGVkID1cbiAgICAgICAgICAgICAgICBjYWNoZWREYXRhLmFza1NlcnZlckVuYWJsZWQgJiYgIWRpc2FibGVBc2tTZXJ2ZXI7XG5cbiAgICAgICAgICAgIC8vIElmIHRoZSBjYWNoZSBpcyBlbmFibGVkLCBhbmQgd2UgaGF2ZSB0dXBsZSBkYXRhLCB0aGVuIG5vdGlmeVxuICAgICAgICAgICAgaWYgKGNhY2hlZERhdGEuY2FjaGVFbmFibGVkICYmIGNhY2hlZERhdGEudHVwbGVzICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gY2FjaGVkRGF0YS5vYnNlcnZhYmxlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBFTFNFLCBDcmVhdGUgdGhlI