UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

648 lines (639 loc) 76 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getAssetRequest = getAssetRequest; exports.getCategoricalColormapDataUrl = getCategoricalColormapDataUrl; exports.getImageDataURL = getImageDataURL; exports.getSTACImageRequests = getSTACImageRequests; exports.loadImages = loadImages; exports.loadSTACImageData = loadSTACImageData; exports.loadTerrain = loadTerrain; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _core = require("@loaders.gl/core"); var _terrain = require("@loaders.gl/terrain"); var _memoize = _interopRequireDefault(require("lodash/memoize")); var _commonUtils = require("@kepler.gl/common-utils"); var _constants = require("@kepler.gl/constants"); var _utils = require("@kepler.gl/utils"); var _config = require("./config"); var _gpuUtils = require("./gpu-utils"); var _rasterTileUtils = require("./raster-tile-utils"); var _url = require("./url"); var _requestThrottle = require("./request-throttle"); 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 /** * Utility functions to create objects to pass to deck.gl-raster */ /** * Create dataUrl image from bitmap array * @param bitmap - RGBA byte bitmap array * @param width - width of the image * @param height - height of the image * @returns base64 data url of the image */ function getImageDataURL(bitmap, width, height) { var canvas = document.createElement('canvas'); if (!canvas.getContext) { return null; } canvas.setAttribute('width', "".concat(width)); canvas.setAttribute('height', "".concat(height)); var ctx = canvas.getContext('2d'); if (!ctx) { return null; } for (var i = 0; i < width; i++) { var _bitmap$subarray = bitmap.subarray(i * 4, i * 4 + 4), _bitmap$subarray2 = (0, _slicedToArray2["default"])(_bitmap$subarray, 4), r = _bitmap$subarray2[0], g = _bitmap$subarray2[1], b = _bitmap$subarray2[2], a = _bitmap$subarray2[3]; if (a !== 0) { ctx.fillStyle = "rgb(".concat(r, ", ").concat(g, ", ").concat(b, ")"); ctx.fillRect(i, 0, 1, 1); } } return canvas.toDataURL('image/png'); } // Global multiplier to be applied to terrain mesh resolution // A smaller number towards 0 will cause generation of mesh data with a smaller max error and thus a // higher resolution mesh var MESH_MULTIPLIER = 0.8; /** * Multiplier used to calculate the height of terrain mesh skirts. * The skirt is an extension of the terrain mesh that helps prevent gaps between terrain tiles * by extending the mesh edges downward. This multiplier is applied to the mesh's maximum error * to determine the skirt height, ensuring no gaps are visible. */ var SKIRT_HEIGHT_MULTIPLIER = 3; /** * Load images required for raster tile * @param assetRequests - STAC asset requests * @param colormapId - colormap id * @param categoricalOptions - categorical options requred for making of categorical colormap * @returns images map */ function loadImages(_x, _x2, _x3) { return _loadImages.apply(this, arguments); } /** * Cache loading of colormap. Each colormap file is very small, on the order of a few hundred bytes, * so a global cache like this is not expected to eat up too much memory. * Most colormaps are loaded from CDN. In case colormapId is categorical, the image is created from * categorical colormap options. * NOTE: this implementation will cache promise rejections as well, but since these are small files * on the CDN, that's an acceptable risk for the time being. */ function _loadImages() { _loadImages = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(assetRequests, colormapId, categoricalOptions) { var _yield$Promise$all, _yield$Promise$all2, stacImages, colormap, imageColormap; return _regenerator["default"].wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: _context.next = 2; return Promise.all([loadSTACImageData(assetRequests), colormapId !== _config.CATEGORICAL_COLORMAP_ID && memoizedLoadColormap(colormapId) || null]); case 2: _yield$Promise$all = _context.sent; _yield$Promise$all2 = (0, _slicedToArray2["default"])(_yield$Promise$all, 2); stacImages = _yield$Promise$all2[0]; colormap = _yield$Promise$all2[1]; imageColormap = colormap; if (colormapId === _config.CATEGORICAL_COLORMAP_ID) { imageColormap = (0, _gpuUtils.generateCategoricalColormapTexture)(categoricalOptions); } return _context.abrupt("return", _objectSpread(_objectSpread({}, stacImages), {}, { imageColormap: imageColormap })); case 9: case "end": return _context.stop(); } }, _callee); })); return _loadImages.apply(this, arguments); } var memoizedLoadColormap = (0, _memoize["default"])(function (colormapId) { if (colormapId === _config.CATEGORICAL_COLORMAP_ID) { return null; } else { return loadColormap(colormapId); } }); /** * Load PNG colormap. * Since the colormap files are so small (couple hundred bytes), we load them always, even for an * RGB composite where the colormap won't be used. After the first load, the colormap should be * loaded from the disk cache. By loading always, we can prevent unnecessarily triggering * getTileData, which reduces flashing from reloading the images on the GPU. * * @param colormapId ID of colormap PNG * * @returns image object to pass to Texture2D constructor */ function loadColormap(_x4) { return _loadColormap.apply(this, arguments); } /** * Get the request data for loading a single or multi asset STAC metadata object. * @param options STAC metadata object and user-defined parameters. * @returns Image data, or null if the STAC metadata object is not supported. */ function _loadColormap() { _loadColormap = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(colormapId) { var request; return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: if (colormapId) { _context2.next = 2; break; } return _context2.abrupt("return", null); case 2: _context2.next = 4; return getAssetRequest({ url: _url.RasterLayerResources.rasterColorMap(colormapId), rasterServerUrl: '', options: {} }); case 4: request = _context2.sent; return _context2.abrupt("return", (0, _gpuUtils.loadImage)(request.url, _gpuUtils.COLORMAP_TEXTURE_PARAMETERS, request.options)); case 6: case "end": return _context2.stop(); } }, _callee2); })); return _loadColormap.apply(this, arguments); } function getSTACImageRequests(_x5) { return _getSTACImageRequests.apply(this, arguments); } // TODO: image loading should really be _driven by the modules_ // E.g. the user chooses what kind of pipeline they want on the GPU, then from those modules we can // see whether the user wants the pansharpened band, colormap, etc /** * Load images defined by a STAC metadata object plus user-defined parameters */ function _getSTACImageRequests() { _getSTACImageRequests = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(options) { var loadAssetIds, request; return _regenerator["default"].wrap(function _callee3$(_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: loadAssetIds = options.loadAssetIds; if (!(loadAssetIds.length === 1)) { _context3.next = 6; break; } _context3.next = 4; return getSingleAssetSTACRequest(options); case 4: request = _context3.sent; return _context3.abrupt("return", request ? [request] : null); case 6: _context3.next = 8; return getMultiAssetSTACRequest(options); case 8: return _context3.abrupt("return", _context3.sent); case 9: case "end": return _context3.stop(); } }, _callee3); })); return _getSTACImageRequests.apply(this, arguments); } function loadSTACImageData(_x6) { return _loadSTACImageData.apply(this, arguments); } /** * Utility function that forms an asset request based on given parameters. */ function _loadSTACImageData() { _loadSTACImageData = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(requests) { return _regenerator["default"].wrap(function _callee4$(_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: return _context4.abrupt("return", requests.length === 1 ? loadSingleAssetSTAC(requests[0]) : loadMultiAssetSTAC(requests)); case 1: case "end": return _context4.stop(); } }, _callee4); })); return _loadSTACImageData.apply(this, arguments); } function getAssetRequest(_x7) { return _getAssetRequest.apply(this, arguments); } /** * Utility function for forming a request for a single asset STAC metadata object. */ // eslint-disable-next-line complexity, max-statements function _getAssetRequest() { _getAssetRequest = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5(_ref) { var url, rasterServerUrl, params, options, _ref$useMask, useMask, responseRequiredBandIndices, requestUrl, requestParams, requestOptions, assetUrl; return _regenerator["default"].wrap(function _callee5$(_context5) { while (1) switch (_context5.prev = _context5.next) { case 0: url = _ref.url, rasterServerUrl = _ref.rasterServerUrl, params = _ref.params, options = _ref.options, _ref$useMask = _ref.useMask, useMask = _ref$useMask === void 0 ? true : _ref$useMask, responseRequiredBandIndices = _ref.responseRequiredBandIndices; requestUrl = url; requestParams = params !== null && params !== void 0 ? params : new URLSearchParams(); requestOptions = options; assetUrl = requestParams ? "".concat(requestUrl, "?").concat(requestParams.toString()) : requestUrl; return _context5.abrupt("return", { url: assetUrl, rasterServerUrl: rasterServerUrl, options: requestOptions, useMask: useMask, responseRequiredBandIndices: responseRequiredBandIndices }); case 6: case "end": return _context5.stop(); } }, _callee5); })); return _getAssetRequest.apply(this, arguments); } function getSingleAssetSTACRequest(_x8) { return _getSingleAssetSTACRequest.apply(this, arguments); } /** * Utility function for forming requests for a multi asset STAC metadata object. */ function _getSingleAssetSTACRequest() { _getSingleAssetSTACRequest = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6(options) { var stac, loadAssetIds, loadBandIndexes, signal, useSTACSearching, _options$index, x, y, z, stacSearchProvider, startDate, endDate, _stacQuery, useMask, urlInfo, urlParams, responseRequiredBandIndices; return _regenerator["default"].wrap(function _callee6$(_context6) { while (1) switch (_context6.prev = _context6.next) { case 0: stac = options.stac, loadAssetIds = options.loadAssetIds, loadBandIndexes = options.loadBandIndexes, signal = options.signal, useSTACSearching = options.useSTACSearching, _options$index = options.index, x = _options$index.x, y = _options$index.y, z = _options$index.z, stacSearchProvider = options.stacSearchProvider, startDate = options.startDate, endDate = options.endDate, _stacQuery = options._stacQuery; useMask = true; // Only a single URL because only a single asset urlInfo = null; urlParams = new URLSearchParams(); responseRequiredBandIndices = loadBandIndexes; if (!(useSTACSearching && stac.type !== 'Feature')) { _context6.next = 10; break; } urlParams = (0, _url.getStacApiUrlParams)({ stac: stac, stacSearchProvider: stacSearchProvider, startDate: startDate, endDate: endDate, mask: useMask, loadAssetIds: loadAssetIds, _stacQuery: _stacQuery }); urlInfo = (0, _url.getTitilerUrl)({ stac: stac, useSTACSearching: useSTACSearching, x: x, y: y, z: z }); _context6.next = 17; break; case 10: if (!(stac.type === 'Feature')) { _context6.next = 16; break; } // stac is an Item urlParams = (0, _url.getSingleCOGUrlParams)({ stac: stac, loadAssetId: loadAssetIds[0], loadBandIndexes: loadBandIndexes, mask: useMask }); urlInfo = (0, _url.getTitilerUrl)({ stac: stac, useSTACSearching: useSTACSearching, x: x, y: y, z: z }); responseRequiredBandIndices = null; _context6.next = 17; break; case 16: return _context6.abrupt("return", null); case 17: if (urlInfo.url) { _context6.next = 19; break; } return _context6.abrupt("return", null); case 19: _context6.next = 21; return getAssetRequest(_objectSpread(_objectSpread({}, urlInfo), {}, { params: urlParams, options: { signal: signal }, useMask: useMask, responseRequiredBandIndices: responseRequiredBandIndices })); case 21: return _context6.abrupt("return", _context6.sent); case 22: case "end": return _context6.stop(); } }, _callee6); })); return _getSingleAssetSTACRequest.apply(this, arguments); } function getMultiAssetSTACRequest(_x9) { return _getMultiAssetSTACRequest.apply(this, arguments); } /** * Load image data when consolidated in bands within a single STAC Asset */ function _getMultiAssetSTACRequest() { _getMultiAssetSTACRequest = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee7(options) { var stac, loadAssetIds, loadBandIndexes, signal, useSTACSearching, stacSearchProvider, startDate, endDate, _options$index2, x, y, z, _stacQuery, requestData, requestMask; return _regenerator["default"].wrap(function _callee7$(_context7) { while (1) switch (_context7.prev = _context7.next) { case 0: stac = options.stac, loadAssetIds = options.loadAssetIds, loadBandIndexes = options.loadBandIndexes, signal = options.signal, useSTACSearching = options.useSTACSearching, stacSearchProvider = options.stacSearchProvider, startDate = options.startDate, endDate = options.endDate, _options$index2 = options.index, x = _options$index2.x, y = _options$index2.y, z = _options$index2.z, _stacQuery = options._stacQuery; // Multiple urls, one for each asset requestData = []; // We assume that there's only one validity mask for the entire asset, and therefore any of the // requests would return the same validity bitmap. Therefore we only request a mask for the first // asset requestMask = new Array(loadAssetIds.length).fill(false); requestMask[0] = true; if (useSTACSearching && stac.type !== 'Feature') { requestData = zip(loadAssetIds, requestMask).map(function (_ref2) { var _ref3 = (0, _slicedToArray2["default"])(_ref2, 2), assetId = _ref3[0], mask = _ref3[1]; var params = (0, _url.getStacApiUrlParams)({ stac: stac, stacSearchProvider: stacSearchProvider, startDate: startDate, endDate: endDate, mask: mask, loadAssetIds: [assetId], _stacQuery: _stacQuery }); var urlInfo = (0, _url.getTitilerUrl)({ stac: stac, useSTACSearching: useSTACSearching, x: x, y: y, z: z }); return _objectSpread(_objectSpread({}, urlInfo), {}, { params: params }); }); } else if (stac.type === 'Feature') { requestData = zip3(loadAssetIds, loadBandIndexes, requestMask).map(function (_ref4) { var _ref5 = (0, _slicedToArray2["default"])(_ref4, 3), assetId = _ref5[0], bandIndex = _ref5[1], mask = _ref5[2]; var params = (0, _url.getSingleCOGUrlParams)({ stac: stac, loadAssetId: assetId, loadBandIndexes: [bandIndex], mask: mask }); var urlInfo = (0, _url.getTitilerUrl)({ stac: stac, useSTACSearching: useSTACSearching, x: x, y: y, z: z }); return _objectSpread(_objectSpread({}, urlInfo), {}, { params: params }); }); } if (!isValidRequestData(requestData)) { _context7.next = 9; break; } _context7.next = 8; return Promise.all(requestData.map(function (request) { var _request$options; return getAssetRequest(_objectSpread(_objectSpread({}, request), {}, { options: (_request$options = request.options) !== null && _request$options !== void 0 ? _request$options : { signal: signal } })); })); case 8: return _context7.abrupt("return", _context7.sent); case 9: return _context7.abrupt("return", null); case 10: case "end": return _context7.stop(); } }, _callee7); })); return _getMultiAssetSTACRequest.apply(this, arguments); } function loadSingleAssetSTAC(_x10) { return _loadSingleAssetSTAC.apply(this, arguments); } /** * Load image data when split among multiple STAC Assets */ function _loadSingleAssetSTAC() { _loadSingleAssetSTAC = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee8(request) { var imageBands, imageMask, mappedImageBands; return _regenerator["default"].wrap(function _callee8$(_context8) { while (1) switch (_context8.prev = _context8.next) { case 0: _context8.next = 2; return (0, _gpuUtils.loadNpyArray)(request, true); case 2: imageBands = _context8.sent; if (imageBands) { _context8.next = 5; break; } return _context8.abrupt("return", { imageBands: imageBands }); case 5: imageMask = request.useMask && imageBands.pop() || null; mappedImageBands = imageBands; if (request.responseRequiredBandIndices) { mappedImageBands = request.responseRequiredBandIndices.map(function (i) { return imageBands[i]; }); } return _context8.abrupt("return", { imageBands: mappedImageBands, imageMask: imageMask }); case 9: case "end": return _context8.stop(); } }, _callee8); })); return _loadSingleAssetSTAC.apply(this, arguments); } function loadMultiAssetSTAC(_x11) { return _loadMultiAssetSTAC.apply(this, arguments); } /** * Iterate over two arrays simultaneously, similar to Python's zip builtin */ function _loadMultiAssetSTAC() { _loadMultiAssetSTAC = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee9(requests) { var _results$; var results, imageMask, imageBands; return _regenerator["default"].wrap(function _callee9$(_context9) { while (1) switch (_context9.prev = _context9.next) { case 0: _context9.next = 2; return Promise.all(requests.map(function (request) { return (0, _gpuUtils.loadNpyArray)(request, true); })); case 2: results = _context9.sent; // The first request includes a mask imageMask = ((_results$ = results[0]) === null || _results$ === void 0 ? void 0 : _results$.pop()) || null; imageBands = results.flat().filter(function (band) { return band !== null; }); return _context9.abrupt("return", { imageBands: imageBands, imageMask: imageMask }); case 6: case "end": return _context9.stop(); } }, _callee9); })); return _loadMultiAssetSTAC.apply(this, arguments); } function zip(a, b) { return a.map(function (k, i) { return [k, b[i]]; }); } /** * Iterate over three arrays simultaneously, similar to Python's zip builtin */ function zip3(a, b, c) { return a.map(function (k, i) { return [k, b[i], c[i]]; }); } /** * Type guard to check if all array elements are strings */ function isValidRequestData(arr) { return arr.every(function (x) { return typeof (x === null || x === void 0 ? void 0 : x.url) === 'string' && (x === null || x === void 0 ? void 0 : x.params) !== null; }); } /** * Create base64 image data url for categorical colormap * @param categoricalOptions - color map configuration and min-max values of categorical band * @returns base64 image data url */ function getCategoricalColormapDataUrl(categoricalOptions) { var bitmap = (0, _rasterTileUtils.generateCategoricalBitmapArray)(categoricalOptions); if (!bitmap) { return null; } return getImageDataURL(bitmap, _rasterTileUtils.CATEGORICAL_TEXTURE_WIDTH, 1); } /** * Load terrain mesh from raster tile server * @param props - properties to load terrain data * @returns terrain mesh data */ function loadTerrain(_x12) { return _loadTerrain.apply(this, arguments); } function _loadTerrain() { _loadTerrain = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee11(props) { var _props$index, x, y, z, boundsForGeometry, signal, rasterTileServerUrls, meshMaxError, terrainUrlInfo, loaderOptions, numAttempts, mesh; return _regenerator["default"].wrap(function _callee11$(_context11) { while (1) switch (_context11.prev = _context11.next) { case 0: _props$index = props.index, x = _props$index.x, y = _props$index.y, z = _props$index.z, boundsForGeometry = props.boundsForGeometry, signal = props.signal, rasterTileServerUrls = props.rasterTileServerUrls; meshMaxError = (0, _url.getMeshMaxError)(z, MESH_MULTIPLIER); terrainUrlInfo = (0, _url.getTerrainUrl)(rasterTileServerUrls, x, y, z, meshMaxError); loaderOptions = (0, _constants.getLoaderOptions)(); numAttempts = 1 + (0, _utils.getApplicationConfig)().rasterServerMaxRetries; _context11.next = 7; return (0, _requestThrottle.getRequestThrottle)().throttleRequest(terrainUrlInfo.rasterServerUrl, /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee10() { var attempt, _getApplicationConfig, _error$response; return _regenerator["default"].wrap(function _callee10$(_context10) { while (1) switch (_context10.prev = _context10.next) { case 0: attempt = 0; case 1: if (!(attempt < numAttempts)) { _context10.next = 17; break; } _context10.prev = 2; _context10.next = 5; return (0, _core.load)(terrainUrlInfo.url, _terrain.QuantizedMeshLoader, { fetch: { signal: signal }, 'quantized-mesh': _objectSpread(_objectSpread({}, loaderOptions['quantized-mesh']), {}, { bounds: boundsForGeometry, skirtHeight: meshMaxError * SKIRT_HEIGHT_MULTIPLIER }) }); case 5: return _context10.abrupt("return", _context10.sent); case 8: _context10.prev = 8; _context10.t0 = _context10["catch"](2); if (!(attempt < numAttempts && _context10.t0 instanceof _core.FetchError && (_getApplicationConfig = (0, _utils.getApplicationConfig)().rasterServerServerErrorsToRetry) !== null && _getApplicationConfig !== void 0 && _getApplicationConfig.includes((_error$response = _context10.t0.response) === null || _error$response === void 0 ? void 0 : _error$response.status))) { _context10.next = 14; break; } _context10.next = 13; return (0, _commonUtils.sleep)((0, _utils.getApplicationConfig)().rasterServerRetryDelay); case 13: return _context10.abrupt("continue", 14); case 14: attempt++; _context10.next = 1; break; case 17: return _context10.abrupt("return", null); case 18: case "end": return _context10.stop(); } }, _callee10, null, [[2, 8]]); }))); case 7: mesh = _context11.sent; return _context11.abrupt("return", mesh); case 9: case "end": return _context11.stop(); } }, _callee11); })); return _loadTerrain.apply(this, arguments); } //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfY29yZSIsInJlcXVpcmUiLCJfdGVycmFpbiIsIl9tZW1vaXplIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsIl9jb21tb25VdGlscyIsIl9jb25zdGFudHMiLCJfdXRpbHMiLCJfY29uZmlnIiwiX2dwdVV0aWxzIiwiX3Jhc3RlclRpbGVVdGlscyIsIl91cmwiLCJfcmVxdWVzdFRocm90dGxlIiwib3duS2V5cyIsImUiLCJyIiwidCIsIk9iamVjdCIsImtleXMiLCJnZXRPd25Qcm9wZXJ0eVN5bWJvbHMiLCJvIiwiZmlsdGVyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZW51bWVyYWJsZSIsInB1c2giLCJhcHBseSIsIl9vYmplY3RTcHJlYWQiLCJhcmd1bWVudHMiLCJsZW5ndGgiLCJmb3JFYWNoIiwiX2RlZmluZVByb3BlcnR5MiIsImdldE93blByb3BlcnR5RGVzY3JpcHRvcnMiLCJkZWZpbmVQcm9wZXJ0aWVzIiwiZGVmaW5lUHJvcGVydHkiLCJnZXRJbWFnZURhdGFVUkwiLCJiaXRtYXAiLCJ3aWR0aCIsImhlaWdodCIsImNhbnZhcyIsImRvY3VtZW50IiwiY3JlYXRlRWxlbWVudCIsImdldENvbnRleHQiLCJzZXRBdHRyaWJ1dGUiLCJjb25jYXQiLCJjdHgiLCJpIiwiX2JpdG1hcCRzdWJhcnJheSIsInN1YmFycmF5IiwiX2JpdG1hcCRzdWJhcnJheTIiLCJfc2xpY2VkVG9BcnJheTIiLCJnIiwiYiIsImEiLCJmaWxsU3R5bGUiLCJmaWxsUmVjdCIsInRvRGF0YVVSTCIsIk1FU0hfTVVMVElQTElFUiIsIlNLSVJUX0hFSUdIVF9NVUxUSVBMSUVSIiwibG9hZEltYWdlcyIsIl94IiwiX3gyIiwiX3gzIiwiX2xvYWRJbWFnZXMiLCJfYXN5bmNUb0dlbmVyYXRvcjIiLCJfcmVnZW5lcmF0b3IiLCJtYXJrIiwiX2NhbGxlZSIsImFzc2V0UmVxdWVzdHMiLCJjb2xvcm1hcElkIiwiY2F0ZWdvcmljYWxPcHRpb25zIiwiX3lpZWxkJFByb21pc2UkYWxsIiwiX3lpZWxkJFByb21pc2UkYWxsMiIsInN0YWNJbWFnZXMiLCJjb2xvcm1hcCIsImltYWdlQ29sb3JtYXAiLCJ3cmFwIiwiX2NhbGxlZSQiLCJfY29udGV4dCIsInByZXYiLCJuZXh0IiwiUHJvbWlzZSIsImFsbCIsImxvYWRTVEFDSW1hZ2VEYXRhIiwiQ0FURUdPUklDQUxfQ09MT1JNQVBfSUQiLCJtZW1vaXplZExvYWRDb2xvcm1hcCIsInNlbnQiLCJnZW5lcmF0ZUNhdGVnb3JpY2FsQ29sb3JtYXBUZXh0dXJlIiwiYWJydXB0Iiwic3RvcCIsIm1lbW9pemUiLCJsb2FkQ29sb3JtYXAiLCJfeDQiLCJfbG9hZENvbG9ybWFwIiwiX2NhbGxlZTIiLCJyZXF1ZXN0IiwiX2NhbGxlZTIkIiwiX2NvbnRleHQyIiwiZ2V0QXNzZXRSZXF1ZXN0IiwidXJsIiwiUmFzdGVyTGF5ZXJSZXNvdXJjZXMiLCJyYXN0ZXJDb2xvck1hcCIsInJhc3RlclNlcnZlclVybCIsIm9wdGlvbnMiLCJsb2FkSW1hZ2UiLCJDT0xPUk1BUF9URVhUVVJFX1BBUkFNRVRFUlMiLCJnZXRTVEFDSW1hZ2VSZXF1ZXN0cyIsIl94NSIsIl9nZXRTVEFDSW1hZ2VSZXF1ZXN0cyIsIl9jYWxsZWUzIiwibG9hZEFzc2V0SWRzIiwiX2NhbGxlZTMkIiwiX2NvbnRleHQzIiwiZ2V0U2luZ2xlQXNzZXRTVEFDUmVxdWVzdCIsImdldE11bHRpQXNzZXRTVEFDUmVxdWVzdCIsIl94NiIsIl9sb2FkU1RBQ0ltYWdlRGF0YSIsIl9jYWxsZWU0IiwicmVxdWVzdHMiLCJfY2FsbGVlNCQiLCJfY29udGV4dDQiLCJsb2FkU2luZ2xlQXNzZXRTVEFDIiwibG9hZE11bHRpQXNzZXRTVEFDIiwiX3g3IiwiX2dldEFzc2V0UmVxdWVzdCIsIl9jYWxsZWU1IiwiX3JlZiIsInBhcmFtcyIsIl9yZWYkdXNlTWFzayIsInVzZU1hc2siLCJyZXNwb25zZVJlcXVpcmVkQmFuZEluZGljZXMiLCJyZXF1ZXN0VXJsIiwicmVxdWVzdFBhcmFtcyIsInJlcXVlc3RPcHRpb25zIiwiYXNzZXRVcmwiLCJfY2FsbGVlNSQiLCJfY29udGV4dDUiLCJVUkxTZWFyY2hQYXJhbXMiLCJ0b1N0cmluZyIsIl94OCIsIl9nZXRTaW5nbGVBc3NldFNUQUNSZXF1ZXN0IiwiX2NhbGxlZTYiLCJzdGFjIiwibG9hZEJhbmRJbmRleGVzIiwic2lnbmFsIiwidXNlU1RBQ1NlYXJjaGluZyIsIl9vcHRpb25zJGluZGV4IiwieCIsInkiLCJ6Iiwic3RhY1NlYXJjaFByb3ZpZGVyIiwic3RhcnREYXRlIiwiZW5kRGF0ZSIsIl9zdGFjUXVlcnkiLCJ1cmxJbmZvIiwidXJsUGFyYW1zIiwiX2NhbGxlZTYkIiwiX2NvbnRleHQ2IiwiaW5kZXgiLCJ0eXBlIiwiZ2V0U3RhY0FwaVVybFBhcmFtcyIsIm1hc2siLCJnZXRUaXRpbGVyVXJsIiwiZ2V0U2luZ2xlQ09HVXJsUGFyYW1zIiwibG9hZEFzc2V0SWQiLCJfeDkiLCJfZ2V0TXVsdGlBc3NldFNUQUNSZXF1ZXN0IiwiX2NhbGxlZTciLCJfb3B0aW9ucyRpbmRleDIiLCJyZXF1ZXN0RGF0YSIsInJlcXVlc3RNYXNrIiwiX2NhbGxlZTckIiwiX2NvbnRleHQ3IiwiQXJyYXkiLCJmaWxsIiwiemlwIiwibWFwIiwiX3JlZjIiLCJfcmVmMyIsImFzc2V0SWQiLCJ6aXAzIiwiX3JlZjQiLCJfcmVmNSIsImJhbmRJbmRleCIsImlzVmFsaWRSZXF1ZXN0RGF0YSIsIl9yZXF1ZXN0JG9wdGlvbnMiLCJfeDEwIiwiX2xvYWRTaW5nbGVBc3NldFNUQUMiLCJfY2FsbGVlOCIsImltYWdlQmFuZHMiLCJpbWFnZU1hc2siLCJtYXBwZWRJbWFnZUJhbmRzIiwiX2NhbGxlZTgkIiwiX2NvbnRleHQ4IiwibG9hZE5weUFycmF5IiwicG9wIiwiX3gxMSIsIl9sb2FkTXVsdGlBc3NldFNUQUMiLCJfY2FsbGVlOSIsIl9yZXN1bHRzJCIsInJlc3VsdHMiLCJfY2FsbGVlOSQiLCJfY29udGV4dDkiLCJmbGF0IiwiYmFuZCIsImsiLCJjIiwiYXJyIiwiZXZlcnkiLCJnZXRDYXRlZ29yaWNhbENvbG9ybWFwRGF0YVVybCIsImdlbmVyYXRlQ2F0ZWdvcmljYWxCaXRtYXBBcnJheSIsIkNBVEVHT1JJQ0FMX1RFWFRVUkVfV0lEVEgiLCJsb2FkVGVycmFpbiIsIl94MTIiLCJfbG9hZFRlcnJhaW4iLCJfY2FsbGVlMTEiLCJwcm9wcyIsIl9wcm9wcyRpbmRleCIsImJvdW5kc0Zvckdlb21ldHJ5IiwicmFzdGVyVGlsZVNlcnZlclVybHMiLCJtZXNoTWF4RXJyb3IiLCJ0ZXJyYWluVXJsSW5mbyIsImxvYWRlck9wdGlvbnMiLCJudW1BdHRlbXB0cyIsIm1lc2giLCJfY2FsbGVlMTEkIiwiX2NvbnRleHQxMSIsImdldE1lc2hNYXhFcnJvciIsImdldFRlcnJhaW5VcmwiLCJnZXRMb2FkZXJPcHRpb25zIiwiZ2V0QXBwbGljYXRpb25Db25maWciLCJyYXN0ZXJTZXJ2ZXJNYXhSZXRyaWVzIiwiZ2V0UmVxdWVzdFRocm90dGxlIiwidGhyb3R0bGVSZXF1ZXN0IiwiX2NhbGxlZTEwIiwiYXR0ZW1wdCIsIl9nZXRBcHBsaWNhdGlvbkNvbmZpZyIsIl9lcnJvciRyZXNwb25zZSIsIl9jYWxsZWUxMCQiLCJfY29udGV4dDEwIiwibG9hZCIsIlF1YW50aXplZE1lc2hMb2FkZXIiLCJmZXRjaCIsImJvdW5kcyIsInNraXJ0SGVpZ2h0IiwidDAiLCJGZXRjaEVycm9yIiwicmFzdGVyU2VydmVyU2VydmVyRXJyb3JzVG9SZXRyeSIsImluY2x1ZGVzIiwicmVzcG9uc2UiLCJzdGF0dXMiLCJzbGVlcCIsInJhc3RlclNlcnZlclJldHJ5RGVsYXkiXSwic291cmNlcyI6WyIuLi8uLi9zcmMvcmFzdGVyLXRpbGUvaW1hZ2UudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVFxuLy8gQ29weXJpZ2h0IGNvbnRyaWJ1dG9ycyB0byB0aGUga2VwbGVyLmdsIHByb2plY3RcblxuLyoqXG4gKiBVdGlsaXR5IGZ1bmN0aW9ucyB0byBjcmVhdGUgb2JqZWN0cyB0byBwYXNzIHRvIGRlY2suZ2wtcmFzdGVyXG4gKi9cblxuaW1wb3J0IHtsb2FkLCBGZXRjaEVycm9yfSBmcm9tICdAbG9hZGVycy5nbC9jb3JlJztcbmltcG9ydCB7UXVhbnRpemVkTWVzaExvYWRlcn0gZnJvbSAnQGxvYWRlcnMuZ2wvdGVycmFpbic7XG5pbXBvcnQgbWVtb2l6ZSBmcm9tICdsb2Rhc2gvbWVtb2l6ZSc7XG5cbmltcG9ydCB7c2xlZXB9IGZyb20gJ0BrZXBsZXIuZ2wvY29tbW9uLXV0aWxzJztcbmltcG9ydCB7Z2V0TG9hZGVyT3B0aW9uc30gZnJvbSAnQGtlcGxlci5nbC9jb25zdGFudHMnO1xuaW1wb3J0IHtnZXRBcHBsaWNhdGlvbkNvbmZpZ30gZnJvbSAnQGtlcGxlci5nbC91dGlscyc7XG5cbmltcG9ydCB7Q0FURUdPUklDQUxfQ09MT1JNQVBfSUR9IGZyb20gJy4vY29uZmlnJztcbmltcG9ydCB7XG4gIGxvYWROcHlBcnJheSxcbiAgZ2VuZXJhdGVDYXRlZ29yaWNhbENvbG9ybWFwVGV4dHVyZSxcbiAgbG9hZEltYWdlLFxuICBDT0xPUk1BUF9URVhUVVJFX1BBUkFNRVRFUlNcbn0gZnJvbSAnLi9ncHUtdXRpbHMnO1xuaW1wb3J0IHtDQVRFR09SSUNBTF9URVhUVVJFX1dJRFRILCBnZW5lcmF0ZUNhdGVnb3JpY2FsQml0bWFwQXJyYXl9IGZyb20gJy4vcmFzdGVyLXRpbGUtdXRpbHMnO1xuaW1wb3J0IHtcbiAgR2V0VGlsZURhdGFQcm9wcyxcbiAgSW1hZ2VEYXRhLFxuICBUZXJyYWluRGF0YSxcbiAgQXNzZXRSZXF1ZXN0RGF0YSxcbiAgQ2F0ZWdvcmljYWxDb2xvcm1hcE9wdGlvbnMsXG4gIENvbG9ybWFwSW1hZ2VEYXRhXG59IGZyb20gJy4vdHlwZXMnO1xuaW1wb3J0IHtcbiAgZ2V0VGl0aWxlclVybCxcbiAgZ2V0VGVycmFpblVybCxcbiAgZ2V0U2luZ2xlQ09HVXJsUGFyYW1zLFxuICBnZXRTdGFjQXBpVXJsUGFyYW1zLFxuICBnZXRNZXNoTWF4RXJyb3IsXG4gIFJhc3RlckxheWVyUmVzb3VyY2VzXG59IGZyb20gJy4vdXJsJztcbmltcG9ydCB7Z2V0UmVxdWVzdFRocm90dGxlfSBmcm9tICcuL3JlcXVlc3QtdGhyb3R0bGUnO1xuXG4vKipcbiAqIENyZWF0ZSBkYXRhVXJsIGltYWdlIGZyb20gYml0bWFwIGFycmF5XG4gKiBAcGFyYW0gYml0bWFwIC0gUkdCQSBieXRlIGJpdG1hcCBhcnJheVxuICogQHBhcmFtIHdpZHRoIC0gd2lkdGggb2YgdGhlIGltYWdlXG4gKiBAcGFyYW0gaGVpZ2h0IC0gaGVpZ2h0IG9mIHRoZSBpbWFnZVxuICogQHJldHVybnMgYmFzZTY0IGRhdGEgdXJsIG9mIHRoZSBpbWFnZVxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0SW1hZ2VEYXRhVVJMKGJpdG1hcDogVWludDhBcnJheSwgd2lkdGg6IG51bWJlciwgaGVpZ2h0OiBudW1iZXIpOiBzdHJpbmcgfCBudWxsIHtcbiAgY29uc3QgY2FudmFzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnY2FudmFzJyk7XG4gIGlmICghY2FudmFzLmdldENvbnRleHQpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBjYW52YXMuc2V0QXR0cmlidXRlKCd3aWR0aCcsIGAke3dpZHRofWApO1xuICBjYW52YXMuc2V0QXR0cmlidXRlKCdoZWlnaHQnLCBgJHtoZWlnaHR9YCk7XG4gIGNvbnN0IGN0eCA9IGNhbnZhcy5nZXRDb250ZXh0KCcyZCcpO1xuICBpZiAoIWN0eCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgd2lkdGg7IGkrKykge1xuICAgIGNvbnN0IFtyLCBnLCBiLCBhXSA9IGJpdG1hcC5zdWJhcnJheShpICogNCwgaSAqIDQgKyA0KTtcbiAgICBpZiAoYSAhPT0gMCkge1xuICAgICAgY3R4LmZpbGxTdHlsZSA9IGByZ2IoJHtyfSwgJHtnfSwgJHtifSlgO1xuICAgICAgY3R4LmZpbGxSZWN0KGksIDAsIDEsIDEpO1xuICAgIH1cbiAgfVxuICByZXR1cm4gY2FudmFzLnRvRGF0YVVSTCgnaW1hZ2UvcG5nJyk7XG59XG5cbi8vIEdsb2JhbCBtdWx0aXBsaWVyIHRvIGJlIGFwcGxpZWQgdG8gdGVycmFpbiBtZXNoIHJlc29sdXRpb25cbi8vIEEgc21hbGxlciBudW1iZXIgdG93YXJkcyAwIHdpbGwgY2F1c2UgZ2VuZXJhdGlvbiBvZiBtZXNoIGRhdGEgd2l0aCBhIHNtYWxsZXIgbWF4IGVycm9yIGFuZCB0aHVzIGFcbi8vIGhpZ2hlciByZXNvbHV0aW9uIG1lc2hcbmNvbnN0IE1FU0hfTVVMVElQTElFUiA9IDAuODtcblxuLyoqXG4gKiBNdWx0aXBsaWVyIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBoZWlnaHQgb2YgdGVycmFpbiBtZXNoIHNraXJ0cy5cbiAqIFRoZSBza2lydCBpcyBhbiBleHRlbnNpb24gb2YgdGhlIHRlcnJhaW4gbWVzaCB0aGF0IGhlbHBzIHByZXZlbnQgZ2FwcyBiZXR3ZWVuIHRlcnJhaW4gdGlsZXNcbiAqIGJ5IGV4dGVuZGluZyB0aGUgbWVzaCBlZGdlcyBkb3dud2FyZC4gVGhpcyBtdWx0aXBsaWVyIGlzIGFwcGxpZWQgdG8gdGhlIG1lc2gncyBtYXhpbXVtIGVycm9yXG4gKiB0byBkZXRlcm1pbmUgdGhlIHNraXJ0IGhlaWdodCwgZW5zdXJpbmcgbm8gZ2FwcyBhcmUgdmlzaWJsZS5cbiAqL1xuY29uc3QgU0tJUlRfSEVJR0hUX01VTFRJUExJRVIgPSAzO1xuXG4vKipcbiAqIExvYWQgaW1hZ2VzIHJlcXVpcmVkIGZvciByYXN0ZXIgdGlsZVxuICogQHBhcmFtIGFzc2V0UmVxdWVzdHMgLSBTVEFDIGFzc2V0IHJlcXVlc3RzXG4gKiBAcGFyYW0gY29sb3JtYXBJZCAtIGNvbG9ybWFwIGlkXG4gKiBAcGFyYW0gY2F0ZWdvcmljYWxPcHRpb25zIC0gY2F0ZWdvcmljYWwgb3B0aW9ucyByZXF1cmVkIGZvciBtYWtpbmcgb2YgY2F0ZWdvcmljYWwgY29sb3JtYXBcbiAqIEByZXR1cm5zIGltYWdlcyBtYXBcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRJbWFnZXMoXG4gIGFzc2V0UmVxdWVzdHM6IEFzc2V0UmVxdWVzdERhdGFbXSxcbiAgY29sb3JtYXBJZDogc3RyaW5nLFxuICBjYXRlZ29yaWNhbE9wdGlvbnM6IENhdGVnb3JpY2FsQ29sb3JtYXBPcHRpb25zXG4pOiBQcm9taXNlPEltYWdlRGF0YT4ge1xuICAvLyBXZSBsb2FkIGltYWdlIGRhdGEgKHNpbmdsZSBvciBtdWx0aSBhc3NldCBiYXNlZCBvbiBudW1iZXIgb2YgcmVxdWVzdHMpIGFuZCBjb2xvcm1hcCBpbiBwYXJhbGxlbFxuICBjb25zdCBbc3RhY0ltYWdlcywgY29sb3JtYXBdID0gYXdhaXQgUHJvbWlzZS5hbGwoW1xuICAgIGxvYWRTVEFDSW1hZ2VEYXRhKGFzc2V0UmVxdWVzdHMpLFxuICAgIChjb2xvcm1hcElkICE9PSBDQVRFR09SSUNBTF9DT0xPUk1BUF9JRCAmJiBtZW1vaXplZExvYWRDb2xvcm1hcChjb2xvcm1hcElkKSkgfHwgbnVsbFxuICBdKTtcbiAgbGV0IGltYWdlQ29sb3JtYXAgPSBjb2xvcm1hcDtcbiAgaWYgKGNvbG9ybWFwSWQgPT09IENBVEVHT1JJQ0FMX0NPTE9STUFQX0lEKSB7XG4gICAgaW1hZ2VDb2xvcm1hcCA9IGdlbmVyYXRlQ2F0ZWdvcmljYWxDb2xvcm1hcFRleHR1cmUoY2F0ZWdvcmljYWxPcHRpb25zKTtcbiAgfVxuICByZXR1cm4ge1xuICAgIC4uLnN0YWNJbWFnZXMsXG4gICAgaW1hZ2VDb2xvcm1hcFxuICB9O1xufVxuXG4vKipcbiAqIENhY2hlIGxvYWRpbmcgb2YgY29sb3JtYXAuIEVhY2ggY29sb3JtYXAgZmlsZSBpcyB2ZXJ5IHNtYWxsLCBvbiB0aGUgb3JkZXIgb2YgYSBmZXcgaHVuZHJlZCBieXRlcyxcbiAqIHNvIGEgZ2xvYmFsIGNhY2hlIGxpa2UgdGhpcyBpcyBub3QgZXhwZWN0ZWQgdG8gZWF0IHVwIHRvbyBtdWNoIG1lbW9yeS5cbiAqIE1vc3QgY29sb3JtYXBzIGFyZSBsb2FkZWQgZnJvbSBDRE4uIEluIGNhc2UgY29sb3JtYXBJZCBpcyBjYXRlZ29yaWNhbCwgdGhlIGltYWdlIGlzIGNyZWF0ZWQgZnJvbVxuICogY2F0ZWdvcmljYWwgY29sb3JtYXAgb3B0aW9ucy5cbiAqIE5PVEU6IHRoaXMgaW1wbGVtZW50YXRpb24gd2lsbCBjYWNoZSBwcm9taXNlIHJlamVjdGlvbnMgYXMgd2VsbCwgYnV0IHNpbmNlIHRoZXNlIGFyZSBzbWFsbCBmaWxlc1xuICogb24gdGhlIENETiwgdGhhdCdzIGFuIGFjY2VwdGFibGUgcmlzayBmb3IgdGhlIHRpbWUgYmVpbmcuXG4gKi9cbmNvbnN0IG1lbW9pemVkTG9hZENvbG9ybWFwOiAoY29sb3JtYXBJZDogc3RyaW5nKSA9PiBQcm9taXNlPENvbG9ybWFwSW1hZ2VEYXRhIHwgbnVsbD4gfCBudWxsID1cbiAgbWVtb2l6ZSgoY29sb3JtYXBJZDogc3RyaW5nKSA9PiB7XG4gICAgaWYgKGNvbG9ybWFwSWQgPT09IENBVEVHT1JJQ0FMX0NPTE9STUFQX0lEKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIGxvYWRDb2xvcm1hcChjb2xvcm1hcElkKTtcbiAgICB9XG4gIH0pO1xuXG4vKipcbiAqIExvYWQgUE5HIGNvbG9ybWFwLlxuICogU2luY2UgdGhlIGNvbG9ybWFwIGZpbGVzIGFyZSBzbyBzbWFsbCAoY291cGxlIGh1bmRyZWQgYnl0ZXMpLCB3ZSBsb2FkIHRoZW0gYWx3YXlzLCBldmVuIGZvciBhblxuICogUkdCIGNvbXBvc2l0ZSB3aGVyZSB0aGUgY29sb3JtYXAgd29uJ3QgYmUgdXNlZC4gQWZ0ZXIgdGhlIGZpcnN0IGxvYWQsIHRoZSBjb2xvcm1hcCBzaG91bGQgYmVcbiAqIGxvYWRlZCBmcm9tIHRoZSBkaXNrIGNhY2hlLiBCeSBsb2FkaW5nIGFsd2F5cywgd2UgY2FuIHByZXZlbnQgdW5uZWNlc3NhcmlseSB0cmlnZ2VyaW5nXG4gKiBnZXRUaWxlRGF0YSwgd2hpY2ggcmVkdWNlcyBmbGFzaGluZyBmcm9tIHJlbG9hZGluZyB0aGUgaW1hZ2VzIG9uIHRoZSBHUFUuXG4gKlxuICogQHBhcmFtIGNvbG9ybWFwSWQgICAgICBJRCBvZiBjb2xvcm1hcCBQTkdcbiAqXG4gKiBAcmV0dXJucyBpbWFnZSBvYmplY3QgdG8gcGFzcyB0byBUZXh0dXJlMkQgY29uc3RydWN0b3JcbiAqL1xuYXN5bmMgZnVuY3Rpb24gbG9hZENvbG9ybWFwKGNvbG9ybWFwSWQ6IHN0cmluZyk6IFByb21pc2U8Q29sb3JtYXBJbWFnZURhdGEgfCBudWxsPiB7XG4gIGlmICghY29sb3JtYXBJZCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgY29uc3QgcmVxdWVzdCA9IGF3YWl0IGdldEFzc2V0UmVxdWVzdCh7XG4gICAgdXJsOiBSYXN0ZXJMYXllclJlc291cmNlcy5yYXN0ZXJDb2xvck1hcChjb2xvcm1hcElkKSxcbiAgICByYXN0ZXJTZXJ2ZXJVcmw6ICcnLFxuICAgIG9wdGlvbnM6IHt9XG4gIH0pO1xuICByZXR1cm4gbG9hZEltYWdlKHJlcXVlc3QudXJsLCBDT0xPUk1BUF9URVhUVVJFX1BBUkFNRVRFUlMsIHJlcXVlc3Qub3B0aW9ucyk7XG59XG5cbi8qKlxuICogR2V0IHRoZSByZXF1ZXN0IGRhdGEgZm9yIGxvYWRpbmcgYSBzaW5nbGUgb3IgbXVsdGkgYXNzZXQgU1RBQyBtZXRhZGF0YSBvYmplY3QuXG4gKiBAcGFyYW0gb3B0aW9ucyBTVEFDIG1ldGFkYXRhIG9iamVjdCBhbmQgdXNlci1kZWZpbmVkIHBhcmFtZXRlcnMuXG4gKiBAcmV0dXJucyBJbWFnZSBkYXRhLCBvciBudWxsIGlmIHRoZSBTVEFDIG1ldGFkYXRhIG9iamVjdCBpcyBub3Qgc3VwcG9ydGVkLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0U1RBQ0ltYWdlUmVxdWVzdHMoXG4gIG9wdGlvbnM6IEdldFRpbGVEYXRhUHJvcHNcbik6IFByb21pc2U8QXNzZXRSZXF1ZXN0RGF0YVtdIHwgbnVsbD4ge1xuICBjb25zdCB7bG9hZEFzc2V0SWRzfSA9IG9wdGlvbnM7XG5cbiAgaWYgKGxvYWRBc3NldElkcy5sZW5ndGggPT09IDEpIHtcbiAgICBjb25zdCByZXF1ZXN0ID0gYXdhaXQgZ2V0U2luZ2xlQXNzZXRTVEFDUmVxdWVzdChvcHRpb25zKTtcbiAgICByZXR1cm4gcmVxdWVzdCA/IFtyZXF1ZXN0XSA6IG51bGw7XG4gIH1cblxuICByZXR1cm4gYXdhaXQgZ2V0TXVsdGlBc3NldFNUQUNSZXF1ZXN0KG9wdGlvbnMpO1xufVxuXG4vLyBUT0RPOiBpbWFnZSBsb2FkaW5nIHNob3VsZCByZWFsbHkgYmUgX2RyaXZlbiBieSB0aGUgbW9kdWxlc19cbi8vIEUuZy4gdGhlIHVzZXIgY2hvb3NlcyB3aGF0IGtpbmQgb2YgcGlwZWxpbmUgdGhleSB3YW50IG9uIHRoZSBHUFUsIHRoZW4gZnJvbSB0aG9zZSBtb2R1bGVzIHdlIGNhblxuLy8gc2VlIHdoZXRoZXIgdGhlIHVzZXIgd2FudHMgdGhlIHBhbnNoYXJwZW5lZCBiYW5kLCBjb2xvcm1hcCwgZXRjXG4vKipcbiAqIExvYWQgaW1hZ2VzIGRlZmluZWQgYnkgYSBTVEFDIG1ldGFkYXRhIG9iamVjdCBwbHVzIHVzZXItZGVmaW5lZCBwYXJhbWV0ZXJzXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkU1RBQ0ltYWdlRGF0YShyZXF1ZXN0czogQXNzZXRSZXF1ZXN0RGF0YVtdKTogUHJvbWlzZTxJbWFnZURhdGE+IHtcbiAgcmV0dXJuIHJlcXVlc3RzLmxlbmd0aCA9PT0gMSA/IGxvYWRTaW5nbGVBc3NldFNUQUMocmVxdWVzdHNbMF0pIDogbG9hZE11bHRpQXNzZXRTVEFDKHJlcXVlc3RzKTtcbn1cblxuLyoqXG4gKiBVdGlsaXR5IGZ1bmN0aW9uIHRoYXQgZm9ybXMgYW4gYXNzZXQgcmVxdWVzdCBiYXNlZCBvbiBnaXZlbiBwYXJhbWV0ZXJzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0QXNzZXRSZXF1ZXN0KHtcbiAgdXJsLFxuICByYXN0ZXJTZXJ2ZXJVcmwsXG4gIHBhcmFtcyxcbiAgb3B0aW9ucyxcbiAgdXNlTWFzayA9IHRydWUsXG4gIHJlc3BvbnNlUmVxdWlyZWRCYW5kSW5kaWNlc1xufToge1xuICB1cmw6IHN0cmluZztcbiAgcmFzdGVyU2VydmVyVXJsOiBzdHJpbmc7XG4gIHBhcmFtcz86IFVSTFNlYXJjaFBhcmFtcyB8IG51bGw7XG4gIG9wdGlvbnM6IFJlcXVlc3RJbml0O1xuICB1c2VNYXNrPzogYm9vbGVhbjtcbiAgcmVzcG9uc2VSZXF1aXJlZEJhbmRJbmRpY2VzPzogbnVtYmVyW10gfCBudWxsO1xufSk6IFByb21pc2U8QXNzZXRSZXF1ZXN0RGF0YT4ge1xuICBjb25zdCByZXF1ZXN0VXJsID0gdXJsO1xuICBjb25zdCByZXF1ZXN0UGFyYW1zID0gcGFyYW1zID8/IG5ldyBVUkxTZWFyY2hQYXJhbXMoKTtcbiAgY29uc3QgcmVxdWVzdE9wdGlvbnMgPSBvcHRpb25zO1xuXG4gIGNvbnN0IGFzc2V0VXJsID0gcmVxdWVzdFBhcmFtcyA/IGAke3JlcXVlc3RVcmx9PyR7cmVxdWVzdFBhcmFtcy50b1N0cmluZygpfWAgOiByZXF1ZXN0VXJsO1xuICByZXR1cm4ge1xuICAgIHVybDogYXNzZXRVcmwsXG4gICAgcmFzdGVyU2VydmVyVXJsLFxuICAgIG9wdGlvbnM6IHJlcXVlc3RPcHRpb25zLFxuICAgIHVzZU1hc2ssXG4gICAgcmVzcG9uc2VSZXF1aXJlZEJhbmRJbmRpY2VzXG4gIH07XG59XG5cbi8qKlxuICogVXRpbGl0eSBmdW5jdGlvbiBmb3IgZm9ybWluZyBhIHJlcXVlc3QgZm9yIGEgc2luZ2xlIGFzc2V0IFNUQUMgbWV0YWRhdGEgb2JqZWN0LlxuICovXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY29tcGxleGl0eSwgbWF4LXN0YXRlbWVudHNcbmFzeW5jIGZ1bmN0aW9uIGdldFNpbmdsZUFzc2V0U1RBQ1JlcXVlc3QoXG4gIG9wdGlvbnM6IEdldFRpbGVEYXRhUHJvcHNcbik6IFByb21pc2U8QXNzZXRSZXF1ZXN0RGF0YSB8IG51bGw+IHtcbiAgY29uc3Qge1xuICAgIHN0YWMsXG4gICAgbG9hZEFzc2V0SWRzLFxuICAgIGxvYWRCYW5kSW5kZXhlcyxcbiAgICBzaWduYWwsXG4gICAgdXNlU1RBQ1NlYXJjaGluZyxcbiAgICBpbmRleDoge3gsIHksIHp9LFxuICAgIHN0YWNTZWFyY2hQcm92aWRlcixcbiAgICBzdGFydERhdGUsXG4gICAgZW5kRGF0ZSxcbiAgICBfc3RhY1F1ZXJ5XG4gIH0gPSBvcHRpb25zO1xuXG4gIGNvbnN0IHVzZU1hc2sgPSB0cnVlO1xuXG4gIC8vIE9ubHkgYSBzaW5nbGUgVVJMIGJlY2F1c2Ugb25seSBhIHNpbmdsZSBhc3NldFxuICBsZXQgdXJsSW5mbzoge3VybDogc3RyaW5nOyByYXN0ZXJTZXJ2ZXJVcmw6IHN0cmluZ30gfCBudWxsID0gbnVsbDtcbiAgbGV0IHVybFBhcmFtczogVVJMU2VhcmNoUGFyYW1zIHwgbnVsbCA9IG5ldyBVUkxTZWFyY2hQYXJhbXMoKTtcbiAgbGV0IHJlc3BvbnNlUmVxdWlyZWRCYW5kSW5kaWNlczogbnVtYmVyW10gfCBudWxsID0gbG9hZEJhbmRJbmRleGVzO1xuICBpZiAodXNlU1RBQ1NlYXJjaGluZyAmJiBzdGFjLnR5cGUgIT09ICdGZWF0dXJlJykge1xuICAgIHVybFBhcmFtcyA9IGdldFN0YWNBcGlVcmxQYXJhbXMoe1xuICAgICAgc3RhYyxcbiAgICAgIHN0YWNTZWFyY2hQcm92aWRlcixcbiAgICAgIHN0YXJ0RGF0ZSxcbiAgICAgIGVuZERhdGUsXG4gICAgICBtYXNrOiB1c2VNYXNrLFxuICAgICAgbG9hZEFzc2V0SWRzLFxuICAgICAgX3N0YWNRdWVyeVxuICAgIH0pO1xuICAgIHVybEluZm8gPSBnZXRUaXRpbGVyVXJsKHtzdGFjLCB1c2VTVEFDU2VhcmNoaW5nLCB4LCB5LCB6fSk7XG4gIH0gZWxzZSBpZiAoc3RhYy50eXBlID09PSAnRmVhdHVyZScpIHtcbiAgICAvLyBzdGFjIGlzIGFuIEl0ZW1cbiAgICB1cmxQYXJhbXMgPSBnZXRTaW5nbGVDT0dVcmxQYXJhbXMoe1xuICAgICAgc3RhYyxcbiAgICAgIGxvYWRBc3NldElkOiBsb2FkQXNzZXRJZHNbMF0sXG4gICAgICBsb2FkQmFuZEluZGV4ZXMsXG4gICAgICBtYXNrOiB1c2VNYXNrXG4gICAgfSk7XG4gICAgdXJsSW5mbyA9IGdldFRpdGlsZXJVcmwoe3N0YWMsIHVzZVNUQUNTZWFyY2hpbmcsIHgsIHksIHp9KTtcbiAgICByZXNwb25zZVJlcXVpcmVkQmFuZEluZGljZXMgPSBudWxsO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgaWYgKCF1cmxJbmZvLnVybCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgcmV0dXJuIGF3YWl0IGdldEFzc2V0UmVxdWVzdCh7XG4gICAgLi4udXJsSW5mbyxcbiAgICBwYXJhbXM6IHVybFBhcmFtcyxcbiAgICBvcHRpb25zOiB7c2lnbmFsfSxcbiAgICB1c2VNYXNrLFxuICAgIHJlc3BvbnNlUmVxdWlyZWRCYW5kSW5kaWNlc1xuICB9KTtcbn1cblxuLyoqXG4gKiBVdGlsaXR5IGZ1bmN0aW9uIGZvciBmb3JtaW5nIHJlcXVlc3RzIGZvciBhIG11bHRpIGFzc2V0IFNUQUMgbWV0YWRhdGEgb2JqZWN0LlxuICovXG5hc3luYyBmdW5jdGlvbiBnZXRNdWx0aUFzc2V0U1RBQ1JlcXVlc3QoXG4gIG9wdGlvbnM6IEdldFRpbGVEYXRhUHJvcHNcbik6IFByb21pc2U8QXNzZXRSZXF1ZXN0RGF0YVtdIHwgbnVsbD4ge1xuICBjb25zdCB7XG4gICAgc3RhYyxcbiAgICBsb2FkQXNzZXRJZHMsXG4gICAgbG9hZEJhbmRJbmRleGVzLFxuICAgIHNpZ25hbCxcbiAgICB1c2VTVEFDU2VhcmNoaW5nLFxuICAgIHN0YWNTZWFyY2hQcm92aWRlcixcbiAgICBzdGFydERhdGUsXG4gICAgZW5kRGF0ZSxcbiAgICBpbmRleDoge3gsIHksIHp9LFxuICAgIF9zdGFjUXVlcnlcbiAgfSA9IG9wdGlvbnM7XG5cbiAgLy8gTXVsdGlwbGUgdXJscywgb25lIGZvciBlYWNoIGFzc2V0XG4gIGxldCByZXF1ZXN0RGF0YToge1xuICAgIHVybDogc3RyaW5nO1xuICAgIHJhc3RlclNlcnZlclVybDogc3RyaW5nO1xuICAgIHBhcmFtczogVVJMU2VhcmNoUGFyYW1zIHwgbnVsbDtcbiAgICBvcHRpb25zPzogUmVxdWVzdEluaXQ7XG4gIH1bXSA9IFtdO1xuXG4gIC8vIFdlIGFzc3VtZSB0aGF0IHRoZXJlJ3Mgb25seSBvbmUgdmFsaWRpdHkgbWFzayBmb3IgdGhlIGVudGlyZSBhc3NldCwgYW5kIHRoZXJlZm9yZSBhbnkgb2YgdGhlXG4gIC8vIHJlcXVlc3RzIHdvdWxkIHJldHVybiB0aGUgc2FtZSB2YWxpZGl0eSBiaXRtYXAuIFRoZXJlZm9yZSB3ZSBvbmx5IHJlcXVlc3QgYSBtYXNrIGZvciB0aGUgZmlyc3RcbiAgLy8gYXNzZXRcbiAgY29uc3QgcmVxdWVzdE1hc2s6IGJvb2xlYW5bXSA9IG5ldyBBcnJheShsb2FkQXNzZXRJZHMubGVuZ3RoKS5maWxsKGZhbHNlKTtcbiAgcmVxdWVzdE1hc2tbMF0gPSB0cnVlO1xuXG4gIGlmICh1c2VTVEFDU2VhcmNoaW5nICYmIHN0YWMudHlwZSAhPT0gJ0ZlYXR1cmUnKSB7XG4gICAgcmVxdWVzdERhdGEgPSB6aXAobG9hZEFzc2V0SWRzLCByZXF1ZXN0TWFzaykubWFwKChbYXNzZXRJZCwgbWFza10pID0+IHtcbiAgICAgIGNvbnN0IHBhcmFtcyA9IGdldFN0YWNBcGlVcmxQYXJhbXMoe1xuICAgICAgICBzdGFjLFxuICAgICAgICBzdGFjU2VhcmNoUHJvdmlkZXIsXG4gICAgICAgIHN0YXJ0RGF0ZSxcbiAgICAgICAgZW5kRGF0ZSxcbiAgICAgICAgbWFzayxcbiAgICAgICAgbG9hZEFzc2V0SWRzOiBbYXNzZXRJZF0sXG4gICAgICAgIF9zdGFjUXVlcnlcbiAgICAgIH0pO1xuICAgICAgY29uc3QgdXJsSW5mbyA9IGdldFRpdGlsZXJVcmwoe3N0YWMsIHVzZVNUQUNTZWFyY2hpbmcsIHgsIHksIHp9KTtcbiAgICAgIHJldHVybiB7Li4udXJsSW5mbywgcGFyYW1zfTtcbiAgICB9KTtcbiAgfSBlbHNlIGlmIChzdGFjLnR5cGUgPT09ICdGZWF0dXJlJykge1xuICAgIHJlcXVlc3REYXRhID0gemlwMyhsb2FkQXNzZXRJZHMsIGxvYWRCYW5kSW5kZXhlcywgcmVxdWVzdE1hc2spLm1hcChcbiAgICAgIChbYXNzZXRJZCwgYmFuZEluZGV4LCBtYXNrXSkgPT4ge1xuICAgICAgICBjb25zdCBwYXJhbXMgPSBnZXRTaW5nbGVDT0dVcmxQYXJhbXMoe1xuICAgICAgICAgIHN0YWMsXG4gICAgICAgICAgbG9hZEFzc2V0SWQ6IGFzc2V0SWQsXG4gICAgICAgICAgbG9hZEJhbmRJbmRleGVzOiBbYmFuZEluZGV4XSxcbiAgICAgICAgICBtYXNrXG4gICAgICAgIH0pO1xuICAgICAgICBjb25zdCB1cmxJbmZvID0gZ2V0VGl0aWxlclVybCh7c3RhYywgdXNlU1RBQ1NlYXJjaGluZywgeCwgeSwgen0pO1xuICAgICAgICByZXR1cm4gey4uLnVybEluZm8sIHBhcmFtc307XG4gICAgICB9XG4gICAgKTtcbiAgfVxuXG4gIGlmIChpc1ZhbGlkUmVxdWVzdERhdGEocmVxdWVzdERhdGEpKSB7XG4gICAgcmV0dXJuIGF3YWl0IFByb21pc2UuYWxsKFxuICAgICAgcmVxdWVzdERhdGEubWFwKHJlcXVlc3QgPT5cbiAgICAgICAgZ2V0QXNzZXRSZXF1ZXN0KHsuLi5yZXF1ZXN0LCBvcHRpb25zOiByZXF1ZXN0Lm9wdGlvbnMgPz8ge3NpZ25hbH19KVxuICAgICAgKVxuICAgICk7XG4gIH1cblxuICByZXR1cm4gbnVsbDtcbn1cblxuLyoqXG4gKiBMb2FkIGltYWdlIGRhdGEgd2hlbiBjb25zb2xpZGF0ZWQgaW4gYmFuZHMgd2l0aGluIGEgc2luZ2xlIFNUQUMgQXNzZXRcbiAqL1xuYXN5bmMgZnVuY3Rpb24gbG9hZFNpbmdsZUFzc2V0U1RBQyhyZXF1ZXN0OiBBc3NldFJlcXVlc3REYXRhKTogUHJvbWlzZTxJbWFnZURhdGE+IHtcbiAgY29uc3QgaW1hZ2VCYW5kcyA9IGF3YWl0IGxvYWROcHlBcnJheShyZXF1ZXN0LCB0cnVlKTtcblxuICBpZiAoIWltYWdlQmFuZHMpIHtcbiAgICByZXR1cm4ge1xuICAgICAgaW1hZ2VCYW5kc1xuICAgIH07XG4gIH1cblxuICBjb25zdCBpbWFnZU1hc2sgPSAocmVxdWVzdC51c2VNYXNrICYmIGltYWdlQmFuZHMucG9wKCkpIHx8IG51bGw7XG4gIGxldCBtYXBwZWRJbWFnZUJhbmRzID0gaW1hZ2VCYW5kcztcbiAgaWYgKHJlcXVlc3QucmVzcG9uc2VSZXF1aXJlZEJhbmRJbmRpY2VzKSB7XG4gICAgbWFwcGVkSW1hZ2VCYW5kcyA9IHJlcXVlc3QucmVzcG9uc2VSZXF1aXJlZEJhbmRJbmRpY2VzLm1hcChpID0+IGltYWdlQmFuZHNbaV0pO1xuICB9XG4gIHJldHVybiB7aW1hZ2VCYW5kczogbWFwcGVkSW1hZ2VCYW5kcywgaW1hZ2VNYXNrfTtcbn1cblxuLyoqXG4gKiBMb2FkIGltYWdlIGRhdGEgd2hlbiBzcGxpdCBhbW9uZyBtdWx0aXBsZSBTVEFDIEFzc2V0c1xuICovXG5hc3luYyBmdW5jdGlvbiBsb2FkTXVsdGlBc3NldFNUQUMocmVxdWVzdHM6IEFzc2V0UmVxdWVzdERhdGFbXSk6IFByb21pc2U8SW1hZ2VEYXRhPiB7XG4gIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChyZXF1ZXN0cy5tYXAocmVxdWVzdCA9PiBsb2FkTnB5QXJyYXkocmVxdWVzdCwgdHJ1ZSkpKTtcbiAgLy8gVGhlIGZpcnN0IHJlcXVlc3QgaW5jbHVkZXMgYSBtYXNrXG4gIGNvbnN0IGltYWdlTWFzayA9IHJlc3VsdHNbMF0/LnBvcCgpIHx8IG51bGw7XG4gIGNvbnN0IGltYWdlQmFuZHMgPSByZXN1bHRzLmZsYXQoKS5maWx0ZXIoYmFuZCA9PiBiYW5kICE9PSBudWxsKSBhcyBJbWFnZURhdGFbJ2ltYWdlQmFuZHMnXTtcbiAgcmV0dXJuIHtpbWFnZUJhbmRzLCBpbWFnZU1hc2t9O1xufVxuXG4vKipcbiAqIEl0ZXJhdGUgb3ZlciB0d28gYXJyYXlzIHNpbXVsdGFuZW91c2x5LCBzaW1pbGFyIHRvIFB5dGhvbidzIHppcCBidWlsdGluXG4gKi9cbmZ1bmN0aW9uIHppcDxUMSwgVDI+KGE6IFQxW10sIGI6IFQyW10pOiBbVDEsIFQyXVtdIHtcbiAgcmV0dXJuIGEubWFwKChrLCBpKSA9PiBbaywgYltpXV0pO1xufVxuXG4vKipcbiAqIEl0ZXJhdGUgb3ZlciB0aHJlZSBhcnJheXMgc2ltdWx0YW5lb3VzbHksIHNpbWlsYXIgdG8gUHl0aG9uJ3MgemlwIGJ1aWx0aW5cbiAqL1xuZnVuY3Rpb24gemlwMzxUMSwgVDIsIFQzPihhOiBUMVtdLCBiOiBUMltdLCBjOiBUM1tdKTogW1QxLCBUMiwgVDNdW10ge1xuICByZXR1cm4gYS5tYXAoKGssIGkpID0+IFtrLCBiW2ldLCBjW2ldXSk7XG59XG5cbi8qKlxuICogVHlwZSBndWFyZCB0byBjaGVjayBpZiBhbGwgYXJyYXkgZWxlbWVudHMgYXJlIHN0cmluZ3NcbiAqL1xuZnVuY3Rpb24gaXNWYWxpZFJlcXVlc3REYXRhKFxuICBhcnI6IHt1cmw6IHVua25vd247IHBhcmFtczogdW5rbm93bjsgb3B0aW9ucz86IHVua25vd259W11cbik6IGFyciBpcyB7dXJsOiBzdHJpbmc7IHBhcmFtczogVVJMU2VhcmNoUGFyYW1zOyBvcHRpb25zOiB1bmtub3dufVtdIHtcbiAgcmV0dXJuIGFyci5ldmVyeSh4ID0+IHR5cGVvZiB4Py51cmwgPT09ICdzdHJpbmcnICYmIHg/LnBhcmFtcyAhPT0gbnVsbCk7XG59XG5cbi8qKlxuICogQ3JlYXRlIGJhc2U2NCBpbWFnZSBkYXRhIHVybCBmb3IgY2F0ZWdvcmljYWwgY29sb3JtYXBcbiAqIEBwYXJhbSBjYXRlZ29yaWNhbE9wdGlvbnMgLSBjb2xvciBtYXAgY29uZmlndXJhdGlvbiBhbmQgbWluLW1heCB2YWx1ZXMgb2YgY2F0ZWdvcmljYWwgYmFuZFxuICogQHJldHVybnMgYmFzZTY0IGltYWdlIGRhdGEgdXJsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRDYXRlZ29yaWNhbENvbG9ybWFwRGF0YVVybChcbiAgY2F0ZWdvcmljYWxPcHRpb25zOiBDYXRlZ29yaWNhbENvbG9ybWFwT3B0aW9uc1xuKTogc3RyaW5nIHwgbnVsbCB7XG4gIGNvbnN0IGJpdG1hcCA9IGdlbmVyYXRlQ2F0ZWdvcmljYWxCaXRtYXBBcnJheShjYXRlZ29yaWNhbE9wdGlvbnMpO1xuICBpZiAoIWJpdG1hcCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIHJldHVybiBnZXRJbWFnZURhdGFVUkwoYml0bWFwLCBDQVRFR09SSUNBTF9URVhUVVJFX1dJRFRILCAxKTtcbn1cblxuLyoqXG4gKiBMb2FkIHRlcnJhaW4gbWVzaCBmcm9tIHJhc3RlciB0aWxlIHNlcnZlclxuICogQHBhcmFtIHByb3BzIC0gcHJvcGVydGllcyB0byBsb2FkIHRlcnJhaW4gZGF0YVxuICogQHJldHVybnMgdGVycmFpbiBtZXNoIGRhdGFcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRUZXJyYWluKHByb3BzOiB7XG4gIGluZGV4OiB7eDogbnVtYmVyOyB5OiBudW1iZXI7IHo6IG51bWJlcn07XG4gIHNpZ25hbDogQWJvcnRTaWduYWw7XG4gIHJhc3RlclRpbGVTZXJ2ZXJVcmxzOiBzdHJpbmdbXTtcbiAgYm91bmRzRm9yR2VvbWV0cnk/OiBbbnVtYmVyLCBudW1iZXIsIG51bWJlciwgbnVtYmVyXTtcbn0pOiBQcm9taXNlPFRlcnJhaW5EYXRhIHwgbnVsbD4ge1xuICBjb25zdCB7XG4gICAgaW5kZXg6IHt4LCB5LCB6fSxc