matrix-react-sdk
Version:
SDK for matrix.org using React
438 lines (428 loc) • 83.4 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireWildcard(require("react"));
var _matrix = require("matrix-js-sdk/src/matrix");
var _classnames = _interopRequireDefault(require("classnames"));
var _pin = _interopRequireDefault(require("@vector-im/compound-design-tokens/assets/web/icons/pin"));
var _unpin = _interopRequireDefault(require("@vector-im/compound-design-tokens/assets/web/icons/unpin"));
var _overflowHorizontal = _interopRequireDefault(require("@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal"));
var _edit = require("../../../../res/img/element-icons/room/message-bar/edit.svg");
var _emoji = require("../../../../res/img/element-icons/room/message-bar/emoji.svg");
var _retry = require("../../../../res/img/element-icons/retry.svg");
var _thread = require("../../../../res/img/element-icons/message/thread.svg");
var _trashcan = require("../../../../res/img/element-icons/trashcan.svg");
var _reply = require("../../../../res/img/element-icons/room/message-bar/reply.svg");
var _expandMessage = require("../../../../res/img/element-icons/expand-message.svg");
var _collapseMessage = require("../../../../res/img/element-icons/collapse-message.svg");
var _languageHandler = require("../../../languageHandler");
var _dispatcher = _interopRequireWildcard(require("../../../dispatcher/dispatcher"));
var _ContextMenu = _interopRequireWildcard(require("../../structures/ContextMenu"));
var _EventUtils = require("../../../utils/EventUtils");
var _RoomContext = _interopRequireWildcard(require("../../../contexts/RoomContext"));
var _Toolbar = _interopRequireDefault(require("../../../accessibility/Toolbar"));
var _RovingTabIndex = require("../../../accessibility/RovingTabIndex");
var _MessageContextMenu = _interopRequireDefault(require("../context_menus/MessageContextMenu"));
var _Resend = _interopRequireDefault(require("../../../Resend"));
var _MatrixClientPeg = require("../../../MatrixClientPeg");
var _MediaEventHelper = require("../../../utils/MediaEventHelper");
var _DownloadActionButton = _interopRequireDefault(require("./DownloadActionButton"));
var _ReactionPicker = _interopRequireDefault(require("../emojipicker/ReactionPicker"));
var _context = require("../right_panel/context");
var _Reply = require("../../../utils/Reply");
var _Keyboard = require("../../../Keyboard");
var _KeyboardShortcuts = require("../../../accessibility/KeyboardShortcuts");
var _actions = require("../../../dispatcher/actions");
var _types = require("../../../voice-broadcast/types");
var _PinningUtils = _interopRequireDefault(require("../../../utils/PinningUtils"));
var _PosthogTrackers = _interopRequireDefault(require("../../../PosthogTrackers.ts"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/*
Copyright 2024 New Vector Ltd.
Copyright 2019-2023 The Matrix.org Foundation C.I.C.
Copyright 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
const OptionsButton = ({
mxEvent,
getTile,
getReplyChain,
permalinkCreator,
onFocusChange,
getRelationsForEvent
}) => {
const [menuDisplayed, button, openMenu, closeMenu] = (0, _ContextMenu.useContextMenu)();
const [onFocus, isActive] = (0, _RovingTabIndex.useRovingTabIndex)(button);
(0, _react.useEffect)(() => {
onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]);
const onOptionsClick = (0, _react.useCallback)(e => {
// Don't open the regular browser or our context menu on right-click
e.preventDefault();
e.stopPropagation();
openMenu();
// when the context menu is opened directly, e.g. via mouse click, the onFocus handler which tracks
// the element that is currently focused is skipped. So we want to call onFocus manually to keep the
// position in the page even when someone is clicking around.
onFocus();
}, [openMenu, onFocus]);
let contextMenu;
if (menuDisplayed && button.current) {
const tile = getTile?.();
const replyChain = getReplyChain();
const buttonRect = button.current.getBoundingClientRect();
contextMenu = /*#__PURE__*/_react.default.createElement(_MessageContextMenu.default, (0, _extends2.default)({}, (0, _ContextMenu.aboveLeftOf)(buttonRect), {
mxEvent: mxEvent,
permalinkCreator: permalinkCreator,
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
collapseReplyChain: replyChain?.canCollapse() ? replyChain.collapse : undefined,
onFinished: closeMenu,
getRelationsForEvent: getRelationsForEvent
}));
}
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_ContextMenu.ContextMenuTooltipButton, {
className: "mx_MessageActionBar_iconButton mx_MessageActionBar_optionsButton",
title: (0, _languageHandler._t)("common|options"),
onClick: onOptionsClick,
onContextMenu: onOptionsClick,
isExpanded: menuDisplayed,
ref: button,
onFocus: onFocus,
tabIndex: isActive ? 0 : -1,
placement: "left"
}, /*#__PURE__*/_react.default.createElement(_overflowHorizontal.default, null)), contextMenu);
};
const ReactButton = ({
mxEvent,
reactions,
onFocusChange
}) => {
const [menuDisplayed, button, openMenu, closeMenu] = (0, _ContextMenu.useContextMenu)();
const [onFocus, isActive] = (0, _RovingTabIndex.useRovingTabIndex)(button);
(0, _react.useEffect)(() => {
onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]);
let contextMenu;
if (menuDisplayed && button.current) {
const buttonRect = button.current.getBoundingClientRect();
contextMenu = /*#__PURE__*/_react.default.createElement(_ContextMenu.default, (0, _extends2.default)({}, (0, _ContextMenu.aboveLeftOf)(buttonRect), {
onFinished: closeMenu,
managed: false
}), /*#__PURE__*/_react.default.createElement(_ReactionPicker.default, {
mxEvent: mxEvent,
reactions: reactions,
onFinished: closeMenu
}));
}
const onClick = (0, _react.useCallback)(e => {
// Don't open the regular browser or our context menu on right-click
e.preventDefault();
e.stopPropagation();
openMenu();
// when the context menu is opened directly, e.g. via mouse click, the onFocus handler which tracks
// the element that is currently focused is skipped. So we want to call onFocus manually to keep the
// position in the page even when someone is clicking around.
onFocus();
}, [openMenu, onFocus]);
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_ContextMenu.ContextMenuTooltipButton, {
className: "mx_MessageActionBar_iconButton",
title: (0, _languageHandler._t)("action|react"),
onClick: onClick,
onContextMenu: onClick,
isExpanded: menuDisplayed,
ref: button,
onFocus: onFocus,
tabIndex: isActive ? 0 : -1,
placement: "left"
}, /*#__PURE__*/_react.default.createElement(_emoji.Icon, null)), contextMenu);
};
const ReplyInThreadButton = ({
mxEvent
}) => {
const context = (0, _react.useContext)(_context.CardContext);
const relationType = mxEvent?.getRelation()?.rel_type;
const hasARelation = !!relationType && relationType !== _matrix.RelationType.Thread;
const onClick = e => {
// Don't open the regular browser or our context menu on right-click
e.preventDefault();
e.stopPropagation();
const thread = mxEvent.getThread();
if (thread?.rootEvent && !mxEvent.isThreadRoot) {
_dispatcher.defaultDispatcher.dispatch({
action: _actions.Action.ShowThread,
rootEvent: thread.rootEvent,
initialEvent: mxEvent,
scroll_into_view: true,
highlighted: true,
push: context.isCard
});
} else {
_dispatcher.defaultDispatcher.dispatch({
action: _actions.Action.ShowThread,
rootEvent: mxEvent,
push: context.isCard
});
}
};
const title = !hasARelation ? (0, _languageHandler._t)("action|reply_in_thread") : (0, _languageHandler._t)("threads|error_start_thread_existing_relation");
return /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingAccessibleButton, {
className: "mx_MessageActionBar_iconButton mx_MessageActionBar_threadButton",
disabled: hasARelation,
title: title,
onClick: onClick,
onContextMenu: onClick,
placement: "left"
}, /*#__PURE__*/_react.default.createElement(_thread.Icon, null));
};
class MessageActionBar extends _react.default.PureComponent {
constructor(...args) {
super(...args);
(0, _defineProperty2.default)(this, "onDecrypted", () => {
// When an event decrypts, it is likely to change the set of available
// actions, so we force an update to check again.
this.forceUpdate();
});
(0, _defineProperty2.default)(this, "onBeforeRedaction", () => {
// When an event is redacted, we can't edit it so update the available actions.
this.forceUpdate();
});
(0, _defineProperty2.default)(this, "onRoomEvent", event => {
// If the event is pinned or unpinned, rerender the component.
if (!event || event.getType() !== _matrix.EventType.RoomPinnedEvents) return;
this.forceUpdate();
});
(0, _defineProperty2.default)(this, "onSent", () => {
// When an event is sent and echoed the possible actions change.
this.forceUpdate();
});
(0, _defineProperty2.default)(this, "onFocusChange", focused => {
this.props.onFocusChange?.(focused);
});
(0, _defineProperty2.default)(this, "onReplyClick", e => {
// Don't open the regular browser or our context menu on right-click
e.preventDefault();
e.stopPropagation();
_dispatcher.default.dispatch({
action: "reply_to_event",
event: this.props.mxEvent,
context: this.context.timelineRenderingType
});
});
(0, _defineProperty2.default)(this, "onEditClick", e => {
// Don't open the regular browser or our context menu on right-click
e.preventDefault();
e.stopPropagation();
(0, _EventUtils.editEvent)(_MatrixClientPeg.MatrixClientPeg.safeGet(), this.props.mxEvent, this.context.timelineRenderingType, this.props.getRelationsForEvent);
});
(0, _defineProperty2.default)(this, "forbiddenThreadHeadMsgType", [_matrix.MsgType.KeyVerificationRequest]);
(0, _defineProperty2.default)(this, "onResendClick", ev => {
// Don't open the regular browser or our context menu on right-click
ev.preventDefault();
ev.stopPropagation();
this.runActionOnFailedEv(tarEv => _Resend.default.resend(_MatrixClientPeg.MatrixClientPeg.safeGet(), tarEv));
});
(0, _defineProperty2.default)(this, "onCancelClick", ev => {
this.runActionOnFailedEv(tarEv => _Resend.default.removeFromQueue(_MatrixClientPeg.MatrixClientPeg.safeGet(), tarEv), testEv => (0, _EventUtils.canCancel)(testEv.status));
});
/**
* Pin or unpin the event.
*/
(0, _defineProperty2.default)(this, "onPinClick", async (event, isPinned) => {
// Don't open the regular browser or our context menu on right-click
event.preventDefault();
event.stopPropagation();
await _PinningUtils.default.pinOrUnpinEvent(_MatrixClientPeg.MatrixClientPeg.safeGet(), this.props.mxEvent);
_PosthogTrackers.default.trackPinUnpinMessage(isPinned ? "Pin" : "Unpin", "Timeline");
});
}
componentDidMount() {
if (this.props.mxEvent.status && this.props.mxEvent.status !== _matrix.EventStatus.SENT) {
this.props.mxEvent.on(_matrix.MatrixEventEvent.Status, this.onSent);
}
const client = _MatrixClientPeg.MatrixClientPeg.safeGet();
client.decryptEventIfNeeded(this.props.mxEvent);
if (this.props.mxEvent.isBeingDecrypted()) {
this.props.mxEvent.once(_matrix.MatrixEventEvent.Decrypted, this.onDecrypted);
}
this.props.mxEvent.on(_matrix.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
this.context.room?.getLiveTimeline().getState(_matrix.EventTimeline.FORWARDS)?.on(_matrix.RoomStateEvent.Events, this.onRoomEvent);
}
componentWillUnmount() {
this.props.mxEvent.off(_matrix.MatrixEventEvent.Status, this.onSent);
this.props.mxEvent.off(_matrix.MatrixEventEvent.Decrypted, this.onDecrypted);
this.props.mxEvent.off(_matrix.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
this.context.room?.getLiveTimeline().getState(_matrix.EventTimeline.FORWARDS)?.off(_matrix.RoomStateEvent.Events, this.onRoomEvent);
}
get showReplyInThreadAction() {
const inNotThreadTimeline = this.context.timelineRenderingType !== _RoomContext.TimelineRenderingType.Thread;
const isAllowedMessageType = !this.forbiddenThreadHeadMsgType.includes(this.props.mxEvent.getContent().msgtype) &&
/** forbid threads from live location shares
* until cross-platform support
* (PSF-1041)
*/
!_matrix.M_BEACON_INFO.matches(this.props.mxEvent.getType()) && !(this.props.mxEvent.getType() === _types.VoiceBroadcastInfoEventType);
return inNotThreadTimeline && isAllowedMessageType;
}
/**
* Runs a given fn on the set of possible events to test. The first event
* that passes the checkFn will have fn executed on it. Both functions take
* a MatrixEvent object. If no particular conditions are needed, checkFn can
* be null/undefined. If no functions pass the checkFn, no action will be
* taken.
* @param {Function} fn The execution function.
* @param {Function} checkFn The test function.
*/
runActionOnFailedEv(fn, checkFn) {
if (!checkFn) checkFn = () => true;
const mxEvent = this.props.mxEvent;
const editEvent = mxEvent.replacingEvent();
const redactEvent = mxEvent.localRedactionEvent();
const tryOrder = [redactEvent, editEvent, mxEvent];
for (const ev of tryOrder) {
if (ev && checkFn(ev)) {
fn(ev);
break;
}
}
}
render() {
const toolbarOpts = [];
if ((0, _EventUtils.canEditContent)(_MatrixClientPeg.MatrixClientPeg.safeGet(), this.props.mxEvent)) {
toolbarOpts.push( /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingAccessibleButton, {
className: "mx_MessageActionBar_iconButton",
title: (0, _languageHandler._t)("action|edit"),
onClick: this.onEditClick,
onContextMenu: this.onEditClick,
key: "edit",
placement: "left"
}, /*#__PURE__*/_react.default.createElement(_edit.Icon, null)));
}
if (_PinningUtils.default.canPin(_MatrixClientPeg.MatrixClientPeg.safeGet(), this.props.mxEvent) || _PinningUtils.default.canUnpin(_MatrixClientPeg.MatrixClientPeg.safeGet(), this.props.mxEvent)) {
const isPinned = _PinningUtils.default.isPinned(_MatrixClientPeg.MatrixClientPeg.safeGet(), this.props.mxEvent);
toolbarOpts.push( /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingAccessibleButton, {
className: "mx_MessageActionBar_iconButton",
title: isPinned ? (0, _languageHandler._t)("action|unpin") : (0, _languageHandler._t)("action|pin"),
onClick: e => this.onPinClick(e, isPinned),
onContextMenu: e => this.onPinClick(e, isPinned),
key: "pin",
placement: "left"
}, isPinned ? /*#__PURE__*/_react.default.createElement(_unpin.default, null) : /*#__PURE__*/_react.default.createElement(_pin.default, null)));
}
const cancelSendingButton = /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingAccessibleButton, {
className: "mx_MessageActionBar_iconButton",
title: (0, _languageHandler._t)("action|delete"),
onClick: this.onCancelClick,
onContextMenu: this.onCancelClick,
key: "cancel",
placement: "left"
}, /*#__PURE__*/_react.default.createElement(_trashcan.Icon, null));
const threadTooltipButton = /*#__PURE__*/_react.default.createElement(ReplyInThreadButton, {
mxEvent: this.props.mxEvent,
key: "reply_thread"
});
// We show a different toolbar for failed events, so detect that first.
const mxEvent = this.props.mxEvent;
const editStatus = mxEvent.replacingEvent()?.status;
const redactStatus = mxEvent.localRedactionEvent()?.status;
const allowCancel = (0, _EventUtils.canCancel)(mxEvent.status) || (0, _EventUtils.canCancel)(editStatus) || (0, _EventUtils.canCancel)(redactStatus);
const isFailed = [mxEvent.status, editStatus, redactStatus].includes(_matrix.EventStatus.NOT_SENT);
if (allowCancel && isFailed) {
// The resend button needs to appear ahead of the edit button, so insert to the
// start of the opts
toolbarOpts.splice(0, 0, /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingAccessibleButton, {
className: "mx_MessageActionBar_iconButton",
title: (0, _languageHandler._t)("action|retry"),
onClick: this.onResendClick,
onContextMenu: this.onResendClick,
key: "resend",
placement: "left"
}, /*#__PURE__*/_react.default.createElement(_retry.Icon, null)));
// The delete button should appear last, so we can just drop it at the end
toolbarOpts.push(cancelSendingButton);
} else {
if ((0, _EventUtils.isContentActionable)(this.props.mxEvent)) {
// Like the resend button, the react and reply buttons need to appear before the edit.
// The only catch is we do the reply button first so that we can make sure the react
// button is the very first button without having to do length checks for `splice()`.
if (this.context.canSendMessages) {
if (this.showReplyInThreadAction) {
toolbarOpts.splice(0, 0, threadTooltipButton);
}
toolbarOpts.splice(0, 0, /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingAccessibleButton, {
className: "mx_MessageActionBar_iconButton",
title: (0, _languageHandler._t)("action|reply"),
onClick: this.onReplyClick,
onContextMenu: this.onReplyClick,
key: "reply",
placement: "left"
}, /*#__PURE__*/_react.default.createElement(_reply.Icon, null)));
}
// We hide the react button in search results as we don't show reactions in results
if (this.context.canReact && !this.context.search) {
toolbarOpts.splice(0, 0, /*#__PURE__*/_react.default.createElement(ReactButton, {
mxEvent: this.props.mxEvent,
reactions: this.props.reactions,
onFocusChange: this.onFocusChange,
key: "react"
}));
}
// XXX: Assuming that the underlying tile will be a media event if it is eligible media.
if (_MediaEventHelper.MediaEventHelper.isEligible(this.props.mxEvent)) {
toolbarOpts.splice(0, 0, /*#__PURE__*/_react.default.createElement(_DownloadActionButton.default, {
mxEvent: this.props.mxEvent,
mediaEventHelperGet: () => this.props.getTile()?.getMediaHelper?.(),
key: "download"
}));
}
} else if (
// Show thread icon even for deleted messages, but only within main timeline
this.context.timelineRenderingType === _RoomContext.TimelineRenderingType.Room && this.props.mxEvent.getThread()) {
toolbarOpts.unshift(threadTooltipButton);
}
if (allowCancel) {
toolbarOpts.push(cancelSendingButton);
}
if (this.props.isQuoteExpanded !== undefined && (0, _Reply.shouldDisplayReply)(this.props.mxEvent)) {
const expandClassName = (0, _classnames.default)({
mx_MessageActionBar_iconButton: true,
mx_MessageActionBar_expandCollapseMessageButton: true
});
toolbarOpts.push( /*#__PURE__*/_react.default.createElement(_RovingTabIndex.RovingAccessibleButton, {
className: expandClassName,
title: this.props.isQuoteExpanded ? (0, _languageHandler._t)("timeline|mab|collapse_reply_chain") : (0, _languageHandler._t)("timeline|mab|expand_reply_chain"),
caption: (0, _languageHandler._t)(_KeyboardShortcuts.ALTERNATE_KEY_NAME[_Keyboard.Key.SHIFT]) + " + " + (0, _languageHandler._t)("action|click"),
onClick: this.props.toggleThreadExpanded,
key: "expand",
placement: "left"
}, this.props.isQuoteExpanded ? /*#__PURE__*/_react.default.createElement(_collapseMessage.Icon, null) : /*#__PURE__*/_react.default.createElement(_expandMessage.Icon, null)));
}
// The menu button should be last, so dump it there.
toolbarOpts.push( /*#__PURE__*/_react.default.createElement(OptionsButton, {
mxEvent: this.props.mxEvent,
getReplyChain: this.props.getReplyChain,
getTile: this.props.getTile,
permalinkCreator: this.props.permalinkCreator,
onFocusChange: this.onFocusChange,
key: "menu",
getRelationsForEvent: this.props.getRelationsForEvent
}));
}
// aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive.
return /*#__PURE__*/_react.default.createElement(_Toolbar.default, {
className: "mx_MessageActionBar",
"aria-label": (0, _languageHandler._t)("timeline|mab|label"),
"aria-live": "off"
}, toolbarOpts);
}
}
exports.default = MessageActionBar;
(0, _defineProperty2.default)(MessageActionBar, "contextType", _RoomContext.default);
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJfaW50ZXJvcFJlcXVpcmVXaWxkY2FyZCIsInJlcXVpcmUiLCJfbWF0cml4IiwiX2NsYXNzbmFtZXMiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwiX3BpbiIsIl91bnBpbiIsIl9vdmVyZmxvd0hvcml6b250YWwiLCJfZWRpdCIsIl9lbW9qaSIsIl9yZXRyeSIsIl90aHJlYWQiLCJfdHJhc2hjYW4iLCJfcmVwbHkiLCJfZXhwYW5kTWVzc2FnZSIsIl9jb2xsYXBzZU1lc3NhZ2UiLCJfbGFuZ3VhZ2VIYW5kbGVyIiwiX2Rpc3BhdGNoZXIiLCJfQ29udGV4dE1lbnUiLCJfRXZlbnRVdGlscyIsIl9Sb29tQ29udGV4dCIsIl9Ub29sYmFyIiwiX1JvdmluZ1RhYkluZGV4IiwiX01lc3NhZ2VDb250ZXh0TWVudSIsIl9SZXNlbmQiLCJfTWF0cml4Q2xpZW50UGVnIiwiX01lZGlhRXZlbnRIZWxwZXIiLCJfRG93bmxvYWRBY3Rpb25CdXR0b24iLCJfUmVhY3Rpb25QaWNrZXIiLCJfY29udGV4dCIsIl9SZXBseSIsIl9LZXlib2FyZCIsIl9LZXlib2FyZFNob3J0Y3V0cyIsIl9hY3Rpb25zIiwiX3R5cGVzIiwiX1Bpbm5pbmdVdGlscyIsIl9Qb3N0aG9nVHJhY2tlcnMiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsImRlZmF1bHQiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJPcHRpb25zQnV0dG9uIiwibXhFdmVudCIsImdldFRpbGUiLCJnZXRSZXBseUNoYWluIiwicGVybWFsaW5rQ3JlYXRvciIsIm9uRm9jdXNDaGFuZ2UiLCJnZXRSZWxhdGlvbnNGb3JFdmVudCIsIm1lbnVEaXNwbGF5ZWQiLCJidXR0b24iLCJvcGVuTWVudSIsImNsb3NlTWVudSIsInVzZUNvbnRleHRNZW51Iiwib25Gb2N1cyIsImlzQWN0aXZlIiwidXNlUm92aW5nVGFiSW5kZXgiLCJ1c2VFZmZlY3QiLCJvbk9wdGlvbnNDbGljayIsInVzZUNhbGxiYWNrIiwicHJldmVudERlZmF1bHQiLCJzdG9wUHJvcGFnYXRpb24iLCJjb250ZXh0TWVudSIsImN1cnJlbnQiLCJ0aWxlIiwicmVwbHlDaGFpbiIsImJ1dHRvblJlY3QiLCJnZXRCb3VuZGluZ0NsaWVudFJlY3QiLCJjcmVhdGVFbGVtZW50IiwiX2V4dGVuZHMyIiwiYWJvdmVMZWZ0T2YiLCJldmVudFRpbGVPcHMiLCJnZXRFdmVudFRpbGVPcHMiLCJ1bmRlZmluZWQiLCJjb2xsYXBzZVJlcGx5Q2hhaW4iLCJjYW5Db2xsYXBzZSIsImNvbGxhcHNlIiwib25GaW5pc2hlZCIsIkZyYWdtZW50IiwiQ29udGV4dE1lbnVUb29sdGlwQnV0dG9uIiwiY2xhc3NOYW1lIiwidGl0bGUiLCJfdCIsIm9uQ2xpY2siLCJvbkNvbnRleHRNZW51IiwiaXNFeHBhbmRlZCIsInJlZiIsInRhYkluZGV4IiwicGxhY2VtZW50IiwiUmVhY3RCdXR0b24iLCJyZWFjdGlvbnMiLCJtYW5hZ2VkIiwiSWNvbiIsIlJlcGx5SW5UaHJlYWRCdXR0b24iLCJjb250ZXh0IiwidXNlQ29udGV4dCIsIkNhcmRDb250ZXh0IiwicmVsYXRpb25UeXBlIiwiZ2V0UmVsYXRpb24iLCJyZWxfdHlwZSIsImhhc0FSZWxhdGlvbiIsIlJlbGF0aW9uVHlwZSIsIlRocmVhZCIsInRocmVhZCIsImdldFRocmVhZCIsInJvb3RFdmVudCIsImlzVGhyZWFkUm9vdCIsImRlZmF1bHREaXNwYXRjaGVyIiwiZGlzcGF0Y2giLCJhY3Rpb24iLCJBY3Rpb24iLCJTaG93VGhyZWFkIiwiaW5pdGlhbEV2ZW50Iiwic2Nyb2xsX2ludG9fdmlldyIsImhpZ2hsaWdodGVkIiwicHVzaCIsImlzQ2FyZCIsIlJvdmluZ0FjY2Vzc2libGVCdXR0b24iLCJkaXNhYmxlZCIsIk1lc3NhZ2VBY3Rpb25CYXIiLCJSZWFjdCIsIlB1cmVDb21wb25lbnQiLCJjb25zdHJ1Y3RvciIsImFyZ3MiLCJfZGVmaW5lUHJvcGVydHkyIiwiZm9yY2VVcGRhdGUiLCJldmVudCIsImdldFR5cGUiLCJFdmVudFR5cGUiLCJSb29tUGlubmVkRXZlbnRzIiwiZm9jdXNlZCIsInByb3BzIiwiZGlzIiwidGltZWxpbmVSZW5kZXJpbmdUeXBlIiwiZWRpdEV2ZW50IiwiTWF0cml4Q2xpZW50UGVnIiwic2FmZUdldCIsIk1zZ1R5cGUiLCJLZXlWZXJpZmljYXRpb25SZXF1ZXN0IiwiZXYiLCJydW5BY3Rpb25PbkZhaWxlZEV2IiwidGFyRXYiLCJSZXNlbmQiLCJyZXNlbmQiLCJyZW1vdmVGcm9tUXVldWUiLCJ0ZXN0RXYiLCJjYW5DYW5jZWwiLCJzdGF0dXMiLCJpc1Bpbm5lZCIsIlBpbm5pbmdVdGlscyIsInBpbk9yVW5waW5FdmVudCIsIlBvc3Rob2dUcmFja2VycyIsInRyYWNrUGluVW5waW5NZXNzYWdlIiwiY29tcG9uZW50RGlkTW91bnQiLCJFdmVudFN0YXR1cyIsIlNFTlQiLCJvbiIsIk1hdHJpeEV2ZW50RXZlbnQiLCJTdGF0dXMiLCJvblNlbnQiLCJjbGllbnQiLCJkZWNyeXB0RXZlbnRJZk5lZWRlZCIsImlzQmVpbmdEZWNyeXB0ZWQiLCJvbmNlIiwiRGVjcnlwdGVkIiwib25EZWNyeXB0ZWQiLCJCZWZvcmVSZWRhY3Rpb24iLCJvbkJlZm9yZVJlZGFjdGlvbiIsInJvb20iLCJnZXRMaXZlVGltZWxpbmUiLCJnZXRTdGF0ZSIsIkV2ZW50VGltZWxpbmUiLCJGT1JXQVJEUyIsIlJvb21TdGF0ZUV2ZW50IiwiRXZlbnRzIiwib25Sb29tRXZlbnQiLCJjb21wb25lbnRXaWxsVW5tb3VudCIsIm9mZiIsInNob3dSZXBseUluVGhyZWFkQWN0aW9uIiwiaW5Ob3RUaHJlYWRUaW1lbGluZSIsIlRpbWVsaW5lUmVuZGVyaW5nVHlwZSIsImlzQWxsb3dlZE1lc3NhZ2VUeXBlIiwiZm9yYmlkZGVuVGhyZWFkSGVhZE1zZ1R5cGUiLCJpbmNsdWRlcyIsImdldENvbnRlbnQiLCJtc2d0eXBlIiwiTV9CRUFDT05fSU5GTyIsIm1hdGNoZXMiLCJWb2ljZUJyb2FkY2FzdEluZm9FdmVudFR5cGUiLCJmbiIsImNoZWNrRm4iLCJyZXBsYWNpbmdFdmVudCIsInJlZGFjdEV2ZW50IiwibG9jYWxSZWRhY3Rpb25FdmVudCIsInRyeU9yZGVyIiwicmVuZGVyIiwidG9vbGJhck9wdHMiLCJjYW5FZGl0Q29udGVudCIsIm9uRWRpdENsaWNrIiwia2V5IiwiY2FuUGluIiwiY2FuVW5waW4iLCJvblBpbkNsaWNrIiwiY2FuY2VsU2VuZGluZ0J1dHRvbiIsIm9uQ2FuY2VsQ2xpY2siLCJ0aHJlYWRUb29sdGlwQnV0dG9uIiwiZWRpdFN0YXR1cyIsInJlZGFjdFN0YXR1cyIsImFsbG93Q2FuY2VsIiwiaXNGYWlsZWQiLCJOT1RfU0VOVCIsInNwbGljZSIsIm9uUmVzZW5kQ2xpY2siLCJpc0NvbnRlbnRBY3Rpb25hYmxlIiwiY2FuU2VuZE1lc3NhZ2VzIiwib25SZXBseUNsaWNrIiwiY2FuUmVhY3QiLCJzZWFyY2giLCJNZWRpYUV2ZW50SGVscGVyIiwiaXNFbGlnaWJsZSIsIm1lZGlhRXZlbnRIZWxwZXJHZXQiLCJnZXRNZWRpYUhlbHBlciIsIlJvb20iLCJ1bnNoaWZ0IiwiaXNRdW90ZUV4cGFuZGVkIiwic2hvdWxkRGlzcGxheVJlcGx5IiwiZXhwYW5kQ2xhc3NOYW1lIiwiY2xhc3NOYW1lcyIsIm14X01lc3NhZ2VBY3Rpb25CYXJfaWNvbkJ1dHRvbiIsIm14X01lc3NhZ2VBY3Rpb25CYXJfZXhwYW5kQ29sbGFwc2VNZXNzYWdlQnV0dG9uIiwiY2FwdGlvbiIsIkFMVEVSTkFURV9LRVlfTkFNRSIsIktleSIsIlNISUZUIiwidG9nZ2xlVGhyZWFkRXhwYW5kZWQiLCJleHBvcnRzIiwiUm9vbUNvbnRleHQiXSwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvY29tcG9uZW50cy92aWV3cy9tZXNzYWdlcy9NZXNzYWdlQWN0aW9uQmFyLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAxOS0yMDIzIFRoZSBNYXRyaXgub3JnIEZvdW5kYXRpb24gQy5JLkMuXG5Db3B5cmlnaHQgMjAxOSBOZXcgVmVjdG9yIEx0ZFxuQ29weXJpZ2h0IDIwMTkgTWljaGFlbCBUZWxhdHluc2tpIDw3dDNjaGd1eUBnbWFpbC5jb20+XG5cblNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBR1BMLTMuMC1vbmx5IE9SIEdQTC0zLjAtb25seVxuUGxlYXNlIHNlZSBMSUNFTlNFIGZpbGVzIGluIHRoZSByZXBvc2l0b3J5IHJvb3QgZm9yIGZ1bGwgZGV0YWlscy5cbiovXG5cbmltcG9ydCBSZWFjdCwgeyBSZWFjdEVsZW1lbnQsIHVzZUNhbGxiYWNrLCB1c2VDb250ZXh0LCB1c2VFZmZlY3QgfSBmcm9tIFwicmVhY3RcIjtcbmltcG9ydCB7XG4gICAgRXZlbnRTdGF0dXMsXG4gICAgTWF0cml4RXZlbnQsXG4gICAgTWF0cml4RXZlbnRFdmVudCxcbiAgICBNc2dUeXBlLFxuICAgIFJlbGF0aW9uVHlwZSxcbiAgICBNX0JFQUNPTl9JTkZPLFxuICAgIEV2ZW50VGltZWxpbmUsXG4gICAgUm9vbVN0YXRlRXZlbnQsXG4gICAgRXZlbnRUeXBlLFxufSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbWF0cml4XCI7XG5pbXBvcnQgY2xhc3NOYW1lcyBmcm9tIFwiY2xhc3NuYW1lc1wiO1xuaW1wb3J0IFBpbkljb24gZnJvbSBcIkB2ZWN0b3ItaW0vY29tcG91bmQtZGVzaWduLXRva2Vucy9hc3NldHMvd2ViL2ljb25zL3BpblwiO1xuaW1wb3J0IFVucGluSWNvbiBmcm9tIFwiQHZlY3Rvci1pbS9jb21wb3VuZC1kZXNpZ24tdG9rZW5zL2Fzc2V0cy93ZWIvaWNvbnMvdW5waW5cIjtcbmltcG9ydCBDb250ZXh0TWVudUljb24gZnJvbSBcIkB2ZWN0b3ItaW0vY29tcG91bmQtZGVzaWduLXRva2Vucy9hc3NldHMvd2ViL2ljb25zL292ZXJmbG93LWhvcml6b250YWxcIjtcblxuaW1wb3J0IHsgSWNvbiBhcyBFZGl0SWNvbiB9IGZyb20gXCIuLi8uLi8uLi8uLi9yZXMvaW1nL2VsZW1lbnQtaWNvbnMvcm9vbS9tZXNzYWdlLWJhci9lZGl0LnN2Z1wiO1xuaW1wb3J0IHsgSWNvbiBhcyBFbW9qaUljb24gfSBmcm9tIFwiLi4vLi4vLi4vLi4vcmVzL2ltZy9lbGVtZW50LWljb25zL3Jvb20vbWVzc2FnZS1iYXIvZW1vamkuc3ZnXCI7XG5pbXBvcnQgeyBJY29uIGFzIFJlc2VuZEljb24gfSBmcm9tIFwiLi4vLi4vLi4vLi4vcmVzL2ltZy9lbGVtZW50LWljb25zL3JldHJ5LnN2Z1wiO1xuaW1wb3J0IHsgSWNvbiBhcyBUaHJlYWRJY29uIH0gZnJvbSBcIi4uLy4uLy4uLy4uL3Jlcy9pbWcvZWxlbWVudC1pY29ucy9tZXNzYWdlL3RocmVhZC5zdmdcIjtcbmltcG9ydCB7IEljb24gYXMgVHJhc2hjYW5JY29uIH0gZnJvbSBcIi4uLy4uLy4uLy4uL3Jlcy9pbWcvZWxlbWVudC1pY29ucy90cmFzaGNhbi5zdmdcIjtcbmltcG9ydCB7IEljb24gYXMgUmVwbHlJY29uIH0gZnJvbSBcIi4uLy4uLy4uLy4uL3Jlcy9pbWcvZWxlbWVudC1pY29ucy9yb29tL21lc3NhZ2UtYmFyL3JlcGx5LnN2Z1wiO1xuaW1wb3J0IHsgSWNvbiBhcyBFeHBhbmRNZXNzYWdlSWNvbiB9IGZyb20gXCIuLi8uLi8uLi8uLi9yZXMvaW1nL2VsZW1lbnQtaWNvbnMvZXhwYW5kLW1lc3NhZ2Uuc3ZnXCI7XG5pbXBvcnQgeyBJY29uIGFzIENvbGxhcHNlTWVzc2FnZUljb24gfSBmcm9tIFwiLi4vLi4vLi4vLi4vcmVzL2ltZy9lbGVtZW50LWljb25zL2NvbGxhcHNlLW1lc3NhZ2Uuc3ZnXCI7XG5pbXBvcnQgdHlwZSB7IFJlbGF0aW9ucyB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9tYXRyaXhcIjtcbmltcG9ydCB7IF90IH0gZnJvbSBcIi4uLy4uLy4uL2xhbmd1YWdlSGFuZGxlclwiO1xuaW1wb3J0IGRpcywgeyBkZWZhdWx0RGlzcGF0Y2hlciB9IGZyb20gXCIuLi8uLi8uLi9kaXNwYXRjaGVyL2Rpc3BhdGNoZXJcIjtcbmltcG9ydCBDb250ZXh0TWVudSwgeyBhYm92ZUxlZnRPZiwgQ29udGV4dE1lbnVUb29sdGlwQnV0dG9uLCB1c2VDb250ZXh0TWVudSB9IGZyb20gXCIuLi8uLi9zdHJ1Y3R1cmVzL0NvbnRleHRNZW51XCI7XG5pbXBvcnQgeyBpc0NvbnRlbnRBY3Rpb25hYmxlLCBjYW5FZGl0Q29udGVudCwgZWRpdEV2ZW50LCBjYW5DYW5jZWwgfSBmcm9tIFwiLi4vLi4vLi4vdXRpbHMvRXZlbnRVdGlsc1wiO1xuaW1wb3J0IFJvb21Db250ZXh0LCB7IFRpbWVsaW5lUmVuZGVyaW5nVHlwZSB9IGZyb20gXCIuLi8uLi8uLi9jb250ZXh0cy9Sb29tQ29udGV4dFwiO1xuaW1wb3J0IFRvb2xiYXIgZnJvbSBcIi4uLy4uLy4uL2FjY2Vzc2liaWxpdHkvVG9vbGJhclwiO1xuaW1wb3J0IHsgUm92aW5nQWNjZXNzaWJsZUJ1dHRvbiwgdXNlUm92aW5nVGFiSW5kZXggfSBmcm9tIFwiLi4vLi4vLi4vYWNjZXNzaWJpbGl0eS9Sb3ZpbmdUYWJJbmRleFwiO1xuaW1wb3J0IE1lc3NhZ2VDb250ZXh0TWVudSBmcm9tIFwiLi4vY29udGV4dF9tZW51cy9NZXNzYWdlQ29udGV4dE1lbnVcIjtcbmltcG9ydCBSZXNlbmQgZnJvbSBcIi4uLy4uLy4uL1Jlc2VuZFwiO1xuaW1wb3J0IHsgTWF0cml4Q2xpZW50UGVnIH0gZnJvbSBcIi4uLy4uLy4uL01hdHJpeENsaWVudFBlZ1wiO1xuaW1wb3J0IHsgTWVkaWFFdmVudEhlbHBlciB9IGZyb20gXCIuLi8uLi8uLi91dGlscy9NZWRpYUV2ZW50SGVscGVyXCI7XG5pbXBvcnQgRG93bmxvYWRBY3Rpb25CdXR0b24gZnJvbSBcIi4vRG93bmxvYWRBY3Rpb25CdXR0b25cIjtcbmltcG9ydCB7IFJvb21QZXJtYWxpbmtDcmVhdG9yIH0gZnJvbSBcIi4uLy4uLy4uL3V0aWxzL3Blcm1hbGlua3MvUGVybWFsaW5rc1wiO1xuaW1wb3J0IFJlcGx5Q2hhaW4gZnJvbSBcIi4uL2VsZW1lbnRzL1JlcGx5Q2hhaW5cIjtcbmltcG9ydCBSZWFjdGlvblBpY2tlciBmcm9tIFwiLi4vZW1vamlwaWNrZXIvUmVhY3Rpb25QaWNrZXJcIjtcbmltcG9ydCB7IENhcmRDb250ZXh0IH0gZnJvbSBcIi4uL3JpZ2h0X3BhbmVsL2NvbnRleHRcIjtcbmltcG9ydCB7IHNob3VsZERpc3BsYXlSZXBseSB9IGZyb20gXCIuLi8uLi8uLi91dGlscy9SZXBseVwiO1xuaW1wb3J0IHsgS2V5IH0gZnJvbSBcIi4uLy4uLy4uL0tleWJvYXJkXCI7XG5pbXBvcnQgeyBBTFRFUk5BVEVfS0VZX05BTUUgfSBmcm9tIFwiLi4vLi4vLi4vYWNjZXNzaWJpbGl0eS9LZXlib2FyZFNob3J0Y3V0c1wiO1xuaW1wb3J0IHsgQWN0aW9uIH0gZnJvbSBcIi4uLy4uLy4uL2Rpc3BhdGNoZXIvYWN0aW9uc1wiO1xuaW1wb3J0IHsgU2hvd1RocmVhZFBheWxvYWQgfSBmcm9tIFwiLi4vLi4vLi4vZGlzcGF0Y2hlci9wYXlsb2Fkcy9TaG93VGhyZWFkUGF5bG9hZFwiO1xuaW1wb3J0IHsgR2V0UmVsYXRpb25zRm9yRXZlbnQsIElFdmVudFRpbGVUeXBlIH0gZnJvbSBcIi4uL3Jvb21zL0V2ZW50VGlsZVwiO1xuaW1wb3J0IHsgVm9pY2VCcm9hZGNhc3RJbmZvRXZlbnRUeXBlIH0gZnJvbSBcIi4uLy4uLy4uL3ZvaWNlLWJyb2FkY2FzdC90eXBlc1wiO1xuaW1wb3J0IHsgQnV0dG9uRXZlbnQgfSBmcm9tIFwiLi4vZWxlbWVudHMvQWNjZXNzaWJsZUJ1dHRvblwiO1xuaW1wb3J0IFBpbm5pbmdVdGlscyBmcm9tIFwiLi4vLi4vLi4vdXRpbHMvUGlubmluZ1V0aWxzXCI7XG5pbXBvcnQgUG9zdGhvZ1RyYWNrZXJzIGZyb20gXCIuLi8uLi8uLi9Qb3N0aG9nVHJhY2tlcnMudHNcIjtcblxuaW50ZXJmYWNlIElPcHRpb25zQnV0dG9uUHJvcHMge1xuICAgIG14RXZlbnQ6IE1hdHJpeEV2ZW50O1xuICAgIGdldFRpbGU6ICgpID0+IElFdmVudFRpbGVUeXBlIHwgbnVsbDtcbiAgICBnZXRSZXBseUNoYWluOiAoKSA9PiBSZXBseUNoYWluIHwgbnVsbDtcbiAgICBwZXJtYWxpbmtDcmVhdG9yPzogUm9vbVBlcm1hbGlua0NyZWF0b3I7XG4gICAgb25Gb2N1c0NoYW5nZTogKG1lbnVEaXNwbGF5ZWQ6IGJvb2xlYW4pID0+IHZvaWQ7XG4gICAgZ2V0UmVsYXRpb25zRm9yRXZlbnQ/OiBHZXRSZWxhdGlvbnNGb3JFdmVudDtcbn1cblxuY29uc3QgT3B0aW9uc0J1dHRvbjogUmVhY3QuRkM8SU9wdGlvbnNCdXR0b25Qcm9wcz4gPSAoe1xuICAgIG14RXZlbnQsXG4gICAgZ2V0VGlsZSxcbiAgICBnZXRSZXBseUNoYWluLFxuICAgIHBlcm1hbGlua0NyZWF0b3IsXG4gICAgb25Gb2N1c0NoYW5nZSxcbiAgICBnZXRSZWxhdGlvbnNGb3JFdmVudCxcbn0pID0+IHtcbiAgICBjb25zdCBbbWVudURpc3BsYXllZCwgYnV0dG9uLCBvcGVuTWVudSwgY2xvc2VNZW51XSA9IHVzZUNvbnRleHRNZW51KCk7XG4gICAgY29uc3QgW29uRm9jdXMsIGlzQWN0aXZlXSA9IHVzZVJvdmluZ1RhYkluZGV4KGJ1dHRvbik7XG4gICAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICAgICAgb25Gb2N1c0NoYW5nZShtZW51RGlzcGxheWVkKTtcbiAgICB9LCBbb25Gb2N1c0NoYW5nZSwgbWVudURpc3BsYXllZF0pO1xuXG4gICAgY29uc3Qgb25PcHRpb25zQ2xpY2sgPSB1c2VDYWxsYmFjayhcbiAgICAgICAgKGU6IEJ1dHRvbkV2ZW50KTogdm9pZCA9PiB7XG4gICAgICAgICAgICAvLyBEb24ndCBvcGVuIHRoZSByZWd1bGFyIGJyb3dzZXIgb3Igb3VyIGNvbnRleHQgbWVudSBvbiByaWdodC1jbGlja1xuICAgICAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgZS5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgICAgIG9wZW5NZW51KCk7XG4gICAgICAgICAgICAvLyB3aGVuIHRoZSBjb250ZXh0IG1lbnUgaXMgb3BlbmVkIGRpcmVjdGx5LCBlLmcuIHZpYSBtb3VzZSBjbGljaywgdGhlIG9uRm9jdXMgaGFuZGxlciB3aGljaCB0cmFja3NcbiAgICAgICAgICAgIC8vIHRoZSBlbGVtZW50IHRoYXQgaXMgY3VycmVudGx5IGZvY3VzZWQgaXMgc2tpcHBlZC4gU28gd2Ugd2FudCB0byBjYWxsIG9uRm9jdXMgbWFudWFsbHkgdG8ga2VlcCB0aGVcbiAgICAgICAgICAgIC8vIHBvc2l0aW9uIGluIHRoZSBwYWdlIGV2ZW4gd2hlbiBzb21lb25lIGlzIGNsaWNraW5nIGFyb3VuZC5cbiAgICAgICAgICAgIG9uRm9jdXMoKTtcbiAgICAgICAgfSxcbiAgICAgICAgW29wZW5NZW51LCBvbkZvY3VzXSxcbiAgICApO1xuXG4gICAgbGV0IGNvbnRleHRNZW51OiBSZWFjdEVsZW1lbnQgfCB1bmRlZmluZWQ7XG4gICAgaWYgKG1lbnVEaXNwbGF5ZWQgJiYgYnV0dG9uLmN1cnJlbnQpIHtcbiAgICAgICAgY29uc3QgdGlsZSA9IGdldFRpbGU/LigpO1xuICAgICAgICBjb25zdCByZXBseUNoYWluID0gZ2V0UmVwbHlDaGFpbigpO1xuXG4gICAgICAgIGNvbnN0IGJ1dHRvblJlY3QgPSBidXR0b24uY3VycmVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgY29udGV4dE1lbnUgPSAoXG4gICAgICAgICAgICA8TWVzc2FnZUNvbnRleHRNZW51XG4gICAgICAgICAgICAgICAgey4uLmFib3ZlTGVmdE9mKGJ1dHRvblJlY3QpfVxuICAgICAgICAgICAgICAgIG14RXZlbnQ9e214RXZlbnR9XG4gICAgICAgICAgICAgICAgcGVybWFsaW5rQ3JlYXRvcj17cGVybWFsaW5rQ3JlYXRvcn1cbiAgICAgICAgICAgICAgICBldmVudFRpbGVPcHM9e3RpbGUgJiYgdGlsZS5nZXRFdmVudFRpbGVPcHMgPyB0aWxlLmdldEV2ZW50VGlsZU9wcygpIDogdW5kZWZpbmVkfVxuICAgICAgICAgICAgICAgIGNvbGxhcHNlUmVwbHlDaGFpbj17cmVwbHlDaGFpbj8uY2FuQ29sbGFwc2UoKSA/IHJlcGx5Q2hhaW4uY29sbGFwc2UgOiB1bmRlZmluZWR9XG4gICAgICAgICAgICAgICAgb25GaW5pc2hlZD17Y2xvc2VNZW51fVxuICAgICAgICAgICAgICAgIGdldFJlbGF0aW9uc0ZvckV2ZW50PXtnZXRSZWxhdGlvbnNGb3JFdmVudH1cbiAgICAgICAgICAgIC8+XG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIChcbiAgICAgICAgPFJlYWN0LkZyYWdtZW50PlxuICAgICAgICAgICAgPENvbnRleHRNZW51VG9vbHRpcEJ1dHRvblxuICAgICAgICAgICAgICAgIGNsYXNzTmFtZT1cIm14X01lc3NhZ2VBY3Rpb25CYXJfaWNvbkJ1dHRvbiBteF9NZXNzYWdlQWN0aW9uQmFyX29wdGlvbnNCdXR0b25cIlxuICAgICAgICAgICAgICAgIHRpdGxlPXtfdChcImNvbW1vbnxvcHRpb25zXCIpfVxuICAgICAgICAgICAgICAgIG9uQ2xpY2s9e29uT3B0aW9uc0NsaWNrfVxuICAgICAgICAgICAgICAgIG9uQ29udGV4dE1lbnU9e29uT3B0aW9uc0NsaWNrfVxuICAgICAgICAgICAgICAgIGlzRXhwYW5kZWQ9e21lbnVEaXNwbGF5ZWR9XG4gICAgICAgICAgICAgICAgcmVmPXtidXR0b259XG4gICAgICAgICAgICAgICAgb25Gb2N1cz17b25Gb2N1c31cbiAgICAgICAgICAgICAgICB0YWJJbmRleD17aXNBY3RpdmUgPyAwIDogLTF9XG4gICAgICAgICAgICAgICAgcGxhY2VtZW50PVwibGVmdFwiXG4gICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgPENvbnRleHRNZW51SWNvbiAvPlxuICAgICAgICAgICAgPC9Db250ZXh0TWVudVRvb2x0aXBCdXR0b24+XG4gICAgICAgICAgICB7Y29udGV4dE1lbnV9XG4gICAgICAgIDwvUmVhY3QuRnJhZ21lbnQ+XG4gICAgKTtcbn07XG5cbmludGVyZmFjZSBJUmVhY3RCdXR0b25Qcm9wcyB7XG4gICAgbXhFdmVudDogTWF0cml4RXZlbnQ7XG4gICAgcmVhY3Rpb25zPzogUmVsYXRpb25zIHwgbnVsbCB8IHVuZGVmaW5lZDtcbiAgICBvbkZvY3VzQ2hhbmdlOiAobWVudURpc3BsYXllZDogYm9vbGVhbikgPT4gdm9pZDtcbn1cblxuY29uc3QgUmVhY3RCdXR0b246IFJlYWN0LkZDPElSZWFjdEJ1dHRvblByb3BzPiA9ICh7IG14RXZlbnQsIHJlYWN0aW9ucywgb25Gb2N1c0NoYW5nZSB9KSA9PiB7XG4gICAgY29uc3QgW21lbnVEaXNwbGF5ZWQsIGJ1dHRvbiwgb3Blbk1lbnUsIGNsb3NlTWVudV0gPSB1c2VDb250ZXh0TWVudSgpO1xuICAgIGNvbnN0IFtvbkZvY3VzLCBpc0FjdGl2ZV0gPSB1c2VSb3ZpbmdUYWJJbmRleChidXR0b24pO1xuICAgIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgICAgIG9uRm9jdXNDaGFuZ2UobWVudURpc3BsYXllZCk7XG4gICAgfSwgW29uRm9jdXNDaGFuZ2UsIG1lbnVEaXNwbGF5ZWRdKTtcblxuICAgIGxldCBjb250ZXh0TWVudTogSlNYLkVsZW1lbnQgfCB1bmRlZmluZWQ7XG4gICAgaWYgKG1lbnVEaXNwbGF5ZWQgJiYgYnV0dG9uLmN1cnJlbnQpIHtcbiAgICAgICAgY29uc3QgYnV0dG9uUmVjdCA9IGJ1dHRvbi5jdXJyZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBjb250ZXh0TWVudSA9IChcbiAgICAgICAgICAgIDxDb250ZXh0TWVudSB7Li4uYWJvdmVMZWZ0T2YoYnV0dG9uUmVjdCl9IG9uRmluaXNoZWQ9e2Nsb3NlTWVudX0gbWFuYWdlZD17ZmFsc2V9PlxuICAgICAgICAgICAgICAgIDxSZWFjdGlvblBpY2tlciBteEV2ZW50PXtteEV2ZW50fSByZWFjdGlvbnM9e3JlYWN0aW9uc30gb25GaW5pc2hlZD17Y2xvc2VNZW51fSAvPlxuICAgICAgICAgICAgPC9Db250ZXh0TWVudT5cbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBjb25zdCBvbkNsaWNrID0gdXNlQ2FsbGJhY2soXG4gICAgICAgIChlOiBCdXR0b25FdmVudCkgPT4ge1xuICAgICAgICAgICAgLy8gRG9uJ3Qgb3BlbiB0aGUgcmVndWxhciBicm93c2VyIG9yIG91ciBjb250ZXh0IG1lbnUgb24gcmlnaHQtY2xpY2tcbiAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIGUuc3RvcFByb3BhZ2F0aW9uKCk7XG5cbiAgICAgICAgICAgIG9wZW5NZW51KCk7XG4gICAgICAgICAgICAvLyB3aGVuIHRoZSBjb250ZXh0IG1lbnUgaXMgb3BlbmVkIGRpcmVjdGx5LCBlLmcuIHZpYSBtb3VzZSBjbGljaywgdGhlIG9uRm9jdXMgaGFuZGxlciB3aGljaCB0cmFja3NcbiAgICAgICAgICAgIC8vIHRoZSBlbGVtZW50IHRoYXQgaXMgY3VycmVudGx5IGZvY3VzZWQgaXMgc2tpcHBlZC4gU28gd2Ugd2FudCB0byBjYWxsIG9uRm9jdXMgbWFudWFsbHkgdG8ga2VlcCB0aGVcbiAgICAgICAgICAgIC8vIHBvc2l0aW9uIGluIHRoZSBwYWdlIGV2ZW4gd2hlbiBzb21lb25lIGlzIGNsaWNraW5nIGFyb3VuZC5cbiAgICAgICAgICAgIG9uRm9jdXMoKTtcbiAgICAgICAgfSxcbiAgICAgICAgW29wZW5NZW51LCBvbkZvY3VzXSxcbiAgICApO1xuXG4gICAgcmV0dXJuIChcbiAgICAgICAgPFJlYWN0LkZyYWdtZW50PlxuICAgICAgICAgICAgPENvbnRleHRNZW51VG9vbHRpcEJ1dHRvblxuICAgICAgICAgICAgICAgIGNsYXNzTmFtZT1cIm14X01lc3NhZ2VBY3Rpb25CYXJfaWNvbkJ1dHRvblwiXG4gICAgICAgICAgICAgICAgdGl0bGU9e190KFwiYWN0aW9ufHJlYWN0XCIpfVxuICAgICAgICAgICAgICAgIG9uQ2xpY2s9e29uQ2xpY2t9XG4gICAgICAgICAgICAgICAgb25Db250ZXh0TWVudT17b25DbGlja31cbiAgICAgICAgICAgICAgICBpc0V4cGFuZGVkPXttZW51RGlzcGxheWVkfVxuICAgICAgICAgICAgICAgIHJlZj17YnV0dG9ufVxuICAgICAgICAgICAgICAgIG9uRm9jdXM9e29uRm9jdXN9XG4gICAgICAgICAgICAgICAgdGFiSW5kZXg9e2lzQWN0aXZlID8gMCA6IC0xfVxuICAgICAgICAgICAgICAgIHBsYWNlbWVudD1cImxlZnRcIlxuICAgICAgICAgICAgPlxuICAgICAgICAgICAgICAgIDxFbW9qaUljb24gLz5cbiAgICAgICAgICAgIDwvQ29udGV4dE1lbnVUb29sdGlwQnV0dG9uPlxuXG4gICAgICAgICAgICB7Y29udGV4dE1lbnV9XG4gICAgICAgIDwvUmVhY3QuRnJhZ21lbnQ+XG4gICAgKTtcbn07XG5cbmludGVyZmFjZSBJUmVwbHlJblRocmVhZEJ1dHRvbiB7XG4gICAgbXhFdmVudDogTWF0cml4RXZlbnQ7XG59XG5cbmNvbnN0IFJlcGx5SW5UaHJlYWRCdXR0b246IFJlYWN0LkZDPElSZXBseUluVGhyZWFkQnV0dG9uPiA9ICh7IG14RXZlbnQgfSkgPT4ge1xuICAgIGNvbnN0IGNvbnRleHQgPSB1c2VDb250ZXh0KENhcmRDb250ZXh0KTtcblxuICAgIGNvbnN0IHJlbGF0aW9uVHlwZSA9IG14RXZlbnQ/LmdldFJlbGF0aW9uKCk/LnJlbF90eXBlO1xuICAgIGNvbnN0IGhhc0FSZWxhdGlvbiA9ICEhcmVsYXRpb25UeXBlICYmIHJlbGF0aW9uVHlwZSAhPT0gUmVsYXRpb25UeXBlLlRocmVhZDtcblxuICAgIGNvbnN0IG9uQ2xpY2sgPSAoZTogQnV0dG9uRXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgLy8gRG9uJ3Qgb3BlbiB0aGUgcmVndWxhciBicm93c2VyIG9yIG91ciBjb250ZXh0IG1lbnUgb24gcmlnaHQtY2xpY2tcbiAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICBlLnN0b3BQcm9wYWdhdGlvbigpO1xuXG4gICAgICAgIGNvbnN0IHRocmVhZCA9IG14RXZlbnQuZ2V0VGhyZWFkKCk7XG4gICAgICAgIGlmICh0aHJlYWQ/LnJvb3RFdmVudCAmJiAhbXhFdmVudC5pc1RocmVhZFJvb3QpIHtcbiAgICAgICAgICAgIGRlZmF1bHREaXNwYXRjaGVyLmRpc3BhdGNoPFNob3dUaHJlYWRQYXlsb2FkPih7XG4gICAgICAgICAgICAgICAgYWN0aW9uOiBBY3Rpb24uU2hvd1RocmVhZCxcbiAgICAgICAgICAgICAgICByb290RXZlbnQ6IHRocmVhZC5yb290RXZlbnQsXG4gICAgICAgICAgICAgICAgaW5pdGlhbEV2ZW50OiBteEV2ZW50LFxuICAgICAgICAgICAgICAgIHNjcm9sbF9pbnRvX3ZpZXc6IHRydWUsXG4gICAgICAgICAgICAgICAgaGlnaGxpZ2h0ZWQ6IHRydWUsXG4gICAgICAgICAgICAgICAgcHVzaDogY29udGV4dC5pc0NhcmQsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGRlZmF1bHREaXNwYXRjaGVyLmRpc3BhdGNoPFNob3dUaHJlYWRQYXlsb2FkPih7XG4gICAgICAgICAgICAgICAgYWN0aW9uOiBBY3Rpb24uU2hvd1RocmVhZCxcbiAgICAgICAgICAgICAgICByb290RXZlbnQ6IG14RXZlbnQsXG4gICAgICAgICAgICAgICAgcHVzaDogY29udGV4dC5pc0NhcmQsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBjb25zdCB0aXRsZSA9ICFoYXNBUmVsYXRpb24gPyBfdChcImFjdGlvbnxyZXBseV9pbl90aHJlYWRcIikgOiBfdChcInRocmVhZHN8ZXJyb3Jfc3RhcnRfdGhyZWFkX2V4aXN0aW5nX3JlbGF0aW9uXCIpO1xuXG4gICAgcmV0dXJuIChcbiAgICAgICAgPFJvdmluZ0FjY2Vzc2libGVCdXR0b25cbiAgICAgICAgICAgIGNsYXNzTmFtZT1cIm14X01lc3NhZ2VBY3Rpb25CYXJfaWNvbkJ1dHRvbiBteF9NZXNzYWdlQWN0aW9uQmFyX3RocmVhZEJ1dHRvblwiXG4gICAgICAgICAgICBkaXNhYmxlZD17aGFzQVJlbGF0aW9ufVxuICAgICAgICAgICAgdGl0bGU9e3RpdGxlfVxuICAgICAgICAgICAgb25DbGljaz17b25DbGlja31cbiAgICAgICAgICAgIG9uQ29udGV4dE1lbnU9e29uQ2xpY2t9XG4gICAgICAgICAgICBwbGFjZW1lbnQ9XCJsZWZ0XCJcbiAgICAgICAgPlxuICAgICAgICAgICAgPFRocmVhZEljb24gLz5cbiAgICAgICAgPC9Sb3ZpbmdBY2Nlc3NpYmxlQnV0dG9uPlxuICAgICk7XG59O1xuXG5pbnRlcmZhY2UgSU1lc3NhZ2VBY3Rpb25CYXJQcm9wcyB7XG4gICAgbXhFdmVudDogTWF0cml4RXZlbnQ7XG4gICAgcmVhY3Rpb25zPzogUmVsYXRpb25zIHwgbnVsbCB8IHVuZGVmaW5lZDtcbiAgICBnZXRUaWxlOiAoKSA9PiBJRXZlbnRUaWxlVHlwZSB8IG51bGw7XG4gICAgZ2V0UmVwbHlDaGFpbjogKCkgPT4gUmVwbHlDaGFpbiB8IG51bGw7XG4gICAgcGVybWFsaW5rQ3JlYXRvcj86IFJvb21QZXJtYWxpbmtDcmVhdG9yO1xuICAgIG9uRm9jdXNDaGFuZ2U/OiAobWVudURpc3BsYXllZDogYm9vbGVhbikgPT4gdm9pZDtcbiAgICB0b2dnbGVUaHJlYWRFeHBhbmRlZDogKCkgPT4gdm9pZDtcbiAgICBpc1F1b3RlRXhwYW5kZWQ/OiBib29sZWFuO1xuICAgIGdldFJlbGF0aW9uc0ZvckV2ZW50PzogR2V0UmVsYXRpb25zRm9yRXZlbnQ7XG59XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIE1lc3NhZ2VBY3Rpb25CYXIgZXh0ZW5kcyBSZWFjdC5QdXJlQ29tcG9uZW50PElNZXNzYWdlQWN0aW9uQmFyUHJvcHM+IHtcbiAgICBwdWJsaWMgc3RhdGljIGNvbnRleHRUeXBlID0gUm9vbUNvbnRleHQ7XG4gICAgcHVibGljIGRlY2xhcmUgY29udGV4dDogUmVhY3QuQ29udGV4dFR5cGU8dHlwZW9mIFJvb21Db250ZXh0PjtcblxuICAgIHB1YmxpYyBjb21wb25lbnREaWRNb3VudCgpOiB2b2lkIHtcbiAgICAgICAgaWYgKHRoaXMucHJvcHMubXhFdmVudC5zdGF0dXMgJiYgdGhpcy5wcm9wcy5teEV2ZW50LnN0YXR1cyAhPT0gRXZlbnRTdGF0dXMuU0VOVCkge1xuICAgICAgICAgICAgdGhpcy5wcm9wcy5teEV2ZW50Lm9uKE1hdHJpeEV2ZW50RXZlbnQuU3RhdHVzLCB0aGlzLm9uU2VudCk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBjbGllbnQgPSBNYXRyaXhDbGllbnRQZWcuc2FmZUdldCgpO1xuICAgICAgICBjbGllbnQuZGVjcnlwdEV2ZW50SWZOZWVkZWQodGhpcy5wcm9wcy5teEV2ZW50KTtcblxuICAgICAgICBpZiAodGhpcy5wcm9wcy5teEV2ZW50LmlzQmVpbmdEZWNyeXB0ZWQoKSkge1xuICAgICAgICAgICAgdGhpcy5wcm9wcy5teEV2ZW50Lm9uY2UoTWF0cml4RXZlbnRFdmVudC5EZWNyeXB0ZWQsIHRoaXMub25EZWNyeXB0ZWQpO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMucHJvcHMubXhFdmVudC5vbihNYXRyaXhFdmVudEV2ZW50LkJlZm9yZVJlZGFjdGlvbiwgdGhpcy5vbkJlZm9yZVJlZGFjdGlvbik7XG4gICAgICAgIHRoaXMuY29udGV4dC5yb29tXG4gICAgICAgICAgICA/LmdldExpdmVUaW1lbGluZSgpXG4gICAgICAgICAgICAuZ2V0U3RhdGUoRXZlbnRUaW1lbGluZS5GT1JXQVJEUylcbiAgICAgICAgICAgID8ub24oUm9vbVN0YXRlRXZlbnQuRXZlbnRzLCB0aGlzLm9uUm9vbUV2ZW50KTtcbiAgICB9XG5cbiAgICBwdWJsaWMgY29tcG9uZW50V2lsbFVubW91bnQoKTogdm9pZCB7XG4gICAgICAgIHRoaXMucHJvcHMubXhFdmVudC5vZmYoTWF0cml4RXZlbnRFdmVudC5TdGF0dXMsIHRoaXMub25TZW50KTtcbiAgICAgICAgdGhpcy5wcm9wcy5teEV2ZW50Lm9mZihNYXRyaXhFdmVudEV2ZW50LkRlY3J5cHRlZCwgdGhpcy5vbkRlY3J5cHRlZCk7XG4gICAgICAgIHRoaXMucHJvcHMubXhFdmVudC5vZmYoTWF0cml4RXZlbnRFdmVudC5CZWZvcmVSZWRhY3Rpb24sIHRoaXMub25CZWZvcmVSZWRhY3Rpb24pO1xuICAgICAgICB0aGlzLmNvbnRleHQucm9vbVxuICAgICAgICAgICAgPy5nZXRMaXZlVGltZWxpbmUoKVxuICAgICAgICAgICAgLmdldFN0YXRlKEV2ZW50VGltZWxpbmUuRk9SV0FSRFMpXG4gICAgICAgICAgICA/Lm9mZihSb29tU3RhdGVFdmVudC5FdmVudHMsIHRoaXMub25Sb29tRXZlbnQpO1xuICAgIH1cblxuICAgIHByaXZhdGUgb25EZWNyeXB0ZWQgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIC8vIFdoZW4gYW4gZXZlbnQgZGVjcnlwdHMsIGl0IGlzIGxpa2VseSB0byBjaGFuZ2UgdGhlIHNldCBvZiBhdmFpbGFibGVcbiAgICAgICAgLy8gYWN0aW9ucywgc28gd2UgZm9yY2UgYW4gdXBkYXRlIHRvIGNoZWNrIGFnYWluLlxuICAgICAgICB0aGlzLmZvcmNlVXBkYXRlKCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25CZWZvcmVSZWRhY3Rpb24gPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIC8vIFdoZW4gYW4gZXZlbnQgaXMgcmVkYWN0ZWQsIHdlIGNhbid0IGVkaXQgaXQgc28gdXBkYXRlIHRoZSBhdmFpbGFibGUgYWN0aW9ucy5cbiAgICAgICAgdGhpcy5mb3JjZVVwZGF0ZSgpO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uUm9vbUV2ZW50ID0gKGV2ZW50PzogTWF0cml4RXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgLy8gSWYgdGhlIGV2ZW50IGlzIHBpbm5lZCBvciB1bnBpbm5lZCwgcmVyZW5kZXIgdGhlIGNvbXBvbmVudC5cbiAgICAgICAgaWYgKCFldmVudCB8fCBldmVudC5nZXRUeXBlKCkgIT09IEV2ZW50VHlwZS5Sb29tUGlubmVkRXZlbnRzKSByZXR1cm47XG4gICAgICAgIHRoaXMuZm9yY2VVcGRhdGUoKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvblNlbnQgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIC8vIFdoZW4gYW4gZXZlbnQgaXMgc2VudCBhbmQgZWNob2VkIHRoZSBwb3NzaWJsZSBhY3Rpb25zIGNoYW5nZS5cbiAgICAgICAgdGhpcy5mb3JjZVVwZGF0ZSgpO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uRm9jdXNDaGFuZ2UgPSAoZm9jdXNlZDogYm9vbGVhbik6IHZvaWQgPT4ge1xuICAgICAgICB0aGlzLnByb3BzLm9uRm9jdXNDaGFuZ2U/Lihmb2N1c2VkKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvblJlcGx5Q2xpY2sgPSAoZTogQnV0dG9uRXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgLy8gRG9uJ3Qgb3BlbiB0aGUgcmVndWxhciBicm93c2VyIG9yIG91ciBjb250ZXh0IG1lbnUgb24gcmlnaHQtY2xpY2tcbiAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICBlLnN0b3BQcm9wYWdhdGlvbigpO1xuXG4gICAgICAgIGRpcy5kaXNwYXRjaCh7XG4gICAgICAgICAgICBhY3Rpb246IFwicmVwbHlfdG9fZXZlbnRcIixcbiAgICAgICAgICAgIGV2ZW50OiB0aGlzLnByb3BzLm14RXZlbnQsXG4gICAgICAgICAgICBjb250ZXh0OiB0aGlzLmNvbnRleHQudGltZWxpbmVSZW5kZXJpbmdUeXBlLFxuICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkVkaXRDbGljayA9IChlOiBCdXR0b25FdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICAvLyBEb24ndCBvcGVuIHRoZSByZWd1bGFyIGJyb3dzZXIgb3Igb3VyIGNvbnRleHQgbWVudSBvbiByaWdodC1jbGlja1xuICAgICAgICBlLnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGUuc3RvcFByb3BhZ2F0aW9uKCk7XG5cbiAgICAgICAgZWRpdEV2ZW50KFxuICAgICAgICAgICAgTWF0cml4Q2xpZW50UGVnLnNhZmVHZXQoKSxcbiAgICAgICAgICAgIHRoaXMucHJvcHMubXhFdmVudCxcbiAgICAgICAgICAgIHRoaXMuY29udGV4dC50aW1lbGluZVJlbmRlcmluZ1R5cGUsXG4gICAgICAgICAgICB0aGlzLnByb3BzLmdldFJlbGF0aW9uc0ZvckV2ZW50LFxuICAgICAgICApO1xuICAgIH07XG5cbiAgICBwcml2YXRlIHJlYWRvbmx5IGZvcmJpZGRlblRocmVhZEhlYWRNc2dUeXBlID0gW01zZ1R5cGUuS2V5VmVyaWZpY2F0aW9uUmVxdWVzdF07XG5cbiAgICBwcml2YXRlIGdldCBzaG93UmVwbHlJblRocmVhZEFjdGlvbigpOiBib29sZWFuIHtcbiAgICAgICAgY29uc3QgaW5Ob3RUaHJlYWRUaW1lbGluZSA9IHRoaXMuY29udGV4dC50aW1lbGluZVJlbmRlcmluZ1R5cGUgIT09IFRpbWVsaW5lUmVuZGVyaW5nVHlwZS5UaHJlYWQ7XG5cbiAgICAgICAgY29uc3QgaXNBbGxvd2VkTWVzc2FnZVR5cGUgPVxuICAgICAgICAgICAgIXRoaXMuZm9yYmlkZGVuVGhyZWFkSGVhZE1zZ1R5cGUuaW5jbHVkZXModGhpcy5wcm9wcy5teEV2ZW50LmdldENvbnRlbnQoKS5tc2d0eXBlIGFzIE1zZ1R5cGUpICYmXG4gICAgICAgICAgICAvKiogZm9yYmlkIHRocmVhZHMgZnJvbSBsaXZlIGxvY2F0aW9uIHNoYXJlc1xuICAgICAgICAgICAgICogdW50aWwgY3Jvc3MtcGxhdGZvcm0gc3VwcG9ydFxuICAgICAgICAgICAgICogKFBTRi0xMDQxKVxuICAgICAgICAgICAgICovXG4gICAgICAgICAgICAhTV9CRUFDT05fSU5GTy5tYXRjaGVzKHRoaXMucHJvcHMubXhFdmVudC5nZXRUeXBlKCkpICYmXG4gICAgICAgICAgICAhKHRoaXMucHJvcHMubXhFdmVudC5nZXRUeXBlKCkgPT09IFZvaWNlQnJvYWRjYXN0SW5mb0V2ZW50VHlwZSk7XG5cbiAgICAgICAgcmV0dXJuIGluTm90VGhyZWFkVGltZWxpbmUgJiYgaXNBbGxvd2VkTWVzc2FnZVR5cGU7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUnVucyBhIGdpdmVuIGZuIG9uIHRoZSBzZXQgb2YgcG9zc2libGUgZXZlbnRzIHRvIHRlc3QuIFRoZSBmaXJzdCBldmVudFxuICAgICAqIHRoYXQgcGFzc2VzIHRoZSBjaGVja0ZuIHdpbGwgaGF2ZSBmbiBleGVjdXRlZCBvbiBpdC4gQm90aCBmdW5jdGlvbnMgdGFrZVxuICAgICAqIGEgTWF0cml4RXZlbnQgb2JqZWN0LiBJZiBubyBwYXJ0aWN1bGFyIGNvbmRpdGlvbnMgYXJlIG5lZWRlZCwgY2hlY2tGbiBjYW5cbiAgICAgKiBiZSBudWxsL3VuZGVmaW5lZC4gSWYgbm8gZnVuY3Rpb25zIHBhc3MgdGhlIGNoZWNrRm4sIG5vIGFjdGlvbiB3aWxsIGJlXG4gICAgICogdGFrZW4uXG4gICAgICogQHBhcmFtIHtGdW5jdGlvbn0gZm4gVGhlIGV4ZWN1dGlvbiBmdW5jdGlvbi5cbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBjaGVja0ZuIFRoZSB0ZXN0IGZ1bmN0aW9uLlxuICAgICAqL1xuICAgIHByaXZhdGUgcnVuQWN0aW9uT25GYWlsZWRFdihmbjogKGV2OiBNYXRyaXhFdmVudCkgPT4gdm9pZCwgY2hlY2tGbj86IChldjogTWF0cml4RXZlbnQpID0+IGJvb2xlYW4pOiB2b2lkIHtcbiAgICAgICAgaWYgKCFjaGVja0ZuKSBjaGVja0ZuID0gKCkgPT4gdHJ1ZTtcblxuICAgICAgICBjb25zdCBteEV2ZW50ID0gdGhpcy5wcm9wcy5teEV2ZW50O1xuICAgICAgICBjb25zdCBlZGl0RXZlbnQgPSBteEV2ZW50LnJlcGxhY2luZ0V2ZW50KCk7XG4gICAgICAgIGNvbnN0IHJlZGFjdEV2ZW50ID0gbXhFdmVudC5sb2NhbFJlZGFjdGlvbkV2ZW50KCk7XG4gICAgICAgIGNvbnN0IHRyeU9yZGVyID0gW3JlZGFjdEV2ZW50LCBlZGl0RXZlbnQsIG14RXZlbnRdO1xuICAgICAgICBmb3IgKGNvbnN0IGV2IG9mIHRyeU9yZGVyKSB7XG4gICAgICAgICAgICBpZiAoZXYgJiYgY2hlY2tGbihldikpIHtcbiAgICAgICAgICAgICAgICBmbihldik7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwcml2YXRlIG9uUmVzZW5kQ2xpY2sgPSAoZXY6IEJ1dHRvbkV2ZW50KTogdm9pZCA9PiB7XG4gICAgICAgIC8vIERvbid0IG9wZW4gdGhlIHJlZ3VsYXIgYnJvd3NlciBvciBvdXIgY29udGV4dCBtZW51IG9uIHJpZ2h0LWNsaWNrXG4gICAgICAgIGV2LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGV2LnN0b3BQcm9wYWdhdGlvbigpO1xuXG4gICAgICAgIHRoaXMucnVuQWN0aW9uT25GYWlsZWRFdigodGFyRXYpID0+IFJlc2VuZC5yZXNlbmQoTWF0cml4Q2xpZW50UGVnLnNhZmVHZXQoKSwgdGFyRXYpKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkNhbmNlbENsaWNrID0gKGV2OiBCdXR0b25FdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICB0aGlzLnJ1bkFjdGlvbk9uRmFpbGVkRXYoXG4gICAgICAgICAgICAodGFyRXYpID0+IFJlc2VuZC5yZW1vdmVGcm9tUXVldWUoTWF0cml4Q2xpZW50UGVnLnNhZmVHZXQoKSwgdGFyRXYpLFxuICAgICAgICAgICAgKHRlc3RFdikgPT4gY2FuQ2FuY2VsKHRlc3RFdi5zdGF0dXMpLFxuICAgICAgICApO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBQaW4gb3IgdW5waW4gdGhlIGV2ZW50LlxuICAgICAqL1xuICAgIHByaXZhdGUgb25QaW5DbGljayA9IGFzeW5jIChldmVudDogQnV0dG9uRXZlbnQsIGlzUGlubmVkOiBib29sZWFuKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gICAgICAgIC8vIERvbid0IG9wZW4gdGhlIHJlZ3VsYXIgYnJvd3NlciBvciBvdXIgY29udGV4dCBtZW51IG9uIHJpZ2h0LWNsaWNrXG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuXG4gICAgICAgIGF3YWl0IFBpbm5pbmdVdGlscy5waW5PclVucGluRXZlbnQoTWF0cml4Q2xpZW50UGVnLnNhZmVHZXQoKSwgdGhpcy5wcm9wcy5teEV2ZW50KTtcbiAgICAgICAgUG9zdGhvZ1RyYWNrZXJzLnRyYWNrUGluVW5waW5NZXNzYWdlKGlzUGlubmVkID8gXCJQaW5cIiA6IFwiVW5waW5cIiwgXCJUaW1lbGluZVwiKTtcbiAgICB9O1xuXG4gICAgcHVibGljIHJlbmRlcigpOiBSZWFjdC5SZWFjdE5vZGUge1xuICAgICAgICBjb25zdCB0b29sYmFyT3B0czogSlNYLkVsZW1lbnRbXSA9IFtdO1xuICAgICAgICBpZiAoY2FuRWRpdENvbnRlbnQoTWF0cml4Q2xpZW50UGVnLnNhZmVHZXQoKSwgdGhpcy5wcm9wcy5teEV2ZW50KSkge1xuICAgICAgICAgICAgdG9vbGJhck9wdHMucHVzaChcbiAgICAgICAgICAgICAgICA8Um92aW5nQWNjZXNzaWJsZUJ1dHRvblxuICAgICAgICAgICAgICAgICAgICBjbGFzc05hbWU9XCJteF9N