UNPKG

matrix-react-sdk

Version:
605 lines (567 loc) 93.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.RoomListStoreClass = exports.LISTS_UPDATE_EVENT = exports.LISTS_LOADING_EVENT = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _matrix = require("matrix-js-sdk/src/matrix"); var _types = require("matrix-js-sdk/src/types"); var _logger = require("matrix-js-sdk/src/logger"); var _SettingsStore = _interopRequireDefault(require("../../settings/SettingsStore")); var _models = require("./models"); var _models2 = require("./algorithms/models"); var _dispatcher = _interopRequireDefault(require("../../dispatcher/dispatcher")); var _readReceipts = require("../../utils/read-receipts"); var _IFilterCondition = require("./filters/IFilterCondition"); var _Algorithm = require("./algorithms/Algorithm"); var _membership = require("../../utils/membership"); var _RoomListLayoutStore = _interopRequireDefault(require("./RoomListLayoutStore")); var _MarkedExecution = require("../../utils/MarkedExecution"); var _AsyncStoreWithClient = require("../AsyncStoreWithClient"); var _RoomNotificationStateStore = require("../notifications/RoomNotificationStateStore"); var _VisibilityProvider = require("./filters/VisibilityProvider"); var _SpaceWatcher = require("./SpaceWatcher"); var _Interface = require("./Interface"); var _SlidingRoomListStore = require("./SlidingRoomListStore"); var _AsyncStore = require("../AsyncStore"); var _SDKContext = require("../../contexts/SDKContext"); var _roomMute = require("./utils/roomMute"); /* Copyright 2024 New Vector Ltd. Copyright 2018-2022 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ const LISTS_UPDATE_EVENT = exports.LISTS_UPDATE_EVENT = _Interface.RoomListStoreEvent.ListsUpdate; const LISTS_LOADING_EVENT = exports.LISTS_LOADING_EVENT = _Interface.RoomListStoreEvent.ListsLoading; // unused; used by SlidingRoomListStore class RoomListStoreClass extends _AsyncStoreWithClient.AsyncStoreWithClient { constructor(dis) { super(dis); (0, _defineProperty2.default)(this, "initialListsGenerated", false); (0, _defineProperty2.default)(this, "msc3946ProcessDynamicPredecessor", void 0); (0, _defineProperty2.default)(this, "msc3946SettingWatcherRef", void 0); (0, _defineProperty2.default)(this, "algorithm", new _Algorithm.Algorithm()); (0, _defineProperty2.default)(this, "prefilterConditions", []); (0, _defineProperty2.default)(this, "updateFn", new _MarkedExecution.MarkedExecution(() => { for (const tagId of Object.keys(this.orderedLists)) { _RoomNotificationStateStore.RoomNotificationStateStore.instance.getListState(tagId).setRooms(this.orderedLists[tagId]); } this.emit(LISTS_UPDATE_EVENT); })); (0, _defineProperty2.default)(this, "onAlgorithmListUpdated", forceUpdate => { this.updateFn.mark(); if (forceUpdate) this.updateFn.trigger(); }); (0, _defineProperty2.default)(this, "onAlgorithmFilterUpdated", () => { // The filter can happen off-cycle, so trigger an update. The filter will have // already caused a mark. this.updateFn.trigger(); }); (0, _defineProperty2.default)(this, "onPrefilterUpdated", async () => { await this.recalculatePrefiltering(); this.updateFn.trigger(); }); this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares this.algorithm.start(); this.msc3946ProcessDynamicPredecessor = _SettingsStore.default.getValue("feature_dynamic_room_predecessors"); this.msc3946SettingWatcherRef = _SettingsStore.default.watchSetting("feature_dynamic_room_predecessors", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { this.msc3946ProcessDynamicPredecessor = newVal; this.regenerateAllLists({ trigger: true }); }); } componentWillUnmount() { _SettingsStore.default.unwatchSetting(this.msc3946SettingWatcherRef); } setupWatchers() { // TODO: Maybe destroy this if this class supports destruction new _SpaceWatcher.SpaceWatcher(this); } get orderedLists() { if (!this.algorithm) return {}; // No tags yet. return this.algorithm.getOrderedRooms(); } // Intended for test usage async resetStore() { await this.reset(); this.prefilterConditions = []; this.initialListsGenerated = false; this.algorithm.off(_Algorithm.LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.off(_IFilterCondition.FILTER_CHANGED, this.onAlgorithmListUpdated); this.algorithm.stop(); this.algorithm = new _Algorithm.Algorithm(); this.algorithm.on(_Algorithm.LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.on(_IFilterCondition.FILTER_CHANGED, this.onAlgorithmListUpdated); // Reset state without causing updates as the client will have been destroyed // and downstream code will throw NPE errors. await this.reset(null, true); } // Public for test usage. Do not call this. async makeReady(forcedClient) { if (forcedClient) { this.readyStore.useUnitTestClient(forcedClient); } _SDKContext.SdkContextClass.instance.roomViewStore.addListener(_AsyncStore.UPDATE_EVENT, () => this.handleRVSUpdate({})); this.algorithm.on(_Algorithm.LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); this.algorithm.on(_IFilterCondition.FILTER_CHANGED, this.onAlgorithmFilterUpdated); this.setupWatchers(); // Update any settings here, as some may have happened before we were logically ready. _logger.logger.log("Regenerating room lists: Startup"); this.updateAlgorithmInstances(); this.regenerateAllLists({ trigger: false }); this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed this.updateFn.mark(); // we almost certainly want to trigger an update. this.updateFn.trigger(); } /** * Handles suspected RoomViewStore changes. * @param trigger Set to false to prevent a list update from being sent. Should only * be used if the calling code will manually trigger the update. */ handleRVSUpdate({ trigger = true }) { if (!this.matrixClient) return; // We assume there won't be RVS updates without a client const activeRoomId = _SDKContext.SdkContextClass.instance.roomViewStore.getRoomId(); if (!activeRoomId && this.algorithm.stickyRoom) { this.algorithm.setStickyRoom(null); } else if (activeRoomId) { const activeRoom = this.matrixClient.getRoom(activeRoomId); if (!activeRoom) { _logger.logger.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`); this.algorithm.setStickyRoom(null); } else if (activeRoom !== this.algorithm.stickyRoom) { this.algorithm.setStickyRoom(activeRoom); } } if (trigger) this.updateFn.trigger(); } async onReady() { await this.makeReady(); } async onNotReady() { await this.resetStore(); } async onAction(payload) { // If we're not remotely ready, don't even bother scheduling the dispatch handling. // This is repeated in the handler just in case things change between a decision here and // when the timer fires. const logicallyReady = this.matrixClient && this.initialListsGenerated; if (!logicallyReady) return; // When we're running tests we can't reliably use setImmediate out of timing concerns. // As such, we use a more synchronous model. if (RoomListStoreClass.TEST_MODE) { await this.onDispatchAsync(payload); return; } // We do this to intentionally break out of the current event loop task, allowing // us to instead wait for a more convenient time to run our updates. setTimeout(() => this.onDispatchAsync(payload)); } async onDispatchAsync(payload) { // Everything here requires a MatrixClient or some sort of logical readiness. if (!this.matrixClient || !this.initialListsGenerated) return; if (!this.algorithm) { // This shouldn't happen because `initialListsGenerated` implies we have an algorithm. throw new Error("Room list store has no algorithm to process dispatcher update with"); } if (payload.action === "MatrixActions.Room.receipt") { // First see if the receipt event is for our own user. If it was, trigger // a room update (we probably read the room on a different device). if ((0, _readReceipts.readReceiptChangeIsFor)(payload.event, this.matrixClient)) { const room = payload.room; if (!room) { _logger.logger.warn(`Own read receipt was in unknown room ${room.roomId}`); return; } await this.handleRoomUpdate(room, _models.RoomUpdateCause.ReadReceipt); this.updateFn.trigger(); return; } } else if (payload.action === "MatrixActions.Room.tags") { const roomPayload = payload; // TODO: Type out the dispatcher types await this.handleRoomUpdate(roomPayload.room, _models.RoomUpdateCause.PossibleTagChange); this.updateFn.trigger(); } else if (payload.action === "MatrixActions.Room.timeline") { const eventPayload = payload; // Ignore non-live events (backfill) and notification timeline set events (without a room) if (!eventPayload.isLiveEvent || !eventPayload.isLiveUnfilteredRoomTimelineEvent || !eventPayload.room) { return; } const roomId = eventPayload.event.getRoomId(); const room = this.matrixClient.getRoom(roomId); const tryUpdate = async updatedRoom => { if (eventPayload.event.getType() === _matrix.EventType.RoomTombstone && eventPayload.event.getStateKey() === "") { const newRoom = this.matrixClient?.getRoom(eventPayload.event.getContent()["replacement_room"]); if (newRoom) { // If we have the new room, then the new room check will have seen the predecessor // and did the required updates, so do nothing here. return; } } // If the join rule changes we need to update the tags for the room. // A conference tag is determined by the room public join rule. if (eventPayload.event.getType() === _matrix.EventType.RoomJoinRules) await this.handleRoomUpdate(updatedRoom, _models.RoomUpdateCause.PossibleTagChange);else await this.handleRoomUpdate(updatedRoom, _models.RoomUpdateCause.Timeline); this.updateFn.trigger(); }; if (!room) { _logger.logger.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`); _logger.logger.warn(`Queuing failed room update for retry as a result.`); window.setTimeout(async () => { const updatedRoom = this.matrixClient?.getRoom(roomId); if (updatedRoom) { await tryUpdate(updatedRoom); } }, 100); // 100ms should be enough for the room to show up return; } else { await tryUpdate(room); } } else if (payload.action === "MatrixActions.Event.decrypted") { const eventPayload = payload; // TODO: Type out the dispatcher types const roomId = eventPayload.event.getRoomId(); if (!roomId) { return; } const room = this.matrixClient.getRoom(roomId); if (!room) { _logger.logger.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`); return; } await this.handleRoomUpdate(room, _models.RoomUpdateCause.Timeline); this.updateFn.trigger(); } else if (payload.action === "MatrixActions.accountData" && payload.event_type === _matrix.EventType.Direct) { const eventPayload = payload; // TODO: Type out the dispatcher types const dmMap = eventPayload.event.getContent(); for (const userId of Object.keys(dmMap)) { const roomIds = dmMap[userId]; for (const roomId of roomIds) { const room = this.matrixClient.getRoom(roomId); if (!room) { _logger.logger.warn(`${roomId} was found in DMs but the room is not in the store`); continue; } // We expect this RoomUpdateCause to no-op if there's no change, and we don't expect // the user to have hundreds of rooms to update in one event. As such, we just hammer // away at updates until the problem is solved. If we were expecting more than a couple // of rooms to be updated at once, we would consider batching the rooms up. await this.handleRoomUpdate(room, _models.RoomUpdateCause.PossibleTagChange); } } this.updateFn.trigger(); } else if (payload.action === "MatrixActions.Room.myMembership") { this.onDispatchMyMembership(payload); return; } const possibleMuteChangeRoomIds = (0, _roomMute.getChangedOverrideRoomMutePushRules)(payload); if (possibleMuteChangeRoomIds) { for (const roomId of possibleMuteChangeRoomIds) { const room = roomId && this.matrixClient.getRoom(roomId); if (room) { await this.handleRoomUpdate(room, _models.RoomUpdateCause.PossibleMuteChange); } } this.updateFn.trigger(); } } /** * Handle a MatrixActions.Room.myMembership event from the dispatcher. * * Public for test. */ async onDispatchMyMembership(membershipPayload) { // TODO: Type out the dispatcher types so membershipPayload is not any const oldMembership = (0, _membership.getEffectiveMembership)(membershipPayload.oldMembership); const newMembership = (0, _membership.getEffectiveMembershipTag)(membershipPayload.room, membershipPayload.membership); if (oldMembership !== _membership.EffectiveMembership.Join && newMembership === _membership.EffectiveMembership.Join) { // If we're joining an upgraded room, we'll want to make sure we don't proliferate // the dead room in the list. const roomState = membershipPayload.room.currentState; const predecessor = roomState.findPredecessor(this.msc3946ProcessDynamicPredecessor); if (predecessor) { const prevRoom = this.matrixClient?.getRoom(predecessor.roomId); if (prevRoom) { const isSticky = this.algorithm.stickyRoom === prevRoom; if (isSticky) { this.algorithm.setStickyRoom(null); } // Note: we hit the algorithm instead of our handleRoomUpdate() function to // avoid redundant updates. this.algorithm.handleRoomUpdate(prevRoom, _models.RoomUpdateCause.RoomRemoved); } else { _logger.logger.warn(`Unable to find predecessor room with id ${predecessor.roomId}`); } } await this.handleRoomUpdate(membershipPayload.room, _models.RoomUpdateCause.NewRoom); this.updateFn.trigger(); return; } if (oldMembership !== _membership.EffectiveMembership.Invite && newMembership === _membership.EffectiveMembership.Invite) { await this.handleRoomUpdate(membershipPayload.room, _models.RoomUpdateCause.NewRoom); this.updateFn.trigger(); return; } // If it's not a join, it's transitioning into a different list (possibly historical) if (oldMembership !== newMembership) { await this.handleRoomUpdate(membershipPayload.room, _models.RoomUpdateCause.PossibleTagChange); this.updateFn.trigger(); return; } } async handleRoomUpdate(room, cause) { if (cause === _models.RoomUpdateCause.NewRoom && room.getMyMembership() === _types.KnownMembership.Invite) { // Let the visibility provider know that there is a new invited room. It would be nice // if this could just be an event that things listen for but the point of this is that // we delay doing anything about this room until the VoipUserMapper had had a chance // to do the things it needs to do to decide if we should show this room or not, so // an even wouldn't et us do that. await _VisibilityProvider.VisibilityProvider.instance.onNewInvitedRoom(room); } if (!_VisibilityProvider.VisibilityProvider.instance.isRoomVisible(room)) { return; // don't do anything on rooms that aren't visible } if ((cause === _models.RoomUpdateCause.NewRoom || cause === _models.RoomUpdateCause.PossibleTagChange) && !this.prefilterConditions.every(c => c.isVisible(room))) { return; // don't do anything on new/moved rooms which ought not to be shown } const shouldUpdate = this.algorithm.handleRoomUpdate(room, cause); if (shouldUpdate) { this.updateFn.mark(); } } async recalculatePrefiltering() { if (!this.algorithm) return; if (!this.algorithm.hasTagSortingMap) return; // we're still loading // Inhibit updates because we're about to lie heavily to the algorithm this.algorithm.updatesInhibited = true; // Figure out which rooms are about to be valid, and the state of affairs const rooms = this.getPlausibleRooms(); const currentSticky = this.algorithm.stickyRoom; const stickyIsStillPresent = currentSticky && rooms.includes(currentSticky); // Reset the sticky room before resetting the known rooms so the algorithm // doesn't freak out. this.algorithm.setStickyRoom(null); this.algorithm.setKnownRooms(rooms); // Set the sticky room back, if needed, now that we have updated the store. // This will use relative stickyness to the new room set. if (stickyIsStillPresent) { this.algorithm.setStickyRoom(currentSticky); } // Finally, mark an update and resume updates from the algorithm this.updateFn.mark(); this.algorithm.updatesInhibited = false; } setTagSorting(tagId, sort) { this.setAndPersistTagSorting(tagId, sort); this.updateFn.trigger(); } setAndPersistTagSorting(tagId, sort) { this.algorithm.setTagSorting(tagId, sort); // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 localStorage.setItem(`mx_tagSort_${tagId}`, sort); } getTagSorting(tagId) { return this.algorithm.getTagSorting(tagId); } // noinspection JSMethodCanBeStatic getStoredTagSorting(tagId) { // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 return localStorage.getItem(`mx_tagSort_${tagId}`); } // logic must match calculateListOrder calculateTagSorting(tagId) { const definedSort = this.getTagSorting(tagId); const storedSort = this.getStoredTagSorting(tagId); // We use the following order to determine which of the 4 flags to use: // Stored > Settings > Defined > Default let tagSort = _models2.SortAlgorithm.Recent; if (storedSort) { tagSort = storedSort; } else if (definedSort) { tagSort = definedSort; } // else default (already set) return tagSort; } setListOrder(tagId, order) { this.setAndPersistListOrder(tagId, order); this.updateFn.trigger(); } setAndPersistListOrder(tagId, order) { this.algorithm.setListOrdering(tagId, order); // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 localStorage.setItem(`mx_listOrder_${tagId}`, order); } getListOrder(tagId) { return this.algorithm.getListOrdering(tagId); } // noinspection JSMethodCanBeStatic getStoredListOrder(tagId) { // TODO: Per-account? https://github.com/vector-im/element-web/issues/14114 return localStorage.getItem(`mx_listOrder_${tagId}`); } // logic must match calculateTagSorting calculateListOrder(tagId) { const defaultOrder = _models2.ListAlgorithm.Natural; const definedOrder = this.getListOrder(tagId); const storedOrder = this.getStoredListOrder(tagId); // We use the following order to determine which of the 4 flags to use: // Stored > Settings > Defined > Default let listOrder = defaultOrder; if (storedOrder) { listOrder = storedOrder; } else if (definedOrder) { listOrder = definedOrder; } // else default (already set) return listOrder; } updateAlgorithmInstances() { // We'll require an update, so mark for one. Marking now also prevents the calls // to setTagSorting and setListOrder from causing triggers. this.updateFn.mark(); for (const tag of Object.keys(this.orderedLists)) { const definedSort = this.getTagSorting(tag); const definedOrder = this.getListOrder(tag); const tagSort = this.calculateTagSorting(tag); const listOrder = this.calculateListOrder(tag); if (tagSort !== definedSort) { this.setAndPersistTagSorting(tag, tagSort); } if (listOrder !== definedOrder) { this.setAndPersistListOrder(tag, listOrder); } } } getPlausibleRooms() { if (!this.matrixClient) return []; let rooms = this.matrixClient.getVisibleRooms(this.msc3946ProcessDynamicPredecessor); rooms = rooms.filter(r => _VisibilityProvider.VisibilityProvider.instance.isRoomVisible(r)); if (this.prefilterConditions.length > 0) { rooms = rooms.filter(r => { for (const filter of this.prefilterConditions) { if (!filter.isVisible(r)) { return false; } } return true; }); } return rooms; } /** * Regenerates the room whole room list, discarding any previous results. * * Note: This is only exposed externally for the tests. Do not call this from within * the app. * @param trigger Set to false to prevent a list update from being sent. Should only * be used if the calling code will manually trigger the update. */ regenerateAllLists({ trigger = true }) { _logger.logger.warn("Regenerating all room lists"); const rooms = this.getPlausibleRooms(); const sorts = {}; const orders = {}; const allTags = [..._models.OrderedDefaultTagIDs]; for (const tagId of allTags) { sorts[tagId] = this.calculateTagSorting(tagId); orders[tagId] = this.calculateListOrder(tagId); _RoomListLayoutStore.default.instance.ensureLayoutExists(tagId); } this.algorithm.populateTags(sorts, orders); this.algorithm.setKnownRooms(rooms); this.initialListsGenerated = true; if (trigger) this.updateFn.trigger(); } /** * Adds a filter condition to the room list store. Filters may be applied async, * and thus might not cause an update to the store immediately. * @param {IFilterCondition} filter The filter condition to add. */ async addFilter(filter) { filter.on(_IFilterCondition.FILTER_CHANGED, this.onPrefilterUpdated); this.prefilterConditions.push(filter); const promise = this.recalculatePrefiltering(); promise.then(() => this.updateFn.trigger()); } /** * Removes a filter condition from the room list store. If the filter was * not previously added to the room list store, this will no-op. The effects * of removing a filter may be applied async and therefore might not cause * an update right away. * @param {IFilterCondition} filter The filter condition to remove. */ removeFilter(filter) { let promise = Promise.resolve(); let removed = false; const idx = this.prefilterConditions.indexOf(filter); if (idx >= 0) { filter.off(_IFilterCondition.FILTER_CHANGED, this.onPrefilterUpdated); this.prefilterConditions.splice(idx, 1); promise = this.recalculatePrefiltering(); removed = true; } if (removed) { promise.then(() => this.updateFn.trigger()); } } /** * Gets the tags for a room identified by the store. The returned set * should never be empty, and will contain DefaultTagID.Untagged if * the store is not aware of any tags. * @param room The room to get the tags for. * @returns The tags for the room. */ getTagsForRoom(room) { const algorithmTags = this.algorithm.getTagsForRoom(room); if (!algorithmTags) return [_models.DefaultTagID.Untagged]; return algorithmTags; } getCount(tagId) { // The room list store knows about all the rooms, so just return the length. return this.orderedLists[tagId].length || 0; } /** * Manually update a room with a given cause. This should only be used if the * room list store would otherwise be incapable of doing the update itself. Note * that this may race with the room list's regular operation. * @param {Room} room The room to update. * @param {RoomUpdateCause} cause The cause to update for. */ async manualRoomUpdate(room, cause) { await this.handleRoomUpdate(room, cause); this.updateFn.trigger(); } } exports.RoomListStoreClass = RoomListStoreClass; /** * Set to true if you're running tests on the store. Should not be touched in * any other environment. */ (0, _defineProperty2.default)(RoomListStoreClass, "TEST_MODE", false); class RoomListStore { static get instance() { if (!RoomListStore.internalInstance) { if (_SettingsStore.default.getValue("feature_sliding_sync")) { _logger.logger.info("using SlidingRoomListStoreClass"); const instance = new _SlidingRoomListStore.SlidingRoomListStoreClass(_dispatcher.default, _SDKContext.SdkContextClass.instance); instance.start(); RoomListStore.internalInstance = instance; } else { const instance = new RoomListStoreClass(_dispatcher.default); instance.start(); RoomListStore.internalInstance = instance; } } return this.internalInstance; } } exports.default = RoomListStore; (0, _defineProperty2.default)(RoomListStore, "internalInstance", void 0); window.mxRoomListStore = RoomListStore.instance; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4IiwicmVxdWlyZSIsIl90eXBlcyIsIl9sb2dnZXIiLCJfU2V0dGluZ3NTdG9yZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfbW9kZWxzIiwiX21vZGVsczIiLCJfZGlzcGF0Y2hlciIsIl9yZWFkUmVjZWlwdHMiLCJfSUZpbHRlckNvbmRpdGlvbiIsIl9BbGdvcml0aG0iLCJfbWVtYmVyc2hpcCIsIl9Sb29tTGlzdExheW91dFN0b3JlIiwiX01hcmtlZEV4ZWN1dGlvbiIsIl9Bc3luY1N0b3JlV2l0aENsaWVudCIsIl9Sb29tTm90aWZpY2F0aW9uU3RhdGVTdG9yZSIsIl9WaXNpYmlsaXR5UHJvdmlkZXIiLCJfU3BhY2VXYXRjaGVyIiwiX0ludGVyZmFjZSIsIl9TbGlkaW5nUm9vbUxpc3RTdG9yZSIsIl9Bc3luY1N0b3JlIiwiX1NES0NvbnRleHQiLCJfcm9vbU11dGUiLCJMSVNUU19VUERBVEVfRVZFTlQiLCJleHBvcnRzIiwiUm9vbUxpc3RTdG9yZUV2ZW50IiwiTGlzdHNVcGRhdGUiLCJMSVNUU19MT0FESU5HX0VWRU5UIiwiTGlzdHNMb2FkaW5nIiwiUm9vbUxpc3RTdG9yZUNsYXNzIiwiQXN5bmNTdG9yZVdpdGhDbGllbnQiLCJjb25zdHJ1Y3RvciIsImRpcyIsIl9kZWZpbmVQcm9wZXJ0eTIiLCJkZWZhdWx0IiwiQWxnb3JpdGhtIiwiTWFya2VkRXhlY3V0aW9uIiwidGFnSWQiLCJPYmplY3QiLCJrZXlzIiwib3JkZXJlZExpc3RzIiwiUm9vbU5vdGlmaWNhdGlvblN0YXRlU3RvcmUiLCJpbnN0YW5jZSIsImdldExpc3RTdGF0ZSIsInNldFJvb21zIiwiZW1pdCIsImZvcmNlVXBkYXRlIiwidXBkYXRlRm4iLCJtYXJrIiwidHJpZ2dlciIsInJlY2FsY3VsYXRlUHJlZmlsdGVyaW5nIiwic2V0TWF4TGlzdGVuZXJzIiwiYWxnb3JpdGhtIiwic3RhcnQiLCJtc2MzOTQ2UHJvY2Vzc0R5bmFtaWNQcmVkZWNlc3NvciIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsIm1zYzM5NDZTZXR0aW5nV2F0Y2hlclJlZiIsIndhdGNoU2V0dGluZyIsIl9zZXR0aW5nTmFtZSIsIl9yb29tSWQiLCJfbGV2ZWwiLCJfbmV3VmFsQXRMZXZlbCIsIm5ld1ZhbCIsInJlZ2VuZXJhdGVBbGxMaXN0cyIsImNvbXBvbmVudFdpbGxVbm1vdW50IiwidW53YXRjaFNldHRpbmciLCJzZXR1cFdhdGNoZXJzIiwiU3BhY2VXYXRjaGVyIiwiZ2V0T3JkZXJlZFJvb21zIiwicmVzZXRTdG9yZSIsInJlc2V0IiwicHJlZmlsdGVyQ29uZGl0aW9ucyIsImluaXRpYWxMaXN0c0dlbmVyYXRlZCIsIm9mZiIsIkxJU1RfVVBEQVRFRF9FVkVOVCIsIm9uQWxnb3JpdGhtTGlzdFVwZGF0ZWQiLCJGSUxURVJfQ0hBTkdFRCIsInN0b3AiLCJvbiIsIm1ha2VSZWFkeSIsImZvcmNlZENsaWVudCIsInJlYWR5U3RvcmUiLCJ1c2VVbml0VGVzdENsaWVudCIsIlNka0NvbnRleHRDbGFzcyIsInJvb21WaWV3U3RvcmUiLCJhZGRMaXN0ZW5lciIsIlVQREFURV9FVkVOVCIsImhhbmRsZVJWU1VwZGF0ZSIsIm9uQWxnb3JpdGhtRmlsdGVyVXBkYXRlZCIsImxvZ2dlciIsImxvZyIsInVwZGF0ZUFsZ29yaXRobUluc3RhbmNlcyIsIm1hdHJpeENsaWVudCIsImFjdGl2ZVJvb21JZCIsImdldFJvb21JZCIsInN0aWNreVJvb20iLCJzZXRTdGlja3lSb29tIiwiYWN0aXZlUm9vbSIsImdldFJvb20iLCJ3YXJuIiwib25SZWFkeSIsIm9uTm90UmVhZHkiLCJvbkFjdGlvbiIsInBheWxvYWQiLCJsb2dpY2FsbHlSZWFkeSIsIlRFU1RfTU9ERSIsIm9uRGlzcGF0Y2hBc3luYyIsInNldFRpbWVvdXQiLCJFcnJvciIsImFjdGlvbiIsInJlYWRSZWNlaXB0Q2hhbmdlSXNGb3IiLCJldmVudCIsInJvb20iLCJyb29tSWQiLCJoYW5kbGVSb29tVXBkYXRlIiwiUm9vbVVwZGF0ZUNhdXNlIiwiUmVhZFJlY2VpcHQiLCJyb29tUGF5bG9hZCIsIlBvc3NpYmxlVGFnQ2hhbmdlIiwiZXZlbnRQYXlsb2FkIiwiaXNMaXZlRXZlbnQiLCJpc0xpdmVVbmZpbHRlcmVkUm9vbVRpbWVsaW5lRXZlbnQiLCJ0cnlVcGRhdGUiLCJ1cGRhdGVkUm9vbSIsImdldFR5cGUiLCJFdmVudFR5cGUiLCJSb29tVG9tYnN0b25lIiwiZ2V0U3RhdGVLZXkiLCJuZXdSb29tIiwiZ2V0Q29udGVudCIsIlJvb21Kb2luUnVsZXMiLCJUaW1lbGluZSIsImdldElkIiwid2luZG93IiwiZXZlbnRfdHlwZSIsIkRpcmVjdCIsImRtTWFwIiwidXNlcklkIiwicm9vbUlkcyIsIm9uRGlzcGF0Y2hNeU1lbWJlcnNoaXAiLCJwb3NzaWJsZU11dGVDaGFuZ2VSb29tSWRzIiwiZ2V0Q2hhbmdlZE92ZXJyaWRlUm9vbU11dGVQdXNoUnVsZXMiLCJQb3NzaWJsZU11dGVDaGFuZ2UiLCJtZW1iZXJzaGlwUGF5bG9hZCIsIm9sZE1lbWJlcnNoaXAiLCJnZXRFZmZlY3RpdmVNZW1iZXJzaGlwIiwibmV3TWVtYmVyc2hpcCIsImdldEVmZmVjdGl2ZU1lbWJlcnNoaXBUYWciLCJtZW1iZXJzaGlwIiwiRWZmZWN0aXZlTWVtYmVyc2hpcCIsIkpvaW4iLCJyb29tU3RhdGUiLCJjdXJyZW50U3RhdGUiLCJwcmVkZWNlc3NvciIsImZpbmRQcmVkZWNlc3NvciIsInByZXZSb29tIiwiaXNTdGlja3kiLCJSb29tUmVtb3ZlZCIsIk5ld1Jvb20iLCJJbnZpdGUiLCJjYXVzZSIsImdldE15TWVtYmVyc2hpcCIsIktub3duTWVtYmVyc2hpcCIsIlZpc2liaWxpdHlQcm92aWRlciIsIm9uTmV3SW52aXRlZFJvb20iLCJpc1Jvb21WaXNpYmxlIiwiZXZlcnkiLCJjIiwiaXNWaXNpYmxlIiwic2hvdWxkVXBkYXRlIiwiaGFzVGFnU29ydGluZ01hcCIsInVwZGF0ZXNJbmhpYml0ZWQiLCJyb29tcyIsImdldFBsYXVzaWJsZVJvb21zIiwiY3VycmVudFN0aWNreSIsInN0aWNreUlzU3RpbGxQcmVzZW50IiwiaW5jbHVkZXMiLCJzZXRLbm93blJvb21zIiwic2V0VGFnU29ydGluZyIsInNvcnQiLCJzZXRBbmRQZXJzaXN0VGFnU29ydGluZyIsImxvY2FsU3RvcmFnZSIsInNldEl0ZW0iLCJnZXRUYWdTb3J0aW5nIiwiZ2V0U3RvcmVkVGFnU29ydGluZyIsImdldEl0ZW0iLCJjYWxjdWxhdGVUYWdTb3J0aW5nIiwiZGVmaW5lZFNvcnQiLCJzdG9yZWRTb3J0IiwidGFnU29ydCIsIlNvcnRBbGdvcml0aG0iLCJSZWNlbnQiLCJzZXRMaXN0T3JkZXIiLCJvcmRlciIsInNldEFuZFBlcnNpc3RMaXN0T3JkZXIiLCJzZXRMaXN0T3JkZXJpbmciLCJnZXRMaXN0T3JkZXIiLCJnZXRMaXN0T3JkZXJpbmciLCJnZXRTdG9yZWRMaXN0T3JkZXIiLCJjYWxjdWxhdGVMaXN0T3JkZXIiLCJkZWZhdWx0T3JkZXIiLCJMaXN0QWxnb3JpdGhtIiwiTmF0dXJhbCIsImRlZmluZWRPcmRlciIsInN0b3JlZE9yZGVyIiwibGlzdE9yZGVyIiwidGFnIiwiZ2V0VmlzaWJsZVJvb21zIiwiZmlsdGVyIiwiciIsImxlbmd0aCIsInNvcnRzIiwib3JkZXJzIiwiYWxsVGFncyIsIk9yZGVyZWREZWZhdWx0VGFnSURzIiwiUm9vbUxpc3RMYXlvdXRTdG9yZSIsImVuc3VyZUxheW91dEV4aXN0cyIsInBvcHVsYXRlVGFncyIsImFkZEZpbHRlciIsIm9uUHJlZmlsdGVyVXBkYXRlZCIsInB1c2giLCJwcm9taXNlIiwidGhlbiIsInJlbW92ZUZpbHRlciIsIlByb21pc2UiLCJyZXNvbHZlIiwicmVtb3ZlZCIsImlkeCIsImluZGV4T2YiLCJzcGxpY2UiLCJnZXRUYWdzRm9yUm9vbSIsImFsZ29yaXRobVRhZ3MiLCJEZWZhdWx0VGFnSUQiLCJVbnRhZ2dlZCIsImdldENvdW50IiwibWFudWFsUm9vbVVwZGF0ZSIsIlJvb21MaXN0U3RvcmUiLCJpbnRlcm5hbEluc3RhbmNlIiwiaW5mbyIsIlNsaWRpbmdSb29tTGlzdFN0b3JlQ2xhc3MiLCJkZWZhdWx0RGlzcGF0Y2hlciIsIm14Um9vbUxpc3RTdG9yZSJdLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zdG9yZXMvcm9vbS1saXN0L1Jvb21MaXN0U3RvcmUudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMTgtMjAyMiBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgeyBNYXRyaXhDbGllbnQsIFJvb20sIFJvb21TdGF0ZSwgRXZlbnRUeXBlIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL21hdHJpeFwiO1xuaW1wb3J0IHsgS25vd25NZW1iZXJzaGlwIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3R5cGVzXCI7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbG9nZ2VyXCI7XG5cbmltcG9ydCBTZXR0aW5nc1N0b3JlIGZyb20gXCIuLi8uLi9zZXR0aW5ncy9TZXR0aW5nc1N0b3JlXCI7XG5pbXBvcnQgeyBEZWZhdWx0VGFnSUQsIE9yZGVyZWREZWZhdWx0VGFnSURzLCBSb29tVXBkYXRlQ2F1c2UsIFRhZ0lEIH0gZnJvbSBcIi4vbW9kZWxzXCI7XG5pbXBvcnQgeyBJTGlzdE9yZGVyaW5nTWFwLCBJVGFnTWFwLCBJVGFnU29ydGluZ01hcCwgTGlzdEFsZ29yaXRobSwgU29ydEFsZ29yaXRobSB9IGZyb20gXCIuL2FsZ29yaXRobXMvbW9kZWxzXCI7XG5pbXBvcnQgeyBBY3Rpb25QYXlsb2FkIH0gZnJvbSBcIi4uLy4uL2Rpc3BhdGNoZXIvcGF5bG9hZHNcIjtcbmltcG9ydCBkZWZhdWx0RGlzcGF0Y2hlciwgeyBNYXRyaXhEaXNwYXRjaGVyIH0gZnJvbSBcIi4uLy4uL2Rpc3BhdGNoZXIvZGlzcGF0Y2hlclwiO1xuaW1wb3J0IHsgcmVhZFJlY2VpcHRDaGFuZ2VJc0ZvciB9IGZyb20gXCIuLi8uLi91dGlscy9yZWFkLXJlY2VpcHRzXCI7XG5pbXBvcnQgeyBGSUxURVJfQ0hBTkdFRCwgSUZpbHRlckNvbmRpdGlvbiB9IGZyb20gXCIuL2ZpbHRlcnMvSUZpbHRlckNvbmRpdGlvblwiO1xuaW1wb3J0IHsgQWxnb3JpdGhtLCBMSVNUX1VQREFURURfRVZFTlQgfSBmcm9tIFwiLi9hbGdvcml0aG1zL0FsZ29yaXRobVwiO1xuaW1wb3J0IHsgRWZmZWN0aXZlTWVtYmVyc2hpcCwgZ2V0RWZmZWN0aXZlTWVtYmVyc2hpcCwgZ2V0RWZmZWN0aXZlTWVtYmVyc2hpcFRhZyB9IGZyb20gXCIuLi8uLi91dGlscy9tZW1iZXJzaGlwXCI7XG5pbXBvcnQgUm9vbUxpc3RMYXlvdXRTdG9yZSBmcm9tIFwiLi9Sb29tTGlzdExheW91dFN0b3JlXCI7XG5pbXBvcnQgeyBNYXJrZWRFeGVjdXRpb24gfSBmcm9tIFwiLi4vLi4vdXRpbHMvTWFya2VkRXhlY3V0aW9uXCI7XG5pbXBvcnQgeyBBc3luY1N0b3JlV2l0aENsaWVudCB9IGZyb20gXCIuLi9Bc3luY1N0b3JlV2l0aENsaWVudFwiO1xuaW1wb3J0IHsgUm9vbU5vdGlmaWNhdGlvblN0YXRlU3RvcmUgfSBmcm9tIFwiLi4vbm90aWZpY2F0aW9ucy9Sb29tTm90aWZpY2F0aW9uU3RhdGVTdG9yZVwiO1xuaW1wb3J0IHsgVmlzaWJpbGl0eVByb3ZpZGVyIH0gZnJvbSBcIi4vZmlsdGVycy9WaXNpYmlsaXR5UHJvdmlkZXJcIjtcbmltcG9ydCB7IFNwYWNlV2F0Y2hlciB9IGZyb20gXCIuL1NwYWNlV2F0Y2hlclwiO1xuaW1wb3J0IHsgSVJvb21UaW1lbGluZUFjdGlvblBheWxvYWQgfSBmcm9tIFwiLi4vLi4vYWN0aW9ucy9NYXRyaXhBY3Rpb25DcmVhdG9yc1wiO1xuaW1wb3J0IHsgUm9vbUxpc3RTdG9yZSBhcyBJbnRlcmZhY2UsIFJvb21MaXN0U3RvcmVFdmVudCB9IGZyb20gXCIuL0ludGVyZmFjZVwiO1xuaW1wb3J0IHsgU2xpZGluZ1Jvb21MaXN0U3RvcmVDbGFzcyB9IGZyb20gXCIuL1NsaWRpbmdSb29tTGlzdFN0b3JlXCI7XG5pbXBvcnQgeyBVUERBVEVfRVZFTlQgfSBmcm9tIFwiLi4vQXN5bmNTdG9yZVwiO1xuaW1wb3J0IHsgU2RrQ29udGV4dENsYXNzIH0gZnJvbSBcIi4uLy4uL2NvbnRleHRzL1NES0NvbnRleHRcIjtcbmltcG9ydCB7IGdldENoYW5nZWRPdmVycmlkZVJvb21NdXRlUHVzaFJ1bGVzIH0gZnJvbSBcIi4vdXRpbHMvcm9vbU11dGVcIjtcblxuaW50ZXJmYWNlIElTdGF0ZSB7XG4gICAgLy8gc3RhdGUgaXMgdHJhY2tlZCBpbiB1bmRlcmx5aW5nIGNsYXNzZXNcbn1cblxuZXhwb3J0IGNvbnN0IExJU1RTX1VQREFURV9FVkVOVCA9IFJvb21MaXN0U3RvcmVFdmVudC5MaXN0c1VwZGF0ZTtcbmV4cG9ydCBjb25zdCBMSVNUU19MT0FESU5HX0VWRU5UID0gUm9vbUxpc3RTdG9yZUV2ZW50Lkxpc3RzTG9hZGluZzsgLy8gdW51c2VkOyB1c2VkIGJ5IFNsaWRpbmdSb29tTGlzdFN0b3JlXG5cbmV4cG9ydCBjbGFzcyBSb29tTGlzdFN0b3JlQ2xhc3MgZXh0ZW5kcyBBc3luY1N0b3JlV2l0aENsaWVudDxJU3RhdGU+IGltcGxlbWVudHMgSW50ZXJmYWNlIHtcbiAgICAvKipcbiAgICAgKiBTZXQgdG8gdHJ1ZSBpZiB5b3UncmUgcnVubmluZyB0ZXN0cyBvbiB0aGUgc3RvcmUuIFNob3VsZCBub3QgYmUgdG91Y2hlZCBpblxuICAgICAqIGFueSBvdGhlciBlbnZpcm9ubWVudC5cbiAgICAgKi9cbiAgICBwdWJsaWMgc3RhdGljIFRFU1RfTU9ERSA9IGZhbHNlO1xuXG4gICAgcHJpdmF0ZSBpbml0aWFsTGlzdHNHZW5lcmF0ZWQgPSBmYWxzZTtcbiAgICBwcml2YXRlIG1zYzM5NDZQcm9jZXNzRHluYW1pY1ByZWRlY2Vzc29yOiBib29sZWFuO1xuICAgIHByaXZhdGUgbXNjMzk0NlNldHRpbmdXYXRjaGVyUmVmOiBzdHJpbmc7XG4gICAgcHJpdmF0ZSBhbGdvcml0aG0gPSBuZXcgQWxnb3JpdGhtKCk7XG4gICAgcHJpdmF0ZSBwcmVmaWx0ZXJDb25kaXRpb25zOiBJRmlsdGVyQ29uZGl0aW9uW10gPSBbXTtcbiAgICBwcml2YXRlIHVwZGF0ZUZuID0gbmV3IE1hcmtlZEV4ZWN1dGlvbigoKSA9PiB7XG4gICAgICAgIGZvciAoY29uc3QgdGFnSWQgb2YgT2JqZWN0LmtleXModGhpcy5vcmRlcmVkTGlzdHMpKSB7XG4gICAgICAgICAgICBSb29tTm90aWZpY2F0aW9uU3RhdGVTdG9yZS5pbnN0YW5jZS5nZXRMaXN0U3RhdGUodGFnSWQpLnNldFJvb21zKHRoaXMub3JkZXJlZExpc3RzW3RhZ0lkXSk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5lbWl0KExJU1RTX1VQREFURV9FVkVOVCk7XG4gICAgfSk7XG5cbiAgICBwdWJsaWMgY29uc3RydWN0b3IoZGlzOiBNYXRyaXhEaXNwYXRjaGVyKSB7XG4gICAgICAgIHN1cGVyKGRpcyk7XG4gICAgICAgIHRoaXMuc2V0TWF4TGlzdGVuZXJzKDIwKTsgLy8gUm9vbUxpc3QgKyBMZWZ0UGFuZWwgKyA4eFJvb21TdWJMaXN0ICsgc3BhcmVzXG4gICAgICAgIHRoaXMuYWxnb3JpdGhtLnN0YXJ0KCk7XG5cbiAgICAgICAgdGhpcy5tc2MzOTQ2UHJvY2Vzc0R5bmFtaWNQcmVkZWNlc3NvciA9IFNldHRpbmdzU3RvcmUuZ2V0VmFsdWUoXCJmZWF0dXJlX2R5bmFtaWNfcm9vbV9wcmVkZWNlc3NvcnNcIik7XG4gICAgICAgIHRoaXMubXNjMzk0NlNldHRpbmdXYXRjaGVyUmVmID0gU2V0dGluZ3NTdG9yZS53YXRjaFNldHRpbmcoXG4gICAgICAgICAgICBcImZlYXR1cmVfZHluYW1pY19yb29tX3ByZWRlY2Vzc29yc1wiLFxuICAgICAgICAgICAgbnVsbCxcbiAgICAgICAgICAgIChfc2V0dGluZ05hbWUsIF9yb29tSWQsIF9sZXZlbCwgX25ld1ZhbEF0TGV2ZWwsIG5ld1ZhbCkgPT4ge1xuICAgICAgICAgICAgICAgIHRoaXMubXNjMzk0NlByb2Nlc3NEeW5hbWljUHJlZGVjZXNzb3IgPSBuZXdWYWw7XG4gICAgICAgICAgICAgICAgdGhpcy5yZWdlbmVyYXRlQWxsTGlzdHMoeyB0cmlnZ2VyOiB0cnVlIH0pO1xuICAgICAgICAgICAgfSxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgY29tcG9uZW50V2lsbFVubW91bnQoKTogdm9pZCB7XG4gICAgICAgIFNldHRpbmdzU3RvcmUudW53YXRjaFNldHRpbmcodGhpcy5tc2MzOTQ2U2V0dGluZ1dhdGNoZXJSZWYpO1xuICAgIH1cblxuICAgIHByaXZhdGUgc2V0dXBXYXRjaGVycygpOiB2b2lkIHtcbiAgICAgICAgLy8gVE9ETzogTWF5YmUgZGVzdHJveSB0aGlzIGlmIHRoaXMgY2xhc3Mgc3VwcG9ydHMgZGVzdHJ1Y3Rpb25cbiAgICAgICAgbmV3IFNwYWNlV2F0Y2hlcih0aGlzKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZ2V0IG9yZGVyZWRMaXN0cygpOiBJVGFnTWFwIHtcbiAgICAgICAgaWYgKCF0aGlzLmFsZ29yaXRobSkgcmV0dXJuIHt9OyAvLyBObyB0YWdzIHlldC5cbiAgICAgICAgcmV0dXJuIHRoaXMuYWxnb3JpdGhtLmdldE9yZGVyZWRSb29tcygpO1xuICAgIH1cblxuICAgIC8vIEludGVuZGVkIGZvciB0ZXN0IHVzYWdlXG4gICAgcHVibGljIGFzeW5jIHJlc2V0U3RvcmUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGF3YWl0IHRoaXMucmVzZXQoKTtcbiAgICAgICAgdGhpcy5wcmVmaWx0ZXJDb25kaXRpb25zID0gW107XG4gICAgICAgIHRoaXMuaW5pdGlhbExpc3RzR2VuZXJhdGVkID0gZmFsc2U7XG5cbiAgICAgICAgdGhpcy5hbGdvcml0aG0ub2ZmKExJU1RfVVBEQVRFRF9FVkVOVCwgdGhpcy5vbkFsZ29yaXRobUxpc3RVcGRhdGVkKTtcbiAgICAgICAgdGhpcy5hbGdvcml0aG0ub2ZmKEZJTFRFUl9DSEFOR0VELCB0aGlzLm9uQWxnb3JpdGhtTGlzdFVwZGF0ZWQpO1xuICAgICAgICB0aGlzLmFsZ29yaXRobS5zdG9wKCk7XG4gICAgICAgIHRoaXMuYWxnb3JpdGhtID0gbmV3IEFsZ29yaXRobSgpO1xuICAgICAgICB0aGlzLmFsZ29yaXRobS5vbihMSVNUX1VQREFURURfRVZFTlQsIHRoaXMub25BbGdvcml0aG1MaXN0VXBkYXRlZCk7XG4gICAgICAgIHRoaXMuYWxnb3JpdGhtLm9uKEZJTFRFUl9DSEFOR0VELCB0aGlzLm9uQWxnb3JpdGhtTGlzdFVwZGF0ZWQpO1xuXG4gICAgICAgIC8vIFJlc2V0IHN0YXRlIHdpdGhvdXQgY2F1c2luZyB1cGRhdGVzIGFzIHRoZSBjbGllbnQgd2lsbCBoYXZlIGJlZW4gZGVzdHJveWVkXG4gICAgICAgIC8vIGFuZCBkb3duc3RyZWFtIGNvZGUgd2lsbCB0aHJvdyBOUEUgZXJyb3JzLlxuICAgICAgICBhd2FpdCB0aGlzLnJlc2V0KG51bGwsIHRydWUpO1xuICAgIH1cblxuICAgIC8vIFB1YmxpYyBmb3IgdGVzdCB1c2FnZS4gRG8gbm90IGNhbGwgdGhpcy5cbiAgICBwdWJsaWMgYXN5bmMgbWFrZVJlYWR5KGZvcmNlZENsaWVudD86IE1hdHJpeENsaWVudCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBpZiAoZm9yY2VkQ2xpZW50KSB7XG4gICAgICAgICAgICB0aGlzLnJlYWR5U3RvcmUudXNlVW5pdFRlc3RDbGllbnQoZm9yY2VkQ2xpZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIFNka0NvbnRleHRDbGFzcy5pbnN0YW5jZS5yb29tVmlld1N0b3JlLmFkZExpc3RlbmVyKFVQREFURV9FVkVOVCwgKCkgPT4gdGhpcy5oYW5kbGVSVlNVcGRhdGUoe30pKTtcbiAgICAgICAgdGhpcy5hbGdvcml0aG0ub24oTElTVF9VUERBVEVEX0VWRU5ULCB0aGlzLm9uQWxnb3JpdGhtTGlzdFVwZGF0ZWQpO1xuICAgICAgICB0aGlzLmFsZ29yaXRobS5vbihGSUxURVJfQ0hBTkdFRCwgdGhpcy5vbkFsZ29yaXRobUZpbHRlclVwZGF0ZWQpO1xuICAgICAgICB0aGlzLnNldHVwV2F0Y2hlcnMoKTtcblxuICAgICAgICAvLyBVcGRhdGUgYW55IHNldHRpbmdzIGhlcmUsIGFzIHNvbWUgbWF5IGhhdmUgaGFwcGVuZWQgYmVmb3JlIHdlIHdlcmUgbG9naWNhbGx5IHJlYWR5LlxuICAgICAgICBsb2dnZXIubG9nKFwiUmVnZW5lcmF0aW5nIHJvb20gbGlzdHM6IFN0YXJ0dXBcIik7XG4gICAgICAgIHRoaXMudXBkYXRlQWxnb3JpdGhtSW5zdGFuY2VzKCk7XG4gICAgICAgIHRoaXMucmVnZW5lcmF0ZUFsbExpc3RzKHsgdHJpZ2dlcjogZmFsc2UgfSk7XG4gICAgICAgIHRoaXMuaGFuZGxlUlZTVXBkYXRlKHsgdHJpZ2dlcjogZmFsc2UgfSk7IC8vIGZha2UgYW4gUlZTIHVwZGF0ZSB0byBhZGp1c3Qgc3RpY2t5IHJvb20sIGlmIG5lZWRlZFxuXG4gICAgICAgIHRoaXMudXBkYXRlRm4ubWFyaygpOyAvLyB3ZSBhbG1vc3QgY2VydGFpbmx5IHdhbnQgdG8gdHJpZ2dlciBhbiB1cGRhdGUuXG4gICAgICAgIHRoaXMudXBkYXRlRm4udHJpZ2dlcigpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEhhbmRsZXMgc3VzcGVjdGVkIFJvb21WaWV3U3RvcmUgY2hhbmdlcy5cbiAgICAgKiBAcGFyYW0gdHJpZ2dlciBTZXQgdG8gZmFsc2UgdG8gcHJldmVudCBhIGxpc3QgdXBkYXRlIGZyb20gYmVpbmcgc2VudC4gU2hvdWxkIG9ubHlcbiAgICAgKiBiZSB1c2VkIGlmIHRoZSBjYWxsaW5nIGNvZGUgd2lsbCBtYW51YWxseSB0cmlnZ2VyIHRoZSB1cGRhdGUuXG4gICAgICovXG4gICAgcHJpdmF0ZSBoYW5kbGVSVlNVcGRhdGUoeyB0cmlnZ2VyID0gdHJ1ZSB9KTogdm9pZCB7XG4gICAgICAgIGlmICghdGhpcy5tYXRyaXhDbGllbnQpIHJldHVybjsgLy8gV2UgYXNzdW1lIHRoZXJlIHdvbid0IGJlIFJWUyB1cGRhdGVzIHdpdGhvdXQgYSBjbGllbnRcblxuICAgICAgICBjb25zdCBhY3RpdmVSb29tSWQgPSBTZGtDb250ZXh0Q2xhc3MuaW5zdGFuY2Uucm9vbVZpZXdTdG9yZS5nZXRSb29tSWQoKTtcbiAgICAgICAgaWYgKCFhY3RpdmVSb29tSWQgJiYgdGhpcy5hbGdvcml0aG0uc3RpY2t5Um9vbSkge1xuICAgICAgICAgICAgdGhpcy5hbGdvcml0aG0uc2V0U3RpY2t5Um9vbShudWxsKTtcbiAgICAgICAgfSBlbHNlIGlmIChhY3RpdmVSb29tSWQpIHtcbiAgICAgICAgICAgIGNvbnN0IGFjdGl2ZVJvb20gPSB0aGlzLm1hdHJpeENsaWVudC5nZXRSb29tKGFjdGl2ZVJvb21JZCk7XG4gICAgICAgICAgICBpZiAoIWFjdGl2ZVJvb20pIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIud2FybihgJHthY3RpdmVSb29tSWR9IGlzIGN1cnJlbnQgaW4gUlZTIGJ1dCBtaXNzaW5nIGZyb20gY2xpZW50IC0gY2xlYXJpbmcgc3RpY2t5IHJvb21gKTtcbiAgICAgICAgICAgICAgICB0aGlzLmFsZ29yaXRobS5zZXRTdGlja3lSb29tKG51bGwpO1xuICAgICAgICAgICAgfSBlbHNlIGlmIChhY3RpdmVSb29tICE9PSB0aGlzLmFsZ29yaXRobS5zdGlja3lSb29tKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5hbGdvcml0aG0uc2V0U3RpY2t5Um9vbShhY3RpdmVSb29tKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0cmlnZ2VyKSB0aGlzLnVwZGF0ZUZuLnRyaWdnZXIoKTtcbiAgICB9XG5cbiAgICBwcm90ZWN0ZWQgYXN5bmMgb25SZWFkeSgpOiBQcm9taXNlPGFueT4ge1xuICAgICAgICBhd2FpdCB0aGlzLm1ha2VSZWFkeSgpO1xuICAgIH1cblxuICAgIHByb3RlY3RlZCBhc3luYyBvbk5vdFJlYWR5KCk6IFByb21pc2U8YW55PiB7XG4gICAgICAgIGF3YWl0IHRoaXMucmVzZXRTdG9yZSgpO1xuICAgIH1cblxuICAgIHByb3RlY3RlZCBhc3luYyBvbkFjdGlvbihwYXlsb2FkOiBBY3Rpb25QYXlsb2FkKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIC8vIElmIHdlJ3JlIG5vdCByZW1vdGVseSByZWFkeSwgZG9uJ3QgZXZlbiBib3RoZXIgc2NoZWR1bGluZyB0aGUgZGlzcGF0Y2ggaGFuZGxpbmcuXG4gICAgICAgIC8vIFRoaXMgaXMgcmVwZWF0ZWQgaW4gdGhlIGhhbmRsZXIganVzdCBpbiBjYXNlIHRoaW5ncyBjaGFuZ2UgYmV0d2VlbiBhIGRlY2lzaW9uIGhlcmUgYW5kXG4gICAgICAgIC8vIHdoZW4gdGhlIHRpbWVyIGZpcmVzLlxuICAgICAgICBjb25zdCBsb2dpY2FsbHlSZWFkeSA9IHRoaXMubWF0cml4Q2xpZW50ICYmIHRoaXMuaW5pdGlhbExpc3RzR2VuZXJhdGVkO1xuICAgICAgICBpZiAoIWxvZ2ljYWxseVJlYWR5KSByZXR1cm47XG5cbiAgICAgICAgLy8gV2hlbiB3ZSdyZSBydW5uaW5nIHRlc3RzIHdlIGNhbid0IHJlbGlhYmx5IHVzZSBzZXRJbW1lZGlhdGUgb3V0IG9mIHRpbWluZyBjb25jZXJucy5cbiAgICAgICAgLy8gQXMgc3VjaCwgd2UgdXNlIGEgbW9yZSBzeW5jaHJvbm91cyBtb2RlbC5cbiAgICAgICAgaWYgKFJvb21MaXN0U3RvcmVDbGFzcy5URVNUX01PREUpIHtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMub25EaXNwYXRjaEFzeW5jKHBheWxvYWQpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gV2UgZG8gdGhpcyB0byBpbnRlbnRpb25hbGx5IGJyZWFrIG91dCBvZiB0aGUgY3VycmVudCBldmVudCBsb29wIHRhc2ssIGFsbG93aW5nXG4gICAgICAgIC8vIHVzIHRvIGluc3RlYWQgd2FpdCBmb3IgYSBtb3JlIGNvbnZlbmllbnQgdGltZSB0byBydW4gb3VyIHVwZGF0ZXMuXG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4gdGhpcy5vbkRpc3BhdGNoQXN5bmMocGF5bG9hZCkpO1xuICAgIH1cblxuICAgIHByb3RlY3RlZCBhc3luYyBvbkRpc3BhdGNoQXN5bmMocGF5bG9hZDogQWN0aW9uUGF5bG9hZCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICAvLyBFdmVyeXRoaW5nIGhlcmUgcmVxdWlyZXMgYSBNYXRyaXhDbGllbnQgb3Igc29tZSBzb3J0IG9mIGxvZ2ljYWwgcmVhZGluZXNzLlxuICAgICAgICBpZiAoIXRoaXMubWF0cml4Q2xpZW50IHx8ICF0aGlzLmluaXRpYWxMaXN0c0dlbmVyYXRlZCkgcmV0dXJuO1xuXG4gICAgICAgIGlmICghdGhpcy5hbGdvcml0aG0pIHtcbiAgICAgICAgICAgIC8vIFRoaXMgc2hvdWxkbid0IGhhcHBlbiBiZWNhdXNlIGBpbml0aWFsTGlzdHNHZW5lcmF0ZWRgIGltcGxpZXMgd2UgaGF2ZSBhbiBhbGdvcml0aG0uXG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJSb29tIGxpc3Qgc3RvcmUgaGFzIG5vIGFsZ29yaXRobSB0byBwcm9jZXNzIGRpc3BhdGNoZXIgdXBkYXRlIHdpdGhcIik7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAocGF5bG9hZC5hY3Rpb24gPT09IFwiTWF0cml4QWN0aW9ucy5Sb29tLnJlY2VpcHRcIikge1xuICAgICAgICAgICAgLy8gRmlyc3Qgc2VlIGlmIHRoZSByZWNlaXB0IGV2ZW50IGlzIGZvciBvdXIgb3duIHVzZXIuIElmIGl0IHdhcywgdHJpZ2dlclxuICAgICAgICAgICAgLy8gYSByb29tIHVwZGF0ZSAod2UgcHJvYmFibHkgcmVhZCB0aGUgcm9vbSBvbiBhIGRpZmZlcmVudCBkZXZpY2UpLlxuICAgICAgICAgICAgaWYgKHJlYWRSZWNlaXB0Q2hhbmdlSXNGb3IocGF5bG9hZC5ldmVudCwgdGhpcy5tYXRyaXhDbGllbnQpKSB7XG4gICAgICAgICAgICAgICAgY29uc3Qgcm9vbSA9IHBheWxvYWQucm9vbTtcbiAgICAgICAgICAgICAgICBpZiAoIXJvb20pIHtcbiAgICAgICAgICAgICAgICAgICAgbG9nZ2VyLndhcm4oYE93biByZWFkIHJlY2VpcHQgd2FzIGluIHVua25vd24gcm9vbSAke3Jvb20ucm9vbUlkfWApO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMuaGFuZGxlUm9vbVVwZGF0ZShyb29tLCBSb29tVXBkYXRlQ2F1c2UuUmVhZFJlY2VpcHQpO1xuICAgICAgICAgICAgICAgIHRoaXMudXBkYXRlRm4udHJpZ2dlcigpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChwYXlsb2FkLmFjdGlvbiA9PT0gXCJNYXRyaXhBY3Rpb25zLlJvb20udGFnc1wiKSB7XG4gICAgICAgICAgICBjb25zdCByb29tUGF5bG9hZCA9IDxhbnk+cGF5bG9hZDsgLy8gVE9ETzogVHlwZSBvdXQgdGhlIGRpc3BhdGNoZXIgdHlwZXNcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuaGFuZGxlUm9vbVVwZGF0ZShyb29tUGF5bG9hZC5yb29tLCBSb29tVXBkYXRlQ2F1c2UuUG9zc2libGVUYWdDaGFuZ2UpO1xuICAgICAgICAgICAgdGhpcy51cGRhdGVGbi50cmlnZ2VyKCk7XG4gICAgICAgIH0gZWxzZSBpZiAocGF5bG9hZC5hY3Rpb24gPT09IFwiTWF0cml4QWN0aW9ucy5Sb29tLnRpbWVsaW5lXCIpIHtcbiAgICAgICAgICAgIGNvbnN0IGV2ZW50UGF5bG9hZCA9IDxJUm9vbVRpbWVsaW5lQWN0aW9uUGF5bG9hZD5wYXlsb2FkO1xuXG4gICAgICAgICAgICAvLyBJZ25vcmUgbm9uLWxpdmUgZXZlbnRzIChiYWNrZmlsbCkgYW5kIG5vdGlmaWNhdGlvbiB0aW1lbGluZSBzZXQgZXZlbnRzICh3aXRob3V0IGEgcm9vbSlcbiAgICAgICAgICAgIGlmICghZXZlbnRQYXlsb2FkLmlzTGl2ZUV2ZW50IHx8ICFldmVudFBheWxvYWQuaXNMaXZlVW5maWx0ZXJlZFJvb21UaW1lbGluZUV2ZW50IHx8ICFldmVudFBheWxvYWQucm9vbSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29uc3Qgcm9vbUlkID0gZXZlbnRQYXlsb2FkLmV2ZW50LmdldFJvb21JZCgpO1xuICAgICAgICAgICAgY29uc3Qgcm9vbSA9IHRoaXMubWF0cml4Q2xpZW50LmdldFJvb20ocm9vbUlkKTtcbiAgICAgICAgICAgIGNvbnN0IHRyeVVwZGF0ZSA9IGFzeW5jICh1cGRhdGVkUm9vbTogUm9vbSk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgICAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRQYXlsb2FkLmV2ZW50LmdldFR5cGUoKSA9PT0gRXZlbnRUeXBlLlJvb21Ub21ic3RvbmUgJiZcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRQYXlsb2FkLmV2ZW50LmdldFN0YXRlS2V5KCkgPT09IFwiXCJcbiAgICAgICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgbmV3Um9vbSA9IHRoaXMubWF0cml4Q2xpZW50Py5nZXRSb29tKGV2ZW50UGF5bG9hZC5ldmVudC5nZXRDb250ZW50KClbXCJyZXBsYWNlbWVudF9yb29tXCJdKTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKG5ld1Jvb20pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIElmIHdlIGhhdmUgdGhlIG5ldyByb29tLCB0aGVuIHRoZSBuZXcgcm9vbSBjaGVjayB3aWxsIGhhdmUgc2VlbiB0aGUgcHJlZGVjZXNzb3JcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGFuZCBkaWQgdGhlIHJlcXVpcmVkIHVwZGF0ZXMsIHNvIGRvIG5vdGhpbmcgaGVyZS5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAvLyBJZiB0aGUgam9pbiBydWxlIGNoYW5nZXMgd2UgbmVlZCB0byB1cGRhdGUgdGhlIHRhZ3MgZm9yIHRoZSByb29tLlxuICAgICAgICAgICAgICAgIC8vIEEgY29uZmVyZW5jZSB0YWcgaXMgZGV0ZXJtaW5lZCBieSB0aGUgcm9vbSBwdWJsaWMgam9pbiBydWxlLlxuICAgICAgICAgICAgICAgIGlmIChldmVudFBheWxvYWQuZXZlbnQuZ2V0VHlwZSgpID09PSBFdmVudFR5cGUuUm9vbUpvaW5SdWxlcylcbiAgICAgICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5oYW5kbGVSb29tVXBkYXRlKHVwZGF0ZWRSb29tLCBSb29tVXBkYXRlQ2F1c2UuUG9zc2libGVUYWdDaGFuZ2UpO1xuICAgICAgICAgICAgICAgIGVsc2UgYXdhaXQgdGhpcy5oYW5kbGVSb29tVXBkYXRlKHVwZGF0ZWRSb29tLCBSb29tVXBkYXRlQ2F1c2UuVGltZWxpbmUpO1xuXG4gICAgICAgICAgICAgICAgdGhpcy51cGRhdGVGbi50cmlnZ2VyKCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgaWYgKCFyb29tKSB7XG4gICAgICAgICAgICAgICAgbG9nZ2VyLndhcm4oYExpdmUgdGltZWxpbmUgZXZlbnQgJHtldmVudFBheWxvYWQuZXZlbnQuZ2V0SWQoKX0gcmVjZWl2ZWQgd2l0aG91dCBhc3NvY2lhdGVkIHJvb21gKTtcbiAgICAgICAgICAgICAgICBsb2dnZXIud2FybihgUXVldWluZyBmYWlsZWQgcm9vbSB1cGRhdGUgZm9yIHJldHJ5IGFzIGEgcmVzdWx0LmApO1xuICAgICAgICAgICAgICAgIHdpbmRvdy5zZXRUaW1lb3V0KGFzeW5jICgpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgdXBkYXRlZFJvb20gPSB0aGlzLm1hdHJpeENsaWVudD8uZ2V0Um9vbShyb29tSWQpO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmICh1cGRhdGVkUm9vbSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgYXdhaXQgdHJ5VXBkYXRlKHVwZGF0ZWRSb29tKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH0sIDEwMCk7IC8vIDEwMG1zIHNob3VsZCBiZSBlbm91Z2ggZm9yIHRoZSByb29tIHRvIHNob3cgdXBcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGF3YWl0IHRyeVVwZGF0ZShyb29tKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChwYXlsb2FkLmFjdGlvbiA9PT0gXCJNYXRyaXhBY3Rpb25zLkV2ZW50LmRlY3J5cHRlZFwiKSB7XG4gICAgICAgICAgICBjb25zdCBldmVudFBheWxvYWQgPSA8YW55PnBheWxvYWQ7IC8vIFRPRE86IFR5cGUgb3V0IHRoZSBkaXNwYXRjaGVyIHR5cGVzXG4gICAgICAgICAgICBjb25zdCByb29tSWQgPSBldmVudFBheWxvYWQuZXZlbnQuZ2V0Um9vbUlkKCk7XG4gICAgICAgICAgICBpZiAoIXJvb21JZCkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHJvb20gPSB0aGlzLm1hdHJpeENsaWVudC5nZXRSb29tKHJvb21JZCk7XG4gICAgICAgICAgICBpZiAoIXJvb20pIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIud2FybihgRXZlbnQgJHtldmVudFBheWxvYWQuZXZlbnQuZ2V0SWQoKX0gd2FzIGRlY3J5cHRlZCBpbiBhbiB1bmtub3duIHJvb20gJHtyb29tSWR9YCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYXdhaXQgdGhpcy5oYW5kbGVSb29tVXBkYXRlKHJvb20sIFJvb21VcGRhdGVDYXVzZS5UaW1lbGluZSk7XG4gICAgICAgICAgICB0aGlzLnVwZGF0ZUZuLnRyaWdnZXIoKTtcbiAgICAgICAgfSBlbHNlIGlmIChwYXlsb2FkLmFjdGlvbiA9PT0gXCJNYXRyaXhBY3Rpb25zLmFjY291bnREYXRhXCIgJiYgcGF5bG9hZC5ldmVudF90eXBlID09PSBFdmVudFR5cGUuRGlyZWN0KSB7XG4gICAgICAgICAgICBjb25zdCBldmVudFBheWxvYWQgPSA8YW55PnBheWxvYWQ7IC8vIFRPRE86IFR5cGUgb3V0IHRoZSBkaXNwYXRjaGVyIHR5cGVzXG4gICAgICAgICAgICBjb25zdCBkbU1hcCA9IGV2ZW50UGF5bG9hZC5ldmVudC5nZXRDb250ZW50KCk7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IHVzZXJJZCBvZiBPYmplY3Qua2V5cyhkbU1hcCkpIHtcbiAgICAgICAgICAgICAgICBjb25zdCByb29tSWRzID0gZG1NYXBbdXNlcklkXTtcbiAgICAgICAgICAgICAgICBmb3IgKGNvbnN0IHJvb21JZCBvZiByb29tSWRzKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJvb20gPSB0aGlzLm1hdHJpeENsaWVudC5nZXRSb29tKHJvb21JZCk7XG4gICAgICAgICAgICAgICAgICAgIGlmICghcm9vbSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgbG9nZ2VyLndhcm4oYCR7cm9vbUlkfSB3YXMgZm91bmQgaW4gRE1zIGJ1dCB0aGUgcm9vbSBpcyBub3QgaW4gdGhlIHN0b3JlYCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIC8vIFdlIGV4cGVjdCB0aGlzIFJvb21VcGRhdGVDYXVzZSB0byBuby1vcCBpZiB0aGVyZSdzIG5vIGNoYW5nZSwgYW5kIHdlIGRvbid0IGV4cGVjdFxuICAgICAgICAgICAgICAgICAgICAvLyB0aGUgdXNlciB0byBoYXZlIGh1bmRyZWRzIG9mIHJvb21zIHRvIHVwZGF0ZSBpbiBvbmUgZXZlbnQuIEFzIHN1Y2gsIHdlIGp1c3QgaGFtbWVyXG4gICAgICAgICAgICAgICAgICAgIC8vIGF3YXkgYXQgdXBkYXRlcyB1bnRpbCB0aGUgcHJvYmxlbSBpcyBzb2x2ZWQuIElmIHdlIHdlcmUgZXhwZWN0aW5nIG1vcmUgdGhhbiBhIGNvdXBsZVxuICAgICAgICAgICAgICAgICAgICAvLyBvZiByb29tcyB0byBiZSB1cGRhdGVkIGF0IG9uY2UsIHdlIHdvdWxkIGNvbnNpZGVyIGJhdGNoaW5nIHRoZSByb29tcyB1cC5cbiAgICAgICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5oYW5kbGVSb29tVXBkYXRlKHJvb20sIFJvb21VcGRhdGVDYXVzZS5Qb3NzaWJsZVRhZ0NoYW5nZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy51cGRhdGVGbi50cmlnZ2VyKCk7XG4gICAgICAgIH0gZWxzZSBpZiAocGF5bG9hZC5hY3Rpb24gPT09IFwiTWF0cml4QWN0aW9ucy5Sb29tLm15TWVtYmVyc2hpcFwiKSB7XG4gICAgICAgICAgICB0aGlzLm9uRGlzcGF0Y2hNeU1lbWJlcnNoaXAoPGFueT5wYXlsb2FkKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHBvc3NpYmxlTXV0ZUNoYW5nZVJvb21JZHMgPSBnZXRDaGFuZ2VkT3ZlcnJpZGVSb29tTXV0ZVB1c2hSdWxlcyhwYXlsb2FkKTtcbiAgICAgICAgaWYgKHBvc3NpYmxlTXV0ZUNoYW5nZVJvb21JZHMpIHtcbiAgICAgICAgICAgIGZvciAoY29uc3Qgcm9vbUlkIG9mIHBvc3NpYmxlTXV0ZUNoYW5nZVJvb21JZHMpIHtcbiAgICAgICAgICAgICAgICBjb25zdCByb29tID0gcm9vbUlkICYmIHRoaXMubWF0cml4Q2xpZW50LmdldFJvb20ocm9vbUlkKTtcbiAgICAgICAgICAgICAgICBpZiAoc