matrix-react-sdk
Version:
SDK for matrix.org using React
536 lines (531 loc) • 84.9 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 _react = _interopRequireWildcard(require("react"));
var _call = require("matrix-js-sdk/src/webrtc/call");
var _classnames = _interopRequireDefault(require("classnames"));
var _callEventTypes = require("matrix-js-sdk/src/webrtc/callEventTypes");
var _dispatcher = _interopRequireDefault(require("../../../dispatcher/dispatcher"));
var _LegacyCallHandler = _interopRequireDefault(require("../../../LegacyCallHandler"));
var _MatrixClientPeg = require("../../../MatrixClientPeg");
var _languageHandler = require("../../../languageHandler");
var _VideoFeed = _interopRequireDefault(require("./VideoFeed"));
var _RoomAvatar = _interopRequireDefault(require("../avatars/RoomAvatar"));
var _AccessibleButton = _interopRequireDefault(require("../elements/AccessibleButton"));
var _Avatar = require("../../../Avatar");
var _LegacyCallViewSidebar = _interopRequireDefault(require("./LegacyCallViewSidebar"));
var _LegacyCallViewHeader = _interopRequireDefault(require("./LegacyCallView/LegacyCallViewHeader"));
var _LegacyCallViewButtons = _interopRequireDefault(require("./LegacyCallView/LegacyCallViewButtons"));
var _KeyBindingsManager = require("../../../KeyBindingsManager");
var _KeyboardShortcuts = require("../../../accessibility/KeyboardShortcuts");
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 2021, 2022 Šimon Brandner <simon.bra.ag@gmail.com>
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Copyright 2015, 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
function getFullScreenElement() {
return document.fullscreenElement ||
// moz omitted because firefox supports this unprefixed now (webkit here for safari)
document.webkitFullscreenElement || document.msFullscreenElement;
}
function requestFullscreen(element) {
const method = element.requestFullscreen ||
// moz omitted since firefox supports unprefixed now
element.webkitRequestFullScreen || element.msRequestFullscreen;
if (method) method.call(element);
}
function exitFullscreen() {
const exitMethod = document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen;
if (exitMethod) exitMethod.call(document);
}
class LegacyCallView extends _react.default.Component {
constructor(props) {
super(props);
(0, _defineProperty2.default)(this, "dispatcherRef", void 0);
(0, _defineProperty2.default)(this, "contentWrapperRef", /*#__PURE__*/(0, _react.createRef)());
(0, _defineProperty2.default)(this, "buttonsRef", /*#__PURE__*/(0, _react.createRef)());
(0, _defineProperty2.default)(this, "onAction", payload => {
switch (payload.action) {
case "video_fullscreen":
{
if (!this.contentWrapperRef.current) {
return;
}
if (payload.fullscreen) {
requestFullscreen(this.contentWrapperRef.current);
} else if (getFullScreenElement()) {
exitFullscreen();
}
break;
}
}
});
(0, _defineProperty2.default)(this, "onCallState", state => {
this.setState({
callState: state
});
});
(0, _defineProperty2.default)(this, "onFeedsChanged", newFeeds => {
const {
primary,
secondary,
sidebar
} = LegacyCallView.getOrderedFeeds(newFeeds);
this.setState({
primaryFeed: primary,
secondaryFeed: secondary,
sidebarFeeds: sidebar,
micMuted: this.props.call.isMicrophoneMuted(),
vidMuted: this.props.call.isLocalVideoMuted()
});
});
(0, _defineProperty2.default)(this, "onCallLocalHoldUnhold", () => {
this.setState({
isLocalOnHold: this.props.call.isLocalOnHold()
});
});
(0, _defineProperty2.default)(this, "onCallRemoteHoldUnhold", () => {
this.setState({
isRemoteOnHold: this.props.call.isRemoteOnHold(),
// update both here because isLocalOnHold changes when we hold the call too
isLocalOnHold: this.props.call.isLocalOnHold()
});
});
(0, _defineProperty2.default)(this, "onMouseMove", () => {
this.buttonsRef.current?.showControls();
});
(0, _defineProperty2.default)(this, "onMaximizeClick", () => {
_dispatcher.default.dispatch({
action: "video_fullscreen",
fullscreen: true
});
});
(0, _defineProperty2.default)(this, "onMicMuteClick", async () => {
const newVal = !this.state.micMuted;
this.setState({
micMuted: await this.props.call.setMicrophoneMuted(newVal)
});
});
(0, _defineProperty2.default)(this, "onVidMuteClick", async () => {
const newVal = !this.state.vidMuted;
this.setState({
vidMuted: await this.props.call.setLocalVideoMuted(newVal)
});
});
(0, _defineProperty2.default)(this, "onScreenshareClick", async () => {
let isScreensharing;
if (this.state.screensharing) {
isScreensharing = await this.props.call.setScreensharingEnabled(false);
} else {
isScreensharing = await this.props.call.setScreensharingEnabled(true);
}
this.setState({
sidebarShown: true,
screensharing: isScreensharing
});
});
// we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire
// Note that this assumes we always have a LegacyCallView on screen at any given time
// LegacyCallHandler would probably be a better place for this
(0, _defineProperty2.default)(this, "onNativeKeyDown", ev => {
let handled = false;
const callAction = (0, _KeyBindingsManager.getKeyBindingsManager)().getCallAction(ev);
switch (callAction) {
case _KeyboardShortcuts.KeyBindingAction.ToggleMicInCall:
this.onMicMuteClick();
// show the controls to give feedback
this.buttonsRef.current?.showControls();
handled = true;
break;
case _KeyboardShortcuts.KeyBindingAction.ToggleWebcamInCall:
this.onVidMuteClick();
// show the controls to give feedback
this.buttonsRef.current?.showControls();
handled = true;
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
});
(0, _defineProperty2.default)(this, "onCallResumeClick", () => {
const userFacingRoomId = _LegacyCallHandler.default.instance.roomIdForCall(this.props.call);
if (userFacingRoomId) _LegacyCallHandler.default.instance.setActiveCallRoomId(userFacingRoomId);
});
(0, _defineProperty2.default)(this, "onTransferClick", () => {
const transfereeCall = _LegacyCallHandler.default.instance.getTransfereeForCallId(this.props.call.callId);
if (transfereeCall) this.props.call.transferToCall(transfereeCall);
});
(0, _defineProperty2.default)(this, "onHangupClick", () => {
const roomId = _LegacyCallHandler.default.instance.roomIdForCall(this.props.call);
if (roomId) _LegacyCallHandler.default.instance.hangupOrReject(roomId);
});
(0, _defineProperty2.default)(this, "onToggleSidebar", () => {
this.setState({
sidebarShown: !this.state.sidebarShown
});
});
const {
primary: _primary,
secondary: _secondary,
sidebar: _sidebar
} = LegacyCallView.getOrderedFeeds(this.props.call.getFeeds());
this.state = {
isLocalOnHold: this.props.call.isLocalOnHold(),
isRemoteOnHold: this.props.call.isRemoteOnHold(),
micMuted: this.props.call.isMicrophoneMuted(),
vidMuted: this.props.call.isLocalVideoMuted(),
screensharing: this.props.call.isScreensharing(),
callState: this.props.call.state,
primaryFeed: _primary,
secondaryFeed: _secondary,
sidebarFeeds: _sidebar,
sidebarShown: true
};
this.updateCallListeners(null, this.props.call);
}
componentDidMount() {
this.dispatcherRef = _dispatcher.default.register(this.onAction);
document.addEventListener("keydown", this.onNativeKeyDown);
}
componentWillUnmount() {
if (getFullScreenElement()) {
exitFullscreen();
}
document.removeEventListener("keydown", this.onNativeKeyDown);
this.updateCallListeners(this.props.call, null);
if (this.dispatcherRef) _dispatcher.default.unregister(this.dispatcherRef);
}
static getDerivedStateFromProps(props) {
const {
primary,
secondary,
sidebar
} = LegacyCallView.getOrderedFeeds(props.call.getFeeds());
return {
primaryFeed: primary,
secondaryFeed: secondary,
sidebarFeeds: sidebar
};
}
componentDidUpdate(prevProps) {
if (this.props.call === prevProps.call) return;
this.setState({
isLocalOnHold: this.props.call.isLocalOnHold(),
isRemoteOnHold: this.props.call.isRemoteOnHold(),
micMuted: this.props.call.isMicrophoneMuted(),
vidMuted: this.props.call.isLocalVideoMuted(),
callState: this.props.call.state
});
this.updateCallListeners(null, this.props.call);
}
updateCallListeners(oldCall, newCall) {
if (oldCall === newCall) return;
if (oldCall) {
oldCall.removeListener(_call.CallEvent.State, this.onCallState);
oldCall.removeListener(_call.CallEvent.LocalHoldUnhold, this.onCallLocalHoldUnhold);
oldCall.removeListener(_call.CallEvent.RemoteHoldUnhold, this.onCallRemoteHoldUnhold);
oldCall.removeListener(_call.CallEvent.FeedsChanged, this.onFeedsChanged);
}
if (newCall) {
newCall.on(_call.CallEvent.State, this.onCallState);
newCall.on(_call.CallEvent.LocalHoldUnhold, this.onCallLocalHoldUnhold);
newCall.on(_call.CallEvent.RemoteHoldUnhold, this.onCallRemoteHoldUnhold);
newCall.on(_call.CallEvent.FeedsChanged, this.onFeedsChanged);
}
}
static getOrderedFeeds(feeds) {
if (feeds.length <= 2) {
return {
primary: feeds.find(feed => !feed.isLocal()),
secondary: feeds.find(feed => feed.isLocal()),
sidebar: []
};
}
let primary;
// Try to use a screensharing as primary, a remote one if possible
const screensharingFeeds = feeds.filter(feed => feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare);
primary = screensharingFeeds.find(feed => !feed.isLocal()) || screensharingFeeds[0];
// If we didn't find remote screen-sharing stream, try to find any remote stream
if (!primary) {
primary = feeds.find(feed => !feed.isLocal());
}
const sidebar = [...feeds];
// Remove the primary feed from the array
if (primary) sidebar.splice(sidebar.indexOf(primary), 1);
sidebar.sort((a, b) => {
if (a.isLocal() && !b.isLocal()) return -1;
if (!a.isLocal() && b.isLocal()) return 1;
return 0;
});
return {
primary,
sidebar
};
}
renderCallControls() {
const {
call,
pipMode
} = this.props;
const {
callState,
micMuted,
vidMuted,
screensharing,
sidebarShown,
secondaryFeed,
sidebarFeeds
} = this.state;
// If SDPStreamMetadata isn't supported don't show video mute button in voice calls
const vidMuteButtonShown = call.opponentSupportsSDPStreamMetadata() || call.hasLocalUserMediaVideoTrack;
// Screensharing is possible, if we can send a second stream and
// identify it using SDPStreamMetadata or if we can replace the already
// existing usermedia track by a screensharing track. We also need to be
// connected to know the state of the other side
const screensharingButtonShown = (call.opponentSupportsSDPStreamMetadata() || call.hasLocalUserMediaVideoTrack) && call.state === _call.CallState.Connected;
// Show the sidebar button only if there is something to hide/show
const sidebarButtonShown = secondaryFeed && !secondaryFeed.isVideoMuted() || sidebarFeeds.length > 0;
// The dial pad & 'more' button actions are only relevant in a connected call
const contextMenuButtonShown = callState === _call.CallState.Connected;
const dialpadButtonShown = callState === _call.CallState.Connected && call.opponentSupportsDTMF();
return /*#__PURE__*/_react.default.createElement(_LegacyCallViewButtons.default, {
ref: this.buttonsRef,
call: call,
pipMode: pipMode,
handlers: {
onToggleSidebarClick: this.onToggleSidebar,
onScreenshareClick: this.onScreenshareClick,
onHangupClick: this.onHangupClick,
onMicMuteClick: this.onMicMuteClick,
onVidMuteClick: this.onVidMuteClick
},
buttonsState: {
micMuted: micMuted,
vidMuted: vidMuted,
sidebarShown: sidebarShown,
screensharing: screensharing
},
buttonsVisibility: {
vidMute: vidMuteButtonShown,
screensharing: screensharingButtonShown,
sidebar: sidebarButtonShown,
contextMenu: contextMenuButtonShown,
dialpad: dialpadButtonShown
}
});
}
renderToast() {
const {
call
} = this.props;
const someoneIsScreensharing = call.getFeeds().some(feed => {
return feed.purpose === _callEventTypes.SDPStreamMetadataPurpose.Screenshare;
});
if (!someoneIsScreensharing) return null;
const isScreensharing = call.isScreensharing();
const {
primaryFeed,
sidebarShown
} = this.state;
const sharerName = primaryFeed?.getMember()?.name;
if (!sharerName) return null;
let text = isScreensharing ? (0, _languageHandler._t)("voip|you_are_presenting") : (0, _languageHandler._t)("voip|user_is_presenting", {
sharerName
});
if (!sidebarShown) {
text += " • " + (call.isLocalVideoMuted() ? (0, _languageHandler._t)("voip|camera_disabled") : (0, _languageHandler._t)("voip|camera_enabled"));
}
return /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_toast"
}, text);
}
renderContent() {
const {
pipMode,
call,
onResize
} = this.props;
const {
isLocalOnHold,
isRemoteOnHold,
sidebarShown,
primaryFeed,
secondaryFeed,
sidebarFeeds
} = this.state;
const callRoomId = _LegacyCallHandler.default.instance.roomIdForCall(call);
const callRoom = (callRoomId ? _MatrixClientPeg.MatrixClientPeg.safeGet().getRoom(callRoomId) : undefined) ?? undefined;
const avatarSize = pipMode ? "76px" : "160px";
const transfereeCall = _LegacyCallHandler.default.instance.getTransfereeForCallId(call.callId);
const isOnHold = isLocalOnHold || isRemoteOnHold;
let secondaryFeedElement;
if (sidebarShown && secondaryFeed && !secondaryFeed.isVideoMuted()) {
secondaryFeedElement = /*#__PURE__*/_react.default.createElement(_VideoFeed.default, {
feed: secondaryFeed,
call: call,
pipMode: pipMode,
onResize: onResize,
secondary: true
});
}
if (transfereeCall || isOnHold) {
const containerClasses = (0, _classnames.default)("mx_LegacyCallView_content", {
mx_LegacyCallView_content_hold: isOnHold
});
const backgroundAvatarUrl = (0, _Avatar.avatarUrlForMember)(call.getOpponentMember(), 1024, 1024, "crop");
let holdTransferContent;
if (transfereeCall) {
const cli = _MatrixClientPeg.MatrixClientPeg.safeGet();
const callRoomId = _LegacyCallHandler.default.instance.roomIdForCall(call);
const transferTargetRoom = callRoomId ? cli.getRoom(callRoomId) : null;
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : (0, _languageHandler._t)("voip|unknown_person");
const transfereeCallRoomId = _LegacyCallHandler.default.instance.roomIdForCall(transfereeCall);
const transfereeRoom = transfereeCallRoomId ? cli.getRoom(transfereeCallRoomId) : null;
const transfereeName = transfereeRoom ? transfereeRoom.name : (0, _languageHandler._t)("voip|unknown_person");
holdTransferContent = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_status"
}, (0, _languageHandler._t)("voip|consulting", {
transferTarget: transferTargetName,
transferee: transfereeName
}, {
a: sub => /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
kind: "link_inline",
onClick: this.onTransferClick
}, sub)
}));
} else {
let onHoldText;
if (isRemoteOnHold) {
onHoldText = (0, _languageHandler._t)(_LegacyCallHandler.default.instance.hasAnyUnheldCall() ? (0, _languageHandler._td)("voip|call_held_switch") : (0, _languageHandler._td)("voip|call_held_resume"), {}, {
a: sub => /*#__PURE__*/_react.default.createElement(_AccessibleButton.default, {
kind: "link_inline",
onClick: this.onCallResumeClick
}, sub)
});
} else if (isLocalOnHold) {
onHoldText = (0, _languageHandler._t)("voip|call_held", {
peerName: call.getOpponentMember()?.name
});
}
holdTransferContent = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_status"
}, onHoldText);
}
return /*#__PURE__*/_react.default.createElement("div", {
className: containerClasses,
onMouseMove: this.onMouseMove
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_holdBackground",
style: {
backgroundImage: "url(" + backgroundAvatarUrl + ")"
}
}), holdTransferContent);
} else if (call.noIncomingFeeds()) {
return /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_content",
onMouseMove: this.onMouseMove
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_avatarsContainer"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_avatarContainer",
style: {
width: avatarSize,
height: avatarSize
}
}, /*#__PURE__*/_react.default.createElement(_RoomAvatar.default, {
room: callRoom,
size: avatarSize
}))), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_status"
}, (0, _languageHandler._t)("voip|connecting")), secondaryFeedElement);
} else if (pipMode) {
// We've already checked that we have feeds so we cast away the optional when passing the feed
return /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_content",
onMouseMove: this.onMouseMove
}, /*#__PURE__*/_react.default.createElement(_VideoFeed.default, {
feed: primaryFeed,
call: call,
pipMode: pipMode,
onResize: onResize,
primary: true
}));
} else if (secondaryFeed) {
return /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_content",
onMouseMove: this.onMouseMove
}, /*#__PURE__*/_react.default.createElement(_VideoFeed.default, {
feed: primaryFeed,
call: call,
pipMode: pipMode,
onResize: onResize,
primary: true
}), secondaryFeedElement);
} else {
return /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_content",
onMouseMove: this.onMouseMove
}, /*#__PURE__*/_react.default.createElement(_VideoFeed.default, {
feed: primaryFeed,
call: call,
pipMode: pipMode,
onResize: onResize,
primary: true
}), sidebarShown && /*#__PURE__*/_react.default.createElement(_LegacyCallViewSidebar.default, {
feeds: sidebarFeeds,
call: call,
pipMode: Boolean(pipMode)
}));
}
}
render() {
const {
call,
secondaryCall,
pipMode,
showApps,
onMouseDownOnHeader
} = this.props;
const {
sidebarShown,
sidebarFeeds
} = this.state;
const client = _MatrixClientPeg.MatrixClientPeg.safeGet();
const callRoomId = _LegacyCallHandler.default.instance.roomIdForCall(call);
const secondaryCallRoomId = _LegacyCallHandler.default.instance.roomIdForCall(secondaryCall);
const callRoom = callRoomId ? client.getRoom(callRoomId) : null;
if (!callRoom) return null;
const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null;
const callViewClasses = (0, _classnames.default)({
mx_LegacyCallView: true,
mx_LegacyCallView_pip: pipMode,
mx_LegacyCallView_large: !pipMode,
mx_LegacyCallView_sidebar: sidebarShown && sidebarFeeds.length !== 0 && !pipMode,
mx_LegacyCallView_belowWidget: showApps // css to correct the margins if the call is below the AppsDrawer.
});
return /*#__PURE__*/_react.default.createElement("div", {
className: callViewClasses
}, /*#__PURE__*/_react.default.createElement(_LegacyCallViewHeader.default, {
onPipMouseDown: onMouseDownOnHeader,
pipMode: pipMode,
callRooms: [callRoom, secCallRoom],
onMaximize: this.onMaximizeClick
}), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_LegacyCallView_content_wrapper",
ref: this.contentWrapperRef
}, this.renderToast(), this.renderContent(), this.renderCallControls()));
}
}
exports.default = LegacyCallView;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJfaW50ZXJvcFJlcXVpcmVXaWxkY2FyZCIsInJlcXVpcmUiLCJfY2FsbCIsIl9jbGFzc25hbWVzIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsIl9jYWxsRXZlbnRUeXBlcyIsIl9kaXNwYXRjaGVyIiwiX0xlZ2FjeUNhbGxIYW5kbGVyIiwiX01hdHJpeENsaWVudFBlZyIsIl9sYW5ndWFnZUhhbmRsZXIiLCJfVmlkZW9GZWVkIiwiX1Jvb21BdmF0YXIiLCJfQWNjZXNzaWJsZUJ1dHRvbiIsIl9BdmF0YXIiLCJfTGVnYWN5Q2FsbFZpZXdTaWRlYmFyIiwiX0xlZ2FjeUNhbGxWaWV3SGVhZGVyIiwiX0xlZ2FjeUNhbGxWaWV3QnV0dG9ucyIsIl9LZXlCaW5kaW5nc01hbmFnZXIiLCJfS2V5Ym9hcmRTaG9ydGN1dHMiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsImRlZmF1bHQiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJnZXRGdWxsU2NyZWVuRWxlbWVudCIsImRvY3VtZW50IiwiZnVsbHNjcmVlbkVsZW1lbnQiLCJ3ZWJraXRGdWxsc2NyZWVuRWxlbWVudCIsIm1zRnVsbHNjcmVlbkVsZW1lbnQiLCJyZXF1ZXN0RnVsbHNjcmVlbiIsImVsZW1lbnQiLCJtZXRob2QiLCJ3ZWJraXRSZXF1ZXN0RnVsbFNjcmVlbiIsIm1zUmVxdWVzdEZ1bGxzY3JlZW4iLCJleGl0RnVsbHNjcmVlbiIsImV4aXRNZXRob2QiLCJ3ZWJraXRFeGl0RnVsbHNjcmVlbiIsIm1zRXhpdEZ1bGxzY3JlZW4iLCJMZWdhY3lDYWxsVmlldyIsIlJlYWN0IiwiQ29tcG9uZW50IiwiY29uc3RydWN0b3IiLCJwcm9wcyIsIl9kZWZpbmVQcm9wZXJ0eTIiLCJjcmVhdGVSZWYiLCJwYXlsb2FkIiwiYWN0aW9uIiwiY29udGVudFdyYXBwZXJSZWYiLCJjdXJyZW50IiwiZnVsbHNjcmVlbiIsInN0YXRlIiwic2V0U3RhdGUiLCJjYWxsU3RhdGUiLCJuZXdGZWVkcyIsInByaW1hcnkiLCJzZWNvbmRhcnkiLCJzaWRlYmFyIiwiZ2V0T3JkZXJlZEZlZWRzIiwicHJpbWFyeUZlZWQiLCJzZWNvbmRhcnlGZWVkIiwic2lkZWJhckZlZWRzIiwibWljTXV0ZWQiLCJpc01pY3JvcGhvbmVNdXRlZCIsInZpZE11dGVkIiwiaXNMb2NhbFZpZGVvTXV0ZWQiLCJpc0xvY2FsT25Ib2xkIiwiaXNSZW1vdGVPbkhvbGQiLCJidXR0b25zUmVmIiwic2hvd0NvbnRyb2xzIiwiZGlzIiwiZGlzcGF0Y2giLCJuZXdWYWwiLCJzZXRNaWNyb3Bob25lTXV0ZWQiLCJzZXRMb2NhbFZpZGVvTXV0ZWQiLCJpc1NjcmVlbnNoYXJpbmciLCJzY3JlZW5zaGFyaW5nIiwic2V0U2NyZWVuc2hhcmluZ0VuYWJsZWQiLCJzaWRlYmFyU2hvd24iLCJldiIsImhhbmRsZWQiLCJjYWxsQWN0aW9uIiwiZ2V0S2V5QmluZGluZ3NNYW5hZ2VyIiwiZ2V0Q2FsbEFjdGlvbiIsIktleUJpbmRpbmdBY3Rpb24iLCJUb2dnbGVNaWNJbkNhbGwiLCJvbk1pY011dGVDbGljayIsIlRvZ2dsZVdlYmNhbUluQ2FsbCIsIm9uVmlkTXV0ZUNsaWNrIiwic3RvcFByb3BhZ2F0aW9uIiwicHJldmVudERlZmF1bHQiLCJ1c2VyRmFjaW5nUm9vbUlkIiwiTGVnYWN5Q2FsbEhhbmRsZXIiLCJpbnN0YW5jZSIsInJvb21JZEZvckNhbGwiLCJzZXRBY3RpdmVDYWxsUm9vbUlkIiwidHJhbnNmZXJlZUNhbGwiLCJnZXRUcmFuc2ZlcmVlRm9yQ2FsbElkIiwiY2FsbElkIiwidHJhbnNmZXJUb0NhbGwiLCJyb29tSWQiLCJoYW5ndXBPclJlamVjdCIsImdldEZlZWRzIiwidXBkYXRlQ2FsbExpc3RlbmVycyIsImNvbXBvbmVudERpZE1vdW50IiwiZGlzcGF0Y2hlclJlZiIsInJlZ2lzdGVyIiwib25BY3Rpb24iLCJhZGRFdmVudExpc3RlbmVyIiwib25OYXRpdmVLZXlEb3duIiwiY29tcG9uZW50V2lsbFVubW91bnQiLCJyZW1vdmVFdmVudExpc3RlbmVyIiwidW5yZWdpc3RlciIsImdldERlcml2ZWRTdGF0ZUZyb21Qcm9wcyIsImNvbXBvbmVudERpZFVwZGF0ZSIsInByZXZQcm9wcyIsIm9sZENhbGwiLCJuZXdDYWxsIiwicmVtb3ZlTGlzdGVuZXIiLCJDYWxsRXZlbnQiLCJTdGF0ZSIsIm9uQ2FsbFN0YXRlIiwiTG9jYWxIb2xkVW5ob2xkIiwib25DYWxsTG9jYWxIb2xkVW5ob2xkIiwiUmVtb3RlSG9sZFVuaG9sZCIsIm9uQ2FsbFJlbW90ZUhvbGRVbmhvbGQiLCJGZWVkc0NoYW5nZWQiLCJvbkZlZWRzQ2hhbmdlZCIsIm9uIiwiZmVlZHMiLCJsZW5ndGgiLCJmaW5kIiwiZmVlZCIsImlzTG9jYWwiLCJzY3JlZW5zaGFyaW5nRmVlZHMiLCJmaWx0ZXIiLCJwdXJwb3NlIiwiU0RQU3RyZWFtTWV0YWRhdGFQdXJwb3NlIiwiU2NyZWVuc2hhcmUiLCJzcGxpY2UiLCJpbmRleE9mIiwic29ydCIsImIiLCJyZW5kZXJDYWxsQ29udHJvbHMiLCJwaXBNb2RlIiwidmlkTXV0ZUJ1dHRvblNob3duIiwib3Bwb25lbnRTdXBwb3J0c1NEUFN0cmVhbU1ldGFkYXRhIiwiaGFzTG9jYWxVc2VyTWVkaWFWaWRlb1RyYWNrIiwic2NyZWVuc2hhcmluZ0J1dHRvblNob3duIiwiQ2FsbFN0YXRlIiwiQ29ubmVjdGVkIiwic2lkZWJhckJ1dHRvblNob3duIiwiaXNWaWRlb011dGVkIiwiY29udGV4dE1lbnVCdXR0b25TaG93biIsImRpYWxwYWRCdXR0b25TaG93biIsIm9wcG9uZW50U3VwcG9ydHNEVE1GIiwiY3JlYXRlRWxlbWVudCIsInJlZiIsImhhbmRsZXJzIiwib25Ub2dnbGVTaWRlYmFyQ2xpY2siLCJvblRvZ2dsZVNpZGViYXIiLCJvblNjcmVlbnNoYXJlQ2xpY2siLCJvbkhhbmd1cENsaWNrIiwiYnV0dG9uc1N0YXRlIiwiYnV0dG9uc1Zpc2liaWxpdHkiLCJ2aWRNdXRlIiwiY29udGV4dE1lbnUiLCJkaWFscGFkIiwicmVuZGVyVG9hc3QiLCJzb21lb25lSXNTY3JlZW5zaGFyaW5nIiwic29tZSIsInNoYXJlck5hbWUiLCJnZXRNZW1iZXIiLCJuYW1lIiwidGV4dCIsIl90IiwiY2xhc3NOYW1lIiwicmVuZGVyQ29udGVudCIsIm9uUmVzaXplIiwiY2FsbFJvb21JZCIsImNhbGxSb29tIiwiTWF0cml4Q2xpZW50UGVnIiwic2FmZUdldCIsImdldFJvb20iLCJ1bmRlZmluZWQiLCJhdmF0YXJTaXplIiwiaXNPbkhvbGQiLCJzZWNvbmRhcnlGZWVkRWxlbWVudCIsImNvbnRhaW5lckNsYXNzZXMiLCJjbGFzc05hbWVzIiwibXhfTGVnYWN5Q2FsbFZpZXdfY29udGVudF9ob2xkIiwiYmFja2dyb3VuZEF2YXRhclVybCIsImF2YXRhclVybEZvck1lbWJlciIsImdldE9wcG9uZW50TWVtYmVyIiwiaG9sZFRyYW5zZmVyQ29udGVudCIsImNsaSIsInRyYW5zZmVyVGFyZ2V0Um9vbSIsInRyYW5zZmVyVGFyZ2V0TmFtZSIsInRyYW5zZmVyZWVDYWxsUm9vbUlkIiwidHJhbnNmZXJlZVJvb20iLCJ0cmFuc2ZlcmVlTmFtZSIsInRyYW5zZmVyVGFyZ2V0IiwidHJhbnNmZXJlZSIsInN1YiIsImtpbmQiLCJvbkNsaWNrIiwib25UcmFuc2ZlckNsaWNrIiwib25Ib2xkVGV4dCIsImhhc0FueVVuaGVsZENhbGwiLCJfdGQiLCJvbkNhbGxSZXN1bWVDbGljayIsInBlZXJOYW1lIiwib25Nb3VzZU1vdmUiLCJzdHlsZSIsImJhY2tncm91bmRJbWFnZSIsIm5vSW5jb21pbmdGZWVkcyIsIndpZHRoIiwiaGVpZ2h0Iiwicm9vbSIsInNpemUiLCJCb29sZWFuIiwicmVuZGVyIiwic2Vjb25kYXJ5Q2FsbCIsInNob3dBcHBzIiwib25Nb3VzZURvd25PbkhlYWRlciIsImNsaWVudCIsInNlY29uZGFyeUNhbGxSb29tSWQiLCJzZWNDYWxsUm9vbSIsImNhbGxWaWV3Q2xhc3NlcyIsIm14X0xlZ2FjeUNhbGxWaWV3IiwibXhfTGVnYWN5Q2FsbFZpZXdfcGlwIiwibXhfTGVnYWN5Q2FsbFZpZXdfbGFyZ2UiLCJteF9MZWdhY3lDYWxsVmlld19zaWRlYmFyIiwibXhfTGVnYWN5Q2FsbFZpZXdfYmVsb3dXaWRnZXQiLCJvblBpcE1vdXNlRG93biIsImNhbGxSb29tcyIsIm9uTWF4aW1pemUiLCJvbk1heGltaXplQ2xpY2siLCJleHBvcnRzIl0sInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvdmlld3Mvdm9pcC9MZWdhY3lDYWxsVmlldy50c3giXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMjEsIDIwMjIgxaBpbW9uIEJyYW5kbmVyIDxzaW1vbi5icmEuYWdAZ21haWwuY29tPlxuQ29weXJpZ2h0IDIwMTktMjAyMSBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuQ29weXJpZ2h0IDIwMTUsIDIwMTYgT3Blbk1hcmtldCBMdGRcblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuaW1wb3J0IFJlYWN0LCB7IGNyZWF0ZVJlZiB9IGZyb20gXCJyZWFjdFwiO1xuaW1wb3J0IHsgQ2FsbEV2ZW50LCBDYWxsU3RhdGUsIE1hdHJpeENhbGwgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvd2VicnRjL2NhbGxcIjtcbmltcG9ydCBjbGFzc05hbWVzIGZyb20gXCJjbGFzc25hbWVzXCI7XG5pbXBvcnQgeyBDYWxsRmVlZCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy93ZWJydGMvY2FsbEZlZWRcIjtcbmltcG9ydCB7IFNEUFN0cmVhbU1ldGFkYXRhUHVycG9zZSB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy93ZWJydGMvY2FsbEV2ZW50VHlwZXNcIjtcblxuaW1wb3J0IGRpcyBmcm9tIFwiLi4vLi4vLi4vZGlzcGF0Y2hlci9kaXNwYXRjaGVyXCI7XG5pbXBvcnQgTGVnYWN5Q2FsbEhhbmRsZXIgZnJvbSBcIi4uLy4uLy4uL0xlZ2FjeUNhbGxIYW5kbGVyXCI7XG5pbXBvcnQgeyBNYXRyaXhDbGllbnRQZWcgfSBmcm9tIFwiLi4vLi4vLi4vTWF0cml4Q2xpZW50UGVnXCI7XG5pbXBvcnQgeyBfdCwgX3RkIH0gZnJvbSBcIi4uLy4uLy4uL2xhbmd1YWdlSGFuZGxlclwiO1xuaW1wb3J0IFZpZGVvRmVlZCBmcm9tIFwiLi9WaWRlb0ZlZWRcIjtcbmltcG9ydCBSb29tQXZhdGFyIGZyb20gXCIuLi9hdmF0YXJzL1Jvb21BdmF0YXJcIjtcbmltcG9ydCBBY2Nlc3NpYmxlQnV0dG9uIGZyb20gXCIuLi9lbGVtZW50cy9BY2Nlc3NpYmxlQnV0dG9uXCI7XG5pbXBvcnQgeyBhdmF0YXJVcmxGb3JNZW1iZXIgfSBmcm9tIFwiLi4vLi4vLi4vQXZhdGFyXCI7XG5pbXBvcnQgTGVnYWN5Q2FsbFZpZXdTaWRlYmFyIGZyb20gXCIuL0xlZ2FjeUNhbGxWaWV3U2lkZWJhclwiO1xuaW1wb3J0IExlZ2FjeUNhbGxWaWV3SGVhZGVyIGZyb20gXCIuL0xlZ2FjeUNhbGxWaWV3L0xlZ2FjeUNhbGxWaWV3SGVhZGVyXCI7XG5pbXBvcnQgTGVnYWN5Q2FsbFZpZXdCdXR0b25zIGZyb20gXCIuL0xlZ2FjeUNhbGxWaWV3L0xlZ2FjeUNhbGxWaWV3QnV0dG9uc1wiO1xuaW1wb3J0IHsgQWN0aW9uUGF5bG9hZCB9IGZyb20gXCIuLi8uLi8uLi9kaXNwYXRjaGVyL3BheWxvYWRzXCI7XG5pbXBvcnQgeyBnZXRLZXlCaW5kaW5nc01hbmFnZXIgfSBmcm9tIFwiLi4vLi4vLi4vS2V5QmluZGluZ3NNYW5hZ2VyXCI7XG5pbXBvcnQgeyBLZXlCaW5kaW5nQWN0aW9uIH0gZnJvbSBcIi4uLy4uLy4uL2FjY2Vzc2liaWxpdHkvS2V5Ym9hcmRTaG9ydGN1dHNcIjtcblxuaW50ZXJmYWNlIElQcm9wcyB7XG4gICAgLy8gVGhlIGNhbGwgZm9yIHVzIHRvIGRpc3BsYXlcbiAgICBjYWxsOiBNYXRyaXhDYWxsO1xuXG4gICAgLy8gQW5vdGhlciBvbmdvaW5nIGNhbGwgdG8gZGlzcGxheSBpbmZvcm1hdGlvbiBhYm91dFxuICAgIHNlY29uZGFyeUNhbGw/OiBNYXRyaXhDYWxsO1xuXG4gICAgLy8gYSBjYWxsYmFjayB3aGljaCBpcyBjYWxsZWQgd2hlbiB0aGUgY29udGVudCBpbiB0aGUgTGVnYWN5Q2FsbFZpZXcgY2hhbmdlc1xuICAgIC8vIGluIGEgd2F5IHRoYXQgaXMgbGlrZWx5IHRvIGNhdXNlIGEgcmVzaXplLlxuICAgIG9uUmVzaXplPzogKGV2ZW50OiBFdmVudCkgPT4gdm9pZDtcblxuICAgIC8vIFdoZXRoZXIgdGhpcyBjYWxsIHZpZXcgaXMgZm9yIHBpY3R1cmUtaW4tcGljdHVyZSBtb2RlXG4gICAgLy8gb3RoZXJ3aXNlLCBpdCdzIHRoZSBsYXJnZXIgY2FsbCB2aWV3IHdoZW4gdmlld2luZyB0aGUgcm9vbSB0aGUgY2FsbCBpcyBpbi5cbiAgICAvLyBUaGlzIGlzIHNvcnQgb2YgYSBwcm94eSBmb3IgYSBudW1iZXIgb2YgdGhpbmdzIGJ1dCB3ZSBjdXJyZW50bHkgaGF2ZSBub1xuICAgIC8vIG5lZWQgdG8gY29udHJvbCB0aG9zZSB0aGluZ3Mgc2VwYXJhdGVseSwgc28gdGhpcyBpcyBzaW1wbGVyLlxuICAgIHBpcE1vZGU/OiBib29sZWFuO1xuXG4gICAgLy8gVXNlZCBmb3IgZHJhZ2dpbmcgdGhlIFBpUCBMZWdhY3lDYWxsVmlld1xuICAgIG9uTW91c2VEb3duT25IZWFkZXI/OiAoZXZlbnQ6IFJlYWN0Lk1vdXNlRXZlbnQ8RWxlbWVudCwgTW91c2VFdmVudD4pID0+IHZvaWQ7XG5cbiAgICBzaG93QXBwcz86IGJvb2xlYW47XG59XG5cbmludGVyZmFjZSBJU3RhdGUge1xuICAgIGlzTG9jYWxPbkhvbGQ6IGJvb2xlYW47XG4gICAgaXNSZW1vdGVPbkhvbGQ6IGJvb2xlYW47XG4gICAgbWljTXV0ZWQ6IGJvb2xlYW47XG4gICAgdmlkTXV0ZWQ6IGJvb2xlYW47XG4gICAgc2NyZWVuc2hhcmluZzogYm9vbGVhbjtcbiAgICBjYWxsU3RhdGU6IENhbGxTdGF0ZTtcbiAgICBwcmltYXJ5RmVlZD86IENhbGxGZWVkO1xuICAgIHNlY29uZGFyeUZlZWQ/OiBDYWxsRmVlZDtcbiAgICBzaWRlYmFyRmVlZHM6IEFycmF5PENhbGxGZWVkPjtcbiAgICBzaWRlYmFyU2hvd246IGJvb2xlYW47XG59XG5cbmZ1bmN0aW9uIGdldEZ1bGxTY3JlZW5FbGVtZW50KCk6IEVsZW1lbnQgfCBudWxsIHtcbiAgICByZXR1cm4gKFxuICAgICAgICBkb2N1bWVudC5mdWxsc2NyZWVuRWxlbWVudCB8fFxuICAgICAgICAvLyBtb3ogb21pdHRlZCBiZWNhdXNlIGZpcmVmb3ggc3VwcG9ydHMgdGhpcyB1bnByZWZpeGVkIG5vdyAod2Via2l0IGhlcmUgZm9yIHNhZmFyaSlcbiAgICAgICAgZG9jdW1lbnQud2Via2l0RnVsbHNjcmVlbkVsZW1lbnQgfHxcbiAgICAgICAgZG9jdW1lbnQubXNGdWxsc2NyZWVuRWxlbWVudFxuICAgICk7XG59XG5cbmZ1bmN0aW9uIHJlcXVlc3RGdWxsc2NyZWVuKGVsZW1lbnQ6IEVsZW1lbnQpOiB2b2lkIHtcbiAgICBjb25zdCBtZXRob2QgPVxuICAgICAgICBlbGVtZW50LnJlcXVlc3RGdWxsc2NyZWVuIHx8XG4gICAgICAgIC8vIG1veiBvbWl0dGVkIHNpbmNlIGZpcmVmb3ggc3VwcG9ydHMgdW5wcmVmaXhlZCBub3dcbiAgICAgICAgZWxlbWVudC53ZWJraXRSZXF1ZXN0RnVsbFNjcmVlbiB8fFxuICAgICAgICBlbGVtZW50Lm1zUmVxdWVzdEZ1bGxzY3JlZW47XG4gICAgaWYgKG1ldGhvZCkgbWV0aG9kLmNhbGwoZWxlbWVudCk7XG59XG5cbmZ1bmN0aW9uIGV4aXRGdWxsc2NyZWVuKCk6IHZvaWQge1xuICAgIGNvbnN0IGV4aXRNZXRob2QgPSBkb2N1bWVudC5leGl0RnVsbHNjcmVlbiB8fCBkb2N1bWVudC53ZWJraXRFeGl0RnVsbHNjcmVlbiB8fCBkb2N1bWVudC5tc0V4aXRGdWxsc2NyZWVuO1xuICAgIGlmIChleGl0TWV0aG9kKSBleGl0TWV0aG9kLmNhbGwoZG9jdW1lbnQpO1xufVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBMZWdhY3lDYWxsVmlldyBleHRlbmRzIFJlYWN0LkNvbXBvbmVudDxJUHJvcHMsIElTdGF0ZT4ge1xuICAgIHByaXZhdGUgZGlzcGF0Y2hlclJlZj86IHN0cmluZztcbiAgICBwcml2YXRlIGNvbnRlbnRXcmFwcGVyUmVmID0gY3JlYXRlUmVmPEhUTUxEaXZFbGVtZW50PigpO1xuICAgIHByaXZhdGUgYnV0dG9uc1JlZiA9IGNyZWF0ZVJlZjxMZWdhY3lDYWxsVmlld0J1dHRvbnM+KCk7XG5cbiAgICBwdWJsaWMgY29uc3RydWN0b3IocHJvcHM6IElQcm9wcykge1xuICAgICAgICBzdXBlcihwcm9wcyk7XG5cbiAgICAgICAgY29uc3QgeyBwcmltYXJ5LCBzZWNvbmRhcnksIHNpZGViYXIgfSA9IExlZ2FjeUNhbGxWaWV3LmdldE9yZGVyZWRGZWVkcyh0aGlzLnByb3BzLmNhbGwuZ2V0RmVlZHMoKSk7XG5cbiAgICAgICAgdGhpcy5zdGF0ZSA9IHtcbiAgICAgICAgICAgIGlzTG9jYWxPbkhvbGQ6IHRoaXMucHJvcHMuY2FsbC5pc0xvY2FsT25Ib2xkKCksXG4gICAgICAgICAgICBpc1JlbW90ZU9uSG9sZDogdGhpcy5wcm9wcy5jYWxsLmlzUmVtb3RlT25Ib2xkKCksXG4gICAgICAgICAgICBtaWNNdXRlZDogdGhpcy5wcm9wcy5jYWxsLmlzTWljcm9waG9uZU11dGVkKCksXG4gICAgICAgICAgICB2aWRNdXRlZDogdGhpcy5wcm9wcy5jYWxsLmlzTG9jYWxWaWRlb011dGVkKCksXG4gICAgICAgICAgICBzY3JlZW5zaGFyaW5nOiB0aGlzLnByb3BzLmNhbGwuaXNTY3JlZW5zaGFyaW5nKCksXG4gICAgICAgICAgICBjYWxsU3RhdGU6IHRoaXMucHJvcHMuY2FsbC5zdGF0ZSxcbiAgICAgICAgICAgIHByaW1hcnlGZWVkOiBwcmltYXJ5LFxuICAgICAgICAgICAgc2Vjb25kYXJ5RmVlZDogc2Vjb25kYXJ5LFxuICAgICAgICAgICAgc2lkZWJhckZlZWRzOiBzaWRlYmFyLFxuICAgICAgICAgICAgc2lkZWJhclNob3duOiB0cnVlLFxuICAgICAgICB9O1xuXG4gICAgICAgIHRoaXMudXBkYXRlQ2FsbExpc3RlbmVycyhudWxsLCB0aGlzLnByb3BzLmNhbGwpO1xuICAgIH1cblxuICAgIHB1YmxpYyBjb21wb25lbnREaWRNb3VudCgpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5kaXNwYXRjaGVyUmVmID0gZGlzLnJlZ2lzdGVyKHRoaXMub25BY3Rpb24pO1xuICAgICAgICBkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKFwia2V5ZG93blwiLCB0aGlzLm9uTmF0aXZlS2V5RG93bik7XG4gICAgfVxuXG4gICAgcHVibGljIGNvbXBvbmVudFdpbGxVbm1vdW50KCk6IHZvaWQge1xuICAgICAgICBpZiAoZ2V0RnVsbFNjcmVlbkVsZW1lbnQoKSkge1xuICAgICAgICAgICAgZXhpdEZ1bGxzY3JlZW4oKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGRvY3VtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIoXCJrZXlkb3duXCIsIHRoaXMub25OYXRpdmVLZXlEb3duKTtcbiAgICAgICAgdGhpcy51cGRhdGVDYWxsTGlzdGVuZXJzKHRoaXMucHJvcHMuY2FsbCwgbnVsbCk7XG4gICAgICAgIGlmICh0aGlzLmRpc3BhdGNoZXJSZWYpIGRpcy51bnJlZ2lzdGVyKHRoaXMuZGlzcGF0Y2hlclJlZik7XG4gICAgfVxuXG4gICAgcHVibGljIHN0YXRpYyBnZXREZXJpdmVkU3RhdGVGcm9tUHJvcHMocHJvcHM6IElQcm9wcyk6IFBhcnRpYWw8SVN0YXRlPiB7XG4gICAgICAgIGNvbnN0IHsgcHJpbWFyeSwgc2Vjb25kYXJ5LCBzaWRlYmFyIH0gPSBMZWdhY3lDYWxsVmlldy5nZXRPcmRlcmVkRmVlZHMocHJvcHMuY2FsbC5nZXRGZWVkcygpKTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgcHJpbWFyeUZlZWQ6IHByaW1hcnksXG4gICAgICAgICAgICBzZWNvbmRhcnlGZWVkOiBzZWNvbmRhcnksXG4gICAgICAgICAgICBzaWRlYmFyRmVlZHM6IHNpZGViYXIsXG4gICAgICAgIH07XG4gICAgfVxuXG4gICAgcHVibGljIGNvbXBvbmVudERpZFVwZGF0ZShwcmV2UHJvcHM6IElQcm9wcyk6IHZvaWQge1xuICAgICAgICBpZiAodGhpcy5wcm9wcy5jYWxsID09PSBwcmV2UHJvcHMuY2FsbCkgcmV0dXJuO1xuXG4gICAgICAgIHRoaXMuc2V0U3RhdGUoe1xuICAgICAgICAgICAgaXNMb2NhbE9uSG9sZDogdGhpcy5wcm9wcy5jYWxsLmlzTG9jYWxPbkhvbGQoKSxcbiAgICAgICAgICAgIGlzUmVtb3RlT25Ib2xkOiB0aGlzLnByb3BzLmNhbGwuaXNSZW1vdGVPbkhvbGQoKSxcbiAgICAgICAgICAgIG1pY011dGVkOiB0aGlzLnByb3BzLmNhbGwuaXNNaWNyb3Bob25lTXV0ZWQoKSxcbiAgICAgICAgICAgIHZpZE11dGVkOiB0aGlzLnByb3BzLmNhbGwuaXNMb2NhbFZpZGVvTXV0ZWQoKSxcbiAgICAgICAgICAgIGNhbGxTdGF0ZTogdGhpcy5wcm9wcy5jYWxsLnN0YXRlLFxuICAgICAgICB9KTtcblxuICAgICAgICB0aGlzLnVwZGF0ZUNhbGxMaXN0ZW5lcnMobnVsbCwgdGhpcy5wcm9wcy5jYWxsKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIG9uQWN0aW9uID0gKHBheWxvYWQ6IEFjdGlvblBheWxvYWQpOiB2b2lkID0+IHtcbiAgICAgICAgc3dpdGNoIChwYXlsb2FkLmFjdGlvbikge1xuICAgICAgICAgICAgY2FzZSBcInZpZGVvX2Z1bGxzY3JlZW5cIjoge1xuICAgICAgICAgICAgICAgIGlmICghdGhpcy5jb250ZW50V3JhcHBlclJlZi5jdXJyZW50KSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKHBheWxvYWQuZnVsbHNjcmVlbikge1xuICAgICAgICAgICAgICAgICAgICByZXF1ZXN0RnVsbHNjcmVlbih0aGlzLmNvbnRlbnRXcmFwcGVyUmVmLmN1cnJlbnQpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAoZ2V0RnVsbFNjcmVlbkVsZW1lbnQoKSkge1xuICAgICAgICAgICAgICAgICAgICBleGl0RnVsbHNjcmVlbigpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwcml2YXRlIHVwZGF0ZUNhbGxMaXN0ZW5lcnMob2xkQ2FsbDogTWF0cml4Q2FsbCB8IG51bGwsIG5ld0NhbGw6IE1hdHJpeENhbGwgfCBudWxsKTogdm9pZCB7XG4gICAgICAgIGlmIChvbGRDYWxsID09PSBuZXdDYWxsKSByZXR1cm47XG5cbiAgICAgICAgaWYgKG9sZENhbGwpIHtcbiAgICAgICAgICAgIG9sZENhbGwucmVtb3ZlTGlzdGVuZXIoQ2FsbEV2ZW50LlN0YXRlLCB0aGlzLm9uQ2FsbFN0YXRlKTtcbiAgICAgICAgICAgIG9sZENhbGwucmVtb3ZlTGlzdGVuZXIoQ2FsbEV2ZW50LkxvY2FsSG9sZFVuaG9sZCwgdGhpcy5vbkNhbGxMb2NhbEhvbGRVbmhvbGQpO1xuICAgICAgICAgICAgb2xkQ2FsbC5yZW1vdmVMaXN0ZW5lcihDYWxsRXZlbnQuUmVtb3RlSG9sZFVuaG9sZCwgdGhpcy5vbkNhbGxSZW1vdGVIb2xkVW5ob2xkKTtcbiAgICAgICAgICAgIG9sZENhbGwucmVtb3ZlTGlzdGVuZXIoQ2FsbEV2ZW50LkZlZWRzQ2hhbmdlZCwgdGhpcy5vbkZlZWRzQ2hhbmdlZCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKG5ld0NhbGwpIHtcbiAgICAgICAgICAgIG5ld0NhbGwub24oQ2FsbEV2ZW50LlN0YXRlLCB0aGlzLm9uQ2FsbFN0YXRlKTtcbiAgICAgICAgICAgIG5ld0NhbGwub24oQ2FsbEV2ZW50LkxvY2FsSG9sZFVuaG9sZCwgdGhpcy5vbkNhbGxMb2NhbEhvbGRVbmhvbGQpO1xuICAgICAgICAgICAgbmV3Q2FsbC5vbihDYWxsRXZlbnQuUmVtb3RlSG9sZFVuaG9sZCwgdGhpcy5vbkNhbGxSZW1vdGVIb2xkVW5ob2xkKTtcbiAgICAgICAgICAgIG5ld0NhbGwub24oQ2FsbEV2ZW50LkZlZWRzQ2hhbmdlZCwgdGhpcy5vbkZlZWRzQ2hhbmdlZCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwcml2YXRlIG9uQ2FsbFN0YXRlID0gKHN0YXRlOiBDYWxsU3RhdGUpOiB2b2lkID0+IHtcbiAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICBjYWxsU3RhdGU6IHN0YXRlLFxuICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkZlZWRzQ2hhbmdlZCA9IChuZXdGZWVkczogQXJyYXk8Q2FsbEZlZWQ+KTogdm9pZCA9PiB7XG4gICAgICAgIGNvbnN0IHsgcHJpbWFyeSwgc2Vjb25kYXJ5LCBzaWRlYmFyIH0gPSBMZWdhY3lDYWxsVmlldy5nZXRPcmRlcmVkRmVlZHMobmV3RmVlZHMpO1xuICAgICAgICB0aGlzLnNldFN0YXRlKHtcbiAgICAgICAgICAgIHByaW1hcnlGZWVkOiBwcmltYXJ5LFxuICAgICAgICAgICAgc2Vjb25kYXJ5RmVlZDogc2Vjb25kYXJ5LFxuICAgICAgICAgICAgc2lkZWJhckZlZWRzOiBzaWRlYmFyLFxuICAgICAgICAgICAgbWljTXV0ZWQ6IHRoaXMucHJvcHMuY2FsbC5pc01pY3JvcGhvbmVNdXRlZCgpLFxuICAgICAgICAgICAgdmlkTXV0ZWQ6IHRoaXMucHJvcHMuY2FsbC5pc0xvY2FsVmlkZW9NdXRlZCgpLFxuICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkNhbGxMb2NhbEhvbGRVbmhvbGQgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIHRoaXMuc2V0U3RhdGUoe1xuICAgICAgICAgICAgaXNMb2NhbE9uSG9sZDogdGhpcy5wcm9wcy5jYWxsLmlzTG9jYWxPbkhvbGQoKSxcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25DYWxsUmVtb3RlSG9sZFVuaG9sZCA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICBpc1JlbW90ZU9uSG9sZDogdGhpcy5wcm9wcy5jYWxsLmlzUmVtb3RlT25Ib2xkKCksXG4gICAgICAgICAgICAvLyB1cGRhdGUgYm90aCBoZXJlIGJlY2F1c2UgaXNMb2NhbE9uSG9sZCBjaGFuZ2VzIHdoZW4gd2UgaG9sZCB0aGUgY2FsbCB0b29cbiAgICAgICAgICAgIGlzTG9jYWxPbkhvbGQ6IHRoaXMucHJvcHMuY2FsbC5pc0xvY2FsT25Ib2xkKCksXG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uTW91c2VNb3ZlID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICB0aGlzLmJ1dHRvbnNSZWYuY3VycmVudD8uc2hvd0NvbnRyb2xzKCk7XG4gICAgfTtcblxuICAgIHB1YmxpYyBzdGF0aWMgZ2V0T3JkZXJlZEZlZWRzKGZlZWRzOiBBcnJheTxDYWxsRmVlZD4pOiB7XG4gICAgICAgIHByaW1hcnk/OiBDYWxsRmVlZDtcbiAgICAgICAgc2Vjb25kYXJ5PzogQ2FsbEZlZWQ7XG4gICAgICAgIHNpZGViYXI6IEFycmF5PENhbGxGZWVkPjtcbiAgICB9IHtcbiAgICAgICAgaWYgKGZlZWRzLmxlbmd0aCA8PSAyKSB7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHByaW1hcnk6IGZlZWRzLmZpbmQoKGZlZWQpID0+ICFmZWVkLmlzTG9jYWwoKSksXG4gICAgICAgICAgICAgICAgc2Vjb25kYXJ5OiBmZWVkcy5maW5kKChmZWVkKSA9PiBmZWVkLmlzTG9jYWwoKSksXG4gICAgICAgICAgICAgICAgc2lkZWJhcjogW10sXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG5cbiAgICAgICAgbGV0IHByaW1hcnk6IENhbGxGZWVkIHwgdW5kZWZpbmVkO1xuXG4gICAgICAgIC8vIFRyeSB0byB1c2UgYSBzY3JlZW5zaGFyaW5nIGFzIHByaW1hcnksIGEgcmVtb3RlIG9uZSBpZiBwb3NzaWJsZVxuICAgICAgICBjb25zdCBzY3JlZW5zaGFyaW5nRmVlZHMgPSBmZWVkcy5maWx0ZXIoKGZlZWQpID0+IGZlZWQucHVycG9zZSA9PT0gU0RQU3RyZWFtTWV0YWRhdGFQdXJwb3NlLlNjcmVlbnNoYXJlKTtcbiAgICAgICAgcHJpbWFyeSA9IHNjcmVlbnNoYXJpbmdGZWVkcy5maW5kKChmZWVkKSA9PiAhZmVlZC5pc0xvY2FsKCkpIHx8IHNjcmVlbnNoYXJpbmdGZWVkc1swXTtcbiAgICAgICAgLy8gSWYgd2UgZGlkbid0IGZpbmQgcmVtb3RlIHNjcmVlbi1zaGFyaW5nIHN0cmVhbSwgdHJ5IHRvIGZpbmQgYW55IHJlbW90ZSBzdHJlYW1cbiAgICAgICAgaWYgKCFwcmltYXJ5KSB7XG4gICAgICAgICAgICBwcmltYXJ5ID0gZmVlZHMuZmluZCgoZmVlZCkgPT4gIWZlZWQuaXNMb2NhbCgpKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHNpZGViYXIgPSBbLi4uZmVlZHNdO1xuICAgICAgICAvLyBSZW1vdmUgdGhlIHByaW1hcnkgZmVlZCBmcm9tIHRoZSBhcnJheVxuICAgICAgICBpZiAocHJpbWFyeSkgc2lkZWJhci5zcGxpY2Uoc2lkZWJhci5pbmRleE9mKHByaW1hcnkpLCAxKTtcbiAgICAgICAgc2lkZWJhci5zb3J0KChhLCBiKSA9PiB7XG4gICAgICAgICAgICBpZiAoYS5pc0xvY2FsKCkgJiYgIWIuaXNMb2NhbCgpKSByZXR1cm4gLTE7XG4gICAgICAgICAgICBpZiAoIWEuaXNMb2NhbCgpICYmIGIuaXNMb2NhbCgpKSByZXR1cm4gMTtcbiAgICAgICAgICAgIHJldHVybiAwO1xuICAgICAgICB9KTtcblxuICAgICAgICByZXR1cm4geyBwcmltYXJ5LCBzaWRlYmFyIH07XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvbk1heGltaXplQ2xpY2sgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIGRpcy5kaXNwYXRjaCh7XG4gICAgICAgICAgICBhY3Rpb246IFwidmlkZW9fZnVsbHNjcmVlblwiLFxuICAgICAgICAgICAgZnVsbHNjcmVlbjogdHJ1ZSxcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25NaWNNdXRlQ2xpY2sgPSBhc3luYyAoKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gICAgICAgIGNvbnN0IG5ld1ZhbCA9ICF0aGlzLnN0YXRlLm1pY011dGVkO1xuICAgICAgICB0aGlzLnNldFN0YXRlKHsgbWljTXV0ZWQ6IGF3YWl0IHRoaXMucHJvcHMuY2FsbC5zZXRNaWNyb3Bob25lTXV0ZWQobmV3VmFsKSB9KTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvblZpZE11dGVDbGljayA9IGFzeW5jICgpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICAgICAgY29uc3QgbmV3VmFsID0gIXRoaXMuc3RhdGUudmlkTXV0ZWQ7XG4gICAgICAgIHRoaXMuc2V0U3RhdGUoeyB2aWRNdXRlZDogYXdhaXQgdGhpcy5wcm9wcy5jYWxsLnNldExvY2FsVmlkZW9NdXRlZChuZXdWYWwpIH0pO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uU2NyZWVuc2hhcmVDbGljayA9IGFzeW5jICgpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICAgICAgbGV0IGlzU2NyZWVuc2hhcmluZztcbiAgICAgICAgaWYgKHRoaXMuc3RhdGUuc2NyZWVuc2hhcmluZykge1xuICAgICAgICAgICAgaXNTY3JlZW5zaGFyaW5nID0gYXdhaXQgdGhpcy5wcm9wcy5jYWxsLnNldFNjcmVlbnNoYXJpbmdFbmFibGVkKGZhbHNlKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGlzU2NyZWVuc2hhcmluZyA9IGF3YWl0IHRoaXMucHJvcHMuY2FsbC5zZXRTY3JlZW5zaGFyaW5nRW5hYmxlZCh0cnVlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc2V0U3RhdGUoe1xuICAgICAgICAgICAgc2lkZWJhclNob3duOiB0cnVlLFxuICAgICAgICAgICAgc2NyZWVuc2hhcmluZzogaXNTY3JlZW5zaGFyaW5nLFxuICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgLy8gd2UgcmVnaXN0ZXIgZ2xvYmFsIHNob3J0Y3V0cyBoZXJlLCB0aGV5ICptdXN0IG5vdCBjb25mbGljdCogd2l0aCBsb2NhbCBzaG9ydGN1dHMgZWxzZXdoZXJlIG9yIGJvdGggd2lsbCBmaXJlXG4gICAgLy8gTm90ZSB0aGF0IHRoaXMgYXNzdW1lcyB3ZSBhbHdheXMgaGF2ZSBhIExlZ2FjeUNhbGxWaWV3IG9uIHNjcmVlbiBhdCBhbnkgZ2l2ZW4gdGltZVxuICAgIC8vIExlZ2FjeUNhbGxIYW5kbGVyIHdvdWxkIHByb2JhYmx5IGJlIGEgYmV0dGVyIHBsYWNlIGZvciB0aGlzXG4gICAgcHJpdmF0ZSBvbk5hdGl2ZUtleURvd24gPSAoZXY6IEtleWJvYXJkRXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgbGV0IGhhbmRsZWQgPSBmYWxzZTtcblxuICAgICAgICBjb25zdCBjYWxsQWN0aW9uID0gZ2V0S2V5QmluZGluZ3NNYW5hZ2VyKCkuZ2V0Q2FsbEFjdGlvbihldik7XG4gICAgICAgIHN3aXRjaCAoY2FsbEFjdGlvbikge1xuICAgICAgICAgICAgY2FzZSBLZXlCaW5kaW5nQWN0aW9uLlRvZ2dsZU1pY0luQ2FsbDpcbiAgICAgICAgICAgICAgICB0aGlzLm9uTWljTXV0ZUNsaWNrKCk7XG4gICAgICAgICAgICAgICAgLy8gc2hvdyB0aGUgY29udHJvbHMgdG8gZ2l2ZSBmZWVkYmFja1xuICAgICAgICAgICAgICAgIHRoaXMuYnV0dG9uc1JlZi5jdXJyZW50Py5zaG93Q29udHJvbHMoKTtcbiAgICAgICAgICAgICAgICBoYW5kbGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBicmVhaztcblxuICAgICAgICAgICAgY2FzZSBLZXlCaW5kaW5nQWN0aW9uLlRvZ2dsZVdlYmNhbUluQ2FsbDpcbiAgICAgICAgICAgICAgICB0aGlzLm9uVmlkTXV0ZUNsaWNrKCk7XG4gICAgICAgICAgICAgICAgLy8gc2hvdyB0aGUgY29udHJvbHMgdG8gZ2l2ZSBmZWVkYmFja1xuICAgICAgICAgICAgICAgIHRoaXMuYnV0dG9uc1JlZi5jdXJyZW50Py5zaG93Q29udHJvbHMoKTtcbiAgICAgICAgICAgICAgICBoYW5kbGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChoYW5kbGVkKSB7XG4gICAgICAgICAgICBldi5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgICAgIGV2LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkNhbGxSZXN1bWVDbGljayA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgY29uc3QgdXNlckZhY2luZ1Jvb21JZCA9IExlZ2FjeUNhbGxIYW5kbGVyLmluc3RhbmNlLnJvb21JZEZvckNhbGwodGhpcy5wcm9wcy5jYWxsKTtcbiAgICAgICAgaWYgKHVzZXJGYWNpbmdSb29tSWQpIExlZ2FjeUNhbGxIYW5kbGVyLmluc3RhbmNlLnNldEFjdGl2ZUNhbGxSb29tSWQodXNlckZhY2luZ1Jvb21JZCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25UcmFuc2ZlckNsaWNrID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICBjb25zdCB0cmFuc2ZlcmVlQ2FsbCA9IExlZ2FjeUNhbGxIYW5kbGVyLmluc3RhbmNlLmdldFRyYW5zZmVyZWVGb3JDYWxsSWQodGhpcy5wcm9wcy5jYWxsLmNhbGxJZCk7XG4gICAgICAgIGlmICh0cmFuc2ZlcmVlQ2FsbCkgdGhpcy5wcm9wcy5jYWxsLnRyYW5zZmVyVG9DYWxsKHRyYW5zZmVyZWVDYWxsKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkhhbmd1cENsaWNrID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICBjb25zdCByb29tSWQgPSBMZWdhY3lDYWxsSGFuZGxlci5pbnN0YW5jZS5yb29tSWRGb3JDYWxsKHRoaXMucHJvcHMuY2FsbCk7XG4gICAgICAgIGlmIChyb29tSWQpIExlZ2FjeUNhbGxIYW5kbGVyLmluc3RhbmNlLmhhbmd1cE9yUmVqZWN0KHJvb21JZCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25Ub2dnbGVTaWRlYmFyID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICB0aGlzLnNldFN0YXRlKHsgc2lkZWJhclNob3duOiAhdGhpcy5zdGF0ZS5zaWRlYmFyU2hvd24gfSk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgcmVuZGVyQ2FsbENvbnRyb2xzKCk6IEpTWC5FbGVtZW50IHtcbiAgICAgICAgY29uc3QgeyBjYWxsLCBwaXBNb2RlIH0gPSB0aGlzLnByb3BzO1xuICAgICAgICBjb25zdCB7IGNhbGxTdGF0ZSwgbWljTXV0ZWQsIHZpZE11dGVkLCBzY3JlZW5zaGFyaW5nLCBzaWRlYmFyU2hvd24sIHNlY29uZGFyeUZlZWQsIHNpZGViYXJGZWVkcyB9ID0gdGhpcy5zdGF0ZTtcblxuICAgICAgICAvLyBJZiBTRFBTdHJlYW1NZXRhZGF0YSBpc24ndCBzdXBwb3J0ZWQgZG9uJ3Qgc2hvdyB2aWRlbyBtdXRlIGJ1dHRvbiBpbiB2b2ljZSBjYWxsc1xuICAgICAgICBjb25zdCB2aWRNdXRlQnV0dG9uU2hvd24gPSBjYWxsLm9wcG9uZW50U3VwcG9ydHNTRFBTdHJlYW1NZXRhZGF0YSgpIHx8IGNhbGwuaGFzTG9jYWxVc2VyTWVkaWFWaWRlb1RyYWNrO1xuICAgICAgICAvLyBTY3JlZW5zaGFyaW5nIGlzIHBvc3NpYmxlLCBpZiB3ZSBjYW4gc2VuZCBhIHNlY29uZCBzdHJlYW0gYW5kXG4gICAgICAgIC8vIGlkZW50aWZ5IGl0IHVzaW5nIFNEUFN0cmVhbU1ldGFkYXRhIG9yIGlmIHdlIGNhbiByZXBsYWNlIHRoZSBhbHJlYWR5XG4gICAgICAgIC8vIGV4aXN0aW5nIHVzZXJtZWRpYSB0cmFjayBieSBhIHNjcmVlbnNoYXJpbmcgdHJhY2suIFdlIGFsc28gbmVlZCB0byBiZVxuICAgICAgICAvLyBjb25uZWN0ZWQgdG8ga25vdyB0aGUgc3RhdGUgb2YgdGhlIG90aGVyIHNpZGVcbiAgICAgICAgY29uc3Qgc2NyZWVuc2hhcmluZ0J1dHRvblNob3duID1cbiAgICAgICAgICAgIChjYWxsLm9wcG9uZW50U3VwcG9ydHNTRFBTdHJlYW1NZXRhZGF0YSgpIHx8IGNhbGwuaGFzTG9jYWxVc2VyTWVkaWFWaWRlb1RyYWNrKSAmJlxuICAgICAgICAgICAgY2FsbC5zdGF0ZSA9PT0gQ2FsbFN0YXRlLkNvbm5lY3RlZDtcbiAgICAgICAgLy8gU2hvdyB0aGUgc2lkZWJhciBidXR0b24gb25seSBpZiB0aGVyZSBpcyBzb21ldGhpbmcgdG8gaGlkZS9zaG93XG4gICAgICAgIGNvbnN0IHNpZGViYXJCdXR0b25TaG93biA9IChzZWNvbmRhcnlGZWVkICYmICFzZWNvbmRhcnlGZWVkLmlzVmlkZW9NdXRlZCgpKSB8fCBzaWRlYmFyRmVlZHMubGVuZ3RoID4gMDtcbiAgICAgICAgLy8gVGhlIGRpYWwgcGFkICYgJ21vcmUnIGJ1dHRvbiBhY3Rpb25zIGFyZSBvbmx5IHJlbGV2YW50IGluIGEgY29ubmVjdGVkIGNhbGxcbiAgICAgICAgY29uc3QgY29udGV4dE1lbnVCdXR0b25TaG93biA9IGNhbGxTdGF0ZSA9PT0gQ2FsbFN0YXRlLkNvbm5lY3RlZDtcbiAgICAgICAgY29uc3QgZGlhbHBhZEJ1dHRvblNob3duID0gY2FsbFN0YXRlID09PSBDYWxsU3RhdGUuQ29ubmVjdGVkICYmIGNhbGwub3Bwb25lbnRTdXBwb3J0c0RUTUYoKTtcblxuICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgPExlZ2FjeUNhbGxWaWV3QnV0dG9uc1xuICAgICAgICAgICAgICAgIHJlZj17dGhpcy5idXR0b25zUmVmfVxuICAgICAgICAgICAgICAgIGNhbGw9e2NhbGx9XG4gICAgICAgICAgICAgICAgcGlwTW9kZT17cGlwTW9kZX1cbiAgICAgICAgICAgICAgICBoYW5kbGVycz17e1xuICAgICAgICAgICAgICAgICAgICBvblRvZ2dsZVNpZGViYXJDbGljazogdGhpcy5vblRvZ2dsZVNpZGViYXIsXG4gICAgICAgICAgICAgICAgICAgIG9uU2NyZWVuc2hhcmVDbGljazogdGhpcy5vblNjcmVlbnNoYXJlQ2xpY2ssXG4gICAgICAgICAgICAgICAgICAgIG9uSGFuZ3VwQ2xpY2s6IHRoaXMub25IYW5ndXBDbGljayxcbiAgICAgICAgICAgICAgICAgICAgb25NaWNNdXRlQ2xpY2s6IHRoaXMub25NaWNNdXRlQ2xpY2ssXG4gICAgICAgICAgICAgICAgICAgIG9uVmlkTXV0ZUNsaWNrOiB0aGlzLm9uVmlkTXV0ZUNsaWNrLFxuICAgICAgICAgICAgICAgIH19XG4gICAgICAgICAgICAgICAgYnV0dG9uc1N0YXRlPXt7XG4gICAgICAgICAgICAgICAgICAgIG1pY011dGVkOiBtaWNNdXRlZCxcbiAgICAgICAgICAgICAgICAgICAgdmlkTXV0ZWQ6IHZpZE11dGVkLFxuICAgICAgICAgICAgICAgICAgICBzaWRlYmFyU2hvd246IHNpZGViYXJTaG93bixcbiAgICAgICAgICAgICAgICAgICAgc2NyZWVuc2hhcmluZzogc2NyZWVuc2hhcmluZyxcbiAgICAgICAgICAgICAgICB9fVxuICAgICAgICAgICAgICAgIGJ1dHRvbnNWaXNpYmlsaXR5PXt7XG4gICAgICAgICAgICAgICAgICAgIHZpZE11dGU6IHZpZE11dGVCdXR0b25TaG93bixcbiAgICAgICAgICAgICAgICAgICAgc2NyZWVuc2hhcmluZzogc2NyZWVuc2hhcmluZ0J1dHRvblNob3duLFxuICAgICAgICAgICAgICAgICAgICBzaWRlYmFyOiBzaWRlYmFyQnV0dG9uU2hvd24sXG4gICAgICAgICAgICAgICAgICAgIGNvbnRleHRNZW51OiBjb250ZXh0TWVudUJ1dHRvblNob3duLFxuICAgICAgICAgICAgICAgICAgICBkaWFscGFkOiBkaWFscGFkQnV0dG9uU2hvd24sXG4gICAgICAgICAgICAgICAgfX1cbiAgICAgICAgICAgIC8+XG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSByZW5kZXJUb2FzdCgpOiBKU1guRWxlbWVudCB8IG51bGwge1xuICAgICAgICBjb25zdCB7IGNhbGwgfSA9IHRoaXMucHJvcHM7XG4gICAgICAgIGNvbnN0IHNvbWVvbmVJc1NjcmVlbnNoYXJpbmcgPSBjYWxsLmdldEZlZWRzKCkuc29tZSgoZmVlZCkgPT4ge1xuICAgICAgICAgICAgcmV0dXJuIGZlZWQucHVycG9zZSA9PT0gU0RQU3RyZWFtTWV0YWRhdGFQdXJwb3NlLlNjcmVlbnNoYXJlO1xuICAgICAgICB9KTtcblxuICAgICAgICBpZiAoIXNvbWVvbmVJc1NjcmVlbnNoYXJpbmcpIHJldHVybiBudWxsO1xuXG4gICAgICAgIGNvbnN0IGlzU2NyZWVuc2hhcmluZyA9IGNhbGwuaXNTY3JlZW5zaGFyaW5nKCk7XG4gICAgICAgIGNvbnN0IHsgcHJpbWFyeUZlZWQsIHNpZGViYXJTaG93biB9ID0gdGhpcy5zdGF0ZTtcbiAgICAgICAgY29uc3Qgc2hhcmVyTmFtZSA9IHByaW1hcnlGZWVkPy5nZXRNZW1iZXIoKT8ubmFtZTtcbiAgICAgICAgaWYgKCFzaGFyZXJOYW1lKSByZXR1cm4gbnVsbDtcblxuICAgICAgICBsZXQgdGV4dCA9IGlzU2NyZWVuc2hhcmluZyA/IF90KFwidm9pcHx5b3VfYXJlX3ByZXNlbnRpbmdcIikgOiBfdChcInZvaXB8dXNlcl9pc19wcmVzZW50aW5nXCIsIHsgc2hhcmVyTmFtZSB9KTtcbiAgICAgICAgaWYgKCFzaWRlYmFyU2hvd24pIHtcbiAgICAgICAgICAgIHRleHQgKz0gXCIg4oCiIFwiICsgKGNhbGwuaXNMb2NhbFZpZGVvTXV0ZWQoKSA/IF90KFwidm9pcHxjYW1lcmFfZGlzYWJsZWRcIikgOiBfdChcInZvaXB8Y2FtZXJhX2VuYWJsZWRcIikpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIDxkaXYgY2xhc3NOYW1lPVwibXhfTGVnYWN5Q2FsbFZpZXdfdG9hc3RcIj57dGV4dH08L2Rpdj47XG4gICAgfVxuXG4gICAgcHJpdmF0ZSByZW5kZXJDb250ZW50KCk6IEpTWC5FbGVtZW50IHtcbiAgICAgICAgY29uc3QgeyBwaXBNb2RlLCBjYWxsLCBvblJlc2l6ZSB9ID0gdGhpcy5wcm9wcztcbiAgICAgICAgY29uc3QgeyBpc0xvY2FsT25Ib2xkLCBpc1JlbW90ZU9uSG9sZCwgc2lkZWJhclNob3duLCBwcmltYXJ5RmVlZCwgc2Vjb25kYXJ5RmVlZCwgc2lkZWJhckZlZWRzIH0gPSB0aGlzLnN0YXRlO1xuXG4gICAgICAgIGNvbnN0IGNhbGxSb29tSWQgPSBMZWdhY3lDYWxsSGFuZGxlci5pbnN0YW5jZS5yb29tSWRGb3JDYWxsKGNhbGwpO1xuICAgICAgICBjb25zdCBjYWxsUm9vbSA9IChjYWxsUm9vbUlkID8gTWF0cml4Q2xpZW50UGVnLnNhZmVHZXQoKS5nZXRSb29tKGNhbGxSb29tSWQpIDogdW5kZWZpbmVkKSA/PyB1bmRlZmluZWQ7XG4gICAgICAgIGNvbnN0IGF2YXRhclNpemUgPSBwaXBNb2