matrix-react-sdk
Version:
SDK for matrix.org using React
474 lines (453 loc) • 74.9 kB
JavaScript
"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