matrix-react-sdk
Version:
SDK for matrix.org using React
358 lines (325 loc) • 50.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findSuggestionInText = findSuggestionInText;
exports.getMappedSuggestion = getMappedSuggestion;
exports.processCommand = processCommand;
exports.processEmojiReplacement = processEmojiReplacement;
exports.processMention = processMention;
exports.processSelectionChange = processSelectionChange;
exports.useSuggestion = useSuggestion;
var _emojibaseBindings = require("@matrix-org/emojibase-bindings");
var _react = require("react");
var _logger = require("matrix-js-sdk/src/logger");
var _Typeguards = require("../../../../../Typeguards");
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
/**
* Information about the current state of the `useSuggestion` hook.
*/
/**
* React hook to allow tracking and replacing of mentions and commands in a div element
*
* @param editorRef - a ref to the div that is the composer textbox
* @param setText - setter function to set the content of the composer
* @param isAutoReplaceEmojiEnabled - whether plain text emoticons should be auto replaced with emojis
* @returns
* - `handleMention`: a function that will insert @ or # mentions which are selected from
* the autocomplete into the composer, given an href, the text to display, and any additional attributes
* - `handleCommand`: a function that will replace the content of the composer with the given replacement text.
* Can be used to process autocomplete of slash commands
* - `onSelect`: a selection change listener to be attached to the plain text composer
* - `suggestion`: if the cursor is inside something that could be interpreted as a command or a mention,
* this will be an object representing that command or mention, otherwise it is null
*/
function useSuggestion(editorRef, setText, isAutoReplaceEmojiEnabled) {
const [suggestionData, setSuggestionData0] = (0, _react.useState)(null);
// debug for https://github.com/vector-im/element-web/issues/26037
const setSuggestionData = suggestionData => {
// setState allows either the data itself or a callback which returns the data
_logger.logger.log(`## 26037 ## wysiwyg useSuggestion hook setting suggestion data to ${suggestionData === null || suggestionData instanceof Function ? suggestionData : suggestionData.mappedSuggestion.keyChar + suggestionData.mappedSuggestion.text}`);
setSuggestionData0(suggestionData);
};
// We create a `selectionchange` handler here because we need to know when the user has moved the cursor,
// we can not depend on input events only
const onSelect = () => processSelectionChange(editorRef, setSuggestionData, isAutoReplaceEmojiEnabled);
const handleMention = (href, displayName, attributes) => processMention(href, displayName, attributes, suggestionData, setSuggestionData, setText);
const handleAtRoomMention = attributes => processMention("#", "@room", attributes, suggestionData, setSuggestionData, setText);
const handleCommand = replacementText => processCommand(replacementText, suggestionData, setSuggestionData, setText);
const handleEmojiReplacement = () => processEmojiReplacement(suggestionData, setSuggestionData, setText);
return {
suggestion: suggestionData?.mappedSuggestion ?? null,
handleCommand,
handleMention,
handleAtRoomMention,
handleEmojiReplacement,
onSelect
};
}
/**
* When the selection changes inside the current editor, check to see if the cursor is inside
* something that could be a command or a mention and update the suggestion state if so
*
* @param editorRef - ref to the composer
* @param setSuggestionData - the setter for the suggestion state
* @param isAutoReplaceEmojiEnabled - whether plain text emoticons should be auto replaced with emojis
*/
function processSelectionChange(editorRef, setSuggestionData, isAutoReplaceEmojiEnabled) {
const selection = document.getSelection();
// return early if we do not have a current editor ref with a cursor selection inside a text node
if (editorRef.current === null || selection === null || !selection.isCollapsed || selection.anchorNode?.nodeName !== "#text") {
setSuggestionData(null);
return;
}
// from here onwards we have a cursor inside a text node
const {
anchorNode: currentNode,
anchorOffset: currentOffset
} = selection;
// if we have no text content, return, clearing the suggestion state
if (currentNode.textContent === null) {
setSuggestionData(null);
return;
}
const firstTextNode = document.createNodeIterator(editorRef.current, NodeFilter.SHOW_TEXT).nextNode();
const isFirstTextNode = currentNode === firstTextNode;
const foundSuggestion = findSuggestionInText(currentNode.textContent, currentOffset, isFirstTextNode, isAutoReplaceEmojiEnabled);
// if we have not found a suggestion, return, clearing the suggestion state
if (foundSuggestion === null) {
setSuggestionData(null);
return;
}
setSuggestionData({
mappedSuggestion: foundSuggestion.mappedSuggestion,
node: currentNode,
startOffset: foundSuggestion.startOffset,
endOffset: foundSuggestion.endOffset
});
}
/**
* Replaces the relevant part of the editor text with a link representing a mention after it
* is selected from the autocomplete.
*
* @param href - the href that the inserted link will use
* @param displayName - the text content of the link
* @param attributes - additional attributes to add to the link, can include data-* attributes
* @param suggestionData - representation of the part of the DOM that will be replaced
* @param setSuggestionData - setter function to set the suggestion state
* @param setText - setter function to set the content of the composer
*/
function processMention(href, displayName, attributes,
// these will be used when formatting the link as a pill
suggestionData, setSuggestionData, setText) {
// if we do not have a suggestion, return early
if (suggestionData === null) {
return;
}
const {
node
} = suggestionData;
// create an <a> element with the required attributes to allow us to interpret the mention as being a pill
const linkElement = document.createElement("a");
const linkTextNode = document.createTextNode(displayName);
linkElement.setAttribute("href", href);
linkElement.setAttribute("contenteditable", "false");
for (const [attr, value] of attributes.entries()) {
linkElement.setAttribute(attr, value);
}
linkElement.appendChild(linkTextNode);
// create text nodes to go before and after the link
const leadingTextNode = document.createTextNode(node.textContent?.slice(0, suggestionData.startOffset) || "\u200b");
const trailingTextNode = document.createTextNode(` ${node.textContent?.slice(suggestionData.endOffset) ?? ""}`);
// now add the leading text node, link element and trailing text node before removing the node we are replacing
const parentNode = node.parentNode;
if ((0, _Typeguards.isNotNull)(parentNode)) {
parentNode.insertBefore(leadingTextNode, node);
parentNode.insertBefore(linkElement, node);
parentNode.insertBefore(trailingTextNode, node);
parentNode.removeChild(node);
}
// move the selection to the trailing text node
document.getSelection()?.setBaseAndExtent(trailingTextNode, 1, trailingTextNode, 1);
// set the text content to be the innerHTML of the current editor ref and clear the suggestion state
setText();
setSuggestionData(null);
}
/**
* Replaces the relevant part of the editor text with the replacement text after a command is selected
* from the autocomplete.
*
* @param replacementText - the text that we will insert into the DOM
* @param suggestionData - representation of the part of the DOM that will be replaced
* @param setSuggestionData - setter function to set the suggestion state
* @param setText - setter function to set the content of the composer
*/
function processCommand(replacementText, suggestionData, setSuggestionData, setText) {
// if we do not have a suggestion, return early
if (suggestionData === null) {
return;
}
const {
node
} = suggestionData;
// for a command, we know we start at the beginning of the text node, so build the replacement
// string (note trailing space) and manually adjust the node's textcontent
const newContent = `${replacementText} `;
node.textContent = newContent;
// then set the cursor to the end of the node, update the `content` state in the usePlainTextListeners
// hook and clear the suggestion from state
document.getSelection()?.setBaseAndExtent(node, newContent.length, node, newContent.length);
setText(newContent);
setSuggestionData(null);
}
/**
* Replaces the relevant part of the editor text, replacing the plain text emoitcon with the suggested emoji.
*
* @param suggestionData - representation of the part of the DOM that will be replaced
* @param setSuggestionData - setter function to set the suggestion state
* @param setText - setter function to set the content of the composer
*/
function processEmojiReplacement(suggestionData, setSuggestionData, setText) {
// if we do not have a suggestion of the correct type, return early
if (suggestionData === null || suggestionData.mappedSuggestion.type !== `custom`) {
return;
}
const {
node,
mappedSuggestion
} = suggestionData;
const existingContent = node.textContent;
if (existingContent == null) {
return;
}
// replace the emoticon with the suggesed emoji
const newContent = existingContent.slice(0, suggestionData.startOffset) + mappedSuggestion.text + existingContent.slice(suggestionData.endOffset);
node.textContent = newContent;
document.getSelection()?.setBaseAndExtent(node, newContent.length, node, newContent.length);
setText(newContent);
setSuggestionData(null);
}
/**
* Given some text content from a node and the cursor position, find the word that the cursor is currently inside
* and then test that word to see if it is a suggestion. Return the `MappedSuggestion` with start and end offsets if
* the cursor is inside a valid suggestion, null otherwise.
*
* @param text - the text content of a node
* @param offset - the current cursor offset position within the node
* @param isFirstTextNode - whether or not the node is the first text node in the editor. Used to determine
* if a command suggestion is found or not
* @param isAutoReplaceEmojiEnabled - whether plain text emoticons should be auto replaced with emojis
* @returns the `MappedSuggestion` along with its start and end offsets if found, otherwise null
*/
function findSuggestionInText(text, offset, isFirstTextNode, isAutoReplaceEmojiEnabled) {
// Return null early if the offset is outside the content
if (offset < 0 || offset > text.length) {
return null;
}
// Variables to keep track of the indices we will be slicing from and to in order to create
// a substring of the word that the cursor is currently inside
let startSliceIndex = offset;
let endSliceIndex = offset;
// Search backwards from the current cursor position to find the start index of the word
// containing the cursor
while (shouldDecrementStartIndex(text, startSliceIndex)) {
startSliceIndex--;
}
// Search forwards from the current cursor position to find the end index of the word
// containing the cursor
while (shouldIncrementEndIndex(text, endSliceIndex)) {
endSliceIndex++;
}
// Get the word at the cursor then check if it contains a suggestion or not
const wordAtCursor = text.slice(startSliceIndex, endSliceIndex);
const mappedSuggestion = getMappedSuggestion(wordAtCursor, isAutoReplaceEmojiEnabled);
/**
* If we have a word that could be a command, it is not a valid command if:
* - the node we're looking at isn't the first text node in the editor (adding paragraphs can
* result in nested <p> tags inside the editor <div>)
* - the starting index is anything other than 0 (they can only appear at the start of a message)
* - there is more text following the command (eg `/spo asdf|` should not be interpreted as
* something requiring autocomplete)
*/
if (mappedSuggestion === null || mappedSuggestion.type === "command" && (!isFirstTextNode || startSliceIndex !== 0 || endSliceIndex !== text.length)) {
return null;
}
return {
mappedSuggestion,
startOffset: startSliceIndex,
endOffset: startSliceIndex + wordAtCursor.length
};
}
/**
* Associated function for findSuggestionInText. Checks the character at the preceding index
* to determine if the search loop should continue.
*
* @param text - text content to check for mentions or commands
* @param index - the current index to check
* @returns true if check should keep moving backwards, false otherwise
*/
function shouldDecrementStartIndex(text, index) {
// If the index is at or outside the beginning of the string, return false
if (index <= 0) return false;
// We are inside the string so can guarantee that there is a preceding character
// Keep searching backwards if the preceding character is not a space
return !/\s/.test(text[index - 1]);
}
/**
* Associated function for findSuggestionInText. Checks the character at the current index
* to determine if the search loop should continue.
*
* @param text - text content to check for mentions or commands
* @param index - the current index to check
* @returns true if check should keep moving forwards, false otherwise
*/
function shouldIncrementEndIndex(text, index) {
// If the index is at or outside the end of the string, return false
if (index >= text.length) return false;
// Keep searching forwards if the current character is not a space
return !/\s/.test(text[index]);
}
/**
* Given a string, return a `MappedSuggestion` if the string contains a suggestion. Otherwise return null.
*
* @param text - string to check for a suggestion
* @param isAutoReplaceEmojiEnabled - whether plain text emoticons should be auto replaced with emojis
* @returns a `MappedSuggestion` if a suggestion is present, null otherwise
*/
function getMappedSuggestion(text, isAutoReplaceEmojiEnabled) {
if (isAutoReplaceEmojiEnabled) {
// variations of plaintext emoitcons(E.g. :P vs :p vs :-P) are handled upstream by the emojibase-bindings/emojibase libraries.
// See rules for variations here https://github.com/milesj/emojibase/blob/master/packages/core/src/generateEmoticonPermutations.ts#L3-L32
const emoji = _emojibaseBindings.EMOTICON_TO_EMOJI.get(text);
if (emoji?.unicode) {
return {
keyChar: "",
text: emoji.unicode,
type: "custom"
};
}
}
const firstChar = text.charAt(0);
const restOfString = text.slice(1);
switch (firstChar) {
case "/":
return {
keyChar: firstChar,
text: restOfString,
type: "command"
};
case "#":
case "@":
return {
keyChar: firstChar,
text: restOfString,
type: "mention"
};
default:
return null;
}
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfZW1vamliYXNlQmluZGluZ3MiLCJyZXF1aXJlIiwiX3JlYWN0IiwiX2xvZ2dlciIsIl9UeXBlZ3VhcmRzIiwidXNlU3VnZ2VzdGlvbiIsImVkaXRvclJlZiIsInNldFRleHQiLCJpc0F1dG9SZXBsYWNlRW1vamlFbmFibGVkIiwic3VnZ2VzdGlvbkRhdGEiLCJzZXRTdWdnZXN0aW9uRGF0YTAiLCJ1c2VTdGF0ZSIsInNldFN1Z2dlc3Rpb25EYXRhIiwibG9nZ2VyIiwibG9nIiwiRnVuY3Rpb24iLCJtYXBwZWRTdWdnZXN0aW9uIiwia2V5Q2hhciIsInRleHQiLCJvblNlbGVjdCIsInByb2Nlc3NTZWxlY3Rpb25DaGFuZ2UiLCJoYW5kbGVNZW50aW9uIiwiaHJlZiIsImRpc3BsYXlOYW1lIiwiYXR0cmlidXRlcyIsInByb2Nlc3NNZW50aW9uIiwiaGFuZGxlQXRSb29tTWVudGlvbiIsImhhbmRsZUNvbW1hbmQiLCJyZXBsYWNlbWVudFRleHQiLCJwcm9jZXNzQ29tbWFuZCIsImhhbmRsZUVtb2ppUmVwbGFjZW1lbnQiLCJwcm9jZXNzRW1vamlSZXBsYWNlbWVudCIsInN1Z2dlc3Rpb24iLCJzZWxlY3Rpb24iLCJkb2N1bWVudCIsImdldFNlbGVjdGlvbiIsImN1cnJlbnQiLCJpc0NvbGxhcHNlZCIsImFuY2hvck5vZGUiLCJub2RlTmFtZSIsImN1cnJlbnROb2RlIiwiYW5jaG9yT2Zmc2V0IiwiY3VycmVudE9mZnNldCIsInRleHRDb250ZW50IiwiZmlyc3RUZXh0Tm9kZSIsImNyZWF0ZU5vZGVJdGVyYXRvciIsIk5vZGVGaWx0ZXIiLCJTSE9XX1RFWFQiLCJuZXh0Tm9kZSIsImlzRmlyc3RUZXh0Tm9kZSIsImZvdW5kU3VnZ2VzdGlvbiIsImZpbmRTdWdnZXN0aW9uSW5UZXh0Iiwibm9kZSIsInN0YXJ0T2Zmc2V0IiwiZW5kT2Zmc2V0IiwibGlua0VsZW1lbnQiLCJjcmVhdGVFbGVtZW50IiwibGlua1RleHROb2RlIiwiY3JlYXRlVGV4dE5vZGUiLCJzZXRBdHRyaWJ1dGUiLCJhdHRyIiwidmFsdWUiLCJlbnRyaWVzIiwiYXBwZW5kQ2hpbGQiLCJsZWFkaW5nVGV4dE5vZGUiLCJzbGljZSIsInRyYWlsaW5nVGV4dE5vZGUiLCJwYXJlbnROb2RlIiwiaXNOb3ROdWxsIiwiaW5zZXJ0QmVmb3JlIiwicmVtb3ZlQ2hpbGQiLCJzZXRCYXNlQW5kRXh0ZW50IiwibmV3Q29udGVudCIsImxlbmd0aCIsInR5cGUiLCJleGlzdGluZ0NvbnRlbnQiLCJvZmZzZXQiLCJzdGFydFNsaWNlSW5kZXgiLCJlbmRTbGljZUluZGV4Iiwic2hvdWxkRGVjcmVtZW50U3RhcnRJbmRleCIsInNob3VsZEluY3JlbWVudEVuZEluZGV4Iiwid29yZEF0Q3Vyc29yIiwiZ2V0TWFwcGVkU3VnZ2VzdGlvbiIsImluZGV4IiwidGVzdCIsImVtb2ppIiwiRU1PVElDT05fVE9fRU1PSkkiLCJnZXQiLCJ1bmljb2RlIiwiZmlyc3RDaGFyIiwiY2hhckF0IiwicmVzdE9mU3RyaW5nIl0sInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvdmlld3Mvcm9vbXMvd3lzaXd5Z19jb21wb3Nlci9ob29rcy91c2VTdWdnZXN0aW9uLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qXG5Db3B5cmlnaHQgMjAyNCBOZXcgVmVjdG9yIEx0ZC5cbkNvcHlyaWdodCAyMDIzIFRoZSBNYXRyaXgub3JnIEZvdW5kYXRpb24gQy5JLkMuXG5cblNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBR1BMLTMuMC1vbmx5IE9SIEdQTC0zLjAtb25seVxuUGxlYXNlIHNlZSBMSUNFTlNFIGZpbGVzIGluIHRoZSByZXBvc2l0b3J5IHJvb3QgZm9yIGZ1bGwgZGV0YWlscy5cbiovXG5cbmltcG9ydCB7IEVNT1RJQ09OX1RPX0VNT0pJIH0gZnJvbSBcIkBtYXRyaXgtb3JnL2Vtb2ppYmFzZS1iaW5kaW5nc1wiO1xuaW1wb3J0IHsgQWxsb3dlZE1lbnRpb25BdHRyaWJ1dGVzLCBNYXBwZWRTdWdnZXN0aW9uIH0gZnJvbSBcIkB2ZWN0b3ItaW0vbWF0cml4LXd5c2l3eWdcIjtcbmltcG9ydCB7IFN5bnRoZXRpY0V2ZW50LCB1c2VTdGF0ZSwgU2V0U3RhdGVBY3Rpb24gfSBmcm9tIFwicmVhY3RcIjtcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9sb2dnZXJcIjtcblxuaW1wb3J0IHsgaXNOb3ROdWxsIH0gZnJvbSBcIi4uLy4uLy4uLy4uLy4uL1R5cGVndWFyZHNcIjtcblxuLyoqXG4gKiBJbmZvcm1hdGlvbiBhYm91dCB0aGUgY3VycmVudCBzdGF0ZSBvZiB0aGUgYHVzZVN1Z2dlc3Rpb25gIGhvb2suXG4gKi9cbmV4cG9ydCB0eXBlIFN1Z2dlc3Rpb24gPSB7XG4gICAgbWFwcGVkU3VnZ2VzdGlvbjogTWFwcGVkU3VnZ2VzdGlvbjtcbiAgICAvKiBUaGUgaW5mb3JtYXRpb24gaW4gYSBgTWFwcGVkU3VnZ2VzdGlvbmAgaXMgc3VmZmljaWVudCB0byBnZW5lcmF0ZSBhIHF1ZXJ5IGZvciB0aGUgYXV0b2NvbXBsZXRlXG4gICAgY29tcG9uZW50IGJ1dCBtb3JlIGluZm9ybWF0aW9uIGlzIHJlcXVpcmVkIHRvIGFsbG93IG1hbmlwdWxhdGlvbiBvZiB0aGUgY29ycmVjdCBwYXJ0IG9mIHRoZSBET01cbiAgICB3aGVuIHNlbGVjdGluZyBhbiBvcHRpb24gZnJvbSB0aGUgYXV0b2NvbXBsZXRlLiBUaGVzZSB0aHJlZSBwaWVjZXMgb2YgaW5mb3JtYXRpb24gYWxsb3cgdXMgdG9cbiAgICBkbyB0aGF0LlxuICAgICovXG4gICAgbm9kZTogTm9kZTtcbiAgICBzdGFydE9mZnNldDogbnVtYmVyO1xuICAgIGVuZE9mZnNldDogbnVtYmVyO1xufTtcbnR5cGUgU3VnZ2VzdGlvblN0YXRlID0gU3VnZ2VzdGlvbiB8IG51bGw7XG5cbi8qKlxuICogUmVhY3QgaG9vayB0byBhbGxvdyB0cmFja2luZyBhbmQgcmVwbGFjaW5nIG9mIG1lbnRpb25zIGFuZCBjb21tYW5kcyBpbiBhIGRpdiBlbGVtZW50XG4gKlxuICogQHBhcmFtIGVkaXRvclJlZiAtIGEgcmVmIHRvIHRoZSBkaXYgdGhhdCBpcyB0aGUgY29tcG9zZXIgdGV4dGJveFxuICogQHBhcmFtIHNldFRleHQgLSBzZXR0ZXIgZnVuY3Rpb24gdG8gc2V0IHRoZSBjb250ZW50IG9mIHRoZSBjb21wb3NlclxuICogQHBhcmFtIGlzQXV0b1JlcGxhY2VFbW9qaUVuYWJsZWQgLSB3aGV0aGVyIHBsYWluIHRleHQgZW1vdGljb25zIHNob3VsZCBiZSBhdXRvIHJlcGxhY2VkIHdpdGggZW1vamlzXG4gKiBAcmV0dXJuc1xuICogLSBgaGFuZGxlTWVudGlvbmA6IGEgZnVuY3Rpb24gdGhhdCB3aWxsIGluc2VydCBAIG9yICMgbWVudGlvbnMgd2hpY2ggYXJlIHNlbGVjdGVkIGZyb21cbiAqIHRoZSBhdXRvY29tcGxldGUgaW50byB0aGUgY29tcG9zZXIsIGdpdmVuIGFuIGhyZWYsIHRoZSB0ZXh0IHRvIGRpc3BsYXksIGFuZCBhbnkgYWRkaXRpb25hbCBhdHRyaWJ1dGVzXG4gKiAtIGBoYW5kbGVDb21tYW5kYDogYSBmdW5jdGlvbiB0aGF0IHdpbGwgcmVwbGFjZSB0aGUgY29udGVudCBvZiB0aGUgY29tcG9zZXIgd2l0aCB0aGUgZ2l2ZW4gcmVwbGFjZW1lbnQgdGV4dC5cbiAqIENhbiBiZSB1c2VkIHRvIHByb2Nlc3MgYXV0b2NvbXBsZXRlIG9mIHNsYXNoIGNvbW1hbmRzXG4gKiAtIGBvblNlbGVjdGA6IGEgc2VsZWN0aW9uIGNoYW5nZSBsaXN0ZW5lciB0byBiZSBhdHRhY2hlZCB0byB0aGUgcGxhaW4gdGV4dCBjb21wb3NlclxuICogLSBgc3VnZ2VzdGlvbmA6IGlmIHRoZSBjdXJzb3IgaXMgaW5zaWRlIHNvbWV0aGluZyB0aGF0IGNvdWxkIGJlIGludGVycHJldGVkIGFzIGEgY29tbWFuZCBvciBhIG1lbnRpb24sXG4gKiB0aGlzIHdpbGwgYmUgYW4gb2JqZWN0IHJlcHJlc2VudGluZyB0aGF0IGNvbW1hbmQgb3IgbWVudGlvbiwgb3RoZXJ3aXNlIGl0IGlzIG51bGxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHVzZVN1Z2dlc3Rpb24oXG4gICAgZWRpdG9yUmVmOiBSZWFjdC5SZWZPYmplY3Q8SFRNTERpdkVsZW1lbnQ+LFxuICAgIHNldFRleHQ6ICh0ZXh0Pzogc3RyaW5nKSA9PiB2b2lkLFxuICAgIGlzQXV0b1JlcGxhY2VFbW9qaUVuYWJsZWQ/OiBib29sZWFuLFxuKToge1xuICAgIGhhbmRsZU1lbnRpb246IChocmVmOiBzdHJpbmcsIGRpc3BsYXlOYW1lOiBzdHJpbmcsIGF0dHJpYnV0ZXM6IEFsbG93ZWRNZW50aW9uQXR0cmlidXRlcykgPT4gdm9pZDtcbiAgICBoYW5kbGVBdFJvb21NZW50aW9uOiAoYXR0cmlidXRlczogQWxsb3dlZE1lbnRpb25BdHRyaWJ1dGVzKSA9PiB2b2lkO1xuICAgIGhhbmRsZUNvbW1hbmQ6ICh0ZXh0OiBzdHJpbmcpID0+IHZvaWQ7XG4gICAgaGFuZGxlRW1vamlSZXBsYWNlbWVudDogKCkgPT4gdm9pZDtcbiAgICBvblNlbGVjdDogKGV2ZW50OiBTeW50aGV0aWNFdmVudDxIVE1MRGl2RWxlbWVudD4pID0+IHZvaWQ7XG4gICAgc3VnZ2VzdGlvbjogTWFwcGVkU3VnZ2VzdGlvbiB8IG51bGw7XG59IHtcbiAgICBjb25zdCBbc3VnZ2VzdGlvbkRhdGEsIHNldFN1Z2dlc3Rpb25EYXRhMF0gPSB1c2VTdGF0ZTxTdWdnZXN0aW9uU3RhdGU+KG51bGwpO1xuXG4gICAgLy8gZGVidWcgZm9yIGh0dHBzOi8vZ2l0aHViLmNvbS92ZWN0b3ItaW0vZWxlbWVudC13ZWIvaXNzdWVzLzI2MDM3XG4gICAgY29uc3Qgc2V0U3VnZ2VzdGlvbkRhdGEgPSAoc3VnZ2VzdGlvbkRhdGE6IFNldFN0YXRlQWN0aW9uPFN1Z2dlc3Rpb25TdGF0ZT4pOiB2b2lkID0+IHtcbiAgICAgICAgLy8gc2V0U3RhdGUgYWxsb3dzIGVpdGhlciB0aGUgZGF0YSBpdHNlbGYgb3IgYSBjYWxsYmFjayB3aGljaCByZXR1cm5zIHRoZSBkYXRhXG4gICAgICAgIGxvZ2dlci5sb2coXG4gICAgICAgICAgICBgIyMgMjYwMzcgIyMgd3lzaXd5ZyB1c2VTdWdnZXN0aW9uIGhvb2sgc2V0dGluZyBzdWdnZXN0aW9uIGRhdGEgdG8gJHtcbiAgICAgICAgICAgICAgICBzdWdnZXN0aW9uRGF0YSA9PT0gbnVsbCB8fCBzdWdnZXN0aW9uRGF0YSBpbnN0YW5jZW9mIEZ1bmN0aW9uXG4gICAgICAgICAgICAgICAgICAgID8gc3VnZ2VzdGlvbkRhdGFcbiAgICAgICAgICAgICAgICAgICAgOiBzdWdnZXN0aW9uRGF0YS5tYXBwZWRTdWdnZXN0aW9uLmtleUNoYXIgKyBzdWdnZXN0aW9uRGF0YS5tYXBwZWRTdWdnZXN0aW9uLnRleHRcbiAgICAgICAgICAgIH1gLFxuICAgICAgICApO1xuICAgICAgICBzZXRTdWdnZXN0aW9uRGF0YTAoc3VnZ2VzdGlvbkRhdGEpO1xuICAgIH07XG5cbiAgICAvLyBXZSBjcmVhdGUgYSBgc2VsZWN0aW9uY2hhbmdlYCBoYW5kbGVyIGhlcmUgYmVjYXVzZSB3ZSBuZWVkIHRvIGtub3cgd2hlbiB0aGUgdXNlciBoYXMgbW92ZWQgdGhlIGN1cnNvcixcbiAgICAvLyB3ZSBjYW4gbm90IGRlcGVuZCBvbiBpbnB1dCBldmVudHMgb25seVxuICAgIGNvbnN0IG9uU2VsZWN0ID0gKCk6IHZvaWQgPT4gcHJvY2Vzc1NlbGVjdGlvbkNoYW5nZShlZGl0b3JSZWYsIHNldFN1Z2dlc3Rpb25EYXRhLCBpc0F1dG9SZXBsYWNlRW1vamlFbmFibGVkKTtcblxuICAgIGNvbnN0IGhhbmRsZU1lbnRpb24gPSAoaHJlZjogc3RyaW5nLCBkaXNwbGF5TmFtZTogc3RyaW5nLCBhdHRyaWJ1dGVzOiBBbGxvd2VkTWVudGlvbkF0dHJpYnV0ZXMpOiB2b2lkID0+XG4gICAgICAgIHByb2Nlc3NNZW50aW9uKGhyZWYsIGRpc3BsYXlOYW1lLCBhdHRyaWJ1dGVzLCBzdWdnZXN0aW9uRGF0YSwgc2V0U3VnZ2VzdGlvbkRhdGEsIHNldFRleHQpO1xuXG4gICAgY29uc3QgaGFuZGxlQXRSb29tTWVudGlvbiA9IChhdHRyaWJ1dGVzOiBBbGxvd2VkTWVudGlvbkF0dHJpYnV0ZXMpOiB2b2lkID0+XG4gICAgICAgIHByb2Nlc3NNZW50aW9uKFwiI1wiLCBcIkByb29tXCIsIGF0dHJpYnV0ZXMsIHN1Z2dlc3Rpb25EYXRhLCBzZXRTdWdnZXN0aW9uRGF0YSwgc2V0VGV4dCk7XG5cbiAgICBjb25zdCBoYW5kbGVDb21tYW5kID0gKHJlcGxhY2VtZW50VGV4dDogc3RyaW5nKTogdm9pZCA9PlxuICAgICAgICBwcm9jZXNzQ29tbWFuZChyZXBsYWNlbWVudFRleHQsIHN1Z2dlc3Rpb25EYXRhLCBzZXRTdWdnZXN0aW9uRGF0YSwgc2V0VGV4dCk7XG5cbiAgICBjb25zdCBoYW5kbGVFbW9qaVJlcGxhY2VtZW50ID0gKCk6IHZvaWQgPT4gcHJvY2Vzc0Vtb2ppUmVwbGFjZW1lbnQoc3VnZ2VzdGlvbkRhdGEsIHNldFN1Z2dlc3Rpb25EYXRhLCBzZXRUZXh0KTtcblxuICAgIHJldHVybiB7XG4gICAgICAgIHN1Z2dlc3Rpb246IHN1Z2dlc3Rpb25EYXRhPy5tYXBwZWRTdWdnZXN0aW9uID8/IG51bGwsXG4gICAgICAgIGhhbmRsZUNvbW1hbmQsXG4gICAgICAgIGhhbmRsZU1lbnRpb24sXG4gICAgICAgIGhhbmRsZUF0Um9vbU1lbnRpb24sXG4gICAgICAgIGhhbmRsZUVtb2ppUmVwbGFjZW1lbnQsXG4gICAgICAgIG9uU2VsZWN0LFxuICAgIH07XG59XG5cbi8qKlxuICogV2hlbiB0aGUgc2VsZWN0aW9uIGNoYW5nZXMgaW5zaWRlIHRoZSBjdXJyZW50IGVkaXRvciwgY2hlY2sgdG8gc2VlIGlmIHRoZSBjdXJzb3IgaXMgaW5zaWRlXG4gKiBzb21ldGhpbmcgdGhhdCBjb3VsZCBiZSBhIGNvbW1hbmQgb3IgYSBtZW50aW9uIGFuZCB1cGRhdGUgdGhlIHN1Z2dlc3Rpb24gc3RhdGUgaWYgc29cbiAqXG4gKiBAcGFyYW0gZWRpdG9yUmVmIC0gcmVmIHRvIHRoZSBjb21wb3NlclxuICogQHBhcmFtIHNldFN1Z2dlc3Rpb25EYXRhIC0gdGhlIHNldHRlciBmb3IgdGhlIHN1Z2dlc3Rpb24gc3RhdGVcbiAqIEBwYXJhbSBpc0F1dG9SZXBsYWNlRW1vamlFbmFibGVkIC0gd2hldGhlciBwbGFpbiB0ZXh0IGVtb3RpY29ucyBzaG91bGQgYmUgYXV0byByZXBsYWNlZCB3aXRoIGVtb2ppc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc1NlbGVjdGlvbkNoYW5nZShcbiAgICBlZGl0b3JSZWY6IFJlYWN0LlJlZk9iamVjdDxIVE1MRGl2RWxlbWVudD4sXG4gICAgc2V0U3VnZ2VzdGlvbkRhdGE6IFJlYWN0LkRpc3BhdGNoPFJlYWN0LlNldFN0YXRlQWN0aW9uPFN1Z2dlc3Rpb25TdGF0ZT4+LFxuICAgIGlzQXV0b1JlcGxhY2VFbW9qaUVuYWJsZWQ/OiBib29sZWFuLFxuKTogdm9pZCB7XG4gICAgY29uc3Qgc2VsZWN0aW9uID0gZG9jdW1lbnQuZ2V0U2VsZWN0aW9uKCk7XG5cbiAgICAvLyByZXR1cm4gZWFybHkgaWYgd2UgZG8gbm90IGhhdmUgYSBjdXJyZW50IGVkaXRvciByZWYgd2l0aCBhIGN1cnNvciBzZWxlY3Rpb24gaW5zaWRlIGEgdGV4dCBub2RlXG4gICAgaWYgKFxuICAgICAgICBlZGl0b3JSZWYuY3VycmVudCA9PT0gbnVsbCB8fFxuICAgICAgICBzZWxlY3Rpb24gPT09IG51bGwgfHxcbiAgICAgICAgIXNlbGVjdGlvbi5pc0NvbGxhcHNlZCB8fFxuICAgICAgICBzZWxlY3Rpb24uYW5jaG9yTm9kZT8ubm9kZU5hbWUgIT09IFwiI3RleHRcIlxuICAgICkge1xuICAgICAgICBzZXRTdWdnZXN0aW9uRGF0YShudWxsKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIGZyb20gaGVyZSBvbndhcmRzIHdlIGhhdmUgYSBjdXJzb3IgaW5zaWRlIGEgdGV4dCBub2RlXG4gICAgY29uc3QgeyBhbmNob3JOb2RlOiBjdXJyZW50Tm9kZSwgYW5jaG9yT2Zmc2V0OiBjdXJyZW50T2Zmc2V0IH0gPSBzZWxlY3Rpb247XG5cbiAgICAvLyBpZiB3ZSBoYXZlIG5vIHRleHQgY29udGVudCwgcmV0dXJuLCBjbGVhcmluZyB0aGUgc3VnZ2VzdGlvbiBzdGF0ZVxuICAgIGlmIChjdXJyZW50Tm9kZS50ZXh0Q29udGVudCA9PT0gbnVsbCkge1xuICAgICAgICBzZXRTdWdnZXN0aW9uRGF0YShudWxsKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IGZpcnN0VGV4dE5vZGUgPSBkb2N1bWVudC5jcmVhdGVOb2RlSXRlcmF0b3IoZWRpdG9yUmVmLmN1cnJlbnQsIE5vZGVGaWx0ZXIuU0hPV19URVhUKS5uZXh0Tm9kZSgpO1xuICAgIGNvbnN0IGlzRmlyc3RUZXh0Tm9kZSA9IGN1cnJlbnROb2RlID09PSBmaXJzdFRleHROb2RlO1xuICAgIGNvbnN0IGZvdW5kU3VnZ2VzdGlvbiA9IGZpbmRTdWdnZXN0aW9uSW5UZXh0KFxuICAgICAgICBjdXJyZW50Tm9kZS50ZXh0Q29udGVudCxcbiAgICAgICAgY3VycmVudE9mZnNldCxcbiAgICAgICAgaXNGaXJzdFRleHROb2RlLFxuICAgICAgICBpc0F1dG9SZXBsYWNlRW1vamlFbmFibGVkLFxuICAgICk7XG5cbiAgICAvLyBpZiB3ZSBoYXZlIG5vdCBmb3VuZCBhIHN1Z2dlc3Rpb24sIHJldHVybiwgY2xlYXJpbmcgdGhlIHN1Z2dlc3Rpb24gc3RhdGVcbiAgICBpZiAoZm91bmRTdWdnZXN0aW9uID09PSBudWxsKSB7XG4gICAgICAgIHNldFN1Z2dlc3Rpb25EYXRhKG51bGwpO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgc2V0U3VnZ2VzdGlvbkRhdGEoe1xuICAgICAgICBtYXBwZWRTdWdnZXN0aW9uOiBmb3VuZFN1Z2dlc3Rpb24ubWFwcGVkU3VnZ2VzdGlvbixcbiAgICAgICAgbm9kZTogY3VycmVudE5vZGUsXG4gICAgICAgIHN0YXJ0T2Zmc2V0OiBmb3VuZFN1Z2dlc3Rpb24uc3RhcnRPZmZzZXQsXG4gICAgICAgIGVuZE9mZnNldDogZm91bmRTdWdnZXN0aW9uLmVuZE9mZnNldCxcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBSZXBsYWNlcyB0aGUgcmVsZXZhbnQgcGFydCBvZiB0aGUgZWRpdG9yIHRleHQgd2l0aCBhIGxpbmsgcmVwcmVzZW50aW5nIGEgbWVudGlvbiBhZnRlciBpdFxuICogaXMgc2VsZWN0ZWQgZnJvbSB0aGUgYXV0b2NvbXBsZXRlLlxuICpcbiAqIEBwYXJhbSBocmVmIC0gdGhlIGhyZWYgdGhhdCB0aGUgaW5zZXJ0ZWQgbGluayB3aWxsIHVzZVxuICogQHBhcmFtIGRpc3BsYXlOYW1lIC0gdGhlIHRleHQgY29udGVudCBvZiB0aGUgbGlua1xuICogQHBhcmFtIGF0dHJpYnV0ZXMgLSBhZGRpdGlvbmFsIGF0dHJpYnV0ZXMgdG8gYWRkIHRvIHRoZSBsaW5rLCBjYW4gaW5jbHVkZSBkYXRhLSogYXR0cmlidXRlc1xuICogQHBhcmFtIHN1Z2dlc3Rpb25EYXRhIC0gcmVwcmVzZW50YXRpb24gb2YgdGhlIHBhcnQgb2YgdGhlIERPTSB0aGF0IHdpbGwgYmUgcmVwbGFjZWRcbiAqIEBwYXJhbSBzZXRTdWdnZXN0aW9uRGF0YSAtIHNldHRlciBmdW5jdGlvbiB0byBzZXQgdGhlIHN1Z2dlc3Rpb24gc3RhdGVcbiAqIEBwYXJhbSBzZXRUZXh0IC0gc2V0dGVyIGZ1bmN0aW9uIHRvIHNldCB0aGUgY29udGVudCBvZiB0aGUgY29tcG9zZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NNZW50aW9uKFxuICAgIGhyZWY6IHN0cmluZyxcbiAgICBkaXNwbGF5TmFtZTogc3RyaW5nLFxuICAgIGF0dHJpYnV0ZXM6IEFsbG93ZWRNZW50aW9uQXR0cmlidXRlcywgLy8gdGhlc2Ugd2lsbCBiZSB1c2VkIHdoZW4gZm9ybWF0dGluZyB0aGUgbGluayBhcyBhIHBpbGxcbiAgICBzdWdnZXN0aW9uRGF0YTogU3VnZ2VzdGlvblN0YXRlLFxuICAgIHNldFN1Z2dlc3Rpb25EYXRhOiBSZWFjdC5EaXNwYXRjaDxSZWFjdC5TZXRTdGF0ZUFjdGlvbjxTdWdnZXN0aW9uU3RhdGU+PixcbiAgICBzZXRUZXh0OiAodGV4dD86IHN0cmluZykgPT4gdm9pZCxcbik6IHZvaWQge1xuICAgIC8vIGlmIHdlIGRvIG5vdCBoYXZlIGEgc3VnZ2VzdGlvbiwgcmV0dXJuIGVhcmx5XG4gICAgaWYgKHN1Z2dlc3Rpb25EYXRhID09PSBudWxsKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCB7IG5vZGUgfSA9IHN1Z2dlc3Rpb25EYXRhO1xuXG4gICAgLy8gY3JlYXRlIGFuIDxhPiBlbGVtZW50IHdpdGggdGhlIHJlcXVpcmVkIGF0dHJpYnV0ZXMgdG8gYWxsb3cgdXMgdG8gaW50ZXJwcmV0IHRoZSBtZW50aW9uIGFzIGJlaW5nIGEgcGlsbFxuICAgIGNvbnN0IGxpbmtFbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcImFcIik7XG4gICAgY29uc3QgbGlua1RleHROb2RlID0gZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoZGlzcGxheU5hbWUpO1xuICAgIGxpbmtFbGVtZW50LnNldEF0dHJpYnV0ZShcImhyZWZcIiwgaHJlZik7XG4gICAgbGlua0VsZW1lbnQuc2V0QXR0cmlidXRlKFwiY29udGVudGVkaXRhYmxlXCIsIFwiZmFsc2VcIik7XG5cbiAgICBmb3IgKGNvbnN0IFthdHRyLCB2YWx1ZV0gb2YgYXR0cmlidXRlcy5lbnRyaWVzKCkpIHtcbiAgICAgICAgbGlua0VsZW1lbnQuc2V0QXR0cmlidXRlKGF0dHIsIHZhbHVlKTtcbiAgICB9XG5cbiAgICBsaW5rRWxlbWVudC5hcHBlbmRDaGlsZChsaW5rVGV4dE5vZGUpO1xuXG4gICAgLy8gY3JlYXRlIHRleHQgbm9kZXMgdG8gZ28gYmVmb3JlIGFuZCBhZnRlciB0aGUgbGlua1xuICAgIGNvbnN0IGxlYWRpbmdUZXh0Tm9kZSA9IGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKG5vZGUudGV4dENvbnRlbnQ/LnNsaWNlKDAsIHN1Z2dlc3Rpb25EYXRhLnN0YXJ0T2Zmc2V0KSB8fCBcIlxcdTIwMGJcIik7XG4gICAgY29uc3QgdHJhaWxpbmdUZXh0Tm9kZSA9IGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKGAgJHtub2RlLnRleHRDb250ZW50Py5zbGljZShzdWdnZXN0aW9uRGF0YS5lbmRPZmZzZXQpID8/IFwiXCJ9YCk7XG5cbiAgICAvLyBub3cgYWRkIHRoZSBsZWFkaW5nIHRleHQgbm9kZSwgbGluayBlbGVtZW50IGFuZCB0cmFpbGluZyB0ZXh0IG5vZGUgYmVmb3JlIHJlbW92aW5nIHRoZSBub2RlIHdlIGFyZSByZXBsYWNpbmdcbiAgICBjb25zdCBwYXJlbnROb2RlID0gbm9kZS5wYXJlbnROb2RlO1xuICAgIGlmIChpc05vdE51bGwocGFyZW50Tm9kZSkpIHtcbiAgICAgICAgcGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUobGVhZGluZ1RleHROb2RlLCBub2RlKTtcbiAgICAgICAgcGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUobGlua0VsZW1lbnQsIG5vZGUpO1xuICAgICAgICBwYXJlbnROb2RlLmluc2VydEJlZm9yZSh0cmFpbGluZ1RleHROb2RlLCBub2RlKTtcbiAgICAgICAgcGFyZW50Tm9kZS5yZW1vdmVDaGlsZChub2RlKTtcbiAgICB9XG5cbiAgICAvLyBtb3ZlIHRoZSBzZWxlY3Rpb24gdG8gdGhlIHRyYWlsaW5nIHRleHQgbm9kZVxuICAgIGRvY3VtZW50LmdldFNlbGVjdGlvbigpPy5zZXRCYXNlQW5kRXh0ZW50KHRyYWlsaW5nVGV4dE5vZGUsIDEsIHRyYWlsaW5nVGV4dE5vZGUsIDEpO1xuXG4gICAgLy8gc2V0IHRoZSB0ZXh0IGNvbnRlbnQgdG8gYmUgdGhlIGlubmVySFRNTCBvZiB0aGUgY3VycmVudCBlZGl0b3IgcmVmIGFuZCBjbGVhciB0aGUgc3VnZ2VzdGlvbiBzdGF0ZVxuICAgIHNldFRleHQoKTtcbiAgICBzZXRTdWdnZXN0aW9uRGF0YShudWxsKTtcbn1cblxuLyoqXG4gKiBSZXBsYWNlcyB0aGUgcmVsZXZhbnQgcGFydCBvZiB0aGUgZWRpdG9yIHRleHQgd2l0aCB0aGUgcmVwbGFjZW1lbnQgdGV4dCBhZnRlciBhIGNvbW1hbmQgaXMgc2VsZWN0ZWRcbiAqIGZyb20gdGhlIGF1dG9jb21wbGV0ZS5cbiAqXG4gKiBAcGFyYW0gcmVwbGFjZW1lbnRUZXh0IC0gdGhlIHRleHQgdGhhdCB3ZSB3aWxsIGluc2VydCBpbnRvIHRoZSBET01cbiAqIEBwYXJhbSBzdWdnZXN0aW9uRGF0YSAtIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBwYXJ0IG9mIHRoZSBET00gdGhhdCB3aWxsIGJlIHJlcGxhY2VkXG4gKiBAcGFyYW0gc2V0U3VnZ2VzdGlvbkRhdGEgLSBzZXR0ZXIgZnVuY3Rpb24gdG8gc2V0IHRoZSBzdWdnZXN0aW9uIHN0YXRlXG4gKiBAcGFyYW0gc2V0VGV4dCAtIHNldHRlciBmdW5jdGlvbiB0byBzZXQgdGhlIGNvbnRlbnQgb2YgdGhlIGNvbXBvc2VyXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzQ29tbWFuZChcbiAgICByZXBsYWNlbWVudFRleHQ6IHN0cmluZyxcbiAgICBzdWdnZXN0aW9uRGF0YTogU3VnZ2VzdGlvblN0YXRlLFxuICAgIHNldFN1Z2dlc3Rpb25EYXRhOiBSZWFjdC5EaXNwYXRjaDxSZWFjdC5TZXRTdGF0ZUFjdGlvbjxTdWdnZXN0aW9uU3RhdGU+PixcbiAgICBzZXRUZXh0OiAodGV4dD86IHN0cmluZykgPT4gdm9pZCxcbik6IHZvaWQge1xuICAgIC8vIGlmIHdlIGRvIG5vdCBoYXZlIGEgc3VnZ2VzdGlvbiwgcmV0dXJuIGVhcmx5XG4gICAgaWYgKHN1Z2dlc3Rpb25EYXRhID09PSBudWxsKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCB7IG5vZGUgfSA9IHN1Z2dlc3Rpb25EYXRhO1xuXG4gICAgLy8gZm9yIGEgY29tbWFuZCwgd2Uga25vdyB3ZSBzdGFydCBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSB0ZXh0IG5vZGUsIHNvIGJ1aWxkIHRoZSByZXBsYWNlbWVudFxuICAgIC8vIHN0cmluZyAobm90ZSB0cmFpbGluZyBzcGFjZSkgYW5kIG1hbnVhbGx5IGFkanVzdCB0aGUgbm9kZSdzIHRleHRjb250ZW50XG4gICAgY29uc3QgbmV3Q29udGVudCA9IGAke3JlcGxhY2VtZW50VGV4dH0gYDtcbiAgICBub2RlLnRleHRDb250ZW50ID0gbmV3Q29udGVudDtcblxuICAgIC8vIHRoZW4gc2V0IHRoZSBjdXJzb3IgdG8gdGhlIGVuZCBvZiB0aGUgbm9kZSwgdXBkYXRlIHRoZSBgY29udGVudGAgc3RhdGUgaW4gdGhlIHVzZVBsYWluVGV4dExpc3RlbmVyc1xuICAgIC8vIGhvb2sgYW5kIGNsZWFyIHRoZSBzdWdnZXN0aW9uIGZyb20gc3RhdGVcbiAgICBkb2N1bWVudC5nZXRTZWxlY3Rpb24oKT8uc2V0QmFzZUFuZEV4dGVudChub2RlLCBuZXdDb250ZW50Lmxlbmd0aCwgbm9kZSwgbmV3Q29udGVudC5sZW5ndGgpO1xuICAgIHNldFRleHQobmV3Q29udGVudCk7XG4gICAgc2V0U3VnZ2VzdGlvbkRhdGEobnVsbCk7XG59XG5cbi8qKlxuICogUmVwbGFjZXMgdGhlIHJlbGV2YW50IHBhcnQgb2YgdGhlIGVkaXRvciB0ZXh0LCByZXBsYWNpbmcgdGhlIHBsYWluIHRleHQgZW1vaXRjb24gd2l0aCB0aGUgc3VnZ2VzdGVkIGVtb2ppLlxuICpcbiAqIEBwYXJhbSBzdWdnZXN0aW9uRGF0YSAtIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBwYXJ0IG9mIHRoZSBET00gdGhhdCB3aWxsIGJlIHJlcGxhY2VkXG4gKiBAcGFyYW0gc2V0U3VnZ2VzdGlvbkRhdGEgLSBzZXR0ZXIgZnVuY3Rpb24gdG8gc2V0IHRoZSBzdWdnZXN0aW9uIHN0YXRlXG4gKiBAcGFyYW0gc2V0VGV4dCAtIHNldHRlciBmdW5jdGlvbiB0byBzZXQgdGhlIGNvbnRlbnQgb2YgdGhlIGNvbXBvc2VyXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzRW1vamlSZXBsYWNlbWVudChcbiAgICBzdWdnZXN0aW9uRGF0YTogU3VnZ2VzdGlvblN0YXRlLFxuICAgIHNldFN1Z2dlc3Rpb25EYXRhOiBSZWFjdC5EaXNwYXRjaDxSZWFjdC5TZXRTdGF0ZUFjdGlvbjxTdWdnZXN0aW9uU3RhdGU+PixcbiAgICBzZXRUZXh0OiAodGV4dD86IHN0cmluZykgPT4gdm9pZCxcbik6IHZvaWQge1xuICAgIC8vIGlmIHdlIGRvIG5vdCBoYXZlIGEgc3VnZ2VzdGlvbiBvZiB0aGUgY29ycmVjdCB0eXBlLCByZXR1cm4gZWFybHlcbiAgICBpZiAoc3VnZ2VzdGlvbkRhdGEgPT09IG51bGwgfHwgc3VnZ2VzdGlvbkRhdGEubWFwcGVkU3VnZ2VzdGlvbi50eXBlICE9PSBgY3VzdG9tYCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHsgbm9kZSwgbWFwcGVkU3VnZ2VzdGlvbiB9ID0gc3VnZ2VzdGlvbkRhdGE7XG4gICAgY29uc3QgZXhpc3RpbmdDb250ZW50ID0gbm9kZS50ZXh0Q29udGVudDtcblxuICAgIGlmIChleGlzdGluZ0NvbnRlbnQgPT0gbnVsbCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gcmVwbGFjZSB0aGUgZW1vdGljb24gd2l0aCB0aGUgc3VnZ2VzZWQgZW1vamlcbiAgICBjb25zdCBuZXdDb250ZW50ID1cbiAgICAgICAgZXhpc3RpbmdDb250ZW50LnNsaWNlKDAsIHN1Z2dlc3Rpb25EYXRhLnN0YXJ0T2Zmc2V0KSArXG4gICAgICAgIG1hcHBlZFN1Z2dlc3Rpb24udGV4dCArXG4gICAgICAgIGV4aXN0aW5nQ29udGVudC5zbGljZShzdWdnZXN0aW9uRGF0YS5lbmRPZmZzZXQpO1xuXG4gICAgbm9kZS50ZXh0Q29udGVudCA9IG5ld0NvbnRlbnQ7XG5cbiAgICBkb2N1bWVudC5nZXRTZWxlY3Rpb24oKT8uc2V0QmFzZUFuZEV4dGVudChub2RlLCBuZXdDb250ZW50Lmxlbmd0aCwgbm9kZSwgbmV3Q29udGVudC5sZW5ndGgpO1xuICAgIHNldFRleHQobmV3Q29udGVudCk7XG4gICAgc2V0U3VnZ2VzdGlvbkRhdGEobnVsbCk7XG59XG5cbi8qKlxuICogR2l2ZW4gc29tZSB0ZXh0IGNvbnRlbnQgZnJvbSBhIG5vZGUgYW5kIHRoZSBjdXJzb3IgcG9zaXRpb24sIGZpbmQgdGhlIHdvcmQgdGhhdCB0aGUgY3Vyc29yIGlzIGN1cnJlbnRseSBpbnNpZGVcbiAqIGFuZCB0aGVuIHRlc3QgdGhhdCB3b3JkIHRvIHNlZSBpZiBpdCBpcyBhIHN1Z2dlc3Rpb24uIFJldHVybiB0aGUgYE1hcHBlZFN1Z2dlc3Rpb25gIHdpdGggc3RhcnQgYW5kIGVuZCBvZmZzZXRzIGlmXG4gKiB0aGUgY3Vyc29yIGlzIGluc2lkZSBhIHZhbGlkIHN1Z2dlc3Rpb24sIG51bGwgb3RoZXJ3aXNlLlxuICpcbiAqIEBwYXJhbSB0ZXh0IC0gdGhlIHRleHQgY29udGVudCBvZiBhIG5vZGVcbiAqIEBwYXJhbSBvZmZzZXQgLSB0aGUgY3VycmVudCBjdXJzb3Igb2Zmc2V0IHBvc2l0aW9uIHdpdGhpbiB0aGUgbm9kZVxuICogQHBhcmFtIGlzRmlyc3RUZXh0Tm9kZSAtIHdoZXRoZXIgb3Igbm90IHRoZSBub2RlIGlzIHRoZSBmaXJzdCB0ZXh0IG5vZGUgaW4gdGhlIGVkaXRvci4gVXNlZCB0byBkZXRlcm1pbmVcbiAqIGlmIGEgY29tbWFuZCBzdWdnZXN0aW9uIGlzIGZvdW5kIG9yIG5vdFxuICogQHBhcmFtIGlzQXV0b1JlcGxhY2VFbW9qaUVuYWJsZWQgLSB3aGV0aGVyIHBsYWluIHRleHQgZW1vdGljb25zIHNob3VsZCBiZSBhdXRvIHJlcGxhY2VkIHdpdGggZW1vamlzXG4gKiBAcmV0dXJucyB0aGUgYE1hcHBlZFN1Z2dlc3Rpb25gIGFsb25nIHdpdGggaXRzIHN0YXJ0IGFuZCBlbmQgb2Zmc2V0cyBpZiBmb3VuZCwgb3RoZXJ3aXNlIG51bGxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZpbmRTdWdnZXN0aW9uSW5UZXh0KFxuICAgIHRleHQ6IHN0cmluZyxcbiAgICBvZmZzZXQ6IG51bWJlcixcbiAgICBpc0ZpcnN0VGV4dE5vZGU6IGJvb2xlYW4sXG4gICAgaXNBdXRvUmVwbGFjZUVtb2ppRW5hYmxlZD86IGJvb2xlYW4sXG4pOiB7IG1hcHBlZFN1Z2dlc3Rpb246IE1hcHBlZFN1Z2dlc3Rpb247IHN0YXJ0T2Zmc2V0OiBudW1iZXI7IGVuZE9mZnNldDogbnVtYmVyIH0gfCBudWxsIHtcbiAgICAvLyBSZXR1cm4gbnVsbCBlYXJseSBpZiB0aGUgb2Zmc2V0IGlzIG91dHNpZGUgdGhlIGNvbnRlbnRcbiAgICBpZiAob2Zmc2V0IDwgMCB8fCBvZmZzZXQgPiB0ZXh0Lmxlbmd0aCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICAvLyBWYXJpYWJsZXMgdG8ga2VlcCB0cmFjayBvZiB0aGUgaW5kaWNlcyB3ZSB3aWxsIGJlIHNsaWNpbmcgZnJvbSBhbmQgdG8gaW4gb3JkZXIgdG8gY3JlYXRlXG4gICAgLy8gYSBzdWJzdHJpbmcgb2YgdGhlIHdvcmQgdGhhdCB0aGUgY3Vyc29yIGlzIGN1cnJlbnRseSBpbnNpZGVcbiAgICBsZXQgc3RhcnRTbGljZUluZGV4ID0gb2Zmc2V0O1xuICAgIGxldCBlbmRTbGljZUluZGV4ID0gb2Zmc2V0O1xuXG4gICAgLy8gU2VhcmNoIGJhY2t3YXJkcyBmcm9tIHRoZSBjdXJyZW50IGN1cnNvciBwb3NpdGlvbiB0byBmaW5kIHRoZSBzdGFydCBpbmRleCBvZiB0aGUgd29yZFxuICAgIC8vIGNvbnRhaW5pbmcgdGhlIGN1cnNvclxuICAgIHdoaWxlIChzaG91bGREZWNyZW1lbnRTdGFydEluZGV4KHRleHQsIHN0YXJ0U2xpY2VJbmRleCkpIHtcbiAgICAgICAgc3RhcnRTbGljZUluZGV4LS07XG4gICAgfVxuXG4gICAgLy8gU2VhcmNoIGZvcndhcmRzIGZyb20gdGhlIGN1cnJlbnQgY3Vyc29yIHBvc2l0aW9uIHRvIGZpbmQgdGhlIGVuZCBpbmRleCBvZiB0aGUgd29yZFxuICAgIC8vIGNvbnRhaW5pbmcgdGhlIGN1cnNvclxuICAgIHdoaWxlIChzaG91bGRJbmNyZW1lbnRFbmRJbmRleCh0ZXh0LCBlbmRTbGljZUluZGV4KSkge1xuICAgICAgICBlbmRTbGljZUluZGV4Kys7XG4gICAgfVxuXG4gICAgLy8gR2V0IHRoZSB3b3JkIGF0IHRoZSBjdXJzb3IgdGhlbiBjaGVjayBpZiBpdCBjb250YWlucyBhIHN1Z2dlc3Rpb24gb3Igbm90XG4gICAgY29uc3Qgd29yZEF0Q3Vyc29yID0gdGV4dC5zbGljZShzdGFydFNsaWNlSW5kZXgsIGVuZFNsaWNlSW5kZXgpO1xuICAgIGNvbnN0IG1hcHBlZFN1Z2dlc3Rpb24gPSBnZXRNYXBwZWRTdWdnZXN0aW9uKHdvcmRBdEN1cnNvciwgaXNBdXRvUmVwbGFjZUVtb2ppRW5hYmxlZCk7XG5cbiAgICAvKipcbiAgICAgKiBJZiB3ZSBoYXZlIGEgd29yZCB0aGF0IGNvdWxkIGJlIGEgY29tbWFuZCwgaXQgaXMgbm90IGEgdmFsaWQgY29tbWFuZCBpZjpcbiAgICAgKiAtIHRoZSBub2RlIHdlJ3JlIGxvb2tpbmcgYXQgaXNuJ3QgdGhlIGZpcnN0IHRleHQgbm9kZSBpbiB0aGUgZWRpdG9yIChhZGRpbmcgcGFyYWdyYXBocyBjYW5cbiAgICAgKiAgIHJlc3VsdCBpbiBuZXN0ZWQgPHA+IHRhZ3MgaW5zaWRlIHRoZSBlZGl0b3IgPGRpdj4pXG4gICAgICogLSB0aGUgc3RhcnRpbmcgaW5kZXggaXMgYW55dGhpbmcgb3RoZXIgdGhhbiAwICh0aGV5IGNhbiBvbmx5IGFwcGVhciBhdCB0aGUgc3RhcnQgb2YgYSBtZXNzYWdlKVxuICAgICAqIC0gdGhlcmUgaXMgbW9yZSB0ZXh0IGZvbGxvd2luZyB0aGUgY29tbWFuZCAoZWcgYC9zcG8gYXNkZnxgIHNob3VsZCBub3QgYmUgaW50ZXJwcmV0ZWQgYXNcbiAgICAgKiAgIHNvbWV0aGluZyByZXF1aXJpbmcgYXV0b2NvbXBsZXRlKVxuICAgICAqL1xuICAgIGlmIChcbiAgICAgICAgbWFwcGVkU3VnZ2VzdGlvbiA9PT0gbnVsbCB8fFxuICAgICAgICAobWFwcGVkU3VnZ2VzdGlvbi50eXBlID09PSBcImNvbW1hbmRcIiAmJlxuICAgICAgICAgICAgKCFpc0ZpcnN0VGV4dE5vZGUgfHwgc3RhcnRTbGljZUluZGV4ICE9PSAwIHx8IGVuZFNsaWNlSW5kZXggIT09IHRleHQubGVuZ3RoKSlcbiAgICApIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuXG4gICAgcmV0dXJuIHsgbWFwcGVkU3VnZ2VzdGlvbiwgc3RhcnRPZmZzZXQ6IHN0YXJ0U2xpY2VJbmRleCwgZW5kT2Zmc2V0OiBzdGFydFNsaWNlSW5kZXggKyB3b3JkQXRDdXJzb3IubGVuZ3RoIH07XG59XG5cbi8qKlxuICogQXNzb2NpYXRlZCBmdW5jdGlvbiBmb3IgZmluZFN1Z2dlc3Rpb25JblRleHQuIENoZWNrcyB0aGUgY2hhcmFjdGVyIGF0IHRoZSBwcmVjZWRpbmcgaW5kZXhcbiAqIHRvIGRldGVybWluZSBpZiB0aGUgc2VhcmNoIGxvb3Agc2hvdWxkIGNvbnRpbnVlLlxuICpcbiAqIEBwYXJhbSB0ZXh0IC0gdGV4dCBjb250ZW50IHRvIGNoZWNrIGZvciBtZW50aW9ucyBvciBjb21tYW5kc1xuICogQHBhcmFtIGluZGV4IC0gdGhlIGN1cnJlbnQgaW5kZXggdG8gY2hlY2tcbiAqIEByZXR1cm5zIHRydWUgaWYgY2hlY2sgc2hvdWxkIGtlZXAgbW92aW5nIGJhY2t3YXJkcywgZmFsc2Ugb3RoZXJ3aXNlXG4gKi9cbmZ1bmN0aW9uIHNob3VsZERlY3JlbWVudFN0YXJ0SW5kZXgodGV4dDogc3RyaW5nLCBpbmRleDogbnVtYmVyKTogYm9vbGVhbiB7XG4gICAgLy8gSWYgdGhlIGluZGV4IGlzIGF0IG9yIG91dHNpZGUgdGhlIGJlZ2lubmluZyBvZiB0aGUgc3RyaW5nLCByZXR1cm4gZmFsc2VcbiAgICBpZiAoaW5kZXggPD0gMCkgcmV0dXJuIGZhbHNlO1xuXG4gICAgLy8gV2UgYXJlIGluc2lkZSB0aGUgc3RyaW5nIHNvIGNhbiBndWFyYW50ZWUgdGhhdCB0aGVyZSBpcyBhIHByZWNlZGluZyBjaGFyYWN0ZXJcbiAgICAvLyBLZWVwIHNlYXJjaGluZyBiYWNrd2FyZHMgaWYgdGhlIHByZWNlZGluZyBjaGFyYWN0ZXIgaXMgbm90IGEgc3BhY2VcbiAgICByZXR1cm4gIS9cXHMvLnRlc3QodGV4dFtpbmRleCAtIDFdKTtcbn1cblxuLyoqXG4gKiBBc3NvY2lhdGVkIGZ1bmN0aW9uIGZvciBmaW5kU3VnZ2VzdGlvbkluVGV4dC4gQ2hlY2tzIHRoZSBjaGFyYWN0ZXIgYXQgdGhlIGN1cnJlbnQgaW5kZXhcbiAqIHRvIGRldGVybWluZSBpZiB0aGUgc2VhcmNoIGxvb3Agc2hvdWxkIGNvbnRpbnVlLlxuICpcbiAqIEBwYXJhbSB0ZXh0IC0gdGV4dCBjb250ZW50IHRvIGNoZWNrIGZvciBtZW50aW9ucyBvciBjb21tYW5kc1xuICogQHBhcmFtIGluZGV4IC0gdGhlIGN1cnJlbnQgaW5kZXggdG8gY2hlY2tcbiAqIEByZXR1cm5zIHRydWUgaWYgY2hlY2sgc2hvdWxkIGtlZXAgbW92aW5nIGZvcndhcmRzLCBmYWxzZSBvdGhlcndpc2VcbiAqL1xuZnVuY3Rpb24gc2hvdWxkSW5jcmVtZW50RW5kSW5kZXgodGV4dDogc3RyaW5nLCBpbmRleDogbnVtYmVyKTogYm9vbGVhbiB7XG4gICAgLy8gSWYgdGhlIGluZGV4IGlzIGF0IG9yIG91dHNpZGUgdGhlIGVuZCBvZiB0aGUgc3RyaW5nLCByZXR1cm4gZmFsc2VcbiAgICBpZiAoaW5kZXggPj0gdGV4dC5sZW5ndGgpIHJldHVybiBmYWxzZTtcblxuICAgIC8vIEtlZXAgc2VhcmNoaW5nIGZvcndhcmRzIGlmIHRoZSBjdXJyZW50IGNoYXJhY3RlciBpcyBub3QgYSBzcGFjZVxuICAgIHJldHVybiAhL1xccy8udGVzdCh0ZXh0W2luZGV4XSk7XG59XG5cbi8qKlxuICogR2l2ZW4gYSBzdHJpbmcsIHJldHVybiBhIGBNYXBwZWRTdWdnZXN0aW9uYCBpZiB0aGUgc3RyaW5nIGNvbnRhaW5zIGEgc3VnZ2VzdGlvbi4gT3RoZXJ3aXNlIHJldHVybiBudWxsLlxuICpcbiAqIEBwYXJhbSB0ZXh0IC0gc3RyaW5nIHRvIGNoZWNrIGZvciBhIHN1Z2dlc3Rpb25cbiAqIEBwYXJhbSBpc0F1dG9SZXBsYWNlRW1vamlFbmFibGVkIC0gd2hldGhlciBwbGFpbiB0ZXh0IGVtb3RpY29ucyBzaG91bGQgYmUgYXV0byByZXBsYWNlZCB3aXRoIGVtb2ppc1xuICogQHJldHVybnMgYSBgTWFwcGVkU3VnZ2VzdGlvbmAgaWYgYSBzdWdnZXN0aW9uIGlzIHByZXNlbnQsIG51bGwgb3RoZXJ3aXNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRNYXBwZWRTdWdnZXN0aW9uKHRleHQ6IHN0cmluZywgaXNBdXRvUmVwbGFjZUVtb2ppRW5hYmxlZD86IGJvb2xlYW4pOiBNYXBwZWRTdWdnZXN0aW9uIHwgbnVsbCB7XG4gICAgaWYgKGlzQXV0b1JlcGxhY2VFbW9qaUVuYWJsZWQpIHtcbiAgICAgICAgLy8gdmFyaWF0aW9ucyBvZiBwbGFpbnRleHQgZW1vaXRjb25zKEUuZy4gOlAgdnMgOnAgdnMgOi1QKSBhcmUgaGFuZGxlZCB1cHN0cmVhbSBieSB0aGUgZW1vamliYXNlLWJpbmRpbmdzL2Vtb2ppYmFzZSBsaWJyYXJpZXMuXG4gICAgICAgIC8vIFNlZSBydWxlcyBmb3IgdmFyaWF0aW9ucyBoZXJlIGh0dHBzOi8vZ2l0aHViLmNvbS9taWxlc2ovZW1vamliYXNlL2Jsb2IvbWFzdGVyL3BhY2thZ2VzL2NvcmUvc3JjL2dlbmVyYXRlRW1vdGljb25QZXJtdXRhdGlvbnMudHMjTDMtTDMyXG4gICAgICAgIGNvbnN0IGVtb2ppID0gRU1PVElDT05fVE9fRU1PSkkuZ2V0KHRleHQpO1xuICAgICAgICBpZiAoZW1vamk/LnVuaWNvZGUpIHtcbiAgICAgICAgICAgIHJldHVybiB7IGtleUNoYXI6IFwiXCIsIHRleHQ6IGVtb2ppLnVuaWNvZGUsIHR5cGU6IFwiY3VzdG9tXCIgfTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IGZpcnN0Q2hhciA9IHRleHQuY2hhckF0KDApO1xuICAgIGNvbnN0IHJlc3RPZlN0cmluZyA9IHRleHQuc2xpY2UoMSk7XG5cbiAgICBzd2l0Y2ggKGZpcnN0Q2hhcikge1xuICAgICAgICBjYXNlIFwiL1wiOlxuICAgICAgICAgICAgcmV0dXJuIHsga2V5Q2hhcjogZmlyc3RDaGFyLCB0ZXh0OiByZXN0T2ZTdHJpbmcsIHR5cGU6IFwiY29tbWFuZFwiIH07XG4gICAgICAgIGNhc2UgXCIjXCI6XG4gICAgICAgIGNhc2UgXCJAXCI6XG4gICAgICAgICAgICByZXR1cm4geyBrZXlDaGFyOiBmaXJzdENoYXIsIHRleHQ6IHJlc3RPZlN0cmluZywgdHlwZTogXCJtZW50aW9uXCIgfTtcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBUUEsSUFBQUEsa0JBQUEsR0FBQUMsT0FBQTtBQUVBLElBQUFDLE1BQUEsR0FBQUQsT0FBQTtBQUNBLElBQUFFLE9BQUEsR0FBQUYsT0FBQTtBQUVBLElBQUFHLFdBQUEsR0FBQUgsT0FBQTtBQWJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQVNBO0FBQ0E7QUFDQTs7QUFjQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDTyxTQUFTSSxhQUFhQSxDQUN6QkMsU0FBMEMsRUFDMUNDLE9BQWdDLEVBQ2hDQyx5QkFBbUMsRUFRckM7RUFDRSxNQUFNLENBQUNDLGNBQWMsRUFBRUMsa0JBQWtCLENBQUMsR0FBRyxJQUFBQyxlQUFRLEVBQWtCLElBQUksQ0FBQzs7RUFFNUU7RUFDQSxNQUFNQyxpQkFBaUIsR0FBSUgsY0FBK0MsSUFBVztJQUNqRjtJQUNBSSxjQUFNLENBQUNDLEdBQUcsQ0FDTixxRUFDSUwsY0FBYyxLQUFLLElBQUksSUFBSUEsY0FBYyxZQUFZTSxRQUFRLEdBQ3ZETixjQUFjLEdBQ2RBLGNBQWMsQ0FBQ08sZ0JBQWdCLENB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