kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
647 lines (620 loc) • 83.2 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.copyTable = copyTable;
exports.copyTableAndUpdate = copyTableAndUpdate;
exports["default"] = void 0;
exports.findPointFieldPairs = findPointFieldPairs;
exports.getFieldValueAccessor = getFieldValueAccessor;
exports.maybeToDate = maybeToDate;
exports.pinTableColumns = pinTableColumns;
exports.sortDatasetByColumn = sortDatasetByColumn;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _console = _interopRequireDefault(require("global/console"));
var _d3Array = require("d3-array");
var _constants = require("@kepler.gl/constants");
var _gpuFilterUtils = require("./gpu-filter-utils");
var _utils = require("@kepler.gl/utils");
var _commonUtils = require("@kepler.gl/common-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 isolate layer type, depends on @kepler.gl/layers
// Unique identifier of each field
var FID_KEY = 'name';
function maybeToDate(isTime, fieldIdx, format, dc,
// An object with row index or a materialized row array (for materialized hover info from trip layer)
d) {
if (isTime) {
return (0, _utils.timeToUnixMilli)(Array.isArray(d) ? d[fieldIdx] : dc.valueAt(d.index, fieldIdx), format);
}
return Array.isArray(d) ? d[fieldIdx] : dc.valueAt(d.index, fieldIdx);
}
var KeplerTable = /*#__PURE__*/function () {
function KeplerTable(_ref) {
var info = _ref.info,
color = _ref.color,
metadata = _ref.metadata,
_ref$supportedFilterT = _ref.supportedFilterTypes,
supportedFilterTypes = _ref$supportedFilterT === void 0 ? null : _ref$supportedFilterT,
_ref$disableDataOpera = _ref.disableDataOperation,
disableDataOperation = _ref$disableDataOpera === void 0 ? false : _ref$disableDataOpera;
(0, _classCallCheck2["default"])(this, KeplerTable);
(0, _defineProperty2["default"])(this, "id", void 0);
(0, _defineProperty2["default"])(this, "type", void 0);
(0, _defineProperty2["default"])(this, "label", void 0);
(0, _defineProperty2["default"])(this, "color", void 0);
// fields and data
(0, _defineProperty2["default"])(this, "fields", []);
(0, _defineProperty2["default"])(this, "dataContainer", void 0);
(0, _defineProperty2["default"])(this, "allIndexes", []);
(0, _defineProperty2["default"])(this, "filteredIndex", []);
(0, _defineProperty2["default"])(this, "filteredIdxCPU", void 0);
(0, _defineProperty2["default"])(this, "filteredIndexForDomain", []);
(0, _defineProperty2["default"])(this, "fieldPairs", []);
(0, _defineProperty2["default"])(this, "gpuFilter", void 0);
(0, _defineProperty2["default"])(this, "filterRecord", void 0);
(0, _defineProperty2["default"])(this, "filterRecordCPU", void 0);
(0, _defineProperty2["default"])(this, "changedFilters", void 0);
// table-injected metadata
(0, _defineProperty2["default"])(this, "sortColumn", void 0);
(0, _defineProperty2["default"])(this, "sortOrder", void 0);
(0, _defineProperty2["default"])(this, "pinnedColumns", void 0);
(0, _defineProperty2["default"])(this, "supportedFilterTypes", void 0);
(0, _defineProperty2["default"])(this, "disableDataOperation", void 0);
// table-injected metadata
(0, _defineProperty2["default"])(this, "metadata", void 0);
(0, _defineProperty2["default"])(this, "getFileProcessor", void 0);
// TODO - what to do if validation fails? Can kepler handle exceptions?
// const validatedData = validateInputData(data);
// if (!validatedData) {
// return this;
// }
var datasetInfo = _objectSpread({
id: (0, _commonUtils.generateHashId)(4),
label: 'new dataset',
type: ''
}, info);
var defaultMetadata = {
id: datasetInfo.id,
// @ts-ignore
format: datasetInfo.format || '',
label: datasetInfo.label || ''
};
this.id = datasetInfo.id;
this.type = datasetInfo.type;
this.label = datasetInfo.label;
this.color = color;
this.metadata = _objectSpread(_objectSpread({}, defaultMetadata), metadata);
this.supportedFilterTypes = supportedFilterTypes;
this.disableDataOperation = disableDataOperation;
this.dataContainer = (0, _utils.createDataContainer)([]);
this.gpuFilter = (0, _gpuFilterUtils.getGpuFilterProps)([], this.id, [], undefined);
}
return (0, _createClass2["default"])(KeplerTable, [{
key: "importData",
value: function () {
var _importData = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(_ref2) {
var data, dataContainerData, inputDataFormat, dataContainer, fields, allIndexes;
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
data = _ref2.data;
dataContainerData = data.cols ? data.cols : data.rows;
inputDataFormat = data.cols ? _utils.DataForm.COLS_ARRAY : _utils.DataForm.ROWS_ARRAY;
dataContainer = (0, _utils.createDataContainer)(dataContainerData, {
fields: data.fields,
inputDataFormat: inputDataFormat
});
fields = data.fields.map(function (f, i) {
return _objectSpread(_objectSpread({}, f), {}, {
fieldIdx: i,
id: f.name,
displayName: f.displayName || f.name,
analyzerType: f.analyzerType || _constants.ALL_FIELD_TYPES.string,
format: f.format || '',
valueAccessor: getFieldValueAccessor(f, i, dataContainer)
});
});
allIndexes = dataContainer.getPlainIndex();
this.dataContainer = dataContainer;
this.allIndexes = allIndexes;
this.filteredIndex = allIndexes;
this.filteredIndexForDomain = allIndexes;
this.fieldPairs = findPointFieldPairs(fields);
// @ts-expect-error Make sure that fields satisfies F extends Field
this.fields = fields;
this.gpuFilter = (0, _gpuFilterUtils.getGpuFilterProps)([], this.id, fields, undefined);
case 13:
case "end":
return _context.stop();
}
}, _callee, this);
}));
function importData(_x) {
return _importData.apply(this, arguments);
}
return importData;
}()
/**
* update table with new data
* @param data - new data e.g. the arrow data with new batches loaded
*/
}, {
key: "update",
value: (function () {
var _update = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(data) {
var _this$dataContainer$u, _this$dataContainer;
var dataContainerData;
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
dataContainerData = data.cols ? data.cols : data.rows;
(_this$dataContainer$u = (_this$dataContainer = this.dataContainer).update) === null || _this$dataContainer$u === void 0 || _this$dataContainer$u.call(_this$dataContainer, dataContainerData);
this.allIndexes = this.dataContainer.getPlainIndex();
this.filteredIndex = this.allIndexes;
this.filteredIndexForDomain = this.allIndexes;
return _context2.abrupt("return", this);
case 6:
case "end":
return _context2.stop();
}
}, _callee2, this);
}));
function update(_x2) {
return _update.apply(this, arguments);
}
return update;
}())
}, {
key: "length",
get: function get() {
return this.dataContainer.numRows();
}
/**
* Get field
* @param columnName
*/
}, {
key: "getColumnField",
value: function getColumnField(columnName) {
var field = this.fields.find(function (fd) {
return fd[FID_KEY] === columnName;
});
this._assetField(columnName, field);
return field;
}
/**
* Get fieldIdx
* @param columnName
*/
}, {
key: "getColumnFieldIdx",
value: function getColumnFieldIdx(columnName) {
var fieldIdx = this.fields.findIndex(function (fd) {
return fd[FID_KEY] === columnName;
});
this._assetField(columnName, Boolean(fieldIdx > -1));
return fieldIdx;
}
/**
* Get displayFormat
* @param columnName
*/
}, {
key: "getColumnDisplayFormat",
value: function getColumnDisplayFormat(columnName) {
var field = this.fields.find(function (fd) {
return fd[FID_KEY] === columnName;
});
this._assetField(columnName, field);
return field === null || field === void 0 ? void 0 : field.displayFormat;
}
/**
* Get the value of a cell
*/
}, {
key: "getValue",
value: function getValue(columnName, rowIdx) {
var field = this.getColumnField(columnName);
return field ? field.valueAccessor({
index: rowIdx
}) : null;
}
/**
* Updates existing field with a new object
* @param fieldIdx
* @param newField
*/
}, {
key: "updateColumnField",
value: function updateColumnField(fieldIdx, newField) {
this.fields = Object.assign((0, _toConsumableArray2["default"])(this.fields), (0, _defineProperty2["default"])({}, fieldIdx, newField));
}
/**
* Update dataset color by custom color
* @param newColor
*/
}, {
key: "updateTableColor",
value: function updateTableColor(newColor) {
this.color = newColor;
}
/**
* Save filterProps to field and retrieve it
* @param columnName
*/
}, {
key: "getColumnFilterProps",
value: function getColumnFilterProps(columnName) {
var fieldIdx = this.getColumnFieldIdx(columnName);
if (fieldIdx < 0) {
return null;
}
var field = this.fields[fieldIdx];
if (Object.prototype.hasOwnProperty.call(field, 'filterProps')) {
return field.filterProps;
}
var fieldDomain = this.getColumnFilterDomain(field);
if (!fieldDomain) {
return null;
}
var filterProps = (0, _utils.getFilterProps)(field, fieldDomain);
var newField = _objectSpread(_objectSpread({}, field), {}, {
filterProps: filterProps
});
this.updateColumnField(fieldIdx, newField);
return filterProps;
}
/**
* Apply filters to dataset, return the filtered dataset with updated `gpuFilter`, `filterRecord`, `filteredIndex`, `filteredIndexForDomain`
* @param filters
* @param layers
* @param opt
*/
}, {
key: "filterTable",
value: function filterTable(filters, layers, opt) {
var _this = this;
var dataContainer = this.dataContainer,
dataId = this.id,
oldFilterRecord = this.filterRecord,
fields = this.fields;
// if there is no filters
var filterRecord = (0, _utils.getFilterRecord)(dataId, filters, opt || {});
this.filterRecord = filterRecord;
this.gpuFilter = (0, _gpuFilterUtils.getGpuFilterProps)(filters, dataId, fields, this.gpuFilter);
this.changedFilters = (0, _utils.diffFilters)(filterRecord, oldFilterRecord);
if (!filters.length) {
this.filteredIndex = this.allIndexes;
this.filteredIndexForDomain = this.allIndexes;
return this;
}
// generate 2 sets of filter result
// filteredIndex used to calculate layer data
// filteredIndexForDomain used to calculate layer Domain
var shouldCalDomain = Boolean(this.changedFilters.dynamicDomain);
var shouldCalIndex = Boolean(this.changedFilters.cpu);
var filterResult = {};
if (shouldCalDomain || shouldCalIndex) {
var dynamicDomainFilters = shouldCalDomain ? filterRecord.dynamicDomain : null;
var cpuFilters = shouldCalIndex ? filterRecord.cpu : null;
var filterFuncs = filters.reduce(function (acc, filter) {
var fieldIndex = (0, _gpuFilterUtils.getDatasetFieldIndexForFilter)(_this.id, filter);
var field = fieldIndex !== -1 ? fields[fieldIndex] : null;
return _objectSpread(_objectSpread({}, acc), {}, (0, _defineProperty2["default"])({}, filter.id, (0, _utils.getFilterFunction)(field, _this.id, filter, layers, dataContainer)));
}, {});
filterResult = (0, _utils.filterDataByFilterTypes)({
dynamicDomainFilters: dynamicDomainFilters,
cpuFilters: cpuFilters,
filterFuncs: filterFuncs
}, dataContainer);
}
this.filteredIndex = filterResult.filteredIndex || this.filteredIndex;
this.filteredIndexForDomain = filterResult.filteredIndexForDomain || this.filteredIndexForDomain;
return this;
}
/**
* Apply filters to a dataset all on CPU, assign to `filteredIdxCPU`, `filterRecordCPU`
* @param filters
* @param layers
*/
}, {
key: "filterTableCPU",
value: function filterTableCPU(filters, layers) {
var opt = {
cpuOnly: true,
ignoreDomain: true
};
// no filter
if (!filters.length) {
this.filteredIdxCPU = this.allIndexes;
this.filterRecordCPU = (0, _utils.getFilterRecord)(this.id, filters, opt);
return this;
}
// no gpu filter
if (!filters.find(function (f) {
return f.gpu;
})) {
this.filteredIdxCPU = this.filteredIndex;
this.filterRecordCPU = (0, _utils.getFilterRecord)(this.id, filters, opt);
return this;
}
// make a copy for cpu filtering
var copied = copyTable(this);
copied.filterRecord = this.filterRecordCPU;
copied.filteredIndex = this.filteredIdxCPU || [];
var filtered = copied.filterTable(filters, layers, opt);
this.filteredIdxCPU = filtered.filteredIndex;
this.filterRecordCPU = filtered.filterRecord;
return this;
}
/**
* Calculate field domain based on field type and data
* for Filter
*/
}, {
key: "getColumnFilterDomain",
value: function getColumnFilterDomain(field) {
var dataContainer = this.dataContainer;
var valueAccessor = field.valueAccessor;
var domain;
switch (field.type) {
case _constants.ALL_FIELD_TYPES.real:
case _constants.ALL_FIELD_TYPES.integer:
// calculate domain and step
return (0, _utils.getNumericFieldDomain)(dataContainer, valueAccessor);
case _constants.ALL_FIELD_TYPES["boolean"]:
return {
domain: [true, false]
};
case _constants.ALL_FIELD_TYPES.string:
case _constants.ALL_FIELD_TYPES.h3:
case _constants.ALL_FIELD_TYPES.date:
domain = (0, _utils.getOrdinalDomain)(dataContainer, valueAccessor);
return {
domain: domain
};
case _constants.ALL_FIELD_TYPES.timestamp:
return (0, _utils.getTimestampFieldDomain)(dataContainer, valueAccessor);
default:
return {
domain: (0, _utils.getOrdinalDomain)(dataContainer, valueAccessor)
};
}
}
/**
* Get the domain of this column based on scale type
*/
}, {
key: "getColumnLayerDomain",
value: function getColumnLayerDomain(field, scaleType) {
var dataContainer = this.dataContainer,
filteredIndexForDomain = this.filteredIndexForDomain;
if (!_constants.SCALE_TYPES[scaleType]) {
_console["default"].error("scale type ".concat(scaleType, " not supported"));
return null;
}
var valueAccessor = field.valueAccessor;
var indexValueAccessor = function indexValueAccessor(i) {
return valueAccessor({
index: i
});
};
var sortFunction = (0, _utils.getSortingFunction)(field.type);
switch (scaleType) {
case _constants.SCALE_TYPES.ordinal:
case _constants.SCALE_TYPES.customOrdinal:
case _constants.SCALE_TYPES.point:
// do not recalculate ordinal domain based on filtered data
// don't need to update ordinal domain every time
return (0, _utils.getOrdinalDomain)(dataContainer, valueAccessor);
case _constants.SCALE_TYPES.quantile:
return (0, _utils.getQuantileDomain)(filteredIndexForDomain, indexValueAccessor, sortFunction);
case _constants.SCALE_TYPES.log:
return (0, _utils.getLogDomain)(filteredIndexForDomain, indexValueAccessor);
case _constants.SCALE_TYPES.quantize:
case _constants.SCALE_TYPES.linear:
case _constants.SCALE_TYPES.sqrt:
case _constants.SCALE_TYPES.custom:
default:
return (0, _utils.getLinearDomain)(filteredIndexForDomain, indexValueAccessor);
}
}
/**
* Get a sample of rows to calculate layer boundaries
*/
// getSampleData(rows)
/**
* Parse cell value based on column type and return a string representation
* Value the field value, type the field type
*/
// parseFieldValue(value, type)
// sortDatasetByColumn()
/**
* Assert whether field exist
* @param fieldName
* @param condition
*/
}, {
key: "_assetField",
value: function _assetField(fieldName, condition) {
if (!condition) {
_console["default"].error("".concat(fieldName, " doesnt exist in dataset ").concat(this.id));
}
}
}]);
}();
// HELPER FUNCTIONS (MAINLY EXPORTED FOR TEST...)
// have to double excape
var specialCharacterSet = "[#_&@\\.\\-\\ ]";
function foundMatchingFields(re, suffixPair, allNames, fieldName) {
var partnerIdx = allNames.findIndex(function (d) {
return d === fieldName.replace(re, function (match) {
return match.replace(suffixPair[0], suffixPair[1]);
});
});
var altIdx = -1;
if (partnerIdx > -1) {
// if found partner, go on and look for altitude
_constants.ALTITUDE_FIELDS.some(function (alt) {
altIdx = allNames.findIndex(function (d) {
return d === fieldName.replace(re, function (match) {
return match.replace(suffixPair[0], alt);
});
});
return altIdx > -1;
});
}
return {
partnerIdx: partnerIdx,
altIdx: altIdx
};
}
/**
* Find point fields pairs from fields
*
* @param fields
* @returns found point fields
*/
function findPointFieldPairs(fields) {
var allNames = fields.map(function (f) {
return f.name.toLowerCase();
});
// get list of all fields with matching suffixes
var acc = [];
return allNames.reduce(function (carry, fieldName, idx) {
// This search for pairs will early exit if found.
var _iterator = _createForOfIteratorHelper(_constants.TRIP_POINT_FIELDS),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var suffixPair = _step.value;
// match first suffix
// (^|[#_&@\.\-\ ])lat([#_&@\.\-\ ]|$)
var re = new RegExp("(^|".concat(specialCharacterSet, ")").concat(suffixPair[0], "(").concat(specialCharacterSet, "|$)"));
if (re.test(fieldName)) {
var _foundMatchingFields = foundMatchingFields(re, suffixPair, allNames, fieldName),
partnerIdx = _foundMatchingFields.partnerIdx,
altIdx = _foundMatchingFields.altIdx;
if (partnerIdx > -1) {
var trimName = fieldName.replace(re, '').trim();
carry.push({
defaultName: trimName || 'point',
pair: _objectSpread({
lat: {
fieldIdx: idx,
value: fields[idx].name
},
lng: {
fieldIdx: partnerIdx,
value: fields[partnerIdx].name
}
}, altIdx > -1 ? {
altitude: {
fieldIdx: altIdx,
value: fields[altIdx].name
}
} : {}),
suffix: suffixPair
});
return carry;
}
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return carry;
}, acc);
}
/**
*
* @param dataset
* @param column
* @param mode
* @type
*/
function sortDatasetByColumn(dataset, column, mode) {
var allIndexes = dataset.allIndexes,
fields = dataset.fields,
dataContainer = dataset.dataContainer;
var fieldIndex = fields.findIndex(function (f) {
return f.name === column;
});
if (fieldIndex < 0) {
return dataset;
}
var sortBy = _constants.SORT_ORDER[mode || ''] || _constants.SORT_ORDER.ASCENDING;
if (sortBy === _constants.SORT_ORDER.UNSORT) {
dataset.sortColumn = {};
dataset.sortOrder = null;
return dataset;
}
var sortFunction = sortBy === _constants.SORT_ORDER.ASCENDING ? _d3Array.ascending : _d3Array.descending;
var sortOrder = allIndexes.slice().sort(function (a, b) {
var value1 = dataContainer.valueAt(a, fieldIndex);
var value2 = dataContainer.valueAt(b, fieldIndex);
if (!(0, _commonUtils.notNullorUndefined)(value1) && (0, _commonUtils.notNullorUndefined)(value2)) {
return 1;
} else if ((0, _commonUtils.notNullorUndefined)(value1) && !(0, _commonUtils.notNullorUndefined)(value2)) {
return -1;
}
return sortFunction(value1, value2);
});
dataset.sortColumn = (0, _defineProperty2["default"])({}, column, sortBy);
dataset.sortOrder = sortOrder;
return dataset;
}
function pinTableColumns(dataset, column) {
var field = dataset.getColumnField(column);
if (!field) {
return dataset;
}
var pinnedColumns;
if (Array.isArray(dataset.pinnedColumns) && dataset.pinnedColumns.includes(field.name)) {
// unpin it
pinnedColumns = dataset.pinnedColumns.filter(function (co) {
return co !== field.name;
});
} else {
pinnedColumns = (dataset.pinnedColumns || []).concat(field.name);
}
// @ts-ignore
return copyTableAndUpdate(dataset, {
pinnedColumns: pinnedColumns
});
}
function copyTable(original) {
return Object.assign(Object.create(Object.getPrototypeOf(original)), original);
}
/**
* @type
* @returns
*/
function copyTableAndUpdate(original) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return Object.entries(options).reduce(function (acc, entry) {
acc[entry[0]] = entry[1];
return acc;
}, copyTable(original));
}
function getFieldValueAccessor(f, i, dc) {
return maybeToDate.bind(null,
// is time
f.type === _constants.ALL_FIELD_TYPES.timestamp, i, f.format || '', dc);
}
var _default = exports["default"] = KeplerTable;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfY29uc29sZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJyZXF1aXJlIiwiX2QzQXJyYXkiLCJfY29uc3RhbnRzIiwiX2dwdUZpbHRlclV0aWxzIiwiX3V0aWxzIiwiX2NvbW1vblV0aWxzIiwiX2NyZWF0ZUZvck9mSXRlcmF0b3JIZWxwZXIiLCJyIiwiZSIsInQiLCJTeW1ib2wiLCJpdGVyYXRvciIsIkFycmF5IiwiaXNBcnJheSIsIl91bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheSIsImxlbmd0aCIsIl9uIiwiRiIsInMiLCJuIiwiZG9uZSIsInZhbHVlIiwiZiIsIlR5cGVFcnJvciIsIm8iLCJhIiwidSIsImNhbGwiLCJuZXh0IiwiX2FycmF5TGlrZVRvQXJyYXkiLCJ0b1N0cmluZyIsInNsaWNlIiwiY29uc3RydWN0b3IiLCJuYW1lIiwiZnJvbSIsInRlc3QiLCJvd25LZXlzIiwiT2JqZWN0Iiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsImZpbHRlciIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsImVudW1lcmFibGUiLCJwdXNoIiwiYXBwbHkiLCJfb2JqZWN0U3ByZWFkIiwiYXJndW1lbnRzIiwiZm9yRWFjaCIsIl9kZWZpbmVQcm9wZXJ0eTIiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3JzIiwiZGVmaW5lUHJvcGVydGllcyIsImRlZmluZVByb3BlcnR5IiwiRklEX0tFWSIsIm1heWJlVG9EYXRlIiwiaXNUaW1lIiwiZmllbGRJZHgiLCJmb3JtYXQiLCJkYyIsImQiLCJ0aW1lVG9Vbml4TWlsbGkiLCJ2YWx1ZUF0IiwiaW5kZXgiLCJLZXBsZXJUYWJsZSIsIl9yZWYiLCJpbmZvIiwiY29sb3IiLCJtZXRhZGF0YSIsIl9yZWYkc3VwcG9ydGVkRmlsdGVyVCIsInN1cHBvcnRlZEZpbHRlclR5cGVzIiwiX3JlZiRkaXNhYmxlRGF0YU9wZXJhIiwiZGlzYWJsZURhdGFPcGVyYXRpb24iLCJfY2xhc3NDYWxsQ2hlY2syIiwiZGF0YXNldEluZm8iLCJpZCIsImdlbmVyYXRlSGFzaElkIiwibGFiZWwiLCJ0eXBlIiwiZGVmYXVsdE1ldGFkYXRhIiwiZGF0YUNvbnRhaW5lciIsImNyZWF0ZURhdGFDb250YWluZXIiLCJncHVGaWx0ZXIiLCJnZXRHcHVGaWx0ZXJQcm9wcyIsInVuZGVmaW5lZCIsIl9jcmVhdGVDbGFzczIiLCJrZXkiLCJfaW1wb3J0RGF0YSIsIl9hc3luY1RvR2VuZXJhdG9yMiIsIl9yZWdlbmVyYXRvciIsIm1hcmsiLCJfY2FsbGVlIiwiX3JlZjIiLCJkYXRhIiwiZGF0YUNvbnRhaW5lckRhdGEiLCJpbnB1dERhdGFGb3JtYXQiLCJmaWVsZHMiLCJhbGxJbmRleGVzIiwid3JhcCIsIl9jYWxsZWUkIiwiX2NvbnRleHQiLCJwcmV2IiwiY29scyIsInJvd3MiLCJEYXRhRm9ybSIsIkNPTFNfQVJSQVkiLCJST1dTX0FSUkFZIiwibWFwIiwiaSIsImRpc3BsYXlOYW1lIiwiYW5hbHl6ZXJUeXBlIiwiQUxMX0ZJRUxEX1RZUEVTIiwic3RyaW5nIiwidmFsdWVBY2Nlc3NvciIsImdldEZpZWxkVmFsdWVBY2Nlc3NvciIsImdldFBsYWluSW5kZXgiLCJmaWx0ZXJlZEluZGV4IiwiZmlsdGVyZWRJbmRleEZvckRvbWFpbiIsImZpZWxkUGFpcnMiLCJmaW5kUG9pbnRGaWVsZFBhaXJzIiwic3RvcCIsImltcG9ydERhdGEiLCJfeCIsIl91cGRhdGUiLCJfY2FsbGVlMiIsIl90aGlzJGRhdGFDb250YWluZXIkdSIsIl90aGlzJGRhdGFDb250YWluZXIiLCJfY2FsbGVlMiQiLCJfY29udGV4dDIiLCJ1cGRhdGUiLCJhYnJ1cHQiLCJfeDIiLCJnZXQiLCJudW1Sb3dzIiwiZ2V0Q29sdW1uRmllbGQiLCJjb2x1bW5OYW1lIiwiZmllbGQiLCJmaW5kIiwiZmQiLCJfYXNzZXRGaWVsZCIsImdldENvbHVtbkZpZWxkSWR4IiwiZmluZEluZGV4IiwiQm9vbGVhbiIsImdldENvbHVtbkRpc3BsYXlGb3JtYXQiLCJkaXNwbGF5Rm9ybWF0IiwiZ2V0VmFsdWUiLCJyb3dJZHgiLCJ1cGRhdGVDb2x1bW5GaWVsZCIsIm5ld0ZpZWxkIiwiYXNzaWduIiwiX3RvQ29uc3VtYWJsZUFycmF5MiIsInVwZGF0ZVRhYmxlQ29sb3IiLCJuZXdDb2xvciIsImdldENvbHVtbkZpbHRlclByb3BzIiwicHJvdG90eXBlIiwiaGFzT3duUHJvcGVydHkiLCJmaWx0ZXJQcm9wcyIsImZpZWxkRG9tYWluIiwiZ2V0Q29sdW1uRmlsdGVyRG9tYWluIiwiZ2V0RmlsdGVyUHJvcHMiLCJmaWx0ZXJUYWJsZSIsImZpbHRlcnMiLCJsYXllcnMiLCJvcHQiLCJfdGhpcyIsImRhdGFJZCIsIm9sZEZpbHRlclJlY29yZCIsImZpbHRlclJlY29yZCIsImdldEZpbHRlclJlY29yZCIsImNoYW5nZWRGaWx0ZXJzIiwiZGlmZkZpbHRlcnMiLCJzaG91bGRDYWxEb21haW4iLCJkeW5hbWljRG9tYWluIiwic2hvdWxkQ2FsSW5kZXgiLCJjcHUiLCJmaWx0ZXJSZXN1bHQiLCJkeW5hbWljRG9tYWluRmlsdGVycyIsImNwdUZpbHRlcnMiLCJmaWx0ZXJGdW5jcyIsInJlZHVjZSIsImFjYyIsImZpZWxkSW5kZXgiLCJnZXREYXRhc2V0RmllbGRJbmRleEZvckZpbHRlciIsImdldEZpbHRlckZ1bmN0aW9uIiwiZmlsdGVyRGF0YUJ5RmlsdGVyVHlwZXMiLCJmaWx0ZXJUYWJsZUNQVSIsImNwdU9ubHkiLCJpZ25vcmVEb21haW4iLCJmaWx0ZXJlZElkeENQVSIsImZpbHRlclJlY29yZENQVSIsImdwdSIsImNvcGllZCIsImNvcHlUYWJsZSIsImZpbHRlcmVkIiwiZG9tYWluIiwicmVhbCIsImludGVnZXIiLCJnZXROdW1lcmljRmllbGREb21haW4iLCJoMyIsImRhdGUiLCJnZXRPcmRpbmFsRG9tYWluIiwidGltZXN0YW1wIiwiZ2V0VGltZXN0YW1wRmllbGREb21haW4iLCJnZXRDb2x1bW5MYXllckRvbWFpbiIsInNjYWxlVHlwZSIsIlNDQUxFX1RZUEVTIiwiQ29uc29sZSIsImVycm9yIiwiY29uY2F0IiwiaW5kZXhWYWx1ZUFjY2Vzc29yIiwic29ydEZ1bmN0aW9uIiwiZ2V0U29ydGluZ0Z1bmN0aW9uIiwib3JkaW5hbCIsImN1c3RvbU9yZGluYWwiLCJwb2ludCIsInF1YW50aWxlIiwiZ2V0UXVhbnRpbGVEb21haW4iLCJsb2ciLCJnZXRMb2dEb21haW4iLCJxdWFudGl6ZSIsImxpbmVhciIsInNxcnQiLCJjdXN0b20iLCJnZXRMaW5lYXJEb21haW4iLCJmaWVsZE5hbWUiLCJjb25kaXRpb24iLCJzcGVjaWFsQ2hhcmFjdGVyU2V0IiwiZm91bmRNYXRjaGluZ0ZpZWxkcyIsInJlIiwic3VmZml4UGFpciIsImFsbE5hbWVzIiwicGFydG5lcklkeCIsInJlcGxhY2UiLCJtYXRjaCIsImFsdElkeCIsIkFMVElUVURFX0ZJRUxEUyIsInNvbWUiLCJhbHQiLCJ0b0xvd2VyQ2FzZSIsImNhcnJ5IiwiaWR4IiwiX2l0ZXJhdG9yIiwiVFJJUF9QT0lOVF9GSUVMRFMiLCJfc3RlcCIsIlJlZ0V4cCIsIl9mb3VuZE1hdGNoaW5nRmllbGRzIiwidHJpbU5hbWUiLCJ0cmltIiwiZGVmYXVsdE5hbWUiLCJwYWlyIiwibGF0IiwibG5nIiwiYWx0aXR1ZGUiLCJzdWZmaXgiLCJlcnIiLCJzb3J0RGF0YXNldEJ5Q29sdW1uIiwiZGF0YXNldCIsImNvbHVtbiIsIm1vZGUiLCJzb3J0QnkiLCJTT1JUX09SREVSIiwiQVNDRU5ESU5HIiwiVU5TT1JUIiwic29ydENvbHVtbiIsInNvcnRPcmRlciIsImFzY2VuZGluZyIsImRlc2NlbmRpbmciLCJzb3J0IiwiYiIsInZhbHVlMSIsInZhbHVlMiIsIm5vdE51bGxvclVuZGVmaW5lZCIsInBpblRhYmxlQ29sdW1ucyIsInBpbm5lZENvbHVtbnMiLCJpbmNsdWRlcyIsImNvIiwiY29weVRhYmxlQW5kVXBkYXRlIiwib3JpZ2luYWwiLCJjcmVhdGUiLCJnZXRQcm90b3R5cGVPZiIsIm9wdGlvbnMiLCJlbnRyaWVzIiwiZW50cnkiLCJiaW5kIiwiX2RlZmF1bHQiLCJleHBvcnRzIl0sInNvdXJjZXMiOlsiLi4vc3JjL2tlcGxlci10YWJsZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgY29udHJpYnV0b3JzIHRvIHRoZSBrZXBsZXIuZ2wgcHJvamVjdFxuXG5pbXBvcnQgQ29uc29sZSBmcm9tICdnbG9iYWwvY29uc29sZSc7XG5pbXBvcnQge2FzY2VuZGluZywgZGVzY2VuZGluZ30gZnJvbSAnZDMtYXJyYXknO1xuXG5pbXBvcnQge1xuICBUUklQX1BPSU5UX0ZJRUxEUyxcbiAgU09SVF9PUkRFUixcbiAgQUxMX0ZJRUxEX1RZUEVTLFxuICBBTFRJVFVERV9GSUVMRFMsXG4gIFNDQUxFX1RZUEVTXG59IGZyb20gJ0BrZXBsZXIuZ2wvY29uc3RhbnRzJztcbmltcG9ydCB7XG4gIFJHQkNvbG9yLFxuICBGaWVsZCxcbiAgRmllbGRQYWlyLFxuICBGaWVsZERvbWFpbixcbiAgRmlsdGVyLFxuICBQcm90b0RhdGFzZXQsXG4gIEZpbHRlclJlY29yZCxcbiAgRmlsdGVyRGF0YXNldE9wdCxcbiAgUmFuZ2VGaWVsZERvbWFpbixcbiAgU2VsZWN0RmllbGREb21haW4sXG4gIE11bHRpU2VsZWN0RmllbGREb21haW4sXG4gIFRpbWVSYW5nZUZpZWxkRG9tYWluXG59IGZyb20gJ0BrZXBsZXIuZ2wvdHlwZXMnO1xuXG5pbXBvcnQge2dldEdwdUZpbHRlclByb3BzLCBnZXREYXRhc2V0RmllbGRJbmRleEZvckZpbHRlcn0gZnJvbSAnLi9ncHUtZmlsdGVyLXV0aWxzJztcblxuaW1wb3J0IHtcbiAgZ2V0U29ydGluZ0Z1bmN0aW9uLFxuICB0aW1lVG9Vbml4TWlsbGksXG4gIGNyZWF0ZURhdGFDb250YWluZXIsXG4gIERhdGFGb3JtLFxuICBkaWZmRmlsdGVycyxcbiAgZmlsdGVyRGF0YUJ5RmlsdGVyVHlwZXMsXG4gIEZpbHRlclJlc3VsdCxcbiAgZ2V0RmlsdGVyRnVuY3Rpb24sXG4gIGdldEZpbHRlclByb3BzLFxuICBnZXRGaWx0ZXJSZWNvcmQsXG4gIGdldE51bWVyaWNGaWVsZERvbWFpbixcbiAgZ2V0VGltZXN0YW1wRmllbGREb21haW4sXG4gIGdldExpbmVhckRvbWFpbixcbiAgZ2V0TG9nRG9tYWluLFxuICBnZXRPcmRpbmFsRG9tYWluLFxuICBnZXRRdWFudGlsZURvbWFpbixcbiAgRGF0YUNvbnRhaW5lckludGVyZmFjZSxcbiAgRmlsdGVyQ2hhbmdlZFxufSBmcm9tICdAa2VwbGVyLmdsL3V0aWxzJztcbmltcG9ydCB7Z2VuZXJhdGVIYXNoSWQsIG5vdE51bGxvclVuZGVmaW5lZH0gZnJvbSAnQGtlcGxlci5nbC9jb21tb24tdXRpbHMnO1xuXG4vLyBUT0RPIGlzb2xhdGUgbGF5ZXIgdHlwZSwgZGVwZW5kcyBvbiBAa2VwbGVyLmdsL2xheWVyc1xudHlwZSBMYXllciA9IGFueTtcblxuZXhwb3J0IHR5cGUgR3B1RmlsdGVyID0ge1xuICBmaWx0ZXJSYW5nZTogbnVtYmVyW11bXTtcbiAgZmlsdGVyVmFsdWVVcGRhdGVUcmlnZ2Vyczoge1xuICAgIFtpZDogc3RyaW5nXToge25hbWU6IHN0cmluZzsgZG9tYWluMDogbnVtYmVyfSB8IG51bGw7XG4gIH07XG4gIGZpbHRlclZhbHVlQWNjZXNzb3I6IChcbiAgICBkYzogRGF0YUNvbnRhaW5lckludGVyZmFjZVxuICApID0+IChcbiAgICBnZXRJbmRleD86IChhbnkpID0+IG51bWJlcixcbiAgICBnZXREYXRhPzogKGRjXzogRGF0YUNvbnRhaW5lckludGVyZmFjZSwgZDogYW55LCBmaWVsZEluZGV4OiBudW1iZXIpID0+IGFueVxuICApID0+IChkOiBhbnksIG9iamVjdEluZm8/OiB7aW5kZXg6IG51bWJlcn0pID0+IChudW1iZXIgfCBudW1iZXJbXSlbXTtcbn07XG5cbmV4cG9ydCB0eXBlIEZpbHRlclByb3BzID1cbiAgfCBOdW1lcmljRmllbGRGaWx0ZXJQcm9wc1xuICB8IEJvb2xlYW5GaWVsZEZpbHRlclByb3BzXG4gIHwgU3RyaW5nRmllbGRGaWx0ZXJQcm9wc1xuICB8IFRpbWVGaWVsZEZpbHRlclByb3BzO1xuXG5leHBvcnQgdHlwZSBOdW1lcmljRmllbGRGaWx0ZXJQcm9wcyA9IFJhbmdlRmllbGREb21haW4gJiB7XG4gIHZhbHVlOiBbbnVtYmVyLCBudW1iZXJdO1xuICB0eXBlOiBzdHJpbmc7XG4gIHR5cGVPcHRpb25zOiBzdHJpbmdbXTtcbiAgZ3B1OiBib29sZWFuO1xuICBjb2x1bW5TdGF0cz86IFJlY29yZDxzdHJpbmcsIGFueT47XG59O1xuZXhwb3J0IHR5cGUgQm9vbGVhbkZpZWxkRmlsdGVyUHJvcHMgPSBTZWxlY3RGaWVsZERvbWFpbiAmIHtcbiAgdHlwZTogc3RyaW5nO1xuICB2YWx1ZTogYm9vbGVhbjtcbiAgZ3B1OiBib29sZWFuO1xuICBjb2x1bW5TdGF0cz86IFJlY29yZDxzdHJpbmcsIGFueT47XG59O1xuZXhwb3J0IHR5cGUgU3RyaW5nRmllbGRGaWx0ZXJQcm9wcyA9IE11bHRpU2VsZWN0RmllbGREb21haW4gJiB7XG4gIHR5cGU6IHN0cmluZztcbiAgdmFsdWU6IHN0cmluZ1tdO1xuICBncHU6IGJvb2xlYW47XG4gIGNvbHVtblN0YXRzPzogUmVjb3JkPHN0cmluZywgYW55Pjtcbn07XG5leHBvcnQgdHlwZSBUaW1lRmllbGRGaWx0ZXJQcm9wcyA9IFRpbWVSYW5nZUZpZWxkRG9tYWluICYge1xuICB0eXBlOiBzdHJpbmc7XG4gIHZpZXc6IEZpbHRlclsndmlldyddO1xuICBmaXhlZERvbWFpbjogYm9vbGVhbjtcbiAgdmFsdWU6IG51bWJlcltdO1xuICBncHU6IGJvb2xlYW47XG4gIGNvbHVtblN0YXRzPzogUmVjb3JkPHN0cmluZywgYW55Pjtcbn07XG5cbi8vIFVuaXF1ZSBpZGVudGlmaWVyIG9mIGVhY2ggZmllbGRcbmNvbnN0IEZJRF9LRVkgPSAnbmFtZSc7XG5cbmV4cG9ydCBmdW5jdGlvbiBtYXliZVRvRGF0ZShcbiAgaXNUaW1lOiBib29sZWFuLFxuICBmaWVsZElkeDogbnVtYmVyLFxuICBmb3JtYXQ6IHN0cmluZyxcbiAgZGM6IERhdGFDb250YWluZXJJbnRlcmZhY2UsXG4gIC8vIEFuIG9iamVjdCB3aXRoIHJvdyBpbmRleCBvciBhIG1hdGVyaWFsaXplZCByb3cgYXJyYXkgKGZvciBtYXRlcmlhbGl6ZWQgaG92ZXIgaW5mbyBmcm9tIHRyaXAgbGF5ZXIpXG4gIGQ6IHtpbmRleDogbnVtYmVyfSB8IGFueVtdXG4pIHtcbiAgaWYgKGlzVGltZSkge1xuICAgIHJldHVybiB0aW1lVG9Vbml4TWlsbGkoQXJyYXkuaXNBcnJheShkKSA/IGRbZmllbGRJZHhdIDogZGMudmFsdWVBdChkLmluZGV4LCBmaWVsZElkeCksIGZvcm1hdCk7XG4gIH1cblxuICByZXR1cm4gQXJyYXkuaXNBcnJheShkKSA/IGRbZmllbGRJZHhdIDogZGMudmFsdWVBdChkLmluZGV4LCBmaWVsZElkeCk7XG59XG5cbmNsYXNzIEtlcGxlclRhYmxlPEYgZXh0ZW5kcyBGaWVsZCA9IEZpZWxkPiB7XG4gIHJlYWRvbmx5IGlkOiBzdHJpbmc7XG5cbiAgdHlwZT86IHN0cmluZztcbiAgbGFiZWw6IHN0cmluZztcbiAgY29sb3I6IFJHQkNvbG9yO1xuXG4gIC8vIGZpZWxkcyBhbmQgZGF0YVxuICBmaWVsZHM6IEZbXSA9IFtdO1xuXG4gIGRhdGFDb250YWluZXI6IERhdGFDb250YWluZXJJbnRlcmZhY2U7XG5cbiAgYWxsSW5kZXhlczogbnVtYmVyW10gPSBbXTtcbiAgZmlsdGVyZWRJbmRleDogbnVtYmVyW10gPSBbXTtcbiAgZmlsdGVyZWRJZHhDUFU/OiBudW1iZXJbXTtcbiAgZmlsdGVyZWRJbmRleEZvckRvbWFpbjogbnVtYmVyW10gPSBbXTtcbiAgZmllbGRQYWlyczogRmllbGRQYWlyW10gPSBbXTtcbiAgZ3B1RmlsdGVyOiBHcHVGaWx0ZXI7XG4gIGZpbHRlclJlY29yZD86IEZpbHRlclJlY29yZDtcbiAgZmlsdGVyUmVjb3JkQ1BVPzogRmlsdGVyUmVjb3JkO1xuICBjaGFuZ2VkRmlsdGVycz86IEZpbHRlckNoYW5nZWQ7XG5cbiAgLy8gdGFibGUtaW5qZWN0ZWQgbWV0YWRhdGFcbiAgc29ydENvbHVtbj86IHtcbiAgICAvLyBjb2x1bW4gbmFtZTogc29ydGVkIGlkeFxuICAgIFtrZXk6IHN0cmluZ106IHN0cmluZzsgLy8gQVNDRU5ESU5HIHwgREVTQ0VORElORyB8IFVOU09SVFxuICB9O1xuICBzb3J0T3JkZXI/OiBudW1iZXJbXSB8IG51bGw7XG5cbiAgcGlubmVkQ29sdW1ucz86IHN0cmluZ1tdO1xuICBzdXBwb3J0ZWRGaWx0ZXJUeXBlcz86IHN0cmluZ1tdIHwgbnVsbDtcbiAgZGlzYWJsZURhdGFPcGVyYXRpb24/OiBib29sZWFuO1xuXG4gIC8vIHRhYmxlLWluamVjdGVkIG1ldGFkYXRhXG4gIG1ldGFkYXRhOiBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xuXG4gIGdldEZpbGVQcm9jZXNzb3I/OiAoZGF0YTogYW55LCBpbnB1dEZvcm1hdD86IHN0cmluZykgPT4ge2RhdGE6IGFueTsgZm9ybWF0OiBzdHJpbmd9O1xuXG4gIGNvbnN0cnVjdG9yKHtcbiAgICBpbmZvLFxuICAgIGNvbG9yLFxuICAgIG1ldGFkYXRhLFxuICAgIHN1cHBvcnRlZEZpbHRlclR5cGVzID0gbnVsbCxcbiAgICBkaXNhYmxlRGF0YU9wZXJhdGlvbiA9IGZhbHNlXG4gIH06IHtcbiAgICBpbmZvPzogUHJvdG9EYXRhc2V0WydpbmZvJ107XG4gICAgY29sb3I6IFJHQkNvbG9yO1xuICAgIG1ldGFkYXRhPzogUHJvdG9EYXRhc2V0WydtZXRhZGF0YSddO1xuICAgIHN1cHBvcnRlZEZpbHRlclR5cGVzPzogUHJvdG9EYXRhc2V0WydzdXBwb3J0ZWRGaWx0ZXJUeXBlcyddO1xuICAgIGRpc2FibGVEYXRhT3BlcmF0aW9uPzogUHJvdG9EYXRhc2V0WydkaXNhYmxlRGF0YU9wZXJhdGlvbiddO1xuICB9KSB7XG4gICAgLy8gVE9ETyAtIHdoYXQgdG8gZG8gaWYgdmFsaWRhdGlvbiBmYWlscz8gQ2FuIGtlcGxlciBoYW5kbGUgZXhjZXB0aW9ucz9cbiAgICAvLyBjb25zdCB2YWxpZGF0ZWREYXRhID0gdmFsaWRhdGVJbnB1dERhdGEoZGF0YSk7XG4gICAgLy8gaWYgKCF2YWxpZGF0ZWREYXRhKSB7XG4gICAgLy8gICByZXR1cm4gdGhpcztcbiAgICAvLyB9XG5cbiAgICBjb25zdCBkYXRhc2V0SW5mbyA9IHtcbiAgICAgIGlkOiBnZW5lcmF0ZUhhc2hJZCg0KSxcbiAgICAgIGxhYmVsOiAnbmV3IGRhdGFzZXQnLFxuICAgICAgdHlwZTogJycsXG4gICAgICAuLi5pbmZvXG4gICAgfTtcblxuICAgIGNvbnN0IGRlZmF1bHRNZXRhZGF0YSA9IHtcbiAgICAgIGlkOiBkYXRhc2V0SW5mby5pZCxcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGZvcm1hdDogZGF0YXNldEluZm8uZm9ybWF0IHx8ICcnLFxuICAgICAgbGFiZWw6IGRhdGFzZXRJbmZvLmxhYmVsIHx8ICcnXG4gICAgfTtcblxuICAgIHRoaXMuaWQgPSBkYXRhc2V0SW5mby5pZDtcbiAgICB0aGlzLnR5cGUgPSBkYXRhc2V0SW5mby50eXBlO1xuICAgIHRoaXMubGFiZWwgPSBkYXRhc2V0SW5mby5sYWJlbDtcbiAgICB0aGlzLmNvbG9yID0gY29sb3I7XG4gICAgdGhpcy5tZXRhZGF0YSA9IHtcbiAgICAgIC4uLmRlZmF1bHRNZXRhZGF0YSxcbiAgICAgIC4uLm1ldGFkYXRhXG4gICAgfTtcblxuICAgIHRoaXMuc3VwcG9ydGVkRmlsdGVyVHlwZXMgPSBzdXBwb3J0ZWRGaWx0ZXJUeXBlcztcbiAgICB0aGlzLmRpc2FibGVEYXRhT3BlcmF0aW9uID0gZGlzYWJsZURhdGFPcGVyYXRpb247XG5cbiAgICB0aGlzLmRhdGFDb250YWluZXIgPSBjcmVhdGVEYXRhQ29udGFpbmVyKFtdKTtcbiAgICB0aGlzLmdwdUZpbHRlciA9IGdldEdwdUZpbHRlclByb3BzKFtdLCB0aGlzLmlkLCBbXSwgdW5kZWZpbmVkKTtcbiAgfVxuXG4gIGFzeW5jIGltcG9ydERhdGEoe2RhdGF9OiB7ZGF0YTogUHJvdG9EYXRhc2V0WydkYXRhJ119KSB7XG4gICAgY29uc3QgZGF0YUNvbnRhaW5lckRhdGEgPSBkYXRhLmNvbHMgPyBkYXRhLmNvbHMgOiBkYXRhLnJvd3M7XG4gICAgY29uc3QgaW5wdXREYXRhRm9ybWF0ID0gZGF0YS5jb2xzID8gRGF0YUZvcm0uQ09MU19BUlJBWSA6IERhdGFGb3JtLlJPV1NfQVJSQVk7XG5cbiAgICBjb25zdCBkYXRhQ29udGFpbmVyID0gY3JlYXRlRGF0YUNvbnRhaW5lcihkYXRhQ29udGFpbmVyRGF0YSwge1xuICAgICAgZmllbGRzOiBkYXRhLmZpZWxkcyxcbiAgICAgIGlucHV0RGF0YUZvcm1hdFxuICAgIH0pO1xuXG4gICAgY29uc3QgZmllbGRzOiBGaWVsZFtdID0gZGF0YS5maWVsZHMubWFwKChmLCBpKSA9PiAoe1xuICAgICAgLi4uZixcbiAgICAgIGZpZWxkSWR4OiBpLFxuICAgICAgaWQ6IGYubmFtZSxcbiAgICAgIGRpc3BsYXlOYW1lOiBmLmRpc3BsYXlOYW1lIHx8IGYubmFtZSxcbiAgICAgIGFuYWx5emVyVHlwZTogZi5hbmFseXplclR5cGUgfHwgQUxMX0ZJRUxEX1RZUEVTLnN0cmluZyxcbiAgICAgIGZvcm1hdDogZi5mb3JtYXQgfHwgJycsXG4gICAgICB2YWx1ZUFjY2Vzc29yOiBnZXRGaWVsZFZhbHVlQWNjZXNzb3IoZiwgaSwgZGF0YUNvbnRhaW5lcilcbiAgICB9KSk7XG5cbiAgICBjb25zdCBhbGxJbmRleGVzID0gZGF0YUNvbnRhaW5lci5nZXRQbGFpbkluZGV4KCk7XG4gICAgdGhpcy5kYXRhQ29udGFpbmVyID0gZGF0YUNvbnRhaW5lcjtcbiAgICB0aGlzLmFsbEluZGV4ZXMgPSBhbGxJbmRleGVzO1xuICAgIHRoaXMuZmlsdGVyZWRJbmRleCA9IGFsbEluZGV4ZXM7XG4gICAgdGhpcy5maWx0ZXJlZEluZGV4Rm9yRG9tYWluID0gYWxsSW5kZXhlcztcbiAgICB0aGlzLmZpZWxkUGFpcnMgPSBmaW5kUG9pbnRGaWVsZFBhaXJzKGZpZWxkcyk7XG4gICAgLy8gQHRzLWV4cGVjdC1lcnJvciBNYWtlIHN1cmUgdGhhdCBmaWVsZHMgc2F0aXNmaWVzIEYgZXh0ZW5kcyBGaWVsZFxuICAgIHRoaXMuZmllbGRzID0gZmllbGRzO1xuICAgIHRoaXMuZ3B1RmlsdGVyID0gZ2V0R3B1RmlsdGVyUHJvcHMoW10sIHRoaXMuaWQsIGZpZWxkcywgdW5kZWZpbmVkKTtcbiAgfVxuXG4gIC8qKlxuICAgKiB1cGRhdGUgdGFibGUgd2l0aCBuZXcgZGF0YVxuICAgKiBAcGFyYW0gZGF0YSAtIG5ldyBkYXRhIGUuZy4gdGhlIGFycm93IGRhdGEgd2l0aCBuZXcgYmF0Y2hlcyBsb2FkZWRcbiAgICovXG4gIGFzeW5jIHVwZGF0ZShkYXRhOiBQcm90b0RhdGFzZXRbJ2RhdGEnXSkge1xuICAgIGNvbnN0IGRhdGFDb250YWluZXJEYXRhID0gZGF0YS5jb2xzID8gZGF0YS5jb2xzIDogZGF0YS5yb3dzO1xuICAgIHRoaXMuZGF0YUNvbnRhaW5lci51cGRhdGU/LihkYXRhQ29udGFpbmVyRGF0YSk7XG4gICAgdGhpcy5hbGxJbmRleGVzID0gdGhpcy5kYXRhQ29udGFpbmVyLmdldFBsYWluSW5kZXgoKTtcbiAgICB0aGlzLmZpbHRlcmVkSW5kZXggPSB0aGlzLmFsbEluZGV4ZXM7XG4gICAgdGhpcy5maWx0ZXJlZEluZGV4Rm9yRG9tYWluID0gdGhpcy5hbGxJbmRleGVzO1xuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH1cblxuICBnZXQgbGVuZ3RoKCkge1xuICAgIHJldHVybiB0aGlzLmRhdGFDb250YWluZXIubnVtUm93cygpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBmaWVsZFxuICAgKiBAcGFyYW0gY29sdW1uTmFtZVxuICAgKi9cbiAgZ2V0Q29sdW1uRmllbGQoY29sdW1uTmFtZTogc3RyaW5nKTogRiB8IHVuZGVmaW5lZCB7XG4gICAgY29uc3QgZmllbGQgPSB0aGlzLmZpZWxkcy5maW5kKGZkID0+IGZkW0ZJRF9LRVldID09PSBjb2x1bW5OYW1lKTtcbiAgICB0aGlzLl9hc3NldEZpZWxkKGNvbHVtbk5hbWUsIGZpZWxkKTtcbiAgICByZXR1cm4gZmllbGQ7XG4gIH1cblxuICAvKipcbiAgICogR2V0IGZpZWxkSWR4XG4gICAqIEBwYXJhbSBjb2x1bW5OYW1lXG4gICAqL1xuICBnZXRDb2x1bW5GaWVsZElkeChjb2x1bW5OYW1lOiBzdHJpbmcpOiBudW1iZXIge1xuICAgIGNvbnN0IGZpZWxkSWR4ID0gdGhpcy5maWVsZHMuZmluZEluZGV4KGZkID0+IGZkW0ZJRF9LRVldID09PSBjb2x1bW5OYW1lKTtcbiAgICB0aGlzLl9hc3NldEZpZWxkKGNvbHVtbk5hbWUsIEJvb2xlYW4oZmllbGRJZHggPiAtMSkpO1xuICAgIHJldHVybiBmaWVsZElkeDtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgZGlzcGxheUZvcm1hdFxuICAgKiBAcGFyYW0gY29sdW1uTmFtZVxuICAgKi9cbiAgZ2V0Q29sdW1uRGlzcGxheUZvcm1hdChjb2x1bW5OYW1lKSB7XG4gICAgY29uc3QgZmllbGQgPSB0aGlzLmZpZWxkcy5maW5kKGZkID0+IGZkW0ZJRF9LRVldID09PSBjb2x1bW5OYW1lKTtcbiAgICB0aGlzLl9hc3NldEZpZWxkKGNvbHVtbk5hbWUsIGZpZWxkKTtcbiAgICByZXR1cm4gZmllbGQ/LmRpc3BsYXlGb3JtYXQ7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSB2YWx1ZSBvZiBhIGNlbGxcbiAgICovXG4gIGdldFZhbHVlKGNvbHVtbk5hbWU6IHN0cmluZywgcm93SWR4OiBudW1iZXIpOiBhbnkge1xuICAgIGNvbnN0IGZpZWxkID0gdGhpcy5nZXRDb2x1bW5GaWVsZChjb2x1bW5OYW1lKTtcbiAgICByZXR1cm4gZmllbGQgPyBmaWVsZC52YWx1ZUFjY2Vzc29yKHtpbmRleDogcm93SWR4fSkgOiBudWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIFVwZGF0ZXMgZXhpc3RpbmcgZmllbGQgd2l0aCBhIG5ldyBvYmplY3RcbiAgICogQHBhcmFtIGZpZWxkSWR4XG4gICAqIEBwYXJhbSBuZXdGaWVsZFxuICAgKi9cbiAgdXBkYXRlQ29sdW1uRmllbGQoZmllbGRJZHg6IG51bWJlciwgbmV3RmllbGQ6IEYpOiB2b2lkIHtcbiAgICB0aGlzLmZpZWxkcyA9IE9iamVjdC5hc3NpZ24oWy4uLnRoaXMuZmllbGRzXSwge1tmaWVsZElkeF06IG5ld0ZpZWxkfSk7XG4gIH1cblxuICAvKipcbiAgICogVXBkYXRlIGRhdGFzZXQgY29sb3IgYnkgY3VzdG9tIGNvbG9yXG4gICAqIEBwYXJhbSBuZXdDb2xvclxuICAgKi9cbiAgdXBkYXRlVGFibGVDb2xvcihuZXdDb2xvcjogUkdCQ29sb3IpOiB2b2lkIHtcbiAgICB0aGlzLmNvbG9yID0gbmV3Q29sb3I7XG4gIH1cblxuICAvKipcbiAgICogU2F2ZSBmaWx0ZXJQcm9wcyB0byBmaWVsZCBhbmQgcmV0cmlldmUgaXRcbiAgICogQHBhcmFtIGNvbHVtbk5hbWVcbiAgICovXG4gIGdldENvbHVtbkZpbHRlclByb3BzKGNvbHVtbk5hbWU6IHN0cmluZyk6IEZbJ2ZpbHRlclByb3BzJ10gfCBudWxsIHwgdW5kZWZpbmVkIHtcbiAgICBjb25zdCBmaWVsZElkeCA9IHRoaXMuZ2V0Q29sdW1uRmllbGRJZHgoY29sdW1uTmFtZSk7XG4gICAgaWYgKGZpZWxkSWR4IDwgMCkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIGNvbnN0IGZpZWxkID0gdGhpcy5maWVsZHNbZmllbGRJZHhdO1xuICAgIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZmllbGQsICdmaWx0ZXJQcm9wcycpKSB7XG4gICAgICByZXR1cm4gZmllbGQuZmlsdGVyUHJvcHM7XG4gICAgfVxuXG4gICAgY29uc3QgZmllbGREb21haW4gPSB0aGlzLmdldENvbHVtbkZpbHRlckRvbWFpbihmaWVsZCk7XG4gICAgaWYgKCFmaWVsZERvbWFpbikge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuXG4gICAgY29uc3QgZmlsdGVyUHJvcHMgPSBnZXRGaWx0ZXJQcm9wcyhmaWVsZCwgZmllbGREb21haW4pO1xuICAgIGNvbnN0IG5ld0ZpZWxkID0ge1xuICAgICAgLi4uZmllbGQsXG4gICAgICBmaWx0ZXJQcm9wc1xuICAgIH07XG5cbiAgICB0aGlzLnVwZGF0ZUNvbHVtbkZpZWxkKGZpZWxkSWR4LCBuZXdGaWVsZCk7XG5cbiAgICByZXR1cm4gZmlsdGVyUHJvcHM7XG4gIH1cblxuICAvKipcbiAgICogQXBwbHkgZmlsdGVycyB0byBkYXRhc2V0LCByZXR1cm4gdGhlIGZpbHRlcmVkIGRhdGFzZXQgd2l0aCB1cGRhdGVkIGBncHVGaWx0ZXJgLCBgZmlsdGVyUmVjb3JkYCwgYGZpbHRlcmVkSW5kZXhgLCBgZmlsdGVyZWRJbmRleEZvckRvbWFpbmBcbiAgICogQHBhcmFtIGZpbHRlcnNcbiAgICogQHBhcmFtIGxheWVyc1xuICAgKiBAcGFyYW0gb3B0XG4gICAqL1xuICBmaWx0ZXJUYWJsZShmaWx0ZXJzOiBGaWx0ZXJbXSwgbGF5ZXJzOiBMYXllcltdLCBvcHQ/OiBGaWx0ZXJEYXRhc2V0T3B0KTogS2VwbGVyVGFibGU8RmllbGQ+IHtcbiAgICBjb25zdCB7ZGF0YUNvbnRhaW5lciwgaWQ6IGRhdGFJZCwgZmlsdGVyUmVjb3JkOiBvbGRGaWx0ZXJSZWNvcmQsIGZpZWxkc30gPSB0aGlzO1xuXG4gICAgLy8gaWYgdGhlcmUgaXMgbm8gZmlsdGVyc1xuICAgIGNvbnN0IGZpbHRlclJlY29yZCA9IGdldEZpbHRlclJlY29yZChkYXRhSWQsIGZpbHRlcnMsIG9wdCB8fCB7fSk7XG5cbiAgICB0aGlzLmZpbHRlclJlY29yZCA9IGZpbHRlclJlY29yZDtcbiAgICB0aGlzLmdwdUZpbHRlciA9IGdldEdwdUZpbHRlclByb3BzKGZpbHRlcnMsIGRhdGFJZCwgZmllbGRzLCB0aGlzLmdwdUZpbHRlcik7XG5cbiAgICB0aGlzLmNoYW5nZWRGaWx0ZXJzID0gZGlmZkZpbHRlcnMoZmlsdGVyUmVjb3JkLCBvbGRGaWx0ZXJSZWNvcmQpO1xuXG4gICAgaWYgKCFmaWx0ZXJzLmxlbmd0aCkge1xuICAgICAgdGhpcy5maWx0ZXJlZEluZGV4ID0gdGhpcy5hbGxJbmRleGVzO1xuICAgICAgdGhpcy5maWx0ZXJlZEluZGV4Rm9yRG9tYWluID0gdGhpcy5hbGxJbmRleGVzO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuXG4gICAgLy8gZ2VuZXJhdGUgMiBzZXRzIG9mIGZpbHRlciByZXN1bHRcbiAgICAvLyBmaWx0ZXJlZEluZGV4IHVzZWQgdG8gY2FsY3VsYXRlIGxheWVyIGRhdGFcbiAgICAvLyBmaWx0ZXJlZEluZGV4Rm9yRG9tYWluIHVzZWQgdG8gY2FsY3VsYXRlIGxheWVyIERvbWFpblxuICAgIGNvbnN0IHNob3VsZENhbERvbWFpbiA9IEJvb2xlYW4odGhpcy5jaGFuZ2VkRmlsdGVycy5keW5hbWljRG9tYWluKTtcbiAgICBjb25zdCBzaG91bGRDYWxJbmRleCA9IEJvb2xlYW4odGhpcy5jaGFuZ2VkRmlsdGVycy5jcHUpO1xuXG4gICAgbGV0IGZpbHRlclJlc3VsdDogRmlsdGVyUmVzdWx0ID0ge307XG4gICAgaWYgKHNob3VsZENhbERvbWFpbiB8fCBzaG91bGRDYWxJbmRleCkge1xuICAgICAgY29uc3QgZHluYW1pY0RvbWFpbkZpbHRlcnMgPSBzaG91bGRDYWxEb21haW4gPyBmaWx0ZXJSZWNvcmQuZHluYW1pY0RvbWFpbiA6IG51bGw7XG4gICAgICBjb25zdCBjcHVGaWx0ZXJzID0gc2hvdWxkQ2FsSW5kZXggPyBmaWx0ZXJSZWNvcmQuY3B1IDogbnVsbDtcblxuICAgICAgY29uc3QgZmlsdGVyRnVuY3MgPSBmaWx0ZXJzLnJlZHVjZSgoYWNjLCBmaWx0ZXIpID0+IHtcbiAgICAgICAgY29uc3QgZmllbGRJbmRleCA9IGdldERhdGFzZXRGaWVsZEluZGV4Rm9yRmlsdGVyKHRoaXMuaWQsIGZpbHRlcik7XG4gICAgICAgIGNvbnN0IGZpZWxkID0gZmllbGRJbmRleCAhPT0gLTEgPyBmaWVsZHNbZmllbGRJbmRleF0gOiBudWxsO1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgLi4uYWNjLFxuICAgICAgICAgIFtmaWx0ZXIuaWRdOiBnZXRGaWx0ZXJGdW5jdGlvbihmaWVsZCwgdGhpcy5pZCwgZmlsdGVyLCBsYXllcnMsIGRhdGFDb250YWluZXIpXG4gICAgICAgIH07XG4gICAgICB9LCB7fSk7XG5cbiAgICAgIGZpbHRlclJlc3VsdCA9IGZpbHRlckRhdGFCeUZpbHRlclR5cGVzKFxuICAgICAgICB7ZHluYW1pY0RvbWFpbkZpbHRlcnMsIGNwdUZpbHRlcnMsIGZpbHRlckZ1bmNzfSxcbiAgICAgICAgZGF0YUNvbnRhaW5lclxuICAgICAgKTtcbiAgICB9XG5cbiAgICB0aGlzLmZpbHRlcmVkSW5kZXggPSBmaWx0ZXJSZXN1bHQuZmlsdGVyZWRJbmRleCB8fCB0aGlzLmZpbHRlcmVkSW5kZXg7XG4gICAgdGhpcy5maWx0ZXJlZEluZGV4Rm9yRG9tYWluID1cbiAgICAgIGZpbHRlclJlc3VsdC5maWx0ZXJlZEluZGV4Rm9yRG9tYWluIHx8IHRoaXMuZmlsdGVyZWRJbmRleEZvckRvbWFpbjtcblxuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgLyoqXG4gICAqIEFwcGx5IGZpbHRlcnMgdG8gYSBkYXRhc2V0IGFsbCBvbiBDUFUsIGFzc2lnbiB0byBgZmlsdGVyZWRJZHhDUFVgLCBgZmlsdGVyUmVjb3JkQ1BVYFxuICAgKiBAcGFyYW0gZmlsdGVyc1xuICAgKiBAcGFyYW0gbGF5ZXJzXG4gICAqL1xuICBmaWx0ZXJUYWJsZUNQVShmaWx0ZXJzOiBGaWx0ZXJbXSwgbGF5ZXJzOiBMYXllcltdKTogS2VwbGVyVGFibGU8RmllbGQ+IHtcbiAgICBjb25zdCBvcHQgPSB7XG4gICAgICBjcHVPbmx5OiB0cnVlLFxuICAgICAgaWdub3JlRG9tYWluOiB0cnVlXG4gICAgfTtcblxuICAgIC8vIG5vIGZpbHRlclxuICAgIGlmICghZmlsdGVycy5sZW5ndGgpIHtcbiAgICAgIHRoaXMuZmlsdGVyZWRJZHhDUFUgPSB0aGlzLmFsbEluZGV4ZXM7XG4gICAgICB0aGlzLmZpbHRlclJlY29yZENQVSA9IGdldEZpbHRlclJlY29yZCh0aGlzLmlkLCBmaWx0ZXJzLCBvcHQpO1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuXG4gICAgLy8gbm8gZ3B1IGZpbHRlclxuICAgIGlmICghZmlsdGVycy5maW5kKGYgPT4gZi5ncHUpKSB7XG4gICAgICB0aGlzLmZpbHRlcmVkSWR4Q1BVID0gdGhpcy5maWx0ZXJlZEluZGV4O1xuICAgICAgdGhpcy5maWx0ZXJSZWNvcmRDUFUgPSBnZXRGaWx0ZXJSZWNvcmQodGhpcy5pZCwgZmlsdGVycywgb3B0KTtcbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cblxuICAgIC8vIG1ha2UgYSBjb3B5IGZvciBjcHUgZmlsdGVyaW5nXG4gICAgY29uc3QgY29waWVkID0gY29weVRhYmxlKHRoaXMpO1xuXG4gICAgY29waWVkLmZpbHRlclJlY29yZCA9IHRoaXMuZmlsdGVyUmVjb3JkQ1BVO1xuICAgIGNvcGllZC5maWx0ZXJlZEluZGV4ID0gdGhpcy5maWx0ZXJlZElkeENQVSB8fCBbXTtcblxuICAgIGNvbnN0IGZpbHRlcmVkID0gY29waWVkLmZpbHRlclRhYmxlKGZpbHRlcnMsIGxheWVycywgb3B0KTtcblxuICAgIHRoaXMuZmlsdGVyZWRJZHhDUFUgPSBmaWx0ZXJlZC5maWx0ZXJlZEluZGV4O1xuICAgIHRoaXMuZmlsdGVyUmVjb3JkQ1BVID0gZmlsdGVyZWQuZmlsdGVyUmVjb3JkO1xuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH1cblxuICAvKipcbiAgICogQ2FsY3VsYXRlIGZpZWxkIGRvbWFpbiBiYXNlZCBvbiBmaWVsZCB0eXBlIGFuZCBkYXRhXG4gICAqIGZvciBGaWx0ZXJcbiAgICovXG4gIGdldENvbHVtbkZpbHRlckRvbWFpbihmaWVsZDogRik6IEZpZWxkRG9tYWluIHtcbiAgICBjb25zdCB7ZGF0YUNvbnRhaW5lcn0gPSB0aGlzO1xuICAgIGNvbnN0IHt2YWx1ZUFjY2Vzc29yfSA9IGZpZWxkO1xuXG4gICAgbGV0IGRvbWFpbjtcblxuICAgIHN3aXRjaCAoZmllbGQudHlwZSkge1xuICAgICAgY2FzZSBBTExfRklFTERfVFlQRVMucmVhbDpcbiAgICAgIGNhc2UgQUxMX0ZJRUxEX1RZUEVTLmludGVnZXI6XG4gICAgICAgIC8vIGNhbGN1bGF0ZSBkb21haW4gYW5kIHN0ZXBcbiAgICAgICAgcmV0dXJuIGdldE51bWVyaWNGaWVsZERvbWFpbihkYXRhQ29udGFpbmVyLCB2YWx1ZUFjY2Vzc29yKTtcblxuICAgICAgY2FzZSBBTExfRklFTERfVFlQRVMuYm9vbGVhbjpcbiAgICAgICAgcmV0dXJuIHtkb21haW46IFt0cnVlLCBmYWxzZV19O1xuXG4gICAgICBjYXNlIEFMTF9GSUVMRF9UWVBFUy5zdHJpbmc6XG4gICAgICBjYXNlIEFMTF9GSUVMRF9UWVBFUy5oMzpcbiAgICAgIGNhc2UgQUxMX0ZJRUxEX1RZUEVTLmRhdGU6XG4gICAgICAgIGRvbWFpbiA9IGdldE9yZGluYWxEb21haW4oZGF0YUNvbnRhaW5lciwgdmFsdWVBY2Nlc3Nvcik7XG4gICAgICAgIHJldHVybiB7ZG9tYWlufTtcblxuICAgICAgY2FzZSBBTExfRklFTERfVFlQRVMudGltZXN0YW1wOlxuICAgICAgICByZXR1cm4gZ2V0VGltZXN0YW1wRmllbGREb21haW4oZGF0YUNvbnRhaW5lciwgdmFsdWVBY2Nlc3Nvcik7XG5cbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybiB7ZG9tYWluOiBnZXRPcmRpbmFsRG9tYWluKGRhdGFDb250YWluZXIsIHZhbHVlQWNjZXNzb3IpfTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogIEdldCB0aGUgZG9tYWluIG9mIHRoaXMgY29sdW1uIGJhc2VkIG9uIHNjYWxlIHR5cGVcbiAgICovXG4gIGdldENvbHVtbkxheWVyRG9tYWluKGZpZWxkOiBGLCBzY2FsZVR5cGU6IHN0cmluZyk6IG51bWJlcltdIHwgc3RyaW5nW10gfCBbbnVtYmVyLCBudW1iZXJdIHwgbnVsbCB7XG4gICAgY29uc3Qge2RhdGFDb250YWluZXIsIGZpbHRlcmVkSW5kZXhGb3JEb21haW59ID0gdGhpcztcblxuICAgIGlmICghU0NBTEVfVFlQRVNbc2NhbGVUeXBlXSkge1xuICAgICAgQ29uc29sZS5lcnJvcihgc2NhbGUgdHlwZSAke3NjYWxlVHlwZX0gbm90IHN1cHBvcnRlZGApO1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuXG4gICAgY29uc3Qge3ZhbHVlQWNjZXNzb3J9ID0gZmllbGQ7XG4gICAgY29uc3QgaW5kZXhWYWx1ZUFjY2Vzc29yID0gaSA9PiB2YWx1ZUFjY2Vzc29yKHtpbmRleDogaX0pO1xuICAgIGNvbnN0IHNvcnRGdW5jdGlvbiA9IGdldFNvcnRpbmdGdW5jdGlvbihmaWVsZC50eXBlKTtcblxuICAgIHN3aXRjaCAoc2NhbGVUeXBlKSB7XG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLm9yZGluYWw6XG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLmN1c3RvbU9yZGluYWw6XG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLnBvaW50OlxuICAgICAgICAvLyBkbyBub3QgcmVjYWxjdWxhdGUgb3JkaW5hbCBkb21haW4gYmFzZWQgb24gZmlsdGVyZWQgZGF0YVxuICAgICAgICAvLyBkb24ndCBuZWVkIHRvIHVwZGF0ZSBvcmRpbmFsIGRvbWFpbiBldmVyeSB0aW1lXG4gICAgICAgIHJldHVybiBnZXRPcmRpbmFsRG9tYWluKGRhdGFDb250YWluZXIsIHZhbHVlQWNjZXNzb3IpO1xuXG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLnF1YW50aWxlOlxuICAgICAgICByZXR1cm4gZ2V0UXVhbnRpbGVEb21haW4oZmlsdGVyZWRJbmRleEZvckRvbWFpbiwgaW5kZXhWYWx1ZUFjY2Vzc29yLCBzb3J0RnVuY3Rpb24pO1xuXG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLmxvZzpcbiAgICAgICAgcmV0dXJuIGdldExvZ0RvbWFpbihmaWx0ZXJlZEluZGV4Rm9yRG9tYWluLCBpbmRleFZhbHVlQWNjZXNzb3IpO1xuXG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLnF1YW50aXplOlxuICAgICAgY2FzZSBTQ0FMRV9UWVBFUy5saW5lYXI6XG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLnNxcnQ6XG4gICAgICBjYXNlIFNDQUxFX1RZUEVTLmN1c3RvbTpcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybiBnZXRMaW5lYXJEb21haW4oZmlsdGVyZWRJbmRleEZvckRvbWFpbiwgaW5kZXhWYWx1ZUFjY2Vzc29yKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogR2V0IGEgc2FtcGxlIG9mIHJvd3MgdG8gY2FsY3VsYXRlIGxheWVyIGJvdW5kYXJpZX