UNPKG

matrix-react-sdk

Version:
456 lines (437 loc) 69.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateRoomVia = exports.RoomPermalinkCreator = void 0; exports.getHostnameFromMatrixServerName = getHostnameFromMatrixServerName; exports.getPrimaryPermalinkEntity = getPrimaryPermalinkEntity; exports.getServerName = getServerName; exports.isPermalinkHost = isPermalinkHost; exports.makeGenericPermalink = makeGenericPermalink; exports.makeRoomPermalink = makeRoomPermalink; exports.makeUserPermalink = makeUserPermalink; exports.parsePermalink = parsePermalink; exports.tryTransformEntityToPermalink = tryTransformEntityToPermalink; exports.tryTransformPermalinkToLocalHref = tryTransformPermalinkToLocalHref; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _isIp = _interopRequireDefault(require("is-ip")); var utils = _interopRequireWildcard(require("matrix-js-sdk/src/utils")); var _matrix = require("matrix-js-sdk/src/matrix"); var _types = require("matrix-js-sdk/src/types"); var _logger = require("matrix-js-sdk/src/logger"); var _MatrixToPermalinkConstructor = _interopRequireWildcard(require("./MatrixToPermalinkConstructor")); var _ElementPermalinkConstructor = _interopRequireDefault(require("./ElementPermalinkConstructor")); var _SdkConfig = _interopRequireDefault(require("../../SdkConfig")); var _linkifyMatrix = require("../../linkify-matrix"); var _MatrixSchemePermalinkConstructor = _interopRequireDefault(require("./MatrixSchemePermalinkConstructor")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /* Copyright 2024 New Vector Ltd. Copyright 2019-2021 The Matrix.org Foundation C.I.C. SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ // The maximum number of servers to pick when working out which servers // to add to permalinks. The servers are appended as ?via=example.org const MAX_SERVER_CANDIDATES = 3; const ANY_REGEX = /.*/; // Permalinks can have servers appended to them so that the user // receiving them can have a fighting chance at joining the room. // These servers are called "candidates" at this point because // it is unclear whether they are going to be useful to actually // join in the future. // // We pick 3 servers based on the following criteria: // // Server 1: The highest power level user in the room, provided // they are at least PL 50. We don't calculate "what is a moderator" // here because it is less relevant for the vast majority of rooms. // We also want to ensure that we get an admin or high-ranking mod // as they are less likely to leave the room. If no user happens // to meet this criteria, we'll pick the most popular server in the // room. // // Server 2: The next most popular server in the room (in user // distribution). This cannot be the same as Server 1. If no other // servers are available then we'll only return Server 1. // // Server 3: The next most popular server by user distribution. This // has the same rules as Server 2, with the added exception that it // must be unique from Server 1 and 2. // Rationale for popular servers: It's hard to get rid of people when // they keep flocking in from a particular server. Sure, the server could // be ACL'd in the future or for some reason be evicted from the room // however an event like that is unlikely the larger the room gets. If // the server is ACL'd at the time of generating the link however, we // shouldn't pick them. We also don't pick IP addresses. // Note: we don't pick the server the room was created on because the // homeserver should already be using that server as a last ditch attempt // and there's less of a guarantee that the server is a resident server. // Instead, we actively figure out which servers are likely to be residents // in the future and try to use those. // Note: Users receiving permalinks that happen to have all 3 potential // servers fail them (in terms of joining) are somewhat expected to hunt // down the person who gave them the link to ask for a participating server. // The receiving user can then manually append the known-good server to // the list and magically have the link work. class RoomPermalinkCreator { // We support being given a roomId as a fallback in the event the `room` object // doesn't exist or is not healthy for us to rely on. For example, loading a // permalink to a room which the MatrixClient doesn't know about. // Some of the tests done by this class are relatively expensive, so normally // throttled to not happen on every update. Pass false as the shouldThrottle // param to disable this behaviour, eg. for tests. constructor(room, roomId = null, shouldThrottle = true) { (0, _defineProperty2.default)(this, "roomId", void 0); (0, _defineProperty2.default)(this, "highestPlUserId", null); (0, _defineProperty2.default)(this, "populationMap", {}); (0, _defineProperty2.default)(this, "bannedHostsRegexps", []); (0, _defineProperty2.default)(this, "allowedHostsRegexps", []); (0, _defineProperty2.default)(this, "_serverCandidates", void 0); (0, _defineProperty2.default)(this, "started", false); (0, _defineProperty2.default)(this, "onRoomStateUpdate", () => { this.fullUpdate(); }); (0, _defineProperty2.default)(this, "updateServerCandidates", () => { const candidates = new Set(); if (this.highestPlUserId) { candidates.add(getServerName(this.highestPlUserId)); } const serversByPopulation = Object.keys(this.populationMap).sort((a, b) => this.populationMap[b] - this.populationMap[a]); for (let i = 0; i < serversByPopulation.length && candidates.size < MAX_SERVER_CANDIDATES; i++) { const serverName = serversByPopulation[i]; const domain = getHostnameFromMatrixServerName(serverName) ?? ""; if (!candidates.has(serverName) && !isHostnameIpAddress(domain) && !isHostInRegex(domain, this.bannedHostsRegexps) && isHostInRegex(domain, this.allowedHostsRegexps)) { candidates.add(serverName); } } this._serverCandidates = [...candidates]; }); this.room = room; this.roomId = room ? room.roomId : roomId; if (!this.roomId) { throw new Error("Failed to resolve a roomId for the permalink creator to use"); } } load() { if (!this.room || !this.room.currentState) { // Under rare and unknown circumstances it is possible to have a room with no // currentState, at least potentially at the early stages of joining a room. // To avoid breaking everything, we'll just warn rather than throw as well as // not bother updating the various aspects of the share link. _logger.logger.warn("Tried to load a permalink creator with no room state"); return; } this.fullUpdate(); } start() { if (this.started) return; this.load(); this.room?.currentState.on(_matrix.RoomStateEvent.Update, this.onRoomStateUpdate); this.started = true; } stop() { this.room?.currentState.removeListener(_matrix.RoomStateEvent.Update, this.onRoomStateUpdate); this.started = false; } get serverCandidates() { return this._serverCandidates; } forEvent(eventId) { return getPermalinkConstructor().forEvent(this.roomId, eventId, this._serverCandidates); } forShareableRoom() { if (this.room) { // Prefer to use canonical alias for permalink if possible const alias = this.room.getCanonicalAlias(); if (alias) { return getPermalinkConstructor().forRoom(alias); } } return getPermalinkConstructor().forRoom(this.roomId, this._serverCandidates); } forRoom() { return getPermalinkConstructor().forRoom(this.roomId, this._serverCandidates); } fullUpdate() { // This updates the internal state of this object from the room state. It's broken // down into separate functions, previously because we did some of these as incremental // updates, but they were on member events which can be very numerous, so the incremental // updates ended up being much slower than a full update. We now have the batch state update // event, so we just update in full, but on each batch of updates. this.updateAllowedServers(); this.updateHighestPlUser(); this.updatePopulationMap(); this.updateServerCandidates(); } updateHighestPlUser() { const plEvent = this.room?.currentState.getStateEvents("m.room.power_levels", ""); if (plEvent) { const content = plEvent.getContent(); if (content) { const users = content.users; if (users) { const entries = Object.entries(users); const allowedEntries = entries.filter(([userId]) => { const member = this.room?.getMember(userId); if (!member || member.membership !== _types.KnownMembership.Join) { return false; } const serverName = getServerName(userId); const domain = getHostnameFromMatrixServerName(serverName) ?? serverName; return !isHostnameIpAddress(domain) && !isHostInRegex(domain, this.bannedHostsRegexps) && isHostInRegex(domain, this.allowedHostsRegexps); }); const maxEntry = allowedEntries.reduce((max, entry) => { return entry[1] > max[1] ? entry : max; }, [null, 0]); const [userId, powerLevel] = maxEntry; // object wasn't empty, and max entry wasn't a demotion from the default if (userId !== null && powerLevel >= 50) { this.highestPlUserId = userId; return; } } } } this.highestPlUserId = null; } updateAllowedServers() { const bannedHostsRegexps = []; let allowedHostsRegexps = [ANY_REGEX]; // default allow everyone if (this.room?.currentState) { const aclEvent = this.room?.currentState.getStateEvents(_matrix.EventType.RoomServerAcl, ""); if (aclEvent && aclEvent.getContent()) { const getRegex = hostname => new RegExp("^" + utils.globToRegexp(hostname) + "$"); const denied = aclEvent.getContent().deny; if (Array.isArray(denied)) { denied.forEach(h => bannedHostsRegexps.push(getRegex(h))); } const allowed = aclEvent.getContent().allow; allowedHostsRegexps = []; // we don't want to use the default rule here if (Array.isArray(denied)) { allowed.forEach(h => allowedHostsRegexps.push(getRegex(h))); } } } this.bannedHostsRegexps = bannedHostsRegexps; this.allowedHostsRegexps = allowedHostsRegexps; } updatePopulationMap() { const populationMap = {}; if (this.room) { for (const member of this.room.getJoinedMembers()) { const serverName = getServerName(member.userId); if (!populationMap[serverName]) { populationMap[serverName] = 0; } populationMap[serverName]++; } } this.populationMap = populationMap; } } /** * Creates a permalink for an Entity. If isPill is set it uses a spec-compliant * prefix for the permalink, instead of permalink_prefix * @param {string} entityId The entity to link to. * @param {boolean} isPill Link should be pillifyable. * @returns {string|null} The transformed permalink or null if unable. */ exports.RoomPermalinkCreator = RoomPermalinkCreator; function makeGenericPermalink(entityId, isPill = false) { return getPermalinkConstructor(isPill).forEntity(entityId); } /** * Creates a permalink for a User. If isPill is set it uses a spec-compliant * prefix for the permalink, instead of permalink_prefix * @param {string} userId The user to link to. * @param {boolean} isPill Link should be pillifyable. * @returns {string|null} The transformed permalink or null if unable. */ function makeUserPermalink(userId, isPill = false) { return getPermalinkConstructor(isPill).forUser(userId); } /** * Creates a permalink for a room. If isPill is set it uses a spec-compliant * prefix for the permalink, instead of permalink_prefix * @param {MatrixClient} matrixClient The MatrixClient to use * @param {string} roomId The user to link to. * @param {boolean} isPill Link should be pillifyable. * @returns {string|null} The transformed permalink or null if unable. */ function makeRoomPermalink(matrixClient, roomId, isPill = false) { if (!roomId) { throw new Error("can't permalink a falsy roomId"); } // If the roomId isn't actually a room ID, don't try to list the servers. // Aliases are already routable, and don't need extra information. if (roomId[0] !== "!") return getPermalinkConstructor(isPill).forRoom(roomId, []); const room = matrixClient.getRoom(roomId); if (!room) { return getPermalinkConstructor(isPill).forRoom(roomId, []); } const permalinkCreator = new RoomPermalinkCreator(room); permalinkCreator.load(); return permalinkCreator.forShareableRoom(); } function isPermalinkHost(host) { // Always check if the permalink is a spec permalink (callers are likely to call // parsePermalink after this function). if (new _MatrixToPermalinkConstructor.default().isPermalinkHost(host)) return true; return getPermalinkConstructor().isPermalinkHost(host); } /** * Transforms an entity (permalink, room alias, user ID, etc) into a local URL * if possible. If it is already a permalink (matrix.to) it gets returned * unchanged. * @param {string} entity The entity to transform. * @returns {string|null} The transformed permalink or null if unable. */ function tryTransformEntityToPermalink(matrixClient, entity) { if (!entity) return null; // Check to see if it is a bare entity for starters if (entity[0] === "#" || entity[0] === "!") return makeRoomPermalink(matrixClient, entity); if (entity[0] === "@") return makeUserPermalink(entity); if (entity.slice(0, 7) === "matrix:") { try { const permalinkParts = parsePermalink(entity); if (permalinkParts) { if (permalinkParts.roomIdOrAlias) { const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : ""; let pl = _MatrixToPermalinkConstructor.baseUrl + `/#/${permalinkParts.roomIdOrAlias}${eventIdPart}`; if (permalinkParts.viaServers?.length) { pl += new _MatrixToPermalinkConstructor.default().encodeServerCandidates(permalinkParts.viaServers); } return pl; } else if (permalinkParts.userId) { return _MatrixToPermalinkConstructor.baseUrl + `/#/${permalinkParts.userId}`; } } } catch {} } return entity; } /** * Transforms a permalink (or possible permalink) into a local URL if possible. If * the given permalink is found to not be a permalink, it'll be returned unaltered. * @param {string} permalink The permalink to try and transform. * @returns {string} The transformed permalink or original URL if unable. */ function tryTransformPermalinkToLocalHref(permalink) { if (!permalink.startsWith("http:") && !permalink.startsWith("https:") && !permalink.startsWith("matrix:") && !permalink.startsWith("vector:") // Element Desktop ) { return permalink; } try { const m = decodeURIComponent(permalink).match(_linkifyMatrix.ELEMENT_URL_PATTERN); if (m) { return m[1]; } } catch (e) { // Not a valid URI return permalink; } // A bit of a hack to convert permalinks of unknown origin to Element links try { const permalinkParts = parsePermalink(permalink); if (permalinkParts) { if (permalinkParts.roomIdOrAlias) { const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : ""; permalink = `#/room/${permalinkParts.roomIdOrAlias}${eventIdPart}`; if (permalinkParts.viaServers?.length) { permalink += new _MatrixToPermalinkConstructor.default().encodeServerCandidates(permalinkParts.viaServers); } } else if (permalinkParts.userId) { permalink = `#/user/${permalinkParts.userId}`; } // else not a valid permalink for our purposes - do not handle } } catch (e) { // Not an href we need to care about } return permalink; } function getPrimaryPermalinkEntity(permalink) { try { let permalinkParts = parsePermalink(permalink); // If not a permalink, try the vector patterns. if (!permalinkParts) { const m = permalink.match(_linkifyMatrix.ELEMENT_URL_PATTERN); if (m) { // A bit of a hack, but it gets the job done const handler = new _ElementPermalinkConstructor.default("http://localhost"); const entityInfo = m[1].split("#").slice(1).join("#"); permalinkParts = handler.parsePermalink(`http://localhost/#${entityInfo}`); } } if (!permalinkParts) return null; // not processable if (permalinkParts.userId) return permalinkParts.userId; if (permalinkParts.roomIdOrAlias) return permalinkParts.roomIdOrAlias; } catch (e) { // no entity - not a permalink } return null; } /** * Returns the correct PermalinkConstructor based on permalink_prefix * and isPill * @param {boolean} isPill Should constructed links be pillifyable. * @returns {string|null} The transformed permalink or null if unable. */ function getPermalinkConstructor(isPill = false) { const elementPrefix = _SdkConfig.default.get("permalink_prefix"); if (elementPrefix && elementPrefix !== _MatrixToPermalinkConstructor.baseUrl && !isPill) { return new _ElementPermalinkConstructor.default(elementPrefix); } return new _MatrixToPermalinkConstructor.default(); } function parsePermalink(fullUrl) { try { const elementPrefix = _SdkConfig.default.get("permalink_prefix"); const decodedUrl = decodeURIComponent(fullUrl); if (new RegExp(_MatrixToPermalinkConstructor.baseUrlPattern, "i").test(decodedUrl)) { return new _MatrixToPermalinkConstructor.default().parsePermalink(decodedUrl); } else if (fullUrl.startsWith("matrix:")) { return new _MatrixSchemePermalinkConstructor.default().parsePermalink(fullUrl); } else if (elementPrefix && fullUrl.startsWith(elementPrefix)) { return new _ElementPermalinkConstructor.default(elementPrefix).parsePermalink(fullUrl); } } catch (e) { _logger.logger.error("Failed to parse permalink", e); } return null; // not a permalink we can handle } function getServerName(userId) { return userId.split(":").splice(1).join(":"); } function getHostnameFromMatrixServerName(serverName) { if (!serverName) return null; try { return new URL(`https://${serverName}`).hostname; } catch (e) { console.error("Error encountered while extracting hostname from server name", e); return null; } } function isHostInRegex(hostname, regexps) { if (!hostname) return true; // assumed if (regexps.length > 0 && !regexps[0].test) throw new Error(regexps[0].toString()); return regexps.some(h => h.test(hostname)); } function isHostnameIpAddress(hostname) { if (!hostname) return false; // is-ip doesn't want IPv6 addresses surrounded by brackets, so // take them off. if (hostname.startsWith("[") && hostname.endsWith("]")) { hostname = hostname.substring(1, hostname.length - 1); } return (0, _isIp.default)(hostname); } const calculateRoomVia = room => { const permalinkCreator = new RoomPermalinkCreator(room); permalinkCreator.load(); return permalinkCreator.serverCandidates ?? []; }; exports.calculateRoomVia = calculateRoomVia; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfaXNJcCIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJyZXF1aXJlIiwidXRpbHMiLCJfaW50ZXJvcFJlcXVpcmVXaWxkY2FyZCIsIl9tYXRyaXgiLCJfdHlwZXMiLCJfbG9nZ2VyIiwiX01hdHJpeFRvUGVybWFsaW5rQ29uc3RydWN0b3IiLCJfRWxlbWVudFBlcm1hbGlua0NvbnN0cnVjdG9yIiwiX1Nka0NvbmZpZyIsIl9saW5raWZ5TWF0cml4IiwiX01hdHJpeFNjaGVtZVBlcm1hbGlua0NvbnN0cnVjdG9yIiwiX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlIiwiZSIsIldlYWtNYXAiLCJyIiwidCIsIl9fZXNNb2R1bGUiLCJkZWZhdWx0IiwiaGFzIiwiZ2V0IiwibiIsIl9fcHJvdG9fXyIsImEiLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsInUiLCJoYXNPd25Qcm9wZXJ0eSIsImNhbGwiLCJpIiwic2V0IiwiTUFYX1NFUlZFUl9DQU5ESURBVEVTIiwiQU5ZX1JFR0VYIiwiUm9vbVBlcm1hbGlua0NyZWF0b3IiLCJjb25zdHJ1Y3RvciIsInJvb20iLCJyb29tSWQiLCJzaG91bGRUaHJvdHRsZSIsIl9kZWZpbmVQcm9wZXJ0eTIiLCJmdWxsVXBkYXRlIiwiY2FuZGlkYXRlcyIsIlNldCIsImhpZ2hlc3RQbFVzZXJJZCIsImFkZCIsImdldFNlcnZlck5hbWUiLCJzZXJ2ZXJzQnlQb3B1bGF0aW9uIiwia2V5cyIsInBvcHVsYXRpb25NYXAiLCJzb3J0IiwiYiIsImxlbmd0aCIsInNpemUiLCJzZXJ2ZXJOYW1lIiwiZG9tYWluIiwiZ2V0SG9zdG5hbWVGcm9tTWF0cml4U2VydmVyTmFtZSIsImlzSG9zdG5hbWVJcEFkZHJlc3MiLCJpc0hvc3RJblJlZ2V4IiwiYmFubmVkSG9zdHNSZWdleHBzIiwiYWxsb3dlZEhvc3RzUmVnZXhwcyIsIl9zZXJ2ZXJDYW5kaWRhdGVzIiwiRXJyb3IiLCJsb2FkIiwiY3VycmVudFN0YXRlIiwibG9nZ2VyIiwid2FybiIsInN0YXJ0Iiwic3RhcnRlZCIsIm9uIiwiUm9vbVN0YXRlRXZlbnQiLCJVcGRhdGUiLCJvblJvb21TdGF0ZVVwZGF0ZSIsInN0b3AiLCJyZW1vdmVMaXN0ZW5lciIsInNlcnZlckNhbmRpZGF0ZXMiLCJmb3JFdmVudCIsImV2ZW50SWQiLCJnZXRQZXJtYWxpbmtDb25zdHJ1Y3RvciIsImZvclNoYXJlYWJsZVJvb20iLCJhbGlhcyIsImdldENhbm9uaWNhbEFsaWFzIiwiZm9yUm9vbSIsInVwZGF0ZUFsbG93ZWRTZXJ2ZXJzIiwidXBkYXRlSGlnaGVzdFBsVXNlciIsInVwZGF0ZVBvcHVsYXRpb25NYXAiLCJ1cGRhdGVTZXJ2ZXJDYW5kaWRhdGVzIiwicGxFdmVudCIsImdldFN0YXRlRXZlbnRzIiwiY29udGVudCIsImdldENvbnRlbnQiLCJ1c2VycyIsImVudHJpZXMiLCJhbGxvd2VkRW50cmllcyIsImZpbHRlciIsInVzZXJJZCIsIm1lbWJlciIsImdldE1lbWJlciIsIm1lbWJlcnNoaXAiLCJLbm93bk1lbWJlcnNoaXAiLCJKb2luIiwibWF4RW50cnkiLCJyZWR1Y2UiLCJtYXgiLCJlbnRyeSIsInBvd2VyTGV2ZWwiLCJhY2xFdmVudCIsIkV2ZW50VHlwZSIsIlJvb21TZXJ2ZXJBY2wiLCJnZXRSZWdleCIsImhvc3RuYW1lIiwiUmVnRXhwIiwiZ2xvYlRvUmVnZXhwIiwiZGVuaWVkIiwiZGVueSIsIkFycmF5IiwiaXNBcnJheSIsImZvckVhY2giLCJoIiwicHVzaCIsImFsbG93ZWQiLCJhbGxvdyIsImdldEpvaW5lZE1lbWJlcnMiLCJleHBvcnRzIiwibWFrZUdlbmVyaWNQZXJtYWxpbmsiLCJlbnRpdHlJZCIsImlzUGlsbCIsImZvckVudGl0eSIsIm1ha2VVc2VyUGVybWFsaW5rIiwiZm9yVXNlciIsIm1ha2VSb29tUGVybWFsaW5rIiwibWF0cml4Q2xpZW50IiwiZ2V0Um9vbSIsInBlcm1hbGlua0NyZWF0b3IiLCJpc1Blcm1hbGlua0hvc3QiLCJob3N0IiwiTWF0cml4VG9QZXJtYWxpbmtDb25zdHJ1Y3RvciIsInRyeVRyYW5zZm9ybUVudGl0eVRvUGVybWFsaW5rIiwiZW50aXR5Iiwic2xpY2UiLCJwZXJtYWxpbmtQYXJ0cyIsInBhcnNlUGVybWFsaW5rIiwicm9vbUlkT3JBbGlhcyIsImV2ZW50SWRQYXJ0IiwicGwiLCJtYXRyaXh0b0Jhc2VVcmwiLCJ2aWFTZXJ2ZXJzIiwiZW5jb2RlU2VydmVyQ2FuZGlkYXRlcyIsInRyeVRyYW5zZm9ybVBlcm1hbGlua1RvTG9jYWxIcmVmIiwicGVybWFsaW5rIiwic3RhcnRzV2l0aCIsIm0iLCJkZWNvZGVVUklDb21wb25lbnQiLCJtYXRjaCIsIkVMRU1FTlRfVVJMX1BBVFRFUk4iLCJnZXRQcmltYXJ5UGVybWFsaW5rRW50aXR5IiwiaGFuZGxlciIsIkVsZW1lbnRQZXJtYWxpbmtDb25zdHJ1Y3RvciIsImVudGl0eUluZm8iLCJzcGxpdCIsImpvaW4iLCJlbGVtZW50UHJlZml4IiwiU2RrQ29uZmlnIiwiZnVsbFVybCIsImRlY29kZWRVcmwiLCJtYXRyaXhUb0Jhc2VVcmxQYXR0ZXJuIiwidGVzdCIsIk1hdHJpeFNjaGVtZVBlcm1hbGlua0NvbnN0cnVjdG9yIiwiZXJyb3IiLCJzcGxpY2UiLCJVUkwiLCJjb25zb2xlIiwicmVnZXhwcyIsInRvU3RyaW5nIiwic29tZSIsImVuZHNXaXRoIiwic3Vic3RyaW5nIiwiaXNJcCIsImNhbGN1bGF0ZVJvb21WaWEiXSwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvdXRpbHMvcGVybWFsaW5rcy9QZXJtYWxpbmtzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qXG5Db3B5cmlnaHQgMjAyNCBOZXcgVmVjdG9yIEx0ZC5cbkNvcHlyaWdodCAyMDE5LTIwMjEgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuaW1wb3J0IGlzSXAgZnJvbSBcImlzLWlwXCI7XG5pbXBvcnQgKiBhcyB1dGlscyBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvdXRpbHNcIjtcbmltcG9ydCB7IFJvb20sIE1hdHJpeENsaWVudCwgUm9vbVN0YXRlRXZlbnQsIEV2ZW50VHlwZSB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9tYXRyaXhcIjtcbmltcG9ydCB7IEtub3duTWVtYmVyc2hpcCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy90eXBlc1wiO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL2xvZ2dlclwiO1xuXG5pbXBvcnQgTWF0cml4VG9QZXJtYWxpbmtDb25zdHJ1Y3Rvciwge1xuICAgIGJhc2VVcmwgYXMgbWF0cml4dG9CYXNlVXJsLFxuICAgIGJhc2VVcmxQYXR0ZXJuIGFzIG1hdHJpeFRvQmFzZVVybFBhdHRlcm4sXG59IGZyb20gXCIuL01hdHJpeFRvUGVybWFsaW5rQ29uc3RydWN0b3JcIjtcbmltcG9ydCBQZXJtYWxpbmtDb25zdHJ1Y3RvciwgeyBQZXJtYWxpbmtQYXJ0cyB9IGZyb20gXCIuL1Blcm1hbGlua0NvbnN0cnVjdG9yXCI7XG5pbXBvcnQgRWxlbWVudFBlcm1hbGlua0NvbnN0cnVjdG9yIGZyb20gXCIuL0VsZW1lbnRQZXJtYWxpbmtDb25zdHJ1Y3RvclwiO1xuaW1wb3J0IFNka0NvbmZpZyBmcm9tIFwiLi4vLi4vU2RrQ29uZmlnXCI7XG5pbXBvcnQgeyBFTEVNRU5UX1VSTF9QQVRURVJOIH0gZnJvbSBcIi4uLy4uL2xpbmtpZnktbWF0cml4XCI7XG5pbXBvcnQgTWF0cml4U2NoZW1lUGVybWFsaW5rQ29uc3RydWN0b3IgZnJvbSBcIi4vTWF0cml4U2NoZW1lUGVybWFsaW5rQ29uc3RydWN0b3JcIjtcblxuLy8gVGhlIG1heGltdW0gbnVtYmVyIG9mIHNlcnZlcnMgdG8gcGljayB3aGVuIHdvcmtpbmcgb3V0IHdoaWNoIHNlcnZlcnNcbi8vIHRvIGFkZCB0byBwZXJtYWxpbmtzLiBUaGUgc2VydmVycyBhcmUgYXBwZW5kZWQgYXMgP3ZpYT1leGFtcGxlLm9yZ1xuY29uc3QgTUFYX1NFUlZFUl9DQU5ESURBVEVTID0gMztcblxuY29uc3QgQU5ZX1JFR0VYID0gLy4qLztcblxuLy8gUGVybWFsaW5rcyBjYW4gaGF2ZSBzZXJ2ZXJzIGFwcGVuZGVkIHRvIHRoZW0gc28gdGhhdCB0aGUgdXNlclxuLy8gcmVjZWl2aW5nIHRoZW0gY2FuIGhhdmUgYSBmaWdodGluZyBjaGFuY2UgYXQgam9pbmluZyB0aGUgcm9vbS5cbi8vIFRoZXNlIHNlcnZlcnMgYXJlIGNhbGxlZCBcImNhbmRpZGF0ZXNcIiBhdCB0aGlzIHBvaW50IGJlY2F1c2Vcbi8vIGl0IGlzIHVuY2xlYXIgd2hldGhlciB0aGV5IGFyZSBnb2luZyB0byBiZSB1c2VmdWwgdG8gYWN0dWFsbHlcbi8vIGpvaW4gaW4gdGhlIGZ1dHVyZS5cbi8vXG4vLyBXZSBwaWNrIDMgc2VydmVycyBiYXNlZCBvbiB0aGUgZm9sbG93aW5nIGNyaXRlcmlhOlxuLy9cbi8vICAgU2VydmVyIDE6IFRoZSBoaWdoZXN0IHBvd2VyIGxldmVsIHVzZXIgaW4gdGhlIHJvb20sIHByb3ZpZGVkXG4vLyAgIHRoZXkgYXJlIGF0IGxlYXN0IFBMIDUwLiBXZSBkb24ndCBjYWxjdWxhdGUgXCJ3aGF0IGlzIGEgbW9kZXJhdG9yXCJcbi8vICAgaGVyZSBiZWNhdXNlIGl0IGlzIGxlc3MgcmVsZXZhbnQgZm9yIHRoZSB2YXN0IG1ham9yaXR5IG9mIHJvb21zLlxuLy8gICBXZSBhbHNvIHdhbnQgdG8gZW5zdXJlIHRoYXQgd2UgZ2V0IGFuIGFkbWluIG9yIGhpZ2gtcmFua2luZyBtb2Rcbi8vICAgYXMgdGhleSBhcmUgbGVzcyBsaWtlbHkgdG8gbGVhdmUgdGhlIHJvb20uIElmIG5vIHVzZXIgaGFwcGVuc1xuLy8gICB0byBtZWV0IHRoaXMgY3JpdGVyaWEsIHdlJ2xsIHBpY2sgdGhlIG1vc3QgcG9wdWxhciBzZXJ2ZXIgaW4gdGhlXG4vLyAgIHJvb20uXG4vL1xuLy8gICBTZXJ2ZXIgMjogVGhlIG5leHQgbW9zdCBwb3B1bGFyIHNlcnZlciBpbiB0aGUgcm9vbSAoaW4gdXNlclxuLy8gICBkaXN0cmlidXRpb24pLiBUaGlzIGNhbm5vdCBiZSB0aGUgc2FtZSBhcyBTZXJ2ZXIgMS4gSWYgbm8gb3RoZXJcbi8vICAgc2VydmVycyBhcmUgYXZhaWxhYmxlIHRoZW4gd2UnbGwgb25seSByZXR1cm4gU2VydmVyIDEuXG4vL1xuLy8gICBTZXJ2ZXIgMzogVGhlIG5leHQgbW9zdCBwb3B1bGFyIHNlcnZlciBieSB1c2VyIGRpc3RyaWJ1dGlvbi4gVGhpc1xuLy8gICBoYXMgdGhlIHNhbWUgcnVsZXMgYXMgU2VydmVyIDIsIHdpdGggdGhlIGFkZGVkIGV4Y2VwdGlvbiB0aGF0IGl0XG4vLyAgIG11c3QgYmUgdW5pcXVlIGZyb20gU2VydmVyIDEgYW5kIDIuXG5cbi8vIFJhdGlvbmFsZSBmb3IgcG9wdWxhciBzZXJ2ZXJzOiBJdCdzIGhhcmQgdG8gZ2V0IHJpZCBvZiBwZW9wbGUgd2hlblxuLy8gdGhleSBrZWVwIGZsb2NraW5nIGluIGZyb20gYSBwYXJ0aWN1bGFyIHNlcnZlci4gU3VyZSwgdGhlIHNlcnZlciBjb3VsZFxuLy8gYmUgQUNMJ2QgaW4gdGhlIGZ1dHVyZSBvciBmb3Igc29tZSByZWFzb24gYmUgZXZpY3RlZCBmcm9tIHRoZSByb29tXG4vLyBob3dldmVyIGFuIGV2ZW50IGxpa2UgdGhhdCBpcyB1bmxpa2VseSB0aGUgbGFyZ2VyIHRoZSByb29tIGdldHMuIElmXG4vLyB0aGUgc2VydmVyIGlzIEFDTCdkIGF0IHRoZSB0aW1lIG9mIGdlbmVyYXRpbmcgdGhlIGxpbmsgaG93ZXZlciwgd2Vcbi8vIHNob3VsZG4ndCBwaWNrIHRoZW0uIFdlIGFsc28gZG9uJ3QgcGljayBJUCBhZGRyZXNzZXMuXG5cbi8vIE5vdGU6IHdlIGRvbid0IHBpY2sgdGhlIHNlcnZlciB0aGUgcm9vbSB3YXMgY3JlYXRlZCBvbiBiZWNhdXNlIHRoZVxuLy8gaG9tZXNlcnZlciBzaG91bGQgYWxyZWFkeSBiZSB1c2luZyB0aGF0IHNlcnZlciBhcyBhIGxhc3QgZGl0Y2ggYXR0ZW1wdFxuLy8gYW5kIHRoZXJlJ3MgbGVzcyBvZiBhIGd1YXJhbnRlZSB0aGF0IHRoZSBzZXJ2ZXIgaXMgYSByZXNpZGVudCBzZXJ2ZXIuXG4vLyBJbnN0ZWFkLCB3ZSBhY3RpdmVseSBmaWd1cmUgb3V0IHdoaWNoIHNlcnZlcnMgYXJlIGxpa2VseSB0byBiZSByZXNpZGVudHNcbi8vIGluIHRoZSBmdXR1cmUgYW5kIHRyeSB0byB1c2UgdGhvc2UuXG5cbi8vIE5vdGU6IFVzZXJzIHJlY2VpdmluZyBwZXJtYWxpbmtzIHRoYXQgaGFwcGVuIHRvIGhhdmUgYWxsIDMgcG90ZW50aWFsXG4vLyBzZXJ2ZXJzIGZhaWwgdGhlbSAoaW4gdGVybXMgb2Ygam9pbmluZykgYXJlIHNvbWV3aGF0IGV4cGVjdGVkIHRvIGh1bnRcbi8vIGRvd24gdGhlIHBlcnNvbiB3aG8gZ2F2ZSB0aGVtIHRoZSBsaW5rIHRvIGFzayBmb3IgYSBwYXJ0aWNpcGF0aW5nIHNlcnZlci5cbi8vIFRoZSByZWNlaXZpbmcgdXNlciBjYW4gdGhlbiBtYW51YWxseSBhcHBlbmQgdGhlIGtub3duLWdvb2Qgc2VydmVyIHRvXG4vLyB0aGUgbGlzdCBhbmQgbWFnaWNhbGx5IGhhdmUgdGhlIGxpbmsgd29yay5cblxuZXhwb3J0IGNsYXNzIFJvb21QZXJtYWxpbmtDcmVhdG9yIHtcbiAgICBwcml2YXRlIHJvb21JZDogc3RyaW5nO1xuICAgIHByaXZhdGUgaGlnaGVzdFBsVXNlcklkOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbiAgICBwcml2YXRlIHBvcHVsYXRpb25NYXA6IHsgW3NlcnZlck5hbWU6IHN0cmluZ106IG51bWJlciB9ID0ge307XG4gICAgcHJpdmF0ZSBiYW5uZWRIb3N0c1JlZ2V4cHM6IFJlZ0V4cFtdID0gW107XG4gICAgcHJpdmF0ZSBhbGxvd2VkSG9zdHNSZWdleHBzOiBSZWdFeHBbXSA9IFtdO1xuICAgIHByaXZhdGUgX3NlcnZlckNhbmRpZGF0ZXM/OiBzdHJpbmdbXTtcbiAgICBwcml2YXRlIHN0YXJ0ZWQgPSBmYWxzZTtcblxuICAgIC8vIFdlIHN1cHBvcnQgYmVpbmcgZ2l2ZW4gYSByb29tSWQgYXMgYSBmYWxsYmFjayBpbiB0aGUgZXZlbnQgdGhlIGByb29tYCBvYmplY3RcbiAgICAvLyBkb2Vzbid0IGV4aXN0IG9yIGlzIG5vdCBoZWFsdGh5IGZvciB1cyB0byByZWx5IG9uLiBGb3IgZXhhbXBsZSwgbG9hZGluZyBhXG4gICAgLy8gcGVybWFsaW5rIHRvIGEgcm9vbSB3aGljaCB0aGUgTWF0cml4Q2xpZW50IGRvZXNuJ3Qga25vdyBhYm91dC5cbiAgICAvLyBTb21lIG9mIHRoZSB0ZXN0cyBkb25lIGJ5IHRoaXMgY2xhc3MgYXJlIHJlbGF0aXZlbHkgZXhwZW5zaXZlLCBzbyBub3JtYWxseVxuICAgIC8vIHRocm90dGxlZCB0byBub3QgaGFwcGVuIG9uIGV2ZXJ5IHVwZGF0ZS4gUGFzcyBmYWxzZSBhcyB0aGUgc2hvdWxkVGhyb3R0bGVcbiAgICAvLyBwYXJhbSB0byBkaXNhYmxlIHRoaXMgYmVoYXZpb3VyLCBlZy4gZm9yIHRlc3RzLlxuICAgIHB1YmxpYyBjb25zdHJ1Y3RvcihcbiAgICAgICAgcHJpdmF0ZSByb29tOiBSb29tIHwgbnVsbCxcbiAgICAgICAgcm9vbUlkOiBzdHJpbmcgfCBudWxsID0gbnVsbCxcbiAgICAgICAgc2hvdWxkVGhyb3R0bGUgPSB0cnVlLFxuICAgICkge1xuICAgICAgICB0aGlzLnJvb21JZCA9IHJvb20gPyByb29tLnJvb21JZCA6IHJvb21JZCE7XG5cbiAgICAgICAgaWYgKCF0aGlzLnJvb21JZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiRmFpbGVkIHRvIHJlc29sdmUgYSByb29tSWQgZm9yIHRoZSBwZXJtYWxpbmsgY3JlYXRvciB0byB1c2VcIik7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwdWJsaWMgbG9hZCgpOiB2b2lkIHtcbiAgICAgICAgaWYgKCF0aGlzLnJvb20gfHwgIXRoaXMucm9vbS5jdXJyZW50U3RhdGUpIHtcbiAgICAgICAgICAgIC8vIFVuZGVyIHJhcmUgYW5kIHVua25vd24gY2lyY3Vtc3RhbmNlcyBpdCBpcyBwb3NzaWJsZSB0byBoYXZlIGEgcm9vbSB3aXRoIG5vXG4gICAgICAgICAgICAvLyBjdXJyZW50U3RhdGUsIGF0IGxlYXN0IHBvdGVudGlhbGx5IGF0IHRoZSBlYXJseSBzdGFnZXMgb2Ygam9pbmluZyBhIHJvb20uXG4gICAgICAgICAgICAvLyBUbyBhdm9pZCBicmVha2luZyBldmVyeXRoaW5nLCB3ZSdsbCBqdXN0IHdhcm4gcmF0aGVyIHRoYW4gdGhyb3cgYXMgd2VsbCBhc1xuICAgICAgICAgICAgLy8gbm90IGJvdGhlciB1cGRhdGluZyB0aGUgdmFyaW91cyBhc3BlY3RzIG9mIHRoZSBzaGFyZSBsaW5rLlxuICAgICAgICAgICAgbG9nZ2VyLndhcm4oXCJUcmllZCB0byBsb2FkIGEgcGVybWFsaW5rIGNyZWF0b3Igd2l0aCBubyByb29tIHN0YXRlXCIpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuZnVsbFVwZGF0ZSgpO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdGFydCgpOiB2b2lkIHtcbiAgICAgICAgaWYgKHRoaXMuc3RhcnRlZCkgcmV0dXJuO1xuICAgICAgICB0aGlzLmxvYWQoKTtcbiAgICAgICAgdGhpcy5yb29tPy5jdXJyZW50U3RhdGUub24oUm9vbVN0YXRlRXZlbnQuVXBkYXRlLCB0aGlzLm9uUm9vbVN0YXRlVXBkYXRlKTtcbiAgICAgICAgdGhpcy5zdGFydGVkID0gdHJ1ZTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc3RvcCgpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5yb29tPy5jdXJyZW50U3RhdGUucmVtb3ZlTGlzdGVuZXIoUm9vbVN0YXRlRXZlbnQuVXBkYXRlLCB0aGlzLm9uUm9vbVN0YXRlVXBkYXRlKTtcbiAgICAgICAgdGhpcy5zdGFydGVkID0gZmFsc2U7XG4gICAgfVxuXG4gICAgcHVibGljIGdldCBzZXJ2ZXJDYW5kaWRhdGVzKCk6IHN0cmluZ1tdIHwgdW5kZWZpbmVkIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3NlcnZlckNhbmRpZGF0ZXM7XG4gICAgfVxuXG4gICAgcHVibGljIGZvckV2ZW50KGV2ZW50SWQ6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiBnZXRQZXJtYWxpbmtDb25zdHJ1Y3RvcigpLmZvckV2ZW50KHRoaXMucm9vbUlkLCBldmVudElkLCB0aGlzLl9zZXJ2ZXJDYW5kaWRhdGVzKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZm9yU2hhcmVhYmxlUm9vbSgpOiBzdHJpbmcge1xuICAgICAgICBpZiAodGhpcy5yb29tKSB7XG4gICAgICAgICAgICAvLyBQcmVmZXIgdG8gdXNlIGNhbm9uaWNhbCBhbGlhcyBmb3IgcGVybWFsaW5rIGlmIHBvc3NpYmxlXG4gICAgICAgICAgICBjb25zdCBhbGlhcyA9IHRoaXMucm9vbS5nZXRDYW5vbmljYWxBbGlhcygpO1xuICAgICAgICAgICAgaWYgKGFsaWFzKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGdldFBlcm1hbGlua0NvbnN0cnVjdG9yKCkuZm9yUm9vbShhbGlhcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGdldFBlcm1hbGlua0NvbnN0cnVjdG9yKCkuZm9yUm9vbSh0aGlzLnJvb21JZCwgdGhpcy5fc2VydmVyQ2FuZGlkYXRlcyk7XG4gICAgfVxuXG4gICAgcHVibGljIGZvclJvb20oKTogc3RyaW5nIHtcbiAgICAgICAgcmV0dXJuIGdldFBlcm1hbGlua0NvbnN0cnVjdG9yKCkuZm9yUm9vbSh0aGlzLnJvb21JZCwgdGhpcy5fc2VydmVyQ2FuZGlkYXRlcyk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvblJvb21TdGF0ZVVwZGF0ZSA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgdGhpcy5mdWxsVXBkYXRlKCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgZnVsbFVwZGF0ZSgpOiB2b2lkIHtcbiAgICAgICAgLy8gVGhpcyB1cGRhdGVzIHRoZSBpbnRlcm5hbCBzdGF0ZSBvZiB0aGlzIG9iamVjdCBmcm9tIHRoZSByb29tIHN0YXRlLiBJdCdzIGJyb2tlblxuICAgICAgICAvLyBkb3duIGludG8gc2VwYXJhdGUgZnVuY3Rpb25zLCBwcmV2aW91c2x5IGJlY2F1c2Ugd2UgZGlkIHNvbWUgb2YgdGhlc2UgYXMgaW5jcmVtZW50YWxcbiAgICAgICAgLy8gdXBkYXRlcywgYnV0IHRoZXkgd2VyZSBvbiBtZW1iZXIgZXZlbnRzIHdoaWNoIGNhbiBiZSB2ZXJ5IG51bWVyb3VzLCBzbyB0aGUgaW5jcmVtZW50YWxcbiAgICAgICAgLy8gdXBkYXRlcyBlbmRlZCB1cCBiZWluZyBtdWNoIHNsb3dlciB0aGFuIGEgZnVsbCB1cGRhdGUuIFdlIG5vdyBoYXZlIHRoZSBiYXRjaCBzdGF0ZSB1cGRhdGVcbiAgICAgICAgLy8gZXZlbnQsIHNvIHdlIGp1c3QgdXBkYXRlIGluIGZ1bGwsIGJ1dCBvbiBlYWNoIGJhdGNoIG9mIHVwZGF0ZXMuXG4gICAgICAgIHRoaXMudXBkYXRlQWxsb3dlZFNlcnZlcnMoKTtcbiAgICAgICAgdGhpcy51cGRhdGVIaWdoZXN0UGxVc2VyKCk7XG4gICAgICAgIHRoaXMudXBkYXRlUG9wdWxhdGlvbk1hcCgpO1xuICAgICAgICB0aGlzLnVwZGF0ZVNlcnZlckNhbmRpZGF0ZXMoKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIHVwZGF0ZUhpZ2hlc3RQbFVzZXIoKTogdm9pZCB7XG4gICAgICAgIGNvbnN0IHBsRXZlbnQgPSB0aGlzLnJvb20/LmN1cnJlbnRTdGF0ZS5nZXRTdGF0ZUV2ZW50cyhcIm0ucm9vbS5wb3dlcl9sZXZlbHNcIiwgXCJcIik7XG4gICAgICAgIGlmIChwbEV2ZW50KSB7XG4gICAgICAgICAgICBjb25zdCBjb250ZW50ID0gcGxFdmVudC5nZXRDb250ZW50KCk7XG4gICAgICAgICAgICBpZiAoY29udGVudCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHVzZXJzOiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+ID0gY29udGVudC51c2VycztcbiAgICAgICAgICAgICAgICBpZiAodXNlcnMpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgZW50cmllcyA9IE9iamVjdC5lbnRyaWVzKHVzZXJzKTtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgYWxsb3dlZEVudHJpZXMgPSBlbnRyaWVzLmZpbHRlcigoW3VzZXJJZF0pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IG1lbWJlciA9IHRoaXMucm9vbT8uZ2V0TWVtYmVyKHVzZXJJZCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoIW1lbWJlciB8fCBtZW1iZXIubWVtYmVyc2hpcCAhPT0gS25vd25NZW1iZXJzaGlwLkpvaW4pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBzZXJ2ZXJOYW1lID0gZ2V0U2VydmVyTmFtZSh1c2VySWQpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBkb21haW4gPSBnZXRIb3N0bmFtZUZyb21NYXRyaXhTZXJ2ZXJOYW1lKHNlcnZlck5hbWUpID8/IHNlcnZlck5hbWU7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpc0hvc3RuYW1lSXBBZGRyZXNzKGRvbWFpbikgJiZcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXNIb3N0SW5SZWdleChkb21haW4sIHRoaXMuYmFubmVkSG9zdHNSZWdleHBzKSAmJlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzSG9zdEluUmVnZXgoZG9tYWluLCB0aGlzLmFsbG93ZWRIb3N0c1JlZ2V4cHMpXG4gICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgbWF4RW50cnkgPSBhbGxvd2VkRW50cmllcy5yZWR1Y2U8W3N0cmluZyB8IG51bGwsIG51bWJlcl0+KFxuICAgICAgICAgICAgICAgICAgICAgICAgKG1heCwgZW50cnkpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZW50cnlbMV0gPiBtYXhbMV0gPyBlbnRyeSA6IG1heDtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICBbbnVsbCwgMF0sXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IFt1c2VySWQsIHBvd2VyTGV2ZWxdID0gbWF4RW50cnk7XG4gICAgICAgICAgICAgICAgICAgIC8vIG9iamVjdCB3YXNuJ3QgZW1wdHksIGFuZCBtYXggZW50cnkgd2Fzbid0IGEgZGVtb3Rpb24gZnJvbSB0aGUgZGVmYXVsdFxuICAgICAgICAgICAgICAgICAgICBpZiAodXNlcklkICE9PSBudWxsICYmIHBvd2VyTGV2ZWwgPj0gNTApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuaGlnaGVzdFBsVXNlcklkID0gdXNlcklkO1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHRoaXMuaGlnaGVzdFBsVXNlcklkID0gbnVsbDtcbiAgICB9XG5cbiAgICBwcml2YXRlIHVwZGF0ZUFsbG93ZWRTZXJ2ZXJzKCk6IHZvaWQge1xuICAgICAgICBjb25zdCBiYW5uZWRIb3N0c1JlZ2V4cHM6IFJlZ0V4cFtdID0gW107XG4gICAgICAgIGxldCBhbGxvd2VkSG9zdHNSZWdleHBzID0gW0FOWV9SRUdFWF07IC8vIGRlZmF1bHQgYWxsb3cgZXZlcnlvbmVcbiAgICAgICAgaWYgKHRoaXMucm9vbT8uY3VycmVudFN0YXRlKSB7XG4gICAgICAgICAgICBjb25zdCBhY2xFdmVudCA9IHRoaXMucm9vbT8uY3VycmVudFN0YXRlLmdldFN0YXRlRXZlbnRzKEV2ZW50VHlwZS5Sb29tU2VydmVyQWNsLCBcIlwiKTtcbiAgICAgICAgICAgIGlmIChhY2xFdmVudCAmJiBhY2xFdmVudC5nZXRDb250ZW50KCkpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBnZXRSZWdleCA9IChob3N0bmFtZTogc3RyaW5nKTogUmVnRXhwID0+IG5ldyBSZWdFeHAoXCJeXCIgKyB1dGlscy5nbG9iVG9SZWdleHAoaG9zdG5hbWUpICsgXCIkXCIpO1xuXG4gICAgICAgICAgICAgICAgY29uc3QgZGVuaWVkID0gYWNsRXZlbnQuZ2V0Q29udGVudDx7IGRlbnk6IHN0cmluZ1tdIH0+KCkuZGVueTtcbiAgICAgICAgICAgICAgICBpZiAoQXJyYXkuaXNBcnJheShkZW5pZWQpKSB7XG4gICAgICAgICAgICAgICAgICAgIGRlbmllZC5mb3JFYWNoKChoKSA9PiBiYW5uZWRIb3N0c1JlZ2V4cHMucHVzaChnZXRSZWdleChoKSkpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNvbnN0IGFsbG93ZWQgPSBhY2xFdmVudC5nZXRDb250ZW50PHsgYWxsb3c6IHN0cmluZ1tdIH0+KCkuYWxsb3c7XG4gICAgICAgICAgICAgICAgYWxsb3dlZEhvc3RzUmVnZXhwcyA9IFtdOyAvLyB3ZSBkb24ndCB3YW50IHRvIHVzZSB0aGUgZGVmYXVsdCBydWxlIGhlcmVcbiAgICAgICAgICAgICAgICBpZiAoQXJyYXkuaXNBcnJheShkZW5pZWQpKSB7XG4gICAgICAgICAgICAgICAgICAgIGFsbG93ZWQuZm9yRWFjaCgoaCkgPT4gYWxsb3dlZEhvc3RzUmVnZXhwcy5wdXNoKGdldFJlZ2V4KGgpKSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHRoaXMuYmFubmVkSG9zdHNSZWdleHBzID0gYmFubmVkSG9zdHNSZWdleHBzO1xuICAgICAgICB0aGlzLmFsbG93ZWRIb3N0c1JlZ2V4cHMgPSBhbGxvd2VkSG9zdHNSZWdleHBzO1xuICAgIH1cblxuICAgIHByaXZhdGUgdXBkYXRlUG9wdWxhdGlvbk1hcCgpOiB2b2lkIHtcbiAgICAgICAgY29uc3QgcG9wdWxhdGlvbk1hcDogeyBbc2VydmVyOiBzdHJpbmddOiBudW1iZXIgfSA9IHt9O1xuICAgICAgICBpZiAodGhpcy5yb29tKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IG1lbWJlciBvZiB0aGlzLnJvb20uZ2V0Sm9pbmVkTWVtYmVycygpKSB7XG4gICAgICAgICAgICAgICAgY29uc3Qgc2VydmVyTmFtZSA9IGdldFNlcnZlck5hbWUobWVtYmVyLnVzZXJJZCk7XG4gICAgICAgICAgICAgICAgaWYgKCFwb3B1bGF0aW9uTWFwW3NlcnZlck5hbWVdKSB7XG4gICAgICAgICAgICAgICAgICAgIHBvcHVsYXRpb25NYXBbc2VydmVyTmFtZV0gPSAwO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBwb3B1bGF0aW9uTWFwW3NlcnZlck5hbWVdKys7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5wb3B1bGF0aW9uTWFwID0gcG9wdWxhdGlvbk1hcDtcbiAgICB9XG5cbiAgICBwcml2YXRlIHVwZGF0ZVNlcnZlckNhbmRpZGF0ZXMgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIGNvbnN0IGNhbmRpZGF0ZXMgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgICAgICAgaWYgKHRoaXMuaGlnaGVzdFBsVXNlcklkKSB7XG4gICAgICAgICAgICBjYW5kaWRhdGVzLmFkZChnZXRTZXJ2ZXJOYW1lKHRoaXMuaGlnaGVzdFBsVXNlcklkKSk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBzZXJ2ZXJzQnlQb3B1bGF0aW9uID0gT2JqZWN0LmtleXModGhpcy5wb3B1bGF0aW9uTWFwKS5zb3J0KFxuICAgICAgICAgICAgKGEsIGIpID0+IHRoaXMucG9wdWxhdGlvbk1hcFtiXSAtIHRoaXMucG9wdWxhdGlvbk1hcFthXSxcbiAgICAgICAgKTtcblxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNlcnZlcnNCeVBvcHVsYXRpb24ubGVuZ3RoICYmIGNhbmRpZGF0ZXMuc2l6ZSA8IE1BWF9TRVJWRVJfQ0FORElEQVRFUzsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBzZXJ2ZXJOYW1lID0gc2VydmVyc0J5UG9wdWxhdGlvbltpXTtcbiAgICAgICAgICAgIGNvbnN0IGRvbWFpbiA9IGdldEhvc3RuYW1lRnJvbU1hdHJpeFNlcnZlck5hbWUoc2VydmVyTmFtZSkgPz8gXCJcIjtcbiAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICAhY2FuZGlkYXRlcy5oYXMoc2VydmVyTmFtZSkgJiZcbiAgICAgICAgICAgICAgICAhaXNIb3N0bmFtZUlwQWRkcmVzcyhkb21haW4pICYmXG4gICAgICAgICAgICAgICAgIWlzSG9zdEluUmVnZXgoZG9tYWluLCB0aGlzLmJhbm5lZEhvc3RzUmVnZXhwcykgJiZcbiAgICAgICAgICAgICAgICBpc0hvc3RJblJlZ2V4KGRvbWFpbiwgdGhpcy5hbGxvd2VkSG9zdHNSZWdleHBzKVxuICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgY2FuZGlkYXRlcy5hZGQoc2VydmVyTmFtZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLl9zZXJ2ZXJDYW5kaWRhdGVzID0gWy4uLmNhbmRpZGF0ZXNdO1xuICAgIH07XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIHBlcm1hbGluayBmb3IgYW4gRW50aXR5LiBJZiBpc1BpbGwgaXMgc2V0IGl0IHVzZXMgYSBzcGVjLWNvbXBsaWFudFxuICogcHJlZml4IGZvciB0aGUgcGVybWFsaW5rLCBpbnN0ZWFkIG9mIHBlcm1hbGlua19wcmVmaXhcbiAqIEBwYXJhbSB7c3RyaW5nfSBlbnRpdHlJZCBUaGUgZW50aXR5IHRvIGxpbmsgdG8uXG4gKiBAcGFyYW0ge2Jvb2xlYW59IGlzUGlsbCBMaW5rIHNob3VsZCBiZSBwaWxsaWZ5YWJsZS5cbiAqIEByZXR1cm5zIHtzdHJpbmd8bnVsbH0gVGhlIHRyYW5zZm9ybWVkIHBlcm1hbGluayBvciBudWxsIGlmIHVuYWJsZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1ha2VHZW5lcmljUGVybWFsaW5rKGVudGl0eUlkOiBzdHJpbmcsIGlzUGlsbCA9IGZhbHNlKTogc3RyaW5nIHtcbiAgICByZXR1cm4gZ2V0UGVybWFsaW5rQ29uc3RydWN0b3IoaXNQaWxsKS5mb3JFbnRpdHkoZW50aXR5SWQpO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSBwZXJtYWxpbmsgZm9yIGEgVXNlci4gSWYgaXNQaWxsIGlzIHNldCBpdCB1c2VzIGEgc3BlYy1jb21wbGlhbnRcbiAqIHByZWZpeCBmb3IgdGhlIHBlcm1hbGluaywgaW5zdGVhZCBvZiBwZXJtYWxpbmtfcHJlZml4XG4gKiBAcGFyYW0ge3N0cmluZ30gdXNlcklkIFRoZSB1c2VyIHRvIGxpbmsgdG8uXG4gKiBAcGFyYW0ge2Jvb2xlYW59IGlzUGlsbCBMaW5rIHNob3VsZCBiZSBwaWxsaWZ5YWJsZS5cbiAqIEByZXR1cm5zIHtzdHJpbmd8bnVsbH0gVGhlIHRyYW5zZm9ybWVkIHBlcm1hbGluayBvciBudWxsIGlmIHVuYWJsZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1ha2VVc2VyUGVybWFsaW5rKHVzZXJJZDogc3RyaW5nLCBpc1BpbGwgPSBmYWxzZSk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGdldFBlcm1hbGlua0NvbnN0cnVjdG9yKGlzUGlsbCkuZm9yVXNlcih1c2VySWQpO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSBwZXJtYWxpbmsgZm9yIGEgcm9vbS4gSWYgaXNQaWxsIGlzIHNldCBpdCB1c2VzIGEgc3BlYy1jb21wbGlhbnRcbiAqIHByZWZpeCBmb3IgdGhlIHBlcm1hbGluaywgaW5zdGVhZCBvZiBwZXJtYWxpbmtfcHJlZml4XG4gKiBAcGFyYW0ge01hdHJpeENsaWVudH0gbWF0cml4Q2xpZW50IFRoZSBNYXRyaXhDbGllbnQgdG8gdXNlXG4gKiBAcGFyYW0ge3N0cmluZ30gcm9vbUlkIFRoZSB1c2VyIHRvIGxpbmsgdG8uXG4gKiBAcGFyYW0ge2Jvb2xlYW59IGlzUGlsbCBMaW5rIHNob3VsZCBiZSBwaWxsaWZ5YWJsZS5cbiAqIEByZXR1cm5zIHtzdHJpbmd8bnVsbH0gVGhlIHRyYW5zZm9ybWVkIHBlcm1hbGluayBvciBudWxsIGlmIHVuYWJsZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1ha2VSb29tUGVybWFsaW5rKG1hdHJpeENsaWVudDogTWF0cml4Q2xpZW50LCByb29tSWQ6IHN0cmluZywgaXNQaWxsID0gZmFsc2UpOiBzdHJpbmcge1xuICAgIGlmICghcm9vbUlkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcImNhbid0IHBlcm1hbGluayBhIGZhbHN5IHJvb21JZFwiKTtcbiAgICB9XG5cbiAgICAvLyBJZiB0aGUgcm9vbUlkIGlzbid0IGFjdHVhbGx5IGEgcm9vbSBJRCwgZG9uJ3QgdHJ5IHRvIGxpc3QgdGhlIHNlcnZlcnMuXG4gICAgLy8gQWxpYXNlcyBhcmUgYWxyZWFkeSByb3V0YWJsZSwgYW5kIGRvbid0IG5lZWQgZXh0cmEgaW5mb3JtYXRpb24uXG4gICAgaWYgKHJvb21JZFswXSAhPT0gXCIhXCIpIHJldHVybiBnZXRQZXJtYWxpbmtDb25zdHJ1Y3Rvcihpc1BpbGwpLmZvclJvb20ocm9vbUlkLCBbXSk7XG5cbiAgICBjb25zdCByb29tID0gbWF0cml4Q2xpZW50LmdldFJvb20ocm9vbUlkKTtcbiAgICBpZiAoIXJvb20pIHtcbiAgICAgICAgcmV0dXJuIGdldFBlcm1hbGlua0NvbnN0cnVjdG9yKGlzUGlsbCkuZm9yUm9vbShyb29tSWQsIFtdKTtcbiAgICB9XG4gICAgY29uc3QgcGVybWFsaW5rQ3JlYXRvciA9IG5ldyBSb29tUGVybWFsaW5rQ3JlYXRvcihyb29tKTtcbiAgICBwZXJtYWxpbmtDcmVhdG9yLmxvYWQoKTtcbiAgICByZXR1cm4gcGVybWFsaW5rQ3JlYXRvci5mb3JTaGFyZWFibGVSb29tKCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBpc1Blcm1hbGlua0hvc3QoaG9zdDogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgLy8gQWx3YXlzIGNoZWNrIGlmIHRoZSBwZXJtYWxpbmsgaXMgYSBzcGVjIHBlcm1hbGluayAoY2FsbGVycyBhcmUgbGlrZWx5IHRvIGNhbGxcbiAgICAvLyBwYXJzZVBlcm1hbGluayBhZnRlciB0aGlzIGZ1bmN0aW9uKS5cbiAgICBpZiAobmV3IE1hdHJpeFRvUGVybWFsaW5rQ29uc3RydWN0b3IoKS5pc1Blcm1hbGlua0hvc3QoaG9zdCkpIHJldHVybiB0cnVlO1xuICAgIHJldHVybiBnZXRQZXJtYWxpbmtDb25zdHJ1Y3RvcigpLmlzUGVybWFsaW5rSG9zdChob3N0KTtcbn1cblxuLyoqXG4gKiBUcmFuc2Zvcm1zIGFuIGVudGl0eSAocGVybWFsaW5rLCByb29tIGFsaWFzLCB1c2VyIElELCBldGMpIGludG8gYSBsb2NhbCBVUkxcbiAqIGlmIHBvc3NpYmxlLiBJZiBpdCBpcyBhbHJlYWR5IGEgcGVybWFsaW5rIChtYXRyaXgudG8pIGl0IGdldHMgcmV0dXJuZWRcbiAqIHVuY2hhbmdlZC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBlbnRpdHkgVGhlIGVudGl0eSB0byB0cmFuc2Zvcm0uXG4gKiBAcmV0dXJucyB7c3RyaW5nfG51bGx9IFRoZSB0cmFuc2Zvcm1lZCBwZXJtYWxpbmsgb3IgbnVsbCBpZiB1bmFibGUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0cnlUcmFuc2Zvcm1FbnRpdHlUb1Blcm1hbGluayhtYXRyaXhDbGllbnQ6IE1hdHJpeENsaWVudCwgZW50aXR5OiBzdHJpbmcpOiBzdHJpbmcgfCBudWxsIHtcbiAgICBpZiAoIWVudGl0eSkgcmV0dXJuIG51bGw7XG5cbiAgICAvLyBDaGVjayB0byBzZWUgaWYgaXQgaXMgYSBiYXJlIGVudGl0eSBmb3Igc3RhcnRlcnNcbiAgICBpZiAoZW50aXR5WzBdID09PSBcIiNcIiB8fCBlbnRpdHlbMF0gPT09IFwiIVwiKSByZXR1cm4gbWFrZVJvb21QZXJtYWxpbmsobWF0cml4Q2xpZW50LCBlbnRpdHkpO1xuICAgIGlmIChlbnRpdHlbMF0gPT09IFwiQFwiKSByZXR1cm4gbWFrZVVzZXJQZXJtYWxpbmsoZW50aXR5KTtcblxuICAgIGlmIChlbnRpdHkuc2xpY2UoMCwgNykgPT09IFwibWF0cml4OlwiKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCBwZXJtYWxpbmtQYXJ0cyA9IHBhcnNlUGVybWFsaW5rKGVudGl0eSk7XG4gICAgICAgICAgICBpZiAocGVybWFsaW5rUGFydHMpIHtcbiAgICAgICAgICAgICAgICBpZiAocGVybWFsaW5rUGFydHMucm9vbUlkT3JBbGlhcykge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBldmVudElkUGFydCA9IHBlcm1hbGlua1BhcnRzLmV2ZW50SWQgPyBgLyR7cGVybWFsaW5rUGFydHMuZXZlbnRJZH1gIDogXCJcIjtcbiAgICAgICAgICAgICAgICAgICAgbGV0IHBsID0gbWF0cml4dG9CYXNlVXJsICsgYC8jLyR7cGVybWFsaW5rUGFydHMucm9vbUlkT3JBbGlhc30ke2V2ZW50SWRQYXJ0fWA7XG4gICAgICAgICAgICAgICAgICAgIGlmIChwZXJtYWxpbmtQYXJ0cy52aWFTZXJ2ZXJzPy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHBsICs9IG5ldyBNYXRyaXhUb1Blcm1hbGlua0NvbnN0cnVjdG9yKCkuZW5jb2RlU2VydmVyQ2FuZGlkYXRlcyhwZXJtYWxpbmtQYXJ0cy52aWFTZXJ2ZXJzKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICByZXR1cm4gcGw7XG4gICAgICAgICAgICAgICAgfSBlbHNlIGlmIChwZXJtYWxpbmtQYXJ0cy51c2VySWQpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG1hdHJpeHRvQmFzZVVybCArIGAvIy8ke3Blcm1hbGlua1BhcnRzLnVzZXJJZH1gO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCB7fVxuICAgIH1cblxuICAgIHJldHVybiBlbnRpdHk7XG59XG5cbi8qKlxuICogVHJhbnNmb3JtcyBhIHBlcm1hbGluayAob3IgcG9zc2libGUgcGVybWFsaW5rKSBpbnRvIGEgbG9jYWwgVVJMIGlmIHBvc3NpYmxlLiBJZlxuICogdGhlIGdpdmVuIHBlcm1hbGluayBpcyBmb3VuZCB0byBub3QgYmUgYSBwZXJtYWxpbmssIGl0J2xsIGJlIHJldHVybmVkIHVuYWx0ZXJlZC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBwZXJtYWxpbmsgVGhlIHBlcm1hbGluayB0byB0cnkgYW5kIHRyYW5zZm9ybS5cbiAqIEByZXR1cm5zIHtzdHJpbmd9IFRoZSB0cmFuc2Zvcm1lZCBwZXJtYWxpbmsgb3Igb3JpZ2luYWwgVVJMIGlmIHVuYWJsZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRyeVRyYW5zZm9ybVBlcm1hbGlua1RvTG9jYWxIcmVmKHBlcm1hbGluazogc3RyaW5nKTogc3RyaW5nIHtcbiAgICBpZiAoXG4gICAgICAgICFwZXJtYWxpbmsuc3RhcnRzV2l0aChcImh0dHA6XCIpICYmXG4gICAgICAgICFwZXJtYWxpbmsuc3RhcnRzV2l0aChcImh0dHBzOlwiKSAmJlxuICAgICAgICAhcGVybWFsaW5rLnN0YXJ0c1dpdGgoXCJtYXRyaXg6XCIpICYmXG4gICAgICAgICFwZXJtYWxpbmsuc3RhcnRzV2l0aChcInZlY3RvcjpcIikgLy8gRWxlbWVudCBEZXNrdG9wXG4gICAgKSB7XG4gICAgICAgIHJldHVybiBwZXJtYWxpbms7XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgICAgY29uc3QgbSA9IGRlY29kZVVSSUNvbXBvbmVudChwZXJtYWxpbmspLm1hdGNoKEVMRU1FTlRfVVJMX1BBVFRFUk4pO1xuICAgICAgICBpZiAobSkge1xuICAgICAgICAgICAgcmV0dXJuIG1bMV07XG4gICAgICAgIH1cbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIC8vIE5vdCBhIHZhbGlkIFVSSVxuICAgICAgICByZXR1cm4gcGVybWFsaW5rO1xuICAgIH1cblxuICAgIC8vIEEgYml0IG9mIGEgaGFjayB0byBjb252ZXJ0IHBlcm1hbGlua3Mgb2YgdW5rbm93biBvcmlnaW4gdG8gRWxlbWVudCBsaW5rc1xuICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHBlcm1hbGlua1BhcnRzID0gcGFyc2VQZXJtYWxpbmsocGVybWFsaW5rKTtcbiAgICAgICAgaWYgKHBlcm1hbGlua1BhcnRzKSB7XG4gICAgICAgICAgICBpZiAocGVybWFsaW5rUGFydHMucm9vbUlkT3JBbGlhcykge1xuICAgICAgICAgICAgICAgIGNvbnN0IGV2ZW50SWRQYXJ0ID0gcGVybWFsaW5rUGFydHMuZXZlbnRJZCA/IGAvJHtwZXJtYWxpbmtQYXJ0cy5ldmVudElkfWAgOiBcIlwiO1xuICAgICAgICAgICAgICAgIHBlcm1hbGluayA9IGAjL3Jvb20vJHtwZXJtYWxpbmtQYXJ0cy5yb29tSWRPckFsaWFzfSR7ZXZlbnRJZFBhcnR9YDtcbiAgICAgICAgICAgICAgICBpZiAocGVybWFsaW5rUGFydHMudmlhU2VydmVycz8ubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgIHBlcm1hbGluayArPSBuZXcgTWF0cml4VG9QZXJtYWxpbmtDb25zdHJ1Y3RvcigpLmVuY29kZVNlcnZlckNhbmRpZGF0ZXMocGVybWFsaW5rUGFydHMudmlhU2VydmVycyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIGlmIChwZXJtYWxpbmtQYXJ0cy51c2VySWQpIHtcbiAgICAgICAgICAgICAgICBwZXJtYWxpbmsgPSBgIy91c2VyLyR7cGVybWFsaW5rUGFydHMudXNlcklkfWA7XG4gICAgICAgICAgICB9IC8vIGVsc2Ugbm90IGEgdmFsaWQgcGVybWFsaW5rIGZvciBvdXIgcHVycG9zZXMgLSBkbyBub3QgaGFuZGxlXG4gICAgICAgIH1cbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIC8vIE5vdCBhbiBocmVmIHdlIG5lZWQgdG8gY2FyZSBhYm91dFxuICAgIH1cblxuICAgIHJldHVybiBwZXJtYWxpbms7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRQcmltYXJ5UGVybWFsaW5rRW50aXR5KHBlcm1hbGluazogc3RyaW5nKTogc3RyaW5nIHwgbnVsbCB7XG4gICAgdHJ5IHtcbiAgICAgICAgbGV0IHBlcm1hbGlua1BhcnRzID0gcGFyc2VQZXJtYWxpbmsocGVybWFsaW5rKTtcblxuICAgICAgICAvLyBJZiBub3QgYSBwZXJtYWxpbmssIHRyeSB0aGUgdmVjdG9yIHBhdHRlcm5zLlxuICAgICAgICBpZiAoIXBlcm1hbGlua1BhcnRzKSB7XG4gICAgICAgICAgICBjb25zdCBtID0gcGVybWFsaW5rLm1hdGNoKEVMRU1FTlRfVVJMX1BBVFRFUk4pO1xuICAgICAgICAgICAgaWYgKG0pIHtcbiAgICAgICAgICAgICAgICAvLyBBIGJpdCBvZiBhIGhhY2ssIGJ1dCBpdCBnZXRzIHRoZSBqb2IgZG9uZVxuICAgICAgICAgICAgICAgIGNvbnN0IGhhbmRsZXIgPSBuZXcgRWxlbWVudFBlcm1hbGlua0NvbnN0cnVjdG9yKFwiaHR0cDovL2xvY2FsaG9zdFwiKTtcbiAgICAgICAgICAgICAgICBjb25zdCBlbnRpdHlJbmZvID0gbVsxXS5zcGxpdChcIiNcIikuc2xpY2UoMSkuam9pbihcIiNcIik7XG4gICAgICAgICAgICAgICAgcGVybWFsaW5rUGFydHMgPSBoYW5kbGVyLnBhcnNlUGVybWFsaW5rKGBodHRwOi8vbG9jYWxob3N0LyMke2VudGl0eUluZm99YCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIXBlcm1hbGlua1BhcnRzKSByZXR1cm4gbnVsbDsgLy8gbm90IHByb2Nlc3NhYmxlXG4gICAgICAgIGlmIChwZXJtYWxpbmtQYXJ0cy51c2VySWQpIHJldHVybiBwZXJtYWxpbmtQYXJ0cy51c2VySWQ7XG4gICAgICAgIGlmIChwZXJtYWxpbmtQYXJ0cy5yb29tSWRPckFsaWFzKSByZXR1cm4gcGVybWFsaW5rUGFydHMucm9vbUlkT3JBbGlhcztcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIC8vIG5vIGVudGl0eSAtIG5vdCBhIHBlcm1hbGlua1xuICAgIH1cblxuICAgIHJldHVybiBudWxsO1xufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIGNvcnJlY3QgUGVybWFsaW5rQ29uc3RydWN0b3IgYmFzZWQgb24gcGVybWFsaW5rX3ByZWZpeFxuICogYW5kIGlzUGlsbFxuICogQHBhcmFtIHtib29sZWFufSBpc1BpbGwgU2hvdWxkIGNvbnN0cnVjdGVkIGxpbmtzIGJlIHBpbGxpZnlhYmxlLlxuICogQHJldHVybnMge3N0cmluZ3xudWxsfSBUaGUgdHJhbnNmb3JtZWQgcGVybWFsaW5rIG9yIG51bGwgaWYgdW5hYmxlLlxuICovXG5mdW5jdGlvbiBnZXRQZXJtYWxpbmtDb25zdHJ1Y3Rvcihpc1BpbGwgPSBmYWxzZSk6IFBlcm1hbGlua0NvbnN0cnVjdG9yIHtcbiAgICBjb25zdCBlbGVtZW50UHJlZml4ID0gU2RrQ29uZmlnLmdldChcInBlcm1hbGlua19wcmVmaXhcIik7XG4gICAgaWYgKGVsZW1lbnRQcmVmaXggJiYgZWxlbWVudFByZWZpeCAhPT0gbWF0cml4dG9CYXNlVXJsICYmICFpc1BpbGwpIHtcbiAgICAgICAgcmV0dXJuIG5ldyBFbGVtZW50UGVybWFsaW5rQ29uc3RydWN0b3IoZWxlbWVudFByZWZpeCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIG5ldyBNYXRyaXhUb1Blcm1hbGlua0NvbnN0cnVjdG9yKCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVBlcm1hbGluayhmdWxsVXJsOiBzdHJpbmcpOiBQZXJtYWxpbmtQYXJ0cyB8IG51bGwge1xuICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGVsZW1lbnRQcmVmaXggPSBTZGtDb25maWcuZ2V0KFwicGVybWFsaW5rX3ByZWZpeFwiKTtcbiAgICAgICAgY29uc3QgZGVjb2RlZFVybCA9IGRlY29kZVVSSUNvbXBvbmVudChmdWxsVXJsKTtcbiAgICAgICAgaWYgKG5ldyBSZWdFeHAobWF0cml4VG9CYXNlVXJsUGF0dGVybiwgXCJpXCIpLnRlc3QoZGVjb2RlZFVybCkpIHtcbiAgICAgICAgICAgIHJldHVybiBuZXcgTWF0cml4VG9QZXJtYWxpbmtDb25zdHJ1Y3RvcigpLnBhcnNlUGVybWFsaW5rKGRlY29kZWRVcmwpO1xuICAgICAgICB9IGVsc2UgaWYgKGZ1bGxVcmwuc3RhcnRzV2l0aChcIm1hdHJpeDpcIikpIHtcbiAgICAgICAgICAgIHJldHVybiBuZXcgTWF0cml4U2NoZW1lUGVybWFsaW5rQ29uc3RydWN0b3IoKS5wYXJzZVBlcm1hbGluayhmdWxsVXJsKTtcbiAgICAgICAgfSBlbHNlIGlmIChlbGVtZW50UHJlZml4ICYmIGZ1bGxVcmwuc3RhcnRzV2l0aChlbGVtZW50UHJlZml4KSkge1xuICAgICAgICAgICAgcmV0dXJuIG5ldyBFbGVtZW50UGVybWFsaW5rQ29uc3RydWN0b3IoZWxlbWVu