UNPKG

matrix-react-sdk

Version:
384 lines (377 loc) 61.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.PosthogAnalytics = exports.Anonymity = void 0; exports.getRedactedCurrentLocation = getRedactedCurrentLocation; var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _posthogJs = _interopRequireDefault(require("posthog-js")); var _logger = require("matrix-js-sdk/src/logger"); var _PlatformPeg = _interopRequireDefault(require("./PlatformPeg")); var _SdkConfig = _interopRequireDefault(require("./SdkConfig")); var _MatrixClientPeg = require("./MatrixClientPeg"); var _SettingsStore = _interopRequireDefault(require("./settings/SettingsStore")); var _actions = require("./dispatcher/actions"); var _dispatcher = _interopRequireDefault(require("./dispatcher/dispatcher")); var _Layout = require("./settings/enums/Layout"); const _excluded = ["eventName"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* Copyright 2024 New Vector Ltd. Copyright 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. */ /* Posthog analytics tracking. * * Anonymity behaviour is as follows: * * - If Posthog isn't configured in `config.json`, events are not sent. * - If [Do Not Track](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack) is * enabled, events are not sent (this detection is built into posthog and turned on via the * `respect_dnt` flag being passed to `posthog.init`). * - If the `feature_pseudonymous_analytics_opt_in` labs flag is `true`, track pseudonomously by maintaining * a randomised analytics ID in account_data for that user (shared between devices) and sending it to posthog to identify the user. * - Otherwise, if the existing `analyticsOptIn` flag is `true`, track anonymously, i.e. do not identify the user using any identifier that would be consistent across devices. * - If both flags are false or not set, events are not sent. */ let Anonymity = exports.Anonymity = /*#__PURE__*/function (Anonymity) { Anonymity[Anonymity["Disabled"] = 0] = "Disabled"; Anonymity[Anonymity["Anonymous"] = 1] = "Anonymous"; Anonymity[Anonymity["Pseudonymous"] = 2] = "Pseudonymous"; return Anonymity; }({}); const whitelistedScreens = new Set(["register", "login", "forgot_password", "soft_logout", "new", "settings", "welcome", "home", "start", "directory", "start_sso", "start_cas", "complete_security", "post_registration", "room", "user"]); function getRedactedCurrentLocation(origin, hash, pathname) { // Redact PII from the current location. // For known screens, assumes a URL structure of /<screen name>/might/be/pii if (origin.startsWith("file://")) { pathname = "/<redacted_file_scheme_url>/"; } let hashStr; if (hash == "") { hashStr = ""; } else { let [beforeFirstSlash, screen] = hash.split("/"); if (!whitelistedScreens.has(screen)) { screen = "<redacted_screen_name>"; } hashStr = `${beforeFirstSlash}/${screen}/<redacted>`; } return origin + pathname + hashStr; } class PosthogAnalytics { static get instance() { if (!this._instance) { this._instance = new PosthogAnalytics(_posthogJs.default); } return this._instance; } constructor(posthog) { /* Wrapper for Posthog analytics. * 3 modes of anonymity are supported, governed by this.anonymity * - Anonymity.Disabled means *no data* is passed to posthog * - Anonymity.Anonymous means no identifier is passed to posthog * - Anonymity.Pseudonymous means an analytics ID stored in account_data and shared between devices * is passed to posthog. * * To update anonymity, call updateAnonymityFromSettings() or you can set it directly via setAnonymity(). * * To pass an event to Posthog: * * 1. Declare a type for the event, extending IAnonymousEvent or IPseudonymousEvent. * 2. Call the appropriate track*() method. Pseudonymous events will be dropped when anonymity is * Anonymous or Disabled; Anonymous events will be dropped when anonymity is Disabled. */ (0, _defineProperty2.default)(this, "anonymity", Anonymity.Disabled); // set true during the constructor if posthog config is present, otherwise false (0, _defineProperty2.default)(this, "enabled", false); (0, _defineProperty2.default)(this, "platformSuperProperties", {}); (0, _defineProperty2.default)(this, "propertiesForNextEvent", {}); (0, _defineProperty2.default)(this, "userPropertyCache", {}); (0, _defineProperty2.default)(this, "authenticationType", "Other"); (0, _defineProperty2.default)(this, "watchSettingRef", void 0); // Will be set when the matrixClient is passed to the analytics object (e.g. on login). (0, _defineProperty2.default)(this, "currentCryptoBackend", undefined); (0, _defineProperty2.default)(this, "onLayoutUpdated", () => { let layout; switch (_SettingsStore.default.getValue("layout")) { case _Layout.Layout.IRC: layout = "IRC"; break; case _Layout.Layout.Bubble: layout = "Bubble"; break; case _Layout.Layout.Group: layout = _SettingsStore.default.getValue("useCompactLayout") ? "Compact" : "Group"; break; } // This is known to clobber other devices but is a good enough solution // to get an idea of how much use each layout gets. this.setProperty("WebLayout", layout); }); (0, _defineProperty2.default)(this, "onAction", payload => { if (payload.action !== _actions.Action.SettingUpdated) return; const settingsPayload = payload; if (["layout", "useCompactLayout"].includes(settingsPayload.settingName)) { this.onLayoutUpdated(); } }); // we persist the last `$screen_name` and send it for all events until it is replaced (0, _defineProperty2.default)(this, "lastScreen", "Loading"); (0, _defineProperty2.default)(this, "sanitizeProperties", (properties, eventName) => { // Callback from posthog to sanitize properties before sending them to the server. // // Here we sanitize posthog's built in properties which leak PII e.g. url reporting. // See utils.js _.info.properties in posthog-js. if (eventName === "$pageview") { this.lastScreen = properties["$current_url"]; } // We inject a screen identifier in $current_url as per https://posthog.com/tutorials/spa properties["$current_url"] = this.lastScreen; if (this.anonymity == Anonymity.Anonymous) { // drop referrer information for anonymous users properties["$referrer"] = null; properties["$referring_domain"] = null; properties["$initial_referrer"] = null; properties["$initial_referring_domain"] = null; // drop device ID, which is a UUID persisted in local storage properties["$device_id"] = null; } return properties; }); this.posthog = posthog; const posthogConfig = _SdkConfig.default.getObject("posthog"); if (posthogConfig) { this.posthog.init(posthogConfig.get("project_api_key"), { api_host: posthogConfig.get("api_host"), autocapture: false, mask_all_text: true, mask_all_element_attributes: true, // This only triggers on page load, which for our SPA isn't particularly useful. // Plus, the .capture call originating from somewhere in posthog makes it hard // to redact URLs, which requires async code. // // To raise this manually, just call .capture("$pageview") or posthog.capture_pageview. capture_pageview: false, sanitize_properties: this.sanitizeProperties, respect_dnt: true, advanced_disable_decide: true }); this.enabled = true; } else { this.enabled = false; } _dispatcher.default.register(this.onAction); _SettingsStore.default.monitorSetting("layout", null); _SettingsStore.default.monitorSetting("useCompactLayout", null); this.onLayoutUpdated(); this.updateCryptoSuperProperty(); } registerSuperProperties(properties) { if (this.enabled) { this.posthog.register(properties); } } static async getPlatformProperties() { const platform = _PlatformPeg.default.get(); let appVersion; try { appVersion = await platform?.getAppVersion(); } catch (e) { // this happens if no version is set i.e. in dev appVersion = "unknown"; } return { appVersion, appPlatform: platform?.getHumanReadableName() }; } // eslint-disable-nextline no-unused-vars capture(eventName, properties, options) { if (!this.enabled) { return; } const { origin, hash, pathname } = window.location; properties["redactedCurrentUrl"] = getRedactedCurrentLocation(origin, hash, pathname); this.posthog.capture(eventName, _objectSpread(_objectSpread({}, this.propertiesForNextEvent), properties), options); this.propertiesForNextEvent = {}; } isEnabled() { return this.enabled; } setAnonymity(anonymity) { // Update this.anonymity. // This is public for testing purposes, typically you want to call updateAnonymityFromSettings // to ensure this value is in step with the user's settings. if (this.enabled && (anonymity == Anonymity.Disabled || anonymity == Anonymity.Anonymous)) { // when transitioning to Disabled or Anonymous ensure we clear out any prior state // set in posthog e.g. distinct ID this.posthog.reset(); // Restore any previously set platform super properties this.registerSuperProperties(this.platformSuperProperties); } this.anonymity = anonymity; // update anyhow, no-op if not enabled or Disabled. this.updateCryptoSuperProperty(); } static getRandomAnalyticsId() { return [...crypto.getRandomValues(new Uint8Array(16))].map(c => c.toString(16)).join(""); } async identifyUser(client, analyticsIdGenerator) { if (this.anonymity == Anonymity.Pseudonymous) { // Check the user's account_data for an analytics ID to use. Storing the ID in account_data allows // different devices to send the same ID. try { const accountData = await client.getAccountDataFromServer(PosthogAnalytics.ANALYTICS_EVENT_TYPE); let analyticsID = accountData?.id; if (!analyticsID) { // Couldn't retrieve an analytics ID from user settings, so create one and set it on the server. // Note there's a race condition here - if two devices do these steps at the same time, last write // wins, and the first writer will send tracking with an ID that doesn't match the one on the server // until the next time account data is refreshed and this function is called (most likely on next // page load). This will happen pretty infrequently, so we can tolerate the possibility. analyticsID = analyticsIdGenerator(); await client.setAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE, Object.assign({ id: analyticsID }, accountData)); } if (this.posthog.get_distinct_id() === analyticsID) { // No point identifying again return; } if (this.posthog.persistence?.get_property("$user_state") === "identified") { // Analytics ID has changed, reset as Posthog will refuse to merge in this case this.posthog.reset(); } this.posthog.identify(analyticsID); } catch (e) { // The above could fail due to network requests, but not essential to starting the application, // so swallow it. _logger.logger.log("Unable to identify user for tracking", e); } } } getAnonymity() { return this.anonymity; } logout() { if (this.enabled) { this.posthog.reset(); } if (this.watchSettingRef) _SettingsStore.default.unwatchSetting(this.watchSettingRef); this.setAnonymity(Anonymity.Disabled); } trackEvent(_ref, options) { let { eventName } = _ref, properties = (0, _objectWithoutProperties2.default)(_ref, _excluded); if (this.anonymity == Anonymity.Disabled || this.anonymity == Anonymity.Anonymous) return; this.capture(eventName, properties, options); } setProperty(key, value) { if (this.userPropertyCache[key] === value) return; // nothing to do this.userPropertyCache[key] = value; if (!this.propertiesForNextEvent["$set"]) { this.propertiesForNextEvent["$set"] = {}; } this.propertiesForNextEvent["$set"][key] = value; } setPropertyOnce(key, value) { if (this.userPropertyCache[key]) return; // nothing to do this.userPropertyCache[key] = value; if (!this.propertiesForNextEvent["$set_once"]) { this.propertiesForNextEvent["$set_once"] = {}; } this.propertiesForNextEvent["$set_once"][key] = value; } async updatePlatformSuperProperties() { // Update super properties in posthog with our platform (app version, platform). // These properties will be subsequently passed in every event. // // This only needs to be done once per page lifetime. Note that getPlatformProperties // is async and can involve a network request if we are running in a browser. this.platformSuperProperties = await PosthogAnalytics.getPlatformProperties(); this.registerSuperProperties(this.platformSuperProperties); } updateCryptoSuperProperty() { if (!this.enabled || this.anonymity === Anonymity.Disabled) return; // Update super property for cryptoSDK in posthog. // This property will be subsequently passed in every event. if (this.currentCryptoBackend) { this.registerSuperProperties({ cryptoSDK: this.currentCryptoBackend }); } } async updateAnonymityFromSettings(client, pseudonymousOptIn) { // Temporary until we have migration code to switch crypto sdk. if (client.getCrypto()) { const cryptoVersion = client.getCrypto().getVersion(); // version for rust is something like "Rust SDK 0.6.0 (9c6b550), Vodozemac 0.5.0" // for legacy it will be 'Olm x.x.x" if (cryptoVersion.includes("Rust SDK")) { this.currentCryptoBackend = "Rust"; } else { this.currentCryptoBackend = "Legacy"; } } // Update this.anonymity based on the user's analytics opt-in settings const anonymity = pseudonymousOptIn ? Anonymity.Pseudonymous : Anonymity.Disabled; this.setAnonymity(anonymity); if (anonymity === Anonymity.Pseudonymous) { await this.identifyUser(client, PosthogAnalytics.getRandomAnalyticsId); if (_MatrixClientPeg.MatrixClientPeg.currentUserIsJustRegistered()) { this.trackNewUserEvent(); } } if (anonymity !== Anonymity.Disabled) { await this.updatePlatformSuperProperties(); this.updateCryptoSuperProperty(); } } startListeningToSettingsChanges(client) { // Listen to account data changes from sync so we can observe changes to relevant flags and update. // This is called - // * On page load, when the account data is first received by sync // * On login // * When another device changes account data // * When the user changes their preferences on this device // Note that for new accounts, pseudonymousAnalyticsOptIn won't be set, so updateAnonymityFromSettings // won't be called (i.e. this.anonymity will be left as the default, until the setting changes) this.watchSettingRef = _SettingsStore.default.watchSetting("pseudonymousAnalyticsOptIn", null, (originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue) => { this.updateAnonymityFromSettings(client, !!newValue); }); } setAuthenticationType(authenticationType) { this.authenticationType = authenticationType; } trackNewUserEvent() { // This is the only event that could have occured before analytics opt-in // that we want to accumulate before the user has given consent // All other scenarios should not track a user before they have given // explicit consent that they are ok with their analytics data being collected const options = {}; const registrationTime = parseInt(window.localStorage.getItem("mx_registration_time"), 10); if (!isNaN(registrationTime)) { options.timestamp = new Date(registrationTime); } return this.trackEvent({ eventName: "Signup", authenticationType: this.authenticationType }, options); } } exports.PosthogAnalytics = PosthogAnalytics; (0, _defineProperty2.default)(PosthogAnalytics, "_instance", null); (0, _defineProperty2.default)(PosthogAnalytics, "ANALYTICS_EVENT_TYPE", "im.vector.analytics"); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcG9zdGhvZ0pzIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsInJlcXVpcmUiLCJfbG9nZ2VyIiwiX1BsYXRmb3JtUGVnIiwiX1Nka0NvbmZpZyIsIl9NYXRyaXhDbGllbnRQZWciLCJfU2V0dGluZ3NTdG9yZSIsIl9hY3Rpb25zIiwiX2Rpc3BhdGNoZXIiLCJfTGF5b3V0IiwiX2V4Y2x1ZGVkIiwib3duS2V5cyIsImUiLCJyIiwidCIsIk9iamVjdCIsImtleXMiLCJnZXRPd25Qcm9wZXJ0eVN5bWJvbHMiLCJvIiwiZmlsdGVyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZW51bWVyYWJsZSIsInB1c2giLCJhcHBseSIsIl9vYmplY3RTcHJlYWQiLCJhcmd1bWVudHMiLCJsZW5ndGgiLCJmb3JFYWNoIiwiX2RlZmluZVByb3BlcnR5MiIsImRlZmF1bHQiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3JzIiwiZGVmaW5lUHJvcGVydGllcyIsImRlZmluZVByb3BlcnR5IiwiQW5vbnltaXR5IiwiZXhwb3J0cyIsIndoaXRlbGlzdGVkU2NyZWVucyIsIlNldCIsImdldFJlZGFjdGVkQ3VycmVudExvY2F0aW9uIiwib3JpZ2luIiwiaGFzaCIsInBhdGhuYW1lIiwic3RhcnRzV2l0aCIsImhhc2hTdHIiLCJiZWZvcmVGaXJzdFNsYXNoIiwic2NyZWVuIiwic3BsaXQiLCJoYXMiLCJQb3N0aG9nQW5hbHl0aWNzIiwiaW5zdGFuY2UiLCJfaW5zdGFuY2UiLCJwb3N0aG9nIiwiY29uc3RydWN0b3IiLCJEaXNhYmxlZCIsInVuZGVmaW5lZCIsImxheW91dCIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsIkxheW91dCIsIklSQyIsIkJ1YmJsZSIsIkdyb3VwIiwic2V0UHJvcGVydHkiLCJwYXlsb2FkIiwiYWN0aW9uIiwiQWN0aW9uIiwiU2V0dGluZ1VwZGF0ZWQiLCJzZXR0aW5nc1BheWxvYWQiLCJpbmNsdWRlcyIsInNldHRpbmdOYW1lIiwib25MYXlvdXRVcGRhdGVkIiwicHJvcGVydGllcyIsImV2ZW50TmFtZSIsImxhc3RTY3JlZW4iLCJhbm9ueW1pdHkiLCJBbm9ueW1vdXMiLCJwb3N0aG9nQ29uZmlnIiwiU2RrQ29uZmlnIiwiZ2V0T2JqZWN0IiwiaW5pdCIsImdldCIsImFwaV9ob3N0IiwiYXV0b2NhcHR1cmUiLCJtYXNrX2FsbF90ZXh0IiwibWFza19hbGxfZWxlbWVudF9hdHRyaWJ1dGVzIiwiY2FwdHVyZV9wYWdldmlldyIsInNhbml0aXplX3Byb3BlcnRpZXMiLCJzYW5pdGl6ZVByb3BlcnRpZXMiLCJyZXNwZWN0X2RudCIsImFkdmFuY2VkX2Rpc2FibGVfZGVjaWRlIiwiZW5hYmxlZCIsImRpcyIsInJlZ2lzdGVyIiwib25BY3Rpb24iLCJtb25pdG9yU2V0dGluZyIsInVwZGF0ZUNyeXB0b1N1cGVyUHJvcGVydHkiLCJyZWdpc3RlclN1cGVyUHJvcGVydGllcyIsImdldFBsYXRmb3JtUHJvcGVydGllcyIsInBsYXRmb3JtIiwiUGxhdGZvcm1QZWciLCJhcHBWZXJzaW9uIiwiZ2V0QXBwVmVyc2lvbiIsImFwcFBsYXRmb3JtIiwiZ2V0SHVtYW5SZWFkYWJsZU5hbWUiLCJjYXB0dXJlIiwib3B0aW9ucyIsIndpbmRvdyIsImxvY2F0aW9uIiwicHJvcGVydGllc0Zvck5leHRFdmVudCIsImlzRW5hYmxlZCIsInNldEFub255bWl0eSIsInJlc2V0IiwicGxhdGZvcm1TdXBlclByb3BlcnRpZXMiLCJnZXRSYW5kb21BbmFseXRpY3NJZCIsImNyeXB0byIsImdldFJhbmRvbVZhbHVlcyIsIlVpbnQ4QXJyYXkiLCJtYXAiLCJjIiwidG9TdHJpbmciLCJqb2luIiwiaWRlbnRpZnlVc2VyIiwiY2xpZW50IiwiYW5hbHl0aWNzSWRHZW5lcmF0b3IiLCJQc2V1ZG9ueW1vdXMiLCJhY2NvdW50RGF0YSIsImdldEFjY291bnREYXRhRnJvbVNlcnZlciIsIkFOQUxZVElDU19FVkVOVF9UWVBFIiwiYW5hbHl0aWNzSUQiLCJpZCIsInNldEFjY291bnREYXRhIiwiYXNzaWduIiwiZ2V0X2Rpc3RpbmN0X2lkIiwicGVyc2lzdGVuY2UiLCJnZXRfcHJvcGVydHkiLCJpZGVudGlmeSIsImxvZ2dlciIsImxvZyIsImdldEFub255bWl0eSIsImxvZ291dCIsIndhdGNoU2V0dGluZ1JlZiIsInVud2F0Y2hTZXR0aW5nIiwidHJhY2tFdmVudCIsIl9yZWYiLCJfb2JqZWN0V2l0aG91dFByb3BlcnRpZXMyIiwia2V5IiwidmFsdWUiLCJ1c2VyUHJvcGVydHlDYWNoZSIsInNldFByb3BlcnR5T25jZSIsInVwZGF0ZVBsYXRmb3JtU3VwZXJQcm9wZXJ0aWVzIiwiY3VycmVudENyeXB0b0JhY2tlbmQiLCJjcnlwdG9TREsiLCJ1cGRhdGVBbm9ueW1pdHlGcm9tU2V0dGluZ3MiLCJwc2V1ZG9ueW1vdXNPcHRJbiIsImdldENyeXB0byIsImNyeXB0b1ZlcnNpb24iLCJnZXRWZXJzaW9uIiwiTWF0cml4Q2xpZW50UGVnIiwiY3VycmVudFVzZXJJc0p1c3RSZWdpc3RlcmVkIiwidHJhY2tOZXdVc2VyRXZlbnQiLCJzdGFydExpc3RlbmluZ1RvU2V0dGluZ3NDaGFuZ2VzIiwid2F0Y2hTZXR0aW5nIiwib3JpZ2luYWxTZXR0aW5nTmFtZSIsImNoYW5nZWRJblJvb21JZCIsImF0TGV2ZWwiLCJuZXdWYWx1ZUF0TGV2ZWwiLCJuZXdWYWx1ZSIsInNldEF1dGhlbnRpY2F0aW9uVHlwZSIsImF1dGhlbnRpY2F0aW9uVHlwZSIsInJlZ2lzdHJhdGlvblRpbWUiLCJwYXJzZUludCIsImxvY2FsU3RvcmFnZSIsImdldEl0ZW0iLCJpc05hTiIsInRpbWVzdGFtcCIsIkRhdGUiXSwic291cmNlcyI6WyIuLi9zcmMvUG9zdGhvZ0FuYWx5dGljcy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAyMSBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgcG9zdGhvZywgeyBDYXB0dXJlT3B0aW9ucywgUG9zdEhvZywgUHJvcGVydGllcyB9IGZyb20gXCJwb3N0aG9nLWpzXCI7XG5pbXBvcnQgeyBNYXRyaXhDbGllbnQgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbWF0cml4XCI7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbG9nZ2VyXCI7XG5pbXBvcnQgeyBVc2VyUHJvcGVydGllcyB9IGZyb20gXCJAbWF0cml4LW9yZy9hbmFseXRpY3MtZXZlbnRzL3R5cGVzL3R5cGVzY3JpcHQvVXNlclByb3BlcnRpZXNcIjtcbmltcG9ydCB7IFNpZ251cCB9IGZyb20gXCJAbWF0cml4LW9yZy9hbmFseXRpY3MtZXZlbnRzL3R5cGVzL3R5cGVzY3JpcHQvU2lnbnVwXCI7XG5cbmltcG9ydCBQbGF0Zm9ybVBlZyBmcm9tIFwiLi9QbGF0Zm9ybVBlZ1wiO1xuaW1wb3J0IFNka0NvbmZpZyBmcm9tIFwiLi9TZGtDb25maWdcIjtcbmltcG9ydCB7IE1hdHJpeENsaWVudFBlZyB9IGZyb20gXCIuL01hdHJpeENsaWVudFBlZ1wiO1xuaW1wb3J0IFNldHRpbmdzU3RvcmUgZnJvbSBcIi4vc2V0dGluZ3MvU2V0dGluZ3NTdG9yZVwiO1xuaW1wb3J0IHsgU2NyZWVuTmFtZSB9IGZyb20gXCIuL1Bvc3Rob2dUcmFja2Vyc1wiO1xuaW1wb3J0IHsgQWN0aW9uUGF5bG9hZCB9IGZyb20gXCIuL2Rpc3BhdGNoZXIvcGF5bG9hZHNcIjtcbmltcG9ydCB7IEFjdGlvbiB9IGZyb20gXCIuL2Rpc3BhdGNoZXIvYWN0aW9uc1wiO1xuaW1wb3J0IHsgU2V0dGluZ1VwZGF0ZWRQYXlsb2FkIH0gZnJvbSBcIi4vZGlzcGF0Y2hlci9wYXlsb2Fkcy9TZXR0aW5nVXBkYXRlZFBheWxvYWRcIjtcbmltcG9ydCBkaXMgZnJvbSBcIi4vZGlzcGF0Y2hlci9kaXNwYXRjaGVyXCI7XG5pbXBvcnQgeyBMYXlvdXQgfSBmcm9tIFwiLi9zZXR0aW5ncy9lbnVtcy9MYXlvdXRcIjtcblxuLyogUG9zdGhvZyBhbmFseXRpY3MgdHJhY2tpbmcuXG4gKlxuICogQW5vbnltaXR5IGJlaGF2aW91ciBpcyBhcyBmb2xsb3dzOlxuICpcbiAqIC0gSWYgUG9zdGhvZyBpc24ndCBjb25maWd1cmVkIGluIGBjb25maWcuanNvbmAsIGV2ZW50cyBhcmUgbm90IHNlbnQuXG4gKiAtIElmIFtEbyBOb3QgVHJhY2tdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9OYXZpZ2F0b3IvZG9Ob3RUcmFjaykgaXNcbiAqICAgZW5hYmxlZCwgZXZlbnRzIGFyZSBub3Qgc2VudCAodGhpcyBkZXRlY3Rpb24gaXMgYnVpbHQgaW50byBwb3N0aG9nIGFuZCB0dXJuZWQgb24gdmlhIHRoZVxuICogICBgcmVzcGVjdF9kbnRgIGZsYWcgYmVpbmcgcGFzc2VkIHRvIGBwb3N0aG9nLmluaXRgKS5cbiAqIC0gSWYgdGhlIGBmZWF0dXJlX3BzZXVkb255bW91c19hbmFseXRpY3Nfb3B0X2luYCBsYWJzIGZsYWcgaXMgYHRydWVgLCB0cmFjayBwc2V1ZG9ub21vdXNseSBieSBtYWludGFpbmluZ1xuICogICBhIHJhbmRvbWlzZWQgYW5hbHl0aWNzIElEIGluIGFjY291bnRfZGF0YSBmb3IgdGhhdCB1c2VyIChzaGFyZWQgYmV0d2VlbiBkZXZpY2VzKSBhbmQgc2VuZGluZyBpdCB0byBwb3N0aG9nIHRvXG4gICAgIGlkZW50aWZ5IHRoZSB1c2VyLlxuICogLSBPdGhlcndpc2UsIGlmIHRoZSBleGlzdGluZyBgYW5hbHl0aWNzT3B0SW5gIGZsYWcgaXMgYHRydWVgLCB0cmFjayBhbm9ueW1vdXNseSwgaS5lLiBkbyBub3QgaWRlbnRpZnkgdGhlIHVzZXJcbiAgICAgdXNpbmcgYW55IGlkZW50aWZpZXIgdGhhdCB3b3VsZCBiZSBjb25zaXN0ZW50IGFjcm9zcyBkZXZpY2VzLlxuICogLSBJZiBib3RoIGZsYWdzIGFyZSBmYWxzZSBvciBub3Qgc2V0LCBldmVudHMgYXJlIG5vdCBzZW50LlxuICovXG5cbmV4cG9ydCBpbnRlcmZhY2UgSVBvc3Rob2dFdmVudCB7XG4gICAgLy8gVGhlIGV2ZW50IG5hbWUgdGhhdCB3aWxsIGJlIHVzZWQgYnkgUG9zdEhvZy4gRXZlbnQgbmFtZXMgc2hvdWxkIHVzZSBjYW1lbENhc2UuXG4gICAgZXZlbnROYW1lOiBzdHJpbmc7XG5cbiAgICAvLyBkbyBub3QgYWxsb3cgdGhlc2UgdG8gYmUgc2VudCBtYW51YWxseSwgd2UgZW5xdWV1ZSB0aGVtIGFsbCBmb3IgY2FjaGluZyBwdXJwb3Nlc1xuICAgICRzZXQ/OiB2b2lkO1xuICAgICRzZXRfb25jZT86IHZvaWQ7XG59XG5cbmV4cG9ydCBlbnVtIEFub255bWl0eSB7XG4gICAgRGlzYWJsZWQsXG4gICAgQW5vbnltb3VzLFxuICAgIFBzZXVkb255bW91cyxcbn1cblxuY29uc3Qgd2hpdGVsaXN0ZWRTY3JlZW5zID0gbmV3IFNldChbXG4gICAgXCJyZWdpc3RlclwiLFxuICAgIFwibG9naW5cIixcbiAgICBcImZvcmdvdF9wYXNzd29yZFwiLFxuICAgIFwic29mdF9sb2dvdXRcIixcbiAgICBcIm5ld1wiLFxuICAgIFwic2V0dGluZ3NcIixcbiAgICBcIndlbGNvbWVcIixcbiAgICBcImhvbWVcIixcbiAgICBcInN0YXJ0XCIsXG4gICAgXCJkaXJlY3RvcnlcIixcbiAgICBcInN0YXJ0X3Nzb1wiLFxuICAgIFwic3RhcnRfY2FzXCIsXG4gICAgXCJjb21wbGV0ZV9zZWN1cml0eVwiLFxuICAgIFwicG9zdF9yZWdpc3RyYXRpb25cIixcbiAgICBcInJvb21cIixcbiAgICBcInVzZXJcIixcbl0pO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0UmVkYWN0ZWRDdXJyZW50TG9jYXRpb24ob3JpZ2luOiBzdHJpbmcsIGhhc2g6IHN0cmluZywgcGF0aG5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgLy8gUmVkYWN0IFBJSSBmcm9tIHRoZSBjdXJyZW50IGxvY2F0aW9uLlxuICAgIC8vIEZvciBrbm93biBzY3JlZW5zLCBhc3N1bWVzIGEgVVJMIHN0cnVjdHVyZSBvZiAvPHNjcmVlbiBuYW1lPi9taWdodC9iZS9waWlcbiAgICBpZiAob3JpZ2luLnN0YXJ0c1dpdGgoXCJmaWxlOi8vXCIpKSB7XG4gICAgICAgIHBhdGhuYW1lID0gXCIvPHJlZGFjdGVkX2ZpbGVfc2NoZW1lX3VybD4vXCI7XG4gICAgfVxuXG4gICAgbGV0IGhhc2hTdHI7XG4gICAgaWYgKGhhc2ggPT0gXCJcIikge1xuICAgICAgICBoYXNoU3RyID0gXCJcIjtcbiAgICB9IGVsc2Uge1xuICAgICAgICBsZXQgW2JlZm9yZUZpcnN0U2xhc2gsIHNjcmVlbl0gPSBoYXNoLnNwbGl0KFwiL1wiKTtcblxuICAgICAgICBpZiAoIXdoaXRlbGlzdGVkU2NyZWVucy5oYXMoc2NyZWVuKSkge1xuICAgICAgICAgICAgc2NyZWVuID0gXCI8cmVkYWN0ZWRfc2NyZWVuX25hbWU+XCI7XG4gICAgICAgIH1cblxuICAgICAgICBoYXNoU3RyID0gYCR7YmVmb3JlRmlyc3RTbGFzaH0vJHtzY3JlZW59LzxyZWRhY3RlZD5gO1xuICAgIH1cbiAgICByZXR1cm4gb3JpZ2luICsgcGF0aG5hbWUgKyBoYXNoU3RyO1xufVxuXG5pbnRlcmZhY2UgUGxhdGZvcm1Qcm9wZXJ0aWVzIHtcbiAgICBhcHBWZXJzaW9uOiBzdHJpbmc7XG4gICAgYXBwUGxhdGZvcm06IHN0cmluZztcbn1cblxuZXhwb3J0IGNsYXNzIFBvc3Rob2dBbmFseXRpY3Mge1xuICAgIC8qIFdyYXBwZXIgZm9yIFBvc3Rob2cgYW5hbHl0aWNzLlxuICAgICAqIDMgbW9kZXMgb2YgYW5vbnltaXR5IGFyZSBzdXBwb3J0ZWQsIGdvdmVybmVkIGJ5IHRoaXMuYW5vbnltaXR5XG4gICAgICogLSBBbm9ueW1pdHkuRGlzYWJsZWQgbWVhbnMgKm5vIGRhdGEqIGlzIHBhc3NlZCB0byBwb3N0aG9nXG4gICAgICogLSBBbm9ueW1pdHkuQW5vbnltb3VzIG1lYW5zIG5vIGlkZW50aWZpZXIgaXMgcGFzc2VkIHRvIHBvc3Rob2dcbiAgICAgKiAtIEFub255bWl0eS5Qc2V1ZG9ueW1vdXMgbWVhbnMgYW4gYW5hbHl0aWNzIElEIHN0b3JlZCBpbiBhY2NvdW50X2RhdGEgYW5kIHNoYXJlZCBiZXR3ZWVuIGRldmljZXNcbiAgICAgKiAgIGlzIHBhc3NlZCB0byBwb3N0aG9nLlxuICAgICAqXG4gICAgICogVG8gdXBkYXRlIGFub255bWl0eSwgY2FsbCB1cGRhdGVBbm9ueW1pdHlGcm9tU2V0dGluZ3MoKSBvciB5b3UgY2FuIHNldCBpdCBkaXJlY3RseSB2aWEgc2V0QW5vbnltaXR5KCkuXG4gICAgICpcbiAgICAgKiBUbyBwYXNzIGFuIGV2ZW50IHRvIFBvc3Rob2c6XG4gICAgICpcbiAgICAgKiAxLiBEZWNsYXJlIGEgdHlwZSBmb3IgdGhlIGV2ZW50LCBleHRlbmRpbmcgSUFub255bW91c0V2ZW50IG9yIElQc2V1ZG9ueW1vdXNFdmVudC5cbiAgICAgKiAyLiBDYWxsIHRoZSBhcHByb3ByaWF0ZSB0cmFjayooKSBtZXRob2QuIFBzZXVkb255bW91cyBldmVudHMgd2lsbCBiZSBkcm9wcGVkIHdoZW4gYW5vbnltaXR5IGlzXG4gICAgICogICAgQW5vbnltb3VzIG9yIERpc2FibGVkOyBBbm9ueW1vdXMgZXZlbnRzIHdpbGwgYmUgZHJvcHBlZCB3aGVuIGFub255bWl0eSBpcyBEaXNhYmxlZC5cbiAgICAgKi9cblxuICAgIHByaXZhdGUgYW5vbnltaXR5ID0gQW5vbnltaXR5LkRpc2FibGVkO1xuICAgIC8vIHNldCB0cnVlIGR1cmluZyB0aGUgY29uc3RydWN0b3IgaWYgcG9zdGhvZyBjb25maWcgaXMgcHJlc2VudCwgb3RoZXJ3aXNlIGZhbHNlXG4gICAgcHJpdmF0ZSByZWFkb25seSBlbmFibGVkOiBib29sZWFuID0gZmFsc2U7XG4gICAgcHJpdmF0ZSBzdGF0aWMgX2luc3RhbmNlOiBQb3N0aG9nQW5hbHl0aWNzIHwgbnVsbCA9IG51bGw7XG4gICAgcHJpdmF0ZSBwbGF0Zm9ybVN1cGVyUHJvcGVydGllczogUHJvcGVydGllcyA9IHt9O1xuICAgIHB1YmxpYyBzdGF0aWMgcmVhZG9ubHkgQU5BTFlUSUNTX0VWRU5UX1RZUEUgPSBcImltLnZlY3Rvci5hbmFseXRpY3NcIjtcbiAgICBwcml2YXRlIHByb3BlcnRpZXNGb3JOZXh0RXZlbnQ6IFBhcnRpYWw8UmVjb3JkPFwiJHNldFwiIHwgXCIkc2V0X29uY2VcIiwgVXNlclByb3BlcnRpZXM+PiA9IHt9O1xuICAgIHByaXZhdGUgdXNlclByb3BlcnR5Q2FjaGU6IFVzZXJQcm9wZXJ0aWVzID0ge307XG4gICAgcHJpdmF0ZSBhdXRoZW50aWNhdGlvblR5cGU6IFNpZ251cFtcImF1dGhlbnRpY2F0aW9uVHlwZVwiXSA9IFwiT3RoZXJcIjtcbiAgICBwcml2YXRlIHdhdGNoU2V0dGluZ1JlZj86IHN0cmluZztcblxuICAgIC8vIFdpbGwgYmUgc2V0IHdoZW4gdGhlIG1hdHJpeENsaWVudCBpcyBwYXNzZWQgdG8gdGhlIGFuYWx5dGljcyBvYmplY3QgKGUuZy4gb24gbG9naW4pLlxuICAgIHByaXZhdGUgY3VycmVudENyeXB0b0JhY2tlbmQ/OiBcIlJ1c3RcIiB8IFwiTGVnYWN5XCIgPSB1bmRlZmluZWQ7XG5cbiAgICBwdWJsaWMgc3RhdGljIGdldCBpbnN0YW5jZSgpOiBQb3N0aG9nQW5hbHl0aWNzIHtcbiAgICAgICAgaWYgKCF0aGlzLl9pbnN0YW5jZSkge1xuICAgICAgICAgICAgdGhpcy5faW5zdGFuY2UgPSBuZXcgUG9zdGhvZ0FuYWx5dGljcyhwb3N0aG9nKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5faW5zdGFuY2U7XG4gICAgfVxuXG4gICAgcHVibGljIGNvbnN0cnVjdG9yKHByaXZhdGUgcmVhZG9ubHkgcG9zdGhvZzogUG9zdEhvZykge1xuICAgICAgICBjb25zdCBwb3N0aG9nQ29uZmlnID0gU2RrQ29uZmlnLmdldE9iamVjdChcInBvc3Rob2dcIik7XG4gICAgICAgIGlmIChwb3N0aG9nQ29uZmlnKSB7XG4gICAgICAgICAgICB0aGlzLnBvc3Rob2cuaW5pdChwb3N0aG9nQ29uZmlnLmdldChcInByb2plY3RfYXBpX2tleVwiKSwge1xuICAgICAgICAgICAgICAgIGFwaV9ob3N0OiBwb3N0aG9nQ29uZmlnLmdldChcImFwaV9ob3N0XCIpLFxuICAgICAgICAgICAgICAgIGF1dG9jYXB0dXJlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBtYXNrX2FsbF90ZXh0OiB0cnVlLFxuICAgICAgICAgICAgICAgIG1hc2tfYWxsX2VsZW1lbnRfYXR0cmlidXRlczogdHJ1ZSxcbiAgICAgICAgICAgICAgICAvLyBUaGlzIG9ubHkgdHJpZ2dlcnMgb24gcGFnZSBsb2FkLCB3aGljaCBmb3Igb3VyIFNQQSBpc24ndCBwYXJ0aWN1bGFybHkgdXNlZnVsLlxuICAgICAgICAgICAgICAgIC8vIFBsdXMsIHRoZSAuY2FwdHVyZSBjYWxsIG9yaWdpbmF0aW5nIGZyb20gc29tZXdoZXJlIGluIHBvc3Rob2cgbWFrZXMgaXQgaGFyZFxuICAgICAgICAgICAgICAgIC8vIHRvIHJlZGFjdCBVUkxzLCB3aGljaCByZXF1aXJlcyBhc3luYyBjb2RlLlxuICAgICAgICAgICAgICAgIC8vXG4gICAgICAgICAgICAgICAgLy8gVG8gcmFpc2UgdGhpcyBtYW51YWxseSwganVzdCBjYWxsIC5jYXB0dXJlKFwiJHBhZ2V2aWV3XCIpIG9yIHBvc3Rob2cuY2FwdHVyZV9wYWdldmlldy5cbiAgICAgICAgICAgICAgICBjYXB0dXJlX3BhZ2V2aWV3OiBmYWxzZSxcbiAgICAgICAgICAgICAgICBzYW5pdGl6ZV9wcm9wZXJ0aWVzOiB0aGlzLnNhbml0aXplUHJvcGVydGllcyxcbiAgICAgICAgICAgICAgICByZXNwZWN0X2RudDogdHJ1ZSxcbiAgICAgICAgICAgICAgICBhZHZhbmNlZF9kaXNhYmxlX2RlY2lkZTogdHJ1ZSxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgdGhpcy5lbmFibGVkID0gdHJ1ZTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuZW5hYmxlZCA9IGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgZGlzLnJlZ2lzdGVyKHRoaXMub25BY3Rpb24pO1xuICAgICAgICBTZXR0aW5nc1N0b3JlLm1vbml0b3JTZXR0aW5nKFwibGF5b3V0XCIsIG51bGwpO1xuICAgICAgICBTZXR0aW5nc1N0b3JlLm1vbml0b3JTZXR0aW5nKFwidXNlQ29tcGFjdExheW91dFwiLCBudWxsKTtcbiAgICAgICAgdGhpcy5vbkxheW91dFVwZGF0ZWQoKTtcbiAgICAgICAgdGhpcy51cGRhdGVDcnlwdG9TdXBlclByb3BlcnR5KCk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvbkxheW91dFVwZGF0ZWQgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIGxldCBsYXlvdXQ6IFVzZXJQcm9wZXJ0aWVzW1wiV2ViTGF5b3V0XCJdO1xuXG4gICAgICAgIHN3aXRjaCAoU2V0dGluZ3NTdG9yZS5nZXRWYWx1ZShcImxheW91dFwiKSkge1xuICAgICAgICAgICAgY2FzZSBMYXlvdXQuSVJDOlxuICAgICAgICAgICAgICAgIGxheW91dCA9IFwiSVJDXCI7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIExheW91dC5CdWJibGU6XG4gICAgICAgICAgICAgICAgbGF5b3V0ID0gXCJCdWJibGVcIjtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIGNhc2UgTGF5b3V0Lkdyb3VwOlxuICAgICAgICAgICAgICAgIGxheW91dCA9IFNldHRpbmdzU3RvcmUuZ2V0VmFsdWUoXCJ1c2VDb21wYWN0TGF5b3V0XCIpID8gXCJDb21wYWN0XCIgOiBcIkdyb3VwXCI7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUaGlzIGlzIGtub3duIHRvIGNsb2JiZXIgb3RoZXIgZGV2aWNlcyBidXQgaXMgYSBnb29kIGVub3VnaCBzb2x1dGlvblxuICAgICAgICAvLyB0byBnZXQgYW4gaWRlYSBvZiBob3cgbXVjaCB1c2UgZWFjaCBsYXlvdXQgZ2V0cy5cbiAgICAgICAgdGhpcy5zZXRQcm9wZXJ0eShcIldlYkxheW91dFwiLCBsYXlvdXQpO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uQWN0aW9uID0gKHBheWxvYWQ6IEFjdGlvblBheWxvYWQpOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKHBheWxvYWQuYWN0aW9uICE9PSBBY3Rpb24uU2V0dGluZ1VwZGF0ZWQpIHJldHVybjtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3NQYXlsb2FkID0gcGF5bG9hZCBhcyBTZXR0aW5nVXBkYXRlZFBheWxvYWQ7XG4gICAgICAgIGlmIChbXCJsYXlvdXRcIiwgXCJ1c2VDb21wYWN0TGF5b3V0XCJdLmluY2x1ZGVzKHNldHRpbmdzUGF5bG9hZC5zZXR0aW5nTmFtZSkpIHtcbiAgICAgICAgICAgIHRoaXMub25MYXlvdXRVcGRhdGVkKCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgLy8gd2UgcGVyc2lzdCB0aGUgbGFzdCBgJHNjcmVlbl9uYW1lYCBhbmQgc2VuZCBpdCBmb3IgYWxsIGV2ZW50cyB1bnRpbCBpdCBpcyByZXBsYWNlZFxuICAgIHByaXZhdGUgbGFzdFNjcmVlbjogU2NyZWVuTmFtZSA9IFwiTG9hZGluZ1wiO1xuXG4gICAgcHJpdmF0ZSBzYW5pdGl6ZVByb3BlcnRpZXMgPSAocHJvcGVydGllczogUHJvcGVydGllcywgZXZlbnROYW1lOiBzdHJpbmcpOiBQcm9wZXJ0aWVzID0+IHtcbiAgICAgICAgLy8gQ2FsbGJhY2sgZnJvbSBwb3N0aG9nIHRvIHNhbml0aXplIHByb3BlcnRpZXMgYmVmb3JlIHNlbmRpbmcgdGhlbSB0byB0aGUgc2VydmVyLlxuICAgICAgICAvL1xuICAgICAgICAvLyBIZXJlIHdlIHNhbml0aXplIHBvc3Rob2cncyBidWlsdCBpbiBwcm9wZXJ0aWVzIHdoaWNoIGxlYWsgUElJIGUuZy4gdXJsIHJlcG9ydGluZy5cbiAgICAgICAgLy8gU2VlIHV0aWxzLmpzIF8uaW5mby5wcm9wZXJ0aWVzIGluIHBvc3Rob2ctanMuXG5cbiAgICAgICAgaWYgKGV2ZW50TmFtZSA9PT0gXCIkcGFnZXZpZXdcIikge1xuICAgICAgICAgICAgdGhpcy5sYXN0U2NyZWVuID0gcHJvcGVydGllc1tcIiRjdXJyZW50X3VybFwiXTtcbiAgICAgICAgfVxuICAgICAgICAvLyBXZSBpbmplY3QgYSBzY3JlZW4gaWRlbnRpZmllciBpbiAkY3VycmVudF91cmwgYXMgcGVyIGh0dHBzOi8vcG9zdGhvZy5jb20vdHV0b3JpYWxzL3NwYVxuICAgICAgICBwcm9wZXJ0aWVzW1wiJGN1cnJlbnRfdXJsXCJdID0gdGhpcy5sYXN0U2NyZWVuO1xuXG4gICAgICAgIGlmICh0aGlzLmFub255bWl0eSA9PSBBbm9ueW1pdHkuQW5vbnltb3VzKSB7XG4gICAgICAgICAgICAvLyBkcm9wIHJlZmVycmVyIGluZm9ybWF0aW9uIGZvciBhbm9ueW1vdXMgdXNlcnNcbiAgICAgICAgICAgIHByb3BlcnRpZXNbXCIkcmVmZXJyZXJcIl0gPSBudWxsO1xuICAgICAgICAgICAgcHJvcGVydGllc1tcIiRyZWZlcnJpbmdfZG9tYWluXCJdID0gbnVsbDtcbiAgICAgICAgICAgIHByb3BlcnRpZXNbXCIkaW5pdGlhbF9yZWZlcnJlclwiXSA9IG51bGw7XG4gICAgICAgICAgICBwcm9wZXJ0aWVzW1wiJGluaXRpYWxfcmVmZXJyaW5nX2RvbWFpblwiXSA9IG51bGw7XG5cbiAgICAgICAgICAgIC8vIGRyb3AgZGV2aWNlIElELCB3aGljaCBpcyBhIFVVSUQgcGVyc2lzdGVkIGluIGxvY2FsIHN0b3JhZ2VcbiAgICAgICAgICAgIHByb3BlcnRpZXNbXCIkZGV2aWNlX2lkXCJdID0gbnVsbDtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBwcm9wZXJ0aWVzO1xuICAgIH07XG5cbiAgICBwcml2YXRlIHJlZ2lzdGVyU3VwZXJQcm9wZXJ0aWVzKHByb3BlcnRpZXM6IFByb3BlcnRpZXMpOiB2b2lkIHtcbiAgICAgICAgaWYgKHRoaXMuZW5hYmxlZCkge1xuICAgICAgICAgICAgdGhpcy5wb3N0aG9nLnJlZ2lzdGVyKHByb3BlcnRpZXMpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBzdGF0aWMgYXN5bmMgZ2V0UGxhdGZvcm1Qcm9wZXJ0aWVzKCk6IFByb21pc2U8UGFydGlhbDxQbGF0Zm9ybVByb3BlcnRpZXM+PiB7XG4gICAgICAgIGNvbnN0IHBsYXRmb3JtID0gUGxhdGZvcm1QZWcuZ2V0KCk7XG4gICAgICAgIGxldCBhcHBWZXJzaW9uOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhcHBWZXJzaW9uID0gYXdhaXQgcGxhdGZvcm0/LmdldEFwcFZlcnNpb24oKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgLy8gdGhpcyBoYXBwZW5zIGlmIG5vIHZlcnNpb24gaXMgc2V0IGkuZS4gaW4gZGV2XG4gICAgICAgICAgICBhcHBWZXJzaW9uID0gXCJ1bmtub3duXCI7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgYXBwVmVyc2lvbixcbiAgICAgICAgICAgIGFwcFBsYXRmb3JtOiBwbGF0Zm9ybT8uZ2V0SHVtYW5SZWFkYWJsZU5hbWUoKSxcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0bGluZSBuby11bnVzZWQtdmFyc1xuICAgIHByaXZhdGUgY2FwdHVyZShldmVudE5hbWU6IHN0cmluZywgcHJvcGVydGllczogUHJvcGVydGllcywgb3B0aW9ucz86IENhcHR1cmVPcHRpb25zKTogdm9pZCB7XG4gICAgICAgIGlmICghdGhpcy5lbmFibGVkKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgeyBvcmlnaW4sIGhhc2gsIHBhdGhuYW1lIH0gPSB3aW5kb3cubG9jYXRpb247XG4gICAgICAgIHByb3BlcnRpZXNbXCJyZWRhY3RlZEN1cnJlbnRVcmxcIl0gPSBnZXRSZWRhY3RlZEN1cnJlbnRMb2NhdGlvbihvcmlnaW4sIGhhc2gsIHBhdGhuYW1lKTtcbiAgICAgICAgdGhpcy5wb3N0aG9nLmNhcHR1cmUoZXZlbnROYW1lLCB7IC4uLnRoaXMucHJvcGVydGllc0Zvck5leHRFdmVudCwgLi4ucHJvcGVydGllcyB9LCBvcHRpb25zKTtcbiAgICAgICAgdGhpcy5wcm9wZXJ0aWVzRm9yTmV4dEV2ZW50ID0ge307XG4gICAgfVxuXG4gICAgcHVibGljIGlzRW5hYmxlZCgpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZW5hYmxlZDtcbiAgICB9XG5cbiAgICBwdWJsaWMgc2V0QW5vbnltaXR5KGFub255bWl0eTogQW5vbnltaXR5KTogdm9pZCB7XG4gICAgICAgIC8vIFVwZGF0ZSB0aGlzLmFub255bWl0eS5cbiAgICAgICAgLy8gVGhpcyBpcyBwdWJsaWMgZm9yIHRlc3RpbmcgcHVycG9zZXMsIHR5cGljYWxseSB5b3Ugd2FudCB0byBjYWxsIHVwZGF0ZUFub255bWl0eUZyb21TZXR0aW5nc1xuICAgICAgICAvLyB0byBlbnN1cmUgdGhpcyB2YWx1ZSBpcyBpbiBzdGVwIHdpdGggdGhlIHVzZXIncyBzZXR0aW5ncy5cbiAgICAgICAgaWYgKHRoaXMuZW5hYmxlZCAmJiAoYW5vbnltaXR5ID09IEFub255bWl0eS5EaXNhYmxlZCB8fCBhbm9ueW1pdHkgPT0gQW5vbnltaXR5LkFub255bW91cykpIHtcbiAgICAgICAgICAgIC8vIHdoZW4gdHJhbnNpdGlvbmluZyB0byBEaXNhYmxlZCBvciBBbm9ueW1vdXMgZW5zdXJlIHdlIGNsZWFyIG91dCBhbnkgcHJpb3Igc3RhdGVcbiAgICAgICAgICAgIC8vIHNldCBpbiBwb3N0aG9nIGUuZy4gZGlzdGluY3QgSURcbiAgICAgICAgICAgIHRoaXMucG9zdGhvZy5yZXNldCgpO1xuICAgICAgICAgICAgLy8gUmVzdG9yZSBhbnkgcHJldmlvdXNseSBzZXQgcGxhdGZvcm0gc3VwZXIgcHJvcGVydGllc1xuICAgICAgICAgICAgdGhpcy5yZWdpc3RlclN1cGVyUHJvcGVydGllcyh0aGlzLnBsYXRmb3JtU3VwZXJQcm9wZXJ0aWVzKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLmFub255bWl0eSA9IGFub255bWl0eTtcbiAgICAgICAgLy8gdXBkYXRlIGFueWhvdywgbm8tb3AgaWYgbm90IGVuYWJsZWQgb3IgRGlzYWJsZWQuXG4gICAgICAgIHRoaXMudXBkYXRlQ3J5cHRvU3VwZXJQcm9wZXJ0eSgpO1xuICAgIH1cblxuICAgIHByaXZhdGUgc3RhdGljIGdldFJhbmRvbUFuYWx5dGljc0lkKCk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiBbLi4uY3J5cHRvLmdldFJhbmRvbVZhbHVlcyhuZXcgVWludDhBcnJheSgxNikpXS5tYXAoKGMpID0+IGMudG9TdHJpbmcoMTYpKS5qb2luKFwiXCIpO1xuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyBpZGVudGlmeVVzZXIoY2xpZW50OiBNYXRyaXhDbGllbnQsIGFuYWx5dGljc0lkR2VuZXJhdG9yOiAoKSA9PiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgaWYgKHRoaXMuYW5vbnltaXR5ID09IEFub255bWl0eS5Qc2V1ZG9ueW1vdXMpIHtcbiAgICAgICAgICAgIC8vIENoZWNrIHRoZSB1c2VyJ3MgYWNjb3VudF9kYXRhIGZvciBhbiBhbmFseXRpY3MgSUQgdG8gdXNlLiBTdG9yaW5nIHRoZSBJRCBpbiBhY2NvdW50X2RhdGEgYWxsb3dzXG4gICAgICAgICAgICAvLyBkaWZmZXJlbnQgZGV2aWNlcyB0byBzZW5kIHRoZSBzYW1lIElELlxuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBjb25zdCBhY2NvdW50RGF0YSA9IGF3YWl0IGNsaWVudC5nZXRBY2NvdW50RGF0YUZyb21TZXJ2ZXIoUG9zdGhvZ0FuYWx5dGljcy5BTkFMWVRJQ1NfRVZFTlRfVFlQRSk7XG4gICAgICAgICAgICAgICAgbGV0IGFuYWx5dGljc0lEID0gYWNjb3VudERhdGE/LmlkO1xuICAgICAgICAgICAgICAgIGlmICghYW5hbHl0aWNzSUQpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gQ291bGRuJ3QgcmV0cmlldmUgYW4gYW5hbHl0aWNzIElEIGZyb20gdXNlciBzZXR0aW5ncywgc28gY3JlYXRlIG9uZSBhbmQgc2V0IGl0IG9uIHRoZSBzZXJ2ZXIuXG4gICAgICAgICAgICAgICAgICAgIC8vIE5vdGUgdGhlcmUncyBhIHJhY2UgY29uZGl0aW9uIGhlcmUgLSBpZiB0d28gZGV2aWNlcyBkbyB0aGVzZSBzdGVwcyBhdCB0aGUgc2FtZSB0aW1lLCBsYXN0IHdyaXRlXG4gICAgICAgICAgICAgICAgICAgIC8vIHdpbnMsIGFuZCB0aGUgZmlyc3Qgd3JpdGVyIHdpbGwgc2VuZCB0cmFja2luZyB3aXRoIGFuIElEIHRoYXQgZG9lc24ndCBtYXRjaCB0aGUgb25lIG9uIHRoZSBzZXJ2ZXJcbiAgICAgICAgICAgICAgICAgICAgLy8gdW50aWwgdGhlIG5leHQgdGltZSBhY2NvdW50IGRhdGEgaXMgcmVmcmVzaGVkIGFuZCB0aGlzIGZ1bmN0aW9uIGlzIGNhbGxlZCAobW9zdCBsaWtlbHkgb24gbmV4dFxuICAgICAgICAgICAgICAgICAgICAvLyBwYWdlIGxvYWQpLiBUaGlzIHdpbGwgaGFwcGVuIHByZXR0eSBpbmZyZXF1ZW50bHksIHNvIHdlIGNhbiB0b2xlcmF0ZSB0aGUgcG9zc2liaWxpdHkuXG4gICAgICAgICAgICAgICAgICAgIGFuYWx5dGljc0lEID0gYW5hbHl0aWNzSWRHZW5lcmF0b3IoKTtcbiAgICAgICAgICAgICAgICAgICAgYXdhaXQgY2xpZW50LnNldEFjY291bnREYXRhKFxuICAgICAgICAgICAgICAgICAgICAgICAgUG9zdGhvZ0FuYWx5dGljcy5BTkFMWVRJQ1NfRVZFTlRfVFlQRSxcbiAgICAgICAgICAgICAgICAgICAgICAgIE9iamVjdC5hc3NpZ24oeyBpZDogYW5hbHl0aWNzSUQgfSwgYWNjb3VudERhdGEpLFxuICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAodGhpcy5wb3N0aG9nLmdldF9kaXN0aW5jdF9pZCgpID09PSBhbmFseXRpY3NJRCkge1xuICAgICAgICAgICAgICAgICAgICAvLyBObyBwb2ludCBpZGVudGlmeWluZyBhZ2FpblxuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmICh0aGlzLnBvc3Rob2cucGVyc2lzdGVuY2U/LmdldF9wcm9wZXJ0eShcIiR1c2VyX3N0YXRlXCIpID09PSBcImlkZW50aWZpZWRcIikge1xuICAgICAgICAgICAgICAgICAgICAvLyBBbmFseXRpY3MgSUQgaGFzIGNoYW5nZWQsIHJlc2V0IGFzIFBvc3Rob2cgd2lsbCByZWZ1c2UgdG8gbWVyZ2UgaW4gdGhpcyBjYXNlXG4gICAgICAgICAgICAgICAgICAgIHRoaXMucG9zdGhvZy5yZXNldCgpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB0aGlzLnBvc3Rob2cuaWRlbnRpZnkoYW5hbHl0aWNzSUQpO1xuICAgICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgICAgIC8vIFRoZSBhYm92ZSBjb3VsZCBmYWlsIGR1ZSB0byBuZXR3b3JrIHJlcXVlc3RzLCBidXQgbm90IGVzc2VudGlhbCB0byBzdGFydGluZyB0aGUgYXBwbGljYXRpb24sXG4gICAgICAgICAgICAgICAgLy8gc28gc3dhbGxvdyBpdC5cbiAgICAgICAgICAgICAgICBsb2dnZXIubG9nKFwiVW5hYmxlIHRvIGlkZW50aWZ5IHVzZXIgZm9yIHRyYWNraW5nXCIsIGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIGdldEFub255bWl0eSgpOiBBbm9ueW1pdHkge1xuICAgICAgICByZXR1cm4gdGhpcy5hbm9ueW1pdHk7XG4gICAgfVxuXG4gICAgcHVibGljIGxvZ291dCgpOiB2b2lkIHtcbiAgICAgICAgaWYgKHRoaXMuZW5hYmxlZCkge1xuICAgICAgICAgICAgdGhpcy5wb3N0aG9nLnJlc2V0KCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMud2F0Y2hTZXR0aW5nUmVmKSBTZXR0aW5nc1N0b3JlLnVud2F0Y2hTZXR0aW5nKHRoaXMud2F0Y2hTZXR0aW5nUmVmKTtcbiAgICAgICAgdGhpcy5zZXRBbm9ueW1pdHkoQW5vbnltaXR5LkRpc2FibGVkKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgdHJhY2tFdmVudDxFIGV4dGVuZHMgSVBvc3Rob2dFdmVudD4oeyBldmVudE5hbWUsIC4uLnByb3BlcnRpZXMgfTogRSwgb3B0aW9ucz86IENhcHR1cmVPcHRpb25zKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLmFub255bWl0eSA9PSBBbm9ueW1pdHkuRGlzYWJsZWQgfHwgdGhpcy5hbm9ueW1pdHkgPT0gQW5vbnltaXR5LkFub255bW91cykgcmV0dXJuO1xuICAgICAgICB0aGlzLmNhcHR1cmUoZXZlbnROYW1lLCBwcm9wZXJ0aWVzLCBvcHRpb25zKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc2V0UHJvcGVydHk8SyBleHRlbmRzIGtleW9mIFVzZXJQcm9wZXJ0aWVzPihrZXk6IEssIHZhbHVlOiBVc2VyUHJvcGVydGllc1tLXSk6IHZvaWQge1xuICAgICAgICBpZiAodGhpcy51c2VyUHJvcGVydHlDYWNoZVtrZXldID09PSB2YWx1ZSkgcmV0dXJuOyAvLyBub3RoaW5nIHRvIGRvXG4gICAgICAgIHRoaXMudXNlclByb3BlcnR5Q2FjaGVba2V5XSA9IHZhbHVlO1xuXG4gICAgICAgIGlmICghdGhpcy5wcm9wZXJ0aWVzRm9yTmV4dEV2ZW50W1wiJHNldFwiXSkge1xuICAgICAgICAgICAgdGhpcy5wcm9wZXJ0aWVzRm9yTmV4dEV2ZW50W1wiJHNldFwiXSA9IHt9O1xuICAgICAgICB9XG4gICAgICAgIHRoaXMucHJvcGVydGllc0Zvck5leHRFdmVudFtcIiRzZXRcIl1ba2V5XSA9IHZhbHVlO1xuICAgIH1cblxuICAgIHB1YmxpYyBzZXRQcm9wZXJ0eU9uY2U8SyBleHRlbmRzIGtleW9mIFVzZXJQcm9wZXJ0aWVzPihrZXk6IEssIHZhbHVlOiBVc2VyUHJvcGVydGllc1tLXSk6IHZvaWQge1xuICAgICAgICBpZiAodGhpcy51c2VyUHJvcGVydHlDYWNoZVtrZXldKSByZXR1cm47IC8vIG5vdGhpbmcgdG8gZG9cbiAgICAgICAgdGhpcy51c2VyUHJvcGVydHlDYWNoZVtrZXldID0gdmFsdWU7XG5cbiAgICAgICAgaWYgKCF0aGlzLnByb3BlcnRpZXNGb3JOZXh0RXZlbnRbXCIkc2V0X29uY2VcIl0pIHtcbiAgICAgICAgICAgIHRoaXMucHJvcGVydGllc0Zvck5leHRFdmVudFtcIiRzZXRfb25jZVwiXSA9IHt9O1xuICAgICAgICB9XG4gICAgICAgIHRoaXMucHJvcGVydGllc0Zvck5leHRFdmVudFtcIiRzZXRfb25jZVwiXVtrZXldID0gdmFsdWU7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHVwZGF0ZVBsYXRmb3JtU3VwZXJQcm9wZXJ0aWVzKCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICAvLyBVcGRhdGUgc3VwZXIgcHJvcGVydGllcyBpbiBwb3N0aG9nIHdpdGggb3VyIHBsYXRmb3JtIChhcHAgdmVyc2lvbiwgcGxhdGZvcm0pLlxuICAgICAgICAvLyBUaGVzZSBwcm9wZXJ0aWVzIHdpbGwgYmUgc3Vic2VxdWVudGx5IHBhc3NlZCBpbiBldmVyeSBldmVudC5cbiAgICAgICAgLy9cbiAgICAgICAgLy8gVGhpcyBvbmx5IG5lZWRzIHRvIGJlIGRvbmUgb25jZSBwZXIgcGFnZSBsaWZldGltZS4gTm90ZSB0aGF0IGdldFBsYXRmb3JtUHJvcGVydGllc1xuICAgICAgICAvLyBpcyBhc3luYyBhbmQgY2FuIGludm9sdmUgYSBuZXR3b3JrIHJlcXVlc3QgaWYgd2UgYXJlIHJ1bm5pbmcgaW4gYSBicm93c2VyLlxuICAgICAgICB0aGlzLnBsYXRmb3JtU3VwZXJQcm9wZXJ0aWVzID0gYXdhaXQgUG9zdGhvZ0FuYWx5dGljcy5nZXRQbGF0Zm9ybVByb3BlcnRpZXMoKTtcbiAgICAgICAgdGhpcy5yZWdpc3RlclN1cGVyUHJvcGVydGllcyh0aGlzLnBsYXRmb3JtU3VwZXJQcm9wZXJ0aWVzKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIHVwZGF0ZUNyeXB0b1N1cGVyUHJvcGVydHkoKTogdm9pZCB7XG4gICAgICAgIGlmICghdGhpcy5lbmFibGVkIHx8IHRoaXMuYW5vbnltaXR5ID09PSBBbm9ueW1pdHkuRGlzYWJsZWQpIHJldHVybjtcbiAgICAgICAgLy8gVXBkYXRlIHN1cGVyIHByb3BlcnR5IGZvciBjcnlwdG9TREsgaW4gcG9zdGhvZy5cbiAgICAgICAgLy8gVGhpcyBwcm9wZXJ0eSB3aWxsIGJlIHN1YnNlcXVlbnRseSBwYXNzZWQgaW4gZXZlcnkgZXZlbnQuXG4gICAgICAgIGlmICh0aGlzLmN1cnJlbnRDcnlwdG9CYWNrZW5kKSB7XG4gICAgICAgICAgICB0aGlzLnJlZ2lzdGVyU3VwZXJQcm9wZXJ0aWVzKHsgY3J5cHRvU0RLOiB0aGlzLmN1cnJlbnRDcnlwdG9CYWNrZW5kIH0pO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHVwZGF0ZUFub255bWl0eUZyb21TZXR0aW5ncyhjbGllbnQ6IE1hdHJpeENsaWVudCwgcHNldWRvbnltb3VzT3B0SW46IGJvb2xlYW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgLy8gVGVtcG9yYXJ5IHVudGlsIHdlIGhhdmUgbWlncmF0aW9uIGNvZGUgdG8gc3dpdGNoIGNyeXB0byBzZGsuXG4gICAgICAgIGlmIChjbGllbnQuZ2V0Q3J5cHRvKCkpIHtcbiAgICAgICAgICAgIGNvbnN0IGNyeXB0b1ZlcnNpb24gPSBjbGllbnQuZ2V0Q3J5cHRvKCkhLmdldFZlcnNpb24oKTtcbiAgICAgICAgICAgIC8vIHZlcnNpb24gZm9yIHJ1c3QgaXMgc29tZXRoaW5nIGxpa2UgXCJSdXN0IFNESyAwLjYuMCAoOWM2YjU1MCksIFZvZG96ZW1hYyAwLjUuMFwiXG4gICAgICAgICAgICAvLyBmb3IgbGVnYWN5IGl0IHdpbGwgYmUgJ09sbSB4LngueFwiXG4gICAgICAgICAgICBpZiAoY3J5cHRvVmVyc2lvbi5pbmNsdWRlcyhcIlJ1c3QgU0RLXCIpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5jdXJyZW50Q3J5cHRvQmFja2VuZCA9IFwiUnVzdFwiO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLmN1cnJlbnRDcnlwdG9CYWNrZW5kID0gXCJMZWdhY3lcIjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFVwZGF0ZSB0aGlzLmFub255bWl0eSBiYXNlZCBvbiB0aGUgdXNlcidzIGFuYWx5dGljcyBvcHQtaW4gc2V0dGluZ3NcbiAgICAgICAgY29uc3QgYW5vbnltaXR5ID0gcHNldWRvbnltb3VzT3B0SW4gPyBBbm9ueW1pdHkuUHNldWRvbnltb3VzIDogQW5vbnltaXR5LkRpc2FibGVkO1xuICAgICAgICB0aGlzLnNldEFub255bWl0eShhbm9ueW1pdHkpO1xuICAgICAgICBpZiAoYW5vbnltaXR5ID09PSBBbm9ueW1pdHkuUHNldWRvbnltb3VzKSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLmlkZW50aWZ5VXNlcihjbGllbnQsIFBvc3Rob2dBbmFseXRpY3MuZ2V0UmFuZG9tQW5hbHl0aWNzSWQpO1xuICAgICAgICAgICAgaWYgKE1hdHJpeENsaWVudFBlZy5jdXJyZW50VXNlcklzSnVzdFJlZ2lzdGVyZWQoKSkge1xuICAgICAgICAgICAgICAgIHRoaXMudHJhY2tOZXdVc2VyRXZlbnQoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChhbm9ueW1pdHkgIT09IEFub255bWl0eS5EaXNhYmxlZCkge1xuICAgICAgICAgICAgYXdhaXQgdGhpcy51cGRhdGVQbGF0Zm9ybVN1cGVyUHJvcGVydGllcygpO1xuICAgICAgICAgICAgdGhpcy51cGRhdGVDcnlwdG9TdXBlclByb3BlcnR5KCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwdWJsaWMgc3RhcnRMaXN0ZW5pbmdUb1NldHRpbmdzQ2hhbmdlcyhjbGllbnQ6IE1hdHJpeENsaWVudCk6IHZvaWQge1xuICAgICAgICAvLyBMaXN0ZW4gdG8gYWNjb3VudCBkYXRhIGNoYW5nZXMgZnJvbSBzeW5jIHNvIHdlIGNhbiBvYnNlcnZlIGNoYW5nZXMgdG8gcmVsZXZhbnQgZmxhZ3MgYW5kIHVwZGF0ZS5cbiAgICAgICAgLy8gVGhpcyBpcyBjYWxsZWQgLVxuICAgICAgICAvLyAgKiBPbiBwYWdlIGxvYWQsIHdoZW4gdGhlIGFjY291bnQgZGF0YSBpcyBmaXJzdCByZWNlaXZlZCBieSBzeW5jXG4gICAgICAgIC8vICAqIE9uIGxvZ2luXG4gICAgICAgIC8vICAqIFdoZW4gYW5vdGhlciBkZXZpY2UgY2hhbmdlcyBhY2NvdW50IGRhdGFcbiAgICAgICAgLy8gICogV2hlbiB0aGUgdXNlciBjaGFuZ2VzIHRoZWlyIHByZWZlcmVuY2VzIG9uIHRoaXMgZGV2aWNlXG4gICAgICAgIC8vIE5vdGUgdGhhdCBmb3IgbmV3IGFjY291bnRzLCBwc2V1ZG9ueW1vdXNBbmFseXRpY3NPcHRJbiB3b24ndCBiZSBzZXQsIHNvIHVwZGF0ZUFub255bWl0eUZyb21TZXR0aW5nc1xuICAgICAgICAvLyB3b24ndCBiZSBjYWxsZWQgKGkuZS4gdGhpcy5hbm9ueW1pdHkgd2lsbCBiZSBsZWZ0IGFzIHRoZSBkZWZhdWx0LCB1bnRpbCB0aGUgc2V0dGluZyBjaGFuZ2VzKVxuICAgICAgICB0aGlzLndhdGNoU2V0dGluZ1JlZiA9IFNldHRpbmdzU3RvcmUud2F0Y2hTZXR0aW5nKFxuICAgICAgICAgICAgXCJwc2V1ZG9ueW1vdXNBbmFseXRpY3NPcHRJblwiLFxuICAgICAgICAgICAgbnVsbCxcbiAgICAgICAgICAgIChvcmlnaW5hbFNldHRpbmdOYW1lLCBjaGFuZ2VkSW5Sb29tSWQsIGF0TGV2ZWwsIG5ld1ZhbHVlQXRMZXZlbCwgbmV3VmFsdWUpID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLnVwZGF0ZUFub255bWl0eUZyb21TZXR0aW5ncyhjbGllbnQsICEhbmV3VmFsdWUpO1xuICAgICAgICAgICAgfSxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgc2V0QXV0aGVudGljYXRpb25UeXBlKGF1dGhlbnRpY2F0aW9uVHlwZTogU2lnbnVwW1wiYXV0aGVudGljYXRpb25UeXBlXCJdKTogdm9pZCB7XG4gICAgICAgIHRoaXMuYXV0aGVudGljYXRpb25UeXBlID0gYXV0aGVudGljYXRpb25UeXBlO1xuICAgIH1cblxuICAgIHByaXZhdGUgdHJhY2tOZXdVc2VyRXZlbnQoKTogdm9pZCB7XG4gICAgICAgIC8vIFRoaXMgaXMgdGhlIG9ubHkgZXZlbnQgdGhhdCBjb3VsZCBoYXZlIG9jY3VyZWQgYmVmb3JlIGFuYWx5dGljcyBvcHQtaW5cbiAgICAgICAgLy8gdGhhdCB3ZSB3YW50IHRvIGFjY3VtdWxhdGUgYmVmb3JlIHRoZSB1c2VyIGhhcyBnaXZlbiBjb25zZW50XG4gICAgICAgIC8vIEFsbCBvdGhlciBzY2VuYXJpb3Mgc2hvdWxkIG5vdCB0cmFjayBhIHVzZXIgYmVmb3JlIHRoZXkgaGF2ZSBnaXZlblxuICAgICAgICAvLyBleHBsaWNpdCBjb25zZW50IHRoYXQgdGhleSBhcmUgb2sgd2l0aCB0aGVpciBhbmFseXRpY3MgZGF0YSBiZWluZyBjb2xsZWN0ZWRcbiAgICAgICAgY29uc3Qgb3B0aW9uczogQ2FwdHVyZU9wdGlvbnMgPSB7fTtcbiAgICAgICAgY29uc3QgcmVnaXN0cmF0aW9uVGltZSA9IHBhcnNlSW50KHdpbmRvdy5sb2NhbFN0b3JhZ2UuZ2V0SXRlbShcIm14X3JlZ2lzdHJhdGlvbl90aW1lXCIpISwgMTApO1xuICAgICAgICBpZiAoIWlzTmFOKHJlZ2lzdHJhdGlvblRpbWUpKSB7XG4gICAgICAgICAgICBvcHRpb25zLnRpbWVzdGFtcCA9IG5ldyBEYXRlKHJlZ2lzdHJhdGlvblRpbWUpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXMudHJhY2tFdmVudDxTaWdudXA+KFxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIGV2ZW50TmFtZTogXCJTaWdudXBcIixcbiAgICAgICAgICAgICAgICBhdXRoZW50aWNhdGlvblR5cGU6IHRoaXMuYXV0aGVudGljYXRpb25UeXBlLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIG9wdGlvbnMsXG4gICAgICAgICk7XG4gICAgfVxufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBUUEsSUFBQUEsVUFBQSxHQUFBQyxzQkFBQSxDQUFBQyxPQUFBO0FBRUEsSUFBQUMsT0FBQSxHQUFBRCxPQUFBO0FBSUEsSUFBQUUsWUFBQSxHQUFBSCxzQkFBQSxDQUFBQyxPQUFBO0FBQ0EsSUFBQUcsVUFBQSxHQUFBSixzQkFBQSxDQUFBQyxPQUFBO0FBQ0EsSUFBQUksZ0JBQUEsR0FBQUosT0FBQTtBQUNBLElBQUFLLGNBQUEsR0FBQU4sc0JBQUEsQ0FBQUMsT0FBQTtBQUdBLElBQUFNLFFBQUEsR0FBQU4sT0FBQTtBQUVBLElBQUFPLFdBQUEsR0FBQVIsc0JBQUEsQ0FBQUMsT0FBQTtBQUNBLElBQUFRLE9BQUEsR0FBQVIsT0FBQTtBQUFpRCxNQUFBUyxTQUFBO0FBQUEsU0FBQUMsUUFBQUMsQ0FBQSxFQUFBQyxDQUFBLFFBQUFDLENBQUEsR0FBQUMsTUFBQSxDQUFBQyxJQUFBLENBQUFKLENBQUEsT0FBQUcsTUFBQSxDQUFBRSxxQkFBQSxRQUFBQyxDQUFBLEdBQUFILE1BQUEsQ0FBQUUscUJBQUEsQ0FBQUwsQ0FBQSxHQUFBQyxDQUFBLEtBQUFLLENBQUEsR0FBQUEsQ0FBQSxDQUFBQyxNQUFBLFdBQUFOLENBQUEsV0FBQUUs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