kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
637 lines (613 loc) • 81.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.adjustValueToAnimationWindow = adjustValueToAnimationWindow;
exports.binByTime = binByTime;
exports.getAggregationOptiosnBasedOnField = void 0;
exports.getBinThresholds = getBinThresholds;
exports.getChartTitle = getChartTitle;
exports.getDefaultPlotType = getDefaultPlotType;
exports.getFilterDataFunc = getFilterDataFunc;
exports.getLineChart = getLineChart;
exports.getPctChange = getPctChange;
exports.getRangeFilterBins = getRangeFilterBins;
exports.getTimeBins = getTimeBins;
exports.getValueAggrFunc = void 0;
exports.histogramFromDomain = histogramFromDomain;
exports.histogramFromOrdinal = histogramFromOrdinal;
exports.histogramFromThreshold = histogramFromThreshold;
exports.histogramFromValues = histogramFromValues;
exports.isPercentField = isPercentField;
exports.normalizeValue = normalizeValue;
exports.runGpuFilterForPlot = runGpuFilterForPlot;
exports.snapToMarks = snapToMarks;
exports.splitSeries = splitSeries;
exports.updateAggregationByField = updateAggregationByField;
exports.updateRangeFilterPlotType = updateRangeFilterPlotType;
exports.updateTimeFilterPlotType = updateTimeFilterPlotType;
exports.validBin = validBin;
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _d3Array = require("d3-array");
var _lodash = _interopRequireDefault(require("lodash.isequal"));
var _time = require("./time");
var _moment = _interopRequireDefault(require("moment"));
var _commonUtils = require("@kepler.gl/common-utils");
var _constants = require("@kepler.gl/constants");
var _dataUtils = require("./data-utils");
var _aggregation = require("./aggregation");
var _strings = require("./strings");
var _format = require("./format");
var _colorUtils = require("./color-utils");
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project
// TODO kepler-table module isn't accessible from utils. Add compatible interface to types
/**
*
* @param thresholds
* @param values
* @param indexes
*/
function histogramFromThreshold(thresholds, values, valueAccessor) {
var filterEmptyBins = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var getBins = (0, _d3Array.histogram)().domain([thresholds[0], thresholds[thresholds.length - 1]]).thresholds(thresholds);
if (valueAccessor) {
getBins.value(valueAccessor);
}
// @ts-ignore
var bins = getBins(values).map(function (bin) {
return {
count: bin.length,
indexes: bin,
x0: bin.x0,
x1: bin.x1
};
});
// d3-histogram ignores threshold values outside the domain
// The first bin.x0 is always equal to the minimum domain value, and the last bin.x1 is always equal to the maximum domain value.
// bins[0].x0 = thresholds[0];
// bins[bins.length - 1].x1 = thresholds[thresholds.length - 1];
// @ts-ignore
return filterEmptyBins ? bins.filter(function (b) {
return b.count > 0;
}) : bins;
}
/**
*
* @param values
* @param numBins
* @param valueAccessor
*/
function histogramFromValues(values, numBins, valueAccessor) {
var getBins = (0, _d3Array.histogram)().thresholds(numBins);
if (valueAccessor) {
getBins.value(valueAccessor);
}
// @ts-ignore d3-array types doesn't match
return getBins(values).map(function (bin) {
return {
count: bin.length,
indexes: bin,
x0: bin.x0,
x1: bin.x1
};
}).filter(function (b) {
var x0 = b.x0,
x1 = b.x1;
return (0, _dataUtils.isNumber)(x0) && (0, _dataUtils.isNumber)(x1);
});
}
function histogramFromOrdinal(domain, values, valueAccessor) {
// @ts-expect-error to typed to expect strings
var getBins = (0, _d3Array.histogram)().thresholds(domain);
if (valueAccessor) {
// @ts-expect-error to typed to expect strings
getBins.value(valueAccessor);
}
// @ts-expect-error null values aren't expected
var bins = getBins(values);
// @ts-ignore d3-array types doesn't match
return bins.map(function (bin) {
return {
count: bin.length,
indexes: bin,
x0: bin.x0,
x1: bin.x0
};
});
}
/**
*
* @param domain
* @param values
* @param numBins
* @param valueAccessor
*/
function histogramFromDomain(domain, values, numBins, valueAccessor) {
var getBins = (0, _d3Array.histogram)().thresholds((0, _d3Array.ticks)(domain[0], domain[1], numBins)).domain(domain);
if (valueAccessor) {
getBins.value(valueAccessor);
}
// @ts-ignore d3-array types doesn't match
return getBins(values).map(function (bin) {
return {
count: bin.length,
indexes: bin,
x0: bin.x0,
x1: bin.x1
};
});
}
/**
* @param filter
* @param datasets
* @param interval
*/
function getTimeBins(filter, datasets, interval) {
var bins = filter.timeBins || {};
filter.dataId.forEach(function (dataId) {
// reuse bins if filterData did not change
if (bins[dataId] && bins[dataId][interval]) {
return;
}
var dataset = datasets[dataId];
// do not apply current filter
var indexes = runGpuFilterForPlot(dataset, filter);
bins = _objectSpread(_objectSpread({}, bins), {}, (0, _defineProperty2["default"])({}, dataId, _objectSpread(_objectSpread({}, bins[dataId]), {}, (0, _defineProperty2["default"])({}, interval, binByTime(indexes, dataset, interval, filter)))));
});
return bins;
}
function binByTime(indexes, dataset, interval, filter) {
// gpuFilters need to be apply to filteredIndex
var mappedValue = (0, _time.getFilterMappedValue)(dataset, filter);
if (!mappedValue) {
return null;
}
var intervalBins = getBinThresholds(interval, filter.domain);
var valueAccessor = function valueAccessor(idx) {
return mappedValue[idx];
};
var bins = histogramFromThreshold(intervalBins, indexes, valueAccessor);
return bins;
}
function getBinThresholds(interval, domain) {
var timeInterval = (0, _time.intervalToFunction)(interval);
var _domain = (0, _slicedToArray2["default"])(domain, 2),
t0 = _domain[0],
t1 = _domain[1];
var floor = timeInterval.floor(t0).getTime();
var ceiling = timeInterval.ceil(t1).getTime();
if (!timeInterval) {
// if time interval is not defined
// this should not happen
return [t0, t0 + _constants.durationDay];
}
var binThresholds = timeInterval.range(floor, ceiling + 1).map(function (t) {
return _moment["default"].utc(t).valueOf();
});
var lastStep = binThresholds[binThresholds.length - 1];
if (lastStep === t1) {
// when last step equal to domain max, add one more step
binThresholds.push(_moment["default"].utc(timeInterval.offset(lastStep)).valueOf());
}
return binThresholds;
}
/**
* Run GPU filter on current filter result to generate indexes for ploting chart
* Skip ruuning for the same field
* @param dataset
* @param filter
*/
function runGpuFilterForPlot(dataset, filter) {
var skipIndexes = getSkipIndexes(dataset, filter);
var _dataset$gpuFilter = dataset.gpuFilter,
filterValueUpdateTriggers = _dataset$gpuFilter.filterValueUpdateTriggers,
filterRange = _dataset$gpuFilter.filterRange,
filterValueAccessor = _dataset$gpuFilter.filterValueAccessor,
filteredIndex = dataset.filteredIndex;
var getFilterValue = filterValueAccessor(dataset.dataContainer)();
var allChannels = Object.keys(filterValueUpdateTriggers).map(function (_, i) {
return i;
}).filter(function (i) {
return Object.values(filterValueUpdateTriggers)[i];
});
var skipAll = !allChannels.filter(function (i) {
return !skipIndexes.includes(i);
}).length;
if (skipAll) {
return filteredIndex;
}
var filterData = getFilterDataFunc(filterRange, getFilterValue, dataset.dataContainer, skipIndexes);
return filteredIndex.filter(filterData);
}
function getSkipIndexes(dataset, filter) {
// array of gpu filter names
if (!filter) {
return [];
}
var gpuFilters = Object.values(dataset.gpuFilter.filterValueUpdateTriggers);
var valueIndex = filter.dataId.findIndex(function (id) {
return id === dataset.id;
});
var filterColumn = filter.name[valueIndex];
return gpuFilters.reduce(function (accu, item, idx) {
if (item && filterColumn === item.name) {
accu.push(idx);
}
return accu;
}, []);
}
function getFilterDataFunc(filterRange, getFilterValue, dataContainer, skips) {
return function (index) {
return getFilterValue({
index: index
}).every(function (val, i) {
return skips.includes(i) || val >= filterRange[i][0] && val <= filterRange[i][1];
});
};
}
function validBin(b) {
return b.x0 !== undefined && b.x1 !== undefined;
}
/**
* Use in slider, given a number and an array of numbers, return the nears number from the array.
* Takes a value, timesteps and return the actual step.
* @param value
* @param marks
*/
function snapToMarks(value, marks) {
// always use bin x0
if (!marks.length) {
// @ts-expect-error looking at the usage null return value isn't expected and requires extra handling in a lot of places
return null;
}
var i = (0, _d3Array.bisectLeft)(marks, value);
if (i === 0) {
return marks[i];
} else if (i === marks.length) {
return marks[i - 1];
}
var idx = marks[i] - value < value - marks[i - 1] ? i : i - 1;
return marks[idx];
}
function normalizeValue(val, minValue, step, marks) {
if (marks && marks.length) {
return snapToMarks(val, marks);
}
return (0, _dataUtils.roundValToStep)(minValue, step, val);
}
function isPercentField(field) {
return field.metadata && field.metadata.numerator && field.metadata.denominator;
}
function updateAggregationByField(field, aggregation) {
// shouldn't apply sum to percent fiele type
// default aggregation is average
return field && isPercentField(field) ? _constants.AGGREGATION_TYPES.average : aggregation || _constants.AGGREGATION_TYPES.average;
}
var getAgregationType = function getAgregationType(field, aggregation) {
if (isPercentField(field)) {
return 'mean_of_percent';
}
return aggregation;
};
var getAggregationAccessor = function getAggregationAccessor(field, dataContainer, fields) {
if (isPercentField(field)) {
var numeratorIdx = fields.findIndex(function (f) {
return f.name === field.metadata.numerator;
});
var denominatorIdx = fields.findIndex(function (f) {
return f.name === field.metadata.denominator;
});
return {
getNumerator: function getNumerator(i) {
return dataContainer.valueAt(i, numeratorIdx);
},
getDenominator: function getDenominator(i) {
return dataContainer.valueAt(i, denominatorIdx);
}
};
}
return function (i) {
return field.valueAccessor({
index: i
});
};
};
var getValueAggrFunc = exports.getValueAggrFunc = function getValueAggrFunc(field, aggregation, dataset) {
var dataContainer = dataset.dataContainer,
fields = dataset.fields;
// The passed-in field might not have all the fields set (e.g. valueAccessor)
var datasetField = fields.find(function (f) {
return field && (f.name === field || f.name === field.name);
});
return datasetField && aggregation ? function (bin) {
return (0, _aggregation.aggregate)(bin.indexes, getAgregationType(datasetField, aggregation),
// @ts-expect-error can return {getNumerator, getDenominator}
getAggregationAccessor(datasetField, dataContainer, fields));
} : function (bin) {
return bin.count;
};
};
var getAggregationOptiosnBasedOnField = exports.getAggregationOptiosnBasedOnField = function getAggregationOptiosnBasedOnField(field) {
if (isPercentField(field)) {
// don't show sum
return _constants.TIME_AGGREGATION.filter(function (_ref) {
var id = _ref.id;
return id !== _constants.AGGREGATION_TYPES.sum;
});
}
return _constants.TIME_AGGREGATION;
};
function getDelta(bins, y, _interval) {
// if (WOW[interval]) return getWow(bins, y, interval);
var lastBin = bins[bins.length - 1];
return {
delta: 'last',
pct: lastBin ? getPctChange(y, lastBin.y) : null
};
}
function getPctChange(y, y0) {
if (Number.isFinite(y) && Number.isFinite(y0) && y0 !== 0) {
return (y - y0) / y0;
}
return null;
}
/**
*
* @param datasets
* @param filter
*/
function getLineChart(datasets, filter) {
var _timeBins;
var dataId = filter.dataId,
yAxis = filter.yAxis,
plotType = filter.plotType,
lineChart = filter.lineChart;
var aggregation = plotType.aggregation,
interval = plotType.interval;
var seriesDataId = dataId[0];
var bins = (_timeBins = filter.timeBins) === null || _timeBins === void 0 || (_timeBins = _timeBins[seriesDataId]) === null || _timeBins === void 0 ? void 0 : _timeBins[interval];
if (lineChart && lineChart.aggregation === aggregation && lineChart.interval === interval && lineChart.yAxis === (yAxis === null || yAxis === void 0 ? void 0 : yAxis.name) &&
// we need to make sure we validate bins because of cross filter data changes
(0, _lodash["default"])(bins, lineChart === null || lineChart === void 0 ? void 0 : lineChart.bins)) {
// don't update lineChart if plotType hasn't change
return lineChart;
}
var dataset = datasets[seriesDataId];
var getYValue = getValueAggrFunc(yAxis, aggregation, dataset);
var init = [];
var series = (bins || []).reduce(function (accu, bin) {
var y = getYValue(bin);
var delta = getDelta(accu, y, interval);
accu.push(_objectSpread({
x: bin.x0,
y: y
}, delta));
return accu;
}, init);
var yDomain = (0, _d3Array.extent)(series, function (d) {
return d.y;
});
var xDomain = bins ? [bins[0].x0, bins[bins.length - 1].x1] : [];
// treat missing data as another series
var split = splitSeries(series);
var aggrName = _aggregation.AGGREGATION_NAME[aggregation];
return {
// @ts-ignore
yDomain: yDomain,
// @ts-ignore
xDomain: xDomain,
interval: interval,
aggregation: aggregation,
// @ts-ignore
series: split,
title: "".concat(aggrName, ' of ').concat(yAxis ? yAxis.name : 'Count'),
fieldType: yAxis ? yAxis.type : 'integer',
yAxis: yAxis ? yAxis.name : null,
allTime: {
title: "All Time Average",
value: (0, _aggregation.aggregate)(series, _constants.AGGREGATION_TYPES.average, function (d) {
return d.y;
})
},
// @ts-expect-error bins is Bins[], not a Bins map. Refactor to use correct types.
bins: bins
};
}
// split into multiple series when see missing data
function splitSeries(series) {
var lines = [];
var temp = [];
for (var i = 0; i < series.length; i++) {
var d = series[i];
if (!(0, _commonUtils.notNullorUndefined)(d.y) && temp.length) {
// ends temp
lines.push(temp);
temp = [];
} else if ((0, _commonUtils.notNullorUndefined)(d.y)) {
temp.push(d);
}
if (i === series.length - 1 && temp.length) {
lines.push(temp);
}
}
var markers = lines.length > 1 ? series.filter(function (d) {
return (0, _commonUtils.notNullorUndefined)(d.y);
}) : [];
return {
lines: lines,
markers: markers
};
}
function adjustValueToAnimationWindow(state, filter) {
var plotType = filter.plotType,
_filter$value = (0, _slicedToArray2["default"])(filter.value, 2),
value0 = _filter$value[0],
value1 = _filter$value[1],
animationWindow = filter.animationWindow;
var interval = plotType.interval || (0, _time.getInitialInterval)(filter, state.datasets);
var bins = getTimeBins(filter, state.datasets, interval);
var datasetBins = bins && Object.keys(bins).length && Object.values(bins)[0][interval];
var thresholds = (datasetBins || []).map(function (b) {
return b.x0;
});
var val0 = value0;
var val1 = value1;
var idx;
if (animationWindow === _constants.ANIMATION_WINDOW.interval) {
val0 = snapToMarks(value1, thresholds);
idx = thresholds.indexOf(val0);
val1 = idx > -1 ? datasetBins[idx].x1 : NaN;
} else {
// fit current value to window
val0 = snapToMarks(value0, thresholds);
val1 = snapToMarks(value1, thresholds);
if (val0 === val1) {
idx = thresholds.indexOf(val0);
if (idx === thresholds.length - 1) {
val0 = thresholds[idx - 1];
} else {
val1 = thresholds[idx + 1];
}
}
}
var updatedFilter = _objectSpread(_objectSpread({}, filter), {}, {
plotType: _objectSpread(_objectSpread({}, filter.plotType), {}, {
interval: interval
}),
timeBins: bins,
value: [val0, val1]
});
return updatedFilter;
}
/**
* Create or update colors for a filter plot
* @param filter
* @param datasets
* @param oldColorsByDataId
*/
function getFilterPlotColorsByDataId(filter, datasets, oldColorsByDataId) {
var colorsByDataId = oldColorsByDataId || {};
var _iterator = _createForOfIteratorHelper(filter.dataId),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var dataId = _step.value;
if (!colorsByDataId[dataId] && datasets[dataId]) {
colorsByDataId = _objectSpread(_objectSpread({}, colorsByDataId), {}, (0, _defineProperty2["default"])({}, dataId, (0, _colorUtils.rgbToHex)(datasets[dataId].color)));
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return colorsByDataId;
}
/**
*
* @param filter
* @param plotType
* @param datasets
* @param dataId
*/
function updateTimeFilterPlotType(filter, plotType, datasets, _dataId) {
var nextFilter = filter;
var nextPlotType = plotType;
if ((0, _typeof2["default"])(nextPlotType) !== 'object' || !nextPlotType.aggregation || !nextPlotType.interval) {
nextPlotType = getDefaultPlotType(filter, datasets);
}
if (filter.dataId.length > 1) {
nextPlotType = _objectSpread(_objectSpread({}, nextPlotType), {}, {
colorsByDataId: getFilterPlotColorsByDataId(filter, datasets, nextPlotType.colorsByDataId)
});
}
nextFilter = _objectSpread(_objectSpread({}, nextFilter), {}, {
plotType: nextPlotType
});
var bins = getTimeBins(nextFilter, datasets, nextPlotType.interval);
nextFilter = _objectSpread(_objectSpread({}, nextFilter), {}, {
timeBins: bins
});
if (plotType.type === _constants.PLOT_TYPES.histogram) {
// Histogram is calculated and memoized in the chart itself
} else if (plotType.type === _constants.PLOT_TYPES.lineChart) {
// we should be able to move this into its own component so react will do the shallow comparison for us.
nextFilter = _objectSpread(_objectSpread({}, nextFilter), {}, {
lineChart: getLineChart(datasets, nextFilter)
});
}
return nextFilter;
}
function getRangeFilterBins(filter, datasets, numBins) {
var domain = filter.domain;
if (!filter.dataId) return null;
return filter.dataId.reduce(function (acc, dataId, datasetIdx) {
var _filter$bins;
if ((_filter$bins = filter.bins) !== null && _filter$bins !== void 0 && _filter$bins[dataId]) {
// don't recalculate bins
acc[dataId] = filter.bins[dataId];
return acc;
}
var fieldName = filter.name[datasetIdx];
if (dataId && fieldName) {
var dataset = datasets[dataId];
var field = dataset === null || dataset === void 0 ? void 0 : dataset.getColumnField(fieldName);
if (dataset && field) {
var indexes = runGpuFilterForPlot(dataset, filter);
var valueAccessor = function valueAccessor(index) {
return field.valueAccessor({
index: index
});
};
acc[dataId] = histogramFromDomain(domain, indexes, numBins, valueAccessor);
}
}
return acc;
}, {});
}
function updateRangeFilterPlotType(filter, plotType, datasets, _dataId) {
var nextFilter = _objectSpread(_objectSpread({}, filter), {}, {
plotType: plotType
});
// if (dataId) {
// // clear bins
// nextFilter = {
// ...nextFilter,
// bins: {
// ...nextFilter.bins,
// [dataId]: null
// }
// };
// }
return _objectSpread(_objectSpread({}, filter), {}, {
plotType: plotType,
bins: getRangeFilterBins(nextFilter, datasets, _constants.BINS)
});
}
function getChartTitle(yAxis, plotType) {
var yAxisName = yAxis === null || yAxis === void 0 ? void 0 : yAxis.displayName;
var aggregation = plotType.aggregation;
if (yAxisName) {
return (0, _strings.capitalizeFirstLetter)("".concat(aggregation, " ").concat(yAxisName, " over Time"));
}
return "Count of Rows over Time";
}
function getDefaultPlotType(filter, datasets) {
var interval = (0, _time.getInitialInterval)(filter, datasets);
var defaultTimeFormat = (0, _format.getDefaultTimeFormat)(interval);
return {
interval: interval,
defaultTimeFormat: defaultTimeFormat,
type: _constants.PLOT_TYPES.histogram,
aggregation: _constants.AGGREGATION_TYPES.sum
};
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfZDNBcnJheSIsInJlcXVpcmUiLCJfbG9kYXNoIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsIl90aW1lIiwiX21vbWVudCIsIl9jb21tb25VdGlscyIsIl9jb25zdGFudHMiLCJfZGF0YVV0aWxzIiwiX2FnZ3JlZ2F0aW9uIiwiX3N0cmluZ3MiLCJfZm9ybWF0IiwiX2NvbG9yVXRpbHMiLCJfY3JlYXRlRm9yT2ZJdGVyYXRvckhlbHBlciIsInIiLCJlIiwidCIsIlN5bWJvbCIsIml0ZXJhdG9yIiwiQXJyYXkiLCJpc0FycmF5IiwiX3Vuc3VwcG9ydGVkSXRlcmFibGVUb0FycmF5IiwibGVuZ3RoIiwiX24iLCJGIiwicyIsIm4iLCJkb25lIiwidmFsdWUiLCJmIiwiVHlwZUVycm9yIiwibyIsImEiLCJ1IiwiY2FsbCIsIm5leHQiLCJfYXJyYXlMaWtlVG9BcnJheSIsInRvU3RyaW5nIiwic2xpY2UiLCJjb25zdHJ1Y3RvciIsIm5hbWUiLCJmcm9tIiwidGVzdCIsIm93bktleXMiLCJPYmplY3QiLCJrZXlzIiwiZ2V0T3duUHJvcGVydHlTeW1ib2xzIiwiZmlsdGVyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZW51bWVyYWJsZSIsInB1c2giLCJhcHBseSIsIl9vYmplY3RTcHJlYWQiLCJhcmd1bWVudHMiLCJmb3JFYWNoIiwiX2RlZmluZVByb3BlcnR5MiIsImdldE93blByb3BlcnR5RGVzY3JpcHRvcnMiLCJkZWZpbmVQcm9wZXJ0aWVzIiwiZGVmaW5lUHJvcGVydHkiLCJoaXN0b2dyYW1Gcm9tVGhyZXNob2xkIiwidGhyZXNob2xkcyIsInZhbHVlcyIsInZhbHVlQWNjZXNzb3IiLCJmaWx0ZXJFbXB0eUJpbnMiLCJ1bmRlZmluZWQiLCJnZXRCaW5zIiwiZDNIaXN0b2dyYW0iLCJkb21haW4iLCJiaW5zIiwibWFwIiwiYmluIiwiY291bnQiLCJpbmRleGVzIiwieDAiLCJ4MSIsImIiLCJoaXN0b2dyYW1Gcm9tVmFsdWVzIiwibnVtQmlucyIsImlzTnVtYmVyIiwiaGlzdG9ncmFtRnJvbU9yZGluYWwiLCJoaXN0b2dyYW1Gcm9tRG9tYWluIiwidGlja3MiLCJnZXRUaW1lQmlucyIsImRhdGFzZXRzIiwiaW50ZXJ2YWwiLCJ0aW1lQmlucyIsImRhdGFJZCIsImRhdGFzZXQiLCJydW5HcHVGaWx0ZXJGb3JQbG90IiwiYmluQnlUaW1lIiwibWFwcGVkVmFsdWUiLCJnZXRGaWx0ZXJNYXBwZWRWYWx1ZSIsImludGVydmFsQmlucyIsImdldEJpblRocmVzaG9sZHMiLCJpZHgiLCJ0aW1lSW50ZXJ2YWwiLCJpbnRlcnZhbFRvRnVuY3Rpb24iLCJfZG9tYWluIiwiX3NsaWNlZFRvQXJyYXkyIiwidDAiLCJ0MSIsImZsb29yIiwiZ2V0VGltZSIsImNlaWxpbmciLCJjZWlsIiwiZHVyYXRpb25EYXkiLCJiaW5UaHJlc2hvbGRzIiwicmFuZ2UiLCJtb21lbnQiLCJ1dGMiLCJ2YWx1ZU9mIiwibGFzdFN0ZXAiLCJvZmZzZXQiLCJza2lwSW5kZXhlcyIsImdldFNraXBJbmRleGVzIiwiX2RhdGFzZXQkZ3B1RmlsdGVyIiwiZ3B1RmlsdGVyIiwiZmlsdGVyVmFsdWVVcGRhdGVUcmlnZ2VycyIsImZpbHRlclJhbmdlIiwiZmlsdGVyVmFsdWVBY2Nlc3NvciIsImZpbHRlcmVkSW5kZXgiLCJnZXRGaWx0ZXJWYWx1ZSIsImRhdGFDb250YWluZXIiLCJhbGxDaGFubmVscyIsIl8iLCJpIiwic2tpcEFsbCIsImluY2x1ZGVzIiwiZmlsdGVyRGF0YSIsImdldEZpbHRlckRhdGFGdW5jIiwiZ3B1RmlsdGVycyIsInZhbHVlSW5kZXgiLCJmaW5kSW5kZXgiLCJpZCIsImZpbHRlckNvbHVtbiIsInJlZHVjZSIsImFjY3UiLCJpdGVtIiwic2tpcHMiLCJpbmRleCIsImV2ZXJ5IiwidmFsIiwidmFsaWRCaW4iLCJzbmFwVG9NYXJrcyIsIm1hcmtzIiwiYmlzZWN0TGVmdCIsIm5vcm1hbGl6ZVZhbHVlIiwibWluVmFsdWUiLCJzdGVwIiwicm91bmRWYWxUb1N0ZXAiLCJpc1BlcmNlbnRGaWVsZCIsImZpZWxkIiwibWV0YWRhdGEiLCJudW1lcmF0b3IiLCJkZW5vbWluYXRvciIsInVwZGF0ZUFnZ3JlZ2F0aW9uQnlGaWVsZCIsImFnZ3JlZ2F0aW9uIiwiQUdHUkVHQVRJT05fVFlQRVMiLCJhdmVyYWdlIiwiZ2V0QWdyZWdhdGlvblR5cGUiLCJnZXRBZ2dyZWdhdGlvbkFjY2Vzc29yIiwiZmllbGRzIiwibnVtZXJhdG9ySWR4IiwiZGVub21pbmF0b3JJZHgiLCJnZXROdW1lcmF0b3IiLCJ2YWx1ZUF0IiwiZ2V0RGVub21pbmF0b3IiLCJnZXRWYWx1ZUFnZ3JGdW5jIiwiZXhwb3J0cyIsImRhdGFzZXRGaWVsZCIsImZpbmQiLCJhZ2dyZWdhdGUiLCJnZXRBZ2dyZWdhdGlvbk9wdGlvc25CYXNlZE9uRmllbGQiLCJUSU1FX0FHR1JFR0FUSU9OIiwiX3JlZiIsInN1bSIsImdldERlbHRhIiwieSIsIl9pbnRlcnZhbCIsImxhc3RCaW4iLCJkZWx0YSIsInBjdCIsImdldFBjdENoYW5nZSIsInkwIiwiTnVtYmVyIiwiaXNGaW5pdGUiLCJnZXRMaW5lQ2hhcnQiLCJfdGltZUJpbnMiLCJ5QXhpcyIsInBsb3RUeXBlIiwibGluZUNoYXJ0Iiwic2VyaWVzRGF0YUlkIiwiaXNFcXVhbCIsImdldFlWYWx1ZSIsImluaXQiLCJzZXJpZXMiLCJ4IiwieURvbWFpbiIsImV4dGVudCIsImQiLCJ4RG9tYWluIiwic3BsaXQiLCJzcGxpdFNlcmllcyIsImFnZ3JOYW1lIiwiQUdHUkVHQVRJT05fTkFNRSIsInRpdGxlIiwiY29uY2F0IiwiZmllbGRUeXBlIiwidHlwZSIsImFsbFRpbWUiLCJsaW5lcyIsInRlbXAiLCJub3ROdWxsb3JVbmRlZmluZWQiLCJtYXJrZXJzIiwiYWRqdXN0VmFsdWVUb0FuaW1hdGlvbldpbmRvdyIsInN0YXRlIiwiX2ZpbHRlciR2YWx1ZSIsInZhbHVlMCIsInZhbHVlMSIsImFuaW1hdGlvbldpbmRvdyIsImdldEluaXRpYWxJbnRlcnZhbCIsImRhdGFzZXRCaW5zIiwidmFsMCIsInZhbDEiLCJBTklNQVRJT05fV0lORE9XIiwiaW5kZXhPZiIsIk5hTiIsInVwZGF0ZWRGaWx0ZXIiLCJnZXRGaWx0ZXJQbG90Q29sb3JzQnlEYXRhSWQiLCJvbGRDb2xvcnNCeURhdGFJZCIsImNvbG9yc0J5RGF0YUlkIiwiX2l0ZXJhdG9yIiwiX3N0ZXAiLCJyZ2JUb0hleCIsImNvbG9yIiwiZXJyIiwidXBkYXRlVGltZUZpbHRlclBsb3RUeXBlIiwiX2RhdGFJZCIsIm5leHRGaWx0ZXIiLCJuZXh0UGxvdFR5cGUiLCJfdHlwZW9mMiIsImdldERlZmF1bHRQbG90VHlwZSIsIlBMT1RfVFlQRVMiLCJoaXN0b2dyYW0iLCJnZXRSYW5nZUZpbHRlckJpbnMiLCJhY2MiLCJkYXRhc2V0SWR4IiwiX2ZpbHRlciRiaW5zIiwiZmllbGROYW1lIiwiZ2V0Q29sdW1uRmllbGQiLCJ1cGRhdGVSYW5nZUZpbHRlclBsb3RUeXBlIiwiQklOUyIsImdldENoYXJ0VGl0bGUiLCJ5QXhpc05hbWUiLCJkaXNwbGF5TmFtZSIsImNhcGl0YWxpemVGaXJzdExldHRlciIsImRlZmF1bHRUaW1lRm9ybWF0IiwiZ2V0RGVmYXVsdFRpbWVGb3JtYXQiXSwic291cmNlcyI6WyIuLi9zcmMvcGxvdC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgY29udHJpYnV0b3JzIHRvIHRoZSBrZXBsZXIuZ2wgcHJvamVjdFxuXG5pbXBvcnQge2Jpc2VjdExlZnQsIGV4dGVudCwgaGlzdG9ncmFtIGFzIGQzSGlzdG9ncmFtLCB0aWNrc30gZnJvbSAnZDMtYXJyYXknO1xuaW1wb3J0IGlzRXF1YWwgZnJvbSAnbG9kYXNoLmlzZXF1YWwnO1xuaW1wb3J0IHtnZXRGaWx0ZXJNYXBwZWRWYWx1ZSwgZ2V0SW5pdGlhbEludGVydmFsLCBpbnRlcnZhbFRvRnVuY3Rpb259IGZyb20gJy4vdGltZSc7XG5pbXBvcnQgbW9tZW50IGZyb20gJ21vbWVudCc7XG5pbXBvcnQge1xuICBCaW4sXG4gIFRpbWVCaW5zLFxuICBNaWxsaXNlY29uZCxcbiAgVGltZVJhbmdlRmlsdGVyLFxuICBSYW5nZUZpbHRlcixcbiAgUGxvdFR5cGUsXG4gIEZpbHRlcixcbiAgTGluZUNoYXJ0LFxuICBGaWVsZCxcbiAgVmFsdWVPZixcbiAgTGluZURhdHVtXG59IGZyb20gJ0BrZXBsZXIuZ2wvdHlwZXMnO1xuaW1wb3J0IHtub3ROdWxsb3JVbmRlZmluZWR9IGZyb20gJ0BrZXBsZXIuZ2wvY29tbW9uLXV0aWxzJztcbmltcG9ydCB7XG4gIEFOSU1BVElPTl9XSU5ET1csXG4gIEJJTlMsXG4gIGR1cmF0aW9uRGF5LFxuICBUSU1FX0FHR1JFR0FUSU9OLFxuICBBR0dSRUdBVElPTl9UWVBFUyxcbiAgUExPVF9UWVBFUyxcbiAgQWdncmVnYXRpb25UeXBlc1xufSBmcm9tICdAa2VwbGVyLmdsL2NvbnN0YW50cyc7XG5cbmltcG9ydCB7aXNOdW1iZXIsIHJvdW5kVmFsVG9TdGVwfSBmcm9tICcuL2RhdGEtdXRpbHMnO1xuaW1wb3J0IHthZ2dyZWdhdGUsIEFHR1JFR0FUSU9OX05BTUV9IGZyb20gJy4vYWdncmVnYXRpb24nO1xuaW1wb3J0IHtjYXBpdGFsaXplRmlyc3RMZXR0ZXJ9IGZyb20gJy4vc3RyaW5ncyc7XG5pbXBvcnQge2dldERlZmF1bHRUaW1lRm9ybWF0fSBmcm9tICcuL2Zvcm1hdCc7XG5pbXBvcnQge3JnYlRvSGV4fSBmcm9tICcuL2NvbG9yLXV0aWxzJztcbmltcG9ydCB7RGF0YUNvbnRhaW5lckludGVyZmFjZX0gZnJvbSAnLic7XG5pbXBvcnQge0tlcGxlclRhYmxlTW9kZWx9IGZyb20gJy4vdHlwZXMnO1xuXG4vLyBUT0RPIGtlcGxlci10YWJsZSBtb2R1bGUgaXNuJ3QgYWNjZXNzaWJsZSBmcm9tIHV0aWxzLiBBZGQgY29tcGF0aWJsZSBpbnRlcmZhY2UgdG8gdHlwZXNcbnR5cGUgRGF0YXNldHMgPSBhbnk7XG5cbi8qKlxuICpcbiAqIEBwYXJhbSB0aHJlc2hvbGRzXG4gKiBAcGFyYW0gdmFsdWVzXG4gKiBAcGFyYW0gaW5kZXhlc1xuICovXG5leHBvcnQgZnVuY3Rpb24gaGlzdG9ncmFtRnJvbVRocmVzaG9sZChcbiAgdGhyZXNob2xkczogbnVtYmVyW10sXG4gIHZhbHVlczogbnVtYmVyW10sXG4gIHZhbHVlQWNjZXNzb3I/OiAoZDogdW5rbm93bikgPT4gbnVtYmVyLFxuICBmaWx0ZXJFbXB0eUJpbnMgPSB0cnVlXG4pOiBCaW5bXSB7XG4gIGNvbnN0IGdldEJpbnMgPSBkM0hpc3RvZ3JhbSgpXG4gICAgLmRvbWFpbihbdGhyZXNob2xkc1swXSwgdGhyZXNob2xkc1t0aHJlc2hvbGRzLmxlbmd0aCAtIDFdXSlcbiAgICAudGhyZXNob2xkcyh0aHJlc2hvbGRzKTtcblxuICBpZiAodmFsdWVBY2Nlc3Nvcikge1xuICAgIGdldEJpbnMudmFsdWUodmFsdWVBY2Nlc3Nvcik7XG4gIH1cblxuICAvLyBAdHMtaWdub3JlXG4gIGNvbnN0IGJpbnMgPSBnZXRCaW5zKHZhbHVlcykubWFwKGJpbiA9PiAoe1xuICAgIGNvdW50OiBiaW4ubGVuZ3RoLFxuICAgIGluZGV4ZXM6IGJpbixcbiAgICB4MDogYmluLngwLFxuICAgIHgxOiBiaW4ueDFcbiAgfSkpO1xuXG4gIC8vIGQzLWhpc3RvZ3JhbSBpZ25vcmVzIHRocmVzaG9sZCB2YWx1ZXMgb3V0c2lkZSB0aGUgZG9tYWluXG4gIC8vIFRoZSBmaXJzdCBiaW4ueDAgaXMgYWx3YXlzIGVxdWFsIHRvIHRoZSBtaW5pbXVtIGRvbWFpbiB2YWx1ZSwgYW5kIHRoZSBsYXN0IGJpbi54MSBpcyBhbHdheXMgZXF1YWwgdG8gdGhlIG1heGltdW0gZG9tYWluIHZhbHVlLlxuXG4gIC8vIGJpbnNbMF0ueDAgPSB0aHJlc2hvbGRzWzBdO1xuICAvLyBiaW5zW2JpbnMubGVuZ3RoIC0gMV0ueDEgPSB0aHJlc2hvbGRzW3RocmVzaG9sZHMubGVuZ3RoIC0gMV07XG5cbiAgLy8gQHRzLWlnbm9yZVxuICByZXR1cm4gZmlsdGVyRW1wdHlCaW5zID8gYmlucy5maWx0ZXIoYiA9PiBiLmNvdW50ID4gMCkgOiBiaW5zO1xufVxuXG4vKipcbiAqXG4gKiBAcGFyYW0gdmFsdWVzXG4gKiBAcGFyYW0gbnVtQmluc1xuICogQHBhcmFtIHZhbHVlQWNjZXNzb3JcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGhpc3RvZ3JhbUZyb21WYWx1ZXMoXG4gIHZhbHVlczogKE1pbGxpc2Vjb25kIHwgbnVsbCB8IG51bWJlcilbXSxcbiAgbnVtQmluczogbnVtYmVyLFxuICB2YWx1ZUFjY2Vzc29yPzogKGQ6IG51bWJlcikgPT4gbnVtYmVyXG4pIHtcbiAgY29uc3QgZ2V0QmlucyA9IGQzSGlzdG9ncmFtKCkudGhyZXNob2xkcyhudW1CaW5zKTtcblxuICBpZiAodmFsdWVBY2Nlc3Nvcikge1xuICAgIGdldEJpbnMudmFsdWUodmFsdWVBY2Nlc3Nvcik7XG4gIH1cblxuICAvLyBAdHMtaWdub3JlIGQzLWFycmF5IHR5cGVzIGRvZXNuJ3QgbWF0Y2hcbiAgcmV0dXJuIGdldEJpbnModmFsdWVzKVxuICAgIC5tYXAoYmluID0+ICh7XG4gICAgICBjb3VudDogYmluLmxlbmd0aCxcbiAgICAgIGluZGV4ZXM6IGJpbixcbiAgICAgIHgwOiBiaW4ueDAsXG4gICAgICB4MTogYmluLngxXG4gICAgfSkpXG4gICAgLmZpbHRlcihiID0+IHtcbiAgICAgIGNvbnN0IHt4MCwgeDF9ID0gYjtcbiAgICAgIHJldHVybiBpc051bWJlcih4MCkgJiYgaXNOdW1iZXIoeDEpO1xuICAgIH0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gaGlzdG9ncmFtRnJvbU9yZGluYWwoXG4gIGRvbWFpbjogW3N0cmluZ10sXG4gIHZhbHVlczogKE1pbGxpc2Vjb25kIHwgbnVsbCB8IG51bWJlcilbXSxcbiAgdmFsdWVBY2Nlc3Nvcj86IChkOiB1bmtub3duKSA9PiBzdHJpbmdcbik6IEJpbltdIHtcbiAgLy8gQHRzLWV4cGVjdC1lcnJvciB0byB0eXBlZCB0byBleHBlY3Qgc3RyaW5nc1xuICBjb25zdCBnZXRCaW5zID0gZDNIaXN0b2dyYW0oKS50aHJlc2hvbGRzKGRvbWFpbik7XG4gIGlmICh2YWx1ZUFjY2Vzc29yKSB7XG4gICAgLy8gQHRzLWV4cGVjdC1lcnJvciB0byB0eXBlZCB0byBleHBlY3Qgc3RyaW5nc1xuICAgIGdldEJpbnMudmFsdWUodmFsdWVBY2Nlc3Nvcik7XG4gIH1cblxuICAvLyBAdHMtZXhwZWN0LWVycm9yIG51bGwgdmFsdWVzIGFyZW4ndCBleHBlY3RlZFxuICBjb25zdCBiaW5zID0gZ2V0Qmlucyh2YWx1ZXMpO1xuXG4gIC8vIEB0cy1pZ25vcmUgZDMtYXJyYXkgdHlwZXMgZG9lc24ndCBtYXRjaFxuICByZXR1cm4gYmlucy5tYXAoYmluID0+ICh7XG4gICAgY291bnQ6IGJpbi5sZW5ndGgsXG4gICAgaW5kZXhlczogYmluLFxuICAgIHgwOiBiaW4ueDAsXG4gICAgeDE6IGJpbi54MFxuICB9KSk7XG59XG5cbi8qKlxuICpcbiAqIEBwYXJhbSBkb21haW5cbiAqIEBwYXJhbSB2YWx1ZXNcbiAqIEBwYXJhbSBudW1CaW5zXG4gKiBAcGFyYW0gdmFsdWVBY2Nlc3NvclxuICovXG5leHBvcnQgZnVuY3Rpb24gaGlzdG9ncmFtRnJvbURvbWFpbihcbiAgZG9tYWluOiBbbnVtYmVyLCBudW1iZXJdLFxuICB2YWx1ZXM6IChNaWxsaXNlY29uZCB8IG51bGwgfCBudW1iZXIpW10sXG4gIG51bUJpbnM6IG51bWJlcixcbiAgdmFsdWVBY2Nlc3Nvcj86IChkOiB1bmtub3duKSA9PiBudW1iZXJcbik6IEJpbltdIHtcbiAgY29uc3QgZ2V0QmlucyA9IGQzSGlzdG9ncmFtKCkudGhyZXNob2xkcyh0aWNrcyhkb21haW5bMF0sIGRvbWFpblsxXSwgbnVtQmlucykpLmRvbWFpbihkb21haW4pO1xuICBpZiAodmFsdWVBY2Nlc3Nvcikge1xuICAgIGdldEJpbnMudmFsdWUodmFsdWVBY2Nlc3Nvcik7XG4gIH1cblxuICAvLyBAdHMtaWdub3JlIGQzLWFycmF5IHR5cGVzIGRvZXNuJ3QgbWF0Y2hcbiAgcmV0dXJuIGdldEJpbnModmFsdWVzKS5tYXAoYmluID0+ICh7XG4gICAgY291bnQ6IGJpbi5sZW5ndGgsXG4gICAgaW5kZXhlczogYmluLFxuICAgIHgwOiBiaW4ueDAsXG4gICAgeDE6IGJpbi54MVxuICB9KSk7XG59XG5cbi8qKlxuICogQHBhcmFtIGZpbHRlclxuICogQHBhcmFtIGRhdGFzZXRzXG4gKiBAcGFyYW0gaW50ZXJ2YWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFRpbWVCaW5zKFxuICBmaWx0ZXI6IFRpbWVSYW5nZUZpbHRlcixcbiAgZGF0YXNldHM6IERhdGFzZXRzLFxuICBpbnRlcnZhbDogUGxvdFR5cGVbJ2ludGVydmFsJ11cbik6IFRpbWVCaW5zIHtcbiAgbGV0IGJpbnMgPSBmaWx0ZXIudGltZUJpbnMgfHwge307XG5cbiAgZmlsdGVyLmRhdGFJZC5mb3JFYWNoKGRhdGFJZCA9PiB7XG4gICAgLy8gcmV1c2UgYmlucyBpZiBmaWx0ZXJEYXRhIGRpZCBub3QgY2hhbmdlXG4gICAgaWYgKGJpbnNbZGF0YUlkXSAmJiBiaW5zW2RhdGFJZF1baW50ZXJ2YWxdKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGRhdGFzZXQgPSBkYXRhc2V0c1tkYXRhSWRdO1xuXG4gICAgLy8gZG8gbm90IGFwcGx5IGN1cnJlbnQgZmlsdGVyXG4gICAgY29uc3QgaW5kZXhlcyA9IHJ1bkdwdUZpbHRlckZvclBsb3QoZGF0YXNldCwgZmlsdGVyKTtcblxuICAgIGJpbnMgPSB7XG4gICAgICAuLi5iaW5zLFxuICAgICAgW2RhdGFJZF06IHtcbiAgICAgICAgLi4uYmluc1tkYXRhSWRdLFxuICAgICAgICBbaW50ZXJ2YWxdOiBiaW5CeVRpbWUoaW5kZXhlcywgZGF0YXNldCwgaW50ZXJ2YWwsIGZpbHRlcilcbiAgICAgIH1cbiAgICB9O1xuICB9KTtcblxuICByZXR1cm4gYmlucztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGJpbkJ5VGltZShpbmRleGVzLCBkYXRhc2V0LCBpbnRlcnZhbCwgZmlsdGVyKSB7XG4gIC8vIGdwdUZpbHRlcnMgbmVlZCB0byBiZSBhcHBseSB0byBmaWx0ZXJlZEluZGV4XG4gIGNvbnN0IG1hcHBlZFZhbHVlID0gZ2V0RmlsdGVyTWFwcGVkVmFsdWUoZGF0YXNldCwgZmlsdGVyKTtcbiAgaWYgKCFtYXBwZWRWYWx1ZSkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGNvbnN0IGludGVydmFsQmlucyA9IGdldEJpblRocmVzaG9sZHMoaW50ZXJ2YWwsIGZpbHRlci5kb21haW4pO1xuICBjb25zdCB2YWx1ZUFjY2Vzc29yID0gaWR4ID0+IG1hcHBlZFZhbHVlW2lkeF07XG4gIGNvbnN0IGJpbnMgPSBoaXN0b2dyYW1Gcm9tVGhyZXNob2xkKGludGVydmFsQmlucywgaW5kZXhlcywgdmFsdWVBY2Nlc3Nvcik7XG5cbiAgcmV0dXJuIGJpbnM7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRCaW5UaHJlc2hvbGRzKGludGVydmFsOiBzdHJpbmcsIGRvbWFpbjogbnVtYmVyW10pOiBudW1iZXJbXSB7XG4gIGNvbnN0IHRpbWVJbnRlcnZhbCA9IGludGVydmFsVG9GdW5jdGlvbihpbnRlcnZhbCk7XG4gIGNvbnN0IFt0MCwgdDFdID0gZG9tYWluO1xuICBjb25zdCBmbG9vciA9IHRpbWVJbnRlcnZhbC5mbG9vcih0MCkuZ2V0VGltZSgpO1xuICBjb25zdCBjZWlsaW5nID0gdGltZUludGVydmFsLmNlaWwodDEpLmdldFRpbWUoKTtcblxuICBpZiAoIXRpbWVJbnRlcnZhbCkge1xuICAgIC8vIGlmIHRpbWUgaW50ZXJ2YWwgaXMgbm90IGRlZmluZWRcbiAgICAvLyB0aGlzIHNob3VsZCBub3QgaGFwcGVuXG4gICAgcmV0dXJuIFt0MCwgdDAgKyBkdXJhdGlvbkRheV07XG4gIH1cbiAgY29uc3QgYmluVGhyZXNob2xkcyA9IHRpbWVJbnRlcnZhbC5yYW5nZShmbG9vciwgY2VpbGluZyArIDEpLm1hcCh0ID0+IG1vbWVudC51dGModCkudmFsdWVPZigpKTtcbiAgY29uc3QgbGFzdFN0ZXAgPSBiaW5UaHJlc2hvbGRzW2JpblRocmVzaG9sZHMubGVuZ3RoIC0gMV07XG4gIGlmIChsYXN0U3RlcCA9PT0gdDEpIHtcbiAgICAvLyB3aGVuIGxhc3Qgc3RlcCBlcXVhbCB0byBkb21haW4gbWF4LCBhZGQgb25lIG1vcmUgc3RlcFxuICAgIGJpblRocmVzaG9sZHMucHVzaChtb21lbnQudXRjKHRpbWVJbnRlcnZhbC5vZmZzZXQobGFzdFN0ZXApKS52YWx1ZU9mKCkpO1xuICB9XG5cbiAgcmV0dXJuIGJpblRocmVzaG9sZHM7XG59XG5cbi8qKlxuICogUnVuIEdQVSBmaWx0ZXIgb24gY3VycmVudCBmaWx0ZXIgcmVzdWx0IHRvIGdlbmVyYXRlIGluZGV4ZXMgZm9yIHBsb3RpbmcgY2hhcnRcbiAqIFNraXAgcnV1bmluZyBmb3IgdGhlIHNhbWUgZmllbGRcbiAqIEBwYXJhbSBkYXRhc2V0XG4gKiBAcGFyYW0gZmlsdGVyXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBydW5HcHVGaWx0ZXJGb3JQbG90PEsgZXh0ZW5kcyBLZXBsZXJUYWJsZU1vZGVsPEssIEw+LCBMPihcbiAgZGF0YXNldDogSyxcbiAgZmlsdGVyPzogRmlsdGVyXG4pOiBudW1iZXJbXSB7XG4gIGNvbnN0IHNraXBJbmRleGVzID0gZ2V0U2tpcEluZGV4ZXMoZGF0YXNldCwgZmlsdGVyKTtcblxuICBjb25zdCB7XG4gICAgZ3B1RmlsdGVyOiB7ZmlsdGVyVmFsdWVVcGRhdGVUcmlnZ2VycywgZmlsdGVyUmFuZ2UsIGZpbHRlclZhbHVlQWNjZXNzb3J9LFxuICAgIGZpbHRlcmVkSW5kZXhcbiAgfSA9IGRhdGFzZXQ7XG4gIGNvbnN0IGdldEZpbHRlclZhbHVlID0gZmlsdGVyVmFsdWVBY2Nlc3NvcihkYXRhc2V0LmRhdGFDb250YWluZXIpKCk7XG5cbiAgY29uc3QgYWxsQ2hhbm5lbHMgPSBPYmplY3Qua2V5cyhmaWx0ZXJWYWx1ZVVwZGF0ZVRyaWdnZXJzKVxuICAgIC5tYXAoKF8sIGkpID0+IGkpXG4gICAgLmZpbHRlcihpID0+IE9iamVjdC52YWx1ZXMoZmlsdGVyVmFsdWVVcGRhdGVUcmlnZ2VycylbaV0pO1xuICBjb25zdCBza2lwQWxsID0gIWFsbENoYW5uZWxzLmZpbHRlcihpID0+ICFza2lwSW5kZXhlcy5pbmNsdWRlcyhpKSkubGVuZ3RoO1xuICBpZiAoc2tpcEFsbCkge1xuICAgIHJldHVybiBmaWx0ZXJlZEluZGV4O1xuICB9XG5cbiAgY29uc3QgZmlsdGVyRGF0YSA9IGdldEZpbHRlckRhdGFGdW5jKFxuICAgIGZpbHRlclJhbmdlLFxuICAgIGdldEZpbHRlclZhbHVlLFxuICAgIGRhdGFzZXQuZGF0YUNvbnRhaW5lcixcbiAgICBza2lwSW5kZXhlc1xuICApO1xuXG4gIHJldHVybiBmaWx0ZXJlZEluZGV4LmZpbHRlcihmaWx0ZXJEYXRhKTtcbn1cblxuZnVuY3Rpb24gZ2V0U2tpcEluZGV4ZXMoZGF0YXNldCwgZmlsdGVyKSB7XG4gIC8vIGFycmF5IG9mIGdwdSBmaWx0ZXIgbmFtZXNcbiAgaWYgKCFmaWx0ZXIpIHtcbiAgICByZXR1cm4gW107XG4gIH1cbiAgY29uc3QgZ3B1RmlsdGVycyA9IE9iamVjdC52YWx1ZXMoZGF0YXNldC5ncHVGaWx0ZXIuZmlsdGVyVmFsdWVVcGRhdGVUcmlnZ2VycykgYXMgKHtcbiAgICBuYW1lOiBzdHJpbmc7XG4gIH0gfCBudWxsKVtdO1xuICBjb25zdCB2YWx1ZUluZGV4ID0gZmlsdGVyLmRhdGFJZC5maW5kSW5kZXgoaWQgPT4gaWQgPT09IGRhdGFzZXQuaWQpO1xuICBjb25zdCBmaWx0ZXJDb2x1bW4gPSBmaWx0ZXIubmFtZVt2YWx1ZUluZGV4XTtcblxuICByZXR1cm4gZ3B1RmlsdGVycy5yZWR1Y2UoKGFjY3UsIGl0ZW0sIGlkeCkgPT4ge1xuICAgIGlmIChpdGVtICYmIGZpbHRlckNvbHVtbiA9PT0gaXRlbS5uYW1lKSB7XG4gICAgICBhY2N1LnB1c2goaWR4KTtcbiAgICB9XG4gICAgcmV0dXJuIGFjY3U7XG4gIH0sIFtdIGFzIG51bWJlcltdKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldEZpbHRlckRhdGFGdW5jKFxuICBmaWx0ZXJSYW5nZSxcbiAgZ2V0RmlsdGVyVmFsdWUsXG4gIGRhdGFDb250YWluZXI6IERhdGFDb250YWluZXJJbnRlcmZhY2UsXG4gIHNraXBzXG4pIHtcbiAgcmV0dXJuIGluZGV4ID0+XG4gICAgZ2V0RmlsdGVyVmFsdWUoe2luZGV4fSkuZXZlcnkoXG4gICAgICAodmFsLCBpKSA9PiBza2lwcy5pbmNsdWRlcyhpKSB8fCAodmFsID49IGZpbHRlclJhbmdlW2ldWzBdICYmIHZhbCA8PSBmaWx0ZXJSYW5nZVtpXVsxXSlcbiAgICApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gdmFsaWRCaW4oYikge1xuICByZXR1cm4gYi54MCAhPT0gdW5kZWZpbmVkICYmIGIueDEgIT09IHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBVc2UgaW4gc2xpZGVyLCBnaXZlbiBhIG51bWJlciBhbmQgYW4gYXJyYXkgb2YgbnVtYmVycywgcmV0dXJuIHRoZSBuZWFycyBudW1iZXIgZnJvbSB0aGUgYXJyYXkuXG4gKiBUYWtlcyBhIHZhbHVlLCB0aW1lc3RlcHMgYW5kIHJldHVybiB0aGUgYWN0dWFsIHN0ZXAuXG4gKiBAcGFyYW0gdmFsdWVcbiAqIEBwYXJhbSBtYXJrc1xuICovXG5leHBvcnQgZnVuY3Rpb24gc25hcFRvTWFya3ModmFsdWU6IG51bWJlciwgbWFya3M6IG51bWJlcltdKTogbnVtYmVyIHtcbiAgLy8gYWx3YXlzIHVzZSBiaW4geDBcbiAgaWYgKCFtYXJrcy5sZW5ndGgpIHtcbiAgICAvLyBAdHMtZXhwZWN0LWVycm9yIGxvb2tpbmcgYXQgdGhlIHVzYWdlIG51bGwgcmV0dXJuIHZhbHVlIGlzbid0IGV4cGVjdGVkIGFuZCByZXF1aXJlcyBleHRyYSBoYW5kbGluZyBpbiBhIGxvdCBvZiBwbGFjZXNcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBjb25zdCBpID0gYmlzZWN0TGVmdChtYXJrcywgdmFsdWUpO1xuICBpZiAoaSA9PT0gMCkge1xuICAgIHJldHVybiBtYXJrc1tpXTtcbiAgfSBlbHNlIGlmIChpID09PSBtYXJrcy5sZW5ndGgpIHtcbiAgICByZXR1cm4gbWFya3NbaSAtIDFdO1xuICB9XG4gIGNvbnN0IGlkeCA9IG1hcmtzW2ldIC0gdmFsdWUgPCB2YWx1ZSAtIG1hcmtzW2kgLSAxXSA/IGkgOiBpIC0gMTtcbiAgcmV0dXJuIG1hcmtzW2lkeF07XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVWYWx1ZSh2YWwsIG1pblZhbHVlLCBzdGVwLCBtYXJrcykge1xuICBpZiAobWFya3MgJiYgbWFya3MubGVuZ3RoKSB7XG4gICAgcmV0dXJuIHNuYXBUb01hcmtzKHZhbCwgbWFya3MpO1xuICB9XG5cbiAgcmV0dXJuIHJvdW5kVmFsVG9TdGVwKG1pblZhbHVlLCBzdGVwLCB2YWwpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gaXNQZXJjZW50RmllbGQoZmllbGQpIHtcbiAgcmV0dXJuIGZpZWxkLm1ldGFkYXRhICYmIGZpZWxkLm1ldGFkYXRhLm51bWVyYXRvciAmJiBmaWVsZC5tZXRhZGF0YS5kZW5vbWluYXRvcjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHVwZGF0ZUFnZ3JlZ2F0aW9uQnlGaWVsZChmaWVsZDogRmllbGQsIGFnZ3JlZ2F0aW9uOiBWYWx1ZU9mPEFnZ3JlZ2F0aW9uVHlwZXM+KSB7XG4gIC8vIHNob3VsZG4ndCBhcHBseSBzdW0gdG8gcGVyY2VudCBmaWVsZSB0eXBlXG4gIC8vIGRlZmF1bHQgYWdncmVnYXRpb24gaXMgYXZlcmFnZVxuICByZXR1cm4gZmllbGQgJiYgaXNQZXJjZW50RmllbGQoZmllbGQpXG4gICAgPyBBR0dSRUdBVElPTl9UWVBFUy5hdmVyYWdlXG4gICAgOiBhZ2dyZWdhdGlvbiB8fCBBR0dSRUdBVElPTl9UWVBFUy5hdmVyYWdlO1xufVxuXG5jb25zdCBnZXRBZ3JlZ2F0aW9uVHlwZSA9IChmaWVsZCwgYWdncmVnYXRpb24pID0+IHtcbiAgaWYgKGlzUGVyY2VudEZpZWxkKGZpZWxkKSkge1xuICAgIHJldHVybiAnbWVhbl9vZl9wZXJjZW50JztcbiAgfVxuICByZXR1cm4gYWdncmVnYXRpb247XG59O1xuXG5jb25zdCBnZXRBZ2dyZWdhdGlvbkFjY2Vzc29yID0gKGZpZWxkLCBkYXRhQ29udGFpbmVyOiBEYXRhQ29udGFpbmVySW50ZXJmYWNlLCBmaWVsZHMpID0+IHtcbiAgaWYgKGlzUGVyY2VudEZpZWxkKGZpZWxkKSkge1xuICAgIGNvbnN0IG51bWVyYXRvcklkeCA9IGZpZWxkcy5maW5kSW5kZXgoZiA9PiBmLm5hbWUgPT09IGZpZWxkLm1ldGFkYXRhLm51bWVyYXRvcik7XG4gICAgY29uc3QgZGVub21pbmF0b3JJZHggPSBmaWVsZHMuZmluZEluZGV4KGYgPT4gZi5uYW1lID09PSBmaWVsZC5tZXRhZGF0YS5kZW5vbWluYXRvcik7XG5cbiAgICByZXR1cm4ge1xuICAgICAgZ2V0TnVtZXJhdG9yOiBpID0+IGRhdGFDb250YWluZXIudmFsdWVBdChpLCBudW1lcmF0b3JJZHgpLFxuICAgICAgZ2V0RGVub21pbmF0b3I6IGkgPT4gZGF0YUNvbnRhaW5lci52YWx1ZUF0KGksIGRlbm9taW5hdG9ySWR4KVxuICAgIH07XG4gIH1cblxuICByZXR1cm4gaSA9PiBmaWVsZC52YWx1ZUFjY2Vzc29yKHtpbmRleDogaX0pO1xufTtcblxuZXhwb3J0IGNvbnN0IGdldFZhbHVlQWdnckZ1bmMgPSAoXG4gIGZpZWxkOiBGaWVsZCB8IHN0cmluZyB8IG51bGwsXG4gIGFnZ3JlZ2F0aW9uOiBzdHJpbmcsXG4gIGRhdGFzZXQ6IEtlcGxlclRhYmxlTW9kZWw8YW55LCBhbnk+XG4pOiAoKGJpbjogQmluKSA9PiBudW1iZXIpID0+IHtcbiAgY29uc3Qge2RhdGFDb250YWluZXIsIGZpZWxkc30gPSBkYXRhc2V0O1xuXG4gIC8vIFRoZSBwYXNzZWQtaW4gZmllbGQgbWlnaHQgbm90IGhhdmUgYWxsIHRoZSBmaWVsZHMgc2V0IChlLmcuIHZhbHVlQWNjZXNzb3IpXG4gIGNvbnN0IGRhdGFzZXRGaWVsZCA9IGZpZWxkcy5maW5kKFxuICAgIGYgPT4gZmllbGQgJiYgKGYubmFtZSA9PT0gZmllbGQgfHwgZi5uYW1lID09PSAoZmllbGQgYXMgRmllbGQpLm5hbWUpXG4gICk7XG5cbiAgcmV0dXJuIGRhdGFzZXRGaWVsZCAmJiBhZ2dyZWdhdGlvblxuICAgID8gYmluID0+XG4gICAgICAgIGFnZ3JlZ2F0ZShcbiAgICAgICAgICBiaW4uaW5kZXhlcyxcbiAgICAgICAgICBnZXRBZ3JlZ2F0aW9uVHlwZShkYXRhc2V0RmllbGQsIGFnZ3JlZ2F0aW9uKSxcbiAgICAgICAgICAvLyBAdHMtZXhwZWN0LWVycm9yIGNhbiByZXR1cm4ge2dldE51bWVyYXRvciwgZ2V0RGVub21pbmF0b3J9XG4gICAgICAgICAgZ2V0QWdncmVnYXRpb25BY2Nlc3NvcihkYXRhc2V0RmllbGQsIGRhdGFDb250YWluZXIsIGZpZWxkcylcbiAgICAgICAgKVxuICAgIDogYmluID0+IGJpbi5jb3VudDtcbn07XG5cbmV4cG9ydCBjb25zdCBnZXRBZ2dyZWdhdGlvbk9wdGlvc25CYXNlZE9uRmllbGQgPSBmaWVsZCA9PiB7XG4gIGlmIChpc1BlcmNlbnRGaWVsZChmaWVsZCkpIHtcbiAgICAvLyBkb24ndCBzaG93IHN1bVxuICAgIHJldHVybiBUSU1FX0FHR1JFR0FUSU9OLmZpbHRlcigoe2lkfSkgPT4gaWQgIT09IEFHR1JFR0FUSU9OX1RZUEVTLnN1bSk7XG4gIH1cbiAgcmV0dXJuIFRJTUVfQUdHUkVHQVRJT047XG59O1xuXG5mdW5jdGlvbiBnZXREZWx0YShcbiAgYmluczogTGluZURhdHVtW10sXG4gIHk6IG51bWJlcixcbiAgX2ludGVydmFsOiBQbG90VHlwZVsnaW50ZXJ2YWwnXVxuKTogUGFydGlhbDxMaW5lRGF0dW0+ICYge2RlbHRhOiAnbGFzdCc7IHBjdDogbnVtYmVyIHwgbnVsbH0ge1xuICAvLyBpZiAoV09XW2ludGVydmFsXSkgcmV0dXJuIGdldFdvdyhiaW5zLCB5LCBpbnRlcnZhbCk7XG4gIGNvbnN0IGxhc3RCaW4gPSBiaW5zW2JpbnMubGVuZ3RoIC0gMV07XG5cbiAgcmV0dXJuIHtcbiAgICBkZWx0YTogJ2xhc3QnLFxuICAgIHBjdDogbGFzdEJpbiA/IGdldFBjdENoYW5nZSh5LCBsYXN0QmluLnkpIDogbnVsbFxuICB9O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0UGN0Q2hhbmdlKHk6IHVua25vd24sIHkwOiB1bmtub3duKTogbnVtYmVyIHwgbnVsbCB7XG4gIGlmIChOdW1iZXIuaXNGaW5pdGUoeSkgJiYgTnVtYmVyLmlzRmluaXRlKHkwKSAmJiB5MCAhPT0gMCkge1xuICAgIHJldHVybiAoKHkgYXMgbnVtYmVyKSAtICh5MCBhcyBudW1iZXIpKSAvICh5MCBhcyBudW1iZXIpO1xuICB9XG4gIHJldHVybiBudWxsO1xufVxuXG4vKipcbiAqXG4gKiBAcGFyYW0gZGF0YXNldHNcbiAqIEBwYXJhbSBmaWx0ZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldExpbmVDaGFydChkYXRhc2V0czogRGF0YXNldHMsIGZpbHRlcjogRmlsdGVyKTogTGluZUNoYXJ0IHtcbiAgY29uc3Qge2RhdGFJZCwgeUF4aXMsIHBsb3RUeXBlLCBsaW5lQ2hhcnR9ID0gZmlsdGVyO1xuICBjb25zdCB7YWdncmVnYXRpb24sIGludGVydmFsfSA9IHBsb3RUeXBlO1xuICBjb25zdCBzZXJpZXNEYXRhSWQgPSBkYXRhSWRbMF07XG4gIGNvbnN0IGJpbnMgPSAoZmlsdGVyIGFzIFRpbWVSYW5nZUZpbHRlcikudGltZUJpbnM/LltzZXJpZXNEYXRhSWRdPy5baW50ZXJ2YWxdO1xuXG4gIGlmIChcbiAgICBsaW5lQ2hhcnQgJiZcbiAgICBsaW5lQ2hhcnQuYWdncmVnYXRpb24gPT09IGFnZ3JlZ2F0aW9uICYmXG4gICAgbGluZUNoYXJ0LmludGVydmFsID09PSBpbnRlcnZhbCAmJlxuICAgIGxpbmVDaGFydC55QXhpcyA9PT0geUF4aXM/Lm5hbWUgJiZcbiAgICAvLyB3ZSBuZWVkIHRvIG1ha2Ugc3VyZSB3ZSB2YWxpZGF0ZSBiaW5zIGJlY2F1c2Ugb2YgY3Jvc3MgZmlsdGVyIGRhdGEgY2hhbmdlc1xuICAgIGlzRXF1YWwoYmlucywgbGluZUNoYXJ0Py5iaW5zKVxuICApIHtcbiAgICAvLyBkb24ndCB1cGRhdGUgbGluZUNoYXJ0IGlmIHBsb3RUeXBlIGhhc24ndCBjaGFuZ2VcbiAgICByZXR1cm4gbGluZUNoYXJ0O1xuICB9XG5cbiAgY29uc3QgZGF0YXNldCA9IGRhdGFzZXRzW3Nlcmllc0RhdGFJZF07XG4gIGNvbnN0IGdldFlWYWx1ZSA9IGdldFZhbHVlQWdnckZ1bmMoeUF4aXMsIGFnZ3JlZ2F0aW9uLCBkYXRhc2V0KTtcblxuICBjb25zdCBpbml0OiBMaW5lRGF0dW1bXSA9IFtdO1xuICBjb25zdCBzZXJpZXMgPSAoYmlucyB8fCBbXSkucmVkdWNlKChhY2N1LCBiaW4pID0+IHtcbiAgICBjb25zdCB5ID0gZ2V0WVZhbHVlKGJpbik7XG4gICAgY29uc3QgZGVsdGEgPSBnZXREZWx0YShhY2N1LCB5LCBpbnRlcnZhbCk7XG4gICAgYWNjdS5wdXNoKHtcbiAgICAgIHg6IGJpbi54MCxcbiAgICAgIHksXG4gICAgICAuLi5kZWx0YVxuICAgIH0pO1xuICAgIHJldHVybiBhY2N1O1xuICB9LCBpbml0KTtcblxuICBjb25zdCB5RG9tYWluID0gZXh0ZW50PHt5OiBhbnl9PihzZXJpZXMsIGQgPT4gZC55KTtcbiAgY29uc3QgeERvbWFpbiA9IGJpbnMgPyBbYmluc1swXS54MCwgYmluc1tiaW5zLmxlbmd0aCAtIDFdLngxXSA6IFtdO1xuXG4gIC8vIHRyZWF0IG1pc3NpbmcgZGF0YSBhcyBhbm90aGVyIHNlcmllc1xuICBjb25zdCBzcGxpdCA9IHNwbGl0U2VyaWVzKHNlcmllcyk7XG4gIGNvbnN0IGFnZ3JOYW1lID0gQUdHUkVHQVRJT05fTkFNRVthZ2dyZWdhdGlvbl07XG5cbiAgcmV0dXJuIHtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgeURvbWFpbixcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgeERvbWFpbixcbiAgICBpbnRlcnZhbCxcbiAgICBhZ2dyZWdhdGlvbixcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgc2VyaWVzOiBzcGxpdCxcbiAgICB0aXRsZTogYCR7YWdnck5hbWV9JHsnIG9mICd9JHt5QXhpcyA/IHlBeGlzLm5hbWUgOiAnQ291bnQnfWAsXG4gICAgZmllbGRUeXBlOiB5QXhpcyA/IHlBeGlzLnR5cGUgOiAnaW50ZWdlcicsXG4gICAgeUF4aXM6IHlBeGlzID8geUF4aXMubmFtZSA6IG51bGwsXG4gICAgYWxsVGltZToge1xuICAgICAgdGl0bGU6IGBBbGwgVGltZSBBdmVyYWdlYCxcbiAgICAgIHZhbHVlOiBhZ2dyZWdhdGUoc2VyaWVzLCBBR0dSRUdBVElPTl9UWVBFUy5hdmVyYWdlLCBkID0+IGQueSlcbiAgICB9LFxuICAgIC8vIEB0cy1leHBlY3QtZXJyb3IgYmlucyBpcyBCaW5zW10sIG5vdCBhIEJpbnMgbWFwLiBSZWZhY3RvciB0byB1c2UgY29ycmVjdCB0eXBlcy5cbiAgICBiaW5zXG4gIH07XG59XG5cbi8vIHNwbGl0IGludG8gbXVsdGlwbGUgc2VyaWVzIHdoZW4gc2VlIG1pc3NpbmcgZGF0YVxuZXhwb3J0IGZ1bmN0aW9uIHNwbGl0U2VyaWVzKHNlcmllcykge1xuICBjb25zdCBsaW5lczogYW55W10gPSBbXTtcbiAgbGV0IHRlbXA6IGFueVtdID0gW107XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgc2VyaWVzLmxlbmd0aDsgaSsrKSB7XG4gICAgY29uc3QgZCA9IHNlcmllc1tpXTtcbiAgICBpZiAoIW5vdE51bGxvclVuZGVmaW5lZChkLnkpICYmIHRlbXAubGVuZ3RoKSB7XG4gICAgICAvLyBlbmRzIHRlbXBcbiAgICAgIGxpbmVzLnB1c2godGVtcCk7XG4gICAgICB0ZW1wID0gW107XG4gICAgfSBlbHNlIGlmIChub3ROdWxsb3JVbmRlZmluZWQoZC55KSkge1xuICAgICAgdGVtcC5wdXNoKGQpO1xuICAgIH1cblxuICAgIGlmIChpID09PSBzZXJpZXMubGVuZ3RoIC0gMSAmJiB0ZW1wLmxlbmd0aCkge1xuICAgICAgbGluZXMucHVzaCh0ZW1wKTtcbiAgICB9XG4gIH1cblxuICBjb25zdCBtYXJrZXJzID0gbGluZXMubGVuZ3RoID4gMSA/IHNlcmllcy5maWx0ZXIoZCA9PiBub3ROdWxsb3JVbmRlZmluZWQoZC55KSkgOiBbXTtcblxuICByZXR1cm4ge2xpbmVzLCBtYXJrZXJzfTtcbn1cblxudHlwZSBNaW5WaXNTdGF0ZUZvckFuaW1hdGlvbldpbmRvdyA9IHtcbiAgZGF0YXNldHM6IERhdGFzZXRzO1xufTtcblxuZXhwb3J0IGZ1bmN0aW9uIGFkanVzdFZhbHVlVG9BbmltYXRpb25XaW5kb3c8UyBleHRlbmRzIE1pblZpc1N0YXRlRm9yQW5pbWF0aW9uV2luZG93PihcbiAgc3RhdGU6IFMsXG4gIGZpbHRlcjogVGltZVJhbmdlRmlsdGVyXG4pIHtcbiAgY29uc3Qge1xuICAgIHBsb3RUeXBlLFxuICAgIHZhbHVlOiBbdmFsdWUwLCB2YWx1ZTFdLFxuICAgIGFuaW1hdGlvbldpbmRvd1xuICB9ID0gZmlsdGVyO1xuXG4gIGNvbnN0IGludGVydmFsID0gcGxvdFR5cGUuaW50ZXJ2YWwgfHwgZ2V0SW5pdGlhbEludGVydmFsKGZpbHRlciwgc3RhdGUuZGF0YXNldHMpO1xuICBjb25zdCBiaW5zID0gZ2V0VGltZUJpbnMoZmlsdGVyLCBzdGF0ZS5kYXRhc2V0cywgaW50ZXJ2YWwpO1xuICBjb25zdCBkYXRhc2V0QmlucyA9IGJpbnMgJiYgT2JqZWN0LmtleXMoYmlucykubGVuZ3RoICYmIE9iamVjdC52YWx1ZXMoYmlucylbMF1baW50ZXJ2YWxdO1xuICBjb25zdCB0aHJlc2hvbGRzID0gKGRhdGFzZXRCaW5zIHx8IFtdKS5tYXAoYiA9PiBiLngwKTtcblxuICBsZXQgdmFsMCA9IHZhbHVlMDtcbiAgbGV0IHZhbDEgPSB2YWx1ZTE7XG4gIGxldCBpZHg7XG4gIGlmIChhbmltYXRpb25XaW5kb3cgPT09IEFOSU1BVElPTl9XSU5ET1cuaW50ZXJ2YWwpIHtcbiAgICB2YWwwID0gc25hcFRvTWFya3ModmFsdWUxLCB0aHJlc2hvbGRzKTtcbiAgICBpZHggPSB0aHJlc2hvbGRzLmluZGV4T2YodmFsMCk7XG4gICAgdmFsMSA9IGlkeCA+IC0xID8gZGF0YXNldEJpbnNbaWR4XS54MSA6IE5hTjtcbiAgfSBlbHNlIHtcbiAgICAvLyBmaXQgY3VycmVudCB2YWx1ZSB0byB3aW5kb3dcbiAgICB2YWwwID0gc25hcFRvTWFya3ModmFsdWUwLCB0aHJlc2hvbGRzKTtcbiAgICB2YWwxID0gc25hcFRvTWFya3ModmFsdWUxLCB0aHJlc2hvbGRzKTtcblxuICAgIGlmICh2YWwwID09PSB2YWwxKSB7XG4gICAgICBpZHggPSB0aHJlc2hvbGRzLmluZGV4T2YodmFsMCk7XG4gICAgICBpZiAoaWR4ID09PSB0aHJlc2hvbGRzLmxlbmd0aCAtIDEpIHtcbiAgICAgICAgdmFsMCA9IHRocmVzaG9sZHNbaWR4IC0gMV07XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB2YWwxID0gdGhyZXNob2xkc1tpZHggKyAxXTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBjb25zdCB1cGRhdGVkRmlsdGVyID0ge1xuICAgIC4uLmZpbHRlcixcbiAgICBwbG90VHlwZToge1xuICAgICAgLi4uZmlsdGVyLnBsb3RUeXBlLFxuICAgICAgaW50ZXJ2YWxcbiAgICB9LFxuICAgIHRpbWVCaW5zOiBiaW5zLFxuICAgIHZhbHVlOiBbdmFsMCwgdmFsMV1cbiAgfTtcblxuICByZXR1cm4gdXBkYXRlZEZpbHRlcjtcbn1cblxuLyoqXG4gKiBDcmVhdGUgb3IgdXBkYXRlIGNvbG9ycyBmb3IgYSBmaWx0ZXIgcGxvdFxuICogQHBhcmFtIGZpbHRlclxuICogQHBhcmFtIGRhdGFzZXRzXG4gKiBAcGFyYW0gb2xkQ29sb3JzQnlEYXRhSWRcbiAqL1xuZnVuY3Rpb24gZ2V0RmlsdGVyUGxvdENvbG9yc0J5RGF0YUlkKGZpbHRlciwgZGF0YXNldHMsIG9sZENvbG9yc0J5RGF0YUlkKSB7XG4gIGxldCBjb2xvcnNCeURhdGFJZCA9IG9sZENvbG9yc0J5RGF0YUlkIHx8IHt9O1xuICBmb3IgKGNvbnN0IGRhdGFJZCBvZiBmaWx0ZXIuZGF0YUlkKSB7XG4gICAgaWYgKCFjb2xvcnNCeURhdGFJZFtkYXRhSWRdICYmIGRhdGFzZXRzW2RhdGFJZF0pIHtcbiAgICAgIGNvbG9yc0J5RGF0YUlkID0ge1xuICAgICAgICAuLi5jb2xvcnNCeURhdGFJZCxcbiAgICAgICAgW2RhdGFJZF06IHJnYlRvSGV4KGRhdGFzZXRzW2RhdGFJZF0uY29sb3IpXG4gICAgICB9O1xuICAgIH1cbiAgfVxuICByZXR1cm4gY29sb3JzQnlEYXRhSWQ7XG59XG5cbi8qKlxuICpcbiAqIEBwYXJhbSBmaWx0ZXJcbiAqIEBwYXJhbSBwbG90VHlwZVxuICogQHBhcmFtIGRhdGFzZXRzXG4gKiBAcGFyYW0gZGF0YUlkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB1cGRhdGVUaW1lRmlsdGVyUGxvdFR5cGUoXG4gIGZpbHRlcjogVGltZVJhbmdlRmlsdGVyLFxuICBwbG90VHlwZTogVGltZVJhbmdlRmlsdGVyWydwbG90VHlwZSddLFxuICBkYXRhc2V0czogRGF0YXNldHMsXG4gIF9kYXRhSWQ/OiBzdHJpbmdcbik6IFRpbWVSYW5nZUZpbHRlciB7XG4gIGxldCBuZXh0RmlsdGVyID0gZmlsdGVyO1xuICBsZXQgbmV4dFBsb3RUeXBlID0gcGxvdFR5cGU7XG4gIGlmICh0eXBlb2YgbmV4dFBsb3RUeXBlICE9PSAnb2JqZWN0JyB8fCAhbmV4dFBsb3RUeXBlLmFnZ3JlZ2F0aW9uIHx8ICFuZXh0UGxvdFR5cGUuaW50ZXJ2YWwpIHtcbiAgICBuZXh0UGxvdFR5cGUgPSBnZXREZWZhdWx0UGxvdFR5cGUoZmlsdGVyLCBkYXRhc2V0cyk7XG4gIH1cblxuICBpZiAoZmlsdGVyLmRhdGFJZC5sZW5ndGggPiAxKSB7XG4gICAgbmV4dFBsb3RUeXBlID0ge1xuICAgICAgLi4ubmV4dFBsb3RUeXBlLFxuICAgICAgY29sb3JzQnlEYXRhSWQ6IGdldEZpbHRlclBsb3RDb2xvcnNCeURhdGFJZChmaWx0ZXIsIGRhdGFzZXRzLCBuZXh0UGxvdFR5cGUuY29sb3JzQnlEYXRhSWQpXG4gICAgfTtcbiAgfVxuICBuZXh0RmlsdGVyID0ge1xuICAgIC4uLm5leHRGaWx0ZXIsXG4gICAgcGxvdFR5cGU6IG5leHRQbG90VHlwZVxuICB9O1xuXG4gIGNvbnN0IGJpbnMgPSBnZXRUaW1lQmlucyhuZXh0RmlsdGVyLCBkYXRhc2V0cywgbmV4dFBsb3RUeXBlLmludGVydmFsKTtcblxuICBuZXh0RmlsdGVyID0ge1xuICAgIC4uLm5leHR