UNPKG

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
"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