UNPKG

matrix-react-sdk

Version:
438 lines (428 loc) 83.4 kB
"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