UNPKG

matrix-react-sdk

Version:
474 lines (459 loc) 82.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "Container", { enumerable: true, get: function () { return _types.Container; } }); exports.MAX_PINNED = void 0; Object.defineProperty(exports, "WIDGET_LAYOUT_EVENT_TYPE", { enumerable: true, get: function () { return _types.WIDGET_LAYOUT_EVENT_TYPE; } }); exports.WidgetLayoutStore = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _matrix = require("matrix-js-sdk/src/matrix"); var _utils = require("matrix-js-sdk/src/utils"); var _SettingsStore = _interopRequireDefault(require("../../settings/SettingsStore")); var _WidgetStore = _interopRequireDefault(require("../WidgetStore")); var _WidgetType = require("../../widgets/WidgetType"); var _numbers = require("../../utils/numbers"); var _dispatcher = _interopRequireDefault(require("../../dispatcher/dispatcher")); var _ReadyWatchingStore = require("../ReadyWatchingStore"); var _SettingLevel = require("../../settings/SettingLevel"); var _arrays = require("../../utils/arrays"); var _AsyncStore = require("../AsyncStore"); var _types = require("./types"); function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* * Copyright 2024 New Vector Ltd. * Copyright 2021, 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. */ // Dev note: "Pinned" widgets are ones in the top container. const MAX_PINNED = exports.MAX_PINNED = 3; // These two are whole percentages and don't really mean anything. Later values will decide // minimum, but these help determine proportions during our calculations here. In fact, these // values should be *smaller* than the actual minimums imposed by later components. const MIN_WIDGET_WIDTH_PCT = 10; // 10% const MIN_WIDGET_HEIGHT_PCT = 2; // 2% class WidgetLayoutStore extends _ReadyWatchingStore.ReadyWatchingStore { constructor() { super(_dispatcher.default); // Map: room Id → container → ContainerValue (0, _defineProperty2.default)(this, "byRoom", new _utils.MapWithDefault(() => new Map())); (0, _defineProperty2.default)(this, "pinnedRef", void 0); (0, _defineProperty2.default)(this, "layoutRef", void 0); (0, _defineProperty2.default)(this, "dynamicRef", void 0); (0, _defineProperty2.default)(this, "updateAllRooms", () => { const msc3946ProcessDynamicPredecessor = _SettingsStore.default.getValue("feature_dynamic_room_predecessors"); if (!this.matrixClient) return; this.byRoom = new _utils.MapWithDefault(() => new Map()); for (const room of this.matrixClient.getVisibleRooms(msc3946ProcessDynamicPredecessor)) { this.recalculateRoom(room); } }); (0, _defineProperty2.default)(this, "updateFromWidgetStore", roomId => { if (roomId) { const room = this.matrixClient?.getRoom(roomId); if (room) this.recalculateRoom(room); } else { this.updateAllRooms(); } }); (0, _defineProperty2.default)(this, "updateRoomFromState", ev => { if (ev.getType() !== _types.WIDGET_LAYOUT_EVENT_TYPE) return; const room = this.matrixClient?.getRoom(ev.getRoomId()); if (room) this.recalculateRoom(room); }); (0, _defineProperty2.default)(this, "updateFromSettings", (_settingName, roomId, _atLevel, _newValAtLevel, _newVal) => { if (roomId) { const room = this.matrixClient?.getRoom(roomId); if (room) this.recalculateRoom(room); } else { this.updateAllRooms(); } }); } static get instance() { if (!this.internalInstance) { this.internalInstance = new WidgetLayoutStore(); this.internalInstance.start(); } return this.internalInstance; } static emissionForRoom(room) { return `update_${room.roomId}`; } emitFor(room) { this.emit(WidgetLayoutStore.emissionForRoom(room)); } async onReady() { this.updateAllRooms(); this.matrixClient?.on(_matrix.RoomStateEvent.Events, this.updateRoomFromState); this.pinnedRef = _SettingsStore.default.watchSetting("Widgets.pinned", null, this.updateFromSettings); this.layoutRef = _SettingsStore.default.watchSetting("Widgets.layout", null, this.updateFromSettings); this.dynamicRef = _SettingsStore.default.watchSetting("feature_dynamic_room_predecessors", null, this.updateFromSettings); _WidgetStore.default.instance.on(_AsyncStore.UPDATE_EVENT, this.updateFromWidgetStore); } async onNotReady() { this.byRoom = new _utils.MapWithDefault(() => new Map()); this.matrixClient?.off(_matrix.RoomStateEvent.Events, this.updateRoomFromState); if (this.pinnedRef) _SettingsStore.default.unwatchSetting(this.pinnedRef); if (this.layoutRef) _SettingsStore.default.unwatchSetting(this.layoutRef); if (this.dynamicRef) _SettingsStore.default.unwatchSetting(this.dynamicRef); _WidgetStore.default.instance.off(_AsyncStore.UPDATE_EVENT, this.updateFromWidgetStore); } recalculateRoom(room) { const widgets = _WidgetStore.default.instance.getApps(room.roomId); if (!widgets?.length) { this.byRoom.set(room.roomId, new Map()); this.emitFor(room); return; } const roomContainers = this.byRoom.getOrCreate(room.roomId); const beforeChanges = JSON.stringify((0, _utils.recursiveMapToObject)(roomContainers)); const layoutEv = room.currentState.getStateEvents(_types.WIDGET_LAYOUT_EVENT_TYPE, ""); const legacyPinned = _SettingsStore.default.getValue("Widgets.pinned", room.roomId); let userLayout = _SettingsStore.default.getValue("Widgets.layout", room.roomId); if (layoutEv && userLayout && userLayout.overrides !== layoutEv.getId()) { // For some other layout that we don't really care about. The user can reset this // by updating their personal layout. userLayout = null; } const roomLayout = layoutEv?.getContent() ?? null; // We filter for the center container first. // (An error is raised, if there are multiple widgets marked for the center container) // For the right and top container multiple widgets are allowed. const topWidgets = []; const rightWidgets = []; const centerWidgets = []; for (const widget of widgets) { const stateContainer = roomLayout?.widgets?.[widget.id]?.container; const manualContainer = userLayout?.widgets?.[widget.id]?.container; const isLegacyPinned = !!legacyPinned?.[widget.id]; const defaultContainer = _WidgetType.WidgetType.JITSI.matches(widget.type) ? _types.Container.Top : _types.Container.Right; if (manualContainer ? manualContainer === _types.Container.Center : stateContainer === _types.Container.Center) { if (centerWidgets.length) { console.error("Tried to push a second widget into the center container"); } else { centerWidgets.push(widget); } // The widget won't need to be put in any other container. continue; } let targetContainer = defaultContainer; if (!!manualContainer || !!stateContainer) { targetContainer = manualContainer ?? stateContainer; } else if (isLegacyPinned && !stateContainer) { // Special legacy case targetContainer = _types.Container.Top; } (targetContainer === _types.Container.Top ? topWidgets : rightWidgets).push(widget); } // Trim to MAX_PINNED const runoff = topWidgets.slice(MAX_PINNED); rightWidgets.push(...runoff); const collator = new Intl.Collator(); // Order the widgets in the top container, putting autopinned Jitsi widgets first // unless they have a specific order in mind topWidgets.sort((a, b) => { const layoutA = roomLayout?.widgets?.[a.id]; const layoutB = roomLayout?.widgets?.[b.id]; const userLayoutA = userLayout?.widgets?.[a.id]; const userLayoutB = userLayout?.widgets?.[b.id]; // Jitsi widgets are defaulted to be the leftmost widget whereas other widgets // default to the right side. const defaultA = _WidgetType.WidgetType.JITSI.matches(a.type) ? Number.MIN_SAFE_INTEGER : Number.MAX_SAFE_INTEGER; const defaultB = _WidgetType.WidgetType.JITSI.matches(b.type) ? Number.MIN_SAFE_INTEGER : Number.MAX_SAFE_INTEGER; const orderA = (0, _numbers.defaultNumber)(userLayoutA?.index, (0, _numbers.defaultNumber)(layoutA?.index, defaultA)); const orderB = (0, _numbers.defaultNumber)(userLayoutB?.index, (0, _numbers.defaultNumber)(layoutB?.index, defaultB)); if (orderA === orderB) { // We just need a tiebreak return collator.compare(a.id, b.id); } return orderA - orderB; }); // Determine width distribution and height of the top container now (the only relevant one) const widths = []; let maxHeight = null; // null == default let doAutobalance = true; for (let i = 0; i < topWidgets.length; i++) { const widget = topWidgets[i]; const widgetLayout = roomLayout?.widgets?.[widget.id]; const userWidgetLayout = userLayout?.widgets?.[widget.id]; if (Number.isFinite(userWidgetLayout?.width) || Number.isFinite(widgetLayout?.width)) { const val = userWidgetLayout?.width || widgetLayout?.width; const normalized = (0, _numbers.clamp)(val, MIN_WIDGET_WIDTH_PCT, 100); widths.push(normalized); doAutobalance = false; // a manual width was specified } else { widths.push(100); // we'll figure this out later } if (widgetLayout?.height || userWidgetLayout?.height) { const defRoomHeight = (0, _numbers.defaultNumber)(widgetLayout?.height, MIN_WIDGET_HEIGHT_PCT); const h = (0, _numbers.defaultNumber)(userWidgetLayout?.height, defRoomHeight); maxHeight = Math.max(maxHeight ?? 0, (0, _numbers.clamp)(h, MIN_WIDGET_HEIGHT_PCT, 100)); } } if (doAutobalance) { for (let i = 0; i < widths.length; i++) { widths[i] = 100 / widths.length; } } else { // If we're not autobalancing then it means that we're trying to make // sure that widgets make up exactly 100% of space (not over, not under) const difference = (0, _numbers.sum)(...widths) - 100; // positive = over, negative = under if (difference < 0) { // For a deficit we just fill everything in equally for (let i = 0; i < widths.length; i++) { widths[i] += Math.abs(difference) / widths.length; } } else if (difference > 0) { // When we're over, we try to scale all the widgets within range first. // We clamp values to try and keep ourselves sane and within range. for (let i = 0; i < widths.length; i++) { widths[i] = (0, _numbers.clamp)(widths[i] - difference / widths.length, MIN_WIDGET_WIDTH_PCT, 100); } // If we're still over, find the widgets which have more width than the minimum // and balance them out until we're at 100%. This should keep us as close as possible // to the intended distributions. // // Note: if we ever decide to set a minimum which is larger than 100%/MAX_WIDGETS then // we probably have other issues - this code assumes we don't do that. const toReclaim = (0, _numbers.sum)(...widths) - 100; if (toReclaim > 0) { const largeIndices = widths.map((v, i) => [i, v]).filter(p => p[1] > MIN_WIDGET_WIDTH_PCT).map(p => p[0]); for (const idx of largeIndices) { widths[idx] -= toReclaim / largeIndices.length; } } } } // Finally, fill in our cache and update const newRoomContainers = new Map(); this.byRoom.set(room.roomId, newRoomContainers); if (topWidgets.length) { newRoomContainers.set(_types.Container.Top, { ordered: topWidgets, distributions: widths, height: maxHeight }); } if (rightWidgets.length) { newRoomContainers.set(_types.Container.Right, { ordered: rightWidgets }); } if (centerWidgets.length) { newRoomContainers.set(_types.Container.Center, { ordered: centerWidgets }); } const afterChanges = JSON.stringify((0, _utils.recursiveMapToObject)(newRoomContainers)); if (afterChanges !== beforeChanges) { this.emitFor(room); } } getContainerWidgets(room, container) { return room && this.byRoom.get(room.roomId)?.get(container)?.ordered || []; } isInContainer(room, widget, container) { return this.getContainerWidgets(room, container).some(w => w.id === widget.id); } canAddToContainer(room, container) { switch (container) { case _types.Container.Top: return this.getContainerWidgets(room, container).length < MAX_PINNED; case _types.Container.Right: return this.getContainerWidgets(room, container).length < MAX_PINNED; case _types.Container.Center: return this.getContainerWidgets(room, container).length < 1; } } getResizerDistributions(room, container) { // yes, string. let distributions = this.byRoom.get(room.roomId)?.get(container)?.distributions; if (!distributions || distributions.length < 2) return []; // The distributor actually expects to be fed N-1 sizes and expands the middle section // instead of the edges. Therefore, we need to return [0] when there's two widgets or // [0, 2] when there's three (skipping [1] because it's irrelevant). if (distributions.length === 2) distributions = [distributions[0]]; if (distributions.length === 3) distributions = [distributions[0], distributions[2]]; return distributions.map(d => `${d.toFixed(1)}%`); // actual percents - these are decoded later } setResizerDistributions(room, container, distributions) { if (container !== _types.Container.Top) return; // ignore - not relevant const numbers = distributions.map(d => Number(Number(d.substring(0, d.length - 1)).toFixed(1))); const widgets = this.getContainerWidgets(room, container); // From getResizerDistributions, we need to fill in the middle size if applicable. const remaining = 100 - (0, _numbers.sum)(...numbers); if (numbers.length === 2) numbers.splice(1, 0, remaining); if (numbers.length === 1) numbers.push(remaining); const localLayout = {}; widgets.forEach((w, i) => { localLayout[w.id] = { container: container, width: numbers[i], index: i, height: this.byRoom.get(room.roomId)?.get(container)?.height || MIN_WIDGET_HEIGHT_PCT }; }); this.updateUserLayout(room, localLayout); } getContainerHeight(room, container) { return this.byRoom.get(room.roomId)?.get(container)?.height ?? null; // let the default get returned if needed } setContainerHeight(room, container, height) { const widgets = this.getContainerWidgets(room, container); const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; const localLayout = {}; widgets.forEach((w, i) => { localLayout[w.id] = { container: container, width: widths?.[i], index: i, height: height }; }); this.updateUserLayout(room, localLayout); } moveWithinContainer(room, container, widget, delta) { const widgets = (0, _arrays.arrayFastClone)(this.getContainerWidgets(room, container)); const currentIdx = widgets.findIndex(w => w.id === widget.id); if (currentIdx < 0) return; // no change needed widgets.splice(currentIdx, 1); // remove existing widget const newIdx = (0, _numbers.clamp)(currentIdx + delta, 0, widgets.length); widgets.splice(newIdx, 0, widget); const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; const height = this.byRoom.get(room.roomId)?.get(container)?.height; const localLayout = {}; widgets.forEach((w, i) => { localLayout[w.id] = { container: container, width: widths?.[i], index: i, height }; }); this.updateUserLayout(room, localLayout); } moveToContainer(room, widget, toContainer) { const allWidgets = this.getAllWidgets(room); if (!allWidgets.some(([w]) => w.id === widget.id)) return; // invalid // Prepare other containers (potentially move widgets to obey the following rules) const newLayout = {}; switch (toContainer) { case _types.Container.Right: // new "right" widget break; case _types.Container.Center: // new "center" widget => all other widgets go into "right" for (const w of this.getContainerWidgets(room, _types.Container.Top)) { newLayout[w.id] = { container: _types.Container.Right }; } for (const w of this.getContainerWidgets(room, _types.Container.Center)) { newLayout[w.id] = { container: _types.Container.Right }; } break; case _types.Container.Top: // new "top" widget => the center widget moves into "right" if (this.hasMaximisedWidget(room)) { const centerWidget = this.getContainerWidgets(room, _types.Container.Center)[0]; newLayout[centerWidget.id] = { container: _types.Container.Right }; } break; } newLayout[widget.id] = { container: toContainer }; // move widgets into requested containers. this.updateUserLayout(room, newLayout); } hasMaximisedWidget(room) { return this.getContainerWidgets(room, _types.Container.Center).length > 0; } hasPinnedWidgets(room) { return this.getContainerWidgets(room, _types.Container.Top).length > 0; } canCopyLayoutToRoom(room) { if (!this.matrixClient) return false; // not ready yet return room.currentState.maySendStateEvent(_types.WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId()); } copyLayoutToRoom(room) { const allWidgets = this.getAllWidgets(room); const evContent = { widgets: {} }; for (const [widget, container] of allWidgets) { evContent.widgets[widget.id] = { container }; if (container === _types.Container.Top) { const containerWidgets = this.getContainerWidgets(room, container); const idx = containerWidgets.findIndex(w => w.id === widget.id); const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; const height = this.byRoom.get(room.roomId)?.get(container)?.height; evContent.widgets[widget.id] = _objectSpread(_objectSpread({}, evContent.widgets[widget.id]), {}, { height: height ? Math.round(height) : undefined, width: widths?.[idx] ? Math.round(widths[idx]) : undefined, index: idx }); } } this.matrixClient?.sendStateEvent(room.roomId, _types.WIDGET_LAYOUT_EVENT_TYPE, evContent, ""); } getAllWidgets(room) { const containers = this.byRoom.get(room.roomId); if (!containers) return []; const ret = []; for (const [container, containerValue] of containers) { const widgets = containerValue.ordered; for (const widget of widgets) { ret.push([widget, container]); } } return ret; } updateUserLayout(room, newLayout) { // Polyfill any missing widgets const allWidgets = this.getAllWidgets(room); for (const [widget, container] of allWidgets) { const containerWidgets = this.getContainerWidgets(room, container); const idx = containerWidgets.findIndex(w => w.id === widget.id); const widths = this.byRoom.get(room.roomId)?.get(container)?.distributions; if (!newLayout[widget.id]) { newLayout[widget.id] = { container: container, index: idx, height: this.byRoom.get(room.roomId)?.get(container)?.height, width: widths?.[idx] }; } } const layoutEv = room.currentState.getStateEvents(_types.WIDGET_LAYOUT_EVENT_TYPE, ""); _SettingsStore.default.setValue("Widgets.layout", room.roomId, _SettingLevel.SettingLevel.ROOM_ACCOUNT, { overrides: layoutEv?.getId(), widgets: newLayout }).catch(() => this.recalculateRoom(room)); this.recalculateRoom(room); // call to try local echo on changes (the catch above undoes any errors) } } exports.WidgetLayoutStore = WidgetLayoutStore; (0, _defineProperty2.default)(WidgetLayoutStore, "internalInstance", void 0); window.mxWidgetLayoutStore = WidgetLayoutStore.instance; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4IiwicmVxdWlyZSIsIl91dGlscyIsIl9TZXR0aW5nc1N0b3JlIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsIl9XaWRnZXRTdG9yZSIsIl9XaWRnZXRUeXBlIiwiX251bWJlcnMiLCJfZGlzcGF0Y2hlciIsIl9SZWFkeVdhdGNoaW5nU3RvcmUiLCJfU2V0dGluZ0xldmVsIiwiX2FycmF5cyIsIl9Bc3luY1N0b3JlIiwiX3R5cGVzIiwib3duS2V5cyIsImUiLCJyIiwidCIsIk9iamVjdCIsImtleXMiLCJnZXRPd25Qcm9wZXJ0eVN5bWJvbHMiLCJvIiwiZmlsdGVyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZW51bWVyYWJsZSIsInB1c2giLCJhcHBseSIsIl9vYmplY3RTcHJlYWQiLCJhcmd1bWVudHMiLCJsZW5ndGgiLCJmb3JFYWNoIiwiX2RlZmluZVByb3BlcnR5MiIsImRlZmF1bHQiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3JzIiwiZGVmaW5lUHJvcGVydGllcyIsImRlZmluZVByb3BlcnR5IiwiTUFYX1BJTk5FRCIsImV4cG9ydHMiLCJNSU5fV0lER0VUX1dJRFRIX1BDVCIsIk1JTl9XSURHRVRfSEVJR0hUX1BDVCIsIldpZGdldExheW91dFN0b3JlIiwiUmVhZHlXYXRjaGluZ1N0b3JlIiwiY29uc3RydWN0b3IiLCJkZWZhdWx0RGlzcGF0Y2hlciIsIk1hcFdpdGhEZWZhdWx0IiwiTWFwIiwibXNjMzk0NlByb2Nlc3NEeW5hbWljUHJlZGVjZXNzb3IiLCJTZXR0aW5nc1N0b3JlIiwiZ2V0VmFsdWUiLCJtYXRyaXhDbGllbnQiLCJieVJvb20iLCJyb29tIiwiZ2V0VmlzaWJsZVJvb21zIiwicmVjYWxjdWxhdGVSb29tIiwicm9vbUlkIiwiZ2V0Um9vbSIsInVwZGF0ZUFsbFJvb21zIiwiZXYiLCJnZXRUeXBlIiwiV0lER0VUX0xBWU9VVF9FVkVOVF9UWVBFIiwiZ2V0Um9vbUlkIiwiX3NldHRpbmdOYW1lIiwiX2F0TGV2ZWwiLCJfbmV3VmFsQXRMZXZlbCIsIl9uZXdWYWwiLCJpbnN0YW5jZSIsImludGVybmFsSW5zdGFuY2UiLCJzdGFydCIsImVtaXNzaW9uRm9yUm9vbSIsImVtaXRGb3IiLCJlbWl0Iiwib25SZWFkeSIsIm9uIiwiUm9vbVN0YXRlRXZlbnQiLCJFdmVudHMiLCJ1cGRhdGVSb29tRnJvbVN0YXRlIiwicGlubmVkUmVmIiwid2F0Y2hTZXR0aW5nIiwidXBkYXRlRnJvbVNldHRpbmdzIiwibGF5b3V0UmVmIiwiZHluYW1pY1JlZiIsIldpZGdldFN0b3JlIiwiVVBEQVRFX0VWRU5UIiwidXBkYXRlRnJvbVdpZGdldFN0b3JlIiwib25Ob3RSZWFkeSIsIm9mZiIsInVud2F0Y2hTZXR0aW5nIiwid2lkZ2V0cyIsImdldEFwcHMiLCJzZXQiLCJyb29tQ29udGFpbmVycyIsImdldE9yQ3JlYXRlIiwiYmVmb3JlQ2hhbmdlcyIsIkpTT04iLCJzdHJpbmdpZnkiLCJyZWN1cnNpdmVNYXBUb09iamVjdCIsImxheW91dEV2IiwiY3VycmVudFN0YXRlIiwiZ2V0U3RhdGVFdmVudHMiLCJsZWdhY3lQaW5uZWQiLCJ1c2VyTGF5b3V0Iiwib3ZlcnJpZGVzIiwiZ2V0SWQiLCJyb29tTGF5b3V0IiwiZ2V0Q29udGVudCIsInRvcFdpZGdldHMiLCJyaWdodFdpZGdldHMiLCJjZW50ZXJXaWRnZXRzIiwid2lkZ2V0Iiwic3RhdGVDb250YWluZXIiLCJpZCIsImNvbnRhaW5lciIsIm1hbnVhbENvbnRhaW5lciIsImlzTGVnYWN5UGlubmVkIiwiZGVmYXVsdENvbnRhaW5lciIsIldpZGdldFR5cGUiLCJKSVRTSSIsIm1hdGNoZXMiLCJ0eXBlIiwiQ29udGFpbmVyIiwiVG9wIiwiUmlnaHQiLCJDZW50ZXIiLCJjb25zb2xlIiwiZXJyb3IiLCJ0YXJnZXRDb250YWluZXIiLCJydW5vZmYiLCJzbGljZSIsImNvbGxhdG9yIiwiSW50bCIsIkNvbGxhdG9yIiwic29ydCIsImEiLCJiIiwibGF5b3V0QSIsImxheW91dEIiLCJ1c2VyTGF5b3V0QSIsInVzZXJMYXlvdXRCIiwiZGVmYXVsdEEiLCJOdW1iZXIiLCJNSU5fU0FGRV9JTlRFR0VSIiwiTUFYX1NBRkVfSU5URUdFUiIsImRlZmF1bHRCIiwib3JkZXJBIiwiZGVmYXVsdE51bWJlciIsImluZGV4Iiwib3JkZXJCIiwiY29tcGFyZSIsIndpZHRocyIsIm1heEhlaWdodCIsImRvQXV0b2JhbGFuY2UiLCJpIiwid2lkZ2V0TGF5b3V0IiwidXNlcldpZGdldExheW91dCIsImlzRmluaXRlIiwid2lkdGgiLCJ2YWwiLCJub3JtYWxpemVkIiwiY2xhbXAiLCJoZWlnaHQiLCJkZWZSb29tSGVpZ2h0IiwiaCIsIk1hdGgiLCJtYXgiLCJkaWZmZXJlbmNlIiwic3VtIiwiYWJzIiwidG9SZWNsYWltIiwibGFyZ2VJbmRpY2VzIiwibWFwIiwidiIsInAiLCJpZHgiLCJuZXdSb29tQ29udGFpbmVycyIsIm9yZGVyZWQiLCJkaXN0cmlidXRpb25zIiwiYWZ0ZXJDaGFuZ2VzIiwiZ2V0Q29udGFpbmVyV2lkZ2V0cyIsImdldCIsImlzSW5Db250YWluZXIiLCJzb21lIiwidyIsImNhbkFkZFRvQ29udGFpbmVyIiwiZ2V0UmVzaXplckRpc3RyaWJ1dGlvbnMiLCJkIiwidG9GaXhlZCIsInNldFJlc2l6ZXJEaXN0cmlidXRpb25zIiwibnVtYmVycyIsInN1YnN0cmluZyIsInJlbWFpbmluZyIsInNwbGljZSIsImxvY2FsTGF5b3V0IiwidXBkYXRlVXNlckxheW91dCIsImdldENvbnRhaW5lckhlaWdodCIsInNldENvbnRhaW5lckhlaWdodCIsIm1vdmVXaXRoaW5Db250YWluZXIiLCJkZWx0YSIsImFycmF5RmFzdENsb25lIiwiY3VycmVudElkeCIsImZpbmRJbmRleCIsIm5ld0lkeCIsIm1vdmVUb0NvbnRhaW5lciIsInRvQ29udGFpbmVyIiwiYWxsV2lkZ2V0cyIsImdldEFsbFdpZGdldHMiLCJuZXdMYXlvdXQiLCJoYXNNYXhpbWlzZWRXaWRnZXQiLCJjZW50ZXJXaWRnZXQiLCJoYXNQaW5uZWRXaWRnZXRzIiwiY2FuQ29weUxheW91dFRvUm9vbSIsIm1heVNlbmRTdGF0ZUV2ZW50IiwiZ2V0VXNlcklkIiwiY29weUxheW91dFRvUm9vbSIsImV2Q29udGVudCIsImNvbnRhaW5lcldpZGdldHMiLCJyb3VuZCIsInVuZGVmaW5lZCIsInNlbmRTdGF0ZUV2ZW50IiwiY29udGFpbmVycyIsInJldCIsImNvbnRhaW5lclZhbHVlIiwic2V0VmFsdWUiLCJTZXR0aW5nTGV2ZWwiLCJST09NX0FDQ09VTlQiLCJjYXRjaCIsIndpbmRvdyIsIm14V2lkZ2V0TGF5b3V0U3RvcmUiXSwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc3RvcmVzL3dpZGdldHMvV2lkZ2V0TGF5b3V0U3RvcmUudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLypcbiAqIENvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuICogQ29weXJpZ2h0IDIwMjEsIDIwMjIgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cbiAqXG4gKiBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcbiAqIFBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4gKi9cblxuaW1wb3J0IHsgUm9vbSwgUm9vbVN0YXRlRXZlbnQsIE1hdHJpeEV2ZW50IH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL21hdHJpeFwiO1xuaW1wb3J0IHsgT3B0aW9uYWwgfSBmcm9tIFwibWF0cml4LWV2ZW50cy1zZGtcIjtcbmltcG9ydCB7IE1hcFdpdGhEZWZhdWx0LCByZWN1cnNpdmVNYXBUb09iamVjdCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy91dGlsc1wiO1xuaW1wb3J0IHsgSVdpZGdldCB9IGZyb20gXCJtYXRyaXgtd2lkZ2V0LWFwaVwiO1xuXG5pbXBvcnQgU2V0dGluZ3NTdG9yZSBmcm9tIFwiLi4vLi4vc2V0dGluZ3MvU2V0dGluZ3NTdG9yZVwiO1xuaW1wb3J0IFdpZGdldFN0b3JlLCB7IElBcHAgfSBmcm9tIFwiLi4vV2lkZ2V0U3RvcmVcIjtcbmltcG9ydCB7IFdpZGdldFR5cGUgfSBmcm9tIFwiLi4vLi4vd2lkZ2V0cy9XaWRnZXRUeXBlXCI7XG5pbXBvcnQgeyBjbGFtcCwgZGVmYXVsdE51bWJlciwgc3VtIH0gZnJvbSBcIi4uLy4uL3V0aWxzL251bWJlcnNcIjtcbmltcG9ydCBkZWZhdWx0RGlzcGF0Y2hlciBmcm9tIFwiLi4vLi4vZGlzcGF0Y2hlci9kaXNwYXRjaGVyXCI7XG5pbXBvcnQgeyBSZWFkeVdhdGNoaW5nU3RvcmUgfSBmcm9tIFwiLi4vUmVhZHlXYXRjaGluZ1N0b3JlXCI7XG5pbXBvcnQgeyBTZXR0aW5nTGV2ZWwgfSBmcm9tIFwiLi4vLi4vc2V0dGluZ3MvU2V0dGluZ0xldmVsXCI7XG5pbXBvcnQgeyBhcnJheUZhc3RDbG9uZSB9IGZyb20gXCIuLi8uLi91dGlscy9hcnJheXNcIjtcbmltcG9ydCB7IFVQREFURV9FVkVOVCB9IGZyb20gXCIuLi9Bc3luY1N0b3JlXCI7XG5pbXBvcnQgeyBDb250YWluZXIsIElTdG9yZWRMYXlvdXQsIElMYXlvdXRTdGF0ZUV2ZW50LCBXSURHRVRfTEFZT1VUX0VWRU5UX1RZUEUsIElXaWRnZXRMYXlvdXRzIH0gZnJvbSBcIi4vdHlwZXNcIjtcblxuZXhwb3J0IHR5cGUgeyBJU3RvcmVkTGF5b3V0LCBJTGF5b3V0U3RhdGVFdmVudCB9O1xuZXhwb3J0IHsgQ29udGFpbmVyLCBXSURHRVRfTEFZT1VUX0VWRU5UX1RZUEUgfTtcblxuaW50ZXJmYWNlIElMYXlvdXRTZXR0aW5ncyBleHRlbmRzIElMYXlvdXRTdGF0ZUV2ZW50IHtcbiAgICBvdmVycmlkZXM/OiBzdHJpbmc7IC8vIGV2ZW50IElEIGZvciBsYXlvdXQgc3RhdGUgZXZlbnQsIGlmIHByZXNlbnRcbn1cblxuLy8gRGV2IG5vdGU6IFwiUGlubmVkXCIgd2lkZ2V0cyBhcmUgb25lcyBpbiB0aGUgdG9wIGNvbnRhaW5lci5cbmV4cG9ydCBjb25zdCBNQVhfUElOTkVEID0gMztcblxuLy8gVGhlc2UgdHdvIGFyZSB3aG9sZSBwZXJjZW50YWdlcyBhbmQgZG9uJ3QgcmVhbGx5IG1lYW4gYW55dGhpbmcuIExhdGVyIHZhbHVlcyB3aWxsIGRlY2lkZVxuLy8gbWluaW11bSwgYnV0IHRoZXNlIGhlbHAgZGV0ZXJtaW5lIHByb3BvcnRpb25zIGR1cmluZyBvdXIgY2FsY3VsYXRpb25zIGhlcmUuIEluIGZhY3QsIHRoZXNlXG4vLyB2YWx1ZXMgc2hvdWxkIGJlICpzbWFsbGVyKiB0aGFuIHRoZSBhY3R1YWwgbWluaW11bXMgaW1wb3NlZCBieSBsYXRlciBjb21wb25lbnRzLlxuY29uc3QgTUlOX1dJREdFVF9XSURUSF9QQ1QgPSAxMDsgLy8gMTAlXG5jb25zdCBNSU5fV0lER0VUX0hFSUdIVF9QQ1QgPSAyOyAvLyAyJVxuXG5pbnRlcmZhY2UgQ29udGFpbmVyVmFsdWUge1xuICAgIG9yZGVyZWQ6IElBcHBbXTtcbiAgICBoZWlnaHQ/OiBudW1iZXI7XG4gICAgZGlzdHJpYnV0aW9ucz86IG51bWJlcltdO1xufVxuXG5leHBvcnQgY2xhc3MgV2lkZ2V0TGF5b3V0U3RvcmUgZXh0ZW5kcyBSZWFkeVdhdGNoaW5nU3RvcmUge1xuICAgIHByaXZhdGUgc3RhdGljIGludGVybmFsSW5zdGFuY2U6IFdpZGdldExheW91dFN0b3JlO1xuXG4gICAgLy8gTWFwOiByb29tIElkIOKGkiBjb250YWluZXIg4oaSIENvbnRhaW5lclZhbHVlXG4gICAgcHJpdmF0ZSBieVJvb206IE1hcFdpdGhEZWZhdWx0PHN0cmluZywgTWFwPENvbnRhaW5lciwgQ29udGFpbmVyVmFsdWU+PiA9IG5ldyBNYXBXaXRoRGVmYXVsdCgoKSA9PiBuZXcgTWFwKCkpO1xuICAgIHByaXZhdGUgcGlubmVkUmVmOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG4gICAgcHJpdmF0ZSBsYXlvdXRSZWY6IHN0cmluZyB8IHVuZGVmaW5lZDtcbiAgICBwcml2YXRlIGR5bmFtaWNSZWY6IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuICAgIHByaXZhdGUgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHN1cGVyKGRlZmF1bHREaXNwYXRjaGVyKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc3RhdGljIGdldCBpbnN0YW5jZSgpOiBXaWRnZXRMYXlvdXRTdG9yZSB7XG4gICAgICAgIGlmICghdGhpcy5pbnRlcm5hbEluc3RhbmNlKSB7XG4gICAgICAgICAgICB0aGlzLmludGVybmFsSW5zdGFuY2UgPSBuZXcgV2lkZ2V0TGF5b3V0U3RvcmUoKTtcbiAgICAgICAgICAgIHRoaXMuaW50ZXJuYWxJbnN0YW5jZS5zdGFydCgpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmludGVybmFsSW5zdGFuY2U7XG4gICAgfVxuXG4gICAgcHVibGljIHN0YXRpYyBlbWlzc2lvbkZvclJvb20ocm9vbTogUm9vbSk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiBgdXBkYXRlXyR7cm9vbS5yb29tSWR9YDtcbiAgICB9XG5cbiAgICBwcml2YXRlIGVtaXRGb3Iocm9vbTogUm9vbSk6IHZvaWQge1xuICAgICAgICB0aGlzLmVtaXQoV2lkZ2V0TGF5b3V0U3RvcmUuZW1pc3Npb25Gb3JSb29tKHJvb20pKTtcbiAgICB9XG5cbiAgICBwcm90ZWN0ZWQgYXN5bmMgb25SZWFkeSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgdGhpcy51cGRhdGVBbGxSb29tcygpO1xuXG4gICAgICAgIHRoaXMubWF0cml4Q2xpZW50Py5vbihSb29tU3RhdGVFdmVudC5FdmVudHMsIHRoaXMudXBkYXRlUm9vbUZyb21TdGF0ZSk7XG4gICAgICAgIHRoaXMucGlubmVkUmVmID0gU2V0dGluZ3NTdG9yZS53YXRjaFNldHRpbmcoXCJXaWRnZXRzLnBpbm5lZFwiLCBudWxsLCB0aGlzLnVwZGF0ZUZyb21TZXR0aW5ncyk7XG4gICAgICAgIHRoaXMubGF5b3V0UmVmID0gU2V0dGluZ3NTdG9yZS53YXRjaFNldHRpbmcoXCJXaWRnZXRzLmxheW91dFwiLCBudWxsLCB0aGlzLnVwZGF0ZUZyb21TZXR0aW5ncyk7XG4gICAgICAgIHRoaXMuZHluYW1pY1JlZiA9IFNldHRpbmdzU3RvcmUud2F0Y2hTZXR0aW5nKFxuICAgICAgICAgICAgXCJmZWF0dXJlX2R5bmFtaWNfcm9vbV9wcmVkZWNlc3NvcnNcIixcbiAgICAgICAgICAgIG51bGwsXG4gICAgICAgICAgICB0aGlzLnVwZGF0ZUZyb21TZXR0aW5ncyxcbiAgICAgICAgKTtcbiAgICAgICAgV2lkZ2V0U3RvcmUuaW5zdGFuY2Uub24oVVBEQVRFX0VWRU5ULCB0aGlzLnVwZGF0ZUZyb21XaWRnZXRTdG9yZSk7XG4gICAgfVxuXG4gICAgcHJvdGVjdGVkIGFzeW5jIG9uTm90UmVhZHkoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHRoaXMuYnlSb29tID0gbmV3IE1hcFdpdGhEZWZhdWx0KCgpID0+IG5ldyBNYXAoKSk7XG5cbiAgICAgICAgdGhpcy5tYXRyaXhDbGllbnQ/Lm9mZihSb29tU3RhdGVFdmVudC5FdmVudHMsIHRoaXMudXBkYXRlUm9vbUZyb21TdGF0ZSk7XG4gICAgICAgIGlmICh0aGlzLnBpbm5lZFJlZikgU2V0dGluZ3NTdG9yZS51bndhdGNoU2V0dGluZyh0aGlzLnBpbm5lZFJlZik7XG4gICAgICAgIGlmICh0aGlzLmxheW91dFJlZikgU2V0dGluZ3NTdG9yZS51bndhdGNoU2V0dGluZyh0aGlzLmxheW91dFJlZik7XG4gICAgICAgIGlmICh0aGlzLmR5bmFtaWNSZWYpIFNldHRpbmdzU3RvcmUudW53YXRjaFNldHRpbmcodGhpcy5keW5hbWljUmVmKTtcbiAgICAgICAgV2lkZ2V0U3RvcmUuaW5zdGFuY2Uub2ZmKFVQREFURV9FVkVOVCwgdGhpcy51cGRhdGVGcm9tV2lkZ2V0U3RvcmUpO1xuICAgIH1cblxuICAgIHByaXZhdGUgdXBkYXRlQWxsUm9vbXMgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIGNvbnN0IG1zYzM5NDZQcm9jZXNzRHluYW1pY1ByZWRlY2Vzc29yID0gU2V0dGluZ3NTdG9yZS5nZXRWYWx1ZShcImZlYXR1cmVfZHluYW1pY19yb29tX3ByZWRlY2Vzc29yc1wiKTtcbiAgICAgICAgaWYgKCF0aGlzLm1hdHJpeENsaWVudCkgcmV0dXJuO1xuICAgICAgICB0aGlzLmJ5Um9vbSA9IG5ldyBNYXBXaXRoRGVmYXVsdCgoKSA9PiBuZXcgTWFwKCkpO1xuICAgICAgICBmb3IgKGNvbnN0IHJvb20gb2YgdGhpcy5tYXRyaXhDbGllbnQuZ2V0VmlzaWJsZVJvb21zKG1zYzM5NDZQcm9jZXNzRHluYW1pY1ByZWRlY2Vzc29yKSkge1xuICAgICAgICAgICAgdGhpcy5yZWNhbGN1bGF0ZVJvb20ocm9vbSk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcHJpdmF0ZSB1cGRhdGVGcm9tV2lkZ2V0U3RvcmUgPSAocm9vbUlkPzogc3RyaW5nKTogdm9pZCA9PiB7XG4gICAgICAgIGlmIChyb29tSWQpIHtcbiAgICAgICAgICAgIGNvbnN0IHJvb20gPSB0aGlzLm1hdHJpeENsaWVudD8uZ2V0Um9vbShyb29tSWQpO1xuICAgICAgICAgICAgaWYgKHJvb20pIHRoaXMucmVjYWxjdWxhdGVSb29tKHJvb20pO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy51cGRhdGVBbGxSb29tcygpO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIHByaXZhdGUgdXBkYXRlUm9vbUZyb21TdGF0ZSA9IChldjogTWF0cml4RXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKGV2LmdldFR5cGUoKSAhPT0gV0lER0VUX0xBWU9VVF9FVkVOVF9UWVBFKSByZXR1cm47XG4gICAgICAgIGNvbnN0IHJvb20gPSB0aGlzLm1hdHJpeENsaWVudD8uZ2V0Um9vbShldi5nZXRSb29tSWQoKSk7XG4gICAgICAgIGlmIChyb29tKSB0aGlzLnJlY2FsY3VsYXRlUm9vbShyb29tKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSB1cGRhdGVGcm9tU2V0dGluZ3MgPSAoXG4gICAgICAgIF9zZXR0aW5nTmFtZTogc3RyaW5nLFxuICAgICAgICByb29tSWQ6IHN0cmluZyB8IG51bGwsXG4gICAgICAgIF9hdExldmVsOiBTZXR0aW5nTGV2ZWwsXG4gICAgICAgIF9uZXdWYWxBdExldmVsOiBhbnksXG4gICAgICAgIF9uZXdWYWw6IGFueSxcbiAgICApOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKHJvb21JZCkge1xuICAgICAgICAgICAgY29uc3Qgcm9vbSA9IHRoaXMubWF0cml4Q2xpZW50Py5nZXRSb29tKHJvb21JZCk7XG4gICAgICAgICAgICBpZiAocm9vbSkgdGhpcy5yZWNhbGN1bGF0ZVJvb20ocm9vbSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aGlzLnVwZGF0ZUFsbFJvb21zKCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcHVibGljIHJlY2FsY3VsYXRlUm9vbShyb29tOiBSb29tKTogdm9pZCB7XG4gICAgICAgIGNvbnN0IHdpZGdldHMgPSBXaWRnZXRTdG9yZS5pbnN0YW5jZS5nZXRBcHBzKHJvb20ucm9vbUlkKTtcbiAgICAgICAgaWYgKCF3aWRnZXRzPy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHRoaXMuYnlSb29tLnNldChyb29tLnJvb21JZCwgbmV3IE1hcCgpKTtcbiAgICAgICAgICAgIHRoaXMuZW1pdEZvcihyb29tKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHJvb21Db250YWluZXJzID0gdGhpcy5ieVJvb20uZ2V0T3JDcmVhdGUocm9vbS5yb29tSWQpO1xuICAgICAgICBjb25zdCBiZWZvcmVDaGFuZ2VzID0gSlNPTi5zdHJpbmdpZnkocmVjdXJzaXZlTWFwVG9PYmplY3Qocm9vbUNvbnRhaW5lcnMpKTtcblxuICAgICAgICBjb25zdCBsYXlvdXRFdiA9IHJvb20uY3VycmVudFN0YXRlLmdldFN0YXRlRXZlbnRzKFdJREdFVF9MQVlPVVRfRVZFTlRfVFlQRSwgXCJcIik7XG4gICAgICAgIGNvbnN0IGxlZ2FjeVBpbm5lZCA9IFNldHRpbmdzU3RvcmUuZ2V0VmFsdWUoXCJXaWRnZXRzLnBpbm5lZFwiLCByb29tLnJvb21JZCk7XG4gICAgICAgIGxldCB1c2VyTGF5b3V0ID0gU2V0dGluZ3NTdG9yZS5nZXRWYWx1ZTxJTGF5b3V0U2V0dGluZ3MgfCBudWxsPihcIldpZGdldHMubGF5b3V0XCIsIHJvb20ucm9vbUlkKTtcblxuICAgICAgICBpZiAobGF5b3V0RXYgJiYgdXNlckxheW91dCAmJiB1c2VyTGF5b3V0Lm92ZXJyaWRlcyAhPT0gbGF5b3V0RXYuZ2V0SWQoKSkge1xuICAgICAgICAgICAgLy8gRm9yIHNvbWUgb3RoZXIgbGF5b3V0IHRoYXQgd2UgZG9uJ3QgcmVhbGx5IGNhcmUgYWJvdXQuIFRoZSB1c2VyIGNhbiByZXNldCB0aGlzXG4gICAgICAgICAgICAvLyBieSB1cGRhdGluZyB0aGVpciBwZXJzb25hbCBsYXlvdXQuXG4gICAgICAgICAgICB1c2VyTGF5b3V0ID0gbnVsbDtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHJvb21MYXlvdXQgPSBsYXlvdXRFdj8uZ2V0Q29udGVudDxJTGF5b3V0U3RhdGVFdmVudD4oKSA/PyBudWxsO1xuICAgICAgICAvLyBXZSBmaWx0ZXIgZm9yIHRoZSBjZW50ZXIgY29udGFpbmVyIGZpcnN0LlxuICAgICAgICAvLyAoQW4gZXJyb3IgaXMgcmFpc2VkLCBpZiB0aGVyZSBhcmUgbXVsdGlwbGUgd2lkZ2V0cyBtYXJrZWQgZm9yIHRoZSBjZW50ZXIgY29udGFpbmVyKVxuICAgICAgICAvLyBGb3IgdGhlIHJpZ2h0IGFuZCB0b3AgY29udGFpbmVyIG11bHRpcGxlIHdpZGdldHMgYXJlIGFsbG93ZWQuXG4gICAgICAgIGNvbnN0IHRvcFdpZGdldHM6IElBcHBbXSA9IFtdO1xuICAgICAgICBjb25zdCByaWdodFdpZGdldHM6IElBcHBbXSA9IFtdO1xuICAgICAgICBjb25zdCBjZW50ZXJXaWRnZXRzOiBJQXBwW10gPSBbXTtcbiAgICAgICAgZm9yIChjb25zdCB3aWRnZXQgb2Ygd2lkZ2V0cykge1xuICAgICAgICAgICAgY29uc3Qgc3RhdGVDb250YWluZXIgPSByb29tTGF5b3V0Py53aWRnZXRzPy5bd2lkZ2V0LmlkXT8uY29udGFpbmVyO1xuICAgICAgICAgICAgY29uc3QgbWFudWFsQ29udGFpbmVyID0gdXNlckxheW91dD8ud2lkZ2V0cz8uW3dpZGdldC5pZF0/LmNvbnRhaW5lcjtcbiAgICAgICAgICAgIGNvbnN0IGlzTGVnYWN5UGlubmVkID0gISFsZWdhY3lQaW5uZWQ/Llt3aWRnZXQuaWRdO1xuICAgICAgICAgICAgY29uc3QgZGVmYXVsdENvbnRhaW5lciA9IFdpZGdldFR5cGUuSklUU0kubWF0Y2hlcyh3aWRnZXQudHlwZSkgPyBDb250YWluZXIuVG9wIDogQ29udGFpbmVyLlJpZ2h0O1xuICAgICAgICAgICAgaWYgKG1hbnVhbENvbnRhaW5lciA/IG1hbnVhbENvbnRhaW5lciA9PT0gQ29udGFpbmVyLkNlbnRlciA6IHN0YXRlQ29udGFpbmVyID09PSBDb250YWluZXIuQ2VudGVyKSB7XG4gICAgICAgICAgICAgICAgaWYgKGNlbnRlcldpZGdldHMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJUcmllZCB0byBwdXNoIGEgc2Vjb25kIHdpZGdldCBpbnRvIHRoZSBjZW50ZXIgY29udGFpbmVyXCIpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGNlbnRlcldpZGdldHMucHVzaCh3aWRnZXQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAvLyBUaGUgd2lkZ2V0IHdvbid0IG5lZWQgdG8gYmUgcHV0IGluIGFueSBvdGhlciBjb250YWluZXIuXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBsZXQgdGFyZ2V0Q29udGFpbmVyOiBDb250YWluZXIgPSBkZWZhdWx0Q29udGFpbmVyO1xuICAgICAgICAgICAgaWYgKCEhbWFudWFsQ29udGFpbmVyIHx8ICEhc3RhdGVDb250YWluZXIpIHtcbiAgICAgICAgICAgICAgICB0YXJnZXRDb250YWluZXIgPSBtYW51YWxDb250YWluZXIgPz8gc3RhdGVDb250YWluZXIhO1xuICAgICAgICAgICAgfSBlbHNlIGlmIChpc0xlZ2FjeVBpbm5lZCAmJiAhc3RhdGVDb250YWluZXIpIHtcbiAgICAgICAgICAgICAgICAvLyBTcGVjaWFsIGxlZ2FjeSBjYXNlXG4gICAgICAgICAgICAgICAgdGFyZ2V0Q29udGFpbmVyID0gQ29udGFpbmVyLlRvcDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgICh0YXJnZXRDb250YWluZXIgPT09IENvbnRhaW5lci5Ub3AgPyB0b3BXaWRnZXRzIDogcmlnaHRXaWRnZXRzKS5wdXNoKHdpZGdldCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUcmltIHRvIE1BWF9QSU5ORURcbiAgICAgICAgY29uc3QgcnVub2ZmID0gdG9wV2lkZ2V0cy5zbGljZShNQVhfUElOTkVEKTtcbiAgICAgICAgcmlnaHRXaWRnZXRzLnB1c2goLi4ucnVub2ZmKTtcblxuICAgICAgICBjb25zdCBjb2xsYXRvciA9IG5ldyBJbnRsLkNvbGxhdG9yKCk7XG5cbiAgICAgICAgLy8gT3JkZXIgdGhlIHdpZGdldHMgaW4gdGhlIHRvcCBjb250YWluZXIsIHB1dHRpbmcgYXV0b3Bpbm5lZCBKaXRzaSB3aWRnZXRzIGZpcnN0XG4gICAgICAgIC8vIHVubGVzcyB0aGV5IGhhdmUgYSBzcGVjaWZpYyBvcmRlciBpbiBtaW5kXG4gICAgICAgIHRvcFdpZGdldHMuc29ydCgoYSwgYikgPT4ge1xuICAgICAgICAgICAgY29uc3QgbGF5b3V0QSA9IHJvb21MYXlvdXQ/LndpZGdldHM/LlthLmlkXTtcbiAgICAgICAgICAgIGNvbnN0IGxheW91dEIgPSByb29tTGF5b3V0Py53aWRnZXRzPy5bYi5pZF07XG5cbiAgICAgICAgICAgIGNvbnN0IHVzZXJMYXlvdXRBID0gdXNlckxheW91dD8ud2lkZ2V0cz8uW2EuaWRdO1xuICAgICAgICAgICAgY29uc3QgdXNlckxheW91dEIgPSB1c2VyTGF5b3V0Py53aWRnZXRzPy5bYi5pZF07XG5cbiAgICAgICAgICAgIC8vIEppdHNpIHdpZGdldHMgYXJlIGRlZmF1bHRlZCB0byBiZSB0aGUgbGVmdG1vc3Qgd2lkZ2V0IHdoZXJlYXMgb3RoZXIgd2lkZ2V0c1xuICAgICAgICAgICAgLy8gZGVmYXVsdCB0byB0aGUgcmlnaHQgc2lkZS5cbiAgICAgICAgICAgIGNvbnN0IGRlZmF1bHRBID0gV2lkZ2V0VHlwZS5KSVRTSS5tYXRjaGVzKGEudHlwZSkgPyBOdW1iZXIuTUlOX1NBRkVfSU5URUdFUiA6IE51bWJlci5NQVhfU0FGRV9JTlRFR0VSO1xuICAgICAgICAgICAgY29uc3QgZGVmYXVsdEIgPSBXaWRnZXRUeXBlLkpJVFNJLm1hdGNoZXMoYi50eXBlKSA/IE51bWJlci5NSU5fU0FGRV9JTlRFR0VSIDogTnVtYmVyLk1BWF9TQUZFX0lOVEVHRVI7XG5cbiAgICAgICAgICAgIGNvbnN0IG9yZGVyQSA9IGRlZmF1bHROdW1iZXIodXNlckxheW91dEE/LmluZGV4LCBkZWZhdWx0TnVtYmVyKGxheW91dEE/LmluZGV4LCBkZWZhdWx0QSkpO1xuICAgICAgICAgICAgY29uc3Qgb3JkZXJCID0gZGVmYXVsdE51bWJlcih1c2VyTGF5b3V0Qj8uaW5kZXgsIGRlZmF1bHROdW1iZXIobGF5b3V0Qj8uaW5kZXgsIGRlZmF1bHRCKSk7XG5cbiAgICAgICAgICAgIGlmIChvcmRlckEgPT09IG9yZGVyQikge1xuICAgICAgICAgICAgICAgIC8vIFdlIGp1c3QgbmVlZCBhIHRpZWJyZWFrXG4gICAgICAgICAgICAgICAgcmV0dXJuIGNvbGxhdG9yLmNvbXBhcmUoYS5pZCwgYi5pZCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiBvcmRlckEgLSBvcmRlckI7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIERldGVybWluZSB3aWR0aCBkaXN0cmlidXRpb24gYW5kIGhlaWdodCBvZiB0aGUgdG9wIGNvbnRhaW5lciBub3cgKHRoZSBvbmx5IHJlbGV2YW50IG9uZSlcbiAgICAgICAgY29uc3Qgd2lkdGhzOiBudW1iZXJbXSA9IFtdO1xuICAgICAgICBsZXQgbWF4SGVpZ2h0OiBudW1iZXIgfCBudWxsID0gbnVsbDsgLy8gbnVsbCA9PSBkZWZhdWx0XG4gICAgICAgIGxldCBkb0F1dG9iYWxhbmNlID0gdHJ1ZTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0b3BXaWRnZXRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCB3aWRnZXQgPSB0b3BXaWRnZXRzW2ldO1xuICAgICAgICAgICAgY29uc3Qgd2lkZ2V0TGF5b3V0ID0gcm9vbUxheW91dD8ud2lkZ2V0cz8uW3dpZGdldC5pZF07XG4gICAgICAgICAgICBjb25zdCB1c2VyV2lkZ2V0TGF5b3V0ID0gdXNlckxheW91dD8ud2lkZ2V0cz8uW3dpZGdldC5pZF07XG5cbiAgICAgICAgICAgIGlmIChOdW1iZXIuaXNGaW5pdGUodXNlcldpZGdldExheW91dD8ud2lkdGgpIHx8IE51bWJlci5pc0Zpbml0ZSh3aWRnZXRMYXlvdXQ/LndpZHRoKSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHZhbCA9ICh1c2VyV2lkZ2V0TGF5b3V0Py53aWR0aCB8fCB3aWRnZXRMYXlvdXQ/LndpZHRoKSE7XG4gICAgICAgICAgICAgICAgY29uc3Qgbm9ybWFsaXplZCA9IGNsYW1wKHZhbCwgTUlOX1dJREdFVF9XSURUSF9QQ1QsIDEwMCk7XG4gICAgICAgICAgICAgICAgd2lkdGhzLnB1c2gobm9ybWFsaXplZCk7XG4gICAgICAgICAgICAgICAgZG9BdXRvYmFsYW5jZSA9IGZhbHNlOyAvLyBhIG1hbnVhbCB3aWR0aCB3YXMgc3BlY2lmaWVkXG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHdpZHRocy5wdXNoKDEwMCk7IC8vIHdlJ2xsIGZpZ3VyZSB0aGlzIG91dCBsYXRlclxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAod2lkZ2V0TGF5b3V0Py5oZWlnaHQgfHwgdXNlcldpZGdldExheW91dD8uaGVpZ2h0KSB7XG4gICAgICAgICAgICAgICAgY29uc3QgZGVmUm9vbUhlaWdodCA9IGRlZmF1bHROdW1iZXIod2lkZ2V0TGF5b3V0Py5oZWlnaHQsIE1JTl9XSURHRVRfSEVJR0hUX1BDVCk7XG4gICAgICAgICAgICAgICAgY29uc3QgaCA9IGRlZmF1bHROdW1iZXIodXNlcldpZGdldExheW91dD8uaGVpZ2h0LCBkZWZSb29tSGVpZ2h0KTtcbiAgICAgICAgICAgICAgICBtYXhIZWlnaHQgPSBNYXRoLm1heChtYXhIZWlnaHQgPz8gMCwgY2xhbXAoaCwgTUlOX1dJREdFVF9IRUlHSFRfUENULCAxMDApKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBpZiAoZG9BdXRvYmFsYW5jZSkge1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB3aWR0aHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB3aWR0aHNbaV0gPSAxMDAgLyB3aWR0aHMubGVuZ3RoO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gSWYgd2UncmUgbm90IGF1dG9iYWxhbmNpbmcgdGhlbiBpdCBtZWFucyB0aGF0IHdlJ3JlIHRyeWluZyB0byBtYWtlXG4gICAgICAgICAgICAvLyBzdXJlIHRoYXQgd2lkZ2V0cyBtYWtlIHVwIGV4YWN0bHkgMTAwJSBvZiBzcGFjZSAobm90IG92ZXIsIG5vdCB1bmRlcilcbiAgICAgICAgICAgIGNvbnN0IGRpZmZlcmVuY2UgPSBzdW0oLi4ud2lkdGhzKSAtIDEwMDsgLy8gcG9zaXRpdmUgPSBvdmVyLCBuZWdhdGl2ZSA9IHVuZGVyXG4gICAgICAgICAgICBpZiAoZGlmZmVyZW5jZSA8IDApIHtcbiAgICAgICAgICAgICAgICAvLyBGb3IgYSBkZWZpY2l0IHdlIGp1c3QgZmlsbCBldmVyeXRoaW5nIGluIGVxdWFsbHlcbiAgICAgICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHdpZHRocy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgICAgICB3aWR0aHNbaV0gKz0gTWF0aC5hYnMoZGlmZmVyZW5jZSkgLyB3aWR0aHMubGVuZ3RoO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSBpZiAoZGlmZmVyZW5jZSA+IDApIHtcbiAgICAgICAgICAgICAgICAvLyBXaGVuIHdlJ3JlIG92ZXIsIHdlIHRyeSB0byBzY2FsZSBhbGwgdGhlIHdpZGdldHMgd2l0aGluIHJhbmdlIGZpcnN0LlxuICAgICAgICAgICAgICAgIC8vIFdlIGNsYW1wIHZhbHVlcyB0byB0cnkgYW5kIGtlZXAgb3Vyc2VsdmVzIHNhbmUgYW5kIHdpdGhpbiByYW5nZS5cbiAgICAgICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHdpZHRocy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgICAgICB3aWR0aHNbaV0gPSBjbGFtcCh3aWR0aHNbaV0gLSBkaWZmZXJlbmNlIC8gd2lkdGhzLmxlbmd0aCwgTUlOX1dJREdFVF9XSURUSF9QQ1QsIDEwMCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgLy8gSWYgd2UncmUgc3RpbGwgb3ZlciwgZmluZCB0aGUgd2lkZ2V0cyB3aGljaCBoYXZlIG1vcmUgd2lkdGggdGhhbiB0aGUgbWluaW11bVxuICAgICAgICAgICAgICAgIC8vIGFuZCBiYWxhbmNlIHRoZW0gb3V0IHVudGlsIHdlJ3JlIGF0IDEwMCUuIFRoaXMgc2hvdWxkIGtlZXAgdXMgYXMgY2xvc2UgYXMgcG9zc2libGVcbiAgICAgICAgICAgICAgICAvLyB0byB0aGUgaW50ZW5kZWQgZGlzdHJpYnV0aW9ucy5cbiAgICAgICAgICAgICAgICAvL1xuICAgICAgICAgICAgICAgIC8vIE5vdGU6IGlmIHdlIGV2ZXIgZGVjaWRlIHRvIHNldCBhIG1pbmltdW0gd2hpY2ggaXMgbGFyZ2VyIHRoYW4gMTAwJS9NQVhfV0lER0VUUyB0aGVuXG4gICAgICAgICAgICAgICAgLy8gd2UgcHJvYmFibHkgaGF2ZSBvdGhlciBpc3N1ZXMgLSB0aGlzIGNvZGUgYXNzdW1lcyB3ZSBkb24ndCBkbyB0aGF0LlxuICAgICAgICAgICAgICAgIGNvbnN0IHRvUmVjbGFpbSA9IHN1bSguLi53aWR0aHMpIC0gMTAwO1xuICAgICAgICAgICAgICAgIGlmICh0b1JlY2xhaW0gPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGxhcmdlSW5kaWNlcyA9IHdpZHRoc1xuICAgICAgICAgICAgICAgICAgICAgICAgLm1hcCgodiwgaSkgPT4gW2ksIHZdKVxuICAgICAgICAgICAgICAgICAgICAgICAgLmZpbHRlcigocCkgPT4gcFsxXSA+IE1JTl9XSURHRVRfV0lEVEhfUENUKVxuICAgICAgICAgICAgICAgICAgICAgICAgLm1hcCgocCkgPT4gcFswXSk7XG4gICAgICAgICAgICAgICAgICAgIGZvciAoY29uc3QgaWR4IG9mIGxhcmdlSW5kaWNlcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGhzW2lkeF0gLT0gdG9SZWNsYWltIC8gbGFyZ2VJbmRpY2VzLmxlbmd0aDtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEZpbmFsbHksIGZpbGwgaW4gb3VyIGNhY2hlIGFuZCB1cGRhdGVcbiAgICAgICAgY29uc3QgbmV3Um9vbUNvbnRhaW5lcnMgPSBuZXcgTWFwKCk7XG4gICAgICAgIHRoaXMuYnlSb29tLnNldChyb29tLnJvb21JZCwgbmV3Um9vbUNvbnRhaW5lcnMpO1xuICAgICAgICBpZiAodG9wV2lkZ2V0cy5sZW5ndGgpIHtcbiAgICAgICAgICAgIG5ld1Jvb21Db250YWluZXJzLnNldChDb250YWluZXIuVG9wLCB7XG4gICAgICAgICAgICAgICAgb3JkZXJlZDogdG9wV2lkZ2V0cyxcbiAgICAgICAgICAgICAgICBkaXN0cmlidXRpb25zOiB3aWR0aHMsXG4gICAgICAgICAgICAgICAgaGVpZ2h0OiBtYXhIZWlnaHQsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICBpZiAocmlnaHRXaWRnZXRzLmxlbmd0aCkge1xuICAgICAgICAgICAgbmV3Um9vbUNvbnRhaW5lcnMuc2V0KENvbnRhaW5lci5SaWdodCwge1xuICAgICAgICAgICAgICAgIG9yZGVyZWQ6IHJpZ2h0V2lkZ2V0cyxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjZW50ZXJXaWRnZXRzLmxlbmd0aCkge1xuICAgICAgICAgICAgbmV3Um9vbUNvbnRhaW5lcnMuc2V0KENvbnRhaW5lci5DZW50ZXIsIHtcbiAgICAgICAgICAgICAgICBvcmRlcmVkOiBjZW50ZXJXaWRnZXRzLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBhZnRlckNoYW5nZXMgPSBKU09OLnN0cmluZ2lmeShyZWN1cnNpdmVNYXBUb09iamVjdChuZXdSb29tQ29udGFpbmVycykpO1xuXG4gICAgICAgIGlmIChhZnRlckNoYW5nZXMgIT09IGJlZm9yZUNoYW5nZXMpIHtcbiAgICAgICAgICAgIHRoaXMuZW1pdEZvcihyb29tKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHB1YmxpYyBnZXRDb250YWluZXJXaWRnZXRzKHJvb206IE9wdGlvbmFsPFJvb20+LCBjb250YWluZXI6IENvbnRhaW5lcik6IElXaWRnZXRbXSB7XG4gICAgICAgIHJldHVybiAocm9vbSAmJiB0aGlzLmJ5Um9vbS5nZXQocm9vbS5yb29tSWQpPy5nZXQoY29udGFpbmVyKT8ub3JkZXJlZCkgfHwgW107XG4gICAgfVxuXG4gICAgcHVibGljIGlzSW5Db250YWluZXIocm9vbTogUm9vbSwgd2lkZ2V0OiBJV2lkZ2V0LCBjb250YWluZXI6IENvbnRhaW5lcik6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gdGhpcy5nZXRDb250YWluZXJXaWRnZXRzKHJvb20sIGNvbnRhaW5lcikuc29tZSgodykgPT4gdy5pZCA9PT0gd2lkZ2V0LmlkKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgY2FuQWRkVG9Db250YWluZXIocm9vbTogUm9vbSwgY29udGFpbmVyOiBDb250YWluZXIpOiBib29sZWFuIHtcbiAgICAgICAgc3dpdGNoIChjb250YWluZXIpIHtcbiAgICAgICAgICAgIGNhc2UgQ29udGFpbmVyLlRvcDpcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5nZXRDb250YWluZXJXaWRnZXRzKHJvb20sIGNvbnRhaW5lcikubGVuZ3RoIDwgTUFYX1BJTk5FRDtcbiAgICAgICAgICAgIGNhc2UgQ29udGFpbmVyLlJpZ2h0OlxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmdldENvbnRhaW5lcldpZGdldHMocm9vbSwgY29udGFpbmVyKS5sZW5ndGggPCBNQVhfUElOTkVEO1xuICAgICAgICAgICAgY2FzZSBDb250YWluZXIuQ2VudGVyOlxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmdldENvbnRhaW5lcldpZGdldHMocm9vbSwgY29udGFpbmVyKS5sZW5ndGggPCAxO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIGdldFJlc2l6ZXJEaXN0cmlidXRpb25zKHJvb206IFJvb20sIGNvbnRhaW5lcjogQ29udGFpbmVyKTogc3RyaW5nW10ge1xuICAgICAgICAvLyB5ZXMsIHN0cmluZy5cbiAgICAgICAgbGV0IGRpc3RyaWJ1dGlvbnMgPSB0aGlzLmJ5Um9vbS5nZXQocm9vbS5yb29tSWQpPy5nZXQoY29udGFpbmVyKT8uZGlzdHJpYnV0aW9ucztcbiAgICAgICAgaWYgKCFkaXN0cmlidXRpb25zIHx8IGRpc3RyaWJ1dGlvbnMubGVuZ3RoIDwgMikgcmV0dXJuIFtdO1xuXG4gICAgICAgIC8vIFRoZSBkaXN0cmlidXRvciBhY3R1YWxseSBleHBlY3RzIHRvIGJlIGZlZCBOLTEgc2l6ZXMgYW5kIGV4cGFuZHMgdGhlIG1pZGRsZSBzZWN0aW9uXG4gICAgICAgIC8vIGluc3RlYWQgb2YgdGhlIGVkZ2VzLiBUaGVyZWZvcmUsIHdlIG5lZWQgdG8gcmV0dXJuIFswXSB3aGVuIHRoZXJlJ3MgdHdvIHdpZGdldHMgb3JcbiAgICAgICAgLy8gWzAsIDJdIHdoZW4gdGhlcmUncyB0aHJlZSAoc2tpcHBpbmcgWzFdIGJlY2F1c2UgaXQncyBpcnJlbGV2YW50KS5cblxuICAgICAgICBpZiAoZGlzdHJpYnV0aW9ucy5sZW5ndGggPT09IDIpIGRpc3RyaWJ1dGlvbnMgPSBbZGlzdHJpYnV0aW9uc1swXV07XG4gICAgICAgIGlmIChkaXN0cmlidXRpb25zLmxlbmd0aCA9PT0gMykgZGlzdHJpYnV0aW9ucyA9IFtkaXN0cmlidXRpb25zWzBdLCBkaXN0cmlidXRpb25zWzJdXTtcbiAgICAgICAgcmV0dXJuIGRpc3RyaWJ1dGlvbnMubWFwKChkKSA9PiBgJHtkLnRvRml4ZWQoMSl9JWApOyAvLyBhY3R1YWwgcGVyY2VudHMgLSB0aGVzZSBhcmUgZGVjb2RlZCBsYXRlclxuICAgIH1cblxuICAgIHB1YmxpYyBzZXRSZXNpemVyRGlzdHJpYnV0aW9ucyhyb29tOiBSb29tLCBjb250YWluZXI6IENvbnRhaW5lciwgZGlzdHJpYnV0aW9uczogc3RyaW5nW10pOiB2b2lkIHtcbiAgICAgICAgaWYgKGNvbnRhaW5lciAhPT0gQ29udGFpbmVyLlRvcCkgcmV0dXJuOyAvLyBpZ25vcmUgLSBub3QgcmVsZXZhbnRcblxuICAgICAgICBjb25zdCBudW1iZXJzID0gZGlzdHJpYnV0aW9ucy5tYXAoKGQpID0+IE51bWJlcihOdW1iZXIoZC5zdWJzdHJpbmcoMCwgZC5sZW5ndGggLSAxKSkudG9GaXhlZCgxKSkpO1xuICAgICAgICBjb25zdCB3aWRnZXRzID0gdGhpcy5nZXRDb250YWluZXJXaWRnZXRzKHJvb20sIGNvbnRhaW5lcik7XG5cbiAgICAgICAgLy8gRnJvbSBnZXRSZXNpemVyRGlzdHJpYnV0aW9ucywgd2UgbmVlZCB0byBmaWxsIGluIHRoZSBtaWRkbGUgc2l6ZSBpZiBhcHBsaWNhYmxlLlxuICAgICAgICBjb25zdCByZW1haW5pbmcgPSAxMDAgLSBzdW0oLi4ubnVtYmVycyk7XG4gICAgICAgIGlmIChudW1iZXJzLmxlbmd0aCA9PT0gMikgbnVtYmVycy5zcGxpY2UoMSwgMCwgcmVtYWluaW5nKTtcbiAgICAgICAgaWYgKG51bWJlcnMubGVuZ3RoID09PSAxKSBudW1iZXJzLnB1c2gocmVtYWluaW5nKTtcblxuICAgICAgICBjb25zdCBsb2NhbExheW91dDogUmVjb3JkPHN0cmluZywgSVN0b3JlZExheW91dD4gPSB7fTtcbiAgICAgICAgd2lkZ2V0cy5mb3JFYWNoKCh3LCBpKSA9PiB7XG4gICAgICAgICAgICBsb2NhbExheW91dFt3LmlkXSA9IHtcbiAgICAgICAgICAgICAgICBjb250YWluZXI6IGNvbnRhaW5lcixcbiAgICAgICAgICAgICAgICB3aWR0aDogbnVtYmVyc1tpXSxcbiAgICAgICAgICAgICAgICBpbmRleDogaSxcbiAgICAgICAgICAgICAgICBoZWlnaHQ6IHRoaXMuYnlSb29tLmdldChyb29tLnJvb21JZCk/LmdldChjb250YWluZXIpPy5oZWlnaHQgfHwgTUlOX1dJREdFVF9IRUlHSFRfUENULFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMudXBkYXRlVXNlckxheW91dChyb29tLCBsb2NhbExheW91dCk7XG4gICAgfVxuXG4gICAgcHVibGljIGdldENvbnRhaW5lckhlaWdodChyb29tOiBSb29tLCBjb250YWluZXI6IENvbnRhaW5lcik6IG51bWJlciB8IG51bGwge1xuICAgICAgICByZXR1cm4gdGhpcy5ieVJvb20uZ2V0KHJvb20ucm9vbUlkKT8uZ2V0KGNvbnRhaW5lcik/LmhlaWdodCA/PyBudWxsOyAvLyBsZXQgdGhlIGRlZmF1bHQgZ2V0IHJldHVybmVkIGlmIG5lZWRlZFxuICAgIH1cblxuICAgIHB1YmxpYyBzZXRDb250YWluZXJIZWlnaHQocm9vbTogUm9vbSwgY29udGFpbmVyOiBDb250YWluZXIsIGhlaWdodD86IG51bWJlciB8IG51bGwpOiB2b2lkIHtcbiAgICAgICAgY29uc3Qgd2lkZ2V0cyA9IHRoaXMuZ2V0Q29udGFpbmVyV2lkZ2V0cyhyb29tLCBjb250YWluZXIpO1xuICAgICAgICBjb25zdCB3aWR0aHMgPSB0aGlzLmJ5Um9vbS5nZXQocm9vbS5yb29tSWQpPy5nZXQoY29udGFpbmVyKT8uZGlzdHJpYnV0aW9ucztcbiAgICAgICAgY29uc3QgbG9jYWxMYXlvdXQ6IFJlY29yZDxzdHJpbmcsIElTdG9yZWRMYXlvdXQ+ID0ge307XG4gICAgICAgIHdpZGdldHMuZm9yRWFjaCgodywgaSkgPT4ge1xuICAgICAgICAgICAgbG9jYWxMYXlvdXRbdy5pZF0gPSB7XG4gICAgICAgICAgICAgICAgY29udGFpbmVyOiBjb250YWluZXIsXG4gICAgICAgICAgICAgICAgd2lkdGg6IHdpZHRocz8uW2ldLFxuICAgICAgICAgICAgICAgIGluZGV4OiBpLFxuICAgICAgICAgICAgICAgIGhlaWdodDogaGVpZ2h0LFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMudXBkYXRlVXNlckxheW91dChyb29tLCBsb2NhbExheW91dCk7XG4gICAgfVxuXG4gICAgcHVibGljIG1vdmVXaXRoaW5Db250YWluZXIocm9vbTogUm9vbSwgY29udGFpbmVyOiBDb250YWluZXIsIHdpZGdldDogSVdpZGdldCwgZGVsdGE6IG51bWJlcik6IHZvaWQge1xuICAgICAgICBjb25zdCB3aWRnZXRzID0gYXJyYXlGYXN0Q2xvbmUodGhpcy5nZXRDb250YWluZXJXaWRnZXRzKHJvb20sIGNvbnRhaW5lcikpO1xuICAgICAgICBjb25zdCBjdXJyZW50SWR4ID0gd2lkZ2V0cy5maW5kSW5kZXgoKHcpID0+IHcuaWQgPT09IHdpZGdldC5pZCk7XG4gICAgICAgIGlmIChjdXJyZW50SWR4IDwgMCkgcmV0dXJuOyAvLyBubyBjaGFuZ2UgbmVlZGVkXG5cbiAgICAgICAgd2lkZ2V0cy5zcGxpY2UoY3VycmVudElkeCwgMSk7IC8vIHJlbW92ZSBleGlzdGluZyB3aWRnZXRcbiAgICAgICAgY29uc3QgbmV3SWR4ID0gY2xhbXAoY3VycmVudElkeCArIGRlbHRhLCAwLCB3aWRnZXRzLmxlbmd0aCk7XG4gICAgICAgIHdpZGdldHMuc3BsaWNlKG5ld0lkeCwgMCwgd2lkZ2V0KTtcblxuICAgICAgICBjb25zdCB3aWR0aHMgPSB0aGlzLmJ5Um9vbS5nZXQocm9vbS5yb29tSWQpPy5nZXQoY29udGFpbmVyKT8uZGlzdHJpYnV0aW9ucztcbiAgICAgICAgY29uc3QgaGVpZ2h0ID0gdGhpcy5ieVJvb20uZ2V0KHJvb20ucm9vbUlkKT8uZ2V0KGNvbnRhaW5lcik/LmhlaWdodDtcbiAgICAgICAgY29uc3QgbG9jYWxMYXlvdXQ6IFJlY29yZDxzdHJpbmcsIElTdG9yZWRMYXlvdXQ+ID0ge307XG4gICAgICAgIHdpZGdldHMuZm9yRWFjaCgodywgaSkgPT4ge1xuICAgICAgICAgICAgbG9jYWxMYXlvdXRbdy5pZF0gPSB7XG4gICAgICAgICAgICAgICAgY29udGFpbmVyOiBjb250YWluZXIsXG4gICAgICAgICAgICAgICAgd2lkdGg6IHdpZHRocz8uW2ldLFxuICAgICAgICAgICAgICAgIGluZGV4OiBpLFxuICAgICAgICAgICAgICAgIGhlaWdodCxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLnVwZGF0ZVVzZXJMYXlvdXQocm9vbSwgbG9jYWxMYXlvdXQpO1xuICAgIH1cblxuICAgIHB1YmxpYyBtb3ZlV