kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
545 lines (535 loc) • 82.2 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.pointVisConfigs = exports.pointRequiredColumns = exports.pointPosAccessor = exports.pointOptionalColumns = exports.geojsonRequiredColumns = exports.geojsonPosAccessor = exports.geoarrowRequiredColumns = exports.geoarrowPosAccessor = exports["default"] = exports.COLUMN_MODE_POINTS = exports.COLUMN_MODE_GEOJSON = exports.COLUMN_MODE_GEOARROW = void 0;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _extensions = require("@deck.gl/extensions");
var _layers = require("@deck.gl/layers");
var _deckglArrowLayers = require("@kepler.gl/deckgl-arrow-layers");
var _deckglLayers = require("@kepler.gl/deckgl-layers");
var _baseLayer = _interopRequireDefault(require("../base-layer"));
var _utils = require("@kepler.gl/utils");
var _pointLayerIcon = _interopRequireDefault(require("./point-layer-icon"));
var _constants = require("@kepler.gl/constants");
var _layerTextLabel = require("../layer-text-label");
var _layerUtils = require("../layer-utils");
var _geojsonUtils = require("../geojson-layer/geojson-utils");
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2["default"])(o), (0, _possibleConstructorReturn2["default"])(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2["default"])(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _superPropGet(t, e, r, o) { var p = (0, _get2["default"])((0, _getPrototypeOf2["default"])(1 & o ? t.prototype : t), e, r); return 2 & o ? function (t) { return p.apply(r, t); } : p; }
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
var pointPosAccessor = exports.pointPosAccessor = function pointPosAccessor(_ref) {
var lat = _ref.lat,
lng = _ref.lng,
altitude = _ref.altitude;
return function (dc) {
return function (d) {
return [dc.valueAt(d.index, lng.fieldIdx), dc.valueAt(d.index, lat.fieldIdx), altitude && altitude.fieldIdx > -1 ? dc.valueAt(d.index, altitude.fieldIdx) : 0];
};
};
};
var geojsonPosAccessor = exports.geojsonPosAccessor = function geojsonPosAccessor(_ref2) {
var geojson = _ref2.geojson;
return function (d) {
return d[geojson.fieldIdx];
};
};
var geoarrowPosAccessor = exports.geoarrowPosAccessor = function geoarrowPosAccessor(_ref3) {
var geoarrow = _ref3.geoarrow;
return function (dataContainer) {
return function (d) {
var row = dataContainer.valueAt(d.index, geoarrow.fieldIdx);
return [row.get(0), row.get(1), 0];
};
};
};
var COLUMN_MODE_POINTS = exports.COLUMN_MODE_POINTS = 'points';
var COLUMN_MODE_GEOJSON = exports.COLUMN_MODE_GEOJSON = 'geojson';
var COLUMN_MODE_GEOARROW = exports.COLUMN_MODE_GEOARROW = 'geoarrow';
var pointRequiredColumns = exports.pointRequiredColumns = ['lat', 'lng'];
var pointOptionalColumns = exports.pointOptionalColumns = ['altitude', 'neighbors'];
var geojsonRequiredColumns = exports.geojsonRequiredColumns = ['geojson'];
var geoarrowRequiredColumns = exports.geoarrowRequiredColumns = ['geoarrow'];
var SUPPORTED_COLUMN_MODES = [{
key: COLUMN_MODE_POINTS,
label: 'Point Columns',
requiredColumns: pointRequiredColumns,
optionalColumns: pointOptionalColumns
}, {
key: COLUMN_MODE_GEOJSON,
label: 'GeoJSON Feature',
requiredColumns: geojsonRequiredColumns,
verifyField: function verifyField(f) {
return !(0, _layerUtils.isGeoArrowPointField)(f);
}
}, {
key: COLUMN_MODE_GEOARROW,
label: 'Geoarrow Points',
requiredColumns: geoarrowRequiredColumns,
verifyField: function verifyField(f) {
return (0, _layerUtils.isGeoArrowPointField)(f);
}
}];
var DEFAULT_COLUMN_MODE = COLUMN_MODE_POINTS;
var brushingExtension = new _extensions.BrushingExtension();
var arrowCPUFilterExtension = new _deckglLayers.FilterArrowExtension();
function pushPointPosition(data, pos, index, neighbors) {
if (pos.every(Number.isFinite)) {
data.push(_objectSpread({
position: pos,
// index is important for filter
index: index
}, neighbors ? {
neighbors: neighbors
} : {}));
}
}
var pointVisConfigs = exports.pointVisConfigs = {
radius: 'radius',
fixedRadius: 'fixedRadius',
opacity: 'opacity',
outline: 'outline',
thickness: 'thickness',
strokeColor: 'strokeColor',
colorRange: 'colorRange',
strokeColorRange: 'strokeColorRange',
radiusRange: 'radiusRange',
filled: _objectSpread(_objectSpread({}, _constants.LAYER_VIS_CONFIGS.filled), {}, {
type: 'boolean',
label: 'layer.fillColor',
defaultValue: true,
property: 'filled'
}),
billboard: 'billboard',
allowHover: 'allowHover',
showNeighborOnHover: 'showNeighborOnHover',
showHighlightColor: 'showHighlightColor'
};
var PointLayer = exports["default"] = /*#__PURE__*/function (_Layer) {
function PointLayer(props) {
var _this;
(0, _classCallCheck2["default"])(this, PointLayer);
_this = _callSuper(this, PointLayer, [props]);
(0, _defineProperty2["default"])(_this, "dataToFeature", []);
(0, _defineProperty2["default"])(_this, "dataContainer", null);
(0, _defineProperty2["default"])(_this, "geoArrowVector", undefined);
/*
* CPU filtering an arrow table by values and assembling a partial copy of the raw table is expensive
* so we will use filteredIndex to create an attribute e.g. filteredIndex [0|1] for GPU filtering
* in deck.gl layer, see: FilterArrowExtension in @kepler.gl/deckgl-layers.
* Note that this approach can create visible lags in case of a lot of discarted geometry.
*/
(0, _defineProperty2["default"])(_this, "filteredIndex", null);
(0, _defineProperty2["default"])(_this, "filteredIndexTrigger", []);
_this.registerVisConfig(pointVisConfigs);
_this.getPositionAccessor = function (dataContainer) {
switch (_this.config.columnMode) {
case COLUMN_MODE_GEOARROW:
return geoarrowPosAccessor(_this.config.columns)(dataContainer);
case COLUMN_MODE_GEOJSON:
return geojsonPosAccessor(_this.config.columns);
default:
// COLUMN_MODE_POINTS
return pointPosAccessor(_this.config.columns)(dataContainer);
}
};
return _this;
}
(0, _inherits2["default"])(PointLayer, _Layer);
return (0, _createClass2["default"])(PointLayer, [{
key: "type",
get: function get() {
return 'point';
}
}, {
key: "isAggregated",
get: function get() {
return false;
}
}, {
key: "layerIcon",
get: function get() {
return _pointLayerIcon["default"];
}
}, {
key: "optionalColumns",
get: function get() {
return pointOptionalColumns;
}
}, {
key: "columnPairs",
get: function get() {
return this.defaultPointColumnPairs;
}
}, {
key: "supportedColumnModes",
get: function get() {
return SUPPORTED_COLUMN_MODES;
}
}, {
key: "noneLayerDataAffectingProps",
get: function get() {
return [].concat((0, _toConsumableArray2["default"])(_superPropGet(PointLayer, "noneLayerDataAffectingProps", this, 1)), ['radius']);
}
}, {
key: "visualChannels",
get: function get() {
return {
color: _objectSpread(_objectSpread({}, _superPropGet(PointLayer, "visualChannels", this, 1).color), {}, {
accessor: 'getFillColor',
condition: function condition(config) {
return config.visConfig.filled;
},
defaultValue: function defaultValue(config) {
return config.color;
}
}),
strokeColor: {
property: 'strokeColor',
key: 'strokeColor',
field: 'strokeColorField',
scale: 'strokeColorScale',
domain: 'strokeColorDomain',
range: 'strokeColorRange',
channelScaleType: _constants.CHANNEL_SCALES.color,
accessor: 'getLineColor',
condition: function condition(config) {
return config.visConfig.outline;
},
defaultValue: function defaultValue(config) {
return config.visConfig.strokeColor || config.color;
}
},
size: _objectSpread(_objectSpread({}, _superPropGet(PointLayer, "visualChannels", this, 1).size), {}, {
property: 'radius',
range: 'radiusRange',
fixed: 'fixedRadius',
channelScaleType: 'radius',
accessor: 'getRadius',
defaultValue: 1
})
};
}
}, {
key: "setInitialLayerConfig",
value: function setInitialLayerConfig(dataset) {
if (!dataset.dataContainer.numRows()) {
return this;
}
var defaultColorField = (0, _utils.findDefaultColorField)(dataset);
if (defaultColorField) {
this.updateLayerConfig({
colorField: defaultColorField
});
this.updateLayerVisualChannel(dataset, 'color');
}
return this;
}
}, {
key: "getDefaultLayerConfig",
value: function getDefaultLayerConfig(props) {
var _props$columnMode;
var defaultLayerConfig = _superPropGet(PointLayer, "getDefaultLayerConfig", this, 3)([props !== null && props !== void 0 ? props : {}]);
return _objectSpread(_objectSpread({}, defaultLayerConfig), {}, {
columnMode: (_props$columnMode = props === null || props === void 0 ? void 0 : props.columnMode) !== null && _props$columnMode !== void 0 ? _props$columnMode : DEFAULT_COLUMN_MODE,
// add stroke color visual channel
strokeColorField: null,
strokeColorDomain: [0, 1],
strokeColorScale: 'quantile',
colorUI: _objectSpread(_objectSpread({}, defaultLayerConfig.colorUI), {}, {
strokeColorRange: _constants.DEFAULT_COLOR_UI
})
});
}
}, {
key: "calculateDataAttribute",
value: function calculateDataAttribute(_ref4, getPosition) {
var _this2 = this;
var filteredIndex = _ref4.filteredIndex,
dataContainer = _ref4.dataContainer;
var columnMode = this.config.columnMode;
// 1) COLUMN_MODE_GEOARROW - when we have a geoarrow point column
// 2) COLUMN_MODE_POINTS + ArrowDataContainer > create geoarrow point column on the fly
if (dataContainer instanceof _utils.ArrowDataContainer && (columnMode === COLUMN_MODE_GEOARROW || columnMode === COLUMN_MODE_POINTS)) {
this.filteredIndex = (0, _layerUtils.getFilteredIndex)(dataContainer.numRows(), filteredIndex, this.filteredIndex);
this.filteredIndexTrigger = filteredIndex;
if (this.config.columnMode === COLUMN_MODE_GEOARROW) {
this.geoArrowVector = dataContainer.getColumn(this.config.columns.geoarrow.fieldIdx);
} else {
// generate a column compatible with geoarrow point
this.geoArrowVector = (0, _layerUtils.createGeoArrowPointVector)(dataContainer, getPosition);
}
return dataContainer.getTable();
}
// we don't need these in non-Arrow modes atm.
this.geoArrowVector = undefined;
this.filteredIndex = null;
var data = [];
var _loop = function _loop() {
var index = filteredIndex[i];
var neighbors;
if (_this2.config.columnMode === COLUMN_MODE_POINTS) {
var _this2$config$columns;
if ((_this2$config$columns = _this2.config.columns.neighbors) !== null && _this2$config$columns !== void 0 && _this2$config$columns.value) {
var fieldIdx = _this2.config.columns.neighbors.fieldIdx;
neighbors = Array.isArray(dataContainer.valueAt(index, fieldIdx)) ? dataContainer.valueAt(index, fieldIdx) : [];
}
var pos = getPosition({
index: index
});
// if doesn't have point lat or lng, do not add the point
// deck.gl can't handle position = null
pushPointPosition(data, pos, index, neighbors);
} else {
// COLUMN_MODE_GEOJSON mode - point from geojson coordinates
var coordinates = _this2.dataToFeature[i];
// if multi points
if (coordinates && Array.isArray(coordinates[0])) {
coordinates.forEach(function (coord) {
pushPointPosition(data, coord, index);
});
} else if (coordinates && Number.isFinite(coordinates[0])) {
pushPointPosition(data, coordinates, index);
}
}
};
for (var i = 0; i < filteredIndex.length; i++) {
_loop();
}
return data;
}
}, {
key: "formatLayerData",
value: function formatLayerData(datasets, oldLayerData) {
var _this3 = this;
if (this.config.dataId === null) {
return {};
}
var textLabel = this.config.textLabel;
var _datasets$this$config = datasets[this.config.dataId],
gpuFilter = _datasets$this$config.gpuFilter,
dataContainer = _datasets$this$config.dataContainer;
var _this$updateData = this.updateData(datasets, oldLayerData),
data = _this$updateData.data,
triggerChanged = _this$updateData.triggerChanged;
var getPosition = function getPosition(d) {
return d.position;
};
// get all distinct characters in the text labels
var textLabels = (0, _layerTextLabel.formatTextLabelData)({
textLabel: textLabel,
triggerChanged: triggerChanged,
oldLayerData: oldLayerData,
data: data,
dataContainer: dataContainer,
filteredIndex: this.filteredIndex
});
var accessors = this.getAttributeAccessors({
dataContainer: dataContainer
});
var isFilteredAccessor = function isFilteredAccessor(data) {
return _this3.filteredIndex ? _this3.filteredIndex[data.index] : 1;
};
return _objectSpread({
data: data,
getPosition: getPosition,
getFilterValue: gpuFilter.filterValueAccessor(dataContainer)(),
getFiltered: isFilteredAccessor,
textLabels: textLabels
}, accessors);
}
/* eslint-enable complexity */
}, {
key: "updateLayerMeta",
value: function updateLayerMeta(dataset) {
var dataContainer = dataset.dataContainer;
this.dataContainer = dataContainer;
if (this.config.columnMode === COLUMN_MODE_GEOJSON) {
var getFeature = this.getPositionAccessor();
this.dataToFeature = (0, _geojsonUtils.getGeojsonPointDataMaps)(dataContainer, getFeature);
} else if (this.config.columnMode === COLUMN_MODE_GEOARROW) {
var boundsFromMetadata = (0, _layerUtils.getBoundsFromArrowMetadata)(this.config.columns.geoarrow, dataContainer);
if (boundsFromMetadata) {
this.updateMeta({
bounds: boundsFromMetadata
});
} else {
var getPosition = this.getPositionAccessor(dataContainer);
var bounds = this.getPointsBounds(dataContainer, getPosition);
this.updateMeta({
bounds: bounds
});
}
} else {
var _getPosition = this.getPositionAccessor(dataContainer);
var _bounds = this.getPointsBounds(dataContainer, _getPosition);
this.updateMeta({
bounds: _bounds
});
}
}
// eslint-disable-next-line complexity
}, {
key: "renderLayer",
value: function renderLayer(opts) {
var _this$config$columns$;
var data = opts.data,
gpuFilter = opts.gpuFilter,
objectHovered = opts.objectHovered,
mapState = opts.mapState,
interactionConfig = opts.interactionConfig,
dataset = opts.dataset;
// if no field size is defined we need to pass fixed radius = false
var fixedRadius = this.config.visConfig.fixedRadius && Boolean(this.config.sizeField);
var radiusScale = this.getRadiusScaleByZoom(mapState, fixedRadius);
var layerProps = _objectSpread({
stroked: this.config.visConfig.outline,
filled: this.config.visConfig.filled,
lineWidthScale: this.config.visConfig.thickness,
billboard: this.config.visConfig.billboard,
radiusScale: radiusScale
}, this.config.visConfig.fixedRadius ? {} : {
radiusMaxPixels: 500
});
var updateTriggers = _objectSpread({
getPosition: this.config.columns,
getFilterValue: gpuFilter.filterValueUpdateTriggers,
getFiltered: this.filteredIndexTrigger
}, this.getVisualChannelUpdateTriggers());
var useArrowLayer = Boolean(this.geoArrowVector);
var defaultLayerProps = this.getDefaultDeckLayerProps(opts);
var brushingProps = this.getBrushingExtensionProps(interactionConfig);
var getPixelOffset = (0, _layerTextLabel.getTextOffsetByRadius)(radiusScale, data.getRadius, mapState);
var extensions = [].concat((0, _toConsumableArray2["default"])(defaultLayerProps.extensions), [brushingExtension], (0, _toConsumableArray2["default"])(useArrowLayer ? [arrowCPUFilterExtension] : []));
var sharedProps = _objectSpread({
getFilterValue: data.getFilterValue,
extensions: extensions,
filterRange: defaultLayerProps.filterRange,
visible: defaultLayerProps.visible
}, brushingProps);
var hoveredObject = this.hasHoveredObject(objectHovered);
var _this$config$visConfi = this.config.visConfig,
showNeighborOnHover = _this$config$visConfi.showNeighborOnHover,
allowHover = _this$config$visConfi.allowHover;
var neighborsData = [];
if (allowHover && showNeighborOnHover && hoveredObject) {
// find neighbors
neighborsData = (0, _layerUtils.getNeighbors)(this.config.columns.neighbors, dataset.dataContainer, hoveredObject.index, this.getPositionAccessor(dataset.dataContainer));
}
var ScatterplotLayerClass = _layers.ScatterplotLayer;
var deckLayerData = data.data;
var getPosition = data.getPosition;
if (useArrowLayer) {
ScatterplotLayerClass = _deckglArrowLayers.GeoArrowScatterplotLayer;
deckLayerData = dataset.dataContainer.getTable();
getPosition = this.geoArrowVector;
}
return [
// @ts-expect-error
new ScatterplotLayerClass(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({}, defaultLayerProps), brushingProps), layerProps), data), {}, {
data: deckLayerData,
getPosition: getPosition,
parameters: {
// circles will be flat on the map when the altitude column is not used
depthTest: ((_this$config$columns$ = this.config.columns.altitude) === null || _this$config$columns$ === void 0 ? void 0 : _this$config$columns$.fieldIdx) > -1
},
lineWidthUnits: 'pixels',
updateTriggers: updateTriggers,
extensions: extensions,
opacity: hoveredObject && showNeighborOnHover ? 0.2 : this.config.visConfig.opacity,
pickable: allowHover,
autoHighlight: false
}))].concat((0, _toConsumableArray2["default"])(hoveredObject ? [new _layers.ScatterplotLayer(_objectSpread(_objectSpread(_objectSpread({}, this.getDefaultHoverLayerProps()), layerProps), {}, {
visible: defaultLayerProps.visible,
data: [].concat((0, _toConsumableArray2["default"])(neighborsData), [hoveredObject]),
getLineColor: this.config.visConfig.showHighlightColor ? this.config.highlightColor : data.getLineColor,
getFillColor: this.config.visConfig.showHighlightColor ? this.config.highlightColor : data.getFillColor,
getRadius: data.getRadius,
getPosition: data.getPosition
}))] : []), (0, _toConsumableArray2["default"])(this.renderTextLabelLayer({
getPosition: getPosition,
sharedProps: sharedProps,
getPixelOffset: getPixelOffset,
updateTriggers: updateTriggers,
getFiltered: data.getFiltered
}, this.geoArrowVector ? _objectSpread(_objectSpread({}, opts), {}, {
data: _objectSpread(_objectSpread({}, opts.data), {}, {
getPosition: getPosition
})
}) : opts)));
}
}, {
key: "hasHoveredObject",
value: function hasHoveredObject(objectInfo) {
if ((0, _layerUtils.isLayerHoveredFromArrow)(objectInfo, this.id) && objectInfo.index >= 0 && this.dataContainer) {
return {
index: objectInfo.index,
position: this.getPositionAccessor(this.dataContainer)(objectInfo)
};
}
return _superPropGet(PointLayer, "hasHoveredObject", this, 3)([objectInfo]);
}
}, {
key: "getHoverData",
value: function getHoverData(object, dataContainer, fields, animationConfig, hoverInfo) {
// for arrow format, `object` is the Arrow row object Proxy,
// and index is passed in `hoverInfo`.
var index = this.geoArrowVector ? hoverInfo === null || hoverInfo === void 0 ? void 0 : hoverInfo.index : object.index;
if (index >= 0) {
return dataContainer.row(index);
}
return null;
}
}], [{
key: "findDefaultLayerProps",
value: function findDefaultLayerProps(dataset) {
var _dataset$fieldPairs = dataset.fieldPairs,
fieldPairs = _dataset$fieldPairs === void 0 ? [] : _dataset$fieldPairs,
type = dataset.type,
label = dataset.label;
var props = [];
if (type === _constants.DatasetType.VECTOR_TILE) {
return {
props: props
};
}
// Make layer for each pair
fieldPairs.forEach(function (pair) {
var latField = pair.pair.lat;
var prop = {
label:
// Skip the generic 'point' fallback from findPointFieldPairs and use the dataset label instead
pair.defaultName && pair.defaultName !== 'point' ? pair.defaultName : typeof label === 'string' && label.replace(/\.[^/.]+$/, '') || 'Point'
};
// default layer color for begintrip and dropoff point
if (latField.value in _constants.DEFAULT_LAYER_COLOR) {
prop.color = (0, _utils.hexToRgb)(_constants.DEFAULT_LAYER_COLOR[latField.value]);
}
// set the first layer to be visible
if (props.length === 0) {
prop.isVisible = true;
}
// @ts-expect-error logically separate geojson column type?
prop.columns = (0, _layerUtils.assignPointPairToLayerColumn)(pair, true);
props.push(prop);
});
var altProps = (0, _layerUtils.getGeoArrowPointLayerProps)(dataset);
return {
props: props,
altProps: altProps
};
}
}]);
}(_baseLayer["default"]);
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfZXh0ZW5zaW9ucyIsInJlcXVpcmUiLCJfbGF5ZXJzIiwiX2RlY2tnbEFycm93TGF5ZXJzIiwiX2RlY2tnbExheWVycyIsIl9iYXNlTGF5ZXIiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwiX3V0aWxzIiwiX3BvaW50TGF5ZXJJY29uIiwiX2NvbnN0YW50cyIsIl9sYXllclRleHRMYWJlbCIsIl9sYXllclV0aWxzIiwiX2dlb2pzb25VdGlscyIsIl9jYWxsU3VwZXIiLCJ0IiwibyIsImUiLCJfZ2V0UHJvdG90eXBlT2YyIiwiX3Bvc3NpYmxlQ29uc3RydWN0b3JSZXR1cm4yIiwiX2lzTmF0aXZlUmVmbGVjdENvbnN0cnVjdCIsIlJlZmxlY3QiLCJjb25zdHJ1Y3QiLCJjb25zdHJ1Y3RvciIsImFwcGx5IiwiQm9vbGVhbiIsInByb3RvdHlwZSIsInZhbHVlT2YiLCJjYWxsIiwiX3N1cGVyUHJvcEdldCIsInIiLCJwIiwiX2dldDIiLCJvd25LZXlzIiwiT2JqZWN0Iiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsImZpbHRlciIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsImVudW1lcmFibGUiLCJwdXNoIiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJkZWZpbmVQcm9wZXJ0eSIsInBvaW50UG9zQWNjZXNzb3IiLCJleHBvcnRzIiwiX3JlZiIsImxhdCIsImxuZyIsImFsdGl0dWRlIiwiZGMiLCJkIiwidmFsdWVBdCIsImluZGV4IiwiZmllbGRJZHgiLCJnZW9qc29uUG9zQWNjZXNzb3IiLCJfcmVmMiIsImdlb2pzb24iLCJnZW9hcnJvd1Bvc0FjY2Vzc29yIiwiX3JlZjMiLCJnZW9hcnJvdyIsImRhdGFDb250YWluZXIiLCJyb3ciLCJnZXQiLCJDT0xVTU5fTU9ERV9QT0lOVFMiLCJDT0xVTU5fTU9ERV9HRU9KU09OIiwiQ09MVU1OX01PREVfR0VPQVJST1ciLCJwb2ludFJlcXVpcmVkQ29sdW1ucyIsInBvaW50T3B0aW9uYWxDb2x1bW5zIiwiZ2VvanNvblJlcXVpcmVkQ29sdW1ucyIsImdlb2Fycm93UmVxdWlyZWRDb2x1bW5zIiwiU1VQUE9SVEVEX0NPTFVNTl9NT0RFUyIsImtleSIsImxhYmVsIiwicmVxdWlyZWRDb2x1bW5zIiwib3B0aW9uYWxDb2x1bW5zIiwidmVyaWZ5RmllbGQiLCJmIiwiaXNHZW9BcnJvd1BvaW50RmllbGQiLCJERUZBVUxUX0NPTFVNTl9NT0RFIiwiYnJ1c2hpbmdFeHRlbnNpb24iLCJCcnVzaGluZ0V4dGVuc2lvbiIsImFycm93Q1BVRmlsdGVyRXh0ZW5zaW9uIiwiRmlsdGVyQXJyb3dFeHRlbnNpb24iLCJwdXNoUG9pbnRQb3NpdGlvbiIsImRhdGEiLCJwb3MiLCJuZWlnaGJvcnMiLCJldmVyeSIsIk51bWJlciIsImlzRmluaXRlIiwicG9zaXRpb24iLCJwb2ludFZpc0NvbmZpZ3MiLCJyYWRpdXMiLCJmaXhlZFJhZGl1cyIsIm9wYWNpdHkiLCJvdXRsaW5lIiwidGhpY2tuZXNzIiwic3Ryb2tlQ29sb3IiLCJjb2xvclJhbmdlIiwic3Ryb2tlQ29sb3JSYW5nZSIsInJhZGl1c1JhbmdlIiwiZmlsbGVkIiwiTEFZRVJfVklTX0NPTkZJR1MiLCJ0eXBlIiwiZGVmYXVsdFZhbHVlIiwicHJvcGVydHkiLCJiaWxsYm9hcmQiLCJhbGxvd0hvdmVyIiwic2hvd05laWdoYm9yT25Ib3ZlciIsInNob3dIaWdobGlnaHRDb2xvciIsIlBvaW50TGF5ZXIiLCJfTGF5ZXIiLCJwcm9wcyIsIl90aGlzIiwiX2NsYXNzQ2FsbENoZWNrMiIsInVuZGVmaW5lZCIsInJlZ2lzdGVyVmlzQ29uZmlnIiwiZ2V0UG9zaXRpb25BY2Nlc3NvciIsImNvbmZpZyIsImNvbHVtbk1vZGUiLCJjb2x1bW5zIiwiX2luaGVyaXRzMiIsIl9jcmVhdGVDbGFzczIiLCJQb2ludExheWVySWNvbiIsImRlZmF1bHRQb2ludENvbHVtblBhaXJzIiwiY29uY2F0IiwiX3RvQ29uc3VtYWJsZUFycmF5MiIsImNvbG9yIiwiYWNjZXNzb3IiLCJjb25kaXRpb24iLCJ2aXNDb25maWciLCJmaWVsZCIsInNjYWxlIiwiZG9tYWluIiwicmFuZ2UiLCJjaGFubmVsU2NhbGVUeXBlIiwiQ0hBTk5FTF9TQ0FMRVMiLCJzaXplIiwiZml4ZWQiLCJ2YWx1ZSIsInNldEluaXRpYWxMYXllckNvbmZpZyIsImRhdGFzZXQiLCJudW1Sb3dzIiwiZGVmYXVsdENvbG9yRmllbGQiLCJmaW5kRGVmYXVsdENvbG9yRmllbGQiLCJ1cGRhdGVMYXllckNvbmZpZyIsImNvbG9yRmllbGQiLCJ1cGRhdGVMYXllclZpc3VhbENoYW5uZWwiLCJnZXREZWZhdWx0TGF5ZXJDb25maWciLCJfcHJvcHMkY29sdW1uTW9kZSIsImRlZmF1bHRMYXllckNvbmZpZyIsInN0cm9rZUNvbG9yRmllbGQiLCJzdHJva2VDb2xvckRvbWFpbiIsInN0cm9rZUNvbG9yU2NhbGUiLCJjb2xvclVJIiwiREVGQVVMVF9DT0xPUl9VSSIsImNhbGN1bGF0ZURhdGFBdHRyaWJ1dGUiLCJfcmVmNCIsImdldFBvc2l0aW9uIiwiX3RoaXMyIiwiZmlsdGVyZWRJbmRleCIsIkFycm93RGF0YUNvbnRhaW5lciIsImdldEZpbHRlcmVkSW5kZXgiLCJmaWx0ZXJlZEluZGV4VHJpZ2dlciIsImdlb0Fycm93VmVjdG9yIiwiZ2V0Q29sdW1uIiwiY3JlYXRlR2VvQXJyb3dQb2ludFZlY3RvciIsImdldFRhYmxlIiwiX2xvb3AiLCJpIiwiX3RoaXMyJGNvbmZpZyRjb2x1bW5zIiwiQXJyYXkiLCJpc0FycmF5IiwiY29vcmRpbmF0ZXMiLCJkYXRhVG9GZWF0dXJlIiwiY29vcmQiLCJmb3JtYXRMYXllckRhdGEiLCJkYXRhc2V0cyIsIm9sZExheWVyRGF0YSIsIl90aGlzMyIsImRhdGFJZCIsInRleHRMYWJlbCIsIl9kYXRhc2V0cyR0aGlzJGNvbmZpZyIsImdwdUZpbHRlciIsIl90aGlzJHVwZGF0ZURhdGEiLCJ1cGRhdGVEYXRhIiwidHJpZ2dlckNoYW5nZWQiLCJ0ZXh0TGFiZWxzIiwiZm9ybWF0VGV4dExhYmVsRGF0YSIsImFjY2Vzc29ycyIsImdldEF0dHJpYnV0ZUFjY2Vzc29ycyIsImlzRmlsdGVyZWRBY2Nlc3NvciIsImdldEZpbHRlclZhbHVlIiwiZmlsdGVyVmFsdWVBY2Nlc3NvciIsImdldEZpbHRlcmVkIiwidXBkYXRlTGF5ZXJNZXRhIiwiZ2V0RmVhdHVyZSIsImdldEdlb2pzb25Qb2ludERhdGFNYXBzIiwiYm91bmRzRnJvbU1ldGFkYXRhIiwiZ2V0Qm91bmRzRnJvbUFycm93TWV0YWRhdGEiLCJ1cGRhdGVNZXRhIiwiYm91bmRzIiwiZ2V0UG9pbnRzQm91bmRzIiwicmVuZGVyTGF5ZXIiLCJvcHRzIiwiX3RoaXMkY29uZmlnJGNvbHVtbnMkIiwib2JqZWN0SG92ZXJlZCIsIm1hcFN0YXRlIiwiaW50ZXJhY3Rpb25Db25maWciLCJzaXplRmllbGQiLCJyYWRpdXNTY2FsZSIsImdldFJhZGl1c1NjYWxlQnlab29tIiwibGF5ZXJQcm9wcyIsInN0cm9rZWQiLCJsaW5lV2lkdGhTY2FsZSIsInJhZGl1c01heFBpeGVscyIsInVwZGF0ZVRyaWdnZXJzIiwiZmlsdGVyVmFsdWVVcGRhdGVUcmlnZ2VycyIsImdldFZpc3VhbENoYW5uZWxVcGRhdGVUcmlnZ2VycyIsInVzZUFycm93TGF5ZXIiLCJkZWZhdWx0TGF5ZXJQcm9wcyIsImdldERlZmF1bHREZWNrTGF5ZXJQcm9wcyIsImJydXNoaW5nUHJvcHMiLCJnZXRCcnVzaGluZ0V4dGVuc2lvblByb3BzIiwiZ2V0UGl4ZWxPZmZzZXQiLCJnZXRUZXh0T2Zmc2V0QnlSYWRpdXMiLCJnZXRSYWRpdXMiLCJleHRlbnNpb25zIiwic2hhcmVkUHJvcHMiLCJmaWx0ZXJSYW5nZSIsInZpc2libGUiLCJob3ZlcmVkT2JqZWN0IiwiaGFzSG92ZXJlZE9iamVjdCIsIl90aGlzJGNvbmZpZyR2aXNDb25maSIsIm5laWdoYm9yc0RhdGEiLCJnZXROZWlnaGJvcnMiLCJTY2F0dGVycGxvdExheWVyQ2xhc3MiLCJTY2F0dGVycGxvdExheWVyIiwiZGVja0xheWVyRGF0YSIsIkdlb0Fycm93U2NhdHRlcnBsb3RMYXllciIsInBhcmFtZXRlcnMiLCJkZXB0aFRlc3QiLCJsaW5lV2lkdGhVbml0cyIsInBpY2thYmxlIiwiYXV0b0hpZ2hsaWdodCIsImdldERlZmF1bHRIb3ZlckxheWVyUHJvcHMiLCJnZXRMaW5lQ29sb3IiLCJoaWdobGlnaHRDb2xvciIsImdldEZpbGxDb2xvciIsInJlbmRlclRleHRMYWJlbExheWVyIiwib2JqZWN0SW5mbyIsImlzTGF5ZXJIb3ZlcmVkRnJvbUFycm93IiwiaWQiLCJnZXRIb3ZlckRhdGEiLCJvYmplY3QiLCJmaWVsZHMiLCJhbmltYXRpb25Db25maWciLCJob3ZlckluZm8iLCJmaW5kRGVmYXVsdExheWVyUHJvcHMiLCJfZGF0YXNldCRmaWVsZFBhaXJzIiwiZmllbGRQYWlycyIsIkRhdGFzZXRUeXBlIiwiVkVDVE9SX1RJTEUiLCJwYWlyIiwibGF0RmllbGQiLCJwcm9wIiwiZGVmYXVsdE5hbWUiLCJyZXBsYWNlIiwiREVGQVVMVF9MQVlFUl9DT0xPUiIsImhleFRvUmdiIiwiaXNWaXNpYmxlIiwiYXNzaWduUG9pbnRQYWlyVG9MYXllckNvbHVtbiIsImFsdFByb3BzIiwiZ2V0R2VvQXJyb3dQb2ludExheWVyUHJvcHMiLCJMYXllciJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wb2ludC1sYXllci9wb2ludC1sYXllci50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgY29udHJpYnV0b3JzIHRvIHRoZSBrZXBsZXIuZ2wgcHJvamVjdFxuXG5pbXBvcnQgKiBhcyBhcnJvdyBmcm9tICdhcGFjaGUtYXJyb3cnO1xuXG5pbXBvcnQge0JydXNoaW5nRXh0ZW5zaW9ufSBmcm9tICdAZGVjay5nbC9leHRlbnNpb25zJztcbmltcG9ydCB7U2NhdHRlcnBsb3RMYXllcn0gZnJvbSAnQGRlY2suZ2wvbGF5ZXJzJztcblxuaW1wb3J0IHtHZW9BcnJvd1NjYXR0ZXJwbG90TGF5ZXJ9IGZyb20gJ0BrZXBsZXIuZ2wvZGVja2dsLWFycm93LWxheWVycyc7XG5pbXBvcnQge0ZpbHRlckFycm93RXh0ZW5zaW9ufSBmcm9tICdAa2VwbGVyLmdsL2RlY2tnbC1sYXllcnMnO1xuXG5pbXBvcnQgTGF5ZXIsIHtcbiAgTGF5ZXJCYXNlQ29uZmlnLFxuICBMYXllckJhc2VDb25maWdQYXJ0aWFsLFxuICBMYXllckNvbG9yQ29uZmlnLFxuICBMYXllclNpemVDb25maWcsXG4gIExheWVyU3Ryb2tlQ29sb3JDb25maWdcbn0gZnJvbSAnLi4vYmFzZS1sYXllcic7XG5pbXBvcnQge1xuICBoZXhUb1JnYixcbiAgZmluZERlZmF1bHRDb2xvckZpZWxkLFxuICBEYXRhQ29udGFpbmVySW50ZXJmYWNlLFxuICBBcnJvd0RhdGFDb250YWluZXJcbn0gZnJvbSAnQGtlcGxlci5nbC91dGlscyc7XG5pbXBvcnQge2RlZmF1bHQgYXMgS2VwbGVyVGFibGV9IGZyb20gJ0BrZXBsZXIuZ2wvdGFibGUnO1xuaW1wb3J0IFBvaW50TGF5ZXJJY29uIGZyb20gJy4vcG9pbnQtbGF5ZXItaWNvbic7XG5pbXBvcnQge1xuICBEYXRhc2V0VHlwZSxcbiAgTEFZRVJfVklTX0NPTkZJR1MsXG4gIERFRkFVTFRfTEFZRVJfQ09MT1IsXG4gIENIQU5ORUxfU0NBTEVTLFxuICBERUZBVUxUX0NPTE9SX1VJXG59IGZyb20gJ0BrZXBsZXIuZ2wvY29uc3RhbnRzJztcbmltcG9ydCB7Z2V0VGV4dE9mZnNldEJ5UmFkaXVzLCBmb3JtYXRUZXh0TGFiZWxEYXRhfSBmcm9tICcuLi9sYXllci10ZXh0LWxhYmVsJztcbmltcG9ydCB7XG4gIGFzc2lnblBvaW50UGFpclRvTGF5ZXJDb2x1bW4sXG4gIGlzTGF5ZXJIb3ZlcmVkRnJvbUFycm93LFxuICBnZXRCb3VuZHNGcm9tQXJyb3dNZXRhZGF0YSxcbiAgZ2V0R2VvQXJyb3dQb2ludExheWVyUHJvcHMsXG4gIGlzR2VvQXJyb3dQb2ludEZpZWxkLFxuICBjcmVhdGVHZW9BcnJvd1BvaW50VmVjdG9yLFxuICBnZXRGaWx0ZXJlZEluZGV4LFxuICBnZXROZWlnaGJvcnMsXG4gIEZpbmREZWZhdWx0TGF5ZXJQcm9wc1xufSBmcm9tICcuLi9sYXllci11dGlscyc7XG5pbXBvcnQge2dldEdlb2pzb25Qb2ludERhdGFNYXBzLCBHZW9qc29uUG9pbnREYXRhTWFwc30gZnJvbSAnLi4vZ2VvanNvbi1sYXllci9nZW9qc29uLXV0aWxzJztcbmltcG9ydCB7XG4gIENvbG9yUmFuZ2UsXG4gIE1lcmdlLFxuICBSR0JDb2xvcixcbiAgVmlzQ29uZmlnQm9vbGVhbixcbiAgVmlzQ29uZmlnQ29sb3JSYW5nZSxcbiAgVmlzQ29uZmlnQ29sb3JTZWxlY3QsXG4gIFZpc0NvbmZpZ051bWJlcixcbiAgVmlzQ29uZmlnUmFuZ2UsXG4gIExheWVyQ29sdW1uLFxuICBGaWVsZCxcbiAgQW5pbWF0aW9uQ29uZmlnXG59IGZyb20gJ0BrZXBsZXIuZ2wvdHlwZXMnO1xuXG5leHBvcnQgdHlwZSBQb2ludExheWVyVmlzQ29uZmlnU2V0dGluZ3MgPSB7XG4gIHJhZGl1czogVmlzQ29uZmlnTnVtYmVyO1xuICBmaXhlZFJhZGl1czogVmlzQ29uZmlnQm9vbGVhbjtcbiAgb3BhY2l0eTogVmlzQ29uZmlnTnVtYmVyO1xuICBvdXRsaW5lOiBWaXNDb25maWdCb29sZWFuO1xuICB0aGlja25lc3M6IFZpc0NvbmZpZ051bWJlcjtcbiAgc3Ryb2tlQ29sb3I6IFZpc0NvbmZpZ0NvbG9yU2VsZWN0O1xuICBjb2xvclJhbmdlOiBWaXNDb25maWdDb2xvclJhbmdlO1xuICBzdHJva2VDb2xvclJhbmdlOiBWaXNDb25maWdDb2xvclJhbmdlO1xuICByYWRpdXNSYW5nZTogVmlzQ29uZmlnUmFuZ2U7XG4gIGZpbGxlZDogVmlzQ29uZmlnQm9vbGVhbjtcbn07XG5cbmV4cG9ydCB0eXBlIFBvaW50TGF5ZXJDb2x1bW5zQ29uZmlnID0ge1xuICBsYXQ6IExheWVyQ29sdW1uO1xuICBsbmc6IExheWVyQ29sdW1uO1xuICBhbHRpdHVkZT86IExheWVyQ29sdW1uO1xuICBuZWlnaGJvcnM/OiBMYXllckNvbHVtbjtcbiAgZ2VvanNvbjogTGF5ZXJDb2x1bW47XG4gIGdlb2Fycm93OiBMYXllckNvbHVtbjtcbn07XG5cbmV4cG9ydCB0eXBlIFBvaW50TGF5ZXJWaXNDb25maWcgPSB7XG4gIHJhZGl1czogbnVtYmVyO1xuICBmaXhlZFJhZGl1czogYm9vbGVhbjtcbiAgb3BhY2l0eTogbnVtYmVyO1xuICBvdXRsaW5lOiBib29sZWFuO1xuICB0aGlja25lc3M6IG51bWJlcjtcbiAgc3Ryb2tlQ29sb3I6IFJHQkNvbG9yO1xuICBjb2xvclJhbmdlOiBDb2xvclJhbmdlO1xuICBzdHJva2VDb2xvclJhbmdlOiBDb2xvclJhbmdlO1xuICByYWRpdXNSYW5nZTogW251bWJlciwgbnVtYmVyXTtcbiAgZmlsbGVkOiBib29sZWFuO1xuICBiaWxsYm9hcmQ6IGJvb2xlYW47XG4gIGFsbG93SG92ZXI6IGJvb2xlYW47XG4gIHNob3dOZWlnaGJvck9uSG92ZXI6IGJvb2xlYW47XG4gIHNob3dIaWdobGlnaHRDb2xvcjogYm9vbGVhbjtcbn07XG5leHBvcnQgdHlwZSBQb2ludExheWVyVmlzdWFsQ2hhbm5lbENvbmZpZyA9IExheWVyQ29sb3JDb25maWcgJlxuICBMYXllclNpemVDb25maWcgJlxuICBMYXllclN0cm9rZUNvbG9yQ29uZmlnO1xuZXhwb3J0IHR5cGUgUG9pbnRMYXllckNvbmZpZyA9IE1lcmdlPFxuICBMYXllckJhc2VDb25maWcsXG4gIHtjb2x1bW5zOiBQb2ludExheWVyQ29sdW1uc0NvbmZpZzsgdmlzQ29uZmlnOiBQb2ludExheWVyVmlzQ29uZmlnfVxuPiAmXG4gIFBvaW50TGF5ZXJWaXN1YWxDaGFubmVsQ29uZmlnO1xuXG5leHBvcnQgdHlwZSBQb2ludExheWVyRGF0YSA9IHtcbiAgcG9zaXRpb246IG51bWJlcltdO1xuICBpbmRleDogbnVtYmVyO1xuICBuZWlnaGJvcnM6IGFueVtdO1xufTtcblxuZXhwb3J0IGNvbnN0IHBvaW50UG9zQWNjZXNzb3IgPVxuICAoe2xhdCwgbG5nLCBhbHRpdHVkZX06IFBvaW50TGF5ZXJDb2x1bW5zQ29uZmlnKSA9PlxuICAoZGM6IERhdGFDb250YWluZXJJbnRlcmZhY2UpID0+XG4gIChkOiB7aW5kZXg6IG51bWJlcn0pID0+XG4gICAgW1xuICAgICAgZGMudmFsdWVBdChkLmluZGV4LCBsbmcuZmllbGRJZHgpLFxuICAgICAgZGMudmFsdWVBdChkLmluZGV4LCBsYXQuZmllbGRJZHgpLFxuICAgICAgYWx0aXR1ZGUgJiYgYWx0aXR1ZGUuZmllbGRJZHggPiAtMSA/IGRjLnZhbHVlQXQoZC5pbmRleCwgYWx0aXR1ZGUuZmllbGRJZHgpIDogMFxuICAgIF07XG5cbmV4cG9ydCBjb25zdCBnZW9qc29uUG9zQWNjZXNzb3IgPVxuICAoe2dlb2pzb259OiB7Z2VvanNvbjogTGF5ZXJDb2x1bW59KSA9PlxuICBkID0+XG4gICAgZFtnZW9qc29uLmZpZWxkSWR4XTtcblxuZXhwb3J0IGNvbnN0IGdlb2Fycm93UG9zQWNjZXNzb3IgPVxuICAoe2dlb2Fycm93fTogUG9pbnRMYXllckNvbHVtbnNDb25maWcpID0+XG4gIChkYXRhQ29udGFpbmVyOiBEYXRhQ29udGFpbmVySW50ZXJmYWNlKSA9PlxuICAoZDoge2luZGV4OiBudW1iZXJ9KSA9PiB7XG4gICAgY29uc3Qgcm93ID0gZGF0YUNvbnRhaW5lci52YWx1ZUF0KGQuaW5kZXgsIGdlb2Fycm93LmZpZWxkSWR4KTtcbiAgICByZXR1cm4gW3Jvdy5nZXQoMCksIHJvdy5nZXQoMSksIDBdO1xuICB9O1xuXG5leHBvcnQgY29uc3QgQ09MVU1OX01PREVfUE9JTlRTID0gJ3BvaW50cyc7XG5leHBvcnQgY29uc3QgQ09MVU1OX01PREVfR0VPSlNPTiA9ICdnZW9qc29uJztcbmV4cG9ydCBjb25zdCBDT0xVTU5fTU9ERV9HRU9BUlJPVyA9ICdnZW9hcnJvdyc7XG5cbmV4cG9ydCBjb25zdCBwb2ludFJlcXVpcmVkQ29sdW1uczogWydsYXQnLCAnbG5nJ10gPSBbJ2xhdCcsICdsbmcnXTtcbmV4cG9ydCBjb25zdCBwb2ludE9wdGlvbmFsQ29sdW1uczogWydhbHRpdHVkZScsICduZWlnaGJvcnMnXSA9IFsnYWx0aXR1ZGUnLCAnbmVpZ2hib3JzJ107XG5leHBvcnQgY29uc3QgZ2VvanNvblJlcXVpcmVkQ29sdW1uczogWydnZW9qc29uJ10gPSBbJ2dlb2pzb24nXTtcbmV4cG9ydCBjb25zdCBnZW9hcnJvd1JlcXVpcmVkQ29sdW1uczogWydnZW9hcnJvdyddID0gWydnZW9hcnJvdyddO1xuXG5jb25zdCBTVVBQT1JURURfQ09MVU1OX01PREVTID0gW1xuICB7XG4gICAga2V5OiBDT0xVTU5fTU9ERV9QT0lOVFMsXG4gICAgbGFiZWw6ICdQb2ludCBDb2x1bW5zJyxcbiAgICByZXF1aXJlZENvbHVtbnM6IHBvaW50UmVxdWlyZWRDb2x1bW5zLFxuICAgIG9wdGlvbmFsQ29sdW1uczogcG9pbnRPcHRpb25hbENvbHVtbnNcbiAgfSxcbiAge1xuICAgIGtleTogQ09MVU1OX01PREVfR0VPSlNPTixcbiAgICBsYWJlbDogJ0dlb0pTT04gRmVhdHVyZScsXG4gICAgcmVxdWlyZWRDb2x1bW5zOiBnZW9qc29uUmVxdWlyZWRDb2x1bW5zLFxuICAgIHZlcmlmeUZpZWxkOiBmID0+ICFpc0dlb0Fycm93UG9pbnRGaWVsZChmKVxuICB9LFxuICB7XG4gICAga2V5OiBDT0xVTU5fTU9ERV9HRU9BUlJPVyxcbiAgICBsYWJlbDogJ0dlb2Fycm93IFBvaW50cycsXG4gICAgcmVxdWlyZWRDb2x1bW5zOiBnZW9hcnJvd1JlcXVpcmVkQ29sdW1ucyxcbiAgICB2ZXJpZnlGaWVsZDogZiA9PiBpc0dlb0Fycm93UG9pbnRGaWVsZChmKVxuICB9XG5dO1xuY29uc3QgREVGQVVMVF9DT0xVTU5fTU9ERSA9IENPTFVNTl9NT0RFX1BPSU5UUztcblxuY29uc3QgYnJ1c2hpbmdFeHRlbnNpb24gPSBuZXcgQnJ1c2hpbmdFeHRlbnNpb24oKTtcbmNvbnN0IGFycm93Q1BVRmlsdGVyRXh0ZW5zaW9uID0gbmV3IEZpbHRlckFycm93RXh0ZW5zaW9uKCk7XG5cbmZ1bmN0aW9uIHB1c2hQb2ludFBvc2l0aW9uKGRhdGE6IGFueVtdLCBwb3M6IG51bWJlcltdLCBpbmRleDogbnVtYmVyLCBuZWlnaGJvcnM/OiBudW1iZXJbXSkge1xuICBpZiAocG9zLmV2ZXJ5KE51bWJlci5pc0Zpbml0ZSkpIHtcbiAgICBkYXRhLnB1c2goe1xuICAgICAgcG9zaXRpb246IHBvcyxcbiAgICAgIC8vIGluZGV4IGlzIGltcG9ydGFudCBmb3IgZmlsdGVyXG4gICAgICBpbmRleCxcbiAgICAgIC4uLihuZWlnaGJvcnMgPyB7bmVpZ2hib3JzfSA6IHt9KVxuICAgIH0pO1xuICB9XG59XG5cbmV4cG9ydCBjb25zdCBwb2ludFZpc0NvbmZpZ3M6IHtcbiAgcmFkaXVzOiAncmFkaXVzJztcbiAgZml4ZWRSYWRpdXM6ICdmaXhlZFJhZGl1cyc7XG4gIG9wYWNpdHk6ICdvcGFjaXR5JztcbiAgb3V0bGluZTogJ291dGxpbmUnO1xuICB0aGlja25lc3M6ICd0aGlja25lc3MnO1xuICBzdHJva2VDb2xvcjogJ3N0cm9rZUNvbG9yJztcbiAgY29sb3JSYW5nZTogJ2NvbG9yUmFuZ2UnO1xuICBzdHJva2VDb2xvclJhbmdlOiAnc3Ryb2tlQ29sb3JSYW5nZSc7XG4gIHJhZGl1c1JhbmdlOiAncmFkaXVzUmFuZ2UnO1xuICBmaWxsZWQ6IFZpc0NvbmZpZ0Jvb2xlYW47XG4gIGJpbGxib2FyZDogJ2JpbGxib2FyZCc7XG4gIGFsbG93SG92ZXI6ICdhbGxvd0hvdmVyJztcbiAgc2hvd05laWdoYm9yT25Ib3ZlcjogJ3Nob3dOZWlnaGJvck9uSG92ZXInO1xuICBzaG93SGlnaGxpZ2h0Q29sb3I6ICdzaG93SGlnaGxpZ2h0Q29sb3InO1xufSA9IHtcbiAgcmFkaXVzOiAncmFkaXVzJyxcbiAgZml4ZWRSYWRpdXM6ICdmaXhlZFJhZGl1cycsXG4gIG9wYWNpdHk6ICdvcGFjaXR5JyxcbiAgb3V0bGluZTogJ291dGxpbmUnLFxuICB0aGlja25lc3M6ICd0aGlja25lc3MnLFxuICBzdHJva2VDb2xvcjogJ3N0cm9rZUNvbG9yJyxcbiAgY29sb3JSYW5nZTogJ2NvbG9yUmFuZ2UnLFxuICBzdHJva2VDb2xvclJhbmdlOiAnc3Ryb2tlQ29sb3JSYW5nZScsXG4gIHJhZGl1c1JhbmdlOiAncmFkaXVzUmFuZ2UnLFxuICBmaWxsZWQ6IHtcbiAgICAuLi5MQVlFUl9WSVNfQ09ORklHUy5maWxsZWQsXG4gICAgdHlwZTogJ2Jvb2xlYW4nLFxuICAgIGxhYmVsOiAnbGF5ZXIuZmlsbENvbG9yJyxcbiAgICBkZWZhdWx0VmFsdWU6IHRydWUsXG4gICAgcHJvcGVydHk6ICdmaWxsZWQnXG4gIH0sXG4gIGJpbGxib2FyZDogJ2JpbGxib2FyZCcsXG4gIGFsbG93SG92ZXI6ICdhbGxvd0hvdmVyJyxcbiAgc2hvd05laWdoYm9yT25Ib3ZlcjogJ3Nob3dOZWlnaGJvck9uSG92ZXInLFxuICBzaG93SGlnaGxpZ2h0Q29sb3I6ICdzaG93SGlnaGxpZ2h0Q29sb3InXG59O1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBQb2ludExheWVyIGV4dGVuZHMgTGF5ZXIge1xuICBkZWNsYXJlIGNvbmZpZzogUG9pbnRMYXllckNvbmZpZztcbiAgZGVjbGFyZSB2aXNDb25maWdTZXR0aW5nczogUG9pbnRMYXllclZpc0NvbmZpZ1NldHRpbmdzO1xuICBkYXRhVG9GZWF0dXJlOiBHZW9qc29uUG9pbnREYXRhTWFwcyA9IFtdO1xuXG4gIGRhdGFDb250YWluZXI6IERhdGFDb250YWluZXJJbnRlcmZhY2UgfCBudWxsID0gbnVsbDtcbiAgZ2VvQXJyb3dWZWN0b3I6IGFycm93LlZlY3RvciB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcblxuICAvKlxuICAgKiBDUFUgZmlsdGVyaW5nIGFuIGFycm93IHRhYmxlIGJ5IHZhbHVlcyBhbmQgYXNzZW1ibGluZyBhIHBhcnRpYWwgY29weSBvZiB0aGUgcmF3IHRhYmxlIGlzIGV4cGVuc2l2ZVxuICAgKiBzbyB3ZSB3aWxsIHVzZSBmaWx0ZXJlZEluZGV4IHRvIGNyZWF0ZSBhbiBhdHRyaWJ1dGUgZS5nLiBmaWx0ZXJlZEluZGV4IFswfDFdIGZvciBHUFUgZmlsdGVyaW5nXG4gICAqIGluIGRlY2suZ2wgbGF5ZXIsIHNlZTogRmlsdGVyQXJyb3dFeHRlbnNpb24gaW4gQGtlcGxlci5nbC9kZWNrZ2wtbGF5ZXJzLlxuICAgKiBOb3RlIHRoYXQgdGhpcyBhcHByb2FjaCBjYW4gY3JlYXRlIHZpc2libGUgbGFncyBpbiBjYXNlIG9mIGEgbG90IG9mIGRpc2NhcnRlZCBnZW9tZXRyeS5cbiAgICovXG4gIGZpbHRlcmVkSW5kZXg6IFVpbnQ4Q2xhbXBlZEFycmF5IHwgbnVsbCA9IG51bGw7XG4gIGZpbHRlcmVkSW5kZXhUcmlnZ2VyOiBudW1iZXJbXSA9IFtdO1xuXG4gIGNvbnN0cnVjdG9yKHByb3BzKSB7XG4gICAgc3VwZXIocHJvcHMpO1xuXG4gICAgdGhpcy5yZWdpc3RlclZpc0NvbmZpZyhwb2ludFZpc0NvbmZpZ3MpO1xuICAgIHRoaXMuZ2V0UG9zaXRpb25BY2Nlc3NvciA9IChkYXRhQ29udGFpbmVyOiBEYXRhQ29udGFpbmVySW50ZXJmYWNlKSA9PiB7XG4gICAgICBzd2l0Y2ggKHRoaXMuY29uZmlnLmNvbHVtbk1vZGUpIHtcbiAgICAgICAgY2FzZSBDT0xVTU5fTU9ERV9HRU9BUlJPVzpcbiAgICAgICAgICByZXR1cm4gZ2VvYXJyb3dQb3NBY2Nlc3Nvcih0aGlzLmNvbmZpZy5jb2x1bW5zKShkYXRhQ29udGFpbmVyKTtcbiAgICAgICAgY2FzZSBDT0xVTU5fTU9ERV9HRU9KU09OOlxuICAgICAgICAgIHJldHVybiBnZW9qc29uUG9zQWNjZXNzb3IodGhpcy5jb25maWcuY29sdW1ucyk7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgLy8gQ09MVU1OX01PREVfUE9JTlRTXG4gICAgICAgICAgcmV0dXJuIHBvaW50UG9zQWNjZXNzb3IodGhpcy5jb25maWcuY29sdW1ucykoZGF0YUNvbnRhaW5lcik7XG4gICAgICB9XG4gICAgfTtcbiAgfVxuXG4gIGdldCB0eXBlKCk6ICdwb2ludCcge1xuICAgIHJldHVybiAncG9pbnQnO1xuICB9XG5cbiAgZ2V0IGlzQWdncmVnYXRlZCgpOiBmYWxzZSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgZ2V0IGxheWVySWNvbigpIHtcbiAgICByZXR1cm4gUG9pbnRMYXllckljb247XG4gIH1cblxuICBnZXQgb3B0aW9uYWxDb2x1bW5zKCkge1xuICAgIHJldHVybiBwb2ludE9wdGlvbmFsQ29sdW1ucztcbiAgfVxuXG4gIGdldCBjb2x1bW5QYWlycygpIHtcbiAgICByZXR1cm4gdGhpcy5kZWZhdWx0UG9pbnRDb2x1bW5QYWlycztcbiAgfVxuXG4gIGdldCBzdXBwb3J0ZWRDb2x1bW5Nb2RlcygpIHtcbiAgICByZXR1cm4gU1VQUE9SVEVEX0NPTFVNTl9NT0RFUztcbiAgfVxuXG4gIGdldCBub25lTGF5ZXJEYXRhQWZmZWN0aW5nUHJvcHMoKSB7XG4gICAgcmV0dXJuIFsuLi5zdXBlci5ub25lTGF5ZXJEYXRhQWZmZWN0aW5nUHJvcHMsICdyYWRpdXMnXTtcbiAgfVxuXG4gIGdldCB2aXN1YWxDaGFubmVscygpIHtcbiAgICByZXR1cm4ge1xuICAgICAgY29sb3I6IHtcbiAgICAgICAgLi4uc3VwZXIudmlzdWFsQ2hhbm5lbHMuY29sb3IsXG4gICAgICAgIGFjY2Vzc29yOiAnZ2V0RmlsbENvbG9yJyxcbiAgICAgICAgY29uZGl0aW9uOiBjb25maWcgPT4gY29uZmlnLnZpc0NvbmZpZy5maWxsZWQsXG4gICAgICAgIGRlZmF1bHRWYWx1ZTogY29uZmlnID0+IGNvbmZpZy5jb2xvclxuICAgICAgfSxcbiAgICAgIHN0cm9rZUNvbG9yOiB7XG4gICAgICAgIHByb3BlcnR5OiAnc3Ryb2tlQ29sb3InLFxuICAgICAgICBrZXk6ICdzdHJva2VDb2xvcicsXG4gICAgICAgIGZpZWxkOiAnc3Ryb2tlQ29sb3JGaWVsZCcsXG4gICAgICAgIHNjYWxlOiAnc3Ryb2tlQ29sb3JTY2FsZScsXG4gICAgICAgIGRvbWFpbjogJ3N0cm9rZUNvbG9yRG9tYWluJyxcbiAgICAgICAgcmFuZ2U6ICdzdHJva2VDb2xvclJhbmdlJyxcbiAgICAgICAgY2hhbm5lbFNjYWxlVHlwZTogQ0hBTk5FTF9TQ0FMRVMuY29sb3IsXG4gICAgICAgIGFjY2Vzc29yOiAnZ2V0TGluZUNvbG9yJyxcbiAgICAgICAgY29uZGl0aW9uOiBjb25maWcgPT4gY29uZmlnLnZpc0NvbmZpZy5vdXRsaW5lLFxuICAgICAgICBkZWZhdWx0VmFsdWU6IGNvbmZpZyA9PiBjb25maWcudmlzQ29uZmlnLnN0cm9rZUNvbG9yIHx8IGNvbmZpZy5jb2xvclxuICAgICAgfSxcbiAgICAgIHNpemU6IHtcbiAgICAgICAgLi4uc3VwZXIudmlzdWFsQ2hhbm5lbHMuc2l6ZSxcbiAgICAgICAgcHJvcGVydHk6ICdyYWRpdXMnLFxuICAgICAgICByYW5nZTogJ3JhZGl1c1JhbmdlJyxcbiAgICAgICAgZml4ZWQ6ICdmaXhlZFJhZGl1cycsXG4gICAgICAgIGNoYW5uZWxTY2FsZVR5cGU6ICdyYWRpdXMnLFxuICAgICAgICBhY2Nlc3NvcjogJ2dldFJhZGl1cycsXG4gICAgICAgIGRlZmF1bHRWYWx1ZTogMVxuICAgICAgfVxuICAgIH07XG4gIH1cblxuICBzZXRJbml0aWFsTGF5ZXJDb25maWcoZGF0YXNldCkge1xuICAgIGlmICghZGF0YXNldC5kYXRhQ29udGFpbmVyLm51bVJvd3MoKSkge1xuICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuICAgIGNvbnN0IGRlZmF1bHRDb2xvckZpZWxkID0gZmluZERlZmF1bHRDb2xvckZpZWxkKGRhdGFzZXQpO1xuXG4gICAgaWYgKGRlZmF1bHRDb2xvckZpZWxkKSB7XG4gICAgICB0aGlzLnVwZGF0ZUxheWVyQ29uZmlnKHtcbiAgICAgICAgY29sb3JGaWVsZDogZGVmYXVsdENvbG9yRmllbGRcbiAgICAgIH0pO1xuICAgICAgdGhpcy51cGRhdGVMYXllclZpc3VhbENoYW5uZWwoZGF0YXNldCwgJ2NvbG9yJyk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG4gIH1cblxuICBzdGF0aWMgZmluZERlZmF1bHRMYXllclByb3BzKGRhdGFzZXQ6IEtlcGxlclRhYmxlKSB7XG4gICAgY29uc3Qge2ZpZWxkUGFpcnMgPSBbXSwgdHlwZSwgbGFiZWx9ID0gZGF0YXNldDtcblxuICAgIGNvbnN0IHByb3BzOiBGaW5kRGVmYXVsdExheWVyUHJvcHNbXSA9IFtdO1xuXG4gICAgaWYgKHR5cGUgPT09IERhdGFzZXRUeXBlLlZFQ1RPUl9USUxFKSB7XG4gICAgICByZXR1cm4ge3Byb3BzfTtcbiAgICB9XG5cbiAgICAvLyBNYWtlIGxheWVyIGZvciBlYWNoIHBhaXJcbiAgICBmaWVsZFBhaXJzLmZvckVhY2gocGFpciA9PiB7XG4gICAgICBjb25zdCBsYXRGaWVsZCA9IHBhaXIucGFpci5sYXQ7XG5cbiAgICAgIGNvbnN0IHByb3A6IHtcbiAgICAgICAgbGFiZWw6IHN0cmluZztcbiAgICAgICAgY29sb3I/OiBSR0JDb2xvcjtcbiAgICAgICAgaXNWaXNpYmxlPzogYm9vbGVhbjtcbiAgICAgICAgY29sdW1ucz86IFBvaW50TGF5ZXJDb2x1bW5zQ29uZmlnO1xuICAgICAgfSA9IHtcbiAgICAgICAgbGFiZWw6XG4gICAgICAgICAgLy8gU2tpcCB0aGUgZ2VuZXJpYyAncG9pbnQnIGZhbGxiYWNrIGZyb20gZmluZFBvaW50RmllbGRQYWlycyBhbmQgdXNlIHRoZSBkYXRhc2V0IGxhYmVsIGluc3RlYWRcbiAgICAgICAgICBwYWlyLmRlZmF1bHROYW1lICYmIHBhaXIuZGVmYXVsdE5hbWUgIT09ICdwb2ludCdcbiAgICAgICAgICAgID8gcGFpci5kZWZhdWx0TmFtZVxuICAgICAgICAgICAgOiAodHlwZW9mIGxhYmVsID09PSAnc3RyaW5nJyAmJiBsYWJlbC5yZXBsYWNlKC9cXC5bXi8uXSskLywgJycpKSB8fCAnUG9pbnQnXG4gICAgICB9O1xuXG4gICAgICAvLyBkZWZhdWx0IGxheWVyIGNvbG9yIGZvciBiZWdpbnRyaXAgYW5kIGRyb3BvZmYgcG9pbnRcbiAgICAgIGlmIChsYXRGaWVsZC52YWx1ZSBpbiBERUZBVUxUX0xBWUVSX0NPTE9SKSB7XG4gICAgICAgIHByb3AuY29sb3IgPSBoZXhUb1JnYihERUZBVUxUX0xBWUVSX0NPTE9SW2xhdEZpZWxkLnZhbHVlXSk7XG4gICAgICB9XG5cbiAgICAgIC8vIHNldCB0aGUgZmlyc3QgbGF5ZXIgdG8gYmUgdmlzaWJsZVxuICAgICAgaWYgKHByb3BzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBwcm9wLmlzVmlzaWJsZSA9IHRydWU7XG4gICAgICB9XG4gICAgICAvLyBAdHMtZXhwZWN0LWVycm9yIGxvZ2ljYWxseSBzZXBhcmF0ZSBnZW9qc29uIGNvbHVtbiB0eXBlP1xuICAgICAgcHJvcC5jb2x1bW5zID0gYXNzaWduUG9pbnRQYWlyVG9MYXllckNvbHVtbihwYWlyLCB0cnVlKTtcblxuICAgICAgcHJvcHMucHVzaChwcm9wKTtcbiAgICB9KTtcblxuICAgIGNvbnN0IGFsdFByb3BzID0gZ2V0R2VvQXJyb3dQb2ludExheWVyUHJvcHMoZGF0YXNldCk7XG5cbiAgICByZXR1cm4ge3Byb3BzLCBhbHRQcm9wc307XG4gIH1cblxuICBnZXREZWZhdWx0TGF5ZXJDb25maWcocHJvcHM6IExheWVyQmFzZUNvbmZpZ1BhcnRpYWwpIHtcbiAgICBjb25zdCBkZWZhdWx0TGF5ZXJDb25maWcgPSBzdXBlci5nZXREZWZhdWx0TGF5ZXJDb25maWcocHJvcHMgPz8ge30pO1xuICAgIHJldHVybiB7XG4gICAgICAuLi5kZWZhdWx0TGF5ZXJDb25maWcsXG5cbiAgICAgIGNvbHVtbk1vZGU6IHByb3BzPy5jb2x1bW5Nb2RlID8/IERFRkFVTFRfQ09MVU1OX01PREUsXG5cbiAgICAgIC8vIGFkZCBzdHJva2UgY29sb3IgdmlzdWFsIGNoYW5uZWxcbiAgICAgIHN0cm9rZUNvbG9yRmllbGQ6IG51bGwsXG4gICAgICBzdHJva2VDb2xvckRvbWFpbjogWzAsIDFdLFxuICAgICAgc3Ryb2tlQ29sb3JTY2FsZTogJ3F1YW50aWxlJyxcbiAgICAgIGNvbG9yVUk6IHtcbiAgICAgICAgLi4uZGVmYXVsdExheWVyQ29uZmlnLmNvbG9yVUksXG4gICAgICAgIHN0cm9rZUNvbG9yUmFuZ2U6IERFRkFVTFRfQ09MT1JfVUlcbiAgICAgIH1cbiAgICB9O1xuICB9XG5cbiAgY2FsY3VsYXRlRGF0YUF0dHJpYnV0ZSh7ZmlsdGVyZWRJbmRleCwgZGF0YUNvbnRhaW5lcn06IEtlcGxlclRhYmxlLCBnZXRQb3NpdGlvbikge1xuICAgIGNvbnN0IHtjb2x1bW5Nb2RlfSA9IHRoaXMuY29uZmlnO1xuXG4gICAgLy8gMSkgQ09MVU1OX01PREVfR0VPQVJST1cgLSB3aGVuIHdlIGhhdmUgYSBnZW9hcnJvdyBwb2ludCBjb2x1bW5cbiAgICAvLyAyKSBDT0xVTU5fTU9ERV9QT0lOVFMgKyBBcnJvd0RhdGFDb250YWluZXIgPiBjcmVhdGUgZ2VvYXJyb3cgcG9pbnQgY29sdW1uIG9uIHRoZSBmbHlcbiAgICBpZiAoXG4gICAgICBkYXRhQ29udGFpbmVyIGluc3RhbmNlb2YgQXJyb3dEYXRhQ29udGFpbmVyICYmXG4gICAgICAoY29sdW1uTW9kZSA9PT0gQ09MVU1OX01PREVfR0VPQVJST1cgfHwgY29sdW1uTW9kZSA9PT0gQ09MVU1OX01PREVfUE9JTlRTKVxuICAgICkge1xuICAgICAgdGhpcy5maWx0ZXJlZEluZGV4ID0gZ2V0RmlsdGVyZWRJbmRleChcbiAgICAgICAgZGF0YUNvbnRhaW5lci5udW1Sb3dzKCksXG4gICAgICAgIGZpbHRlcmVkSW5kZXgsXG4gICAgICAgIHRoaXMuZmlsdGVyZWRJbmRleFxuICAgICAgKTtcbiAgICAgIHRoaXMuZmlsdGVyZWRJbmRleFRyaWdnZXIgPSBmaWx0ZXJlZEluZGV4O1xuXG4gICAgICBpZiAodGhpcy5jb25maWcuY29sdW1uTW9kZSA9PT0gQ09MVU1OX01PREVfR0VPQVJST1cpIHtcbiAgICAgICAgdGhpcy5nZW9BcnJvd1ZlY3RvciA9IGRhdGFDb250YWluZXIuZ2V0Q29sdW1uKHRoaXMuY29uZmlnLmNvbHVtbnMuZ2VvYXJyb3cuZmllbGRJZHgpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gZ2VuZXJhdGUgYSBjb2x1bW4gY29tcGF0aWJsZSB3aXRoIGdlb2Fycm93IHBvaW50XG4gICAgICAgIHRoaXMuZ2VvQXJyb3dWZWN0b3IgPSBjcmVhdGVHZW9BcnJvd1BvaW50VmVjdG9yKGRhdGFDb250YWluZXIsIGdldFBvc2l0aW9uKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIGRhdGFDb250YWluZXIuZ2V0VGFibGUoKTtcbiAgICB9XG5cbiAgICAvLyB3ZSBkb24ndCBuZWVkIHRoZXNlIGluIG5vbi1BcnJvdyBtb2RlcyBhdG0uXG4gICAgdGhpcy5nZW9BcnJvd1ZlY3RvciA9IHVuZGVmaW5lZDtcbiAgICB0aGlzLmZpbHRlcmVkSW5kZXggPSBudWxsO1xuXG4gICAgY29uc3QgZGF0YTogUG9pbnRMYXllckRhdGFbXSA9IFtdO1xuXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBmaWx0ZXJlZEluZGV4Lmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBpbmRleCA9IGZpbHRlcmVkSW5kZXhbaV07XG4gICAgICBsZXQgbmVpZ2hib3JzO1xuXG4gICAgICBpZiAodGhpcy5jb25maWcuY29sdW1uTW9kZSA9PT0gQ09MVU1OX01PREVfUE9JTlRTKSB7XG4gICAgICAgIGlmICh0aGlzLmNvbmZpZy5jb2x1bW5zLm5laWdoYm9ycz8udmFsdWUpIHtcbiAgICAgICAgICBjb25zdCB7ZmllbGRJZHh9ID0gdGhpcy5jb25maWcuY29sdW1ucy5uZWlnaGJvcnM7XG4gICAgICAgICAgbmVpZ2hib3JzID0gQXJyYXkuaXNBcnJheShkYXRhQ29udGFpbmVyLnZhbHVlQXQoaW5kZXgsIGZpZWxkSWR4KSlcbiAgICAgICAgICAgID8gZGF0YUNvbnRhaW5lci52YWx1ZUF0KGluZGV4LCBmaWVsZElkeClcbiAgICAgICAgICAgIDogW107XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgcG9zID0gZ2V0UG9zaXRpb24oe2luZGV4fSk7XG5cbiAgICAgICAgLy8gaWYgZG9lc24ndCBoYXZlIHBvaW50IGxhdCBvciBsbmcsIGRvIG5vdCBhZGQgdGhlIHBvaW50XG4gICAgICAgIC8vIGRlY2suZ2wgY2FuJ3QgaGFuZGxlIHBvc2l0aW9uID0gbnVsbFxuICAgICAgICBwdXNoUG9pbnRQb3NpdGlvbihkYXRhLCBwb3MsIGluZGV4LCBuZWlnaGJvcnMpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gQ09MVU1OX01PREVfR0VPSlNPTiBtb2RlIC0gcG9pbnQgZnJvbSBnZW9qc29uIGNvb3JkaW5hdGVzXG4gICAgICAgIGNvbnN0IGNvb3JkaW5hdGVzID0gdGhpcy5kYXRhVG9GZWF0dXJlW2ldO1xuICAgICAgICAvLyBpZiBtdWx0aSBwb2ludHNcbiAgICAgICAgaWYgKGNvb3JkaW5hdGVzICYmIEFycmF5LmlzQXJyYXkoY29vcmRpbmF0ZXNbMF0pKSB7XG4gICAgICAgICAgY29vcmRpbmF0ZXMuZm9yRWFjaChjb29yZCA9PiB7XG4gICAgICAgICAgICBwdXNoUG9pbnRQb3NpdGlvbihkYXRhLCBjb29yZCwgaW5kZXgpO1xuICAgICAgICAgIH0pO1xuICAgICAgICB9IGVsc2UgaWYgKGNvb3JkaW5hdGVzICYmIE51bWJlci5pc0Zpbml0ZShjb29yZGluYXRlc1swXSkpIHtcbiAgICAgICAgICBwdXNoUG9pbnRQb3NpdGlvbihkYXRhLCBjb29yZGluYXRlcyBhcyBudW1iZXJbXSwgaW5kZXgpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGRhdGE7XG4gIH1cblxuICBmb3JtYXRMYXllckRhdGEoZGF0YXNldHMsIG9sZExheWVyRGF0YSkge1xuICAgIGlmICh0aGlzLmNvbmZpZy5kYXRhSWQgPT09IG51bGwpIHtcbiAgICAgIHJldHVybiB7fTtcbiAgICB9XG4gICAgY29uc3Qge3RleHRMYWJlbH0gPSB0aGlzLmNvbmZpZztcbiAgICBjb25zdCB7Z3B1RmlsdGVyLCBkYXRhQ29udGFpbmVyfSA9IGRhdGFzZXRzW3RoaXMuY29uZmlnLmRhdGFJZF07XG4gICAgY29uc3Qge2RhdGEsIHRyaWdnZXJDaGFuZ2VkfSA9IHRoaXMudXBkYXRlRGF0YShkYXRhc2V0cywgb2xkTGF5ZXJEYXRhKTtcbiAgICBjb25zdCBnZXRQb3NpdGlvbiA9IGQgPT4gZC5wb3NpdGlvbjtcblxuICAgIC8vIGdldCBhbGwgZGlzdGluY3QgY2hhcmFjdGVycyBpbiB0aGUgdGV4dCBsYWJlbHNcbiAgICBjb25zdCB0ZXh0TGFiZWxzID0gZm9ybWF0VGV4dExhYmVsRGF0YSh7XG4gICAgICB0ZXh0TGFiZWwsXG4gICAgICB0cmlnZ2VyQ2hhbmdlZCxcbiAgICAgIG9sZExheWVyRGF0YSxcbiAgICAgIGRhdGEsXG4gICAgICBkYXRhQ29udGFpbmVyLFxuICAgICAgZmlsdGVyZWRJbmRleDogdGhpcy5maWx0ZXJlZEluZGV4XG4gICAgfSk7XG5cbiAgICBjb25zdCBhY2Nlc3NvcnMgPSB0aGlzLmdldEF0dHJpYnV0ZUFjY2Vzc29ycyh7ZGF0YUNvbnRhaW5lcn0pO1xuXG4gICAgY29uc3QgaXNGaWx0ZXJlZEFjY2Vzc29yID0gKGRhdGE6IHtpbmRleDogbnVtYmVyfSkgPT4ge1xuICAgICAgcmV0dXJuIHRoaXMuZmlsdGVyZWRJbmRleCA/IHRoaXMuZmlsdGVyZWRJbmRleFtkYXRhLmluZGV4XSA6IDE7XG4gICAgfTtcblxuICAgIHJldHVybiB7XG4gICAgICBkYXRhLFxuICAgICAgZ2V0UG9zaXRpb24sXG4gICAgICBnZXRGaWx0ZXJWYWx1ZTogZ3B1RmlsdGVyLmZpbHRlclZhbHVlQWNjZXNzb3IoZGF0YUNvbnRhaW5lcikoKSxcbiAgICAgIGdldEZpbHRlcmVkOiBpc0ZpbHRlcmVkQWNjZXNzb3IsXG4gICAgICB0ZXh0TGFiZWxzLFxuICAgICAgLi4uYWNjZXNzb3JzXG4gICAgfTtcbiAgfVxuICAvKiBlc2xpbnQtZW5hYmxlIGNvbXBsZXhpdHkgKi9cblxuICB1cGRhdGVMYXllck1ldGEoZGF0YXNldDogS2VwbGVyVGFibGUpIHtcbiAgICBjb25zdCB7ZGF0YUNvbnRhaW5lcn0gPSBkYXRhc2V0O1xuICAgIHRoaXMuZGF0YUNvbnRhaW5lciA9IGRhdGFDb250YWluZXI7XG5cbiAgICBpZiAodGhpcy5jb25maWcuY29sdW1uTW9kZSA9PT0gQ09MVU1OX01PREVfR0VPSlNPTikge1xuICAgICAgY29uc3QgZ2V0RmVhdHVyZSA9IHRoaXMuZ2V0UG9zaXRpb25BY2Nlc3NvcigpO1xuICAgICAgdGhpcy5kYXRhVG9GZWF0dXJlID0gZ2V0R2VvanNvblBvaW50RGF0YU1hcHMoZGF0YUNvbnRhaW5lciwgZ2V0RmVhdHVyZSk7XG4gICAgfSBlbHNl