matrix-react-sdk
Version:
SDK for matrix.org using React
605 lines (567 loc) • 93.1 kB
JavaScript
"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