UNPKG

matrix-react-sdk

Version:
474 lines (453 loc) 74.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useWidgets = exports.default = void 0; var _react = require("react"); var _rfc = require("rfc4648"); 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 _call = require("matrix-js-sdk/src/webrtc/call"); var _randomstring = require("matrix-js-sdk/src/randomstring"); var _PlatformPeg = _interopRequireDefault(require("../PlatformPeg")); var _SdkConfig = _interopRequireDefault(require("../SdkConfig")); var _dispatcher = _interopRequireDefault(require("../dispatcher/dispatcher")); var _WidgetEchoStore = _interopRequireDefault(require("../stores/WidgetEchoStore")); var _IntegrationManagers = require("../integrations/IntegrationManagers"); var _WidgetType = require("../widgets/WidgetType"); var _Jitsi = require("../widgets/Jitsi"); var _objects = require("./objects"); var _languageHandler = require("../languageHandler"); var _WidgetStore = _interopRequireWildcard(require("../stores/WidgetStore")); var _UrlUtils = require("./UrlUtils"); var _useEventEmitter = require("../hooks/useEventEmitter"); var _WidgetLayoutStore = require("../stores/widgets/WidgetLayoutStore"); 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 2017-2020 The Matrix.org Foundation C.I.C. Copyright 2019 Travis Ralston SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ // How long we wait for the state event echo to come back from the server // before waitFor[Room/User]Widget rejects its promise const WIDGET_WAIT_TIME = 20000; class WidgetUtils { /** * Returns true if user is able to send state events to modify widgets in this room * (Does not apply to non-room-based / user widgets) * @param client The matrix client of the logged-in user * @param roomId -- The ID of the room to check * @return Boolean -- true if the user can modify widgets in this room * @throws Error -- specifies the error reason */ static canUserModifyWidgets(client, roomId) { if (!roomId) { _logger.logger.warn("No room ID specified"); return false; } if (!client) { _logger.logger.warn("User must be be logged in"); return false; } const room = client.getRoom(roomId); if (!room) { _logger.logger.warn(`Room ID ${roomId} is not recognised`); return false; } const me = client.getUserId(); if (!me) { _logger.logger.warn("Failed to get user ID"); return false; } if (room.getMyMembership() !== _types.KnownMembership.Join) { _logger.logger.warn(`User ${me} is not in room ${roomId}`); return false; } // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) return room.currentState.maySendStateEvent("im.vector.modular.widgets", me); } // TODO: Generify the name of this function. It's not just scalar. /** * Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api * @param matrixClient The matrix client of the logged-in user * @param {[type]} testUrlString URL to check * @return {Boolean} True if specified URL is a scalar URL */ static isScalarUrl(testUrlString) { if (!testUrlString) { _logger.logger.error("Scalar URL check failed. No URL specified"); return false; } const testUrl = (0, _UrlUtils.parseUrl)(testUrlString); let scalarUrls = _SdkConfig.default.get().integrations_widgets_urls; if (!scalarUrls || scalarUrls.length === 0) { const defaultManager = _IntegrationManagers.IntegrationManagers.sharedInstance().getPrimaryManager(); if (defaultManager) { scalarUrls = [defaultManager.apiUrl]; } else { scalarUrls = []; } } for (let i = 0; i < scalarUrls.length; i++) { const scalarUrl = (0, _UrlUtils.parseUrl)(scalarUrls[i]); if (testUrl && scalarUrl) { if (testUrl.protocol === scalarUrl.protocol && testUrl.host === scalarUrl.host && scalarUrl.pathname && testUrl.pathname?.startsWith(scalarUrl.pathname)) { return true; } } } return false; } /** * Returns a promise that resolves when a widget with the given * ID has been added as a user widget (ie. the accountData event * arrives) or rejects after a timeout * * @param client The matrix client of the logged-in user * @param widgetId The ID of the widget to wait for * @param add True to wait for the widget to be added, * false to wait for it to be deleted. * @returns {Promise} that resolves when the widget is in the * requested state according to the `add` param */ static waitForUserWidget(client, widgetId, add) { return new Promise((resolve, reject) => { // Tests an account data event, returning true if it's in the state // we're waiting for it to be in function eventInIntendedState(ev) { if (!ev) return false; if (add) { return ev.getContent()[widgetId] !== undefined; } else { return ev.getContent()[widgetId] === undefined; } } const startingAccountDataEvent = client.getAccountData("m.widgets"); if (eventInIntendedState(startingAccountDataEvent)) { resolve(); return; } function onAccountData(ev) { const currentAccountDataEvent = client.getAccountData("m.widgets"); if (eventInIntendedState(currentAccountDataEvent)) { client.removeListener(_matrix.ClientEvent.AccountData, onAccountData); clearTimeout(timerId); resolve(); } } const timerId = window.setTimeout(() => { client.removeListener(_matrix.ClientEvent.AccountData, onAccountData); reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear")); }, WIDGET_WAIT_TIME); client.on(_matrix.ClientEvent.AccountData, onAccountData); }); } /** * Returns a promise that resolves when a widget with the given * ID has been added as a room widget in the given room (ie. the * room state event arrives) or rejects after a timeout * * @param client The matrix client of the logged-in user * @param {string} widgetId The ID of the widget to wait for * @param {string} roomId The ID of the room to wait for the widget in * @param {boolean} add True to wait for the widget to be added, * false to wait for it to be deleted. * @returns {Promise} that resolves when the widget is in the * requested state according to the `add` param */ static waitForRoomWidget(client, widgetId, roomId, add) { return new Promise((resolve, reject) => { // Tests a list of state events, returning true if it's in the state // we're waiting for it to be in function eventsInIntendedState(evList) { const widgetPresent = evList?.some(ev => { return ev.getContent() && ev.getContent()["id"] === widgetId; }); if (add) { return !!widgetPresent; } else { return !widgetPresent; } } const room = client.getRoom(roomId); // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) const startingWidgetEvents = room?.currentState.getStateEvents("im.vector.modular.widgets"); if (eventsInIntendedState(startingWidgetEvents)) { resolve(); return; } function onRoomStateEvents(ev) { if (ev.getRoomId() !== roomId || ev.getType() !== "im.vector.modular.widgets") return; // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) const currentWidgetEvents = room?.currentState.getStateEvents("im.vector.modular.widgets"); if (eventsInIntendedState(currentWidgetEvents)) { client.removeListener(_matrix.RoomStateEvent.Events, onRoomStateEvents); clearTimeout(timerId); resolve(); } } const timerId = window.setTimeout(() => { client.removeListener(_matrix.RoomStateEvent.Events, onRoomStateEvents); reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear")); }, WIDGET_WAIT_TIME); client.on(_matrix.RoomStateEvent.Events, onRoomStateEvents); }); } static setUserWidget(client, widgetId, widgetType, widgetUrl, widgetName, widgetData) { // Get the current widgets and clone them before we modify them, otherwise // we'll modify the content of the old event. const userWidgets = (0, _objects.objectClone)(WidgetUtils.getUserWidgets(client)); // Delete existing widget with ID try { delete userWidgets[widgetId]; } catch (e) { _logger.logger.error(`$widgetId is non-configurable`); } const addingWidget = Boolean(widgetUrl); const userId = client.getSafeUserId(); const content = { id: widgetId, type: widgetType.preferred, url: widgetUrl, name: widgetName, data: widgetData, creatorUserId: userId }; // Add new widget / update if (addingWidget) { userWidgets[widgetId] = { content: content, sender: userId, state_key: widgetId, type: "m.widget", id: widgetId }; } // This starts listening for when the echo comes back from the server // since the widget won't appear added until this happens. If we don't // wait for this, the action will complete but if the user is fast enough, // the widget still won't actually be there. return client.setAccountData("m.widgets", userWidgets).then(() => { return WidgetUtils.waitForUserWidget(client, widgetId, addingWidget); }).then(() => { _dispatcher.default.dispatch({ action: "user_widget_updated" }); }); } static setRoomWidget(client, roomId, widgetId, widgetType, widgetUrl, widgetName, widgetData, widgetAvatarUrl) { let content; const addingWidget = Boolean(widgetUrl); if (addingWidget) { content = { // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) // For now we'll send the legacy event type for compatibility with older apps/elements type: widgetType?.legacy, url: widgetUrl, name: widgetName, data: widgetData, avatar_url: widgetAvatarUrl }; } else { content = {}; } return WidgetUtils.setRoomWidgetContent(client, roomId, widgetId, content); } static setRoomWidgetContent(client, roomId, widgetId, content) { const addingWidget = !!content.url; _WidgetEchoStore.default.setRoomWidgetEcho(roomId, widgetId, content); // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) return client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => { return WidgetUtils.waitForRoomWidget(client, widgetId, roomId, addingWidget); }).finally(() => { _WidgetEchoStore.default.removeRoomWidgetEcho(roomId, widgetId); }); } /** * Get room specific widgets * @param {Room} room The room to get widgets force * @return {[object]} Array containing current / active room widgets */ static getRoomWidgets(room) { // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) const appsStateEvents = room.currentState.getStateEvents("im.vector.modular.widgets"); if (!appsStateEvents) { return []; } return appsStateEvents.filter(ev => { return ev.getContent().type && ev.getContent().url; }); } /** * Get user specific widgets (not linked to a specific room) * @param client The matrix client of the logged-in user * @return {object} Event content object containing current / active user widgets */ static getUserWidgets(client) { if (!client) { throw new Error("User not logged in"); } const userWidgets = client.getAccountData("m.widgets"); if (userWidgets && userWidgets.getContent()) { return userWidgets.getContent(); } return {}; } /** * Get user specific widgets (not linked to a specific room) as an array * @param client The matrix client of the logged-in user * @return {[object]} Array containing current / active user widgets */ static getUserWidgetsArray(client) { return Object.values(WidgetUtils.getUserWidgets(client)); } /** * Get active stickerpicker widgets (stickerpickers are user widgets by nature) * @param client The matrix client of the logged-in user * @return {[object]} Array containing current / active stickerpicker widgets */ static getStickerpickerWidgets(client) { const widgets = WidgetUtils.getUserWidgetsArray(client); return widgets.filter(widget => widget.content?.type === "m.stickerpicker"); } /** * Get all integration manager widgets for this user. * @param client The matrix client of the logged-in user * @returns {Object[]} An array of integration manager user widgets. */ static getIntegrationManagerWidgets(client) { const widgets = WidgetUtils.getUserWidgetsArray(client); return widgets.filter(w => w.content?.type === "m.integration_manager"); } /** * Remove all stickerpicker widgets (stickerpickers are user widgets by nature) * @param client The matrix client of the logged-in user * @return {Promise} Resolves on account data updated */ static async removeStickerpickerWidgets(client) { if (!client) { throw new Error("User not logged in"); } const widgets = client.getAccountData("m.widgets"); if (!widgets) return; const userWidgets = widgets.getContent() || {}; Object.entries(userWidgets).forEach(([key, widget]) => { if (widget.content && widget.content.type === "m.stickerpicker") { delete userWidgets[key]; } }); await client.setAccountData("m.widgets", userWidgets); } static async addJitsiWidget(client, roomId, type, name, isVideoChannel, oobRoomName) { const domain = _Jitsi.Jitsi.getInstance().preferredDomain; const auth = (await _Jitsi.Jitsi.getInstance().getJitsiAuth()) ?? undefined; const widgetId = (0, _randomstring.randomString)(24); // Must be globally unique let confId; if (auth === "openidtoken-jwt") { // Create conference ID from room ID // For compatibility with Jitsi, use base32 without padding. // More details here: // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification confId = _rfc.base32.stringify(Buffer.from(roomId), { pad: false }); } else { // Create a random conference ID confId = `Jitsi${(0, _randomstring.randomUppercaseString)(1)}${(0, _randomstring.randomLowercaseString)(23)}`; } // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets const widgetUrl = new URL(WidgetUtils.getLocalJitsiWrapperUrl({ auth })); widgetUrl.search = ""; // Causes the URL class use searchParams instead widgetUrl.searchParams.set("confId", confId); await WidgetUtils.setRoomWidget(client, roomId, widgetId, _WidgetType.WidgetType.JITSI, widgetUrl.toString(), name, { conferenceId: confId, roomName: oobRoomName ?? client.getRoom(roomId)?.name, isAudioOnly: type === _call.CallType.Voice, isVideoChannel, domain, auth }); } static makeAppConfig(appId, app, senderUserId, roomId, eventId) { if (!senderUserId) { throw new Error("Widgets must be created by someone - provide a senderUserId"); } app.creatorUserId = senderUserId; app.id = appId; app.roomId = roomId; app.eventId = eventId; app.name = app.name || app.type; return app; } static getLocalJitsiWrapperUrl(opts = {}) { // NB. we can't just encodeURIComponent all of these because the $ signs need to be there const queryStringParts = ["conferenceDomain=$domain", "conferenceId=$conferenceId", "isAudioOnly=$isAudioOnly", "startWithAudioMuted=$startWithAudioMuted", "startWithVideoMuted=$startWithVideoMuted", "isVideoChannel=$isVideoChannel", "displayName=$matrix_display_name", "avatarUrl=$matrix_avatar_url", "userId=$matrix_user_id", "roomId=$matrix_room_id", "theme=$theme", "roomName=$roomName", `supportsScreensharing=${_PlatformPeg.default.get()?.supportsJitsiScreensharing()}`, "language=$org.matrix.msc2873.client_language"]; if (opts.auth) { queryStringParts.push(`auth=${opts.auth}`); } const queryString = queryStringParts.join("&"); let baseUrl = window.location.href; if (window.location.protocol !== "https:" && !opts.forLocalRender) { // Use an external wrapper if we're not locally rendering the widget. This is usually // the URL that will end up in the widget event, so we want to make sure it's relatively // safe to send. // We'll end up using a local render URL when we see a Jitsi widget anyways, so this is // really just for backwards compatibility and to appease the spec. baseUrl = _PlatformPeg.default.get().baseUrl; } const url = new URL("jitsi.html#" + queryString, baseUrl); // this strips hash fragment from baseUrl return url.href; } static getWidgetName(app) { return app?.name?.trim() || (0, _languageHandler._t)("widget|no_name"); } static getWidgetDataTitle(app) { return app?.data?.title?.trim() || ""; } static getWidgetUid(app) { return app ? WidgetUtils.calcWidgetUid(app.id, (0, _WidgetStore.isAppWidget)(app) ? app.roomId : undefined) : ""; } static calcWidgetUid(widgetId, roomId) { return roomId ? `room_${roomId}_${widgetId}` : `user_${widgetId}`; } static editWidget(room, app) { // noinspection JSIgnoredPromiseFromCall _IntegrationManagers.IntegrationManagers.sharedInstance().getPrimaryManager()?.open(room, "type_" + app.type, app.id); } static isManagedByManager(app) { if (WidgetUtils.isScalarUrl(app.url)) { const managers = _IntegrationManagers.IntegrationManagers.sharedInstance(); if (managers.hasManager()) { // TODO: Pick the right manager for the widget const defaultManager = managers.getPrimaryManager(); return WidgetUtils.isScalarUrl(defaultManager?.apiUrl); } } return false; } } /** * Hook to get the widgets for a room and update when they change * @param room the room to get widgets for */ exports.default = WidgetUtils; const useWidgets = room => { const [apps, setApps] = (0, _react.useState)(() => _WidgetStore.default.instance.getApps(room.roomId)); const updateApps = (0, _react.useCallback)(() => { // Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings setApps([..._WidgetStore.default.instance.getApps(room.roomId)]); }, [room]); (0, _react.useEffect)(updateApps, [room, updateApps]); (0, _useEventEmitter.useEventEmitter)(_WidgetStore.default.instance, room.roomId, updateApps); (0, _useEventEmitter.useEventEmitter)(_WidgetLayoutStore.WidgetLayoutStore.instance, _WidgetLayoutStore.WidgetLayoutStore.emissionForRoom(room), updateApps); return apps; }; exports.useWidgets = useWidgets; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcmVhY3QiLCJyZXF1aXJlIiwiX3JmYyIsIl9tYXRyaXgiLCJfdHlwZXMiLCJfbG9nZ2VyIiwiX2NhbGwiLCJfcmFuZG9tc3RyaW5nIiwiX1BsYXRmb3JtUGVnIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsIl9TZGtDb25maWciLCJfZGlzcGF0Y2hlciIsIl9XaWRnZXRFY2hvU3RvcmUiLCJfSW50ZWdyYXRpb25NYW5hZ2VycyIsIl9XaWRnZXRUeXBlIiwiX0ppdHNpIiwiX29iamVjdHMiLCJfbGFuZ3VhZ2VIYW5kbGVyIiwiX1dpZGdldFN0b3JlIiwiX2ludGVyb3BSZXF1aXJlV2lsZGNhcmQiLCJfVXJsVXRpbHMiLCJfdXNlRXZlbnRFbWl0dGVyIiwiX1dpZGdldExheW91dFN0b3JlIiwiX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlIiwiZSIsIldlYWtNYXAiLCJyIiwidCIsIl9fZXNNb2R1bGUiLCJkZWZhdWx0IiwiaGFzIiwiZ2V0IiwibiIsIl9fcHJvdG9fXyIsImEiLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsInUiLCJoYXNPd25Qcm9wZXJ0eSIsImNhbGwiLCJpIiwic2V0IiwiV0lER0VUX1dBSVRfVElNRSIsIldpZGdldFV0aWxzIiwiY2FuVXNlck1vZGlmeVdpZGdldHMiLCJjbGllbnQiLCJyb29tSWQiLCJsb2dnZXIiLCJ3YXJuIiwicm9vbSIsImdldFJvb20iLCJtZSIsImdldFVzZXJJZCIsImdldE15TWVtYmVyc2hpcCIsIktub3duTWVtYmVyc2hpcCIsIkpvaW4iLCJjdXJyZW50U3RhdGUiLCJtYXlTZW5kU3RhdGVFdmVudCIsImlzU2NhbGFyVXJsIiwidGVzdFVybFN0cmluZyIsImVycm9yIiwidGVzdFVybCIsInBhcnNlVXJsIiwic2NhbGFyVXJscyIsIlNka0NvbmZpZyIsImludGVncmF0aW9uc193aWRnZXRzX3VybHMiLCJsZW5ndGgiLCJkZWZhdWx0TWFuYWdlciIsIkludGVncmF0aW9uTWFuYWdlcnMiLCJzaGFyZWRJbnN0YW5jZSIsImdldFByaW1hcnlNYW5hZ2VyIiwiYXBpVXJsIiwic2NhbGFyVXJsIiwicHJvdG9jb2wiLCJob3N0IiwicGF0aG5hbWUiLCJzdGFydHNXaXRoIiwid2FpdEZvclVzZXJXaWRnZXQiLCJ3aWRnZXRJZCIsImFkZCIsIlByb21pc2UiLCJyZXNvbHZlIiwicmVqZWN0IiwiZXZlbnRJbkludGVuZGVkU3RhdGUiLCJldiIsImdldENvbnRlbnQiLCJ1bmRlZmluZWQiLCJzdGFydGluZ0FjY291bnREYXRhRXZlbnQiLCJnZXRBY2NvdW50RGF0YSIsIm9uQWNjb3VudERhdGEiLCJjdXJyZW50QWNjb3VudERhdGFFdmVudCIsInJlbW92ZUxpc3RlbmVyIiwiQ2xpZW50RXZlbnQiLCJBY2NvdW50RGF0YSIsImNsZWFyVGltZW91dCIsInRpbWVySWQiLCJ3aW5kb3ciLCJzZXRUaW1lb3V0IiwiRXJyb3IiLCJvbiIsIndhaXRGb3JSb29tV2lkZ2V0IiwiZXZlbnRzSW5JbnRlbmRlZFN0YXRlIiwiZXZMaXN0Iiwid2lkZ2V0UHJlc2VudCIsInNvbWUiLCJzdGFydGluZ1dpZGdldEV2ZW50cyIsImdldFN0YXRlRXZlbnRzIiwib25Sb29tU3RhdGVFdmVudHMiLCJnZXRSb29tSWQiLCJnZXRUeXBlIiwiY3VycmVudFdpZGdldEV2ZW50cyIsIlJvb21TdGF0ZUV2ZW50IiwiRXZlbnRzIiwic2V0VXNlcldpZGdldCIsIndpZGdldFR5cGUiLCJ3aWRnZXRVcmwiLCJ3aWRnZXROYW1lIiwid2lkZ2V0RGF0YSIsInVzZXJXaWRnZXRzIiwib2JqZWN0Q2xvbmUiLCJnZXRVc2VyV2lkZ2V0cyIsImFkZGluZ1dpZGdldCIsIkJvb2xlYW4iLCJ1c2VySWQiLCJnZXRTYWZlVXNlcklkIiwiY29udGVudCIsImlkIiwidHlwZSIsInByZWZlcnJlZCIsInVybCIsIm5hbWUiLCJkYXRhIiwiY3JlYXRvclVzZXJJZCIsInNlbmRlciIsInN0YXRlX2tleSIsInNldEFjY291bnREYXRhIiwidGhlbiIsImRpcyIsImRpc3BhdGNoIiwiYWN0aW9uIiwic2V0Um9vbVdpZGdldCIsIndpZGdldEF2YXRhclVybCIsImxlZ2FjeSIsImF2YXRhcl91cmwiLCJzZXRSb29tV2lkZ2V0Q29udGVudCIsIldpZGdldEVjaG9TdG9yZSIsInNldFJvb21XaWRnZXRFY2hvIiwic2VuZFN0YXRlRXZlbnQiLCJmaW5hbGx5IiwicmVtb3ZlUm9vbVdpZGdldEVjaG8iLCJnZXRSb29tV2lkZ2V0cyIsImFwcHNTdGF0ZUV2ZW50cyIsImZpbHRlciIsImdldFVzZXJXaWRnZXRzQXJyYXkiLCJ2YWx1ZXMiLCJnZXRTdGlja2VycGlja2VyV2lkZ2V0cyIsIndpZGdldHMiLCJ3aWRnZXQiLCJnZXRJbnRlZ3JhdGlvbk1hbmFnZXJXaWRnZXRzIiwidyIsInJlbW92ZVN0aWNrZXJwaWNrZXJXaWRnZXRzIiwiZW50cmllcyIsImZvckVhY2giLCJrZXkiLCJhZGRKaXRzaVdpZGdldCIsImlzVmlkZW9DaGFubmVsIiwib29iUm9vbU5hbWUiLCJkb21haW4iLCJKaXRzaSIsImdldEluc3RhbmNlIiwicHJlZmVycmVkRG9tYWluIiwiYXV0aCIsImdldEppdHNpQXV0aCIsInJhbmRvbVN0cmluZyIsImNvbmZJZCIsImJhc2UzMiIsInN0cmluZ2lmeSIsIkJ1ZmZlciIsImZyb20iLCJwYWQiLCJyYW5kb21VcHBlcmNhc2VTdHJpbmciLCJyYW5kb21Mb3dlcmNhc2VTdHJpbmciLCJVUkwiLCJnZXRMb2NhbEppdHNpV3JhcHBlclVybCIsInNlYXJjaCIsInNlYXJjaFBhcmFtcyIsIldpZGdldFR5cGUiLCJKSVRTSSIsInRvU3RyaW5nIiwiY29uZmVyZW5jZUlkIiwicm9vbU5hbWUiLCJpc0F1ZGlvT25seSIsIkNhbGxUeXBlIiwiVm9pY2UiLCJtYWtlQXBwQ29uZmlnIiwiYXBwSWQiLCJhcHAiLCJzZW5kZXJVc2VySWQiLCJldmVudElkIiwib3B0cyIsInF1ZXJ5U3RyaW5nUGFydHMiLCJQbGF0Zm9ybVBlZyIsInN1cHBvcnRzSml0c2lTY3JlZW5zaGFyaW5nIiwicHVzaCIsInF1ZXJ5U3RyaW5nIiwiam9pbiIsImJhc2VVcmwiLCJsb2NhdGlvbiIsImhyZWYiLCJmb3JMb2NhbFJlbmRlciIsImdldFdpZGdldE5hbWUiLCJ0cmltIiwiX3QiLCJnZXRXaWRnZXREYXRhVGl0bGUiLCJ0aXRsZSIsImdldFdpZGdldFVpZCIsImNhbGNXaWRnZXRVaWQiLCJpc0FwcFdpZGdldCIsImVkaXRXaWRnZXQiLCJvcGVuIiwiaXNNYW5hZ2VkQnlNYW5hZ2VyIiwibWFuYWdlcnMiLCJoYXNNYW5hZ2VyIiwiZXhwb3J0cyIsInVzZVdpZGdldHMiLCJhcHBzIiwic2V0QXBwcyIsInVzZVN0YXRlIiwiV2lkZ2V0U3RvcmUiLCJpbnN0YW5jZSIsImdldEFwcHMiLCJ1cGRhdGVBcHBzIiwidXNlQ2FsbGJhY2siLCJ1c2VFZmZlY3QiLCJ1c2VFdmVudEVtaXR0ZXIiLCJXaWRnZXRMYXlvdXRTdG9yZSIsImVtaXNzaW9uRm9yUm9vbSJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9XaWRnZXRVdGlscy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAxNy0yMDIwIFRoZSBNYXRyaXgub3JnIEZvdW5kYXRpb24gQy5JLkMuXG5Db3B5cmlnaHQgMjAxOSBUcmF2aXMgUmFsc3RvblxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgeyB1c2VDYWxsYmFjaywgdXNlRWZmZWN0LCB1c2VTdGF0ZSB9IGZyb20gXCJyZWFjdFwiO1xuaW1wb3J0IHsgYmFzZTMyIH0gZnJvbSBcInJmYzQ2NDhcIjtcbmltcG9ydCB7IElXaWRnZXQsIElXaWRnZXREYXRhIH0gZnJvbSBcIm1hdHJpeC13aWRnZXQtYXBpXCI7XG5pbXBvcnQgeyBSb29tLCBDbGllbnRFdmVudCwgTWF0cml4Q2xpZW50LCBSb29tU3RhdGVFdmVudCwgTWF0cml4RXZlbnQgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbWF0cml4XCI7XG5pbXBvcnQgeyBLbm93bk1lbWJlcnNoaXAgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvdHlwZXNcIjtcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9sb2dnZXJcIjtcbmltcG9ydCB7IENhbGxUeXBlIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3dlYnJ0Yy9jYWxsXCI7XG5pbXBvcnQgeyByYW5kb21TdHJpbmcsIHJhbmRvbUxvd2VyY2FzZVN0cmluZywgcmFuZG9tVXBwZXJjYXNlU3RyaW5nIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL3JhbmRvbXN0cmluZ1wiO1xuXG5pbXBvcnQgUGxhdGZvcm1QZWcgZnJvbSBcIi4uL1BsYXRmb3JtUGVnXCI7XG5pbXBvcnQgU2RrQ29uZmlnIGZyb20gXCIuLi9TZGtDb25maWdcIjtcbmltcG9ydCBkaXMgZnJvbSBcIi4uL2Rpc3BhdGNoZXIvZGlzcGF0Y2hlclwiO1xuaW1wb3J0IFdpZGdldEVjaG9TdG9yZSBmcm9tIFwiLi4vc3RvcmVzL1dpZGdldEVjaG9TdG9yZVwiO1xuaW1wb3J0IHsgSW50ZWdyYXRpb25NYW5hZ2VycyB9IGZyb20gXCIuLi9pbnRlZ3JhdGlvbnMvSW50ZWdyYXRpb25NYW5hZ2Vyc1wiO1xuaW1wb3J0IHsgV2lkZ2V0VHlwZSB9IGZyb20gXCIuLi93aWRnZXRzL1dpZGdldFR5cGVcIjtcbmltcG9ydCB7IEppdHNpIH0gZnJvbSBcIi4uL3dpZGdldHMvSml0c2lcIjtcbmltcG9ydCB7IG9iamVjdENsb25lIH0gZnJvbSBcIi4vb2JqZWN0c1wiO1xuaW1wb3J0IHsgX3QgfSBmcm9tIFwiLi4vbGFuZ3VhZ2VIYW5kbGVyXCI7XG5pbXBvcnQgV2lkZ2V0U3RvcmUsIHsgSUFwcCwgaXNBcHBXaWRnZXQgfSBmcm9tIFwiLi4vc3RvcmVzL1dpZGdldFN0b3JlXCI7XG5pbXBvcnQgeyBwYXJzZVVybCB9IGZyb20gXCIuL1VybFV0aWxzXCI7XG5pbXBvcnQgeyB1c2VFdmVudEVtaXR0ZXIgfSBmcm9tIFwiLi4vaG9va3MvdXNlRXZlbnRFbWl0dGVyXCI7XG5pbXBvcnQgeyBXaWRnZXRMYXlvdXRTdG9yZSB9IGZyb20gXCIuLi9zdG9yZXMvd2lkZ2V0cy9XaWRnZXRMYXlvdXRTdG9yZVwiO1xuXG4vLyBIb3cgbG9uZyB3ZSB3YWl0IGZvciB0aGUgc3RhdGUgZXZlbnQgZWNobyB0byBjb21lIGJhY2sgZnJvbSB0aGUgc2VydmVyXG4vLyBiZWZvcmUgd2FpdEZvcltSb29tL1VzZXJdV2lkZ2V0IHJlamVjdHMgaXRzIHByb21pc2VcbmNvbnN0IFdJREdFVF9XQUlUX1RJTUUgPSAyMDAwMDtcblxuZXhwb3J0IGludGVyZmFjZSBJV2lkZ2V0RXZlbnQge1xuICAgIGlkOiBzdHJpbmc7XG4gICAgdHlwZTogc3RyaW5nO1xuICAgIHNlbmRlcjogc3RyaW5nO1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjYW1lbGNhc2VcbiAgICBzdGF0ZV9rZXk6IHN0cmluZztcbiAgICBjb250ZW50OiBJQXBwO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFVzZXJXaWRnZXQgZXh0ZW5kcyBPbWl0PElXaWRnZXRFdmVudCwgXCJjb250ZW50XCI+IHtcbiAgICBjb250ZW50OiBJV2lkZ2V0ICYgUGFydGlhbDxJQXBwPjtcbn1cblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgV2lkZ2V0VXRpbHMge1xuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiB1c2VyIGlzIGFibGUgdG8gc2VuZCBzdGF0ZSBldmVudHMgdG8gbW9kaWZ5IHdpZGdldHMgaW4gdGhpcyByb29tXG4gICAgICogKERvZXMgbm90IGFwcGx5IHRvIG5vbi1yb29tLWJhc2VkIC8gdXNlciB3aWRnZXRzKVxuICAgICAqIEBwYXJhbSBjbGllbnQgVGhlIG1hdHJpeCBjbGllbnQgb2YgdGhlIGxvZ2dlZC1pbiB1c2VyXG4gICAgICogQHBhcmFtIHJvb21JZCAtLSBUaGUgSUQgb2YgdGhlIHJvb20gdG8gY2hlY2tcbiAgICAgKiBAcmV0dXJuIEJvb2xlYW4gLS0gdHJ1ZSBpZiB0aGUgdXNlciBjYW4gbW9kaWZ5IHdpZGdldHMgaW4gdGhpcyByb29tXG4gICAgICogQHRocm93cyBFcnJvciAtLSBzcGVjaWZpZXMgdGhlIGVycm9yIHJlYXNvblxuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgY2FuVXNlck1vZGlmeVdpZGdldHMoY2xpZW50OiBNYXRyaXhDbGllbnQsIHJvb21JZD86IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgICAgICBpZiAoIXJvb21JZCkge1xuICAgICAgICAgICAgbG9nZ2VyLndhcm4oXCJObyByb29tIElEIHNwZWNpZmllZFwiKTtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghY2xpZW50KSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihcIlVzZXIgbXVzdCBiZSBiZSBsb2dnZWQgaW5cIik7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCByb29tID0gY2xpZW50LmdldFJvb20ocm9vbUlkKTtcbiAgICAgICAgaWYgKCFyb29tKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihgUm9vbSBJRCAke3Jvb21JZH0gaXMgbm90IHJlY29nbmlzZWRgKTtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IG1lID0gY2xpZW50LmdldFVzZXJJZCgpO1xuICAgICAgICBpZiAoIW1lKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihcIkZhaWxlZCB0byBnZXQgdXNlciBJRFwiKTtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChyb29tLmdldE15TWVtYmVyc2hpcCgpICE9PSBLbm93bk1lbWJlcnNoaXAuSm9pbikge1xuICAgICAgICAgICAgbG9nZ2VyLndhcm4oYFVzZXIgJHttZX0gaXMgbm90IGluIHJvb20gJHtyb29tSWR9YCk7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUT0RPOiBFbmFibGUgc3VwcG9ydCBmb3IgbS53aWRnZXQgZXZlbnQgdHlwZSAoaHR0cHM6Ly9naXRodWIuY29tL3ZlY3Rvci1pbS9lbGVtZW50LXdlYi9pc3N1ZXMvMTMxMTEpXG4gICAgICAgIHJldHVybiByb29tLmN1cnJlbnRTdGF0ZS5tYXlTZW5kU3RhdGVFdmVudChcImltLnZlY3Rvci5tb2R1bGFyLndpZGdldHNcIiwgbWUpO1xuICAgIH1cblxuICAgIC8vIFRPRE86IEdlbmVyaWZ5IHRoZSBuYW1lIG9mIHRoaXMgZnVuY3Rpb24uIEl0J3Mgbm90IGp1c3Qgc2NhbGFyLlxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdHJ1ZSBpZiBzcGVjaWZpZWQgdXJsIGlzIGEgc2NhbGFyIFVSTCwgdHlwaWNhbGx5IGh0dHBzOi8vc2NhbGFyLnZlY3Rvci5pbS9hcGlcbiAgICAgKiBAcGFyYW0gbWF0cml4Q2xpZW50IFRoZSBtYXRyaXggY2xpZW50IG9mIHRoZSBsb2dnZWQtaW4gdXNlclxuICAgICAqIEBwYXJhbSAge1t0eXBlXX0gIHRlc3RVcmxTdHJpbmcgVVJMIHRvIGNoZWNrXG4gICAgICogQHJldHVybiB7Qm9vbGVhbn0gVHJ1ZSBpZiBzcGVjaWZpZWQgVVJMIGlzIGEgc2NhbGFyIFVSTFxuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgaXNTY2FsYXJVcmwodGVzdFVybFN0cmluZz86IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgICAgICBpZiAoIXRlc3RVcmxTdHJpbmcpIHtcbiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihcIlNjYWxhciBVUkwgY2hlY2sgZmFpbGVkLiBObyBVUkwgc3BlY2lmaWVkXCIpO1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgdGVzdFVybCA9IHBhcnNlVXJsKHRlc3RVcmxTdHJpbmcpO1xuICAgICAgICBsZXQgc2NhbGFyVXJscyA9IFNka0NvbmZpZy5nZXQoKS5pbnRlZ3JhdGlvbnNfd2lkZ2V0c191cmxzO1xuICAgICAgICBpZiAoIXNjYWxhclVybHMgfHwgc2NhbGFyVXJscy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIGNvbnN0IGRlZmF1bHRNYW5hZ2VyID0gSW50ZWdyYXRpb25NYW5hZ2Vycy5zaGFyZWRJbnN0YW5jZSgpLmdldFByaW1hcnlNYW5hZ2VyKCk7XG4gICAgICAgICAgICBpZiAoZGVmYXVsdE1hbmFnZXIpIHtcbiAgICAgICAgICAgICAgICBzY2FsYXJVcmxzID0gW2RlZmF1bHRNYW5hZ2VyLmFwaVVybF07XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHNjYWxhclVybHMgPSBbXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2NhbGFyVXJscy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgY29uc3Qgc2NhbGFyVXJsID0gcGFyc2VVcmwoc2NhbGFyVXJsc1tpXSk7XG4gICAgICAgICAgICBpZiAodGVzdFVybCAmJiBzY2FsYXJVcmwpIHtcbiAgICAgICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgICAgIHRlc3RVcmwucHJvdG9jb2wgPT09IHNjYWxhclVybC5wcm90b2NvbCAmJlxuICAgICAgICAgICAgICAgICAgICB0ZXN0VXJsLmhvc3QgPT09IHNjYWxhclVybC5ob3N0ICYmXG4gICAgICAgICAgICAgICAgICAgIHNjYWxhclVybC5wYXRobmFtZSAmJlxuICAgICAgICAgICAgICAgICAgICB0ZXN0VXJsLnBhdGhuYW1lPy5zdGFydHNXaXRoKHNjYWxhclVybC5wYXRobmFtZSlcbiAgICAgICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHdoZW4gYSB3aWRnZXQgd2l0aCB0aGUgZ2l2ZW5cbiAgICAgKiBJRCBoYXMgYmVlbiBhZGRlZCBhcyBhIHVzZXIgd2lkZ2V0IChpZS4gdGhlIGFjY291bnREYXRhIGV2ZW50XG4gICAgICogYXJyaXZlcykgb3IgcmVqZWN0cyBhZnRlciBhIHRpbWVvdXRcbiAgICAgKlxuICAgICAqIEBwYXJhbSBjbGllbnQgVGhlIG1hdHJpeCBjbGllbnQgb2YgdGhlIGxvZ2dlZC1pbiB1c2VyXG4gICAgICogQHBhcmFtIHdpZGdldElkIFRoZSBJRCBvZiB0aGUgd2lkZ2V0IHRvIHdhaXQgZm9yXG4gICAgICogQHBhcmFtIGFkZCBUcnVlIHRvIHdhaXQgZm9yIHRoZSB3aWRnZXQgdG8gYmUgYWRkZWQsXG4gICAgICogICAgIGZhbHNlIHRvIHdhaXQgZm9yIGl0IHRvIGJlIGRlbGV0ZWQuXG4gICAgICogQHJldHVybnMge1Byb21pc2V9IHRoYXQgcmVzb2x2ZXMgd2hlbiB0aGUgd2lkZ2V0IGlzIGluIHRoZVxuICAgICAqICAgICByZXF1ZXN0ZWQgc3RhdGUgYWNjb3JkaW5nIHRvIHRoZSBgYWRkYCBwYXJhbVxuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgd2FpdEZvclVzZXJXaWRnZXQoY2xpZW50OiBNYXRyaXhDbGllbnQsIHdpZGdldElkOiBzdHJpbmcsIGFkZDogYm9vbGVhbik6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgLy8gVGVzdHMgYW4gYWNjb3VudCBkYXRhIGV2ZW50LCByZXR1cm5pbmcgdHJ1ZSBpZiBpdCdzIGluIHRoZSBzdGF0ZVxuICAgICAgICAgICAgLy8gd2UncmUgd2FpdGluZyBmb3IgaXQgdG8gYmUgaW5cbiAgICAgICAgICAgIGZ1bmN0aW9uIGV2ZW50SW5JbnRlbmRlZFN0YXRlKGV2PzogTWF0cml4RXZlbnQpOiBib29sZWFuIHtcbiAgICAgICAgICAgICAgICBpZiAoIWV2KSByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICAgICAgaWYgKGFkZCkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gZXYuZ2V0Q29udGVudCgpW3dpZGdldElkXSAhPT0gdW5kZWZpbmVkO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBldi5nZXRDb250ZW50KClbd2lkZ2V0SWRdID09PSB1bmRlZmluZWQ7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBzdGFydGluZ0FjY291bnREYXRhRXZlbnQgPSBjbGllbnQuZ2V0QWNjb3VudERhdGEoXCJtLndpZGdldHNcIik7XG4gICAgICAgICAgICBpZiAoZXZlbnRJbkludGVuZGVkU3RhdGUoc3RhcnRpbmdBY2NvdW50RGF0YUV2ZW50KSkge1xuICAgICAgICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGZ1bmN0aW9uIG9uQWNjb3VudERhdGEoZXY6IE1hdHJpeEV2ZW50KTogdm9pZCB7XG4gICAgICAgICAgICAgICAgY29uc3QgY3VycmVudEFjY291bnREYXRhRXZlbnQgPSBjbGllbnQuZ2V0QWNjb3VudERhdGEoXCJtLndpZGdldHNcIik7XG4gICAgICAgICAgICAgICAgaWYgKGV2ZW50SW5JbnRlbmRlZFN0YXRlKGN1cnJlbnRBY2NvdW50RGF0YUV2ZW50KSkge1xuICAgICAgICAgICAgICAgICAgICBjbGllbnQucmVtb3ZlTGlzdGVuZXIoQ2xpZW50RXZlbnQuQWNjb3VudERhdGEsIG9uQWNjb3VudERhdGEpO1xuICAgICAgICAgICAgICAgICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgICAgICAgICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCB0aW1lcklkID0gd2luZG93LnNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNsaWVudC5yZW1vdmVMaXN0ZW5lcihDbGllbnRFdmVudC5BY2NvdW50RGF0YSwgb25BY2NvdW50RGF0YSk7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihcIlRpbWVkIG91dCB3YWl0aW5nIGZvciB3aWRnZXQgSUQgXCIgKyB3aWRnZXRJZCArIFwiIHRvIGFwcGVhclwiKSk7XG4gICAgICAgICAgICB9LCBXSURHRVRfV0FJVF9USU1FKTtcbiAgICAgICAgICAgIGNsaWVudC5vbihDbGllbnRFdmVudC5BY2NvdW50RGF0YSwgb25BY2NvdW50RGF0YSk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiBhIHdpZGdldCB3aXRoIHRoZSBnaXZlblxuICAgICAqIElEIGhhcyBiZWVuIGFkZGVkIGFzIGEgcm9vbSB3aWRnZXQgaW4gdGhlIGdpdmVuIHJvb20gKGllLiB0aGVcbiAgICAgKiByb29tIHN0YXRlIGV2ZW50IGFycml2ZXMpIG9yIHJlamVjdHMgYWZ0ZXIgYSB0aW1lb3V0XG4gICAgICpcbiAgICAgKiBAcGFyYW0gY2xpZW50IFRoZSBtYXRyaXggY2xpZW50IG9mIHRoZSBsb2dnZWQtaW4gdXNlclxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSB3aWRnZXRJZCBUaGUgSUQgb2YgdGhlIHdpZGdldCB0byB3YWl0IGZvclxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSByb29tSWQgVGhlIElEIG9mIHRoZSByb29tIHRvIHdhaXQgZm9yIHRoZSB3aWRnZXQgaW5cbiAgICAgKiBAcGFyYW0ge2Jvb2xlYW59IGFkZCBUcnVlIHRvIHdhaXQgZm9yIHRoZSB3aWRnZXQgdG8gYmUgYWRkZWQsXG4gICAgICogICAgIGZhbHNlIHRvIHdhaXQgZm9yIGl0IHRvIGJlIGRlbGV0ZWQuXG4gICAgICogQHJldHVybnMge1Byb21pc2V9IHRoYXQgcmVzb2x2ZXMgd2hlbiB0aGUgd2lkZ2V0IGlzIGluIHRoZVxuICAgICAqICAgICByZXF1ZXN0ZWQgc3RhdGUgYWNjb3JkaW5nIHRvIHRoZSBgYWRkYCBwYXJhbVxuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgd2FpdEZvclJvb21XaWRnZXQoXG4gICAgICAgIGNsaWVudDogTWF0cml4Q2xpZW50LFxuICAgICAgICB3aWRnZXRJZDogc3RyaW5nLFxuICAgICAgICByb29tSWQ6IHN0cmluZyxcbiAgICAgICAgYWRkOiBib29sZWFuLFxuICAgICk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgLy8gVGVzdHMgYSBsaXN0IG9mIHN0YXRlIGV2ZW50cywgcmV0dXJuaW5nIHRydWUgaWYgaXQncyBpbiB0aGUgc3RhdGVcbiAgICAgICAgICAgIC8vIHdlJ3JlIHdhaXRpbmcgZm9yIGl0IHRvIGJlIGluXG4gICAgICAgICAgICBmdW5jdGlvbiBldmVudHNJbkludGVuZGVkU3RhdGUoZXZMaXN0PzogTWF0cml4RXZlbnRbXSk6IGJvb2xlYW4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IHdpZGdldFByZXNlbnQgPSBldkxpc3Q/LnNvbWUoKGV2KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBldi5nZXRDb250ZW50KCkgJiYgZXYuZ2V0Q29udGVudCgpW1wiaWRcIl0gPT09IHdpZGdldElkO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIGlmIChhZGQpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuICEhd2lkZ2V0UHJlc2VudDtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gIXdpZGdldFByZXNlbnQ7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCByb29tID0gY2xpZW50LmdldFJvb20ocm9vbUlkKTtcbiAgICAgICAgICAgIC8vIFRPRE86IEVuYWJsZSBzdXBwb3J0IGZvciBtLndpZGdldCBldmVudCB0eXBlIChodHRwczovL2dpdGh1Yi5jb20vdmVjdG9yLWltL2VsZW1lbnQtd2ViL2lzc3Vlcy8xMzExMSlcbiAgICAgICAgICAgIGNvbnN0IHN0YXJ0aW5nV2lkZ2V0RXZlbnRzID0gcm9vbT8uY3VycmVudFN0YXRlLmdldFN0YXRlRXZlbnRzKFwiaW0udmVjdG9yLm1vZHVsYXIud2lkZ2V0c1wiKTtcbiAgICAgICAgICAgIGlmIChldmVudHNJbkludGVuZGVkU3RhdGUoc3RhcnRpbmdXaWRnZXRFdmVudHMpKSB7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZnVuY3Rpb24gb25Sb29tU3RhdGVFdmVudHMoZXY6IE1hdHJpeEV2ZW50KTogdm9pZCB7XG4gICAgICAgICAgICAgICAgaWYgKGV2LmdldFJvb21JZCgpICE9PSByb29tSWQgfHwgZXYuZ2V0VHlwZSgpICE9PSBcImltLnZlY3Rvci5tb2R1bGFyLndpZGdldHNcIikgcmV0dXJuO1xuXG4gICAgICAgICAgICAgICAgLy8gVE9ETzogRW5hYmxlIHN1cHBvcnQgZm9yIG0ud2lkZ2V0IGV2ZW50IHR5cGUgKGh0dHBzOi8vZ2l0aHViLmNvbS92ZWN0b3ItaW0vZWxlbWVudC13ZWIvaXNzdWVzLzEzMTExKVxuICAgICAgICAgICAgICAgIGNvbnN0IGN1cnJlbnRXaWRnZXRFdmVudHMgPSByb29tPy5jdXJyZW50U3RhdGUuZ2V0U3RhdGVFdmVudHMoXCJpbS52ZWN0b3IubW9kdWxhci53aWRnZXRzXCIpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGV2ZW50c0luSW50ZW5kZWRTdGF0ZShjdXJyZW50V2lkZ2V0RXZlbnRzKSkge1xuICAgICAgICAgICAgICAgICAgICBjbGllbnQucmVtb3ZlTGlzdGVuZXIoUm9vbVN0YXRlRXZlbnQuRXZlbnRzLCBvblJvb21TdGF0ZUV2ZW50cyk7XG4gICAgICAgICAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aW1lcklkKTtcbiAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHRpbWVySWQgPSB3aW5kb3cuc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgY2xpZW50LnJlbW92ZUxpc3RlbmVyKFJvb21TdGF0ZUV2ZW50LkV2ZW50cywgb25Sb29tU3RhdGVFdmVudHMpO1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoXCJUaW1lZCBvdXQgd2FpdGluZyBmb3Igd2lkZ2V0IElEIFwiICsgd2lkZ2V0SWQgKyBcIiB0byBhcHBlYXJcIikpO1xuICAgICAgICAgICAgfSwgV0lER0VUX1dBSVRfVElNRSk7XG4gICAgICAgICAgICBjbGllbnQub24oUm9vbVN0YXRlRXZlbnQuRXZlbnRzLCBvblJvb21TdGF0ZUV2ZW50cyk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdGF0aWMgc2V0VXNlcldpZGdldChcbiAgICAgICAgY2xpZW50OiBNYXRyaXhDbGllbnQsXG4gICAgICAgIHdpZGdldElkOiBzdHJpbmcsXG4gICAgICAgIHdpZGdldFR5cGU6IFdpZGdldFR5cGUsXG4gICAgICAgIHdpZGdldFVybDogc3RyaW5nLFxuICAgICAgICB3aWRnZXROYW1lOiBzdHJpbmcsXG4gICAgICAgIHdpZGdldERhdGE6IElXaWRnZXREYXRhLFxuICAgICk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICAvLyBHZXQgdGhlIGN1cnJlbnQgd2lkZ2V0cyBhbmQgY2xvbmUgdGhlbSBiZWZvcmUgd2UgbW9kaWZ5IHRoZW0sIG90aGVyd2lzZVxuICAgICAgICAvLyB3ZSdsbCBtb2RpZnkgdGhlIGNvbnRlbnQgb2YgdGhlIG9sZCBldmVudC5cbiAgICAgICAgY29uc3QgdXNlcldpZGdldHMgPSBvYmplY3RDbG9uZShXaWRnZXRVdGlscy5nZXRVc2VyV2lkZ2V0cyhjbGllbnQpKTtcblxuICAgICAgICAvLyBEZWxldGUgZXhpc3Rpbmcgd2lkZ2V0IHdpdGggSURcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGRlbGV0ZSB1c2VyV2lkZ2V0c1t3aWRnZXRJZF07XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIGxvZ2dlci5lcnJvcihgJHdpZGdldElkIGlzIG5vbi1jb25maWd1cmFibGVgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGFkZGluZ1dpZGdldCA9IEJvb2xlYW4od2lkZ2V0VXJsKTtcblxuICAgICAgICBjb25zdCB1c2VySWQgPSBjbGllbnQuZ2V0U2FmZVVzZXJJZCgpO1xuXG4gICAgICAgIGNvbnN0IGNvbnRlbnQgPSB7XG4gICAgICAgICAgICBpZDogd2lkZ2V0SWQsXG4gICAgICAgICAgICB0eXBlOiB3aWRnZXRUeXBlLnByZWZlcnJlZCxcbiAgICAgICAgICAgIHVybDogd2lkZ2V0VXJsLFxuICAgICAgICAgICAgbmFtZTogd2lkZ2V0TmFtZSxcbiAgICAgICAgICAgIGRhdGE6IHdpZGdldERhdGEsXG4gICAgICAgICAgICBjcmVhdG9yVXNlcklkOiB1c2VySWQsXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gQWRkIG5ldyB3aWRnZXQgLyB1cGRhdGVcbiAgICAgICAgaWYgKGFkZGluZ1dpZGdldCkge1xuICAgICAgICAgICAgdXNlcldpZGdldHNbd2lkZ2V0SWRdID0ge1xuICAgICAgICAgICAgICAgIGNvbnRlbnQ6IGNvbnRlbnQsXG4gICAgICAgICAgICAgICAgc2VuZGVyOiB1c2VySWQsXG4gICAgICAgICAgICAgICAgc3RhdGVfa2V5OiB3aWRnZXRJZCxcbiAgICAgICAgICAgICAgICB0eXBlOiBcIm0ud2lkZ2V0XCIsXG4gICAgICAgICAgICAgICAgaWQ6IHdpZGdldElkLFxuICAgICAgICAgICAgfTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFRoaXMgc3RhcnRzIGxpc3RlbmluZyBmb3Igd2hlbiB0aGUgZWNobyBjb21lcyBiYWNrIGZyb20gdGhlIHNlcnZlclxuICAgICAgICAvLyBzaW5jZSB0aGUgd2lkZ2V0IHdvbid0IGFwcGVhciBhZGRlZCB1bnRpbCB0aGlzIGhhcHBlbnMuIElmIHdlIGRvbid0XG4gICAgICAgIC8vIHdhaXQgZm9yIHRoaXMsIHRoZSBhY3Rpb24gd2lsbCBjb21wbGV0ZSBidXQgaWYgdGhlIHVzZXIgaXMgZmFzdCBlbm91Z2gsXG4gICAgICAgIC8vIHRoZSB3aWRnZXQgc3RpbGwgd29uJ3QgYWN0dWFsbHkgYmUgdGhlcmUuXG4gICAgICAgIHJldHVybiBjbGllbnRcbiAgICAgICAgICAgIC5zZXRBY2NvdW50RGF0YShcIm0ud2lkZ2V0c1wiLCB1c2VyV2lkZ2V0cylcbiAgICAgICAgICAgIC50aGVuKCgpID0+IHtcbiAgICAgICAgICAgICAgICByZXR1cm4gV2lkZ2V0VXRpbHMud2FpdEZvclVzZXJXaWRnZXQoY2xpZW50LCB3aWRnZXRJZCwgYWRkaW5nV2lkZ2V0KTtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgICAgICAgICAgZGlzLmRpc3BhdGNoKHsgYWN0aW9uOiBcInVzZXJfd2lkZ2V0X3VwZGF0ZWRcIiB9KTtcbiAgICAgICAgICAgIH0pO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdGF0aWMgc2V0Um9vbVdpZGdldChcbiAgICAgICAgY2xpZW50OiBNYXRyaXhDbGllbnQsXG4gICAgICAgIHJvb21JZDogc3RyaW5nLFxuICAgICAgICB3aWRnZXRJZDogc3RyaW5nLFxuICAgICAgICB3aWRnZXRUeXBlPzogV2lkZ2V0VHlwZSxcbiAgICAgICAgd2lkZ2V0VXJsPzogc3RyaW5nLFxuICAgICAgICB3aWRnZXROYW1lPzogc3RyaW5nLFxuICAgICAgICB3aWRnZXREYXRhPzogSVdpZGdldERhdGEsXG4gICAgICAgIHdpZGdldEF2YXRhclVybD86IHN0cmluZyxcbiAgICApOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgbGV0IGNvbnRlbnQ6IFBhcnRpYWw8SVdpZGdldD4gJiB7IGF2YXRhcl91cmw/OiBzdHJpbmcgfTtcblxuICAgICAgICBjb25zdCBhZGRpbmdXaWRnZXQgPSBCb29sZWFuKHdpZGdldFVybCk7XG5cbiAgICAgICAgaWYgKGFkZGluZ1dpZGdldCkge1xuICAgICAgICAgICAgY29udGVudCA9IHtcbiAgICAgICAgICAgICAgICAvLyBUT0RPOiBFbmFibGUgc3VwcG9ydCBmb3IgbS53aWRnZXQgZXZlbnQgdHlwZSAoaHR0cHM6Ly9naXRodWIuY29tL3ZlY3Rvci1pbS9lbGVtZW50LXdlYi9pc3N1ZXMvMTMxMTEpXG4gICAgICAgICAgICAgICAgLy8gRm9yIG5vdyB3ZSdsbCBzZW5kIHRoZSBsZWdhY3kgZXZlbnQgdHlwZSBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG9sZGVyIGFwcHMvZWxlbWVudHNcbiAgICAgICAgICAgICAgICB0eXBlOiB3aWRnZXRUeXBlPy5sZWdhY3ksXG4gICAgICAgICAgICAgICAgdXJsOiB3aWRnZXRVcmwsXG4gICAgICAgICAgICAgICAgbmFtZTogd2lkZ2V0TmFtZSxcbiAgICAgICAgICAgICAgICBkYXRhOiB3aWRnZXREYXRhLFxuICAgICAgICAgICAgICAgIGF2YXRhcl91cmw6IHdpZGdldEF2YXRhclVybCxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBjb250ZW50ID0ge307XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gV2lkZ2V0VXRpbHMuc2V0Um9vbVdpZGdldENvbnRlbnQoY2xpZW50LCByb29tSWQsIHdpZGdldElkLCBjb250ZW50IGFzIElXaWRnZXQpO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdGF0aWMgc2V0Um9vbVdpZGdldENvbnRlbnQoXG4gICAgICAgIGNsaWVudDogTWF0cml4Q2xpZW50LFxuICAgICAgICByb29tSWQ6IHN0cmluZyxcbiAgICAgICAgd2lkZ2V0SWQ6IHN0cmluZyxcbiAgICAgICAgY29udGVudDogSVdpZGdldCAmIFJlY29yZDxzdHJpbmcsIGFueT4sXG4gICAgKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IGFkZGluZ1dpZGdldCA9ICEhY29udGVudC51cmw7XG5cbiAgICAgICAgV2lkZ2V0RWNob1N0b3JlLnNldFJvb21XaWRnZXRFY2hvKHJvb21JZCwgd2lkZ2V0SWQsIGNvbnRlbnQpO1xuXG4gICAgICAgIC8vIFRPRE86IEVuYWJsZSBzdXBwb3J0IGZvciBtLndpZGdldCBldmVudCB0eXBlIChodHRwczovL2dpdGh1Yi5jb20vdmVjdG9yLWltL2VsZW1lbnQtd2ViL2lzc3Vlcy8xMzExMSlcbiAgICAgICAgcmV0dXJuIGNsaWVudFxuICAgICAgICAgICAgLnNlbmRTdGF0ZUV2ZW50KHJvb21JZCwgXCJpbS52ZWN0b3IubW9kdWxhci53aWRnZXRzXCIsIGNvbnRlbnQsIHdpZGdldElkKVxuICAgICAgICAgICAgLnRoZW4oKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJldHVybiBXaWRnZXRVdGlscy53YWl0Rm9yUm9vbVdpZGdldChjbGllbnQsIHdpZGdldElkLCByb29tSWQsIGFkZGluZ1dpZGdldCk7XG4gICAgICAgICAgICB9KVxuICAgICAgICAgICAgLmZpbmFsbHkoKCkgPT4ge1xuICAgICAgICAgICAgICAgIFdpZGdldEVjaG9TdG9yZS5yZW1vdmVSb29tV2lkZ2V0RWNobyhyb29tSWQsIHdpZGdldElkKTtcbiAgICAgICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldCByb29tIHNwZWNpZmljIHdpZGdldHNcbiAgICAgKiBAcGFyYW0gIHtSb29tfSByb29tIFRoZSByb29tIHRvIGdldCB3aWRnZXRzIGZvcmNlXG4gICAgICogQHJldHVybiB7W29iamVjdF19IEFycmF5IGNvbnRhaW5pbmcgY3VycmVudCAvIGFjdGl2ZSByb29tIHdpZGdldHNcbiAgICAgKi9cbiAgICBwdWJsaWMgc3RhdGljIGdldFJvb21XaWRnZXRzKHJvb206IFJvb20pOiBNYXRyaXhFdmVudFtdIHtcbiAgICAgICAgLy8gVE9ETzogRW5hYmxlIHN1cHBvcnQgZm9yIG0ud2lkZ2V0IGV2ZW50IHR5cGUgKGh0dHBzOi8vZ2l0aHViLmNvbS92ZWN0b3ItaW0vZWxlbWVudC13ZWIvaXNzdWVzLzEzMTExKVxuICAgICAgICBjb25zdCBhcHBzU3RhdGVFdmVudHMgPSByb29tLmN1cnJlbnRTdGF0ZS5nZXRTdGF0ZUV2ZW50cyhcImltLnZlY3Rvci5tb2R1bGFyLndpZGdldHNcIik7XG4gICAgICAgIGlmICghYXBwc1N0YXRlRXZlbnRzKSB7XG4gICAgICAgICAgICByZXR1cm4gW107XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gYXBwc1N0YXRlRXZlbnRzLmZpbHRlcigoZXYpID0+IHtcbiAgICAgICAgICAgIHJldHVybiBldi5nZXRDb250ZW50KCkudHlwZSAmJiBldi5nZXRDb250ZW50KCkudXJsO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXQgdXNlciBzcGVjaWZpYyB3aWRnZXRzIChub3QgbGlua2VkIHRvIGEgc3BlY2lmaWMgcm9vbSlcbiAgICAgKiBAcGFyYW0gY2xpZW50IFRoZSBtYXRyaXggY2xpZW50IG9mIHRoZSBsb2dnZWQtaW4gdXNlclxuICAgICAqIEByZXR1cm4ge29iamVjdH0gRXZlbnQgY29udGVudCBvYmplY3QgY29udGFpbmluZyBjdXJyZW50IC8gYWN0aXZlIHVzZXIgd2lkZ2V0c1xuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgZ2V0VXNlcldpZGdldHMoY2xpZW50OiBNYXRyaXhDbGllbnQgfCB1bmRlZmluZWQpOiBSZWNvcmQ8c3RyaW5nLCBVc2VyV2lkZ2V0PiB7XG4gICAgICAgIGlmICghY2xpZW50KSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJVc2VyIG5vdCBsb2dnZWQgaW5cIik7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgdXNlcldpZGdldHMgPSBjbGllbnQuZ2V0QWNjb3VudERhdGEoXCJtLndpZGdldHNcIik7XG4gICAgICAgIGlmICh1c2VyV2lkZ2V0cyAmJiB1c2VyV2lkZ2V0cy5nZXRDb250ZW50KCkpIHtcbiAgICAgICAgICAgIHJldHVybiB1c2VyV2lkZ2V0cy5nZXRDb250ZW50KCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHt9O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldCB1c2VyIHNwZWNpZmljIHdpZGdldHMgKG5vdCBsaW5rZWQgdG8gYSBzcGVjaWZpYyByb29tKSBhcyBhbiBhcnJheVxuICAgICAqIEBwYXJhbSBjbGllbnQgVGhlIG1hdHJpeCBjbGllbnQgb2YgdGhlIGxvZ2dlZC1pbiB1c2VyXG4gICAgICogQHJldHVybiB7W29iamVjdF19IEFycmF5IGNvbnRhaW5pbmcgY3VycmVudCAvIGFjdGl2ZSB1c2VyIHdpZGdldHNcbiAgICAgKi9cbiAgICBwdWJsaWMgc3RhdGljIGdldFVzZXJXaWRnZXRzQXJyYXkoY2xpZW50OiBNYXRyaXhDbGllbnQgfCB1bmRlZmluZWQpOiBVc2VyV2lkZ2V0W10ge1xuICAgICAgICByZXR1cm4gT2JqZWN0LnZhbHVlcyhXaWRnZXRVdGlscy5nZXRVc2VyV2lkZ2V0cyhjbGllbnQpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXQgYWN0aXZlIHN0aWNrZXJwaWNrZXIgd2lkZ2V0cyAoc3RpY2tlcnBpY2tlcnMgYXJlIHVzZXIgd2lkZ2V0cyBieSBuYXR1cmUpXG4gICAgICogQHBhcmFtIGNsaWVudCBUaGUgbWF0cml4IGNsaWVudCBvZiB0aGUgbG9nZ2VkLWluIHVzZXJcbiAgICAgKiBAcmV0dXJuIHtbb2JqZWN0XX0gQXJyYXkgY29udGFpbmluZyBjdXJyZW50IC8gYWN0aXZlIHN0aWNrZXJwaWNrZXIgd2lkZ2V0c1xuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgZ2V0U3RpY2tlcnBpY2tlcldpZGdldHMoY2xpZW50OiBNYXRyaXhDbGllbnQgfCB1bmRlZmluZWQpOiBVc2VyV2lkZ2V0W10ge1xuICAgICAgICBjb25zdCB3aWRnZXRzID0gV2lkZ2V0VXRpbHMuZ2V0VXNlcldpZGdldHNBcnJheShjbGllbnQpO1xuICAgICAgICByZXR1cm4gd2lkZ2V0cy5maWx0ZXIoKHdpZGdldCkgPT4gd2lkZ2V0LmNvbnRlbnQ/LnR5cGUgPT09IFwibS5zdGlja2VycGlja2VyXCIpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldCBhbGwgaW50ZWdyYXRpb24gbWFuYWdlciB3aWRnZXRzIGZvciB0aGlzIHVzZXIuXG4gICAgICogQHBhcmFtIGNsaWVudCBUaGUgbWF0cml4IGNsaWVudCBvZiB0aGUgbG9nZ2VkLWluIHVzZXJcbiAgICAgKiBAcmV0dXJucyB7T2JqZWN0W119IEFuIGFycmF5IG9mIGludGVncmF0aW9uIG1hbmFnZXIgdXNlciB3aWRnZXRzLlxuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgZ2V0SW50ZWdyYXRpb25NYW5hZ2VyV2lkZ2V0cyhjbGllbnQ6IE1hdHJpeENsaWVudCB8IHVuZGVmaW5lZCk6IFVzZXJXaWRnZXRbXSB7XG4gICAgICAgIGNvbnN0IHdpZGdldHMgPSBXaWRnZXRVdGlscy5nZXRVc2VyV2lkZ2V0c0FycmF5KGNsaWVudCk7XG4gICAgICAgIHJldHVybiB3aWRnZXRzLmZpbHRlcigodykgPT4gdy5jb250ZW50Py50eXBlID09PSBcIm0uaW50ZWdyYXRpb25fbWFuYWdlclwiKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZW1vdmUgYWxsIHN0aWNrZXJwaWNrZXIgd2lkZ2V0cyAoc3RpY2tlcnBpY2tlcnMgYXJlIHVzZXIgd2lkZ2V0cyBieSBuYXR1cmUpXG4gICAgICogQHBhcmFtIGNsaWVudCBUaGUgbWF0cml4IGNsaWVudCBvZiB0aGUgbG9nZ2VkLWluIHVzZXJcbiAgICAgKiBAcmV0dXJuIHtQcm9taXNlfSBSZXNvbHZlcyBvbiBhY2NvdW50IGRhdGEgdXBkYXRlZFxuICAgICAqL1xuICAgIHB1YmxpYyBzdGF0aWMgYXN5bmMgcmVtb3ZlU3RpY2tlcnBpY2tlcldpZGdldHMoY2xpZW50OiBNYXRyaXhDbGllbnQgfCB1bmRlZmluZWQpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgaWYgKCFjbGllbnQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcIlVzZXIgbm90IGxvZ2dlZCBpblwiKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCB3aWRnZXRzID0gY2xpZW50LmdldEFjY291bnREYXRhKFwibS53aWRnZXRzXCIpO1xuICAgICAgICBpZiAoIXdpZGdldHMpIHJldHVybjtcbiAgICAgICAgY29uc3QgdXNlcldpZGdldHM6IFJlY29yZDxzdHJpbmcsIElXaWRnZXRFdmVudD4gPSB3aWRnZXRzLmdldENvbnRlbnQoKSB8fCB7fTtcbiAgICAgICAgT2JqZWN0LmVudHJpZXModXNlcldpZGdldHMpLmZvckVhY2goKFtrZXksIHdpZGdldF0pID0+IHtcbiAgICAgICAgICAgIGlmICh3aWRnZXQuY29udGVudCAmJiB3aWRnZXQuY29udGVudC50eXBlID09PSBcIm0uc3RpY2tlcnBpY2tlclwiKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIHVzZXJXaWRnZXRzW2tleV07XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICBhd2FpdCBjbGllbnQuc2V0QWNjb3VudERhdGEoXCJtLndpZGdldHNcIiwgdXNlcldpZGdldHMpO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdGF0aWMgYXN5bmMgYWRkSml0c2lXaWRnZXQoXG4gICAgICAgIGNsaWVudDogTWF0cml4Q2xpZW50LFxuICAgICAgICByb29tSWQ6IHN0cmluZyxcbiAgICAgICAgdHlwZTogQ2FsbFR5cGUsXG4gICAgICAgIG5hbWU6IHN0cmluZyxcbiAgICAgICAgaXNWaWRlb0NoYW5uZWw6IGJvb2xlYW4sXG4gICAgICAgIG9vYlJvb21OYW1lPzogc3RyaW5nLFxuICAgICk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBkb21haW4gPSBKaXRzaS5nZXRJbnN0YW5jZSgpLnByZWZlcnJlZERvbWFpbjtcbiAgICAgICAgY29uc3QgYXV0aCA9IChhd2FpdCBKaXRzaS5nZXRJbnN0YW5jZSgpLmdldEppdHNpQXV0aCgpKSA/PyB1bmRlZmluZWQ7XG4gICAgICAgIGNvbnN0IHdpZGdldElkID0gcmFuZG9tU3RyaW5nKDI0KTsgLy8gTXVzdCBiZSBnbG9iYWxseSB1bmlxdWVcblxuICAgICAgICBsZXQgY29uZklkOiBzdHJpbmc7XG4gICAgICAgIGlmIChhdXRoID09PSBcIm9wZW5pZHRva2VuLWp3dFwiKSB7XG4gICAgICAgICAgICAvLyBDcmVhdGUgY29uZmVyZW5jZSBJRCBmcm9tIHJvb20gSURcbiAgICAgICAgICAgIC8vIEZvciBjb21wYXRpYmlsaXR5IHdpdGggSml0c2ksIHVzZSBiYXNlMzIgd2l0aG91dCBwYWRkaW5nLlxuICAgICAgICAgICAgLy8gTW9yZSBkZXRhaWxzIGhlcmU6XG4gICAgICAgICAgICAvLyBodHRwczovL2dpdGh1Yi5jb20vbWF0cml4LW9yZy9wcm9zb2R5LW1vZC1hdXRoLW1hdHJpeC11c2VyLXZlcmlmaWNhdGlvblxuICAgICAgICAgICAgY29uZklkID0gYmFzZTMyLnN0cmluZ2lmeShCdWZmZXIuZnJvbShyb29tSWQpLCB7IHBhZDogZmFsc2UgfSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBDcmVhdGUgYSByYW5kb20gY29uZmVyZW5jZSBJRFxuICAgICAgICAgICAgY29uZklkID0gYEppdHNpJHtyYW5kb21VcHBlcmNhc2VTdHJpbmcoMSl9JHtyYW5kb21Mb3dlcmNhc2VTdHJpbmcoMjMpfWA7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUT0RPOiBSZW1vdmUgVVJMIGhhY2tzIHdoZW4gdGhlIG1vYmlsZSBjbGllbnRzIGV2ZW50dWFsbHkgc3VwcG9ydCB2MiB3aWRnZXRzXG4gICAgICAgIGNvbnN0IHdpZGdldFVybCA9IG5ldyBVUkwoV2lkZ2V0VXRpbHMuZ2V0TG9jYWxKaXRzaVdyYXBwZXJVcmwoeyBhdXRoIH0pKTtcbiAgICAgICAgd2lkZ2V0VXJsLnNlYXJjaCA9IFwiXCI7IC8vIENhdXNlcyB0aGUgVVJMIGNsYXNzIHVzZSBzZWFyY2hQYXJhbXMgaW5zdGVhZFxuICAgICAgICB3aWRnZXRVcmwuc2VhcmNoUGFyYW1zLnNldChcImNvbmZJZFwiLCBjb25mSWQpO1xuXG4gICAgICAgIGF3YWl0IFdpZGdldFV0aWxzLnNldFJvb21XaWRnZXQoY2xpZW50LCByb29tSWQsIHdpZGdldElkLCBXaWRnZXRUeXBlLkpJVFNJLCB3aWRnZXRVcmwudG9TdHJpbmcoKSwgbmFtZSwge1xuICAgICAgICAgICAgY29uZmVyZW5jZUlkOiBjb25mSWQsXG4gICAgICAgICAgICByb29tTmFtZTogb29iUm9vbU5hbWUgPz8gY2xpZW50LmdldFJvb20ocm9vbUlkKT8ubmFtZSxcbiAgICAgICAgICA