UNPKG

xng-breadcrumb

Version:

A declarative and reactive breadcrumb approach for Angular 6 and beyond https://www.npmjs.com/package/xng-breadcrumb

663 lines 52.1 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import * as tslib_1 from "tslib"; import { Injectable } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; import { distinctUntilChanged, filter } from 'rxjs/operators'; import * as i0 from "@angular/core"; import * as i1 from "@angular/router"; var BreadcrumbService = /** @class */ (function () { function BreadcrumbService(activatedRoute, router) { this.activatedRoute = activatedRoute; this.router = router; this.baseHref = '/'; /** * dynamicBreadcrumbStore holds information about dynamically updated breadcrumbs. * Breadcrumbs can be set from anywhere (component, service) in the app. * On every breadcrumb update check this store and use the info if available. */ this.dynamicBreadcrumbStore = []; /** * breadcrumbList for the current route * When breadcrumb info is changed dynamically, check if the currentBreadcrumbs is effected * If effected, update the change and emit a new stream */ this.currentBreadcrumbs = []; /** * Breadcrumbs observable to be subscribed by BreadcrumbComponent * Emits on every route change OR dynamic update of breadcrumb */ this.breadcrumbs = new BehaviorSubject([]); this.breadcrumbs$ = this.breadcrumbs.asObservable(); this.pathParamPrefix = ':'; this.pathParamRegexIdentifier = '/:[^/]+'; this.pathParamRegexReplacer = '/[^/]+'; this.setBaseBreadcrumb(); this.detectRouteChanges(); } /** * Update breadcrumb label or options for - * * route (complete route path). route can be passed the same way you define angular routes * 1) update label Ex: set('/mentor', 'Mentor'), set('/mentor/:id', 'Mentor Details') * 2) change visibility Ex: set('/mentor/:id/edit', { skip: true }) * 3) add info Ex: set('/mentor/:id/edit', { info: { icon: 'edit', iconColor: 'blue' } }) * ------------------------ OR ------------------------- * * alias (prefixed with '@'). breadcrumb alias is unique for a route * 1) update label Ex: set('@mentor', 'Enabler') * 2) change visibility Ex: set('@mentorEdit', { skip: true }) * 3) add info Ex: set('@mentorEdit', { info: { icon: 'edit', iconColor: 'blue' } }) */ /** * Update breadcrumb label or options for - * * route (complete route path). route can be passed the same way you define angular routes * 1) update label Ex: set('/mentor', 'Mentor'), set('/mentor/:id', 'Mentor Details') * 2) change visibility Ex: set('/mentor/:id/edit', { skip: true }) * 3) add info Ex: set('/mentor/:id/edit', { info: { icon: 'edit', iconColor: 'blue' } }) * ------------------------ OR ------------------------- * * alias (prefixed with '\@'). breadcrumb alias is unique for a route * 1) update label Ex: set('\@mentor', 'Enabler') * 2) change visibility Ex: set('\@mentorEdit', { skip: true }) * 3) add info Ex: set('\@mentorEdit', { info: { icon: 'edit', iconColor: 'blue' } }) * @param {?} pathOrAlias * @param {?} breadcrumb * @return {?} */ BreadcrumbService.prototype.set = /** * Update breadcrumb label or options for - * * route (complete route path). route can be passed the same way you define angular routes * 1) update label Ex: set('/mentor', 'Mentor'), set('/mentor/:id', 'Mentor Details') * 2) change visibility Ex: set('/mentor/:id/edit', { skip: true }) * 3) add info Ex: set('/mentor/:id/edit', { info: { icon: 'edit', iconColor: 'blue' } }) * ------------------------ OR ------------------------- * * alias (prefixed with '\@'). breadcrumb alias is unique for a route * 1) update label Ex: set('\@mentor', 'Enabler') * 2) change visibility Ex: set('\@mentorEdit', { skip: true }) * 3) add info Ex: set('\@mentorEdit', { info: { icon: 'edit', iconColor: 'blue' } }) * @param {?} pathOrAlias * @param {?} breadcrumb * @return {?} */ function (pathOrAlias, breadcrumb) { if (!this.validateArguments(pathOrAlias, breadcrumb)) { return; } if (typeof breadcrumb === 'string') { breadcrumb = { label: breadcrumb }; } if (pathOrAlias.startsWith('@')) { this.updateStore(tslib_1.__assign({}, breadcrumb, { alias: pathOrAlias.slice(1) })); } else { /** @type {?} */ var breadcrumbExtraProps = this.buildRouteRegExp(pathOrAlias); this.updateStore(tslib_1.__assign({}, breadcrumb, breadcrumbExtraProps)); } }; /** * @private * @return {?} */ BreadcrumbService.prototype.setBaseBreadcrumb = /** * @private * @return {?} */ function () { /** @type {?} */ var baseConfig = this.router.config.find((/** * @param {?} pathConfig * @return {?} */ function (pathConfig) { return pathConfig.path === ''; })); if (baseConfig && baseConfig.data) { var _a = this.getBreadcrumbOptions(baseConfig.data), label = _a.label, alias = _a.alias, _b = _a.skip, skip = _b === void 0 ? false : _b, info = _a.info; /** @type {?} */ var isAutoGeneratedLabel = false; if (typeof label !== 'string' && !label) { label = ''; isAutoGeneratedLabel = true; } this.baseBreadcrumb = { label: label, alias: alias, skip: skip, info: info, routeLink: this.baseHref, isAutoGeneratedLabel: isAutoGeneratedLabel }; } }; /** * Whenever route changes build breadcrumb list again */ /** * Whenever route changes build breadcrumb list again * @private * @return {?} */ BreadcrumbService.prototype.detectRouteChanges = /** * Whenever route changes build breadcrumb list again * @private * @return {?} */ function () { var _this = this; this.router.events .pipe(filter((/** * @param {?} event * @return {?} */ function (event) { return event instanceof NavigationEnd; })), distinctUntilChanged()) .subscribe((/** * @param {?} event * @return {?} */ function (event) { _this.currentBreadcrumbs = _this.baseBreadcrumb ? [_this.baseBreadcrumb] : []; _this.prepareBreadcrumbList(_this.activatedRoute.root, _this.baseHref); })); }; /** * @private * @param {?} activatedRoute * @param {?} routeLinkPrefix * @return {?} */ BreadcrumbService.prototype.prepareBreadcrumbList = /** * @private * @param {?} activatedRoute * @param {?} routeLinkPrefix * @return {?} */ function (activatedRoute, routeLinkPrefix) { if (activatedRoute.routeConfig && activatedRoute.routeConfig.path) { /** @type {?} */ var breadcrumbItem = this.prepareBreadcrumbItem(activatedRoute, routeLinkPrefix); this.currentBreadcrumbs.push(breadcrumbItem); if (activatedRoute.firstChild) { return this.prepareBreadcrumbList(activatedRoute.firstChild, breadcrumbItem.routeLink + '/'); } } else if (activatedRoute.firstChild) { return this.prepareBreadcrumbList(activatedRoute.firstChild, routeLinkPrefix); } // remove breadcrumb items that needs to be hidden or don't have a label /** @type {?} */ var breacrumbsToShow = this.currentBreadcrumbs.filter((/** * @param {?} item * @return {?} */ function (item) { return !item.skip; })); this.breadcrumbs.next(breacrumbsToShow); }; /** * @private * @param {?} activatedRoute * @param {?} routeLinkPrefix * @return {?} */ BreadcrumbService.prototype.prepareBreadcrumbItem = /** * @private * @param {?} activatedRoute * @param {?} routeLinkPrefix * @return {?} */ function (activatedRoute, routeLinkPrefix) { var _a = this.parseRouteData(activatedRoute.routeConfig), path = _a.path, breadcrumb = _a.breadcrumb; // in case of path param get the resolved for param /** @type {?} */ var resolvedPath = this.resolvePathParam(path, activatedRoute); /** @type {?} */ var routeLink = "" + routeLinkPrefix + resolvedPath; var _b = this.getFromStore(breadcrumb.alias, routeLink), label = _b.label, alias = _b.alias, skip = _b.skip, info = _b.info; /** @type {?} */ var isAutoGeneratedLabel = false; if (typeof label !== 'string') { if (typeof breadcrumb.label === 'string') { label = breadcrumb.label; } else { label = resolvedPath; isAutoGeneratedLabel = true; } } return { label: label, alias: alias || breadcrumb.alias, skip: skip || breadcrumb.skip, info: info || breadcrumb.info, routeLink: routeLink, isAutoGeneratedLabel: isAutoGeneratedLabel }; }; /** * For a specific route, breadcrumb can be defined either on parent data OR it's child(which has empty path) data * When both are defined, child takes precedence * * Ex: Below we are setting breadcrumb on both parent and child. * So, child takes precedence and "Defined On Child" is displayed for the route 'home' * { path: 'home', loadChildren: './home/home.module#HomeModule' , data: {breadcrumb: "Defined On Module"}} * AND * children: [ * { path: '', component: ShowUserComponent, data: {breadcrumb: "Defined On Child" } * ] */ /** * For a specific route, breadcrumb can be defined either on parent data OR it's child(which has empty path) data * When both are defined, child takes precedence * * Ex: Below we are setting breadcrumb on both parent and child. * So, child takes precedence and "Defined On Child" is displayed for the route 'home' * { path: 'home', loadChildren: './home/home.module#HomeModule' , data: {breadcrumb: "Defined On Module"}} * AND * children: [ * { path: '', component: ShowUserComponent, data: {breadcrumb: "Defined On Child" } * ] * @private * @param {?} routeConfig * @return {?} */ BreadcrumbService.prototype.parseRouteData = /** * For a specific route, breadcrumb can be defined either on parent data OR it's child(which has empty path) data * When both are defined, child takes precedence * * Ex: Below we are setting breadcrumb on both parent and child. * So, child takes precedence and "Defined On Child" is displayed for the route 'home' * { path: 'home', loadChildren: './home/home.module#HomeModule' , data: {breadcrumb: "Defined On Module"}} * AND * children: [ * { path: '', component: ShowUserComponent, data: {breadcrumb: "Defined On Child" } * ] * @private * @param {?} routeConfig * @return {?} */ function (routeConfig) { var path = routeConfig.path, _a = routeConfig.data, data = _a === void 0 ? {} : _a; /** @type {?} */ var breadcrumb = this.mergeWithBaseChildData(routeConfig, tslib_1.__assign({}, data)); return { path: path, breadcrumb: breadcrumb }; }; /** * @private * @param {?} breadcrumbAlias * @param {?} routeLink * @return {?} */ BreadcrumbService.prototype.getFromStore = /** * @private * @param {?} breadcrumbAlias * @param {?} routeLink * @return {?} */ function (breadcrumbAlias, routeLink) { /** @type {?} */ var matchingItem; if (breadcrumbAlias) { matchingItem = this.dynamicBreadcrumbStore.find((/** * @param {?} item * @return {?} */ function (item) { return item.alias === breadcrumbAlias; })); } if (!matchingItem && routeLink) { matchingItem = this.dynamicBreadcrumbStore.find((/** * @param {?} item * @return {?} */ function (item) { return (item.routeLink && item.routeLink === routeLink) || (item.routeRegex && new RegExp(item.routeRegex).test(routeLink + '/')); })); } return matchingItem || {}; }; /** * To update breadcrumb label for a route with path param, we need regex that matches route. * Instead of user providing regex, we help in preparing regex dynamically * * Ex: route declaration - path: '/mentor/:id' * breadcrumbService.set('/mentor/:id', 'Uday'); * '/mentor/2' OR 'mentor/adasd' we should use 'Uday' as label * * regex string is built, if route has path params(contains with ':') */ /** * To update breadcrumb label for a route with path param, we need regex that matches route. * Instead of user providing regex, we help in preparing regex dynamically * * Ex: route declaration - path: '/mentor/:id' * breadcrumbService.set('/mentor/:id', 'Uday'); * '/mentor/2' OR 'mentor/adasd' we should use 'Uday' as label * * regex string is built, if route has path params(contains with ':') * @private * @param {?} path * @return {?} */ BreadcrumbService.prototype.buildRouteRegExp = /** * To update breadcrumb label for a route with path param, we need regex that matches route. * Instead of user providing regex, we help in preparing regex dynamically * * Ex: route declaration - path: '/mentor/:id' * breadcrumbService.set('/mentor/:id', 'Uday'); * '/mentor/2' OR 'mentor/adasd' we should use 'Uday' as label * * regex string is built, if route has path params(contains with ':') * @private * @param {?} path * @return {?} */ function (path) { // ensure leading slash is provided in the path if (!path.startsWith('/')) { path = '/' + path; } if (path.includes(this.pathParamPrefix)) { // replace mathing path param with a regex // '/mentor/:id' becomes '/mentor/[^/]', which further will be matched in updateStore /** @type {?} */ var routeRegex = path.replace(new RegExp(this.pathParamRegexIdentifier, 'g'), this.pathParamRegexReplacer); return { routeRegex: routeRegex }; } else { return { routeLink: path }; } }; /** * Update current breadcrumb definition and emit a new stream of breadcrumbs * Also update the store to reuse dynamic declarations */ /** * Update current breadcrumb definition and emit a new stream of breadcrumbs * Also update the store to reuse dynamic declarations * @private * @param {?} breadcrumb * @return {?} */ BreadcrumbService.prototype.updateStore = /** * Update current breadcrumb definition and emit a new stream of breadcrumbs * Also update the store to reuse dynamic declarations * @private * @param {?} breadcrumb * @return {?} */ function (breadcrumb) { var _a = this.getBreadcrumbIndexes(breadcrumb), breadcrumbItemIndex = _a.breadcrumbItemIndex, storeItemIndex = _a.storeItemIndex; // if breadcrumb is present in current breadcrumbs update it and emit new stream if (breadcrumbItemIndex > -1) { this.currentBreadcrumbs[breadcrumbItemIndex] = tslib_1.__assign({}, this.currentBreadcrumbs[breadcrumbItemIndex], breadcrumb); /** @type {?} */ var breacrumbsToShow = this.currentBreadcrumbs.filter((/** * @param {?} item * @return {?} */ function (item) { return !item.skip; })); this.breadcrumbs.next(tslib_1.__spread(breacrumbsToShow)); } // If the store already has this route definition update it, else add if (storeItemIndex > -1) { this.dynamicBreadcrumbStore[storeItemIndex] = tslib_1.__assign({}, this.dynamicBreadcrumbStore[storeItemIndex], breadcrumb); } else { this.dynamicBreadcrumbStore.push(breadcrumb); } }; /** * @private * @param {?} breadcrumb * @return {?} */ BreadcrumbService.prototype.getBreadcrumbIndexes = /** * @private * @param {?} breadcrumb * @return {?} */ function (breadcrumb) { var alias = breadcrumb.alias, routeLink = breadcrumb.routeLink, routeRegex = breadcrumb.routeRegex; /** @type {?} */ var indexMap = {}; // identify macthing breadcrumb and store item if (alias) { indexMap = this.getBreadcrumbIndexesByType('alias', alias); } else if (routeLink) { indexMap = this.getBreadcrumbIndexesByType('routeLink', routeLink); } else if (routeRegex) { indexMap = this.getBreadcrumbIndexesByType('routeRegex', routeRegex); } return indexMap; }; /** * @private * @param {?} key * @param {?} value * @return {?} */ BreadcrumbService.prototype.getBreadcrumbIndexesByType = /** * @private * @param {?} key * @param {?} value * @return {?} */ function (key, value) { /** @type {?} */ var breadcrumbItemIndex = this.currentBreadcrumbs.findIndex((/** * @param {?} item * @return {?} */ function (item) { return value === item[key]; })); /** @type {?} */ var storeItemIndex = this.dynamicBreadcrumbStore.findIndex((/** * @param {?} item * @return {?} */ function (item) { return value === item[key]; })); return { breadcrumbItemIndex: breadcrumbItemIndex, storeItemIndex: storeItemIndex }; }; /** * @private * @param {?} path * @param {?} activatedRoute * @return {?} */ BreadcrumbService.prototype.resolvePathParam = /** * @private * @param {?} path * @param {?} activatedRoute * @return {?} */ function (path, activatedRoute) { // if the path segment is a route param, read the param value from url if (path.startsWith(this.pathParamPrefix)) { return activatedRoute.snapshot.params[path.slice(1)]; } return path; }; /** * get empty children of a module or Component. Empty child is the one with path: '' * When parent and it's children (that has empty route path) define data * merge them both with child taking precedence */ /** * get empty children of a module or Component. Empty child is the one with path: '' * When parent and it's children (that has empty route path) define data * merge them both with child taking precedence * @private * @param {?} routeConfig * @param {?} data * @return {?} */ BreadcrumbService.prototype.mergeWithBaseChildData = /** * get empty children of a module or Component. Empty child is the one with path: '' * When parent and it's children (that has empty route path) define data * merge them both with child taking precedence * @private * @param {?} routeConfig * @param {?} data * @return {?} */ function (routeConfig, data) { if (!routeConfig) { return this.getBreadcrumbOptions(data); } /** @type {?} */ var baseChild; if (routeConfig.loadChildren) { // To handle a module with empty child route baseChild = routeConfig._loadedConfig.routes.find((/** * @param {?} route * @return {?} */ function (route) { return route.path === ''; })); } else if (routeConfig.children) { // To handle a component with empty child route baseChild = routeConfig.children.find((/** * @param {?} route * @return {?} */ function (route) { return route.path === ''; })); } return baseChild && baseChild.data ? this.mergeWithBaseChildData(baseChild, tslib_1.__assign({}, this.getBreadcrumbOptions(data), this.getBreadcrumbOptions(baseChild.data))) : this.getBreadcrumbOptions(data); }; /** * @private * @param {?} pathOrAlias * @param {?} breadcrumb * @return {?} */ BreadcrumbService.prototype.validateArguments = /** * @private * @param {?} pathOrAlias * @param {?} breadcrumb * @return {?} */ function (pathOrAlias, breadcrumb) { if (pathOrAlias === null || pathOrAlias === undefined) { console.error('Invalid first argument. Please pass a route path or a breadcrumb alias.'); return false; } else if (breadcrumb === null || breadcrumb === undefined) { console.error('Invalid second argument. Please pass a string or an Object with breadcrumb options.'); return false; } return true; }; /** * breadcrumb can be passed a label or an options object * If passed as a string convert to breadcrumb options object */ /** * breadcrumb can be passed a label or an options object * If passed as a string convert to breadcrumb options object * @private * @param {?} data * @return {?} */ BreadcrumbService.prototype.getBreadcrumbOptions = /** * breadcrumb can be passed a label or an options object * If passed as a string convert to breadcrumb options object * @private * @param {?} data * @return {?} */ function (data) { var breadcrumb = data.breadcrumb; if (typeof breadcrumb === 'string' || !breadcrumb) { breadcrumb = { label: breadcrumb }; } return breadcrumb; }; BreadcrumbService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; /** @nocollapse */ BreadcrumbService.ctorParameters = function () { return [ { type: ActivatedRoute }, { type: Router } ]; }; /** @nocollapse */ BreadcrumbService.ngInjectableDef = i0.ɵɵdefineInjectable({ factory: function BreadcrumbService_Factory() { return new BreadcrumbService(i0.ɵɵinject(i1.ActivatedRoute), i0.ɵɵinject(i1.Router)); }, token: BreadcrumbService, providedIn: "root" }); return BreadcrumbService; }()); export { BreadcrumbService }; if (false) { /** * breadcrumb label for base OR root path. Usually, this can be set as 'Home' * @type {?} * @private */ BreadcrumbService.prototype.baseBreadcrumb; /** * @type {?} * @private */ BreadcrumbService.prototype.baseHref; /** * dynamicBreadcrumbStore holds information about dynamically updated breadcrumbs. * Breadcrumbs can be set from anywhere (component, service) in the app. * On every breadcrumb update check this store and use the info if available. * @type {?} * @private */ BreadcrumbService.prototype.dynamicBreadcrumbStore; /** * breadcrumbList for the current route * When breadcrumb info is changed dynamically, check if the currentBreadcrumbs is effected * If effected, update the change and emit a new stream * @type {?} * @private */ BreadcrumbService.prototype.currentBreadcrumbs; /** * Breadcrumbs observable to be subscribed by BreadcrumbComponent * Emits on every route change OR dynamic update of breadcrumb * @type {?} * @private */ BreadcrumbService.prototype.breadcrumbs; /** @type {?} */ BreadcrumbService.prototype.breadcrumbs$; /** * @type {?} * @private */ BreadcrumbService.prototype.pathParamPrefix; /** * @type {?} * @private */ BreadcrumbService.prototype.pathParamRegexIdentifier; /** * @type {?} * @private */ BreadcrumbService.prototype.pathParamRegexReplacer; /** * @type {?} * @private */ BreadcrumbService.prototype.activatedRoute; /** * @type {?} * @private */ BreadcrumbService.prototype.router; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJlYWRjcnVtYi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6Im5nOi8veG5nLWJyZWFkY3J1bWIvIiwic291cmNlcyI6WyJsaWIvYnJlYWRjcnVtYi5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQUUsY0FBYyxFQUFFLGFBQWEsRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN4RSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQzs7O0FBRzlEO0lBb0NFLDJCQUFvQixjQUE4QixFQUFVLE1BQWM7UUFBdEQsbUJBQWMsR0FBZCxjQUFjLENBQWdCO1FBQVUsV0FBTSxHQUFOLE1BQU0sQ0FBUTtRQTNCbEUsYUFBUSxHQUFHLEdBQUcsQ0FBQzs7Ozs7O1FBT2YsMkJBQXNCLEdBQWlCLEVBQUUsQ0FBQzs7Ozs7O1FBTzFDLHVCQUFrQixHQUFpQixFQUFFLENBQUM7Ozs7O1FBTXRDLGdCQUFXLEdBQUcsSUFBSSxlQUFlLENBQWUsRUFBRSxDQUFDLENBQUM7UUFDckQsaUJBQVksR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksRUFBRSxDQUFDO1FBRTlDLG9CQUFlLEdBQUcsR0FBRyxDQUFDO1FBQ3RCLDZCQUF3QixHQUFHLFNBQVMsQ0FBQztRQUNyQywyQkFBc0IsR0FBRyxRQUFRLENBQUM7UUFHeEMsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDekIsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7SUFDNUIsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7O09BYUc7Ozs7Ozs7Ozs7Ozs7Ozs7OztJQUNILCtCQUFHOzs7Ozs7Ozs7Ozs7Ozs7OztJQUFILFVBQUksV0FBbUIsRUFBRSxVQUErQjtRQUN0RCxJQUFJLENB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dGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEFjdGl2YXRlZFJvdXRlLCBOYXZpZ2F0aW9uRW5kLCBSb3V0ZXIgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xuaW1wb3J0IHsgQmVoYXZpb3JTdWJqZWN0IH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBkaXN0aW5jdFVudGlsQ2hhbmdlZCwgZmlsdGVyIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuaW1wb3J0IHsgQnJlYWRjcnVtYiB9IGZyb20gJy4vYnJlYWRjcnVtYic7XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnXG59KVxuZXhwb3J0IGNsYXNzIEJyZWFkY3J1bWJTZXJ2aWNlIHtcbiAgLyoqXG4gICAqIGJyZWFkY3J1bWIgbGFiZWwgZm9yIGJhc2UgT1Igcm9vdCBwYXRoLiBVc3VhbGx5LCB0aGlzIGNhbiBiZSBzZXQgYXMgJ0hvbWUnXG4gICAqL1xuICBwcml2YXRlIGJhc2VCcmVhZGNydW1iOiBCcmVhZGNydW1iO1xuXG4gIHByaXZhdGUgYmFzZUhyZWYgPSAnLyc7XG5cbiAgLyoqXG4gICAqIGR5bmFtaWNCcmVhZGNydW1iU3RvcmUgaG9sZHMgaW5mb3JtYXRpb24gYWJvdXQgZHluYW1pY2FsbHkgdXBkYXRlZCBicmVhZGNydW1icy5cbiAgICogQnJlYWRjcnVtYnMgY2FuIGJlIHNldCBmcm9tIGFueXdoZXJlIChjb21wb25lbnQsIHNlcnZpY2UpIGluIHRoZSBhcHAuXG4gICAqIE9uIGV2ZXJ5IGJyZWFkY3J1bWIgdXBkYXRlIGNoZWNrIHRoaXMgc3RvcmUgYW5kIHVzZSB0aGUgaW5mbyBpZiBhdmFpbGFibGUuXG4gICAqL1xuICBwcml2YXRlIGR5bmFtaWNCcmVhZGNydW1iU3RvcmU6IEJyZWFkY3J1bWJbXSA9IFtdO1xuXG4gIC8qKlxuICAgKiBicmVhZGNydW1iTGlzdCBmb3IgdGhlIGN1cnJlbnQgcm91dGVcbiAgICogV2hlbiBicmVhZGNydW1iIGluZm8gaXMgY2hhbmdlZCBkeW5hbWljYWxseSwgY2hlY2sgaWYgdGhlIGN1cnJlbnRCcmVhZGNydW1icyBpcyBlZmZlY3RlZFxuICAgKiBJZiBlZmZlY3RlZCwgdXBkYXRlIHRoZSBjaGFuZ2UgYW5kIGVtaXQgYSBuZXcgc3RyZWFtXG4gICAqL1xuICBwcml2YXRlIGN1cnJlbnRCcmVhZGNydW1iczogQnJlYWRjcnVtYltdID0gW107XG5cbiAgLyoqXG4gICAqIEJyZWFkY3J1bWJzIG9ic2VydmFibGUgdG8gYmUgc3Vic2NyaWJlZCBieSBCcmVhZGNydW1iQ29tcG9uZW50XG4gICAqIEVtaXRzIG9uIGV2ZXJ5IHJvdXRlIGNoYW5nZSBPUiBkeW5hbWljIHVwZGF0ZSBvZiBicmVhZGNydW1iXG4gICAqL1xuICBwcml2YXRlIGJyZWFkY3J1bWJzID0gbmV3IEJlaGF2aW9yU3ViamVjdDxCcmVhZGNydW1iW10+KFtdKTtcbiAgcHVibGljIGJyZWFkY3J1bWJzJCA9IHRoaXMuYnJlYWRjcnVtYnMuYXNPYnNlcnZhYmxlKCk7XG5cbiAgcHJpdmF0ZSBwYXRoUGFyYW1QcmVmaXggPSAnOic7XG4gIHByaXZhdGUgcGF0aFBhcmFtUmVnZXhJZGVudGlmaWVyID0gJy86W14vXSsnO1xuICBwcml2YXRlIHBhdGhQYXJhbVJlZ2V4UmVwbGFjZXIgPSAnL1teL10rJztcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGFjdGl2YXRlZFJvdXRlOiBBY3RpdmF0ZWRSb3V0ZSwgcHJpdmF0ZSByb3V0ZXI6IFJvdXRlcikge1xuICAgIHRoaXMuc2V0QmFzZUJyZWFkY3J1bWIoKTtcbiAgICB0aGlzLmRldGVjdFJvdXRlQ2hhbmdlcygpO1xuICB9XG5cbiAgLyoqXG4gICAqIFVwZGF0ZSBicmVhZGNydW1iIGxhYmVsIG9yIG9wdGlvbnMgZm9yIC1cbiAgICpcbiAgICogcm91dGUgKGNvbXBsZXRlIHJvdXRlIHBhdGgpLiByb3V0ZSBjYW4gYmUgcGFzc2VkIHRoZSBzYW1lIHdheSB5b3UgZGVmaW5lIGFuZ3VsYXIgcm91dGVzXG4gICAqIDEpIHVwZGF0ZSBsYWJlbCBFeDogc2V0KCcvbWVudG9yJywgJ01lbnRvcicpLCBzZXQoJy9tZW50b3IvOmlkJywgJ01lbnRvciBEZXRhaWxzJylcbiAgICogMikgY2hhbmdlIHZpc2liaWxpdHkgRXg6IHNldCgnL21lbnRvci86aWQvZWRpdCcsIHsgc2tpcDogdHJ1ZSB9KVxuICAgKiAzKSBhZGQgaW5mbyBFeDogc2V0KCcvbWVudG9yLzppZC9lZGl0JywgeyBpbmZvOiB7IGljb246ICdlZGl0JywgaWNvbkNvbG9yOiAnYmx1ZScgfSB9KVxuICAgKiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gT1IgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgKlxuICAgKiBhbGlhcyAocHJlZml4ZWQgd2l0aCAnQCcpLiBicmVhZGNydW1iIGFsaWFzIGlzIHVuaXF1ZSBmb3IgYSByb3V0ZVxuICAgKiAxKSB1cGRhdGUgbGFiZWwgRXg6IHNldCgnQG1lbnRvcicsICdFbmFibGVyJylcbiAgICogMikgY2hhbmdlIHZpc2liaWxpdHkgRXg6IHNldCgnQG1lbnRvckVkaXQnLCB7IHNraXA6IHRydWUgfSlcbiAgICogMykgYWRkIGluZm8gRXg6IHNldCgnQG1lbnRvckVkaXQnLCB7IGluZm86IHsgaWNvbjogJ2VkaXQnLCBpY29uQ29sb3I6ICdibHVlJyB9IH0pXG4gICAqL1xuICBzZXQocGF0aE9yQWxpYXM6IHN0cmluZywgYnJlYWRjcnVtYjogc3RyaW5nIHwgQnJlYWRjcnVtYikge1xuICAgIGlmICghdGhpcy52YWxpZGF0ZUFyZ3VtZW50cyhwYXRoT3JBbGlhcywgYnJlYWRjcnVtYikpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAodHlwZW9mIGJyZWFkY3J1bWIgPT09ICdzdHJpbmcnKSB7XG4gICAgICBicmVhZGNydW1iID0ge1xuICAgICAgICBsYWJlbDogYnJlYWRjcnVtYlxuICAgICAgfTtcbiAgICB9XG5cbiAgICBpZiAocGF0aE9yQWxpYXMuc3RhcnRzV2l0aCgnQCcpKSB7XG4gICAgICB0aGlzLnVwZGF0ZVN0b3JlKHsgLi4uYnJlYWRjcnVtYiwgYWxpYXM6IHBhdGhPckFsaWFzLnNsaWNlKDEpIH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb25zdCBicmVhZGNydW1iRXh0cmFQcm9wcyA9IHRoaXMuYnVpbGRSb3V0ZVJlZ0V4cChwYXRoT3JBbGlhcyk7XG4gICAgICB0aGlzLnVwZGF0ZVN0b3JlKHsgLi4uYnJlYWRjcnVtYiwgLi4uYnJlYWRjcnVtYkV4dHJhUHJvcHMgfSk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBzZXRCYXNlQnJlYWRjcnVtYigpIHtcbiAgICBjb25zdCBiYXNlQ29uZmlnID0gdGhpcy5yb3V0ZXIuY29uZmlnLmZpbmQocGF0aENvbmZpZyA9PiBwYXRoQ29uZmlnLnBhdGggPT09ICcnKTtcbiAgICBpZiAoYmFzZUNvbmZpZyAmJiBiYXNlQ29uZmlnLmRhdGEpIHtcbiAgICAgIGxldCB7IGxhYmVsLCBhbGlhcywgc2tpcCA9IGZhbHNlLCBpbmZvIH0gPSB0aGlzLmdldEJyZWFkY3J1bWJPcHRpb25zKGJhc2VDb25maWcuZGF0YSk7XG5cbiAgICAgIGxldCBpc0F1dG9HZW5lcmF0ZWRMYWJlbCA9IGZhbHNlO1xuICAgICAgaWYgKHR5cGVvZiBsYWJlbCAhPT0gJ3N0cmluZycgJiYgIWxhYmVsKSB7XG4gICAgICAgIGxhYmVsID0gJyc7XG4gICAgICAgIGlzQXV0b0dlbmVyYXRlZExhYmVsID0gdHJ1ZTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5iYXNlQnJlYWRjcnVtYiA9IHtcbiAgICAgICAgbGFiZWwsXG4gICAgICAgIGFsaWFzLFxuICAgICAgICBza2lwLFxuICAgICAgICBpbmZvLFxuICAgICAgICByb3V0ZUxpbms6IHRoaXMuYmFzZUhyZWYsXG4gICAgICAgIGlzQXV0b0dlbmVyYXRlZExhYmVsXG4gICAgICB9O1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBXaGVuZXZlciByb3V0ZSBjaGFuZ2VzIGJ1aWxkIGJyZWFkY3J1bWIgbGlzdCBhZ2FpblxuICAgKi9cbiAgcHJpdmF0ZSBkZXRlY3RSb3V0ZUNoYW5nZXMoKSB7XG4gICAgdGhpcy5yb3V0ZXIuZXZlbnRzXG4gICAgICAucGlwZShcbiAgICAgICAgZmlsdGVyKGV2ZW50ID0+IGV2ZW50IGluc3RhbmNlb2YgTmF2aWdhdGlvbkVuZCksXG4gICAgICAgIGRpc3RpbmN0VW50aWxDaGFuZ2VkKClcbiAgICAgIClcbiAgICAgIC5zdWJzY3JpYmUoZXZlbnQgPT4ge1xuICAgICAgICB0aGlzLmN1cnJlbnRCcmVhZGNydW1icyA9IHRoaXMuYmFzZUJyZWFkY3J1bWIgPyBbdGhpcy5iYXNlQnJlYWRjcnVtYl0gOiBbXTtcbiAgICAgICAgdGhpcy5wcmVwYXJlQnJlYWRjcnVtYkxpc3QodGhpcy5hY3RpdmF0ZWRSb3V0ZS5yb290LCB0aGlzLmJhc2VIcmVmKTtcbiAgICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBwcmVwYXJlQnJlYWRjcnVtYkxpc3QoYWN0aXZhdGVkUm91dGU6IEFjdGl2YXRlZFJvdXRlLCByb3V0ZUxpbmtQcmVmaXg6IHN0cmluZyk6IEJyZWFkY3J1bWJbXSB7XG4gICAgaWYgKGFjdGl2YXRlZFJvdXRlLnJvdXRlQ29uZmlnICYmIGFjdGl2YXRlZFJvdXRlLnJvdXRlQ29uZmlnLnBhdGgpIHtcbiAgICAgIGNvbnN0IGJyZWFkY3J1bWJJdGVtID0gdGhpcy5wcmVwYXJlQnJlYWRjcnVtYkl0ZW0oYWN0aXZhdGVkUm91dGUsIHJvdXRlTGlua1ByZWZpeCk7XG4gICAgICB0aGlzLmN1cnJlbnRCcmVhZGNydW1icy5wdXNoKGJyZWFkY3J1bWJJdGVtKTtcblxuICAgICAgaWYgKGFjdGl2YXRlZFJvdXRlLmZpcnN0Q2hpbGQpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMucHJlcGFyZUJyZWFkY3J1bWJMaXN0KGFjdGl2YXRlZFJvdXRlLmZpcnN0Q2hpbGQsIGJyZWFkY3J1bWJJdGVtLnJvdXRlTGluayArICcvJyk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChhY3RpdmF0ZWRSb3V0ZS5maXJzdENoaWxkKSB7XG4gICAgICByZXR1cm4gdGhpcy5wcmVwYXJlQnJlYWRjcnVtYkxpc3QoYWN0aXZhdGVkUm91dGUuZmlyc3RDaGlsZCwgcm91dGVMaW5rUHJlZml4KTtcbiAgICB9XG4gICAgLy8gcmVtb3ZlIGJyZWFkY3J1bWIgaXRlbXMgdGhhdCBuZWVkcyB0byBiZSBoaWRkZW4gb3IgZG9uJ3QgaGF2ZSBhIGxhYmVsXG4gICAgY29uc3QgYnJlYWNydW1ic1RvU2hvdyA9IHRoaXMuY3VycmVudEJyZWFkY3J1bWJzLmZpbHRlcihpdGVtID0+ICFpdGVtLnNraXApO1xuXG4gICAgdGhpcy5icmVhZGNydW1icy5uZXh0KGJyZWFjcnVtYnNUb1Nob3cpO1xuICB9XG5cbiAgcHJpdmF0ZSBwcmVwYXJlQnJlYWRjcnVtYkl0ZW0oYWN0aXZhdGVkUm91dGU6IEFjdGl2YXRlZFJvdXRlLCByb3V0ZUxpbmtQcmVmaXg6IHN0cmluZyk6IEJyZWFkY3J1bWIge1xuICAgIGNvbnN0IHsgcGF0aCwgYnJlYWRjcnVtYiB9ID0gdGhpcy5wYXJzZVJvdXRlRGF0YShhY3RpdmF0ZWRSb3V0ZS5yb3V0ZUNvbmZpZyk7XG5cbiAgICAvLyBpbiBjYXNlIG9mIHBhdGggcGFyYW0gZ2V0IHRoZSByZXNvbHZlZCBmb3IgcGFyYW1cbiAgICBjb25zdCByZXNvbHZlZFBhdGggPSB0aGlzLnJlc29sdmVQYXRoUGFyYW0ocGF0aCwgYWN0aXZhdGVkUm91dGUpO1xuICAgIGNvbnN0IHJvdXRlTGluayA9IGAke3JvdXRlTGlua1ByZWZpeH0ke3Jlc29sdmVkUGF0aH1gO1xuXG4gICAgbGV0IHsgbGFiZWwsIGFsaWFzLCBza2lwLCBpbmZvIH0gPSB0aGlzLmdldEZyb21TdG9yZShicmVhZGNydW1iLmFsaWFzLCByb3V0ZUxpbmspO1xuICAgIGxldCBpc0F1dG9HZW5lcmF0ZWRMYWJlbCA9IGZhbHNlO1xuXG4gICAgaWYgKHR5cGVvZiBsYWJlbCAhPT0gJ3N0cmluZycpIHtcbiAgICAgIGlmICh0eXBlb2YgYnJlYWRjcnVtYi5sYWJlbCA9PT0gJ3N0cmluZycpIHtcbiAgICAgICAgbGFiZWwgPSBicmVhZGNydW1iLmxhYmVsO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgbGFiZWwgPSByZXNvbHZlZFBhdGg7XG4gICAgICAgIGlzQXV0b0dlbmVyYXRlZExhYmVsID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4ge1xuICAgICAgbGFiZWwsXG4gICAgICBhbGlhczogYWxpYXMgfHwgYnJlYWRjcnVtYi5hbGlhcyxcbiAgICAgIHNraXA6IHNraXAgfHwgYnJlYWRjcnVtYi5za2lwLFxuICAgICAgaW5mbzogaW5mbyB8fCBicmVhZGNydW1iLmluZm8sXG4gICAgICByb3V0ZUxpbmssXG4gICAgICBpc0F1dG9HZW5lcmF0ZWRMYWJlbFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogRm9yIGEgc3BlY2lmaWMgcm91dGUsIGJyZWFkY3J1bWIgY2FuIGJlIGRlZmluZWQgZWl0aGVyIG9uIHBhcmVudCBkYXRhIE9SIGl0J3MgY2hpbGQod2hpY2ggaGFzIGVtcHR5IHBhdGgpIGRhdGFcbiAgICogV2hlbiBib3RoIGFyZSBkZWZpbmVkLCBjaGlsZCB0YWtlcyBwcmVjZWRlbmNlXG4gICAqXG4gICAqIEV4OiBCZWxvdyB3ZSBhcmUgc2V0dGluZyBicmVhZGNydW1iIG9uIGJvdGggcGFyZW50IGFuZCBjaGlsZC5cbiAgICogU28sIGNoaWxkIHRha2VzIHByZWNlZGVuY2UgYW5kIFwiRGVmaW5lZCBPbiBDaGlsZFwiIGlzIGRpc3BsYXllZCBmb3IgdGhlIHJvdXRlICdob21lJ1xuICAgKiB7IHBhdGg6ICdob21lJywgbG9hZENoaWxkcmVuOiAnLi9ob21lL2hvbWUubW9kdWxlI0hvbWVNb2R1bGUnICwgZGF0YToge2JyZWFkY3J1bWI6IFwiRGVmaW5lZCBPbiBNb2R1bGVcIn19XG4gICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQU5EXG4gICAqIGNoaWxkcmVuOiBbXG4gICAqICAgeyBwYXRoOiAnJywgY29tcG9uZW50OiBTaG93VXNlckNvbXBvbmVudCwgZGF0YToge2JyZWFkY3J1bWI6IFwiRGVmaW5lZCBPbiBDaGlsZFwiIH1cbiAgICogXVxuICAgKi9cbiAgcHJpdmF0ZSBwYXJzZVJvdXRlRGF0YShyb3V0ZUNvbmZpZykge1xuICAgIGNvbnN0IHsgcGF0aCwgZGF0YSA9IHt9IH0gPSByb3V0ZUNvbmZpZztcbiAgICBjb25zdCBicmVhZGNydW1iID0gdGhpcy5tZXJnZVdpdGhCYXNlQ2hpbGREYXRhKHJvdXRlQ29uZmlnLCB7IC4uLmRhdGEgfSk7XG5cbiAgICByZXR1cm4geyBwYXRoLCBicmVhZGNydW1iIH07XG4gIH1cblxuICBwcml2YXRlIGdldEZyb21TdG9yZShicmVhZGNydW1iQWxpYXM6IHN0cmluZywgcm91dGVMaW5rOiBzdHJpbmcpOiBCcmVhZGNydW1iIHtcbiAgICBsZXQgbWF0Y2hpbmdJdGVtO1xuICAgIGlmIChicmVhZGNydW1iQWxpYXMpIHtcbiAgICAgIG1hdGNoaW5nSXRlbSA9IHRoaXMuZHluYW1pY0JyZWFkY3J1bWJTdG9yZS5maW5kKGl0ZW0gPT4gaXRlbS5hbGlhcyA9PT0gYnJlYWRjcnVtYkFsaWFzKTtcbiAgICB9XG5cbiAgICBpZiAoIW1hdGNoaW5nSXRlbSAmJiByb3V0ZUxpbmspIHtcbiAgICAgIG1hdGNoaW5nSXRlbSA9IHRoaXMuZHluYW1pY0JyZWFkY3J1bWJTdG9yZS5maW5kKGl0ZW0gPT4ge1xuICAgICAgICByZXR1cm4gKGl0ZW0ucm91dGVMaW5rICYmIGl0ZW0ucm91dGVMaW5rID09PSByb3V0ZUxpbmspIHx8IChpdGVtLnJvdXRlUmVnZXggJiYgbmV3IFJlZ0V4cChpdGVtLnJvdXRlUmVnZXgpLnRlc3Qocm91dGVMaW5rICsgJy8nKSk7XG4gICAgICB9KTtcbiAgICB9XG5cbiAgICByZXR1cm4gbWF0Y2hpbmdJdGVtIHx8IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIFRvIHVwZGF0ZSBicmVhZGNydW1iIGxhYmVsIGZvciBhIHJvdXRlIHdpdGggcGF0aCBwYXJhbSwgd2UgbmVlZCByZWdleCB0aGF0IG1hdGNoZXMgcm91dGUuXG4gICAqIEluc3RlYWQgb2YgdXNlciBwcm92aWRpbmcgcmVnZXgsIHdlIGhlbHAgaW4gcHJlcGFyaW5nIHJlZ2V4IGR5bmFtaWNhbGx5XG4gICAqXG4gICAqIEV4OiByb3V0ZSBkZWNsYXJhdGlvbiAtIHBhdGg6ICcvbWVudG9yLzppZCdcbiAgICogYnJlYWRjcnVtYlNlcnZpY2Uuc2V0KCcvbWVudG9yLzppZCcsICdVZGF5Jyk7XG4gICAqICcvbWVudG9yLzInIE9SICdtZW50b3IvYWRhc2QnIHdlIHNob3VsZCB1c2UgJ1VkYXknIGFzIGxhYmVsXG4gICAqXG4gICAqIHJlZ2V4IHN0cmluZyBpcyBidWlsdCwgaWYgcm91dGUgaGFzIHBhdGggcGFyYW1zKGNvbnRhaW5zIHdpdGggJzonKVxuICAgKi9cbiAgcHJpdmF0ZSBidWlsZFJvdXRlUmVnRXhwKHBhdGgpIHtcbiAgICAvLyBlbnN1cmUgbGVhZGluZyBzbGFzaCBpcyBwcm92aWRlZCBpbiB0aGUgcGF0aFxuICAgIGlmICghcGF0aC5zdGFydHNXaXRoKCcvJykpIHtcbiAgICAgIHBhdGggPSAnLycgKyBwYXRoO1xuICAgIH1cblxuICAgIGlmIChwYXRoLmluY2x1ZGVzKHRoaXMucGF0aFBhcmFtUHJlZml4KSkge1xuICAgICAgLy8gcmVwbGFjZSBtYXRoaW5nIHBhdGggcGFyYW0gd2l0aCBhIHJlZ2V4XG4gICAgICAvLyAnL21lbnRvci86aWQnIGJlY29tZXMgJy9tZW50b3IvW14vXScsIHdoaWNoIGZ1cnRoZXIgd2lsbCBiZSBtYXRjaGVkIGluIHVwZGF0ZVN0b3JlXG4gICAgICBjb25zdCByb3V0ZVJlZ2V4ID0gcGF0aC5yZXBsYWNlKG5ldyBSZWdFeHAodGhpcy5wYXRoUGFyYW1SZWdleElkZW50aWZpZXIsICdnJyksIHRoaXMucGF0aFBhcmFtUmVnZXhSZXBsYWNlcik7XG4gICAgICByZXR1cm4geyByb3V0ZVJlZ2V4IH07XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiB7IHJvdXRlTGluazogcGF0aCB9O1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBVcGRhdGUgY3VycmVudCBicmVhZGNydW1iIGRlZmluaXRpb24gYW5kIGVtaXQgYSBuZXcgc3RyZWFtIG9mIGJyZWFkY3J1bWJzXG4gICAqIEFsc28gdXBkYXRlIHRoZSBzdG9yZSB0byByZXVzZSBkeW5hbWljIGRlY2xhcmF0aW9uc1xuICAgKi9cbiAgcHJpdmF0ZSB1cGRhdGVTdG9yZShicmVhZGNydW1iKSB7XG4gICAgY29uc3QgeyBicmVhZGNydW1iSXRlbUluZGV4LCBzdG9yZUl0ZW1JbmRleCB9ID0gdGhpcy5nZXRCcmVhZGNydW1iSW5kZXhlcyhicmVhZGNydW1iKTtcblxuICAgIC8vIGlmIGJyZWFkY3J1bWIgaXMgcHJlc2VudCBpbiBjdXJyZW50IGJyZWFkY3J1bWJzIHVwZGF0ZSBpdCBhbmQgZW1pdCBuZXcgc3RyZWFtXG4gICAgaWYgKGJyZWFkY3J1bWJJdGVtSW5kZXggPiAtMSkge1xuICAgICAgdGhpcy5jdXJyZW50QnJlYWRjcnVtYnNbYnJlYWRjcnVtYkl0ZW1JbmRleF0gPSB7IC4uLnRoaXMuY3VycmVudEJyZWFkY3J1bWJzW2JyZWFkY3J1bWJJdGVtSW5kZXhdLCAuLi5icmVhZGNydW1iIH07XG4gICAgICBjb25zdCBicmVhY3J1bWJzVG9TaG93ID0gdGhpcy5jdXJyZW50QnJlYWRjcnVtYnMuZmlsdGVyKGl0ZW0gPT4gIWl0ZW0uc2tpcCk7XG4gICAgICB0aGlzLmJyZWFkY3J1bWJzLm5leHQoWy4uLmJyZWFjcnVtYnNUb1Nob3ddKTtcbiAgICB9XG5cbiAgICAvLyBJZiB0aGUgc3RvcmUgYWxyZWFkeSBoYXMgdGhpcyByb3V0ZSBkZWZpbml0aW9uIHVwZGF0ZSBpdCwgZWxzZSBhZGRcbiAgICBpZiAoc3RvcmVJdGVtSW5kZXggPiAtMSkge1xuICAgICAgdGhpcy5keW5hbWljQnJlYWRjcnVtYlN0b3JlW3N0b3JlSXRlbUluZGV4XSA9IHsgLi4udGhpcy5keW5hbWljQnJlYWRjcnVtYlN0b3JlW3N0b3JlSXRlbUluZGV4XSwgLi4uYnJlYWRjcnVtYiB9O1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmR5bmFtaWNCcmVhZGNydW1iU3RvcmUucHVzaChicmVhZGNydW1iKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGdldEJyZWFkY3J1bWJJbmRleGVzKGJyZWFkY3J1bWIpOiBhbnkge1xuICAgIGNvbnN0IHsgYWxpYXMsIHJvdXRlTGluaywgcm91dGVSZWdleCB9ID0gYnJlYWRjcnVtYjtcbiAgICBsZXQgaW5kZXhNYXAgPSB7fTtcbiAgICAvLyBpZGVudGlmeSBtYWN0aGluZyBicmVhZGNydW1iIGFuZCBzdG9yZSBpdGVtXG4gICAgaWYgKGFsaWFzKSB7XG4gICAgICBpbmRleE1hcCA9IHRoaXMuZ2V0QnJlYWRjcnVtYkluZGV4ZXNCeVR5cGUoJ2FsaWFzJywgYWxpYXMpO1xuICAgIH0gZWxzZSBpZiAocm91dGVMaW5rKSB7XG4gICAgICBpbmRleE1hcCA9IHRoaXMuZ2V0QnJlYWRjcnVtYkluZGV4ZXNCeVR5cGUoJ3JvdXRlTGluaycsIHJvdXRlTGluayk7XG4gICAgfSBlbHNlIGlmIChyb3V0ZVJlZ2V4KSB7XG4gICAgICBpbmRleE1hcCA9IHRoaXMuZ2V0QnJlYWRjcnVtYkluZGV4ZXNCeVR5cGUoJ3JvdXRlUmVnZXgnLCByb3V0ZVJlZ2V4KTtcbiAgICB9XG4gICAgcmV0dXJuIGluZGV4TWFwO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXRCcmVhZGNydW1iSW5kZXhlc0J5VHlwZShrZXk6IHN0cmluZywgdmFsdWU6IHN0cmluZykge1xuICAgIGNvbnN0IGJyZWFkY3J1bWJJdGVtSW5kZXggPSB0aGlzLmN1cnJlbnRCcmVhZGNydW1icy5maW5kSW5kZXgoaXRlbSA9PiB2YWx1ZSA9PT0gaXRlbVtrZXldKTtcbiAgICBjb25zdCBzdG9yZUl0ZW1JbmRleCA9IHRoaXMuZHluYW1pY0JyZWFkY3J1bWJTdG9yZS5maW5kSW5kZXgoaXRlbSA9PiB2YWx1ZSA9PT0gaXRlbVtrZXldKTtcbiAgICByZXR1cm4geyBicmVhZGNydW1iSXRlbUluZGV4LCBzdG9yZUl0ZW1JbmRleCB9O1xuICB9XG5cbiAgcHJpdmF0ZSByZXNvbHZlUGF0aFBhcmFtKHBhdGg6IHN0cmluZywgYWN0aXZhdGVkUm91dGU6IEFjdGl2YXRlZFJvdXRlKSB7XG4gICAgLy8gaWYgdGhlIHBhdGggc2VnbWVudCBpcyBhIHJvdXRlIHBhcmFtLCByZWFkIHRoZSBwYXJhbSB2YWx1ZSBmcm9tIHVybFxuICAgIGlmIChwYXRoLnN0YXJ0c1dpdGgodGhpcy5wYXRoUGFyYW1QcmVmaXgpKSB7XG4gICAgICByZXR1cm4gYWN0aXZhdGVkUm91dGUuc25hcHNob3QucGFyYW1zW3BhdGguc2xpY2UoMSldO1xuICAgIH1cbiAgICByZXR1cm4gcGF0aDtcbiAgfVxuXG4gIC8qKlxuICAgKiBnZXQgZW1wdHkgY2hpbGRyZW4gb2YgYSBtb2R1bGUgb3IgQ29tcG9uZW50LiBFbXB0eSBjaGlsZCBpcyB0aGUgb25lIHdpdGggcGF0aDogJydcbiAgICogV2hlbiBwYXJlbnQgYW5kIGl0J3MgY2hpbGRyZW4gKHRoYXQgaGFzIGVtcHR5IHJvdXRlIHBhdGgpIGRlZmluZSBkYXRhXG4gICAqIG1lcmdlIHRoZW0gYm90aCB3aXRoIGNoaWxkIHRha2luZyBwcmVjZWRlbmNlXG4gICAqL1xuICBwcml2YXRlIG1lcmdlV2l0aEJhc2VDaGlsZERhd