UNPKG

matrix-react-sdk

Version:
687 lines (662 loc) 105 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.RoomViewStore = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireDefault(require("react")); var utils = _interopRequireWildcard(require("matrix-js-sdk/src/utils")); var _matrix = require("matrix-js-sdk/src/matrix"); var _types = require("matrix-js-sdk/src/types"); var _logger = require("matrix-js-sdk/src/logger"); var _events = _interopRequireDefault(require("events")); var _RoomViewLifecycle = require("@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle"); var _MatrixClientPeg = require("../MatrixClientPeg"); var _Modal = _interopRequireDefault(require("../Modal")); var _languageHandler = require("../languageHandler"); var _RoomAliasCache = require("../RoomAliasCache"); var _actions = require("../dispatcher/actions"); var _promise = require("../utils/promise"); var _RoomContext = require("../contexts/RoomContext"); var _DMRoomMap = _interopRequireDefault(require("../utils/DMRoomMap")); var _spaces = require("./spaces"); var _ErrorDialog = _interopRequireDefault(require("../components/views/dialogs/ErrorDialog")); var _SettingsStore = _interopRequireDefault(require("../settings/SettingsStore")); var _RoomUpgrade = require("../utils/RoomUpgrade"); var _AsyncStore = require("./AsyncStore"); var _CallStore = require("./CallStore"); var _voiceBroadcast = require("../voice-broadcast"); var _showCantStartACallDialog = require("../voice-broadcast/utils/showCantStartACallDialog"); var _pauseNonLiveBroadcastFromOtherRoom = require("../voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom"); var _ModuleRunner = require("../modules/ModuleRunner"); var _notifications = require("../utils/notifications"); 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; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* Copyright 2024 New Vector Ltd. Copyright 2019-2022 The Matrix.org Foundation C.I.C. Copyright 2017, 2018 New Vector Ltd Copyright 2017 Vector Creations Ltd SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ const NUM_JOIN_RETRY = 5; const INITIAL_STATE = { joining: false, joinError: null, roomId: null, threadId: null, subscribingRoomId: null, initialEventId: null, initialEventPixelOffset: null, isInitialEventHighlighted: false, initialEventScrollIntoView: true, roomAlias: null, roomLoading: false, roomLoadError: null, replyingToEvent: null, shouldPeek: false, viaServers: [], wasContextSwitch: false, viewingCall: false, promptAskToJoin: false, viewRoomOpts: { buttons: [] } }; /** * A class for storing application state for RoomView. */ class RoomViewStore extends _events.default { constructor(dis, stores) { super(); // initialize state as a copy of the initial state. We need to copy else one RVS can talk to // another RVS via INITIAL_STATE as they share the same underlying object. Mostly relevant for tests. (0, _defineProperty2.default)(this, "state", utils.deepCopy(INITIAL_STATE)); (0, _defineProperty2.default)(this, "dis", void 0); (0, _defineProperty2.default)(this, "dispatchToken", void 0); (0, _defineProperty2.default)(this, "onCurrentBroadcastRecordingChanged", recording => { if (recording === null) { const room = this.stores.client?.getRoom(this.state.roomId || undefined); if (room) { this.doMaybeSetCurrentVoiceBroadcastPlayback(room); } } }); this.stores = stores; this.resetDispatcher(dis); this.stores.voiceBroadcastRecordingsStore.addListener(_voiceBroadcast.VoiceBroadcastRecordingsStoreEvent.CurrentChanged, this.onCurrentBroadcastRecordingChanged); } addRoomListener(roomId, fn) { this.on(roomId, fn); } removeRoomListener(roomId, fn) { this.off(roomId, fn); } emitForRoom(roomId, isActive) { this.emit(roomId, isActive); } setState(newState) { // If values haven't changed, there's nothing to do. // This only tries a shallow comparison, so unchanged objects will slip // through, but that's probably okay for now. let stateChanged = false; for (const key of Object.keys(newState)) { if (this.state[key] !== newState[key]) { stateChanged = true; break; } } if (!stateChanged) { return; } if (newState.viewingCall) { // Pause current broadcast, if any this.stores.voiceBroadcastPlaybacksStore.getCurrent()?.pause(); if (this.stores.voiceBroadcastRecordingsStore.getCurrent()) { (0, _showCantStartACallDialog.showCantStartACallDialog)(); newState.viewingCall = false; } } const lastRoomId = this.state.roomId; this.state = Object.assign(this.state, newState); if (lastRoomId !== this.state.roomId) { if (lastRoomId) this.emitForRoom(lastRoomId, false); if (this.state.roomId) this.emitForRoom(this.state.roomId, true); // Fired so we can reduce dependency on event emitters to this store, which is relatively // central to the application and can easily cause import cycles. this.dis?.dispatch({ action: _actions.Action.ActiveRoomChanged, oldRoomId: lastRoomId, newRoomId: this.state.roomId }); } this.emit(_AsyncStore.UPDATE_EVENT); } doMaybeSetCurrentVoiceBroadcastPlayback(room) { if (!this.stores.client) return; (0, _voiceBroadcast.doMaybeSetCurrentVoiceBroadcastPlayback)(room, this.stores.client, this.stores.voiceBroadcastPlaybacksStore, this.stores.voiceBroadcastRecordingsStore); } onRoomStateEvents(event) { const roomId = event.getRoomId?.(); // no room or not current room if (!roomId || roomId !== this.state.roomId) return; const room = this.stores.client?.getRoom(roomId); if (room) { this.doMaybeSetCurrentVoiceBroadcastPlayback(room); } } onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention switch (payload.action) { // view_room: // - room_alias: '#somealias:matrix.org' // - room_id: '!roomid123:matrix.org' // - event_id: '$213456782:matrix.org' // - event_offset: 100 // - highlighted: true case _actions.Action.ViewRoom: this.viewRoom(payload); break; case _actions.Action.ViewThread: this.viewThread(payload); break; // for these events blank out the roomId as we are no longer in the RoomView case "view_welcome_page": case _actions.Action.ViewHomePage: this.setState({ roomId: null, roomAlias: null, viaServers: [], wasContextSwitch: false, viewingCall: false }); (0, _voiceBroadcast.doClearCurrentVoiceBroadcastPlaybackIfStopped)(this.stores.voiceBroadcastPlaybacksStore); break; case "MatrixActions.RoomState.events": this.onRoomStateEvents(payload.event); break; case _actions.Action.ViewRoomError: this.viewRoomError(payload); break; case "will_join": this.setState({ joining: true }); break; case "cancel_join": this.setState({ joining: false }); break; // join_room: // - opts: options for joinRoom case _actions.Action.JoinRoom: this.joinRoom(payload); break; case _actions.Action.JoinRoomError: this.joinRoomError(payload); break; case _actions.Action.JoinRoomReady: { if (this.state.roomId === payload.roomId) { this.setState({ shouldPeek: false }); } (0, _RoomUpgrade.awaitRoomDownSync)(_MatrixClientPeg.MatrixClientPeg.safeGet(), payload.roomId).then(room => { const numMembers = room.getJoinedMemberCount(); const roomSize = numMembers > 1000 ? "MoreThanAThousand" : numMembers > 100 ? "OneHundredAndOneToAThousand" : numMembers > 10 ? "ElevenToOneHundred" : numMembers > 2 ? "ThreeToTen" : numMembers > 1 ? "Two" : "One"; this.stores.posthogAnalytics.trackEvent({ eventName: "JoinedRoom", trigger: payload.metricsTrigger, roomSize, isDM: !!_DMRoomMap.default.shared().getUserIdForRoomId(room.roomId), isSpace: room.isSpaceRoom() }); }); break; } case "on_client_not_viable": case _actions.Action.OnLoggedOut: this.reset(); break; case "reply_to_event": // Thread timeline view handles its own reply-to-state if (_RoomContext.TimelineRenderingType.Thread !== payload.context) { // If currently viewed room does not match the room in which we wish to reply then change rooms this // can happen when performing a search across all rooms. Persist the data from this event for both // room and search timeline rendering types, search will get auto-closed by RoomView at this time. if (payload.event && payload.event.getRoomId() !== this.state.roomId) { this.dis?.dispatch({ action: _actions.Action.ViewRoom, room_id: payload.event.getRoomId(), replyingToEvent: payload.event, metricsTrigger: undefined // room doesn't change }); } else { this.setState({ replyingToEvent: payload.event }); } } break; case _actions.Action.PromptAskToJoin: { this.setState({ promptAskToJoin: true }); break; } case _actions.Action.SubmitAskToJoin: { this.submitAskToJoin(payload); break; } case _actions.Action.CancelAskToJoin: { this.cancelAskToJoin(payload); break; } case _actions.Action.RoomLoaded: { this.setViewRoomOpts(); break; } } } async viewRoom(payload) { if (payload.room_id) { const room = _MatrixClientPeg.MatrixClientPeg.safeGet().getRoom(payload.room_id); if (payload.metricsTrigger !== null && payload.room_id !== this.state.roomId) { let activeSpace; if (this.stores.spaceStore.activeSpace === _spaces.MetaSpace.Home) { activeSpace = "Home"; } else if ((0, _spaces.isMetaSpace)(this.stores.spaceStore.activeSpace)) { activeSpace = "Meta"; } else { activeSpace = this.stores.spaceStore.activeSpaceRoom?.getJoinRule() === _matrix.JoinRule.Public ? "Public" : "Private"; } this.stores.posthogAnalytics.trackEvent({ eventName: "ViewRoom", trigger: payload.metricsTrigger, viaKeyboard: payload.metricsViaKeyboard, isDM: !!_DMRoomMap.default.shared().getUserIdForRoomId(payload.room_id), isSpace: room?.isSpaceRoom(), activeSpace }); } if (_SettingsStore.default.getValue("feature_sliding_sync") && this.state.roomId !== payload.room_id) { if (this.state.subscribingRoomId && this.state.subscribingRoomId !== payload.room_id) { // unsubscribe from this room, but don't await it as we don't care when this gets done. this.stores.slidingSyncManager.setRoomVisible(this.state.subscribingRoomId, false); } this.setState({ subscribingRoomId: payload.room_id, roomId: payload.room_id, initialEventId: null, initialEventPixelOffset: null, initialEventScrollIntoView: true, roomAlias: null, roomLoading: true, roomLoadError: null, viaServers: payload.via_servers, wasContextSwitch: payload.context_switch, viewingCall: payload.view_call ?? false }); // set this room as the room subscription. We need to await for it as this will fetch // all room state for this room, which is required before we get the state below. await this.stores.slidingSyncManager.setRoomVisible(payload.room_id, true); // Whilst we were subscribing another room was viewed, so stop what we're doing and // unsubscribe if (this.state.subscribingRoomId !== payload.room_id) { this.stores.slidingSyncManager.setRoomVisible(payload.room_id, false); return; } // Re-fire the payload: we won't re-process it because the prev room ID == payload room ID now this.dis?.dispatch(_objectSpread({}, payload)); return; } const newState = { roomId: payload.room_id, roomAlias: payload.room_alias ?? null, initialEventId: payload.event_id ?? null, isInitialEventHighlighted: payload.highlighted ?? false, initialEventScrollIntoView: payload.scroll_into_view ?? true, roomLoading: false, roomLoadError: null, // should peek by default shouldPeek: payload.should_peek === undefined ? true : payload.should_peek, // have we sent a join request for this room and are waiting for a response? joining: payload.joining || false, // Reset replyingToEvent because we don't want cross-room because bad UX replyingToEvent: null, viaServers: payload.via_servers ?? [], wasContextSwitch: payload.context_switch ?? false, skipLobby: payload.skipLobby, viewingCall: payload.view_call ?? (payload.room_id === this.state.roomId ? this.state.viewingCall : _CallStore.CallStore.instance.getActiveCall(payload.room_id) !== null) }; // Allow being given an event to be replied to when switching rooms but sanity check its for this room if (payload.replyingToEvent?.getRoomId() === payload.room_id) { newState.replyingToEvent = payload.replyingToEvent; } else if (this.state.replyingToEvent?.getRoomId() === payload.room_id) { // if the reply-to matches the desired room, e.g visiting a permalink then maintain replyingToEvent // See https://github.com/vector-im/element-web/issues/21462 newState.replyingToEvent = this.state.replyingToEvent; } this.setState(newState); if (payload.auto_join) { this.dis?.dispatch(_objectSpread(_objectSpread({}, payload), {}, { action: _actions.Action.JoinRoom, roomId: payload.room_id, metricsTrigger: payload.metricsTrigger })); } if (room) { (0, _pauseNonLiveBroadcastFromOtherRoom.pauseNonLiveBroadcastFromOtherRoom)(room, this.stores.voiceBroadcastPlaybacksStore); this.doMaybeSetCurrentVoiceBroadcastPlayback(room); await (0, _notifications.setMarkedUnreadState)(room, _MatrixClientPeg.MatrixClientPeg.safeGet(), false); } } else if (payload.room_alias) { // Try the room alias to room ID navigation cache first to avoid // blocking room navigation on the homeserver. let roomId = (0, _RoomAliasCache.getCachedRoomIDForAlias)(payload.room_alias); if (!roomId) { // Room alias cache miss, so let's ask the homeserver. Resolve the alias // and then do a second dispatch with the room ID acquired. this.setState({ roomId: null, initialEventId: null, initialEventPixelOffset: null, isInitialEventHighlighted: false, initialEventScrollIntoView: true, roomAlias: payload.room_alias, roomLoading: true, roomLoadError: null, viaServers: payload.via_servers, wasContextSwitch: payload.context_switch, viewingCall: payload.view_call ?? false, skipLobby: payload.skipLobby }); try { const result = await _MatrixClientPeg.MatrixClientPeg.safeGet().getRoomIdForAlias(payload.room_alias); (0, _RoomAliasCache.storeRoomAliasInCache)(payload.room_alias, result.room_id); roomId = result.room_id; } catch (err) { _logger.logger.error("RVS failed to get room id for alias: ", err); this.dis?.dispatch({ action: _actions.Action.ViewRoomError, room_id: null, room_alias: payload.room_alias, err: err instanceof _matrix.MatrixError ? err : undefined }); return; } } // Re-fire the payload with the newly found room_id this.dis?.dispatch(_objectSpread(_objectSpread({}, payload), {}, { room_id: roomId })); } } viewThread(payload) { this.setState({ threadId: payload.thread_id }); } viewRoomError(payload) { this.setState({ roomId: payload.room_id, roomAlias: payload.room_alias, roomLoading: false, roomLoadError: payload.err }); } async joinRoom(payload) { this.setState({ joining: true }); // take a copy of roomAlias & roomId as they may change by the time the join is complete const { roomAlias, roomId = payload.roomId } = this.state; const address = roomAlias || roomId; const viaServers = this.state.viaServers || []; try { const cli = _MatrixClientPeg.MatrixClientPeg.safeGet(); await (0, _promise.retry)(() => cli.joinRoom(address, _objectSpread({ viaServers }, payload.opts || {})), NUM_JOIN_RETRY, err => { // if we received a Gateway timeout or Cloudflare timeout then retry return err.httpStatus === 504 || err.httpStatus === 524; }); // We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not // have come down the sync stream yet, and that's the point at which we'd consider the user joined to the // room. this.dis?.dispatch({ action: _actions.Action.JoinRoomReady, roomId: roomId, metricsTrigger: payload.metricsTrigger }); } catch (err) { this.dis?.dispatch({ action: _actions.Action.JoinRoomError, roomId, err, canAskToJoin: payload.canAskToJoin }); if (payload.canAskToJoin) { this.dis?.dispatch({ action: _actions.Action.PromptAskToJoin }); } } } getInvitingUserId(roomId) { const cli = _MatrixClientPeg.MatrixClientPeg.safeGet(); const room = cli.getRoom(roomId); if (room?.getMyMembership() === _types.KnownMembership.Invite) { const myMember = room.getMember(cli.getSafeUserId()); const inviteEvent = myMember ? myMember.events.member : null; return inviteEvent?.getSender(); } } showJoinRoomError(err, roomId) { let description = err.message ? err.message : JSON.stringify(err); _logger.logger.log("Failed to join room:", description); if (err.name === "ConnectionError") { description = (0, _languageHandler._t)("room|error_join_connection"); } else if (err.errcode === "M_INCOMPATIBLE_ROOM_VERSION") { description = /*#__PURE__*/_react.default.createElement("div", null, (0, _languageHandler._t)("room|error_join_incompatible_version_1"), /*#__PURE__*/_react.default.createElement("br", null), (0, _languageHandler._t)("room|error_join_incompatible_version_2")); } else if (err.httpStatus === 404) { const invitingUserId = this.getInvitingUserId(roomId); // provide a better error message for invites if (invitingUserId) { // if the inviting user is on the same HS, there can only be one cause: they left. if (invitingUserId.endsWith(`:${_MatrixClientPeg.MatrixClientPeg.safeGet().getDomain()}`)) { description = (0, _languageHandler._t)("room|error_join_404_invite_same_hs"); } else { description = (0, _languageHandler._t)("room|error_join_404_invite"); } } // provide a more detailed error than "No known servers" when attempting to // join using a room ID and no via servers if (roomId === this.state.roomId && this.state.viaServers.length === 0) { description = /*#__PURE__*/_react.default.createElement("div", null, (0, _languageHandler._t)("room|error_join_404_1"), /*#__PURE__*/_react.default.createElement("br", null), /*#__PURE__*/_react.default.createElement("br", null), (0, _languageHandler._t)("room|error_join_404_2")); } } _Modal.default.createDialog(_ErrorDialog.default, { title: (0, _languageHandler._t)("room|error_join_title"), description }); } joinRoomError(payload) { this.setState({ joining: false, joinError: payload.err }); if (payload.err && !payload.canAskToJoin) { this.showJoinRoomError(payload.err, payload.roomId); } } reset() { this.state = Object.assign({}, INITIAL_STATE); } /** * Reset which dispatcher should be used to listen for actions. The old dispatcher will be * unregistered. * @param dis The new dispatcher to use. */ resetDispatcher(dis) { if (this.dispatchToken) { this.dis?.unregister(this.dispatchToken); } this.dis = dis; if (dis) { // Some tests mock the dispatcher file resulting in an empty defaultDispatcher // so rather than dying here, just ignore it. When we no longer mock files like this, // we should remove the null check. this.dispatchToken = this.dis.register(this.onDispatch.bind(this)); } } // The room ID of the room currently being viewed getRoomId() { return this.state.roomId; } getThreadId() { return this.state.threadId; } // The event to scroll to when the room is first viewed getInitialEventId() { return this.state.initialEventId; } // Whether to highlight the initial event isInitialEventHighlighted() { return this.state.isInitialEventHighlighted; } // Whether to avoid jumping to the initial event initialEventScrollIntoView() { return this.state.initialEventScrollIntoView; } // The room alias of the room (or null if not originally specified in view_room) getRoomAlias() { return this.state.roomAlias; } // Whether the current room is loading (true whilst resolving an alias) isRoomLoading() { return this.state.roomLoading; } // Any error that has occurred during loading getRoomLoadError() { return this.state.roomLoadError; } // True if we're expecting the user to be joined to the room currently being // viewed. Note that this is left true after the join request has finished, // since we should still consider a join to be in progress until the room // & member events come down the sync. // // This flag remains true after the room has been successfully joined, // (this store doesn't listen for the appropriate member events) // so you should always observe the joined state from the member event // if a room object is present. // ie. The correct logic is: // if (room) { // if (myMember.membership == 'joined') { // // user is joined to the room // } else { // // Not joined // } // } else { // if (this.stores.roomViewStore.isJoining()) { // // show spinner // } else { // // show join prompt // } // } isJoining() { return this.state.joining; } // Any error that has occurred during joining getJoinError() { return this.state.joinError; } // The mxEvent if one is currently being replied to/quoted getQuotingEvent() { return this.state.replyingToEvent; } shouldPeek() { return this.state.shouldPeek; } getWasContextSwitch() { return this.state.wasContextSwitch; } isViewingCall() { return this.state.viewingCall; } skipCallLobby() { return this.state.skipLobby; } /** * Gets the current state of the 'promptForAskToJoin' property. * * @returns {boolean} The value of the 'promptForAskToJoin' property. */ promptAskToJoin() { return this.state.promptAskToJoin; } /** * Submits a request to join a room by sending a knock request. * * @param {SubmitAskToJoinPayload} payload - The payload containing information to submit the request. * @returns {void} */ submitAskToJoin(payload) { _MatrixClientPeg.MatrixClientPeg.safeGet().knockRoom(payload.roomId, _objectSpread({ viaServers: this.state.viaServers }, payload.opts)).catch(err => _Modal.default.createDialog(_ErrorDialog.default, { title: (0, _languageHandler._t)("room|error_join_title"), description: err.httpStatus === 403 ? (0, _languageHandler._t)("room|error_join_403") : err.message })).finally(() => this.setState({ promptAskToJoin: false })); } /** * Cancels a request to join a room by sending a leave request. * * @param {CancelAskToJoinPayload} payload - The payload containing information to cancel the request. * @returns {void} */ cancelAskToJoin(payload) { _MatrixClientPeg.MatrixClientPeg.safeGet().leave(payload.roomId).catch(err => _Modal.default.createDialog(_ErrorDialog.default, { title: (0, _languageHandler._t)("room|error_cancel_knock_title"), description: err.message })); } /** * Gets the current state of the 'viewRoomOpts' property. * * @returns {ViewRoomOpts} The value of the 'viewRoomOpts' property. */ getViewRoomOpts() { return this.state.viewRoomOpts; } /** * Invokes the view room lifecycle to set the view room options. * * @returns {void} */ setViewRoomOpts() { const viewRoomOpts = { buttons: [] }; _ModuleRunner.ModuleRunner.instance.invoke(_RoomViewLifecycle.RoomViewLifecycle.ViewRoom, viewRoomOpts, this.getRoomId()); this.setState({ viewRoomOpts }); } } exports.RoomViewStore = RoomViewStore; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwicmVxdWlyZSIsInV0aWxzIiwiX2ludGVyb3BSZXF1aXJlV2lsZGNhcmQiLCJfbWF0cml4IiwiX3R5cGVzIiwiX2xvZ2dlciIsIl9ldmVudHMiLCJfUm9vbVZpZXdMaWZlY3ljbGUiLCJfTWF0cml4Q2xpZW50UGVnIiwiX01vZGFsIiwiX2xhbmd1YWdlSGFuZGxlciIsIl9Sb29tQWxpYXNDYWNoZSIsIl9hY3Rpb25zIiwiX3Byb21pc2UiLCJfUm9vbUNvbnRleHQiLCJfRE1Sb29tTWFwIiwiX3NwYWNlcyIsIl9FcnJvckRpYWxvZyIsIl9TZXR0aW5nc1N0b3JlIiwiX1Jvb21VcGdyYWRlIiwiX0FzeW5jU3RvcmUiLCJfQ2FsbFN0b3JlIiwiX3ZvaWNlQnJvYWRjYXN0IiwiX3Nob3dDYW50U3RhcnRBQ2FsbERpYWxvZyIsIl9wYXVzZU5vbkxpdmVCcm9hZGNhc3RGcm9tT3RoZXJSb29tIiwiX01vZHVsZVJ1bm5lciIsIl9ub3RpZmljYXRpb25zIiwiX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlIiwiZSIsIldlYWtNYXAiLCJyIiwidCIsIl9fZXNNb2R1bGUiLCJkZWZhdWx0IiwiaGFzIiwiZ2V0IiwibiIsIl9fcHJvdG9fXyIsImEiLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsInUiLCJoYXNPd25Qcm9wZXJ0eSIsImNhbGwiLCJpIiwic2V0Iiwib3duS2V5cyIsImtleXMiLCJnZXRPd25Qcm9wZXJ0eVN5bWJvbHMiLCJvIiwiZmlsdGVyIiwiZW51bWVyYWJsZSIsInB1c2giLCJhcHBseSIsIl9vYmplY3RTcHJlYWQiLCJhcmd1bWVudHMiLCJsZW5ndGgiLCJmb3JFYWNoIiwiX2RlZmluZVByb3BlcnR5MiIsImdldE93blByb3BlcnR5RGVzY3JpcHRvcnMiLCJkZWZpbmVQcm9wZXJ0aWVzIiwiTlVNX0pPSU5fUkVUUlkiLCJJTklUSUFMX1NUQVRFIiwiam9pbmluZyIsImpvaW5FcnJvciIsInJvb21JZCIsInRocmVhZElkIiwic3Vic2NyaWJpbmdSb29tSWQiLCJpbml0aWFsRXZlbnRJZCIsImluaXRpYWxFdmVudFBpeGVsT2Zmc2V0IiwiaXNJbml0aWFsRXZlbnRIaWdobGlnaHRlZCIsImluaXRpYWxFdmVudFNjcm9sbEludG9WaWV3Iiwicm9vbUFsaWFzIiwicm9vbUxvYWRpbmciLCJyb29tTG9hZEVycm9yIiwicmVwbHlpbmdUb0V2ZW50Iiwic2hvdWxkUGVlayIsInZpYVNlcnZlcnMiLCJ3YXNDb250ZXh0U3dpdGNoIiwidmlld2luZ0NhbGwiLCJwcm9tcHRBc2tUb0pvaW4iLCJ2aWV3Um9vbU9wdHMiLCJidXR0b25zIiwiUm9vbVZpZXdTdG9yZSIsIkV2ZW50RW1pdHRlciIsImNvbnN0cnVjdG9yIiwiZGlzIiwic3RvcmVzIiwiZGVlcENvcHkiLCJyZWNvcmRpbmciLCJyb29tIiwiY2xpZW50IiwiZ2V0Um9vbSIsInN0YXRlIiwidW5kZWZpbmVkIiwiZG9NYXliZVNldEN1cnJlbnRWb2ljZUJyb2FkY2FzdFBsYXliYWNrIiwicmVzZXREaXNwYXRjaGVyIiwidm9pY2VCcm9hZGNhc3RSZWNvcmRpbmdzU3RvcmUiLCJhZGRMaXN0ZW5lciIsIlZvaWNlQnJvYWRjYXN0UmVjb3JkaW5nc1N0b3JlRXZlbnQiLCJDdXJyZW50Q2hhbmdlZCIsIm9uQ3VycmVudEJyb2FkY2FzdFJlY29yZGluZ0NoYW5nZWQiLCJhZGRSb29tTGlzdGVuZXIiLCJmbiIsIm9uIiwicmVtb3ZlUm9vbUxpc3RlbmVyIiwib2ZmIiwiZW1pdEZvclJvb20iLCJpc0FjdGl2ZSIsImVtaXQiLCJzZXRTdGF0ZSIsIm5ld1N0YXRlIiwic3RhdGVDaGFuZ2VkIiwia2V5Iiwidm9pY2VCcm9hZGNhc3RQbGF5YmFja3NTdG9yZSIsImdldEN1cnJlbnQiLCJwYXVzZSIsInNob3dDYW50U3RhcnRBQ2FsbERpYWxvZyIsImxhc3RSb29tSWQiLCJhc3NpZ24iLCJkaXNwYXRjaCIsImFjdGlvbiIsIkFjdGlvbiIsIkFjdGl2ZVJvb21DaGFuZ2VkIiwib2xkUm9vbUlkIiwibmV3Um9vbUlkIiwiVVBEQVRFX0VWRU5UIiwib25Sb29tU3RhdGVFdmVudHMiLCJldmVudCIsImdldFJvb21JZCIsIm9uRGlzcGF0Y2giLCJwYXlsb2FkIiwiVmlld1Jvb20iLCJ2aWV3Um9vbSIsIlZpZXdUaHJlYWQiLCJ2aWV3VGhyZWFkIiwiVmlld0hvbWVQYWdlIiwiZG9DbGVhckN1cnJlbnRWb2ljZUJyb2FkY2FzdFBsYXliYWNrSWZTdG9wcGVkIiwiVmlld1Jvb21FcnJvciIsInZpZXdSb29tRXJyb3IiLCJKb2luUm9vbSIsImpvaW5Sb29tIiwiSm9pblJvb21FcnJvciIsImpvaW5Sb29tRXJyb3IiLCJKb2luUm9vbVJlYWR5IiwiYXdhaXRSb29tRG93blN5bmMiLCJNYXRyaXhDbGllbnRQZWciLCJzYWZlR2V0IiwidGhlbiIsIm51bU1lbWJlcnMiLCJnZXRKb2luZWRNZW1iZXJDb3VudCIsInJvb21TaXplIiwicG9zdGhvZ0FuYWx5dGljcyIsInRyYWNrRXZlbnQiLCJldmVudE5hbWUiLCJ0cmlnZ2VyIiwibWV0cmljc1RyaWdnZXIiLCJpc0RNIiwiRE1Sb29tTWFwIiwic2hhcmVkIiwiZ2V0VXNlcklkRm9yUm9vbUlkIiwiaXNTcGFjZSIsImlzU3BhY2VSb29tIiwiT25Mb2dnZWRPdXQiLCJyZXNldCIsIlRpbWVsaW5lUmVuZGVyaW5nVHlwZSIsIlRocmVhZCIsImNvbnRleHQiLCJyb29tX2lkIiwiUHJvbXB0QXNrVG9Kb2luIiwiU3VibWl0QXNrVG9Kb2luIiwic3VibWl0QXNrVG9Kb2luIiwiQ2FuY2VsQXNrVG9Kb2luIiwiY2FuY2VsQXNrVG9Kb2luIiwiUm9vbUxvYWRlZCIsInNldFZpZXdSb29tT3B0cyIsImFjdGl2ZVNwYWNlIiwic3BhY2VTdG9yZSIsIk1ldGFTcGFjZSIsIkhvbWUiLCJpc01ldGFTcGFjZSIsImFjdGl2ZVNwYWNlUm9vbSIsImdldEpvaW5SdWxlIiwiSm9pblJ1bGUiLCJQdWJsaWMiLCJ2aWFLZXlib2FyZCIsIm1ldHJpY3NWaWFLZXlib2FyZCIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsInNsaWRpbmdTeW5jTWFuYWdlciIsInNldFJvb21WaXNpYmxlIiwidmlhX3NlcnZlcnMiLCJjb250ZXh0X3N3aXRjaCIsInZpZXdfY2FsbCIsInJvb21fYWxpYXMiLCJldmVudF9pZCIsImhpZ2hsaWdodGVkIiwic2Nyb2xsX2ludG9fdmlldyIsInNob3VsZF9wZWVrIiwic2tpcExvYmJ5IiwiQ2FsbFN0b3JlIiwiaW5zdGFuY2UiLCJnZXRBY3RpdmVDYWxsIiwiYXV0b19qb2luIiwicGF1c2VOb25MaXZlQnJvYWRjYXN0RnJvbU90aGVyUm9vbSIsInNldE1hcmtlZFVucmVhZFN0YXRlIiwiZ2V0Q2FjaGVkUm9vbUlERm9yQWxpYXMiLCJyZXN1bHQiLCJnZXRSb29tSWRGb3JBbGlhcyIsInN0b3JlUm9vbUFsaWFzSW5DYWNoZSIsImVyciIsImxvZ2dlciIsImVycm9yIiwiTWF0cml4RXJyb3IiLCJ0aHJlYWRfaWQiLCJhZGRyZXNzIiwiY2xpIiwicmV0cnkiLCJvcHRzIiwiaHR0cFN0YXR1cyIsImNhbkFza1RvSm9pbiIsImdldEludml0aW5nVXNlcklkIiwiZ2V0TXlNZW1iZXJzaGlwIiwiS25vd25NZW1iZXJzaGlwIiwiSW52aXRlIiwibXlNZW1iZXIiLCJnZXRNZW1iZXIiLCJnZXRTYWZlVXNlcklkIiwiaW52aXRlRXZlbnQiLCJldmVudHMiLCJtZW1iZXIiLCJnZXRTZW5kZXIiLCJzaG93Sm9pblJvb21FcnJvciIsImRlc2NyaXB0aW9uIiwibWVzc2FnZSIsIkpTT04iLCJzdHJpbmdpZnkiLCJsb2ciLCJuYW1lIiwiX3QiLCJlcnJjb2RlIiwiY3JlYXRlRWxlbWVudCIsImludml0aW5nVXNlcklkIiwiZW5kc1dpdGgiLCJnZXREb21haW4iLCJNb2RhbCIsImNyZWF0ZURpYWxvZyIsIkVycm9yRGlhbG9nIiwidGl0bGUiLCJkaXNwYXRjaFRva2VuIiwidW5yZWdpc3RlciIsInJlZ2lzdGVyIiwiYmluZCIsImdldFRocmVhZElkIiwiZ2V0SW5pdGlhbEV2ZW50SWQiLCJnZXRSb29tQWxpYXMiLCJpc1Jvb21Mb2FkaW5nIiwiZ2V0Um9vbUxvYWRFcnJvciIsImlzSm9pbmluZyIsImdldEpvaW5FcnJvciIsImdldFF1b3RpbmdFdmVudCIsImdldFdhc0NvbnRleHRTd2l0Y2giLCJpc1ZpZXdpbmdDYWxsIiwic2tpcENhbGxMb2JieSIsImtub2NrUm9vbSIsImNhdGNoIiwiZmluYWxseSIsImxlYXZlIiwiZ2V0Vmlld1Jvb21PcHRzIiwiTW9kdWxlUnVubmVyIiwiaW52b2tlIiwiUm9vbVZpZXdMaWZlY3ljbGUiLCJleHBvcnRzIl0sInNvdXJjZXMiOlsiLi4vLi4vc3JjL3N0b3Jlcy9Sb29tVmlld1N0b3JlLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAxOS0yMDIyIFRoZSBNYXRyaXgub3JnIEZvdW5kYXRpb24gQy5JLkMuXG5Db3B5cmlnaHQgMjAxNywgMjAxOCBOZXcgVmVjdG9yIEx0ZFxuQ29weXJpZ2h0IDIwMTcgVmVjdG9yIENyZWF0aW9ucyBMdGRcblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuaW1wb3J0IFJlYWN0LCB7IFJlYWN0Tm9kZSB9IGZyb20gXCJyZWFjdFwiO1xuaW1wb3J0ICogYXMgdXRpbHMgZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3V0aWxzXCI7XG5pbXBvcnQgeyBNYXRyaXhFcnJvciwgSm9pblJ1bGUsIFJvb20sIE1hdHJpeEV2ZW50IH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL21hdHJpeFwiO1xuaW1wb3J0IHsgS25vd25NZW1iZXJzaGlwIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3R5cGVzXCI7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbG9nZ2VyXCI7XG5pbXBvcnQgeyBWaWV3Um9vbSBhcyBWaWV3Um9vbUV2ZW50IH0gZnJvbSBcIkBtYXRyaXgtb3JnL2FuYWx5dGljcy1ldmVudHMvdHlwZXMvdHlwZXNjcmlwdC9WaWV3Um9vbVwiO1xuaW1wb3J0IHsgSm9pbmVkUm9vbSBhcyBKb2luZWRSb29tRXZlbnQgfSBmcm9tIFwiQG1hdHJpeC1vcmcvYW5hbHl0aWNzLWV2ZW50cy90eXBlcy90eXBlc2NyaXB0L0pvaW5lZFJvb21cIjtcbmltcG9ydCB7IE9wdGlvbmFsIH0gZnJvbSBcIm1hdHJpeC1ldmVudHMtc2RrXCI7XG5pbXBvcnQgRXZlbnRFbWl0dGVyIGZyb20gXCJldmVudHNcIjtcbmltcG9ydCB7IFJvb21WaWV3TGlmZWN5Y2xlLCBWaWV3Um9vbU9wdHMgfSBmcm9tIFwiQG1hdHJpeC1vcmcvcmVhY3Qtc2RrLW1vZHVsZS1hcGkvbGliL2xpZmVjeWNsZXMvUm9vbVZpZXdMaWZlY3ljbGVcIjtcblxuaW1wb3J0IHsgTWF0cml4RGlzcGF0Y2hlciB9IGZyb20gXCIuLi9kaXNwYXRjaGVyL2Rpc3BhdGNoZXJcIjtcbmltcG9ydCB7IE1hdHJpeENsaWVudFBlZyB9IGZyb20gXCIuLi9NYXRyaXhDbGllbnRQZWdcIjtcbmltcG9ydCBNb2RhbCBmcm9tIFwiLi4vTW9kYWxcIjtcbmltcG9ydCB7IF90IH0gZnJvbSBcIi4uL2xhbmd1YWdlSGFuZGxlclwiO1xuaW1wb3J0IHsgZ2V0Q2FjaGVkUm9vbUlERm9yQWxpYXMsIHN0b3JlUm9vbUFsaWFzSW5DYWNoZSB9IGZyb20gXCIuLi9Sb29tQWxpYXNDYWNoZVwiO1xuaW1wb3J0IHsgQWN0aW9uIH0gZnJvbSBcIi4uL2Rpc3BhdGNoZXIvYWN0aW9uc1wiO1xuaW1wb3J0IHsgcmV0cnkgfSBmcm9tIFwiLi4vdXRpbHMvcHJvbWlzZVwiO1xuaW1wb3J0IHsgVGltZWxpbmVSZW5kZXJpbmdUeXBlIH0gZnJvbSBcIi4uL2NvbnRleHRzL1Jvb21Db250ZXh0XCI7XG5pbXBvcnQgeyBWaWV3Um9vbVBheWxvYWQgfSBmcm9tIFwiLi4vZGlzcGF0Y2hlci9wYXlsb2Fkcy9WaWV3Um9vbVBheWxvYWRcIjtcbmltcG9ydCBETVJvb21NYXAgZnJvbSBcIi4uL3V0aWxzL0RNUm9vbU1hcFwiO1xuaW1wb3J0IHsgaXNNZXRhU3BhY2UsIE1ldGFTcGFjZSB9IGZyb20gXCIuL3NwYWNlc1wiO1xuaW1wb3J0IHsgSm9pblJvb21QYXlsb2FkIH0gZnJvbSBcIi4uL2Rpc3BhdGNoZXIvcGF5bG9hZHMvSm9pblJvb21QYXlsb2FkXCI7XG5pbXBvcnQgeyBKb2luUm9vbVJlYWR5UGF5bG9hZCB9IGZyb20gXCIuLi9kaXNwYXRjaGVyL3BheWxvYWRzL0pvaW5Sb29tUmVhZHlQYXlsb2FkXCI7XG5pbXBvcnQgeyBKb2luUm9vbUVycm9yUGF5bG9hZCB9IGZyb20gXCIuLi9kaXNwYXRjaGVyL3BheWxvYWRzL0pvaW5Sb29tRXJyb3JQYXlsb2FkXCI7XG5pbXBvcnQgeyBWaWV3Um9vbUVycm9yUGF5bG9hZCB9IGZyb20gXCIuLi9kaXNwYXRjaGVyL3BheWxvYWRzL1ZpZXdSb29tRXJyb3JQYXlsb2FkXCI7XG5pbXBvcnQgRXJyb3JEaWFsb2cgZnJvbSBcIi4uL2NvbXBvbmVudHMvdmlld3MvZGlhbG9ncy9FcnJvckRpYWxvZ1wiO1xuaW1wb3J0IHsgQWN0aXZlUm9vbUNoYW5nZWRQYXlsb2FkIH0gZnJvbSBcIi4uL2Rpc3BhdGNoZXIvcGF5bG9hZHMvQWN0aXZlUm9vbUNoYW5nZWRQYXlsb2FkXCI7XG5pbXBvcnQgU2V0dGluZ3NTdG9yZSBmcm9tIFwiLi4vc2V0dGluZ3MvU2V0dGluZ3NTdG9yZVwiO1xuaW1wb3J0IHsgYXdhaXRSb29tRG93blN5bmMgfSBmcm9tIFwiLi4vdXRpbHMvUm9vbVVwZ3JhZGVcIjtcbmltcG9ydCB7IFVQREFURV9FVkVOVCB9IGZyb20gXCIuL0FzeW5jU3RvcmVcIjtcbmltcG9ydCB7IFNka0NvbnRleHRDbGFzcyB9IGZyb20gXCIuLi9jb250ZXh0cy9TREtDb250ZXh0XCI7XG5pbXBvcnQgeyBDYWxsU3RvcmUgfSBmcm9tIFwiLi9DYWxsU3RvcmVcIjtcbmltcG9ydCB7IFRocmVhZFBheWxvYWQgfSBmcm9tIFwiLi4vZGlzcGF0Y2hlci9wYXlsb2Fkcy9UaHJlYWRQYXlsb2FkXCI7XG5pbXBvcnQge1xuICAgIGRvQ2xlYXJDdXJyZW50Vm9pY2VCcm9hZGNhc3RQbGF5YmFja0lmU3RvcHBlZCxcbiAgICBkb01heWJlU2V0Q3VycmVudFZvaWNlQnJvYWRjYXN0UGxheWJhY2ssXG4gICAgVm9pY2VCcm9hZGNhc3RSZWNvcmRpbmcsXG4gICAgVm9pY2VCcm9hZGNhc3RSZWNvcmRpbmdzU3RvcmVFdmVudCxcbn0gZnJvbSBcIi4uL3ZvaWNlLWJyb2FkY2FzdFwiO1xuaW1wb3J0IHsgSVJvb21TdGF0ZUV2ZW50c0FjdGlvblBheWxvYWQgfSBmcm9tIFwiLi4vYWN0aW9ucy9NYXRyaXhBY3Rpb25DcmVhdG9yc1wiO1xuaW1wb3J0IHsgc2hvd0NhbnRTdGFydEFDYWxsRGlhbG9nIH0gZnJvbSBcIi4uL3ZvaWNlLWJyb2FkY2FzdC91dGlscy9zaG93Q2FudFN0YXJ0QUNhbGxEaWFsb2dcIjtcbmltcG9ydCB7IHBhdXNlTm9uTGl2ZUJyb2FkY2FzdEZyb21PdGhlclJvb20gfSBmcm9tIFwiLi4vdm9pY2UtYnJvYWRjYXN0L3V0aWxzL3BhdXNlTm9uTGl2ZUJyb2FkY2FzdEZyb21PdGhlclJvb21cIjtcbmltcG9ydCB7IEFjdGlvblBheWxvYWQgfSBmcm9tIFwiLi4vZGlzcGF0Y2hlci9wYXlsb2Fkc1wiO1xuaW1wb3J0IHsgQ2FuY2VsQXNrVG9Kb2luUGF5bG9hZCB9IGZyb20gXCIuLi9kaXNwYXRjaGVyL3BheWxvYWRzL0NhbmNlbEFza1RvSm9pblBheWxvYWRcIjtcbmltcG9ydCB7IFN1Ym1pdEFza1RvSm9pblBheWxvYWQgfSBmcm9tIFwiLi4vZGlzcGF0Y2hlci9wYXlsb2Fkcy9TdWJtaXRBc2tUb0pvaW5QYXlsb2FkXCI7XG5pbXBvcnQgeyBNb2R1bGVSdW5uZXIgfSBmcm9tIFwiLi4vbW9kdWxlcy9Nb2R1bGVSdW5uZXJcIjtcbmltcG9ydCB7IHNldE1hcmtlZFVucmVhZFN0YXRlIH0gZnJvbSBcIi4uL3V0aWxzL25vdGlmaWNhdGlvbnNcIjtcblxuY29uc3QgTlVNX0pPSU5fUkVUUlkgPSA1O1xuXG5pbnRlcmZhY2UgU3RhdGUge1xuICAgIC8qKlxuICAgICAqIFdoZXRoZXIgd2UncmUgam9pbmluZyB0aGUgY3VycmVudGx5IHZpZXdlZCAoc2VlIGlzSm9pbmluZygpKVxuICAgICAqL1xuICAgIGpvaW5pbmc6IGJvb2xlYW47XG4gICAgLyoqXG4gICAgICogQW55IGVycm9yIHRoYXQgaGFzIG9jY3VycmVkIGR1cmluZyBqb2luaW5nXG4gICAgICovXG4gICAgam9pbkVycm9yOiBFcnJvciB8IG51bGw7XG4gICAgLyoqXG4gICAgICogVGhlIElEIG9mIHRoZSByb29tIGN1cnJlbnRseSBiZWluZyB2aWV3ZWRcbiAgICAgKi9cbiAgICByb29tSWQ6IHN0cmluZyB8IG51bGw7XG4gICAgLyoqXG4gICAgICogVGhlIElEIG9mIHRoZSB0aHJlYWQgY3VycmVudGx5IGJlaW5nIHZpZXdlZFxuICAgICAqL1xuICAgIHRocmVhZElkOiBzdHJpbmcgfCBudWxsO1xuICAgIC8qKlxuICAgICAqIFRoZSBJRCBvZiB0aGUgcm9vbSBiZWluZyBzdWJzY3JpYmVkIHRvIChpbiBTbGlkaW5nIFN5bmMpXG4gICAgICovXG4gICAgc3Vic2NyaWJpbmdSb29tSWQ6IHN0cmluZyB8IG51bGw7XG4gICAgLyoqXG4gICAgICogVGhlIGV2ZW50IHRvIHNjcm9sbCB0byB3aGVuIHRoZSByb29tIGlzIGZpcnN0IHZpZXdlZFxuICAgICAqL1xuICAgIGluaXRpYWxFdmVudElkOiBzdHJpbmcgfCBudWxsO1xuICAgIGluaXRpYWxFdmVudFBpeGVsT2Zmc2V0OiBudW1iZXIgfCBudWxsO1xuICAgIC8qKlxuICAgICAqIFdoZXRoZXIgdG8gaGlnaGxpZ2h0IHRoZSBpbml0aWFsIGV2ZW50XG4gICAgICovXG4gICAgaXNJbml0aWFsRXZlbnRIaWdobGlnaHRlZDogYm9vbGVhbjtcbiAgICAvKipcbiAgICAgKiBXaGV0aGVyIHRvIHNjcm9sbCB0aGUgaW5pdGlhbCBldmVudCBpbnRvIHZpZXdcbiAgICAgKi9cbiAgICBpbml0aWFsRXZlbnRTY3JvbGxJbnRvVmlldzogYm9vbGVhbjtcbiAgICAvKipcbiAgICAgKiBUaGUgYWxpYXMgb2YgdGhlIHJvb20gKG9yIG51bGwgaWYgbm90IG9yaWdpbmFsbHkgc3BlY2lmaWVkIGluIHZpZXdfcm9vbSlcbiAgICAgKi9cbiAgICByb29tQWxpYXM6IHN0cmluZyB8IG51bGw7XG4gICAgLyoqXG4gICAgICogV2hldGhlciB0aGUgY3VycmVudCByb29tIGlzIGxvYWRpbmdcbiAgICAgKi9cbiAgICByb29tTG9hZGluZzogYm9vbGVhbjtcbiAgICAvKipcbiAgICAgKiBBbnkgZXJyb3IgdGhhdCBoYXMgb2NjdXJyZWQgZHVyaW5nIGxvYWRpbmdcbiAgICAgKi9cbiAgICByb29tTG9hZEVycm9yOiBNYXRyaXhFcnJvciB8IG51bGw7XG4gICAgcmVwbHlpbmdUb0V2ZW50OiBNYXRyaXhFdmVudCB8IG51bGw7XG4gICAgc2hvdWxkUGVlazogYm9vbGVhbjtcbiAgICB2aWFTZXJ2ZXJzOiBzdHJpbmdbXTtcbiAgICB3YXNDb250ZXh0U3dpdGNoOiBib29sZWFuO1xuICAgIC8qKlxuICAgICAqIFdoZXRoZXIgd2UncmUgdmlld2luZyBhIGNhbGwgb3IgY2FsbCBsb2JieSBpbiB0aGlzIHJvb21cbiAgICAgKi9cbiAgICB2aWV3aW5nQ2FsbDogYm9vbGVhbjtcbiAgICAvKipcbiAgICAgKiBJZiB3ZSB3YW50IHRoZSBjYWxsIHRvIHNraXAgdGhlIGxvYmJ5IGFuZCBpbW1lZGlhdGVseSBqb2luXG4gICAgICovXG4gICAgc2tpcExvYmJ5PzogYm9vbGVhbjtcblxuICAgIHByb21wdEFza1RvSm9pbjogYm9vbGVhbjtcblxuICAgIHZpZXdSb29tT3B0czogVmlld1Jvb21PcHRzO1xufVxuXG5jb25zdCBJTklUSUFMX1NUQVRFOiBTdGF0ZSA9IHtcbiAgICBqb2luaW5nOiBmYWxzZSxcbiAgICBqb2luRXJyb3I6IG51bGwsXG4gICAgcm9vbUlkOiBudWxsLFxuICAgIHRocmVhZElkOiBudWxsLFxuICAgIHN1YnNjcmliaW5nUm9vbUlkOiBudWxsLFxuICAgIGluaXRpYWxFdmVudElkOiBudWxsLFxuICAgIGluaXRpYWxFdmVudFBpeGVsT2Zmc2V0OiBudWxsLFxuICAgIGlzSW5pdGlhbEV2ZW50SGlnaGxpZ2h0ZWQ6IGZhbHNlLFxuICAgIGluaXRpYWxFdmVudFNjcm9sbEludG9WaWV3OiB0cnVlLFxuICAgIHJvb21BbGlhczogbnVsbCxcbiAgICByb29tTG9hZGluZzogZmFsc2UsXG4gICAgcm9vbUxvYWRFcnJvcjogbnVsbCxcbiAgICByZXBseWluZ1RvRXZlbnQ6IG51bGwsXG4gICAgc2hvdWxkUGVlazogZmFsc2UsXG4gICAgdmlhU2VydmVyczogW10sXG4gICAgd2FzQ29udGV4dFN3aXRjaDogZmFsc2UsXG4gICAgdmlld2luZ0NhbGw6IGZhbHNlLFxuICAgIHByb21wdEFza1RvSm9pbjogZmFsc2UsXG4gICAgdmlld1Jvb21PcHRzOiB7IGJ1dHRvbnM6IFtdIH0sXG59O1xuXG50eXBlIExpc3RlbmVyID0gKGlzQWN0aXZlOiBib29sZWFuKSA9PiB2b2lkO1xuXG4vKipcbiAqIEEgY2xhc3MgZm9yIHN0b3JpbmcgYXBwbGljYXRpb24gc3RhdGUgZm9yIFJvb21WaWV3LlxuICovXG5leHBvcnQgY2xhc3MgUm9vbVZpZXdTdG9yZSBleHRlbmRzIEV2ZW50RW1pdHRlciB7XG4gICAgLy8gaW5pdGlhbGl6ZSBzdGF0ZSBhcyBhIGNvcHkgb2YgdGhlIGluaXRpYWwgc3RhdGUuIFdlIG5lZWQgdG8gY29weSBlbHNlIG9uZSBSVlMgY2FuIHRhbGsgdG9cbiAgICAvLyBhbm90aGVyIFJWUyB2aWEgSU5JVElBTF9TVEFURSBhcyB0aGV5IHNoYXJlIHRoZSBzYW1lIHVuZGVybHlpbmcgb2JqZWN0LiBNb3N0bHkgcmVsZXZhbnQgZm9yIHRlc3RzLlxuICAgIHByaXZhdGUgc3RhdGUgPSB1dGlscy5kZWVwQ29weShJTklUSUFMX1NUQVRFKTtcblxuICAgIHByaXZhdGUgZGlzPzogTWF0cml4RGlzcGF0Y2hlcjtcbiAgICBwcml2YXRlIGRpc3BhdGNoVG9rZW4/OiBzdHJpbmc7XG5cbiAgICBwdWJsaWMgY29uc3RydWN0b3IoXG4gICAgICAgIGRpczogTWF0cml4RGlzcGF0Y2hlcixcbiAgICAgICAgcHJpdmF0ZSByZWFkb25seSBzdG9yZXM6IFNka0NvbnRleHRDbGFzcyxcbiAgICApIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5yZXNldERpc3BhdGNoZXIoZGlzKTtcbiAgICAgICAgdGhpcy5zdG9yZXMudm9pY2VCcm9hZGNhc3RSZWNvcmRpbmdzU3RvcmUuYWRkTGlzdGVuZXIoXG4gICAgICAgICAgICBWb2ljZUJyb2FkY2FzdFJlY29yZGluZ3NTdG9yZUV2ZW50LkN1cnJlbnRDaGFuZ2VkLFxuICAgICAgICAgICAgdGhpcy5vbkN1cnJlbnRCcm9hZGNhc3RSZWNvcmRpbmdDaGFuZ2VkLFxuICAgICAgICApO1xuICAgIH1cblxuICAgIHB1YmxpYyBhZGRSb29tTGlzdGVuZXIocm9vbUlkOiBzdHJpbmcsIGZuOiBMaXN0ZW5lcik6IHZvaWQge1xuICAgICAgICB0aGlzLm9uKHJvb21JZCwgZm4pO1xuICAgIH1cblxuICAgIHB1YmxpYyByZW1vdmVSb29tTGlzdGVuZXIocm9vbUlkOiBzdHJpbmcsIGZuOiBMaXN0ZW5lcik6IHZvaWQge1xuICAgICAgICB0aGlzLm9mZihyb29tSWQsIGZuKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIGVtaXRGb3JSb29tKHJvb21JZDogc3RyaW5nLCBpc0FjdGl2ZTogYm9vbGVhbik6IHZvaWQge1xuICAgICAgICB0aGlzLmVtaXQocm9vbUlkLCBpc0FjdGl2ZSk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvbkN1cnJlbnRCcm9hZGNhc3RSZWNvcmRpbmdDaGFuZ2VkID0gKHJlY29yZGluZzogVm9pY2VCcm9hZGNhc3RSZWNvcmRpbmcgfCBudWxsKTogdm9pZCA9PiB7XG4gICAgICAgIGlmIChyZWNvcmRpbmcgPT09IG51bGwpIHtcbiAgICAgICAgICAgIGNvbnN0IHJvb20gPSB0aGlzLnN0b3Jlcy5jbGllbnQ/LmdldFJvb20odGhpcy5zdGF0ZS5yb29tSWQgfHwgdW5kZWZpbmVkKTtcblxuICAgICAgICAgICAgaWYgKHJvb20pIHtcbiAgICAgICAgICAgICAgICB0aGlzLmRvTWF5YmVTZXRDdXJyZW50Vm9pY2VCcm9hZGNhc3RQbGF5YmFjayhyb29tKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwcml2YXRlIHNldFN0YXRlKG5ld1N0YXRlOiBQYXJ0aWFsPFN0YXRlPik6IHZvaWQge1xuICAgICAgICAvLyBJZiB2YWx1ZXMgaGF2ZW4ndCBjaGFuZ2VkLCB0aGVyZSdzIG5vdGhpbmcgdG8gZG8uXG4gICAgICAgIC8vIFRoaXMgb25seSB0cmllcyBhIHNoYWxsb3cgY29tcGFyaXNvbiwgc28gdW5jaGFuZ2VkIG9iamVjdHMgd2lsbCBzbGlwXG4gICAgICAgIC8vIHRocm91Z2gsIGJ1dCB0aGF0J3MgcHJvYmFibHkgb2theSBmb3Igbm93LlxuICAgICAgICBsZXQgc3RhdGVDaGFuZ2VkID0gZmFsc2U7XG4gICAgICAgIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKG5ld1N0YXRlKSkge1xuICAgICAgICAgICAgaWYgKHRoaXMuc3RhdGVba2V5IGFzIGtleW9mIFN0YXRlXSAhPT0gbmV3U3RhdGVba2V5IGFzIGtleW9mIFN0YXRlXSkge1xuICAgICAgICAgICAgICAgIHN0YXRlQ2hhbmdlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFzdGF0ZUNoYW5nZWQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChuZXdTdGF0ZS52aWV3aW5nQ2FsbCkge1xuICAgICAgICAgICAgLy8gUGF1c2UgY3VycmVudCBicm9hZGNhc3QsIGlmIGFueVxuICAgICAgICAgICAgdGhpcy5zdG9yZXMudm9pY2VCcm9hZGNhc3RQbGF5YmFja3NTdG9yZS5nZXRDdXJyZW50KCk/LnBhdXNlKCk7XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnN0b3Jlcy52b2ljZUJyb2FkY2FzdFJlY29yZGluZ3NTdG9yZS5nZXRDdXJyZW50KCkpIHtcbiAgICAgICAgICAgICAgICBzaG93Q2FudFN0YXJ0QUNhbGxEaWFsb2coKTtcbiAgICAgICAgICAgICAgICBuZXdTdGF0ZS52aWV3aW5nQ2FsbCA9IGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgbGFzdFJvb21JZCA9IHRoaXMuc3RhdGUucm9vbUlkO1xuICAgICAgICB0aGlzLnN0YXRlID0gT2JqZWN0LmFzc2lnbih0aGlzLnN0YXRlLCBuZXdTdGF0ZSk7XG4gICAgICAgIGlmIChsYXN0Um9vbUlkICE9PSB0aGlzLnN0YXRlLnJvb21JZCkge1xuICAgICAgICAgICAgaWYgKGxhc3RSb29tSWQpIHRoaXMuZW1pdEZvclJvb20obGFzdFJvb21JZCwgZmFsc2UpO1xuICAgICAgICAgICAgaWYgKHRoaXMuc3RhdGUucm9vbUlkKSB0aGlzLmVtaXRGb3JSb29tKHRoaXMuc3RhdGUucm9vbUlkLCB0cnVlKTtcblxuICAgICAgICAgICAgLy8gRmlyZWQgc28gd2UgY2FuIHJlZHVjZSBkZXBlbmRlbmN5IG9uIGV2ZW50IGVtaXR0ZXJzIHRvIHRoaXMgc3RvcmUsIHdoaWNoIGlzIHJlbGF0aXZlbHlcbiAgICAgICAgICAgIC8vIGNlbnRyYWwgdG8gdGhlIGFwcGxpY2F0aW9uIGFuZCBjYW4gZWFzaWx5IGNhdXNlIGltcG9ydCBjeWNsZXMuXG4gICAgICAgICAgICB0aGlzLmRpcz8uZGlzcGF0Y2g8QWN0aXZlUm9vbUNoYW5nZWRQYXlsb2FkPih7XG4gICAgICAgICAgICAgICAgYWN0aW9uOiBBY3Rpb24uQWN0aXZlUm9vbUNoYW5nZWQsXG4gICAgICAgICAgICAgICAgb2xkUm9vbUlkOiBsYXN0Um9vbUlkLFxuICAgICAgICAgICAgICAgIG5ld1Jvb21JZDogdGhpcy5zdGF0ZS5yb29tSWQsXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuZW1pdChVUERBVEVfRVZFTlQpO1xuICAgIH1cblxuICAgIHByaXZhdGUgZG9NYXliZVNldEN1cnJlbnRWb2ljZUJyb2FkY2FzdFBsYXliYWNrKHJvb206IFJvb20pOiB2b2lkIHtcbiAgICAgICAgaWYgKCF0aGlzLnN0b3Jlcy5jbGllbnQpIHJldHVybjtcbiAgICAgICAgZG9NYXliZVNldEN1cnJlbnRWb2ljZUJyb2FkY2FzdFBsYXliYWNrKFxuICAgICAgICAgICAgcm9vbSxcbiAgICAgICAgICAgIHRoaXMuc3RvcmVzLmNsaWVudCxcbiAgICAgICAgICAgIHRoaXMuc3RvcmVzLnZvaWNlQnJvYWRjYXN0UGxheWJhY2tzU3RvcmUsXG4gICAgICAgICAgICB0aGlzLnN0b3Jlcy52b2ljZUJyb2FkY2FzdFJlY29yZGluZ3NTdG9yZSxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIG9uUm9vbVN0YXRlRXZlbnRzKGV2ZW50OiBNYXRyaXhFdmVudCk6IHZvaWQge1xuICAgICAgICBjb25zdCByb29tSWQgPSBldmVudC5nZXRSb29tSWQ/LigpO1xuXG4gICAgICAgIC8vIG5vIHJvb20gb3Igbm90IGN1cnJlbnQgcm9vbVxuICAgICAgICBpZiAoIXJvb21JZCB8fCByb29tSWQgIT09IHRoaXMuc3RhdGUucm9vbUlkKSByZXR1cm47XG5cbiAgICAgICAgY29uc3Qgcm9vbSA9IHRoaXMuc3RvcmVzLmNsaWVudD8uZ2V0Um9vbShyb29tSWQpO1xuXG4gICAgICAgIGlmIChyb29tKSB7XG4gICAgICAgICAgICB0aGlzLmRvTWF5YmVTZXRDdXJyZW50Vm9pY2VCcm9hZGNhc3RQbGF5YmFjayhyb29tKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHByaXZhdGUgb25EaXNwYXRjaChwYXlsb2FkOiBBY3Rpb25QYXlsb2FkKTogdm9pZCB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25hbWluZy1jb252ZW50aW9uXG4gICAgICAgIHN3aXRjaCAocGF5bG9hZC5hY3Rpb24pIHtcbiAgICAgICAgICAgIC8vIHZpZXdfcm9vbTpcbiAgICAgICAgICAgIC8vICAgICAgLSByb29tX2FsaWFzOiAgICcjc29tZWFsaWFzOm1hdHJpeC5vcmcnXG4gICAgICAgICAgICAvLyAgICAgIC0gcm9vbV9pZDogICAgICAnIXJvb21pZDEyMzptYXRyaXgub3JnJ1xuICAgICAgICAgICAgLy8gICAgICAtIGV2ZW50X2lkOiAgICAgJyQyMTM0NTY3ODI6bWF0cml4Lm9yZydcbiAgICAgICAgICAgIC8vICAgICAgLSBldmVudF9vZmZzZXQ6IDEwMFxuICAgICAgICAgICAgLy8gICAgICAtIGhpZ2hsaWdodGVkOiAgdHJ1ZVxuICAgICAgICAgICAgY2FzZSBBY3Rpb24uVmlld1Jvb206XG4gICAgICAgICAgICAgICAgdGhpcy52aWV3Um9vbShwYXlsb2FkIGFzIFZpZXdSb29tUGF5bG9hZCk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIEFjdGlvbi5WaWV3VGhyZWFkOlxuICAgICAgICAgICAgICAgIHRoaXMudmlld1RocmVhZChwYXlsb2FkIGFzIFRocmVhZFBheWxvYWQpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgLy8gZm9yIHRoZXNlIGV2ZW50cyBibGFuayBvdXQgdGhlIHJvb21JZCBhcyB3ZSBhcmUgbm8gbG9uZ2VyIGluIHRoZSBSb29tVmlld1xuICAgICAgICAgICAgY2FzZSBcInZpZXdfd2VsY29tZV9wYWdlXCI6XG4gICAgICAgICAgICBjYXNlIEFjdGlvbi5WaWV3SG9tZVBhZ2U6XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICAgICAgICAgIHJvb21JZDogbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgcm9vbUFsaWFzOiBudWxsLFxuICAgICAgICAgICAgICAgICAgICB2aWFTZXJ2ZXJzOiBbXSxcbiAgICAgICAgICAgICAgICAgICAgd2FzQ29udGV4dFN3aXRjaDogZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIHZpZXdpbmdDYWxsOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICBkb0NsZWFyQ3VycmVudFZvaWNlQnJvYWRjYXN0UGxheWJhY2tJZlN0b3BwZWQodGhpcy5zdG9yZXMudm9pY2VCcm9hZGNhc3RQbGF5YmFja3NTdG9yZSk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIFwiTWF0cml4QWN0aW9ucy5Sb29tU3RhdGUuZXZlbnRzXCI6XG4gICAgICAgICAgICAgICAgdGhpcy5vblJvb21TdGF0ZUV2ZW50cygocGF5bG9hZCBhcyBJUm9vbVN0YXRlRXZlbnRzQWN0aW9uUGF5bG9hZCkuZXZlbnQpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgY2FzZSBBY3Rpb24uVmlld1Jvb21FcnJvcjpcbiAgICAgICAgICAgICAgICB0aGlzLnZpZXdSb29tRXJyb3IocGF5bG9hZCBhcyBWaWV3Um9vbUVycm9yUGF5bG9hZCk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIFwid2lsbF9qb2luXCI6XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRTdGF0ZSh7XG4gICAgICAgICAgICAgICAgICAgIGpvaW5pbmc6IHRydWUsXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIFwiY2FuY2VsX2pvaW5cIjpcbiAgICAgICAgICAgICAgICB0aGlzLnNldFN0YXRlKHtcbiAgICAgICAgICAgICAgICAgICAgam9pbmluZzogZmFsc2UsXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAvLyBqb2luX3Jvb206XG4gICAgICAgICAgICAvLyAgICAgIC0gb3B0czogb3B0aW9ucyBmb3Igam9pblJvb21cbiAgICAgICAgICAgIGNhc2UgQWN0aW9uLkpvaW5Sb29tOlxuICAgICAgICAgICAgICAgIHRoaXMuam9pblJvb20ocGF5bG9hZCBhcyBKb2luUm9vbVBheWxvYWQpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgY2FzZSBBY3Rpb24uSm9pblJvb21FcnJvcjpcbiAgICAgICAgICAgICAgICB0aGlzLmpvaW5Sb29tRXJyb3IocGF5bG9hZCBhcyBKb2luUm9vbUVycm9yUGF5bG9hZCk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIEFjdGlvbi5Kb2luUm9vbVJlYWR5OiB7XG4gICAgICAgICAgICAgICAgaWYgKHRoaXMuc3RhdGUucm9vbUlkID09PSBwYXlsb2FkLnJvb21JZCkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnNldFN0YXRlKHsgc2hvdWxkUGVlazogZmFsc2UgfSk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgYXdhaXRSb29tRG93blN5bmMoTWF0cml4Q2xpZW50UGVnLnNhZmVHZXQoKSwgcGF5bG9hZC5yb29tSWQpLnRoZW4oKHJvb20pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgbnVtTWVtYmVycyA9IHJvb20uZ2V0Sm9pbmVkTWVtYmVyQ291bnQoKTtcbiAgICAgICAgICAgICAgICAgICAgY29uc3Qgcm9vbVNpemUgPVxuICAgICAgICAgICAgICAgICAgICAgICAgbnVtTWVtYmVycyA+IDEwMDBcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICA/IFwiTW9yZVRoYW5BVGhvdXNhbmRcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDogbnVtTWVtYmVycyA+IDEwMFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPyBcIk9uZUh1bmRyZWRBbmRPbmVUb0FUaG91c2FuZFwiXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IG51bU1lbWJlcnMgPiAxMFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA/IFwiRWxldmVuVG9PbmVIdW5kcmVkXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgOiBudW1NZW1iZXJzID4gMlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID8gXCJUaHJlZVRvVGVuXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IG51bU1lbWJlcnMgPiAxXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA/IFwiVHdvXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDogXCJPbmVcIjtcblxuICAgICAgICAgICAgICAgICAgICB0aGlzLnN0b3Jlcy5wb3N0aG9nQW5hbHl0aWNzLnRyYWNrRXZlbnQ8Sm9pbmVkUm9vbUV2ZW50Pih7XG4gICAgICAgICAgICAgICAgICAgICAgICBldmVudE5hbWU6IFwiSm9pbmVkUm9vbVwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgdHJpZ2dlcjogcG