matrix-react-sdk
Version:
SDK for matrix.org using React
674 lines (629 loc) • 92.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UserFriendlyError = exports.CustomTranslationOptions = void 0;
exports._t = _t;
exports._tDom = _tDom;
exports._td = _td;
exports.getAllLanguagesFromJson = getAllLanguagesFromJson;
exports.getAllLanguagesWithLabels = getAllLanguagesWithLabels;
exports.getCurrentLanguage = getCurrentLanguage;
exports.getLanguageFromBrowser = getLanguageFromBrowser;
exports.getLanguagesFromBrowser = getLanguagesFromBrowser;
Object.defineProperty(exports, "getNormalizedLanguageKeys", {
enumerable: true,
get: function () {
return _matrixWebI18n.getNormalizedLanguageKeys;
}
});
exports.getUserLanguage = getUserLanguage;
exports.lookupString = lookupString;
Object.defineProperty(exports, "normalizeLanguageKey", {
enumerable: true,
get: function () {
return _matrixWebI18n.normalizeLanguageKey;
}
});
exports.pickBestLanguage = pickBestLanguage;
exports.registerCustomTranslations = registerCustomTranslations;
exports.replaceByRegexes = replaceByRegexes;
exports.sanitizeForTranslation = sanitizeForTranslation;
exports.setLanguage = setLanguage;
exports.setMissingEntryGenerator = setMissingEntryGenerator;
exports.substitute = substitute;
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _counterpart = _interopRequireDefault(require("counterpart"));
var _react = _interopRequireDefault(require("react"));
var _logger = require("matrix-js-sdk/src/logger");
var _utils = require("matrix-js-sdk/src/utils");
var _matrixWebI18n = require("matrix-web-i18n");
var _lodash = _interopRequireDefault(require("lodash"));
var _SettingsStore = _interopRequireDefault(require("./settings/SettingsStore"));
var _PlatformPeg = _interopRequireDefault(require("./PlatformPeg"));
var _SettingLevel = require("./settings/SettingLevel");
var _promise = require("./utils/promise");
var _SdkConfig = _interopRequireDefault(require("./SdkConfig"));
var _ModuleRunner = require("./modules/ModuleRunner");
var _languages = _interopRequireDefault(require("$webapp/i18n/languages.json"));
const _excluded = ["cause"];
/*
Copyright 2024 New Vector Ltd.
Copyright 2019-2022 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
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; }
const i18nFolder = "i18n/";
// Control whether to also return original, untranslated strings
// Useful for debugging and testing
const ANNOTATE_STRINGS = false;
// We use english strings as keys, some of which contain full stops
_counterpart.default.setSeparator(_matrixWebI18n.KEY_SEPARATOR);
// see `translateWithFallback` for an explanation of fallback handling
const FALLBACK_LOCALE = "en";
_counterpart.default.setFallbackLocale(FALLBACK_LOCALE);
/**
* Used to rethrow an error with a user-friendly translatable message while maintaining
* access to that original underlying error. Downstream consumers can display the
* `translatedMessage` property in the UI and inspect the underlying error with the
* `cause` property.
*
* The error message will display as English in the console and logs so Element
* developers can easily understand the error and find the source in the code. It also
* helps tools like Sentry deduplicate the error, or just generally searching in
* rageshakes to find all instances regardless of the users locale.
*
* @param message - The untranslated error message text, e.g "Something went wrong with %(foo)s".
* @param substitutionVariablesAndCause - Variable substitutions for the translation and
* original cause of the error. If there is no cause, just pass `undefined`, e.g { foo:
* 'bar', cause: err || undefined }
*/
class UserFriendlyError extends Error {
constructor(message, substitutionVariablesAndCause) {
// Prevent "Could not find /%\(cause\)s/g in x" logs to the console by removing it from the list
const _ref = substitutionVariablesAndCause ?? {},
{
cause
} = _ref,
substitutionVariables = (0, _objectWithoutProperties2.default)(_ref, _excluded);
const errorOptions = {
cause
};
// Create the error with the English version of the message that we want to show up in the logs
const englishTranslatedMessage = _t(message, _objectSpread(_objectSpread({}, substitutionVariables), {}, {
locale: "en"
}));
super(englishTranslatedMessage, errorOptions);
// Also provide a translated version of the error in the users locale to display
(0, _defineProperty2.default)(this, "translatedMessage", void 0);
this.translatedMessage = _t(message, substitutionVariables);
}
}
exports.UserFriendlyError = UserFriendlyError;
function getUserLanguage() {
const language = _SettingsStore.default.getValue("language", null, /*excludeDefault:*/true);
if (typeof language === "string" && language !== "") {
return language;
} else {
return (0, _matrixWebI18n.normalizeLanguageKey)(getLanguageFromBrowser());
}
}
/**
* A type representing the union of possible keys into the translation file using `|` delimiter to access nested fields.
* @example `common|error` to access `error` within the `common` sub-object.
* {
* "common": {
* "error": "Error"
* }
* }
*/
// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
function _td(s) {
return s;
}
/**
* to improve screen reader experience translations that are not in the main page language
* eg a translation that fell back to english from another language
* should be wrapped with an appropriate `lang='en'` attribute
* counterpart's `translate` doesn't expose a way to determine if the resulting translation
* is in the target locale or a fallback locale
* for this reason, force fallbackLocale === locale in the first call to translate
* and fallback 'manually' so we can mark fallback strings appropriately
* */
const translateWithFallback = (text, options) => {
const translated = _counterpart.default.translate(text, _objectSpread(_objectSpread({}, options), {}, {
fallbackLocale: _counterpart.default.getLocale()
}));
if (!translated || translated.startsWith("missing translation:")) {
const fallbackTranslated = _counterpart.default.translate(text, _objectSpread(_objectSpread({}, options), {}, {
locale: FALLBACK_LOCALE
}));
if ((!fallbackTranslated || fallbackTranslated.startsWith("missing translation:")) && process.env.NODE_ENV !== "development") {
// Even the translation via FALLBACK_LOCALE failed; this can happen if
//
// 1. The string isn't in the translations dictionary, usually because you're in develop
// and haven't run yarn i18n
// 2. Loading the translation resources over the network failed, which can happen due to
// to network or if the client tried to load a translation that's been removed from the
// server.
//
// At this point, its the lesser evil to show the untranslated text, which
// will be in English, so the user can still make out *something*, rather than an opaque
// "missing translation" error.
//
// Don't do this in develop so people remember to run yarn i18n.
return {
translated: text,
isFallback: true
};
}
return {
translated: fallbackTranslated,
isFallback: true
};
}
return {
translated
};
};
// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
// Takes the same arguments as counterpart.translate()
function safeCounterpartTranslate(text, variables) {
// Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components
// However, still pass the variables to counterpart so that it can choose the correct plural if count is given
// It is enough to pass the count variable, but in the future counterpart might make use of other information too
const options = _objectSpread(_objectSpread({}, variables), {}, {
interpolate: false
});
// Horrible hack to avoid https://github.com/vector-im/element-web/issues/4191
// The interpolation library that counterpart uses does not support undefined/null
// values and instead will throw an error. This is a problem since everywhere else
// in JS land passing undefined/null will simply stringify instead, and when converting
// valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null
// if there are no existing null guards. To avoid this making the app completely inoperable,
// we'll check all the values for undefined/null and stringify them here.
if (options && typeof options === "object") {
Object.keys(options).forEach(k => {
if (options[k] === undefined) {
_logger.logger.warn("safeCounterpartTranslate called with undefined interpolation name: " + k);
options[k] = "undefined";
}
if (options[k] === null) {
_logger.logger.warn("safeCounterpartTranslate called with null interpolation name: " + k);
options[k] = "null";
}
});
}
return translateWithFallback(text, options);
}
/**
* The value a variable or tag can take for a translation interpolation.
*/
// For development/testing purposes it is useful to also output the original string
// Don't do that for release versions
const annotateStrings = (result, translationKey) => {
if (!ANNOTATE_STRINGS) {
return result;
}
if (typeof result === "string") {
return `@@${translationKey}##${result}@@`;
} else {
return /*#__PURE__*/_react.default.createElement("span", {
className: "translated-string",
"data-orig-string": translationKey
}, result);
}
};
/*
* Translates text and optionally also replaces XML-ish elements in the text with e.g. React components
* @param {string} text The untranslated text, e.g "click <a>here</a> now to %(foo)s".
* @param {object} variables Variable substitutions, e.g { foo: 'bar' }
* @param {object} tags Tag substitutions e.g. { 'a': (sub) => <a>{sub}</a> }
*
* In both variables and tags, the values to substitute with can be either simple strings, React components,
* or functions that return the value to use in the substitution (e.g. return a React component). In case of
* a tag replacement, the function receives as the argument the text inside the element corresponding to the tag.
*
* Use tag substitutions if you need to translate text between tags (e.g. "<a>Click here!</a>"), otherwise
* you will end up with literal "<a>" in your output, rather than HTML. Note that you can also use variable
* substitution to insert React components, but you can't use it to translate text between tags.
*
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/
// eslint-next-line @typescript-eslint/naming-convention
function _t(text, variables, tags) {
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
const {
translated
} = safeCounterpartTranslate(text, variables);
const substituted = substitute(translated, variables, tags);
return annotateStrings(substituted, text);
}
/**
* Utility function to look up a string by its translation key without resolving variables & tags
* @param key - the translation key to return the value for
*/
function lookupString(key) {
return safeCounterpartTranslate(key, {}).translated;
}
/*
* Wraps normal _t function and adds atttribution for translations that used a fallback locale
* Wraps translations that fell back from active locale to fallback locale with a `<span lang=<fallback locale>>`
* @param {string} text The untranslated text, e.g "click <a>here</a> now to %(foo)s".
* @param {object} variables Variable substitutions, e.g { foo: 'bar' }
* @param {object} tags Tag substitutions e.g. { 'a': (sub) => <a>{sub}</a> }
*
* @return a React <span> component if any non-strings were used in substitutions
* or translation used a fallback locale, otherwise a string
*/
// eslint-next-line @typescript-eslint/naming-convention
function _tDom(text, variables, tags) {
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
const {
translated,
isFallback
} = safeCounterpartTranslate(text, variables);
const substituted = substitute(translated, variables, tags);
// wrap en fallback translation with lang attribute for screen readers
const result = isFallback ? /*#__PURE__*/_react.default.createElement("span", {
lang: "en"
}, substituted) : substituted;
return annotateStrings(result, text);
}
/**
* Sanitizes unsafe text for the sanitizer, ensuring references to variables will not be considered
* replaceable by the translation functions.
* @param {string} text The text to sanitize.
* @returns {string} The sanitized text.
*/
function sanitizeForTranslation(text) {
// Add a non-breaking space so the regex doesn't trigger when translating.
return text.replace(/%\(([^)]*)\)/g, "%\xa0($1)");
}
/*
* Similar to _t(), except only does substitutions, and no translation
* @param {string} text The text, e.g "click <a>here</a> now to %(foo)s".
* @param {object} variables Variable substitutions, e.g { foo: 'bar' }
* @param {object} tags Tag substitutions e.g. { 'a': (sub) => <a>{sub}</a> }
*
* The values to substitute with can be either simple strings, or functions that return the value to use in
* the substitution (e.g. return a React component). In case of a tag replacement, the function receives as
* the argument the text inside the element corresponding to the tag.
*
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/
function substitute(text, variables, tags) {
let result = text;
if (variables !== undefined) {
const regexpMapping = {};
for (const variable in variables) {
regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
}
result = replaceByRegexes(result, regexpMapping);
}
if (tags !== undefined) {
const regexpMapping = {};
for (const tag in tags) {
regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
}
result = replaceByRegexes(result, regexpMapping);
}
return result;
}
/**
* Replace parts of a text using regular expressions
* @param text - The text on which to perform substitutions
* @param mapping - A mapping from regular expressions in string form to replacement string or a
* function which will receive as the argument the capture groups defined in the regexp. E.g.
* { 'Hello (.?) World': (sub) => sub.toUpperCase() }
*
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/
function replaceByRegexes(text, mapping) {
// We initially store our output as an array of strings and objects (e.g. React components).
// This will then be converted to a string or a <span> at the end
const output = [text];
// If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
let shouldWrapInSpan = false;
for (const regexpString in mapping) {
// TODO: Cache regexps
const regexp = new RegExp(regexpString, "g");
// Loop over what output we have so far and perform replacements
// We look for matches: if we find one, we get three parts: everything before the match, the replaced part,
// and everything after the match. Insert all three into the output. We need to do this because we can insert objects.
// Otherwise there would be no need for the splitting and we could do simple replacement.
let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it
for (let outputIndex = 0; outputIndex < output.length; outputIndex++) {
const inputText = output[outputIndex];
if (typeof inputText !== "string") {
// We might have inserted objects earlier, don't try to replace them
continue;
}
// process every match in the string
// starting with the first
let match = regexp.exec(inputText);
if (!match) continue;
matchFoundSomewhere = true;
// The textual part before the first match
const head = inputText.slice(0, match.index);
const parts = [];
// keep track of prevMatch
let prevMatch;
while (match) {
// store prevMatch
prevMatch = match;
const capturedGroups = match.slice(2);
let replaced;
// If substitution is a function, call it
if (mapping[regexpString] instanceof Function) {
replaced = mapping[regexpString](...capturedGroups);
} else {
replaced = mapping[regexpString];
}
if (typeof replaced === "object") {
shouldWrapInSpan = true;
}
// Here we also need to check that it actually is a string before comparing against one
// The head and tail are always strings
if (typeof replaced !== "string" || replaced !== "") {
parts.push(replaced);
}
// try the next match
match = regexp.exec(inputText);
// add the text between prevMatch and this one
// or the end of the string if prevMatch is the last match
let tail;
if (match) {
const startIndex = prevMatch.index + prevMatch[0].length;
tail = inputText.slice(startIndex, match.index);
} else {
tail = inputText.slice(prevMatch.index + prevMatch[0].length);
}
if (tail) {
parts.push(tail);
}
}
// Insert in reverse order as splice does insert-before and this way we get the final order correct
// remove the old element at the same time
output.splice(outputIndex, 1, ...parts);
if (head !== "") {
// Don't push empty nodes, they are of no use
output.splice(outputIndex, 0, head);
}
}
if (!matchFoundSomewhere) {
if (
// The current regexp did not match anything in the input. Missing
// matches is entirely possible because you might choose to show some
// variables only in the case of e.g. plurals. It's still a bit
// suspicious, and could be due to an error, so log it. However, not
// showing count is so common that it's not worth logging. And other
// commonly unused variables here, if there are any.
regexpString !== "%\\(count\\)s" &&
// Ignore the `locale` option which can be used to override the locale
// in counterpart
regexpString !== "%\\(locale\\)s") {
_logger.logger.log(`Could not find ${regexp} in ${text}`);
}
}
}
if (shouldWrapInSpan) {
return /*#__PURE__*/_react.default.createElement("span", null, ...output);
} else {
return output.join("");
}
}
// Allow overriding the text displayed when no translation exists
// Currently only used in unit tests to avoid having to load
// the translations in element-web
function setMissingEntryGenerator(f) {
_counterpart.default.setMissingEntryGenerator(f);
}
function setLanguage(preferredLangs) {
if (!Array.isArray(preferredLangs)) {
preferredLangs = [preferredLangs];
}
const plaf = _PlatformPeg.default.get();
if (plaf) {
plaf.setLanguage(preferredLangs);
}
let langToUse;
let availLangs;
return getLangsJson().then(result => {
availLangs = result;
for (let i = 0; i < preferredLangs.length; ++i) {
if (availLangs.hasOwnProperty(preferredLangs[i])) {
langToUse = preferredLangs[i];
break;
}
}
if (!langToUse) {
// Fallback to en_EN if none is found
langToUse = "en";
_logger.logger.error("Unable to find an appropriate language");
}
return getLanguageRetry(i18nFolder + availLangs[langToUse]);
}).then(async langData => {
_counterpart.default.registerTranslations(langToUse, langData);
await registerCustomTranslations();
_counterpart.default.setLocale(langToUse);
await _SettingsStore.default.setValue("language", null, _SettingLevel.SettingLevel.DEVICE, langToUse);
// Adds a lot of noise to test runs, so disable logging there.
if (process.env.NODE_ENV !== "test") {
_logger.logger.log("set language to " + langToUse);
}
// Set 'en' as fallback language:
if (langToUse !== "en") {
return getLanguageRetry(i18nFolder + availLangs["en"]);
}
}).then(async langData => {
if (langData) _counterpart.default.registerTranslations("en", langData);
await registerCustomTranslations();
});
}
async function getAllLanguagesFromJson() {
return Object.keys(await getLangsJson());
}
async function getAllLanguagesWithLabels() {
const languageNames = new Intl.DisplayNames([getUserLanguage()], {
type: "language",
style: "short"
});
const languages = await getAllLanguagesFromJson();
return languages.map(langKey => {
return {
value: langKey,
label: languageNames.of(langKey),
labelInTargetLanguage: new Intl.DisplayNames([langKey], {
type: "language",
style: "short"
}).of(langKey)
};
});
}
function getLanguagesFromBrowser() {
if (navigator.languages && navigator.languages.length) return navigator.languages;
if (navigator.language) return [navigator.language];
return [navigator.userLanguage || "en"];
}
function getLanguageFromBrowser() {
return getLanguagesFromBrowser()[0];
}
function getCurrentLanguage() {
return _counterpart.default.getLocale();
}
/**
* Given a list of language codes, pick the most appropriate one
* given the current language (ie. getCurrentLanguage())
* English is assumed to be a reasonable default.
*
* @param {string[]} langs List of language codes to pick from
* @returns {string} The most appropriate language code from langs
*/
function pickBestLanguage(langs) {
const currentLang = getCurrentLanguage();
const normalisedLangs = langs.map(_matrixWebI18n.normalizeLanguageKey);
{
// Best is an exact match
const currentLangIndex = normalisedLangs.indexOf(currentLang);
if (currentLangIndex > -1) return langs[currentLangIndex];
}
{
// Failing that, a different dialect of the same language
const closeLangIndex = normalisedLangs.findIndex(l => l.slice(0, 2) === currentLang.slice(0, 2));
if (closeLangIndex > -1) return langs[closeLangIndex];
}
{
// Neither of those? Try an english variant.
const enIndex = normalisedLangs.findIndex(l => l.startsWith("en"));
if (enIndex > -1) return langs[enIndex];
}
// if nothing else, use the first
return langs[0];
}
async function getLangsJson() {
let url;
if (typeof _languages.default === "string") {
// in Jest this 'url' isn't a URL, so just fall through
url = _languages.default;
} else {
url = i18nFolder + "languages.json";
}
const res = await fetch(url, {
method: "GET"
});
if (!res.ok) {
throw new Error(`Failed to load ${url}, got ${res.status}`);
}
return res.json();
}
async function getLanguageRetry(langPath, num = 3) {
return (0, _promise.retry)(() => getLanguage(langPath), num, e => {
_logger.logger.log("Failed to load i18n", langPath);
_logger.logger.error(e);
return true; // always retry
});
}
async function getLanguage(langPath) {
const res = await fetch(langPath, {
method: "GET"
});
if (!res.ok) {
throw new Error(`Failed to load ${langPath}, got ${res.status}`);
}
return res.json();
}
let cachedCustomTranslations = null;
let cachedCustomTranslationsExpire = 0; // zero to trigger expiration right away
// This awkward class exists so the test runner can get at the function. It is
// not intended for practical or realistic usage.
class CustomTranslationOptions {
constructor() {
// static access for tests only
}
}
exports.CustomTranslationOptions = CustomTranslationOptions;
(0, _defineProperty2.default)(CustomTranslationOptions, "lookupFn", void 0);
function doRegisterTranslations(customTranslations) {
// We convert the operator-friendly version into something counterpart can consume.
// Map: lang → Record: string → translation
const langs = new _utils.MapWithDefault(() => ({}));
for (const [translationKey, translations] of Object.entries(customTranslations)) {
for (const [lang, translation] of Object.entries(translations)) {
_lodash.default.set(langs.getOrCreate(lang), translationKey.split(_matrixWebI18n.KEY_SEPARATOR), translation);
}
}
// Finally, tell counterpart about our translations
for (const [lang, translations] of langs) {
_counterpart.default.registerTranslations(lang, translations);
}
}
/**
* Any custom modules with translations to load are parsed first, followed by an
* optionally defined translations file in the config. If no customization is made,
* or the file can't be parsed, no action will be taken.
*
* This function should be called *after* registering other translations data to
* ensure it overrides strings properly.
*/
async function registerCustomTranslations({
testOnlyIgnoreCustomTranslationsCache = false
} = {}) {
const moduleTranslations = _ModuleRunner.ModuleRunner.instance.allTranslations;
doRegisterTranslations(moduleTranslations);
const lookupUrl = _SdkConfig.default.get().custom_translations_url;
if (!lookupUrl) return; // easy - nothing to do
try {
let json;
if (testOnlyIgnoreCustomTranslationsCache || Date.now() >= cachedCustomTranslationsExpire) {
json = CustomTranslationOptions.lookupFn ? CustomTranslationOptions.lookupFn(lookupUrl) : await (await fetch(lookupUrl)).json();
cachedCustomTranslations = json;
// Set expiration to the future, but not too far. Just trying to avoid
// repeated, successive, calls to the server rather than anything long-term.
cachedCustomTranslationsExpire = Date.now() + 5 * 60 * 1000;
} else {
json = cachedCustomTranslations;
}
// If the (potentially cached) json is invalid, don't use it.
if (!json) return;
// Finally, register it.
doRegisterTranslations(json);
} catch (e) {
// We consume all exceptions because it's considered non-fatal for custom
// translations to break. Most failures will be during initial development
// of the json file and not (hopefully) at runtime.
_logger.logger.warn("Ignoring error while registering custom translations: ", e);
// Like above: trigger a cache of the json to avoid successive calls.
cachedCustomTranslationsExpire = Date.now() + 5 * 60 * 1000;
}
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfY291bnRlcnBhcnQiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwicmVxdWlyZSIsIl9yZWFjdCIsIl9sb2dnZXIiLCJfdXRpbHMiLCJfbWF0cml4V2ViSTE4biIsIl9sb2Rhc2giLCJfU2V0dGluZ3NTdG9yZSIsIl9QbGF0Zm9ybVBlZyIsIl9TZXR0aW5nTGV2ZWwiLCJfcHJvbWlzZSIsIl9TZGtDb25maWciLCJfTW9kdWxlUnVubmVyIiwiX2xhbmd1YWdlcyIsIl9leGNsdWRlZCIsIm93bktleXMiLCJlIiwiciIsInQiLCJPYmplY3QiLCJrZXlzIiwiZ2V0T3duUHJvcGVydHlTeW1ib2xzIiwibyIsImZpbHRlciIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsImVudW1lcmFibGUiLCJwdXNoIiwiYXBwbHkiLCJfb2JqZWN0U3ByZWFkIiwiYXJndW1lbnRzIiwibGVuZ3RoIiwiZm9yRWFjaCIsIl9kZWZpbmVQcm9wZXJ0eTIiLCJkZWZhdWx0IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJkZWZpbmVQcm9wZXJ0eSIsImkxOG5Gb2xkZXIiLCJBTk5PVEFURV9TVFJJTkdTIiwiY291bnRlcnBhcnQiLCJzZXRTZXBhcmF0b3IiLCJLRVlfU0VQQVJBVE9SIiwiRkFMTEJBQ0tfTE9DQUxFIiwic2V0RmFsbGJhY2tMb2NhbGUiLCJVc2VyRnJpZW5kbHlFcnJvciIsIkVycm9yIiwiY29uc3RydWN0b3IiLCJtZXNzYWdlIiwic3Vic3RpdHV0aW9uVmFyaWFibGVzQW5kQ2F1c2UiLCJfcmVmIiwiY2F1c2UiLCJzdWJzdGl0dXRpb25WYXJpYWJsZXMiLCJfb2JqZWN0V2l0aG91dFByb3BlcnRpZXMyIiwiZXJyb3JPcHRpb25zIiwiZW5nbGlzaFRyYW5zbGF0ZWRNZXNzYWdlIiwiX3QiLCJsb2NhbGUiLCJ0cmFuc2xhdGVkTWVzc2FnZSIsImV4cG9ydHMiLCJnZXRVc2VyTGFuZ3VhZ2UiLCJsYW5ndWFnZSIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsIm5vcm1hbGl6ZUxhbmd1YWdlS2V5IiwiZ2V0TGFuZ3VhZ2VGcm9tQnJvd3NlciIsIl90ZCIsInMiLCJ0cmFuc2xhdGVXaXRoRmFsbGJhY2siLCJ0ZXh0Iiwib3B0aW9ucyIsInRyYW5zbGF0ZWQiLCJ0cmFuc2xhdGUiLCJmYWxsYmFja0xvY2FsZSIsImdldExvY2FsZSIsInN0YXJ0c1dpdGgiLCJmYWxsYmFja1RyYW5zbGF0ZWQiLCJwcm9jZXNzIiwiZW52IiwiTk9ERV9FTlYiLCJpc0ZhbGxiYWNrIiwic2FmZUNvdW50ZXJwYXJ0VHJhbnNsYXRlIiwidmFyaWFibGVzIiwiaW50ZXJwb2xhdGUiLCJrIiwidW5kZWZpbmVkIiwibG9nZ2VyIiwid2FybiIsImFubm90YXRlU3RyaW5ncyIsInJlc3VsdCIsInRyYW5zbGF0aW9uS2V5IiwiY3JlYXRlRWxlbWVudCIsImNsYXNzTmFtZSIsInRhZ3MiLCJzdWJzdGl0dXRlZCIsInN1YnN0aXR1dGUiLCJsb29rdXBTdHJpbmciLCJrZXkiLCJfdERvbSIsImxhbmciLCJzYW5pdGl6ZUZvclRyYW5zbGF0aW9uIiwicmVwbGFjZSIsInJlZ2V4cE1hcHBpbmciLCJ2YXJpYWJsZSIsInJlcGxhY2VCeVJlZ2V4ZXMiLCJ0YWciLCJtYXBwaW5nIiwib3V0cHV0Iiwic2hvdWxkV3JhcEluU3BhbiIsInJlZ2V4cFN0cmluZyIsInJlZ2V4cCIsIlJlZ0V4cCIsIm1hdGNoRm91bmRTb21ld2hlcmUiLCJvdXRwdXRJbmRleCIsImlucHV0VGV4dCIsIm1hdGNoIiwiZXhlYyIsImhlYWQiLCJzbGljZSIsImluZGV4IiwicGFydHMiLCJwcmV2TWF0Y2giLCJjYXB0dXJlZEdyb3VwcyIsInJlcGxhY2VkIiwiRnVuY3Rpb24iLCJ0YWlsIiwic3RhcnRJbmRleCIsInNwbGljZSIsImxvZyIsIlJlYWN0Iiwiam9pbiIsInNldE1pc3NpbmdFbnRyeUdlbmVyYXRvciIsImYiLCJzZXRMYW5ndWFnZSIsInByZWZlcnJlZExhbmdzIiwiQXJyYXkiLCJpc0FycmF5IiwicGxhZiIsIlBsYXRmb3JtUGVnIiwiZ2V0IiwibGFuZ1RvVXNlIiwiYXZhaWxMYW5ncyIsImdldExhbmdzSnNvbiIsInRoZW4iLCJpIiwiaGFzT3duUHJvcGVydHkiLCJlcnJvciIsImdldExhbmd1YWdlUmV0cnkiLCJsYW5nRGF0YSIsInJlZ2lzdGVyVHJhbnNsYXRpb25zIiwicmVnaXN0ZXJDdXN0b21UcmFuc2xhdGlvbnMiLCJzZXRMb2NhbGUiLCJzZXRWYWx1ZSIsIlNldHRpbmdMZXZlbCIsIkRFVklDRSIsImdldEFsbExhbmd1YWdlc0Zyb21Kc29uIiwiZ2V0QWxsTGFuZ3VhZ2VzV2l0aExhYmVscyIsImxhbmd1YWdlTmFtZXMiLCJJbnRsIiwiRGlzcGxheU5hbWVzIiwidHlwZSIsInN0eWxlIiwibGFuZ3VhZ2VzIiwibWFwIiwibGFuZ0tleSIsInZhbHVlIiwibGFiZWwiLCJvZiIsImxhYmVsSW5UYXJnZXRMYW5ndWFnZSIsImdldExhbmd1YWdlc0Zyb21Ccm93c2VyIiwibmF2aWdhdG9yIiwidXNlckxhbmd1YWdlIiwiZ2V0Q3VycmVudExhbmd1YWdlIiwicGlja0Jlc3RMYW5ndWFnZSIsImxhbmdzIiwiY3VycmVudExhbmciLCJub3JtYWxpc2VkTGFuZ3MiLCJjdXJyZW50TGFuZ0luZGV4IiwiaW5kZXhPZiIsImNsb3NlTGFuZ0luZGV4IiwiZmluZEluZGV4IiwibCIsImVuSW5kZXgiLCJ1cmwiLCJ3ZWJwYWNrTGFuZ0pzb25VcmwiLCJyZXMiLCJmZXRjaCIsIm1ldGhvZCIsIm9rIiwic3RhdHVzIiwianNvbiIsImxhbmdQYXRoIiwibnVtIiwicmV0cnkiLCJnZXRMYW5ndWFnZSIsImNhY2hlZEN1c3RvbVRyYW5zbGF0aW9ucyIsImNhY2hlZEN1c3RvbVRyYW5zbGF0aW9uc0V4cGlyZSIsIkN1c3RvbVRyYW5zbGF0aW9uT3B0aW9ucyIsImRvUmVnaXN0ZXJUcmFuc2xhdGlvbnMiLCJjdXN0b21UcmFuc2xhdGlvbnMiLCJNYXBXaXRoRGVmYXVsdCIsInRyYW5zbGF0aW9ucyIsImVudHJpZXMiLCJ0cmFuc2xhdGlvbiIsIl8iLCJzZXQiLCJnZXRPckNyZWF0ZSIsInNwbGl0IiwidGVzdE9ubHlJZ25vcmVDdXN0b21UcmFuc2xhdGlvbnNDYWNoZSIsIm1vZHVsZVRyYW5zbGF0aW9ucyIsIk1vZHVsZVJ1bm5lciIsImluc3RhbmNlIiwiYWxsVHJhbnNsYXRpb25zIiwibG9va3VwVXJsIiwiU2RrQ29uZmlnIiwiY3VzdG9tX3RyYW5zbGF0aW9uc191cmwiLCJEYXRlIiwibm93IiwibG9va3VwRm4iXSwic291cmNlcyI6WyIuLi9zcmMvbGFuZ3VhZ2VIYW5kbGVyLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAxOS0yMDIyIFRoZSBNYXRyaXgub3JnIEZvdW5kYXRpb24gQy5JLkMuXG5Db3B5cmlnaHQgMjAxOSBNaWNoYWVsIFRlbGF0eW5za2kgPDd0M2NoZ3V5QGdtYWlsLmNvbT5cbkNvcHlyaWdodCAyMDE3IE1UUk5vcmQgYW5kIENvb3BlcmF0aXZlIEVJVEFcbkNvcHlyaWdodCAyMDE3IFZlY3RvciBDcmVhdGlvbnMgTHRkLlxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG5pbXBvcnQgY291bnRlcnBhcnQgZnJvbSBcImNvdW50ZXJwYXJ0XCI7XG5pbXBvcnQgUmVhY3QgZnJvbSBcInJlYWN0XCI7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbG9nZ2VyXCI7XG5pbXBvcnQgeyBPcHRpb25hbCB9IGZyb20gXCJtYXRyaXgtZXZlbnRzLXNka1wiO1xuaW1wb3J0IHsgTWFwV2l0aERlZmF1bHQgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvdXRpbHNcIjtcbmltcG9ydCB7IG5vcm1hbGl6ZUxhbmd1YWdlS2V5LCBUcmFuc2xhdGlvbktleSBhcyBfVHJhbnNsYXRpb25LZXksIEtFWV9TRVBBUkFUT1IgfSBmcm9tIFwibWF0cml4LXdlYi1pMThuXCI7XG5pbXBvcnQgeyBUcmFuc2xhdGlvblN0cmluZ3NPYmplY3QgfSBmcm9tIFwiQG1hdHJpeC1vcmcvcmVhY3Qtc2RrLW1vZHVsZS1hcGlcIjtcbmltcG9ydCBfIGZyb20gXCJsb2Rhc2hcIjtcblxuaW1wb3J0IHR5cGUgVHJhbnNsYXRpb25zIGZyb20gXCIuL2kxOG4vc3RyaW5ncy9lbl9FTi5qc29uXCI7XG5pbXBvcnQgU2V0dGluZ3NTdG9yZSBmcm9tIFwiLi9zZXR0aW5ncy9TZXR0aW5nc1N0b3JlXCI7XG5pbXBvcnQgUGxhdGZvcm1QZWcgZnJvbSBcIi4vUGxhdGZvcm1QZWdcIjtcbmltcG9ydCB7IFNldHRpbmdMZXZlbCB9IGZyb20gXCIuL3NldHRpbmdzL1NldHRpbmdMZXZlbFwiO1xuaW1wb3J0IHsgcmV0cnkgfSBmcm9tIFwiLi91dGlscy9wcm9taXNlXCI7XG5pbXBvcnQgU2RrQ29uZmlnIGZyb20gXCIuL1Nka0NvbmZpZ1wiO1xuaW1wb3J0IHsgTW9kdWxlUnVubmVyIH0gZnJvbSBcIi4vbW9kdWxlcy9Nb2R1bGVSdW5uZXJcIjtcblxuLy8gQHRzLWlnbm9yZSAtICR3ZWJhcHAgaXMgYSB3ZWJwYWNrIHJlc29sdmUgYWxpYXMgcG9pbnRpbmcgdG8gdGhlIG91dHB1dCBkaXJlY3RvcnksIHNlZSB3ZWJwYWNrIGNvbmZpZ1xuaW1wb3J0IHdlYnBhY2tMYW5nSnNvblVybCBmcm9tIFwiJHdlYmFwcC9pMThuL2xhbmd1YWdlcy5qc29uXCI7XG5cbmV4cG9ydCB7IG5vcm1hbGl6ZUxhbmd1YWdlS2V5LCBnZXROb3JtYWxpemVkTGFuZ3VhZ2VLZXlzIH0gZnJvbSBcIm1hdHJpeC13ZWItaTE4blwiO1xuXG5jb25zdCBpMThuRm9sZGVyID0gXCJpMThuL1wiO1xuXG4vLyBDb250cm9sIHdoZXRoZXIgdG8gYWxzbyByZXR1cm4gb3JpZ2luYWwsIHVudHJhbnNsYXRlZCBzdHJpbmdzXG4vLyBVc2VmdWwgZm9yIGRlYnVnZ2luZyBhbmQgdGVzdGluZ1xuY29uc3QgQU5OT1RBVEVfU1RSSU5HUyA9IGZhbHNlO1xuXG4vLyBXZSB1c2UgZW5nbGlzaCBzdHJpbmdzIGFzIGtleXMsIHNvbWUgb2Ygd2hpY2ggY29udGFpbiBmdWxsIHN0b3BzXG5jb3VudGVycGFydC5zZXRTZXBhcmF0b3IoS0VZX1NFUEFSQVRPUik7XG5cbi8vIHNlZSBgdHJhbnNsYXRlV2l0aEZhbGxiYWNrYCBmb3IgYW4gZXhwbGFuYXRpb24gb2YgZmFsbGJhY2sgaGFuZGxpbmdcbmNvbnN0IEZBTExCQUNLX0xPQ0FMRSA9IFwiZW5cIjtcbmNvdW50ZXJwYXJ0LnNldEZhbGxiYWNrTG9jYWxlKEZBTExCQUNLX0xPQ0FMRSk7XG5cbmV4cG9ydCBpbnRlcmZhY2UgRXJyb3JPcHRpb25zIHtcbiAgICAvLyBCZWNhdXNlIHdlJ3JlIG1peGluZyB0aGUgc3Vic3RpdHV0aW9uIHZhcmlhYmxlcyBhbmQgYGNhdXNlYCBpbnRvIHRoZSBzYW1lIG9iamVjdFxuICAgIC8vIGJlbG93LCB3ZSB3YW50IHRoZW0gdG8gYWx3YXlzIGV4cGxpY2l0bHkgc2F5IHdoZXRoZXIgdGhlcmUgaXMgYW4gdW5kZXJseWluZyBlcnJvclxuICAgIC8vIG9yIG5vdCB0byBhdm9pZCB0eXBvcyBvZiBcImNhdXNlXCIgc2xpcHBpbmcgdGhyb3VnaCB1bm5vdGljZWQuXG4gICAgY2F1c2U6IHVua25vd24gfCB1bmRlZmluZWQ7XG59XG5cbi8qKlxuICogVXNlZCB0byByZXRocm93IGFuIGVycm9yIHdpdGggYSB1c2VyLWZyaWVuZGx5IHRyYW5zbGF0YWJsZSBtZXNzYWdlIHdoaWxlIG1haW50YWluaW5nXG4gKiBhY2Nlc3MgdG8gdGhhdCBvcmlnaW5hbCB1bmRlcmx5aW5nIGVycm9yLiBEb3duc3RyZWFtIGNvbnN1bWVycyBjYW4gZGlzcGxheSB0aGVcbiAqIGB0cmFuc2xhdGVkTWVzc2FnZWAgcHJvcGVydHkgaW4gdGhlIFVJIGFuZCBpbnNwZWN0IHRoZSB1bmRlcmx5aW5nIGVycm9yIHdpdGggdGhlXG4gKiBgY2F1c2VgIHByb3BlcnR5LlxuICpcbiAqIFRoZSBlcnJvciBtZXNzYWdlIHdpbGwgZGlzcGxheSBhcyBFbmdsaXNoIGluIHRoZSBjb25zb2xlIGFuZCBsb2dzIHNvIEVsZW1lbnRcbiAqIGRldmVsb3BlcnMgY2FuIGVhc2lseSB1bmRlcnN0YW5kIHRoZSBlcnJvciBhbmQgZmluZCB0aGUgc291cmNlIGluIHRoZSBjb2RlLiBJdCBhbHNvXG4gKiBoZWxwcyB0b29scyBsaWtlIFNlbnRyeSBkZWR1cGxpY2F0ZSB0aGUgZXJyb3IsIG9yIGp1c3QgZ2VuZXJhbGx5IHNlYXJjaGluZyBpblxuICogcmFnZXNoYWtlcyB0byBmaW5kIGFsbCBpbnN0YW5jZXMgcmVnYXJkbGVzcyBvZiB0aGUgdXNlcnMgbG9jYWxlLlxuICpcbiAqIEBwYXJhbSBtZXNzYWdlIC0gVGhlIHVudHJhbnNsYXRlZCBlcnJvciBtZXNzYWdlIHRleHQsIGUuZyBcIlNvbWV0aGluZyB3ZW50IHdyb25nIHdpdGggJShmb28pc1wiLlxuICogQHBhcmFtIHN1YnN0aXR1dGlvblZhcmlhYmxlc0FuZENhdXNlIC0gVmFyaWFibGUgc3Vic3RpdHV0aW9ucyBmb3IgdGhlIHRyYW5zbGF0aW9uIGFuZFxuICogb3JpZ2luYWwgY2F1c2Ugb2YgdGhlIGVycm9yLiBJZiB0aGVyZSBpcyBubyBjYXVzZSwganVzdCBwYXNzIGB1bmRlZmluZWRgLCBlLmcgeyBmb286XG4gKiAnYmFyJywgY2F1c2U6IGVyciB8fCB1bmRlZmluZWQgfVxuICovXG5leHBvcnQgY2xhc3MgVXNlckZyaWVuZGx5RXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gICAgcHVibGljIHJlYWRvbmx5IHRyYW5zbGF0ZWRNZXNzYWdlOiBzdHJpbmc7XG5cbiAgICBwdWJsaWMgY29uc3RydWN0b3IoXG4gICAgICAgIG1lc3NhZ2U6IFRyYW5zbGF0aW9uS2V5LFxuICAgICAgICBzdWJzdGl0dXRpb25WYXJpYWJsZXNBbmRDYXVzZT86IE9taXQ8SVZhcmlhYmxlcywga2V5b2YgRXJyb3JPcHRpb25zPiB8IEVycm9yT3B0aW9ucyxcbiAgICApIHtcbiAgICAgICAgLy8gUHJldmVudCBcIkNvdWxkIG5vdCBmaW5kIC8lXFwoY2F1c2VcXClzL2cgaW4geFwiIGxvZ3MgdG8gdGhlIGNvbnNvbGUgYnkgcmVtb3ZpbmcgaXQgZnJvbSB0aGUgbGlzdFxuICAgICAgICBjb25zdCB7IGNhdXNlLCAuLi5zdWJzdGl0dXRpb25WYXJpYWJsZXMgfSA9IHN1YnN0aXR1dGlvblZhcmlhYmxlc0FuZENhdXNlID8/IHt9O1xuICAgICAgICBjb25zdCBlcnJvck9wdGlvbnMgPSB7IGNhdXNlIH07XG5cbiAgICAgICAgLy8gQ3JlYXRlIHRoZSBlcnJvciB3aXRoIHRoZSBFbmdsaXNoIHZlcnNpb24gb2YgdGhlIG1lc3NhZ2UgdGhhdCB3ZSB3YW50IHRvIHNob3cgdXAgaW4gdGhlIGxvZ3NcbiAgICAgICAgY29uc3QgZW5nbGlzaFRyYW5zbGF0ZWRNZXNzYWdlID0gX3QobWVzc2FnZSwgeyAuLi5zdWJzdGl0dXRpb25WYXJpYWJsZXMsIGxvY2FsZTogXCJlblwiIH0pO1xuICAgICAgICBzdXBlcihlbmdsaXNoVHJhbnNsYXRlZE1lc3NhZ2UsIGVycm9yT3B0aW9ucyk7XG5cbiAgICAgICAgLy8gQWxzbyBwcm92aWRlIGEgdHJhbnNsYXRlZCB2ZXJzaW9uIG9mIHRoZSBlcnJvciBpbiB0aGUgdXNlcnMgbG9jYWxlIHRvIGRpc3BsYXlcbiAgICAgICAgdGhpcy50cmFuc2xhdGVkTWVzc2FnZSA9IF90KG1lc3NhZ2UsIHN1YnN0aXR1dGlvblZhcmlhYmxlcyk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0VXNlckxhbmd1YWdlKCk6IHN0cmluZyB7XG4gICAgY29uc3QgbGFuZ3VhZ2UgPSBTZXR0aW5nc1N0b3JlLmdldFZhbHVlKFwibGFuZ3VhZ2VcIiwgbnVsbCwgLypleGNsdWRlRGVmYXVsdDoqLyB0cnVlKTtcbiAgICBpZiAodHlwZW9mIGxhbmd1YWdlID09PSBcInN0cmluZ1wiICYmIGxhbmd1YWdlICE9PSBcIlwiKSB7XG4gICAgICAgIHJldHVybiBsYW5ndWFnZTtcbiAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gbm9ybWFsaXplTGFuZ3VhZ2VLZXkoZ2V0TGFuZ3VhZ2VGcm9tQnJvd3NlcigpKTtcbiAgICB9XG59XG5cbi8qKlxuICogQSB0eXBlIHJlcHJlc2VudGluZyB0aGUgdW5pb24gb2YgcG9zc2libGUga2V5cyBpbnRvIHRoZSB0cmFuc2xhdGlvbiBmaWxlIHVzaW5nIGB8YCBkZWxpbWl0ZXIgdG8gYWNjZXNzIG5lc3RlZCBmaWVsZHMuXG4gKiBAZXhhbXBsZSBgY29tbW9ufGVycm9yYCB0byBhY2Nlc3MgYGVycm9yYCB3aXRoaW4gdGhlIGBjb21tb25gIHN1Yi1vYmplY3QuXG4gKiB7XG4gKiAgICAgXCJjb21tb25cIjoge1xuICogICAgICAgICBcImVycm9yXCI6IFwiRXJyb3JcIlxuICogICAgIH1cbiAqIH1cbiAqL1xuZXhwb3J0IHR5cGUgVHJhbnNsYXRpb25LZXkgPSBfVHJhbnNsYXRpb25LZXk8dHlwZW9mIFRyYW5zbGF0aW9ucz47XG5cbi8vIEZ1bmN0aW9uIHdoaWNoIG9ubHkgcHVycG9zZSBpcyB0byBtYXJrIHRoYXQgYSBzdHJpbmcgaXMgdHJhbnNsYXRhYmxlXG4vLyBEb2VzIG5vdCBhY3R1YWxseSBkbyBhbnl0aGluZy4gSXQncyBoZWxwZnVsIGZvciBhdXRvbWF0aWMgZXh0cmFjdGlvbiBvZiB0cmFuc2xhdGFibGUgc3RyaW5nc1xuZXhwb3J0IGZ1bmN0aW9uIF90ZChzOiBUcmFuc2xhdGlvbktleSk6IFRyYW5zbGF0aW9uS2V5IHtcbiAgICByZXR1cm4gcztcbn1cblxuLyoqXG4gKiB0byBpbXByb3ZlIHNjcmVlbiByZWFkZXIgZXhwZXJpZW5jZSB0cmFuc2xhdGlvbnMgdGhhdCBhcmUgbm90IGluIHRoZSBtYWluIHBhZ2UgbGFuZ3VhZ2VcbiAqIGVnIGEgdHJhbnNsYXRpb24gdGhhdCBmZWxsIGJhY2sgdG8gZW5nbGlzaCBmcm9tIGFub3RoZXIgbGFuZ3VhZ2VcbiAqIHNob3VsZCBiZSB3cmFwcGVkIHdpdGggYW4gYXBwcm9wcmlhdGUgYGxhbmc9J2VuJ2AgYXR0cmlidXRlXG4gKiBjb3VudGVycGFydCdzIGB0cmFuc2xhdGVgIGRvZXNuJ3QgZXhwb3NlIGEgd2F5IHRvIGRldGVybWluZSBpZiB0aGUgcmVzdWx0aW5nIHRyYW5zbGF0aW9uXG4gKiBpcyBpbiB0aGUgdGFyZ2V0IGxvY2FsZSBvciBhIGZhbGxiYWNrIGxvY2FsZVxuICogZm9yIHRoaXMgcmVhc29uLCBmb3JjZSBmYWxsYmFja0xvY2FsZSA9PT0gbG9jYWxlIGluIHRoZSBmaXJzdCBjYWxsIHRvIHRyYW5zbGF0ZVxuICogYW5kIGZhbGxiYWNrICdtYW51YWxseScgc28gd2UgY2FuIG1hcmsgZmFsbGJhY2sgc3RyaW5ncyBhcHByb3ByaWF0ZWx5XG4gKiAqL1xuY29uc3QgdHJhbnNsYXRlV2l0aEZhbGxiYWNrID0gKHRleHQ6IHN0cmluZywgb3B0aW9ucz86IElWYXJpYWJsZXMpOiB7IHRyYW5zbGF0ZWQ6IHN0cmluZzsgaXNGYWxsYmFjaz86IGJvb2xlYW4gfSA9PiB7XG4gICAgY29uc3QgdHJhbnNsYXRlZCA9IGNvdW50ZXJwYXJ0LnRyYW5zbGF0ZSh0ZXh0LCB7IC4uLm9wdGlvbnMsIGZhbGxiYWNrTG9jYWxlOiBjb3VudGVycGFydC5nZXRMb2NhbGUoKSB9KTtcbiAgICBpZiAoIXRyYW5zbGF0ZWQgfHwgdHJhbnNsYXRlZC5zdGFydHNXaXRoKFwibWlzc2luZyB0cmFuc2xhdGlvbjpcIikpIHtcbiAgICAgICAgY29uc3QgZmFsbGJhY2tUcmFuc2xhdGVkID0gY291bnRlcnBhcnQudHJhbnNsYXRlKHRleHQsIHsgLi4ub3B0aW9ucywgbG9jYWxlOiBGQUxMQkFDS19MT0NBTEUgfSk7XG4gICAgICAgIGlmIChcbiAgICAgICAgICAgICghZmFsbGJhY2tUcmFuc2xhdGVkIHx8IGZhbGxiYWNrVHJhbnNsYXRlZC5zdGFydHNXaXRoKFwibWlzc2luZyB0cmFuc2xhdGlvbjpcIikpICYmXG4gICAgICAgICAgICBwcm9jZXNzLmVudi5OT0RFX0VOViAhPT0gXCJkZXZlbG9wbWVudFwiXG4gICAgICAgICkge1xuICAgICAgICAgICAgLy8gRXZlbiB0aGUgdHJhbnNsYXRpb24gdmlhIEZBTExCQUNLX0xPQ0FMRSBmYWlsZWQ7IHRoaXMgY2FuIGhhcHBlbiBpZlxuICAgICAgICAgICAgLy9cbiAgICAgICAgICAgIC8vIDEuIFRoZSBzdHJpbmcgaXNuJ3QgaW4gdGhlIHRyYW5zbGF0aW9ucyBkaWN0aW9uYXJ5LCB1c3VhbGx5IGJlY2F1c2UgeW91J3JlIGluIGRldmVsb3BcbiAgICAgICAgICAgIC8vIGFuZCBoYXZlbid0IHJ1biB5YXJuIGkxOG5cbiAgICAgICAgICAgIC8vIDIuIExvYWRpbmcgdGhlIHRyYW5zbGF0aW9uIHJlc291cmNlcyBvdmVyIHRoZSBuZXR3b3JrIGZhaWxlZCwgd2hpY2ggY2FuIGhhcHBlbiBkdWUgdG9cbiAgICAgICAgICAgIC8vIHRvIG5ldHdvcmsgb3IgaWYgdGhlIGNsaWVudCB0cmllZCB0byBsb2FkIGEgdHJhbnNsYXRpb24gdGhhdCdzIGJlZW4gcmVtb3ZlZCBmcm9tIHRoZVxuICAgICAgICAgICAgLy8gc2VydmVyLlxuICAgICAgICAgICAgLy9cbiAgICAgICAgICAgIC8vIEF0IHRoaXMgcG9pbnQsIGl0cyB0aGUgbGVzc2VyIGV2aWwgdG8gc2hvdyB0aGUgdW50cmFuc2xhdGVkIHRleHQsIHdoaWNoXG4gICAgICAgICAgICAvLyB3aWxsIGJlIGluIEVuZ2xpc2gsIHNvIHRoZSB1c2VyIGNhbiBzdGlsbCBtYWtlIG91dCAqc29tZXRoaW5nKiwgcmF0aGVyIHRoYW4gYW4gb3BhcXVlXG4gICAgICAgICAgICAvLyBcIm1pc3NpbmcgdHJhbnNsYXRpb25cIiBlcnJvci5cbiAgICAgICAgICAgIC8vXG4gICAgICAgICAgICAvLyBEb24ndCBkbyB0aGlzIGluIGRldmVsb3Agc28gcGVvcGxlIHJlbWVtYmVyIHRvIHJ1biB5YXJuIGkxOG4uXG4gICAgICAgICAgICByZXR1cm4geyB0cmFuc2xhdGVkOiB0ZXh0LCBpc0ZhbGxiYWNrOiB0cnVlIH07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHsgdHJhbnNsYXRlZDogZmFsbGJhY2tUcmFuc2xhdGVkLCBpc0ZhbGxiYWNrOiB0cnVlIH07XG4gICAgfVxuICAgIHJldHVybiB7IHRyYW5zbGF0ZWQgfTtcbn07XG5cbi8vIFdyYXBwZXIgZm9yIGNvdW50ZXJwYXJ0J3MgdHJhbnNsYXRpb24gZnVuY3Rpb24gc28gdGhhdCBpdCBoYW5kbGVzIG51bGxzIGFuZCB1bmRlZmluZWRzIHByb3Blcmx5XG4vLyBUYWtlcyB0aGUgc2FtZSBhcmd1bWVudHMgYXMgY291bnRlcnBhcnQudHJhbnNsYXRlKClcbmZ1bmN0aW9uIHNhZmVDb3VudGVycGFydFRyYW5zbGF0ZSh0ZXh0OiBzdHJpbmcsIHZhcmlhYmxlcz86IElWYXJpYWJsZXMpOiB7IHRyYW5zbGF0ZWQ6IHN0cmluZzsgaXNGYWxsYmFjaz86IGJvb2xlYW4gfSB7XG4gICAgLy8gRG9uJ3QgZG8gc3Vic3RpdHV0aW9ucyBpbiBjb3VudGVycGFydC4gV2UgaGFuZGxlIGl0IG91cnNlbHZlcyBzbyB3ZSBjYW4gcmVwbGFjZSB3aXRoIFJlYWN0IGNvbXBvbmVudHNcbiAgICAvLyBIb3dldmVyLCBzdGlsbCBwYXNzIHRoZSB2YXJpYWJsZXMgdG8gY291bnRlcnBhcnQgc28gdGhhdCBpdCBjYW4gY2hvb3NlIHRoZSBjb3JyZWN0IHBsdXJhbCBpZiBjb3VudCBpcyBnaXZlblxuICAgIC8vIEl0IGlzIGVub3VnaCB0byBwYXNzIHRoZSBjb3VudCB2YXJpYWJsZSwgYnV0IGluIHRoZSBmdXR1cmUgY291bnRlcnBhcnQgbWlnaHQgbWFrZSB1c2Ugb2Ygb3RoZXIgaW5mb3JtYXRpb24gdG9vXG4gICAgY29uc3Qgb3B0aW9uczogSVZhcmlhYmxlcyAmIHtcbiAgICAgICAgaW50ZXJwb2xhdGU6IGJvb2xlYW47XG4gICAgfSA9IHsgLi4udmFyaWFibGVzLCBpbnRlcnBvbGF0ZTogZmFsc2UgfTtcblxuICAgIC8vIEhvcnJpYmxlIGhhY2sgdG8gYXZvaWQgaHR0cHM6Ly9naXRodWIuY29tL3ZlY3Rvci1pbS9lbGVtZW50LXdlYi9pc3N1ZXMvNDE5MVxuICAgIC8vIFRoZSBpbnRlcnBvbGF0aW9uIGxpYnJhcnkgdGhhdCBjb3VudGVycGFydCB1c2VzIGRvZXMgbm90IHN1cHBvcnQgdW5kZWZpbmVkL251bGxcbiAgICAvLyB2YWx1ZXMgYW5kIGluc3RlYWQgd2lsbCB0aHJvdyBhbiBlcnJvci4gVGhpcyBpcyBhIHByb2JsZW0gc2luY2UgZXZlcnl3aGVyZSBlbHNlXG4gICAgLy8gaW4gSlMgbGFuZCBwYXNzaW5nIHVuZGVmaW5lZC9udWxsIHdpbGwgc2ltcGx5IHN0cmluZ2lmeSBpbnN0ZWFkLCBhbmQgd2hlbiBjb252ZXJ0aW5nXG4gICAgLy8gdmFsaWQgRVM2IHRlbXBsYXRlIHN0cmluZ3MgdG8gaTE4biBzdHJpbmdzIGl0J3MgZXh0cmVtZWx5IGVhc3kgdG8gcGFzcyB1bmRlZmluZWQvbnVsbFxuICAgIC8vIGlmIHRoZXJlIGFyZSBubyBleGlzdGluZyBudWxsIGd1YXJkcy4gVG8gYXZvaWQgdGhpcyBtYWtpbmcgdGhlIGFwcCBjb21wbGV0ZWx5IGlub3BlcmFibGUsXG4gICAgLy8gd2UnbGwgY2hlY2sgYWxsIHRoZSB2YWx1ZXMgZm9yIHVuZGVmaW5lZC9udWxsIGFuZCBzdHJpbmdpZnkgdGhlbSBoZXJlLlxuICAgIGlmIChvcHRpb25zICYmIHR5cGVvZiBvcHRpb25zID09PSBcIm9iamVjdFwiKSB7XG4gICAgICAgIE9iamVjdC5rZXlzKG9wdGlvbnMpLmZvckVhY2goKGspID0+IHtcbiAgICAgICAgICAgIGlmIChvcHRpb25zW2tdID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIud2FybihcInNhZmVDb3VudGVycGFydFRyYW5zbGF0ZSBjYWxsZWQgd2l0aCB1bmRlZmluZWQgaW50ZXJwb2xhdGlvbiBuYW1lOiBcIiArIGspO1xuICAgICAgICAgICAgICAgIG9wdGlvbnNba10gPSBcInVuZGVmaW5lZFwiO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKG9wdGlvbnNba10gPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIud2FybihcInNhZmVDb3VudGVycGFydFRyYW5zbGF0ZSBjYWxsZWQgd2l0aCBudWxsIGludGVycG9sYXRpb24gbmFtZTogXCIgKyBrKTtcbiAgICAgICAgICAgICAgICBvcHRpb25zW2tdID0gXCJudWxsXCI7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICByZXR1cm4gdHJhbnNsYXRlV2l0aEZhbGxiYWNrKHRleHQsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFRoZSB2YWx1ZSBhIHZhcmlhYmxlIG9yIHRhZyBjYW4gdGFrZSBmb3IgYSB0cmFuc2xhdGlvbiBpbnRlcnBvbGF0aW9uLlxuICovXG50eXBlIFN1YnN0aXR1dGlvblZhbHVlID0gbnVtYmVyIHwgc3RyaW5nIHwgUmVhY3QuUmVhY3ROb2RlIHwgKChzdWI6IHN0cmluZykgPT4gUmVhY3QuUmVhY3ROb2RlKTtcblxuZXhwb3J0IGludGVyZmFjZSBJVmFyaWFibGVzIHtcbiAgICBjb3VudD86IG51bWJlcjtcbiAgICBba2V5OiBzdHJpbmddOiBTdWJzdGl0dXRpb25WYWx1ZTtcbn1cblxuZXhwb3J0IHR5cGUgVGFncyA9IFJlY29yZDxzdHJpbmcsIFN1YnN0aXR1dGlvblZhbHVlPjtcblxuZXhwb3J0IHR5cGUgVHJhbnNsYXRlZFN0cmluZyA9IHN0cmluZyB8IFJlYWN0LlJlYWN0Tm9kZTtcblxuLy8gRm9yIGRldmVsb3BtZW50L3Rlc3RpbmcgcHVycG9zZXMgaXQgaXMgdXNlZnVsIHRvIGFsc28gb3V0cHV0IHRoZSBvcmlnaW5hbCBzdHJpbmdcbi8vIERvbid0IGRvIHRoYXQgZm9yIHJlbGVhc2UgdmVyc2lvbnNcbmNvbnN0IGFubm90YXRlU3RyaW5ncyA9IChyZXN1bHQ6IFRyYW5zbGF0ZWRTdHJpbmcsIHRyYW5zbGF0aW9uS2V5OiBUcmFuc2xhdGlvbktleSk6IFRyYW5zbGF0ZWRTdHJpbmcgPT4ge1xuICAgIGlmICghQU5OT1RBVEVfU1RSSU5HUykge1xuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH1cblxuICAgIGlmICh0eXBlb2YgcmVzdWx0ID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIHJldHVybiBgQEAke3RyYW5zbGF0aW9uS2V5fSMjJHtyZXN1bHR9QEBgO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiAoXG4gICAgICAgICAgICA8c3BhbiBjbGFzc05hbWU9XCJ0cmFuc2xhdGVkLXN0cmluZ1wiIGRhdGEtb3JpZy1zdHJpbmc9e3RyYW5zbGF0aW9uS2V5fT5cbiAgICAgICAgICAgICAgICB7cmVzdWx0fVxuICAgICAgICAgICAgPC9zcGFuPlxuICAgICAgICApO1xuICAgIH1cbn07XG5cbi8qXG4gKiBUcmFuc2xhdGVzIHRleHQgYW5kIG9wdGlvbmFsbHkgYWxzbyByZXBsYWNlcyBYTUwtaXNoIGVsZW1lbnRzIGluIHRoZSB0ZXh0IHdpdGggZS5nLiBSZWFjdCBjb21wb25lbnRzXG4gKiBAcGFyYW0ge3N0cmluZ30gdGV4dCBUaGUgdW50cmFuc2xhdGVkIHRleHQsIGUuZyBcImNsaWNrIDxhPmhlcmU8L2E+IG5vdyB0byAlKGZvbylzXCIuXG4gKiBAcGFyYW0ge29iamVjdH0gdmFyaWFibGVzIFZhcmlhYmxlIHN1YnN0aXR1dGlvbnMsIGUuZyB7IGZvbzogJ2JhcicgfVxuICogQHBhcmFtIHtvYmplY3R9IHRhZ3MgVGFnIHN1YnN0aXR1dGlvbnMgZS5nLiB7ICdhJzogKHN1YikgPT4gPGE+e3N1Yn08L2E+IH1cbiAqXG4gKiBJbiBib3RoIHZhcmlhYmxlcyBhbmQgdGFncywgdGhlIHZhbHVlcyB0byBzdWJzdGl0dXRlIHdpdGggY2FuIGJlIGVpdGhlciBzaW1wbGUgc3RyaW5ncywgUmVhY3QgY29tcG9uZW50cyxcbiAqIG9yIGZ1bmN0aW9ucyB0aGF0IHJldHVybiB0aGUgdmFsdWUgdG8gdXNlIGluIHRoZSBzdWJzdGl0dXRpb24gKGUuZy4gcmV0dXJuIGEgUmVhY3QgY29tcG9uZW50KS4gSW4gY2FzZSBvZlxuICogYSB0YWcgcmVwbGFjZW1lbnQsIHRoZSBmdW5jdGlvbiByZWNlaXZlcyBhcyB0aGUgYXJndW1lbnQgdGhlIHRleHQgaW5zaWRlIHRoZSBlbGVtZW50IGNvcnJlc3BvbmRpbmcgdG8gdGhlIHRhZy5cbiAqXG4gKiBVc2UgdGFnIHN1YnN0aXR1dGlvbnMgaWYgeW91IG5lZWQgdG8gdHJhbnNsYXRlIHRleHQgYmV0d2VlbiB0YWdzIChlLmcuIFwiPGE+Q2xpY2sgaGVyZSE8L2E+XCIpLCBvdGhlcndpc2VcbiAqIHlvdSB3aWxsIGVuZCB1cCB3aXRoIGxpdGVyYWwgXCI8YT5cIiBpbiB5b3VyIG91dHB1dCwgcmF0aGVyIHRoYW4gSFRNTC4gTm90ZSB0aGF0IHlvdSBjYW4gYWxzbyB1c2UgdmFyaWFibGVcbiAqIHN1YnN0aXR1dGlvbiB0byBpbnNlcnQgUmVhY3QgY29tcG9uZW50cywgYnV0IHlvdSBjYW4ndCB1c2UgaXQgdG8gdHJhbnNsYXRlIHRleHQgYmV0d2VlbiB0YWdzLlxuICpcbiAqIEByZXR1cm4gYSBSZWFjdCA8c3Bhbj4gY29tcG9uZW50IGlmIGFueSBub24tc3RyaW5ncyB3ZXJlIHVzZWQgaW4gc3Vic3RpdHV0aW9ucywgb3RoZXJ3aXNlIGEgc3RyaW5nXG4gKi9cbi8vIGVzbGludC1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25hbWluZy1jb252ZW50aW9uXG5leHBvcnQgZnVuY3Rpb24gX3QodGV4dDogVHJhbnNsYXRpb25LZXksIHZhcmlhYmxlcz86IElWYXJpYWJsZXMpOiBzdHJpbmc7XG5leHBvcnQgZnVuY3Rpb24gX3QodGV4dDogVHJhbnNsYXRpb25LZXksIHZhcmlhYmxlczogSVZhcmlhYmxlcyB8IHVuZGVmaW5lZCwgdGFnczogVGFncyk6IFJlYWN0LlJlYWN0Tm9kZTtcbmV4cG9ydCBmdW5jdGlvbiBfdCh0ZXh0OiBUcmFuc2xhdGlvbktleSwgdmFyaWFibGVzPzogSVZhcmlhYmxlcywgdGFncz86IFRhZ3MpOiBUcmFuc2xhdGVkU3RyaW5nIHtcbiAgICAvLyBUaGUgdHJhbnNsYXRpb24gcmV0dXJucyB0ZXh0IHNvIHRoZXJlJ3Mgbm8gWFNTIHZlY3RvciBoZXJlIChubyB1bnNhZmUgSFRNTCwgbm8gY29kZSBleGVjdXRpb24pXG4gICAgY29uc3QgeyB0cmFuc2xhdGVkIH0gPSBzYWZlQ291bnRlcnBhcnRUcmFuc2xhdGUodGV4dCwgdmFyaWFibGVzKTtcbiAgICBjb25zdCBzdWJzdGl0dXRlZCA9IHN1YnN0aXR1dGUodHJhbnNsYXRlZCwgdmFyaWFibGVzLCB0YWdzKTtcblxuICAgIHJldHVybiBhbm5vdGF0ZVN0cmluZ3Moc3Vic3RpdHV0ZWQsIHRleHQpO1xufVxuXG4vKipcbiAqIFV0aWxpdHkgZnVuY3Rpb24gdG8gbG9vayB1cCBhIHN0cmluZyBieSBpdHMgdHJhbnNsYXRpb24ga2V5IHdpdGhvdXQgcmVzb2x2aW5nIHZhcmlhYmxlcyAmIHRhZ3NcbiAqIEBwYXJhbSBrZXkgLSB0aGUgdHJhbnNsYXRpb24ga2V5IHRvIHJldHVybiB0aGUgdmFsdWUgZm9yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBsb29rdXBTdHJpbmcoa2V5OiBUcmFuc2xhdGlvbktleSk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHNhZmVDb3VudGVycGFydFRyYW5zbGF0ZShrZXksIHt9KS50cmFuc2xhdGVkO1xufVxuXG4vKlxuICogV3JhcHMgbm9ybWFsIF90IGZ1bmN0aW9uIGFuZCBhZGRzIGF0dHRyaWJ1dGlvbiBmb3IgdHJhbnNsYXRpb25zIHRoYXQgdXNlZCBhIGZhbGxiYWNrIGxvY2FsZVxuICogV3JhcHMgdHJhbnNsYXRpb25zIHRoYXQgZmVsbCBiYWNrIGZyb20gYWN0aXZlIGxvY2FsZSB0byBmYWxsYmFjayBsb2NhbGUgd2l0aCBhIGA8c3BhbiBsYW5nPTxmYWxsYmFjayBsb2NhbGU+PmBcbiAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0IFRoZSB1bnRyYW5zbGF0ZWQgdGV4dCwgZS5nIFwiY2xpY2sgPGE+aGVyZTwvYT4gbm93IHRvICUoZm9vKXNcIi5cbiAqIEBwYXJhbSB7b2JqZWN0fSB2YXJpYWJsZXMgVmFyaWFibGUgc3Vic3RpdHV0aW9ucywgZS5nIHsgZm9vOiAnYmFyJyB9XG4gKiBAcGFyYW0ge29iamVjdH0gdGFncyBUYWcgc3Vic3RpdHV0aW9ucyBlLmcuIHsgJ2EnOiAoc3ViKSA9PiA8YT57c3VifTwvYT4gfVxuICpcbiAqIEByZXR1cm4gYSBSZWFjdCA8c3Bhbj4gY29tcG9uZW50IGlmIGFueSBub24tc3RyaW5ncyB3ZXJlIHVzZWQgaW4gc3Vic3RpdHV0aW9uc1xuICogb3IgdHJhbnNsYXRpb24gdXNlZCBhIGZhbGxiYWNrIGxvY2FsZSwgb3RoZXJ3aXNlIGEgc3RyaW5nXG4gKi9cbi8vIGVzbGludC1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25hbWluZy1jb252ZW50aW9uXG5leHBvcnQgZnVuY3Rpb24gX3REb20odGV4dDogVHJhbnNsYXRpb25LZXksIHZhcmlhYmxlcz86IElWYXJpYWJsZXMpOiBUcmFuc2xhdGVkU3RyaW5nO1xuZXhwb3J0IGZ1bmN0aW9uIF90RG9tKHRleHQ6IFRyYW5zbGF0aW9uS2V5LCB2YXJpYWJsZXM6IElWYXJpYWJsZXMsIHRhZ3M6IFRhZ3MpOiBSZWFjdC5SZWFjdE5vZGU7XG5leHBvcnQgZnVuY3Rpb24gX3REb20odGV4dDogVHJhbnNsYXRpb25LZXksIHZhcmlhYmxlcz86IElWYXJpYWJsZXMsIHRhZ3M/OiBUYWdzKTogVHJhbnNsYXRlZFN0cmluZyB7XG4gICAgLy8gVGhlIHRyYW5zbGF0aW9uIHJldHVybnMgdGV4dCBzbyB0aGVyZSdzIG5vIFhTUyB2ZWN0b3IgaGVyZSAobm8gdW5zYWZlIEhUTUwsIG5vIGNvZGUgZXhlY3V0aW9uKVxuICAgIGNvbnN0IHsgdHJhbnNsYXRlZCwgaXNGYWxsYmFjayB9ID0gc2FmZUNvdW50ZXJwYXJ0VHJhbnNsYXRlKHRleHQsIHZhcmlhYmxlcyk7XG4gICAgY29uc3Qgc3Vic3RpdHV0ZWQgPSBzdWJzdGl0dXRlKHRyYW5zbGF0ZWQsIHZhcmlhYmxlcywgdGFncyk7XG5cbiAgICAvLyB3cmFwIGVuIGZhbGxiYWNrIHRyYW5zbGF0aW9uIHdpdGggbGFuZyBhdHRyaWJ1dGUgZm9yIHNjcmVlbiByZWFkZXJzXG4gICAgY29uc3QgcmVzdWx0ID0gaXNGYWxsYmFjayA/IDxzcGFuIGxhbmc9XCJlblwiPntzdWJzdGl0dXRlZH08L3NwYW4+IDogc3Vic3RpdHV0ZWQ7XG5cbiAgICByZXR1cm4gYW5ub3RhdGVTdHJpbmdzKHJlc3VsdCwgdGV4dCk7XG59XG5cbi8qKlxuICogU2FuaXRpemVzIHVuc2FmZSB0ZXh0