UNPKG

matrix-react-sdk

Version:
674 lines (629 loc) 92.1 kB
"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