(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Tinode = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;iSupports conversion of user keyboard input to formatted text:
*
* *abc* → abc
* _abc_ → abc
* ~abc~ → abc
* `abc` → abc
*
* Also supports forms and buttons.
*
* Nested formatting is supported, e.g. *abc _def_* -> abc def
* URLs, @mentions, and #hashtags are extracted and converted into links.
* Forms and buttons can be added procedurally.
* JSON data representation is inspired by Draft.js raw formatting.
*
*
* @example
* Text:
*
* this is *bold*, `code` and _italic_, ~strike~
* combined *bold and _italic_*
* an url: https://www.example.com/abc#fragment and another _www.tinode.co_
* this is a @mention and a #hashtag in a string
* second #hashtag
*
*
* Sample JSON representation of the text above:
* {
* "txt": "this is bold, code and italic, strike combined bold and italic an url: https://www.example.com/abc#fragment " +
* "and another www.tinode.co this is a @mention and a #hashtag in a string second #hashtag",
* "fmt": [
* { "at":8, "len":4,"tp":"ST" },{ "at":14, "len":4, "tp":"CO" },{ "at":23, "len":6, "tp":"EM"},
* { "at":31, "len":6, "tp":"DL" },{ "tp":"BR", "len":1, "at":37 },{ "at":56, "len":6, "tp":"EM" },
* { "at":47, "len":15, "tp":"ST" },{ "tp":"BR", "len":1, "at":62 },{ "at":120, "len":13, "tp":"EM" },
* { "at":71, "len":36, "key":0 },{ "at":120, "len":13, "key":1 },{ "tp":"BR", "len":1, "at":133 },
* { "at":144, "len":8, "key":2 },{ "at":159, "len":8, "key":3 },{ "tp":"BR", "len":1, "at":179 },
* { "at":187, "len":8, "key":3 },{ "tp":"BR", "len":1, "at":195 }
* ],
* "ent": [
* { "tp":"LN", "data":{ "url":"https://www.example.com/abc#fragment" } },
* { "tp":"LN", "data":{ "url":"http://www.tinode.co" } },
* { "tp":"MN", "data":{ "val":"mention" } },
* { "tp":"HT", "data":{ "val":"hashtag" } }
* ]
* }
*/
'use strict';
const MAX_FORM_ELEMENTS = 8;
const JSON_MIME_TYPE = 'application/json';
// Regular expressions for parsing inline formats. Javascript does not support lookbehind,
// so it's a bit messy.
const INLINE_STYLES = [
// Strong = bold, *bold text*
{
name: 'ST',
start: /(?:^|[\W_])(\*)[^\s*]/,
end: /[^\s*](\*)(?=$|[\W_])/
},
// Emphesized = italic, _italic text_
{
name: 'EM',
start: /(?:^|\W)(_)[^\s_]/,
end: /[^\s_](_)(?=$|\W)/
},
// Deleted, ~strike this though~
{
name: 'DL',
start: /(?:^|[\W_])(~)[^\s~]/,
end: /[^\s~](~)(?=$|[\W_])/
},
// Code block `this is monospace`
{
name: 'CO',
start: /(?:^|\W)(`)[^`]/,
end: /[^`](`)(?=$|\W)/
}
];
// RegExps for entity extraction (RF = reference)
const ENTITY_TYPES = [
// URLs
{
name: 'LN',
dataName: 'url',
pack: function(val) {
// Check if the protocol is specified, if not use http
if (!/^[a-z]+:\/\//i.test(val)) {
val = 'http://' + val;
}
return {
url: val
};
},
re: /(?:(?:https?|ftp):\/\/|www\.|ftp\.)[-A-Z0-9+&@#\/%=~_|$?!:,.]*[A-Z0-9+&@#\/%=~_|$]/ig
},
// Mentions @user (must be 2 or more characters)
{
name: 'MN',
dataName: 'val',
pack: function(val) {
return {
val: val.slice(1)
};
},
re: /\B@(\w\w+)/g
},
// Hashtags #hashtag, like metion 2 or more characters.
{
name: 'HT',
dataName: 'val',
pack: function(val) {
return {
val: val.slice(1)
};
},
re: /\B#(\w\w+)/g
}
];
// HTML tag name suggestions
const HTML_TAGS = {
ST: {
name: 'b',
isVoid: false
},
EM: {
name: 'i',
isVoid: false
},
DL: {
name: 'del',
isVoid: false
},
CO: {
name: 'tt',
isVoid: false
},
BR: {
name: 'br',
isVoid: true
},
LN: {
name: 'a',
isVoid: false
},
MN: {
name: 'a',
isVoid: false
},
HT: {
name: 'a',
isVoid: false
},
IM: {
name: 'img',
isVoid: true
},
FM: {
name: 'div',
isVoid: false
},
RW: {
name: 'div',
isVoid: false,
},
BN: {
name: 'button',
isVoid: false
},
HD: {
name: '',
isVoid: false
}
};
// Convert base64-encoded string into Blob.
function base64toObjectUrl(b64, contentType) {
let bin;
try {
bin = atob(b64);
let length = bin.length;
let buf = new ArrayBuffer(length);
let arr = new Uint8Array(buf);
for (let i = 0; i < length; i++) {
arr[i] = bin.charCodeAt(i);
}
return URL.createObjectURL(new Blob([buf], {
type: contentType
}));
} catch (err) {
console.log("Drafty: failed to convert object.", err.message);
}
return null;
}
// Helpers for converting Drafty to HTML.
const DECORATORS = {
// Visial styles
ST: {
open: function() {
return '';
},
close: function() {
return ' ';
}
},
EM: {
open: function() {
return '';
},
close: function() {
return ' '
}
},
DL: {
open: function() {
return '';
},
close: function() {
return ''
}
},
CO: {
open: function() {
return '';
},
close: function() {
return ' '
}
},
// Line break
BR: {
open: function() {
return ' ';
},
close: function() {
return ''
}
},
// Hidden element
HD: {
open: function() {
return '';
},
close: function() {
return '';
}
},
// Link (URL)
LN: {
open: function(data) {
return '';
},
close: function(data) {
return ' ';
},
props: function(data) {
return data ? {
href: data.url,
target: "_blank"
} : null;
},
},
// Mention
MN: {
open: function(data) {
return '';
},
close: function(data) {
return ' ';
},
props: function(data) {
return data ? {
name: data.val
} : null;
},
},
// Hashtag
HT: {
open: function(data) {
return '';
},
close: function(data) {
return ' ';
},
props: function(data) {
return data ? {
name: data.val
} : null;
},
},
// Button
BN: {
open: function(data) {
return '';
},
close: function(data) {
return ' ';
},
props: function(data) {
return data ? {
'data-act': data.act,
'data-val': data.val,
'data-name': data.name,
'data-ref': data.ref
} : null;
},
},
// Image
IM: {
open: function(data) {
// Don't use data.ref for preview: it's a security risk.
const previewUrl = base64toObjectUrl(data.val, data.mime);
const downloadUrl = data.ref ? data.ref : previewUrl;
return (data.name ? '' : '') +
' ';
},
close: function(data) {
return (data.name ? ' ' : '');
},
props: function(data) {
if (!data) return null;
let url = base64toObjectUrl(data.val, data.mime);
return {
src: url,
title: data.name,
'data-width': data.width,
'data-height': data.height,
'data-name': data.name,
'data-size': data.val ? (data.val.length * 0.75) | 0 : 0,
'data-mime': data.mime
};
},
},
// Form - structured layout of elements.
FM: {
open: function(data) {
return '';
},
close: function(data) {
return '
';
}
},
// Row: logic grouping of elements
RW: {
open: function(data) {
return '';
},
close: function(data) {
return '
';
}
}
};
/**
* The main object which performs all the formatting actions.
* @class Drafty
* @constructor
*/
var Drafty = function() {}
// Take a string and defined earlier style spans, re-compose them into a tree where each leaf is
// a same-style (including unstyled) string. I.e. 'hello *bold _italic_* and ~more~ world' ->
// ('hello ', (b: 'bold ', (i: 'italic')), ' and ', (s: 'more'), ' world');
//
// This is needed in order to clear markup, i.e. 'hello *world*' -> 'hello world' and convert
// ranges from markup-ed offsets to plain text offsets.
function chunkify(line, start, end, spans) {
var chunks = [];
if (spans.length == 0) {
return [];
}
for (var i in spans) {
// Get the next chunk from the queue
var span = spans[i];
// Grab the initial unstyled chunk
if (span.start > start) {
chunks.push({
text: line.slice(start, span.start)
});
}
// Grab the styled chunk. It may include subchunks.
var chunk = {
type: span.type
};
var chld = chunkify(line, span.start + 1, span.end, span.children);
if (chld.length > 0) {
chunk.children = chld;
} else {
chunk.text = span.text;
}
chunks.push(chunk);
start = span.end + 1; // '+1' is to skip the formatting character
}
// Grab the remaining unstyled chunk, after the last span
if (start < end) {
chunks.push({
text: line.slice(start, end)
});
}
return chunks;
}
// Inverse of chunkify. Returns a tree of formatted spans.
function forEach(line, start, end, spans, formatter, context) {
let result = [];
// Process ranges calling formatter for each range.
for (let i = 0; i < spans.length; i++) {
let span = spans[i];
if (span.at < 0) {
// throw out non-visual spans.
continue;
}
// Add un-styled range before the styled span starts.
if (start < span.at) {
result.push(formatter.call(context, null, undefined, line.slice(start, span.at), result.length));
start = span.at;
}
// Get all spans which are within current span.
const subspans = [];
for (let si = i + 1; si < spans.length && spans[si].at < span.at + span.len; si++) {
subspans.push(spans[si]);
i = si;
}
const tag = HTML_TAGS[span.tp] || {}
result.push(formatter.call(context, span.tp, span.data,
tag.isVoid ? null : forEach(line, start, span.at + span.len, subspans, formatter, context),
result.length));
start = span.at + span.len;
}
// Add the last unformatted range.
if (start < end) {
result.push(formatter.call(context, null, undefined, line.slice(start, end), result.length));
}
return result;
}
// Detect starts and ends of formatting spans. Unformatted spans are
// ignored at this stage.
function spannify(original, re_start, re_end, type) {
let result = [];
let index = 0;
let line = original.slice(0); // make a copy;
while (line.length > 0) {
// match[0]; // match, like '*abc*'
// match[1]; // match captured in parenthesis, like 'abc'
// match['index']; // offset where the match started.
// Find the opening token.
let start = re_start.exec(line);
if (start == null) {
break;
}
// Because javascript RegExp does not support lookbehind, the actual offset may not point
// at the markup character. Find it in the matched string.
let start_offset = start['index'] + start[0].lastIndexOf(start[1]);
// Clip the processed part of the string.
line = line.slice(start_offset + 1);
// start_offset is an offset within the clipped string. Convert to original index.
start_offset += index;
// Index now point to the beginning of 'line' within the 'original' string.
index = start_offset + 1;
// Find the matching closing token.
let end = re_end ? re_end.exec(line) : null;
if (end == null) {
break;
}
let end_offset = end['index'] + end[0].indexOf(end[1]);
// Clip the processed part of the string.
line = line.slice(end_offset + 1);
// Update offsets
end_offset += index;
// Index now point to the beginning of 'line' within the 'original' string.
index = end_offset + 1;
result.push({
text: original.slice(start_offset + 1, end_offset),
children: [],
start: start_offset,
end: end_offset,
type: type
});
}
return result;
}
// Convert linear array or spans into a tree representation.
// Keep standalone and nested spans, throw away partially overlapping spans.
function toTree(spans) {
if (spans.length == 0) {
return [];
}
var tree = [spans[0]];
var last = spans[0];
for (var i = 1; i < spans.length; i++) {
// Keep spans which start after the end of the previous span or those which
// are complete within the previous span.
if (spans[i].start > last.end) {
// Span is completely outside of the previous span.
tree.push(spans[i]);
last = spans[i];
} else if (spans[i].end < last.end) {
// Span is fully inside of the previous span. Push to subnode.
last.children.push(spans[i]);
}
// Span could partially overlap, ignoring it as invalid.
}
// Recursively rearrange the subnodes.
for (var i in tree) {
tree[i].children = toTree(tree[i].children);
}
return tree;
}
// Get a list of entities from a text.
function extractEntities(line) {
var match;
var extracted = [];
ENTITY_TYPES.map(function(entity) {
while ((match = entity.re.exec(line)) !== null) {
extracted.push({
offset: match['index'],
len: match[0].length,
unique: match[0],
data: entity.pack(match[0]),
type: entity.name
});
}
});
if (extracted.length == 0) {
return extracted;
}
// Remove entities detected inside other entities, like #hashtag in a URL.
extracted.sort(function(a, b) {
return a.offset - b.offset;
});
var idx = -1;
extracted = extracted.filter(function(el) {
var result = (el.offset > idx);
idx = el.offset + el.len;
return result;
});
return extracted;
}
// Convert the chunks into format suitable for serialization.
function draftify(chunks, startAt) {
var plain = "";
var ranges = [];
for (var i in chunks) {
var chunk = chunks[i];
if (!chunk.text) {
var drafty = draftify(chunk.children, plain.length + startAt);
chunk.text = drafty.txt;
ranges = ranges.concat(drafty.fmt);
}
if (chunk.type) {
ranges.push({
at: plain.length + startAt,
len: chunk.text.length,
tp: chunk.type
});
}
plain += chunk.text;
}
return {
txt: plain,
fmt: ranges
};
}
// Splice two strings: insert second string into the first one at the given index
function splice(src, at, insert) {
return src.slice(0, at) + insert + src.slice(at);
}
/**
* Parse plain text into structured representation.
* @memberof Drafty
* @static
*
* @param {String} content plain-text content to parse.
* @return {Drafty} parsed object or null if the source is not plain text.
*/
Drafty.parse = function(content) {
// Make sure we are parsing strings only.
if (typeof content != 'string') {
return null;
}
// Split text into lines. It makes further processing easier.
var lines = content.split(/\r?\n/);
// Holds entities referenced from text
var entityMap = [];
var entityIndex = {};
// Processing lines one by one, hold intermediate result in blx.
var blx = [];
lines.map(function(line) {
var spans = [];
var entities = [];
// Find formatted spans in the string.
// Try to match each style.
INLINE_STYLES.map(function(style) {
// Each style could be matched multiple times.
spans = spans.concat(spannify(line, style.start, style.end, style.name));
});
var block;
if (spans.length == 0) {
block = {
txt: line
};
} else {
// Sort spans by style occurence early -> late
spans.sort(function(a, b) {
return a.start - b.start;
});
// Convert an array of possibly overlapping spans into a tree
spans = toTree(spans);
// Build a tree representation of the entire string, not
// just the formatted parts.
var chunks = chunkify(line, 0, line.length, spans);
var drafty = draftify(chunks, 0);
block = {
txt: drafty.txt,
fmt: drafty.fmt
};
}
// Extract entities from the cleaned up string.
entities = extractEntities(block.txt);
if (entities.length > 0) {
var ranges = [];
for (var i in entities) {
// {offset: match['index'], unique: match[0], len: match[0].length, data: ent.packer(), type: ent.name}
var entity = entities[i];
var index = entityIndex[entity.unique];
if (!index) {
index = entityMap.length;
entityIndex[entity.unique] = index;
entityMap.push({
tp: entity.type,
data: entity.data
});
}
ranges.push({
at: entity.offset,
len: entity.len,
key: index
});
}
block.ent = ranges;
}
blx.push(block);
});
var result = {
txt: ""
};
// Merge lines and save line breaks as BR inline formatting.
if (blx.length > 0) {
result.txt = blx[0].txt;
result.fmt = (blx[0].fmt || []).concat(blx[0].ent || []);
for (var i = 1; i < blx.length; i++) {
var block = blx[i];
var offset = result.txt.length + 1;
result.fmt.push({
tp: 'BR',
len: 1,
at: offset - 1
});
result.txt += " " + block.txt;
if (block.fmt) {
result.fmt = result.fmt.concat(block.fmt.map(function(s) {
s.at += offset;
return s;
}));
}
if (block.ent) {
result.fmt = result.fmt.concat(block.ent.map(function(s) {
s.at += offset;
return s;
}));
}
}
if (result.fmt.length == 0) {
delete result.fmt;
}
if (entityMap.length > 0) {
result.ent = entityMap;
}
}
return result;
}
/**
* Insert inline image into Drafty content.
* @memberof Drafty
* @static
*
* @param {Drafty} content object to add image to.
* @param {integer} at index where the object is inserted. The length of the image is always 1.
* @param {string} mime mime-type of the image, e.g. "image/png"
* @param {string} base64bits base64-encoded image content (or preview, if large image is attached)
* @param {integer} width width of the image
* @param {integer} height height of the image
* @param {string} fname file name suggestion for downloading the image.
* @param {integer} size size of the external file. Treat is as an untrusted hint.
* @param {string} refurl reference to the content. Could be null or undefined.
*
* @return {Drafty} updated content.
*/
Drafty.insertImage = function(content, at, mime, base64bits, width, height, fname, size, refurl) {
content = content || {
txt: " "
};
content.ent = content.ent || [];
content.fmt = content.fmt || [];
content.fmt.push({
at: at,
len: 1,
key: content.ent.length
});
content.ent.push({
tp: 'IM',
data: {
mime: mime,
val: base64bits,
width: width,
height: height,
name: fname,
ref: refurl,
size: size | 0
}
});
return content;
}
/**
* Append image to Drafty content.
* @memberof Drafty
* @static
*
* @param {Drafty} content object to add image to.
* @param {string} mime mime-type of the image, e.g. "image/png"
* @param {string} base64bits base64-encoded image content (or preview, if large image is attached)
* @param {integer} width width of the image
* @param {integer} height height of the image
* @param {string} fname file name suggestion for downloading the image.
* @param {integer} size size of the external file. Treat is as an untrusted hint.
* @param {string} refurl reference to the content. Could be null or undefined.
*
* @return {Drafty} updated content.
*/
Drafty.appendImage = function(content, mime, base64bits, width, height, fname, size, refurl) {
content = content || {
txt: ""
};
content.txt += " ";
return Drafty.insertImage(content, content.txt.length - 1, mime, base64bits, width, height, fname, size, refurl);
}
/**
* Attach file to Drafty content. Either as a blob or as a reference.
* @memberof Drafty
* @static
*
* @param {Drafty} content object to attach file to.
* @param {string} mime mime-type of the file, e.g. "image/png"
* @param {string} base64bits base64-encoded file content
* @param {string} fname file name suggestion for downloading.
* @param {integer} size size of the external file. Treat is as an untrusted hint.
* @param {string | Promise} refurl optional reference to the content.
*
* @return {Drafty} updated content.
*/
Drafty.attachFile = function(content, mime, base64bits, fname, size, refurl) {
content = content || {
txt: ""
};
content.ent = content.ent || [];
content.fmt = content.fmt || [];
content.fmt.push({
at: -1,
len: 0,
key: content.ent.length
});
let ex = {
tp: 'EX',
data: {
mime: mime,
val: base64bits,
name: fname,
ref: refurl,
size: size | 0
}
}
if (refurl instanceof Promise) {
ex.data.ref = refurl.then(
(url) => {
ex.data.ref = url;
},
(err) => {
/* catch the error, otherwise it will appear in the console. */
}
);
}
content.ent.push(ex);
return content;
}
/**
* Wraps content into an interactive form.
* @memberof Drafty
* @static
*
* @param {Drafty|string} content to wrap into a form.
* @param {number} at index where the forms starts.
* @param {number} len length of the form content.
*
* @return {Drafty} updated content.
*/
Drafty.wrapAsForm = function(content, at, len) {
if (typeof content == 'string') {
content = {
txt: content
};
}
content.fmt = content.fmt || [];
content.fmt.push({
at: at,
len: len,
tp: 'FM'
});
return content;
}
/**
* Insert clickable button into Drafty document.
* @memberof Drafty
* @static
*
* @param {Drafty|string} content is Drafty object to insert button to or a string to be used as button text.
* @param {number} at is location where the button is inserted.
* @param {number} len is the length of the text to be used as button title.
* @param {string} name of the button. Client should return it to the server when the button is clicked.
* @param {string} actionType is the type of the button, one of 'url' or 'pub'.
* @param {string} actionValue is the value to return on click:
* @param {string} refUrl is the URL to go to when the 'url' button is clicked.
*
* @return {Drafty} updated content.
*/
Drafty.insertButton = function(content, at, len, name, actionType, actionValue, refUrl) {
if (typeof content == 'string') {
content = {
txt: content
};
}
if (!content || !content.txt || content.txt.length < at + len) {
return null;
}
if (len <= 0 || ['url', 'pub'].indexOf(actionType) == -1) {
return null;
}
// Ensure refUrl is a string.
if (actionType == 'url' && !refUrl) {
return null;
}
refUrl = '' + refUrl;
content.ent = content.ent || [];
content.fmt = content.fmt || [];
content.fmt.push({
at: at,
len: len,
key: content.ent.length
});
content.ent.push({
tp: 'BN',
data: {
act: actionType,
val: actionValue,
ref: refUrl,
name: name
}
});
return content;
}
/**
* Append clickable button to Drafty document.
* @memberof Drafty
* @static
*
* @param {Drafty|string} content is Drafty object to insert button to or a string to be used as button text.
* @param {string} title is the text to be used as button title.
* @param {string} name of the button. Client should return it to the server when the button is clicked.
* @param {string} actionType is the type of the button, one of 'url' or 'pub'.
* @param {string} actionValue is the value to return on click:
* @param {string} refUrl is the URL to go to when the 'url' button is clicked.
*
* @return {Drafty} updated content.
*/
Drafty.appendButton = function(content, title, name, actionType, actionValue, refUrl) {
content = content || {
txt: ""
};
let at = content.txt.length;
content.txt += title;
return Drafty.insertButton(content, at, title.length, name, actionType, actionValue, refUrl);
}
/**
* Attach a generic JS object. The object is attached as a json string.
* Intended for representing a form response.
*
* @memberof Drafty
* @static
*
* @param {Drafty} content object to attach file to.
* @param {Object} data to convert to json string and attach.
*/
Drafty.attachJSON = function(content, data) {
content = content || {
txt: ""
};
content.ent = content.ent || [];
content.fmt = content.fmt || [];
content.fmt.push({
at: -1,
len: 0,
key: content.ent.length
});
content.ent.push({
tp: 'EX',
data: {
mime: JSON_MIME_TYPE,
val: data
}
});
return content;
}
Drafty.appendLineBreak = function(content) {
content = content || {
txt: ""
};
content.fmt = content.fmt || [];
content.fmt.push({
at: content.txt.length,
len: 1,
tp: 'BR'
});
content.txt += " ";
return content;
}
/**
* Given the structured representation of rich text, convert it to HTML.
* No attempt is made to strip pre-existing html markup.
* This is potentially unsafe because `content.txt` may contain malicious
* markup.
* @memberof Tinode.Drafty
* @static
*
* @param {drafy} content - structured representation of rich text.
*
* @return HTML-representation of content.
*/
Drafty.UNSAFE_toHTML = function(content) {
var {
txt,
fmt,
ent
} = content;
var markup = [];
if (fmt) {
for (let i in fmt) {
let range = fmt[i];
let tp = range.tp,
data;
if (!tp) {
let entity = ent[range.key | 0];
if (entity) {
tp = entity.tp;
data = entity.data;
}
}
if (DECORATORS[tp]) {
// Because we later sort in descending order, closing markup must come first.
// Otherwise zero-length objects will not be represented correctly.
markup.push({
idx: range.at + range.len,
len: -range.len,
what: DECORATORS[tp].close(data)
});
markup.push({
idx: range.at,
len: range.len,
what: DECORATORS[tp].open(data)
});
}
}
}
markup.sort(function(a, b) {
return b.idx == a.idx ? b.len - a.len : b.idx - a.idx; // in descending order
});
for (var i in markup) {
if (markup[i].what) {
txt = splice(txt, markup[i].idx, markup[i].what);
}
}
return txt;
}
/**
* Callback for applying custom formatting/transformation to a Drafty object.
* Called once for each syle span.
* @memberof Drafty
* @static
*
* @callback Formatter
* @param {string} style style code such as "ST" or "IM".
* @param {Object} data entity's data
* @param {Object} values possibly styled subspans contained in this style span.
* @param {number} index of the current element among its siblings.
*/
/**
* Transform Drafty using custom formatting.
* @memberof Drafty
* @static
*
* @param {Drafty} content - content to transform.
* @param {Formatter} formatter - callback which transforms individual elements
* @param {Object} context - context provided to formatter as 'this'.
*
* @return {Object} transformed object
*/
Drafty.format = function(content, formatter, context) {
let {
txt,
fmt,
ent
} = content;
// Assign default values.
txt = txt || "";
if (!Array.isArray(ent)) {
ent = [];
}
if (!Array.isArray(fmt)) {
// Handle special case when all values in fmt are 0 and fmt is skipped.
if (ent.length == 1) {
fmt = [{
at: 0,
len: 0,
key: 0
}];
} else {
return [txt];
}
}
let spans = [].concat(fmt);
// Zero values may have been stripped. Restore them.
// Also ensure indexes and lengths are sane.
spans.map(function(s) {
s.at = s.at || 0;
s.len = s.len || 0;
if (s.len < 0) {
s.len = 0;
}
if (s.at < -1) {
s.at = -1;
}
});
// Sort spans first by start index (asc) then by length (desc).
spans.sort(function(a, b) {
if (a.at - b.at == 0) {
return b.len - a.len; // longer one comes first (<0)
}
return a.at - b.at;
});
// Denormalize entities into spans. Create a copy of the objects to leave
// original Drafty object unchanged.
spans = spans.map((s) => {
let data;
let tp = s.tp;
if (!tp) {
s.key = s.key || 0;
if (ent[s.key]) {
data = ent[s.key].data;
tp = ent[s.key].tp;
}
}
// Type still not defined? Hide invalid element.
tp = tp || 'HD';
return {
tp: tp,
data: data,
at: s.at,
len: s.len
};
});
return forEach(txt, 0, txt.length, spans, formatter, context);
}
/**
* Given structured representation of rich text, convert it to plain text.
* @memberof Drafty
* @static
*
* @param {Drafty} content - content to convert to plain text.
*/
Drafty.toPlainText = function(content) {
return typeof content == 'string' ? content : content.txt;
}
/**
* Returns true if content has no markup and no entities.
* @memberof Drafty
* @static
*
* @param {Drafty} content - content to check for presence of markup.
* @returns true is content is plain text, false otherwise.
*/
Drafty.isPlainText = function(content) {
return typeof content == 'string' || !(content.fmt || content.ent);
}
/**
* Check if the drafty content has attachments.
* @memberof Drafty
* @static
*
* @param {Drafty} content - content to check for attachments.
* @returns true if there are attachments.
*/
Drafty.hasAttachments = function(content) {
if (content.ent && content.ent.length > 0) {
for (var i in content.ent) {
if (content.ent[i] && content.ent[i].tp == 'EX') {
return true;
}
}
}
return false;
}
/**
* Callback for applying custom formatting/transformation to a Drafty object.
* Called once for each syle span.
* @memberof Drafty
* @static
*
* @callback AttachmentCallback
* @param {Object} data attachment data
* @param {number} index attachment's index in `content.ent`.
*/
/**
* Enumerate attachments.
* @memberof Drafty
* @static
*
* @param {Drafty} content - drafty object to process for attachments.
* @param {AttachmentCallback} callback - callback to call for each attachment.
* @param {Object} content - value of "this" for callback.
*/
Drafty.attachments = function(content, callback, context) {
if (content.ent && content.ent.length > 0) {
for (var i in content.ent) {
if (content.ent[i] && content.ent[i].tp == 'EX') {
callback.call(context, content.ent[i].data, i);
}
}
}
}
/**
* Given the entity, get URL which can be used for downloading
* entity data.
* @memberof Drafty
* @static
*
* @param {Object} entity.data to get the URl from.
*/
Drafty.getDownloadUrl = function(entData) {
let url = null;
if (entData.mime != JSON_MIME_TYPE && entData.val) {
url = base64toObjectUrl(entData.val, entData.mime);
} else if (typeof entData.ref == 'string') {
url = entData.ref;
}
return url;
}
/**
* Check if the entity data is being uploaded to the server.
* @memberof Drafty
* @static
*
* @param {Object} entity.data to get the URl from.
* @returns {boolean} true if upload is in progress, false otherwise.
*/
Drafty.isUploading = function(entData) {
return entData.ref instanceof Promise;
}
/**
* Given the entity, get URL which can be used for previewing
* the entity.
* @memberof Drafty
* @static
*
* @param {Object} entity.data to get the URl from.
*
* @returns {string} url for previewing or null if no such url is available.
*/
Drafty.getPreviewUrl = function(entData) {
return entData.val ? base64toObjectUrl(entData.val, entData.mime) : null;
}
/**
* Get approximate size of the entity.
* @memberof Drafty
* @static
*
* @param {Object} entity.data to get the size for.
*/
Drafty.getEntitySize = function(entData) {
// Either size hint or length of value. The value is base64 encoded,
// the actual object size is smaller than the encoded length.
return entData.size ? entData.size : entData.val ? (entData.val.length * 0.75) | 0 : 0;
}
/**
* Get entity mime type.
* @memberof Drafty
* @static
*
* @param {Object} entity.data to get the type for.
*/
Drafty.getEntityMimeType = function(entData) {
return entData.mime || 'text/plain';
}
/**
* Get HTML tag for a given two-letter style name
* @memberof Drafty
* @static
*
* @param {string} style - two-letter style, like ST or LN
*
* @returns {string} tag name
*/
Drafty.tagName = function(style) {
return HTML_TAGS[style] ? HTML_TAGS[style].name : undefined;
}
/**
* For a given data bundle generate an object with HTML attributes,
* for instance, given {url: "http://www.example.com/"} return
* {href: "http://www.example.com/"}
* @memberof Drafty
* @static
*
* @param {string} style - tw-letter style to generate attributes for.
* @param {Object} data - data bundle to convert to attributes
*
* @returns {Object} object with HTML attributes.
*/
Drafty.attrValue = function(style, data) {
if (data && DECORATORS[style]) {
return DECORATORS[style].props(data);
}
return undefined;
}
/**
* Drafty MIME type.
* @memberof Drafty
* @static
*
* @returns {string} HTTP Content-Type "text/x-drafty".
*/
Drafty.getContentType = function() {
return 'text/x-drafty';
}
if (typeof module != 'undefined') {
module.exports = Drafty;
}
},{}],2:[function(require,module,exports){
(function (global){
/**
* @file SDK to connect to Tinode chat server.
* See
* https://github.com/tinode/webapp for real-life usage.
*
* @copyright 2015-2019 Tinode
* @summary Javascript bindings for Tinode.
* @license Apache 2.0
* @version 0.16
*
* @example
*
*
*
*
*
* ...
*
*
*/
'use strict';
// NOTE TO DEVELOPERS:
// Localizable strings should be double quoted "строка на другом языке",
// non-localizable strings should be single quoted 'non-localized'.
if (typeof require == 'function') {
if (typeof Drafty == 'undefined') {
var Drafty = require('./drafty.js');
}
var package_version = require('../version.json').version;
}
let WebSocketProvider;
if (typeof WebSocket != 'undefined') {
WebSocketProvider = WebSocket;
}
initForNonBrowserApp();
// Global constants
const PROTOCOL_VERSION = '0';
const VERSION = package_version || '0.16';
const LIBRARY = 'tinodejs/' + VERSION;
const TOPIC_NEW = 'new';
const TOPIC_ME = 'me';
const TOPIC_FND = 'fnd';
const TOPIC_SYS = 'sys';
const USER_NEW = 'new';
// Starting value of a locally-generated seqId used for pending messages.
const LOCAL_SEQID = 0xFFFFFFF;
const MESSAGE_STATUS_NONE = 0; // Status not assigned.
const MESSAGE_STATUS_QUEUED = 1; // Local ID assigned, in progress to be sent.
const MESSAGE_STATUS_SENDING = 2; // Transmission started.
const MESSAGE_STATUS_FAILED = 3; // At least one attempt was made to send the message.
const MESSAGE_STATUS_SENT = 4; // Delivered to the server.
const MESSAGE_STATUS_RECEIVED = 5; // Received by the client.
const MESSAGE_STATUS_READ = 6; // Read by the user.
const MESSAGE_STATUS_TO_ME = 7; // Message from another user.
// Error code to return in case of a network problem.
const NETWORK_ERROR = 503;
const NETWORK_ERROR_TEXT = "Connection failed";
// Reject unresolved futures after this many milliseconds.
const EXPIRE_PROMISES_TIMEOUT = 5000;
// Periodicity of garbage collection of unresolved futures.
const EXPIRE_PROMISES_PERIOD = 1000;
// Error code to return when user disconnected from server.
const NETWORK_USER = 418;
const NETWORK_USER_TEXT = "Disconnected by client";
// Utility functions
// Add brower missing function for non browser app, eg nodeJs
function initForNonBrowserApp() {
// Tinode requirement in native mode because react native doesn't provide Base64 method
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
if (typeof btoa == 'undefined') {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
global.btoa = function(input = '') {
let str = input;
let output = '';
for (let block = 0, charCode, i = 0, map = chars; str.charAt(i | 0) || (map = '=', i % 1); output += map.charAt(63 & block >> 8 - i % 1 * 8)) {
charCode = str.charCodeAt(i += 3 / 4);
if (charCode > 0xFF) {
throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
}
block = block << 8 | charCode;
}
return output;
};
}
if (typeof atob == 'undefined') {
global.atob = function(input = '') {
let str = input.replace(/=+$/, '');
let output = '';
if (str.length % 4 == 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (let bc = 0, bs = 0, buffer, i = 0; buffer = str.charAt(i++);
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
buffer = chars.indexOf(buffer);
}
return output;
};
}
if (typeof window == 'undefined') {
global.window = {
WebSocket: WebSocketProvider,
URL: {
createObjectURL: function() {
throw new Error("Unable to use window.URL in a non browser application");
}
}
}
}
}
// RFC3339 formater of Date
function rfc3339DateString(d) {
if (!d || d.getTime() == 0) {
return undefined;
}
function pad(val, sp) {
sp = sp || 2;
return '0'.repeat(sp - ('' + val).length) + val;
}
const millis = d.getUTCMilliseconds();
return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate()) +
'T' + pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds()) +
(millis ? '.' + pad(millis, 3) : '') + 'Z';
}
// btoa replacement. Stock btoa fails on on non-Latin1 strings.
function b64EncodeUnicode(str) {
// The encodeURIComponent percent-encodes UTF-8 string,
// then the percent encoding is converted into raw bytes which
// can be fed into btoa.
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
// Recursively merge src's own properties to dst.
// Ignore properties where ignore[property] is true.
// Array and Date objects are shallow-copied.
function mergeObj(dst, src, ignore) {
if (typeof src != 'object') {
if (src === Tinode.DEL_CHAR) {
return undefined;
}
if (src === undefined) {
return dst;
}
return src;
}
// JS is crazy: typeof null is 'object'.
if (src === null) {
return src;
}
// Handle Date
if (src instanceof Date) {
return (!dst || !(dst instanceof Date) || dst < src) ? src : dst;
}
// Access mode
if (src instanceof AccessMode) {
return new AccessMode(src);
}
// Handle Array
if (src instanceof Array) {
return src;
}
if (!dst || dst === Tinode.DEL_CHAR) {
dst = src.constructor();
}
for (let prop in src) {
if (src.hasOwnProperty(prop) &&
(!ignore || !ignore[prop]) &&
(prop != '_noForwarding')) {
dst[prop] = mergeObj(dst[prop], src[prop]);
}
}
return dst;
}
// Update object stored in a cache. Returns updated value.
function mergeToCache(cache, key, newval, ignore) {
cache[key] = mergeObj(cache[key], newval, ignore);
return cache[key];
}
// Basic cross-domain requester. Supports normal browsers and IE8+
function xdreq() {
let xdreq = null;
// Detect browser support for CORS
if ('withCredentials' in new XMLHttpRequest()) {
// Support for standard cross-domain requests
xdreq = new XMLHttpRequest();
} else if (typeof XDomainRequest != 'undefined') {
// IE-specific "CORS" with XDR
xdreq = new XDomainRequest();
} else {
// Browser without CORS support, don't know how to handle
throw new Error("Browser not supported");
}
return xdreq;
};
// JSON stringify helper - pre-processor for JSON.stringify
function jsonBuildHelper(key, val) {
if (val instanceof Date) {
// Convert javascript Date objects to rfc3339 strings
val = rfc3339DateString(val);
} else if (val instanceof AccessMode) {
val = val.jsonHelper();
} else if (val === undefined || val === null || val === false ||
(Array.isArray(val) && val.length == 0) ||
((typeof val == 'object') && (Object.keys(val).length == 0))) {
// strip out empty elements while serializing objects to JSON
return undefined;
}
return val;
};
// Strips all values from an object of they evaluate to false or if their name starts with '_'.
function simplify(obj) {
Object.keys(obj).forEach(function(key) {
if (key[0] == '_') {
// Strip fields like "obj._key".
delete obj[key];
} else if (!obj[key]) {
// Strip fields which evaluate to false.
delete obj[key];
} else if (Array.isArray(obj[key]) && obj[key].length == 0) {
// Strip empty arrays.
delete obj[key];
} else if (!obj[key]) {
// Strip fields which evaluate to false.
delete obj[key];
} else if (typeof obj[key] == 'object' && !(obj[key] instanceof Date)) {
simplify(obj[key]);
// Strip empty objects.
if (Object.getOwnPropertyNames(obj[key]).length == 0) {
delete obj[key];
}
}
});
return obj;
};
// Trim whitespace, strip empty and duplicate elements elements.
// If the result is an empty array, add a single element "\u2421" (Unicode Del character).
function normalizeArray(arr) {
let out = [];
if (Array.isArray(arr)) {
// Trim, throw away very short and empty tags.
for (let i = 0, l = arr.length; i < l; i++) {
let t = arr[i];
if (t) {
t = t.trim().toLowerCase();
if (t.length > 1) {
out.push(t);
}
}
}
out.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
}
if (out.length == 0) {
// Add single tag with a Unicode Del character, otherwise an ampty array
// is ambiguos. The Del tag will be stripped by the server.
out.push(Tinode.DEL_CHAR);
}
return out;
}
// Attempt to convert date strings to objects.
function jsonParseHelper(key, val) {
// Convert string timestamps with optional milliseconds to Date
// 2015-09-02T01:45:43[.123]Z
if (key === 'ts' && typeof val === 'string' &&
val.length >= 20 && val.length <= 24) {
let date = new Date(val);
if (date) {
return date;
}
} else if (key === 'acs' && typeof val === 'object') {
return new AccessMode(val);
}
return val;
};
// Trims very long strings (encoded images) to make logged packets more readable.
function jsonLoggerHelper(key, val) {
if (typeof val == 'string' && val.length > 128) {
return '<' + val.length + ', bytes: ' + val.substring(0, 12) + '...' + val.substring(val.length - 12) + '>';
}
return jsonBuildHelper(key, val);
};
// Parse browser user agent to extract browser name and version.
function getBrowserInfo(ua, product) {
ua = ua || '';
let reactnative = '';
// Check if this is a ReactNative app.
if (/reactnative/i.test(product)) {
reactnative = 'ReactNative; ';
}
// Then test for WebKit based browser.
ua = ua.replace(' (KHTML, like Gecko)', '');
let m = ua.match(/(AppleWebKit\/[.\d]+)/i);
let result;
if (m) {
// List of common strings, from more useful to less useful.
let priority = ['chrome', 'safari', 'mobile', 'version'];
let tmp = ua.substr(m.index + m[0].length).split(" ");
let tokens = [];
// Split Name/0.0.0 into Name and version 0.0.0
for (let i = 0; i < tmp.length; i++) {
let m2 = /([\w.]+)[\/]([\.\d]+)/.exec(tmp[i]);
if (m2) {
tokens.push([m2[1], m2[2], priority.findIndex(function(e) {
return (e == m2[1].toLowerCase());
})]);
}
}
// Sort by priority: more interesting is earlier than less interesting.
tokens.sort(function(a, b) {
let diff = a[2] - b[2];
return diff != 0 ? diff : b[0].length - a[0].length;
});
if (tokens.length > 0) {
// Return the least common browser string and version.
result = tokens[0][0] + '/' + tokens[0][1];
} else {
// Failed to ID the browser. Return the webkit version.
result = m[1];
}
// Test for MSIE.
} else if (/trident/i.test(ua)) {
m = /(?:\brv[ :]+([.\d]+))|(?:\bMSIE ([.\d]+))/g.exec(ua);
if (m) {
result = 'MSIE/' + (m[1] || m[2]);
} else {
result = 'MSIE/?';
}
// Test for Firefox.
} else if (/firefox/i.test(ua)) {
m = /Firefox\/([.\d]+)/g.exec(ua);
if (m) {
result = 'Firefox/' + m[1];
} else {
result = 'Firefox/?';
}
// Older Opera.
} else if (/presto/i.test(ua)) {
m = /Opera\/([.\d]+)/g.exec(ua);
if (m) {
result = 'Opera/' + m[1];
} else {
result = 'Opera/?';
}
} else {
// Failed to parse anything meaningfull. Try the last resort.
m = /([\w.]+)\/([.\d]+)/.exec(ua);
if (m) {
result = m[1] + '/' + m[2];
} else {
m = ua.split(' ');
result = m[0];
}
}
// Shorten the version to one dot 'a.bb.ccc.d -> a.bb' at most.
m = result.split('/');
if (m.length > 1) {
let v = m[1].split('.');
result = m[0] + '/' + v[0] + (v[1] ? '.' + v[1] : '');
}
return reactnative + result;
}
/**
* In-memory sorted cache of objects.
*
* @class CBuffer
* @memberof Tinode
* @protected
*
* @param {function} compare custom comparator of objects. Returns -1 if a < b, 0 if a == b, 1 otherwise.
*/
var CBuffer = function(compare) {
let buffer = [];
compare = compare || function(a, b) {
return a === b ? 0 : a < b ? -1 : 1;
};
function findNearest(elem, arr, exact) {
let start = 0;
let end = arr.length - 1;
let pivot = 0;
let diff = 0;
let found = false;
while (start <= end) {
pivot = (start + end) / 2 | 0;
diff = compare(arr[pivot], elem);
if (diff < 0) {
start = pivot + 1;
} else if (diff > 0) {
end = pivot - 1;
} else {
found = true;
break;
}
}
if (found) {
return pivot;
}
if (exact) {
return -1;
}
// Not exact - insertion point
return diff < 0 ? pivot + 1 : pivot;
}
// Insert element into a sorted array.
function insertSorted(elem, arr) {
arr.splice(findNearest(elem, arr, false), 0, elem);
return arr;
}
return {
/**
* Get an element at the given position.
* @memberof Tinode.CBuffer#
* @param {number} at - Position to fetch from.
* @returns {Object} Element at the given position or undefined
*/
getAt: function(at) {
return buffer[at];
},
/** Add new element(s) to the buffer. Variadic: takes one or more arguments. If an array is passed as a single
* argument, its elements are inserted individually.
* @memberof Tinode.CBuffer#
*
* @param {...Object|Array} - One or more objects to insert.
*/
put: function() {
let insert;
// inspect arguments: if array, insert its elements, if one or more non-array arguments, insert them one by one
if (arguments.length == 1 && Array.isArray(arguments[0])) {
insert = arguments[0];
} else {
insert = arguments;
}
for (let idx in insert) {
insertSorted(insert[idx], buffer);
}
},
/**
* Remove element at the given position.
* @memberof Tinode.CBuffer#
* @param {number} at - Position to delete at.
* @returns {Object} Element at the given position or undefined
*/
delAt: function(at) {
let r = buffer.splice(at, 1);
if (r && r.length > 0) {
return r[0];
}
return undefined;
},
/**
* Remove elements between two positions.
* @memberof Tinode.CBuffer#
* @param {number} since - Position to delete from (inclusive).
* @param {number} before - Position to delete to (exclusive).
*
* @returns {Array} array of removed elements (could be zero length).
*/
delRange: function(since, before) {
return buffer.splice(since, before - since);
},
/**
* Return the maximum number of element the buffer can hold
* @memberof Tinode.CBuffer#
* @return {number} The size of the buffer.
*/
size: function() {
return buffer.length;
},
/**
* Discard all elements and reset the buffer to the new size (maximum number of elements).
* @memberof Tinode.CBuffer#
* @param {number} newSize - New size of the buffer.
*/
reset: function(newSize) {
buffer = [];
},
/**
* Callback for iterating contents of buffer. See {@link Tinode.CBuffer#forEach}.
* @callback ForEachCallbackType
* @memberof Tinode.CBuffer#
* @param {Object} elem - Element of the buffer.
* @param {number} index - Index of the current element.
*/
/**
* Apply given function `callback` to all elements of the buffer.
* @memberof Tinode.CBuffer#
*
* @param {Tinode.ForEachCallbackType} callback - Function to call for each element.
* @param {integer} startIdx- Optional index to start iterating from (inclusive).
* @param {integer} beforeIdx - Optional index to stop iterating before (exclusive).
* @param {Object} context - calling context (i.e. value of 'this' in callback)
*/
forEach: function(callback, startIdx, beforeIdx, context) {
startIdx = startIdx | 0;
beforeIdx = beforeIdx || buffer.length;
for (let i = startIdx; i < beforeIdx; i++) {
callback.call(context, buffer[i], i);
}
},
/**
* Find element in buffer using buffer's comparison function.
* @memberof Tinode.CBuffer#
*
* @param {Object} elem - element to find.
* @param {boolean=} nearest - when true and exact match is not found, return the nearest element (insertion point).
* @returns {number} index of the element in the buffer or -1.
*/
find: function(elem, nearest) {
return findNearest(elem, buffer, !nearest);
}
}
}
// Helper function for creating an endpoint URL
function makeBaseUrl(host, protocol, apiKey) {
let url = null;
if (protocol === 'http' || protocol === 'https' || protocol === 'ws' || protocol === 'wss') {
url = protocol + '://';
url += host;
if (url.charAt(url.length - 1) !== '/') {
url += '/';
}
url += 'v' + PROTOCOL_VERSION + '/channels';
if (protocol === 'http' || protocol === 'https') {
// Long polling endpoint end with "lp", i.e.
// '/v0/channels/lp' vs just '/v0/channels' for ws
url += '/lp';
}
url += '?apikey=' + apiKey;
}
return url;
}
/**
* An abstraction for a websocket or a long polling connection.
*
* @class Connection
* @memberof Tinode
*
* @param {string} host_ - Host name and port number to connect to.
* @param {string} apiKey_ - API key generated by keygen
* @param {string} transport_ - Network transport to use, either `ws`/`wss` for websocket or `lp` for long polling.
* @param {boolean} secure_ - Use secure WebSocket (wss) if true.
* @param {boolean} autoreconnect_ - If connection is lost, try to reconnect automatically.
*/
var Connection = function(host_, apiKey_, transport_, secure_, autoreconnect_) {
let host = host_;
let secure = secure_;
let apiKey = apiKey_;
let autoreconnect = autoreconnect_;
// Settings for exponential backoff
const _BOFF_BASE = 2000; // 2000 milliseconds, minimum delay between reconnects
const _BOFF_MAX_ITER = 10; // Maximum delay between reconnects 2^10 * 2000 ~ 34 minutes
const _BOFF_JITTER = 0.3; // Add random delay
let _boffTimer = null;
let _boffIteration = 0;
let _boffClosed = false; // Indicator if the socket was manually closed - don't autoreconnect if true.
let log = (text, ...args) => {
if (this.logger) {
this.logger(text, ...args);
}
}
// Backoff implementation - reconnect after a timeout.
function boffReconnect() {
// Clear timer
clearTimeout(_boffTimer);
// Calculate when to fire the reconnect attempt
let timeout = _BOFF_BASE * (Math.pow(2, _boffIteration) * (1.0 + _BOFF_JITTER * Math.random()));
// Update iteration counter for future use
_boffIteration = (_boffIteration >= _BOFF_MAX_ITER ? _boffIteration : _boffIteration + 1);
if (this.onAutoreconnectIteration) {
this.onAutoreconnectIteration(timeout);
}
_boffTimer = setTimeout(() => {
log("Reconnecting, iter=" + _boffIteration + ", timeout=" + timeout);
// Maybe the socket was closed while we waited for the timer?
if (!_boffClosed) {
let prom = this.connect();
if (this.onAutoreconnectIteration) {
this.onAutoreconnectIteration(0, prom);
} else {
// Suppress error if it's not used.
prom.catch(() => {
/* do nothing */
});
}
} else if (this.onAutoreconnectIteration) {
this.onAutoreconnectIteration(-1);
}
}, timeout);
}
// Terminate auto-reconnect process.
function boffStop() {
clearTimeout(_boffTimer);
_boffTimer = null;
}
// Reset auto-reconnect iteration counter.
function boffReset() {
_boffIteration = 0;
}
// Initialization for Websocket
function init_ws(instance) {
let _socket = null;
/**
* Initiate a new connection
* @memberof Tinode.Connection#
* @param {string} host_ Host name to connect to; if null the old host name will be used.
* @param {boolean} force Force new connection even if one already exists.
* @return {Promise} Promise resolved/rejected when the connection call completes, resolution is called without parameters, rejection passes the {Error} as parameter.
*/
instance.connect = function(host_, force) {
_boffClosed = false;
if (_socket && _socket.readyState == _socket.OPEN) {
if (!force) {
return Promise.resolve();
}
_socket.close();
_socket = null;
}
if (host_) {
host = host_;
}
return new Promise(function(resolve, reject) {
const url = makeBaseUrl(host, secure ? 'wss' : 'ws', apiKey);
log("Connecting to: ", url);
const conn = new WebSocketProvider(url);
conn.onopen = function(evt) {
if (autoreconnect) {
boffStop();
}
if (instance.onOpen) {
instance.onOpen();
}
resolve();
}
conn.onclose = function(evt) {
_socket = null;
if (instance.onDisconnect) {
const code = _boffClosed ? NETWORK_USER : NETWORK_ERROR;
instance.onDisconnect(new Error(_boffClosed ? NETWORK_USER_TEXT : NETWORK_ERROR_TEXT +
' (' + code + ')'), code);
}
if (!_boffClosed && autoreconnect) {
boffReconnect.call(instance);
}
}
conn.onerror = function(err) {
reject(err);
}
conn.onmessage = function(evt) {
if (instance.onMessage) {
instance.onMessage(evt.data);
}
}
_socket = conn;
});
}
/**
* Try to restore a network connection, also reset backoff.
* @memberof Tinode.Connection#
*
* @param {boolean} force - reconnect even if there is a live connection already.
*/
instance.reconnect = function(force) {
boffStop();
instance.connect(null, force);
}
/**
* Terminate the network connection
* @memberof Tinode.Connection#
*/
instance.disconnect = function() {
_boffClosed = true;
if (!_socket) {
return;
}
boffStop();
_socket.close();
_socket = null;
}
/**
* Send a string to the server.
* @memberof Tinode.Connection#
*
* @param {string} msg - String to send.
* @throws Throws an exception if the underlying connection is not live.
*/
instance.sendText = function(msg) {
if (_socket && (_socket.readyState == _socket.OPEN)) {
_socket.send(msg);
} else {
throw new Error("Websocket is not connected");
}
};
/**
* Check if socket is alive.
* @memberof Tinode.Connection#
* @returns {boolean} true if connection is live, false otherwise
*/
instance.isConnected = function() {
return (_socket && (_socket.readyState == _socket.OPEN));
}
/**
* Get the name of the current network transport.
* @memberof Tinode.Connection#
* @returns {string} name of the transport such as 'ws' or 'lp'.
*/
instance.transport = function() {
return 'ws';
}
/**
* Send network probe to check if connection is indeed live.
* @memberof Tinode.Connection#
*/
instance.probe = function() {
instance.sendText('1');
}
}
// Initialization for long polling.
function init_lp(instance) {
const XDR_UNSENT = 0; // Client has been created. open() not called yet.
const XDR_OPENED = 1; // open() has been called.
const XDR_HEADERS_RECEIVED = 2; // send() has been called, and headers and status are available.
const XDR_LOADING = 3; // Downloading; responseText holds partial data.
const XDR_DONE = 4; // The operation is complete.
// Fully composed endpoint URL, with API key & SID
let _lpURL = null;
let _poller = null;
let _sender = null;
function lp_sender(url_) {
let sender = xdreq();
sender.onreadystatechange = function(evt) {
if (sender.readyState == XDR_DONE && sender.status >= 400) {
// Some sort of error response
throw new Error("LP sender failed, " + sender.status);
}
}
sender.open('POST', url_, true);
return sender;
}
function lp_poller(url_, resolve, reject) {
let poller = xdreq();
let promiseCompleted = false;
poller.onreadystatechange = function(evt) {
if (poller.readyState == XDR_DONE) {
if (poller.status == 201) { // 201 == HTTP.Created, get SID
let pkt = JSON.parse(poller.responseText, jsonParseHelper);
_lpURL = url_ + '&sid=' + pkt.ctrl.params.sid
poller = lp_poller(_lpURL);
poller.send(null)
if (instance.onOpen) {
instance.onOpen();
}
if (resolve) {
promiseCompleted = true;
resolve();
}
if (autoreconnect) {
boffStop();
}
} else if (poller.status < 400) { // 400 = HTTP.BadRequest
if (instance.onMessage) {
instance.onMessage(poller.responseText)
}
poller = lp_poller(_lpURL);
poller.send(null);
} else {
// Don't throw an error here, gracefully handle server errors
if (reject && !promiseCompleted) {
promiseCompleted = true;
reject(poller.responseText);
}
if (instance.onMessage && poller.responseText) {
instance.onMessage(poller.responseText);
}
if (instance.onDisconnect) {
const code = poller.status || (_boffClosed ? NETWORK_USER : NETWORK_ERROR);
const text = poller.responseText || (_boffClosed ? NETWORK_USER_TEXT : NETWORK_ERROR_TEXT);
instance.onDisconnect(new Error(text + ' (' + code + ')'), code);
}
// Polling has stopped. Indicate it by setting poller to null.
poller = null;
if (!_boffClosed && autoreconnect) {
boffReconnect.call(instance);
}
}
}
}
poller.open('GET', url_, true);
return poller;
}
instance.connect = function(host_, force) {
_boffClosed = false;
if (_poller) {
if (!force) {
return Promise.resolve();
}
_poller.onreadystatechange = undefined;
_poller.abort();
_poller = null;
}
if (host_) {
host = host_;
}
return new Promise(function(resolve, reject) {
const url = makeBaseUrl(host, secure ? 'https' : 'http', apiKey);
log("Connecting to: ", url);
_poller = lp_poller(url, resolve, reject);
_poller.send(null)
}).catch(function() {
// Catch an error and do nothing.
});
};
instance.reconnect = function(force) {
boffStop();
instance.connect(null, force);
};
instance.disconnect = function() {
_boffClosed = true;
boffStop();
if (_sender) {
_sender.onreadystatechange = undefined;
_sender.abort();
_sender = null;
}
if (_poller) {
_poller.onreadystatechange = undefined;
_poller.abort();
_poller = null;
}
if (instance.onDisconnect) {
instance.onDisconnect(new Error(NETWORK_USER_TEXT + ' (' + NETWORK_USER + ')'), NETWORK_USER);
}
// Ensure it's reconstructed
_lpURL = null;
}
instance.sendText = function(msg) {
_sender = lp_sender(_lpURL);
if (_sender && (_sender.readyState == 1)) { // 1 == OPENED
_sender.send(msg);
} else {
throw new Error("Long poller failed to connect");
}
};
instance.isConnected = function() {
return (_poller && true);
}
instance.transport = function() {
return 'lp';
}
instance.probe = function() {
instance.sendText('1');
}
}
if (transport_ === 'lp') {
// explicit request to use long polling
init_lp(this);
} else if (transport_ === 'ws') {
// explicit request to use web socket
// if websockets are not available, horrible things will happen
init_ws(this);
} else {
// Default transport selection
if (typeof window != 'object' || !window['WebSocket']) {
// The browser has no websockets
init_lp(this);
} else {
// Using web sockets -- default.
init_ws(this);
}
}
/**
* Reset autoreconnect counter to zero.
* @memberof Tinode.Connection#
*/
this.backoffReset = function() {
boffReset();
}
// Callbacks:
/**
* A callback to pass incoming messages to. See {@link Tinode.Connection#onMessage}.
* @callback Tinode.Connection.OnMessage
* @memberof Tinode.Connection
* @param {string} message - Message to process.
*/
/**
* A callback to pass incoming messages to.
* @type {Tinode.Connection.OnMessage}
* @memberof Tinode.Connection#
*/
this.onMessage = undefined;
/**
* A callback for reporting a dropped connection.
* @type {function}
* @memberof Tinode.Connection#
*/
this.onDisconnect = undefined;
/**
* A callback called when the connection is ready to be used for sending. For websockets it's socket open,
* for long polling it's readyState=1 (OPENED)
* @type {function}
* @memberof Tinode.Connection#
*/
this.onOpen = undefined;
/**
* A callback to notify of reconnection attempts. See {@link Tinode.Connection#onAutoreconnectIteration}.
* @memberof Tinode.Connection
* @callback AutoreconnectIterationType
* @param {string} timeout - time till the next reconnect attempt in milliseconds. -1 means reconnect was skipped.
* @param {Promise} promise resolved or rejected when the reconnect attemp completes.
*
*/
/**
* A callback to inform when the next attampt to reconnect will happen and to receive connection promise.
* @memberof Tinode.Connection#
* @type {Tinode.Connection.AutoreconnectIterationType}
*/
this.onAutoreconnectIteration = undefined;
/**
* A callback to log events from Connection. See {@link Tinode.Connection#logger}.
* @memberof Tinode.Connection
* @callback LoggerCallbackType
* @param {string} event - Event to log.
*/
/**
* A callback to report logging events.
* @memberof Tinode.Connection#
* @type {Tinode.Connection.LoggerCallbackType}
*/
this.logger = undefined;
};
/**
* @class Tinode
*
* @param {string} appname_ - Name of the caliing application to be reported in User Agent.
* @param {string} host_ - Host name and port number to connect to.
* @param {string} apiKey_ - API key generated by keygen
* @param {string} transport_ - See {@link Tinode.Connection#transport}.
* @param {boolean} secure_ - Use Secure WebSocket if true.
* @param {string} platform_ - Optional platform identifier, one of "ios", "web", "android".
*/
var Tinode = function(appname_, host_, apiKey_, transport_, secure_, platform_) {
// Client-provided application name, format /
if (appname_) {
this._appName = appname_;
} else {
this._appName = "Undefined";
}
// API Key.
this._apiKey = apiKey_;
// Name and version of the browser.
this._browser = '';
this._platform = platform_;
this._hwos = 'undefined';
this._humanLanguage = 'xx';
// Underlying OS.
if (typeof navigator != 'undefined') {
this._browser = getBrowserInfo(navigator.userAgent, navigator.product);
this._hwos = navigator.platform;
this._humanLanguage = navigator.language || 'en-US';
}
// Logging to console enabled
this._loggingEnabled = false;
// When logging, trip long strings (base64-encoded images) for readability
this._trimLongStrings = false;
// UID of the currently authenticated user.
this._myUID = null;
// Status of connection: authenticated or not.
this._authenticated = false;
// Login used in the last successful basic authentication
this._login = null;
// Token which can be used for login instead of login/password.
this._authToken = null;
// Counter of received packets
this._inPacketCount = 0;
// Counter for generating unique message IDs
this._messageId = Math.floor((Math.random() * 0xFFFF) + 0xFFFF);
// Information about the server, if connected
this._serverInfo = null;
// Push notification token. Called deviceToken for consistency with the Android SDK.
this._deviceToken = null;
// Cache of pending promises by message id.
this._pendingPromises = {};
/** A connection object, see {@link Tinode.Connection}. */
this._connection = new Connection(host_, apiKey_, transport_, secure_, true);
// Console logger. Babel somehow fails to parse '...rest' parameter.
this.logger = (str, ...args) => {
if (this._loggingEnabled) {
const d = new Date()
const dateString = ('0' + d.getUTCHours()).slice(-2) + ':' +
('0' + d.getUTCMinutes()).slice(-2) + ':' +
('0' + d.getUTCSeconds()).slice(-2) + ':' +
('0' + d.getUTCMilliseconds()).slice(-3);
console.log('[' + dateString + ']', str, args.join(' '));
}
}
this._connection.logger = this.logger;
// Tinode's cache of objects
this._cache = {};
const cachePut = this.cachePut = (type, name, obj) => {
this._cache[type + ':' + name] = obj;
}
const cacheGet = this.cacheGet = (type, name) => {
return this._cache[type + ':' + name];
}
const cacheDel = this.cacheDel = (type, name) => {
delete this._cache[type + ':' + name];
}
// Enumerate all items in cache, call func for each item.
// Enumeration stops if func returns true.
const cacheMap = this.cacheMap = (func, context) => {
for (let idx in this._cache) {
if (func(this._cache[idx], idx, context)) {
break;
}
}
}
// Make limited cache management available to topic.
// Caching user.public only. Everything else is per-topic.
this.attachCacheToTopic = (topic) => {
topic._tinode = this;
topic._cacheGetUser = (uid) => {
const pub = cacheGet('user', uid);
if (pub) {
return {
user: uid,
public: mergeObj({}, pub)
};
}
return undefined;
};
topic._cachePutUser = (uid, user) => {
return cachePut('user', uid, mergeObj({}, user.public));
};
topic._cacheDelUser = (uid) => {
return cacheDel('user', uid);
};
topic._cachePutSelf = () => {
return cachePut('topic', topic.name, topic);
}
topic._cacheDelSelf = () => {
return cacheDel('topic', topic.name);
}
}
// Resolve or reject a pending promise.
// Unresolved promises are stored in _pendingPromises.
const execPromise = (id, code, onOK, errorText) => {
const callbacks = this._pendingPromises[id];
if (callbacks) {
delete this._pendingPromises[id];
if (code >= 200 && code < 400) {
if (callbacks.resolve) {
callbacks.resolve(onOK);
}
} else if (callbacks.reject) {
callbacks.reject(new Error(errorText + " (" + code + ")"));
}
}
}
// Generator of default promises for sent packets.
const makePromise = (id) => {
let promise = null;
if (id) {
promise = new Promise((resolve, reject) => {
// Stored callbacks will be called when the response packet with this Id arrives
this._pendingPromises[id] = {
'resolve': resolve,
'reject': reject,
'ts': new Date()
};
})
}
return promise;
}
// Reject promises which have not been resolved for too long.
const expirePromises = setInterval(() => {
const err = new Error("Timeout (504)");
const expires = new Date(new Date().getTime() - EXPIRE_PROMISES_TIMEOUT);
for (let id in this._pendingPromises) {
let callbacks = this._pendingPromises[id];
if (callbacks && callbacks.ts < expires) {
this.logger("Promise expired", id);
delete this._pendingPromises[id];
if (callbacks.reject) {
callbacks.reject(err);
}
}
}
}, EXPIRE_PROMISES_PERIOD);
// Generates unique message IDs
const getNextUniqueId = this.getNextUniqueId = () => {
return (this._messageId != 0) ? '' + this._messageId++ : undefined;
}
// Get User Agent string
const getUserAgent = () => {
return this._appName + ' (' + (this._browser ? this._browser + '; ' : '') + this._hwos + '); ' + LIBRARY;
}
// Generator of packets stubs
this.initPacket = (type, topic) => {
switch (type) {
case 'hi':
return {
'hi': {
'id': getNextUniqueId(),
'ver': VERSION,
'ua': getUserAgent(),
'dev': this._deviceToken,
'lang': this._humanLanguage,
'platf': this._platform
}
};
case 'acc':
return {
'acc': {
'id': getNextUniqueId(),
'user': null,
'scheme': null,
'secret': null,
'login': false,
'tags': null,
'desc': {},
'cred': {}
}
};
case 'login':
return {
'login': {
'id': getNextUniqueId(),
'scheme': null,
'secret': null
}
};
case 'sub':
return {
'sub': {
'id': getNextUniqueId(),
'topic': topic,
'set': {},
'get': {}
}
};
case 'leave':
return {
'leave': {
'id': getNextUniqueId(),
'topic': topic,
'unsub': false
}
};
case 'pub':
return {
'pub': {
'id': getNextUniqueId(),
'topic': topic,
'noecho': false,
'head': null,
'content': {}
}
};
case 'get':
return {
'get': {
'id': getNextUniqueId(),
'topic': topic,
'what': null, // data, sub, desc, space separated list; unknown strings are ignored
'desc': {},
'sub': {},
'data': {}
}
};
case 'set':
return {
'set': {
'id': getNextUniqueId(),
'topic': topic,
'desc': {},
'sub': {},
'tags': []
}
};
case 'del':
return {
'del': {
'id': getNextUniqueId(),
'topic': topic,
'what': null,
'delseq': null,
'user': null,
'hard': false
}
};
case 'note':
return {
'note': {
// no id by design
'topic': topic,
'what': null, // one of "recv", "read", "kp"
'seq': undefined // the server-side message id aknowledged as received or read
}
};
default:
throw new Error("Unknown packet type requested: " + type);
}
}
// Send a packet. If packet id is provided return a promise.
this.send = (pkt, id) => {
let promise;
if (id) {
promise = makePromise(id);
}
pkt = simplify(pkt);
let msg = JSON.stringify(pkt);
this.logger("out: " + (this._trimLongStrings ? JSON.stringify(pkt, jsonLoggerHelper) : msg));
try {
this._connection.sendText(msg);
} catch (err) {
// If sendText throws, wrap the error in a promise or rethrow.
if (id) {
execPromise(id, NETWORK_ERROR, null, err.message);
} else {
throw err;
}
}
return promise;
}
// On successful login save server-provided data.
this.loginSuccessful = (ctrl) => {
if (!ctrl.params || !ctrl.params.user) {
return;
}
// This is a response to a successful login,
// extract UID and security token, save it in Tinode module
this._myUID = ctrl.params.user;
this._authenticated = (ctrl && ctrl.code >= 200 && ctrl.code < 300);
if (ctrl.params && ctrl.params.token && ctrl.params.expires) {
this._authToken = {
token: ctrl.params.token,
expires: new Date(ctrl.params.expires)
};
} else {
this._authToken = null;
}
if (this.onLogin) {
this.onLogin(ctrl.code, ctrl.text);
}
}
// The main message dispatcher.
this._connection.onMessage = (data) => {
// Skip empty response. This happens when LP times out.
if (!data) return;
this._inPacketCount++;
// Send raw message to listener
if (this.onRawMessage) {
this.onRawMessage(data);
}
if (data === '0') {
// Server response to a network probe.
if (this.onNetworkProbe) {
this.onNetworkProbe();
}
// No processing is necessary.
return;
}
let pkt = JSON.parse(data, jsonParseHelper);
if (!pkt) {
this.logger("in: " + data);
this.logger("ERROR: failed to parse data");
} else {
this.logger("in: " + (this._trimLongStrings ? JSON.stringify(pkt, jsonLoggerHelper) : data));
// Send complete packet to listener
if (this.onMessage) {
this.onMessage(pkt);
}
if (pkt.ctrl) {
// Handling {ctrl} message
if (this.onCtrlMessage) {
this.onCtrlMessage(pkt.ctrl);
}
// Resolve or reject a pending promise, if any
if (pkt.ctrl.id) {
execPromise(pkt.ctrl.id, pkt.ctrl.code, pkt.ctrl, pkt.ctrl.text);
}
setTimeout(() => {
if (pkt.ctrl.code == 205 && pkt.ctrl.text == 'evicted') {
// User evicted from topic.
const topic = cacheGet('topic', pkt.ctrl.topic);
if (topic) {
topic._resetSub();
}
} else if (pkt.ctrl.params && pkt.ctrl.params.what == 'data') {
// All messages received: "params":{"count":11,"what":"data"},
const topic = cacheGet('topic', pkt.ctrl.topic);
if (topic) {
topic._allMessagesReceived(pkt.ctrl.params.count);
}
} else if (pkt.ctrl.params && pkt.ctrl.params.what == 'sub') {
// The topic has no subscriptions.
const topic = cacheGet('topic', pkt.ctrl.topic);
if (topic) {
// Trigger topic.onSubsUpdated.
topic._processMetaSub([]);
}
}
}, 0);
} else {
setTimeout(() => {
if (pkt.meta) {
// Handling a {meta} message.
// Preferred API: Route meta to topic, if one is registered
const topic = cacheGet('topic', pkt.meta.topic);
if (topic) {
topic._routeMeta(pkt.meta);
}
if (pkt.meta.id) {
execPromise(pkt.meta.id, 200, pkt.meta, 'META');
}
// Secondary API: callback
if (this.onMetaMessage) {
this.onMetaMessage(pkt.meta);
}
} else if (pkt.data) {
// Handling {data} message
// Preferred API: Route data to topic, if one is registered
const topic = cacheGet('topic', pkt.data.topic);
if (topic) {
topic._routeData(pkt.data);
}
// Secondary API: Call callback
if (this.onDataMessage) {
this.onDataMessage(pkt.data);
}
} else if (pkt.pres) {
// Handling {pres} message
// Preferred API: Route presence to topic, if one is registered
const topic = cacheGet('topic', pkt.pres.topic);
if (topic) {
topic._routePres(pkt.pres);
}
// Secondary API - callback
if (this.onPresMessage) {
this.onPresMessage(pkt.pres);
}
} else if (pkt.info) {
// {info} message - read/received notifications and key presses
// Preferred API: Route {info}} to topic, if one is registered
const topic = cacheGet('topic', pkt.info.topic);
if (topic) {
topic._routeInfo(pkt.info);
}
// Secondary API - callback
if (this.onInfoMessage) {
this.onInfoMessage(pkt.info);
}
} else {
this.logger("ERROR: Unknown packet received.");
}
}, 0);
}
}
}
// Ready to start sending.
this._connection.onOpen = () => {
this.hello();
}
// Wrapper for the reconnect iterator callback.
this._connection.onAutoreconnectIteration = (timeout, promise) => {
if (this.onAutoreconnectIteration) {
this.onAutoreconnectIteration(timeout, promise);
}
}
this._connection.onDisconnect = (err, code) => {
this._inPacketCount = 0;
this._serverInfo = null;
this._authenticated = false;
// Mark all topics as unsubscribed
cacheMap((obj, key) => {
if (key.lastIndexOf('topic:', 0) === 0) {
obj._resetSub();
}
});
// Reject all pending promises
for (let key in this._pendingPromises) {
let callbacks = this._pendingPromises[key];
if (callbacks && callbacks.reject) {
callbacks.reject(err);
}
}
this._pendingPromises = {};
if (this.onDisconnect) {
this.onDisconnect(err);
}
}
};
// Static methods.
/**
* Helper method to package account credential.
* @memberof Tinode
* @static
*
* @param {String|Object} meth - validation method or object with validation data.
* @param {String=} val - validation value (e.g. email or phone number).
* @param {Object=} params - validation parameters.
* @param {String=} resp - validation response.
*
* @returns {Array} array with a single credentail or null if no valid credentials were given.
*/
Tinode.credential = function(meth, val, params, resp) {
if (typeof meth == 'object') {
({
val,
params,
resp,
meth
} = meth);
}
if (meth && (val || resp)) {
return [{
'meth': meth,
'val': val,
'resp': resp,
'params': params
}];
}
return null;
};
/**
* Determine topic type from topic's name: grp, p2p, me, fnd.
* @memberof Tinode
* @static
*
* @param {string} name - Name of the topic to test.
* @returns {string} One of 'me' , 'grp' , 'p2p' or undefined .
*/
Tinode.topicType = function(name) {
const types = {
'me': 'me',
'fnd': 'fnd',
'grp': 'grp',
'new': 'grp',
'usr': 'p2p',
'sys': 'sys'
};
return types[(typeof name == 'string') ? name.substring(0, 3) : 'xxx'];
};
/**
* Check if the topic name is a name of a new topic.
* @memberof Tinode
* @static
*
* @param {string} name - topic name to check.
* @returns {boolean} true if the name is a name of a new topic.
*/
Tinode.isNewGroupTopicName = function(name) {
return (typeof name == 'string') && name.substring(0, 3) == TOPIC_NEW;
};
/**
* Return information about the current version of this Tinode client library.
* @memberof Tinode
* @static
*
* @returns {string} semantic version of the library, e.g. '0.15.5-rc1'.
*/
Tinode.getVersion = function() {
return VERSION;
};
/**
* To use for non browser app, allow to specify WebSocket provider
* @param provider webSocket provider ex: for nodeJS require('ws')
* @memberof Tinode
* @static
*
*/
Tinode.setWebSocketProvider = function(provider) {
WebSocketProvider = provider;
};
/**
* Return information about the current name and version of this Tinode library.
* @memberof Tinode
* @static
*
* @returns {string} the name of the library and it's version.
*/
Tinode.getLibrary = function() {
return LIBRARY;
};
// Exported constants
Tinode.MESSAGE_STATUS_NONE = MESSAGE_STATUS_NONE;
Tinode.MESSAGE_STATUS_QUEUED = MESSAGE_STATUS_QUEUED;
Tinode.MESSAGE_STATUS_SENDING = MESSAGE_STATUS_SENDING;
Tinode.MESSAGE_STATUS_FAILED = MESSAGE_STATUS_FAILED;
Tinode.MESSAGE_STATUS_SENT = MESSAGE_STATUS_SENT;
Tinode.MESSAGE_STATUS_RECEIVED = MESSAGE_STATUS_RECEIVED;
Tinode.MESSAGE_STATUS_READ = MESSAGE_STATUS_READ;
Tinode.MESSAGE_STATUS_TO_ME = MESSAGE_STATUS_TO_ME;
// Unicode [del] symbol.
Tinode.DEL_CHAR = '\u2421';
/**
* Check if the given string reprsents NULL value.
* @memberof Tinode
* @static
*
* @param {String} str - string to check for null value.
*
* @returns {boolean} true if string is null, false otherwise.
*/
Tinode.isNullValue = function(str) {
return str === Tinode.DEL_CHAR;
};
// Public methods;
Tinode.prototype = {
/**
* Connect to the server.
* @memberof Tinode#
*
* @param {String} host_ - name of the host to connect to.
*
* @return {Promise} Promise resolved/rejected when the connection call completes:
* resolve() is called without parameters, reject() receives the Error as a single parameter.
*/
connect: function(host_) {
return this._connection.connect(host_);
},
/**
* Attempt to reconnect to the server immediately.
* @memberof Tinode#
*
* @param {String} force - reconnect even if there is a connection already.
*/
reconnect: function(force) {
this._connection.reconnect(force);
},
/**
* Disconnect from the server.
* @memberof Tinode#
*/
disconnect: function() {
this._connection.disconnect();
},
/**
* Send a network probe message to make sure the connection is alive.
* @memberof Tinode#
*/
networkProbe: function() {
this._connection.probe();
},
/**
* Check for live connection to server.
* @memberof Tinode#
*
* @returns {Boolean} true if there is a live connection, false otherwise.
*/
isConnected: function() {
return this._connection.isConnected();
},
/**
* Check if connection is authenticated (last login was successful).
* @memberof Tinode#
* @returns {boolean} true if authenticated, false otherwise.
*/
isAuthenticated: function() {
return this._authenticated;
},
/**
* @typedef AccountParams
* @memberof Tinode
* @type Object
* @property {Tinode.DefAcs=} defacs - Default access parameters for user's me topic.
* @property {Object=} public - Public application-defined data exposed on me topic.
* @property {Object=} private - Private application-defined data accessible on me topic.
* @property {Array} tags - array of string tags for user discovery.
* @property {string=} token - authentication token to use.
*/
/**
* @typedef DefAcs
* @memberof Tinode
* @type Object
* @property {string=} auth - Access mode for me for authenticated users.
* @property {string=} anon - Access mode for me anonymous users.
*/
/**
* Create or update an account.
* @memberof Tinode#
*
* @param {String} uid - User id to update
* @param {String} scheme - Authentication scheme; "basic" and "anonymous" are the currently supported schemes.
* @param {String} secret - Authentication secret, assumed to be already base64 encoded.
* @param {Boolean=} login - Use new account to authenticate current session
* @param {Tinode.AccountParams=} params - User data to pass to the server.
*/
account: function(uid, scheme, secret, login, params) {
const pkt = this.initPacket('acc');
pkt.acc.user = uid;
pkt.acc.scheme = scheme;
pkt.acc.secret = secret;
// Log in to the new account using selected scheme
pkt.acc.login = login;
if (params) {
pkt.acc.desc.defacs = params.defacs;
pkt.acc.desc.public = params.public;
pkt.acc.desc.private = params.private;
pkt.acc.tags = params.tags;
pkt.acc.cred = params.cred;
pkt.acc.token = params.token;
}
return this.send(pkt, pkt.acc.id);
},
/**
* Create a new user. Wrapper for {@link Tinode#account}.
* @memberof Tinode#
*
* @param {String} scheme - Authentication scheme; "basic" is the only currently supported scheme.
* @param {String} secret - Authentication.
* @param {Boolean=} login - Use new account to authenticate current session
* @param {Tinode.AccountParams=} params - User data to pass to the server.
*
* @returns {Promise} Promise which will be resolved/rejected when server reply is received.
*/
createAccount: function(scheme, secret, login, params) {
let promise = this.account(USER_NEW, scheme, secret, login, params);
if (login) {
promise = promise.then((ctrl) => {
this.loginSuccessful(ctrl);
return ctrl;
});
}
return promise;
},
/**
* Create user with 'basic' authentication scheme and immediately
* use it for authentication. Wrapper for {@link Tinode#account}.
* @memberof Tinode#
*
* @param {string} username - Login to use for the new account.
* @param {string} password - User's password.
* @param {Tinode.AccountParams=} params - User data to pass to the server.
*
* @returns {Promise} Promise which will be resolved/rejected when server reply is received.
*/
createAccountBasic: function(username, password, params) {
// Make sure we are not using 'null' or 'undefined';
username = username || '';
password = password || '';
return this.createAccount('basic',
b64EncodeUnicode(username + ':' + password), true, params);
},
/**
* Update user's credentials for 'basic' authentication scheme. Wrapper for {@link Tinode#account}.
* @memberof Tinode#
*
* @param {string} uid - User ID to update.
* @param {string} username - Login to use for the new account.
* @param {string} password - User's password.
* @param {Tinode.AccountParams=} params - data to pass to the server.
*
* @returns {Promise} Promise which will be resolved/rejected when server reply is received.
*/
updateAccountBasic: function(uid, username, password, params) {
// Make sure we are not using 'null' or 'undefined';
username = username || '';
password = password || '';
return this.account(uid, 'basic',
b64EncodeUnicode(username + ':' + password), false, params);
},
/**
* Send handshake to the server.
* @memberof Tinode#
*
* @returns {Promise} Promise which will be resolved/rejected when server reply is received.
*/
hello: function() {
const pkt = this.initPacket('hi');
return this.send(pkt, pkt.hi.id)
.then((ctrl) => {
// Reset backoff counter on successful connection.
this._connection.backoffReset();
// Server response contains server protocol version, build, constraints,
// session ID for long polling. Save them.
if (ctrl.params) {
this._serverInfo = ctrl.params;
}
if (this.onConnect) {
this.onConnect();
}
return ctrl;
}).catch((err) => {
this._connection.reconnect(true);
if (this.onDisconnect) {
this.onDisconnect(err);
}
});
},
/**
* Set or refresh the push notifications/device token. If the client is connected,
* the deviceToken can be sent to the server.
*
* @memberof Tinode#
* @param {string} dt - token obtained from the provider.
* @param {boolean} sendToServer - if true, send dt to server immediately.
*
* @param true if attempt was made to send the token to the server.
*/
setDeviceToken: function(dt, sendToServer) {
let sent = false;
if (dt && dt != this._deviceToken) {
this._deviceToken = dt;
if (sendToServer && this.isConnected() && this.isAuthenticated()) {
this.send({
'hi': {
'dev': dt
}
});
sent = true;
}
}
return sent;
},
/**
* Authenticate current session.
* @memberof Tinode#
*
* @param {String} scheme - Authentication scheme; "basic" is the only currently supported scheme.
* @param {String} secret - Authentication secret, assumed to be already base64 encoded.
*
* @returns {Promise} Promise which will be resolved/rejected when server reply is received.
*/
login: function(scheme, secret, cred) {
const pkt = this.initPacket('login');
pkt.login.scheme = scheme;
pkt.login.secret = secret;
pkt.login.cred = cred;
return this.send(pkt, pkt.login.id)
.then((ctrl) => {
this.loginSuccessful(ctrl);
return ctrl;
});
},
/**
* Wrapper for {@link Tinode#login} with basic authentication
* @memberof Tinode#
*
* @param {String} uname - User name.
* @param {String} password - Password.
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
loginBasic: function(uname, password, cred) {
return this.login('basic', b64EncodeUnicode(uname + ':' + password), cred)
.then((ctrl) => {
this._login = uname;
return ctrl;
});
},
/**
* Wrapper for {@link Tinode#login} with token authentication
* @memberof Tinode#
*
* @param {String} token - Token received in response to earlier login.
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
loginToken: function(token, cred) {
return this.login('token', token, cred);
},
/**
* Send a request for resetting an authentication secret.
* @memberof Tinode#
*
* @param {String} scheme - authentication scheme to reset.
* @param {String} method - method to use for resetting the secret, such as "email" or "tel".
* @param {String} value - value of the credential to use, a specific email address or a phone number.
*
* @returns {Promise} Promise which will be resolved/rejected on receiving the server reply.
*/
requestResetAuthSecret: function(scheme, method, value) {
return this.login('reset', b64EncodeUnicode(scheme + ':' + method + ':' + value));
},
/**
* @typedef AuthToken
* @memberof Tinode
* @type Object
* @property {String} token - Token value.
* @property {Date} expires - Token expiration time.
*/
/**
* Get stored authentication token.
* @memberof Tinode#
*
* @returns {Tinode.AuthToken} authentication token.
*/
getAuthToken: function() {
if (this._authToken && (this._authToken.expires.getTime() > Date.now())) {
return this._authToken;
} else {
this._authToken = null;
}
return null;
},
/**
* Application may provide a saved authentication token.
* @memberof Tinode#
*
* @param {Tinode.AuthToken} token - authentication token.
*/
setAuthToken: function(token) {
this._authToken = token;
},
/**
* @typedef SetParams
* @memberof Tinode
* @property {Tinode.SetDesc=} desc - Topic initialization parameters when creating a new topic or a new subscription.
* @property {Tinode.SetSub=} sub - Subscription initialization parameters.
*/
/**
* @typedef SetDesc
* @memberof Tinode
* @property {Tinode.DefAcs=} defacs - Default access mode.
* @property {Object=} public - Free-form topic description, publically accessible.
* @property {Object=} private - Free-form topic descriptionaccessible only to the owner.
*/
/**
* @typedef SetSub
* @memberof Tinode
* @property {String=} user - UID of the user affected by the request. Default (empty) - current user.
* @property {String=} mode - User access mode, either requested or assigned dependent on context.
* @property {Object=} info - Free-form payload to pass to the invited user or topic manager.
*/
/**
* Parameters passed to {@link Tinode#subscribe}.
*
* @typedef SubscriptionParams
* @memberof Tinode
* @property {Tinode.SetParams=} set - Parameters used to initialize topic
* @property {Tinode.GetQuery=} get - Query for fetching data from topic.
*/
/**
* Send a topic subscription request.
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to subscribe to.
* @param {Tinode.GetQuery=} getParams - Optional subscription metadata query
* @param {Tinode.SetParams=} setParams - Optional initialization parameters
*
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
subscribe: function(topicName, getParams, setParams) {
const pkt = this.initPacket('sub', topicName)
if (!topicName) {
topicName = TOPIC_NEW;
}
pkt.sub.get = getParams;
if (setParams) {
if (setParams.sub) {
pkt.sub.set.sub = setParams.sub;
}
if (setParams.desc) {
if (Tinode.isNewGroupTopicName(topicName)) {
// Full set.desc params are used for new topics only
pkt.sub.set.desc = setParams.desc;
} else if (Tinode.topicType(topicName) == 'p2p' && setParams.desc.defacs) {
// Use optional default permissions only.
pkt.sub.set.desc = {
defacs: setParams.desc.defacs
};
}
}
if (setParams.tags) {
pkt.sub.set.tags = setParams.tags;
}
}
return this.send(pkt, pkt.sub.id);
},
/**
* Detach and optionally unsubscribe from the topic
* @memberof Tinode#
*
* @param {String} topic - Topic to detach from.
* @param {Boolean} unsub - If true , detach and unsubscribe, otherwise just detach.
*
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
leave: function(topic, unsub) {
const pkt = this.initPacket('leave', topic);
pkt.leave.unsub = unsub;
return this.send(pkt, pkt.leave.id);
},
/**
* Create message draft without sending it to the server.
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to publish to.
* @param {Object} data - Payload to publish.
* @param {Boolean=} noEcho - If true , tell the server not to echo the message to the original session.
*
* @returns {Object} new message which can be sent to the server or otherwise used.
*/
createMessage: function(topic, data, noEcho) {
const pkt = this.initPacket('pub', topic);
let dft = typeof data == 'string' ? Drafty.parse(data) : data;
if (dft && !Drafty.isPlainText(dft)) {
pkt.pub.head = {
mime: Drafty.getContentType()
};
data = dft;
}
pkt.pub.noecho = noEcho;
pkt.pub.content = data;
return pkt.pub;
},
/**
* Publish {data} message to topic.
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to publish to.
* @param {Object} data - Payload to publish.
* @param {Boolean=} noEcho - If true , tell the server not to echo the message to the original session.
*
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
publish: function(topic, data, noEcho) {
return this.publishMessage(
this.createMessage(topic, data, noEcho)
);
},
/**
* Publish message to topic. The message should be created by {@link Tinode#createMessage}.
* @memberof Tinode#
*
* @param {Object} pub - Message to publish.
*
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
publishMessage: function(pub) {
// Make a shallow copy. Needed in order to clear locally-assigned temp values;
pub = Object.assign({}, pub);
pub.seq = undefined;
pub.from = undefined;
pub.ts = undefined;
return this.send({
pub: pub
}, pub.id);
},
/**
* @typedef GetQuery
* @type Object
* @memberof Tinode
* @property {Tinode.GetOptsType=} desc - If provided (even if empty), fetch topic description.
* @property {Tinode.GetOptsType=} sub - If provided (even if empty), fetch topic subscriptions.
* @property {Tinode.GetDataType=} data - If provided (even if empty), get messages.
*/
/**
* @typedef GetOptsType
* @type Object
* @memberof Tinode
* @property {Date=} ims - "If modified since", fetch data only it was was modified since stated date.
* @property {Number=} limit - Maximum number of results to return. Ignored when querying topic description.
*/
/**
* @typedef GetDataType
* @type Object
* @memberof Tinode
* @property {Number=} since - Load messages with seq id equal or greater than this value.
* @property {Number=} before - Load messages with seq id lower than this number.
* @property {Number=} limit - Maximum number of results to return.
*/
/**
* Request topic metadata
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to query.
* @param {Tinode.GetQuery} params - Parameters of the query. Use {Tinode.MetaGetBuilder} to generate.
*
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
getMeta: function(topic, params) {
const pkt = this.initPacket('get', topic);
pkt.get = mergeObj(pkt.get, params);
return this.send(pkt, pkt.get.id);
},
/**
* Update topic's metadata: description, subscribtions.
* @memberof Tinode#
*
* @param {String} topic - Topic to update.
* @param {Tinode.SetParams} params - topic metadata to update.
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
setMeta: function(topic, params) {
const pkt = this.initPacket('set', topic);
const what = [];
if (params) {
['desc', 'sub', 'tags', 'cred'].map(function(key) {
if (params.hasOwnProperty(key)) {
what.push(key);
pkt.set[key] = params[key];
}
});
}
if (what.length == 0) {
return Promise.reject(new Error("Invalid {set} parameters"));
}
return this.send(pkt, pkt.set.id);
},
/**
* Range of message IDs to delete.
*
* @typedef DelRange
* @type Object
* @memberof Tinode
* @property {Number} low - low end of the range, inclusive (closed).
* @property {Number=} hi - high end of the range, exclusive (open).
*/
/**
* Delete some or all messages in a topic.
* @memberof Tinode#
*
* @param {String} topic - Topic name to delete messages from.
* @param {Tinode.DelRange[]} list - Ranges of message IDs to delete.
* @param {Boolean=} hard - Hard or soft delete
*
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
delMessages: function(topic, ranges, hard) {
const pkt = this.initPacket('del', topic);
pkt.del.what = 'msg';
pkt.del.delseq = ranges;
pkt.del.hard = hard;
return this.send(pkt, pkt.del.id);
},
/**
* Delete the topic alltogether. Requires Owner permission.
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to delete
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
delTopic: function(topic) {
const pkt = this.initPacket('del', topic);
pkt.del.what = 'topic';
return this.send(pkt, pkt.del.id).then((ctrl) => {
this.cacheDel('topic', topic);
return this.ctrl;
});
},
/**
* Delete subscription. Requires Share permission.
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to delete
* @param {String} user - User ID to remove.
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
delSubscription: function(topic, user) {
const pkt = this.initPacket('del', topic);
pkt.del.what = 'sub';
pkt.del.user = user;
return this.send(pkt, pkt.del.id);
},
/**
* Delete credential. Must be 'me' topic.
* @memberof Tinode#
*
* @param {String} topic - 'me'.
* @param {String} method - validation method such as 'email' or 'tel'.
* @param {String} value - validation value, i.e. 'alice@example.com'.
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
delCredential: function(topic, method, value) {
if (topic != 'me') {
throw new Error("Invalid topic for deleting credentials '" + topic + "'");
}
const pkt = this.initPacket('del', topic);
pkt.del.what = 'cred';
pkt.del.cred = {
meth: method,
val: value
};
return this.send(pkt, pkt.del.id);
},
/**
* Notify server that a message or messages were read or received. Does NOT return promise.
* @memberof Tinode#
*
* @param {String} topic - Name of the topic where the mesage is being aknowledged.
* @param {String} what - Action being aknowledged, either "read" or "recv".
* @param {Number} seq - Maximum id of the message being acknowledged.
*/
note: function(topic, what, seq) {
if (seq <= 0 || seq >= LOCAL_SEQID) {
throw new Error("Invalid message id " + seq);
}
const pkt = this.initPacket('note', topic);
pkt.note.what = what;
pkt.note.seq = seq;
this.send(pkt);
},
/**
* Broadcast a key-press notification to topic subscribers. Used to show
* typing notifications "user X is typing...".
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to broadcast to.
*/
noteKeyPress: function(topic) {
const pkt = this.initPacket('note', topic);
pkt.note.what = 'kp';
this.send(pkt);
},
/**
* Get a named topic, either pull it from cache or create a new instance.
* There is a single instance of topic for each name.
* @memberof Tinode#
*
* @param {String} topic - Name of the topic to get.
* @returns {Tinode.Topic} Requested or newly created topic or undefined if topic name is invalid.
*/
getTopic: function(name) {
let topic = this.cacheGet('topic', name);
if (!topic && name) {
if (name == TOPIC_ME) {
topic = new TopicMe();
} else if (name == TOPIC_FND) {
topic = new TopicFnd();
} else {
topic = new Topic(name);
}
// topic._new = false;
this.cachePut('topic', name, topic);
this.attachCacheToTopic(topic);
}
return topic;
},
/**
* Instantiate a new unnamed topic. An actual name will be assigned by the server
* on {@link Tinode.Topic.subscribe}.
* @memberof Tinode#
*
* @param {Tinode.Callbacks} callbacks - Object with callbacks for various events.
* @returns {Tinode.Topic} Newly created topic.
*/
newTopic: function(callbacks) {
const topic = new Topic(TOPIC_NEW, callbacks);
this.attachCacheToTopic(topic);
return topic;
},
/**
* Generate unique name like 'new123456' suitable for creating a new group topic.
* @memberof Tinode#
*
* @returns {string} name which can be used for creating a new group topic.
*/
newGroupTopicName: function() {
return TOPIC_NEW + this.getNextUniqueId();
},
/**
* Instantiate a new P2P topic with a given peer.
* @memberof Tinode#
*
* @param {string} peer - UID of the peer to start topic with.
* @param {Tinode.Callbacks} callbacks - Object with callbacks for various events.
* @returns {Tinode.Topic} Newly created topic.
*/
newTopicWith: function(peer, callbacks) {
const topic = new Topic(peer, callbacks);
this.attachCacheToTopic(topic);
return topic;
},
/**
* Instantiate 'me' topic or get it from cache.
* @memberof Tinode#
*
* @returns {Tinode.TopicMe} Instance of 'me' topic.
*/
getMeTopic: function() {
return this.getTopic(TOPIC_ME);
},
/**
* Instantiate 'fnd' (find) topic or get it from cache.
* @memberof Tinode#
*
* @returns {Tinode.Topic} Instance of 'fnd' topic.
*/
getFndTopic: function() {
return this.getTopic(TOPIC_FND);
},
/**
* Create a new LargeFileHelper instance
* @memberof Tinode#
*
* @returns {Tinode.LargeFileHelper} instance of a LargeFileHelper.
*/
getLargeFileHelper: function() {
return new LargeFileHelper(this);
},
/**
* Get the UID of the the current authenticated user.
* @memberof Tinode#
* @returns {string} UID of the current user or undefined if the session is not yet authenticated or if there is no session.
*/
getCurrentUserID: function() {
return this._myUID;
},
/**
* Check if the given user ID is equal to the current user's UID.
* @memberof Tinode#
* @param {string} uid - UID to check.
* @returns {boolean} true if the given UID belongs to the current logged in user.
*/
isMe: function(uid) {
return this._myUID === uid;
},
/**
* Get login used for last successful authentication.
* @memberof Tinode#
* @returns {string} login last used successfully or undefined .
*/
getCurrentLogin: function() {
return this._login;
},
/**
* Return information about the server: protocol version and build timestamp.
* @memberof Tinode#
* @returns {Object} build and version of the server or null if there is no connection or if the first server response has not been received yet.
*/
getServerInfo: function() {
return this._serverInfo;
},
/**
* Toggle console logging. Logging is off by default.
* @memberof Tinode#
* @param {boolean} enabled - Set to true to enable logging to console.
*/
enableLogging: function(enabled, trimLongStrings) {
this._loggingEnabled = enabled;
this._trimLongStrings = enabled && trimLongStrings;
},
/**
* Check if given topic is online.
* @memberof Tinode#
*
* @param {String} name - Name of the topic to test.
* @returns {Boolean} true if topic is online, false otherwise.
*/
isTopicOnline: function(name) {
const me = this.getMeTopic();
const cont = me && me.getContact(name);
return cont && cont.online;
},
/**
* Include message ID into all subsequest messages to server instructin it to send aknowledgemens.
* Required for promises to function. Default is "on".
* @memberof Tinode#
*
* @param {Boolean} status - Turn aknowledgemens on or off.
* @deprecated
*/
wantAkn: function(status) {
if (status) {
this._messageId = Math.floor((Math.random() * 0xFFFFFF) + 0xFFFFFF);
} else {
this._messageId = 0;
}
},
// Callbacks:
/**
* Callback to report when the websocket is opened. The callback has no parameters.
* @memberof Tinode#
* @type {Tinode.onWebsocketOpen}
*/
onWebsocketOpen: undefined,
/**
* @typedef Tinode.ServerParams
* @memberof Tinode
* @type Object
* @property {string} ver - Server version
* @property {string} build - Server build
* @property {string=} sid - Session ID, long polling connections only.
*/
/**
* @callback Tinode.onConnect
* @param {number} code - Result code
* @param {string} text - Text epxplaining the completion, i.e "OK" or an error message.
* @param {Tinode.ServerParams} params - Parameters returned by the server.
*/
/**
* Callback to report when connection with Tinode server is established.
* @memberof Tinode#
* @type {Tinode.onConnect}
*/
onConnect: undefined,
/**
* Callback to report when connection is lost. The callback has no parameters.
* @memberof Tinode#
* @type {Tinode.onDisconnect}
*/
onDisconnect: undefined,
/**
* @callback Tinode.onLogin
* @param {number} code - NUmeric completion code, same as HTTP status codes.
* @param {string} text - Explanation of the completion code.
*/
/**
* Callback to report login completion.
* @memberof Tinode#
* @type {Tinode.onLogin}
*/
onLogin: undefined,
/**
* Callback to receive {ctrl} (control) messages.
* @memberof Tinode#
* @type {Tinode.onCtrlMessage}
*/
onCtrlMessage: undefined,
/**
* Callback to recieve {data} (content) messages.
* @memberof Tinode#
* @type {Tinode.onDataMessage}
*/
onDataMessage: undefined,
/**
* Callback to receive {pres} (presence) messages.
* @memberof Tinode#
* @type {Tinode.onPresMessage}
*/
onPresMessage: undefined,
/**
* Callback to receive all messages as objects.
* @memberof Tinode#
* @type {Tinode.onMessage}
*/
onMessage: undefined,
/**
* Callback to receive all messages as unparsed text.
* @memberof Tinode#
* @type {Tinode.onRawMessage}
*/
onRawMessage: undefined,
/**
* Callback to receive server responses to network probes. See {@link Tinode#networkProbe}
* @memberof Tinode#
* @type {Tinode.onNetworkProbe}
*/
onNetworkProbe: undefined,
/**
* Callback to be notified when exponential backoff is iterating.
* @memberof Tinode#
* @type {Tinode.onAutoreconnectIteration}
*/
onAutoreconnectIteration: undefined,
};
/**
* Helper class for constructing {@link Tinode.GetQuery}.
*
* @class MetaGetBuilder
* @memberof Tinode
*
* @param {Tinode.Topic} parent topic which instantiated this builder.
*/
var MetaGetBuilder = function(parent) {
this.topic = parent;
const me = parent._tinode.getMeTopic();
this.contact = me && me.getContact(parent.name);
this.what = {};
}
MetaGetBuilder.prototype = {
// Get latest timestamp
_get_ims: function() {
const cupd = this.contact && this.contact.updated;
const tupd = this.topic._lastDescUpdate || 0;
return cupd > tupd ? cupd : tupd;
},
/**
* Add query parameters to fetch messages within explicit limits.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Number=} since messages newer than this (inclusive);
* @param {Number=} before older than this (exclusive)
* @param {Number=} limit number of messages to fetch
* @returns {Tinode.MetaGetBuilder} this object.
*/
withData: function(since, before, limit) {
this.what['data'] = {
since: since,
before: before,
limit: limit
};
return this;
},
/**
* Add query parameters to fetch messages newer than the latest saved message.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Number=} limit number of messages to fetch
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withLaterData: function(limit) {
return this.withData(this.topic._maxSeq > 0 ? this.topic._maxSeq + 1 : undefined, undefined, limit);
},
/**
* Add query parameters to fetch messages older than the earliest saved message.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Number=} limit maximum number of messages to fetch.
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withEarlierData: function(limit) {
return this.withData(undefined, this.topic._minSeq > 0 ? this.topic._minSeq : undefined, limit);
},
/**
* Add query parameters to fetch topic description if it's newer than the given timestamp.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Date=} ims fetch messages newer than this timestamp.
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withDesc: function(ims) {
this.what['desc'] = {
ims: ims
};
return this;
},
/**
* Add query parameters to fetch topic description if it's newer than the last update.
* @memberof Tinode.MetaGetBuilder#
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withLaterDesc: function() {
return this.withDesc(this._get_ims());
},
/**
* Add query parameters to fetch subscriptions.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Date=} ims fetch subscriptions modified more recently than this timestamp
* @param {Number=} limit maximum number of subscriptions to fetch.
* @param {String=} userOrTopic user ID or topic name to fetch for fetching one subscription.
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withSub: function(ims, limit, userOrTopic) {
const opts = {
ims: ims,
limit: limit
};
if (this.topic.getType() == 'me') {
opts.topic = userOrTopic;
} else {
opts.user = userOrTopic;
}
this.what['sub'] = opts;
return this;
},
/**
* Add query parameters to fetch a single subscription.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Date=} ims fetch subscriptions modified more recently than this timestamp
* @param {String=} userOrTopic user ID or topic name to fetch for fetching one subscription.
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withOneSub: function(ims, userOrTopic) {
return this.withSub(ims, undefined, userOrTopic);
},
/**
* Add query parameters to fetch a single subscription if it's been updated since the last update.
* @memberof Tinode.MetaGetBuilder#
*
* @param {String=} userOrTopic user ID or topic name to fetch for fetching one subscription.
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withLaterOneSub: function(userOrTopic) {
return this.withOneSub(this.topic._lastSubsUpdate, userOrTopic);
},
/**
* Add query parameters to fetch subscriptions updated since the last update.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Number=} limit maximum number of subscriptions to fetch.
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withLaterSub: function(limit) {
return this.withSub(
this.topic.getType() == 'p2p' ? this._get_ims() : this.topic._lastSubsUpdate,
limit);
},
/**
* Add query parameters to fetch topic tags.
* @memberof Tinode.MetaGetBuilder#
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withTags: function() {
this.what['tags'] = true;
return this;
},
/**
* Add query parameters to fetch user's credentials. 'me' topic only.
* @memberof Tinode.MetaGetBuilder#
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withCred: function() {
if (this.topic.getType() == 'me') {
this.what['cred'] = true;
} else {
this.topic._tinode.logger("ERROR: Invalid topic type for MetaGetBuilder:withCreds", this.topic.getType());
}
return this;
},
/**
* Add query parameters to fetch deleted messages within explicit limits. Any/all parameters can be null.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Number=} since ids of messages deleted since this 'del' id (inclusive)
* @param {Number=} limit number of deleted message ids to fetch
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withDel: function(since, limit) {
if (since || limit) {
this.what['del'] = {
since: since,
limit: limit
};
}
return this;
},
/**
* Add query parameters to fetch messages deleted after the saved 'del' id.
* @memberof Tinode.MetaGetBuilder#
*
* @param {Number=} limit number of deleted message ids to fetch
*
* @returns {Tinode.MetaGetBuilder} this object.
*/
withLaterDel: function(limit) {
// Specify 'since' only if we have already received some messages. If
// we have no locally cached messages then we don't care if any messages were deleted.
return this.withDel(this.topic._maxSeq > 0 ? this.topic._maxDel + 1 : undefined, limit);
},
/**
* Construct parameters
* @memberof Tinode.MetaGetBuilder#
*
* @returns {Tinode.GetQuery} Get query
*/
build: function() {
const what = [];
const instance = this;
let params = {};
['data', 'sub', 'desc', 'tags', 'cred', 'del'].map(function(key) {
if (instance.what.hasOwnProperty(key)) {
what.push(key);
if (Object.getOwnPropertyNames(instance.what[key]).length > 0) {
params[key] = instance.what[key];
}
}
});
if (what.length > 0) {
params.what = what.join(' ');
} else {
params = undefined;
}
return params;
}
};
/**
* Helper class for handling access mode.
*
* @class AccessMode
* @memberof Tinode
*
* @param {AccessMode|Object=} acs AccessMode to copy or access mode object received from the server.
*/
var AccessMode = function(acs) {
if (acs) {
this.given = typeof acs.given == 'number' ? acs.given : AccessMode.decode(acs.given);
this.want = typeof acs.want == 'number' ? acs.want : AccessMode.decode(acs.want);
this.mode = acs.mode ? (typeof acs.mode == 'number' ? acs.mode : AccessMode.decode(acs.mode)) :
(this.given & this.want);
}
};
AccessMode._NONE = 0x00;
AccessMode._JOIN = 0x01;
AccessMode._READ = 0x02;
AccessMode._WRITE = 0x04;
AccessMode._PRES = 0x08;
AccessMode._APPROVE = 0x10;
AccessMode._SHARE = 0x20;
AccessMode._DELETE = 0x40;
AccessMode._OWNER = 0x80;
AccessMode._BITMASK = AccessMode._JOIN | AccessMode._READ | AccessMode._WRITE | AccessMode._PRES |
AccessMode._APPROVE | AccessMode._SHARE | AccessMode._DELETE | AccessMode._OWNER;
AccessMode._INVALID = 0x100000;
AccessMode._checkFlag = function(val, side, flag) {
side = side || 'mode';
if (['given', 'want', 'mode'].includes(side)) {
return ((val[side] & flag) != 0);
}
throw new Error("Invalid AccessMode component '" + side + "'");
}
/**
* Parse string into an access mode value.
* @memberof Tinode.AccessMode
* @static
*
* @param {string | number} mode - either a String representation of the access mode to parse or a set of bits to assign.
* @returns {number} - Access mode as a numeric value.
*/
AccessMode.decode = function(str) {
if (!str) {
return null;
} else if (typeof str == 'number') {
return str & AccessMode._BITMASK;
} else if (str === 'N' || str === 'n') {
return AccessMode._NONE;
}
const bitmask = {
'J': AccessMode._JOIN,
'R': AccessMode._READ,
'W': AccessMode._WRITE,
'P': AccessMode._PRES,
'A': AccessMode._APPROVE,
'S': AccessMode._SHARE,
'D': AccessMode._DELETE,
'O': AccessMode._OWNER
};
let m0 = AccessMode._NONE;
for (let i = 0; i < str.length; i++) {
const bit = bitmask[str.charAt(i).toUpperCase()];
if (!bit) {
// Unrecognized bit, skip.
continue;
}
m0 |= bit;
}
return m0;
};
/**
* Convert numeric representation of the access mode into a string.
*
* @memberof Tinode.AccessMode
* @static
*
* @param {number} val - access mode value to convert to a string.
* @returns {string} - Access mode as a string.
*/
AccessMode.encode = function(val) {
if (val === null || val === AccessMode._INVALID) {
return null;
} else if (val === AccessMode._NONE) {
return 'N';
}
const bitmask = ['J', 'R', 'W', 'P', 'A', 'S', 'D', 'O'];
let res = '';
for (let i = 0; i < bitmask.length; i++) {
if ((val & (1 << i)) != 0) {
res = res + bitmask[i];
}
}
return res;
};
/**
* Update numeric representation of access mode with the new value. The value
* is one of the following:
* - a string starting with '+' or '-' then the bits to add or remove, e.g. '+R-W' or '-PS'.
* - a new value of access mode
*
* @memberof Tinode.AccessMode
* @static
*
* @param {number} val - access mode value to update.
* @param {string} upd - update to apply to val.
* @returns {number} - updated access mode.
*/
AccessMode.update = function(val, upd) {
if (!upd || typeof upd != 'string') {
return val;
}
let action = upd.charAt(0);
if (action == '+' || action == '-') {
let val0 = val;
// Split delta-string like '+ABC-DEF+Z' into an array of parts including + and -.
const parts = upd.split(/([-+])/);
// Starting iteration from 1 because String.split() creates an array with the first empty element.
// Iterating by 2 because we parse pairs +/- then data.
for (let i = 1; i < parts.length - 1; i += 2) {
action = parts[i];
const m0 = AccessMode.decode(parts[i + 1]);
if (m0 == AccessMode._INVALID) {
return val;
}
if (m0 == null) {
continue;
}
if (action === '+') {
val0 |= m0;
} else if (action === '-') {
val0 &= ~m0;
}
}
val = val0;
} else {
// The string is an explicit new value 'ABC' rather than delta.
const val0 = AccessMode.decode(upd);
if (val0 != AccessMode._INVALID) {
val = val0;
}
}
return val;
};
/**
* AccessMode is a class representing topic access mode.
* @class Topic
* @memberof Tinode
*/
AccessMode.prototype = {
/**
* Custom formatter
*/
toString: function() {
return '{"mode": "' + AccessMode.encode(this.mode) +
'", "given": "' + AccessMode.encode(this.given) +
'", "want": "' + AccessMode.encode(this.want) + '"}';
},
/**
* Convert numeric values to strings.
*/
jsonHelper: function() {
return {
mode: AccessMode.encode(this.mode),
given: AccessMode.encode(this.given),
want: AccessMode.encode(this.want)
};
},
/**
* Assign value to 'mode'.
* @memberof Tinode.AccessMode
*
* @param {string | number} m - either a string representation of the access mode or a set of bits.
* @returns {AccessMode} - this AccessMode.
*/
setMode: function(m) {
this.mode = AccessMode.decode(m);
return this;
},
/**
* Update 'mode' value.
* @memberof Tinode.AccessMode
*
* @param {string} u - string representation of the changes to apply to access mode.
* @returns {AccessMode} - this AccessMode.
*/
updateMode: function(u) {
this.mode = AccessMode.update(this.mode, u);
return this;
},
/**
* Get 'mode' value as a string.
* @memberof Tinode.AccessMode
*
* @returns {string} - mode value.
*/
getMode: function() {
return AccessMode.encode(this.mode);
},
/**
* Assign 'given' value.
* @memberof Tinode.AccessMode
*
* @param {string | number} g - either a string representation of the access mode or a set of bits.
* @returns {AccessMode} - this AccessMode.
*/
setGiven: function(g) {
this.given = AccessMode.decode(g);
return this;
},
/**
* Update 'given' value.
* @memberof Tinode.AccessMode
*
* @param {string} u - string representation of the changes to apply to access mode.
* @returns {AccessMode} - this AccessMode.
*/
updateGiven: function(u) {
this.given = AccessMode.update(this.given, u);
return this;
},
/**
* Get 'given' value as a string.
* @memberof Tinode.AccessMode
*
* @returns {string} - given value.
*/
getGiven: function() {
return AccessMode.encode(this.given);
},
/**
* Assign 'want' value.
* @memberof Tinode.AccessMode
*
* @param {string | number} w - either a string representation of the access mode or a set of bits.
* @returns {AccessMode} - this AccessMode.
*/
setWant: function(w) {
this.want = AccessMode.decode(w);
return this;
},
/**
* Update 'want' value.
* @memberof Tinode.AccessMode
*
* @param {string} u - string representation of the changes to apply to access mode.
* @returns {AccessMode} - this AccessMode.
*/
updateWant: function(u) {
this.want = AccessMode.update(this.want, u);
return this;
},
/**
* Get 'want' value as a string.
* @memberof Tinode.AccessMode
*
* @returns {string} - want value.
*/
getWant: function() {
return AccessMode.encode(this.want);
},
/**
* Get permissions present in 'want' but missing in 'given'.
* Inverse of {@link Tinode.AccessMode#getExcessive}
*
* @memberof Tinode.AccessMode
*
* @returns {string} permissions present in want but missing in given .
*/
getMissing: function() {
return AccessMode.encode(this.want & ~this.given);
},
/**
* Get permissions present in 'given' but missing in 'want'.
* Inverse of {@link Tinode.AccessMode#getMissing}
* @memberof Tinode.AccessMode
*
* @returns {string} permissions present in given but missing in want .
*/
getExcessive: function() {
return AccessMode.encode(this.given & ~this.want);
},
/**
* Update 'want', 'give', and 'mode' values.
* @memberof Tinode.AccessMode
*
* @param {AccessMode} val - new access mode value.
* @returns {AccessMode} - this AccessMode.
*/
updateAll: function(val) {
if (val) {
this.updateGiven(val.given);
this.updateWant(val.want);
this.mode = this.given & this.want;
}
return this;
},
/**
* Check if Owner (O) flag is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isOwner: function(side) {
return AccessMode._checkFlag(this, side, AccessMode._OWNER);
},
/**
* Check if Presence (P) flag is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isPresencer: function(side) {
return AccessMode._checkFlag(this, side, AccessMode._PRES);
},
/**
* Check if Presence (P) flag is NOT set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isMuted: function(side) {
return !this.isPresencer(side);
},
/**
* Check if Join (J) flag is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isJoiner: function(side) {
return AccessMode._checkFlag(this, side, AccessMode._JOIN);
},
/**
* Check if Reader (R) flag is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isReader: function(side) {
return AccessMode._checkFlag(this, side, AccessMode._READ);
},
/**
* Check if Writer (W) flag is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isWriter: function(side) {
return AccessMode._checkFlag(this, side, AccessMode._WRITE);
},
/**
* Check if Approver (A) flag is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isApprover: function(side) {
return AccessMode._checkFlag(this, side, AccessMode._APPROVE);
},
/**
* Check if either one of Owner (O) or Approver (A) flags is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isAdmin: function(side) {
return this.isOwner(side) || this.isApprover(side);
},
/**
* Check if either one of Owner (O), Approver (A), or Sharer (S) flags is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isSharer: function(side) {
return this.isAdmin(side) || AccessMode._checkFlag(this, side, AccessMode._SHARE);
},
/**
* Check if Deleter (D) flag is set.
* @memberof Tinode.AccessMode
* @param {string=} side - which permission to check: given, want, mode; default: mode.
* @returns {boolean} - true if flag is set.
*/
isDeleter: function(side) {
return AccessMode._checkFlag(this, side, AccessMode._DELETE);
}
};
/**
* @callback Tinode.Topic.onData
* @param {Data} data - Data packet
*/
/**
* Topic is a class representing a logical communication channel.
* @class Topic
* @memberof Tinode
*
* @param {string} name - Name of the topic to create.
* @param {Object=} callbacks - Object with various event callbacks.
* @param {Tinode.Topic.onData} callbacks.onData - Callback which receives a {data} message.
* @param {callback} callbacks.onMeta - Callback which receives a {meta} message.
* @param {callback} callbacks.onPres - Callback which receives a {pres} message.
* @param {callback} callbacks.onInfo - Callback which receives an {info} message.
* @param {callback} callbacks.onMetaDesc - Callback which receives changes to topic desctioption {@link desc}.
* @param {callback} callbacks.onMetaSub - Called for a single subscription record change.
* @param {callback} callbacks.onSubsUpdated - Called after a batch of subscription changes have been recieved and cached.
* @param {callback} callbacks.onDeleteTopic - Called after the topic is deleted.
* @param {callback} callbacls.onAllMessagesReceived - Called when all requested {data} messages have been recived.
*/
var Topic = function(name, callbacks) {
// Parent Tinode object.
this._tinode = null;
// Server-provided data, locally immutable.
// topic name
this.name = name;
// timestamp when the topic was created
this.created = null;
// timestamp when the topic was last updated
this.updated = null;
// timestamp of the last messages
this.touched = null;
// access mode, see AccessMode
this.acs = new AccessMode(null);
// per-topic private data
this.private = null;
// per-topic public data
this.public = null;
// Locally cached data
// Subscribed users, for tracking read/recv/msg notifications.
this._users = {};
// Current value of locally issued seqId, used for pending messages.
this._queuedSeqId = LOCAL_SEQID;
// The maximum known {data.seq} value.
this._maxSeq = 0;
// The minimum known {data.seq} value.
this._minSeq = 0;
// Indicator that the last request for earlier messages returned 0.
this._noEarlierMsgs = false;
// The maximum known deletion ID.
this._maxDel = 0;
// User discovery tags
this._tags = [];
// Credentials such as email or phone number.
this._credentials = [];
// Message cache, sorted by message seq values, from old to new.
this._messages = CBuffer(function(a, b) {
return a.seq - b.seq;
});
// Boolean, true if the topic is currently live
this._subscribed = false;
// Timestap when topic meta-desc update was recived.
this._lastDescUpdate = null;
// Timestap when topic meta-subs update was recived.
this._lastSubsUpdate = null;
// Topic created but not yet synced with the server. Used only during initialization.
this._new = true;
// Callbacks
if (callbacks) {
this.onData = callbacks.onData;
this.onMeta = callbacks.onMeta;
this.onPres = callbacks.onPres;
this.onInfo = callbacks.onInfo;
// A single desc update;
this.onMetaDesc = callbacks.onMetaDesc;
// A single subscription record;
this.onMetaSub = callbacks.onMetaSub;
// All subscription records received;
this.onSubsUpdated = callbacks.onSubsUpdated;
this.onTagsUpdated = callbacks.onTagsUpdated;
this.onCredsUpdated = callbacls.onCredsUpdated;
this.onDeleteTopic = callbacks.onDeleteTopic;
this.onAllMessagesReceived = callbacks.onAllMessagesReceived;
}
};
Topic.prototype = {
/**
* Check if the topic is subscribed.
* @memberof Tinode.Topic#
* @returns {boolean} True is topic is attached/subscribed, false otherwise.
*/
isSubscribed: function() {
return this._subscribed;
},
/**
* Request topic to subscribe. Wrapper for {@link Tinode#subscribe}.
* @memberof Tinode.Topic#
*
* @param {Tinode.GetQuery=} getParams - get query parameters.
* @param {Tinode.SetParams=} setParams - set parameters.
* @returns {Promise} Promise to be resolved/rejected when the server responds to the request.
*/
subscribe: function(getParams, setParams) {
// If the topic is already subscribed, return resolved promise
if (this._subscribed) {
return Promise.resolve(this);
}
// Send subscribe message, handle async response.
// If topic name is explicitly provided, use it. If no name, then it's a new group topic,
// use "new".
return this._tinode.subscribe(this.name || TOPIC_NEW, getParams, setParams).then((ctrl) => {
if (ctrl.code >= 300) {
// Do nothing ff the topic is already subscribed to.
return ctrl;
}
this._subscribed = true;
this.acs = (ctrl.params && ctrl.params.acs) ? ctrl.params.acs : this.acs;
// Set topic name for new topics and add it to cache.
if (this._new) {
this._new = false;
// Name may change new123456 -> grpAbCdEf
this.name = ctrl.topic;
this.created = ctrl.ts;
this.updated = ctrl.ts;
// Don't assign touched, otherwise topic will be put on top of the list on subscribe.
this._cachePutSelf();
if (this.name != TOPIC_ME && this.name != TOPIC_FND) {
// Add the new topic to the list of contacts maintained by the 'me' topic.
const me = this._tinode.getMeTopic();
if (me) {
me._processMetaSub([{
_noForwarding: true,
topic: this.name,
created: ctrl.ts,
updated: ctrl.ts,
acs: this.acs
}]);
}
}
if (setParams && setParams.desc) {
setParams.desc._noForwarding = true;
this._processMetaDesc(setParams.desc);
}
}
return ctrl;
});
},
/**
* Create a draft of a message without sending it to the server.
* @memberof Tinode.Topic#
*
* @param {string | Object} data - Content to wrap in a draft.
* @param {Boolean=} noEcho - If true server will not echo message back to originating
* session. Otherwise the server will send a copy of the message to sender.
*
* @returns {Object} message draft.
*/
createMessage: function(data, noEcho) {
return this._tinode.createMessage(this.name, data, noEcho);
},
/**
* Immediately publish data to topic. Wrapper for {@link Tinode#publish}.
* @memberof Tinode.Topic#
*
* @param {string | Object} data - Data to publish, either plain string or a Drafty object.
* @param {Boolean=} noEcho - If true server will not echo message back to originating
* @returns {Promise} Promise to be resolved/rejected when the server responds to the request.
*/
publish: function(data, noEcho) {
return this.publishMessage(this.createMessage(data, noEcho));
},
/**
* Publish message created by {@link Tinode.Topic#createMessage}.
* @memberof Tinode.Topic#
*
* @param {Object} pub - {data} object to publish. Must be created by {@link Tinode.Topic#createMessage}
*
* @returns {Promise} Promise to be resolved/rejected when the server responds to the request.
*/
publishMessage: function(pub) {
if (!this._subscribed) {
return Promise.reject(new Error("Cannot publish on inactive topic"));
}
// Update header with attachment records.
if (Drafty.hasAttachments(pub.content) && !pub.head.attachments) {
let attachments = [];
Drafty.attachments(pub.content, (data) => {
attachments.push(data.ref);
});
pub.head.attachments = attachments;
}
// Send data.
pub._sending = true;
pub._failed = false;
return this._tinode.publishMessage(pub).then((ctrl) => {
pub._sending = false;
pub.seq = ctrl.params.seq;
pub.ts = ctrl.ts;
this._routeData(pub);
return ctrl;
}).catch((err) => {
this._tinode.logger("WARNING: Message rejected by the server", err);
pub._sending = false;
pub._failed = true;
if (this.onData) {
this.onData();
}
});
},
/**
* Add message to local message cache, send to the server when the promise is resolved.
* If promise is null or undefined, the message will be sent immediately.
* The message is sent when the
* The message should be created by {@link Tinode.Topic#createMessage}.
* This is probably not the final API.
* @memberof Tinode.Topic#
*
* @param {Object} pub - Message to use as a draft.
* @param {Promise} prom - Message will be sent when this promise is resolved, discarded if rejected.
*
* @returns {Promise} derived promise.
*/
publishDraft: function(pub, prom) {
if (!prom && !this._subscribed) {
return Promise.reject(new Error("Cannot publish on inactive topic"));
}
const seq = pub.seq || this._getQueuedSeqId();
if (!pub._noForwarding) {
// The 'seq', 'ts', and 'from' are added to mimic {data}. They are removed later
// before the message is sent.
pub._noForwarding = true;
pub.seq = seq;
pub.ts = new Date();
pub.from = this._tinode.getCurrentUserID();
// Don't need an echo message because the message is added to local cache right away.
pub.noecho = true;
// Add to cache.
this._messages.put(pub);
if (this.onData) {
this.onData(pub);
}
}
// If promise is provided, send the queued message when it's resolved.
// If no promise is provided, create a resolved one and send immediately.
prom = (prom || Promise.resolve()).then(
( /* argument ignored */ ) => {
if (pub._cancelled) {
return {
code: 300,
text: "cancelled"
};
}
return this.publishMessage(pub);
},
(err) => {
this._tinode.logger("WARNING: Message draft rejected by the server", err);
pub._sending = false;
pub._failed = true;
this._messages.delAt(this._messages.find(pub));
if (this.onData) {
this.onData();
}
});
return prom;
},
/**
* Leave the topic, optionally unsibscribe. Leaving the topic means the topic will stop
* receiving updates from the server. Unsubscribing will terminate user's relationship with the topic.
* Wrapper for {@link Tinode#leave}.
* @memberof Tinode.Topic#
*
* @param {Boolean=} unsub - If true, unsubscribe, otherwise just leave.
* @returns {Promise} Promise to be resolved/rejected when the server responds to the request.
*/
leave: function(unsub) {
// It's possible to unsubscribe (unsub==true) from inactive topic.
if (!this._subscribed && !unsub) {
return Promise.reject(new Error("Cannot leave inactive topic"));
}
// Send a 'leave' message, handle async response
return this._tinode.leave(this.name, unsub).then((ctrl) => {
this._resetSub();
if (unsub) {
this._gone();
}
return ctrl;
});
},
/**
* Request topic metadata from the server.
* @memberof Tinode.Topic#
*
* @param {Tinode.GetQuery} request parameters
*
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
getMeta: function(params) {
// Send {get} message, return promise.
return this._tinode.getMeta(this.name, params);
},
/**
* Request more messages from the server
* @memberof Tinode.Topic#
*
* @param {integer} limit number of messages to get.
* @param {boolean} forward if true, request newer messages.
*/
getMessagesPage: function(limit, forward) {
const query = this.startMetaQuery();
if (forward) {
query.withLaterData(limit);
} else {
query.withEarlierData(limit);
}
let promise = this.getMeta(query.build());
if (!forward) {
promise = promise.then((ctrl) => {
if (ctrl && ctrl.params && !ctrl.params.count) {
this._noEarlierMsgs = true;
}
});
}
return promise;
},
/**
* Update topic metadata.
* @memberof Tinode.Topic#
*
* @param {Tinode.SetParams} params parameters to update.
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
setMeta: function(params) {
if (params.tags) {
params.tags = normalizeArray(params.tags);
}
// Send Set message, handle async response.
return this._tinode.setMeta(this.name, params)
.then((ctrl) => {
if (ctrl && ctrl.code >= 300) {
// Not modified
return ctrl;
}
if (params.sub) {
if (ctrl.params && ctrl.params.acs) {
params.sub.acs = ctrl.params.acs;
params.sub.updated = ctrl.ts;
}
if (!params.sub.user) {
// This is a subscription update of the current user.
// Assign user ID otherwise the update will be ignored by _processMetaSub.
params.sub.user = this._tinode.getCurrentUserID();
if (!params.desc) {
// Force update to topic's asc.
params.desc = {};
}
}
params.sub._noForwarding = true;
this._processMetaSub([params.sub]);
}
if (params.desc) {
if (ctrl.params && ctrl.params.acs) {
params.desc.acs = ctrl.params.acs;
params.desc.updated = ctrl.ts;
}
this._processMetaDesc(params.desc);
}
if (params.tags) {
this._processMetaTags(params.tags);
}
if (params.cred) {
this._processMetaCreds([params.cred], true);
}
return ctrl;
});
},
/**
* Update access mode of the current user or of another topic subsriber.
* @memberof Tinode.Topic#
*
* @param {string} uid - UID of the user to update or null to update current user.
* @param {string} update - the update value, full or delta.
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
updateMode: function(uid, update) {
const user = uid ? user = this.subscriber(uid) : null;
let am;
if (user) {
am = user.acs.updateGiven(update).getGiven();
} else {
am = topic.getAccessMode().updateWant(update).getWant();
}
return topic.setMeta({
sub: {
user: uid,
mode: am
}
});
},
/**
* Create new topic subscription. Wrapper for {@link Tinode#setMeta}.
* @memberof Tinode.Topic#
*
* @param {String} uid - ID of the user to invite
* @param {String=} mode - Access mode. null means to use default.
*
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
invite: function(uid, mode) {
return this.setMeta({
sub: {
user: uid,
mode: mode
}
});
},
/**
* Archive or un-archive the topic. Wrapper for {@link Tinode#setMeta}.
* @memberof Tinode.Topic#
*
* @param {Boolean} arch - true to archive the topic, false otherwise.
*
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
archive: function(arch) {
if (this.private && this.private.arch == arch) {
return Promise.resolve(arch);
}
return this.setMeta({
desc: {
private: {
arch: arch ? true : Tinode.DEL_CHAR
}
}
});
},
/**
* Delete messages. Hard-deleting messages requires Owner permission.
* Wrapper for {@link Tinode#delMessages}.
* @memberof Tinode.Topic#
*
* @param {Tinode.DelRange[]} ranges - Ranges of message IDs to delete.
* @param {Boolean=} hard - Hard or soft delete
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
delMessages: function(ranges, hard) {
if (!this._subscribed) {
return Promise.reject(new Error("Cannot delete messages in inactive topic"));
}
// Sort ranges in accending order by low, the descending by hi.
ranges.sort((r1, r2) => {
if (r1.low < r2.low) {
return true;
}
if (r1.low == r2.low) {
return !r2.hi || (r1.hi >= r2.hi);
}
return false;
});
// Remove pending messages from ranges possibly clipping some ranges.
let tosend = ranges.reduce((out, r) => {
if (r.low < LOCAL_SEQID) {
if (!r.hi || r.hi < LOCAL_SEQID) {
out.push(r);
} else {
// Clip hi to max allowed value.
out.push({
low: r.low,
hi: this._maxSeq + 1
});
}
}
return out;
}, []);
// Send {del} message, return promise
let result;
if (tosend.length > 0) {
result = this._tinode.delMessages(this.name, tosend, hard);
} else {
result = Promise.resolve({
params: {
del: 0
}
});
}
// Update local cache.
return result.then((ctrl) => {
if (ctrl.params.del > this._maxDel) {
this._maxDel = ctrl.params.del;
}
ranges.map((r) => {
if (r.hi) {
this.flushMessageRange(r.low, r.hi);
} else {
this.flushMessage(r.low);
}
});
if (this.onData) {
// Calling with no parameters to indicate the messages were deleted.
this.onData();
}
return ctrl;
});
},
/**
* Delete all messages. Hard-deleting messages requires Owner permission.
* @memberof Tinode.Topic#
*
* @param {boolean} hardDel - true if messages should be hard-deleted.
*
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
delMessagesAll: function(hardDel) {
if (!this._maxSeq || this._maxSeq <= 0) {
// There are no messages to delete.
return Promise.resolve();
}
return this.delMessages([{
low: 1,
hi: this._maxSeq + 1,
_all: true
}], hardDel);
},
/**
* Delete multiple messages defined by their IDs. Hard-deleting messages requires Owner permission.
* @memberof Tinode.Topic#
*
* @param {Tinode.DelRange[]} list - list of seq IDs to delete
* @param {Boolean=} hardDel - true if messages should be hard-deleted.
*
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
delMessagesList: function(list, hardDel) {
// Sort the list in ascending order
list.sort((a, b) => a - b);
// Convert the array of IDs to ranges.
let ranges = list.reduce((out, id) => {
if (out.length == 0) {
// First element.
out.push({
low: id
});
} else {
let prev = out[out.length - 1];
if ((!prev.hi && (id != prev.low + 1)) || (id > prev.hi)) {
// New range.
out.push({
low: id
});
} else {
// Expand existing range.
prev.hi = prev.hi ? Math.max(prev.hi, id + 1) : id + 1;
}
}
return out;
}, []);
// Send {del} message, return promise
return this.delMessages(ranges, hardDel)
},
/**
* Delete topic. Requires Owner permission. Wrapper for {@link Tinode#delTopic}.
* @memberof Tinode.Topic#
*
* @returns {Promise} Promise to be resolved/rejected when the server responds to the request.
*/
delTopic: function() {
const topic = this;
return this._tinode.delTopic(this.name).then(function(ctrl) {
topic._resetSub();
topic._gone();
return ctrl;
});
},
/**
* Delete subscription. Requires Share permission. Wrapper for {@link Tinode#delSubscription}.
* @memberof Tinode.Topic#
*
* @param {String} user - ID of the user to remove subscription for.
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
delSubscription: function(user) {
if (!this._subscribed) {
return Promise.reject(new Error("Cannot delete subscription in inactive topic"));
}
// Send {del} message, return promise
return this._tinode.delSubscription(this.name, user).then((ctrl) => {
// Remove the object from the subscription cache;
delete this._users[user];
// Notify listeners
if (this.onSubsUpdated) {
this.onSubsUpdated(Object.keys(this._users));
}
return ctrl;
});
},
/**
* Send a read/recv notification
* @memberof Tinode.Topic#
*
* @param {String} what - what notification to send: recv , read .
* @param {Number} seq - ID or the message read or received.
*/
note: function(what, seq) {
const user = this._users[this._tinode.getCurrentUserID()];
if (user) {
if (!user[what] || user[what] < seq) {
if (this._subscribed) {
this._tinode.note(this.name, what, seq);
} else {
this._tinode.logger("INFO: Not sending {note} on inactive topic");
}
user[what] = seq;
}
} else {
this._tinode.logger("ERROR: note(): user not found " + this._tinode.getCurrentUserID());
}
// Update locally cached contact with the new count
const me = this._tinode.getMeTopic();
if (me) {
me.setMsgReadRecv(this.name, what, seq);
}
},
/**
* Send a 'recv' receipt. Wrapper for {@link Tinode#noteRecv}.
* @memberof Tinode.Topic#
*
* @param {Number} seq - ID of the message to aknowledge.
*/
noteRecv: function(seq) {
this.note('recv', seq);
},
/**
* Send a 'read' receipt. Wrapper for {@link Tinode#noteRead}.
* @memberof Tinode.Topic#
*
* @param {Number} seq - ID of the message to aknowledge.
*/
noteRead: function(seq) {
this.note('read', seq);
},
/**
* Send a key-press notification. Wrapper for {@link Tinode#noteKeyPress}.
* @memberof Tinode.Topic#
*/
noteKeyPress: function() {
if (this._subscribed) {
this._tinode.noteKeyPress(this.name);
} else {
this._tinode.logger("INFO: Cannot send notification in inactive topic");
}
},
/**
* Get user description from global cache. The user does not need to be a
* subscriber of this topic.
* @memberof Tinode.Topic#
*
* @param {String} uid - ID of the user to fetch.
* @return {Object} user description or undefined.
*/
userDesc: function(uid) {
// TODO(gene): handle asynchronous requests
const user = this._cacheGetUser(uid);
if (user) {
return user; // Promise.resolve(user)
}
},
/**
* Get description of the p2p peer from subscription cache.
* @memberof Tinode.Topic#
*
* @return {Object} peer's description or undefined.
*/
p2pPeerDesc: function() {
if (this.getType() != 'p2p') {
return undefined;
}
return this._users[this.name];
},
/**
* Iterate over cached subscribers. If callback is undefined, use this.onMetaSub.
* @memberof Tinode.Topic#
*
* @param {Function} callback - Callback which will receive subscribers one by one.
* @param {Object=} context - Value of `this` inside the `callback`.
*/
subscribers: function(callback, context) {
const cb = (callback || this.onMetaSub);
if (cb) {
for (let idx in this._users) {
cb.call(context, this._users[idx], idx, this._users);
}
}
},
/**
* Get a copy of cached tags.
* @memberof Tinode.Topic#
* @return a copy of tags
*/
tags: function() {
// Return a copy.
return this._tags.slice(0);
},
/**
* Get cached subscription for the given user ID.
* @memberof Tinode.Topic#
*
* @param {String} uid - id of the user to query for
* @return user description or undefined.
*/
subscriber: function(uid) {
return this._users[uid];
},
/**
* Iterate over cached messages. If callback is undefined, use this.onData.
* @memberof Tinode.Topic#
*
* @param {function} callback - Callback which will receive messages one by one. See {@link Tinode.CBuffer#forEach}
* @param {integer} sinceId - Optional seqId to start iterating from (inclusive).
* @param {integer} beforeId - Optional seqId to stop iterating before (exclusive).
* @param {Object} context - Value of `this` inside the `callback`.
*/
messages: function(callback, sinceId, beforeId, context) {
const cb = (callback || this.onData);
if (cb) {
let startIdx = typeof sinceId == 'number' ? this._messages.find({
seq: sinceId
}) : undefined;
let beforeIdx = typeof beforeId == 'number' ? this._messages.find({
seq: beforeId
}, true) : undefined;
if (startIdx != -1 && beforeIdx != -1) {
this._messages.forEach(cb, startIdx, beforeIdx, context);
}
}
},
/**
* Iterate over cached unsent messages. Wraps {@link Tinode.Topic#messages}.
* @memberof Tinode.Topic#
*
* @param {function} callback - Callback which will receive messages one by one. See {@link Tinode.CBuffer#forEach}
* @param {Object} context - Value of `this` inside the `callback`.
*/
queuedMessages: function(callback, context) {
if (!callback) {
throw new Error("Callback must be provided");
}
this.messages(callback, LOCAL_SEQID, undefined, context);
},
/**
* Get the number of topic subscribers who marked this message as either recv or read
* Current user is excluded from the count.
* @memberof Tinode.Topic#
*
* @param {String} what - what notification to send: recv , read .
* @param {Number} seq - ID or the message read or received.
*/
msgReceiptCount: function(what, seq) {
let count = 0;
if (seq > 0) {
const me = this._tinode.getCurrentUserID();
for (let idx in this._users) {
const user = this._users[idx];
if (user.user !== me && user[what] >= seq) {
count++;
}
}
}
return count;
},
/**
* Get the number of topic subscribers who marked this message (and all older messages) as read.
* The current user is excluded from the count.
* @memberof Tinode.Topic#
*
* @param {Number} seq - Message id to check.
* @returns {Number} Number of subscribers who claim to have received the message.
*/
msgReadCount: function(seq) {
return this.msgReceiptCount('read', seq);
},
/**
* Get the number of topic subscribers who marked this message (and all older messages) as received.
* The current user is excluded from the count.
* @memberof Tinode.Topic#
*
* @param {number} seq - Message id to check.
* @returns {number} Number of subscribers who claim to have received the message.
*/
msgRecvCount: function(seq) {
return this.msgReceiptCount('recv', seq);
},
/**
* Check if cached message IDs indicate that the server may have more messages.
* @memberof Tinode.Topic#
*
* @param {boolean} newer check for newer messages
*/
msgHasMoreMessages: function(newer) {
return newer ? this.seq > this._maxSeq :
// _minSeq cound be more than 1, but earlier messages could have been deleted.
(this._minSeq > 1 && !this._noEarlierMsgs);
},
/**
* Check if the given seq Id is id of the most recent message.
* @memberof Tinode.Topic#
*
* @param {integer} seqId id of the message to check
*/
isNewMessage: function(seqId) {
return this._maxSeq <= seqId;
},
/**
* Remove one message from local cache.
* @memberof Tinode.Topic#
*
* @param {integer} seqId id of the message to remove from cache.
* @returns {Message} removed message or undefined if such message was not found.
*/
flushMessage: function(seqId) {
let idx = this._messages.find({
seq: seqId
});
return idx >= 0 ? this._messages.delAt(idx) : undefined;
},
/**
* Remove a range of messages from the local cache.
* @memberof Tinode.Topic#
*
* @param {integer} fromId seq ID of the first message to remove (inclusive).
* @param {integer} untilId seqID of the last message to remove (exclusive).
*
* @returns {Message[]} array of removed messages (could be empty).
*/
flushMessageRange: function(fromId, untilId) {
// start, end: find insertion points (nearest == true).
const since = this._messages.find({
seq: fromId
}, true);
return since >= 0 ? this._messages.delRange(since, this._messages.find({
seq: untilId
}, true)) : [];
},
/**
* Attempt to stop message from being sent.
* @memberof Tinode.Topic#
*
* @param {integer} seqId id of the message to stop sending and remove from cache.
*
* @returns {boolean} true if message was cancelled, false otherwise.
*/
cancelSend: function(seqId) {
const idx = this._messages.find({
seq: seqId
});
if (idx >= 0) {
const msg = this._messages.getAt(idx);
const status = this.msgStatus(msg);
if (status == MESSAGE_STATUS_QUEUED || status == MESSAGE_STATUS_FAILED) {
msg._cancelled = true;
this._messages.delAt(idx);
if (this.onData) {
// Calling with no parameters to indicate the message was deleted.
this.onData();
}
return true;
}
}
return false;
},
/**
* Get type of the topic: me, p2p, grp, fnd...
* @memberof Tinode.Topic#
*
* @returns {String} One of 'me', 'p2p', 'grp', 'fnd' or undefined .
*/
getType: function() {
return Tinode.topicType(this.name);
},
/**
* Get user's cumulative access mode of the topic.
* @memberof Tinode.Topic#
*
* @returns {Tinode.AccessMode} - user's access mode
*/
getAccessMode: function() {
return this.acs;
},
/**
* Get topic's default access mode.
* @memberof Tinode.Topic#
*
* @returns {Tinode.DefAcs} - access mode, such as {auth: `RWP`, anon: `N`}.
*/
getDefaultAccess: function() {
return this.defacs;
},
/**
* Initialize new meta {@link Tinode.GetQuery} builder. The query is attched to the current topic.
* It will not work correctly if used with a different topic.
* @memberof Tinode.Topic#
*
* @returns {Tinode.MetaGetBuilder} query attached to the current topic.
*/
startMetaQuery: function() {
return new MetaGetBuilder(this);
},
/**
* Check if topic is archived, i.e. private.arch == true.
* @memberof Tinode.Topic#
*
* @returns {boolean} - true if topic is archived, false otherwise.
*/
isArchived: function() {
return this.private && this.private.arch ? true : false;
},
/**
* Get status (queued, sent, received etc) of a given message in the context
* of this topic.
* @memberof Tinode.Topic#
*
* @param {Message} msg message to check for status.
* @returns message status constant.
*/
msgStatus: function(msg) {
let status = MESSAGE_STATUS_NONE;
if (this._tinode.isMe(msg.from)) {
if (msg._sending) {
status = MESSAGE_STATUS_SENDING;
} else if (msg._failed) {
status = MESSAGE_STATUS_FAILED;
} else if (msg.seq >= LOCAL_SEQID) {
status = MESSAGE_STATUS_QUEUED;
} else if (this.msgReadCount(msg.seq) > 0) {
status = MESSAGE_STATUS_READ;
} else if (this.msgRecvCount(msg.seq) > 0) {
status = MESSAGE_STATUS_RECEIVED;
} else if (msg.seq > 0) {
status = MESSAGE_STATUS_SENT;
}
} else {
status = MESSAGE_STATUS_TO_ME;
}
return status;
},
// Process data message
_routeData: function(data) {
// Maybe this is an empty message to indicate there are no actual messages.
if (data.content) {
if (!this.touched || this.touched < data.ts) {
this.touched = data.ts;
}
if (!data._noForwarding) {
this._messages.put(data);
}
}
if (data.seq > this._maxSeq) {
this._maxSeq = data.seq;
}
if (data.seq < this._minSeq || this._minSeq == 0) {
this._minSeq = data.seq;
}
if (this.onData) {
this.onData(data);
}
// Update locally cached contact with the new message count.
const me = this._tinode.getMeTopic();
if (me) {
// Messages from the current user are considered to be read already.
me.setMsgReadRecv(this.name,
this._tinode.isMe(data.from) ? 'read' : 'msg',
data.seq, data.ts);
}
},
// Process metadata message
_routeMeta: function(meta) {
if (meta.desc) {
this._lastDescUpdate = meta.ts;
this._processMetaDesc(meta.desc);
}
if (meta.sub && meta.sub.length > 0) {
this._lastSubsUpdate = meta.ts;
this._processMetaSub(meta.sub);
}
if (meta.del) {
this._processDelMessages(meta.del.clear, meta.del.delseq);
}
if (meta.tags) {
this._processMetaTags(meta.tags);
}
if (meta.cred) {
this._processMetaCreds(meta.cred);
}
if (this.onMeta) {
this.onMeta(meta);
}
},
// Process presence change message
_routePres: function(pres) {
let user;
switch (pres.what) {
case 'del':
// Delete cached messages.
this._processDelMessages(pres.clear, pres.delseq);
break;
case 'on':
case 'off':
// Update online status of a subscription.
user = this._users[pres.src];
if (user) {
user.online = pres.what == 'on';
} else {
this._tinode.logger("WARNING: Presence update for an unknown user", this.name, pres.src);
}
break;
case 'term':
// Attachment to topic is terminated probably due to cluster rehashing.
this._resetSub();
break;
case 'acs':
user = this._users[pres.src];
if (!user) {
// Update for an unknown user: notification of a new subscription.
const acs = new AccessMode().updateAll(pres.dacs);
if (acs && acs.mode != AccessMode._NONE) {
user = this._cacheGetUser(pres.src);
if (!user) {
user = {
user: pres.src,
acs: acs
};
this.getMeta(this.startMetaQuery().withOneSub(undefined, pres.src).build());
} else {
user.acs = acs;
}
user.updated = new Date();
this._processMetaSub([user]);
}
} else {
// Known user
user.acs.updateAll(pres.dacs);
// Update user's access mode.
this._processMetaSub([{
user: pres.src,
updated: new Date(),
acs: user.acs
}]);
}
break;
default:
this._tinode.logger("INFO: Ignored presence update", pres.what);
}
if (this.onPres) {
this.onPres(pres);
}
},
// Process {info} message
_routeInfo: function(info) {
if (info.what !== 'kp') {
const user = this._users[info.from];
if (user) {
user[info.what] = info.seq;
if (user.recv < user.read) {
user.recv = user.read;
}
}
// If this is an update from the current user, update the contact with the new count too.
if (this._tinode.isMe(info.from)) {
const me = this._tinode.getMeTopic();
if (me) {
me.setMsgReadRecv(info.topic, info.what, info.seq);
}
}
}
if (this.onInfo) {
this.onInfo(info);
}
},
// Called by Tinode when meta.desc packet is received.
// Called by 'me' topic on contact update (desc._noForwarding is true).
_processMetaDesc: function(desc) {
// Synthetic desc may include defacs for p2p topics which is useless.
// Remove it.
if (this.getType() == 'p2p') {
delete desc.defacs;
}
// Copy parameters from desc object to this topic.
mergeObj(this, desc);
if (typeof this.created == 'string') {
this.created = new Date(this.created);
}
if (typeof this.updated == 'string') {
this.updated = new Date(this.updated);
}
if (typeof this.touched == 'string') {
this.touched = new Date(this.touched);
}
// Update relevant contact in the me topic, if available:
if (this.name !== 'me' && !desc._noForwarding) {
const me = this._tinode.getMeTopic();
if (me) {
// Must use original 'desc' instead of 'this' so not to lose DEL_CHAR.
me._processMetaSub([{
_noForwarding: true,
topic: this.name,
updated: this.updated,
touched: this.touched,
acs: desc.acs,
seq: desc.seq,
read: desc.read,
recv: desc.recv,
public: desc.public,
private: desc.private
}]);
}
}
if (this.onMetaDesc) {
this.onMetaDesc(this);
}
},
// Called by Tinode when meta.sub is recived or in response to received
// {ctrl} after setMeta-sub.
_processMetaSub: function(subs) {
for (let idx in subs) {
const sub = subs[idx];
sub.updated = new Date(sub.updated);
sub.deleted = sub.deleted ? new Date(sub.deleted) : null;
let user = null;
if (!sub.deleted) {
// If this is a change to user's own permissions, update them in topic too.
// Desc will update 'me' topic.
if (this._tinode.isMe(sub.user) && sub.acs) {
this._processMetaDesc({
updated: sub.updated || new Date(),
touched: sub.updated,
acs: sub.acs
});
}
user = this._updateCachedUser(sub.user, sub);
} else {
// Subscription is deleted, remove it from topic (but leave in Users cache)
delete this._users[sub.user];
user = sub;
}
if (this.onMetaSub) {
this.onMetaSub(user);
}
}
if (this.onSubsUpdated) {
this.onSubsUpdated(Object.keys(this._users));
}
},
// Called by Tinode when meta.tags is recived.
_processMetaTags: function(tags) {
if (tags.length == 1 && tags[0] == Tinode.DEL_CHAR) {
tags = [];
}
this._tags = tags;
if (this.onTagsUpdated) {
this.onTagsUpdated(tags);
}
},
// Do nothing for topics other than 'me'
_processMetaCreds: function(creds) {},
// Delete cached messages and update cached transaction IDs
_processDelMessages: function(clear, delseq) {
this._maxDel = Math.max(clear, this._maxDel);
this.clear = Math.max(clear, this.clear);
const topic = this;
let count = 0;
if (Array.isArray(delseq)) {
delseq.map(function(range) {
if (!range.hi) {
count++;
topic.flushMessage(range.low);
} else {
for (let i = range.low; i < range.hi; i++) {
count++;
topic.flushMessage(i);
}
}
});
}
if (count > 0 && this.onData) {
this.onData();
}
},
// Topic is informed that the entire response to {get what=data} has been received.
_allMessagesReceived: function(count) {
if (this.onAllMessagesReceived) {
this.onAllMessagesReceived(count);
}
},
// Reset subscribed state
_resetSub: function() {
this._subscribed = false;
},
// This topic is either deleted or unsubscribed from.
_gone: function() {
this._messages.reset();
this._users = {};
this.acs = new AccessMode(null);
this.private = null;
this.public = null;
this._maxSeq = 0;
this._minSeq = 0;
this._subscribed = false;
const me = this._tinode.getMeTopic();
if (me) {
me._routePres({
_noForwarding: true,
what: 'gone',
topic: 'me',
src: this.name
});
}
if (this.onDeleteTopic) {
this.onDeleteTopic();
}
},
// Update global user cache and local subscribers cache.
// Don't call this method for non-subscribers.
_updateCachedUser: function(uid, obj) {
// Fetch user object from the global cache.
// This is a clone of the stored object
let cached = this._cacheGetUser(uid);
cached = mergeObj(cached || {}, obj);
// Save to global cache
this._cachePutUser(uid, cached);
// Save to the list of topic subsribers.
return mergeToCache(this._users, uid, cached);
},
// Get local seqId for a queued message.
_getQueuedSeqId: function() {
return this._queuedSeqId++;
}
};
/**
* @class TopicMe - special case of {@link Tinode.Topic} for
* managing data of the current user, including contact list.
* @extends Tinode.Topic
* @memberof Tinode
*
* @param {TopicMe.Callbacks} callbacks - Callbacks to receive various events.
*/
var TopicMe = function(callbacks) {
Topic.call(this, TOPIC_ME, callbacks);
// List of contacts (topic_name -> Contact object)
this._contacts = {};
// me-specific callbacks
if (callbacks) {
this.onContactUpdate = callbacks.onContactUpdate;
}
};
// Inherit everyting from the generic Topic
TopicMe.prototype = Object.create(Topic.prototype, {
// Override the original Topic._processMetaSub
_processMetaSub: {
value: function(subs) {
let updateCount = 0;
for (let idx in subs) {
const sub = subs[idx];
const topicName = sub.topic;
// Don't show 'me' and 'fnd' topics in the list of contacts.
if (topicName == TOPIC_FND || topicName == TOPIC_ME) {
continue;
}
sub.updated = new Date(sub.updated);
sub.touched = sub.touched ? new Date(sub.touched) : undefined;
sub.deleted = sub.deleted ? new Date(sub.deleted) : null;
let cont = null;
if (sub.deleted) {
cont = sub;
delete this._contacts[topicName];
} else if (sub.acs && !sub.acs.isJoiner()) {
cont = sub;
cont.deleted = new Date();
delete this._contacts[topicName];
} else {
// Ensure the values are defined and are integers.
if (typeof sub.seq != 'undefined') {
sub.seq = sub.seq | 0;
sub.recv = sub.recv | 0;
sub.read = sub.read | 0;
sub.unread = sub.seq - sub.read;
}
if (sub.seen && sub.seen.when) {
sub.seen.when = new Date(sub.seen.when);
}
cont = mergeToCache(this._contacts, topicName, sub);
if (Tinode.topicType(topicName) == 'p2p') {
this._cachePutUser(topicName, cont);
}
// Notify topic of the update if it's an external update.
if (!sub._noForwarding) {
const topic = this._tinode.getTopic(topicName);
if (topic) {
sub._noForwarding = true;
topic._processMetaDesc(sub);
}
}
}
updateCount++;
if (this.onMetaSub) {
this.onMetaSub(cont);
}
}
if (this.onSubsUpdated) {
this.onSubsUpdated(Object.keys(this._contacts), updateCount);
}
},
enumerable: true,
configurable: true,
writable: false
},
// Called by Tinode when meta.sub is recived.
_processMetaCreds: {
value: function(creds, upd) {
if (creds.length == 1 && creds[0] == Tinode.DEL_CHAR) {
creds = [];
}
if (upd) {
creds.map((cr) => {
if (cr.val) {
// Adding a credential.
let idx = this._credentials.findIndex((el) => {
return el.meth == cr.meth && el.val == cr.val;
});
if (idx < 0) {
// Not found.
if (!cr.done) {
// Unconfirmed credential replaces previous unconfirmed credential of the same method.
idx = this._credentials.findIndex((el) => {
return el.meth == cr.meth && !el.done;
});
if (idx >= 0) {
// Remove previous unconfirmed credential.
this._credentials.splice(idx, 1);
}
}
this._credentials.push(cr);
} else {
// Found. Maybe change 'done' status.
this._credentials[idx].done = cr.done;
}
} else if (cr.resp) {
// Handle credential confirmation.
const idx = this._credentials.findIndex((el) => {
return el.meth == cr.meth && !el.done;
});
if (idx >= 0) {
this._credentials[idx].done = true;
}
}
});
} else {
this._credentials = creds;
}
if (this.onCredsUpdated) {
this.onCredsUpdated(this._credentials);
}
},
enumerable: true,
configurable: true,
writable: false
},
// Process presence change message
_routePres: {
value: function(pres) {
if (pres.what == 'term') {
// The 'me' topic itself is detached. Mark as unsubscribed.
this._resetSub();
return;
}
const cont = this._contacts[pres.src];
if (cont) {
switch (pres.what) {
case 'on': // topic came online
cont.online = true;
break;
case 'off': // topic went offline
if (cont.online) {
cont.online = false;
if (cont.seen) {
cont.seen.when = new Date();
} else {
cont.seen = {
when: new Date()
};
}
}
break;
case 'msg': // new message received
cont.touched = new Date();
cont.seq = pres.seq | 0;
cont.unread = cont.seq - cont.read;
break;
case 'upd': // desc updated
// Request updated description
this.getMeta(this.startMetaQuery().withLaterOneSub(pres.src).build());
break;
case 'acs': // access mode changed
if (cont.acs) {
cont.acs.updateAll(pres.dacs);
} else {
cont.acs = new AccessMode().updateAll(pres.dacs);
}
cont.touched = new Date();
break;
case 'ua': // user agent changed
cont.seen = {
when: new Date(),
ua: pres.ua
};
break;
case 'recv': // user's other session marked some messges as received
cont.recv = cont.recv ? Math.max(cont.recv, pres.seq) : (pres.seq | 0);
break;
case 'read': // user's other session marked some messages as read
cont.read = cont.read ? Math.max(cont.read, pres.seq) : (pres.seq | 0);
cont.unread = cont.seq - cont.read;
break;
case 'gone': // topic deleted or unsubscribed from
delete this._contacts[pres.src];
break;
case 'del':
// Update topic.del value.
break;
default:
this._tinode.logger("INFO: Unsupported presence update in 'me'", pres.what);
}
if (this.onContactUpdate) {
this.onContactUpdate(pres.what, cont);
}
} else {
if (pres.what == 'acs') {
// New subscriptions and deleted/banned subscriptions have full
// access mode (no + or - in the dacs string). Changes to known subscriptions are sent as
// deltas, but they should not happen here.
const acs = new AccessMode(pres.dacs);
if (!acs || acs.mode == AccessMode._INVALID) {
this._tinode.logger("ERROR: Invalid access mode update", pres.src, pres.dacs);
return;
} else if (acs.mode == AccessMode._NONE) {
this._tinode.logger("WARNING: Removing non-existent subscription", pres.src, pres.dacs);
return;
} else {
// New subscription. Send request for the full description.
// Using .withOneSub (not .withLaterOneSub) to make sure IfModifiedSince is not set.
this.getMeta(this.startMetaQuery().withOneSub(undefined, pres.src).build());
// Create a dummy entry to catch online status update.
this._contacts[pres.src] = {
touched: new Date(),
topic: pres.src,
online: false,
acs: acs
};
}
} else if (pres.what == 'tags') {
this.getMeta(this.startMetaQuery().withTags().build());
}
}
if (this.onPres) {
this.onPres(pres);
}
},
enumerable: true,
configurable: true,
writable: false
},
/**
* Publishing to TopicMe is not supported. {@link Topic#publish} is overriden and thows an {Error} if called.
* @memberof Tinode.TopicMe#
* @throws {Error} Always throws an error.
*/
publish: {
value: function() {
return Promise.reject(new Error("Publishing to 'me' is not supported"));
},
enumerable: true,
configurable: true,
writable: false
},
/**
* Delete validation credential.
* @memberof Tinode.TopicMe#
*
* @param {String} topic - Name of the topic to delete
* @param {String} user - User ID to remove.
* @returns {Promise} Promise which will be resolved/rejected on receiving server reply.
*/
delCredential: {
value: function(method, value) {
if (!this._subscribed) {
return Promise.reject(new Error("Cannot delete credential in inactive 'me' topic"));
}
// Send {del} message, return promise
return this._tinode.delCredential(this.name, method, value).then((ctrl) => {
// Remove deleted credential from the cache.
const index = this._credentials.findIndex((el) => {
return el.meth == method && el.val == value;
});
if (index > -1) {
this._credentials.splice(index, 1);
}
// Notify listeners
if (this.onCredsUpdated) {
this.onCredsUpdated(this._credentials);
}
return ctrl;
});
},
enumerable: true,
configurable: true,
writable: false
},
/**
* Iterate over cached contacts. If callback is undefined, use {@link this.onMetaSub}.
* @function
* @memberof Tinode.TopicMe#
* @param {TopicMe.ContactCallback=} callback - Callback to call for each contact.
* @param {boolean=} includeBanned - Include banned contacts.
* @param {Object=} context - Context to use for calling the `callback`, i.e. the value of `this` inside the callback.
*/
contacts: {
value: function(callback, includeBanned, context) {
const cb = (callback || this.onMetaSub);
if (cb) {
for (let idx in this._contacts) {
if (!includeBanned &&
(!this._contacts[idx] ||
!this._contacts[idx].acs ||
!this._contacts[idx].acs.isJoiner())) {
continue;
}
cb.call(context, this._contacts[idx], idx, this._contacts);
}
}
},
enumerable: true,
configurable: true,
writable: true
},
/**
* Update a cached contact with new read/received/message count.
* @function
* @memberof Tinode.TopicMe#
*
* @param {String} contactName - UID of contact to update.
* @param {String} what - Whach count to update, one of "read", "recv", "msg"
* @param {Number} seq - New value of the count.
* @param {Date} ts - Timestamp of the update.
*/
setMsgReadRecv: {
value: function(contactName, what, seq, ts) {
const cont = this._contacts[contactName];
let oldVal, doUpdate = false;
let mode = null;
if (cont) {
seq = seq | 0;
cont.seq = cont.seq | 0;
cont.read = cont.read | 0;
cont.recv = cont.recv | 0;
switch (what) {
case 'recv':
oldVal = cont.recv;
cont.recv = Math.max(cont.recv, seq);
doUpdate = (oldVal != cont.recv);
break;
case 'read':
oldVal = cont.read;
cont.read = Math.max(cont.read, seq);
doUpdate = (oldVal != cont.read);
break;
case 'msg':
oldVal = cont.seq;
cont.seq = Math.max(cont.seq, seq);
if (!cont.touched || cont.touched < ts) {
cont.touched = ts;
}
doUpdate = (oldVal != cont.seq);
break;
}
// Sanity checks.
if (cont.recv < cont.read) {
cont.recv = cont.read;
doUpdate = true;
}
if (cont.seq < cont.recv) {
cont.seq = cont.recv;
if (!cont.touched || cont.touched < ts) {
cont.touched = ts;
}
doUpdate = true;
}
cont.unread = cont.seq - cont.read;
if (doUpdate && (!cont.acs || !cont.acs.isMuted()) && this.onContactUpdate) {
this.onContactUpdate(what, cont);
}
}
},
enumerable: true,
configurable: true,
writable: true
},
/**
* Get a contact from cache.
* @memberof Tinode.TopicMe#
*
* @param {string} name - Name of the contact to get, either a UID (for p2p topics) or a topic name.
* @returns {Tinode.Contact} - Contact or `undefined`.
*/
getContact: {
value: function(name) {
return this._contacts[name];
},
enumerable: true,
configurable: true,
writable: true
},
/**
* Get access mode of a given contact from cache.
* @memberof Tinode.TopicMe#
*
* @param {String} name - Name of the contact to get access mode for, either a UID (for p2p topics) or a topic name.
* @returns {string} - access mode, such as `RWP`.
*/
getAccessMode: {
value: function(name) {
const cont = this._contacts[name];
return cont ? cont.acs : null;
},
enumerable: true,
configurable: true,
writable: true
},
/**
* Check if contact is archived, i.e. contact.private.arch == true.
* @memberof Tinode.TopicMe#
*
* @param {String} name - Name of the contact to check archived status, either a UID (for p2p topics) or a topic name.
* @returns {boolean} - true if contact is archived, false otherwise.
*/
isArchived: {
value: function(name) {
const cont = this._contacts[name];
return cont ? ((cont.private && cont.private.arch) ? true : false) : null;
},
enumerable: true,
configurable: true,
writable: true
},
/**
* @typedef Tinode.Credential
* @memberof Tinode
* @type Object
* @property {string} meth - validation method such as 'email' or 'tel'.
* @property {string} val - credential value, i.e. 'jdoe@example.com' or '+17025551234'
* @property {boolean} done - true if credential is validated.
*/
/**
* Get the user's credentials: email, phone, etc.
* @memberof Tinode.TopicMe#
*
* @returns {Tinode.Credential[]} - array of credentials.
*/
getCredentials: {
value: function() {
return this._credentials;
},
enumerable: true,
configurable: true,
writable: true
}
});
TopicMe.prototype.constructor = TopicMe;
/**
* @class TopicFnd - special case of {@link Tinode.Topic} for searching for
* contacts and group topics.
* @extends Tinode.Topic
* @memberof Tinode
*
* @param {TopicFnd.Callbacks} callbacks - Callbacks to receive various events.
*/
var TopicFnd = function(callbacks) {
Topic.call(this, TOPIC_FND, callbacks);
// List of users and topics uid or topic_name -> Contact object)
this._contacts = {};
};
// Inherit everyting from the generic Topic
TopicFnd.prototype = Object.create(Topic.prototype, {
// Override the original Topic._processMetaSub
_processMetaSub: {
value: function(subs) {
let updateCount = Object.getOwnPropertyNames(this._contacts).length;
// Reset contact list.
this._contacts = {};
for (let idx in subs) {
let sub = subs[idx];
const indexBy = sub.topic ? sub.topic : sub.user;
sub.updated = new Date(sub.updated);
if (sub.seen && sub.seen.when) {
sub.seen.when = new Date(sub.seen.when);
}
sub = mergeToCache(this._contacts, indexBy, sub);
updateCount++;
if (this.onMetaSub) {
this.onMetaSub(sub);
}
}
if (updateCount > 0 && this.onSubsUpdated) {
this.onSubsUpdated(Object.keys(this._contacts));
}
},
enumerable: true,
configurable: true,
writable: false
},
/**
* Publishing to TopicFnd is not supported. {@link Topic#publish} is overriden and thows an {Error} if called.
* @memberof Tinode.TopicFnd#
* @throws {Error} Always throws an error.
*/
publish: {
value: function() {
return Promise.reject(new Error("Publishing to 'fnd' is not supported"));
},
enumerable: true,
configurable: true,
writable: false
},
/**
* setMeta to TopicFnd resets contact list in addition to sending the message.
* @memberof Tinode.TopicFnd#
* @param {Tinode.SetParams} params parameters to update.
* @returns {Promise} Promise to be resolved/rejected when the server responds to request.
*/
setMeta: {
value: function(params) {
const instance = this;
return Object.getPrototypeOf(TopicFnd.prototype).setMeta.call(this, params).then(function() {
if (Object.keys(instance._contacts).length > 0) {
instance._contacts = {};
if (instance.onSubsUpdated) {
instance.onSubsUpdated([]);
}
}
});
},
enumerable: true,
configurable: true,
writable: false
},
/**
* Iterate over found contacts. If callback is undefined, use {@link this.onMetaSub}.
* @function
* @memberof Tinode.TopicFnd#
* @param {TopicFnd.ContactCallback} callback - Callback to call for each contact.
* @param {Object} context - Context to use for calling the `callback`, i.e. the value of `this` inside the callback.
*/
contacts: {
value: function(callback, context) {
const cb = (callback || this.onMetaSub);
if (cb) {
for (let idx in this._contacts) {
cb.call(context, this._contacts[idx], idx, this._contacts);
}
}
},
enumerable: true,
configurable: true,
writable: true
}
});
TopicFnd.prototype.constructor = TopicFnd;
/**
* @class LargeFileHelper - collection of utilities for uploading and downloading files
* out of band. Don't instantiate this class directly. Use {Tinode.getLargeFileHelper} instead.
* @memberof Tinode
*
* @param {Tinode} tinode - the main Tinode object.
*/
var LargeFileHelper = function(tinode) {
this._tinode = tinode;
this._apiKey = tinode._apiKey;
this._authToken = tinode.getAuthToken();
this._msgId = tinode.getNextUniqueId();
this.xhr = xdreq();
// Promise
this.toResolve = null;
this.toReject = null;
// Callbacks
this.onProgress = null;
this.onSuccess = null;
this.onFailure = null;
}
LargeFileHelper.prototype = {
/**
* Start uploading the file to a non-default endpoint.
*
* @memberof Tinode.LargeFileHelper#
*
* @param {String} baseUrl alternative base URL of upload server.
* @param {File} file to upload
* @param {Callback} onProgress callback. Takes one {float} parameter 0..1
* @param {Callback} onSuccess callback. Called when the file is successfully uploaded.
* @param {Callback} onFailure callback. Called in case of a failure.
*
* @returns {Promise} resolved/rejected when the upload is completed/failed.
*/
uploadWithBaseUrl: function(baseUrl, file, onProgress, onSuccess, onFailure) {
if (!this._authToken) {
throw new Error("Must authenticate first");
}
const instance = this;
let url = '/v' + PROTOCOL_VERSION + '/file/u/';
if (baseUrl) {
if (baseUrl.indexOf('http://') == 0 || baseUrl.indexOf('https://') == 0) {
url = baseUrl + url;
} else {
throw new Error("Invalid base URL '" + baseUrl + "'");
}
}
this.xhr.open('POST', url, true);
this.xhr.setRequestHeader('X-Tinode-APIKey', this._apiKey);
this.xhr.setRequestHeader('X-Tinode-Auth', 'Token ' + this._authToken.token);
const result = new Promise((resolve, reject) => {
this.toResolve = resolve;
this.toReject = reject;
});
this.onProgress = onProgress;
this.onSuccess = onSuccess;
this.onFailure = onFailure;
this.xhr.upload.onprogress = function(e) {
if (e.lengthComputable && instance.onProgress) {
instance.onProgress(e.loaded / e.total);
}
}
this.xhr.onload = function() {
let pkt;
try {
pkt = JSON.parse(this.response, jsonParseHelper);
} catch (err) {
instance._tinode.logger("ERROR: Invalid server response in LargeFileHelper", this.response);
}
if (this.status >= 200 && this.status < 300) {
if (instance.toResolve) {
instance.toResolve(pkt.ctrl.params.url);
}
if (instance.onSuccess) {
instance.onSuccess(pkt.ctrl);
}
} else if (this.status >= 400) {
if (instance.toReject) {
instance.toReject(new Error(pkt.ctrl.text + " (" + pkt.ctrl.code + ")"));
}
if (instance.onFailure) {
instance.onFailure(pkt.ctrl)
}
} else {
instance._tinode.logger("ERROR: Unexpected server response status", this.status, this.response);
}
};
this.xhr.onerror = function(e) {
if (instance.toReject) {
instance.toReject(new Error("failed"));
}
if (instance.onFailure) {
instance.onFailure(null);
}
};
this.xhr.onabort = function(e) {
if (instance.toReject) {
instance.toReject(new Error("upload cancelled by user"));
}
if (instance.onFailure) {
instance.onFailure(null);
}
};
try {
const form = new FormData();
form.append('file', file);
form.set('id', this._msgId);
this.xhr.send(form);
} catch (err) {
if (this.toReject) {
this.toReject(err);
}
if (this.onFailure) {
this.onFailure(null);
}
}
return result;
},
/**
* Start uploading the file to default endpoint.
*
* @memberof Tinode.LargeFileHelper#
*
* @param {File} file to upload
* @param {Callback} onProgress callback. Takes one {float} parameter 0..1
* @param {Callback} onSuccess callback. Called when the file is successfully uploaded.
* @param {Callback} onFailure callback. Called in case of a failure.
*
* @returns {Promise} resolved/rejected when the upload is completed/failed.
*/
upload: function(file, onProgress, onSuccess, onFailure) {
return this.uploadWithBaseUrl(undefined, file, onProgress, onSuccess, onFailure);
},
/**
* Download the file from a given URL using GET request. This method works with the Tinode server only.
*
* @memberof Tinode.LargeFileHelper#
*
* @param {String} relativeUrl - URL to download the file from. Must be relative url, i.e. must not contain the host.
* @param {String=} filename - file name to use for the downloaded file.
*
* @returns {Promise} resolved/rejected when the download is completed/failed.
*/
download: function(relativeUrl, filename, mimetype, onProgress) {
// Check if the URL is relative. Check for cases like:
// 'http://example.com'
// ' http://example.com'
// '//example.com/'
// 'http:example.com'
// 'http:/example.com'
if (/^\s*([a-z][a-z0-9+.-]*:|\/\/)/im.test(relativeUrl)) {
// As a security measure refuse to download from an absolute URL.
throw new Error("The URL '" + relativeUrl + "' must be relative, not absolute");
}
if (!this._authToken) {
throw new Error("Must authenticate first");
}
const instance = this;
// Get data as blob (stored by the browser as a temporary file).
this.xhr.open('GET', relativeUrl, true);
this.xhr.setRequestHeader('X-Tinode-APIKey', this._apiKey);
this.xhr.setRequestHeader('X-Tinode-Auth', 'Token ' + this._authToken.token);
this.xhr.responseType = 'blob';
this.onProgress = onProgress;
this.xhr.onprogress = function(e) {
if (instance.onProgress) {
// Passing e.loaded instead of e.loaded/e.total because e.total
// is always 0 with gzip compression enabled by the server.
instance.onProgress(e.loaded);
}
};
const result = new Promise((resolve, reject) => {
this.toResolve = resolve;
this.toReject = reject;
});
// The blob needs to be saved as file. There is no known way to
// save the blob as file other than to fake a click on an .
this.xhr.onload = function() {
if (this.status == 200) {
const link = document.createElement('a');
link.href = window.URL.createObjectURL(new Blob([this.response], {
type: mimetype
}));
link.style.display = 'none';
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(link.href);
if (instance.toResolve) {
instance.toResolve();
}
} else if (this.status >= 400 && instance.toReject) {
// The this.responseText is undefined, must use this.response which is a blob.
// Need to convert this.response to JSON. The blob can only be accessed by the
// FileReader.
const reader = new FileReader();
reader.onload = function() {
try {
const pkt = JSON.parse(this.result, jsonParseHelper);
instance.toReject(new Error(pkt.ctrl.text + " (" + pkt.ctrl.code + ")"));
} catch (err) {
instance._tinode.logger("ERROR: Invalid server response in LargeFileHelper", this.result);
instance.toReject(err);
}
};
reader.readAsText(this.response);
}
};
this.xhr.onerror = function(e) {
if (instance.toReject) {
instance.toReject(new Error("failed"));
}
};
this.xhr.onabort = function() {
if (instance.toReject) {
instance.toReject(null);
}
};
try {
this.xhr.send();
} catch (err) {
if (this.toReject) {
this.toReject(err);
}
}
return result;
},
/**
* Try to cancel an ongoing upload or download.
* @memberof Tinode.LargeFileHelper#
*/
cancel: function() {
if (this.xhr && this.xhr.readyState < 4) {
this.xhr.abort();
}
},
/**
* Get unique id of this request.
* @memberof Tinode.LargeFileHelper#
*
* @returns {string} unique id
*/
getId: function() {
return this._msgId;
}
};
/**
* @class Message - definition a communication message.
* Work in progress.
* @memberof Tinode
*
* @param {string} topic_ - name of the topic the message belongs to.
* @param {string | Drafty} content_ - message contant.
*/
var Message = function(topic_, content_) {
this.status = Message.STATUS_NONE;
this.topic = topic_;
this.content = content_;
}
Message.STATUS_NONE = MESSAGE_STATUS_NONE;
Message.STATUS_QUEUED = MESSAGE_STATUS_QUEUED;
Message.STATUS_SENDING = MESSAGE_STATUS_SENDING;
Message.STATUS_FAILED = MESSAGE_STATUS_FAILED;
Message.STATUS_SENT = MESSAGE_STATUS_SENT;
Message.STATUS_RECEIVED = MESSAGE_STATUS_RECEIVED;
Message.STATUS_READ = MESSAGE_STATUS_READ;
Message.STATUS_TO_ME = MESSAGE_STATUS_TO_ME;
Message.prototype = {
/**
* Convert message object to {pub} packet.
*/
toJSON: function() {
},
/**
* Parse JSON into message.
*/
fromJSON: function(json) {
}
}
Message.prototype.constructor = Message;
if (typeof module != 'undefined') {
module.exports = Tinode;
module.exports.Drafty = Drafty;
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../version.json":3,"./drafty.js":1}],3:[function(require,module,exports){
module.exports={"version": "0.16.2"}
},{}]},{},[2])(2)
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvZHJhZnR5LmpzIiwic3JjL3Rpbm9kZS5qcyIsInZlcnNpb24uanNvbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQy8xQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7O0FDcDZLQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24oKXtmdW5jdGlvbiByKGUsbix0KXtmdW5jdGlvbiBvKGksZil7aWYoIW5baV0pe2lmKCFlW2ldKXt2YXIgYz1cImZ1bmN0aW9uXCI9PXR5cGVvZiByZXF1aXJlJiZyZXF1aXJlO2lmKCFmJiZjKXJldHVybiBjKGksITApO2lmKHUpcmV0dXJuIHUoaSwhMCk7dmFyIGE9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitpK1wiJ1wiKTt0aHJvdyBhLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsYX12YXIgcD1uW2ldPXtleHBvcnRzOnt9fTtlW2ldWzBdLmNhbGwocC5leHBvcnRzLGZ1bmN0aW9uKHIpe3ZhciBuPWVbaV1bMV1bcl07cmV0dXJuIG8obnx8cil9LHAscC5leHBvcnRzLHIsZSxuLHQpfXJldHVybiBuW2ldLmV4cG9ydHN9Zm9yKHZhciB1PVwiZnVuY3Rpb25cIj09dHlwZW9mIHJlcXVpcmUmJnJlcXVpcmUsaT0wO2k8dC5sZW5ndGg7aSsrKW8odFtpXSk7cmV0dXJuIG99cmV0dXJuIHJ9KSgpIiwiLyoqXG4gKiBAY29weXJpZ2h0IDIwMTUtMjAxOSBUaW5vZGVcbiAqIEBzdW1tYXJ5IE1pbmltYWxseSByaWNoIHRleHQgcmVwcmVzZW50YXRpb24gYW5kIGZvcm1hdHRpbmcgZm9yIFRpbm9kZS5cbiAqIEBsaWNlbnNlIEFwYWNoZSAyLjBcbiAqIEB2ZXJzaW9uIDAuMTZcbiAqXG4gKiBAZmlsZSBCYXNpYyBwYXJzZXIgYW5kIGZvcm1hdHRlciBmb3IgdmVyeSBzaW1wbGUgdGV4dCBtYXJrdXAuIE1vc3RseSB0YXJnZXRlZCBhdFxuICogbW9iaWxlIHVzZSBjYXNlcyBzaW1pbGFyIHRvIFRlbGVncmFtLCBXaGF0c0FwcCwgYW5kIEZCIE1lc3Nlbmdlci5cbiAqXG4gKiA8cD5TdXBwb3J0cyBjb252ZXJzaW9uIG9mIHVzZXIga2V5Ym9hcmQgaW5wdXQgdG8gZm9ybWF0dGVkIHRleHQ6PC9wPlxuICogPHVsPlxuICogICA8bGk+KmFiYyogJnJhcnI7IDxiPmFiYzwvYj48L2xpPlxuICogICA8bGk+X2FiY18gJnJhcnI7IDxpPmFiYzwvaT48L2xpPlxuICogICA8bGk+fmFiY34gJnJhcnI7IDxkZWw+YWJjPC9kZWw+PC9saT5cbiAqICAgPGxpPmBhYmNgICZyYXJyOyA8dHQ+YWJjPC90dD48L2xpPlxuICogPC91bD5cbiAqIEFsc28gc3VwcG9ydHMgZm9ybXMgYW5kIGJ1dHRvbnMuXG4gKlxuICogTmVzdGVkIGZvcm1hdHRpbmcgaXMgc3VwcG9ydGVkLCBlLmcuICphYmMgX2RlZl8qIC0+IDxiPmFiYyA8aT5kZWY8L2k+PC9iPlxuICogVVJMcywgQG1lbnRpb25zLCBhbmQgI2hhc2h0YWdzIGFyZSBleHRyYWN0ZWQgYW5kIGNvbnZlcnRlZCBpbnRvIGxpbmtzLlxuICogRm9ybXMgYW5kIGJ1dHRvbnMgY2FuIGJlIGFkZGVkIHByb2NlZHVyYWxseS5cbiAqIEpTT04gZGF0YSByZXByZXNlbnRhdGlvbiBpcyBpbnNwaXJlZCBieSBEcmFmdC5qcyByYXcgZm9ybWF0dGluZy5cbiAqXG4gKlxuICogQGV4YW1wbGVcbiAqIFRleHQ6XG4gKiA8cHJlPlxuICogICAgIHRoaXMgaXMgKmJvbGQqLCBgY29kZWAgYW5kIF9pdGFsaWNfLCB+c3RyaWtlflxuICogICAgIGNvbWJpbmVkICpib2xkIGFuZCBfaXRhbGljXypcbiAqICAgICBhbiB1cmw6IGh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2FiYyNmcmFnbWVudCBhbmQgYW5vdGhlciBfd3d3LnRpbm9kZS5jb19cbiAqICAgICB0aGlzIGlzIGEgQG1lbnRpb24gYW5kIGEgI2hhc2h0YWcgaW4gYSBzdHJpbmdcbiAqICAgICBzZWNvbmQgI2hhc2h0YWdcbiAqIDwvcHJlPlxuICpcbiAqICBTYW1wbGUgSlNPTiByZXByZXNlbnRhdGlvbiBvZiB0aGUgdGV4dCBhYm92ZTpcbiAqICB7XG4gKiAgICAgXCJ0eHRcIjogXCJ0aGlzIGlzIGJvbGQsIGNvZGUgYW5kIGl0YWxpYywgc3RyaWtlIGNvbWJpbmVkIGJvbGQgYW5kIGl0YWxpYyBhbiB1cmw6IGh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2FiYyNmcmFnbWVudCBcIiArXG4gKiAgICAgICAgICAgICBcImFuZCBhbm90aGVyIHd3dy50aW5vZGUuY28gdGhpcyBpcyBhIEBtZW50aW9uIGFuZCBhICNoYXNodGFnIGluIGEgc3RyaW5nIHNlY29uZCAjaGFzaHRhZ1wiLFxuICogICAgIFwiZm10XCI6IFtcbiAqICAgICAgICAgeyBcImF0XCI6OCwgXCJsZW5cIjo0LFwidHBcIjpcIlNUXCIgfSx7IFwiYXRcIjoxNCwgXCJsZW5cIjo0LCBcInRwXCI6XCJDT1wiIH0seyBcImF0XCI6MjMsIFwibGVuXCI6NiwgXCJ0cFwiOlwiRU1cIn0sXG4gKiAgICAgICAgIHsgXCJhdFwiOjMxLCBcImxlblwiOjYsIFwidHBcIjpcIkRMXCIgfSx7IFwidHBcIjpcIkJSXCIsIFwibGVuXCI6MSwgXCJhdFwiOjM3IH0seyBcImF0XCI6NTYsIFwibGVuXCI6NiwgXCJ0cFwiOlwiRU1cIiB9LFxuICogICAgICAgICB7IFwiYXRcIjo0NywgXCJsZW5cIjoxNSwgXCJ0cFwiOlwiU1RcIiB9LHsgXCJ0cFwiOlwiQlJcIiwgXCJsZW5cIjoxLCBcImF0XCI6NjIgfSx7IFwiYXRcIjoxMjAsIFwibGVuXCI6MTMsIFwidHBcIjpcIkVNXCIgfSxcbiAqICAgICAgICAgeyBcImF0XCI6NzEsIFwibGVuXCI6MzYsIFwia2V5XCI6MCB9LHsgXCJhdFwiOjEyMCwgXCJsZW5cIjoxMywgXCJrZXlcIjoxIH0seyBcInRwXCI6XCJCUlwiLCBcImxlblwiOjEsIFwiYXRcIjoxMzMgfSxcbiAqICAgICAgICAgeyBcImF0XCI6MTQ0LCBcImxlblwiOjgsIFwia2V5XCI6MiB9LHsgXCJhdFwiOjE1OSwgXCJsZW5cIjo4LCBcImtleVwiOjMgfSx7IFwidHBcIjpcIkJSXCIsIFwibGVuXCI6MSwgXCJhdFwiOjE3OSB9LFxuICogICAgICAgICB7IFwiYXRcIjoxODcsIFwibGVuXCI6OCwgXCJrZXlcIjozIH0seyBcInRwXCI6XCJCUlwiLCBcImxlblwiOjEsIFwiYXRcIjoxOTUgfVxuICogICAgIF0sXG4gKiAgICAgXCJlbnRcIjogW1xuICogICAgICAgICB7IFwidHBcIjpcIkxOXCIsIFwiZGF0YVwiOnsgXCJ1cmxcIjpcImh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2FiYyNmcmFnbWVudFwiIH0gfSxcbiAqICAgICAgICAgeyBcInRwXCI6XCJMTlwiLCBcImRhdGFcIjp7IFwidXJsXCI6XCJodHRwOi8vd3d3LnRpbm9kZS5jb1wiIH0gfSxcbiAqICAgICAgICAgeyBcInRwXCI6XCJNTlwiLCBcImRhdGFcIjp7IFwidmFsXCI6XCJtZW50aW9uXCIgfSB9LFxuICogICAgICAgICB7IFwidHBcIjpcIkhUXCIsIFwiZGF0YVwiOnsgXCJ2YWxcIjpcImhhc2h0YWdcIiB9IH1cbiAqICAgICBdXG4gKiAgfVxuICovXG5cbid1c2Ugc3RyaWN0JztcblxuY29uc3QgTUFYX0ZPUk1fRUxFTUVOVFMgPSA4O1xuY29uc3QgSlNPTl9NSU1FX1RZUEUgPSAnYXBwbGljYXRpb24vanNvbic7XG5cbi8vIFJlZ3VsYXIgZXhwcmVzc2lvbnMgZm9yIHBhcnNpbmcgaW5saW5lIGZvcm1hdHMuIEphdmFzY3JpcHQgZG9lcyBub3Qgc3VwcG9ydCBsb29rYmVoaW5kLFxuLy8gc28gaXQncyBhIGJpdCBtZXNzeS5cbmNvbnN0IElOTElORV9TVFlMRVMgPSBbXG4gIC8vIFN0cm9uZyA9IGJvbGQsICpib2xkIHRleHQqXG4gIHtcbiAgICBuYW1lOiAnU1QnLFxuICAgIHN0YXJ0OiAvKD86XnxbXFxXX10pKFxcKilbXlxccypdLyxcbiAgICBlbmQ6IC9bXlxccypdKFxcKikoPz0kfFtcXFdfXSkvXG4gIH0sXG4gIC8vIEVtcGhlc2l6ZWQgPSBpdGFsaWMsIF9pdGFsaWMgdGV4dF9cbiAge1xuICAgIG5hbWU6ICdFTScsXG4gICAgc3RhcnQ6IC8oPzpefFxcVykoXylbXlxcc19dLyxcbiAgICBlbmQ6IC9bXlxcc19dKF8pKD89JHxcXFcpL1xuICB9LFxuICAvLyBEZWxldGVkLCB+c3RyaWtlIHRoaXMgdGhvdWdoflxuICB7XG4gICAgbmFtZTogJ0RMJyxcbiAgICBzdGFydDogLyg/Ol58W1xcV19dKSh+KVteXFxzfl0vLFxuICAgIGVuZDogL1teXFxzfl0ofikoPz0kfFtcXFdfXSkvXG4gIH0sXG4gIC8vIENvZGUgYmxvY2sgYHRoaXMgaXMgbW9ub3NwYWNlYFxuICB7XG4gICAgbmFtZTogJ0NPJyxcbiAgICBzdGFydDogLyg/Ol58XFxXKShgKVteYF0vLFxuICAgIGVuZDogL1teYF0oYCkoPz0kfFxcVykvXG4gIH1cbl07XG5cbi8vIFJlZ0V4cHMgZm9yIGVudGl0eSBleHRyYWN0aW9uIChSRiA9IHJlZmVyZW5jZSlcbmNvbnN0IEVOVElUWV9UWVBFUyA9IFtcbiAgLy8gVVJMc1xuICB7XG4gICAgbmFtZTogJ0xOJyxcbiAgICBkYXRhTmFtZTogJ3VybCcsXG4gICAgcGFjazogZnVuY3Rpb24odmFsKSB7XG4gICAgICAvLyBDaGVjayBpZiB0aGUgcHJvdG9jb2wgaXMgc3BlY2lmaWVkLCBpZiBub3QgdXNlIGh0dHBcbiAgICAgIGlmICghL15bYS16XSs6XFwvXFwvL2kudGVzdCh2YWwpKSB7XG4gICAgICAgIHZhbCA9ICdodHRwOi8vJyArIHZhbDtcbiAgICAgIH1cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHVybDogdmFsXG4gICAgICB9O1xuICAgIH0sXG4gICAgcmU6IC8oPzooPzpodHRwcz98ZnRwKTpcXC9cXC98d3d3XFwufGZ0cFxcLilbLUEtWjAtOSsmQCNcXC8lPX5ffCQ/ITosLl0qW0EtWjAtOSsmQCNcXC8lPX5ffCRdL2lnXG4gIH0sXG4gIC8vIE1lbnRpb25zIEB1c2VyIChtdXN0IGJlIDIgb3IgbW9yZSBjaGFyYWN0ZXJzKVxuICB7XG4gICAgbmFtZTogJ01OJyxcbiAgICBkYXRhTmFtZTogJ3ZhbCcsXG4gICAgcGFjazogZnVuY3Rpb24odmFsKSB7XG4gICAgICByZXR1cm4ge1xuICAgICAgICB2YWw6IHZhbC5zbGljZSgxKVxuICAgICAgfTtcbiAgICB9LFxuICAgIHJlOiAvXFxCQChcXHdcXHcrKS9nXG4gIH0sXG4gIC8vIEhhc2h0YWdzICNoYXNodGFnLCBsaWtlIG1ldGlvbiAyIG9yIG1vcmUgY2hhcmFjdGVycy5cbiAge1xuICAgIG5hbWU6ICdIVCcsXG4gICAgZGF0YU5hbWU6ICd2YWwnLFxuICAgIHBhY2s6IGZ1bmN0aW9uKHZhbCkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgdmFsOiB2YWwuc2xpY2UoMSlcbiAgICAgIH07XG4gICAgfSxcbiAgICByZTogL1xcQiMoXFx3XFx3KykvZ1xuICB9XG5dO1xuXG4vLyBIVE1MIHRhZyBuYW1lIHN1Z2dlc3Rpb25zXG5jb25zdCBIVE1MX1RBR1MgPSB7XG4gIFNUOiB7XG4gICAgbmFtZTogJ2InLFxuICAgIGlzVm9pZDogZmFsc2VcbiAgfSxcbiAgRU06IHtcbiAgICBuYW1lOiAnaScsXG4gICAgaXNWb2lkOiBmYWxzZVxuICB9LFxuICBETDoge1xuICAgIG5hbWU6ICdkZWwnLFxuICAgIGlzVm9pZDogZmFsc2VcbiAgfSxcbiAgQ086IHtcbiAgICBuYW1lOiAndHQnLFxuICAgIGlzVm9pZDogZmFsc2VcbiAgfSxcbiAgQlI6IHtcbiAgICBuYW1lOiAnYnInLFxuICAgIGlzVm9pZDogdHJ1ZVxuICB9LFxuICBMTjoge1xuICAgIG5hbWU6ICdhJyxcbiAgICBpc1ZvaWQ6IGZhbHNlXG4gIH0sXG4gIE1OOiB7XG4gICAgbmFtZTogJ2EnLFxuICAgIGlzVm9pZDogZmFsc2VcbiAgfSxcbiAgSFQ6IHtcbiAgICBuYW1lOiAnYScsXG4gICAgaXNWb2lkOiBmYWxzZVxuICB9LFxuICBJTToge1xuICAgIG5hbWU6ICdpbWcnLFxuICAgIGlzVm9pZDogdHJ1ZVxuICB9LFxuICBGTToge1xuICAgIG5hbWU6ICdkaXYnLFxuICAgIGlzVm9pZDogZmFsc2VcbiAgfSxcbiAgUlc6IHtcbiAgICBuYW1lOiAnZGl2JyxcbiAgICBpc1ZvaWQ6IGZhbHNlLFxuICB9LFxuICBCTjoge1xuICAgIG5hbWU6ICdidXR0b24nLFxuICAgIGlzVm9pZDogZmFsc2VcbiAgfSxcbiAgSEQ6IHtcbiAgICBuYW1lOiAnJyxcbiAgICBpc1ZvaWQ6IGZhbHNlXG4gIH1cbn07XG5cbi8vIENvbnZlcnQgYmFzZTY0LWVuY29kZWQgc3RyaW5nIGludG8gQmxvYi5cbmZ1bmN0aW9uIGJhc2U2NHRvT2JqZWN0VXJsKGI2NCwgY29udGVudFR5cGUpIHtcbiAgbGV0IGJpbjtcbiAgdHJ5IHtcbiAgICBiaW4gPSBhdG9iKGI2NCk7XG4gICAgbGV0IGxlbmd0aCA9IGJpbi5sZW5ndGg7XG4gICAgbGV0IGJ1ZiA9IG5ldyBBcnJheUJ1ZmZlcihsZW5ndGgpO1xuICAgIGxldCBhcnIgPSBuZXcgVWludDhBcnJheShidWYpO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIGFycltpXSA9IGJpbi5jaGFyQ29kZUF0KGkpO1xuICAgIH1cblxuICAgIHJldHVybiBVUkwuY3JlYXRlT2JqZWN0VVJMKG5ldyBCbG9iKFtidWZdLCB7XG4gICAgICB0eXBlOiBjb250ZW50VHlwZVxuICAgIH0pKTtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgY29uc29sZS5sb2coXCJEcmFmdHk6IGZhaWxlZCB0byBjb252ZXJ0IG9iamVjdC5cIiwgZXJyLm1lc3NhZ2UpO1xuICB9XG5cbiAgcmV0dXJuIG51bGw7XG59XG5cbi8vIEhlbHBlcnMgZm9yIGNvbnZlcnRpbmcgRHJhZnR5IHRvIEhUTUwuXG5jb25zdCBERUNPUkFUT1JTID0ge1xuICAvLyBWaXNpYWwgc3R5bGVzXG4gIFNUOiB7XG4gICAgb3BlbjogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJzxiPic7XG4gICAgfSxcbiAgICBjbG9zZTogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJzwvYj4nO1xuICAgIH1cbiAgfSxcbiAgRU06IHtcbiAgICBvcGVuOiBmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiAnPGk+JztcbiAgICB9LFxuICAgIGNsb3NlOiBmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiAnPC9pPidcbiAgICB9XG4gIH0sXG4gIERMOiB7XG4gICAgb3BlbjogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJzxkZWw+JztcbiAgICB9LFxuICAgIGNsb3NlOiBmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiAnPC9kZWw+J1xuICAgIH1cbiAgfSxcbiAgQ086IHtcbiAgICBvcGVuOiBmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiAnPHR0Pic7XG4gICAgfSxcbiAgICBjbG9zZTogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJzwvdHQ+J1xuICAgIH1cbiAgfSxcbiAgLy8gTGluZSBicmVha1xuICBCUjoge1xuICAgIG9wZW46IGZ1bmN0aW9uKCkge1xuICAgICAgcmV0dXJuICc8YnIvPic7XG4gICAgfSxcbiAgICBjbG9zZTogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJydcbiAgICB9XG4gIH0sXG4gIC8vIEhpZGRlbiBlbGVtZW50XG4gIEhEOiB7XG4gICAgb3BlbjogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJyc7XG4gICAgfSxcbiAgICBjbG9zZTogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJyc7XG4gICAgfVxuICB9LFxuICAvLyBMaW5rIChVUkwpXG4gIExOOiB7XG4gICAgb3BlbjogZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuICc8YSBocmVmPVwiJyArIGRhdGEudXJsICsgJ1wiPic7XG4gICAgfSxcbiAgICBjbG9zZTogZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuICc8L2E+JztcbiAgICB9LFxuICAgIHByb3BzOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgICByZXR1cm4gZGF0YSA/IHtcbiAgICAgICAgaHJlZjogZGF0YS51cmwsXG4gICAgICAgIHRhcmdldDogXCJfYmxhbmtcIlxuICAgICAgfSA6IG51bGw7XG4gICAgfSxcbiAgfSxcbiAgLy8gTWVudGlvblxuICBNTjoge1xuICAgIG9wZW46IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgIHJldHVybiAnPGEgaHJlZj1cIiMnICsgZGF0YS52YWwgKyAnXCI+JztcbiAgICB9LFxuICAgIGNsb3NlOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgICByZXR1cm4gJzwvYT4nO1xuICAgIH0sXG4gICAgcHJvcHM6IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgIHJldHVybiBkYXRhID8ge1xuICAgICAgICBuYW1lOiBkYXRhLnZhbFxuICAgICAgfSA6IG51bGw7XG4gICAgfSxcbiAgfSxcbiAgLy8gSGFzaHRhZ1xuICBIVDoge1xuICAgIG9wZW46IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgIHJldHVybiAnPGEgaHJlZj1cIiMnICsgZGF0YS52YWwgKyAnXCI+JztcbiAgICB9LFxuICAgIGNsb3NlOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgICByZXR1cm4gJzwvYT4nO1xuICAgIH0sXG4gICAgcHJvcHM6IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgIHJldHVybiBkYXRhID8ge1xuICAgICAgICBuYW1lOiBkYXRhLnZhbFxuICAgICAgfSA6IG51bGw7XG4gICAgfSxcbiAgfSxcbiAgLy8gQnV0dG9uXG4gIEJOOiB7XG4gICAgb3BlbjogZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuICc8YnV0dG9uPic7XG4gICAgfSxcbiAgICBjbG9zZTogZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuICc8L2J1dHRvbj4nO1xuICAgIH0sXG4gICAgcHJvcHM6IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgIHJldHVybiBkYXRhID8ge1xuICAgICAgICAnZGF0YS1hY3QnOiBkYXRhLmFjdCxcbiAgICAgICAgJ2RhdGEtdmFsJzogZGF0YS52YWwsXG4gICAgICAgICdkYXRhLW5hbWUnOiBkYXRhLm5hbWUsXG4gICAgICAgICdkYXRhLXJlZic6IGRhdGEucmVmXG4gICAgICB9IDogbnVsbDtcbiAgICB9LFxuICB9LFxuICAvLyBJbWFnZVxuICBJTToge1xuICAgIG9wZW46IGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgIC8vIERvbid0IHVzZSBkYXRhLnJlZiBmb3IgcHJldmlldzogaXQncyBhIHNlY3VyaXR5IHJpc2suXG4gICAgICBjb25zdCBwcmV2aWV3VXJsID0gYmFzZTY0dG9PYmplY3RVcmwoZGF0YS52YWwsIGRhdGEubWltZSk7XG4gICAgICBjb25zdCBkb3dubG9hZFVybCA9IGRhdGEucmVmID8gZGF0YS5yZWYgOiBwcmV2aWV3VXJsO1xuICAgICAgcmV0dXJuIChkYXRhLm5hbWUgPyAnPGEgaHJlZj1cIicgKyBkb3dubG9hZFVybCArICdcIiBkb3dubG9hZD1cIicgKyBkYXRhLm5hbWUgKyAnXCI+JyA6ICcnKSArXG4gICAgICAgICc8aW1nIHNyYz1cIicgKyBwcmV2aWV3VXJsICsgJ1wiJyArXG4gICAgICAgIChkYXRhLndpZHRoID8gJyB3aWR0aD1cIicgKyBkYXRhLndpZHRoICsgJ1wiJyA6ICcnKSArXG4gICAgICAgIChkYXRhLmhlaWdodCA/ICcgaGVpZ2h0PVwiJyArIGRhdGEuaGVpZ2h0ICsgJ1wiJyA6ICcnKSArICcgYm9yZGVyPVwiMFwiIC8+JztcbiAgICB9LFxuICAgIGNsb3NlOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgICByZXR1cm4gKGRhdGEubmFtZSA/ICc8L2E+JyA6ICcnKTtcbiAgICB9LFxuICAgIHByb3BzOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgICBpZiAoIWRhdGEpIHJldHVybiBudWxsO1xuICAgICAgbGV0IHVybCA9IGJhc2U2NHRvT2JqZWN0VXJsKGRhdGEudmFsLCBkYXRhLm1pbWUpO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgc3JjOiB1cmwsXG4gICAgICAgIHRpdGxlOiBkYXRhLm5hbWUsXG4gICAgICAgICdkYXRhLXdpZHRoJzogZGF0YS53aWR0aCxcbiAgICAgICAgJ2RhdGEtaGVpZ2h0JzogZGF0YS5oZWlnaHQsXG4gICAgICAgICdkYXRhLW5hbWUnOiBkYXRhLm5hbWUsXG4gICAgICAgICdkYXRhLXNpemUnOiBkYXRhLnZhbCA/IChkYXRhLnZhbC5sZW5ndGggKiAwLjc1KSB8IDAgOiAwLFxuICAgICAgICAnZGF0YS1taW1lJzogZGF0YS5taW1lXG4gICAgICB9O1xuICAgIH0sXG4gIH0sXG4gIC8vIEZvcm0gLSBzdHJ1Y3R1cmVkIGxheW91dCBvZiBlbGVtZW50cy5cbiAgRk06IHtcbiAgICBvcGVuOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgICByZXR1cm4gJzxkaXY+JztcbiAgICB9LFxuICAgIGNsb3NlOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgICByZXR1cm4gJzwvZGl2Pic7XG4gICAgfVxuICB9LFxuICAvLyBSb3c6IGxvZ2ljIGdyb3VwaW5nIG9mIGVsZW1lbnRzXG4gIFJXOiB7XG4gICAgb3BlbjogZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuICc8ZGl2Pic7XG4gICAgfSxcbiAgICBjbG9zZTogZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuICc8L2Rpdj4nO1xuICAgIH1cbiAgfVxufTtcblxuLyoqXG4gKiBUaGUgbWFpbiBvYmplY3Qgd2hpY2ggcGVyZm9ybXMgYWxsIHRoZSBmb3JtYXR0aW5nIGFjdGlvbnMuXG4gKiBAY2xhc3MgRHJhZnR5XG4gKiBAY29uc3RydWN0b3JcbiAqL1xudmFyIERyYWZ0eSA9IGZ1bmN0aW9uKCkge31cblxuLy8gVGFrZSBhIHN0cmluZyBhbmQgZGVmaW5lZCBlYXJsaWVyIHN0eWxlIHNwYW5zLCByZS1jb21wb3NlIHRoZW0gaW50byBhIHRyZWUgd2hlcmUgZWFjaCBsZWFmIGlzXG4vLyBhIHNhbWUtc3R5bGUgKGluY2x1ZGluZyB1bnN0eWxlZCkgc3RyaW5nLiBJLmUuICdoZWxsbyAqYm9sZCBfaXRhbGljXyogYW5kIH5tb3JlfiB3b3JsZCcgLT5cbi8vICgnaGVsbG8gJywgKGI6ICdib2xkICcsIChpOiAnaXRhbGljJykpLCAnIGFuZCAnLCAoczogJ21vcmUnKSwgJyB3b3JsZCcpO1xuLy9cbi8vIFRoaXMgaXMgbmVlZGVkIGluIG9yZGVyIHRvIGNsZWFyIG1hcmt1cCwgaS5lLiAnaGVsbG8gKndvcmxkKicgLT4gJ2hlbGxvIHdvcmxkJyBhbmQgY29udmVydFxuLy8gcmFuZ2VzIGZyb20gbWFya3VwLWVkIG9mZnNldHMgdG8gcGxhaW4gdGV4dCBvZmZzZXRzLlxuZnVuY3Rpb24gY2h1bmtpZnkobGluZSwgc3RhcnQsIGVuZCwgc3BhbnMpIHtcbiAgdmFyIGNodW5rcyA9IFtdO1xuXG4gIGlmIChzcGFucy5sZW5ndGggPT0gMCkge1xuICAgIHJldHVybiBbXTtcbiAgfVxuXG4gIGZvciAodmFyIGkgaW4gc3BhbnMpIHtcbiAgICAvLyBHZXQgdGhlIG5leHQgY2h1bmsgZnJvbSB0aGUgcXVldWVcbiAgICB2YXIgc3BhbiA9IHNwYW5zW2ldO1xuXG4gICAgLy8gR3JhYiB0aGUgaW5pdGlhbCB1bnN0eWxlZCBjaHVua1xuICAgIGlmIChzcGFuLnN0YXJ0ID4gc3RhcnQpIHtcbiAgICAgIGNodW5rcy5wdXNoKHtcbiAgICAgICAgdGV4dDogbGluZS5zbGljZShzdGFydCwgc3Bhbi5zdGFydClcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8vIEdyYWIgdGhlIHN0eWxlZCBjaHVuay4gSXQgbWF5IGluY2x1ZGUgc3ViY2h1bmtzLlxuICAgIHZhciBjaHVuayA9IHtcbiAgICAgIHR5cGU6IHNwYW4udHlwZVxuICAgIH07XG4gICAgdmFyIGNobGQgPSBjaHVua2lmeShsaW5lLCBzcGFuLnN0YXJ0ICsgMSwgc3Bhbi5lbmQsIHNwYW4uY2hpbGRyZW4pO1xuICAgIGlmIChjaGxkLmxlbmd0aCA+IDApIHtcbiAgICAgIGNodW5rLmNoaWxkcmVuID0gY2hsZDtcbiAgICB9IGVsc2Uge1xuICAgICAgY2h1bmsudGV4dCA9IHNwYW4udGV4dDtcbiAgICB9XG4gICAgY2h1bmtzLnB1c2goY2h1bmspO1xuICAgIHN0YXJ0ID0gc3Bhbi5lbmQgKyAxOyAvLyAnKzEnIGlzIHRvIHNraXAgdGhlIGZvcm1hdHRpbmcgY2hhcmFjdGVyXG4gIH1cblxuICAvLyBHcmFiIHRoZSByZW1haW5pbmcgdW5zdHlsZWQgY2h1bmssIGFmdGVyIHRoZSBsYXN0IHNwYW5cbiAgaWYgKHN0YXJ0IDwgZW5kKSB7XG4gICAgY2h1bmtzLnB1c2goe1xuICAgICAgdGV4dDogbGluZS5zbGljZShzdGFydCwgZW5kKVxuICAgIH0pO1xuICB9XG5cbiAgcmV0dXJuIGNodW5rcztcbn1cblxuLy8gSW52ZXJzZSBvZiBjaHVua2lmeS4gUmV0dXJucyBhIHRyZWUgb2YgZm9ybWF0dGVkIHNwYW5zLlxuZnVuY3Rpb24gZm9yRWFjaChsaW5lLCBzdGFydCwgZW5kLCBzcGFucywgZm9ybWF0dGVyLCBjb250ZXh0KSB7XG4gIGxldCByZXN1bHQgPSBbXTtcblxuICAvLyBQcm9jZXNzIHJhbmdlcyBjYWxsaW5nIGZvcm1hdHRlciBmb3IgZWFjaCByYW5nZS5cbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBzcGFucy5sZW5ndGg7IGkrKykge1xuICAgIGxldCBzcGFuID0gc3BhbnNbaV07XG4gICAgaWYgKHNwYW4uYXQgPCAwKSB7XG4gICAgICAvLyB0aHJvdyBvdXQgbm9uLXZpc3VhbCBzcGFucy5cbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICAvLyBBZGQgdW4tc3R5bGVkIHJhbmdlIGJlZm9yZSB0aGUgc3R5bGVkIHNwYW4gc3RhcnRzLlxuICAgIGlmIChzdGFydCA8IHNwYW4uYXQpIHtcbiAgICAgIHJlc3VsdC5wdXNoKGZvcm1hdHRlci5jYWxsKGNvbnRleHQsIG51bGwsIHVuZGVmaW5lZCwgbGluZS5zbGljZShzdGFydCwgc3Bhbi5hdCksIHJlc3VsdC5sZW5ndGgpKTtcbiAgICAgIHN0YXJ0ID0gc3Bhbi5hdDtcbiAgICB9XG4gICAgLy8gR2V0IGFsbCBzcGFucyB3aGljaCBhcmUgd2l0aGluIGN1cnJlbnQgc3Bhbi5cbiAgICBjb25zdCBzdWJzcGFucyA9IFtdO1xuICAgIGZvciAobGV0IHNpID0gaSArIDE7IHNpIDwgc3BhbnMubGVuZ3RoICYmIHNwYW5zW3NpXS5hdCA8IHNwYW4uYXQgKyBzcGFuLmxlbjsgc2krKykge1xuICAgICAgc3Vic3BhbnMucHVzaChzcGFuc1tzaV0pO1xuICAgICAgaSA9IHNpO1xuICAgIH1cblxuICAgIGNvbnN0IHRhZyA9IEhUTUxfVEFHU1tzcGFuLnRwXSB8fCB7fVxuICAgIHJlc3VsdC5wdXNoKGZvcm1hdHRlci5jYWxsKGNvbnRleHQsIHNwYW4udHAsIHNwYW4uZGF0YSxcbiAgICAgIHRhZy5pc1ZvaWQgPyBudWxsIDogZm9yRWFjaChsaW5lLCBzdGFydCwgc3Bhbi5hdCArIHNwYW4ubGVuLCBzdWJzcGFucywgZm9ybWF0dGVyLCBjb250ZXh0KSxcbiAgICAgIHJlc3VsdC5sZW5ndGgpKTtcblxuICAgIHN0YXJ0ID0gc3Bhbi5hdCArIHNwYW4ubGVuO1xuICB9XG5cbiAgLy8gQWRkIHRoZSBsYXN0IHVuZm9ybWF0dGVkIHJhbmdlLlxuICBpZiAoc3RhcnQgPCBlbmQpIHtcbiAgICByZXN1bHQucHVzaChmb3JtYXR0ZXIuY2FsbChjb250ZXh0LCBudWxsLCB1bmRlZmluZWQsIGxpbmUuc2xpY2Uoc3RhcnQsIGVuZCksIHJlc3VsdC5sZW5ndGgpKTtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbi8vIERldGVjdCBzdGFydHMgYW5kIGVuZHMgb2YgZm9ybWF0dGluZyBzcGFucy4gVW5mb3JtYXR0ZWQgc3BhbnMgYXJlXG4vLyBpZ25vcmVkIGF0IHRoaXMgc3RhZ2UuXG5mdW5jdGlvbiBzcGFubmlmeShvcmlnaW5hbCwgcmVfc3RhcnQsIHJlX2VuZCwgdHlwZSkge1xuICBsZXQgcmVzdWx0ID0gW107XG4gIGxldCBpbmRleCA9IDA7XG4gIGxldCBsaW5lID0gb3JpZ2luYWwuc2xpY2UoMCk7IC8vIG1ha2UgYSBjb3B5O1xuXG4gIHdoaWxlIChsaW5lLmxlbmd0aCA+IDApIHtcbiAgICAvLyBtYXRjaFswXTsgLy8gbWF0Y2gsIGxpa2UgJyphYmMqJ1xuICAgIC8vIG1hdGNoWzFdOyAvLyBtYXRjaCBjYXB0dXJlZCBpbiBwYXJlbnRoZXNpcywgbGlrZSAnYWJjJ1xuICAgIC8vIG1hdGNoWydpbmRleCddOyAvLyBvZmZzZXQgd2hlcmUgdGhlIG1hdGNoIHN0YXJ0ZWQuXG5cbiAgICAvLyBGaW5kIHRoZSBvcGVuaW5nIHRva2VuLlxuICAgIGxldCBzdGFydCA9IHJlX3N0YXJ0LmV4ZWMobGluZSk7XG4gICAgaWYgKHN0YXJ0ID09IG51bGwpIHtcbiAgICAgIGJyZWFrO1xuICAgIH1cblxuICAgIC8vIEJlY2F1c2UgamF2YXNjcmlwdCBSZWdFeHAgZG9lcyBub3Qgc3VwcG9ydCBsb29rYmVoaW5kLCB0aGUgYWN0dWFsIG9mZnNldCBtYXkgbm90IHBvaW50XG4gICAgLy8gYXQgdGhlIG1hcmt1cCBjaGFyYWN0ZXIuIEZpbmQgaXQgaW4gdGhlIG1hdGNoZWQgc3RyaW5nLlxuICAgIGxldCBzdGFydF9vZmZzZXQgPSBzdGFydFsnaW5kZXgnXSArIHN0YXJ0WzBdLmxhc3RJbmRleE9mKHN0YXJ0WzFdKTtcbiAgICAvLyBDbGlwIHRoZSBwcm9jZXNzZWQgcGFydCBvZiB0aGUgc3RyaW5nLlxuICAgIGxpbmUgPSBsaW5lLnNsaWNlKHN0YXJ0X29mZnNldCArIDEpO1xuICAgIC8vIHN0YXJ0X29mZnNldCBpcyBhbiBvZmZzZXQgd2l0aGluIHRoZSBjbGlwcGVkIHN0cmluZy4gQ29udmVydCB0byBvcmlnaW5hbCBpbmRleC5cbiAgICBzdGFydF9vZmZzZXQgKz0gaW5kZXg7XG4gICAgLy8gSW5kZXggbm93IHBvaW50IHRvIHRoZSBiZWdpbm5pbmcgb2YgJ2xpbmUnIHdpdGhpbiB0aGUgJ29yaWdpbmFsJyBzdHJpbmcuXG4gICAgaW5kZXggPSBzdGFydF9vZmZzZXQgKyAxO1xuXG4gICAgLy8gRmluZCB0aGUgbWF0Y2hpbmcgY2xvc2luZyB0b2tlbi5cbiAgICBsZXQgZW5kID0gcmVfZW5kID8gcmVfZW5kLmV4ZWMobGluZSkgOiBudWxsO1xuICAgIGlmIChlbmQgPT0gbnVsbCkge1xuICAgICAgYnJlYWs7XG4gICAgfVxuICAgIGxldCBlbmRfb2Zmc2V0ID0gZW5kWydpbmRleCddICsgZW5kWzBdLmluZGV4T2YoZW5kWzFdKTtcbiAgICAvLyBDbGlwIHRoZSBwcm9jZXNzZWQgcGFydCBvZiB0aGUgc3RyaW5nLlxuICAgIGxpbmUgPSBsaW5lLnNsaWNlKGVuZF9vZmZzZXQgKyAxKTtcbiAgICAvLyBVcGRhdGUgb2Zmc2V0c1xuICAgIGVuZF9vZmZzZXQgKz0gaW5kZXg7XG4gICAgLy8gSW5kZXggbm93IHBvaW50IHRvIHRoZSBiZWdpbm5pbmcgb2YgJ2xpbmUnIHdpdGhpbiB0aGUgJ29yaWdpbmFsJyBzdHJpbmcuXG4gICAgaW5kZXggPSBlbmRfb2Zmc2V0ICsgMTtcblxuICAgIHJlc3VsdC5wdXNoKHtcbiAgICAgIHRleHQ6IG9yaWdpbmFsLnNsaWNlKHN0YXJ0X29mZnNldCArIDEsIGVuZF9vZmZzZXQpLFxuICAgICAgY2hpbGRyZW46IFtdLFxuICAgICAgc3RhcnQ6IHN0YXJ0X29mZnNldCxcbiAgICAgIGVuZDogZW5kX29mZnNldCxcbiAgICAgIHR5cGU6IHR5cGVcbiAgICB9KTtcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbi8vIENvbnZlcnQgbGluZWFyIGFycmF5IG9yIHNwYW5zIGludG8gYSB0cmVlIHJlcHJlc2VudGF0aW9uLlxuLy8gS2VlcCBzdGFuZGFsb25lIGFuZCBuZXN0ZWQgc3BhbnMsIHRocm93IGF3YXkgcGFydGlhbGx5IG92ZXJsYXBwaW5nIHNwYW5zLlxuZnVuY3Rpb24gdG9UcmVlKHNwYW5zKSB7XG4gIGlmIChzcGFucy5sZW5ndGggPT0gMCkge1xuICAgIHJldHVybiBbXTtcbiAgfVxuXG4gIHZhciB0cmVlID0gW3NwYW5zWzBdXTtcbiAgdmFyIGxhc3QgPSBzcGFuc1swXTtcbiAgZm9yICh2YXIgaSA9IDE7IGkgPCBzcGFucy5sZW5ndGg7IGkrKykge1xuICAgIC8vIEtlZXAgc3BhbnMgd2hpY2ggc3RhcnQgYWZ0ZXIgdGhlIGVuZCBvZiB0aGUgcHJldmlvdXMgc3BhbiBvciB0aG9zZSB3aGljaFxuICAgIC8vIGFyZSBjb21wbGV0ZSB3aXRoaW4gdGhlIHByZXZpb3VzIHNwYW4uXG5cbiAgICBpZiAoc3BhbnNbaV0uc3RhcnQgPiBsYXN0LmVuZCkge1xuICAgICAgLy8gU3BhbiBpcyBjb21wbGV0ZWx5IG91dHNpZGUgb2YgdGhlIHByZXZpb3VzIHNwYW4uXG4gICAgICB0cmVlLnB1c2goc3BhbnNbaV0pO1xuICAgICAgbGFzdCA9IHNwYW5zW2ldO1xuICAgIH0gZWxzZSBpZiAoc3BhbnNbaV0uZW5kIDwgbGFzdC5lbmQpIHtcbiAgICAgIC8vIFNwYW4gaXMgZnVsbHkgaW5zaWRlIG9mIHRoZSBwcmV2aW91cyBzcGFuLiBQdXNoIHRvIHN1Ym5vZGUuXG4gICAgICBsYXN0LmNoaWxkcmVuLnB1c2goc3BhbnNbaV0pO1xuICAgIH1cbiAgICAvLyBTcGFuIGNvdWxkIHBhcnRpYWxseSBvdmVybGFwLCBpZ25vcmluZyBpdCBhcyBpbnZhbGlkLlxuICB9XG5cbiAgLy8gUmVjdXJzaXZlbHkgcmVhcnJhbmdlIHRoZSBzdWJub2Rlcy5cbiAgZm9yICh2YXIgaSBpbiB0cmVlKSB7XG4gICAgdHJlZVtpXS5jaGlsZHJlbiA9IHRvVHJlZSh0cmVlW2ldLmNoaWxkcmVuKTtcbiAgfVxuXG4gIHJldHVybiB0cmVlO1xufVxuXG4vLyBHZXQgYSBsaXN0IG9mIGVudGl0aWVzIGZyb20gYSB0ZXh0LlxuZnVuY3Rpb24gZXh0cmFjdEVudGl0aWVzKGxpbmUpIHtcbiAgdmFyIG1hdGNoO1xuICB2YXIgZXh0cmFjdGVkID0gW107XG4gIEVOVElUWV9UWVBFUy5tYXAoZnVuY3Rpb24oZW50aXR5KSB7XG4gICAgd2hpbGUgKChtYXRjaCA9IGVudGl0eS5yZS5leGVjKGxpbmUpKSAhPT0gbnVsbCkge1xuICAgICAgZXh0cmFjdGVkLnB1c2goe1xuICAgICAgICBvZmZzZXQ6IG1hdGNoWydpbmRleCddLFxuICAgICAgICBsZW46IG1hdGNoWzBdLmxlbmd0aCxcbiAgICAgICAgdW5pcXVlOiBtYXRjaFswXSxcbiAgICAgICAgZGF0YTogZW50aXR5LnBhY2sobWF0Y2hbMF0pLFxuICAgICAgICB0eXBlOiBlbnRpdHkubmFtZVxuICAgICAgfSk7XG4gICAgfVxuICB9KTtcblxuICBpZiAoZXh0cmFjdGVkLmxlbmd0aCA9PSAwKSB7XG4gICAgcmV0dXJuIGV4dHJhY3RlZDtcbiAgfVxuXG4gIC8vIFJlbW92ZSBlbnRpdGllcyBkZXRlY3RlZCBpbnNpZGUgb3RoZXIgZW50aXRpZXMsIGxpa2UgI2hhc2h0YWcgaW4gYSBVUkwuXG4gIGV4dHJhY3RlZC5zb3J0KGZ1bmN0aW9uKGEsIGIpIHtcbiAgICByZXR1cm4gYS5vZmZzZXQgLSBiLm9mZnNldDtcbiAgfSk7XG5cbiAgdmFyIGlkeCA9IC0xO1xuICBleHRyYWN0ZWQgPSBleHRyYWN0ZWQuZmlsdGVyKGZ1bmN0aW9uKGVsKSB7XG4gICAgdmFyIHJlc3VsdCA9IChlbC5vZmZzZXQgPiBpZHgpO1xuICAgIGlkeCA9IGVsLm9mZnNldCArIGVsLmxlbjtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9KTtcblxuICByZXR1cm4gZXh0cmFjdGVkO1xufVxuXG4vLyBDb252ZXJ0IHRoZSBjaHVua3MgaW50byBmb3JtYXQgc3VpdGFibGUgZm9yIHNlcmlhbGl6YXRpb24uXG5mdW5jdGlvbiBkcmFmdGlmeShjaHVua3MsIHN0YXJ0QXQpIHtcbiAgdmFyIHBsYWluID0gXCJcIjtcbiAgdmFyIHJhbmdlcyA9IFtdO1xuICBmb3IgKHZhciBpIGluIGNodW5rcykge1xuICAgIHZhciBjaHVuayA9IGNodW5rc1tpXTtcbiAgICBpZiAoIWNodW5rLnRleHQpIHtcbiAgICAgIHZhciBkcmFmdHkgPSBkcmFmdGlmeShjaHVuay5jaGlsZHJlbiwgcGxhaW4ubGVuZ3RoICsgc3RhcnRBdCk7XG4gICAgICBjaHVuay50ZXh0ID0gZHJhZnR5LnR4dDtcbiAgICAgIHJhbmdlcyA9IHJhbmdlcy5jb25jYXQoZHJhZnR5LmZtdCk7XG4gICAgfVxuXG4gICAgaWYgKGNodW5rLnR5cGUpIHtcbiAgICAgIHJhbmdlcy5wdXNoKHtcbiAgICAgICAgYXQ6IHBsYWluLmxlbmd0aCArIHN0YXJ0QXQsXG4gICAgICAgIGxlbjogY2h1bmsudGV4dC5sZW5ndGgsXG4gICAgICAgIHRwOiBjaHVuay50eXBlXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBwbGFpbiArPSBjaHVuay50ZXh0O1xuICB9XG4gIHJldHVybiB7XG4gICAgdHh0OiBwbGFpbixcbiAgICBmbXQ6IHJhbmdlc1xuICB9O1xufVxuXG4vLyBTcGxpY2UgdHdvIHN0cmluZ3M6IGluc2VydCBzZWNvbmQgc3RyaW5nIGludG8gdGhlIGZpcnN0IG9uZSBhdCB0aGUgZ2l2ZW4gaW5kZXhcbmZ1bmN0aW9uIHNwbGljZShzcmMsIGF0LCBpbnNlcnQpIHtcbiAgcmV0dXJuIHNyYy5zbGljZSgwLCBhdCkgKyBpbnNlcnQgKyBzcmMuc2xpY2UoYXQpO1xufVxuXG4vKipcbiAqIFBhcnNlIHBsYWluIHRleHQgaW50byBzdHJ1Y3R1cmVkIHJlcHJlc2VudGF0aW9uLlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7U3RyaW5nfSBjb250ZW50IHBsYWluLXRleHQgY29udGVudCB0byBwYXJzZS5cbiAqIEByZXR1cm4ge0RyYWZ0eX0gcGFyc2VkIG9iamVjdCBvciBudWxsIGlmIHRoZSBzb3VyY2UgaXMgbm90IHBsYWluIHRleHQuXG4gKi9cbkRyYWZ0eS5wYXJzZSA9IGZ1bmN0aW9uKGNvbnRlbnQpIHtcbiAgLy8gTWFrZSBzdXJlIHdlIGFyZSBwYXJzaW5nIHN0cmluZ3Mgb25seS5cbiAgaWYgKHR5cGVvZiBjb250ZW50ICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvLyBTcGxpdCB0ZXh0IGludG8gbGluZXMuIEl0IG1ha2VzIGZ1cnRoZXIgcHJvY2Vzc2luZyBlYXNpZXIuXG4gIHZhciBsaW5lcyA9IGNvbnRlbnQuc3BsaXQoL1xccj9cXG4vKTtcblxuICAvLyBIb2xkcyBlbnRpdGllcyByZWZlcmVuY2VkIGZyb20gdGV4dFxuICB2YXIgZW50aXR5TWFwID0gW107XG4gIHZhciBlbnRpdHlJbmRleCA9IHt9O1xuXG4gIC8vIFByb2Nlc3NpbmcgbGluZXMgb25lIGJ5IG9uZSwgaG9sZCBpbnRlcm1lZGlhdGUgcmVzdWx0IGluIGJseC5cbiAgdmFyIGJseCA9IFtdO1xuICBsaW5lcy5tYXAoZnVuY3Rpb24obGluZSkge1xuICAgIHZhciBzcGFucyA9IFtdO1xuICAgIHZhciBlbnRpdGllcyA9IFtdO1xuXG4gICAgLy8gRmluZCBmb3JtYXR0ZWQgc3BhbnMgaW4gdGhlIHN0cmluZy5cbiAgICAvLyBUcnkgdG8gbWF0Y2ggZWFjaCBzdHlsZS5cbiAgICBJTkxJTkVfU1RZTEVTLm1hcChmdW5jdGlvbihzdHlsZSkge1xuICAgICAgLy8gRWFjaCBzdHlsZSBjb3VsZCBiZSBtYXRjaGVkIG11bHRpcGxlIHRpbWVzLlxuICAgICAgc3BhbnMgPSBzcGFucy5jb25jYXQoc3Bhbm5pZnkobGluZSwgc3R5bGUuc3RhcnQsIHN0eWxlLmVuZCwgc3R5bGUubmFtZSkpO1xuICAgIH0pO1xuXG4gICAgdmFyIGJsb2NrO1xuICAgIGlmIChzcGFucy5sZW5ndGggPT0gMCkge1xuICAgICAgYmxvY2sgPSB7XG4gICAgICAgIHR4dDogbGluZVxuICAgICAgfTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gU29ydCBzcGFucyBieSBzdHlsZSBvY2N1cmVuY2UgZWFybHkgLT4gbGF0ZVxuICAgICAgc3BhbnMuc29ydChmdW5jdGlvbihhLCBiKSB7XG4gICAgICAgIHJldHVybiBhLnN0YXJ0IC0gYi5zdGFydDtcbiAgICAgIH0pO1xuXG4gICAgICAvLyBDb252ZXJ0IGFuIGFycmF5IG9mIHBvc3NpYmx5IG92ZXJsYXBwaW5nIHNwYW5zIGludG8gYSB0cmVlXG4gICAgICBzcGFucyA9IHRvVHJlZShzcGFucyk7XG5cbiAgICAgIC8vIEJ1aWxkIGEgdHJlZSByZXByZXNlbnRhdGlvbiBvZiB0aGUgZW50aXJlIHN0cmluZywgbm90XG4gICAgICAvLyBqdXN0IHRoZSBmb3JtYXR0ZWQgcGFydHMuXG4gICAgICB2YXIgY2h1bmtzID0gY2h1bmtpZnkobGluZSwgMCwgbGluZS5sZW5ndGgsIHNwYW5zKTtcblxuICAgICAgdmFyIGRyYWZ0eSA9IGRyYWZ0aWZ5KGNodW5rcywgMCk7XG5cbiAgICAgIGJsb2NrID0ge1xuICAgICAgICB0eHQ6IGRyYWZ0eS50eHQsXG4gICAgICAgIGZtdDogZHJhZnR5LmZtdFxuICAgICAgfTtcbiAgICB9XG5cbiAgICAvLyBFeHRyYWN0IGVudGl0aWVzIGZyb20gdGhlIGNsZWFuZWQgdXAgc3RyaW5nLlxuICAgIGVudGl0aWVzID0gZXh0cmFjdEVudGl0aWVzKGJsb2NrLnR4dCk7XG4gICAgaWYgKGVudGl0aWVzLmxlbmd0aCA+IDApIHtcbiAgICAgIHZhciByYW5nZXMgPSBbXTtcbiAgICAgIGZvciAodmFyIGkgaW4gZW50aXRpZXMpIHtcbiAgICAgICAgLy8ge29mZnNldDogbWF0Y2hbJ2luZGV4J10sIHVuaXF1ZTogbWF0Y2hbMF0sIGxlbjogbWF0Y2hbMF0ubGVuZ3RoLCBkYXRhOiBlbnQucGFja2VyKCksIHR5cGU6IGVudC5uYW1lfVxuICAgICAgICB2YXIgZW50aXR5ID0gZW50aXRpZXNbaV07XG4gICAgICAgIHZhciBpbmRleCA9IGVudGl0eUluZGV4W2VudGl0eS51bmlxdWVdO1xuICAgICAgICBpZiAoIWluZGV4KSB7XG4gICAgICAgICAgaW5kZXggPSBlbnRpdHlNYXAubGVuZ3RoO1xuICAgICAgICAgIGVudGl0eUluZGV4W2VudGl0eS51bmlxdWVdID0gaW5kZXg7XG4gICAgICAgICAgZW50aXR5TWFwLnB1c2goe1xuICAgICAgICAgICAgdHA6IGVudGl0eS50eXBlLFxuICAgICAgICAgICAgZGF0YTogZW50aXR5LmRhdGFcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICByYW5nZXMucHVzaCh7XG4gICAgICAgICAgYXQ6IGVudGl0eS5vZmZzZXQsXG4gICAgICAgICAgbGVuOiBlbnRpdHkubGVuLFxuICAgICAgICAgIGtleTogaW5kZXhcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgICBibG9jay5lbnQgPSByYW5nZXM7XG4gICAgfVxuXG4gICAgYmx4LnB1c2goYmxvY2spO1xuICB9KTtcblxuICB2YXIgcmVzdWx0ID0ge1xuICAgIHR4dDogXCJcIlxuICB9O1xuXG4gIC8vIE1lcmdlIGxpbmVzIGFuZCBzYXZlIGxpbmUgYnJlYWtzIGFzIEJSIGlubGluZSBmb3JtYXR0aW5nLlxuICBpZiAoYmx4Lmxlbmd0aCA+IDApIHtcbiAgICByZXN1bHQudHh0ID0gYmx4WzBdLnR4dDtcbiAgICByZXN1bHQuZm10ID0gKGJseFswXS5mbXQgfHwgW10pLmNvbmNhdChibHhbMF0uZW50IHx8IFtdKTtcblxuICAgIGZvciAodmFyIGkgPSAxOyBpIDwgYmx4Lmxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgYmxvY2sgPSBibHhbaV07XG4gICAgICB2YXIgb2Zmc2V0ID0gcmVzdWx0LnR4dC5sZW5ndGggKyAxO1xuXG4gICAgICByZXN1bHQuZm10LnB1c2goe1xuICAgICAgICB0cDogJ0JSJyxcbiAgICAgICAgbGVuOiAxLFxuICAgICAgICBhdDogb2Zmc2V0IC0gMVxuICAgICAgfSk7XG5cbiAgICAgIHJlc3VsdC50eHQgKz0gXCIgXCIgKyBibG9jay50eHQ7XG4gICAgICBpZiAoYmxvY2suZm10KSB7XG4gICAgICAgIHJlc3VsdC5mbXQgPSByZXN1bHQuZm10LmNvbmNhdChibG9jay5mbXQubWFwKGZ1bmN0aW9uKHMpIHtcbiAgICAgICAgICBzLmF0ICs9IG9mZnNldDtcbiAgICAgICAgICByZXR1cm4gcztcbiAgICAgICAgfSkpO1xuICAgICAgfVxuICAgICAgaWYgKGJsb2NrLmVudCkge1xuICAgICAgICByZXN1bHQuZm10ID0gcmVzdWx0LmZtdC5jb25jYXQoYmxvY2suZW50Lm1hcChmdW5jdGlvbihzKSB7XG4gICAgICAgICAgcy5hdCArPSBvZmZzZXQ7XG4gICAgICAgICAgcmV0dXJuIHM7XG4gICAgICAgIH0pKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAocmVzdWx0LmZtdC5sZW5ndGggPT0gMCkge1xuICAgICAgZGVsZXRlIHJlc3VsdC5mbXQ7XG4gICAgfVxuXG4gICAgaWYgKGVudGl0eU1hcC5sZW5ndGggPiAwKSB7XG4gICAgICByZXN1bHQuZW50ID0gZW50aXR5TWFwO1xuICAgIH1cbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG4vKipcbiAqIEluc2VydCBpbmxpbmUgaW1hZ2UgaW50byBEcmFmdHkgY29udGVudC5cbiAqIEBtZW1iZXJvZiBEcmFmdHlcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge0RyYWZ0eX0gY29udGVudCBvYmplY3QgdG8gYWRkIGltYWdlIHRvLlxuICogQHBhcmFtIHtpbnRlZ2VyfSBhdCBpbmRleCB3aGVyZSB0aGUgb2JqZWN0IGlzIGluc2VydGVkLiBUaGUgbGVuZ3RoIG9mIHRoZSBpbWFnZSBpcyBhbHdheXMgMS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBtaW1lIG1pbWUtdHlwZSBvZiB0aGUgaW1hZ2UsIGUuZy4gXCJpbWFnZS9wbmdcIlxuICogQHBhcmFtIHtzdHJpbmd9IGJhc2U2NGJpdHMgYmFzZTY0LWVuY29kZWQgaW1hZ2UgY29udGVudCAob3IgcHJldmlldywgaWYgbGFyZ2UgaW1hZ2UgaXMgYXR0YWNoZWQpXG4gKiBAcGFyYW0ge2ludGVnZXJ9IHdpZHRoIHdpZHRoIG9mIHRoZSBpbWFnZVxuICogQHBhcmFtIHtpbnRlZ2VyfSBoZWlnaHQgaGVpZ2h0IG9mIHRoZSBpbWFnZVxuICogQHBhcmFtIHtzdHJpbmd9IGZuYW1lIGZpbGUgbmFtZSBzdWdnZXN0aW9uIGZvciBkb3dubG9hZGluZyB0aGUgaW1hZ2UuXG4gKiBAcGFyYW0ge2ludGVnZXJ9IHNpemUgc2l6ZSBvZiB0aGUgZXh0ZXJuYWwgZmlsZS4gVHJlYXQgaXMgYXMgYW4gdW50cnVzdGVkIGhpbnQuXG4gKiBAcGFyYW0ge3N0cmluZ30gcmVmdXJsIHJlZmVyZW5jZSB0byB0aGUgY29udGVudC4gQ291bGQgYmUgbnVsbCBvciB1bmRlZmluZWQuXG4gKlxuICogQHJldHVybiB7RHJhZnR5fSB1cGRhdGVkIGNvbnRlbnQuXG4gKi9cbkRyYWZ0eS5pbnNlcnRJbWFnZSA9IGZ1bmN0aW9uKGNvbnRlbnQsIGF0LCBtaW1lLCBiYXNlNjRiaXRzLCB3aWR0aCwgaGVpZ2h0LCBmbmFtZSwgc2l6ZSwgcmVmdXJsKSB7XG4gIGNvbnRlbnQgPSBjb250ZW50IHx8IHtcbiAgICB0eHQ6IFwiIFwiXG4gIH07XG4gIGNvbnRlbnQuZW50ID0gY29udGVudC5lbnQgfHwgW107XG4gIGNvbnRlbnQuZm10ID0gY29udGVudC5mbXQgfHwgW107XG5cbiAgY29udGVudC5mbXQucHVzaCh7XG4gICAgYXQ6IGF0LFxuICAgIGxlbjogMSxcbiAgICBrZXk6IGNvbnRlbnQuZW50Lmxlbmd0aFxuICB9KTtcbiAgY29udGVudC5lbnQucHVzaCh7XG4gICAgdHA6ICdJTScsXG4gICAgZGF0YToge1xuICAgICAgbWltZTogbWltZSxcbiAgICAgIHZhbDogYmFzZTY0Yml0cyxcbiAgICAgIHdpZHRoOiB3aWR0aCxcbiAgICAgIGhlaWdodDogaGVpZ2h0LFxuICAgICAgbmFtZTogZm5hbWUsXG4gICAgICByZWY6IHJlZnVybCxcbiAgICAgIHNpemU6IHNpemUgfCAwXG4gICAgfVxuICB9KTtcblxuICByZXR1cm4gY29udGVudDtcbn1cblxuLyoqXG4gKiBBcHBlbmQgaW1hZ2UgdG8gRHJhZnR5IGNvbnRlbnQuXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQHBhcmFtIHtEcmFmdHl9IGNvbnRlbnQgb2JqZWN0IHRvIGFkZCBpbWFnZSB0by5cbiAqIEBwYXJhbSB7c3RyaW5nfSBtaW1lIG1pbWUtdHlwZSBvZiB0aGUgaW1hZ2UsIGUuZy4gXCJpbWFnZS9wbmdcIlxuICogQHBhcmFtIHtzdHJpbmd9IGJhc2U2NGJpdHMgYmFzZTY0LWVuY29kZWQgaW1hZ2UgY29udGVudCAob3IgcHJldmlldywgaWYgbGFyZ2UgaW1hZ2UgaXMgYXR0YWNoZWQpXG4gKiBAcGFyYW0ge2ludGVnZXJ9IHdpZHRoIHdpZHRoIG9mIHRoZSBpbWFnZVxuICogQHBhcmFtIHtpbnRlZ2VyfSBoZWlnaHQgaGVpZ2h0IG9mIHRoZSBpbWFnZVxuICogQHBhcmFtIHtzdHJpbmd9IGZuYW1lIGZpbGUgbmFtZSBzdWdnZXN0aW9uIGZvciBkb3dubG9hZGluZyB0aGUgaW1hZ2UuXG4gKiBAcGFyYW0ge2ludGVnZXJ9IHNpemUgc2l6ZSBvZiB0aGUgZXh0ZXJuYWwgZmlsZS4gVHJlYXQgaXMgYXMgYW4gdW50cnVzdGVkIGhpbnQuXG4gKiBAcGFyYW0ge3N0cmluZ30gcmVmdXJsIHJlZmVyZW5jZSB0byB0aGUgY29udGVudC4gQ291bGQgYmUgbnVsbCBvciB1bmRlZmluZWQuXG4gKlxuICogQHJldHVybiB7RHJhZnR5fSB1cGRhdGVkIGNvbnRlbnQuXG4gKi9cbkRyYWZ0eS5hcHBlbmRJbWFnZSA9IGZ1bmN0aW9uKGNvbnRlbnQsIG1pbWUsIGJhc2U2NGJpdHMsIHdpZHRoLCBoZWlnaHQsIGZuYW1lLCBzaXplLCByZWZ1cmwpIHtcbiAgY29udGVudCA9IGNvbnRlbnQgfHwge1xuICAgIHR4dDogXCJcIlxuICB9O1xuICBjb250ZW50LnR4dCArPSBcIiBcIjtcbiAgcmV0dXJuIERyYWZ0eS5pbnNlcnRJbWFnZShjb250ZW50LCBjb250ZW50LnR4dC5sZW5ndGggLSAxLCBtaW1lLCBiYXNlNjRiaXRzLCB3aWR0aCwgaGVpZ2h0LCBmbmFtZSwgc2l6ZSwgcmVmdXJsKTtcbn1cblxuLyoqXG4gKiBBdHRhY2ggZmlsZSB0byBEcmFmdHkgY29udGVudC4gRWl0aGVyIGFzIGEgYmxvYiBvciBhcyBhIHJlZmVyZW5jZS5cbiAqIEBtZW1iZXJvZiBEcmFmdHlcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge0RyYWZ0eX0gY29udGVudCBvYmplY3QgdG8gYXR0YWNoIGZpbGUgdG8uXG4gKiBAcGFyYW0ge3N0cmluZ30gbWltZSBtaW1lLXR5cGUgb2YgdGhlIGZpbGUsIGUuZy4gXCJpbWFnZS9wbmdcIlxuICogQHBhcmFtIHtzdHJpbmd9IGJhc2U2NGJpdHMgYmFzZTY0LWVuY29kZWQgZmlsZSBjb250ZW50XG4gKiBAcGFyYW0ge3N0cmluZ30gZm5hbWUgZmlsZSBuYW1lIHN1Z2dlc3Rpb24gZm9yIGRvd25sb2FkaW5nLlxuICogQHBhcmFtIHtpbnRlZ2VyfSBzaXplIHNpemUgb2YgdGhlIGV4dGVybmFsIGZpbGUuIFRyZWF0IGlzIGFzIGFuIHVudHJ1c3RlZCBoaW50LlxuICogQHBhcmFtIHtzdHJpbmcgfCBQcm9taXNlfSByZWZ1cmwgb3B0aW9uYWwgcmVmZXJlbmNlIHRvIHRoZSBjb250ZW50LlxuICpcbiAqIEByZXR1cm4ge0RyYWZ0eX0gdXBkYXRlZCBjb250ZW50LlxuICovXG5EcmFmdHkuYXR0YWNoRmlsZSA9IGZ1bmN0aW9uKGNvbnRlbnQsIG1pbWUsIGJhc2U2NGJpdHMsIGZuYW1lLCBzaXplLCByZWZ1cmwpIHtcbiAgY29udGVudCA9IGNvbnRlbnQgfHwge1xuICAgIHR4dDogXCJcIlxuICB9O1xuICBjb250ZW50LmVudCA9IGNvbnRlbnQuZW50IHx8IFtdO1xuICBjb250ZW50LmZtdCA9IGNvbnRlbnQuZm10IHx8IFtdO1xuXG4gIGNvbnRlbnQuZm10LnB1c2goe1xuICAgIGF0OiAtMSxcbiAgICBsZW46IDAsXG4gICAga2V5OiBjb250ZW50LmVudC5sZW5ndGhcbiAgfSk7XG5cbiAgbGV0IGV4ID0ge1xuICAgIHRwOiAnRVgnLFxuICAgIGRhdGE6IHtcbiAgICAgIG1pbWU6IG1pbWUsXG4gICAgICB2YWw6IGJhc2U2NGJpdHMsXG4gICAgICBuYW1lOiBmbmFtZSxcbiAgICAgIHJlZjogcmVmdXJsLFxuICAgICAgc2l6ZTogc2l6ZSB8IDBcbiAgICB9XG4gIH1cbiAgaWYgKHJlZnVybCBpbnN0YW5jZW9mIFByb21pc2UpIHtcbiAgICBleC5kYXRhLnJlZiA9IHJlZnVybC50aGVuKFxuICAgICAgKHVybCkgPT4ge1xuICAgICAgICBleC5kYXRhLnJlZiA9IHVybDtcbiAgICAgIH0sXG4gICAgICAoZXJyKSA9PiB7XG4gICAgICAgIC8qIGNhdGNoIHRoZSBlcnJvciwgb3RoZXJ3aXNlIGl0IHdpbGwgYXBwZWFyIGluIHRoZSBjb25zb2xlLiAqL1xuICAgICAgfVxuICAgICk7XG4gIH1cbiAgY29udGVudC5lbnQucHVzaChleCk7XG5cbiAgcmV0dXJuIGNvbnRlbnQ7XG59XG5cbi8qKlxuICogV3JhcHMgY29udGVudCBpbnRvIGFuIGludGVyYWN0aXZlIGZvcm0uXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQHBhcmFtIHtEcmFmdHl8c3RyaW5nfSBjb250ZW50IHRvIHdyYXAgaW50byBhIGZvcm0uXG4gKiBAcGFyYW0ge251bWJlcn0gYXQgaW5kZXggd2hlcmUgdGhlIGZvcm1zIHN0YXJ0cy5cbiAqIEBwYXJhbSB7bnVtYmVyfSBsZW4gbGVuZ3RoIG9mIHRoZSBmb3JtIGNvbnRlbnQuXG4gKlxuICogQHJldHVybiB7RHJhZnR5fSB1cGRhdGVkIGNvbnRlbnQuXG4gKi9cbkRyYWZ0eS53cmFwQXNGb3JtID0gZnVuY3Rpb24oY29udGVudCwgYXQsIGxlbikge1xuICBpZiAodHlwZW9mIGNvbnRlbnQgPT0gJ3N0cmluZycpIHtcbiAgICBjb250ZW50ID0ge1xuICAgICAgdHh0OiBjb250ZW50XG4gICAgfTtcbiAgfVxuICBjb250ZW50LmZtdCA9IGNvbnRlbnQuZm10IHx8IFtdO1xuXG4gIGNvbnRlbnQuZm10LnB1c2goe1xuICAgIGF0OiBhdCxcbiAgICBsZW46IGxlbixcbiAgICB0cDogJ0ZNJ1xuICB9KTtcblxuICByZXR1cm4gY29udGVudDtcbn1cblxuLyoqXG4gKiBJbnNlcnQgY2xpY2thYmxlIGJ1dHRvbiBpbnRvIERyYWZ0eSBkb2N1bWVudC5cbiAqIEBtZW1iZXJvZiBEcmFmdHlcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge0RyYWZ0eXxzdHJpbmd9IGNvbnRlbnQgaXMgRHJhZnR5IG9iamVjdCB0byBpbnNlcnQgYnV0dG9uIHRvIG9yIGEgc3RyaW5nIHRvIGJlIHVzZWQgYXMgYnV0dG9uIHRleHQuXG4gKiBAcGFyYW0ge251bWJlcn0gYXQgaXMgbG9jYXRpb24gd2hlcmUgdGhlIGJ1dHRvbiBpcyBpbnNlcnRlZC5cbiAqIEBwYXJhbSB7bnVtYmVyfSBsZW4gaXMgdGhlIGxlbmd0aCBvZiB0aGUgdGV4dCB0byBiZSB1c2VkIGFzIGJ1dHRvbiB0aXRsZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lIG9mIHRoZSBidXR0b24uIENsaWVudCBzaG91bGQgcmV0dXJuIGl0IHRvIHRoZSBzZXJ2ZXIgd2hlbiB0aGUgYnV0dG9uIGlzIGNsaWNrZWQuXG4gKiBAcGFyYW0ge3N0cmluZ30gYWN0aW9uVHlwZSBpcyB0aGUgdHlwZSBvZiB0aGUgYnV0dG9uLCBvbmUgb2YgJ3VybCcgb3IgJ3B1YicuXG4gKiBAcGFyYW0ge3N0cmluZ30gYWN0aW9uVmFsdWUgaXMgdGhlIHZhbHVlIHRvIHJldHVybiBvbiBjbGljazpcbiAqIEBwYXJhbSB7c3RyaW5nfSByZWZVcmwgaXMgdGhlIFVSTCB0byBnbyB0byB3aGVuIHRoZSAndXJsJyBidXR0b24gaXMgY2xpY2tlZC5cbiAqXG4gKiBAcmV0dXJuIHtEcmFmdHl9IHVwZGF0ZWQgY29udGVudC5cbiAqL1xuRHJhZnR5Lmluc2VydEJ1dHRvbiA9IGZ1bmN0aW9uKGNvbnRlbnQsIGF0LCBsZW4sIG5hbWUsIGFjdGlvblR5cGUsIGFjdGlvblZhbHVlLCByZWZVcmwpIHtcbiAgaWYgKHR5cGVvZiBjb250ZW50ID09ICdzdHJpbmcnKSB7XG4gICAgY29udGVudCA9IHtcbiAgICAgIHR4dDogY29udGVudFxuICAgIH07XG4gIH1cblxuICBpZiAoIWNvbnRlbnQgfHwgIWNvbnRlbnQudHh0IHx8IGNvbnRlbnQudHh0Lmxlbmd0aCA8IGF0ICsgbGVuKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBpZiAobGVuIDw9IDAgfHwgWyd1cmwnLCAncHViJ10uaW5kZXhPZihhY3Rpb25UeXBlKSA9PSAtMSkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIC8vIEVuc3VyZSByZWZVcmwgaXMgYSBzdHJpbmcuXG4gIGlmIChhY3Rpb25UeXBlID09ICd1cmwnICYmICFyZWZVcmwpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICByZWZVcmwgPSAnJyArIHJlZlVybDtcblxuICBjb250ZW50LmVudCA9IGNvbnRlbnQuZW50IHx8IFtdO1xuICBjb250ZW50LmZtdCA9IGNvbnRlbnQuZm10IHx8IFtdO1xuXG4gIGNvbnRlbnQuZm10LnB1c2goe1xuICAgIGF0OiBhdCxcbiAgICBsZW46IGxlbixcbiAgICBrZXk6IGNvbnRlbnQuZW50Lmxlbmd0aFxuICB9KTtcbiAgY29udGVudC5lbnQucHVzaCh7XG4gICAgdHA6ICdCTicsXG4gICAgZGF0YToge1xuICAgICAgYWN0OiBhY3Rpb25UeXBlLFxuICAgICAgdmFsOiBhY3Rpb25WYWx1ZSxcbiAgICAgIHJlZjogcmVmVXJsLFxuICAgICAgbmFtZTogbmFtZVxuICAgIH1cbiAgfSk7XG5cbiAgcmV0dXJuIGNvbnRlbnQ7XG59XG5cbi8qKlxuICogQXBwZW5kIGNsaWNrYWJsZSBidXR0b24gdG8gRHJhZnR5IGRvY3VtZW50LlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7RHJhZnR5fHN0cmluZ30gY29udGVudCBpcyBEcmFmdHkgb2JqZWN0IHRvIGluc2VydCBidXR0b24gdG8gb3IgYSBzdHJpbmcgdG8gYmUgdXNlZCBhcyBidXR0b24gdGV4dC5cbiAqIEBwYXJhbSB7c3RyaW5nfSB0aXRsZSBpcyB0aGUgdGV4dCB0byBiZSB1c2VkIGFzIGJ1dHRvbiB0aXRsZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lIG9mIHRoZSBidXR0b24uIENsaWVudCBzaG91bGQgcmV0dXJuIGl0IHRvIHRoZSBzZXJ2ZXIgd2hlbiB0aGUgYnV0dG9uIGlzIGNsaWNrZWQuXG4gKiBAcGFyYW0ge3N0cmluZ30gYWN0aW9uVHlwZSBpcyB0aGUgdHlwZSBvZiB0aGUgYnV0dG9uLCBvbmUgb2YgJ3VybCcgb3IgJ3B1YicuXG4gKiBAcGFyYW0ge3N0cmluZ30gYWN0aW9uVmFsdWUgaXMgdGhlIHZhbHVlIHRvIHJldHVybiBvbiBjbGljazpcbiAqIEBwYXJhbSB7c3RyaW5nfSByZWZVcmwgaXMgdGhlIFVSTCB0byBnbyB0byB3aGVuIHRoZSAndXJsJyBidXR0b24gaXMgY2xpY2tlZC5cbiAqXG4gKiBAcmV0dXJuIHtEcmFmdHl9IHVwZGF0ZWQgY29udGVudC5cbiAqL1xuRHJhZnR5LmFwcGVuZEJ1dHRvbiA9IGZ1bmN0aW9uKGNvbnRlbnQsIHRpdGxlLCBuYW1lLCBhY3Rpb25UeXBlLCBhY3Rpb25WYWx1ZSwgcmVmVXJsKSB7XG4gIGNvbnRlbnQgPSBjb250ZW50IHx8IHtcbiAgICB0eHQ6IFwiXCJcbiAgfTtcbiAgbGV0IGF0ID0gY29udGVudC50eHQubGVuZ3RoO1xuICBjb250ZW50LnR4dCArPSB0aXRsZTtcbiAgcmV0dXJuIERyYWZ0eS5pbnNlcnRCdXR0b24oY29udGVudCwgYXQsIHRpdGxlLmxlbmd0aCwgbmFtZSwgYWN0aW9uVHlwZSwgYWN0aW9uVmFsdWUsIHJlZlVybCk7XG59XG5cbi8qKlxuICogQXR0YWNoIGEgZ2VuZXJpYyBKUyBvYmplY3QuIFRoZSBvYmplY3QgaXMgYXR0YWNoZWQgYXMgYSBqc29uIHN0cmluZy5cbiAqIEludGVuZGVkIGZvciByZXByZXNlbnRpbmcgYSBmb3JtIHJlc3BvbnNlLlxuICpcbiAqIEBtZW1iZXJvZiBEcmFmdHlcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge0RyYWZ0eX0gY29udGVudCBvYmplY3QgdG8gYXR0YWNoIGZpbGUgdG8uXG4gKiBAcGFyYW0ge09iamVjdH0gZGF0YSB0byBjb252ZXJ0IHRvIGpzb24gc3RyaW5nIGFuZCBhdHRhY2guXG4gKi9cbkRyYWZ0eS5hdHRhY2hKU09OID0gZnVuY3Rpb24oY29udGVudCwgZGF0YSkge1xuICBjb250ZW50ID0gY29udGVudCB8fCB7XG4gICAgdHh0OiBcIlwiXG4gIH07XG4gIGNvbnRlbnQuZW50ID0gY29udGVudC5lbnQgfHwgW107XG4gIGNvbnRlbnQuZm10ID0gY29udGVudC5mbXQgfHwgW107XG5cbiAgY29udGVudC5mbXQucHVzaCh7XG4gICAgYXQ6IC0xLFxuICAgIGxlbjogMCxcbiAgICBrZXk6IGNvbnRlbnQuZW50Lmxlbmd0aFxuICB9KTtcblxuICBjb250ZW50LmVudC5wdXNoKHtcbiAgICB0cDogJ0VYJyxcbiAgICBkYXRhOiB7XG4gICAgICBtaW1lOiBKU09OX01JTUVfVFlQRSxcbiAgICAgIHZhbDogZGF0YVxuICAgIH1cbiAgfSk7XG5cbiAgcmV0dXJuIGNvbnRlbnQ7XG59XG5cbkRyYWZ0eS5hcHBlbmRMaW5lQnJlYWsgPSBmdW5jdGlvbihjb250ZW50KSB7XG4gIGNvbnRlbnQgPSBjb250ZW50IHx8IHtcbiAgICB0eHQ6IFwiXCJcbiAgfTtcbiAgY29udGVudC5mbXQgPSBjb250ZW50LmZtdCB8fCBbXTtcbiAgY29udGVudC5mbXQucHVzaCh7XG4gICAgYXQ6IGNvbnRlbnQudHh0Lmxlbmd0aCxcbiAgICBsZW46IDEsXG4gICAgdHA6ICdCUidcbiAgfSk7XG4gIGNvbnRlbnQudHh0ICs9IFwiIFwiO1xuXG4gIHJldHVybiBjb250ZW50O1xufVxuLyoqXG4gKiBHaXZlbiB0aGUgc3RydWN0dXJlZCByZXByZXNlbnRhdGlvbiBvZiByaWNoIHRleHQsIGNvbnZlcnQgaXQgdG8gSFRNTC5cbiAqIE5vIGF0dGVtcHQgaXMgbWFkZSB0byBzdHJpcCBwcmUtZXhpc3RpbmcgaHRtbCBtYXJrdXAuXG4gKiBUaGlzIGlzIHBvdGVudGlhbGx5IHVuc2FmZSBiZWNhdXNlIGBjb250ZW50LnR4dGAgbWF5IGNvbnRhaW4gbWFsaWNpb3VzXG4gKiBtYXJrdXAuXG4gKiBAbWVtYmVyb2YgVGlub2RlLkRyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7ZHJhZnl9IGNvbnRlbnQgLSBzdHJ1Y3R1cmVkIHJlcHJlc2VudGF0aW9uIG9mIHJpY2ggdGV4dC5cbiAqXG4gKiBAcmV0dXJuIEhUTUwtcmVwcmVzZW50YXRpb24gb2YgY29udGVudC5cbiAqL1xuRHJhZnR5LlVOU0FGRV90b0hUTUwgPSBmdW5jdGlvbihjb250ZW50KSB7XG4gIHZhciB7XG4gICAgdHh0LFxuICAgIGZtdCxcbiAgICBlbnRcbiAgfSA9IGNvbnRlbnQ7XG5cbiAgdmFyIG1hcmt1cCA9IFtdO1xuICBpZiAoZm10KSB7XG4gICAgZm9yIChsZXQgaSBpbiBmbXQpIHtcbiAgICAgIGxldCByYW5nZSA9IGZtdFtpXTtcbiAgICAgIGxldCB0cCA9IHJhbmdlLnRwLFxuICAgICAgICBkYXRhO1xuICAgICAgaWYgKCF0cCkge1xuICAgICAgICBsZXQgZW50aXR5ID0gZW50W3JhbmdlLmtleSB8IDBdO1xuICAgICAgICBpZiAoZW50aXR5KSB7XG4gICAgICAgICAgdHAgPSBlbnRpdHkudHA7XG4gICAgICAgICAgZGF0YSA9IGVudGl0eS5kYXRhO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGlmIChERUNPUkFUT1JTW3RwXSkge1xuICAgICAgICAvLyBCZWNhdXNlIHdlIGxhdGVyIHNvcnQgaW4gZGVzY2VuZGluZyBvcmRlciwgY2xvc2luZyBtYXJrdXAgbXVzdCBjb21lIGZpcnN0LlxuICAgICAgICAvLyBPdGhlcndpc2UgemVyby1sZW5ndGggb2JqZWN0cyB3aWxsIG5vdCBiZSByZXByZXNlbnRlZCBjb3JyZWN0bHkuXG4gICAgICAgIG1hcmt1cC5wdXNoKHtcbiAgICAgICAgICBpZHg6IHJhbmdlLmF0ICsgcmFuZ2UubGVuLFxuICAgICAgICAgIGxlbjogLXJhbmdlLmxlbixcbiAgICAgICAgICB3aGF0OiBERUNPUkFUT1JTW3RwXS5jbG9zZShkYXRhKVxuICAgICAgICB9KTtcbiAgICAgICAgbWFya3VwLnB1c2goe1xuICAgICAgICAgIGlkeDogcmFuZ2UuYXQsXG4gICAgICAgICAgbGVuOiByYW5nZS5sZW4sXG4gICAgICAgICAgd2hhdDogREVDT1JBVE9SU1t0cF0ub3BlbihkYXRhKVxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBtYXJrdXAuc29ydChmdW5jdGlvbihhLCBiKSB7XG4gICAgcmV0dXJuIGIuaWR4ID09IGEuaWR4ID8gYi5sZW4gLSBhLmxlbiA6IGIuaWR4IC0gYS5pZHg7IC8vIGluIGRlc2NlbmRpbmcgb3JkZXJcbiAgfSk7XG5cbiAgZm9yICh2YXIgaSBpbiBtYXJrdXApIHtcbiAgICBpZiAobWFya3VwW2ldLndoYXQpIHtcbiAgICAgIHR4dCA9IHNwbGljZSh0eHQsIG1hcmt1cFtpXS5pZHgsIG1hcmt1cFtpXS53aGF0KTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gdHh0O1xufVxuXG4vKipcbiAqIENhbGxiYWNrIGZvciBhcHBseWluZyBjdXN0b20gZm9ybWF0dGluZy90cmFuc2Zvcm1hdGlvbiB0byBhIERyYWZ0eSBvYmplY3QuXG4gKiBDYWxsZWQgb25jZSBmb3IgZWFjaCBzeWxlIHNwYW4uXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQGNhbGxiYWNrIEZvcm1hdHRlclxuICogQHBhcmFtIHtzdHJpbmd9IHN0eWxlIHN0eWxlIGNvZGUgc3VjaCBhcyBcIlNUXCIgb3IgXCJJTVwiLlxuICogQHBhcmFtIHtPYmplY3R9IGRhdGEgZW50aXR5J3MgZGF0YVxuICogQHBhcmFtIHtPYmplY3R9IHZhbHVlcyBwb3NzaWJseSBzdHlsZWQgc3Vic3BhbnMgY29udGFpbmVkIGluIHRoaXMgc3R5bGUgc3Bhbi5cbiAqIEBwYXJhbSB7bnVtYmVyfSBpbmRleCBvZiB0aGUgY3VycmVudCBlbGVtZW50IGFtb25nIGl0cyBzaWJsaW5ncy5cbiAqL1xuXG4vKipcbiAqIFRyYW5zZm9ybSBEcmFmdHkgdXNpbmcgY3VzdG9tIGZvcm1hdHRpbmcuXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQHBhcmFtIHtEcmFmdHl9IGNvbnRlbnQgLSBjb250ZW50IHRvIHRyYW5zZm9ybS5cbiAqIEBwYXJhbSB7Rm9ybWF0dGVyfSBmb3JtYXR0ZXIgLSBjYWxsYmFjayB3aGljaCB0cmFuc2Zvcm1zIGluZGl2aWR1YWwgZWxlbWVudHNcbiAqIEBwYXJhbSB7T2JqZWN0fSBjb250ZXh0IC0gY29udGV4dCBwcm92aWRlZCB0byBmb3JtYXR0ZXIgYXMgJ3RoaXMnLlxuICpcbiAqIEByZXR1cm4ge09iamVjdH0gdHJhbnNmb3JtZWQgb2JqZWN0XG4gKi9cbkRyYWZ0eS5mb3JtYXQgPSBmdW5jdGlvbihjb250ZW50LCBmb3JtYXR0ZXIsIGNvbnRleHQpIHtcbiAgbGV0IHtcbiAgICB0eHQsXG4gICAgZm10LFxuICAgIGVudFxuICB9ID0gY29udGVudDtcblxuICAvLyBBc3NpZ24gZGVmYXVsdCB2YWx1ZXMuXG4gIHR4dCA9IHR4dCB8fCBcIlwiO1xuICBpZiAoIUFycmF5LmlzQXJyYXkoZW50KSkge1xuICAgIGVudCA9IFtdO1xuICB9XG5cbiAgaWYgKCFBcnJheS5pc0FycmF5KGZtdCkpIHtcbiAgICAvLyBIYW5kbGUgc3BlY2lhbCBjYXNlIHdoZW4gYWxsIHZhbHVlcyBpbiBmbXQgYXJlIDAgYW5kIGZtdCBpcyBza2lwcGVkLlxuICAgIGlmIChlbnQubGVuZ3RoID09IDEpIHtcbiAgICAgIGZtdCA9IFt7XG4gICAgICAgIGF0OiAwLFxuICAgICAgICBsZW46IDAsXG4gICAgICAgIGtleTogMFxuICAgICAgfV07XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiBbdHh0XTtcbiAgICB9XG4gIH1cblxuICBsZXQgc3BhbnMgPSBbXS5jb25jYXQoZm10KTtcblxuICAvLyBaZXJvIHZhbHVlcyBtYXkgaGF2ZSBiZWVuIHN0cmlwcGVkLiBSZXN0b3JlIHRoZW0uXG4gIC8vIEFsc28gZW5zdXJlIGluZGV4ZXMgYW5kIGxlbmd0aHMgYXJlIHNhbmUuXG4gIHNwYW5zLm1hcChmdW5jdGlvbihzKSB7XG4gICAgcy5hdCA9IHMuYXQgfHwgMDtcbiAgICBzLmxlbiA9IHMubGVuIHx8IDA7XG4gICAgaWYgKHMubGVuIDwgMCkge1xuICAgICAgcy5sZW4gPSAwO1xuICAgIH1cbiAgICBpZiAocy5hdCA8IC0xKSB7XG4gICAgICBzLmF0ID0gLTE7XG4gICAgfVxuICB9KTtcblxuICAvLyBTb3J0IHNwYW5zIGZpcnN0IGJ5IHN0YXJ0IGluZGV4IChhc2MpIHRoZW4gYnkgbGVuZ3RoIChkZXNjKS5cbiAgc3BhbnMuc29ydChmdW5jdGlvbihhLCBiKSB7XG4gICAgaWYgKGEuYXQgLSBiLmF0ID09IDApIHtcbiAgICAgIHJldHVybiBiLmxlbiAtIGEubGVuOyAvLyBsb25nZXIgb25lIGNvbWVzIGZpcnN0ICg8MClcbiAgICB9XG4gICAgcmV0dXJuIGEuYXQgLSBiLmF0O1xuICB9KTtcblxuICAvLyBEZW5vcm1hbGl6ZSBlbnRpdGllcyBpbnRvIHNwYW5zLiBDcmVhdGUgYSBjb3B5IG9mIHRoZSBvYmplY3RzIHRvIGxlYXZlXG4gIC8vIG9yaWdpbmFsIERyYWZ0eSBvYmplY3QgdW5jaGFuZ2VkLlxuICBzcGFucyA9IHNwYW5zLm1hcCgocykgPT4ge1xuICAgIGxldCBkYXRhO1xuICAgIGxldCB0cCA9IHMudHA7XG4gICAgaWYgKCF0cCkge1xuICAgICAgcy5rZXkgPSBzLmtleSB8fCAwO1xuICAgICAgaWYgKGVudFtzLmtleV0pIHtcbiAgICAgICAgZGF0YSA9IGVudFtzLmtleV0uZGF0YTtcbiAgICAgICAgdHAgPSBlbnRbcy5rZXldLnRwO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFR5cGUgc3RpbGwgbm90IGRlZmluZWQ/IEhpZGUgaW52YWxpZCBlbGVtZW50LlxuICAgIHRwID0gdHAgfHwgJ0hEJztcblxuICAgIHJldHVybiB7XG4gICAgICB0cDogdHAsXG4gICAgICBkYXRhOiBkYXRhLFxuICAgICAgYXQ6IHMuYXQsXG4gICAgICBsZW46IHMubGVuXG4gICAgfTtcbiAgfSk7XG5cbiAgcmV0dXJuIGZvckVhY2godHh0LCAwLCB0eHQubGVuZ3RoLCBzcGFucywgZm9ybWF0dGVyLCBjb250ZXh0KTtcbn1cblxuLyoqXG4gKiBHaXZlbiBzdHJ1Y3R1cmVkIHJlcHJlc2VudGF0aW9uIG9mIHJpY2ggdGV4dCwgY29udmVydCBpdCB0byBwbGFpbiB0ZXh0LlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7RHJhZnR5fSBjb250ZW50IC0gY29udGVudCB0byBjb252ZXJ0IHRvIHBsYWluIHRleHQuXG4gKi9cbkRyYWZ0eS50b1BsYWluVGV4dCA9IGZ1bmN0aW9uKGNvbnRlbnQpIHtcbiAgcmV0dXJuIHR5cGVvZiBjb250ZW50ID09ICdzdHJpbmcnID8gY29udGVudCA6IGNvbnRlbnQudHh0O1xufVxuXG4vKipcbiAqIFJldHVybnMgdHJ1ZSBpZiBjb250ZW50IGhhcyBubyBtYXJrdXAgYW5kIG5vIGVudGl0aWVzLlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7RHJhZnR5fSBjb250ZW50IC0gY29udGVudCB0byBjaGVjayBmb3IgcHJlc2VuY2Ugb2YgbWFya3VwLlxuICogQHJldHVybnMgdHJ1ZSBpcyBjb250ZW50IGlzIHBsYWluIHRleHQsIGZhbHNlIG90aGVyd2lzZS5cbiAqL1xuRHJhZnR5LmlzUGxhaW5UZXh0ID0gZnVuY3Rpb24oY29udGVudCkge1xuICByZXR1cm4gdHlwZW9mIGNvbnRlbnQgPT0gJ3N0cmluZycgfHwgIShjb250ZW50LmZtdCB8fCBjb250ZW50LmVudCk7XG59XG5cbi8qKlxuICogQ2hlY2sgaWYgdGhlIGRyYWZ0eSBjb250ZW50IGhhcyBhdHRhY2htZW50cy5cbiAqIEBtZW1iZXJvZiBEcmFmdHlcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge0RyYWZ0eX0gY29udGVudCAtIGNvbnRlbnQgdG8gY2hlY2sgZm9yIGF0dGFjaG1lbnRzLlxuICogQHJldHVybnMgdHJ1ZSBpZiB0aGVyZSBhcmUgYXR0YWNobWVudHMuXG4gKi9cbkRyYWZ0eS5oYXNBdHRhY2htZW50cyA9IGZ1bmN0aW9uKGNvbnRlbnQpIHtcbiAgaWYgKGNvbnRlbnQuZW50ICYmIGNvbnRlbnQuZW50Lmxlbmd0aCA+IDApIHtcbiAgICBmb3IgKHZhciBpIGluIGNvbnRlbnQuZW50KSB7XG4gICAgICBpZiAoY29udGVudC5lbnRbaV0gJiYgY29udGVudC5lbnRbaV0udHAgPT0gJ0VYJykge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG4vKipcbiAqIENhbGxiYWNrIGZvciBhcHBseWluZyBjdXN0b20gZm9ybWF0dGluZy90cmFuc2Zvcm1hdGlvbiB0byBhIERyYWZ0eSBvYmplY3QuXG4gKiBDYWxsZWQgb25jZSBmb3IgZWFjaCBzeWxlIHNwYW4uXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQGNhbGxiYWNrIEF0dGFjaG1lbnRDYWxsYmFja1xuICogQHBhcmFtIHtPYmplY3R9IGRhdGEgYXR0YWNobWVudCBkYXRhXG4gKiBAcGFyYW0ge251bWJlcn0gaW5kZXggYXR0YWNobWVudCdzIGluZGV4IGluIGBjb250ZW50LmVudGAuXG4gKi9cblxuLyoqXG4gKiBFbnVtZXJhdGUgYXR0YWNobWVudHMuXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQHBhcmFtIHtEcmFmdHl9IGNvbnRlbnQgLSBkcmFmdHkgb2JqZWN0IHRvIHByb2Nlc3MgZm9yIGF0dGFjaG1lbnRzLlxuICogQHBhcmFtIHtBdHRhY2htZW50Q2FsbGJhY2t9IGNhbGxiYWNrIC0gY2FsbGJhY2sgdG8gY2FsbCBmb3IgZWFjaCBhdHRhY2htZW50LlxuICogQHBhcmFtIHtPYmplY3R9IGNvbnRlbnQgLSB2YWx1ZSBvZiBcInRoaXNcIiBmb3IgY2FsbGJhY2suXG4gKi9cbkRyYWZ0eS5hdHRhY2htZW50cyA9IGZ1bmN0aW9uKGNvbnRlbnQsIGNhbGxiYWNrLCBjb250ZXh0KSB7XG4gIGlmIChjb250ZW50LmVudCAmJiBjb250ZW50LmVudC5sZW5ndGggPiAwKSB7XG4gICAgZm9yICh2YXIgaSBpbiBjb250ZW50LmVudCkge1xuICAgICAgaWYgKGNvbnRlbnQuZW50W2ldICYmIGNvbnRlbnQuZW50W2ldLnRwID09ICdFWCcpIHtcbiAgICAgICAgY2FsbGJhY2suY2FsbChjb250ZXh0LCBjb250ZW50LmVudFtpXS5kYXRhLCBpKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLyoqXG4gKiBHaXZlbiB0aGUgZW50aXR5LCBnZXQgVVJMIHdoaWNoIGNhbiBiZSB1c2VkIGZvciBkb3dubG9hZGluZ1xuICogZW50aXR5IGRhdGEuXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IGVudGl0eS5kYXRhIHRvIGdldCB0aGUgVVJsIGZyb20uXG4gKi9cbkRyYWZ0eS5nZXREb3dubG9hZFVybCA9IGZ1bmN0aW9uKGVudERhdGEpIHtcbiAgbGV0IHVybCA9IG51bGw7XG4gIGlmIChlbnREYXRhLm1pbWUgIT0gSlNPTl9NSU1FX1RZUEUgJiYgZW50RGF0YS52YWwpIHtcbiAgICB1cmwgPSBiYXNlNjR0b09iamVjdFVybChlbnREYXRhLnZhbCwgZW50RGF0YS5taW1lKTtcbiAgfSBlbHNlIGlmICh0eXBlb2YgZW50RGF0YS5yZWYgPT0gJ3N0cmluZycpIHtcbiAgICB1cmwgPSBlbnREYXRhLnJlZjtcbiAgfVxuICByZXR1cm4gdXJsO1xufVxuXG4vKipcbiAqIENoZWNrIGlmIHRoZSBlbnRpdHkgZGF0YSBpcyBiZWluZyB1cGxvYWRlZCB0byB0aGUgc2VydmVyLlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBlbnRpdHkuZGF0YSB0byBnZXQgdGhlIFVSbCBmcm9tLlxuICogQHJldHVybnMge2Jvb2xlYW59IHRydWUgaWYgdXBsb2FkIGlzIGluIHByb2dyZXNzLCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cbkRyYWZ0eS5pc1VwbG9hZGluZyA9IGZ1bmN0aW9uKGVudERhdGEpIHtcbiAgcmV0dXJuIGVudERhdGEucmVmIGluc3RhbmNlb2YgUHJvbWlzZTtcbn1cblxuLyoqXG4gKiBHaXZlbiB0aGUgZW50aXR5LCBnZXQgVVJMIHdoaWNoIGNhbiBiZSB1c2VkIGZvciBwcmV2aWV3aW5nXG4gKiB0aGUgZW50aXR5LlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBlbnRpdHkuZGF0YSB0byBnZXQgdGhlIFVSbCBmcm9tLlxuICpcbiAqIEByZXR1cm5zIHtzdHJpbmd9IHVybCBmb3IgcHJldmlld2luZyBvciBudWxsIGlmIG5vIHN1Y2ggdXJsIGlzIGF2YWlsYWJsZS5cbiAqL1xuRHJhZnR5LmdldFByZXZpZXdVcmwgPSBmdW5jdGlvbihlbnREYXRhKSB7XG4gIHJldHVybiBlbnREYXRhLnZhbCA/IGJhc2U2NHRvT2JqZWN0VXJsKGVudERhdGEudmFsLCBlbnREYXRhLm1pbWUpIDogbnVsbDtcbn1cblxuLyoqXG4gKiBHZXQgYXBwcm94aW1hdGUgc2l6ZSBvZiB0aGUgZW50aXR5LlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7T2JqZWN0fSBlbnRpdHkuZGF0YSB0byBnZXQgdGhlIHNpemUgZm9yLlxuICovXG5EcmFmdHkuZ2V0RW50aXR5U2l6ZSA9IGZ1bmN0aW9uKGVudERhdGEpIHtcbiAgLy8gRWl0aGVyIHNpemUgaGludCBvciBsZW5ndGggb2YgdmFsdWUuIFRoZSB2YWx1ZSBpcyBiYXNlNjQgZW5jb2RlZCxcbiAgLy8gdGhlIGFjdHVhbCBvYmplY3Qgc2l6ZSBpcyBzbWFsbGVyIHRoYW4gdGhlIGVuY29kZWQgbGVuZ3RoLlxuICByZXR1cm4gZW50RGF0YS5zaXplID8gZW50RGF0YS5zaXplIDogZW50RGF0YS52YWwgPyAoZW50RGF0YS52YWwubGVuZ3RoICogMC43NSkgfCAwIDogMDtcbn1cblxuLyoqXG4gKiBHZXQgZW50aXR5IG1pbWUgdHlwZS5cbiAqIEBtZW1iZXJvZiBEcmFmdHlcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge09iamVjdH0gZW50aXR5LmRhdGEgdG8gZ2V0IHRoZSB0eXBlIGZvci5cbiAqL1xuRHJhZnR5LmdldEVudGl0eU1pbWVUeXBlID0gZnVuY3Rpb24oZW50RGF0YSkge1xuICByZXR1cm4gZW50RGF0YS5taW1lIHx8ICd0ZXh0L3BsYWluJztcbn1cblxuLyoqXG4gKiBHZXQgSFRNTCB0YWcgZm9yIGEgZ2l2ZW4gdHdvLWxldHRlciBzdHlsZSBuYW1lXG4gKiBAbWVtYmVyb2YgRHJhZnR5XG4gKiBAc3RhdGljXG4gKlxuICogQHBhcmFtIHtzdHJpbmd9IHN0eWxlIC0gdHdvLWxldHRlciBzdHlsZSwgbGlrZSBTVCBvciBMTlxuICpcbiAqIEByZXR1cm5zIHtzdHJpbmd9IHRhZyBuYW1lXG4gKi9cbkRyYWZ0eS50YWdOYW1lID0gZnVuY3Rpb24oc3R5bGUpIHtcbiAgcmV0dXJuIEhUTUxfVEFHU1tzdHlsZV0gPyBIVE1MX1RBR1Nbc3R5bGVdLm5hbWUgOiB1bmRlZmluZWQ7XG59XG5cbi8qKlxuICogRm9yIGEgZ2l2ZW4gZGF0YSBidW5kbGUgZ2VuZXJhdGUgYW4gb2JqZWN0IHdpdGggSFRNTCBhdHRyaWJ1dGVzLFxuICogZm9yIGluc3RhbmNlLCBnaXZlbiB7dXJsOiBcImh0dHA6Ly93d3cuZXhhbXBsZS5jb20vXCJ9IHJldHVyblxuICoge2hyZWY6IFwiaHR0cDovL3d3dy5leGFtcGxlLmNvbS9cIn1cbiAqIEBtZW1iZXJvZiBEcmFmdHlcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gc3R5bGUgLSB0dy1sZXR0ZXIgc3R5bGUgdG8gZ2VuZXJhdGUgYXR0cmlidXRlcyBmb3IuXG4gKiBAcGFyYW0ge09iamVjdH0gZGF0YSAtIGRhdGEgYnVuZGxlIHRvIGNvbnZlcnQgdG8gYXR0cmlidXRlc1xuICpcbiAqIEByZXR1cm5zIHtPYmplY3R9IG9iamVjdCB3aXRoIEhUTUwgYXR0cmlidXRlcy5cbiAqL1xuRHJhZnR5LmF0dHJWYWx1ZSA9IGZ1bmN0aW9uKHN0eWxlLCBkYXRhKSB7XG4gIGlmIChkYXRhICYmIERFQ09SQVRPUlNbc3R5bGVdKSB7XG4gICAgcmV0dXJuIERFQ09SQVRPUlNbc3R5bGVdLnByb3BzKGRhdGEpO1xuICB9XG5cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBEcmFmdHkgTUlNRSB0eXBlLlxuICogQG1lbWJlcm9mIERyYWZ0eVxuICogQHN0YXRpY1xuICpcbiAqIEByZXR1cm5zIHtzdHJpbmd9IEhUVFAgQ29udGVudC1UeXBlIFwidGV4dC94LWRyYWZ0eVwiLlxuICovXG5EcmFmdHkuZ2V0Q29udGVudFR5cGUgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuICd0ZXh0L3gtZHJhZnR5Jztcbn1cblxuaWYgKHR5cGVvZiBtb2R1bGUgIT0gJ3VuZGVmaW5lZCcpIHtcbiAgbW9kdWxlLmV4cG9ydHMgPSBEcmFmdHk7XG59XG4iLCIvKipcbiAqIEBmaWxlIFNESyB0byBjb25uZWN0IHRvIFRpbm9kZSBjaGF0IHNlcnZlci5cbiAqIFNlZSA8YSBocmVmPVwiaHR0cHM6Ly9naXRodWIuY29tL3Rpbm9kZS93ZWJhcHBcIj5cbiAqIGh0dHBzOi8vZ2l0aHViLmNvbS90aW5vZGUvd2ViYXBwPC9hPiBmb3IgcmVhbC1saWZlIHVzYWdlLlxuICpcbiAqIEBjb3B5cmlnaHQgMjAxNS0yMDE5IFRpbm9kZVxuICogQHN1bW1hcnkgSmF2YXNjcmlwdCBiaW5kaW5ncyBmb3IgVGlub2RlLlxuICogQGxpY2Vuc2UgQXBhY2hlIDIuMFxuICogQHZlcnNpb24gMC4xNlxuICpcbiAqIEBleGFtcGxlXG4gKiA8aGVhZD5cbiAqIDxzY3JpcHQgc3JjPVwiLi4uL3Rpbm9kZS5qc1wiPjwvc2NyaXB0PlxuICogPC9oZWFkPlxuICpcbiAqIDxib2R5PlxuICogIC4uLlxuICogPHNjcmlwdD5cbiAqICAvLyBJbnN0YW50aWF0ZSB0aW5vZGUuXG4gKiAgY29uc3QgdGlub2RlID0gbmV3IFRpbm9kZShBUFBfTkFNRSwgSE9TVCwgQVBJX0tFWSwgbnVsbCwgdHJ1ZSk7XG4gKiAgdGlub2RlLmVuYWJsZUxvZ2dpbmcodHJ1ZSk7XG4gKiAgLy8gQWRkIGxvZ2ljIHRvIGhhbmRsZSBkaXNjb25uZWN0cy5cbiAqICB0aW5vZGUub25EaXNjb25uZWN0ID0gZnVuY3Rpb24oZXJyKSB7IC4uLiB9O1xuICogIC8vIENvbm5lY3QgdG8gdGhlIHNlcnZlci5cbiAqICB0aW5vZGUuY29ubmVjdCgpLnRoZW4oKCkgPT4ge1xuICogICAgLy8gQ29ubmVjdGVkLiBMb2dpbiBub3cuXG4gKiAgICByZXR1cm4gdGlub2RlLmxvZ2luQmFzaWMobG9naW4sIHBhc3N3b3JkKTtcbiAqICB9KS50aGVuKChjdHJsKSA9PiB7XG4gKiAgICAvLyBMb2dnZWQgaW4gZmluZSwgYXR0YWNoIGNhbGxiYWNrcywgc3Vic2NyaWJlIHRvICdtZScuXG4gKiAgICBjb25zdCBtZSA9IHRpbm9kZS5nZXRNZVRvcGljKCk7XG4gKiAgICBtZS5vbk1ldGFEZXNjID0gZnVuY3Rpb24obWV0YSkgeyAuLi4gfTtcbiAqICAgIC8vIFN1YnNjcmliZSwgZmV0Y2ggdG9waWMgZGVzY3JpcHRpb24gYW5kIHRoZSBsaXN0IG9mIGNvbnRhY3RzLlxuICogICAgbWUuc3Vic2NyaWJlKHtnZXQ6IHtkZXNjOiB7fSwgc3ViOiB7fX0pO1xuICogIH0pLmNhdGNoKChlcnIpID0+IHtcbiAqICAgIC8vIExvZ2luIG9yIHN1YnNjcmlwdGlvbiBmYWlsZWQsIGRvIHNvbWV0aGluZy5cbiAqICAgIC4uLlxuICogIH0pO1xuICogIC4uLlxuICogPC9zY3JpcHQ+XG4gKiA8L2JvZHk+XG4gKi9cbid1c2Ugc3RyaWN0JztcblxuLy8gTk9URSBUTyBERVZFTE9QRVJTOlxuLy8gTG9jYWxpemFibGUgc3RyaW5ncyBzaG91bGQgYmUgZG91YmxlIHF1b3RlZCBcItGB0YLRgNC+0LrQsCDQvdCwINC00YDRg9Cz0L7QvCDRj9C30YvQutC1XCIsXG4vLyBub24tbG9jYWxpemFibGUgc3RyaW5ncyBzaG91bGQgYmUgc2luZ2xlIHF1b3RlZCAnbm9uLWxvY2FsaXplZCcuXG5cbmlmICh0eXBlb2YgcmVxdWlyZSA9PSAnZnVuY3Rpb24nKSB7XG4gIGlmICh0eXBlb2YgRHJhZnR5ID09ICd1bmRlZmluZWQnKSB7XG4gICAgdmFyIERyYWZ0eSA9IHJlcXVpcmUoJy4vZHJhZnR5LmpzJyk7XG4gIH1cbiAgdmFyIHBhY2thZ2VfdmVyc2lvbiA9IHJlcXVpcmUoJy4uL3ZlcnNpb24uanNvbicpLnZlcnNpb247XG59XG5cbmxldCBXZWJTb2NrZXRQcm92aWRlcjtcbmlmICh0eXBlb2YgV2ViU29ja2V0ICE9ICd1bmRlZmluZWQnKSB7XG4gIFdlYlNvY2tldFByb3ZpZGVyID0gV2ViU29ja2V0O1xufVxuaW5pdEZvck5vbkJyb3dzZXJBcHAoKTtcblxuXG4vLyBHbG9iYWwgY29uc3RhbnRzXG5jb25zdCBQUk9UT0NPTF9WRVJTSU9OID0gJzAnO1xuY29uc3QgVkVSU0lPTiA9IHBhY2thZ2VfdmVyc2lvbiB8fCAnMC4xNic7XG5jb25zdCBMSUJSQVJZID0gJ3Rpbm9kZWpzLycgKyBWRVJTSU9OO1xuXG5jb25zdCBUT1BJQ19ORVcgPSAnbmV3JztcbmNvbnN0IFRPUElDX01FID0gJ21lJztcbmNvbnN0IFRPUElDX0ZORCA9ICdmbmQnO1xuY29uc3QgVE9QSUNfU1lTID0gJ3N5cyc7XG5jb25zdCBVU0VSX05FVyA9ICduZXcnO1xuXG4vLyBTdGFydGluZyB2YWx1ZSBvZiBhIGxvY2FsbHktZ2VuZXJhdGVkIHNlcUlkIHVzZWQgZm9yIHBlbmRpbmcgbWVzc2FnZXMuXG5jb25zdCBMT0NBTF9TRVFJRCA9IDB4RkZGRkZGRjtcblxuY29uc3QgTUVTU0FHRV9TVEFUVVNfTk9ORSA9IDA7IC8vIFN0YXR1cyBub3QgYXNzaWduZWQuXG5jb25zdCBNRVNTQUdFX1NUQVRVU19RVUVVRUQgPSAxOyAvLyBMb2NhbCBJRCBhc3NpZ25lZCwgaW4gcHJvZ3Jlc3MgdG8gYmUgc2VudC5cbmNvbnN0IE1FU1NBR0VfU1RBVFVTX1NFTkRJTkcgPSAyOyAvLyBUcmFuc21pc3Npb24gc3RhcnRlZC5cbmNvbnN0IE1FU1NBR0VfU1RBVFVTX0ZBSUxFRCA9IDM7IC8vIEF0IGxlYXN0IG9uZSBhdHRlbXB0IHdhcyBtYWRlIHRvIHNlbmQgdGhlIG1lc3NhZ2UuXG5jb25zdCBNRVNTQUdFX1NUQVRVU19TRU5UID0gNDsgLy8gRGVsaXZlcmVkIHRvIHRoZSBzZXJ2ZXIuXG5jb25zdCBNRVNTQUdFX1NUQVRVU19SRUNFSVZFRCA9IDU7IC8vIFJlY2VpdmVkIGJ5IHRoZSBjbGllbnQuXG5jb25zdCBNRVNTQUdFX1NUQVRVU19SRUFEID0gNjsgLy8gUmVhZCBieSB0aGUgdXNlci5cbmNvbnN0IE1FU1NBR0VfU1RBVFVTX1RPX01FID0gNzsgLy8gTWVzc2FnZSBmcm9tIGFub3RoZXIgdXNlci5cblxuLy8gRXJyb3IgY29kZSB0byByZXR1cm4gaW4gY2FzZSBvZiBhIG5ldHdvcmsgcHJvYmxlbS5cbmNvbnN0IE5FVFdPUktfRVJST1IgPSA1MDM7XG5jb25zdCBORVRXT1JLX0VSUk9SX1RFWFQgPSBcIkNvbm5lY3Rpb24gZmFpbGVkXCI7XG5cbi8vIFJlamVjdCB1bnJlc29sdmVkIGZ1dHVyZXMgYWZ0ZXIgdGhpcyBtYW55IG1pbGxpc2Vjb25kcy5cbmNvbnN0IEVYUElSRV9QUk9NSVNFU19USU1FT1VUID0gNTAwMDtcbi8vIFBlcmlvZGljaXR5IG9mIGdhcmJhZ2UgY29sbGVjdGlvbiBvZiB1bnJlc29sdmVkIGZ1dHVyZXMuXG5jb25zdCBFWFBJUkVfUFJPTUlTRVNfUEVSSU9EID0gMTAwMDtcblxuLy8gRXJyb3IgY29kZSB0byByZXR1cm4gd2hlbiB1c2VyIGRpc2Nvbm5lY3RlZCBmcm9tIHNlcnZlci5cbmNvbnN0IE5FVFdPUktfVVNFUiA9IDQxODtcbmNvbnN0IE5FVFdPUktfVVNFUl9URVhUID0gXCJEaXNjb25uZWN0ZWQgYnkgY2xpZW50XCI7XG5cbi8vIFV0aWxpdHkgZnVuY3Rpb25zXG5cbi8vIEFkZCBicm93ZXIgbWlzc2luZyBmdW5jdGlvbiBmb3Igbm9uIGJyb3dzZXIgYXBwLCBlZyBub2RlSnNcbmZ1bmN0aW9uIGluaXRGb3JOb25Ccm93c2VyQXBwKCkge1xuICAvLyBUaW5vZGUgcmVxdWlyZW1lbnQgaW4gbmF0aXZlIG1vZGUgYmVjYXVzZSByZWFjdCBuYXRpdmUgZG9lc24ndCBwcm92aWRlIEJhc2U2NCBtZXRob2RcbiAgY29uc3QgY2hhcnMgPSAnQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLz0nO1xuXG4gIGlmICh0eXBlb2YgYnRvYSA9PSAndW5kZWZpbmVkJykge1xuICAgIGNvbnN0IGNoYXJzID0gJ0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky89JztcbiAgICBnbG9iYWwuYnRvYSA9IGZ1bmN0aW9uKGlucHV0ID0gJycpIHtcbiAgICAgIGxldCBzdHIgPSBpbnB1dDtcbiAgICAgIGxldCBvdXRwdXQgPSAnJztcblxuICAgICAgZm9yIChsZXQgYmxvY2sgPSAwLCBjaGFyQ29kZSwgaSA9IDAsIG1hcCA9IGNoYXJzOyBzdHIuY2hhckF0KGkgfCAwKSB8fCAobWFwID0gJz0nLCBpICUgMSk7IG91dHB1dCArPSBtYXAuY2hhckF0KDYzICYgYmxvY2sgPj4gOCAtIGkgJSAxICogOCkpIHtcblxuICAgICAgICBjaGFyQ29kZSA9IHN0ci5jaGFyQ29kZUF0KGkgKz0gMyAvIDQpO1xuXG4gICAgICAgIGlmIChjaGFyQ29kZSA+IDB4RkYpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCInYnRvYScgZmFpbGVkOiBUaGUgc3RyaW5nIHRvIGJlIGVuY29kZWQgY29udGFpbnMgY2hhcmFjdGVycyBvdXRzaWRlIG9mIHRoZSBMYXRpbjEgcmFuZ2UuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgYmxvY2sgPSBibG9jayA8PCA4IHwgY2hhckNvZGU7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgfTtcbiAgfVxuXG4gIGlmICh0eXBlb2YgYXRvYiA9PSAndW5kZWZpbmVkJykge1xuICAgIGdsb2JhbC5hdG9iID0gZnVuY3Rpb24oaW5wdXQgPSAnJykge1xuICAgICAgbGV0IHN0ciA9IGlucHV0LnJlcGxhY2UoLz0rJC8sICcnKTtcbiAgICAgIGxldCBvdXRwdXQgPSAnJztcblxuICAgICAgaWYgKHN0ci5sZW5ndGggJSA0ID09IDEpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiJ2F0b2InIGZhaWxlZDogVGhlIHN0cmluZyB0byBiZSBkZWNvZGVkIGlzIG5vdCBjb3JyZWN0bHkgZW5jb2RlZC5cIik7XG4gICAgICB9XG4gICAgICBmb3IgKGxldCBiYyA9IDAsIGJzID0gMCwgYnVmZmVyLCBpID0gMDsgYnVmZmVyID0gc3RyLmNoYXJBdChpKyspO1xuXG4gICAgICAgIH5idWZmZXIgJiYgKGJzID0gYmMgJSA0ID8gYnMgKiA2NCArIGJ1ZmZlciA6IGJ1ZmZlcixcbiAgICAgICAgICBiYysrICUgNCkgPyBvdXRwdXQgKz0gU3RyaW5nLmZyb21DaGFyQ29kZSgyNTUgJiBicyA+PiAoLTIgKiBiYyAmIDYpKSA6IDBcbiAgICAgICkge1xuICAgICAgICBidWZmZXIgPSBjaGFycy5pbmRleE9mKGJ1ZmZlcik7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgfTtcbiAgfVxuXG4gIGlmICh0eXBlb2Ygd2luZG93ID09ICd1bmRlZmluZWQnKSB7XG4gICAgZ2xvYmFsLndpbmRvdyA9IHtcbiAgICAgIFdlYlNvY2tldDogV2ViU29ja2V0UHJvdmlkZXIsXG4gICAgICBVUkw6IHtcbiAgICAgICAgY3JlYXRlT2JqZWN0VVJMOiBmdW5jdGlvbigpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJVbmFibGUgdG8gdXNlIHdpbmRvdy5VUkwgaW4gYSBub24gYnJvd3NlciBhcHBsaWNhdGlvblwiKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vLyBSRkMzMzM5IGZvcm1hdGVyIG9mIERhdGVcbmZ1bmN0aW9uIHJmYzMzMzlEYXRlU3RyaW5nKGQpIHtcbiAgaWYgKCFkIHx8IGQuZ2V0VGltZSgpID09IDApIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgZnVuY3Rpb24gcGFkKHZhbCwgc3ApIHtcbiAgICBzcCA9IHNwIHx8IDI7XG4gICAgcmV0dXJuICcwJy5yZXBlYXQoc3AgLSAoJycgKyB2YWwpLmxlbmd0aCkgKyB2YWw7XG4gIH1cblxuICBjb25zdCBtaWxsaXMgPSBkLmdldFVUQ01pbGxpc2Vjb25kcygpO1xuICByZXR1cm4gZC5nZXRVVENGdWxsWWVhcigpICsgJy0nICsgcGFkKGQuZ2V0VVRDTW9udGgoKSArIDEpICsgJy0nICsgcGFkKGQuZ2V0VVRDRGF0ZSgpKSArXG4gICAgJ1QnICsgcGFkKGQuZ2V0VVRDSG91cnMoKSkgKyAnOicgKyBwYWQoZC5nZXRVVENNaW51dGVzKCkpICsgJzonICsgcGFkKGQuZ2V0VVRDU2Vjb25kcygpKSArXG4gICAgKG1pbGxpcyA/ICcuJyArIHBhZChtaWxsaXMsIDMpIDogJycpICsgJ1onO1xufVxuXG4vLyBidG9hIHJlcGxhY2VtZW50LiBTdG9jayBidG9hIGZhaWxzIG9uIG9uIG5vbi1MYXRpbjEgc3RyaW5ncy5cbmZ1bmN0aW9uIGI2NEVuY29kZVVuaWNvZGUoc3RyKSB7XG4gIC8vIFRoZSBlbmNvZGVVUklDb21wb25lbnQgcGVyY2VudC1lbmNvZGVzIFVURi04IHN0cmluZyxcbiAgLy8gdGhlbiB0aGUgcGVyY2VudCBlbmNvZGluZyBpcyBjb252ZXJ0ZWQgaW50byByYXcgYnl0ZXMgd2hpY2hcbiAgLy8gY2FuIGJlIGZlZCBpbnRvIGJ0b2EuXG4gIHJldHVybiBidG9hKGVuY29kZVVSSUNvbXBvbmVudChzdHIpLnJlcGxhY2UoLyUoWzAtOUEtRl17Mn0pL2csXG4gICAgZnVuY3Rpb24gdG9Tb2xpZEJ5dGVzKG1hdGNoLCBwMSkge1xuICAgICAgcmV0dXJuIFN0cmluZy5mcm9tQ2hhckNvZGUoJzB4JyArIHAxKTtcbiAgICB9KSk7XG59XG5cbi8vIFJlY3Vyc2l2ZWx5IG1lcmdlIHNyYydzIG93biBwcm9wZXJ0aWVzIHRvIGRzdC5cbi8vIElnbm9yZSBwcm9wZXJ0aWVzIHdoZXJlIGlnbm9yZVtwcm9wZXJ0eV0gaXMgdHJ1ZS5cbi8vIEFycmF5IGFuZCBEYXRlIG9iamVjdHMgYXJlIHNoYWxsb3ctY29waWVkLlxuZnVuY3Rpb24gbWVyZ2VPYmooZHN0LCBzcmMsIGlnbm9yZSkge1xuICBpZiAodHlwZW9mIHNyYyAhPSAnb2JqZWN0Jykge1xuICAgIGlmIChzcmMgPT09IFRpbm9kZS5ERUxfQ0hBUikge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgaWYgKHNyYyA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gZHN0O1xuICAgIH1cbiAgICByZXR1cm4gc3JjO1xuICB9XG4gIC8vIEpTIGlzIGNyYXp5OiB0eXBlb2YgbnVsbCBpcyAnb2JqZWN0Jy5cbiAgaWYgKHNyYyA9PT0gbnVsbCkge1xuICAgIHJldHVybiBzcmM7XG4gIH1cblxuICAvLyBIYW5kbGUgRGF0ZVxuICBpZiAoc3JjIGluc3RhbmNlb2YgRGF0ZSkge1xuICAgIHJldHVybiAoIWRzdCB8fCAhKGRzdCBpbnN0YW5jZW9mIERhdGUpIHx8IGRzdCA8IHNyYykgPyBzcmMgOiBkc3Q7XG4gIH1cblxuICAvLyBBY2Nlc3MgbW9kZVxuICBpZiAoc3JjIGluc3RhbmNlb2YgQWNjZXNzTW9kZSkge1xuICAgIHJldHVybiBuZXcgQWNjZXNzTW9kZShzcmMpO1xuICB9XG5cbiAgLy8gSGFuZGxlIEFycmF5XG4gIGlmIChzcmMgaW5zdGFuY2VvZiBBcnJheSkge1xuICAgIHJldHVybiBzcmM7XG4gIH1cblxuICBpZiAoIWRzdCB8fCBkc3QgPT09IFRpbm9kZS5ERUxfQ0hBUikge1xuICAgIGRzdCA9IHNyYy5jb25zdHJ1Y3RvcigpO1xuICB9XG5cbiAgZm9yIChsZXQgcHJvcCBpbiBzcmMpIHtcbiAgICBpZiAoc3JjLmhhc093blByb3BlcnR5KHByb3ApICYmXG4gICAgICAoIWlnbm9yZSB8fCAhaWdub3JlW3Byb3BdKSAmJlxuICAgICAgKHByb3AgIT0gJ19ub0ZvcndhcmRpbmcnKSkge1xuXG4gICAgICBkc3RbcHJvcF0gPSBtZXJnZU9iaihkc3RbcHJvcF0sIHNyY1twcm9wXSk7XG4gICAgfVxuICB9XG4gIHJldHVybiBkc3Q7XG59XG5cbi8vIFVwZGF0ZSBvYmplY3Qgc3RvcmVkIGluIGEgY2FjaGUuIFJldHVybnMgdXBkYXRlZCB2YWx1ZS5cbmZ1bmN0aW9uIG1lcmdlVG9DYWNoZShjYWNoZSwga2V5LCBuZXd2YWwsIGlnbm9yZSkge1xuICBjYWNoZVtrZXldID0gbWVyZ2VPYmooY2FjaGVba2V5XSwgbmV3dmFsLCBpZ25vcmUpO1xuICByZXR1cm4gY2FjaGVba2V5XTtcbn1cblxuLy8gQmFzaWMgY3Jvc3MtZG9tYWluIHJlcXVlc3Rlci4gU3VwcG9ydHMgbm9ybWFsIGJyb3dzZXJzIGFuZCBJRTgrXG5mdW5jdGlvbiB4ZHJlcSgpIHtcbiAgbGV0IHhkcmVxID0gbnVsbDtcblxuICAvLyBEZXRlY3QgYnJvd3NlciBzdXBwb3J0IGZvciBDT1JTXG4gIGlmICgnd2l0aENyZWRlbnRpYWxzJyBpbiBuZXcgWE1MSHR0cFJlcXVlc3QoKSkge1xuICAgIC8vIFN1cHBvcnQgZm9yIHN0YW5kYXJkIGNyb3NzLWRvbWFpbiByZXF1ZXN0c1xuICAgIHhkcmVxID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7XG4gIH0gZWxzZSBpZiAodHlwZW9mIFhEb21haW5SZXF1ZXN0ICE9ICd1bmRlZmluZWQnKSB7XG4gICAgLy8gSUUtc3BlY2lmaWMgXCJDT1JTXCIgd2l0aCBYRFJcbiAgICB4ZHJlcSA9IG5ldyBYRG9tYWluUmVxdWVzdCgpO1xuICB9IGVsc2Uge1xuICAgIC8vIEJyb3dzZXIgd2l0aG91dCBDT1JTIHN1cHBvcnQsIGRvbid0IGtub3cgaG93IHRvIGhhbmRsZVxuICAgIHRocm93IG5ldyBFcnJvcihcIkJyb3dzZXIgbm90IHN1cHBvcnRlZFwiKTtcbiAgfVxuXG4gIHJldHVybiB4ZHJlcTtcbn07XG5cbi8vIEpTT04gc3RyaW5naWZ5IGhlbHBlciAtIHByZS1wcm9jZXNzb3IgZm9yIEpTT04uc3RyaW5naWZ5XG5mdW5jdGlvbiBqc29uQnVpbGRIZWxwZXIoa2V5LCB2YWwpIHtcbiAgaWYgKHZhbCBpbnN0YW5jZW9mIERhdGUpIHtcbiAgICAvLyBDb252ZXJ0IGphdmFzY3JpcHQgRGF0ZSBvYmplY3RzIHRvIHJmYzMzMzkgc3RyaW5nc1xuICAgIHZhbCA9IHJmYzMzMzlEYXRlU3RyaW5nKHZhbCk7XG4gIH0gZWxzZSBpZiAodmFsIGluc3RhbmNlb2YgQWNjZXNzTW9kZSkge1xuICAgIHZhbCA9IHZhbC5qc29uSGVscGVyKCk7XG4gIH0gZWxzZSBpZiAodmFsID09PSB1bmRlZmluZWQgfHwgdmFsID09PSBudWxsIHx8IHZhbCA9PT0gZmFsc2UgfHxcbiAgICAoQXJyYXkuaXNBcnJheSh2YWwpICYmIHZhbC5sZW5ndGggPT0gMCkgfHxcbiAgICAoKHR5cGVvZiB2YWwgPT0gJ29iamVjdCcpICYmIChPYmplY3Qua2V5cyh2YWwpLmxlbmd0aCA9PSAwKSkpIHtcbiAgICAvLyBzdHJpcCBvdXQgZW1wdHkgZWxlbWVudHMgd2hpbGUgc2VyaWFsaXppbmcgb2JqZWN0cyB0byBKU09OXG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHJldHVybiB2YWw7XG59O1xuXG4vLyBTdHJpcHMgYWxsIHZhbHVlcyBmcm9tIGFuIG9iamVjdCBvZiB0aGV5IGV2YWx1YXRlIHRvIGZhbHNlIG9yIGlmIHRoZWlyIG5hbWUgc3RhcnRzIHdpdGggJ18nLlxuZnVuY3Rpb24gc2ltcGxpZnkob2JqKSB7XG4gIE9iamVjdC5rZXlzKG9iaikuZm9yRWFjaChmdW5jdGlvbihrZXkpIHtcbiAgICBpZiAoa2V5WzBdID09ICdfJykge1xuICAgICAgLy8gU3RyaXAgZmllbGRzIGxpa2UgXCJvYmouX2tleVwiLlxuICAgICAgZGVsZXRlIG9ialtrZXldO1xuICAgIH0gZWxzZSBpZiAoIW9ialtrZXldKSB7XG4gICAgICAvLyBTdHJpcCBmaWVsZHMgd2hpY2ggZXZhbHVhdGUgdG8gZmFsc2UuXG4gICAgICBkZWxldGUgb2JqW2tleV07XG4gICAgfSBlbHNlIGlmIChBcnJheS5pc0FycmF5KG9ialtrZXldKSAmJiBvYmpba2V5XS5sZW5ndGggPT0gMCkge1xuICAgICAgLy8gU3RyaXAgZW1wdHkgYXJyYXlzLlxuICAgICAgZGVsZXRlIG9ialtrZXldO1xuICAgIH0gZWxzZSBpZiAoIW9ialtrZXldKSB7XG4gICAgICAvLyBTdHJpcCBmaWVsZHMgd2hpY2ggZXZhbHVhdGUgdG8gZmFsc2UuXG4gICAgICBkZWxldGUgb2JqW2tleV07XG4gICAgfSBlbHNlIGlmICh0eXBlb2Ygb2JqW2tleV0gPT0gJ29iamVjdCcgJiYgIShvYmpba2V5XSBpbnN0YW5jZW9mIERhdGUpKSB7XG4gICAgICBzaW1wbGlmeShvYmpba2V5XSk7XG4gICAgICAvLyBTdHJpcCBlbXB0eSBvYmplY3RzLlxuICAgICAgaWYgKE9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKG9ialtrZXldKS5sZW5ndGggPT0gMCkge1xuICAgICAgICBkZWxldGUgb2JqW2tleV07XG4gICAgICB9XG4gICAgfVxuICB9KTtcbiAgcmV0dXJuIG9iajtcbn07XG5cbi8vIFRyaW0gd2hpdGVzcGFjZSwgc3RyaXAgZW1wdHkgYW5kIGR1cGxpY2F0ZSBlbGVtZW50cyBlbGVtZW50cy5cbi8vIElmIHRoZSByZXN1bHQgaXMgYW4gZW1wdHkgYXJyYXksIGFkZCBhIHNpbmdsZSBlbGVtZW50IFwiXFx1MjQyMVwiIChVbmljb2RlIERlbCBjaGFyYWN0ZXIpLlxuZnVuY3Rpb24gbm9ybWFsaXplQXJyYXkoYXJyKSB7XG4gIGxldCBvdXQgPSBbXTtcbiAgaWYgKEFycmF5LmlzQXJyYXkoYXJyKSkge1xuICAgIC8vIFRyaW0sIHRocm93IGF3YXkgdmVyeSBzaG9ydCBhbmQgZW1wdHkgdGFncy5cbiAgICBmb3IgKGxldCBpID0gMCwgbCA9IGFyci5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgIGxldCB0ID0gYXJyW2ldO1xuICAgICAgaWYgKHQpIHtcbiAgICAgICAgdCA9IHQudHJpbSgpLnRvTG93ZXJDYXNlKCk7XG4gICAgICAgIGlmICh0Lmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICBvdXQucHVzaCh0KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBvdXQuc29ydCgpLmZpbHRlcihmdW5jdGlvbihpdGVtLCBwb3MsIGFyeSkge1xuICAgICAgcmV0dXJuICFwb3MgfHwgaXRlbSAhPSBhcnlbcG9zIC0gMV07XG4gICAgfSk7XG4gIH1cbiAgaWYgKG91dC5sZW5ndGggPT0gMCkge1xuICAgIC8vIEFkZCBzaW5nbGUgdGFnIHdpdGggYSBVbmljb2RlIERlbCBjaGFyYWN0ZXIsIG90aGVyd2lzZSBhbiBhbXB0eSBhcnJheVxuICAgIC8vIGlzIGFtYmlndW9zLiBUaGUgRGVsIHRhZyB3aWxsIGJlIHN0cmlwcGVkIGJ5IHRoZSBzZXJ2ZXIuXG4gICAgb3V0LnB1c2goVGlub2RlLkRFTF9DSEFSKTtcbiAgfVxuICByZXR1cm4gb3V0O1xufVxuXG4vLyBBdHRlbXB0IHRvIGNvbnZlcnQgZGF0ZSBzdHJpbmdzIHRvIG9iamVjdHMuXG5mdW5jdGlvbiBqc29uUGFyc2VIZWxwZXIoa2V5LCB2YWwpIHtcbiAgLy8gQ29udmVydCBzdHJpbmcgdGltZXN0YW1wcyB3aXRoIG9wdGlvbmFsIG1pbGxpc2Vjb25kcyB0byBEYXRlXG4gIC8vIDIwMTUtMDktMDJUMDE6NDU6NDNbLjEyM11aXG4gIGlmIChrZXkgPT09ICd0cycgJiYgdHlwZW9mIHZhbCA9PT0gJ3N0cmluZycgJiZcbiAgICB2YWwubGVuZ3RoID49IDIwICYmIHZhbC5sZW5ndGggPD0gMjQpIHtcbiAgICBsZXQgZGF0ZSA9IG5ldyBEYXRlKHZhbCk7XG4gICAgaWYgKGRhdGUpIHtcbiAgICAgIHJldHVybiBkYXRlO1xuICAgIH1cbiAgfSBlbHNlIGlmIChrZXkgPT09ICdhY3MnICYmIHR5cGVvZiB2YWwgPT09ICdvYmplY3QnKSB7XG4gICAgcmV0dXJuIG5ldyBBY2Nlc3NNb2RlKHZhbCk7XG4gIH1cbiAgcmV0dXJuIHZhbDtcbn07XG5cbi8vIFRyaW1zIHZlcnkgbG9uZyBzdHJpbmdzIChlbmNvZGVkIGltYWdlcykgdG8gbWFrZSBsb2dnZWQgcGFja2V0cyBtb3JlIHJlYWRhYmxlLlxuZnVuY3Rpb24ganNvbkxvZ2dlckhlbHBlcihrZXksIHZhbCkge1xuICBpZiAodHlwZW9mIHZhbCA9PSAnc3RyaW5nJyAmJiB2YWwubGVuZ3RoID4gMTI4KSB7XG4gICAgcmV0dXJuICc8JyArIHZhbC5sZW5ndGggKyAnLCBieXRlczogJyArIHZhbC5zdWJzdHJpbmcoMCwgMTIpICsgJy4uLicgKyB2YWwuc3Vic3RyaW5nKHZhbC5sZW5ndGggLSAxMikgKyAnPic7XG4gIH1cbiAgcmV0dXJuIGpzb25CdWlsZEhlbHBlcihrZXksIHZhbCk7XG59O1xuXG4vLyBQYXJzZSBicm93c2VyIHVzZXIgYWdlbnQgdG8gZXh0cmFjdCBicm93c2VyIG5hbWUgYW5kIHZlcnNpb24uXG5mdW5jdGlvbiBnZXRCcm93c2VySW5mbyh1YSwgcHJvZHVjdCkge1xuICB1YSA9IHVhIHx8ICcnO1xuICBsZXQgcmVhY3RuYXRpdmUgPSAnJztcbiAgLy8gQ2hlY2sgaWYgdGhpcyBpcyBhIFJlYWN0TmF0aXZlIGFwcC5cbiAgaWYgKC9yZWFjdG5hdGl2ZS9pLnRlc3QocHJvZHVjdCkpIHtcbiAgICByZWFjdG5hdGl2ZSA9ICdSZWFjdE5hdGl2ZTsgJztcbiAgfVxuICAvLyBUaGVuIHRlc3QgZm9yIFdlYktpdCBiYXNlZCBicm93c2VyLlxuICB1YSA9IHVhLnJlcGxhY2UoJyAoS0hUTUwsIGxpa2UgR2Vja28pJywgJycpO1xuICBsZXQgbSA9IHVhLm1hdGNoKC8oQXBwbGVXZWJLaXRcXC9bLlxcZF0rKS9pKTtcbiAgbGV0IHJlc3VsdDtcbiAgaWYgKG0pIHtcbiAgICAvLyBMaXN0IG9mIGNvbW1vbiBzdHJpbmdzLCBmcm9tIG1vcmUgdXNlZnVsIHRvIGxlc3MgdXNlZnVsLlxuICAgIGxldCBwcmlvcml0eSA9IFsnY2hyb21lJywgJ3NhZmFyaScsICdtb2JpbGUnLCAndmVyc2lvbiddO1xuICAgIGxldCB0bXAgPSB1YS5zdWJzdHIobS5pbmRleCArIG1bMF0ubGVuZ3RoKS5zcGxpdChcIiBcIik7XG4gICAgbGV0IHRva2VucyA9IFtdO1xuICAgIC8vIFNwbGl0IE5hbWUvMC4wLjAgaW50byBOYW1lIGFuZCB2ZXJzaW9uIDAuMC4wXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0bXAubGVuZ3RoOyBpKyspIHtcbiAgICAgIGxldCBtMiA9IC8oW1xcdy5dKylbXFwvXShbXFwuXFxkXSspLy5leGVjKHRtcFtpXSk7XG4gICAgICBpZiAobTIpIHtcbiAgICAgICAgdG9rZW5zLnB1c2goW20yWzFdLCBtMlsyXSwgcHJpb3JpdHkuZmluZEluZGV4KGZ1bmN0aW9uKGUpIHtcbiAgICAgICAgICByZXR1cm4gKGUgPT0gbTJbMV0udG9Mb3dlckNhc2UoKSk7XG4gICAgICAgIH0pXSk7XG4gICAgICB9XG4gICAgfVxuICAgIC8vIFNvcnQgYnkgcHJpb3JpdHk6IG1vcmUgaW50ZXJlc3RpbmcgaXMgZWFybGllciB0aGFuIGxlc3MgaW50ZXJlc3RpbmcuXG4gICAgdG9rZW5zLnNvcnQoZnVuY3Rpb24oYSwgYikge1xuICAgICAgbGV0IGRpZmYgPSBhWzJdIC0gYlsyXTtcbiAgICAgIHJldHVybiBkaWZmICE9IDAgPyBkaWZmIDogYlswXS5sZW5ndGggLSBhWzBdLmxlbmd0aDtcbiAgICB9KTtcbiAgICBpZiAodG9rZW5zLmxlbmd0aCA+IDApIHtcbiAgICAgIC8vIFJldHVybiB0aGUgbGVhc3QgY29tbW9uIGJyb3dzZXIgc3RyaW5nIGFuZCB2ZXJzaW9uLlxuICAgICAgcmVzdWx0ID0gdG9rZW5zWzBdWzBdICsgJy8nICsgdG9rZW5zWzBdWzFdO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBGYWlsZWQgdG8gSUQgdGhlIGJyb3dzZXIuIFJldHVybiB0aGUgd2Via2l0IHZlcnNpb24uXG4gICAgICByZXN1bHQgPSBtWzFdO1xuICAgIH1cbiAgICAvLyBUZXN0IGZvciBNU0lFLlxuICB9IGVsc2UgaWYgKC90cmlkZW50L2kudGVzdCh1YSkpIHtcbiAgICBtID0gLyg/OlxcYnJ2WyA6XSsoWy5cXGRdKykpfCg/OlxcYk1TSUUgKFsuXFxkXSspKS9nLmV4ZWModWEpO1xuICAgIGlmIChtKSB7XG4gICAgICByZXN1bHQgPSAnTVNJRS8nICsgKG1bMV0gfHwgbVsyXSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJlc3VsdCA9ICdNU0lFLz8nO1xuICAgIH1cbiAgICAvLyBUZXN0IGZvciBGaXJlZm94LlxuICB9IGVsc2UgaWYgKC9maXJlZm94L2kudGVzdCh1YSkpIHtcbiAgICBtID0gL0ZpcmVmb3hcXC8oWy5cXGRdKykvZy5leGVjKHVhKTtcbiAgICBpZiAobSkge1xuICAgICAgcmVzdWx0ID0gJ0ZpcmVmb3gvJyArIG1bMV07XG4gICAgfSBlbHNlIHtcbiAgICAgIHJlc3VsdCA9ICdGaXJlZm94Lz8nO1xuICAgIH1cbiAgICAvLyBPbGRlciBPcGVyYS5cbiAgfSBlbHNlIGlmICgvcHJlc3RvL2kudGVzdCh1YSkpIHtcbiAgICBtID0gL09wZXJhXFwvKFsuXFxkXSspL2cuZXhlYyh1YSk7XG4gICAgaWYgKG0pIHtcbiAgICAgIHJlc3VsdCA9ICdPcGVyYS8nICsgbVsxXTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmVzdWx0ID0gJ09wZXJhLz8nO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICAvLyBGYWlsZWQgdG8gcGFyc2UgYW55dGhpbmcgbWVhbmluZ2Z1bGwuIFRyeSB0aGUgbGFzdCByZXNvcnQuXG4gICAgbSA9IC8oW1xcdy5dKylcXC8oWy5cXGRdKykvLmV4ZWModWEpO1xuICAgIGlmIChtKSB7XG4gICAgICByZXN1bHQgPSBtWzFdICsgJy8nICsgbVsyXTtcbiAgICB9IGVsc2Uge1xuICAgICAgbSA9IHVhLnNwbGl0KCcgJyk7XG4gICAgICByZXN1bHQgPSBtWzBdO1xuICAgIH1cbiAgfVxuXG4gIC8vIFNob3J0ZW4gdGhlIHZlcnNpb24gdG8gb25lIGRvdCAnYS5iYi5jY2MuZCAtPiBhLmJiJyBhdCBtb3N0LlxuICBtID0gcmVzdWx0LnNwbGl0KCcvJyk7XG4gIGlmIChtLmxlbmd0aCA+IDEpIHtcbiAgICBsZXQgdiA9IG1bMV0uc3BsaXQoJy4nKTtcbiAgICByZXN1bHQgPSBtWzBdICsgJy8nICsgdlswXSArICh2WzFdID8gJy4nICsgdlsxXSA6ICcnKTtcbiAgfVxuICByZXR1cm4gcmVhY3RuYXRpdmUgKyByZXN1bHQ7XG59XG5cbi8qKlxuICogSW4tbWVtb3J5IHNvcnRlZCBjYWNoZSBvZiBvYmplY3RzLlxuICpcbiAqIEBjbGFzcyBDQnVmZmVyXG4gKiBAbWVtYmVyb2YgVGlub2RlXG4gKiBAcHJvdGVjdGVkXG4gKlxuICogQHBhcmFtIHtmdW5jdGlvbn0gY29tcGFyZSBjdXN0b20gY29tcGFyYXRvciBvZiBvYmplY3RzLiBSZXR1cm5zIC0xIGlmIGEgPCBiLCAwIGlmIGEgPT0gYiwgMSBvdGhlcndpc2UuXG4gKi9cbnZhciBDQnVmZmVyID0gZnVuY3Rpb24oY29tcGFyZSkge1xuICBsZXQgYnVmZmVyID0gW107XG5cbiAgY29tcGFyZSA9IGNvbXBhcmUgfHwgZnVuY3Rpb24oYSwgYikge1xuICAgIHJldHVybiBhID09PSBiID8gMCA6IGEgPCBiID8gLTEgOiAxO1xuICB9O1xuXG4gIGZ1bmN0aW9uIGZpbmROZWFyZXN0KGVsZW0sIGFyciwgZXhhY3QpIHtcbiAgICBsZXQgc3RhcnQgPSAwO1xuICAgIGxldCBlbmQgPSBhcnIubGVuZ3RoIC0gMTtcbiAgICBsZXQgcGl2b3QgPSAwO1xuICAgIGxldCBkaWZmID0gMDtcbiAgICBsZXQgZm91bmQgPSBmYWxzZTtcblxuICAgIHdoaWxlIChzdGFydCA8PSBlbmQpIHtcbiAgICAgIHBpdm90ID0gKHN0YXJ0ICsgZW5kKSAvIDIgfCAwO1xuICAgICAgZGlmZiA9IGNvbXBhcmUoYXJyW3Bpdm90XSwgZWxlbSk7XG4gICAgICBpZiAoZGlmZiA8IDApIHtcbiAgICAgICAgc3RhcnQgPSBwaXZvdCArIDE7XG4gICAgICB9IGVsc2UgaWYgKGRpZmYgPiAwKSB7XG4gICAgICAgIGVuZCA9IHBpdm90IC0gMTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZvdW5kID0gdHJ1ZTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChmb3VuZCkge1xuICAgICAgcmV0dXJuIHBpdm90O1xuICAgIH1cbiAgICBpZiAoZXhhY3QpIHtcbiAgICAgIHJldHVybiAtMTtcbiAgICB9XG4gICAgLy8gTm90IGV4YWN0IC0gaW5zZXJ0aW9uIHBvaW50XG4gICAgcmV0dXJuIGRpZmYgPCAwID8gcGl2b3QgKyAxIDogcGl2b3Q7XG4gIH1cblxuICAvLyBJbnNlcnQgZWxlbWVudCBpbnRvIGEgc29ydGVkIGFycmF5LlxuICBmdW5jdGlvbiBpbnNlcnRTb3J0ZWQoZWxlbSwgYXJyKSB7XG4gICAgYXJyLnNwbGljZShmaW5kTmVhcmVzdChlbGVtLCBhcnIsIGZhbHNlKSwgMCwgZWxlbSk7XG4gICAgcmV0dXJuIGFycjtcbiAgfVxuXG4gIHJldHVybiB7XG4gICAgLyoqXG4gICAgICogR2V0IGFuIGVsZW1lbnQgYXQgdGhlIGdpdmVuIHBvc2l0aW9uLlxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ0J1ZmZlciNcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gYXQgLSBQb3NpdGlvbiB0byBmZXRjaCBmcm9tLlxuICAgICAqIEByZXR1cm5zIHtPYmplY3R9IEVsZW1lbnQgYXQgdGhlIGdpdmVuIHBvc2l0aW9uIG9yIDx0dD51bmRlZmluZWQ8L3R0PlxuICAgICAqL1xuICAgIGdldEF0OiBmdW5jdGlvbihhdCkge1xuICAgICAgcmV0dXJuIGJ1ZmZlclthdF07XG4gICAgfSxcblxuICAgIC8qKiBBZGQgbmV3IGVsZW1lbnQocykgdG8gdGhlIGJ1ZmZlci4gVmFyaWFkaWM6IHRha2VzIG9uZSBvciBtb3JlIGFyZ3VtZW50cy4gSWYgYW4gYXJyYXkgaXMgcGFzc2VkIGFzIGEgc2luZ2xlXG4gICAgICogYXJndW1lbnQsIGl0cyBlbGVtZW50cyBhcmUgaW5zZXJ0ZWQgaW5kaXZpZHVhbGx5LlxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ0J1ZmZlciNcbiAgICAgKlxuICAgICAqIEBwYXJhbSB7Li4uT2JqZWN0fEFycmF5fSAtIE9uZSBvciBtb3JlIG9iamVjdHMgdG8gaW5zZXJ0LlxuICAgICAqL1xuICAgIHB1dDogZnVuY3Rpb24oKSB7XG4gICAgICBsZXQgaW5zZXJ0O1xuICAgICAgLy8gaW5zcGVjdCBhcmd1bWVudHM6IGlmIGFycmF5LCBpbnNlcnQgaXRzIGVsZW1lbnRzLCBpZiBvbmUgb3IgbW9yZSBub24tYXJyYXkgYXJndW1lbnRzLCBpbnNlcnQgdGhlbSBvbmUgYnkgb25lXG4gICAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PSAxICYmIEFycmF5LmlzQXJyYXkoYXJndW1lbnRzWzBdKSkge1xuICAgICAgICBpbnNlcnQgPSBhcmd1bWVudHNbMF07XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpbnNlcnQgPSBhcmd1bWVudHM7XG4gICAgICB9XG4gICAgICBmb3IgKGxldCBpZHggaW4gaW5zZXJ0KSB7XG4gICAgICAgIGluc2VydFNvcnRlZChpbnNlcnRbaWR4XSwgYnVmZmVyKTtcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlIGVsZW1lbnQgYXQgdGhlIGdpdmVuIHBvc2l0aW9uLlxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ0J1ZmZlciNcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gYXQgLSBQb3NpdGlvbiB0byBkZWxldGUgYXQuXG4gICAgICogQHJldHVybnMge09iamVjdH0gRWxlbWVudCBhdCB0aGUgZ2l2ZW4gcG9zaXRpb24gb3IgPHR0PnVuZGVmaW5lZDwvdHQ+XG4gICAgICovXG4gICAgZGVsQXQ6IGZ1bmN0aW9uKGF0KSB7XG4gICAgICBsZXQgciA9IGJ1ZmZlci5zcGxpY2UoYXQsIDEpO1xuICAgICAgaWYgKHIgJiYgci5sZW5ndGggPiAwKSB7XG4gICAgICAgIHJldHVybiByWzBdO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlIGVsZW1lbnRzIGJldHdlZW4gdHdvIHBvc2l0aW9ucy5cbiAgICAgKiBAbWVtYmVyb2YgVGlub2RlLkNCdWZmZXIjXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IHNpbmNlIC0gUG9zaXRpb24gdG8gZGVsZXRlIGZyb20gKGluY2x1c2l2ZSkuXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IGJlZm9yZSAtIFBvc2l0aW9uIHRvIGRlbGV0ZSB0byAoZXhjbHVzaXZlKS5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIHtBcnJheX0gYXJyYXkgb2YgcmVtb3ZlZCBlbGVtZW50cyAoY291bGQgYmUgemVybyBsZW5ndGgpLlxuICAgICAqL1xuICAgIGRlbFJhbmdlOiBmdW5jdGlvbihzaW5jZSwgYmVmb3JlKSB7XG4gICAgICByZXR1cm4gYnVmZmVyLnNwbGljZShzaW5jZSwgYmVmb3JlIC0gc2luY2UpO1xuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm4gdGhlIG1heGltdW0gbnVtYmVyIG9mIGVsZW1lbnQgdGhlIGJ1ZmZlciBjYW4gaG9sZFxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ0J1ZmZlciNcbiAgICAgKiBAcmV0dXJuIHtudW1iZXJ9IFRoZSBzaXplIG9mIHRoZSBidWZmZXIuXG4gICAgICovXG4gICAgc2l6ZTogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gYnVmZmVyLmxlbmd0aDtcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogRGlzY2FyZCBhbGwgZWxlbWVudHMgYW5kIHJlc2V0IHRoZSBidWZmZXIgdG8gdGhlIG5ldyBzaXplIChtYXhpbXVtIG51bWJlciBvZiBlbGVtZW50cykuXG4gICAgICogQG1lbWJlcm9mIFRpbm9kZS5DQnVmZmVyI1xuICAgICAqIEBwYXJhbSB7bnVtYmVyfSBuZXdTaXplIC0gTmV3IHNpemUgb2YgdGhlIGJ1ZmZlci5cbiAgICAgKi9cbiAgICByZXNldDogZnVuY3Rpb24obmV3U2l6ZSkge1xuICAgICAgYnVmZmVyID0gW107XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIENhbGxiYWNrIGZvciBpdGVyYXRpbmcgY29udGVudHMgb2YgYnVmZmVyLiBTZWUge0BsaW5rIFRpbm9kZS5DQnVmZmVyI2ZvckVhY2h9LlxuICAgICAqIEBjYWxsYmFjayBGb3JFYWNoQ2FsbGJhY2tUeXBlXG4gICAgICogQG1lbWJlcm9mIFRpbm9kZS5DQnVmZmVyI1xuICAgICAqIEBwYXJhbSB7T2JqZWN0fSBlbGVtIC0gRWxlbWVudCBvZiB0aGUgYnVmZmVyLlxuICAgICAqIEBwYXJhbSB7bnVtYmVyfSBpbmRleCAtIEluZGV4IG9mIHRoZSBjdXJyZW50IGVsZW1lbnQuXG4gICAgICovXG5cbiAgICAvKipcbiAgICAgKiBBcHBseSBnaXZlbiBmdW5jdGlvbiBgY2FsbGJhY2tgIHRvIGFsbCBlbGVtZW50cyBvZiB0aGUgYnVmZmVyLlxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ0J1ZmZlciNcbiAgICAgKlxuICAgICAqIEBwYXJhbSB7VGlub2RlLkZvckVhY2hDYWxsYmFja1R5cGV9IGNhbGxiYWNrIC0gRnVuY3Rpb24gdG8gY2FsbCBmb3IgZWFjaCBlbGVtZW50LlxuICAgICAqIEBwYXJhbSB7aW50ZWdlcn0gc3RhcnRJZHgtIE9wdGlvbmFsIGluZGV4IHRvIHN0YXJ0IGl0ZXJhdGluZyBmcm9tIChpbmNsdXNpdmUpLlxuICAgICAqIEBwYXJhbSB7aW50ZWdlcn0gYmVmb3JlSWR4IC0gT3B0aW9uYWwgaW5kZXggdG8gc3RvcCBpdGVyYXRpbmcgYmVmb3JlIChleGNsdXNpdmUpLlxuICAgICAqIEBwYXJhbSB7T2JqZWN0fSBjb250ZXh0IC0gY2FsbGluZyBjb250ZXh0IChpLmUuIHZhbHVlIG9mICd0aGlzJyBpbiBjYWxsYmFjaylcbiAgICAgKi9cbiAgICBmb3JFYWNoOiBmdW5jdGlvbihjYWxsYmFjaywgc3RhcnRJZHgsIGJlZm9yZUlkeCwgY29udGV4dCkge1xuICAgICAgc3RhcnRJZHggPSBzdGFydElkeCB8IDA7XG4gICAgICBiZWZvcmVJZHggPSBiZWZvcmVJZHggfHwgYnVmZmVyLmxlbmd0aDtcbiAgICAgIGZvciAobGV0IGkgPSBzdGFydElkeDsgaSA8IGJlZm9yZUlkeDsgaSsrKSB7XG4gICAgICAgIGNhbGxiYWNrLmNhbGwoY29udGV4dCwgYnVmZmVyW2ldLCBpKTtcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogRmluZCBlbGVtZW50IGluIGJ1ZmZlciB1c2luZyBidWZmZXIncyBjb21wYXJpc29uIGZ1bmN0aW9uLlxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ0J1ZmZlciNcbiAgICAgKlxuICAgICAqIEBwYXJhbSB7T2JqZWN0fSBlbGVtIC0gZWxlbWVudCB0byBmaW5kLlxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbj19IG5lYXJlc3QgLSB3aGVuIHRydWUgYW5kIGV4YWN0IG1hdGNoIGlzIG5vdCBmb3VuZCwgcmV0dXJuIHRoZSBuZWFyZXN0IGVsZW1lbnQgKGluc2VydGlvbiBwb2ludCkuXG4gICAgICogQHJldHVybnMge251bWJlcn0gaW5kZXggb2YgdGhlIGVsZW1lbnQgaW4gdGhlIGJ1ZmZlciBvciAtMS5cbiAgICAgKi9cbiAgICBmaW5kOiBmdW5jdGlvbihlbGVtLCBuZWFyZXN0KSB7XG4gICAgICByZXR1cm4gZmluZE5lYXJlc3QoZWxlbSwgYnVmZmVyLCAhbmVhcmVzdCk7XG4gICAgfVxuICB9XG59XG5cbi8vIEhlbHBlciBmdW5jdGlvbiBmb3IgY3JlYXRpbmcgYW4gZW5kcG9pbnQgVVJMXG5mdW5jdGlvbiBtYWtlQmFzZVVybChob3N0LCBwcm90b2NvbCwgYXBpS2V5KSB7XG4gIGxldCB1cmwgPSBudWxsO1xuXG4gIGlmIChwcm90b2NvbCA9PT0gJ2h0dHAnIHx8IHByb3RvY29sID09PSAnaHR0cHMnIHx8IHByb3RvY29sID09PSAnd3MnIHx8IHByb3RvY29sID09PSAnd3NzJykge1xuICAgIHVybCA9IHByb3RvY29sICsgJzovLyc7XG4gICAgdXJsICs9IGhvc3Q7XG4gICAgaWYgKHVybC5jaGFyQXQodXJsLmxlbmd0aCAtIDEpICE9PSAnLycpIHtcbiAgICAgIHVybCArPSAnLyc7XG4gICAgfVxuICAgIHVybCArPSAndicgKyBQUk9UT0NPTF9WRVJTSU9OICsgJy9jaGFubmVscyc7XG4gICAgaWYgKHByb3RvY29sID09PSAnaHR0cCcgfHwgcHJvdG9jb2wgPT09ICdodHRwcycpIHtcbiAgICAgIC8vIExvbmcgcG9sbGluZyBlbmRwb2ludCBlbmQgd2l0aCBcImxwXCIsIGkuZS5cbiAgICAgIC8vICcvdjAvY2hhbm5lbHMvbHAnIHZzIGp1c3QgJy92MC9jaGFubmVscycgZm9yIHdzXG4gICAgICB1cmwgKz0gJy9scCc7XG4gICAgfVxuICAgIHVybCArPSAnP2FwaWtleT0nICsgYXBpS2V5O1xuICB9XG5cbiAgcmV0dXJuIHVybDtcbn1cblxuLyoqXG4gKiBBbiBhYnN0cmFjdGlvbiBmb3IgYSB3ZWJzb2NrZXQgb3IgYSBsb25nIHBvbGxpbmcgY29ubmVjdGlvbi5cbiAqXG4gKiBAY2xhc3MgQ29ubmVjdGlvblxuICogQG1lbWJlcm9mIFRpbm9kZVxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBob3N0XyAtIEhvc3QgbmFtZSBhbmQgcG9ydCBudW1iZXIgdG8gY29ubmVjdCB0by5cbiAqIEBwYXJhbSB7c3RyaW5nfSBhcGlLZXlfIC0gQVBJIGtleSBnZW5lcmF0ZWQgYnkga2V5Z2VuXG4gKiBAcGFyYW0ge3N0cmluZ30gdHJhbnNwb3J0XyAtIE5ldHdvcmsgdHJhbnNwb3J0IHRvIHVzZSwgZWl0aGVyIGB3c2AvYHdzc2AgZm9yIHdlYnNvY2tldCBvciBgbHBgIGZvciBsb25nIHBvbGxpbmcuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IHNlY3VyZV8gLSBVc2Ugc2VjdXJlIFdlYlNvY2tldCAod3NzKSBpZiB0cnVlLlxuICogQHBhcmFtIHtib29sZWFufSBhdXRvcmVjb25uZWN0XyAtIElmIGNvbm5lY3Rpb24gaXMgbG9zdCwgdHJ5IHRvIHJlY29ubmVjdCBhdXRvbWF0aWNhbGx5LlxuICovXG52YXIgQ29ubmVjdGlvbiA9IGZ1bmN0aW9uKGhvc3RfLCBhcGlLZXlfLCB0cmFuc3BvcnRfLCBzZWN1cmVfLCBhdXRvcmVjb25uZWN0Xykge1xuICBsZXQgaG9zdCA9IGhvc3RfO1xuICBsZXQgc2VjdXJlID0gc2VjdXJlXztcbiAgbGV0IGFwaUtleSA9IGFwaUtleV87XG5cbiAgbGV0IGF1dG9yZWNvbm5lY3QgPSBhdXRvcmVjb25uZWN0XztcblxuICAvLyBTZXR0aW5ncyBmb3IgZXhwb25lbnRpYWwgYmFja29mZlxuICBjb25zdCBfQk9GRl9CQVNFID0gMjAwMDsgLy8gMjAwMCBtaWxsaXNlY29uZHMsIG1pbmltdW0gZGVsYXkgYmV0d2VlbiByZWNvbm5lY3RzXG4gIGNvbnN0IF9CT0ZGX01BWF9JVEVSID0gMTA7IC8vIE1heGltdW0gZGVsYXkgYmV0d2VlbiByZWNvbm5lY3RzIDJeMTAgKiAyMDAwIH4gMzQgbWludXRlc1xuICBjb25zdCBfQk9GRl9KSVRURVIgPSAwLjM7IC8vIEFkZCByYW5kb20gZGVsYXlcblxuICBsZXQgX2JvZmZUaW1lciA9IG51bGw7XG4gIGxldCBfYm9mZkl0ZXJhdGlvbiA9IDA7XG4gIGxldCBfYm9mZkNsb3NlZCA9IGZhbHNlOyAvLyBJbmRpY2F0b3IgaWYgdGhlIHNvY2tldCB3YXMgbWFudWFsbHkgY2xvc2VkIC0gZG9uJ3QgYXV0b3JlY29ubmVjdCBpZiB0cnVlLlxuXG4gIGxldCBsb2cgPSAodGV4dCwgLi4uYXJncykgPT4ge1xuICAgIGlmICh0aGlzLmxvZ2dlcikge1xuICAgICAgdGhpcy5sb2dnZXIodGV4dCwgLi4uYXJncyk7XG4gICAgfVxuICB9XG5cbiAgLy8gQmFja29mZiBpbXBsZW1lbnRhdGlvbiAtIHJlY29ubmVjdCBhZnRlciBhIHRpbWVvdXQuXG4gIGZ1bmN0aW9uIGJvZmZSZWNvbm5lY3QoKSB7XG4gICAgLy8gQ2xlYXIgdGltZXJcbiAgICBjbGVhclRpbWVvdXQoX2JvZmZUaW1lcik7XG4gICAgLy8gQ2FsY3VsYXRlIHdoZW4gdG8gZmlyZSB0aGUgcmVjb25uZWN0IGF0dGVtcHRcbiAgICBsZXQgdGltZW91dCA9IF9CT0ZGX0JBU0UgKiAoTWF0aC5wb3coMiwgX2JvZmZJdGVyYXRpb24pICogKDEuMCArIF9CT0ZGX0pJVFRFUiAqIE1hdGgucmFuZG9tKCkpKTtcbiAgICAvLyBVcGRhdGUgaXRlcmF0aW9uIGNvdW50ZXIgZm9yIGZ1dHVyZSB1c2VcbiAgICBfYm9mZkl0ZXJhdGlvbiA9IChfYm9mZkl0ZXJhdGlvbiA+PSBfQk9GRl9NQVhfSVRFUiA/IF9ib2ZmSXRlcmF0aW9uIDogX2JvZmZJdGVyYXRpb24gKyAxKTtcbiAgICBpZiAodGhpcy5vbkF1dG9yZWNvbm5lY3RJdGVyYXRpb24pIHtcbiAgICAgIHRoaXMub25BdXRvcmVjb25uZWN0SXRlcmF0aW9uKHRpbWVvdXQpO1xuICAgIH1cblxuICAgIF9ib2ZmVGltZXIgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIGxvZyhcIlJlY29ubmVjdGluZywgaXRlcj1cIiArIF9ib2ZmSXRlcmF0aW9uICsgXCIsIHRpbWVvdXQ9XCIgKyB0aW1lb3V0KTtcbiAgICAgIC8vIE1heWJlIHRoZSBzb2NrZXQgd2FzIGNsb3NlZCB3aGlsZSB3ZSB3YWl0ZWQgZm9yIHRoZSB0aW1lcj9cbiAgICAgIGlmICghX2JvZmZDbG9zZWQpIHtcbiAgICAgICAgbGV0IHByb20gPSB0aGlzLmNvbm5lY3QoKTtcbiAgICAgICAgaWYgKHRoaXMub25BdXRvcmVjb25uZWN0SXRlcmF0aW9uKSB7XG4gICAgICAgICAgdGhpcy5vbkF1dG9yZWNvbm5lY3RJdGVyYXRpb24oMCwgcHJvbSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gU3VwcHJlc3MgZXJyb3IgaWYgaXQncyBub3QgdXNlZC5cbiAgICAgICAgICBwcm9tLmNhdGNoKCgpID0+IHtcbiAgICAgICAgICAgIC8qIGRvIG5vdGhpbmcgKi9cbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmICh0aGlzLm9uQXV0b3JlY29ubmVjdEl0ZXJhdGlvbikge1xuICAgICAgICB0aGlzLm9uQXV0b3JlY29ubmVjdEl0ZXJhdGlvbigtMSk7XG4gICAgICB9XG4gICAgfSwgdGltZW91dCk7XG4gIH1cblxuICAvLyBUZXJtaW5hdGUgYXV0by1yZWNvbm5lY3QgcHJvY2Vzcy5cbiAgZnVuY3Rpb24gYm9mZlN0b3AoKSB7XG4gICAgY2xlYXJUaW1lb3V0KF9ib2ZmVGltZXIpO1xuICAgIF9ib2ZmVGltZXIgPSBudWxsO1xuICB9XG5cbiAgLy8gUmVzZXQgYXV0by1yZWNvbm5lY3QgaXRlcmF0aW9uIGNvdW50ZXIuXG4gIGZ1bmN0aW9uIGJvZmZSZXNldCgpIHtcbiAgICBfYm9mZkl0ZXJhdGlvbiA9IDA7XG4gIH1cblxuICAvLyBJbml0aWFsaXphdGlvbiBmb3IgV2Vic29ja2V0XG4gIGZ1bmN0aW9uIGluaXRfd3MoaW5zdGFuY2UpIHtcbiAgICBsZXQgX3NvY2tldCA9IG51bGw7XG5cbiAgICAvKipcbiAgICAgKiBJbml0aWF0ZSBhIG5ldyBjb25uZWN0aW9uXG4gICAgICogQG1lbWJlcm9mIFRpbm9kZS5Db25uZWN0aW9uI1xuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBob3N0XyBIb3N0IG5hbWUgdG8gY29ubmVjdCB0bzsgaWYgbnVsbCB0aGUgb2xkIGhvc3QgbmFtZSB3aWxsIGJlIHVzZWQuXG4gICAgICogQHBhcmFtIHtib29sZWFufSBmb3JjZSBGb3JjZSBuZXcgY29ubmVjdGlvbiBldmVuIGlmIG9uZSBhbHJlYWR5IGV4aXN0cy5cbiAgICAgKiBAcmV0dXJuIHtQcm9taXNlfSBQcm9taXNlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIGNvbm5lY3Rpb24gY2FsbCBjb21wbGV0ZXMsIHJlc29sdXRpb24gaXMgY2FsbGVkIHdpdGhvdXQgcGFyYW1ldGVycywgcmVqZWN0aW9uIHBhc3NlcyB0aGUge0Vycm9yfSBhcyBwYXJhbWV0ZXIuXG4gICAgICovXG4gICAgaW5zdGFuY2UuY29ubmVjdCA9IGZ1bmN0aW9uKGhvc3RfLCBmb3JjZSkge1xuICAgICAgX2JvZmZDbG9zZWQgPSBmYWxzZTtcblxuICAgICAgaWYgKF9zb2NrZXQgJiYgX3NvY2tldC5yZWFkeVN0YXRlID09IF9zb2NrZXQuT1BFTikge1xuICAgICAgICBpZiAoIWZvcmNlKSB7XG4gICAgICAgICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSgpO1xuICAgICAgICB9XG4gICAgICAgIF9zb2NrZXQuY2xvc2UoKTtcbiAgICAgICAgX3NvY2tldCA9IG51bGw7XG4gICAgICB9XG5cbiAgICAgIGlmIChob3N0Xykge1xuICAgICAgICBob3N0ID0gaG9zdF87XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihyZXNvbHZlLCByZWplY3QpIHtcbiAgICAgICAgY29uc3QgdXJsID0gbWFrZUJhc2VVcmwoaG9zdCwgc2VjdXJlID8gJ3dzcycgOiAnd3MnLCBhcGlLZXkpO1xuXG4gICAgICAgIGxvZyhcIkNvbm5lY3RpbmcgdG86IFwiLCB1cmwpO1xuXG4gICAgICAgIGNvbnN0IGNvbm4gPSBuZXcgV2ViU29ja2V0UHJvdmlkZXIodXJsKTtcblxuICAgICAgICBjb25uLm9ub3BlbiA9IGZ1bmN0aW9uKGV2dCkge1xuICAgICAgICAgIGlmIChhdXRvcmVjb25uZWN0KSB7XG4gICAgICAgICAgICBib2ZmU3RvcCgpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGlmIChpbnN0YW5jZS5vbk9wZW4pIHtcbiAgICAgICAgICAgIGluc3RhbmNlLm9uT3BlbigpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbm4ub25jbG9zZSA9IGZ1bmN0aW9uKGV2dCkge1xuICAgICAgICAgIF9zb2NrZXQgPSBudWxsO1xuXG4gICAgICAgICAgaWYgKGluc3RhbmNlLm9uRGlzY29ubmVjdCkge1xuICAgICAgICAgICAgY29uc3QgY29kZSA9IF9ib2ZmQ2xvc2VkID8gTkVUV09SS19VU0VSIDogTkVUV09SS19FUlJPUjtcbiAgICAgICAgICAgIGluc3RhbmNlLm9uRGlzY29ubmVjdChuZXcgRXJyb3IoX2JvZmZDbG9zZWQgPyBORVRXT1JLX1VTRVJfVEVYVCA6IE5FVFdPUktfRVJST1JfVEVYVCArXG4gICAgICAgICAgICAgICcgKCcgKyBjb2RlICsgJyknKSwgY29kZSk7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgaWYgKCFfYm9mZkNsb3NlZCAmJiBhdXRvcmVjb25uZWN0KSB7XG4gICAgICAgICAgICBib2ZmUmVjb25uZWN0LmNhbGwoaW5zdGFuY2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGNvbm4ub25lcnJvciA9IGZ1bmN0aW9uKGVycikge1xuICAgICAgICAgIHJlamVjdChlcnIpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29ubi5vbm1lc3NhZ2UgPSBmdW5jdGlvbihldnQpIHtcbiAgICAgICAgICBpZiAoaW5zdGFuY2Uub25NZXNzYWdlKSB7XG4gICAgICAgICAgICBpbnN0YW5jZS5vbk1lc3NhZ2UoZXZ0LmRhdGEpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBfc29ja2V0ID0gY29ubjtcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRyeSB0byByZXN0b3JlIGEgbmV0d29yayBjb25uZWN0aW9uLCBhbHNvIHJlc2V0IGJhY2tvZmYuXG4gICAgICogQG1lbWJlcm9mIFRpbm9kZS5Db25uZWN0aW9uI1xuICAgICAqXG4gICAgICogQHBhcmFtIHtib29sZWFufSBmb3JjZSAtIHJlY29ubmVjdCBldmVuIGlmIHRoZXJlIGlzIGEgbGl2ZSBjb25uZWN0aW9uIGFscmVhZHkuXG4gICAgICovXG4gICAgaW5zdGFuY2UucmVjb25uZWN0ID0gZnVuY3Rpb24oZm9yY2UpIHtcbiAgICAgIGJvZmZTdG9wKCk7XG4gICAgICBpbnN0YW5jZS5jb25uZWN0KG51bGwsIGZvcmNlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUZXJtaW5hdGUgdGhlIG5ldHdvcmsgY29ubmVjdGlvblxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ29ubmVjdGlvbiNcbiAgICAgKi9cbiAgICBpbnN0YW5jZS5kaXNjb25uZWN0ID0gZnVuY3Rpb24oKSB7XG4gICAgICBfYm9mZkNsb3NlZCA9IHRydWU7XG4gICAgICBpZiAoIV9zb2NrZXQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBib2ZmU3RvcCgpO1xuICAgICAgX3NvY2tldC5jbG9zZSgpO1xuICAgICAgX3NvY2tldCA9IG51bGw7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2VuZCBhIHN0cmluZyB0byB0aGUgc2VydmVyLlxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ29ubmVjdGlvbiNcbiAgICAgKlxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBtc2cgLSBTdHJpbmcgdG8gc2VuZC5cbiAgICAgKiBAdGhyb3dzIFRocm93cyBhbiBleGNlcHRpb24gaWYgdGhlIHVuZGVybHlpbmcgY29ubmVjdGlvbiBpcyBub3QgbGl2ZS5cbiAgICAgKi9cbiAgICBpbnN0YW5jZS5zZW5kVGV4dCA9IGZ1bmN0aW9uKG1zZykge1xuICAgICAgaWYgKF9zb2NrZXQgJiYgKF9zb2NrZXQucmVhZHlTdGF0ZSA9PSBfc29ja2V0Lk9QRU4pKSB7XG4gICAgICAgIF9zb2NrZXQuc2VuZChtc2cpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiV2Vic29ja2V0IGlzIG5vdCBjb25uZWN0ZWRcIik7XG4gICAgICB9XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIENoZWNrIGlmIHNvY2tldCBpcyBhbGl2ZS5cbiAgICAgKiBAbWVtYmVyb2YgVGlub2RlLkNvbm5lY3Rpb24jXG4gICAgICogQHJldHVybnMge2Jvb2xlYW59IHRydWUgaWYgY29ubmVjdGlvbiBpcyBsaXZlLCBmYWxzZSBvdGhlcndpc2VcbiAgICAgKi9cbiAgICBpbnN0YW5jZS5pc0Nvbm5lY3RlZCA9IGZ1bmN0aW9uKCkge1xuICAgICAgcmV0dXJuIChfc29ja2V0ICYmIChfc29ja2V0LnJlYWR5U3RhdGUgPT0gX3NvY2tldC5PUEVOKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0IHRoZSBuYW1lIG9mIHRoZSBjdXJyZW50IG5ldHdvcmsgdHJhbnNwb3J0LlxuICAgICAqIEBtZW1iZXJvZiBUaW5vZGUuQ29ubmVjdGlvbiNcbiAgICAgKiBAcmV0dXJucyB7c3RyaW5nfSBuYW1lIG9mIHRoZSB0cmFuc3BvcnQgc3VjaCBhcyAnd3MnIG9yICdscCcuXG4gICAgICovXG4gICAgaW5zdGFuY2UudHJhbnNwb3J0ID0gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJ3dzJztcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZW5kIG5ldHdvcmsgcHJvYmUgdG8gY2hlY2sgaWYgY29ubmVjdGlvbiBpcyBpbmRlZWQgbGl2ZS5cbiAgICAgKiBAbWVtYmVyb2YgVGlub2RlLkNvbm5lY3Rpb24jXG4gICAgICovXG4gICAgaW5zdGFuY2UucHJvYmUgPSBmdW5jdGlvbigpIHtcbiAgICAgIGluc3RhbmNlLnNlbmRUZXh0KCcxJyk7XG4gICAgfVxuICB9XG5cbiAgLy8gSW5pdGlhbGl6YXRpb24gZm9yIGxvbmcgcG9sbGluZy5cbiAgZnVuY3Rpb24gaW5pdF9scChpbnN0YW5jZSkge1xuICAgIGNvbnN0IFhEUl9VTlNFTlQgPSAwOyAvL1x0Q2xpZW50IGhhcyBiZWVuIGNyZWF0ZWQuIG9wZW4oKSBub3QgY2FsbGVkIHlldC5cbiAgICBjb25zdCBYRFJfT1BFTkVEID0gMTsgLy9cdG9wZW4oKSBoYXMgYmVlbiBjYWxsZWQuXG4gICAgY29uc3QgWERSX0hFQURFUlNfUkVDRUlWRUQgPSAyOyAvLyBzZW5kKCkgaGFzIGJlZW4gY2FsbGVkLCBhbmQgaGVhZGVycyBhbmQgc3RhdHVzIGFyZSBhdmFpbGFibGUuXG4gICAgY29uc3QgWERSX0xPQURJTkcgPSAzOyAvL1x0RG93bmxvYWRpbmc7IHJlc3BvbnNlVGV4dCBob2xkcyBwYXJ0aWFsIGRhdGEuXG4gICAgY29uc3QgWERSX0RPTkUgPSA0OyAvLyBUaGUgb3BlcmF0aW9uIGlzIGNvbXBsZXRlLlxuICAgIC8vIEZ1bGx5IGNvbXBvc2VkIGVuZHBvaW50IFVSTCwgd2l0aCBBUEkga2V5ICYgU0lEXG4gICAgbGV0IF9scFVSTCA9IG51bGw7XG5cbiAgICBsZXQgX3BvbGxlciA9IG51bGw7XG4gICAgbGV0IF9zZW5kZXIgPSBudWxsO1xuXG4gICAgZnVuY3Rpb24gbHBfc2VuZGVyKHVybF8pIHtcbiAgICAgIGxldCBzZW5kZXIgPSB4ZHJlcSgpO1xuICAgICAgc2VuZGVyLm9ucmVhZHlzdGF0ZWNoYW5nZSA9IGZ1bmN0aW9uKGV2dCkge1xuICAgICAgICBpZiAoc2VuZGVyLnJlYWR5U3RhdGUgPT0gWERSX0RPTkUgJiYgc2VuZGVyLnN0YXR1cyA+PSA0MDApIHtcbiAgICAgICAgICAvLyBTb21lIHNvcnQgb2YgZXJyb3IgcmVzcG9uc2VcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJMUCBzZW5kZXIgZmFpbGVkLCBcIiArIHNlbmRlci5zdGF0dXMpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHNlbmRlci5vcGVuKCdQT1NUJywgdXJsXywgdHJ1ZSk7XG4gICAgICByZXR1cm4gc2VuZGVyO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIGxwX3BvbGxlcih1cmxfLCByZXNvbHZlLCByZWplY3QpIHtcbiAgICAgIGxldCBwb2xsZXIgPSB4ZHJlcSgpO1xuICAgICAgbGV0IHByb21pc2VDb21wbGV0ZWQgPSBmYWxzZTtcblxuICAgICAgcG9sbGVyLm9ucmVhZHlzdGF0ZWNoYW5nZSA9IGZ1bmN0aW9uKGV2dCkge1xuXG4gICAgICAgIGlmIChwb2xsZXIucmVhZHlTdGF0ZSA9PSBYRFJfRE9ORSkge1xuICAgICAgICAgIGlmIChwb2xsZXIuc3RhdHVzID09IDIwMSkgeyAvLyAyMDEgPT0gSFRUUC5DcmVhdGVkLCBnZXQgU0lEXG4gICAgICAgICAgICBsZXQgcGt0ID0gSlNPTi5wYXJzZShwb2xsZXIucmVzcG9uc2VUZXh0LCBqc29uUGFyc2VIZWxwZXIpO1xuICAgICAgICAgICAgX2xwVVJMID0gdXJsXyArICcmc2lkPScgKyBwa3QuY3RybC5wYXJhbXMuc2lkXG4gICAgICAgICAgICBwb2xsZXIgPSBscF9wb2xsZXIoX2xwVVJMKTtcbiAgICAgICAgICAgIHBvbGxlci5zZW5kKG51bGwpXG4gICAgICAgICAgICBpZiAoaW5zdGFuY2Uub25PcGVuKSB7XG4gICAgICAgICAgICAgIGluc3RhbmNlLm9uT3BlbigpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAocmVzb2x2ZSkge1xuICAgICAgICAgICAgICBwcm9taXNlQ29tcGxldGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoYXV0b3JlY29ubmVjdCkge1xuICAgICAgICAgICAgICBib2ZmU3RvcCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSBpZiAocG9sbGVyLnN0YXR1cyA8IDQwMCkgeyAvLyA0MDAgPSBIVFRQLkJhZFJlcXVlc3RcbiAgICAgICAgICAgIGlmIChpbnN0YW5jZS5vbk1lc3NhZ2UpIHtcbiAgICAgICAgICAgICAgaW5zdGFuY2Uub25NZXNzYWdlKHBvbGxlci5yZXNwb25zZVRleHQpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBwb2xsZXIgPSBscF9wb2xsZXIoX2xwVVJMKTtcbiAgICAgICAgICAgIHBvbGxlci5zZW5kKG51bGwpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBEb24ndCB0aHJvdyBhbiBlcnJvciBoZXJlLCBncmFjZWZ1bGx5IGhhbmRsZSBzZXJ2ZXIgZXJyb3JzXG4gICAgICAgICAgICBpZiAocmVqZWN0ICYmICFwcm9taXNlQ29tcGxldGVkKSB7XG4gICAgICAgICAgICAgIHByb21pc2VDb21wbGV0ZWQgPSB0cnVlO1xuICAgICAgICAgICAgICByZWplY3QocG9sbGVyLnJlc3BvbnNlVGV4dCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoaW5zdGFuY2Uub25NZXNzYWdlICYmIHBvbGxlci5yZXNwb25zZVRleHQpIHtcbiAgICAgICAgICAgICAgaW5zdGFuY2Uub25NZXNzYWdlKHBvbGxlci5yZXNwb25zZVRleHQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGluc3RhbmNlLm9uRGlzY29ubmVjdCkge1xuICAgICAgICAgICAgICBjb25zdCBjb2RlID0gcG9sbGVyLnN0YXR1cyB8fCAoX2JvZmZDbG9zZWQgPyBORVRXT1JLX1VTRVIgOiBORVRXT1JLX0VSUk9SKTtcbiAgICAgICAgICAgICAgY29uc3QgdGV4dCA9IHBvbGxlci5yZXNwb25zZVRleHQgfHwgKF9ib2ZmQ2xvc2VkID8gTkVUV09SS19VU0VSX1RFWFQgOiBORVRXT1JLX0VSUk9SX1RFWFQpO1xuICAgICAgICAgICAgICBpbnN0YW5jZS5vbkRpc2Nvbm5lY3QobmV3IEVycm9yKHRleHQgKyAnICgnICsgY29kZSArICcpJyksIGNvZGUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBQb2xsaW5nIGhhcyBzdG9wcGVkLiBJbmRpY2F0ZSBpdCBieSBzZXR0aW5nIHBvbGxlciB0byBudWxsLlxuICAgICAgICAgICAgcG9sbGVyID0gbnVsbDtcbiAgICAgICAgICAgIGlmICghX2JvZmZDbG9zZWQgJiYgYXV0b3JlY29ubmVjdCkge1xuICAgICAgICAgICAgICBib2ZmUmVjb25uZWN0LmNhbGwoaW5zdGFuY2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcG9sbGVyLm9wZW4oJ0dFVCcsIHVybF8sIHRydWUpO1xuICAgICAgcmV0dXJuIHBvbGxlcjtcbiAgICB9XG5cbiAgICBpbnN0YW5jZS5jb25uZWN0ID0gZnVuY3Rpb24oaG9zdF8sIGZvcmNlKSB7XG4gICAgICBfYm9mZkNsb3NlZCA9IGZhbHNlO1xuXG4gICAgICBpZiAoX3BvbGxlcikge1xuICAgICAgICBpZiAoIWZvcmNlKSB7XG4gICAgICAgICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSgpO1xuICAgICAgICB9XG4gICAgICAgIF9wb2xsZXIub25yZWFkeXN0YXRlY2hhbmdlID0gdW5kZWZpbmVkO1xuICAgICAgICBfcG9sbGVyLmFib3J0KCk7XG4gICAgICAgIF9wb2xsZXIgPSBudWxsO1xuICAgICAgfVxuXG4gICAgICBpZiAoaG9zdF8pIHtcbiAgICAgICAgaG9zdCA9IGhvc3RfO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24ocmVzb2x2ZSwgcmVqZWN0KSB7XG4gICAgICAgIGNvbnN0IHVybCA9IG1ha2VCYXNlVXJsKGhvc3QsIHNlY3VyZSA/ICdodHRwcycgOiAnaHR0cCcsIGFwaUtleSk7XG4gICAgICAgIGxvZyhcIkNvbm5lY3RpbmcgdG86IFwiLCB1cmwpO1xuICAgICAgICBfcG9sbGVyID0gbHBfcG9sbGVyKHVybCwgcmVzb2x2ZSwgcmVqZWN0KTtcbiAgICAgICAgX3BvbGxlci5zZW5kKG51bGwpXG4gICAgICB9KS5jYXRjaChmdW5jdGlvbigpIHtcbiAgICAgICAgLy8gQ2F0Y2ggYW4gZXJyb3IgYW5kIGRvIG5vdGhpbmcuXG4gICAgICB9KTtcbiAgICB9O1xuXG4gICAgaW5zdGFuY2UucmVjb25uZWN0ID0gZnVuY3Rpb24oZm9yY2UpIHtcbiAgICAgIGJvZmZTdG9wKCk7XG4gICAgICBpbnN0YW5jZS5jb25uZWN0KG51bGwsIGZvcmNlKTtcbiAgICB9O1xuXG4gICAgaW5zdGFuY2UuZGlzY29ubmVjdCA9IGZ1bmN0aW9uKCkge1xuICAgICAgX2JvZmZDbG9zZWQgPSB0cnVlO1xuICAgICAgYm9mZlN0b3AoKTtcblxuICAgICAgaWYgKF9zZW5kZXIpIHtcbiAgICAgICAgX3NlbmRlci5vbnJlYWR5c3RhdGVjaGFuZ2UgPSB1bmRlZmluZWQ7XG4gICAgICAgIF9zZW5kZXIuYWJvcnQoKTtcbiAgICAgICAgX3NlbmRlciA9IG51bGw7XG4gICAgICB9XG4gICAgICBpZiAoX3BvbGxlcikge1xuICAgICAgICBfcG9sbGVyLm9ucmVhZHlzdGF0ZWNoYW5nZSA9IHVuZGVmaW5lZDtcbiAgICAgICAgX3BvbGxlci5hYm9ydCgpO1xuICAgICAgICBfcG9sbGVyID0gbnVsbDtcbiAgICAgIH1cblxuICAgICAgaWYgKGluc3RhbmNlLm9uRGlzY29ubmVjdCkge1xuICAgICAgICBpbnN0YW5jZS5vbkRpc2Nvbm5lY3QobmV3IEVycm9yKE5FVFdPUktfVVNFUl9URVhUICsgJyAoJyArIE5FVFdPUktfVVNFUiArICcpJyksIE5FVFdPUktfVVNFUik7XG4gICAgICB9XG4gICAgICAvLyBFbnN1cmUgaXQncyByZWNvbnN0cnVjdGVkXG4gICAgICBfbHBVUkwgPSBudWxsO1xuICAgIH1cblxuICAgIGluc3RhbmNlLnNlbmRUZXh0ID0gZnVuY3Rpb24obXNnKSB7XG4gICAgICBfc2VuZGVyID0gbHBfc2VuZGVyKF9scFVSTCk7XG4gICAgICBpZiAoX3NlbmRlciAmJiAoX3NlbmRlci5yZWFkeVN0YXRlID09IDEpKSB7IC8vIDEgPT0gT1BFTkVEXG4gICAgICAgIF9zZW5kZXIuc2VuZChtc2cpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiTG9uZyBwb2xsZXIgZmFpbGVkIHRvIGNvbm5lY3RcIik7XG4gICAgICB9XG4gICAgfTtcblxuICAgIGluc3RhbmNlLmlzQ29ubmVjdGVkID0gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gKF9wb2xsZXIgJiYgdHJ1ZSk7XG4gICAgfVxuXG4gICAgaW5zdGFuY2UudHJhbnNwb3J0ID0gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gJ2xwJztcbiAgICB9XG5cbiAgICBpbnN0YW5jZS5wcm9iZSA9IGZ1bmN0aW9uKCkge1xuICAgICAgaW5zdGFuY2Uuc2VuZFRleHQoJzEnKTtcbiAgICB9XG4gIH1cblxuICBpZiAodHJhbnNwb3J0XyA9PT0gJ2xwJykge1xuICAgIC8vIGV4cGxpY2l0IHJlcXVlc3QgdG8gdXNlIGxvbmcgcG9sbGluZ1xuICAgIGluaXRfbHAodGhpcyk7XG4gIH0gZWxzZSBpZiAodHJhbnNwb3J0XyA9PT0gJ3dzJykge1xuICAgIC8vIGV4cGxpY2l0IHJlcXVlc3QgdG8gdXNlIHdlYiBzb2NrZXRcbiAgICAvLyBpZiB3ZWJzb2NrZXRzIGFyZSBub3QgYXZhaWxhYmxlLCBob3JyaWJsZSB0aGluZ3Mgd2lsbCBoYXBwZW5cbiAgICBpbml0X3dzKHRoaXMpO1xuICB9IGVsc2Uge1xuICAgIC8vIERlZmF1bHQgdHJhbnNwb3J0IHNlbGVjdGlvblxuICAgIGlmICh0eXBlb2Ygd2luZG93ICE9ICdvYmplY3QnIHx8ICF3aW5kb3dbJ1dlYlNvY2tldCddKSB7XG4gICAgICAvLyBUaGUgYnJvd3NlciBoYXMgbm8gd2Vic29ja2V0c1xuICAgICAgaW5pdF9scCh0aGlzKTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gVXNpbmcgd2ViIHNvY2tldHMgLS0gZGVmYXVsdC5cbiAgICAgIGluaXRfd3ModGhpcyk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJlc2V0IGF1dG9yZWNvbm5lY3QgY291bnRlciB0byB6ZXJvLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkNvbm5lY3Rpb24jXG4gICAqL1xuICB0aGlzLmJhY2tvZmZSZXNldCA9IGZ1bmN0aW9uKCkge1xuICAgIGJvZmZSZXNldCgpO1xuICB9XG5cbiAgLy8gQ2FsbGJhY2tzOlxuICAvKipcbiAgICogQSBjYWxsYmFjayB0byBwYXNzIGluY29taW5nIG1lc3NhZ2VzIHRvLiBTZWUge0BsaW5rIFRpbm9kZS5Db25uZWN0aW9uI29uTWVzc2FnZX0uXG4gICAqIEBjYWxsYmFjayBUaW5vZGUuQ29ubmVjdGlvbi5Pbk1lc3NhZ2VcbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Db25uZWN0aW9uXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlIC0gTWVzc2FnZSB0byBwcm9jZXNzLlxuICAgKi9cbiAgLyoqXG4gICAqIEEgY2FsbGJhY2sgdG8gcGFzcyBpbmNvbWluZyBtZXNzYWdlcyB0by5cbiAgICogQHR5cGUge1Rpbm9kZS5Db25uZWN0aW9uLk9uTWVzc2FnZX1cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Db25uZWN0aW9uI1xuICAgKi9cbiAgdGhpcy5vbk1lc3NhZ2UgPSB1bmRlZmluZWQ7XG5cbiAgLyoqXG4gICAqIEEgY2FsbGJhY2sgZm9yIHJlcG9ydGluZyBhIGRyb3BwZWQgY29ubmVjdGlvbi5cbiAgICogQHR5cGUge2Z1bmN0aW9ufVxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkNvbm5lY3Rpb24jXG4gICAqL1xuICB0aGlzLm9uRGlzY29ubmVjdCA9IHVuZGVmaW5lZDtcblxuICAvKipcbiAgICogQSBjYWxsYmFjayBjYWxsZWQgd2hlbiB0aGUgY29ubmVjdGlvbiBpcyByZWFkeSB0byBiZSB1c2VkIGZvciBzZW5kaW5nLiBGb3Igd2Vic29ja2V0cyBpdCdzIHNvY2tldCBvcGVuLFxuICAgKiBmb3IgbG9uZyBwb2xsaW5nIGl0J3MgcmVhZHlTdGF0ZT0xIChPUEVORUQpXG4gICAqIEB0eXBlIHtmdW5jdGlvbn1cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Db25uZWN0aW9uI1xuICAgKi9cbiAgdGhpcy5vbk9wZW4gPSB1bmRlZmluZWQ7XG5cbiAgLyoqXG4gICAqIEEgY2FsbGJhY2sgdG8gbm90aWZ5IG9mIHJlY29ubmVjdGlvbiBhdHRlbXB0cy4gU2VlIHtAbGluayBUaW5vZGUuQ29ubmVjdGlvbiNvbkF1dG9yZWNvbm5lY3RJdGVyYXRpb259LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkNvbm5lY3Rpb25cbiAgICogQGNhbGxiYWNrIEF1dG9yZWNvbm5lY3RJdGVyYXRpb25UeXBlXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB0aW1lb3V0IC0gdGltZSB0aWxsIHRoZSBuZXh0IHJlY29ubmVjdCBhdHRlbXB0IGluIG1pbGxpc2Vjb25kcy4gLTEgbWVhbnMgcmVjb25uZWN0IHdhcyBza2lwcGVkLlxuICAgKiBAcGFyYW0ge1Byb21pc2V9IHByb21pc2UgcmVzb2x2ZWQgb3IgcmVqZWN0ZWQgd2hlbiB0aGUgcmVjb25uZWN0IGF0dGVtcCBjb21wbGV0ZXMuXG4gICAqXG4gICAqL1xuICAvKipcbiAgICogQSBjYWxsYmFjayB0byBpbmZvcm0gd2hlbiB0aGUgbmV4dCBhdHRhbXB0IHRvIHJlY29ubmVjdCB3aWxsIGhhcHBlbiBhbmQgdG8gcmVjZWl2ZSBjb25uZWN0aW9uIHByb21pc2UuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQ29ubmVjdGlvbiNcbiAgICogQHR5cGUge1Rpbm9kZS5Db25uZWN0aW9uLkF1dG9yZWNvbm5lY3RJdGVyYXRpb25UeXBlfVxuICAgKi9cbiAgdGhpcy5vbkF1dG9yZWNvbm5lY3RJdGVyYXRpb24gPSB1bmRlZmluZWQ7XG5cbiAgLyoqXG4gICAqIEEgY2FsbGJhY2sgdG8gbG9nIGV2ZW50cyBmcm9tIENvbm5lY3Rpb24uIFNlZSB7QGxpbmsgVGlub2RlLkNvbm5lY3Rpb24jbG9nZ2VyfS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Db25uZWN0aW9uXG4gICAqIEBjYWxsYmFjayBMb2dnZXJDYWxsYmFja1R5cGVcbiAgICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50IC0gRXZlbnQgdG8gbG9nLlxuICAgKi9cbiAgLyoqXG4gICAqIEEgY2FsbGJhY2sgdG8gcmVwb3J0IGxvZ2dpbmcgZXZlbnRzLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkNvbm5lY3Rpb24jXG4gICAqIEB0eXBlIHtUaW5vZGUuQ29ubmVjdGlvbi5Mb2dnZXJDYWxsYmFja1R5cGV9XG4gICAqL1xuICB0aGlzLmxvZ2dlciA9IHVuZGVmaW5lZDtcbn07XG5cbi8qKlxuICogQGNsYXNzIFRpbm9kZVxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBhcHBuYW1lXyAtIE5hbWUgb2YgdGhlIGNhbGlpbmcgYXBwbGljYXRpb24gdG8gYmUgcmVwb3J0ZWQgaW4gVXNlciBBZ2VudC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBob3N0XyAtIEhvc3QgbmFtZSBhbmQgcG9ydCBudW1iZXIgdG8gY29ubmVjdCB0by5cbiAqIEBwYXJhbSB7c3RyaW5nfSBhcGlLZXlfIC0gQVBJIGtleSBnZW5lcmF0ZWQgYnkga2V5Z2VuXG4gKiBAcGFyYW0ge3N0cmluZ30gdHJhbnNwb3J0XyAtIFNlZSB7QGxpbmsgVGlub2RlLkNvbm5lY3Rpb24jdHJhbnNwb3J0fS5cbiAqIEBwYXJhbSB7Ym9vbGVhbn0gc2VjdXJlXyAtIFVzZSBTZWN1cmUgV2ViU29ja2V0IGlmIHRydWUuXG4gKiBAcGFyYW0ge3N0cmluZ30gcGxhdGZvcm1fIC0gT3B0aW9uYWwgcGxhdGZvcm0gaWRlbnRpZmllciwgb25lIG9mIFwiaW9zXCIsIFwid2ViXCIsIFwiYW5kcm9pZFwiLlxuICovXG52YXIgVGlub2RlID0gZnVuY3Rpb24oYXBwbmFtZV8sIGhvc3RfLCBhcGlLZXlfLCB0cmFuc3BvcnRfLCBzZWN1cmVfLCBwbGF0Zm9ybV8pIHtcbiAgLy8gQ2xpZW50LXByb3ZpZGVkIGFwcGxpY2F0aW9uIG5hbWUsIGZvcm1hdCA8TmFtZT4vPHZlcnNpb24gbnVtYmVyPlxuICBpZiAoYXBwbmFtZV8pIHtcbiAgICB0aGlzLl9hcHBOYW1lID0gYXBwbmFtZV87XG4gIH0gZWxzZSB7XG4gICAgdGhpcy5fYXBwTmFtZSA9IFwiVW5kZWZpbmVkXCI7XG4gIH1cblxuICAvLyBBUEkgS2V5LlxuICB0aGlzLl9hcGlLZXkgPSBhcGlLZXlfO1xuXG4gIC8vIE5hbWUgYW5kIHZlcnNpb24gb2YgdGhlIGJyb3dzZXIuXG4gIHRoaXMuX2Jyb3dzZXIgPSAnJztcbiAgdGhpcy5fcGxhdGZvcm0gPSBwbGF0Zm9ybV87XG4gIHRoaXMuX2h3b3MgPSAndW5kZWZpbmVkJztcbiAgdGhpcy5faHVtYW5MYW5ndWFnZSA9ICd4eCc7XG4gIC8vIFVuZGVybHlpbmcgT1MuXG4gIGlmICh0eXBlb2YgbmF2aWdhdG9yICE9ICd1bmRlZmluZWQnKSB7XG4gICAgdGhpcy5fYnJvd3NlciA9IGdldEJyb3dzZXJJbmZvKG5hdmlnYXRvci51c2VyQWdlbnQsIG5hdmlnYXRvci5wcm9kdWN0KTtcbiAgICB0aGlzLl9od29zID0gbmF2aWdhdG9yLnBsYXRmb3JtO1xuICAgIHRoaXMuX2h1bWFuTGFuZ3VhZ2UgPSBuYXZpZ2F0b3IubGFuZ3VhZ2UgfHwgJ2VuLVVTJztcbiAgfVxuICAvLyBMb2dnaW5nIHRvIGNvbnNvbGUgZW5hYmxlZFxuICB0aGlzLl9sb2dnaW5nRW5hYmxlZCA9IGZhbHNlO1xuICAvLyBXaGVuIGxvZ2dpbmcsIHRyaXAgbG9uZyBzdHJpbmdzIChiYXNlNjQtZW5jb2RlZCBpbWFnZXMpIGZvciByZWFkYWJpbGl0eVxuICB0aGlzLl90cmltTG9uZ1N0cmluZ3MgPSBmYWxzZTtcbiAgLy8gVUlEIG9mIHRoZSBjdXJyZW50bHkgYXV0aGVudGljYXRlZCB1c2VyLlxuICB0aGlzLl9teVVJRCA9IG51bGw7XG4gIC8vIFN0YXR1cyBvZiBjb25uZWN0aW9uOiBhdXRoZW50aWNhdGVkIG9yIG5vdC5cbiAgdGhpcy5fYXV0aGVudGljYXRlZCA9IGZhbHNlO1xuICAvLyBMb2dpbiB1c2VkIGluIHRoZSBsYXN0IHN1Y2Nlc3NmdWwgYmFzaWMgYXV0aGVudGljYXRpb25cbiAgdGhpcy5fbG9naW4gPSBudWxsO1xuICAvLyBUb2tlbiB3aGljaCBjYW4gYmUgdXNlZCBmb3IgbG9naW4gaW5zdGVhZCBvZiBsb2dpbi9wYXNzd29yZC5cbiAgdGhpcy5fYXV0aFRva2VuID0gbnVsbDtcbiAgLy8gQ291bnRlciBvZiByZWNlaXZlZCBwYWNrZXRzXG4gIHRoaXMuX2luUGFja2V0Q291bnQgPSAwO1xuICAvLyBDb3VudGVyIGZvciBnZW5lcmF0aW5nIHVuaXF1ZSBtZXNzYWdlIElEc1xuICB0aGlzLl9tZXNzYWdlSWQgPSBNYXRoLmZsb29yKChNYXRoLnJhbmRvbSgpICogMHhGRkZGKSArIDB4RkZGRik7XG4gIC8vIEluZm9ybWF0aW9uIGFib3V0IHRoZSBzZXJ2ZXIsIGlmIGNvbm5lY3RlZFxuICB0aGlzLl9zZXJ2ZXJJbmZvID0gbnVsbDtcbiAgLy8gUHVzaCBub3RpZmljYXRpb24gdG9rZW4uIENhbGxlZCBkZXZpY2VUb2tlbiBmb3IgY29uc2lzdGVuY3kgd2l0aCB0aGUgQW5kcm9pZCBTREsuXG4gIHRoaXMuX2RldmljZVRva2VuID0gbnVsbDtcblxuICAvLyBDYWNoZSBvZiBwZW5kaW5nIHByb21pc2VzIGJ5IG1lc3NhZ2UgaWQuXG4gIHRoaXMuX3BlbmRpbmdQcm9taXNlcyA9IHt9O1xuXG4gIC8qKiBBIGNvbm5lY3Rpb24gb2JqZWN0LCBzZWUge0BsaW5rIFRpbm9kZS5Db25uZWN0aW9ufS4gKi9cbiAgdGhpcy5fY29ubmVjdGlvbiA9IG5ldyBDb25uZWN0aW9uKGhvc3RfLCBhcGlLZXlfLCB0cmFuc3BvcnRfLCBzZWN1cmVfLCB0cnVlKTtcbiAgLy8gQ29uc29sZSBsb2dnZXIuIEJhYmVsIHNvbWVob3cgZmFpbHMgdG8gcGFyc2UgJy4uLnJlc3QnIHBhcmFtZXRlci5cbiAgdGhpcy5sb2dnZXIgPSAoc3RyLCAuLi5hcmdzKSA9PiB7XG4gICAgaWYgKHRoaXMuX2xvZ2dpbmdFbmFibGVkKSB7XG4gICAgICBjb25zdCBkID0gbmV3IERhdGUoKVxuICAgICAgY29uc3QgZGF0ZVN0cmluZyA9ICgnMCcgKyBkLmdldFVUQ0hvdXJzKCkpLnNsaWNlKC0yKSArICc6JyArXG4gICAgICAgICgnMCcgKyBkLmdldFVUQ01pbnV0ZXMoKSkuc2xpY2UoLTIpICsgJzonICtcbiAgICAgICAgKCcwJyArIGQuZ2V0VVRDU2Vjb25kcygpKS5zbGljZSgtMikgKyAnOicgK1xuICAgICAgICAoJzAnICsgZC5nZXRVVENNaWxsaXNlY29uZHMoKSkuc2xpY2UoLTMpO1xuXG4gICAgICBjb25zb2xlLmxvZygnWycgKyBkYXRlU3RyaW5nICsgJ10nLCBzdHIsIGFyZ3Muam9pbignICcpKTtcbiAgICB9XG4gIH1cbiAgdGhpcy5fY29ubmVjdGlvbi5sb2dnZXIgPSB0aGlzLmxvZ2dlcjtcblxuICAvLyBUaW5vZGUncyBjYWNoZSBvZiBvYmplY3RzXG4gIHRoaXMuX2NhY2hlID0ge307XG5cbiAgY29uc3QgY2FjaGVQdXQgPSB0aGlzLmNhY2hlUHV0ID0gKHR5cGUsIG5hbWUsIG9iaikgPT4ge1xuICAgIHRoaXMuX2NhY2hlW3R5cGUgKyAnOicgKyBuYW1lXSA9IG9iajtcbiAgfVxuXG4gIGNvbnN0IGNhY2hlR2V0ID0gdGhpcy5jYWNoZUdldCA9ICh0eXBlLCBuYW1lKSA9PiB7XG4gICAgcmV0dXJuIHRoaXMuX2NhY2hlW3R5cGUgKyAnOicgKyBuYW1lXTtcbiAgfVxuXG4gIGNvbnN0IGNhY2hlRGVsID0gdGhpcy5jYWNoZURlbCA9ICh0eXBlLCBuYW1lKSA9PiB7XG4gICAgZGVsZXRlIHRoaXMuX2NhY2hlW3R5cGUgKyAnOicgKyBuYW1lXTtcbiAgfVxuICAvLyBFbnVtZXJhdGUgYWxsIGl0ZW1zIGluIGNhY2hlLCBjYWxsIGZ1bmMgZm9yIGVhY2ggaXRlbS5cbiAgLy8gRW51bWVyYXRpb24gc3RvcHMgaWYgZnVuYyByZXR1cm5zIHRydWUuXG4gIGNvbnN0IGNhY2hlTWFwID0gdGhpcy5jYWNoZU1hcCA9IChmdW5jLCBjb250ZXh0KSA9PiB7XG4gICAgZm9yIChsZXQgaWR4IGluIHRoaXMuX2NhY2hlKSB7XG4gICAgICBpZiAoZnVuYyh0aGlzLl9jYWNoZVtpZHhdLCBpZHgsIGNvbnRleHQpKSB7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIE1ha2UgbGltaXRlZCBjYWNoZSBtYW5hZ2VtZW50IGF2YWlsYWJsZSB0byB0b3BpYy5cbiAgLy8gQ2FjaGluZyB1c2VyLnB1YmxpYyBvbmx5LiBFdmVyeXRoaW5nIGVsc2UgaXMgcGVyLXRvcGljLlxuICB0aGlzLmF0dGFjaENhY2hlVG9Ub3BpYyA9ICh0b3BpYykgPT4ge1xuICAgIHRvcGljLl90aW5vZGUgPSB0aGlzO1xuXG4gICAgdG9waWMuX2NhY2hlR2V0VXNlciA9ICh1aWQpID0+IHtcbiAgICAgIGNvbnN0IHB1YiA9IGNhY2hlR2V0KCd1c2VyJywgdWlkKTtcbiAgICAgIGlmIChwdWIpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICB1c2VyOiB1aWQsXG4gICAgICAgICAgcHVibGljOiBtZXJnZU9iaih7fSwgcHViKVxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9O1xuICAgIHRvcGljLl9jYWNoZVB1dFVzZXIgPSAodWlkLCB1c2VyKSA9PiB7XG4gICAgICByZXR1cm4gY2FjaGVQdXQoJ3VzZXInLCB1aWQsIG1lcmdlT2JqKHt9LCB1c2VyLnB1YmxpYykpO1xuICAgIH07XG4gICAgdG9waWMuX2NhY2hlRGVsVXNlciA9ICh1aWQpID0+IHtcbiAgICAgIHJldHVybiBjYWNoZURlbCgndXNlcicsIHVpZCk7XG4gICAgfTtcbiAgICB0b3BpYy5fY2FjaGVQdXRTZWxmID0gKCkgPT4ge1xuICAgICAgcmV0dXJuIGNhY2hlUHV0KCd0b3BpYycsIHRvcGljLm5hbWUsIHRvcGljKTtcbiAgICB9XG4gICAgdG9waWMuX2NhY2hlRGVsU2VsZiA9ICgpID0+IHtcbiAgICAgIHJldHVybiBjYWNoZURlbCgndG9waWMnLCB0b3BpYy5uYW1lKTtcbiAgICB9XG4gIH1cblxuICAvLyBSZXNvbHZlIG9yIHJlamVjdCBhIHBlbmRpbmcgcHJvbWlzZS5cbiAgLy8gVW5yZXNvbHZlZCBwcm9taXNlcyBhcmUgc3RvcmVkIGluIF9wZW5kaW5nUHJvbWlzZXMuXG4gIGNvbnN0IGV4ZWNQcm9taXNlID0gKGlkLCBjb2RlLCBvbk9LLCBlcnJvclRleHQpID0+IHtcbiAgICBjb25zdCBjYWxsYmFja3MgPSB0aGlzLl9wZW5kaW5nUHJvbWlzZXNbaWRdO1xuICAgIGlmIChjYWxsYmFja3MpIHtcbiAgICAgIGRlbGV0ZSB0aGlzLl9wZW5kaW5nUHJvbWlzZXNbaWRdO1xuICAgICAgaWYgKGNvZGUgPj0gMjAwICYmIGNvZGUgPCA0MDApIHtcbiAgICAgICAgaWYgKGNhbGxiYWNrcy5yZXNvbHZlKSB7XG4gICAgICAgICAgY2FsbGJhY2tzLnJlc29sdmUob25PSyk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAoY2FsbGJhY2tzLnJlamVjdCkge1xuICAgICAgICBjYWxsYmFja3MucmVqZWN0KG5ldyBFcnJvcihlcnJvclRleHQgKyBcIiAoXCIgKyBjb2RlICsgXCIpXCIpKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvLyBHZW5lcmF0b3Igb2YgZGVmYXVsdCBwcm9taXNlcyBmb3Igc2VudCBwYWNrZXRzLlxuICBjb25zdCBtYWtlUHJvbWlzZSA9IChpZCkgPT4ge1xuICAgIGxldCBwcm9taXNlID0gbnVsbDtcbiAgICBpZiAoaWQpIHtcbiAgICAgIHByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIC8vIFN0b3JlZCBjYWxsYmFja3Mgd2lsbCBiZSBjYWxsZWQgd2hlbiB0aGUgcmVzcG9uc2UgcGFja2V0IHdpdGggdGhpcyBJZCBhcnJpdmVzXG4gICAgICAgIHRoaXMuX3BlbmRpbmdQcm9taXNlc1tpZF0gPSB7XG4gICAgICAgICAgJ3Jlc29sdmUnOiByZXNvbHZlLFxuICAgICAgICAgICdyZWplY3QnOiByZWplY3QsXG4gICAgICAgICAgJ3RzJzogbmV3IERhdGUoKVxuICAgICAgICB9O1xuICAgICAgfSlcbiAgICB9XG4gICAgcmV0dXJuIHByb21pc2U7XG4gIH1cblxuICAvLyBSZWplY3QgcHJvbWlzZXMgd2hpY2ggaGF2ZSBub3QgYmVlbiByZXNvbHZlZCBmb3IgdG9vIGxvbmcuXG4gIGNvbnN0IGV4cGlyZVByb21pc2VzID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgIGNvbnN0IGVyciA9IG5ldyBFcnJvcihcIlRpbWVvdXQgKDUwNClcIik7XG4gICAgY29uc3QgZXhwaXJlcyA9IG5ldyBEYXRlKG5ldyBEYXRlKCkuZ2V0VGltZSgpIC0gRVhQSVJFX1BST01JU0VTX1RJTUVPVVQpO1xuICAgIGZvciAobGV0IGlkIGluIHRoaXMuX3BlbmRpbmdQcm9taXNlcykge1xuICAgICAgbGV0IGNhbGxiYWNrcyA9IHRoaXMuX3BlbmRpbmdQcm9taXNlc1tpZF07XG4gICAgICBpZiAoY2FsbGJhY2tzICYmIGNhbGxiYWNrcy50cyA8IGV4cGlyZXMpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIoXCJQcm9taXNlIGV4cGlyZWRcIiwgaWQpO1xuICAgICAgICBkZWxldGUgdGhpcy5fcGVuZGluZ1Byb21pc2VzW2lkXTtcbiAgICAgICAgaWYgKGNhbGxiYWNrcy5yZWplY3QpIHtcbiAgICAgICAgICBjYWxsYmFja3MucmVqZWN0KGVycik7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH0sIEVYUElSRV9QUk9NSVNFU19QRVJJT0QpO1xuXG4gIC8vIEdlbmVyYXRlcyB1bmlxdWUgbWVzc2FnZSBJRHNcbiAgY29uc3QgZ2V0TmV4dFVuaXF1ZUlkID0gdGhpcy5nZXROZXh0VW5pcXVlSWQgPSAoKSA9PiB7XG4gICAgcmV0dXJuICh0aGlzLl9tZXNzYWdlSWQgIT0gMCkgPyAnJyArIHRoaXMuX21lc3NhZ2VJZCsrIDogdW5kZWZpbmVkO1xuICB9XG5cbiAgLy8gR2V0IFVzZXIgQWdlbnQgc3RyaW5nXG4gIGNvbnN0IGdldFVzZXJBZ2VudCA9ICgpID0+IHtcbiAgICByZXR1cm4gdGhpcy5fYXBwTmFtZSArICcgKCcgKyAodGhpcy5fYnJvd3NlciA/IHRoaXMuX2Jyb3dzZXIgKyAnOyAnIDogJycpICsgdGhpcy5faHdvcyArICcpOyAnICsgTElCUkFSWTtcbiAgfVxuXG4gIC8vIEdlbmVyYXRvciBvZiBwYWNrZXRzIHN0dWJzXG4gIHRoaXMuaW5pdFBhY2tldCA9ICh0eXBlLCB0b3BpYykgPT4ge1xuICAgIHN3aXRjaCAodHlwZSkge1xuICAgICAgY2FzZSAnaGknOlxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICdoaSc6IHtcbiAgICAgICAgICAgICdpZCc6IGdldE5leHRVbmlxdWVJZCgpLFxuICAgICAgICAgICAgJ3Zlcic6IFZFUlNJT04sXG4gICAgICAgICAgICAndWEnOiBnZXRVc2VyQWdlbnQoKSxcbiAgICAgICAgICAgICdkZXYnOiB0aGlzLl9kZXZpY2VUb2tlbixcbiAgICAgICAgICAgICdsYW5nJzogdGhpcy5faHVtYW5MYW5ndWFnZSxcbiAgICAgICAgICAgICdwbGF0Zic6IHRoaXMuX3BsYXRmb3JtXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBjYXNlICdhY2MnOlxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICdhY2MnOiB7XG4gICAgICAgICAgICAnaWQnOiBnZXROZXh0VW5pcXVlSWQoKSxcbiAgICAgICAgICAgICd1c2VyJzogbnVsbCxcbiAgICAgICAgICAgICdzY2hlbWUnOiBudWxsLFxuICAgICAgICAgICAgJ3NlY3JldCc6IG51bGwsXG4gICAgICAgICAgICAnbG9naW4nOiBmYWxzZSxcbiAgICAgICAgICAgICd0YWdzJzogbnVsbCxcbiAgICAgICAgICAgICdkZXNjJzoge30sXG4gICAgICAgICAgICAnY3JlZCc6IHt9XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBjYXNlICdsb2dpbic6XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgJ2xvZ2luJzoge1xuICAgICAgICAgICAgJ2lkJzogZ2V0TmV4dFVuaXF1ZUlkKCksXG4gICAgICAgICAgICAnc2NoZW1lJzogbnVsbCxcbiAgICAgICAgICAgICdzZWNyZXQnOiBudWxsXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBjYXNlICdzdWInOlxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICdzdWInOiB7XG4gICAgICAgICAgICAnaWQnOiBnZXROZXh0VW5pcXVlSWQoKSxcbiAgICAgICAgICAgICd0b3BpYyc6IHRvcGljLFxuICAgICAgICAgICAgJ3NldCc6IHt9LFxuICAgICAgICAgICAgJ2dldCc6IHt9XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBjYXNlICdsZWF2ZSc6XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgJ2xlYXZlJzoge1xuICAgICAgICAgICAgJ2lkJzogZ2V0TmV4dFVuaXF1ZUlkKCksXG4gICAgICAgICAgICAndG9waWMnOiB0b3BpYyxcbiAgICAgICAgICAgICd1bnN1Yic6IGZhbHNlXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBjYXNlICdwdWInOlxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICdwdWInOiB7XG4gICAgICAgICAgICAnaWQnOiBnZXROZXh0VW5pcXVlSWQoKSxcbiAgICAgICAgICAgICd0b3BpYyc6IHRvcGljLFxuICAgICAgICAgICAgJ25vZWNobyc6IGZhbHNlLFxuICAgICAgICAgICAgJ2hlYWQnOiBudWxsLFxuICAgICAgICAgICAgJ2NvbnRlbnQnOiB7fVxuICAgICAgICAgIH1cbiAgICAgICAgfTtcblxuICAgICAgY2FzZSAnZ2V0JzpcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAnZ2V0Jzoge1xuICAgICAgICAgICAgJ2lkJzogZ2V0TmV4dFVuaXF1ZUlkKCksXG4gICAgICAgICAgICAndG9waWMnOiB0b3BpYyxcbiAgICAgICAgICAgICd3aGF0JzogbnVsbCwgLy8gZGF0YSwgc3ViLCBkZXNjLCBzcGFjZSBzZXBhcmF0ZWQgbGlzdDsgdW5rbm93biBzdHJpbmdzIGFyZSBpZ25vcmVkXG4gICAgICAgICAgICAnZGVzYyc6IHt9LFxuICAgICAgICAgICAgJ3N1Yic6IHt9LFxuICAgICAgICAgICAgJ2RhdGEnOiB7fVxuICAgICAgICAgIH1cbiAgICAgICAgfTtcblxuICAgICAgY2FzZSAnc2V0JzpcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAnc2V0Jzoge1xuICAgICAgICAgICAgJ2lkJzogZ2V0TmV4dFVuaXF1ZUlkKCksXG4gICAgICAgICAgICAndG9waWMnOiB0b3BpYyxcbiAgICAgICAgICAgICdkZXNjJzoge30sXG4gICAgICAgICAgICAnc3ViJzoge30sXG4gICAgICAgICAgICAndGFncyc6IFtdXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBjYXNlICdkZWwnOlxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICdkZWwnOiB7XG4gICAgICAgICAgICAnaWQnOiBnZXROZXh0VW5pcXVlSWQoKSxcbiAgICAgICAgICAgICd0b3BpYyc6IHRvcGljLFxuICAgICAgICAgICAgJ3doYXQnOiBudWxsLFxuICAgICAgICAgICAgJ2RlbHNlcSc6IG51bGwsXG4gICAgICAgICAgICAndXNlcic6IG51bGwsXG4gICAgICAgICAgICAnaGFyZCc6IGZhbHNlXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBjYXNlICdub3RlJzpcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAnbm90ZSc6IHtcbiAgICAgICAgICAgIC8vIG5vIGlkIGJ5IGRlc2lnblxuICAgICAgICAgICAgJ3RvcGljJzogdG9waWMsXG4gICAgICAgICAgICAnd2hhdCc6IG51bGwsIC8vIG9uZSBvZiBcInJlY3ZcIiwgXCJyZWFkXCIsIFwia3BcIlxuICAgICAgICAgICAgJ3NlcSc6IHVuZGVmaW5lZCAvLyB0aGUgc2VydmVyLXNpZGUgbWVzc2FnZSBpZCBha25vd2xlZGdlZCBhcyByZWNlaXZlZCBvciByZWFkXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICBkZWZhdWx0OlxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJVbmtub3duIHBhY2tldCB0eXBlIHJlcXVlc3RlZDogXCIgKyB0eXBlKTtcbiAgICB9XG4gIH1cblxuICAvLyBTZW5kIGEgcGFja2V0LiBJZiBwYWNrZXQgaWQgaXMgcHJvdmlkZWQgcmV0dXJuIGEgcHJvbWlzZS5cbiAgdGhpcy5zZW5kID0gKHBrdCwgaWQpID0+IHtcbiAgICBsZXQgcHJvbWlzZTtcbiAgICBpZiAoaWQpIHtcbiAgICAgIHByb21pc2UgPSBtYWtlUHJvbWlzZShpZCk7XG4gICAgfVxuICAgIHBrdCA9IHNpbXBsaWZ5KHBrdCk7XG4gICAgbGV0IG1zZyA9IEpTT04uc3RyaW5naWZ5KHBrdCk7XG4gICAgdGhpcy5sb2dnZXIoXCJvdXQ6IFwiICsgKHRoaXMuX3RyaW1Mb25nU3RyaW5ncyA/IEpTT04uc3RyaW5naWZ5KHBrdCwganNvbkxvZ2dlckhlbHBlcikgOiBtc2cpKTtcbiAgICB0cnkge1xuICAgICAgdGhpcy5fY29ubmVjdGlvbi5zZW5kVGV4dChtc2cpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgLy8gSWYgc2VuZFRleHQgdGhyb3dzLCB3cmFwIHRoZSBlcnJvciBpbiBhIHByb21pc2Ugb3IgcmV0aHJvdy5cbiAgICAgIGlmIChpZCkge1xuICAgICAgICBleGVjUHJvbWlzZShpZCwgTkVUV09SS19FUlJPUiwgbnVsbCwgZXJyLm1lc3NhZ2UpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgZXJyO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcHJvbWlzZTtcbiAgfVxuXG4gIC8vIE9uIHN1Y2Nlc3NmdWwgbG9naW4gc2F2ZSBzZXJ2ZXItcHJvdmlkZWQgZGF0YS5cbiAgdGhpcy5sb2dpblN1Y2Nlc3NmdWwgPSAoY3RybCkgPT4ge1xuICAgIGlmICghY3RybC5wYXJhbXMgfHwgIWN0cmwucGFyYW1zLnVzZXIpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gVGhpcyBpcyBhIHJlc3BvbnNlIHRvIGEgc3VjY2Vzc2Z1bCBsb2dpbixcbiAgICAvLyBleHRyYWN0IFVJRCBhbmQgc2VjdXJpdHkgdG9rZW4sIHNhdmUgaXQgaW4gVGlub2RlIG1vZHVsZVxuICAgIHRoaXMuX215VUlEID0gY3RybC5wYXJhbXMudXNlcjtcbiAgICB0aGlzLl9hdXRoZW50aWNhdGVkID0gKGN0cmwgJiYgY3RybC5jb2RlID49IDIwMCAmJiBjdHJsLmNvZGUgPCAzMDApO1xuICAgIGlmIChjdHJsLnBhcmFtcyAmJiBjdHJsLnBhcmFtcy50b2tlbiAmJiBjdHJsLnBhcmFtcy5leHBpcmVzKSB7XG4gICAgICB0aGlzLl9hdXRoVG9rZW4gPSB7XG4gICAgICAgIHRva2VuOiBjdHJsLnBhcmFtcy50b2tlbixcbiAgICAgICAgZXhwaXJlczogbmV3IERhdGUoY3RybC5wYXJhbXMuZXhwaXJlcylcbiAgICAgIH07XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX2F1dGhUb2tlbiA9IG51bGw7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMub25Mb2dpbikge1xuICAgICAgdGhpcy5vbkxvZ2luKGN0cmwuY29kZSwgY3RybC50ZXh0KTtcbiAgICB9XG4gIH1cblxuICAvLyBUaGUgbWFpbiBtZXNzYWdlIGRpc3BhdGNoZXIuXG4gIHRoaXMuX2Nvbm5lY3Rpb24ub25NZXNzYWdlID0gKGRhdGEpID0+IHtcbiAgICAvLyBTa2lwIGVtcHR5IHJlc3BvbnNlLiBUaGlzIGhhcHBlbnMgd2hlbiBMUCB0aW1lcyBvdXQuXG4gICAgaWYgKCFkYXRhKSByZXR1cm47XG5cbiAgICB0aGlzLl9pblBhY2tldENvdW50Kys7XG5cbiAgICAvLyBTZW5kIHJhdyBtZXNzYWdlIHRvIGxpc3RlbmVyXG4gICAgaWYgKHRoaXMub25SYXdNZXNzYWdlKSB7XG4gICAgICB0aGlzLm9uUmF3TWVzc2FnZShkYXRhKTtcbiAgICB9XG5cbiAgICBpZiAoZGF0YSA9PT0gJzAnKSB7XG4gICAgICAvLyBTZXJ2ZXIgcmVzcG9uc2UgdG8gYSBuZXR3b3JrIHByb2JlLlxuICAgICAgaWYgKHRoaXMub25OZXR3b3JrUHJvYmUpIHtcbiAgICAgICAgdGhpcy5vbk5ldHdvcmtQcm9iZSgpO1xuICAgICAgfVxuICAgICAgLy8gTm8gcHJvY2Vzc2luZyBpcyBuZWNlc3NhcnkuXG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGV0IHBrdCA9IEpTT04ucGFyc2UoZGF0YSwganNvblBhcnNlSGVscGVyKTtcbiAgICBpZiAoIXBrdCkge1xuICAgICAgdGhpcy5sb2dnZXIoXCJpbjogXCIgKyBkYXRhKTtcbiAgICAgIHRoaXMubG9nZ2VyKFwiRVJST1I6IGZhaWxlZCB0byBwYXJzZSBkYXRhXCIpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmxvZ2dlcihcImluOiBcIiArICh0aGlzLl90cmltTG9uZ1N0cmluZ3MgPyBKU09OLnN0cmluZ2lmeShwa3QsIGpzb25Mb2dnZXJIZWxwZXIpIDogZGF0YSkpO1xuXG4gICAgICAvLyBTZW5kIGNvbXBsZXRlIHBhY2tldCB0byBsaXN0ZW5lclxuICAgICAgaWYgKHRoaXMub25NZXNzYWdlKSB7XG4gICAgICAgIHRoaXMub25NZXNzYWdlKHBrdCk7XG4gICAgICB9XG5cbiAgICAgIGlmIChwa3QuY3RybCkge1xuICAgICAgICAvLyBIYW5kbGluZyB7Y3RybH0gbWVzc2FnZVxuICAgICAgICBpZiAodGhpcy5vbkN0cmxNZXNzYWdlKSB7XG4gICAgICAgICAgdGhpcy5vbkN0cmxNZXNzYWdlKHBrdC5jdHJsKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFJlc29sdmUgb3IgcmVqZWN0IGEgcGVuZGluZyBwcm9taXNlLCBpZiBhbnlcbiAgICAgICAgaWYgKHBrdC5jdHJsLmlkKSB7XG4gICAgICAgICAgZXhlY1Byb21pc2UocGt0LmN0cmwuaWQsIHBrdC5jdHJsLmNvZGUsIHBrdC5jdHJsLCBwa3QuY3RybC50ZXh0KTtcbiAgICAgICAgfVxuICAgICAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICBpZiAocGt0LmN0cmwuY29kZSA9PSAyMDUgJiYgcGt0LmN0cmwudGV4dCA9PSAnZXZpY3RlZCcpIHtcbiAgICAgICAgICAgIC8vIFVzZXIgZXZpY3RlZCBmcm9tIHRvcGljLlxuICAgICAgICAgICAgY29uc3QgdG9waWMgPSBjYWNoZUdldCgndG9waWMnLCBwa3QuY3RybC50b3BpYyk7XG4gICAgICAgICAgICBpZiAodG9waWMpIHtcbiAgICAgICAgICAgICAgdG9waWMuX3Jlc2V0U3ViKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIGlmIChwa3QuY3RybC5wYXJhbXMgJiYgcGt0LmN0cmwucGFyYW1zLndoYXQgPT0gJ2RhdGEnKSB7XG4gICAgICAgICAgICAvLyBBbGwgbWVzc2FnZXMgcmVjZWl2ZWQ6IFwicGFyYW1zXCI6e1wiY291bnRcIjoxMSxcIndoYXRcIjpcImRhdGFcIn0sXG4gICAgICAgICAgICBjb25zdCB0b3BpYyA9IGNhY2hlR2V0KCd0b3BpYycsIHBrdC5jdHJsLnRvcGljKTtcbiAgICAgICAgICAgIGlmICh0b3BpYykge1xuICAgICAgICAgICAgICB0b3BpYy5fYWxsTWVzc2FnZXNSZWNlaXZlZChwa3QuY3RybC5wYXJhbXMuY291bnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSBpZiAocGt0LmN0cmwucGFyYW1zICYmIHBrdC5jdHJsLnBhcmFtcy53aGF0ID09ICdzdWInKSB7XG4gICAgICAgICAgICAvLyBUaGUgdG9waWMgaGFzIG5vIHN1YnNjcmlwdGlvbnMuXG4gICAgICAgICAgICBjb25zdCB0b3BpYyA9IGNhY2hlR2V0KCd0b3BpYycsIHBrdC5jdHJsLnRvcGljKTtcbiAgICAgICAgICAgIGlmICh0b3BpYykge1xuICAgICAgICAgICAgICAvLyBUcmlnZ2VyIHRvcGljLm9uU3Vic1VwZGF0ZWQuXG4gICAgICAgICAgICAgIHRvcGljLl9wcm9jZXNzTWV0YVN1YihbXSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LCAwKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgIGlmIChwa3QubWV0YSkge1xuICAgICAgICAgICAgLy8gSGFuZGxpbmcgYSB7bWV0YX0gbWVzc2FnZS5cblxuICAgICAgICAgICAgLy8gUHJlZmVycmVkIEFQSTogUm91dGUgbWV0YSB0byB0b3BpYywgaWYgb25lIGlzIHJlZ2lzdGVyZWRcbiAgICAgICAgICAgIGNvbnN0IHRvcGljID0gY2FjaGVHZXQoJ3RvcGljJywgcGt0Lm1ldGEudG9waWMpO1xuICAgICAgICAgICAgaWYgKHRvcGljKSB7XG4gICAgICAgICAgICAgIHRvcGljLl9yb3V0ZU1ldGEocGt0Lm1ldGEpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAocGt0Lm1ldGEuaWQpIHtcbiAgICAgICAgICAgICAgZXhlY1Byb21pc2UocGt0Lm1ldGEuaWQsIDIwMCwgcGt0Lm1ldGEsICdNRVRBJyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIFNlY29uZGFyeSBBUEk6IGNhbGxiYWNrXG4gICAgICAgICAgICBpZiAodGhpcy5vbk1ldGFNZXNzYWdlKSB7XG4gICAgICAgICAgICAgIHRoaXMub25NZXRhTWVzc2FnZShwa3QubWV0YSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIGlmIChwa3QuZGF0YSkge1xuICAgICAgICAgICAgLy8gSGFuZGxpbmcge2RhdGF9IG1lc3NhZ2VcblxuICAgICAgICAgICAgLy8gUHJlZmVycmVkIEFQSTogUm91dGUgZGF0YSB0byB0b3BpYywgaWYgb25lIGlzIHJlZ2lzdGVyZWRcbiAgICAgICAgICAgIGNvbnN0IHRvcGljID0gY2FjaGVHZXQoJ3RvcGljJywgcGt0LmRhdGEudG9waWMpO1xuICAgICAgICAgICAgaWYgKHRvcGljKSB7XG4gICAgICAgICAgICAgIHRvcGljLl9yb3V0ZURhdGEocGt0LmRhdGEpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBTZWNvbmRhcnkgQVBJOiBDYWxsIGNhbGxiYWNrXG4gICAgICAgICAgICBpZiAodGhpcy5vbkRhdGFNZXNzYWdlKSB7XG4gICAgICAgICAgICAgIHRoaXMub25EYXRhTWVzc2FnZShwa3QuZGF0YSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIGlmIChwa3QucHJlcykge1xuICAgICAgICAgICAgLy8gSGFuZGxpbmcge3ByZXN9IG1lc3NhZ2VcblxuICAgICAgICAgICAgLy8gUHJlZmVycmVkIEFQSTogUm91dGUgcHJlc2VuY2UgdG8gdG9waWMsIGlmIG9uZSBpcyByZWdpc3RlcmVkXG4gICAgICAgICAgICBjb25zdCB0b3BpYyA9IGNhY2hlR2V0KCd0b3BpYycsIHBrdC5wcmVzLnRvcGljKTtcbiAgICAgICAgICAgIGlmICh0b3BpYykge1xuICAgICAgICAgICAgICB0b3BpYy5fcm91dGVQcmVzKHBrdC5wcmVzKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gU2Vjb25kYXJ5IEFQSSAtIGNhbGxiYWNrXG4gICAgICAgICAgICBpZiAodGhpcy5vblByZXNNZXNzYWdlKSB7XG4gICAgICAgICAgICAgIHRoaXMub25QcmVzTWVzc2FnZShwa3QucHJlcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIGlmIChwa3QuaW5mbykge1xuICAgICAgICAgICAgLy8ge2luZm99IG1lc3NhZ2UgLSByZWFkL3JlY2VpdmVkIG5vdGlmaWNhdGlvbnMgYW5kIGtleSBwcmVzc2VzXG5cbiAgICAgICAgICAgIC8vIFByZWZlcnJlZCBBUEk6IFJvdXRlIHtpbmZvfX0gdG8gdG9waWMsIGlmIG9uZSBpcyByZWdpc3RlcmVkXG4gICAgICAgICAgICBjb25zdCB0b3BpYyA9IGNhY2hlR2V0KCd0b3BpYycsIHBrdC5pbmZvLnRvcGljKTtcbiAgICAgICAgICAgIGlmICh0b3BpYykge1xuICAgICAgICAgICAgICB0b3BpYy5fcm91dGVJbmZvKHBrdC5pbmZvKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gU2Vjb25kYXJ5IEFQSSAtIGNhbGxiYWNrXG4gICAgICAgICAgICBpZiAodGhpcy5vbkluZm9NZXNzYWdlKSB7XG4gICAgICAgICAgICAgIHRoaXMub25JbmZvTWVzc2FnZShwa3QuaW5mbyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMubG9nZ2VyKFwiRVJST1I6IFVua25vd24gcGFja2V0IHJlY2VpdmVkLlwiKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0sIDApO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIFJlYWR5IHRvIHN0YXJ0IHNlbmRpbmcuXG4gIHRoaXMuX2Nvbm5lY3Rpb24ub25PcGVuID0gKCkgPT4ge1xuICAgIHRoaXMuaGVsbG8oKTtcbiAgfVxuXG4gIC8vIFdyYXBwZXIgZm9yIHRoZSByZWNvbm5lY3QgaXRlcmF0b3IgY2FsbGJhY2suXG4gIHRoaXMuX2Nvbm5lY3Rpb24ub25BdXRvcmVjb25uZWN0SXRlcmF0aW9uID0gKHRpbWVvdXQsIHByb21pc2UpID0+IHtcbiAgICBpZiAodGhpcy5vbkF1dG9yZWNvbm5lY3RJdGVyYXRpb24pIHtcbiAgICAgIHRoaXMub25BdXRvcmVjb25uZWN0SXRlcmF0aW9uKHRpbWVvdXQsIHByb21pc2UpO1xuICAgIH1cbiAgfVxuXG4gIHRoaXMuX2Nvbm5lY3Rpb24ub25EaXNjb25uZWN0ID0gKGVyciwgY29kZSkgPT4ge1xuICAgIHRoaXMuX2luUGFja2V0Q291bnQgPSAwO1xuICAgIHRoaXMuX3NlcnZlckluZm8gPSBudWxsO1xuICAgIHRoaXMuX2F1dGhlbnRpY2F0ZWQgPSBmYWxzZTtcblxuICAgIC8vIE1hcmsgYWxsIHRvcGljcyBhcyB1bnN1YnNjcmliZWRcbiAgICBjYWNoZU1hcCgob2JqLCBrZXkpID0+IHtcbiAgICAgIGlmIChrZXkubGFzdEluZGV4T2YoJ3RvcGljOicsIDApID09PSAwKSB7XG4gICAgICAgIG9iai5fcmVzZXRTdWIoKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIC8vIFJlamVjdCBhbGwgcGVuZGluZyBwcm9taXNlc1xuICAgIGZvciAobGV0IGtleSBpbiB0aGlzLl9wZW5kaW5nUHJvbWlzZXMpIHtcbiAgICAgIGxldCBjYWxsYmFja3MgPSB0aGlzLl9wZW5kaW5nUHJvbWlzZXNba2V5XTtcbiAgICAgIGlmIChjYWxsYmFja3MgJiYgY2FsbGJhY2tzLnJlamVjdCkge1xuICAgICAgICBjYWxsYmFja3MucmVqZWN0KGVycik7XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuX3BlbmRpbmdQcm9taXNlcyA9IHt9O1xuXG4gICAgaWYgKHRoaXMub25EaXNjb25uZWN0KSB7XG4gICAgICB0aGlzLm9uRGlzY29ubmVjdChlcnIpO1xuICAgIH1cbiAgfVxufTtcblxuLy8gU3RhdGljIG1ldGhvZHMuXG5cbi8qKlxuICogSGVscGVyIG1ldGhvZCB0byBwYWNrYWdlIGFjY291bnQgY3JlZGVudGlhbC5cbiAqIEBtZW1iZXJvZiBUaW5vZGVcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge1N0cmluZ3xPYmplY3R9IG1ldGggLSB2YWxpZGF0aW9uIG1ldGhvZCBvciBvYmplY3Qgd2l0aCB2YWxpZGF0aW9uIGRhdGEuXG4gKiBAcGFyYW0ge1N0cmluZz19IHZhbCAtIHZhbGlkYXRpb24gdmFsdWUgKGUuZy4gZW1haWwgb3IgcGhvbmUgbnVtYmVyKS5cbiAqIEBwYXJhbSB7T2JqZWN0PX0gcGFyYW1zIC0gdmFsaWRhdGlvbiBwYXJhbWV0ZXJzLlxuICogQHBhcmFtIHtTdHJpbmc9fSByZXNwIC0gdmFsaWRhdGlvbiByZXNwb25zZS5cbiAqXG4gKiBAcmV0dXJucyB7QXJyYXl9IGFycmF5IHdpdGggYSBzaW5nbGUgY3JlZGVudGFpbCBvciBudWxsIGlmIG5vIHZhbGlkIGNyZWRlbnRpYWxzIHdlcmUgZ2l2ZW4uXG4gKi9cblRpbm9kZS5jcmVkZW50aWFsID0gZnVuY3Rpb24obWV0aCwgdmFsLCBwYXJhbXMsIHJlc3ApIHtcbiAgaWYgKHR5cGVvZiBtZXRoID09ICdvYmplY3QnKSB7XG4gICAgKHtcbiAgICAgIHZhbCxcbiAgICAgIHBhcmFtcyxcbiAgICAgIHJlc3AsXG4gICAgICBtZXRoXG4gICAgfSA9IG1ldGgpO1xuICB9XG4gIGlmIChtZXRoICYmICh2YWwgfHwgcmVzcCkpIHtcbiAgICByZXR1cm4gW3tcbiAgICAgICdtZXRoJzogbWV0aCxcbiAgICAgICd2YWwnOiB2YWwsXG4gICAgICAncmVzcCc6IHJlc3AsXG4gICAgICAncGFyYW1zJzogcGFyYW1zXG4gICAgfV07XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59O1xuXG4vKipcbiAqIERldGVybWluZSB0b3BpYyB0eXBlIGZyb20gdG9waWMncyBuYW1lOiBncnAsIHAycCwgbWUsIGZuZC5cbiAqIEBtZW1iZXJvZiBUaW5vZGVcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gbmFtZSAtIE5hbWUgb2YgdGhlIHRvcGljIHRvIHRlc3QuXG4gKiBAcmV0dXJucyB7c3RyaW5nfSBPbmUgb2YgPHR0PidtZSc8L3R0PiwgPHR0PidncnAnPC90dD4sIDx0dD4ncDJwJzwvdHQ+IG9yIDx0dD51bmRlZmluZWQ8L3R0Pi5cbiAqL1xuVGlub2RlLnRvcGljVHlwZSA9IGZ1bmN0aW9uKG5hbWUpIHtcbiAgY29uc3QgdHlwZXMgPSB7XG4gICAgJ21lJzogJ21lJyxcbiAgICAnZm5kJzogJ2ZuZCcsXG4gICAgJ2dycCc6ICdncnAnLFxuICAgICduZXcnOiAnZ3JwJyxcbiAgICAndXNyJzogJ3AycCcsXG4gICAgJ3N5cyc6ICdzeXMnXG4gIH07XG4gIHJldHVybiB0eXBlc1sodHlwZW9mIG5hbWUgPT0gJ3N0cmluZycpID8gbmFtZS5zdWJzdHJpbmcoMCwgMykgOiAneHh4J107XG59O1xuXG4vKipcbiAqIENoZWNrIGlmIHRoZSB0b3BpYyBuYW1lIGlzIGEgbmFtZSBvZiBhIG5ldyB0b3BpYy5cbiAqIEBtZW1iZXJvZiBUaW5vZGVcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gbmFtZSAtIHRvcGljIG5hbWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gdHJ1ZSBpZiB0aGUgbmFtZSBpcyBhIG5hbWUgb2YgYSBuZXcgdG9waWMuXG4gKi9cblRpbm9kZS5pc05ld0dyb3VwVG9waWNOYW1lID0gZnVuY3Rpb24obmFtZSkge1xuICByZXR1cm4gKHR5cGVvZiBuYW1lID09ICdzdHJpbmcnKSAmJiBuYW1lLnN1YnN0cmluZygwLCAzKSA9PSBUT1BJQ19ORVc7XG59O1xuXG4vKipcbiAqIFJldHVybiBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY3VycmVudCB2ZXJzaW9uIG9mIHRoaXMgVGlub2RlIGNsaWVudCBsaWJyYXJ5LlxuICogQG1lbWJlcm9mIFRpbm9kZVxuICogQHN0YXRpY1xuICpcbiAqIEByZXR1cm5zIHtzdHJpbmd9IHNlbWFudGljIHZlcnNpb24gb2YgdGhlIGxpYnJhcnksIGUuZy4gJzAuMTUuNS1yYzEnLlxuICovXG5UaW5vZGUuZ2V0VmVyc2lvbiA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gVkVSU0lPTjtcbn07XG5cbi8qKlxuICogVG8gdXNlIGZvciBub24gYnJvd3NlciBhcHAsIGFsbG93IHRvIHNwZWNpZnkgV2ViU29ja2V0IHByb3ZpZGVyXG4gKiBAcGFyYW0gcHJvdmlkZXIgd2ViU29ja2V0IHByb3ZpZGVyIGV4OiBmb3Igbm9kZUpTIHJlcXVpcmUoJ3dzJylcbiAqIEBtZW1iZXJvZiBUaW5vZGVcbiAqIEBzdGF0aWNcbiAqXG4gKi9cblRpbm9kZS5zZXRXZWJTb2NrZXRQcm92aWRlciA9IGZ1bmN0aW9uKHByb3ZpZGVyKSB7XG4gIFdlYlNvY2tldFByb3ZpZGVyID0gcHJvdmlkZXI7XG59O1xuXG4vKipcbiAqIFJldHVybiBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY3VycmVudCBuYW1lIGFuZCB2ZXJzaW9uIG9mIHRoaXMgVGlub2RlIGxpYnJhcnkuXG4gKiBAbWVtYmVyb2YgVGlub2RlXG4gKiBAc3RhdGljXG4gKlxuICogQHJldHVybnMge3N0cmluZ30gdGhlIG5hbWUgb2YgdGhlIGxpYnJhcnkgYW5kIGl0J3MgdmVyc2lvbi5cbiAqL1xuVGlub2RlLmdldExpYnJhcnkgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIExJQlJBUlk7XG59O1xuXG4vLyBFeHBvcnRlZCBjb25zdGFudHNcblRpbm9kZS5NRVNTQUdFX1NUQVRVU19OT05FID0gTUVTU0FHRV9TVEFUVVNfTk9ORTtcblRpbm9kZS5NRVNTQUdFX1NUQVRVU19RVUVVRUQgPSBNRVNTQUdFX1NUQVRVU19RVUVVRUQ7XG5UaW5vZGUuTUVTU0FHRV9TVEFUVVNfU0VORElORyA9IE1FU1NBR0VfU1RBVFVTX1NFTkRJTkc7XG5UaW5vZGUuTUVTU0FHRV9TVEFUVVNfRkFJTEVEID0gTUVTU0FHRV9TVEFUVVNfRkFJTEVEO1xuVGlub2RlLk1FU1NBR0VfU1RBVFVTX1NFTlQgPSBNRVNTQUdFX1NUQVRVU19TRU5UO1xuVGlub2RlLk1FU1NBR0VfU1RBVFVTX1JFQ0VJVkVEID0gTUVTU0FHRV9TVEFUVVNfUkVDRUlWRUQ7XG5UaW5vZGUuTUVTU0FHRV9TVEFUVVNfUkVBRCA9IE1FU1NBR0VfU1RBVFVTX1JFQUQ7XG5UaW5vZGUuTUVTU0FHRV9TVEFUVVNfVE9fTUUgPSBNRVNTQUdFX1NUQVRVU19UT19NRTtcblxuLy8gVW5pY29kZSBbZGVsXSBzeW1ib2wuXG5UaW5vZGUuREVMX0NIQVIgPSAnXFx1MjQyMSc7XG5cbi8qKlxuICogQ2hlY2sgaWYgdGhlIGdpdmVuIHN0cmluZyByZXByc2VudHMgTlVMTCB2YWx1ZS5cbiAqIEBtZW1iZXJvZiBUaW5vZGVcbiAqIEBzdGF0aWNcbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gc3RyIC0gc3RyaW5nIHRvIGNoZWNrIGZvciBudWxsIHZhbHVlLlxuICpcbiAqIEByZXR1cm5zIHtib29sZWFufSB0cnVlIGlmIHN0cmluZyBpcyBudWxsLCBmYWxzZSBvdGhlcndpc2UuXG4gKi9cblRpbm9kZS5pc051bGxWYWx1ZSA9IGZ1bmN0aW9uKHN0cikge1xuICByZXR1cm4gc3RyID09PSBUaW5vZGUuREVMX0NIQVI7XG59O1xuXG4vLyBQdWJsaWMgbWV0aG9kcztcblRpbm9kZS5wcm90b3R5cGUgPSB7XG4gIC8qKlxuICAgKiBDb25uZWN0IHRvIHRoZSBzZXJ2ZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBob3N0XyAtIG5hbWUgb2YgdGhlIGhvc3QgdG8gY29ubmVjdCB0by5cbiAgICpcbiAgICogQHJldHVybiB7UHJvbWlzZX0gUHJvbWlzZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHRoZSBjb25uZWN0aW9uIGNhbGwgY29tcGxldGVzOlxuICAgKiA8dHQ+cmVzb2x2ZSgpPC90dD4gaXMgY2FsbGVkIHdpdGhvdXQgcGFyYW1ldGVycywgPHR0PnJlamVjdCgpPC90dD4gcmVjZWl2ZXMgdGhlIDx0dD5FcnJvcjwvdHQ+IGFzIGEgc2luZ2xlIHBhcmFtZXRlci5cbiAgICovXG4gIGNvbm5lY3Q6IGZ1bmN0aW9uKGhvc3RfKSB7XG4gICAgcmV0dXJuIHRoaXMuX2Nvbm5lY3Rpb24uY29ubmVjdChob3N0Xyk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEF0dGVtcHQgdG8gcmVjb25uZWN0IHRvIHRoZSBzZXJ2ZXIgaW1tZWRpYXRlbHkuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBmb3JjZSAtIHJlY29ubmVjdCBldmVuIGlmIHRoZXJlIGlzIGEgY29ubmVjdGlvbiBhbHJlYWR5LlxuICAgKi9cbiAgcmVjb25uZWN0OiBmdW5jdGlvbihmb3JjZSkge1xuICAgIHRoaXMuX2Nvbm5lY3Rpb24ucmVjb25uZWN0KGZvcmNlKTtcbiAgfSxcblxuICAvKipcbiAgICogRGlzY29ubmVjdCBmcm9tIHRoZSBzZXJ2ZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqL1xuICBkaXNjb25uZWN0OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLl9jb25uZWN0aW9uLmRpc2Nvbm5lY3QoKTtcbiAgfSxcblxuICAvKipcbiAgICogU2VuZCBhIG5ldHdvcmsgcHJvYmUgbWVzc2FnZSB0byBtYWtlIHN1cmUgdGhlIGNvbm5lY3Rpb24gaXMgYWxpdmUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqL1xuICBuZXR3b3JrUHJvYmU6IGZ1bmN0aW9uKCkge1xuICAgIHRoaXMuX2Nvbm5lY3Rpb24ucHJvYmUoKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgZm9yIGxpdmUgY29ubmVjdGlvbiB0byBzZXJ2ZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEByZXR1cm5zIHtCb29sZWFufSB0cnVlIGlmIHRoZXJlIGlzIGEgbGl2ZSBjb25uZWN0aW9uLCBmYWxzZSBvdGhlcndpc2UuXG4gICAqL1xuICBpc0Nvbm5lY3RlZDogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHRoaXMuX2Nvbm5lY3Rpb24uaXNDb25uZWN0ZWQoKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgY29ubmVjdGlvbiBpcyBhdXRoZW50aWNhdGVkIChsYXN0IGxvZ2luIHdhcyBzdWNjZXNzZnVsKS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICogQHJldHVybnMge2Jvb2xlYW59IHRydWUgaWYgYXV0aGVudGljYXRlZCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgKi9cbiAgaXNBdXRoZW50aWNhdGVkOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5fYXV0aGVudGljYXRlZDtcbiAgfSxcblxuICAvKipcbiAgICogQHR5cGVkZWYgQWNjb3VudFBhcmFtc1xuICAgKiBAbWVtYmVyb2YgVGlub2RlXG4gICAqIEB0eXBlIE9iamVjdFxuICAgKiBAcHJvcGVydHkge1Rpbm9kZS5EZWZBY3M9fSBkZWZhY3MgLSBEZWZhdWx0IGFjY2VzcyBwYXJhbWV0ZXJzIGZvciB1c2VyJ3MgPHR0Pm1lPC90dD4gdG9waWMuXG4gICAqIEBwcm9wZXJ0eSB7T2JqZWN0PX0gcHVibGljIC0gUHVibGljIGFwcGxpY2F0aW9uLWRlZmluZWQgZGF0YSBleHBvc2VkIG9uIDx0dD5tZTwvdHQ+IHRvcGljLlxuICAgKiBAcHJvcGVydHkge09iamVjdD19IHByaXZhdGUgLSBQcml2YXRlIGFwcGxpY2F0aW9uLWRlZmluZWQgZGF0YSBhY2Nlc3NpYmxlIG9uIDx0dD5tZTwvdHQ+IHRvcGljLlxuICAgKiBAcHJvcGVydHkge0FycmF5fSB0YWdzIC0gYXJyYXkgb2Ygc3RyaW5nIHRhZ3MgZm9yIHVzZXIgZGlzY292ZXJ5LlxuICAgKiBAcHJvcGVydHkge3N0cmluZz19IHRva2VuIC0gYXV0aGVudGljYXRpb24gdG9rZW4gdG8gdXNlLlxuICAgKi9cbiAgLyoqXG4gICAqIEB0eXBlZGVmIERlZkFjc1xuICAgKiBAbWVtYmVyb2YgVGlub2RlXG4gICAqIEB0eXBlIE9iamVjdFxuICAgKiBAcHJvcGVydHkge3N0cmluZz19IGF1dGggLSBBY2Nlc3MgbW9kZSBmb3IgPHR0Pm1lPC90dD4gZm9yIGF1dGhlbnRpY2F0ZWQgdXNlcnMuXG4gICAqIEBwcm9wZXJ0eSB7c3RyaW5nPX0gYW5vbiAtIEFjY2VzcyBtb2RlIGZvciA8dHQ+bWU8L3R0PiAgYW5vbnltb3VzIHVzZXJzLlxuICAgKi9cblxuICAvKipcbiAgICogQ3JlYXRlIG9yIHVwZGF0ZSBhbiBhY2NvdW50LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdWlkIC0gVXNlciBpZCB0byB1cGRhdGVcbiAgICogQHBhcmFtIHtTdHJpbmd9IHNjaGVtZSAtIEF1dGhlbnRpY2F0aW9uIHNjaGVtZTsgPHR0PlwiYmFzaWNcIjwvdHQ+IGFuZCA8dHQ+XCJhbm9ueW1vdXNcIjwvdHQ+IGFyZSB0aGUgY3VycmVudGx5IHN1cHBvcnRlZCBzY2hlbWVzLlxuICAgKiBAcGFyYW0ge1N0cmluZ30gc2VjcmV0IC0gQXV0aGVudGljYXRpb24gc2VjcmV0LCBhc3N1bWVkIHRvIGJlIGFscmVhZHkgYmFzZTY0IGVuY29kZWQuXG4gICAqIEBwYXJhbSB7Qm9vbGVhbj19IGxvZ2luIC0gVXNlIG5ldyBhY2NvdW50IHRvIGF1dGhlbnRpY2F0ZSBjdXJyZW50IHNlc3Npb25cbiAgICogQHBhcmFtIHtUaW5vZGUuQWNjb3VudFBhcmFtcz19IHBhcmFtcyAtIFVzZXIgZGF0YSB0byBwYXNzIHRvIHRoZSBzZXJ2ZXIuXG4gICAqL1xuICBhY2NvdW50OiBmdW5jdGlvbih1aWQsIHNjaGVtZSwgc2VjcmV0LCBsb2dpbiwgcGFyYW1zKSB7XG4gICAgY29uc3QgcGt0ID0gdGhpcy5pbml0UGFja2V0KCdhY2MnKTtcbiAgICBwa3QuYWNjLnVzZXIgPSB1aWQ7XG4gICAgcGt0LmFjYy5zY2hlbWUgPSBzY2hlbWU7XG4gICAgcGt0LmFjYy5zZWNyZXQgPSBzZWNyZXQ7XG4gICAgLy8gTG9nIGluIHRvIHRoZSBuZXcgYWNjb3VudCB1c2luZyBzZWxlY3RlZCBzY2hlbWVcbiAgICBwa3QuYWNjLmxvZ2luID0gbG9naW47XG5cbiAgICBpZiAocGFyYW1zKSB7XG4gICAgICBwa3QuYWNjLmRlc2MuZGVmYWNzID0gcGFyYW1zLmRlZmFjcztcbiAgICAgIHBrdC5hY2MuZGVzYy5wdWJsaWMgPSBwYXJhbXMucHVibGljO1xuICAgICAgcGt0LmFjYy5kZXNjLnByaXZhdGUgPSBwYXJhbXMucHJpdmF0ZTtcblxuICAgICAgcGt0LmFjYy50YWdzID0gcGFyYW1zLnRhZ3M7XG4gICAgICBwa3QuYWNjLmNyZWQgPSBwYXJhbXMuY3JlZDtcblxuICAgICAgcGt0LmFjYy50b2tlbiA9IHBhcmFtcy50b2tlbjtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LmFjYy5pZCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIG5ldyB1c2VyLiBXcmFwcGVyIGZvciB7QGxpbmsgVGlub2RlI2FjY291bnR9LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gc2NoZW1lIC0gQXV0aGVudGljYXRpb24gc2NoZW1lOyA8dHQ+XCJiYXNpY1wiPC90dD4gaXMgdGhlIG9ubHkgY3VycmVudGx5IHN1cHBvcnRlZCBzY2hlbWUuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzZWNyZXQgLSBBdXRoZW50aWNhdGlvbi5cbiAgICogQHBhcmFtIHtCb29sZWFuPX0gbG9naW4gLSBVc2UgbmV3IGFjY291bnQgdG8gYXV0aGVudGljYXRlIGN1cnJlbnQgc2Vzc2lvblxuICAgKiBAcGFyYW0ge1Rpbm9kZS5BY2NvdW50UGFyYW1zPX0gcGFyYW1zIC0gVXNlciBkYXRhIHRvIHBhc3MgdG8gdGhlIHNlcnZlci5cbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHNlcnZlciByZXBseSBpcyByZWNlaXZlZC5cbiAgICovXG4gIGNyZWF0ZUFjY291bnQ6IGZ1bmN0aW9uKHNjaGVtZSwgc2VjcmV0LCBsb2dpbiwgcGFyYW1zKSB7XG4gICAgbGV0IHByb21pc2UgPSB0aGlzLmFjY291bnQoVVNFUl9ORVcsIHNjaGVtZSwgc2VjcmV0LCBsb2dpbiwgcGFyYW1zKTtcbiAgICBpZiAobG9naW4pIHtcbiAgICAgIHByb21pc2UgPSBwcm9taXNlLnRoZW4oKGN0cmwpID0+IHtcbiAgICAgICAgdGhpcy5sb2dpblN1Y2Nlc3NmdWwoY3RybCk7XG4gICAgICAgIHJldHVybiBjdHJsO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBwcm9taXNlO1xuICB9LFxuXG4gIC8qKlxuICAgKiBDcmVhdGUgdXNlciB3aXRoICdiYXNpYycgYXV0aGVudGljYXRpb24gc2NoZW1lIGFuZCBpbW1lZGlhdGVseVxuICAgKiB1c2UgaXQgZm9yIGF1dGhlbnRpY2F0aW9uLiBXcmFwcGVyIGZvciB7QGxpbmsgVGlub2RlI2FjY291bnR9LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gdXNlcm5hbWUgLSBMb2dpbiB0byB1c2UgZm9yIHRoZSBuZXcgYWNjb3VudC5cbiAgICogQHBhcmFtIHtzdHJpbmd9IHBhc3N3b3JkIC0gVXNlcidzIHBhc3N3b3JkLlxuICAgKiBAcGFyYW0ge1Rpbm9kZS5BY2NvdW50UGFyYW1zPX0gcGFyYW1zIC0gVXNlciBkYXRhIHRvIHBhc3MgdG8gdGhlIHNlcnZlci5cbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHNlcnZlciByZXBseSBpcyByZWNlaXZlZC5cbiAgICovXG4gIGNyZWF0ZUFjY291bnRCYXNpYzogZnVuY3Rpb24odXNlcm5hbWUsIHBhc3N3b3JkLCBwYXJhbXMpIHtcbiAgICAvLyBNYWtlIHN1cmUgd2UgYXJlIG5vdCB1c2luZyAnbnVsbCcgb3IgJ3VuZGVmaW5lZCc7XG4gICAgdXNlcm5hbWUgPSB1c2VybmFtZSB8fCAnJztcbiAgICBwYXNzd29yZCA9IHBhc3N3b3JkIHx8ICcnO1xuICAgIHJldHVybiB0aGlzLmNyZWF0ZUFjY291bnQoJ2Jhc2ljJyxcbiAgICAgIGI2NEVuY29kZVVuaWNvZGUodXNlcm5hbWUgKyAnOicgKyBwYXNzd29yZCksIHRydWUsIHBhcmFtcyk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFVwZGF0ZSB1c2VyJ3MgY3JlZGVudGlhbHMgZm9yICdiYXNpYycgYXV0aGVudGljYXRpb24gc2NoZW1lLiBXcmFwcGVyIGZvciB7QGxpbmsgVGlub2RlI2FjY291bnR9LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gdWlkIC0gVXNlciBJRCB0byB1cGRhdGUuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB1c2VybmFtZSAtIExvZ2luIHRvIHVzZSBmb3IgdGhlIG5ldyBhY2NvdW50LlxuICAgKiBAcGFyYW0ge3N0cmluZ30gcGFzc3dvcmQgLSBVc2VyJ3MgcGFzc3dvcmQuXG4gICAqIEBwYXJhbSB7VGlub2RlLkFjY291bnRQYXJhbXM9fSBwYXJhbXMgLSBkYXRhIHRvIHBhc3MgdG8gdGhlIHNlcnZlci5cbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHNlcnZlciByZXBseSBpcyByZWNlaXZlZC5cbiAgICovXG4gIHVwZGF0ZUFjY291bnRCYXNpYzogZnVuY3Rpb24odWlkLCB1c2VybmFtZSwgcGFzc3dvcmQsIHBhcmFtcykge1xuICAgIC8vIE1ha2Ugc3VyZSB3ZSBhcmUgbm90IHVzaW5nICdudWxsJyBvciAndW5kZWZpbmVkJztcbiAgICB1c2VybmFtZSA9IHVzZXJuYW1lIHx8ICcnO1xuICAgIHBhc3N3b3JkID0gcGFzc3dvcmQgfHwgJyc7XG4gICAgcmV0dXJuIHRoaXMuYWNjb3VudCh1aWQsICdiYXNpYycsXG4gICAgICBiNjRFbmNvZGVVbmljb2RlKHVzZXJuYW1lICsgJzonICsgcGFzc3dvcmQpLCBmYWxzZSwgcGFyYW1zKTtcbiAgfSxcblxuICAvKipcbiAgICogU2VuZCBoYW5kc2hha2UgdG8gdGhlIHNlcnZlci5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHNlcnZlciByZXBseSBpcyByZWNlaXZlZC5cbiAgICovXG4gIGhlbGxvOiBmdW5jdGlvbigpIHtcbiAgICBjb25zdCBwa3QgPSB0aGlzLmluaXRQYWNrZXQoJ2hpJyk7XG5cbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LmhpLmlkKVxuICAgICAgLnRoZW4oKGN0cmwpID0+IHtcbiAgICAgICAgLy8gUmVzZXQgYmFja29mZiBjb3VudGVyIG9uIHN1Y2Nlc3NmdWwgY29ubmVjdGlvbi5cbiAgICAgICAgdGhpcy5fY29ubmVjdGlvbi5iYWNrb2ZmUmVzZXQoKTtcblxuICAgICAgICAvLyBTZXJ2ZXIgcmVzcG9uc2UgY29udGFpbnMgc2VydmVyIHByb3RvY29sIHZlcnNpb24sIGJ1aWxkLCBjb25zdHJhaW50cyxcbiAgICAgICAgLy8gc2Vzc2lvbiBJRCBmb3IgbG9uZyBwb2xsaW5nLiBTYXZlIHRoZW0uXG4gICAgICAgIGlmIChjdHJsLnBhcmFtcykge1xuICAgICAgICAgIHRoaXMuX3NlcnZlckluZm8gPSBjdHJsLnBhcmFtcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0aGlzLm9uQ29ubmVjdCkge1xuICAgICAgICAgIHRoaXMub25Db25uZWN0KCk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gY3RybDtcbiAgICAgIH0pLmNhdGNoKChlcnIpID0+IHtcbiAgICAgICAgdGhpcy5fY29ubmVjdGlvbi5yZWNvbm5lY3QodHJ1ZSk7XG5cbiAgICAgICAgaWYgKHRoaXMub25EaXNjb25uZWN0KSB7XG4gICAgICAgICAgdGhpcy5vbkRpc2Nvbm5lY3QoZXJyKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFNldCBvciByZWZyZXNoIHRoZSBwdXNoIG5vdGlmaWNhdGlvbnMvZGV2aWNlIHRva2VuLiBJZiB0aGUgY2xpZW50IGlzIGNvbm5lY3RlZCxcbiAgICogdGhlIGRldmljZVRva2VuIGNhbiBiZSBzZW50IHRvIHRoZSBzZXJ2ZXIuXG4gICAqXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBkdCAtIHRva2VuIG9idGFpbmVkIGZyb20gdGhlIHByb3ZpZGVyLlxuICAgKiBAcGFyYW0ge2Jvb2xlYW59IHNlbmRUb1NlcnZlciAtIGlmIHRydWUsIHNlbmQgZHQgdG8gc2VydmVyIGltbWVkaWF0ZWx5LlxuICAgKlxuICAgKiBAcGFyYW0gdHJ1ZSBpZiBhdHRlbXB0IHdhcyBtYWRlIHRvIHNlbmQgdGhlIHRva2VuIHRvIHRoZSBzZXJ2ZXIuXG4gICAqL1xuICBzZXREZXZpY2VUb2tlbjogZnVuY3Rpb24oZHQsIHNlbmRUb1NlcnZlcikge1xuICAgIGxldCBzZW50ID0gZmFsc2U7XG4gICAgaWYgKGR0ICYmIGR0ICE9IHRoaXMuX2RldmljZVRva2VuKSB7XG4gICAgICB0aGlzLl9kZXZpY2VUb2tlbiA9IGR0O1xuICAgICAgaWYgKHNlbmRUb1NlcnZlciAmJiB0aGlzLmlzQ29ubmVjdGVkKCkgJiYgdGhpcy5pc0F1dGhlbnRpY2F0ZWQoKSkge1xuICAgICAgICB0aGlzLnNlbmQoe1xuICAgICAgICAgICdoaSc6IHtcbiAgICAgICAgICAgICdkZXYnOiBkdFxuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIHNlbnQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gc2VudDtcbiAgfSxcblxuICAvKipcbiAgICogQXV0aGVudGljYXRlIGN1cnJlbnQgc2Vzc2lvbi5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHNjaGVtZSAtIEF1dGhlbnRpY2F0aW9uIHNjaGVtZTsgPHR0PlwiYmFzaWNcIjwvdHQ+IGlzIHRoZSBvbmx5IGN1cnJlbnRseSBzdXBwb3J0ZWQgc2NoZW1lLlxuICAgKiBAcGFyYW0ge1N0cmluZ30gc2VjcmV0IC0gQXV0aGVudGljYXRpb24gc2VjcmV0LCBhc3N1bWVkIHRvIGJlIGFscmVhZHkgYmFzZTY0IGVuY29kZWQuXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHdoaWNoIHdpbGwgYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgd2hlbiBzZXJ2ZXIgcmVwbHkgaXMgcmVjZWl2ZWQuXG4gICAqL1xuICBsb2dpbjogZnVuY3Rpb24oc2NoZW1lLCBzZWNyZXQsIGNyZWQpIHtcbiAgICBjb25zdCBwa3QgPSB0aGlzLmluaXRQYWNrZXQoJ2xvZ2luJyk7XG4gICAgcGt0LmxvZ2luLnNjaGVtZSA9IHNjaGVtZTtcbiAgICBwa3QubG9naW4uc2VjcmV0ID0gc2VjcmV0O1xuICAgIHBrdC5sb2dpbi5jcmVkID0gY3JlZDtcblxuICAgIHJldHVybiB0aGlzLnNlbmQocGt0LCBwa3QubG9naW4uaWQpXG4gICAgICAudGhlbigoY3RybCkgPT4ge1xuICAgICAgICB0aGlzLmxvZ2luU3VjY2Vzc2Z1bChjdHJsKTtcbiAgICAgICAgcmV0dXJuIGN0cmw7XG4gICAgICB9KTtcbiAgfSxcblxuICAvKipcbiAgICogV3JhcHBlciBmb3Ige0BsaW5rIFRpbm9kZSNsb2dpbn0gd2l0aCBiYXNpYyBhdXRoZW50aWNhdGlvblxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdW5hbWUgLSBVc2VyIG5hbWUuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBwYXNzd29yZCAgLSBQYXNzd29yZC5cbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCBvbiByZWNlaXZpbmcgc2VydmVyIHJlcGx5LlxuICAgKi9cbiAgbG9naW5CYXNpYzogZnVuY3Rpb24odW5hbWUsIHBhc3N3b3JkLCBjcmVkKSB7XG4gICAgcmV0dXJuIHRoaXMubG9naW4oJ2Jhc2ljJywgYjY0RW5jb2RlVW5pY29kZSh1bmFtZSArICc6JyArIHBhc3N3b3JkKSwgY3JlZClcbiAgICAgIC50aGVuKChjdHJsKSA9PiB7XG4gICAgICAgIHRoaXMuX2xvZ2luID0gdW5hbWU7XG4gICAgICAgIHJldHVybiBjdHJsO1xuICAgICAgfSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFdyYXBwZXIgZm9yIHtAbGluayBUaW5vZGUjbG9naW59IHdpdGggdG9rZW4gYXV0aGVudGljYXRpb25cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHRva2VuIC0gVG9rZW4gcmVjZWl2ZWQgaW4gcmVzcG9uc2UgdG8gZWFybGllciBsb2dpbi5cbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCBvbiByZWNlaXZpbmcgc2VydmVyIHJlcGx5LlxuICAgKi9cbiAgbG9naW5Ub2tlbjogZnVuY3Rpb24odG9rZW4sIGNyZWQpIHtcbiAgICByZXR1cm4gdGhpcy5sb2dpbigndG9rZW4nLCB0b2tlbiwgY3JlZCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFNlbmQgYSByZXF1ZXN0IGZvciByZXNldHRpbmcgYW4gYXV0aGVudGljYXRpb24gc2VjcmV0LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gc2NoZW1lIC0gYXV0aGVudGljYXRpb24gc2NoZW1lIHRvIHJlc2V0LlxuICAgKiBAcGFyYW0ge1N0cmluZ30gbWV0aG9kIC0gbWV0aG9kIHRvIHVzZSBmb3IgcmVzZXR0aW5nIHRoZSBzZWNyZXQsIHN1Y2ggYXMgXCJlbWFpbFwiIG9yIFwidGVsXCIuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB2YWx1ZSAtIHZhbHVlIG9mIHRoZSBjcmVkZW50aWFsIHRvIHVzZSwgYSBzcGVjaWZpYyBlbWFpbCBhZGRyZXNzIG9yIGEgcGhvbmUgbnVtYmVyLlxuICAgKlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gUHJvbWlzZSB3aGljaCB3aWxsIGJlIHJlc29sdmVkL3JlamVjdGVkIG9uIHJlY2VpdmluZyB0aGUgc2VydmVyIHJlcGx5LlxuICAgKi9cbiAgcmVxdWVzdFJlc2V0QXV0aFNlY3JldDogZnVuY3Rpb24oc2NoZW1lLCBtZXRob2QsIHZhbHVlKSB7XG4gICAgcmV0dXJuIHRoaXMubG9naW4oJ3Jlc2V0JywgYjY0RW5jb2RlVW5pY29kZShzY2hlbWUgKyAnOicgKyBtZXRob2QgKyAnOicgKyB2YWx1ZSkpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBAdHlwZWRlZiBBdXRoVG9rZW5cbiAgICogQG1lbWJlcm9mIFRpbm9kZVxuICAgKiBAdHlwZSBPYmplY3RcbiAgICogQHByb3BlcnR5IHtTdHJpbmd9IHRva2VuIC0gVG9rZW4gdmFsdWUuXG4gICAqIEBwcm9wZXJ0eSB7RGF0ZX0gZXhwaXJlcyAtIFRva2VuIGV4cGlyYXRpb24gdGltZS5cbiAgICovXG4gIC8qKlxuICAgKiBHZXQgc3RvcmVkIGF1dGhlbnRpY2F0aW9uIHRva2VuLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLkF1dGhUb2tlbn0gYXV0aGVudGljYXRpb24gdG9rZW4uXG4gICAqL1xuICBnZXRBdXRoVG9rZW46IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLl9hdXRoVG9rZW4gJiYgKHRoaXMuX2F1dGhUb2tlbi5leHBpcmVzLmdldFRpbWUoKSA+IERhdGUubm93KCkpKSB7XG4gICAgICByZXR1cm4gdGhpcy5fYXV0aFRva2VuO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLl9hdXRoVG9rZW4gPSBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbiAgfSxcblxuICAvKipcbiAgICogQXBwbGljYXRpb24gbWF5IHByb3ZpZGUgYSBzYXZlZCBhdXRoZW50aWNhdGlvbiB0b2tlbi5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHBhcmFtIHtUaW5vZGUuQXV0aFRva2VufSB0b2tlbiAtIGF1dGhlbnRpY2F0aW9uIHRva2VuLlxuICAgKi9cbiAgc2V0QXV0aFRva2VuOiBmdW5jdGlvbih0b2tlbikge1xuICAgIHRoaXMuX2F1dGhUb2tlbiA9IHRva2VuO1xuICB9LFxuXG4gIC8qKlxuICAgKiBAdHlwZWRlZiBTZXRQYXJhbXNcbiAgICogQG1lbWJlcm9mIFRpbm9kZVxuICAgKiBAcHJvcGVydHkge1Rpbm9kZS5TZXREZXNjPX0gZGVzYyAtIFRvcGljIGluaXRpYWxpemF0aW9uIHBhcmFtZXRlcnMgd2hlbiBjcmVhdGluZyBhIG5ldyB0b3BpYyBvciBhIG5ldyBzdWJzY3JpcHRpb24uXG4gICAqIEBwcm9wZXJ0eSB7VGlub2RlLlNldFN1Yj19IHN1YiAtIFN1YnNjcmlwdGlvbiBpbml0aWFsaXphdGlvbiBwYXJhbWV0ZXJzLlxuICAgKi9cbiAgLyoqXG4gICAqIEB0eXBlZGVmIFNldERlc2NcbiAgICogQG1lbWJlcm9mIFRpbm9kZVxuICAgKiBAcHJvcGVydHkge1Rpbm9kZS5EZWZBY3M9fSBkZWZhY3MgLSBEZWZhdWx0IGFjY2VzcyBtb2RlLlxuICAgKiBAcHJvcGVydHkge09iamVjdD19IHB1YmxpYyAtIEZyZWUtZm9ybSB0b3BpYyBkZXNjcmlwdGlvbiwgcHVibGljYWxseSBhY2Nlc3NpYmxlLlxuICAgKiBAcHJvcGVydHkge09iamVjdD19IHByaXZhdGUgLSBGcmVlLWZvcm0gdG9waWMgZGVzY3JpcHRpb25hY2Nlc3NpYmxlIG9ubHkgdG8gdGhlIG93bmVyLlxuICAgKi9cbiAgLyoqXG4gICAqIEB0eXBlZGVmIFNldFN1YlxuICAgKiBAbWVtYmVyb2YgVGlub2RlXG4gICAqIEBwcm9wZXJ0eSB7U3RyaW5nPX0gdXNlciAtIFVJRCBvZiB0aGUgdXNlciBhZmZlY3RlZCBieSB0aGUgcmVxdWVzdC4gRGVmYXVsdCAoZW1wdHkpIC0gY3VycmVudCB1c2VyLlxuICAgKiBAcHJvcGVydHkge1N0cmluZz19IG1vZGUgLSBVc2VyIGFjY2VzcyBtb2RlLCBlaXRoZXIgcmVxdWVzdGVkIG9yIGFzc2lnbmVkIGRlcGVuZGVudCBvbiBjb250ZXh0LlxuICAgKiBAcHJvcGVydHkge09iamVjdD19IGluZm8gLSBGcmVlLWZvcm0gcGF5bG9hZCB0byBwYXNzIHRvIHRoZSBpbnZpdGVkIHVzZXIgb3IgdG9waWMgbWFuYWdlci5cbiAgICovXG4gIC8qKlxuICAgKiBQYXJhbWV0ZXJzIHBhc3NlZCB0byB7QGxpbmsgVGlub2RlI3N1YnNjcmliZX0uXG4gICAqXG4gICAqIEB0eXBlZGVmIFN1YnNjcmlwdGlvblBhcmFtc1xuICAgKiBAbWVtYmVyb2YgVGlub2RlXG4gICAqIEBwcm9wZXJ0eSB7VGlub2RlLlNldFBhcmFtcz19IHNldCAtIFBhcmFtZXRlcnMgdXNlZCB0byBpbml0aWFsaXplIHRvcGljXG4gICAqIEBwcm9wZXJ0eSB7VGlub2RlLkdldFF1ZXJ5PX0gZ2V0IC0gUXVlcnkgZm9yIGZldGNoaW5nIGRhdGEgZnJvbSB0b3BpYy5cbiAgICovXG5cbiAgLyoqXG4gICAqIFNlbmQgYSB0b3BpYyBzdWJzY3JpcHRpb24gcmVxdWVzdC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHRvcGljIC0gTmFtZSBvZiB0aGUgdG9waWMgdG8gc3Vic2NyaWJlIHRvLlxuICAgKiBAcGFyYW0ge1Rpbm9kZS5HZXRRdWVyeT19IGdldFBhcmFtcyAtIE9wdGlvbmFsIHN1YnNjcmlwdGlvbiBtZXRhZGF0YSBxdWVyeVxuICAgKiBAcGFyYW0ge1Rpbm9kZS5TZXRQYXJhbXM9fSBzZXRQYXJhbXMgLSBPcHRpb25hbCBpbml0aWFsaXphdGlvbiBwYXJhbWV0ZXJzXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHdoaWNoIHdpbGwgYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgb24gcmVjZWl2aW5nIHNlcnZlciByZXBseS5cbiAgICovXG4gIHN1YnNjcmliZTogZnVuY3Rpb24odG9waWNOYW1lLCBnZXRQYXJhbXMsIHNldFBhcmFtcykge1xuICAgIGNvbnN0IHBrdCA9IHRoaXMuaW5pdFBhY2tldCgnc3ViJywgdG9waWNOYW1lKVxuICAgIGlmICghdG9waWNOYW1lKSB7XG4gICAgICB0b3BpY05hbWUgPSBUT1BJQ19ORVc7XG4gICAgfVxuXG4gICAgcGt0LnN1Yi5nZXQgPSBnZXRQYXJhbXM7XG5cbiAgICBpZiAoc2V0UGFyYW1zKSB7XG4gICAgICBpZiAoc2V0UGFyYW1zLnN1Yikge1xuICAgICAgICBwa3Quc3ViLnNldC5zdWIgPSBzZXRQYXJhbXMuc3ViO1xuICAgICAgfVxuXG4gICAgICBpZiAoc2V0UGFyYW1zLmRlc2MpIHtcbiAgICAgICAgaWYgKFRpbm9kZS5pc05ld0dyb3VwVG9waWNOYW1lKHRvcGljTmFtZSkpIHtcbiAgICAgICAgICAvLyBGdWxsIHNldC5kZXNjIHBhcmFtcyBhcmUgdXNlZCBmb3IgbmV3IHRvcGljcyBvbmx5XG4gICAgICAgICAgcGt0LnN1Yi5zZXQuZGVzYyA9IHNldFBhcmFtcy5kZXNjO1xuICAgICAgICB9IGVsc2UgaWYgKFRpbm9kZS50b3BpY1R5cGUodG9waWNOYW1lKSA9PSAncDJwJyAmJiBzZXRQYXJhbXMuZGVzYy5kZWZhY3MpIHtcbiAgICAgICAgICAvLyBVc2Ugb3B0aW9uYWwgZGVmYXVsdCBwZXJtaXNzaW9ucyBvbmx5LlxuICAgICAgICAgIHBrdC5zdWIuc2V0LmRlc2MgPSB7XG4gICAgICAgICAgICBkZWZhY3M6IHNldFBhcmFtcy5kZXNjLmRlZmFjc1xuICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKHNldFBhcmFtcy50YWdzKSB7XG4gICAgICAgIHBrdC5zdWIuc2V0LnRhZ3MgPSBzZXRQYXJhbXMudGFncztcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LnN1Yi5pZCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIERldGFjaCBhbmQgb3B0aW9uYWxseSB1bnN1YnNjcmliZSBmcm9tIHRoZSB0b3BpY1xuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdG9waWMgLSBUb3BpYyB0byBkZXRhY2ggZnJvbS5cbiAgICogQHBhcmFtIHtCb29sZWFufSB1bnN1YiAtIElmIDx0dD50cnVlPC90dD4sIGRldGFjaCBhbmQgdW5zdWJzY3JpYmUsIG90aGVyd2lzZSBqdXN0IGRldGFjaC5cbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCBvbiByZWNlaXZpbmcgc2VydmVyIHJlcGx5LlxuICAgKi9cbiAgbGVhdmU6IGZ1bmN0aW9uKHRvcGljLCB1bnN1Yikge1xuICAgIGNvbnN0IHBrdCA9IHRoaXMuaW5pdFBhY2tldCgnbGVhdmUnLCB0b3BpYyk7XG4gICAgcGt0LmxlYXZlLnVuc3ViID0gdW5zdWI7XG5cbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LmxlYXZlLmlkKTtcbiAgfSxcblxuICAvKipcbiAgICogQ3JlYXRlIG1lc3NhZ2UgZHJhZnQgd2l0aG91dCBzZW5kaW5nIGl0IHRvIHRoZSBzZXJ2ZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b3BpYyAtIE5hbWUgb2YgdGhlIHRvcGljIHRvIHB1Ymxpc2ggdG8uXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBkYXRhIC0gUGF5bG9hZCB0byBwdWJsaXNoLlxuICAgKiBAcGFyYW0ge0Jvb2xlYW49fSBub0VjaG8gLSBJZiA8dHQ+dHJ1ZTwvdHQ+LCB0ZWxsIHRoZSBzZXJ2ZXIgbm90IHRvIGVjaG8gdGhlIG1lc3NhZ2UgdG8gdGhlIG9yaWdpbmFsIHNlc3Npb24uXG4gICAqXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IG5ldyBtZXNzYWdlIHdoaWNoIGNhbiBiZSBzZW50IHRvIHRoZSBzZXJ2ZXIgb3Igb3RoZXJ3aXNlIHVzZWQuXG4gICAqL1xuICBjcmVhdGVNZXNzYWdlOiBmdW5jdGlvbih0b3BpYywgZGF0YSwgbm9FY2hvKSB7XG4gICAgY29uc3QgcGt0ID0gdGhpcy5pbml0UGFja2V0KCdwdWInLCB0b3BpYyk7XG5cbiAgICBsZXQgZGZ0ID0gdHlwZW9mIGRhdGEgPT0gJ3N0cmluZycgPyBEcmFmdHkucGFyc2UoZGF0YSkgOiBkYXRhO1xuICAgIGlmIChkZnQgJiYgIURyYWZ0eS5pc1BsYWluVGV4dChkZnQpKSB7XG4gICAgICBwa3QucHViLmhlYWQgPSB7XG4gICAgICAgIG1pbWU6IERyYWZ0eS5nZXRDb250ZW50VHlwZSgpXG4gICAgICB9O1xuICAgICAgZGF0YSA9IGRmdDtcbiAgICB9XG4gICAgcGt0LnB1Yi5ub2VjaG8gPSBub0VjaG87XG4gICAgcGt0LnB1Yi5jb250ZW50ID0gZGF0YTtcblxuICAgIHJldHVybiBwa3QucHViO1xuICB9LFxuXG4gIC8qKlxuICAgKiBQdWJsaXNoIHtkYXRhfSBtZXNzYWdlIHRvIHRvcGljLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdG9waWMgLSBOYW1lIG9mIHRoZSB0b3BpYyB0byBwdWJsaXNoIHRvLlxuICAgKiBAcGFyYW0ge09iamVjdH0gZGF0YSAtIFBheWxvYWQgdG8gcHVibGlzaC5cbiAgICogQHBhcmFtIHtCb29sZWFuPX0gbm9FY2hvIC0gSWYgPHR0PnRydWU8L3R0PiwgdGVsbCB0aGUgc2VydmVyIG5vdCB0byBlY2hvIHRoZSBtZXNzYWdlIHRvIHRoZSBvcmlnaW5hbCBzZXNzaW9uLlxuICAgKlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gUHJvbWlzZSB3aGljaCB3aWxsIGJlIHJlc29sdmVkL3JlamVjdGVkIG9uIHJlY2VpdmluZyBzZXJ2ZXIgcmVwbHkuXG4gICAqL1xuICBwdWJsaXNoOiBmdW5jdGlvbih0b3BpYywgZGF0YSwgbm9FY2hvKSB7XG4gICAgcmV0dXJuIHRoaXMucHVibGlzaE1lc3NhZ2UoXG4gICAgICB0aGlzLmNyZWF0ZU1lc3NhZ2UodG9waWMsIGRhdGEsIG5vRWNobylcbiAgICApO1xuICB9LFxuXG4gIC8qKlxuICAgKiBQdWJsaXNoIG1lc3NhZ2UgdG8gdG9waWMuIFRoZSBtZXNzYWdlIHNob3VsZCBiZSBjcmVhdGVkIGJ5IHtAbGluayBUaW5vZGUjY3JlYXRlTWVzc2FnZX0uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBwdWIgLSBNZXNzYWdlIHRvIHB1Ymxpc2guXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHdoaWNoIHdpbGwgYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgb24gcmVjZWl2aW5nIHNlcnZlciByZXBseS5cbiAgICovXG4gIHB1Ymxpc2hNZXNzYWdlOiBmdW5jdGlvbihwdWIpIHtcbiAgICAvLyBNYWtlIGEgc2hhbGxvdyBjb3B5LiBOZWVkZWQgaW4gb3JkZXIgdG8gY2xlYXIgbG9jYWxseS1hc3NpZ25lZCB0ZW1wIHZhbHVlcztcbiAgICBwdWIgPSBPYmplY3QuYXNzaWduKHt9LCBwdWIpO1xuICAgIHB1Yi5zZXEgPSB1bmRlZmluZWQ7XG4gICAgcHViLmZyb20gPSB1bmRlZmluZWQ7XG4gICAgcHViLnRzID0gdW5kZWZpbmVkO1xuICAgIHJldHVybiB0aGlzLnNlbmQoe1xuICAgICAgcHViOiBwdWJcbiAgICB9LCBwdWIuaWQpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBAdHlwZWRlZiBHZXRRdWVyeVxuICAgKiBAdHlwZSBPYmplY3RcbiAgICogQG1lbWJlcm9mIFRpbm9kZVxuICAgKiBAcHJvcGVydHkge1Rpbm9kZS5HZXRPcHRzVHlwZT19IGRlc2MgLSBJZiBwcm92aWRlZCAoZXZlbiBpZiBlbXB0eSksIGZldGNoIHRvcGljIGRlc2NyaXB0aW9uLlxuICAgKiBAcHJvcGVydHkge1Rpbm9kZS5HZXRPcHRzVHlwZT19IHN1YiAtIElmIHByb3ZpZGVkIChldmVuIGlmIGVtcHR5KSwgZmV0Y2ggdG9waWMgc3Vic2NyaXB0aW9ucy5cbiAgICogQHByb3BlcnR5IHtUaW5vZGUuR2V0RGF0YVR5cGU9fSBkYXRhIC0gSWYgcHJvdmlkZWQgKGV2ZW4gaWYgZW1wdHkpLCBnZXQgbWVzc2FnZXMuXG4gICAqL1xuXG4gIC8qKlxuICAgKiBAdHlwZWRlZiBHZXRPcHRzVHlwZVxuICAgKiBAdHlwZSBPYmplY3RcbiAgICogQG1lbWJlcm9mIFRpbm9kZVxuICAgKiBAcHJvcGVydHkge0RhdGU9fSBpbXMgLSBcIklmIG1vZGlmaWVkIHNpbmNlXCIsIGZldGNoIGRhdGEgb25seSBpdCB3YXMgd2FzIG1vZGlmaWVkIHNpbmNlIHN0YXRlZCBkYXRlLlxuICAgKiBAcHJvcGVydHkge051bWJlcj19IGxpbWl0IC0gTWF4aW11bSBudW1iZXIgb2YgcmVzdWx0cyB0byByZXR1cm4uIElnbm9yZWQgd2hlbiBxdWVyeWluZyB0b3BpYyBkZXNjcmlwdGlvbi5cbiAgICovXG5cbiAgLyoqXG4gICAqIEB0eXBlZGVmIEdldERhdGFUeXBlXG4gICAqIEB0eXBlIE9iamVjdFxuICAgKiBAbWVtYmVyb2YgVGlub2RlXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyPX0gc2luY2UgLSBMb2FkIG1lc3NhZ2VzIHdpdGggc2VxIGlkIGVxdWFsIG9yIGdyZWF0ZXIgdGhhbiB0aGlzIHZhbHVlLlxuICAgKiBAcHJvcGVydHkge051bWJlcj19IGJlZm9yZSAtIExvYWQgbWVzc2FnZXMgd2l0aCBzZXEgaWQgbG93ZXIgdGhhbiB0aGlzIG51bWJlci5cbiAgICogQHByb3BlcnR5IHtOdW1iZXI9fSBsaW1pdCAtIE1heGltdW0gbnVtYmVyIG9mIHJlc3VsdHMgdG8gcmV0dXJuLlxuICAgKi9cblxuICAvKipcbiAgICogUmVxdWVzdCB0b3BpYyBtZXRhZGF0YVxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdG9waWMgLSBOYW1lIG9mIHRoZSB0b3BpYyB0byBxdWVyeS5cbiAgICogQHBhcmFtIHtUaW5vZGUuR2V0UXVlcnl9IHBhcmFtcyAtIFBhcmFtZXRlcnMgb2YgdGhlIHF1ZXJ5LiBVc2Uge1Rpbm9kZS5NZXRhR2V0QnVpbGRlcn0gdG8gZ2VuZXJhdGUuXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHdoaWNoIHdpbGwgYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgb24gcmVjZWl2aW5nIHNlcnZlciByZXBseS5cbiAgICovXG4gIGdldE1ldGE6IGZ1bmN0aW9uKHRvcGljLCBwYXJhbXMpIHtcbiAgICBjb25zdCBwa3QgPSB0aGlzLmluaXRQYWNrZXQoJ2dldCcsIHRvcGljKTtcblxuICAgIHBrdC5nZXQgPSBtZXJnZU9iaihwa3QuZ2V0LCBwYXJhbXMpO1xuXG4gICAgcmV0dXJuIHRoaXMuc2VuZChwa3QsIHBrdC5nZXQuaWQpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBVcGRhdGUgdG9waWMncyBtZXRhZGF0YTogZGVzY3JpcHRpb24sIHN1YnNjcmlidGlvbnMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b3BpYyAtIFRvcGljIHRvIHVwZGF0ZS5cbiAgICogQHBhcmFtIHtUaW5vZGUuU2V0UGFyYW1zfSBwYXJhbXMgLSB0b3BpYyBtZXRhZGF0YSB0byB1cGRhdGUuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHdoaWNoIHdpbGwgYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgb24gcmVjZWl2aW5nIHNlcnZlciByZXBseS5cbiAgICovXG4gIHNldE1ldGE6IGZ1bmN0aW9uKHRvcGljLCBwYXJhbXMpIHtcbiAgICBjb25zdCBwa3QgPSB0aGlzLmluaXRQYWNrZXQoJ3NldCcsIHRvcGljKTtcbiAgICBjb25zdCB3aGF0ID0gW107XG5cbiAgICBpZiAocGFyYW1zKSB7XG4gICAgICBbJ2Rlc2MnLCAnc3ViJywgJ3RhZ3MnLCAnY3JlZCddLm1hcChmdW5jdGlvbihrZXkpIHtcbiAgICAgICAgaWYgKHBhcmFtcy5oYXNPd25Qcm9wZXJ0eShrZXkpKSB7XG4gICAgICAgICAgd2hhdC5wdXNoKGtleSk7XG4gICAgICAgICAgcGt0LnNldFtrZXldID0gcGFyYW1zW2tleV07XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH1cblxuICAgIGlmICh3aGF0Lmxlbmd0aCA9PSAwKSB7XG4gICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEVycm9yKFwiSW52YWxpZCB7c2V0fSBwYXJhbWV0ZXJzXCIpKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LnNldC5pZCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFJhbmdlIG9mIG1lc3NhZ2UgSURzIHRvIGRlbGV0ZS5cbiAgICpcbiAgICogQHR5cGVkZWYgRGVsUmFuZ2VcbiAgICogQHR5cGUgT2JqZWN0XG4gICAqIEBtZW1iZXJvZiBUaW5vZGVcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IGxvdyAtIGxvdyBlbmQgb2YgdGhlIHJhbmdlLCBpbmNsdXNpdmUgKGNsb3NlZCkuXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyPX0gaGkgLSBoaWdoIGVuZCBvZiB0aGUgcmFuZ2UsIGV4Y2x1c2l2ZSAob3BlbikuXG4gICAqL1xuICAvKipcbiAgICogRGVsZXRlIHNvbWUgb3IgYWxsIG1lc3NhZ2VzIGluIGEgdG9waWMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b3BpYyAtIFRvcGljIG5hbWUgdG8gZGVsZXRlIG1lc3NhZ2VzIGZyb20uXG4gICAqIEBwYXJhbSB7VGlub2RlLkRlbFJhbmdlW119IGxpc3QgLSBSYW5nZXMgb2YgbWVzc2FnZSBJRHMgdG8gZGVsZXRlLlxuICAgKiBAcGFyYW0ge0Jvb2xlYW49fSBoYXJkIC0gSGFyZCBvciBzb2Z0IGRlbGV0ZVxuICAgKlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gUHJvbWlzZSB3aGljaCB3aWxsIGJlIHJlc29sdmVkL3JlamVjdGVkIG9uIHJlY2VpdmluZyBzZXJ2ZXIgcmVwbHkuXG4gICAqL1xuICBkZWxNZXNzYWdlczogZnVuY3Rpb24odG9waWMsIHJhbmdlcywgaGFyZCkge1xuICAgIGNvbnN0IHBrdCA9IHRoaXMuaW5pdFBhY2tldCgnZGVsJywgdG9waWMpO1xuXG4gICAgcGt0LmRlbC53aGF0ID0gJ21zZyc7XG4gICAgcGt0LmRlbC5kZWxzZXEgPSByYW5nZXM7XG4gICAgcGt0LmRlbC5oYXJkID0gaGFyZDtcblxuICAgIHJldHVybiB0aGlzLnNlbmQocGt0LCBwa3QuZGVsLmlkKTtcbiAgfSxcblxuICAvKipcbiAgICogRGVsZXRlIHRoZSB0b3BpYyBhbGx0b2dldGhlci4gUmVxdWlyZXMgT3duZXIgcGVybWlzc2lvbi5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHRvcGljIC0gTmFtZSBvZiB0aGUgdG9waWMgdG8gZGVsZXRlXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHdoaWNoIHdpbGwgYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgb24gcmVjZWl2aW5nIHNlcnZlciByZXBseS5cbiAgICovXG4gIGRlbFRvcGljOiBmdW5jdGlvbih0b3BpYykge1xuICAgIGNvbnN0IHBrdCA9IHRoaXMuaW5pdFBhY2tldCgnZGVsJywgdG9waWMpO1xuICAgIHBrdC5kZWwud2hhdCA9ICd0b3BpYyc7XG5cbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LmRlbC5pZCkudGhlbigoY3RybCkgPT4ge1xuICAgICAgdGhpcy5jYWNoZURlbCgndG9waWMnLCB0b3BpYyk7XG4gICAgICByZXR1cm4gdGhpcy5jdHJsO1xuICAgIH0pO1xuICB9LFxuXG4gIC8qKlxuICAgKiBEZWxldGUgc3Vic2NyaXB0aW9uLiBSZXF1aXJlcyBTaGFyZSBwZXJtaXNzaW9uLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdG9waWMgLSBOYW1lIG9mIHRoZSB0b3BpYyB0byBkZWxldGVcbiAgICogQHBhcmFtIHtTdHJpbmd9IHVzZXIgLSBVc2VyIElEIHRvIHJlbW92ZS5cbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCBvbiByZWNlaXZpbmcgc2VydmVyIHJlcGx5LlxuICAgKi9cbiAgZGVsU3Vic2NyaXB0aW9uOiBmdW5jdGlvbih0b3BpYywgdXNlcikge1xuICAgIGNvbnN0IHBrdCA9IHRoaXMuaW5pdFBhY2tldCgnZGVsJywgdG9waWMpO1xuICAgIHBrdC5kZWwud2hhdCA9ICdzdWInO1xuICAgIHBrdC5kZWwudXNlciA9IHVzZXI7XG5cbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LmRlbC5pZCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIERlbGV0ZSBjcmVkZW50aWFsLiBNdXN0IGJlICdtZScgdG9waWMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b3BpYyAtICdtZScuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBtZXRob2QgLSB2YWxpZGF0aW9uIG1ldGhvZCBzdWNoIGFzICdlbWFpbCcgb3IgJ3RlbCcuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB2YWx1ZSAtIHZhbGlkYXRpb24gdmFsdWUsIGkuZS4gJ2FsaWNlQGV4YW1wbGUuY29tJy5cbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2Ugd2hpY2ggd2lsbCBiZSByZXNvbHZlZC9yZWplY3RlZCBvbiByZWNlaXZpbmcgc2VydmVyIHJlcGx5LlxuICAgKi9cbiAgZGVsQ3JlZGVudGlhbDogZnVuY3Rpb24odG9waWMsIG1ldGhvZCwgdmFsdWUpIHtcbiAgICBpZiAodG9waWMgIT0gJ21lJykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiSW52YWxpZCB0b3BpYyBmb3IgZGVsZXRpbmcgY3JlZGVudGlhbHMgJ1wiICsgdG9waWMgKyBcIidcIik7XG4gICAgfVxuICAgIGNvbnN0IHBrdCA9IHRoaXMuaW5pdFBhY2tldCgnZGVsJywgdG9waWMpO1xuICAgIHBrdC5kZWwud2hhdCA9ICdjcmVkJztcbiAgICBwa3QuZGVsLmNyZWQgPSB7XG4gICAgICBtZXRoOiBtZXRob2QsXG4gICAgICB2YWw6IHZhbHVlXG4gICAgfTtcbiAgICByZXR1cm4gdGhpcy5zZW5kKHBrdCwgcGt0LmRlbC5pZCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIE5vdGlmeSBzZXJ2ZXIgdGhhdCBhIG1lc3NhZ2Ugb3IgbWVzc2FnZXMgd2VyZSByZWFkIG9yIHJlY2VpdmVkLiBEb2VzIE5PVCByZXR1cm4gcHJvbWlzZS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHRvcGljIC0gTmFtZSBvZiB0aGUgdG9waWMgd2hlcmUgdGhlIG1lc2FnZSBpcyBiZWluZyBha25vd2xlZGdlZC5cbiAgICogQHBhcmFtIHtTdHJpbmd9IHdoYXQgLSBBY3Rpb24gYmVpbmcgYWtub3dsZWRnZWQsIGVpdGhlciBcInJlYWRcIiBvciBcInJlY3ZcIi5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IHNlcSAtIE1heGltdW0gaWQgb2YgdGhlIG1lc3NhZ2UgYmVpbmcgYWNrbm93bGVkZ2VkLlxuICAgKi9cbiAgbm90ZTogZnVuY3Rpb24odG9waWMsIHdoYXQsIHNlcSkge1xuICAgIGlmIChzZXEgPD0gMCB8fCBzZXEgPj0gTE9DQUxfU0VRSUQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkludmFsaWQgbWVzc2FnZSBpZCBcIiArIHNlcSk7XG4gICAgfVxuXG4gICAgY29uc3QgcGt0ID0gdGhpcy5pbml0UGFja2V0KCdub3RlJywgdG9waWMpO1xuICAgIHBrdC5ub3RlLndoYXQgPSB3aGF0O1xuICAgIHBrdC5ub3RlLnNlcSA9IHNlcTtcbiAgICB0aGlzLnNlbmQocGt0KTtcbiAgfSxcblxuICAvKipcbiAgICogQnJvYWRjYXN0IGEga2V5LXByZXNzIG5vdGlmaWNhdGlvbiB0byB0b3BpYyBzdWJzY3JpYmVycy4gVXNlZCB0byBzaG93XG4gICAqIHR5cGluZyBub3RpZmljYXRpb25zIFwidXNlciBYIGlzIHR5cGluZy4uLlwiLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdG9waWMgLSBOYW1lIG9mIHRoZSB0b3BpYyB0byBicm9hZGNhc3QgdG8uXG4gICAqL1xuICBub3RlS2V5UHJlc3M6IGZ1bmN0aW9uKHRvcGljKSB7XG4gICAgY29uc3QgcGt0ID0gdGhpcy5pbml0UGFja2V0KCdub3RlJywgdG9waWMpO1xuICAgIHBrdC5ub3RlLndoYXQgPSAna3AnO1xuICAgIHRoaXMuc2VuZChwa3QpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgYSBuYW1lZCB0b3BpYywgZWl0aGVyIHB1bGwgaXQgZnJvbSBjYWNoZSBvciBjcmVhdGUgYSBuZXcgaW5zdGFuY2UuXG4gICAqIFRoZXJlIGlzIGEgc2luZ2xlIGluc3RhbmNlIG9mIHRvcGljIGZvciBlYWNoIG5hbWUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b3BpYyAtIE5hbWUgb2YgdGhlIHRvcGljIHRvIGdldC5cbiAgICogQHJldHVybnMge1Rpbm9kZS5Ub3BpY30gUmVxdWVzdGVkIG9yIG5ld2x5IGNyZWF0ZWQgdG9waWMgb3IgPHR0PnVuZGVmaW5lZDwvdHQ+IGlmIHRvcGljIG5hbWUgaXMgaW52YWxpZC5cbiAgICovXG4gIGdldFRvcGljOiBmdW5jdGlvbihuYW1lKSB7XG4gICAgbGV0IHRvcGljID0gdGhpcy5jYWNoZUdldCgndG9waWMnLCBuYW1lKTtcbiAgICBpZiAoIXRvcGljICYmIG5hbWUpIHtcbiAgICAgIGlmIChuYW1lID09IFRPUElDX01FKSB7XG4gICAgICAgIHRvcGljID0gbmV3IFRvcGljTWUoKTtcbiAgICAgIH0gZWxzZSBpZiAobmFtZSA9PSBUT1BJQ19GTkQpIHtcbiAgICAgICAgdG9waWMgPSBuZXcgVG9waWNGbmQoKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRvcGljID0gbmV3IFRvcGljKG5hbWUpO1xuICAgICAgfVxuICAgICAgLy8gdG9waWMuX25ldyA9IGZhbHNlO1xuICAgICAgdGhpcy5jYWNoZVB1dCgndG9waWMnLCBuYW1lLCB0b3BpYyk7XG4gICAgICB0aGlzLmF0dGFjaENhY2hlVG9Ub3BpYyh0b3BpYyk7XG4gICAgfVxuICAgIHJldHVybiB0b3BpYztcbiAgfSxcblxuICAvKipcbiAgICogSW5zdGFudGlhdGUgYSBuZXcgdW5uYW1lZCB0b3BpYy4gQW4gYWN0dWFsIG5hbWUgd2lsbCBiZSBhc3NpZ25lZCBieSB0aGUgc2VydmVyXG4gICAqIG9uIHtAbGluayBUaW5vZGUuVG9waWMuc3Vic2NyaWJlfS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHBhcmFtIHtUaW5vZGUuQ2FsbGJhY2tzfSBjYWxsYmFja3MgLSBPYmplY3Qgd2l0aCBjYWxsYmFja3MgZm9yIHZhcmlvdXMgZXZlbnRzLlxuICAgKiBAcmV0dXJucyB7VGlub2RlLlRvcGljfSBOZXdseSBjcmVhdGVkIHRvcGljLlxuICAgKi9cbiAgbmV3VG9waWM6IGZ1bmN0aW9uKGNhbGxiYWNrcykge1xuICAgIGNvbnN0IHRvcGljID0gbmV3IFRvcGljKFRPUElDX05FVywgY2FsbGJhY2tzKTtcbiAgICB0aGlzLmF0dGFjaENhY2hlVG9Ub3BpYyh0b3BpYyk7XG4gICAgcmV0dXJuIHRvcGljO1xuICB9LFxuXG4gIC8qKlxuICAgKiBHZW5lcmF0ZSB1bmlxdWUgbmFtZSAgbGlrZSAnbmV3MTIzNDU2JyBzdWl0YWJsZSBmb3IgY3JlYXRpbmcgYSBuZXcgZ3JvdXAgdG9waWMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEByZXR1cm5zIHtzdHJpbmd9IG5hbWUgd2hpY2ggY2FuIGJlIHVzZWQgZm9yIGNyZWF0aW5nIGEgbmV3IGdyb3VwIHRvcGljLlxuICAgKi9cbiAgbmV3R3JvdXBUb3BpY05hbWU6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiBUT1BJQ19ORVcgKyB0aGlzLmdldE5leHRVbmlxdWVJZCgpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBJbnN0YW50aWF0ZSBhIG5ldyBQMlAgdG9waWMgd2l0aCBhIGdpdmVuIHBlZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBwZWVyIC0gVUlEIG9mIHRoZSBwZWVyIHRvIHN0YXJ0IHRvcGljIHdpdGguXG4gICAqIEBwYXJhbSB7VGlub2RlLkNhbGxiYWNrc30gY2FsbGJhY2tzIC0gT2JqZWN0IHdpdGggY2FsbGJhY2tzIGZvciB2YXJpb3VzIGV2ZW50cy5cbiAgICogQHJldHVybnMge1Rpbm9kZS5Ub3BpY30gTmV3bHkgY3JlYXRlZCB0b3BpYy5cbiAgICovXG4gIG5ld1RvcGljV2l0aDogZnVuY3Rpb24ocGVlciwgY2FsbGJhY2tzKSB7XG4gICAgY29uc3QgdG9waWMgPSBuZXcgVG9waWMocGVlciwgY2FsbGJhY2tzKTtcbiAgICB0aGlzLmF0dGFjaENhY2hlVG9Ub3BpYyh0b3BpYyk7XG4gICAgcmV0dXJuIHRvcGljO1xuICB9LFxuXG4gIC8qKlxuICAgKiBJbnN0YW50aWF0ZSAnbWUnIHRvcGljIG9yIGdldCBpdCBmcm9tIGNhY2hlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLlRvcGljTWV9IEluc3RhbmNlIG9mICdtZScgdG9waWMuXG4gICAqL1xuICBnZXRNZVRvcGljOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5nZXRUb3BpYyhUT1BJQ19NRSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEluc3RhbnRpYXRlICdmbmQnIChmaW5kKSB0b3BpYyBvciBnZXQgaXQgZnJvbSBjYWNoZS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICpcbiAgICogQHJldHVybnMge1Rpbm9kZS5Ub3BpY30gSW5zdGFuY2Ugb2YgJ2ZuZCcgdG9waWMuXG4gICAqL1xuICBnZXRGbmRUb3BpYzogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0VG9waWMoVE9QSUNfRk5EKTtcbiAgfSxcblxuICAvKipcbiAgICogQ3JlYXRlIGEgbmV3IExhcmdlRmlsZUhlbHBlciBpbnN0YW5jZVxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLkxhcmdlRmlsZUhlbHBlcn0gaW5zdGFuY2Ugb2YgYSBMYXJnZUZpbGVIZWxwZXIuXG4gICAqL1xuICBnZXRMYXJnZUZpbGVIZWxwZXI6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiBuZXcgTGFyZ2VGaWxlSGVscGVyKHRoaXMpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgdGhlIFVJRCBvZiB0aGUgdGhlIGN1cnJlbnQgYXV0aGVudGljYXRlZCB1c2VyLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKiBAcmV0dXJucyB7c3RyaW5nfSBVSUQgb2YgdGhlIGN1cnJlbnQgdXNlciBvciA8dHQ+dW5kZWZpbmVkPC90dD4gaWYgdGhlIHNlc3Npb24gaXMgbm90IHlldCBhdXRoZW50aWNhdGVkIG9yIGlmIHRoZXJlIGlzIG5vIHNlc3Npb24uXG4gICAqL1xuICBnZXRDdXJyZW50VXNlcklEOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5fbXlVSUQ7XG4gIH0sXG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIHRoZSBnaXZlbiB1c2VyIElEIGlzIGVxdWFsIHRvIHRoZSBjdXJyZW50IHVzZXIncyBVSUQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB1aWQgLSBVSUQgdG8gY2hlY2suXG4gICAqIEByZXR1cm5zIHtib29sZWFufSB0cnVlIGlmIHRoZSBnaXZlbiBVSUQgYmVsb25ncyB0byB0aGUgY3VycmVudCBsb2dnZWQgaW4gdXNlci5cbiAgICovXG4gIGlzTWU6IGZ1bmN0aW9uKHVpZCkge1xuICAgIHJldHVybiB0aGlzLl9teVVJRCA9PT0gdWlkO1xuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgbG9naW4gdXNlZCBmb3IgbGFzdCBzdWNjZXNzZnVsIGF1dGhlbnRpY2F0aW9uLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKiBAcmV0dXJucyB7c3RyaW5nfSBsb2dpbiBsYXN0IHVzZWQgc3VjY2Vzc2Z1bGx5IG9yIDx0dD51bmRlZmluZWQ8L3R0Pi5cbiAgICovXG4gIGdldEN1cnJlbnRMb2dpbjogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHRoaXMuX2xvZ2luO1xuICB9LFxuXG4gIC8qKlxuICAgKiBSZXR1cm4gaW5mb3JtYXRpb24gYWJvdXQgdGhlIHNlcnZlcjogcHJvdG9jb2wgdmVyc2lvbiBhbmQgYnVpbGQgdGltZXN0YW1wLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKiBAcmV0dXJucyB7T2JqZWN0fSBidWlsZCBhbmQgdmVyc2lvbiBvZiB0aGUgc2VydmVyIG9yIDx0dD5udWxsPC90dD4gaWYgdGhlcmUgaXMgbm8gY29ubmVjdGlvbiBvciBpZiB0aGUgZmlyc3Qgc2VydmVyIHJlc3BvbnNlIGhhcyBub3QgYmVlbiByZWNlaXZlZCB5ZXQuXG4gICAqL1xuICBnZXRTZXJ2ZXJJbmZvOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5fc2VydmVySW5mbztcbiAgfSxcblxuICAvKipcbiAgICogVG9nZ2xlIGNvbnNvbGUgbG9nZ2luZy4gTG9nZ2luZyBpcyBvZmYgYnkgZGVmYXVsdC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICogQHBhcmFtIHtib29sZWFufSBlbmFibGVkIC0gU2V0IHRvIDx0dD50cnVlPC90dD4gdG8gZW5hYmxlIGxvZ2dpbmcgdG8gY29uc29sZS5cbiAgICovXG4gIGVuYWJsZUxvZ2dpbmc6IGZ1bmN0aW9uKGVuYWJsZWQsIHRyaW1Mb25nU3RyaW5ncykge1xuICAgIHRoaXMuX2xvZ2dpbmdFbmFibGVkID0gZW5hYmxlZDtcbiAgICB0aGlzLl90cmltTG9uZ1N0cmluZ3MgPSBlbmFibGVkICYmIHRyaW1Mb25nU3RyaW5ncztcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgZ2l2ZW4gdG9waWMgaXMgb25saW5lLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gbmFtZSAtIE5hbWUgb2YgdGhlIHRvcGljIHRvIHRlc3QuXG4gICAqIEByZXR1cm5zIHtCb29sZWFufSB0cnVlIGlmIHRvcGljIGlzIG9ubGluZSwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgKi9cbiAgaXNUb3BpY09ubGluZTogZnVuY3Rpb24obmFtZSkge1xuICAgIGNvbnN0IG1lID0gdGhpcy5nZXRNZVRvcGljKCk7XG4gICAgY29uc3QgY29udCA9IG1lICYmIG1lLmdldENvbnRhY3QobmFtZSk7XG4gICAgcmV0dXJuIGNvbnQgJiYgY29udC5vbmxpbmU7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEluY2x1ZGUgbWVzc2FnZSBJRCBpbnRvIGFsbCBzdWJzZXF1ZXN0IG1lc3NhZ2VzIHRvIHNlcnZlciBpbnN0cnVjdGluIGl0IHRvIHNlbmQgYWtub3dsZWRnZW1lbnMuXG4gICAqIFJlcXVpcmVkIGZvciBwcm9taXNlcyB0byBmdW5jdGlvbi4gRGVmYXVsdCBpcyBcIm9uXCIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqXG4gICAqIEBwYXJhbSB7Qm9vbGVhbn0gc3RhdHVzIC0gVHVybiBha25vd2xlZGdlbWVucyBvbiBvciBvZmYuXG4gICAqIEBkZXByZWNhdGVkXG4gICAqL1xuICB3YW50QWtuOiBmdW5jdGlvbihzdGF0dXMpIHtcbiAgICBpZiAoc3RhdHVzKSB7XG4gICAgICB0aGlzLl9tZXNzYWdlSWQgPSBNYXRoLmZsb29yKChNYXRoLnJhbmRvbSgpICogMHhGRkZGRkYpICsgMHhGRkZGRkYpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLl9tZXNzYWdlSWQgPSAwO1xuICAgIH1cbiAgfSxcblxuICAvLyBDYWxsYmFja3M6XG4gIC8qKlxuICAgKiBDYWxsYmFjayB0byByZXBvcnQgd2hlbiB0aGUgd2Vic29ja2V0IGlzIG9wZW5lZC4gVGhlIGNhbGxiYWNrIGhhcyBubyBwYXJhbWV0ZXJzLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKiBAdHlwZSB7VGlub2RlLm9uV2Vic29ja2V0T3Blbn1cbiAgICovXG4gIG9uV2Vic29ja2V0T3BlbjogdW5kZWZpbmVkLFxuXG4gIC8qKlxuICAgKiBAdHlwZWRlZiBUaW5vZGUuU2VydmVyUGFyYW1zXG4gICAqIEBtZW1iZXJvZiBUaW5vZGVcbiAgICogQHR5cGUgT2JqZWN0XG4gICAqIEBwcm9wZXJ0eSB7c3RyaW5nfSB2ZXIgLSBTZXJ2ZXIgdmVyc2lvblxuICAgKiBAcHJvcGVydHkge3N0cmluZ30gYnVpbGQgLSBTZXJ2ZXIgYnVpbGRcbiAgICogQHByb3BlcnR5IHtzdHJpbmc9fSBzaWQgLSBTZXNzaW9uIElELCBsb25nIHBvbGxpbmcgY29ubmVjdGlvbnMgb25seS5cbiAgICovXG5cbiAgLyoqXG4gICAqIEBjYWxsYmFjayBUaW5vZGUub25Db25uZWN0XG4gICAqIEBwYXJhbSB7bnVtYmVyfSBjb2RlIC0gUmVzdWx0IGNvZGVcbiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQgLSBUZXh0IGVweHBsYWluaW5nIHRoZSBjb21wbGV0aW9uLCBpLmUgXCJPS1wiIG9yIGFuIGVycm9yIG1lc3NhZ2UuXG4gICAqIEBwYXJhbSB7VGlub2RlLlNlcnZlclBhcmFtc30gcGFyYW1zIC0gUGFyYW1ldGVycyByZXR1cm5lZCBieSB0aGUgc2VydmVyLlxuICAgKi9cbiAgLyoqXG4gICAqIENhbGxiYWNrIHRvIHJlcG9ydCB3aGVuIGNvbm5lY3Rpb24gd2l0aCBUaW5vZGUgc2VydmVyIGlzIGVzdGFibGlzaGVkLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKiBAdHlwZSB7VGlub2RlLm9uQ29ubmVjdH1cbiAgICovXG4gIG9uQ29ubmVjdDogdW5kZWZpbmVkLFxuXG4gIC8qKlxuICAgKiBDYWxsYmFjayB0byByZXBvcnQgd2hlbiBjb25uZWN0aW9uIGlzIGxvc3QuIFRoZSBjYWxsYmFjayBoYXMgbm8gcGFyYW1ldGVycy5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICogQHR5cGUge1Rpbm9kZS5vbkRpc2Nvbm5lY3R9XG4gICAqL1xuICBvbkRpc2Nvbm5lY3Q6IHVuZGVmaW5lZCxcblxuICAvKipcbiAgICogQGNhbGxiYWNrIFRpbm9kZS5vbkxvZ2luXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBjb2RlIC0gTlVtZXJpYyBjb21wbGV0aW9uIGNvZGUsIHNhbWUgYXMgSFRUUCBzdGF0dXMgY29kZXMuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0IC0gRXhwbGFuYXRpb24gb2YgdGhlIGNvbXBsZXRpb24gY29kZS5cbiAgICovXG4gIC8qKlxuICAgKiBDYWxsYmFjayB0byByZXBvcnQgbG9naW4gY29tcGxldGlvbi5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICogQHR5cGUge1Rpbm9kZS5vbkxvZ2lufVxuICAgKi9cbiAgb25Mb2dpbjogdW5kZWZpbmVkLFxuXG4gIC8qKlxuICAgKiBDYWxsYmFjayB0byByZWNlaXZlIHtjdHJsfSAoY29udHJvbCkgbWVzc2FnZXMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqIEB0eXBlIHtUaW5vZGUub25DdHJsTWVzc2FnZX1cbiAgICovXG4gIG9uQ3RybE1lc3NhZ2U6IHVuZGVmaW5lZCxcblxuICAvKipcbiAgICogQ2FsbGJhY2sgdG8gcmVjaWV2ZSB7ZGF0YX0gKGNvbnRlbnQpIG1lc3NhZ2VzLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKiBAdHlwZSB7VGlub2RlLm9uRGF0YU1lc3NhZ2V9XG4gICAqL1xuICBvbkRhdGFNZXNzYWdlOiB1bmRlZmluZWQsXG5cbiAgLyoqXG4gICAqIENhbGxiYWNrIHRvIHJlY2VpdmUge3ByZXN9IChwcmVzZW5jZSkgbWVzc2FnZXMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqIEB0eXBlIHtUaW5vZGUub25QcmVzTWVzc2FnZX1cbiAgICovXG4gIG9uUHJlc01lc3NhZ2U6IHVuZGVmaW5lZCxcblxuICAvKipcbiAgICogQ2FsbGJhY2sgdG8gcmVjZWl2ZSBhbGwgbWVzc2FnZXMgYXMgb2JqZWN0cy5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICogQHR5cGUge1Rpbm9kZS5vbk1lc3NhZ2V9XG4gICAqL1xuICBvbk1lc3NhZ2U6IHVuZGVmaW5lZCxcblxuICAvKipcbiAgICogQ2FsbGJhY2sgdG8gcmVjZWl2ZSBhbGwgbWVzc2FnZXMgYXMgdW5wYXJzZWQgdGV4dC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZSNcbiAgICogQHR5cGUge1Rpbm9kZS5vblJhd01lc3NhZ2V9XG4gICAqL1xuICBvblJhd01lc3NhZ2U6IHVuZGVmaW5lZCxcblxuICAvKipcbiAgICogQ2FsbGJhY2sgdG8gcmVjZWl2ZSBzZXJ2ZXIgcmVzcG9uc2VzIHRvIG5ldHdvcmsgcHJvYmVzLiBTZWUge0BsaW5rIFRpbm9kZSNuZXR3b3JrUHJvYmV9XG4gICAqIEBtZW1iZXJvZiBUaW5vZGUjXG4gICAqIEB0eXBlIHtUaW5vZGUub25OZXR3b3JrUHJvYmV9XG4gICAqL1xuICBvbk5ldHdvcmtQcm9iZTogdW5kZWZpbmVkLFxuXG4gIC8qKlxuICAgKiBDYWxsYmFjayB0byBiZSBub3RpZmllZCB3aGVuIGV4cG9uZW50aWFsIGJhY2tvZmYgaXMgaXRlcmF0aW5nLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlI1xuICAgKiBAdHlwZSB7VGlub2RlLm9uQXV0b3JlY29ubmVjdEl0ZXJhdGlvbn1cbiAgICovXG4gIG9uQXV0b3JlY29ubmVjdEl0ZXJhdGlvbjogdW5kZWZpbmVkLFxufTtcblxuLyoqXG4gKiBIZWxwZXIgY2xhc3MgZm9yIGNvbnN0cnVjdGluZyB7QGxpbmsgVGlub2RlLkdldFF1ZXJ5fS5cbiAqXG4gKiBAY2xhc3MgTWV0YUdldEJ1aWxkZXJcbiAqIEBtZW1iZXJvZiBUaW5vZGVcbiAqXG4gKiBAcGFyYW0ge1Rpbm9kZS5Ub3BpY30gcGFyZW50IHRvcGljIHdoaWNoIGluc3RhbnRpYXRlZCB0aGlzIGJ1aWxkZXIuXG4gKi9cbnZhciBNZXRhR2V0QnVpbGRlciA9IGZ1bmN0aW9uKHBhcmVudCkge1xuICB0aGlzLnRvcGljID0gcGFyZW50O1xuICBjb25zdCBtZSA9IHBhcmVudC5fdGlub2RlLmdldE1lVG9waWMoKTtcbiAgdGhpcy5jb250YWN0ID0gbWUgJiYgbWUuZ2V0Q29udGFjdChwYXJlbnQubmFtZSk7XG4gIHRoaXMud2hhdCA9IHt9O1xufVxuXG5NZXRhR2V0QnVpbGRlci5wcm90b3R5cGUgPSB7XG5cbiAgLy8gR2V0IGxhdGVzdCB0aW1lc3RhbXBcbiAgX2dldF9pbXM6IGZ1bmN0aW9uKCkge1xuICAgIGNvbnN0IGN1cGQgPSB0aGlzLmNvbnRhY3QgJiYgdGhpcy5jb250YWN0LnVwZGF0ZWQ7XG4gICAgY29uc3QgdHVwZCA9IHRoaXMudG9waWMuX2xhc3REZXNjVXBkYXRlIHx8IDA7XG4gICAgcmV0dXJuIGN1cGQgPiB0dXBkID8gY3VwZCA6IHR1cGQ7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIG1lc3NhZ2VzIHdpdGhpbiBleHBsaWNpdCBsaW1pdHMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyPX0gc2luY2UgbWVzc2FnZXMgbmV3ZXIgdGhhbiB0aGlzIChpbmNsdXNpdmUpO1xuICAgKiBAcGFyYW0ge051bWJlcj19IGJlZm9yZSBvbGRlciB0aGFuIHRoaXMgKGV4Y2x1c2l2ZSlcbiAgICogQHBhcmFtIHtOdW1iZXI9fSBsaW1pdCBudW1iZXIgb2YgbWVzc2FnZXMgdG8gZmV0Y2hcbiAgICogQHJldHVybnMge1Rpbm9kZS5NZXRhR2V0QnVpbGRlcn0gPHR0PnRoaXM8L3R0PiBvYmplY3QuXG4gICAqL1xuICB3aXRoRGF0YTogZnVuY3Rpb24oc2luY2UsIGJlZm9yZSwgbGltaXQpIHtcbiAgICB0aGlzLndoYXRbJ2RhdGEnXSA9IHtcbiAgICAgIHNpbmNlOiBzaW5jZSxcbiAgICAgIGJlZm9yZTogYmVmb3JlLFxuICAgICAgbGltaXQ6IGxpbWl0XG4gICAgfTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcblxuICAvKipcbiAgICogQWRkIHF1ZXJ5IHBhcmFtZXRlcnMgdG8gZmV0Y2ggbWVzc2FnZXMgbmV3ZXIgdGhhbiB0aGUgbGF0ZXN0IHNhdmVkIG1lc3NhZ2UuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyPX0gbGltaXQgbnVtYmVyIG9mIG1lc3NhZ2VzIHRvIGZldGNoXG4gICAqXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuTWV0YUdldEJ1aWxkZXJ9IDx0dD50aGlzPC90dD4gb2JqZWN0LlxuICAgKi9cbiAgd2l0aExhdGVyRGF0YTogZnVuY3Rpb24obGltaXQpIHtcbiAgICByZXR1cm4gdGhpcy53aXRoRGF0YSh0aGlzLnRvcGljLl9tYXhTZXEgPiAwID8gdGhpcy50b3BpYy5fbWF4U2VxICsgMSA6IHVuZGVmaW5lZCwgdW5kZWZpbmVkLCBsaW1pdCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIG1lc3NhZ2VzIG9sZGVyIHRoYW4gdGhlIGVhcmxpZXN0IHNhdmVkIG1lc3NhZ2UuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyPX0gbGltaXQgbWF4aW11bSBudW1iZXIgb2YgbWVzc2FnZXMgdG8gZmV0Y2guXG4gICAqXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuTWV0YUdldEJ1aWxkZXJ9IDx0dD50aGlzPC90dD4gb2JqZWN0LlxuICAgKi9cbiAgd2l0aEVhcmxpZXJEYXRhOiBmdW5jdGlvbihsaW1pdCkge1xuICAgIHJldHVybiB0aGlzLndpdGhEYXRhKHVuZGVmaW5lZCwgdGhpcy50b3BpYy5fbWluU2VxID4gMCA/IHRoaXMudG9waWMuX21pblNlcSA6IHVuZGVmaW5lZCwgbGltaXQpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBBZGQgcXVlcnkgcGFyYW1ldGVycyB0byBmZXRjaCB0b3BpYyBkZXNjcmlwdGlvbiBpZiBpdCdzIG5ld2VyIHRoYW4gdGhlIGdpdmVuIHRpbWVzdGFtcC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5NZXRhR2V0QnVpbGRlciNcbiAgICpcbiAgICogQHBhcmFtIHtEYXRlPX0gaW1zIGZldGNoIG1lc3NhZ2VzIG5ld2VyIHRoYW4gdGhpcyB0aW1lc3RhbXAuXG4gICAqXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuTWV0YUdldEJ1aWxkZXJ9IDx0dD50aGlzPC90dD4gb2JqZWN0LlxuICAgKi9cbiAgd2l0aERlc2M6IGZ1bmN0aW9uKGltcykge1xuICAgIHRoaXMud2hhdFsnZGVzYyddID0ge1xuICAgICAgaW1zOiBpbXNcbiAgICB9O1xuICAgIHJldHVybiB0aGlzO1xuICB9LFxuXG4gIC8qKlxuICAgKiBBZGQgcXVlcnkgcGFyYW1ldGVycyB0byBmZXRjaCB0b3BpYyBkZXNjcmlwdGlvbiBpZiBpdCdzIG5ld2VyIHRoYW4gdGhlIGxhc3QgdXBkYXRlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLk1ldGFHZXRCdWlsZGVyI1xuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLk1ldGFHZXRCdWlsZGVyfSA8dHQ+dGhpczwvdHQ+IG9iamVjdC5cbiAgICovXG4gIHdpdGhMYXRlckRlc2M6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLndpdGhEZXNjKHRoaXMuX2dldF9pbXMoKSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIHN1YnNjcmlwdGlvbnMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7RGF0ZT19IGltcyBmZXRjaCBzdWJzY3JpcHRpb25zIG1vZGlmaWVkIG1vcmUgcmVjZW50bHkgdGhhbiB0aGlzIHRpbWVzdGFtcFxuICAgKiBAcGFyYW0ge051bWJlcj19IGxpbWl0IG1heGltdW0gbnVtYmVyIG9mIHN1YnNjcmlwdGlvbnMgdG8gZmV0Y2guXG4gICAqIEBwYXJhbSB7U3RyaW5nPX0gdXNlck9yVG9waWMgdXNlciBJRCBvciB0b3BpYyBuYW1lIHRvIGZldGNoIGZvciBmZXRjaGluZyBvbmUgc3Vic2NyaXB0aW9uLlxuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLk1ldGFHZXRCdWlsZGVyfSA8dHQ+dGhpczwvdHQ+IG9iamVjdC5cbiAgICovXG4gIHdpdGhTdWI6IGZ1bmN0aW9uKGltcywgbGltaXQsIHVzZXJPclRvcGljKSB7XG4gICAgY29uc3Qgb3B0cyA9IHtcbiAgICAgIGltczogaW1zLFxuICAgICAgbGltaXQ6IGxpbWl0XG4gICAgfTtcbiAgICBpZiAodGhpcy50b3BpYy5nZXRUeXBlKCkgPT0gJ21lJykge1xuICAgICAgb3B0cy50b3BpYyA9IHVzZXJPclRvcGljO1xuICAgIH0gZWxzZSB7XG4gICAgICBvcHRzLnVzZXIgPSB1c2VyT3JUb3BpYztcbiAgICB9XG4gICAgdGhpcy53aGF0WydzdWInXSA9IG9wdHM7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIGEgc2luZ2xlIHN1YnNjcmlwdGlvbi5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5NZXRhR2V0QnVpbGRlciNcbiAgICpcbiAgICogQHBhcmFtIHtEYXRlPX0gaW1zIGZldGNoIHN1YnNjcmlwdGlvbnMgbW9kaWZpZWQgbW9yZSByZWNlbnRseSB0aGFuIHRoaXMgdGltZXN0YW1wXG4gICAqIEBwYXJhbSB7U3RyaW5nPX0gdXNlck9yVG9waWMgdXNlciBJRCBvciB0b3BpYyBuYW1lIHRvIGZldGNoIGZvciBmZXRjaGluZyBvbmUgc3Vic2NyaXB0aW9uLlxuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLk1ldGFHZXRCdWlsZGVyfSA8dHQ+dGhpczwvdHQ+IG9iamVjdC5cbiAgICovXG4gIHdpdGhPbmVTdWI6IGZ1bmN0aW9uKGltcywgdXNlck9yVG9waWMpIHtcbiAgICByZXR1cm4gdGhpcy53aXRoU3ViKGltcywgdW5kZWZpbmVkLCB1c2VyT3JUb3BpYyk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIGEgc2luZ2xlIHN1YnNjcmlwdGlvbiBpZiBpdCdzIGJlZW4gdXBkYXRlZCBzaW5jZSB0aGUgbGFzdCB1cGRhdGUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nPX0gdXNlck9yVG9waWMgdXNlciBJRCBvciB0b3BpYyBuYW1lIHRvIGZldGNoIGZvciBmZXRjaGluZyBvbmUgc3Vic2NyaXB0aW9uLlxuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLk1ldGFHZXRCdWlsZGVyfSA8dHQ+dGhpczwvdHQ+IG9iamVjdC5cbiAgICovXG4gIHdpdGhMYXRlck9uZVN1YjogZnVuY3Rpb24odXNlck9yVG9waWMpIHtcbiAgICByZXR1cm4gdGhpcy53aXRoT25lU3ViKHRoaXMudG9waWMuX2xhc3RTdWJzVXBkYXRlLCB1c2VyT3JUb3BpYyk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIHN1YnNjcmlwdGlvbnMgdXBkYXRlZCBzaW5jZSB0aGUgbGFzdCB1cGRhdGUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyPX0gbGltaXQgbWF4aW11bSBudW1iZXIgb2Ygc3Vic2NyaXB0aW9ucyB0byBmZXRjaC5cbiAgICpcbiAgICogQHJldHVybnMge1Rpbm9kZS5NZXRhR2V0QnVpbGRlcn0gPHR0PnRoaXM8L3R0PiBvYmplY3QuXG4gICAqL1xuICB3aXRoTGF0ZXJTdWI6IGZ1bmN0aW9uKGxpbWl0KSB7XG4gICAgcmV0dXJuIHRoaXMud2l0aFN1YihcbiAgICAgIHRoaXMudG9waWMuZ2V0VHlwZSgpID09ICdwMnAnID8gdGhpcy5fZ2V0X2ltcygpIDogdGhpcy50b3BpYy5fbGFzdFN1YnNVcGRhdGUsXG4gICAgICBsaW1pdCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIHRvcGljIHRhZ3MuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuTWV0YUdldEJ1aWxkZXJ9IDx0dD50aGlzPC90dD4gb2JqZWN0LlxuICAgKi9cbiAgd2l0aFRhZ3M6IGZ1bmN0aW9uKCkge1xuICAgIHRoaXMud2hhdFsndGFncyddID0gdHJ1ZTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcblxuICAvKipcbiAgICogQWRkIHF1ZXJ5IHBhcmFtZXRlcnMgdG8gZmV0Y2ggdXNlcidzIGNyZWRlbnRpYWxzLiAnbWUnIHRvcGljIG9ubHkuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuTWV0YUdldEJ1aWxkZXJ9IDx0dD50aGlzPC90dD4gb2JqZWN0LlxuICAgKi9cbiAgd2l0aENyZWQ6IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLnRvcGljLmdldFR5cGUoKSA9PSAnbWUnKSB7XG4gICAgICB0aGlzLndoYXRbJ2NyZWQnXSA9IHRydWU7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMudG9waWMuX3Rpbm9kZS5sb2dnZXIoXCJFUlJPUjogSW52YWxpZCB0b3BpYyB0eXBlIGZvciBNZXRhR2V0QnVpbGRlcjp3aXRoQ3JlZHNcIiwgdGhpcy50b3BpYy5nZXRUeXBlKCkpO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcztcbiAgfSxcblxuICAvKipcbiAgICogQWRkIHF1ZXJ5IHBhcmFtZXRlcnMgdG8gZmV0Y2ggZGVsZXRlZCBtZXNzYWdlcyB3aXRoaW4gZXhwbGljaXQgbGltaXRzLiBBbnkvYWxsIHBhcmFtZXRlcnMgY2FuIGJlIG51bGwuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTWV0YUdldEJ1aWxkZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyPX0gc2luY2UgaWRzIG9mIG1lc3NhZ2VzIGRlbGV0ZWQgc2luY2UgdGhpcyAnZGVsJyBpZCAoaW5jbHVzaXZlKVxuICAgKiBAcGFyYW0ge051bWJlcj19IGxpbWl0IG51bWJlciBvZiBkZWxldGVkIG1lc3NhZ2UgaWRzIHRvIGZldGNoXG4gICAqXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuTWV0YUdldEJ1aWxkZXJ9IDx0dD50aGlzPC90dD4gb2JqZWN0LlxuICAgKi9cbiAgd2l0aERlbDogZnVuY3Rpb24oc2luY2UsIGxpbWl0KSB7XG4gICAgaWYgKHNpbmNlIHx8IGxpbWl0KSB7XG4gICAgICB0aGlzLndoYXRbJ2RlbCddID0ge1xuICAgICAgICBzaW5jZTogc2luY2UsXG4gICAgICAgIGxpbWl0OiBsaW1pdFxuICAgICAgfTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBxdWVyeSBwYXJhbWV0ZXJzIHRvIGZldGNoIG1lc3NhZ2VzIGRlbGV0ZWQgYWZ0ZXIgdGhlIHNhdmVkICdkZWwnIGlkLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLk1ldGFHZXRCdWlsZGVyI1xuICAgKlxuICAgKiBAcGFyYW0ge051bWJlcj19IGxpbWl0IG51bWJlciBvZiBkZWxldGVkIG1lc3NhZ2UgaWRzIHRvIGZldGNoXG4gICAqXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuTWV0YUdldEJ1aWxkZXJ9IDx0dD50aGlzPC90dD4gb2JqZWN0LlxuICAgKi9cbiAgd2l0aExhdGVyRGVsOiBmdW5jdGlvbihsaW1pdCkge1xuICAgIC8vIFNwZWNpZnkgJ3NpbmNlJyBvbmx5IGlmIHdlIGhhdmUgYWxyZWFkeSByZWNlaXZlZCBzb21lIG1lc3NhZ2VzLiBJZlxuICAgIC8vIHdlIGhhdmUgbm8gbG9jYWxseSBjYWNoZWQgbWVzc2FnZXMgdGhlbiB3ZSBkb24ndCBjYXJlIGlmIGFueSBtZXNzYWdlcyB3ZXJlIGRlbGV0ZWQuXG4gICAgcmV0dXJuIHRoaXMud2l0aERlbCh0aGlzLnRvcGljLl9tYXhTZXEgPiAwID8gdGhpcy50b3BpYy5fbWF4RGVsICsgMSA6IHVuZGVmaW5lZCwgbGltaXQpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3QgcGFyYW1ldGVyc1xuICAgKiBAbWVtYmVyb2YgVGlub2RlLk1ldGFHZXRCdWlsZGVyI1xuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLkdldFF1ZXJ5fSBHZXQgcXVlcnlcbiAgICovXG4gIGJ1aWxkOiBmdW5jdGlvbigpIHtcbiAgICBjb25zdCB3aGF0ID0gW107XG4gICAgY29uc3QgaW5zdGFuY2UgPSB0aGlzO1xuICAgIGxldCBwYXJhbXMgPSB7fTtcbiAgICBbJ2RhdGEnLCAnc3ViJywgJ2Rlc2MnLCAndGFncycsICdjcmVkJywgJ2RlbCddLm1hcChmdW5jdGlvbihrZXkpIHtcbiAgICAgIGlmIChpbnN0YW5jZS53aGF0Lmhhc093blByb3BlcnR5KGtleSkpIHtcbiAgICAgICAgd2hhdC5wdXNoKGtleSk7XG4gICAgICAgIGlmIChPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhpbnN0YW5jZS53aGF0W2tleV0pLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBwYXJhbXNba2V5XSA9IGluc3RhbmNlLndoYXRba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0pO1xuICAgIGlmICh3aGF0Lmxlbmd0aCA+IDApIHtcbiAgICAgIHBhcmFtcy53aGF0ID0gd2hhdC5qb2luKCcgJyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHBhcmFtcyA9IHVuZGVmaW5lZDtcbiAgICB9XG4gICAgcmV0dXJuIHBhcmFtcztcbiAgfVxufTtcblxuLyoqXG4gKiBIZWxwZXIgY2xhc3MgZm9yIGhhbmRsaW5nIGFjY2VzcyBtb2RlLlxuICpcbiAqIEBjbGFzcyBBY2Nlc3NNb2RlXG4gKiBAbWVtYmVyb2YgVGlub2RlXG4gKlxuICogQHBhcmFtIHtBY2Nlc3NNb2RlfE9iamVjdD19IGFjcyBBY2Nlc3NNb2RlIHRvIGNvcHkgb3IgYWNjZXNzIG1vZGUgb2JqZWN0IHJlY2VpdmVkIGZyb20gdGhlIHNlcnZlci5cbiAqL1xudmFyIEFjY2Vzc01vZGUgPSBmdW5jdGlvbihhY3MpIHtcbiAgaWYgKGFjcykge1xuICAgIHRoaXMuZ2l2ZW4gPSB0eXBlb2YgYWNzLmdpdmVuID09ICdudW1iZXInID8gYWNzLmdpdmVuIDogQWNjZXNzTW9kZS5kZWNvZGUoYWNzLmdpdmVuKTtcbiAgICB0aGlzLndhbnQgPSB0eXBlb2YgYWNzLndhbnQgPT0gJ251bWJlcicgPyBhY3Mud2FudCA6IEFjY2Vzc01vZGUuZGVjb2RlKGFjcy53YW50KTtcbiAgICB0aGlzLm1vZGUgPSBhY3MubW9kZSA/ICh0eXBlb2YgYWNzLm1vZGUgPT0gJ251bWJlcicgPyBhY3MubW9kZSA6IEFjY2Vzc01vZGUuZGVjb2RlKGFjcy5tb2RlKSkgOlxuICAgICAgKHRoaXMuZ2l2ZW4gJiB0aGlzLndhbnQpO1xuICB9XG59O1xuXG5BY2Nlc3NNb2RlLl9OT05FID0gMHgwMDtcbkFjY2Vzc01vZGUuX0pPSU4gPSAweDAxO1xuQWNjZXNzTW9kZS5fUkVBRCA9IDB4MDI7XG5BY2Nlc3NNb2RlLl9XUklURSA9IDB4MDQ7XG5BY2Nlc3NNb2RlLl9QUkVTID0gMHgwODtcbkFjY2Vzc01vZGUuX0FQUFJPVkUgPSAweDEwO1xuQWNjZXNzTW9kZS5fU0hBUkUgPSAweDIwO1xuQWNjZXNzTW9kZS5fREVMRVRFID0gMHg0MDtcbkFjY2Vzc01vZGUuX09XTkVSID0gMHg4MDtcblxuQWNjZXNzTW9kZS5fQklUTUFTSyA9IEFjY2Vzc01vZGUuX0pPSU4gfCBBY2Nlc3NNb2RlLl9SRUFEIHwgQWNjZXNzTW9kZS5fV1JJVEUgfCBBY2Nlc3NNb2RlLl9QUkVTIHxcbiAgQWNjZXNzTW9kZS5fQVBQUk9WRSB8IEFjY2Vzc01vZGUuX1NIQVJFIHwgQWNjZXNzTW9kZS5fREVMRVRFIHwgQWNjZXNzTW9kZS5fT1dORVI7XG5BY2Nlc3NNb2RlLl9JTlZBTElEID0gMHgxMDAwMDA7XG5cbkFjY2Vzc01vZGUuX2NoZWNrRmxhZyA9IGZ1bmN0aW9uKHZhbCwgc2lkZSwgZmxhZykge1xuICBzaWRlID0gc2lkZSB8fCAnbW9kZSc7XG4gIGlmIChbJ2dpdmVuJywgJ3dhbnQnLCAnbW9kZSddLmluY2x1ZGVzKHNpZGUpKSB7XG4gICAgcmV0dXJuICgodmFsW3NpZGVdICYgZmxhZykgIT0gMCk7XG4gIH1cbiAgdGhyb3cgbmV3IEVycm9yKFwiSW52YWxpZCBBY2Nlc3NNb2RlIGNvbXBvbmVudCAnXCIgKyBzaWRlICsgXCInXCIpO1xufVxuXG4vKipcbiAqIFBhcnNlIHN0cmluZyBpbnRvIGFuIGFjY2VzcyBtb2RlIHZhbHVlLlxuICogQG1lbWJlcm9mIFRpbm9kZS5BY2Nlc3NNb2RlXG4gKiBAc3RhdGljXG4gKlxuICogQHBhcmFtIHtzdHJpbmcgfCBudW1iZXJ9IG1vZGUgLSBlaXRoZXIgYSBTdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIGFjY2VzcyBtb2RlIHRvIHBhcnNlIG9yIGEgc2V0IG9mIGJpdHMgdG8gYXNzaWduLlxuICogQHJldHVybnMge251bWJlcn0gLSBBY2Nlc3MgbW9kZSBhcyBhIG51bWVyaWMgdmFsdWUuXG4gKi9cbkFjY2Vzc01vZGUuZGVjb2RlID0gZnVuY3Rpb24oc3RyKSB7XG4gIGlmICghc3RyKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH0gZWxzZSBpZiAodHlwZW9mIHN0ciA9PSAnbnVtYmVyJykge1xuICAgIHJldHVybiBzdHIgJiBBY2Nlc3NNb2RlLl9CSVRNQVNLO1xuICB9IGVsc2UgaWYgKHN0ciA9PT0gJ04nIHx8IHN0ciA9PT0gJ24nKSB7XG4gICAgcmV0dXJuIEFjY2Vzc01vZGUuX05PTkU7XG4gIH1cblxuICBjb25zdCBiaXRtYXNrID0ge1xuICAgICdKJzogQWNjZXNzTW9kZS5fSk9JTixcbiAgICAnUic6IEFjY2Vzc01vZGUuX1JFQUQsXG4gICAgJ1cnOiBBY2Nlc3NNb2RlLl9XUklURSxcbiAgICAnUCc6IEFjY2Vzc01vZGUuX1BSRVMsXG4gICAgJ0EnOiBBY2Nlc3NNb2RlLl9BUFBST1ZFLFxuICAgICdTJzogQWNjZXNzTW9kZS5fU0hBUkUsXG4gICAgJ0QnOiBBY2Nlc3NNb2RlLl9ERUxFVEUsXG4gICAgJ08nOiBBY2Nlc3NNb2RlLl9PV05FUlxuICB9O1xuXG4gIGxldCBtMCA9IEFjY2Vzc01vZGUuX05PTkU7XG5cbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBzdHIubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCBiaXQgPSBiaXRtYXNrW3N0ci5jaGFyQXQoaSkudG9VcHBlckNhc2UoKV07XG4gICAgaWYgKCFiaXQpIHtcbiAgICAgIC8vIFVucmVjb2duaXplZCBiaXQsIHNraXAuXG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgbTAgfD0gYml0O1xuICB9XG4gIHJldHVybiBtMDtcbn07XG5cbi8qKlxuICogQ29udmVydCBudW1lcmljIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBhY2Nlc3MgbW9kZSBpbnRvIGEgc3RyaW5nLlxuICpcbiAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7bnVtYmVyfSB2YWwgLSBhY2Nlc3MgbW9kZSB2YWx1ZSB0byBjb252ZXJ0IHRvIGEgc3RyaW5nLlxuICogQHJldHVybnMge3N0cmluZ30gLSBBY2Nlc3MgbW9kZSBhcyBhIHN0cmluZy5cbiAqL1xuQWNjZXNzTW9kZS5lbmNvZGUgPSBmdW5jdGlvbih2YWwpIHtcbiAgaWYgKHZhbCA9PT0gbnVsbCB8fCB2YWwgPT09IEFjY2Vzc01vZGUuX0lOVkFMSUQpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfSBlbHNlIGlmICh2YWwgPT09IEFjY2Vzc01vZGUuX05PTkUpIHtcbiAgICByZXR1cm4gJ04nO1xuICB9XG5cbiAgY29uc3QgYml0bWFzayA9IFsnSicsICdSJywgJ1cnLCAnUCcsICdBJywgJ1MnLCAnRCcsICdPJ107XG4gIGxldCByZXMgPSAnJztcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBiaXRtYXNrLmxlbmd0aDsgaSsrKSB7XG4gICAgaWYgKCh2YWwgJiAoMSA8PCBpKSkgIT0gMCkge1xuICAgICAgcmVzID0gcmVzICsgYml0bWFza1tpXTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlcztcbn07XG5cbi8qKlxuICogVXBkYXRlIG51bWVyaWMgcmVwcmVzZW50YXRpb24gb2YgYWNjZXNzIG1vZGUgd2l0aCB0aGUgbmV3IHZhbHVlLiBUaGUgdmFsdWVcbiAqIGlzIG9uZSBvZiB0aGUgZm9sbG93aW5nOlxuICogIC0gYSBzdHJpbmcgc3RhcnRpbmcgd2l0aCAnKycgb3IgJy0nIHRoZW4gdGhlIGJpdHMgdG8gYWRkIG9yIHJlbW92ZSwgZS5nLiAnK1ItVycgb3IgJy1QUycuXG4gKiAgLSBhIG5ldyB2YWx1ZSBvZiBhY2Nlc3MgbW9kZVxuICpcbiAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICogQHN0YXRpY1xuICpcbiAqIEBwYXJhbSB7bnVtYmVyfSB2YWwgLSBhY2Nlc3MgbW9kZSB2YWx1ZSB0byB1cGRhdGUuXG4gKiBAcGFyYW0ge3N0cmluZ30gdXBkIC0gdXBkYXRlIHRvIGFwcGx5IHRvIHZhbC5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IC0gdXBkYXRlZCBhY2Nlc3MgbW9kZS5cbiAqL1xuQWNjZXNzTW9kZS51cGRhdGUgPSBmdW5jdGlvbih2YWwsIHVwZCkge1xuICBpZiAoIXVwZCB8fCB0eXBlb2YgdXBkICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbDtcbiAgfVxuXG4gIGxldCBhY3Rpb24gPSB1cGQuY2hhckF0KDApO1xuICBpZiAoYWN0aW9uID09ICcrJyB8fCBhY3Rpb24gPT0gJy0nKSB7XG4gICAgbGV0IHZhbDAgPSB2YWw7XG4gICAgLy8gU3BsaXQgZGVsdGEtc3RyaW5nIGxpa2UgJytBQkMtREVGK1onIGludG8gYW4gYXJyYXkgb2YgcGFydHMgaW5jbHVkaW5nICsgYW5kIC0uXG4gICAgY29uc3QgcGFydHMgPSB1cGQuc3BsaXQoLyhbLStdKS8pO1xuICAgIC8vIFN0YXJ0aW5nIGl0ZXJhdGlvbiBmcm9tIDEgYmVjYXVzZSBTdHJpbmcuc3BsaXQoKSBjcmVhdGVzIGFuIGFycmF5IHdpdGggdGhlIGZpcnN0IGVtcHR5IGVsZW1lbnQuXG4gICAgLy8gSXRlcmF0aW5nIGJ5IDIgYmVjYXVzZSB3ZSBwYXJzZSBwYWlycyArLy0gdGhlbiBkYXRhLlxuICAgIGZvciAobGV0IGkgPSAxOyBpIDwgcGFydHMubGVuZ3RoIC0gMTsgaSArPSAyKSB7XG4gICAgICBhY3Rpb24gPSBwYXJ0c1tpXTtcbiAgICAgIGNvbnN0IG0wID0gQWNjZXNzTW9kZS5kZWNvZGUocGFydHNbaSArIDFdKTtcbiAgICAgIGlmIChtMCA9PSBBY2Nlc3NNb2RlLl9JTlZBTElEKSB7XG4gICAgICAgIHJldHVybiB2YWw7XG4gICAgICB9XG4gICAgICBpZiAobTAgPT0gbnVsbCkge1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGlmIChhY3Rpb24gPT09ICcrJykge1xuICAgICAgICB2YWwwIHw9IG0wO1xuICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICctJykge1xuICAgICAgICB2YWwwICY9IH5tMDtcbiAgICAgIH1cbiAgICB9XG4gICAgdmFsID0gdmFsMDtcbiAgfSBlbHNlIHtcbiAgICAvLyBUaGUgc3RyaW5nIGlzIGFuIGV4cGxpY2l0IG5ldyB2YWx1ZSAnQUJDJyByYXRoZXIgdGhhbiBkZWx0YS5cbiAgICBjb25zdCB2YWwwID0gQWNjZXNzTW9kZS5kZWNvZGUodXBkKTtcbiAgICBpZiAodmFsMCAhPSBBY2Nlc3NNb2RlLl9JTlZBTElEKSB7XG4gICAgICB2YWwgPSB2YWwwO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB2YWw7XG59O1xuXG4vKipcbiAqIEFjY2Vzc01vZGUgaXMgYSBjbGFzcyByZXByZXNlbnRpbmcgdG9waWMgYWNjZXNzIG1vZGUuXG4gKiBAY2xhc3MgVG9waWNcbiAqIEBtZW1iZXJvZiBUaW5vZGVcbiAqL1xuQWNjZXNzTW9kZS5wcm90b3R5cGUgPSB7XG4gIC8qKlxuICAgKiBDdXN0b20gZm9ybWF0dGVyXG4gICAqL1xuICB0b1N0cmluZzogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuICd7XCJtb2RlXCI6IFwiJyArIEFjY2Vzc01vZGUuZW5jb2RlKHRoaXMubW9kZSkgK1xuICAgICAgJ1wiLCBcImdpdmVuXCI6IFwiJyArIEFjY2Vzc01vZGUuZW5jb2RlKHRoaXMuZ2l2ZW4pICtcbiAgICAgICdcIiwgXCJ3YW50XCI6IFwiJyArIEFjY2Vzc01vZGUuZW5jb2RlKHRoaXMud2FudCkgKyAnXCJ9JztcbiAgfSxcbiAgLyoqXG4gICAqIENvbnZlcnQgbnVtZXJpYyB2YWx1ZXMgdG8gc3RyaW5ncy5cbiAgICovXG4gIGpzb25IZWxwZXI6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB7XG4gICAgICBtb2RlOiBBY2Nlc3NNb2RlLmVuY29kZSh0aGlzLm1vZGUpLFxuICAgICAgZ2l2ZW46IEFjY2Vzc01vZGUuZW5jb2RlKHRoaXMuZ2l2ZW4pLFxuICAgICAgd2FudDogQWNjZXNzTW9kZS5lbmNvZGUodGhpcy53YW50KVxuICAgIH07XG4gIH0sXG4gIC8qKlxuICAgKiBBc3NpZ24gdmFsdWUgdG8gJ21vZGUnLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmcgfCBudW1iZXJ9IG0gLSBlaXRoZXIgYSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIGFjY2VzcyBtb2RlIG9yIGEgc2V0IG9mIGJpdHMuXG4gICAqIEByZXR1cm5zIHtBY2Nlc3NNb2RlfSAtIDxiPnRoaXM8L2I+IEFjY2Vzc01vZGUuXG4gICAqL1xuICBzZXRNb2RlOiBmdW5jdGlvbihtKSB7XG4gICAgdGhpcy5tb2RlID0gQWNjZXNzTW9kZS5kZWNvZGUobSk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG4gIC8qKlxuICAgKiBVcGRhdGUgJ21vZGUnIHZhbHVlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IHUgLSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIGNoYW5nZXMgdG8gYXBwbHkgdG8gYWNjZXNzIG1vZGUuXG4gICAqIEByZXR1cm5zIHtBY2Nlc3NNb2RlfSAtIDxiPnRoaXM8L2I+IEFjY2Vzc01vZGUuXG4gICAqL1xuICB1cGRhdGVNb2RlOiBmdW5jdGlvbih1KSB7XG4gICAgdGhpcy5tb2RlID0gQWNjZXNzTW9kZS51cGRhdGUodGhpcy5tb2RlLCB1KTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcbiAgLyoqXG4gICAqIEdldCAnbW9kZScgdmFsdWUgYXMgYSBzdHJpbmcuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICAgKlxuICAgKiBAcmV0dXJucyB7c3RyaW5nfSAtIDxiPm1vZGU8L2I+IHZhbHVlLlxuICAgKi9cbiAgZ2V0TW9kZTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIEFjY2Vzc01vZGUuZW5jb2RlKHRoaXMubW9kZSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFzc2lnbiAnZ2l2ZW4nIHZhbHVlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmcgfCBudW1iZXJ9IGcgLSBlaXRoZXIgYSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIGFjY2VzcyBtb2RlIG9yIGEgc2V0IG9mIGJpdHMuXG4gICAqIEByZXR1cm5zIHtBY2Nlc3NNb2RlfSAtIDxiPnRoaXM8L2I+IEFjY2Vzc01vZGUuXG4gICAqL1xuICBzZXRHaXZlbjogZnVuY3Rpb24oZykge1xuICAgIHRoaXMuZ2l2ZW4gPSBBY2Nlc3NNb2RlLmRlY29kZShnKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcbiAgLyoqXG4gICAqIFVwZGF0ZSAnZ2l2ZW4nIHZhbHVlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IHUgLSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIGNoYW5nZXMgdG8gYXBwbHkgdG8gYWNjZXNzIG1vZGUuXG4gICAqIEByZXR1cm5zIHtBY2Nlc3NNb2RlfSAtIDxiPnRoaXM8L2I+IEFjY2Vzc01vZGUuXG4gICAqL1xuICB1cGRhdGVHaXZlbjogZnVuY3Rpb24odSkge1xuICAgIHRoaXMuZ2l2ZW4gPSBBY2Nlc3NNb2RlLnVwZGF0ZSh0aGlzLmdpdmVuLCB1KTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcbiAgLyoqXG4gICAqIEdldCAnZ2l2ZW4nIHZhbHVlIGFzIGEgc3RyaW5nLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHJldHVybnMge3N0cmluZ30gLSA8Yj5naXZlbjwvYj4gdmFsdWUuXG4gICAqL1xuICBnZXRHaXZlbjogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIEFjY2Vzc01vZGUuZW5jb2RlKHRoaXMuZ2l2ZW4pO1xuICB9LFxuXG4gIC8qKlxuICAgKiBBc3NpZ24gJ3dhbnQnIHZhbHVlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmcgfCBudW1iZXJ9IHcgLSBlaXRoZXIgYSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIGFjY2VzcyBtb2RlIG9yIGEgc2V0IG9mIGJpdHMuXG4gICAqIEByZXR1cm5zIHtBY2Nlc3NNb2RlfSAtIDxiPnRoaXM8L2I+IEFjY2Vzc01vZGUuXG4gICAqL1xuICBzZXRXYW50OiBmdW5jdGlvbih3KSB7XG4gICAgdGhpcy53YW50ID0gQWNjZXNzTW9kZS5kZWNvZGUodyk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG4gIC8qKlxuICAgKiBVcGRhdGUgJ3dhbnQnIHZhbHVlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IHUgLSBzdHJpbmcgcmVwcmVzZW50YXRpb24gb2YgdGhlIGNoYW5nZXMgdG8gYXBwbHkgdG8gYWNjZXNzIG1vZGUuXG4gICAqIEByZXR1cm5zIHtBY2Nlc3NNb2RlfSAtIDxiPnRoaXM8L2I+IEFjY2Vzc01vZGUuXG4gICAqL1xuICB1cGRhdGVXYW50OiBmdW5jdGlvbih1KSB7XG4gICAgdGhpcy53YW50ID0gQWNjZXNzTW9kZS51cGRhdGUodGhpcy53YW50LCB1KTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcbiAgLyoqXG4gICAqIEdldCAnd2FudCcgdmFsdWUgYXMgYSBzdHJpbmcuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICAgKlxuICAgKiBAcmV0dXJucyB7c3RyaW5nfSAtIDxiPndhbnQ8L2I+IHZhbHVlLlxuICAgKi9cbiAgZ2V0V2FudDogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIEFjY2Vzc01vZGUuZW5jb2RlKHRoaXMud2FudCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEdldCBwZXJtaXNzaW9ucyBwcmVzZW50IGluICd3YW50JyBidXQgbWlzc2luZyBpbiAnZ2l2ZW4nLlxuICAgKiBJbnZlcnNlIG9mIHtAbGluayBUaW5vZGUuQWNjZXNzTW9kZSNnZXRFeGNlc3NpdmV9XG4gICAqXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICAgKlxuICAgKiBAcmV0dXJucyB7c3RyaW5nfSBwZXJtaXNzaW9ucyBwcmVzZW50IGluIDxiPndhbnQ8L2I+IGJ1dCBtaXNzaW5nIGluIDxiPmdpdmVuPC9iPi5cbiAgICovXG4gIGdldE1pc3Npbmc6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiBBY2Nlc3NNb2RlLmVuY29kZSh0aGlzLndhbnQgJiB+dGhpcy5naXZlbik7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEdldCBwZXJtaXNzaW9ucyBwcmVzZW50IGluICdnaXZlbicgYnV0IG1pc3NpbmcgaW4gJ3dhbnQnLlxuICAgKiBJbnZlcnNlIG9mIHtAbGluayBUaW5vZGUuQWNjZXNzTW9kZSNnZXRNaXNzaW5nfVxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICpcbiAgICogQHJldHVybnMge3N0cmluZ30gcGVybWlzc2lvbnMgcHJlc2VudCBpbiA8Yj5naXZlbjwvYj4gYnV0IG1pc3NpbmcgaW4gPGI+d2FudDwvYj4uXG4gICAqL1xuICBnZXRFeGNlc3NpdmU6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiBBY2Nlc3NNb2RlLmVuY29kZSh0aGlzLmdpdmVuICYgfnRoaXMud2FudCk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFVwZGF0ZSAnd2FudCcsICdnaXZlJywgYW5kICdtb2RlJyB2YWx1ZXMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICAgKlxuICAgKiBAcGFyYW0ge0FjY2Vzc01vZGV9IHZhbCAtIG5ldyBhY2Nlc3MgbW9kZSB2YWx1ZS5cbiAgICogQHJldHVybnMge0FjY2Vzc01vZGV9IC0gPGI+dGhpczwvYj4gQWNjZXNzTW9kZS5cbiAgICovXG4gIHVwZGF0ZUFsbDogZnVuY3Rpb24odmFsKSB7XG4gICAgaWYgKHZhbCkge1xuICAgICAgdGhpcy51cGRhdGVHaXZlbih2YWwuZ2l2ZW4pO1xuICAgICAgdGhpcy51cGRhdGVXYW50KHZhbC53YW50KTtcbiAgICAgIHRoaXMubW9kZSA9IHRoaXMuZ2l2ZW4gJiB0aGlzLndhbnQ7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9LFxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBPd25lciAoTykgZmxhZyBpcyBzZXQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICAgKiBAcGFyYW0ge3N0cmluZz19IHNpZGUgLSB3aGljaCBwZXJtaXNzaW9uIHRvIGNoZWNrOiBnaXZlbiwgd2FudCwgbW9kZTsgZGVmYXVsdDogbW9kZS5cbiAgICogQHJldHVybnMge2Jvb2xlYW59IC0gdHJ1ZSBpZiBmbGFnIGlzIHNldC5cbiAgICovXG4gIGlzT3duZXI6IGZ1bmN0aW9uKHNpZGUpIHtcbiAgICByZXR1cm4gQWNjZXNzTW9kZS5fY2hlY2tGbGFnKHRoaXMsIHNpZGUsIEFjY2Vzc01vZGUuX09XTkVSKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgUHJlc2VuY2UgKFApIGZsYWcgaXMgc2V0LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICogQHBhcmFtIHtzdHJpbmc9fSBzaWRlIC0gd2hpY2ggcGVybWlzc2lvbiB0byBjaGVjazogZ2l2ZW4sIHdhbnQsIG1vZGU7IGRlZmF1bHQ6IG1vZGUuXG4gICAqIEByZXR1cm5zIHtib29sZWFufSAtIHRydWUgaWYgZmxhZyBpcyBzZXQuXG4gICAqL1xuICBpc1ByZXNlbmNlcjogZnVuY3Rpb24oc2lkZSkge1xuICAgIHJldHVybiBBY2Nlc3NNb2RlLl9jaGVja0ZsYWcodGhpcywgc2lkZSwgQWNjZXNzTW9kZS5fUFJFUyk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIFByZXNlbmNlIChQKSBmbGFnIGlzIE5PVCBzZXQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICAgKiBAcGFyYW0ge3N0cmluZz19IHNpZGUgLSB3aGljaCBwZXJtaXNzaW9uIHRvIGNoZWNrOiBnaXZlbiwgd2FudCwgbW9kZTsgZGVmYXVsdDogbW9kZS5cbiAgICogQHJldHVybnMge2Jvb2xlYW59IC0gdHJ1ZSBpZiBmbGFnIGlzIHNldC5cbiAgICovXG4gIGlzTXV0ZWQ6IGZ1bmN0aW9uKHNpZGUpIHtcbiAgICByZXR1cm4gIXRoaXMuaXNQcmVzZW5jZXIoc2lkZSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIEpvaW4gKEopIGZsYWcgaXMgc2V0LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICogQHBhcmFtIHtzdHJpbmc9fSBzaWRlIC0gd2hpY2ggcGVybWlzc2lvbiB0byBjaGVjazogZ2l2ZW4sIHdhbnQsIG1vZGU7IGRlZmF1bHQ6IG1vZGUuXG4gICAqIEByZXR1cm5zIHtib29sZWFufSAtIHRydWUgaWYgZmxhZyBpcyBzZXQuXG4gICAqL1xuICBpc0pvaW5lcjogZnVuY3Rpb24oc2lkZSkge1xuICAgIHJldHVybiBBY2Nlc3NNb2RlLl9jaGVja0ZsYWcodGhpcywgc2lkZSwgQWNjZXNzTW9kZS5fSk9JTik7XG4gIH0sXG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIFJlYWRlciAoUikgZmxhZyBpcyBzZXQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuQWNjZXNzTW9kZVxuICAgKiBAcGFyYW0ge3N0cmluZz19IHNpZGUgLSB3aGljaCBwZXJtaXNzaW9uIHRvIGNoZWNrOiBnaXZlbiwgd2FudCwgbW9kZTsgZGVmYXVsdDogbW9kZS5cbiAgICogQHJldHVybnMge2Jvb2xlYW59IC0gdHJ1ZSBpZiBmbGFnIGlzIHNldC5cbiAgICovXG4gIGlzUmVhZGVyOiBmdW5jdGlvbihzaWRlKSB7XG4gICAgcmV0dXJuIEFjY2Vzc01vZGUuX2NoZWNrRmxhZyh0aGlzLCBzaWRlLCBBY2Nlc3NNb2RlLl9SRUFEKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgV3JpdGVyIChXKSBmbGFnIGlzIHNldC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5BY2Nlc3NNb2RlXG4gICAqIEBwYXJhbSB7c3RyaW5nPX0gc2lkZSAtIHdoaWNoIHBlcm1pc3Npb24gdG8gY2hlY2s6IGdpdmVuLCB3YW50LCBtb2RlOyBkZWZhdWx0OiBtb2RlLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSB0cnVlIGlmIGZsYWcgaXMgc2V0LlxuICAgKi9cbiAgaXNXcml0ZXI6IGZ1bmN0aW9uKHNpZGUpIHtcbiAgICByZXR1cm4gQWNjZXNzTW9kZS5fY2hlY2tGbGFnKHRoaXMsIHNpZGUsIEFjY2Vzc01vZGUuX1dSSVRFKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgQXBwcm92ZXIgKEEpIGZsYWcgaXMgc2V0LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICogQHBhcmFtIHtzdHJpbmc9fSBzaWRlIC0gd2hpY2ggcGVybWlzc2lvbiB0byBjaGVjazogZ2l2ZW4sIHdhbnQsIG1vZGU7IGRlZmF1bHQ6IG1vZGUuXG4gICAqIEByZXR1cm5zIHtib29sZWFufSAtIHRydWUgaWYgZmxhZyBpcyBzZXQuXG4gICAqL1xuICBpc0FwcHJvdmVyOiBmdW5jdGlvbihzaWRlKSB7XG4gICAgcmV0dXJuIEFjY2Vzc01vZGUuX2NoZWNrRmxhZyh0aGlzLCBzaWRlLCBBY2Nlc3NNb2RlLl9BUFBST1ZFKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgZWl0aGVyIG9uZSBvZiBPd25lciAoTykgb3IgQXBwcm92ZXIgKEEpIGZsYWdzIGlzIHNldC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5BY2Nlc3NNb2RlXG4gICAqIEBwYXJhbSB7c3RyaW5nPX0gc2lkZSAtIHdoaWNoIHBlcm1pc3Npb24gdG8gY2hlY2s6IGdpdmVuLCB3YW50LCBtb2RlOyBkZWZhdWx0OiBtb2RlLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSB0cnVlIGlmIGZsYWcgaXMgc2V0LlxuICAgKi9cbiAgaXNBZG1pbjogZnVuY3Rpb24oc2lkZSkge1xuICAgIHJldHVybiB0aGlzLmlzT3duZXIoc2lkZSkgfHwgdGhpcy5pc0FwcHJvdmVyKHNpZGUpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBlaXRoZXIgb25lIG9mIE93bmVyIChPKSwgQXBwcm92ZXIgKEEpLCBvciBTaGFyZXIgKFMpIGZsYWdzIGlzIHNldC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5BY2Nlc3NNb2RlXG4gICAqIEBwYXJhbSB7c3RyaW5nPX0gc2lkZSAtIHdoaWNoIHBlcm1pc3Npb24gdG8gY2hlY2s6IGdpdmVuLCB3YW50LCBtb2RlOyBkZWZhdWx0OiBtb2RlLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSB0cnVlIGlmIGZsYWcgaXMgc2V0LlxuICAgKi9cbiAgaXNTaGFyZXI6IGZ1bmN0aW9uKHNpZGUpIHtcbiAgICByZXR1cm4gdGhpcy5pc0FkbWluKHNpZGUpIHx8IEFjY2Vzc01vZGUuX2NoZWNrRmxhZyh0aGlzLCBzaWRlLCBBY2Nlc3NNb2RlLl9TSEFSRSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIERlbGV0ZXIgKEQpIGZsYWcgaXMgc2V0LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkFjY2Vzc01vZGVcbiAgICogQHBhcmFtIHtzdHJpbmc9fSBzaWRlIC0gd2hpY2ggcGVybWlzc2lvbiB0byBjaGVjazogZ2l2ZW4sIHdhbnQsIG1vZGU7IGRlZmF1bHQ6IG1vZGUuXG4gICAqIEByZXR1cm5zIHtib29sZWFufSAtIHRydWUgaWYgZmxhZyBpcyBzZXQuXG4gICAqL1xuICBpc0RlbGV0ZXI6IGZ1bmN0aW9uKHNpZGUpIHtcbiAgICByZXR1cm4gQWNjZXNzTW9kZS5fY2hlY2tGbGFnKHRoaXMsIHNpZGUsIEFjY2Vzc01vZGUuX0RFTEVURSk7XG4gIH1cbn07XG5cbi8qKlxuICogQGNhbGxiYWNrIFRpbm9kZS5Ub3BpYy5vbkRhdGFcbiAqIEBwYXJhbSB7RGF0YX0gZGF0YSAtIERhdGEgcGFja2V0XG4gKi9cbi8qKlxuICogVG9waWMgaXMgYSBjbGFzcyByZXByZXNlbnRpbmcgYSBsb2dpY2FsIGNvbW11bmljYXRpb24gY2hhbm5lbC5cbiAqIEBjbGFzcyBUb3BpY1xuICogQG1lbWJlcm9mIFRpbm9kZVxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lIC0gTmFtZSBvZiB0aGUgdG9waWMgdG8gY3JlYXRlLlxuICogQHBhcmFtIHtPYmplY3Q9fSBjYWxsYmFja3MgLSBPYmplY3Qgd2l0aCB2YXJpb3VzIGV2ZW50IGNhbGxiYWNrcy5cbiAqIEBwYXJhbSB7VGlub2RlLlRvcGljLm9uRGF0YX0gY2FsbGJhY2tzLm9uRGF0YSAtIENhbGxiYWNrIHdoaWNoIHJlY2VpdmVzIGEge2RhdGF9IG1lc3NhZ2UuXG4gKiBAcGFyYW0ge2NhbGxiYWNrfSBjYWxsYmFja3Mub25NZXRhIC0gQ2FsbGJhY2sgd2hpY2ggcmVjZWl2ZXMgYSB7bWV0YX0gbWVzc2FnZS5cbiAqIEBwYXJhbSB7Y2FsbGJhY2t9IGNhbGxiYWNrcy5vblByZXMgLSBDYWxsYmFjayB3aGljaCByZWNlaXZlcyBhIHtwcmVzfSBtZXNzYWdlLlxuICogQHBhcmFtIHtjYWxsYmFja30gY2FsbGJhY2tzLm9uSW5mbyAtIENhbGxiYWNrIHdoaWNoIHJlY2VpdmVzIGFuIHtpbmZvfSBtZXNzYWdlLlxuICogQHBhcmFtIHtjYWxsYmFja30gY2FsbGJhY2tzLm9uTWV0YURlc2MgLSBDYWxsYmFjayB3aGljaCByZWNlaXZlcyBjaGFuZ2VzIHRvIHRvcGljIGRlc2N0aW9wdGlvbiB7QGxpbmsgZGVzY30uXG4gKiBAcGFyYW0ge2NhbGxiYWNrfSBjYWxsYmFja3Mub25NZXRhU3ViIC0gQ2FsbGVkIGZvciBhIHNpbmdsZSBzdWJzY3JpcHRpb24gcmVjb3JkIGNoYW5nZS5cbiAqIEBwYXJhbSB7Y2FsbGJhY2t9IGNhbGxiYWNrcy5vblN1YnNVcGRhdGVkIC0gQ2FsbGVkIGFmdGVyIGEgYmF0Y2ggb2Ygc3Vic2NyaXB0aW9uIGNoYW5nZXMgaGF2ZSBiZWVuIHJlY2lldmVkIGFuZCBjYWNoZWQuXG4gKiBAcGFyYW0ge2NhbGxiYWNrfSBjYWxsYmFja3Mub25EZWxldGVUb3BpYyAtIENhbGxlZCBhZnRlciB0aGUgdG9waWMgaXMgZGVsZXRlZC5cbiAqIEBwYXJhbSB7Y2FsbGJhY2t9IGNhbGxiYWNscy5vbkFsbE1lc3NhZ2VzUmVjZWl2ZWQgLSBDYWxsZWQgd2hlbiBhbGwgcmVxdWVzdGVkIHtkYXRhfSBtZXNzYWdlcyBoYXZlIGJlZW4gcmVjaXZlZC5cbiAqL1xudmFyIFRvcGljID0gZnVuY3Rpb24obmFtZSwgY2FsbGJhY2tzKSB7XG4gIC8vIFBhcmVudCBUaW5vZGUgb2JqZWN0LlxuICB0aGlzLl90aW5vZGUgPSBudWxsO1xuXG4gIC8vIFNlcnZlci1wcm92aWRlZCBkYXRhLCBsb2NhbGx5IGltbXV0YWJsZS5cbiAgLy8gdG9waWMgbmFtZVxuICB0aGlzLm5hbWUgPSBuYW1lO1xuICAvLyB0aW1lc3RhbXAgd2hlbiB0aGUgdG9waWMgd2FzIGNyZWF0ZWRcbiAgdGhpcy5jcmVhdGVkID0gbnVsbDtcbiAgLy8gdGltZXN0YW1wIHdoZW4gdGhlIHRvcGljIHdhcyBsYXN0IHVwZGF0ZWRcbiAgdGhpcy51cGRhdGVkID0gbnVsbDtcbiAgLy8gdGltZXN0YW1wIG9mIHRoZSBsYXN0IG1lc3NhZ2VzXG4gIHRoaXMudG91Y2hlZCA9IG51bGw7XG4gIC8vIGFjY2VzcyBtb2RlLCBzZWUgQWNjZXNzTW9kZVxuICB0aGlzLmFjcyA9IG5ldyBBY2Nlc3NNb2RlKG51bGwpO1xuICAvLyBwZXItdG9waWMgcHJpdmF0ZSBkYXRhXG4gIHRoaXMucHJpdmF0ZSA9IG51bGw7XG4gIC8vIHBlci10b3BpYyBwdWJsaWMgZGF0YVxuICB0aGlzLnB1YmxpYyA9IG51bGw7XG5cbiAgLy8gTG9jYWxseSBjYWNoZWQgZGF0YVxuICAvLyBTdWJzY3JpYmVkIHVzZXJzLCBmb3IgdHJhY2tpbmcgcmVhZC9yZWN2L21zZyBub3RpZmljYXRpb25zLlxuICB0aGlzLl91c2VycyA9IHt9O1xuXG4gIC8vIEN1cnJlbnQgdmFsdWUgb2YgbG9jYWxseSBpc3N1ZWQgc2VxSWQsIHVzZWQgZm9yIHBlbmRpbmcgbWVzc2FnZXMuXG4gIHRoaXMuX3F1ZXVlZFNlcUlkID0gTE9DQUxfU0VRSUQ7XG5cbiAgLy8gVGhlIG1heGltdW0ga25vd24ge2RhdGEuc2VxfSB2YWx1ZS5cbiAgdGhpcy5fbWF4U2VxID0gMDtcbiAgLy8gVGhlIG1pbmltdW0ga25vd24ge2RhdGEuc2VxfSB2YWx1ZS5cbiAgdGhpcy5fbWluU2VxID0gMDtcbiAgLy8gSW5kaWNhdG9yIHRoYXQgdGhlIGxhc3QgcmVxdWVzdCBmb3IgZWFybGllciBtZXNzYWdlcyByZXR1cm5lZCAwLlxuICB0aGlzLl9ub0VhcmxpZXJNc2dzID0gZmFsc2U7XG4gIC8vIFRoZSBtYXhpbXVtIGtub3duIGRlbGV0aW9uIElELlxuICB0aGlzLl9tYXhEZWwgPSAwO1xuICAvLyBVc2VyIGRpc2NvdmVyeSB0YWdzXG4gIHRoaXMuX3RhZ3MgPSBbXTtcbiAgLy8gQ3JlZGVudGlhbHMgc3VjaCBhcyBlbWFpbCBvciBwaG9uZSBudW1iZXIuXG4gIHRoaXMuX2NyZWRlbnRpYWxzID0gW107XG4gIC8vIE1lc3NhZ2UgY2FjaGUsIHNvcnRlZCBieSBtZXNzYWdlIHNlcSB2YWx1ZXMsIGZyb20gb2xkIHRvIG5ldy5cbiAgdGhpcy5fbWVzc2FnZXMgPSBDQnVmZmVyKGZ1bmN0aW9uKGEsIGIpIHtcbiAgICByZXR1cm4gYS5zZXEgLSBiLnNlcTtcbiAgfSk7XG4gIC8vIEJvb2xlYW4sIHRydWUgaWYgdGhlIHRvcGljIGlzIGN1cnJlbnRseSBsaXZlXG4gIHRoaXMuX3N1YnNjcmliZWQgPSBmYWxzZTtcbiAgLy8gVGltZXN0YXAgd2hlbiB0b3BpYyBtZXRhLWRlc2MgdXBkYXRlIHdhcyByZWNpdmVkLlxuICB0aGlzLl9sYXN0RGVzY1VwZGF0ZSA9IG51bGw7XG4gIC8vIFRpbWVzdGFwIHdoZW4gdG9waWMgbWV0YS1zdWJzIHVwZGF0ZSB3YXMgcmVjaXZlZC5cbiAgdGhpcy5fbGFzdFN1YnNVcGRhdGUgPSBudWxsO1xuICAvLyBUb3BpYyBjcmVhdGVkIGJ1dCBub3QgeWV0IHN5bmNlZCB3aXRoIHRoZSBzZXJ2ZXIuIFVzZWQgb25seSBkdXJpbmcgaW5pdGlhbGl6YXRpb24uXG4gIHRoaXMuX25ldyA9IHRydWU7XG5cbiAgLy8gQ2FsbGJhY2tzXG4gIGlmIChjYWxsYmFja3MpIHtcbiAgICB0aGlzLm9uRGF0YSA9IGNhbGxiYWNrcy5vbkRhdGE7XG4gICAgdGhpcy5vbk1ldGEgPSBjYWxsYmFja3Mub25NZXRhO1xuICAgIHRoaXMub25QcmVzID0gY2FsbGJhY2tzLm9uUHJlcztcbiAgICB0aGlzLm9uSW5mbyA9IGNhbGxiYWNrcy5vbkluZm87XG4gICAgLy8gQSBzaW5nbGUgZGVzYyB1cGRhdGU7XG4gICAgdGhpcy5vbk1ldGFEZXNjID0gY2FsbGJhY2tzLm9uTWV0YURlc2M7XG4gICAgLy8gQSBzaW5nbGUgc3Vic2NyaXB0aW9uIHJlY29yZDtcbiAgICB0aGlzLm9uTWV0YVN1YiA9IGNhbGxiYWNrcy5vbk1ldGFTdWI7XG4gICAgLy8gQWxsIHN1YnNjcmlwdGlvbiByZWNvcmRzIHJlY2VpdmVkO1xuICAgIHRoaXMub25TdWJzVXBkYXRlZCA9IGNhbGxiYWNrcy5vblN1YnNVcGRhdGVkO1xuICAgIHRoaXMub25UYWdzVXBkYXRlZCA9IGNhbGxiYWNrcy5vblRhZ3NVcGRhdGVkO1xuICAgIHRoaXMub25DcmVkc1VwZGF0ZWQgPSBjYWxsYmFjbHMub25DcmVkc1VwZGF0ZWQ7XG4gICAgdGhpcy5vbkRlbGV0ZVRvcGljID0gY2FsbGJhY2tzLm9uRGVsZXRlVG9waWM7XG4gICAgdGhpcy5vbkFsbE1lc3NhZ2VzUmVjZWl2ZWQgPSBjYWxsYmFja3Mub25BbGxNZXNzYWdlc1JlY2VpdmVkO1xuICB9XG59O1xuXG5Ub3BpYy5wcm90b3R5cGUgPSB7XG4gIC8qKlxuICAgKiBDaGVjayBpZiB0aGUgdG9waWMgaXMgc3Vic2NyaWJlZC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICogQHJldHVybnMge2Jvb2xlYW59IFRydWUgaXMgdG9waWMgaXMgYXR0YWNoZWQvc3Vic2NyaWJlZCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgKi9cbiAgaXNTdWJzY3JpYmVkOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5fc3Vic2NyaWJlZDtcbiAgfSxcblxuICAvKipcbiAgICogUmVxdWVzdCB0b3BpYyB0byBzdWJzY3JpYmUuIFdyYXBwZXIgZm9yIHtAbGluayBUaW5vZGUjc3Vic2NyaWJlfS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtUaW5vZGUuR2V0UXVlcnk9fSBnZXRQYXJhbXMgLSBnZXQgcXVlcnkgcGFyYW1ldGVycy5cbiAgICogQHBhcmFtIHtUaW5vZGUuU2V0UGFyYW1zPX0gc2V0UGFyYW1zIC0gc2V0IHBhcmFtZXRlcnMuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byB0aGUgcmVxdWVzdC5cbiAgICovXG4gIHN1YnNjcmliZTogZnVuY3Rpb24oZ2V0UGFyYW1zLCBzZXRQYXJhbXMpIHtcbiAgICAvLyBJZiB0aGUgdG9waWMgaXMgYWxyZWFkeSBzdWJzY3JpYmVkLCByZXR1cm4gcmVzb2x2ZWQgcHJvbWlzZVxuICAgIGlmICh0aGlzLl9zdWJzY3JpYmVkKSB7XG4gICAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKHRoaXMpO1xuICAgIH1cblxuICAgIC8vIFNlbmQgc3Vic2NyaWJlIG1lc3NhZ2UsIGhhbmRsZSBhc3luYyByZXNwb25zZS5cbiAgICAvLyBJZiB0b3BpYyBuYW1lIGlzIGV4cGxpY2l0bHkgcHJvdmlkZWQsIHVzZSBpdC4gSWYgbm8gbmFtZSwgdGhlbiBpdCdzIGEgbmV3IGdyb3VwIHRvcGljLFxuICAgIC8vIHVzZSBcIm5ld1wiLlxuICAgIHJldHVybiB0aGlzLl90aW5vZGUuc3Vic2NyaWJlKHRoaXMubmFtZSB8fCBUT1BJQ19ORVcsIGdldFBhcmFtcywgc2V0UGFyYW1zKS50aGVuKChjdHJsKSA9PiB7XG4gICAgICBpZiAoY3RybC5jb2RlID49IDMwMCkge1xuICAgICAgICAvLyBEbyBub3RoaW5nIGZmIHRoZSB0b3BpYyBpcyBhbHJlYWR5IHN1YnNjcmliZWQgdG8uXG4gICAgICAgIHJldHVybiBjdHJsO1xuICAgICAgfVxuXG4gICAgICB0aGlzLl9zdWJzY3JpYmVkID0gdHJ1ZTtcbiAgICAgIHRoaXMuYWNzID0gKGN0cmwucGFyYW1zICYmIGN0cmwucGFyYW1zLmFjcykgPyBjdHJsLnBhcmFtcy5hY3MgOiB0aGlzLmFjcztcblxuICAgICAgLy8gU2V0IHRvcGljIG5hbWUgZm9yIG5ldyB0b3BpY3MgYW5kIGFkZCBpdCB0byBjYWNoZS5cbiAgICAgIGlmICh0aGlzLl9uZXcpIHtcbiAgICAgICAgdGhpcy5fbmV3ID0gZmFsc2U7XG5cbiAgICAgICAgLy8gTmFtZSBtYXkgY2hhbmdlIG5ldzEyMzQ1NiAtPiBncnBBYkNkRWZcbiAgICAgICAgdGhpcy5uYW1lID0gY3RybC50b3BpYztcblxuICAgICAgICB0aGlzLmNyZWF0ZWQgPSBjdHJsLnRzO1xuICAgICAgICB0aGlzLnVwZGF0ZWQgPSBjdHJsLnRzO1xuICAgICAgICAvLyBEb24ndCBhc3NpZ24gdG91Y2hlZCwgb3RoZXJ3aXNlIHRvcGljIHdpbGwgYmUgcHV0IG9uIHRvcCBvZiB0aGUgbGlzdCBvbiBzdWJzY3JpYmUuXG5cbiAgICAgICAgdGhpcy5fY2FjaGVQdXRTZWxmKCk7XG5cbiAgICAgICAgaWYgKHRoaXMubmFtZSAhPSBUT1BJQ19NRSAmJiB0aGlzLm5hbWUgIT0gVE9QSUNfRk5EKSB7XG4gICAgICAgICAgLy8gQWRkIHRoZSBuZXcgdG9waWMgdG8gdGhlIGxpc3Qgb2YgY29udGFjdHMgbWFpbnRhaW5lZCBieSB0aGUgJ21lJyB0b3BpYy5cbiAgICAgICAgICBjb25zdCBtZSA9IHRoaXMuX3Rpbm9kZS5nZXRNZVRvcGljKCk7XG4gICAgICAgICAgaWYgKG1lKSB7XG4gICAgICAgICAgICBtZS5fcHJvY2Vzc01ldGFTdWIoW3tcbiAgICAgICAgICAgICAgX25vRm9yd2FyZGluZzogdHJ1ZSxcbiAgICAgICAgICAgICAgdG9waWM6IHRoaXMubmFtZSxcbiAgICAgICAgICAgICAgY3JlYXRlZDogY3RybC50cyxcbiAgICAgICAgICAgICAgdXBkYXRlZDogY3RybC50cyxcbiAgICAgICAgICAgICAgYWNzOiB0aGlzLmFjc1xuICAgICAgICAgICAgfV0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChzZXRQYXJhbXMgJiYgc2V0UGFyYW1zLmRlc2MpIHtcbiAgICAgICAgICBzZXRQYXJhbXMuZGVzYy5fbm9Gb3J3YXJkaW5nID0gdHJ1ZTtcbiAgICAgICAgICB0aGlzLl9wcm9jZXNzTWV0YURlc2Moc2V0UGFyYW1zLmRlc2MpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBjdHJsO1xuICAgIH0pO1xuICB9LFxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBkcmFmdCBvZiBhIG1lc3NhZ2Ugd2l0aG91dCBzZW5kaW5nIGl0IHRvIHRoZSBzZXJ2ZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7c3RyaW5nIHwgT2JqZWN0fSBkYXRhIC0gQ29udGVudCB0byB3cmFwIGluIGEgZHJhZnQuXG4gICAqIEBwYXJhbSB7Qm9vbGVhbj19IG5vRWNobyAtIElmIDx0dD50cnVlPC90dD4gc2VydmVyIHdpbGwgbm90IGVjaG8gbWVzc2FnZSBiYWNrIHRvIG9yaWdpbmF0aW5nXG4gICAqIHNlc3Npb24uIE90aGVyd2lzZSB0aGUgc2VydmVyIHdpbGwgc2VuZCBhIGNvcHkgb2YgdGhlIG1lc3NhZ2UgdG8gc2VuZGVyLlxuICAgKlxuICAgKiBAcmV0dXJucyB7T2JqZWN0fSBtZXNzYWdlIGRyYWZ0LlxuICAgKi9cbiAgY3JlYXRlTWVzc2FnZTogZnVuY3Rpb24oZGF0YSwgbm9FY2hvKSB7XG4gICAgcmV0dXJuIHRoaXMuX3Rpbm9kZS5jcmVhdGVNZXNzYWdlKHRoaXMubmFtZSwgZGF0YSwgbm9FY2hvKTtcbiAgfSxcblxuICAvKipcbiAgICogSW1tZWRpYXRlbHkgcHVibGlzaCBkYXRhIHRvIHRvcGljLiBXcmFwcGVyIGZvciB7QGxpbmsgVGlub2RlI3B1Ymxpc2h9LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZyB8IE9iamVjdH0gZGF0YSAtIERhdGEgdG8gcHVibGlzaCwgZWl0aGVyIHBsYWluIHN0cmluZyBvciBhIERyYWZ0eSBvYmplY3QuXG4gICAqIEBwYXJhbSB7Qm9vbGVhbj19IG5vRWNobyAtIElmIDx0dD50cnVlPC90dD4gc2VydmVyIHdpbGwgbm90IGVjaG8gbWVzc2FnZSBiYWNrIHRvIG9yaWdpbmF0aW5nXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byB0aGUgcmVxdWVzdC5cbiAgICovXG4gIHB1Ymxpc2g6IGZ1bmN0aW9uKGRhdGEsIG5vRWNobykge1xuICAgIHJldHVybiB0aGlzLnB1Ymxpc2hNZXNzYWdlKHRoaXMuY3JlYXRlTWVzc2FnZShkYXRhLCBub0VjaG8pKTtcbiAgfSxcblxuICAvKipcbiAgICogUHVibGlzaCBtZXNzYWdlIGNyZWF0ZWQgYnkge0BsaW5rIFRpbm9kZS5Ub3BpYyNjcmVhdGVNZXNzYWdlfS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtPYmplY3R9IHB1YiAtIHtkYXRhfSBvYmplY3QgdG8gcHVibGlzaC4gTXVzdCBiZSBjcmVhdGVkIGJ5IHtAbGluayBUaW5vZGUuVG9waWMjY3JlYXRlTWVzc2FnZX1cbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2UgdG8gYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgd2hlbiB0aGUgc2VydmVyIHJlc3BvbmRzIHRvIHRoZSByZXF1ZXN0LlxuICAgKi9cbiAgcHVibGlzaE1lc3NhZ2U6IGZ1bmN0aW9uKHB1Yikge1xuICAgIGlmICghdGhpcy5fc3Vic2NyaWJlZCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KG5ldyBFcnJvcihcIkNhbm5vdCBwdWJsaXNoIG9uIGluYWN0aXZlIHRvcGljXCIpKTtcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgaGVhZGVyIHdpdGggYXR0YWNobWVudCByZWNvcmRzLlxuICAgIGlmIChEcmFmdHkuaGFzQXR0YWNobWVudHMocHViLmNvbnRlbnQpICYmICFwdWIuaGVhZC5hdHRhY2htZW50cykge1xuICAgICAgbGV0IGF0dGFjaG1lbnRzID0gW107XG4gICAgICBEcmFmdHkuYXR0YWNobWVudHMocHViLmNvbnRlbnQsIChkYXRhKSA9PiB7XG4gICAgICAgIGF0dGFjaG1lbnRzLnB1c2goZGF0YS5yZWYpO1xuICAgICAgfSk7XG4gICAgICBwdWIuaGVhZC5hdHRhY2htZW50cyA9IGF0dGFjaG1lbnRzO1xuICAgIH1cblxuICAgIC8vIFNlbmQgZGF0YS5cbiAgICBwdWIuX3NlbmRpbmcgPSB0cnVlO1xuICAgIHB1Yi5fZmFpbGVkID0gZmFsc2U7XG4gICAgcmV0dXJuIHRoaXMuX3Rpbm9kZS5wdWJsaXNoTWVzc2FnZShwdWIpLnRoZW4oKGN0cmwpID0+IHtcbiAgICAgIHB1Yi5fc2VuZGluZyA9IGZhbHNlO1xuICAgICAgcHViLnNlcSA9IGN0cmwucGFyYW1zLnNlcTtcbiAgICAgIHB1Yi50cyA9IGN0cmwudHM7XG4gICAgICB0aGlzLl9yb3V0ZURhdGEocHViKTtcbiAgICAgIHJldHVybiBjdHJsO1xuICAgIH0pLmNhdGNoKChlcnIpID0+IHtcbiAgICAgIHRoaXMuX3Rpbm9kZS5sb2dnZXIoXCJXQVJOSU5HOiBNZXNzYWdlIHJlamVjdGVkIGJ5IHRoZSBzZXJ2ZXJcIiwgZXJyKTtcbiAgICAgIHB1Yi5fc2VuZGluZyA9IGZhbHNlO1xuICAgICAgcHViLl9mYWlsZWQgPSB0cnVlO1xuICAgICAgaWYgKHRoaXMub25EYXRhKSB7XG4gICAgICAgIHRoaXMub25EYXRhKCk7XG4gICAgICB9XG4gICAgfSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFkZCBtZXNzYWdlIHRvIGxvY2FsIG1lc3NhZ2UgY2FjaGUsIHNlbmQgdG8gdGhlIHNlcnZlciB3aGVuIHRoZSBwcm9taXNlIGlzIHJlc29sdmVkLlxuICAgKiBJZiBwcm9taXNlIGlzIG51bGwgb3IgdW5kZWZpbmVkLCB0aGUgbWVzc2FnZSB3aWxsIGJlIHNlbnQgaW1tZWRpYXRlbHkuXG4gICAqIFRoZSBtZXNzYWdlIGlzIHNlbnQgd2hlbiB0aGVcbiAgICogVGhlIG1lc3NhZ2Ugc2hvdWxkIGJlIGNyZWF0ZWQgYnkge0BsaW5rIFRpbm9kZS5Ub3BpYyNjcmVhdGVNZXNzYWdlfS5cbiAgICogVGhpcyBpcyBwcm9iYWJseSBub3QgdGhlIGZpbmFsIEFQSS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtPYmplY3R9IHB1YiAtIE1lc3NhZ2UgdG8gdXNlIGFzIGEgZHJhZnQuXG4gICAqIEBwYXJhbSB7UHJvbWlzZX0gcHJvbSAtIE1lc3NhZ2Ugd2lsbCBiZSBzZW50IHdoZW4gdGhpcyBwcm9taXNlIGlzIHJlc29sdmVkLCBkaXNjYXJkZWQgaWYgcmVqZWN0ZWQuXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBkZXJpdmVkIHByb21pc2UuXG4gICAqL1xuICBwdWJsaXNoRHJhZnQ6IGZ1bmN0aW9uKHB1YiwgcHJvbSkge1xuICAgIGlmICghcHJvbSAmJiAhdGhpcy5fc3Vic2NyaWJlZCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KG5ldyBFcnJvcihcIkNhbm5vdCBwdWJsaXNoIG9uIGluYWN0aXZlIHRvcGljXCIpKTtcbiAgICB9XG5cbiAgICBjb25zdCBzZXEgPSBwdWIuc2VxIHx8IHRoaXMuX2dldFF1ZXVlZFNlcUlkKCk7XG4gICAgaWYgKCFwdWIuX25vRm9yd2FyZGluZykge1xuICAgICAgLy8gVGhlICdzZXEnLCAndHMnLCBhbmQgJ2Zyb20nIGFyZSBhZGRlZCB0byBtaW1pYyB7ZGF0YX0uIFRoZXkgYXJlIHJlbW92ZWQgbGF0ZXJcbiAgICAgIC8vIGJlZm9yZSB0aGUgbWVzc2FnZSBpcyBzZW50LlxuXG4gICAgICBwdWIuX25vRm9yd2FyZGluZyA9IHRydWU7XG4gICAgICBwdWIuc2VxID0gc2VxO1xuICAgICAgcHViLnRzID0gbmV3IERhdGUoKTtcbiAgICAgIHB1Yi5mcm9tID0gdGhpcy5fdGlub2RlLmdldEN1cnJlbnRVc2VySUQoKTtcblxuICAgICAgLy8gRG9uJ3QgbmVlZCBhbiBlY2hvIG1lc3NhZ2UgYmVjYXVzZSB0aGUgbWVzc2FnZSBpcyBhZGRlZCB0byBsb2NhbCBjYWNoZSByaWdodCBhd2F5LlxuICAgICAgcHViLm5vZWNobyA9IHRydWU7XG4gICAgICAvLyBBZGQgdG8gY2FjaGUuXG4gICAgICB0aGlzLl9tZXNzYWdlcy5wdXQocHViKTtcblxuICAgICAgaWYgKHRoaXMub25EYXRhKSB7XG4gICAgICAgIHRoaXMub25EYXRhKHB1Yik7XG4gICAgICB9XG4gICAgfVxuICAgIC8vIElmIHByb21pc2UgaXMgcHJvdmlkZWQsIHNlbmQgdGhlIHF1ZXVlZCBtZXNzYWdlIHdoZW4gaXQncyByZXNvbHZlZC5cbiAgICAvLyBJZiBubyBwcm9taXNlIGlzIHByb3ZpZGVkLCBjcmVhdGUgYSByZXNvbHZlZCBvbmUgYW5kIHNlbmQgaW1tZWRpYXRlbHkuXG4gICAgcHJvbSA9IChwcm9tIHx8IFByb21pc2UucmVzb2x2ZSgpKS50aGVuKFxuICAgICAgKCAvKiBhcmd1bWVudCBpZ25vcmVkICovICkgPT4ge1xuICAgICAgICBpZiAocHViLl9jYW5jZWxsZWQpIHtcbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgY29kZTogMzAwLFxuICAgICAgICAgICAgdGV4dDogXCJjYW5jZWxsZWRcIlxuICAgICAgICAgIH07XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5wdWJsaXNoTWVzc2FnZShwdWIpO1xuICAgICAgfSxcbiAgICAgIChlcnIpID0+IHtcbiAgICAgICAgdGhpcy5fdGlub2RlLmxvZ2dlcihcIldBUk5JTkc6IE1lc3NhZ2UgZHJhZnQgcmVqZWN0ZWQgYnkgdGhlIHNlcnZlclwiLCBlcnIpO1xuICAgICAgICBwdWIuX3NlbmRpbmcgPSBmYWxzZTtcbiAgICAgICAgcHViLl9mYWlsZWQgPSB0cnVlO1xuICAgICAgICB0aGlzLl9tZXNzYWdlcy5kZWxBdCh0aGlzLl9tZXNzYWdlcy5maW5kKHB1YikpO1xuICAgICAgICBpZiAodGhpcy5vbkRhdGEpIHtcbiAgICAgICAgICB0aGlzLm9uRGF0YSgpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICByZXR1cm4gcHJvbTtcbiAgfSxcblxuICAvKipcbiAgICogTGVhdmUgdGhlIHRvcGljLCBvcHRpb25hbGx5IHVuc2lic2NyaWJlLiBMZWF2aW5nIHRoZSB0b3BpYyBtZWFucyB0aGUgdG9waWMgd2lsbCBzdG9wXG4gICAqIHJlY2VpdmluZyB1cGRhdGVzIGZyb20gdGhlIHNlcnZlci4gVW5zdWJzY3JpYmluZyB3aWxsIHRlcm1pbmF0ZSB1c2VyJ3MgcmVsYXRpb25zaGlwIHdpdGggdGhlIHRvcGljLlxuICAgKiBXcmFwcGVyIGZvciB7QGxpbmsgVGlub2RlI2xlYXZlfS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtCb29sZWFuPX0gdW5zdWIgLSBJZiB0cnVlLCB1bnN1YnNjcmliZSwgb3RoZXJ3aXNlIGp1c3QgbGVhdmUuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byB0aGUgcmVxdWVzdC5cbiAgICovXG4gIGxlYXZlOiBmdW5jdGlvbih1bnN1Yikge1xuICAgIC8vIEl0J3MgcG9zc2libGUgdG8gdW5zdWJzY3JpYmUgKHVuc3ViPT10cnVlKSBmcm9tIGluYWN0aXZlIHRvcGljLlxuICAgIGlmICghdGhpcy5fc3Vic2NyaWJlZCAmJiAhdW5zdWIpIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlamVjdChuZXcgRXJyb3IoXCJDYW5ub3QgbGVhdmUgaW5hY3RpdmUgdG9waWNcIikpO1xuICAgIH1cblxuICAgIC8vIFNlbmQgYSAnbGVhdmUnIG1lc3NhZ2UsIGhhbmRsZSBhc3luYyByZXNwb25zZVxuICAgIHJldHVybiB0aGlzLl90aW5vZGUubGVhdmUodGhpcy5uYW1lLCB1bnN1YikudGhlbigoY3RybCkgPT4ge1xuICAgICAgdGhpcy5fcmVzZXRTdWIoKTtcbiAgICAgIGlmICh1bnN1Yikge1xuICAgICAgICB0aGlzLl9nb25lKCk7XG4gICAgICB9XG4gICAgICByZXR1cm4gY3RybDtcbiAgICB9KTtcbiAgfSxcblxuICAvKipcbiAgICogUmVxdWVzdCB0b3BpYyBtZXRhZGF0YSBmcm9tIHRoZSBzZXJ2ZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7VGlub2RlLkdldFF1ZXJ5fSByZXF1ZXN0IHBhcmFtZXRlcnNcbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2UgdG8gYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgd2hlbiB0aGUgc2VydmVyIHJlc3BvbmRzIHRvIHJlcXVlc3QuXG4gICAqL1xuICBnZXRNZXRhOiBmdW5jdGlvbihwYXJhbXMpIHtcbiAgICAvLyBTZW5kIHtnZXR9IG1lc3NhZ2UsIHJldHVybiBwcm9taXNlLlxuICAgIHJldHVybiB0aGlzLl90aW5vZGUuZ2V0TWV0YSh0aGlzLm5hbWUsIHBhcmFtcyk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFJlcXVlc3QgbW9yZSBtZXNzYWdlcyBmcm9tIHRoZSBzZXJ2ZXJcbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtpbnRlZ2VyfSBsaW1pdCBudW1iZXIgb2YgbWVzc2FnZXMgdG8gZ2V0LlxuICAgKiBAcGFyYW0ge2Jvb2xlYW59IGZvcndhcmQgaWYgdHJ1ZSwgcmVxdWVzdCBuZXdlciBtZXNzYWdlcy5cbiAgICovXG4gIGdldE1lc3NhZ2VzUGFnZTogZnVuY3Rpb24obGltaXQsIGZvcndhcmQpIHtcbiAgICBjb25zdCBxdWVyeSA9IHRoaXMuc3RhcnRNZXRhUXVlcnkoKTtcbiAgICBpZiAoZm9yd2FyZCkge1xuICAgICAgcXVlcnkud2l0aExhdGVyRGF0YShsaW1pdCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHF1ZXJ5LndpdGhFYXJsaWVyRGF0YShsaW1pdCk7XG4gICAgfVxuICAgIGxldCBwcm9taXNlID0gdGhpcy5nZXRNZXRhKHF1ZXJ5LmJ1aWxkKCkpO1xuICAgIGlmICghZm9yd2FyZCkge1xuICAgICAgcHJvbWlzZSA9IHByb21pc2UudGhlbigoY3RybCkgPT4ge1xuICAgICAgICBpZiAoY3RybCAmJiBjdHJsLnBhcmFtcyAmJiAhY3RybC5wYXJhbXMuY291bnQpIHtcbiAgICAgICAgICB0aGlzLl9ub0VhcmxpZXJNc2dzID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBwcm9taXNlO1xuICB9LFxuXG4gIC8qKlxuICAgKiBVcGRhdGUgdG9waWMgbWV0YWRhdGEuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7VGlub2RlLlNldFBhcmFtc30gcGFyYW1zIHBhcmFtZXRlcnMgdG8gdXBkYXRlLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gUHJvbWlzZSB0byBiZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHRoZSBzZXJ2ZXIgcmVzcG9uZHMgdG8gcmVxdWVzdC5cbiAgICovXG4gIHNldE1ldGE6IGZ1bmN0aW9uKHBhcmFtcykge1xuICAgIGlmIChwYXJhbXMudGFncykge1xuICAgICAgcGFyYW1zLnRhZ3MgPSBub3JtYWxpemVBcnJheShwYXJhbXMudGFncyk7XG4gICAgfVxuICAgIC8vIFNlbmQgU2V0IG1lc3NhZ2UsIGhhbmRsZSBhc3luYyByZXNwb25zZS5cbiAgICByZXR1cm4gdGhpcy5fdGlub2RlLnNldE1ldGEodGhpcy5uYW1lLCBwYXJhbXMpXG4gICAgICAudGhlbigoY3RybCkgPT4ge1xuICAgICAgICBpZiAoY3RybCAmJiBjdHJsLmNvZGUgPj0gMzAwKSB7XG4gICAgICAgICAgLy8gTm90IG1vZGlmaWVkXG4gICAgICAgICAgcmV0dXJuIGN0cmw7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAocGFyYW1zLnN1Yikge1xuICAgICAgICAgIGlmIChjdHJsLnBhcmFtcyAmJiBjdHJsLnBhcmFtcy5hY3MpIHtcbiAgICAgICAgICAgIHBhcmFtcy5zdWIuYWNzID0gY3RybC5wYXJhbXMuYWNzO1xuICAgICAgICAgICAgcGFyYW1zLnN1Yi51cGRhdGVkID0gY3RybC50cztcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBpZiAoIXBhcmFtcy5zdWIudXNlcikge1xuICAgICAgICAgICAgLy8gVGhpcyBpcyBhIHN1YnNjcmlwdGlvbiB1cGRhdGUgb2YgdGhlIGN1cnJlbnQgdXNlci5cbiAgICAgICAgICAgIC8vIEFzc2lnbiB1c2VyIElEIG90aGVyd2lzZSB0aGUgdXBkYXRlIHdpbGwgYmUgaWdub3JlZCBieSBfcHJvY2Vzc01ldGFTdWIuXG4gICAgICAgICAgICBwYXJhbXMuc3ViLnVzZXIgPSB0aGlzLl90aW5vZGUuZ2V0Q3VycmVudFVzZXJJRCgpO1xuICAgICAgICAgICAgaWYgKCFwYXJhbXMuZGVzYykge1xuICAgICAgICAgICAgICAvLyBGb3JjZSB1cGRhdGUgdG8gdG9waWMncyBhc2MuXG4gICAgICAgICAgICAgIHBhcmFtcy5kZXNjID0ge307XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIHBhcmFtcy5zdWIuX25vRm9yd2FyZGluZyA9IHRydWU7XG4gICAgICAgICAgdGhpcy5fcHJvY2Vzc01ldGFTdWIoW3BhcmFtcy5zdWJdKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChwYXJhbXMuZGVzYykge1xuICAgICAgICAgIGlmIChjdHJsLnBhcmFtcyAmJiBjdHJsLnBhcmFtcy5hY3MpIHtcbiAgICAgICAgICAgIHBhcmFtcy5kZXNjLmFjcyA9IGN0cmwucGFyYW1zLmFjcztcbiAgICAgICAgICAgIHBhcmFtcy5kZXNjLnVwZGF0ZWQgPSBjdHJsLnRzO1xuICAgICAgICAgIH1cbiAgICAgICAgICB0aGlzLl9wcm9jZXNzTWV0YURlc2MocGFyYW1zLmRlc2MpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHBhcmFtcy50YWdzKSB7XG4gICAgICAgICAgdGhpcy5fcHJvY2Vzc01ldGFUYWdzKHBhcmFtcy50YWdzKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAocGFyYW1zLmNyZWQpIHtcbiAgICAgICAgICB0aGlzLl9wcm9jZXNzTWV0YUNyZWRzKFtwYXJhbXMuY3JlZF0sIHRydWUpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGN0cmw7XG4gICAgICB9KTtcbiAgfSxcblxuICAvKipcbiAgICogVXBkYXRlIGFjY2VzcyBtb2RlIG9mIHRoZSBjdXJyZW50IHVzZXIgb3Igb2YgYW5vdGhlciB0b3BpYyBzdWJzcmliZXIuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7c3RyaW5nfSB1aWQgLSBVSUQgb2YgdGhlIHVzZXIgdG8gdXBkYXRlIG9yIG51bGwgdG8gdXBkYXRlIGN1cnJlbnQgdXNlci5cbiAgICogQHBhcmFtIHtzdHJpbmd9IHVwZGF0ZSAtIHRoZSB1cGRhdGUgdmFsdWUsIGZ1bGwgb3IgZGVsdGEuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byByZXF1ZXN0LlxuICAgKi9cbiAgdXBkYXRlTW9kZTogZnVuY3Rpb24odWlkLCB1cGRhdGUpIHtcbiAgICBjb25zdCB1c2VyID0gdWlkID8gdXNlciA9IHRoaXMuc3Vic2NyaWJlcih1aWQpIDogbnVsbDtcbiAgICBsZXQgYW07XG4gICAgaWYgKHVzZXIpIHtcbiAgICAgIGFtID0gdXNlci5hY3MudXBkYXRlR2l2ZW4odXBkYXRlKS5nZXRHaXZlbigpO1xuICAgIH0gZWxzZSB7XG4gICAgICBhbSA9IHRvcGljLmdldEFjY2Vzc01vZGUoKS51cGRhdGVXYW50KHVwZGF0ZSkuZ2V0V2FudCgpO1xuICAgIH1cblxuICAgIHJldHVybiB0b3BpYy5zZXRNZXRhKHtcbiAgICAgIHN1Yjoge1xuICAgICAgICB1c2VyOiB1aWQsXG4gICAgICAgIG1vZGU6IGFtXG4gICAgICB9XG4gICAgfSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIENyZWF0ZSBuZXcgdG9waWMgc3Vic2NyaXB0aW9uLiBXcmFwcGVyIGZvciB7QGxpbmsgVGlub2RlI3NldE1ldGF9LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gdWlkIC0gSUQgb2YgdGhlIHVzZXIgdG8gaW52aXRlXG4gICAqIEBwYXJhbSB7U3RyaW5nPX0gbW9kZSAtIEFjY2VzcyBtb2RlLiA8dHQ+bnVsbDwvdHQ+IG1lYW5zIHRvIHVzZSBkZWZhdWx0LlxuICAgKlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gUHJvbWlzZSB0byBiZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHRoZSBzZXJ2ZXIgcmVzcG9uZHMgdG8gcmVxdWVzdC5cbiAgICovXG4gIGludml0ZTogZnVuY3Rpb24odWlkLCBtb2RlKSB7XG4gICAgcmV0dXJuIHRoaXMuc2V0TWV0YSh7XG4gICAgICBzdWI6IHtcbiAgICAgICAgdXNlcjogdWlkLFxuICAgICAgICBtb2RlOiBtb2RlXG4gICAgICB9XG4gICAgfSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEFyY2hpdmUgb3IgdW4tYXJjaGl2ZSB0aGUgdG9waWMuIFdyYXBwZXIgZm9yIHtAbGluayBUaW5vZGUjc2V0TWV0YX0uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7Qm9vbGVhbn0gYXJjaCAtIHRydWUgdG8gYXJjaGl2ZSB0aGUgdG9waWMsIGZhbHNlIG90aGVyd2lzZS5cbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2UgdG8gYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgd2hlbiB0aGUgc2VydmVyIHJlc3BvbmRzIHRvIHJlcXVlc3QuXG4gICAqL1xuICBhcmNoaXZlOiBmdW5jdGlvbihhcmNoKSB7XG4gICAgaWYgKHRoaXMucHJpdmF0ZSAmJiB0aGlzLnByaXZhdGUuYXJjaCA9PSBhcmNoKSB7XG4gICAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKGFyY2gpO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5zZXRNZXRhKHtcbiAgICAgIGRlc2M6IHtcbiAgICAgICAgcHJpdmF0ZToge1xuICAgICAgICAgIGFyY2g6IGFyY2ggPyB0cnVlIDogVGlub2RlLkRFTF9DSEFSXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgfSxcblxuICAvKipcbiAgICogRGVsZXRlIG1lc3NhZ2VzLiBIYXJkLWRlbGV0aW5nIG1lc3NhZ2VzIHJlcXVpcmVzIE93bmVyIHBlcm1pc3Npb24uXG4gICAqIFdyYXBwZXIgZm9yIHtAbGluayBUaW5vZGUjZGVsTWVzc2FnZXN9LlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcGFyYW0ge1Rpbm9kZS5EZWxSYW5nZVtdfSByYW5nZXMgLSBSYW5nZXMgb2YgbWVzc2FnZSBJRHMgdG8gZGVsZXRlLlxuICAgKiBAcGFyYW0ge0Jvb2xlYW49fSBoYXJkIC0gSGFyZCBvciBzb2Z0IGRlbGV0ZVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gUHJvbWlzZSB0byBiZSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHRoZSBzZXJ2ZXIgcmVzcG9uZHMgdG8gcmVxdWVzdC5cbiAgICovXG4gIGRlbE1lc3NhZ2VzOiBmdW5jdGlvbihyYW5nZXMsIGhhcmQpIHtcbiAgICBpZiAoIXRoaXMuX3N1YnNjcmliZWQpIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlamVjdChuZXcgRXJyb3IoXCJDYW5ub3QgZGVsZXRlIG1lc3NhZ2VzIGluIGluYWN0aXZlIHRvcGljXCIpKTtcbiAgICB9XG5cbiAgICAvLyBTb3J0IHJhbmdlcyBpbiBhY2NlbmRpbmcgb3JkZXIgYnkgbG93LCB0aGUgZGVzY2VuZGluZyBieSBoaS5cbiAgICByYW5nZXMuc29ydCgocjEsIHIyKSA9PiB7XG4gICAgICBpZiAocjEubG93IDwgcjIubG93KSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgICAgaWYgKHIxLmxvdyA9PSByMi5sb3cpIHtcbiAgICAgICAgcmV0dXJuICFyMi5oaSB8fCAocjEuaGkgPj0gcjIuaGkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH0pO1xuXG4gICAgLy8gUmVtb3ZlIHBlbmRpbmcgbWVzc2FnZXMgZnJvbSByYW5nZXMgcG9zc2libHkgY2xpcHBpbmcgc29tZSByYW5nZXMuXG4gICAgbGV0IHRvc2VuZCA9IHJhbmdlcy5yZWR1Y2UoKG91dCwgcikgPT4ge1xuICAgICAgaWYgKHIubG93IDwgTE9DQUxfU0VRSUQpIHtcbiAgICAgICAgaWYgKCFyLmhpIHx8IHIuaGkgPCBMT0NBTF9TRVFJRCkge1xuICAgICAgICAgIG91dC5wdXNoKHIpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIC8vIENsaXAgaGkgdG8gbWF4IGFsbG93ZWQgdmFsdWUuXG4gICAgICAgICAgb3V0LnB1c2goe1xuICAgICAgICAgICAgbG93OiByLmxvdyxcbiAgICAgICAgICAgIGhpOiB0aGlzLl9tYXhTZXEgKyAxXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBvdXQ7XG4gICAgfSwgW10pO1xuXG4gICAgLy8gU2VuZCB7ZGVsfSBtZXNzYWdlLCByZXR1cm4gcHJvbWlzZVxuICAgIGxldCByZXN1bHQ7XG4gICAgaWYgKHRvc2VuZC5sZW5ndGggPiAwKSB7XG4gICAgICByZXN1bHQgPSB0aGlzLl90aW5vZGUuZGVsTWVzc2FnZXModGhpcy5uYW1lLCB0b3NlbmQsIGhhcmQpO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXN1bHQgPSBQcm9taXNlLnJlc29sdmUoe1xuICAgICAgICBwYXJhbXM6IHtcbiAgICAgICAgICBkZWw6IDBcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICAgIC8vIFVwZGF0ZSBsb2NhbCBjYWNoZS5cbiAgICByZXR1cm4gcmVzdWx0LnRoZW4oKGN0cmwpID0+IHtcbiAgICAgIGlmIChjdHJsLnBhcmFtcy5kZWwgPiB0aGlzLl9tYXhEZWwpIHtcbiAgICAgICAgdGhpcy5fbWF4RGVsID0gY3RybC5wYXJhbXMuZGVsO1xuICAgICAgfVxuXG4gICAgICByYW5nZXMubWFwKChyKSA9PiB7XG4gICAgICAgIGlmIChyLmhpKSB7XG4gICAgICAgICAgdGhpcy5mbHVzaE1lc3NhZ2VSYW5nZShyLmxvdywgci5oaSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdGhpcy5mbHVzaE1lc3NhZ2Uoci5sb3cpO1xuICAgICAgICB9XG4gICAgICB9KTtcblxuICAgICAgaWYgKHRoaXMub25EYXRhKSB7XG4gICAgICAgIC8vIENhbGxpbmcgd2l0aCBubyBwYXJhbWV0ZXJzIHRvIGluZGljYXRlIHRoZSBtZXNzYWdlcyB3ZXJlIGRlbGV0ZWQuXG4gICAgICAgIHRoaXMub25EYXRhKCk7XG4gICAgICB9XG4gICAgICByZXR1cm4gY3RybDtcbiAgICB9KTtcbiAgfSxcblxuICAvKipcbiAgICogRGVsZXRlIGFsbCBtZXNzYWdlcy4gSGFyZC1kZWxldGluZyBtZXNzYWdlcyByZXF1aXJlcyBPd25lciBwZXJtaXNzaW9uLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcGFyYW0ge2Jvb2xlYW59IGhhcmREZWwgLSB0cnVlIGlmIG1lc3NhZ2VzIHNob3VsZCBiZSBoYXJkLWRlbGV0ZWQuXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byByZXF1ZXN0LlxuICAgKi9cbiAgZGVsTWVzc2FnZXNBbGw6IGZ1bmN0aW9uKGhhcmREZWwpIHtcbiAgICBpZiAoIXRoaXMuX21heFNlcSB8fCB0aGlzLl9tYXhTZXEgPD0gMCkge1xuICAgICAgLy8gVGhlcmUgYXJlIG5vIG1lc3NhZ2VzIHRvIGRlbGV0ZS5cbiAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuZGVsTWVzc2FnZXMoW3tcbiAgICAgIGxvdzogMSxcbiAgICAgIGhpOiB0aGlzLl9tYXhTZXEgKyAxLFxuICAgICAgX2FsbDogdHJ1ZVxuICAgIH1dLCBoYXJkRGVsKTtcbiAgfSxcblxuICAvKipcbiAgICogRGVsZXRlIG11bHRpcGxlIG1lc3NhZ2VzIGRlZmluZWQgYnkgdGhlaXIgSURzLiBIYXJkLWRlbGV0aW5nIG1lc3NhZ2VzIHJlcXVpcmVzIE93bmVyIHBlcm1pc3Npb24uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7VGlub2RlLkRlbFJhbmdlW119IGxpc3QgLSBsaXN0IG9mIHNlcSBJRHMgdG8gZGVsZXRlXG4gICAqIEBwYXJhbSB7Qm9vbGVhbj19IGhhcmREZWwgLSB0cnVlIGlmIG1lc3NhZ2VzIHNob3VsZCBiZSBoYXJkLWRlbGV0ZWQuXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byByZXF1ZXN0LlxuICAgKi9cbiAgZGVsTWVzc2FnZXNMaXN0OiBmdW5jdGlvbihsaXN0LCBoYXJkRGVsKSB7XG4gICAgLy8gU29ydCB0aGUgbGlzdCBpbiBhc2NlbmRpbmcgb3JkZXJcbiAgICBsaXN0LnNvcnQoKGEsIGIpID0+IGEgLSBiKTtcbiAgICAvLyBDb252ZXJ0IHRoZSBhcnJheSBvZiBJRHMgdG8gcmFuZ2VzLlxuICAgIGxldCByYW5nZXMgPSBsaXN0LnJlZHVjZSgob3V0LCBpZCkgPT4ge1xuICAgICAgaWYgKG91dC5sZW5ndGggPT0gMCkge1xuICAgICAgICAvLyBGaXJzdCBlbGVtZW50LlxuICAgICAgICBvdXQucHVzaCh7XG4gICAgICAgICAgbG93OiBpZFxuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGxldCBwcmV2ID0gb3V0W291dC5sZW5ndGggLSAxXTtcbiAgICAgICAgaWYgKCghcHJldi5oaSAmJiAoaWQgIT0gcHJldi5sb3cgKyAxKSkgfHwgKGlkID4gcHJldi5oaSkpIHtcbiAgICAgICAgICAvLyBOZXcgcmFuZ2UuXG4gICAgICAgICAgb3V0LnB1c2goe1xuICAgICAgICAgICAgbG93OiBpZFxuICAgICAgICAgIH0pO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIC8vIEV4cGFuZCBleGlzdGluZyByYW5nZS5cbiAgICAgICAgICBwcmV2LmhpID0gcHJldi5oaSA/IE1hdGgubWF4KHByZXYuaGksIGlkICsgMSkgOiBpZCArIDE7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBvdXQ7XG4gICAgfSwgW10pO1xuICAgIC8vIFNlbmQge2RlbH0gbWVzc2FnZSwgcmV0dXJuIHByb21pc2VcbiAgICByZXR1cm4gdGhpcy5kZWxNZXNzYWdlcyhyYW5nZXMsIGhhcmREZWwpXG4gIH0sXG5cbiAgLyoqXG4gICAqIERlbGV0ZSB0b3BpYy4gUmVxdWlyZXMgT3duZXIgcGVybWlzc2lvbi4gV3JhcHBlciBmb3Ige0BsaW5rIFRpbm9kZSNkZWxUb3BpY30uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byB0aGUgcmVxdWVzdC5cbiAgICovXG4gIGRlbFRvcGljOiBmdW5jdGlvbigpIHtcbiAgICBjb25zdCB0b3BpYyA9IHRoaXM7XG4gICAgcmV0dXJuIHRoaXMuX3Rpbm9kZS5kZWxUb3BpYyh0aGlzLm5hbWUpLnRoZW4oZnVuY3Rpb24oY3RybCkge1xuICAgICAgdG9waWMuX3Jlc2V0U3ViKCk7XG4gICAgICB0b3BpYy5fZ29uZSgpO1xuICAgICAgcmV0dXJuIGN0cmw7XG4gICAgfSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIERlbGV0ZSBzdWJzY3JpcHRpb24uIFJlcXVpcmVzIFNoYXJlIHBlcm1pc3Npb24uIFdyYXBwZXIgZm9yIHtAbGluayBUaW5vZGUjZGVsU3Vic2NyaXB0aW9ufS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHVzZXIgLSBJRCBvZiB0aGUgdXNlciB0byByZW1vdmUgc3Vic2NyaXB0aW9uIGZvci5cbiAgICogQHJldHVybnMge1Byb21pc2V9IFByb21pc2UgdG8gYmUgcmVzb2x2ZWQvcmVqZWN0ZWQgd2hlbiB0aGUgc2VydmVyIHJlc3BvbmRzIHRvIHJlcXVlc3QuXG4gICAqL1xuICBkZWxTdWJzY3JpcHRpb246IGZ1bmN0aW9uKHVzZXIpIHtcbiAgICBpZiAoIXRoaXMuX3N1YnNjcmliZWQpIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlamVjdChuZXcgRXJyb3IoXCJDYW5ub3QgZGVsZXRlIHN1YnNjcmlwdGlvbiBpbiBpbmFjdGl2ZSB0b3BpY1wiKSk7XG4gICAgfVxuICAgIC8vIFNlbmQge2RlbH0gbWVzc2FnZSwgcmV0dXJuIHByb21pc2VcbiAgICByZXR1cm4gdGhpcy5fdGlub2RlLmRlbFN1YnNjcmlwdGlvbih0aGlzLm5hbWUsIHVzZXIpLnRoZW4oKGN0cmwpID0+IHtcbiAgICAgIC8vIFJlbW92ZSB0aGUgb2JqZWN0IGZyb20gdGhlIHN1YnNjcmlwdGlvbiBjYWNoZTtcbiAgICAgIGRlbGV0ZSB0aGlzLl91c2Vyc1t1c2VyXTtcbiAgICAgIC8vIE5vdGlmeSBsaXN0ZW5lcnNcbiAgICAgIGlmICh0aGlzLm9uU3Vic1VwZGF0ZWQpIHtcbiAgICAgICAgdGhpcy5vblN1YnNVcGRhdGVkKE9iamVjdC5rZXlzKHRoaXMuX3VzZXJzKSk7XG4gICAgICB9XG4gICAgICByZXR1cm4gY3RybDtcbiAgICB9KTtcbiAgfSxcblxuICAvKipcbiAgICogU2VuZCBhIHJlYWQvcmVjdiBub3RpZmljYXRpb25cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHdoYXQgLSB3aGF0IG5vdGlmaWNhdGlvbiB0byBzZW5kOiA8dHQ+cmVjdjwvdHQ+LCA8dHQ+cmVhZDwvdHQ+LlxuICAgKiBAcGFyYW0ge051bWJlcn0gc2VxIC0gSUQgb3IgdGhlIG1lc3NhZ2UgcmVhZCBvciByZWNlaXZlZC5cbiAgICovXG4gIG5vdGU6IGZ1bmN0aW9uKHdoYXQsIHNlcSkge1xuICAgIGNvbnN0IHVzZXIgPSB0aGlzLl91c2Vyc1t0aGlzLl90aW5vZGUuZ2V0Q3VycmVudFVzZXJJRCgpXTtcbiAgICBpZiAodXNlcikge1xuICAgICAgaWYgKCF1c2VyW3doYXRdIHx8IHVzZXJbd2hhdF0gPCBzZXEpIHtcbiAgICAgICAgaWYgKHRoaXMuX3N1YnNjcmliZWQpIHtcbiAgICAgICAgICB0aGlzLl90aW5vZGUubm90ZSh0aGlzLm5hbWUsIHdoYXQsIHNlcSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdGhpcy5fdGlub2RlLmxvZ2dlcihcIklORk86IE5vdCBzZW5kaW5nIHtub3RlfSBvbiBpbmFjdGl2ZSB0b3BpY1wiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHVzZXJbd2hhdF0gPSBzZXE7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX3Rpbm9kZS5sb2dnZXIoXCJFUlJPUjogbm90ZSgpOiB1c2VyIG5vdCBmb3VuZCBcIiArIHRoaXMuX3Rpbm9kZS5nZXRDdXJyZW50VXNlcklEKCkpO1xuICAgIH1cblxuICAgIC8vIFVwZGF0ZSBsb2NhbGx5IGNhY2hlZCBjb250YWN0IHdpdGggdGhlIG5ldyBjb3VudFxuICAgIGNvbnN0IG1lID0gdGhpcy5fdGlub2RlLmdldE1lVG9waWMoKTtcbiAgICBpZiAobWUpIHtcbiAgICAgIG1lLnNldE1zZ1JlYWRSZWN2KHRoaXMubmFtZSwgd2hhdCwgc2VxKTtcbiAgICB9XG4gIH0sXG5cbiAgLyoqXG4gICAqIFNlbmQgYSAncmVjdicgcmVjZWlwdC4gV3JhcHBlciBmb3Ige0BsaW5rIFRpbm9kZSNub3RlUmVjdn0uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBzZXEgLSBJRCBvZiB0aGUgbWVzc2FnZSB0byBha25vd2xlZGdlLlxuICAgKi9cbiAgbm90ZVJlY3Y6IGZ1bmN0aW9uKHNlcSkge1xuICAgIHRoaXMubm90ZSgncmVjdicsIHNlcSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFNlbmQgYSAncmVhZCcgcmVjZWlwdC4gV3JhcHBlciBmb3Ige0BsaW5rIFRpbm9kZSNub3RlUmVhZH0uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBzZXEgLSBJRCBvZiB0aGUgbWVzc2FnZSB0byBha25vd2xlZGdlLlxuICAgKi9cbiAgbm90ZVJlYWQ6IGZ1bmN0aW9uKHNlcSkge1xuICAgIHRoaXMubm90ZSgncmVhZCcsIHNlcSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFNlbmQgYSBrZXktcHJlc3Mgbm90aWZpY2F0aW9uLiBXcmFwcGVyIGZvciB7QGxpbmsgVGlub2RlI25vdGVLZXlQcmVzc30uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqL1xuICBub3RlS2V5UHJlc3M6IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLl9zdWJzY3JpYmVkKSB7XG4gICAgICB0aGlzLl90aW5vZGUubm90ZUtleVByZXNzKHRoaXMubmFtZSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX3Rpbm9kZS5sb2dnZXIoXCJJTkZPOiBDYW5ub3Qgc2VuZCBub3RpZmljYXRpb24gaW4gaW5hY3RpdmUgdG9waWNcIik7XG4gICAgfVxuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgdXNlciBkZXNjcmlwdGlvbiBmcm9tIGdsb2JhbCBjYWNoZS4gVGhlIHVzZXIgZG9lcyBub3QgbmVlZCB0byBiZSBhXG4gICAqIHN1YnNjcmliZXIgb2YgdGhpcyB0b3BpYy5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHVpZCAtIElEIG9mIHRoZSB1c2VyIHRvIGZldGNoLlxuICAgKiBAcmV0dXJuIHtPYmplY3R9IHVzZXIgZGVzY3JpcHRpb24gb3IgdW5kZWZpbmVkLlxuICAgKi9cbiAgdXNlckRlc2M6IGZ1bmN0aW9uKHVpZCkge1xuICAgIC8vIFRPRE8oZ2VuZSk6IGhhbmRsZSBhc3luY2hyb25vdXMgcmVxdWVzdHNcblxuICAgIGNvbnN0IHVzZXIgPSB0aGlzLl9jYWNoZUdldFVzZXIodWlkKTtcbiAgICBpZiAodXNlcikge1xuICAgICAgcmV0dXJuIHVzZXI7IC8vIFByb21pc2UucmVzb2x2ZSh1c2VyKVxuICAgIH1cbiAgfSxcblxuICAvKipcbiAgICogR2V0IGRlc2NyaXB0aW9uIG9mIHRoZSBwMnAgcGVlciBmcm9tIHN1YnNjcmlwdGlvbiBjYWNoZS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHJldHVybiB7T2JqZWN0fSBwZWVyJ3MgZGVzY3JpcHRpb24gb3IgdW5kZWZpbmVkLlxuICAgKi9cbiAgcDJwUGVlckRlc2M6IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLmdldFR5cGUoKSAhPSAncDJwJykge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuX3VzZXJzW3RoaXMubmFtZV07XG4gIH0sXG5cbiAgLyoqXG4gICAqIEl0ZXJhdGUgb3ZlciBjYWNoZWQgc3Vic2NyaWJlcnMuIElmIGNhbGxiYWNrIGlzIHVuZGVmaW5lZCwgdXNlIHRoaXMub25NZXRhU3ViLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBjYWxsYmFjayAtIENhbGxiYWNrIHdoaWNoIHdpbGwgcmVjZWl2ZSBzdWJzY3JpYmVycyBvbmUgYnkgb25lLlxuICAgKiBAcGFyYW0ge09iamVjdD19IGNvbnRleHQgLSBWYWx1ZSBvZiBgdGhpc2AgaW5zaWRlIHRoZSBgY2FsbGJhY2tgLlxuICAgKi9cbiAgc3Vic2NyaWJlcnM6IGZ1bmN0aW9uKGNhbGxiYWNrLCBjb250ZXh0KSB7XG4gICAgY29uc3QgY2IgPSAoY2FsbGJhY2sgfHwgdGhpcy5vbk1ldGFTdWIpO1xuICAgIGlmIChjYikge1xuICAgICAgZm9yIChsZXQgaWR4IGluIHRoaXMuX3VzZXJzKSB7XG4gICAgICAgIGNiLmNhbGwoY29udGV4dCwgdGhpcy5fdXNlcnNbaWR4XSwgaWR4LCB0aGlzLl91c2Vycyk7XG4gICAgICB9XG4gICAgfVxuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgYSBjb3B5IG9mIGNhY2hlZCB0YWdzLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKiBAcmV0dXJuIGEgY29weSBvZiB0YWdzXG4gICAqL1xuICB0YWdzOiBmdW5jdGlvbigpIHtcbiAgICAvLyBSZXR1cm4gYSBjb3B5LlxuICAgIHJldHVybiB0aGlzLl90YWdzLnNsaWNlKDApO1xuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgY2FjaGVkIHN1YnNjcmlwdGlvbiBmb3IgdGhlIGdpdmVuIHVzZXIgSUQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB1aWQgLSBpZCBvZiB0aGUgdXNlciB0byBxdWVyeSBmb3JcbiAgICogQHJldHVybiB1c2VyIGRlc2NyaXB0aW9uIG9yIHVuZGVmaW5lZC5cbiAgICovXG4gIHN1YnNjcmliZXI6IGZ1bmN0aW9uKHVpZCkge1xuICAgIHJldHVybiB0aGlzLl91c2Vyc1t1aWRdO1xuICB9LFxuXG4gIC8qKlxuICAgKiBJdGVyYXRlIG92ZXIgY2FjaGVkIG1lc3NhZ2VzLiBJZiBjYWxsYmFjayBpcyB1bmRlZmluZWQsIHVzZSB0aGlzLm9uRGF0YS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2sgLSBDYWxsYmFjayB3aGljaCB3aWxsIHJlY2VpdmUgbWVzc2FnZXMgb25lIGJ5IG9uZS4gU2VlIHtAbGluayBUaW5vZGUuQ0J1ZmZlciNmb3JFYWNofVxuICAgKiBAcGFyYW0ge2ludGVnZXJ9IHNpbmNlSWQgLSBPcHRpb25hbCBzZXFJZCB0byBzdGFydCBpdGVyYXRpbmcgZnJvbSAoaW5jbHVzaXZlKS5cbiAgICogQHBhcmFtIHtpbnRlZ2VyfSBiZWZvcmVJZCAtIE9wdGlvbmFsIHNlcUlkIHRvIHN0b3AgaXRlcmF0aW5nIGJlZm9yZSAoZXhjbHVzaXZlKS5cbiAgICogQHBhcmFtIHtPYmplY3R9IGNvbnRleHQgLSBWYWx1ZSBvZiBgdGhpc2AgaW5zaWRlIHRoZSBgY2FsbGJhY2tgLlxuICAgKi9cbiAgbWVzc2FnZXM6IGZ1bmN0aW9uKGNhbGxiYWNrLCBzaW5jZUlkLCBiZWZvcmVJZCwgY29udGV4dCkge1xuICAgIGNvbnN0IGNiID0gKGNhbGxiYWNrIHx8IHRoaXMub25EYXRhKTtcbiAgICBpZiAoY2IpIHtcbiAgICAgIGxldCBzdGFydElkeCA9IHR5cGVvZiBzaW5jZUlkID09ICdudW1iZXInID8gdGhpcy5fbWVzc2FnZXMuZmluZCh7XG4gICAgICAgIHNlcTogc2luY2VJZFxuICAgICAgfSkgOiB1bmRlZmluZWQ7XG4gICAgICBsZXQgYmVmb3JlSWR4ID0gdHlwZW9mIGJlZm9yZUlkID09ICdudW1iZXInID8gdGhpcy5fbWVzc2FnZXMuZmluZCh7XG4gICAgICAgIHNlcTogYmVmb3JlSWRcbiAgICAgIH0sIHRydWUpIDogdW5kZWZpbmVkO1xuICAgICAgaWYgKHN0YXJ0SWR4ICE9IC0xICYmIGJlZm9yZUlkeCAhPSAtMSkge1xuICAgICAgICB0aGlzLl9tZXNzYWdlcy5mb3JFYWNoKGNiLCBzdGFydElkeCwgYmVmb3JlSWR4LCBjb250ZXh0KTtcbiAgICAgIH1cbiAgICB9XG4gIH0sXG5cbiAgLyoqXG4gICAqIEl0ZXJhdGUgb3ZlciBjYWNoZWQgdW5zZW50IG1lc3NhZ2VzLiBXcmFwcyB7QGxpbmsgVGlub2RlLlRvcGljI21lc3NhZ2VzfS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2sgLSBDYWxsYmFjayB3aGljaCB3aWxsIHJlY2VpdmUgbWVzc2FnZXMgb25lIGJ5IG9uZS4gU2VlIHtAbGluayBUaW5vZGUuQ0J1ZmZlciNmb3JFYWNofVxuICAgKiBAcGFyYW0ge09iamVjdH0gY29udGV4dCAtIFZhbHVlIG9mIGB0aGlzYCBpbnNpZGUgdGhlIGBjYWxsYmFja2AuXG4gICAqL1xuICBxdWV1ZWRNZXNzYWdlczogZnVuY3Rpb24oY2FsbGJhY2ssIGNvbnRleHQpIHtcbiAgICBpZiAoIWNhbGxiYWNrKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJDYWxsYmFjayBtdXN0IGJlIHByb3ZpZGVkXCIpO1xuICAgIH1cbiAgICB0aGlzLm1lc3NhZ2VzKGNhbGxiYWNrLCBMT0NBTF9TRVFJRCwgdW5kZWZpbmVkLCBjb250ZXh0KTtcbiAgfSxcblxuICAvKipcbiAgICogR2V0IHRoZSBudW1iZXIgb2YgdG9waWMgc3Vic2NyaWJlcnMgd2hvIG1hcmtlZCB0aGlzIG1lc3NhZ2UgYXMgZWl0aGVyIHJlY3Ygb3IgcmVhZFxuICAgKiBDdXJyZW50IHVzZXIgaXMgZXhjbHVkZWQgZnJvbSB0aGUgY291bnQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB3aGF0IC0gd2hhdCBub3RpZmljYXRpb24gdG8gc2VuZDogPHR0PnJlY3Y8L3R0PiwgPHR0PnJlYWQ8L3R0Pi5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IHNlcSAtIElEIG9yIHRoZSBtZXNzYWdlIHJlYWQgb3IgcmVjZWl2ZWQuXG4gICAqL1xuICBtc2dSZWNlaXB0Q291bnQ6IGZ1bmN0aW9uKHdoYXQsIHNlcSkge1xuICAgIGxldCBjb3VudCA9IDA7XG4gICAgaWYgKHNlcSA+IDApIHtcbiAgICAgIGNvbnN0IG1lID0gdGhpcy5fdGlub2RlLmdldEN1cnJlbnRVc2VySUQoKTtcbiAgICAgIGZvciAobGV0IGlkeCBpbiB0aGlzLl91c2Vycykge1xuICAgICAgICBjb25zdCB1c2VyID0gdGhpcy5fdXNlcnNbaWR4XTtcbiAgICAgICAgaWYgKHVzZXIudXNlciAhPT0gbWUgJiYgdXNlclt3aGF0XSA+PSBzZXEpIHtcbiAgICAgICAgICBjb3VudCsrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBjb3VudDtcbiAgfSxcblxuICAvKipcbiAgICogR2V0IHRoZSBudW1iZXIgb2YgdG9waWMgc3Vic2NyaWJlcnMgd2hvIG1hcmtlZCB0aGlzIG1lc3NhZ2UgKGFuZCBhbGwgb2xkZXIgbWVzc2FnZXMpIGFzIHJlYWQuXG4gICAqIFRoZSBjdXJyZW50IHVzZXIgaXMgZXhjbHVkZWQgZnJvbSB0aGUgY291bnQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBzZXEgLSBNZXNzYWdlIGlkIHRvIGNoZWNrLlxuICAgKiBAcmV0dXJucyB7TnVtYmVyfSBOdW1iZXIgb2Ygc3Vic2NyaWJlcnMgd2hvIGNsYWltIHRvIGhhdmUgcmVjZWl2ZWQgdGhlIG1lc3NhZ2UuXG4gICAqL1xuICBtc2dSZWFkQ291bnQ6IGZ1bmN0aW9uKHNlcSkge1xuICAgIHJldHVybiB0aGlzLm1zZ1JlY2VpcHRDb3VudCgncmVhZCcsIHNlcSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEdldCB0aGUgbnVtYmVyIG9mIHRvcGljIHN1YnNjcmliZXJzIHdobyBtYXJrZWQgdGhpcyBtZXNzYWdlIChhbmQgYWxsIG9sZGVyIG1lc3NhZ2VzKSBhcyByZWNlaXZlZC5cbiAgICogVGhlIGN1cnJlbnQgdXNlciBpcyBleGNsdWRlZCBmcm9tIHRoZSBjb3VudC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtudW1iZXJ9IHNlcSAtIE1lc3NhZ2UgaWQgdG8gY2hlY2suXG4gICAqIEByZXR1cm5zIHtudW1iZXJ9IE51bWJlciBvZiBzdWJzY3JpYmVycyB3aG8gY2xhaW0gdG8gaGF2ZSByZWNlaXZlZCB0aGUgbWVzc2FnZS5cbiAgICovXG4gIG1zZ1JlY3ZDb3VudDogZnVuY3Rpb24oc2VxKSB7XG4gICAgcmV0dXJuIHRoaXMubXNnUmVjZWlwdENvdW50KCdyZWN2Jywgc2VxKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgY2FjaGVkIG1lc3NhZ2UgSURzIGluZGljYXRlIHRoYXQgdGhlIHNlcnZlciBtYXkgaGF2ZSBtb3JlIG1lc3NhZ2VzLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcGFyYW0ge2Jvb2xlYW59IG5ld2VyIGNoZWNrIGZvciBuZXdlciBtZXNzYWdlc1xuICAgKi9cbiAgbXNnSGFzTW9yZU1lc3NhZ2VzOiBmdW5jdGlvbihuZXdlcikge1xuICAgIHJldHVybiBuZXdlciA/IHRoaXMuc2VxID4gdGhpcy5fbWF4U2VxIDpcbiAgICAgIC8vIF9taW5TZXEgY291bmQgYmUgbW9yZSB0aGFuIDEsIGJ1dCBlYXJsaWVyIG1lc3NhZ2VzIGNvdWxkIGhhdmUgYmVlbiBkZWxldGVkLlxuICAgICAgKHRoaXMuX21pblNlcSA+IDEgJiYgIXRoaXMuX25vRWFybGllck1zZ3MpO1xuICB9LFxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiB0aGUgZ2l2ZW4gc2VxIElkIGlzIGlkIG9mIHRoZSBtb3N0IHJlY2VudCBtZXNzYWdlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcGFyYW0ge2ludGVnZXJ9IHNlcUlkIGlkIG9mIHRoZSBtZXNzYWdlIHRvIGNoZWNrXG4gICAqL1xuICBpc05ld01lc3NhZ2U6IGZ1bmN0aW9uKHNlcUlkKSB7XG4gICAgcmV0dXJuIHRoaXMuX21heFNlcSA8PSBzZXFJZDtcbiAgfSxcblxuICAvKipcbiAgICogUmVtb3ZlIG9uZSBtZXNzYWdlIGZyb20gbG9jYWwgY2FjaGUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEBwYXJhbSB7aW50ZWdlcn0gc2VxSWQgaWQgb2YgdGhlIG1lc3NhZ2UgdG8gcmVtb3ZlIGZyb20gY2FjaGUuXG4gICAqIEByZXR1cm5zIHtNZXNzYWdlfSByZW1vdmVkIG1lc3NhZ2Ugb3IgdW5kZWZpbmVkIGlmIHN1Y2ggbWVzc2FnZSB3YXMgbm90IGZvdW5kLlxuICAgKi9cbiAgZmx1c2hNZXNzYWdlOiBmdW5jdGlvbihzZXFJZCkge1xuICAgIGxldCBpZHggPSB0aGlzLl9tZXNzYWdlcy5maW5kKHtcbiAgICAgIHNlcTogc2VxSWRcbiAgICB9KTtcbiAgICByZXR1cm4gaWR4ID49IDAgPyB0aGlzLl9tZXNzYWdlcy5kZWxBdChpZHgpIDogdW5kZWZpbmVkO1xuICB9LFxuXG4gIC8qKlxuICAgKiBSZW1vdmUgYSByYW5nZSBvZiBtZXNzYWdlcyBmcm9tIHRoZSBsb2NhbCBjYWNoZS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtpbnRlZ2VyfSBmcm9tSWQgc2VxIElEIG9mIHRoZSBmaXJzdCBtZXNzYWdlIHRvIHJlbW92ZSAoaW5jbHVzaXZlKS5cbiAgICogQHBhcmFtIHtpbnRlZ2VyfSB1bnRpbElkIHNlcUlEIG9mIHRoZSBsYXN0IG1lc3NhZ2UgdG8gcmVtb3ZlIChleGNsdXNpdmUpLlxuICAgKlxuICAgKiBAcmV0dXJucyB7TWVzc2FnZVtdfSBhcnJheSBvZiByZW1vdmVkIG1lc3NhZ2VzIChjb3VsZCBiZSBlbXB0eSkuXG4gICAqL1xuICBmbHVzaE1lc3NhZ2VSYW5nZTogZnVuY3Rpb24oZnJvbUlkLCB1bnRpbElkKSB7XG4gICAgLy8gc3RhcnQsIGVuZDogZmluZCBpbnNlcnRpb24gcG9pbnRzIChuZWFyZXN0ID09IHRydWUpLlxuICAgIGNvbnN0IHNpbmNlID0gdGhpcy5fbWVzc2FnZXMuZmluZCh7XG4gICAgICBzZXE6IGZyb21JZFxuICAgIH0sIHRydWUpO1xuICAgIHJldHVybiBzaW5jZSA+PSAwID8gdGhpcy5fbWVzc2FnZXMuZGVsUmFuZ2Uoc2luY2UsIHRoaXMuX21lc3NhZ2VzLmZpbmQoe1xuICAgICAgc2VxOiB1bnRpbElkXG4gICAgfSwgdHJ1ZSkpIDogW107XG4gIH0sXG5cbiAgLyoqXG4gICAqIEF0dGVtcHQgdG8gc3RvcCBtZXNzYWdlIGZyb20gYmVpbmcgc2VudC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtpbnRlZ2VyfSBzZXFJZCBpZCBvZiB0aGUgbWVzc2FnZSB0byBzdG9wIHNlbmRpbmcgYW5kIHJlbW92ZSBmcm9tIGNhY2hlLlxuICAgKlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gdHJ1ZSBpZiBtZXNzYWdlIHdhcyBjYW5jZWxsZWQsIGZhbHNlIG90aGVyd2lzZS5cbiAgICovXG4gIGNhbmNlbFNlbmQ6IGZ1bmN0aW9uKHNlcUlkKSB7XG4gICAgY29uc3QgaWR4ID0gdGhpcy5fbWVzc2FnZXMuZmluZCh7XG4gICAgICBzZXE6IHNlcUlkXG4gICAgfSk7XG4gICAgaWYgKGlkeCA+PSAwKSB7XG4gICAgICBjb25zdCBtc2cgPSB0aGlzLl9tZXNzYWdlcy5nZXRBdChpZHgpO1xuICAgICAgY29uc3Qgc3RhdHVzID0gdGhpcy5tc2dTdGF0dXMobXNnKTtcbiAgICAgIGlmIChzdGF0dXMgPT0gTUVTU0FHRV9TVEFUVVNfUVVFVUVEIHx8IHN0YXR1cyA9PSBNRVNTQUdFX1NUQVRVU19GQUlMRUQpIHtcbiAgICAgICAgbXNnLl9jYW5jZWxsZWQgPSB0cnVlO1xuICAgICAgICB0aGlzLl9tZXNzYWdlcy5kZWxBdChpZHgpO1xuICAgICAgICBpZiAodGhpcy5vbkRhdGEpIHtcbiAgICAgICAgICAvLyBDYWxsaW5nIHdpdGggbm8gcGFyYW1ldGVycyB0byBpbmRpY2F0ZSB0aGUgbWVzc2FnZSB3YXMgZGVsZXRlZC5cbiAgICAgICAgICB0aGlzLm9uRGF0YSgpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEdldCB0eXBlIG9mIHRoZSB0b3BpYzogbWUsIHAycCwgZ3JwLCBmbmQuLi5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHJldHVybnMge1N0cmluZ30gT25lIG9mICdtZScsICdwMnAnLCAnZ3JwJywgJ2ZuZCcgb3IgPHR0PnVuZGVmaW5lZDwvdHQ+LlxuICAgKi9cbiAgZ2V0VHlwZTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIFRpbm9kZS50b3BpY1R5cGUodGhpcy5uYW1lKTtcbiAgfSxcblxuICAvKipcbiAgICogR2V0IHVzZXIncyBjdW11bGF0aXZlIGFjY2VzcyBtb2RlIG9mIHRoZSB0b3BpYy5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHJldHVybnMge1Rpbm9kZS5BY2Nlc3NNb2RlfSAtIHVzZXIncyBhY2Nlc3MgbW9kZVxuICAgKi9cbiAgZ2V0QWNjZXNzTW9kZTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHRoaXMuYWNzO1xuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgdG9waWMncyBkZWZhdWx0IGFjY2VzcyBtb2RlLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljI1xuICAgKlxuICAgKiBAcmV0dXJucyB7VGlub2RlLkRlZkFjc30gLSBhY2Nlc3MgbW9kZSwgc3VjaCBhcyB7YXV0aDogYFJXUGAsIGFub246IGBOYH0uXG4gICAqL1xuICBnZXREZWZhdWx0QWNjZXNzOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5kZWZhY3M7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEluaXRpYWxpemUgbmV3IG1ldGEge0BsaW5rIFRpbm9kZS5HZXRRdWVyeX0gYnVpbGRlci4gVGhlIHF1ZXJ5IGlzIGF0dGNoZWQgdG8gdGhlIGN1cnJlbnQgdG9waWMuXG4gICAqIEl0IHdpbGwgbm90IHdvcmsgY29ycmVjdGx5IGlmIHVzZWQgd2l0aCBhIGRpZmZlcmVudCB0b3BpYy5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHJldHVybnMge1Rpbm9kZS5NZXRhR2V0QnVpbGRlcn0gcXVlcnkgYXR0YWNoZWQgdG8gdGhlIGN1cnJlbnQgdG9waWMuXG4gICAqL1xuICBzdGFydE1ldGFRdWVyeTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIG5ldyBNZXRhR2V0QnVpbGRlcih0aGlzKTtcbiAgfSxcblxuICAvKipcbiAgICogQ2hlY2sgaWYgdG9waWMgaXMgYXJjaGl2ZWQsIGkuZS4gcHJpdmF0ZS5hcmNoID09IHRydWUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWMjXG4gICAqXG4gICAqIEByZXR1cm5zIHtib29sZWFufSAtIHRydWUgaWYgdG9waWMgaXMgYXJjaGl2ZWQsIGZhbHNlIG90aGVyd2lzZS5cbiAgICovXG4gIGlzQXJjaGl2ZWQ6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLnByaXZhdGUgJiYgdGhpcy5wcml2YXRlLmFyY2ggPyB0cnVlIDogZmFsc2U7XG4gIH0sXG5cbiAgLyoqXG4gICAqIEdldCBzdGF0dXMgKHF1ZXVlZCwgc2VudCwgcmVjZWl2ZWQgZXRjKSBvZiBhIGdpdmVuIG1lc3NhZ2UgaW4gdGhlIGNvbnRleHRcbiAgICogb2YgdGhpcyB0b3BpYy5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpYyNcbiAgICpcbiAgICogQHBhcmFtIHtNZXNzYWdlfSBtc2cgbWVzc2FnZSB0byBjaGVjayBmb3Igc3RhdHVzLlxuICAgKiBAcmV0dXJucyBtZXNzYWdlIHN0YXR1cyBjb25zdGFudC5cbiAgICovXG4gIG1zZ1N0YXR1czogZnVuY3Rpb24obXNnKSB7XG4gICAgbGV0IHN0YXR1cyA9IE1FU1NBR0VfU1RBVFVTX05PTkU7XG4gICAgaWYgKHRoaXMuX3Rpbm9kZS5pc01lKG1zZy5mcm9tKSkge1xuICAgICAgaWYgKG1zZy5fc2VuZGluZykge1xuICAgICAgICBzdGF0dXMgPSBNRVNTQUdFX1NUQVRVU19TRU5ESU5HO1xuICAgICAgfSBlbHNlIGlmIChtc2cuX2ZhaWxlZCkge1xuICAgICAgICBzdGF0dXMgPSBNRVNTQUdFX1NUQVRVU19GQUlMRUQ7XG4gICAgICB9IGVsc2UgaWYgKG1zZy5zZXEgPj0gTE9DQUxfU0VRSUQpIHtcbiAgICAgICAgc3RhdHVzID0gTUVTU0FHRV9TVEFUVVNfUVVFVUVEO1xuICAgICAgfSBlbHNlIGlmICh0aGlzLm1zZ1JlYWRDb3VudChtc2cuc2VxKSA+IDApIHtcbiAgICAgICAgc3RhdHVzID0gTUVTU0FHRV9TVEFUVVNfUkVBRDtcbiAgICAgIH0gZWxzZSBpZiAodGhpcy5tc2dSZWN2Q291bnQobXNnLnNlcSkgPiAwKSB7XG4gICAgICAgIHN0YXR1cyA9IE1FU1NBR0VfU1RBVFVTX1JFQ0VJVkVEO1xuICAgICAgfSBlbHNlIGlmIChtc2cuc2VxID4gMCkge1xuICAgICAgICBzdGF0dXMgPSBNRVNTQUdFX1NUQVRVU19TRU5UO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBzdGF0dXMgPSBNRVNTQUdFX1NUQVRVU19UT19NRTtcbiAgICB9XG4gICAgcmV0dXJuIHN0YXR1cztcbiAgfSxcblxuICAvLyBQcm9jZXNzIGRhdGEgbWVzc2FnZVxuICBfcm91dGVEYXRhOiBmdW5jdGlvbihkYXRhKSB7XG4gICAgLy8gTWF5YmUgdGhpcyBpcyBhbiBlbXB0eSBtZXNzYWdlIHRvIGluZGljYXRlIHRoZXJlIGFyZSBubyBhY3R1YWwgbWVzc2FnZXMuXG4gICAgaWYgKGRhdGEuY29udGVudCkge1xuICAgICAgaWYgKCF0aGlzLnRvdWNoZWQgfHwgdGhpcy50b3VjaGVkIDwgZGF0YS50cykge1xuICAgICAgICB0aGlzLnRvdWNoZWQgPSBkYXRhLnRzO1xuICAgICAgfVxuXG4gICAgICBpZiAoIWRhdGEuX25vRm9yd2FyZGluZykge1xuICAgICAgICB0aGlzLl9tZXNzYWdlcy5wdXQoZGF0YSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGRhdGEuc2VxID4gdGhpcy5fbWF4U2VxKSB7XG4gICAgICB0aGlzLl9tYXhTZXEgPSBkYXRhLnNlcTtcbiAgICB9XG4gICAgaWYgKGRhdGEuc2VxIDwgdGhpcy5fbWluU2VxIHx8IHRoaXMuX21pblNlcSA9PSAwKSB7XG4gICAgICB0aGlzLl9taW5TZXEgPSBkYXRhLnNlcTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5vbkRhdGEpIHtcbiAgICAgIHRoaXMub25EYXRhKGRhdGEpO1xuICAgIH1cblxuICAgIC8vIFVwZGF0ZSBsb2NhbGx5IGNhY2hlZCBjb250YWN0IHdpdGggdGhlIG5ldyBtZXNzYWdlIGNvdW50LlxuICAgIGNvbnN0IG1lID0gdGhpcy5fdGlub2RlLmdldE1lVG9waWMoKTtcbiAgICBpZiAobWUpIHtcbiAgICAgIC8vIE1lc3NhZ2VzIGZyb20gdGhlIGN1cnJlbnQgdXNlciBhcmUgY29uc2lkZXJlZCB0byBiZSByZWFkIGFscmVhZHkuXG4gICAgICBtZS5zZXRNc2dSZWFkUmVjdih0aGlzLm5hbWUsXG4gICAgICAgIHRoaXMuX3Rpbm9kZS5pc01lKGRhdGEuZnJvbSkgPyAncmVhZCcgOiAnbXNnJyxcbiAgICAgICAgZGF0YS5zZXEsIGRhdGEudHMpO1xuICAgIH1cbiAgfSxcblxuICAvLyBQcm9jZXNzIG1ldGFkYXRhIG1lc3NhZ2VcbiAgX3JvdXRlTWV0YTogZnVuY3Rpb24obWV0YSkge1xuICAgIGlmIChtZXRhLmRlc2MpIHtcbiAgICAgIHRoaXMuX2xhc3REZXNjVXBkYXRlID0gbWV0YS50cztcbiAgICAgIHRoaXMuX3Byb2Nlc3NNZXRhRGVzYyhtZXRhLmRlc2MpO1xuICAgIH1cbiAgICBpZiAobWV0YS5zdWIgJiYgbWV0YS5zdWIubGVuZ3RoID4gMCkge1xuICAgICAgdGhpcy5fbGFzdFN1YnNVcGRhdGUgPSBtZXRhLnRzO1xuICAgICAgdGhpcy5fcHJvY2Vzc01ldGFTdWIobWV0YS5zdWIpO1xuICAgIH1cbiAgICBpZiAobWV0YS5kZWwpIHtcbiAgICAgIHRoaXMuX3Byb2Nlc3NEZWxNZXNzYWdlcyhtZXRhLmRlbC5jbGVhciwgbWV0YS5kZWwuZGVsc2VxKTtcbiAgICB9XG4gICAgaWYgKG1ldGEudGFncykge1xuICAgICAgdGhpcy5fcHJvY2Vzc01ldGFUYWdzKG1ldGEudGFncyk7XG4gICAgfVxuICAgIGlmIChtZXRhLmNyZWQpIHtcbiAgICAgIHRoaXMuX3Byb2Nlc3NNZXRhQ3JlZHMobWV0YS5jcmVkKTtcbiAgICB9XG4gICAgaWYgKHRoaXMub25NZXRhKSB7XG4gICAgICB0aGlzLm9uTWV0YShtZXRhKTtcbiAgICB9XG4gIH0sXG5cbiAgLy8gUHJvY2VzcyBwcmVzZW5jZSBjaGFuZ2UgbWVzc2FnZVxuICBfcm91dGVQcmVzOiBmdW5jdGlvbihwcmVzKSB7XG4gICAgbGV0IHVzZXI7XG4gICAgc3dpdGNoIChwcmVzLndoYXQpIHtcbiAgICAgIGNhc2UgJ2RlbCc6XG4gICAgICAgIC8vIERlbGV0ZSBjYWNoZWQgbWVzc2FnZXMuXG4gICAgICAgIHRoaXMuX3Byb2Nlc3NEZWxNZXNzYWdlcyhwcmVzLmNsZWFyLCBwcmVzLmRlbHNlcSk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAnb24nOlxuICAgICAgY2FzZSAnb2ZmJzpcbiAgICAgICAgLy8gVXBkYXRlIG9ubGluZSBzdGF0dXMgb2YgYSBzdWJzY3JpcHRpb24uXG4gICAgICAgIHVzZXIgPSB0aGlzLl91c2Vyc1twcmVzLnNyY107XG4gICAgICAgIGlmICh1c2VyKSB7XG4gICAgICAgICAgdXNlci5vbmxpbmUgPSBwcmVzLndoYXQgPT0gJ29uJztcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLl90aW5vZGUubG9nZ2VyKFwiV0FSTklORzogUHJlc2VuY2UgdXBkYXRlIGZvciBhbiB1bmtub3duIHVzZXJcIiwgdGhpcy5uYW1lLCBwcmVzLnNyYyk7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICd0ZXJtJzpcbiAgICAgICAgLy8gQXR0YWNobWVudCB0byB0b3BpYyBpcyB0ZXJtaW5hdGVkIHByb2JhYmx5IGR1ZSB0byBjbHVzdGVyIHJlaGFzaGluZy5cbiAgICAgICAgdGhpcy5fcmVzZXRTdWIoKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICdhY3MnOlxuICAgICAgICB1c2VyID0gdGhpcy5fdXNlcnNbcHJlcy5zcmNdO1xuICAgICAgICBpZiAoIXVzZXIpIHtcbiAgICAgICAgICAvLyBVcGRhdGUgZm9yIGFuIHVua25vd24gdXNlcjogbm90aWZpY2F0aW9uIG9mIGEgbmV3IHN1YnNjcmlwdGlvbi5cbiAgICAgICAgICBjb25zdCBhY3MgPSBuZXcgQWNjZXNzTW9kZSgpLnVwZGF0ZUFsbChwcmVzLmRhY3MpO1xuICAgICAgICAgIGlmIChhY3MgJiYgYWNzLm1vZGUgIT0gQWNjZXNzTW9kZS5fTk9ORSkge1xuICAgICAgICAgICAgdXNlciA9IHRoaXMuX2NhY2hlR2V0VXNlcihwcmVzLnNyYyk7XG4gICAgICAgICAgICBpZiAoIXVzZXIpIHtcbiAgICAgICAgICAgICAgdXNlciA9IHtcbiAgICAgICAgICAgICAgICB1c2VyOiBwcmVzLnNyYyxcbiAgICAgICAgICAgICAgICBhY3M6IGFjc1xuICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICB0aGlzLmdldE1ldGEodGhpcy5zdGFydE1ldGFRdWVyeSgpLndpdGhPbmVTdWIodW5kZWZpbmVkLCBwcmVzLnNyYykuYnVpbGQoKSk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICB1c2VyLmFjcyA9IGFjcztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHVzZXIudXBkYXRlZCA9IG5ldyBEYXRlKCk7XG4gICAgICAgICAgICB0aGlzLl9wcm9jZXNzTWV0YVN1YihbdXNlcl0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBLbm93biB1c2VyXG4gICAgICAgICAgdXNlci5hY3MudXBkYXRlQWxsKHByZXMuZGFjcyk7XG4gICAgICAgICAgLy8gVXBkYXRlIHVzZXIncyBhY2Nlc3MgbW9kZS5cbiAgICAgICAgICB0aGlzLl9wcm9jZXNzTWV0YVN1Yihbe1xuICAgICAgICAgICAgdXNlcjogcHJlcy5zcmMsXG4gICAgICAgICAgICB1cGRhdGVkOiBuZXcgRGF0ZSgpLFxuICAgICAgICAgICAgYWNzOiB1c2VyLmFjc1xuICAgICAgICAgIH1dKTtcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRoaXMuX3Rpbm9kZS5sb2dnZXIoXCJJTkZPOiBJZ25vcmVkIHByZXNlbmNlIHVwZGF0ZVwiLCBwcmVzLndoYXQpO1xuICAgIH1cblxuICAgIGlmICh0aGlzLm9uUHJlcykge1xuICAgICAgdGhpcy5vblByZXMocHJlcyk7XG4gICAgfVxuICB9LFxuXG4gIC8vIFByb2Nlc3Mge2luZm99IG1lc3NhZ2VcbiAgX3JvdXRlSW5mbzogZnVuY3Rpb24oaW5mbykge1xuICAgIGlmIChpbmZvLndoYXQgIT09ICdrcCcpIHtcbiAgICAgIGNvbnN0IHVzZXIgPSB0aGlzLl91c2Vyc1tpbmZvLmZyb21dO1xuICAgICAgaWYgKHVzZXIpIHtcbiAgICAgICAgdXNlcltpbmZvLndoYXRdID0gaW5mby5zZXE7XG4gICAgICAgIGlmICh1c2VyLnJlY3YgPCB1c2VyLnJlYWQpIHtcbiAgICAgICAgICB1c2VyLnJlY3YgPSB1c2VyLnJlYWQ7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gSWYgdGhpcyBpcyBhbiB1cGRhdGUgZnJvbSB0aGUgY3VycmVudCB1c2VyLCB1cGRhdGUgdGhlIGNvbnRhY3Qgd2l0aCB0aGUgbmV3IGNvdW50IHRvby5cbiAgICAgIGlmICh0aGlzLl90aW5vZGUuaXNNZShpbmZvLmZyb20pKSB7XG4gICAgICAgIGNvbnN0IG1lID0gdGhpcy5fdGlub2RlLmdldE1lVG9waWMoKTtcbiAgICAgICAgaWYgKG1lKSB7XG4gICAgICAgICAgbWUuc2V0TXNnUmVhZFJlY3YoaW5mby50b3BpYywgaW5mby53aGF0LCBpbmZvLnNlcSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKHRoaXMub25JbmZvKSB7XG4gICAgICB0aGlzLm9uSW5mbyhpbmZvKTtcbiAgICB9XG4gIH0sXG5cbiAgLy8gQ2FsbGVkIGJ5IFRpbm9kZSB3aGVuIG1ldGEuZGVzYyBwYWNrZXQgaXMgcmVjZWl2ZWQuXG4gIC8vIENhbGxlZCBieSAnbWUnIHRvcGljIG9uIGNvbnRhY3QgdXBkYXRlIChkZXNjLl9ub0ZvcndhcmRpbmcgaXMgdHJ1ZSkuXG4gIF9wcm9jZXNzTWV0YURlc2M6IGZ1bmN0aW9uKGRlc2MpIHtcbiAgICAvLyBTeW50aGV0aWMgZGVzYyBtYXkgaW5jbHVkZSBkZWZhY3MgZm9yIHAycCB0b3BpY3Mgd2hpY2ggaXMgdXNlbGVzcy5cbiAgICAvLyBSZW1vdmUgaXQuXG4gICAgaWYgKHRoaXMuZ2V0VHlwZSgpID09ICdwMnAnKSB7XG4gICAgICBkZWxldGUgZGVzYy5kZWZhY3M7XG4gICAgfVxuXG4gICAgLy8gQ29weSBwYXJhbWV0ZXJzIGZyb20gZGVzYyBvYmplY3QgdG8gdGhpcyB0b3BpYy5cbiAgICBtZXJnZU9iaih0aGlzLCBkZXNjKTtcblxuICAgIGlmICh0eXBlb2YgdGhpcy5jcmVhdGVkID09ICdzdHJpbmcnKSB7XG4gICAgICB0aGlzLmNyZWF0ZWQgPSBuZXcgRGF0ZSh0aGlzLmNyZWF0ZWQpO1xuICAgIH1cbiAgICBpZiAodHlwZW9mIHRoaXMudXBkYXRlZCA9PSAnc3RyaW5nJykge1xuICAgICAgdGhpcy51cGRhdGVkID0gbmV3IERhdGUodGhpcy51cGRhdGVkKTtcbiAgICB9XG4gICAgaWYgKHR5cGVvZiB0aGlzLnRvdWNoZWQgPT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMudG91Y2hlZCA9IG5ldyBEYXRlKHRoaXMudG91Y2hlZCk7XG4gICAgfVxuXG4gICAgLy8gVXBkYXRlIHJlbGV2YW50IGNvbnRhY3QgaW4gdGhlIG1lIHRvcGljLCBpZiBhdmFpbGFibGU6XG4gICAgaWYgKHRoaXMubmFtZSAhPT0gJ21lJyAmJiAhZGVzYy5fbm9Gb3J3YXJkaW5nKSB7XG4gICAgICBjb25zdCBtZSA9IHRoaXMuX3Rpbm9kZS5nZXRNZVRvcGljKCk7XG4gICAgICBpZiAobWUpIHtcbiAgICAgICAgLy8gTXVzdCB1c2Ugb3JpZ2luYWwgJ2Rlc2MnIGluc3RlYWQgb2YgJ3RoaXMnIHNvIG5vdCB0byBsb3NlIERFTF9DSEFSLlxuICAgICAgICBtZS5fcHJvY2Vzc01ldGFTdWIoW3tcbiAgICAgICAgICBfbm9Gb3J3YXJkaW5nOiB0cnVlLFxuICAgICAgICAgIHRvcGljOiB0aGlzLm5hbWUsXG4gICAgICAgICAgdXBkYXRlZDogdGhpcy51cGRhdGVkLFxuICAgICAgICAgIHRvdWNoZWQ6IHRoaXMudG91Y2hlZCxcbiAgICAgICAgICBhY3M6IGRlc2MuYWNzLFxuICAgICAgICAgIHNlcTogZGVzYy5zZXEsXG4gICAgICAgICAgcmVhZDogZGVzYy5yZWFkLFxuICAgICAgICAgIHJlY3Y6IGRlc2MucmVjdixcbiAgICAgICAgICBwdWJsaWM6IGRlc2MucHVibGljLFxuICAgICAgICAgIHByaXZhdGU6IGRlc2MucHJpdmF0ZVxuICAgICAgICB9XSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHRoaXMub25NZXRhRGVzYykge1xuICAgICAgdGhpcy5vbk1ldGFEZXNjKHRoaXMpO1xuICAgIH1cbiAgfSxcblxuICAvLyBDYWxsZWQgYnkgVGlub2RlIHdoZW4gbWV0YS5zdWIgaXMgcmVjaXZlZCBvciBpbiByZXNwb25zZSB0byByZWNlaXZlZFxuICAvLyB7Y3RybH0gYWZ0ZXIgc2V0TWV0YS1zdWIuXG4gIF9wcm9jZXNzTWV0YVN1YjogZnVuY3Rpb24oc3Vicykge1xuICAgIGZvciAobGV0IGlkeCBpbiBzdWJzKSB7XG4gICAgICBjb25zdCBzdWIgPSBzdWJzW2lkeF07XG5cbiAgICAgIHN1Yi51cGRhdGVkID0gbmV3IERhdGUoc3ViLnVwZGF0ZWQpO1xuICAgICAgc3ViLmRlbGV0ZWQgPSBzdWIuZGVsZXRlZCA/IG5ldyBEYXRlKHN1Yi5kZWxldGVkKSA6IG51bGw7XG5cbiAgICAgIGxldCB1c2VyID0gbnVsbDtcbiAgICAgIGlmICghc3ViLmRlbGV0ZWQpIHtcbiAgICAgICAgLy8gSWYgdGhpcyBpcyBhIGNoYW5nZSB0byB1c2VyJ3Mgb3duIHBlcm1pc3Npb25zLCB1cGRhdGUgdGhlbSBpbiB0b3BpYyB0b28uXG4gICAgICAgIC8vIERlc2Mgd2lsbCB1cGRhdGUgJ21lJyB0b3BpYy5cbiAgICAgICAgaWYgKHRoaXMuX3Rpbm9kZS5pc01lKHN1Yi51c2VyKSAmJiBzdWIuYWNzKSB7XG4gICAgICAgICAgdGhpcy5fcHJvY2Vzc01ldGFEZXNjKHtcbiAgICAgICAgICAgIHVwZGF0ZWQ6IHN1Yi51cGRhdGVkIHx8IG5ldyBEYXRlKCksXG4gICAgICAgICAgICB0b3VjaGVkOiBzdWIudXBkYXRlZCxcbiAgICAgICAgICAgIGFjczogc3ViLmFjc1xuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIHVzZXIgPSB0aGlzLl91cGRhdGVDYWNoZWRVc2VyKHN1Yi51c2VyLCBzdWIpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gU3Vic2NyaXB0aW9uIGlzIGRlbGV0ZWQsIHJlbW92ZSBpdCBmcm9tIHRvcGljIChidXQgbGVhdmUgaW4gVXNlcnMgY2FjaGUpXG4gICAgICAgIGRlbGV0ZSB0aGlzLl91c2Vyc1tzdWIudXNlcl07XG4gICAgICAgIHVzZXIgPSBzdWI7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLm9uTWV0YVN1Yikge1xuICAgICAgICB0aGlzLm9uTWV0YVN1Yih1c2VyKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAodGhpcy5vblN1YnNVcGRhdGVkKSB7XG4gICAgICB0aGlzLm9uU3Vic1VwZGF0ZWQoT2JqZWN0LmtleXModGhpcy5fdXNlcnMpKTtcbiAgICB9XG4gIH0sXG5cbiAgLy8gQ2FsbGVkIGJ5IFRpbm9kZSB3aGVuIG1ldGEudGFncyBpcyByZWNpdmVkLlxuICBfcHJvY2Vzc01ldGFUYWdzOiBmdW5jdGlvbih0YWdzKSB7XG4gICAgaWYgKHRhZ3MubGVuZ3RoID09IDEgJiYgdGFnc1swXSA9PSBUaW5vZGUuREVMX0NIQVIpIHtcbiAgICAgIHRhZ3MgPSBbXTtcbiAgICB9XG4gICAgdGhpcy5fdGFncyA9IHRhZ3M7XG4gICAgaWYgKHRoaXMub25UYWdzVXBkYXRlZCkge1xuICAgICAgdGhpcy5vblRhZ3NVcGRhdGVkKHRhZ3MpO1xuICAgIH1cbiAgfSxcblxuICAvLyBEbyBub3RoaW5nIGZvciB0b3BpY3Mgb3RoZXIgdGhhbiAnbWUnXG4gIF9wcm9jZXNzTWV0YUNyZWRzOiBmdW5jdGlvbihjcmVkcykge30sXG5cbiAgLy8gRGVsZXRlIGNhY2hlZCBtZXNzYWdlcyBhbmQgdXBkYXRlIGNhY2hlZCB0cmFuc2FjdGlvbiBJRHNcbiAgX3Byb2Nlc3NEZWxNZXNzYWdlczogZnVuY3Rpb24oY2xlYXIsIGRlbHNlcSkge1xuICAgIHRoaXMuX21heERlbCA9IE1hdGgubWF4KGNsZWFyLCB0aGlzLl9tYXhEZWwpO1xuICAgIHRoaXMuY2xlYXIgPSBNYXRoLm1heChjbGVhciwgdGhpcy5jbGVhcik7XG4gICAgY29uc3QgdG9waWMgPSB0aGlzO1xuICAgIGxldCBjb3VudCA9IDA7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkoZGVsc2VxKSkge1xuICAgICAgZGVsc2VxLm1hcChmdW5jdGlvbihyYW5nZSkge1xuICAgICAgICBpZiAoIXJhbmdlLmhpKSB7XG4gICAgICAgICAgY291bnQrKztcbiAgICAgICAgICB0b3BpYy5mbHVzaE1lc3NhZ2UocmFuZ2UubG93KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBmb3IgKGxldCBpID0gcmFuZ2UubG93OyBpIDwgcmFuZ2UuaGk7IGkrKykge1xuICAgICAgICAgICAgY291bnQrKztcbiAgICAgICAgICAgIHRvcGljLmZsdXNoTWVzc2FnZShpKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH1cbiAgICBpZiAoY291bnQgPiAwICYmIHRoaXMub25EYXRhKSB7XG4gICAgICB0aGlzLm9uRGF0YSgpO1xuICAgIH1cbiAgfSxcblxuICAvLyBUb3BpYyBpcyBpbmZvcm1lZCB0aGF0IHRoZSBlbnRpcmUgcmVzcG9uc2UgdG8ge2dldCB3aGF0PWRhdGF9IGhhcyBiZWVuIHJlY2VpdmVkLlxuICBfYWxsTWVzc2FnZXNSZWNlaXZlZDogZnVuY3Rpb24oY291bnQpIHtcbiAgICBpZiAodGhpcy5vbkFsbE1lc3NhZ2VzUmVjZWl2ZWQpIHtcbiAgICAgIHRoaXMub25BbGxNZXNzYWdlc1JlY2VpdmVkKGNvdW50KTtcbiAgICB9XG4gIH0sXG5cbiAgLy8gUmVzZXQgc3Vic2NyaWJlZCBzdGF0ZVxuICBfcmVzZXRTdWI6IGZ1bmN0aW9uKCkge1xuICAgIHRoaXMuX3N1YnNjcmliZWQgPSBmYWxzZTtcbiAgfSxcblxuICAvLyBUaGlzIHRvcGljIGlzIGVpdGhlciBkZWxldGVkIG9yIHVuc3Vic2NyaWJlZCBmcm9tLlxuICBfZ29uZTogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy5fbWVzc2FnZXMucmVzZXQoKTtcbiAgICB0aGlzLl91c2VycyA9IHt9O1xuICAgIHRoaXMuYWNzID0gbmV3IEFjY2Vzc01vZGUobnVsbCk7XG4gICAgdGhpcy5wcml2YXRlID0gbnVsbDtcbiAgICB0aGlzLnB1YmxpYyA9IG51bGw7XG4gICAgdGhpcy5fbWF4U2VxID0gMDtcbiAgICB0aGlzLl9taW5TZXEgPSAwO1xuICAgIHRoaXMuX3N1YnNjcmliZWQgPSBmYWxzZTtcblxuICAgIGNvbnN0IG1lID0gdGhpcy5fdGlub2RlLmdldE1lVG9waWMoKTtcbiAgICBpZiAobWUpIHtcbiAgICAgIG1lLl9yb3V0ZVByZXMoe1xuICAgICAgICBfbm9Gb3J3YXJkaW5nOiB0cnVlLFxuICAgICAgICB3aGF0OiAnZ29uZScsXG4gICAgICAgIHRvcGljOiAnbWUnLFxuICAgICAgICBzcmM6IHRoaXMubmFtZVxuICAgICAgfSk7XG4gICAgfVxuICAgIGlmICh0aGlzLm9uRGVsZXRlVG9waWMpIHtcbiAgICAgIHRoaXMub25EZWxldGVUb3BpYygpO1xuICAgIH1cbiAgfSxcblxuICAvLyBVcGRhdGUgZ2xvYmFsIHVzZXIgY2FjaGUgYW5kIGxvY2FsIHN1YnNjcmliZXJzIGNhY2hlLlxuICAvLyBEb24ndCBjYWxsIHRoaXMgbWV0aG9kIGZvciBub24tc3Vic2NyaWJlcnMuXG4gIF91cGRhdGVDYWNoZWRVc2VyOiBmdW5jdGlvbih1aWQsIG9iaikge1xuICAgIC8vIEZldGNoIHVzZXIgb2JqZWN0IGZyb20gdGhlIGdsb2JhbCBjYWNoZS5cbiAgICAvLyBUaGlzIGlzIGEgY2xvbmUgb2YgdGhlIHN0b3JlZCBvYmplY3RcbiAgICBsZXQgY2FjaGVkID0gdGhpcy5fY2FjaGVHZXRVc2VyKHVpZCk7XG4gICAgY2FjaGVkID0gbWVyZ2VPYmooY2FjaGVkIHx8IHt9LCBvYmopO1xuICAgIC8vIFNhdmUgdG8gZ2xvYmFsIGNhY2hlXG4gICAgdGhpcy5fY2FjaGVQdXRVc2VyKHVpZCwgY2FjaGVkKTtcbiAgICAvLyBTYXZlIHRvIHRoZSBsaXN0IG9mIHRvcGljIHN1YnNyaWJlcnMuXG4gICAgcmV0dXJuIG1lcmdlVG9DYWNoZSh0aGlzLl91c2VycywgdWlkLCBjYWNoZWQpO1xuICB9LFxuXG4gIC8vIEdldCBsb2NhbCBzZXFJZCBmb3IgYSBxdWV1ZWQgbWVzc2FnZS5cbiAgX2dldFF1ZXVlZFNlcUlkOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5fcXVldWVkU2VxSWQrKztcbiAgfVxufTtcblxuLyoqXG4gKiBAY2xhc3MgVG9waWNNZSAtIHNwZWNpYWwgY2FzZSBvZiB7QGxpbmsgVGlub2RlLlRvcGljfSBmb3JcbiAqIG1hbmFnaW5nIGRhdGEgb2YgdGhlIGN1cnJlbnQgdXNlciwgaW5jbHVkaW5nIGNvbnRhY3QgbGlzdC5cbiAqIEBleHRlbmRzIFRpbm9kZS5Ub3BpY1xuICogQG1lbWJlcm9mIFRpbm9kZVxuICpcbiAqIEBwYXJhbSB7VG9waWNNZS5DYWxsYmFja3N9IGNhbGxiYWNrcyAtIENhbGxiYWNrcyB0byByZWNlaXZlIHZhcmlvdXMgZXZlbnRzLlxuICovXG52YXIgVG9waWNNZSA9IGZ1bmN0aW9uKGNhbGxiYWNrcykge1xuICBUb3BpYy5jYWxsKHRoaXMsIFRPUElDX01FLCBjYWxsYmFja3MpO1xuICAvLyBMaXN0IG9mIGNvbnRhY3RzICh0b3BpY19uYW1lIC0+IENvbnRhY3Qgb2JqZWN0KVxuICB0aGlzLl9jb250YWN0cyA9IHt9O1xuXG4gIC8vIG1lLXNwZWNpZmljIGNhbGxiYWNrc1xuICBpZiAoY2FsbGJhY2tzKSB7XG4gICAgdGhpcy5vbkNvbnRhY3RVcGRhdGUgPSBjYWxsYmFja3Mub25Db250YWN0VXBkYXRlO1xuICB9XG59O1xuXG4vLyBJbmhlcml0IGV2ZXJ5dGluZyBmcm9tIHRoZSBnZW5lcmljIFRvcGljXG5Ub3BpY01lLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoVG9waWMucHJvdG90eXBlLCB7XG4gIC8vIE92ZXJyaWRlIHRoZSBvcmlnaW5hbCBUb3BpYy5fcHJvY2Vzc01ldGFTdWJcbiAgX3Byb2Nlc3NNZXRhU3ViOiB7XG4gICAgdmFsdWU6IGZ1bmN0aW9uKHN1YnMpIHtcbiAgICAgIGxldCB1cGRhdGVDb3VudCA9IDA7XG4gICAgICBmb3IgKGxldCBpZHggaW4gc3Vicykge1xuICAgICAgICBjb25zdCBzdWIgPSBzdWJzW2lkeF07XG4gICAgICAgIGNvbnN0IHRvcGljTmFtZSA9IHN1Yi50b3BpYztcblxuICAgICAgICAvLyBEb24ndCBzaG93ICdtZScgYW5kICdmbmQnIHRvcGljcyBpbiB0aGUgbGlzdCBvZiBjb250YWN0cy5cbiAgICAgICAgaWYgKHRvcGljTmFtZSA9PSBUT1BJQ19GTkQgfHwgdG9waWNOYW1lID09IFRPUElDX01FKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgc3ViLnVwZGF0ZWQgPSBuZXcgRGF0ZShzdWIudXBkYXRlZCk7XG4gICAgICAgIHN1Yi50b3VjaGVkID0gc3ViLnRvdWNoZWQgPyBuZXcgRGF0ZShzdWIudG91Y2hlZCkgOiB1bmRlZmluZWQ7XG4gICAgICAgIHN1Yi5kZWxldGVkID0gc3ViLmRlbGV0ZWQgPyBuZXcgRGF0ZShzdWIuZGVsZXRlZCkgOiBudWxsO1xuXG4gICAgICAgIGxldCBjb250ID0gbnVsbDtcbiAgICAgICAgaWYgKHN1Yi5kZWxldGVkKSB7XG4gICAgICAgICAgY29udCA9IHN1YjtcbiAgICAgICAgICBkZWxldGUgdGhpcy5fY29udGFjdHNbdG9waWNOYW1lXTtcbiAgICAgICAgfSBlbHNlIGlmIChzdWIuYWNzICYmICFzdWIuYWNzLmlzSm9pbmVyKCkpIHtcbiAgICAgICAgICBjb250ID0gc3ViO1xuICAgICAgICAgIGNvbnQuZGVsZXRlZCA9IG5ldyBEYXRlKCk7XG4gICAgICAgICAgZGVsZXRlIHRoaXMuX2NvbnRhY3RzW3RvcGljTmFtZV07XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gRW5zdXJlIHRoZSB2YWx1ZXMgYXJlIGRlZmluZWQgYW5kIGFyZSBpbnRlZ2Vycy5cbiAgICAgICAgICBpZiAodHlwZW9mIHN1Yi5zZXEgIT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgICAgIHN1Yi5zZXEgPSBzdWIuc2VxIHwgMDtcbiAgICAgICAgICAgIHN1Yi5yZWN2ID0gc3ViLnJlY3YgfCAwO1xuICAgICAgICAgICAgc3ViLnJlYWQgPSBzdWIucmVhZCB8IDA7XG4gICAgICAgICAgICBzdWIudW5yZWFkID0gc3ViLnNlcSAtIHN1Yi5yZWFkO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGlmIChzdWIuc2VlbiAmJiBzdWIuc2Vlbi53aGVuKSB7XG4gICAgICAgICAgICBzdWIuc2Vlbi53aGVuID0gbmV3IERhdGUoc3ViLnNlZW4ud2hlbik7XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnQgPSBtZXJnZVRvQ2FjaGUodGhpcy5fY29udGFjdHMsIHRvcGljTmFtZSwgc3ViKTtcblxuICAgICAgICAgIGlmIChUaW5vZGUudG9waWNUeXBlKHRvcGljTmFtZSkgPT0gJ3AycCcpIHtcbiAgICAgICAgICAgIHRoaXMuX2NhY2hlUHV0VXNlcih0b3BpY05hbWUsIGNvbnQpO1xuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBOb3RpZnkgdG9waWMgb2YgdGhlIHVwZGF0ZSBpZiBpdCdzIGFuIGV4dGVybmFsIHVwZGF0ZS5cbiAgICAgICAgICBpZiAoIXN1Yi5fbm9Gb3J3YXJkaW5nKSB7XG4gICAgICAgICAgICBjb25zdCB0b3BpYyA9IHRoaXMuX3Rpbm9kZS5nZXRUb3BpYyh0b3BpY05hbWUpO1xuICAgICAgICAgICAgaWYgKHRvcGljKSB7XG4gICAgICAgICAgICAgIHN1Yi5fbm9Gb3J3YXJkaW5nID0gdHJ1ZTtcbiAgICAgICAgICAgICAgdG9waWMuX3Byb2Nlc3NNZXRhRGVzYyhzdWIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHVwZGF0ZUNvdW50Kys7XG5cbiAgICAgICAgaWYgKHRoaXMub25NZXRhU3ViKSB7XG4gICAgICAgICAgdGhpcy5vbk1ldGFTdWIoY29udCk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKHRoaXMub25TdWJzVXBkYXRlZCkge1xuICAgICAgICB0aGlzLm9uU3Vic1VwZGF0ZWQoT2JqZWN0LmtleXModGhpcy5fY29udGFjdHMpLCB1cGRhdGVDb3VudCk7XG4gICAgICB9XG4gICAgfSxcbiAgICBlbnVtZXJhYmxlOiB0cnVlLFxuICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICB3cml0YWJsZTogZmFsc2VcbiAgfSxcblxuICAvLyBDYWxsZWQgYnkgVGlub2RlIHdoZW4gbWV0YS5zdWIgaXMgcmVjaXZlZC5cbiAgX3Byb2Nlc3NNZXRhQ3JlZHM6IHtcbiAgICB2YWx1ZTogZnVuY3Rpb24oY3JlZHMsIHVwZCkge1xuICAgICAgaWYgKGNyZWRzLmxlbmd0aCA9PSAxICYmIGNyZWRzWzBdID09IFRpbm9kZS5ERUxfQ0hBUikge1xuICAgICAgICBjcmVkcyA9IFtdO1xuICAgICAgfVxuICAgICAgaWYgKHVwZCkge1xuICAgICAgICBjcmVkcy5tYXAoKGNyKSA9PiB7XG4gICAgICAgICAgaWYgKGNyLnZhbCkge1xuICAgICAgICAgICAgLy8gQWRkaW5nIGEgY3JlZGVudGlhbC5cbiAgICAgICAgICAgIGxldCBpZHggPSB0aGlzLl9jcmVkZW50aWFscy5maW5kSW5kZXgoKGVsKSA9PiB7XG4gICAgICAgICAgICAgIHJldHVybiBlbC5tZXRoID09IGNyLm1ldGggJiYgZWwudmFsID09IGNyLnZhbDtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgaWYgKGlkeCA8IDApIHtcbiAgICAgICAgICAgICAgLy8gTm90IGZvdW5kLlxuICAgICAgICAgICAgICBpZiAoIWNyLmRvbmUpIHtcbiAgICAgICAgICAgICAgICAvLyBVbmNvbmZpcm1lZCBjcmVkZW50aWFsIHJlcGxhY2VzIHByZXZpb3VzIHVuY29uZmlybWVkIGNyZWRlbnRpYWwgb2YgdGhlIHNhbWUgbWV0aG9kLlxuICAgICAgICAgICAgICAgIGlkeCA9IHRoaXMuX2NyZWRlbnRpYWxzLmZpbmRJbmRleCgoZWwpID0+IHtcbiAgICAgICAgICAgICAgICAgIHJldHVybiBlbC5tZXRoID09IGNyLm1ldGggJiYgIWVsLmRvbmU7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgaWYgKGlkeCA+PSAwKSB7XG4gICAgICAgICAgICAgICAgICAvLyBSZW1vdmUgcHJldmlvdXMgdW5jb25maXJtZWQgY3JlZGVudGlhbC5cbiAgICAgICAgICAgICAgICAgIHRoaXMuX2NyZWRlbnRpYWxzLnNwbGljZShpZHgsIDEpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB0aGlzLl9jcmVkZW50aWFscy5wdXNoKGNyKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIC8vIEZvdW5kLiBNYXliZSBjaGFuZ2UgJ2RvbmUnIHN0YXR1cy5cbiAgICAgICAgICAgICAgdGhpcy5fY3JlZGVudGlhbHNbaWR4XS5kb25lID0gY3IuZG9uZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGVsc2UgaWYgKGNyLnJlc3ApIHtcbiAgICAgICAgICAgIC8vIEhhbmRsZSBjcmVkZW50aWFsIGNvbmZpcm1hdGlvbi5cbiAgICAgICAgICAgIGNvbnN0IGlkeCA9IHRoaXMuX2NyZWRlbnRpYWxzLmZpbmRJbmRleCgoZWwpID0+IHtcbiAgICAgICAgICAgICAgcmV0dXJuIGVsLm1ldGggPT0gY3IubWV0aCAmJiAhZWwuZG9uZTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgaWYgKGlkeCA+PSAwKSB7XG4gICAgICAgICAgICAgIHRoaXMuX2NyZWRlbnRpYWxzW2lkeF0uZG9uZSA9IHRydWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuX2NyZWRlbnRpYWxzID0gY3JlZHM7XG4gICAgICB9XG4gICAgICBpZiAodGhpcy5vbkNyZWRzVXBkYXRlZCkge1xuICAgICAgICB0aGlzLm9uQ3JlZHNVcGRhdGVkKHRoaXMuX2NyZWRlbnRpYWxzKTtcbiAgICAgIH1cbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiBmYWxzZVxuICB9LFxuXG4gIC8vIFByb2Nlc3MgcHJlc2VuY2UgY2hhbmdlIG1lc3NhZ2VcbiAgX3JvdXRlUHJlczoge1xuICAgIHZhbHVlOiBmdW5jdGlvbihwcmVzKSB7XG4gICAgICBpZiAocHJlcy53aGF0ID09ICd0ZXJtJykge1xuICAgICAgICAvLyBUaGUgJ21lJyB0b3BpYyBpdHNlbGYgaXMgZGV0YWNoZWQuIE1hcmsgYXMgdW5zdWJzY3JpYmVkLlxuICAgICAgICB0aGlzLl9yZXNldFN1YigpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBjb250ID0gdGhpcy5fY29udGFjdHNbcHJlcy5zcmNdO1xuICAgICAgaWYgKGNvbnQpIHtcbiAgICAgICAgc3dpdGNoIChwcmVzLndoYXQpIHtcbiAgICAgICAgICBjYXNlICdvbic6IC8vIHRvcGljIGNhbWUgb25saW5lXG4gICAgICAgICAgICBjb250Lm9ubGluZSA9IHRydWU7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdvZmYnOiAvLyB0b3BpYyB3ZW50IG9mZmxpbmVcbiAgICAgICAgICAgIGlmIChjb250Lm9ubGluZSkge1xuICAgICAgICAgICAgICBjb250Lm9ubGluZSA9IGZhbHNlO1xuICAgICAgICAgICAgICBpZiAoY29udC5zZWVuKSB7XG4gICAgICAgICAgICAgICAgY29udC5zZWVuLndoZW4gPSBuZXcgRGF0ZSgpO1xuICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnQuc2VlbiA9IHtcbiAgICAgICAgICAgICAgICAgIHdoZW46IG5ldyBEYXRlKClcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdtc2cnOiAvLyBuZXcgbWVzc2FnZSByZWNlaXZlZFxuICAgICAgICAgICAgY29udC50b3VjaGVkID0gbmV3IERhdGUoKTtcbiAgICAgICAgICAgIGNvbnQuc2VxID0gcHJlcy5zZXEgfCAwO1xuICAgICAgICAgICAgY29udC51bnJlYWQgPSBjb250LnNlcSAtIGNvbnQucmVhZDtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ3VwZCc6IC8vIGRlc2MgdXBkYXRlZFxuICAgICAgICAgICAgLy8gUmVxdWVzdCB1cGRhdGVkIGRlc2NyaXB0aW9uXG4gICAgICAgICAgICB0aGlzLmdldE1ldGEodGhpcy5zdGFydE1ldGFRdWVyeSgpLndpdGhMYXRlck9uZVN1YihwcmVzLnNyYykuYnVpbGQoKSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdhY3MnOiAvLyBhY2Nlc3MgbW9kZSBjaGFuZ2VkXG4gICAgICAgICAgICBpZiAoY29udC5hY3MpIHtcbiAgICAgICAgICAgICAgY29udC5hY3MudXBkYXRlQWxsKHByZXMuZGFjcyk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICBjb250LmFjcyA9IG5ldyBBY2Nlc3NNb2RlKCkudXBkYXRlQWxsKHByZXMuZGFjcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb250LnRvdWNoZWQgPSBuZXcgRGF0ZSgpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAndWEnOiAvLyB1c2VyIGFnZW50IGNoYW5nZWRcbiAgICAgICAgICAgIGNvbnQuc2VlbiA9IHtcbiAgICAgICAgICAgICAgd2hlbjogbmV3IERhdGUoKSxcbiAgICAgICAgICAgICAgdWE6IHByZXMudWFcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdyZWN2JzogLy8gdXNlcidzIG90aGVyIHNlc3Npb24gbWFya2VkIHNvbWUgbWVzc2dlcyBhcyByZWNlaXZlZFxuICAgICAgICAgICAgY29udC5yZWN2ID0gY29udC5yZWN2ID8gTWF0aC5tYXgoY29udC5yZWN2LCBwcmVzLnNlcSkgOiAocHJlcy5zZXEgfCAwKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ3JlYWQnOiAvLyB1c2VyJ3Mgb3RoZXIgc2Vzc2lvbiBtYXJrZWQgc29tZSBtZXNzYWdlcyBhcyByZWFkXG4gICAgICAgICAgICBjb250LnJlYWQgPSBjb250LnJlYWQgPyBNYXRoLm1heChjb250LnJlYWQsIHByZXMuc2VxKSA6IChwcmVzLnNlcSB8IDApO1xuICAgICAgICAgICAgY29udC51bnJlYWQgPSBjb250LnNlcSAtIGNvbnQucmVhZDtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ2dvbmUnOiAvLyB0b3BpYyBkZWxldGVkIG9yIHVuc3Vic2NyaWJlZCBmcm9tXG4gICAgICAgICAgICBkZWxldGUgdGhpcy5fY29udGFjdHNbcHJlcy5zcmNdO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAnZGVsJzpcbiAgICAgICAgICAgIC8vIFVwZGF0ZSB0b3BpYy5kZWwgdmFsdWUuXG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgdGhpcy5fdGlub2RlLmxvZ2dlcihcIklORk86IFVuc3VwcG9ydGVkIHByZXNlbmNlIHVwZGF0ZSBpbiAnbWUnXCIsIHByZXMud2hhdCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodGhpcy5vbkNvbnRhY3RVcGRhdGUpIHtcbiAgICAgICAgICB0aGlzLm9uQ29udGFjdFVwZGF0ZShwcmVzLndoYXQsIGNvbnQpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAocHJlcy53aGF0ID09ICdhY3MnKSB7XG4gICAgICAgICAgLy8gTmV3IHN1YnNjcmlwdGlvbnMgYW5kIGRlbGV0ZWQvYmFubmVkIHN1YnNjcmlwdGlvbnMgaGF2ZSBmdWxsXG4gICAgICAgICAgLy8gYWNjZXNzIG1vZGUgKG5vICsgb3IgLSBpbiB0aGUgZGFjcyBzdHJpbmcpLiBDaGFuZ2VzIHRvIGtub3duIHN1YnNjcmlwdGlvbnMgYXJlIHNlbnQgYXNcbiAgICAgICAgICAvLyBkZWx0YXMsIGJ1dCB0aGV5IHNob3VsZCBub3QgaGFwcGVuIGhlcmUuXG4gICAgICAgICAgY29uc3QgYWNzID0gbmV3IEFjY2Vzc01vZGUocHJlcy5kYWNzKTtcbiAgICAgICAgICBpZiAoIWFjcyB8fCBhY3MubW9kZSA9PSBBY2Nlc3NNb2RlLl9JTlZBTElEKSB7XG4gICAgICAgICAgICB0aGlzLl90aW5vZGUubG9nZ2VyKFwiRVJST1I6IEludmFsaWQgYWNjZXNzIG1vZGUgdXBkYXRlXCIsIHByZXMuc3JjLCBwcmVzLmRhY3MpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgIH0gZWxzZSBpZiAoYWNzLm1vZGUgPT0gQWNjZXNzTW9kZS5fTk9ORSkge1xuICAgICAgICAgICAgdGhpcy5fdGlub2RlLmxvZ2dlcihcIldBUk5JTkc6IFJlbW92aW5nIG5vbi1leGlzdGVudCBzdWJzY3JpcHRpb25cIiwgcHJlcy5zcmMsIHByZXMuZGFjcyk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8vIE5ldyBzdWJzY3JpcHRpb24uIFNlbmQgcmVxdWVzdCBmb3IgdGhlIGZ1bGwgZGVzY3JpcHRpb24uXG4gICAgICAgICAgICAvLyBVc2luZyAud2l0aE9uZVN1YiAobm90IC53aXRoTGF0ZXJPbmVTdWIpIHRvIG1ha2Ugc3VyZSBJZk1vZGlmaWVkU2luY2UgaXMgbm90IHNldC5cbiAgICAgICAgICAgIHRoaXMuZ2V0TWV0YSh0aGlzLnN0YXJ0TWV0YVF1ZXJ5KCkud2l0aE9uZVN1Yih1bmRlZmluZWQsIHByZXMuc3JjKS5idWlsZCgpKTtcbiAgICAgICAgICAgIC8vIENyZWF0ZSBhIGR1bW15IGVudHJ5IHRvIGNhdGNoIG9ubGluZSBzdGF0dXMgdXBkYXRlLlxuICAgICAgICAgICAgdGhpcy5fY29udGFjdHNbcHJlcy5zcmNdID0ge1xuICAgICAgICAgICAgICB0b3VjaGVkOiBuZXcgRGF0ZSgpLFxuICAgICAgICAgICAgICB0b3BpYzogcHJlcy5zcmMsXG4gICAgICAgICAgICAgIG9ubGluZTogZmFsc2UsXG4gICAgICAgICAgICAgIGFjczogYWNzXG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChwcmVzLndoYXQgPT0gJ3RhZ3MnKSB7XG4gICAgICAgICAgdGhpcy5nZXRNZXRhKHRoaXMuc3RhcnRNZXRhUXVlcnkoKS53aXRoVGFncygpLmJ1aWxkKCkpO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLm9uUHJlcykge1xuICAgICAgICB0aGlzLm9uUHJlcyhwcmVzKTtcbiAgICAgIH1cbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiBmYWxzZVxuICB9LFxuXG4gIC8qKlxuICAgKiBQdWJsaXNoaW5nIHRvIFRvcGljTWUgaXMgbm90IHN1cHBvcnRlZC4ge0BsaW5rIFRvcGljI3B1Ymxpc2h9IGlzIG92ZXJyaWRlbiBhbmQgdGhvd3MgYW4ge0Vycm9yfSBpZiBjYWxsZWQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWNNZSNcbiAgICogQHRocm93cyB7RXJyb3J9IEFsd2F5cyB0aHJvd3MgYW4gZXJyb3IuXG4gICAqL1xuICBwdWJsaXNoOiB7XG4gICAgdmFsdWU6IGZ1bmN0aW9uKCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KG5ldyBFcnJvcihcIlB1Ymxpc2hpbmcgdG8gJ21lJyBpcyBub3Qgc3VwcG9ydGVkXCIpKTtcbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiBmYWxzZVxuICB9LFxuXG4gIC8qKlxuICAgKiBEZWxldGUgdmFsaWRhdGlvbiBjcmVkZW50aWFsLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljTWUjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b3BpYyAtIE5hbWUgb2YgdGhlIHRvcGljIHRvIGRlbGV0ZVxuICAgKiBAcGFyYW0ge1N0cmluZ30gdXNlciAtIFVzZXIgSUQgdG8gcmVtb3ZlLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gUHJvbWlzZSB3aGljaCB3aWxsIGJlIHJlc29sdmVkL3JlamVjdGVkIG9uIHJlY2VpdmluZyBzZXJ2ZXIgcmVwbHkuXG4gICAqL1xuICBkZWxDcmVkZW50aWFsOiB7XG4gICAgdmFsdWU6IGZ1bmN0aW9uKG1ldGhvZCwgdmFsdWUpIHtcbiAgICAgIGlmICghdGhpcy5fc3Vic2NyaWJlZCkge1xuICAgICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEVycm9yKFwiQ2Fubm90IGRlbGV0ZSBjcmVkZW50aWFsIGluIGluYWN0aXZlICdtZScgdG9waWNcIikpO1xuICAgICAgfVxuICAgICAgLy8gU2VuZCB7ZGVsfSBtZXNzYWdlLCByZXR1cm4gcHJvbWlzZVxuICAgICAgcmV0dXJuIHRoaXMuX3Rpbm9kZS5kZWxDcmVkZW50aWFsKHRoaXMubmFtZSwgbWV0aG9kLCB2YWx1ZSkudGhlbigoY3RybCkgPT4ge1xuICAgICAgICAvLyBSZW1vdmUgZGVsZXRlZCBjcmVkZW50aWFsIGZyb20gdGhlIGNhY2hlLlxuICAgICAgICBjb25zdCBpbmRleCA9IHRoaXMuX2NyZWRlbnRpYWxzLmZpbmRJbmRleCgoZWwpID0+IHtcbiAgICAgICAgICByZXR1cm4gZWwubWV0aCA9PSBtZXRob2QgJiYgZWwudmFsID09IHZhbHVlO1xuICAgICAgICB9KTtcbiAgICAgICAgaWYgKGluZGV4ID4gLTEpIHtcbiAgICAgICAgICB0aGlzLl9jcmVkZW50aWFscy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB9XG4gICAgICAgIC8vIE5vdGlmeSBsaXN0ZW5lcnNcbiAgICAgICAgaWYgKHRoaXMub25DcmVkc1VwZGF0ZWQpIHtcbiAgICAgICAgICB0aGlzLm9uQ3JlZHNVcGRhdGVkKHRoaXMuX2NyZWRlbnRpYWxzKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gY3RybDtcbiAgICAgIH0pO1xuXG4gICAgfSxcbiAgICBlbnVtZXJhYmxlOiB0cnVlLFxuICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICB3cml0YWJsZTogZmFsc2VcbiAgfSxcblxuICAvKipcbiAgICogSXRlcmF0ZSBvdmVyIGNhY2hlZCBjb250YWN0cy4gSWYgY2FsbGJhY2sgaXMgdW5kZWZpbmVkLCB1c2Uge0BsaW5rIHRoaXMub25NZXRhU3VifS5cbiAgICogQGZ1bmN0aW9uXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWNNZSNcbiAgICogQHBhcmFtIHtUb3BpY01lLkNvbnRhY3RDYWxsYmFjaz19IGNhbGxiYWNrIC0gQ2FsbGJhY2sgdG8gY2FsbCBmb3IgZWFjaCBjb250YWN0LlxuICAgKiBAcGFyYW0ge2Jvb2xlYW49fSBpbmNsdWRlQmFubmVkIC0gSW5jbHVkZSBiYW5uZWQgY29udGFjdHMuXG4gICAqIEBwYXJhbSB7T2JqZWN0PX0gY29udGV4dCAtIENvbnRleHQgdG8gdXNlIGZvciBjYWxsaW5nIHRoZSBgY2FsbGJhY2tgLCBpLmUuIHRoZSB2YWx1ZSBvZiBgdGhpc2AgaW5zaWRlIHRoZSBjYWxsYmFjay5cbiAgICovXG4gIGNvbnRhY3RzOiB7XG4gICAgdmFsdWU6IGZ1bmN0aW9uKGNhbGxiYWNrLCBpbmNsdWRlQmFubmVkLCBjb250ZXh0KSB7XG4gICAgICBjb25zdCBjYiA9IChjYWxsYmFjayB8fCB0aGlzLm9uTWV0YVN1Yik7XG4gICAgICBpZiAoY2IpIHtcbiAgICAgICAgZm9yIChsZXQgaWR4IGluIHRoaXMuX2NvbnRhY3RzKSB7XG4gICAgICAgICAgaWYgKCFpbmNsdWRlQmFubmVkICYmXG4gICAgICAgICAgICAoIXRoaXMuX2NvbnRhY3RzW2lkeF0gfHxcbiAgICAgICAgICAgICAgIXRoaXMuX2NvbnRhY3RzW2lkeF0uYWNzIHx8XG4gICAgICAgICAgICAgICF0aGlzLl9jb250YWN0c1tpZHhdLmFjcy5pc0pvaW5lcigpKSkge1xuXG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICB9XG4gICAgICAgICAgY2IuY2FsbChjb250ZXh0LCB0aGlzLl9jb250YWN0c1tpZHhdLCBpZHgsIHRoaXMuX2NvbnRhY3RzKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgd3JpdGFibGU6IHRydWVcbiAgfSxcblxuICAvKipcbiAgICogVXBkYXRlIGEgY2FjaGVkIGNvbnRhY3Qgd2l0aCBuZXcgcmVhZC9yZWNlaXZlZC9tZXNzYWdlIGNvdW50LlxuICAgKiBAZnVuY3Rpb25cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpY01lI1xuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gY29udGFjdE5hbWUgLSBVSUQgb2YgY29udGFjdCB0byB1cGRhdGUuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSB3aGF0IC0gV2hhY2ggY291bnQgdG8gdXBkYXRlLCBvbmUgb2YgPHR0PlwicmVhZFwiLCBcInJlY3ZcIiwgXCJtc2dcIjwvdHQ+XG4gICAqIEBwYXJhbSB7TnVtYmVyfSBzZXEgLSBOZXcgdmFsdWUgb2YgdGhlIGNvdW50LlxuICAgKiBAcGFyYW0ge0RhdGV9IHRzIC0gVGltZXN0YW1wIG9mIHRoZSB1cGRhdGUuXG4gICAqL1xuICBzZXRNc2dSZWFkUmVjdjoge1xuICAgIHZhbHVlOiBmdW5jdGlvbihjb250YWN0TmFtZSwgd2hhdCwgc2VxLCB0cykge1xuICAgICAgY29uc3QgY29udCA9IHRoaXMuX2NvbnRhY3RzW2NvbnRhY3ROYW1lXTtcbiAgICAgIGxldCBvbGRWYWwsIGRvVXBkYXRlID0gZmFsc2U7XG4gICAgICBsZXQgbW9kZSA9IG51bGw7XG4gICAgICBpZiAoY29udCkge1xuICAgICAgICBzZXEgPSBzZXEgfCAwO1xuICAgICAgICBjb250LnNlcSA9IGNvbnQuc2VxIHwgMDtcbiAgICAgICAgY29udC5yZWFkID0gY29udC5yZWFkIHwgMDtcbiAgICAgICAgY29udC5yZWN2ID0gY29udC5yZWN2IHwgMDtcbiAgICAgICAgc3dpdGNoICh3aGF0KSB7XG4gICAgICAgICAgY2FzZSAncmVjdic6XG4gICAgICAgICAgICBvbGRWYWwgPSBjb250LnJlY3Y7XG4gICAgICAgICAgICBjb250LnJlY3YgPSBNYXRoLm1heChjb250LnJlY3YsIHNlcSk7XG4gICAgICAgICAgICBkb1VwZGF0ZSA9IChvbGRWYWwgIT0gY29udC5yZWN2KTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ3JlYWQnOlxuICAgICAgICAgICAgb2xkVmFsID0gY29udC5yZWFkO1xuICAgICAgICAgICAgY29udC5yZWFkID0gTWF0aC5tYXgoY29udC5yZWFkLCBzZXEpO1xuICAgICAgICAgICAgZG9VcGRhdGUgPSAob2xkVmFsICE9IGNvbnQucmVhZCk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdtc2cnOlxuICAgICAgICAgICAgb2xkVmFsID0gY29udC5zZXE7XG4gICAgICAgICAgICBjb250LnNlcSA9IE1hdGgubWF4KGNvbnQuc2VxLCBzZXEpO1xuICAgICAgICAgICAgaWYgKCFjb250LnRvdWNoZWQgfHwgY29udC50b3VjaGVkIDwgdHMpIHtcbiAgICAgICAgICAgICAgY29udC50b3VjaGVkID0gdHM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBkb1VwZGF0ZSA9IChvbGRWYWwgIT0gY29udC5zZXEpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBTYW5pdHkgY2hlY2tzLlxuICAgICAgICBpZiAoY29udC5yZWN2IDwgY29udC5yZWFkKSB7XG4gICAgICAgICAgY29udC5yZWN2ID0gY29udC5yZWFkO1xuICAgICAgICAgIGRvVXBkYXRlID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoY29udC5zZXEgPCBjb250LnJlY3YpIHtcbiAgICAgICAgICBjb250LnNlcSA9IGNvbnQucmVjdjtcbiAgICAgICAgICBpZiAoIWNvbnQudG91Y2hlZCB8fCBjb250LnRvdWNoZWQgPCB0cykge1xuICAgICAgICAgICAgY29udC50b3VjaGVkID0gdHM7XG4gICAgICAgICAgfVxuICAgICAgICAgIGRvVXBkYXRlID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICBjb250LnVucmVhZCA9IGNvbnQuc2VxIC0gY29udC5yZWFkO1xuXG4gICAgICAgIGlmIChkb1VwZGF0ZSAmJiAoIWNvbnQuYWNzIHx8ICFjb250LmFjcy5pc011dGVkKCkpICYmIHRoaXMub25Db250YWN0VXBkYXRlKSB7XG4gICAgICAgICAgdGhpcy5vbkNvbnRhY3RVcGRhdGUod2hhdCwgY29udCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiB0cnVlXG4gIH0sXG5cbiAgLyoqXG4gICAqIEdldCBhIGNvbnRhY3QgZnJvbSBjYWNoZS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpY01lI1xuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gbmFtZSAtIE5hbWUgb2YgdGhlIGNvbnRhY3QgdG8gZ2V0LCBlaXRoZXIgYSBVSUQgKGZvciBwMnAgdG9waWNzKSBvciBhIHRvcGljIG5hbWUuXG4gICAqIEByZXR1cm5zIHtUaW5vZGUuQ29udGFjdH0gLSBDb250YWN0IG9yIGB1bmRlZmluZWRgLlxuICAgKi9cbiAgZ2V0Q29udGFjdDoge1xuICAgIHZhbHVlOiBmdW5jdGlvbihuYW1lKSB7XG4gICAgICByZXR1cm4gdGhpcy5fY29udGFjdHNbbmFtZV07XG4gICAgfSxcbiAgICBlbnVtZXJhYmxlOiB0cnVlLFxuICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICB3cml0YWJsZTogdHJ1ZVxuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgYWNjZXNzIG1vZGUgb2YgYSBnaXZlbiBjb250YWN0IGZyb20gY2FjaGUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWNNZSNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBjb250YWN0IHRvIGdldCBhY2Nlc3MgbW9kZSBmb3IsIGVpdGhlciBhIFVJRCAoZm9yIHAycCB0b3BpY3MpIG9yIGEgdG9waWMgbmFtZS5cbiAgICogQHJldHVybnMge3N0cmluZ30gLSBhY2Nlc3MgbW9kZSwgc3VjaCBhcyBgUldQYC5cbiAgICovXG4gIGdldEFjY2Vzc01vZGU6IHtcbiAgICB2YWx1ZTogZnVuY3Rpb24obmFtZSkge1xuICAgICAgY29uc3QgY29udCA9IHRoaXMuX2NvbnRhY3RzW25hbWVdO1xuICAgICAgcmV0dXJuIGNvbnQgPyBjb250LmFjcyA6IG51bGw7XG4gICAgfSxcbiAgICBlbnVtZXJhYmxlOiB0cnVlLFxuICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICB3cml0YWJsZTogdHJ1ZVxuICB9LFxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBjb250YWN0IGlzIGFyY2hpdmVkLCBpLmUuIGNvbnRhY3QucHJpdmF0ZS5hcmNoID09IHRydWUuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWNNZSNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBjb250YWN0IHRvIGNoZWNrIGFyY2hpdmVkIHN0YXR1cywgZWl0aGVyIGEgVUlEIChmb3IgcDJwIHRvcGljcykgb3IgYSB0b3BpYyBuYW1lLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSB0cnVlIGlmIGNvbnRhY3QgaXMgYXJjaGl2ZWQsIGZhbHNlIG90aGVyd2lzZS5cbiAgICovXG4gIGlzQXJjaGl2ZWQ6IHtcbiAgICB2YWx1ZTogZnVuY3Rpb24obmFtZSkge1xuICAgICAgY29uc3QgY29udCA9IHRoaXMuX2NvbnRhY3RzW25hbWVdO1xuICAgICAgcmV0dXJuIGNvbnQgPyAoKGNvbnQucHJpdmF0ZSAmJiBjb250LnByaXZhdGUuYXJjaCkgPyB0cnVlIDogZmFsc2UpIDogbnVsbDtcbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiB0cnVlXG4gIH0sXG5cbiAgLyoqXG4gICAqIEB0eXBlZGVmIFRpbm9kZS5DcmVkZW50aWFsXG4gICAqIEBtZW1iZXJvZiBUaW5vZGVcbiAgICogQHR5cGUgT2JqZWN0XG4gICAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBtZXRoIC0gdmFsaWRhdGlvbiBtZXRob2Qgc3VjaCBhcyAnZW1haWwnIG9yICd0ZWwnLlxuICAgKiBAcHJvcGVydHkge3N0cmluZ30gdmFsIC0gY3JlZGVudGlhbCB2YWx1ZSwgaS5lLiAnamRvZUBleGFtcGxlLmNvbScgb3IgJysxNzAyNTU1MTIzNCdcbiAgICogQHByb3BlcnR5IHtib29sZWFufSBkb25lIC0gdHJ1ZSBpZiBjcmVkZW50aWFsIGlzIHZhbGlkYXRlZC5cbiAgICovXG4gIC8qKlxuICAgKiBHZXQgdGhlIHVzZXIncyBjcmVkZW50aWFsczogZW1haWwsIHBob25lLCBldGMuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuVG9waWNNZSNcbiAgICpcbiAgICogQHJldHVybnMge1Rpbm9kZS5DcmVkZW50aWFsW119IC0gYXJyYXkgb2YgY3JlZGVudGlhbHMuXG4gICAqL1xuICBnZXRDcmVkZW50aWFsczoge1xuICAgIHZhbHVlOiBmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiB0aGlzLl9jcmVkZW50aWFscztcbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiB0cnVlXG4gIH1cbn0pO1xuVG9waWNNZS5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBUb3BpY01lO1xuXG4vKipcbiAqIEBjbGFzcyBUb3BpY0ZuZCAtIHNwZWNpYWwgY2FzZSBvZiB7QGxpbmsgVGlub2RlLlRvcGljfSBmb3Igc2VhcmNoaW5nIGZvclxuICogY29udGFjdHMgYW5kIGdyb3VwIHRvcGljcy5cbiAqIEBleHRlbmRzIFRpbm9kZS5Ub3BpY1xuICogQG1lbWJlcm9mIFRpbm9kZVxuICpcbiAqIEBwYXJhbSB7VG9waWNGbmQuQ2FsbGJhY2tzfSBjYWxsYmFja3MgLSBDYWxsYmFja3MgdG8gcmVjZWl2ZSB2YXJpb3VzIGV2ZW50cy5cbiAqL1xudmFyIFRvcGljRm5kID0gZnVuY3Rpb24oY2FsbGJhY2tzKSB7XG4gIFRvcGljLmNhbGwodGhpcywgVE9QSUNfRk5ELCBjYWxsYmFja3MpO1xuICAvLyBMaXN0IG9mIHVzZXJzIGFuZCB0b3BpY3MgdWlkIG9yIHRvcGljX25hbWUgLT4gQ29udGFjdCBvYmplY3QpXG4gIHRoaXMuX2NvbnRhY3RzID0ge307XG59O1xuXG4vLyBJbmhlcml0IGV2ZXJ5dGluZyBmcm9tIHRoZSBnZW5lcmljIFRvcGljXG5Ub3BpY0ZuZC5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKFRvcGljLnByb3RvdHlwZSwge1xuICAvLyBPdmVycmlkZSB0aGUgb3JpZ2luYWwgVG9waWMuX3Byb2Nlc3NNZXRhU3ViXG4gIF9wcm9jZXNzTWV0YVN1Yjoge1xuICAgIHZhbHVlOiBmdW5jdGlvbihzdWJzKSB7XG4gICAgICBsZXQgdXBkYXRlQ291bnQgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyh0aGlzLl9jb250YWN0cykubGVuZ3RoO1xuICAgICAgLy8gUmVzZXQgY29udGFjdCBsaXN0LlxuICAgICAgdGhpcy5fY29udGFjdHMgPSB7fTtcbiAgICAgIGZvciAobGV0IGlkeCBpbiBzdWJzKSB7XG4gICAgICAgIGxldCBzdWIgPSBzdWJzW2lkeF07XG4gICAgICAgIGNvbnN0IGluZGV4QnkgPSBzdWIudG9waWMgPyBzdWIudG9waWMgOiBzdWIudXNlcjtcblxuICAgICAgICBzdWIudXBkYXRlZCA9IG5ldyBEYXRlKHN1Yi51cGRhdGVkKTtcbiAgICAgICAgaWYgKHN1Yi5zZWVuICYmIHN1Yi5zZWVuLndoZW4pIHtcbiAgICAgICAgICBzdWIuc2Vlbi53aGVuID0gbmV3IERhdGUoc3ViLnNlZW4ud2hlbik7XG4gICAgICAgIH1cblxuICAgICAgICBzdWIgPSBtZXJnZVRvQ2FjaGUodGhpcy5fY29udGFjdHMsIGluZGV4QnksIHN1Yik7XG4gICAgICAgIHVwZGF0ZUNvdW50Kys7XG5cbiAgICAgICAgaWYgKHRoaXMub25NZXRhU3ViKSB7XG4gICAgICAgICAgdGhpcy5vbk1ldGFTdWIoc3ViKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBpZiAodXBkYXRlQ291bnQgPiAwICYmIHRoaXMub25TdWJzVXBkYXRlZCkge1xuICAgICAgICB0aGlzLm9uU3Vic1VwZGF0ZWQoT2JqZWN0LmtleXModGhpcy5fY29udGFjdHMpKTtcbiAgICAgIH1cbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiBmYWxzZVxuICB9LFxuXG4gIC8qKlxuICAgKiBQdWJsaXNoaW5nIHRvIFRvcGljRm5kIGlzIG5vdCBzdXBwb3J0ZWQuIHtAbGluayBUb3BpYyNwdWJsaXNofSBpcyBvdmVycmlkZW4gYW5kIHRob3dzIGFuIHtFcnJvcn0gaWYgY2FsbGVkLlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLlRvcGljRm5kI1xuICAgKiBAdGhyb3dzIHtFcnJvcn0gQWx3YXlzIHRocm93cyBhbiBlcnJvci5cbiAgICovXG4gIHB1Ymxpc2g6IHtcbiAgICB2YWx1ZTogZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEVycm9yKFwiUHVibGlzaGluZyB0byAnZm5kJyBpcyBub3Qgc3VwcG9ydGVkXCIpKTtcbiAgICB9LFxuICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiBmYWxzZVxuICB9LFxuXG4gIC8qKlxuICAgKiBzZXRNZXRhIHRvIFRvcGljRm5kIHJlc2V0cyBjb250YWN0IGxpc3QgaW4gYWRkaXRpb24gdG8gc2VuZGluZyB0aGUgbWVzc2FnZS5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpY0ZuZCNcbiAgICogQHBhcmFtIHtUaW5vZGUuU2V0UGFyYW1zfSBwYXJhbXMgcGFyYW1ldGVycyB0byB1cGRhdGUuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBQcm9taXNlIHRvIGJlIHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIHNlcnZlciByZXNwb25kcyB0byByZXF1ZXN0LlxuICAgKi9cbiAgc2V0TWV0YToge1xuICAgIHZhbHVlOiBmdW5jdGlvbihwYXJhbXMpIHtcbiAgICAgIGNvbnN0IGluc3RhbmNlID0gdGhpcztcbiAgICAgIHJldHVybiBPYmplY3QuZ2V0UHJvdG90eXBlT2YoVG9waWNGbmQucHJvdG90eXBlKS5zZXRNZXRhLmNhbGwodGhpcywgcGFyYW1zKS50aGVuKGZ1bmN0aW9uKCkge1xuICAgICAgICBpZiAoT2JqZWN0LmtleXMoaW5zdGFuY2UuX2NvbnRhY3RzKS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgaW5zdGFuY2UuX2NvbnRhY3RzID0ge307XG4gICAgICAgICAgaWYgKGluc3RhbmNlLm9uU3Vic1VwZGF0ZWQpIHtcbiAgICAgICAgICAgIGluc3RhbmNlLm9uU3Vic1VwZGF0ZWQoW10pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSxcbiAgICBlbnVtZXJhYmxlOiB0cnVlLFxuICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICB3cml0YWJsZTogZmFsc2VcbiAgfSxcblxuICAvKipcbiAgICogSXRlcmF0ZSBvdmVyIGZvdW5kIGNvbnRhY3RzLiBJZiBjYWxsYmFjayBpcyB1bmRlZmluZWQsIHVzZSB7QGxpbmsgdGhpcy5vbk1ldGFTdWJ9LlxuICAgKiBAZnVuY3Rpb25cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5Ub3BpY0ZuZCNcbiAgICogQHBhcmFtIHtUb3BpY0ZuZC5Db250YWN0Q2FsbGJhY2t9IGNhbGxiYWNrIC0gQ2FsbGJhY2sgdG8gY2FsbCBmb3IgZWFjaCBjb250YWN0LlxuICAgKiBAcGFyYW0ge09iamVjdH0gY29udGV4dCAtIENvbnRleHQgdG8gdXNlIGZvciBjYWxsaW5nIHRoZSBgY2FsbGJhY2tgLCBpLmUuIHRoZSB2YWx1ZSBvZiBgdGhpc2AgaW5zaWRlIHRoZSBjYWxsYmFjay5cbiAgICovXG4gIGNvbnRhY3RzOiB7XG4gICAgdmFsdWU6IGZ1bmN0aW9uKGNhbGxiYWNrLCBjb250ZXh0KSB7XG4gICAgICBjb25zdCBjYiA9IChjYWxsYmFjayB8fCB0aGlzLm9uTWV0YVN1Yik7XG4gICAgICBpZiAoY2IpIHtcbiAgICAgICAgZm9yIChsZXQgaWR4IGluIHRoaXMuX2NvbnRhY3RzKSB7XG4gICAgICAgICAgY2IuY2FsbChjb250ZXh0LCB0aGlzLl9jb250YWN0c1tpZHhdLCBpZHgsIHRoaXMuX2NvbnRhY3RzKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgd3JpdGFibGU6IHRydWVcbiAgfVxufSk7XG5Ub3BpY0ZuZC5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBUb3BpY0ZuZDtcblxuLyoqXG4gKiBAY2xhc3MgTGFyZ2VGaWxlSGVscGVyIC0gY29sbGVjdGlvbiBvZiB1dGlsaXRpZXMgZm9yIHVwbG9hZGluZyBhbmQgZG93bmxvYWRpbmcgZmlsZXNcbiAqIG91dCBvZiBiYW5kLiBEb24ndCBpbnN0YW50aWF0ZSB0aGlzIGNsYXNzIGRpcmVjdGx5LiBVc2Uge1Rpbm9kZS5nZXRMYXJnZUZpbGVIZWxwZXJ9IGluc3RlYWQuXG4gKiBAbWVtYmVyb2YgVGlub2RlXG4gKlxuICogQHBhcmFtIHtUaW5vZGV9IHRpbm9kZSAtIHRoZSBtYWluIFRpbm9kZSBvYmplY3QuXG4gKi9cbnZhciBMYXJnZUZpbGVIZWxwZXIgPSBmdW5jdGlvbih0aW5vZGUpIHtcbiAgdGhpcy5fdGlub2RlID0gdGlub2RlO1xuXG4gIHRoaXMuX2FwaUtleSA9IHRpbm9kZS5fYXBpS2V5O1xuICB0aGlzLl9hdXRoVG9rZW4gPSB0aW5vZGUuZ2V0QXV0aFRva2VuKCk7XG4gIHRoaXMuX21zZ0lkID0gdGlub2RlLmdldE5leHRVbmlxdWVJZCgpO1xuICB0aGlzLnhociA9IHhkcmVxKCk7XG5cbiAgLy8gUHJvbWlzZVxuICB0aGlzLnRvUmVzb2x2ZSA9IG51bGw7XG4gIHRoaXMudG9SZWplY3QgPSBudWxsO1xuXG4gIC8vIENhbGxiYWNrc1xuICB0aGlzLm9uUHJvZ3Jlc3MgPSBudWxsO1xuICB0aGlzLm9uU3VjY2VzcyA9IG51bGw7XG4gIHRoaXMub25GYWlsdXJlID0gbnVsbDtcbn1cblxuTGFyZ2VGaWxlSGVscGVyLnByb3RvdHlwZSA9IHtcbiAgLyoqXG4gICAqIFN0YXJ0IHVwbG9hZGluZyB0aGUgZmlsZSB0byBhIG5vbi1kZWZhdWx0IGVuZHBvaW50LlxuICAgKlxuICAgKiBAbWVtYmVyb2YgVGlub2RlLkxhcmdlRmlsZUhlbHBlciNcbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IGJhc2VVcmwgYWx0ZXJuYXRpdmUgYmFzZSBVUkwgb2YgdXBsb2FkIHNlcnZlci5cbiAgICogQHBhcmFtIHtGaWxlfSBmaWxlIHRvIHVwbG9hZFxuICAgKiBAcGFyYW0ge0NhbGxiYWNrfSBvblByb2dyZXNzIGNhbGxiYWNrLiBUYWtlcyBvbmUge2Zsb2F0fSBwYXJhbWV0ZXIgMC4uMVxuICAgKiBAcGFyYW0ge0NhbGxiYWNrfSBvblN1Y2Nlc3MgY2FsbGJhY2suIENhbGxlZCB3aGVuIHRoZSBmaWxlIGlzIHN1Y2Nlc3NmdWxseSB1cGxvYWRlZC5cbiAgICogQHBhcmFtIHtDYWxsYmFja30gb25GYWlsdXJlIGNhbGxiYWNrLiBDYWxsZWQgaW4gY2FzZSBvZiBhIGZhaWx1cmUuXG4gICAqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSByZXNvbHZlZC9yZWplY3RlZCB3aGVuIHRoZSB1cGxvYWQgaXMgY29tcGxldGVkL2ZhaWxlZC5cbiAgICovXG4gIHVwbG9hZFdpdGhCYXNlVXJsOiBmdW5jdGlvbihiYXNlVXJsLCBmaWxlLCBvblByb2dyZXNzLCBvblN1Y2Nlc3MsIG9uRmFpbHVyZSkge1xuICAgIGlmICghdGhpcy5fYXV0aFRva2VuKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJNdXN0IGF1dGhlbnRpY2F0ZSBmaXJzdFwiKTtcbiAgICB9XG4gICAgY29uc3QgaW5zdGFuY2UgPSB0aGlzO1xuXG4gICAgbGV0IHVybCA9ICcvdicgKyBQUk9UT0NPTF9WRVJTSU9OICsgJy9maWxlL3UvJztcbiAgICBpZiAoYmFzZVVybCkge1xuICAgICAgaWYgKGJhc2VVcmwuaW5kZXhPZignaHR0cDovLycpID09IDAgfHwgYmFzZVVybC5pbmRleE9mKCdodHRwczovLycpID09IDApIHtcbiAgICAgICAgdXJsID0gYmFzZVVybCArIHVybDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkludmFsaWQgYmFzZSBVUkwgJ1wiICsgYmFzZVVybCArIFwiJ1wiKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy54aHIub3BlbignUE9TVCcsIHVybCwgdHJ1ZSk7XG4gICAgdGhpcy54aHIuc2V0UmVxdWVzdEhlYWRlcignWC1UaW5vZGUtQVBJS2V5JywgdGhpcy5fYXBpS2V5KTtcbiAgICB0aGlzLnhoci5zZXRSZXF1ZXN0SGVhZGVyKCdYLVRpbm9kZS1BdXRoJywgJ1Rva2VuICcgKyB0aGlzLl9hdXRoVG9rZW4udG9rZW4pO1xuICAgIGNvbnN0IHJlc3VsdCA9IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIHRoaXMudG9SZXNvbHZlID0gcmVzb2x2ZTtcbiAgICAgIHRoaXMudG9SZWplY3QgPSByZWplY3Q7XG4gICAgfSk7XG5cbiAgICB0aGlzLm9uUHJvZ3Jlc3MgPSBvblByb2dyZXNzO1xuICAgIHRoaXMub25TdWNjZXNzID0gb25TdWNjZXNzO1xuICAgIHRoaXMub25GYWlsdXJlID0gb25GYWlsdXJlO1xuXG4gICAgdGhpcy54aHIudXBsb2FkLm9ucHJvZ3Jlc3MgPSBmdW5jdGlvbihlKSB7XG4gICAgICBpZiAoZS5sZW5ndGhDb21wdXRhYmxlICYmIGluc3RhbmNlLm9uUHJvZ3Jlc3MpIHtcbiAgICAgICAgaW5zdGFuY2Uub25Qcm9ncmVzcyhlLmxvYWRlZCAvIGUudG90YWwpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHRoaXMueGhyLm9ubG9hZCA9IGZ1bmN0aW9uKCkge1xuICAgICAgbGV0IHBrdDtcbiAgICAgIHRyeSB7XG4gICAgICAgIHBrdCA9IEpTT04ucGFyc2UodGhpcy5yZXNwb25zZSwganNvblBhcnNlSGVscGVyKTtcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBpbnN0YW5jZS5fdGlub2RlLmxvZ2dlcihcIkVSUk9SOiBJbnZhbGlkIHNlcnZlciByZXNwb25zZSBpbiBMYXJnZUZpbGVIZWxwZXJcIiwgdGhpcy5yZXNwb25zZSk7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLnN0YXR1cyA+PSAyMDAgJiYgdGhpcy5zdGF0dXMgPCAzMDApIHtcbiAgICAgICAgaWYgKGluc3RhbmNlLnRvUmVzb2x2ZSkge1xuICAgICAgICAgIGluc3RhbmNlLnRvUmVzb2x2ZShwa3QuY3RybC5wYXJhbXMudXJsKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoaW5zdGFuY2Uub25TdWNjZXNzKSB7XG4gICAgICAgICAgaW5zdGFuY2Uub25TdWNjZXNzKHBrdC5jdHJsKTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmICh0aGlzLnN0YXR1cyA+PSA0MDApIHtcbiAgICAgICAgaWYgKGluc3RhbmNlLnRvUmVqZWN0KSB7XG4gICAgICAgICAgaW5zdGFuY2UudG9SZWplY3QobmV3IEVycm9yKHBrdC5jdHJsLnRleHQgKyBcIiAoXCIgKyBwa3QuY3RybC5jb2RlICsgXCIpXCIpKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoaW5zdGFuY2Uub25GYWlsdXJlKSB7XG4gICAgICAgICAgaW5zdGFuY2Uub25GYWlsdXJlKHBrdC5jdHJsKVxuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpbnN0YW5jZS5fdGlub2RlLmxvZ2dlcihcIkVSUk9SOiBVbmV4cGVjdGVkIHNlcnZlciByZXNwb25zZSBzdGF0dXNcIiwgdGhpcy5zdGF0dXMsIHRoaXMucmVzcG9uc2UpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICB0aGlzLnhoci5vbmVycm9yID0gZnVuY3Rpb24oZSkge1xuICAgICAgaWYgKGluc3RhbmNlLnRvUmVqZWN0KSB7XG4gICAgICAgIGluc3RhbmNlLnRvUmVqZWN0KG5ldyBFcnJvcihcImZhaWxlZFwiKSk7XG4gICAgICB9XG4gICAgICBpZiAoaW5zdGFuY2Uub25GYWlsdXJlKSB7XG4gICAgICAgIGluc3RhbmNlLm9uRmFpbHVyZShudWxsKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gICAgdGhpcy54aHIub25hYm9ydCA9IGZ1bmN0aW9uKGUpIHtcbiAgICAgIGlmIChpbnN0YW5jZS50b1JlamVjdCkge1xuICAgICAgICBpbnN0YW5jZS50b1JlamVjdChuZXcgRXJyb3IoXCJ1cGxvYWQgY2FuY2VsbGVkIGJ5IHVzZXJcIikpO1xuICAgICAgfVxuICAgICAgaWYgKGluc3RhbmNlLm9uRmFpbHVyZSkge1xuICAgICAgICBpbnN0YW5jZS5vbkZhaWx1cmUobnVsbCk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIHRyeSB7XG4gICAgICBjb25zdCBmb3JtID0gbmV3IEZvcm1EYXRhKCk7XG4gICAgICBmb3JtLmFwcGVuZCgnZmlsZScsIGZpbGUpO1xuICAgICAgZm9ybS5zZXQoJ2lkJywgdGhpcy5fbXNnSWQpO1xuICAgICAgdGhpcy54aHIuc2VuZChmb3JtKTtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGlmICh0aGlzLnRvUmVqZWN0KSB7XG4gICAgICAgIHRoaXMudG9SZWplY3QoZXJyKTtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLm9uRmFpbHVyZSkge1xuICAgICAgICB0aGlzLm9uRmFpbHVyZShudWxsKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9LFxuXG4gIC8qKlxuICAgKiBTdGFydCB1cGxvYWRpbmcgdGhlIGZpbGUgdG8gZGVmYXVsdCBlbmRwb2ludC5cbiAgICpcbiAgICogQG1lbWJlcm9mIFRpbm9kZS5MYXJnZUZpbGVIZWxwZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7RmlsZX0gZmlsZSB0byB1cGxvYWRcbiAgICogQHBhcmFtIHtDYWxsYmFja30gb25Qcm9ncmVzcyBjYWxsYmFjay4gVGFrZXMgb25lIHtmbG9hdH0gcGFyYW1ldGVyIDAuLjFcbiAgICogQHBhcmFtIHtDYWxsYmFja30gb25TdWNjZXNzIGNhbGxiYWNrLiBDYWxsZWQgd2hlbiB0aGUgZmlsZSBpcyBzdWNjZXNzZnVsbHkgdXBsb2FkZWQuXG4gICAqIEBwYXJhbSB7Q2FsbGJhY2t9IG9uRmFpbHVyZSBjYWxsYmFjay4gQ2FsbGVkIGluIGNhc2Ugb2YgYSBmYWlsdXJlLlxuICAgKlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gcmVzb2x2ZWQvcmVqZWN0ZWQgd2hlbiB0aGUgdXBsb2FkIGlzIGNvbXBsZXRlZC9mYWlsZWQuXG4gICAqL1xuICB1cGxvYWQ6IGZ1bmN0aW9uKGZpbGUsIG9uUHJvZ3Jlc3MsIG9uU3VjY2Vzcywgb25GYWlsdXJlKSB7XG4gICAgcmV0dXJuIHRoaXMudXBsb2FkV2l0aEJhc2VVcmwodW5kZWZpbmVkLCBmaWxlLCBvblByb2dyZXNzLCBvblN1Y2Nlc3MsIG9uRmFpbHVyZSk7XG4gIH0sXG5cbiAgLyoqXG4gICAqIERvd25sb2FkIHRoZSBmaWxlIGZyb20gYSBnaXZlbiBVUkwgdXNpbmcgR0VUIHJlcXVlc3QuIFRoaXMgbWV0aG9kIHdvcmtzIHdpdGggdGhlIFRpbm9kZSBzZXJ2ZXIgb25seS5cbiAgICpcbiAgICogQG1lbWJlcm9mIFRpbm9kZS5MYXJnZUZpbGVIZWxwZXIjXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSByZWxhdGl2ZVVybCAtIFVSTCB0byBkb3dubG9hZCB0aGUgZmlsZSBmcm9tLiBNdXN0IGJlIHJlbGF0aXZlIHVybCwgaS5lLiBtdXN0IG5vdCBjb250YWluIHRoZSBob3N0LlxuICAgKiBAcGFyYW0ge1N0cmluZz19IGZpbGVuYW1lIC0gZmlsZSBuYW1lIHRvIHVzZSBmb3IgdGhlIGRvd25sb2FkZWQgZmlsZS5cbiAgICpcbiAgICogQHJldHVybnMge1Byb21pc2V9IHJlc29sdmVkL3JlamVjdGVkIHdoZW4gdGhlIGRvd25sb2FkIGlzIGNvbXBsZXRlZC9mYWlsZWQuXG4gICAqL1xuICBkb3dubG9hZDogZnVuY3Rpb24ocmVsYXRpdmVVcmwsIGZpbGVuYW1lLCBtaW1ldHlwZSwgb25Qcm9ncmVzcykge1xuICAgIC8vIENoZWNrIGlmIHRoZSBVUkwgaXMgcmVsYXRpdmUuIENoZWNrIGZvciBjYXNlcyBsaWtlOlxuICAgIC8vICAnaHR0cDovL2V4YW1wbGUuY29tJ1xuICAgIC8vICAnIGh0dHA6Ly9leGFtcGxlLmNvbSdcbiAgICAvLyAgJy8vZXhhbXBsZS5jb20vJ1xuICAgIC8vICAnaHR0cDpleGFtcGxlLmNvbSdcbiAgICAvLyAgJ2h0dHA6L2V4YW1wbGUuY29tJ1xuICAgIGlmICgvXlxccyooW2Etel1bYS16MC05Ky4tXSo6fFxcL1xcLykvaW0udGVzdChyZWxhdGl2ZVVybCkpIHtcbiAgICAgIC8vIEFzIGEgc2VjdXJpdHkgbWVhc3VyZSByZWZ1c2UgdG8gZG93bmxvYWQgZnJvbSBhbiBhYnNvbHV0ZSBVUkwuXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJUaGUgVVJMICdcIiArIHJlbGF0aXZlVXJsICsgXCInIG11c3QgYmUgcmVsYXRpdmUsIG5vdCBhYnNvbHV0ZVwiKTtcbiAgICB9XG4gICAgaWYgKCF0aGlzLl9hdXRoVG9rZW4pIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIk11c3QgYXV0aGVudGljYXRlIGZpcnN0XCIpO1xuICAgIH1cbiAgICBjb25zdCBpbnN0YW5jZSA9IHRoaXM7XG4gICAgLy8gR2V0IGRhdGEgYXMgYmxvYiAoc3RvcmVkIGJ5IHRoZSBicm93c2VyIGFzIGEgdGVtcG9yYXJ5IGZpbGUpLlxuICAgIHRoaXMueGhyLm9wZW4oJ0dFVCcsIHJlbGF0aXZlVXJsLCB0cnVlKTtcbiAgICB0aGlzLnhoci5zZXRSZXF1ZXN0SGVhZGVyKCdYLVRpbm9kZS1BUElLZXknLCB0aGlzLl9hcGlLZXkpO1xuICAgIHRoaXMueGhyLnNldFJlcXVlc3RIZWFkZXIoJ1gtVGlub2RlLUF1dGgnLCAnVG9rZW4gJyArIHRoaXMuX2F1dGhUb2tlbi50b2tlbik7XG4gICAgdGhpcy54aHIucmVzcG9uc2VUeXBlID0gJ2Jsb2InO1xuXG4gICAgdGhpcy5vblByb2dyZXNzID0gb25Qcm9ncmVzcztcbiAgICB0aGlzLnhoci5vbnByb2dyZXNzID0gZnVuY3Rpb24oZSkge1xuICAgICAgaWYgKGluc3RhbmNlLm9uUHJvZ3Jlc3MpIHtcbiAgICAgICAgLy8gUGFzc2luZyBlLmxvYWRlZCBpbnN0ZWFkIG9mIGUubG9hZGVkL2UudG90YWwgYmVjYXVzZSBlLnRvdGFsXG4gICAgICAgIC8vIGlzIGFsd2F5cyAwIHdpdGggZ3ppcCBjb21wcmVzc2lvbiBlbmFibGVkIGJ5IHRoZSBzZXJ2ZXIuXG4gICAgICAgIGluc3RhbmNlLm9uUHJvZ3Jlc3MoZS5sb2FkZWQpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICBjb25zdCByZXN1bHQgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICB0aGlzLnRvUmVzb2x2ZSA9IHJlc29sdmU7XG4gICAgICB0aGlzLnRvUmVqZWN0ID0gcmVqZWN0O1xuICAgIH0pO1xuXG4gICAgLy8gVGhlIGJsb2IgbmVlZHMgdG8gYmUgc2F2ZWQgYXMgZmlsZS4gVGhlcmUgaXMgbm8ga25vd24gd2F5IHRvXG4gICAgLy8gc2F2ZSB0aGUgYmxvYiBhcyBmaWxlIG90aGVyIHRoYW4gdG8gZmFrZSBhIGNsaWNrIG9uIGFuIDxhIGhyZWYuLi4gZG93bmxvYWQ9Li4uPi5cbiAgICB0aGlzLnhoci5vbmxvYWQgPSBmdW5jdGlvbigpIHtcbiAgICAgIGlmICh0aGlzLnN0YXR1cyA9PSAyMDApIHtcbiAgICAgICAgY29uc3QgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2EnKTtcbiAgICAgICAgbGluay5ocmVmID0gd2luZG93LlVSTC5jcmVhdGVPYmplY3RVUkwobmV3IEJsb2IoW3RoaXMucmVzcG9uc2VdLCB7XG4gICAgICAgICAgdHlwZTogbWltZXR5cGVcbiAgICAgICAgfSkpO1xuICAgICAgICBsaW5rLnN0eWxlLmRpc3BsYXkgPSAnbm9uZSc7XG4gICAgICAgIGxpbmsuc2V0QXR0cmlidXRlKCdkb3dubG9hZCcsIGZpbGVuYW1lKTtcbiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChsaW5rKTtcbiAgICAgICAgbGluay5jbGljaygpO1xuICAgICAgICBkb2N1bWVudC5ib2R5LnJlbW92ZUNoaWxkKGxpbmspO1xuICAgICAgICB3aW5kb3cuVVJMLnJldm9rZU9iamVjdFVSTChsaW5rLmhyZWYpO1xuICAgICAgICBpZiAoaW5zdGFuY2UudG9SZXNvbHZlKSB7XG4gICAgICAgICAgaW5zdGFuY2UudG9SZXNvbHZlKCk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAodGhpcy5zdGF0dXMgPj0gNDAwICYmIGluc3RhbmNlLnRvUmVqZWN0KSB7XG4gICAgICAgIC8vIFRoZSB0aGlzLnJlc3BvbnNlVGV4dCBpcyB1bmRlZmluZWQsIG11c3QgdXNlIHRoaXMucmVzcG9uc2Ugd2hpY2ggaXMgYSBibG9iLlxuICAgICAgICAvLyBOZWVkIHRvIGNvbnZlcnQgdGhpcy5yZXNwb25zZSB0byBKU09OLiBUaGUgYmxvYiBjYW4gb25seSBiZSBhY2Nlc3NlZCBieSB0aGVcbiAgICAgICAgLy8gRmlsZVJlYWRlci5cbiAgICAgICAgY29uc3QgcmVhZGVyID0gbmV3IEZpbGVSZWFkZXIoKTtcbiAgICAgICAgcmVhZGVyLm9ubG9hZCA9IGZ1bmN0aW9uKCkge1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCBwa3QgPSBKU09OLnBhcnNlKHRoaXMucmVzdWx0LCBqc29uUGFyc2VIZWxwZXIpO1xuICAgICAgICAgICAgaW5zdGFuY2UudG9SZWplY3QobmV3IEVycm9yKHBrdC5jdHJsLnRleHQgKyBcIiAoXCIgKyBwa3QuY3RybC5jb2RlICsgXCIpXCIpKTtcbiAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIGluc3RhbmNlLl90aW5vZGUubG9nZ2VyKFwiRVJST1I6IEludmFsaWQgc2VydmVyIHJlc3BvbnNlIGluIExhcmdlRmlsZUhlbHBlclwiLCB0aGlzLnJlc3VsdCk7XG4gICAgICAgICAgICBpbnN0YW5jZS50b1JlamVjdChlcnIpO1xuICAgICAgICAgIH1cbiAgICAgICAgfTtcbiAgICAgICAgcmVhZGVyLnJlYWRBc1RleHQodGhpcy5yZXNwb25zZSk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIHRoaXMueGhyLm9uZXJyb3IgPSBmdW5jdGlvbihlKSB7XG4gICAgICBpZiAoaW5zdGFuY2UudG9SZWplY3QpIHtcbiAgICAgICAgaW5zdGFuY2UudG9SZWplY3QobmV3IEVycm9yKFwiZmFpbGVkXCIpKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gICAgdGhpcy54aHIub25hYm9ydCA9IGZ1bmN0aW9uKCkge1xuICAgICAgaWYgKGluc3RhbmNlLnRvUmVqZWN0KSB7XG4gICAgICAgIGluc3RhbmNlLnRvUmVqZWN0KG51bGwpO1xuICAgICAgfVxuICAgIH07XG5cbiAgICB0cnkge1xuICAgICAgdGhpcy54aHIuc2VuZCgpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgaWYgKHRoaXMudG9SZWplY3QpIHtcbiAgICAgICAgdGhpcy50b1JlamVjdChlcnIpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiByZXN1bHQ7XG4gIH0sXG5cbiAgLyoqXG4gICAqIFRyeSB0byBjYW5jZWwgYW4gb25nb2luZyB1cGxvYWQgb3IgZG93bmxvYWQuXG4gICAqIEBtZW1iZXJvZiBUaW5vZGUuTGFyZ2VGaWxlSGVscGVyI1xuICAgKi9cbiAgY2FuY2VsOiBmdW5jdGlvbigpIHtcbiAgICBpZiAodGhpcy54aHIgJiYgdGhpcy54aHIucmVhZHlTdGF0ZSA8IDQpIHtcbiAgICAgIHRoaXMueGhyLmFib3J0KCk7XG4gICAgfVxuICB9LFxuXG4gIC8qKlxuICAgKiBHZXQgdW5pcXVlIGlkIG9mIHRoaXMgcmVxdWVzdC5cbiAgICogQG1lbWJlcm9mIFRpbm9kZS5MYXJnZUZpbGVIZWxwZXIjXG4gICAqXG4gICAqIEByZXR1cm5zIHtzdHJpbmd9IHVuaXF1ZSBpZFxuICAgKi9cbiAgZ2V0SWQ6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLl9tc2dJZDtcbiAgfVxufTtcblxuLyoqXG4gKiBAY2xhc3MgTWVzc2FnZSAtIGRlZmluaXRpb24gYSBjb21tdW5pY2F0aW9uIG1lc3NhZ2UuXG4gKiBXb3JrIGluIHByb2dyZXNzLlxuICogQG1lbWJlcm9mIFRpbm9kZVxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSB0b3BpY18gLSBuYW1lIG9mIHRoZSB0b3BpYyB0aGUgbWVzc2FnZSBiZWxvbmdzIHRvLlxuICogQHBhcmFtIHtzdHJpbmcgfCBEcmFmdHl9IGNvbnRlbnRfIC0gbWVzc2FnZSBjb250YW50LlxuICovXG52YXIgTWVzc2FnZSA9IGZ1bmN0aW9uKHRvcGljXywgY29udGVudF8pIHtcbiAgdGhpcy5zdGF0dXMgPSBNZXNzYWdlLlNUQVRVU19OT05FO1xuICB0aGlzLnRvcGljID0gdG9waWNfO1xuICB0aGlzLmNvbnRlbnQgPSBjb250ZW50Xztcbn1cblxuTWVzc2FnZS5TVEFUVVNfTk9ORSA9IE1FU1NBR0VfU1RBVFVTX05PTkU7XG5NZXNzYWdlLlNUQVRVU19RVUVVRUQgPSBNRVNTQUdFX1NUQVRVU19RVUVVRUQ7XG5NZXNzYWdlLlNUQVRVU19TRU5ESU5HID0gTUVTU0FHRV9TVEFUVVNfU0VORElORztcbk1lc3NhZ2UuU1RBVFVTX0ZBSUxFRCA9IE1FU1NBR0VfU1RBVFVTX0ZBSUxFRDtcbk1lc3NhZ2UuU1RBVFVTX1NFTlQgPSBNRVNTQUdFX1NUQVRVU19TRU5UO1xuTWVzc2FnZS5TVEFUVVNfUkVDRUlWRUQgPSBNRVNTQUdFX1NUQVRVU19SRUNFSVZFRDtcbk1lc3NhZ2UuU1RBVFVTX1JFQUQgPSBNRVNTQUdFX1NUQVRVU19SRUFEO1xuTWVzc2FnZS5TVEFUVVNfVE9fTUUgPSBNRVNTQUdFX1NUQVRVU19UT19NRTtcblxuTWVzc2FnZS5wcm90b3R5cGUgPSB7XG4gIC8qKlxuICAgKiBDb252ZXJ0IG1lc3NhZ2Ugb2JqZWN0IHRvIHtwdWJ9IHBhY2tldC5cbiAgICovXG4gIHRvSlNPTjogZnVuY3Rpb24oKSB7XG5cbiAgfSxcbiAgLyoqXG4gICAqIFBhcnNlIEpTT04gaW50byBtZXNzYWdlLlxuICAgKi9cbiAgZnJvbUpTT046IGZ1bmN0aW9uKGpzb24pIHtcblxuICB9XG59XG5NZXNzYWdlLnByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IE1lc3NhZ2U7XG5cbmlmICh0eXBlb2YgbW9kdWxlICE9ICd1bmRlZmluZWQnKSB7XG4gIG1vZHVsZS5leHBvcnRzID0gVGlub2RlO1xuICBtb2R1bGUuZXhwb3J0cy5EcmFmdHkgPSBEcmFmdHk7XG59XG4iLCJtb2R1bGUuZXhwb3J0cz17XCJ2ZXJzaW9uXCI6IFwiMC4xNi4yXCJ9XG4iXX0=