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