xng-breadcrumb
Version:
A declarative and reactive breadcrumb approach for Angular 6 and beyond https://www.npmjs.com/package/xng-breadcrumb
328 lines • 46.4 kB
JavaScript
import { Injectable } from '@angular/core';
import { ActivatedRoute, GuardsCheckEnd, Router, } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import * as i0 from "@angular/core";
import * as i1 from "@angular/router";
const PATH_PARAM = {
PREFIX: ':',
REGEX_IDENTIFIER: '/:[^/]+',
REGEX_REPLACER: '/[^/]+',
};
const ALIAS_PREFIX = '@';
const isNonEmpty = (obj) => {
return obj && Object.keys(obj).length > 0;
};
export class BreadcrumbService {
constructor(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 = [];
this.previousBreadcrumbs = [];
/**
* 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.detectRouteChanges();
}
/**
* Whenever route changes build breadcrumb list again
*/
detectRouteChanges() {
// Special case where breadcrumb service & component instantiates after a route is navigated.
// Ex: put breadcrumbs within *ngIf and this.router.events would be empty
// This check is also required where { initialNavigation: 'enabledBlocking' } is applied to routes
this.setupBreadcrumbs(this.activatedRoute.snapshot);
this.router.events
.pipe(filter((event) => event instanceof GuardsCheckEnd))
.subscribe((event) => {
// activatedRoute doesn't carry data when shouldReuseRoute returns false
// use the event data with GuardsCheckEnd as workaround
// Check for shouldActivate in case where the authGuard returns false the breadcrumbs shouldn't be changed
if (event.shouldActivate) {
this.setupBreadcrumbs(event.state.root);
}
});
}
setupBreadcrumbs(activatedRouteSnapshot) {
this.previousBreadcrumbs = this.currentBreadcrumbs;
// breadcrumb label for base OR root path. Usually, this can be set as 'Home'
const rootBreadcrumb = this.getRootBreadcrumb();
this.currentBreadcrumbs = rootBreadcrumb ? [rootBreadcrumb] : [];
this.prepareBreadcrumbList(activatedRouteSnapshot, this.baseHref);
}
getRootBreadcrumb() {
const rootConfig = this.router.config.find((config) => config.path === '');
const rootBreadcrumb = this.extractObject(rootConfig?.data?.breadcrumb);
const storeItem = this.getFromStore(rootBreadcrumb.alias, '/');
if (isNonEmpty(rootBreadcrumb) || isNonEmpty(storeItem)) {
return {
...storeItem,
...rootBreadcrumb,
routeLink: this.baseHref,
...this.getQueryParamsFromPreviousList('/'),
};
}
}
prepareBreadcrumbItem(activatedRouteSnapshot, routeLinkPrefix) {
const { path, breadcrumb } = this.parseRouteData(activatedRouteSnapshot.routeConfig);
const resolvedSegment = this.resolvePathSegment(path, activatedRouteSnapshot);
const routeLink = `${routeLinkPrefix}${resolvedSegment}`;
const storeItem = this.getFromStore(breadcrumb.alias, routeLink);
const label = this.extractLabel(storeItem?.label || breadcrumb?.label, resolvedSegment);
let isAutoGeneratedLabel = false;
let autoGeneratedLabel = '';
if (!label) {
isAutoGeneratedLabel = true;
autoGeneratedLabel = resolvedSegment;
}
return {
...storeItem,
...breadcrumb,
label: isAutoGeneratedLabel ? autoGeneratedLabel : label,
routeLink,
isAutoGeneratedLabel,
...this.getQueryParamsFromPreviousList(routeLink),
};
}
prepareBreadcrumbList(activatedRouteSnapshot, routeLinkPrefix) {
if (activatedRouteSnapshot.routeConfig?.path) {
const breadcrumbItem = this.prepareBreadcrumbItem(activatedRouteSnapshot, routeLinkPrefix);
this.currentBreadcrumbs.push(breadcrumbItem);
if (activatedRouteSnapshot.firstChild) {
return this.prepareBreadcrumbList(activatedRouteSnapshot.firstChild, breadcrumbItem.routeLink + '/');
}
}
else if (activatedRouteSnapshot.firstChild) {
return this.prepareBreadcrumbList(activatedRouteSnapshot.firstChild, routeLinkPrefix);
}
const lastCrumb = this.currentBreadcrumbs[this.currentBreadcrumbs.length - 1];
this.setQueryParamsForActiveBreadcrumb(lastCrumb, activatedRouteSnapshot);
// remove breadcrumb items that needs to be hidden
const breadcrumbsToShow = this.currentBreadcrumbs.filter((item) => !item.skip);
this.breadcrumbs.next(breadcrumbsToShow);
}
getFromStore(alias, routeLink) {
return this.dynamicBreadcrumbStore.find((item) => {
return ((alias && alias === item.alias) ||
(routeLink && routeLink === item.routeLink) ||
this.matchRegex(routeLink, item.routeRegex));
});
}
/**
* use exact match instead of regexp.test
* for /mentor/[^/]+ we should match '/mentor/12' but not '/mentor/12/abc'
*/
matchRegex(routeLink, routeRegex) {
const match = routeLink.match(new RegExp(routeRegex));
return match?.[0] === routeLink;
}
/**
* if the path segment has route params, read the param value from url
* for each segment of route this gets called
*
* for mentor/:id/view - it gets called with mentor, :id, view 3 times
*/
resolvePathSegment(segment, activatedRouteSnapshot) {
//quirk -segment can be defined as view/:id in route config in which case you need to make it view/<resolved-param>
if (segment.includes(PATH_PARAM.PREFIX)) {
Object.entries(activatedRouteSnapshot.params).forEach(([key, value]) => {
segment = segment.replace(`:${key}`, `${value}`);
});
}
return segment;
}
/**
* queryParams & fragments for previous breadcrumb path are copied over to new list
*/
getQueryParamsFromPreviousList(routeLink) {
const { queryParams, fragment } = this.previousBreadcrumbs.find((item) => item.routeLink === routeLink) ||
{};
return { queryParams, fragment };
}
/**
* set current activated route query params to the last breadcrumb item
*/
setQueryParamsForActiveBreadcrumb(lastItem, activatedRouteSnapshot) {
if (lastItem) {
const { queryParams, fragment } = activatedRouteSnapshot;
lastItem.queryParams = queryParams ? { ...queryParams } : undefined;
lastItem.fragment = fragment;
}
}
/**
* For a specific route, breadcrumb can be defined either on parent OR it's child(which has empty path)
* 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: () => import('./home/home.module').then((m) => m.HomeModule) , data: {breadcrumb: "Defined On Module"}}
* AND
* children: [
* { path: '', component: ShowUserComponent, data: {breadcrumb: "Defined On Child" }
* ]
*/
parseRouteData(routeConfig) {
const { path, data } = routeConfig;
const breadcrumb = this.mergeWithBaseChildData(routeConfig, data?.breadcrumb);
return { path, breadcrumb };
}
/**
* 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
*/
mergeWithBaseChildData(routeConfig, config) {
if (!routeConfig) {
return this.extractObject(config);
}
let baseChild;
if (routeConfig.loadChildren) {
// To handle a module with empty child route
baseChild = routeConfig._loadedRoutes.find((route) => route.path === '');
}
else if (routeConfig.children) {
// To handle a component with empty child route
baseChild = routeConfig.children.find((route) => route.path === '');
}
const childConfig = baseChild?.data?.breadcrumb;
return childConfig
? this.mergeWithBaseChildData(baseChild, {
...this.extractObject(config),
...this.extractObject(childConfig),
})
: this.extractObject(config);
}
/**
* Update breadcrumb dynamically
*
* key can be a path | alias
*
* 1) Using complete route path. route can be passed the same way you define angular routes
* - path can be passed as 'exact path(routeLink)' or 'path with params(routeRegex)'
* - update label Ex: set('/mentor', 'Mentor'), set('/mentor/:id', 'Mentor Details')
* - change visibility Ex: set('/mentor/:id/edit', { skip: true })
* ------------------------------------------ OR ------------------------------------------
* 2) Using route alias (prefixed with '@'). alias should be unique for a route
* - update label Ex: set('@mentor', 'Enabler')
* - change visibility Ex: set('@mentorEdit', { skip: true })
*
*
* value can be string | BreadcrumbObject | BreadcrumbFunction
*/
set(key, breadcrumb) {
const breadcrumbObject = this.extractObject(breadcrumb);
let updateArgs;
if (key.startsWith(ALIAS_PREFIX)) {
updateArgs = ['alias', { ...breadcrumbObject, alias: key.slice(1) }];
}
else if (key.includes(PATH_PARAM.PREFIX)) {
updateArgs = [
'routeRegex',
{ ...breadcrumbObject, routeRegex: this.buildRegex(key) },
];
}
else {
updateArgs = [
'routeLink',
{ ...breadcrumbObject, routeLink: this.ensureLeadingSlash(key) },
];
}
// For this route if previously a breadcrumb is not defined that sets isAutoGeneratedLabel: true
// change it to false since this is user supplied value
updateArgs[1].isAutoGeneratedLabel = false;
this.updateStore(...updateArgs);
this.updateCurrentBreadcrumbs(...updateArgs);
}
/**
* Update the store to reuse for dynamic declarations
* If the store already has this route definition update it, else add
*/
updateStore(key, breadcrumb) {
const storeItemIndex = this.dynamicBreadcrumbStore.findIndex((item) => {
return breadcrumb[key] === item[key];
});
if (storeItemIndex > -1) {
this.dynamicBreadcrumbStore[storeItemIndex] = {
...this.dynamicBreadcrumbStore[storeItemIndex],
...breadcrumb,
};
}
else {
this.dynamicBreadcrumbStore.push({ ...breadcrumb });
}
}
/**
* If breadcrumb is present in current breadcrumbs update it and emit new stream
*/
updateCurrentBreadcrumbs(key, breadcrumb) {
const itemIndex = this.currentBreadcrumbs.findIndex((item) => {
return key === 'routeRegex'
? this.matchRegex(item.routeLink, breadcrumb[key])
: breadcrumb[key] === item[key];
});
if (itemIndex > -1) {
this.currentBreadcrumbs[itemIndex] = {
...this.currentBreadcrumbs[itemIndex],
...breadcrumb,
};
const breadcrumbsToShow = this.currentBreadcrumbs.filter((item) => !item.skip);
this.breadcrumbs.next([...breadcrumbsToShow]);
}
}
/**
* For a route with path param, we create regex dynamically from angular route syntax
* '/mentor/:id' becomes '/mentor/[^/]',
* breadcrumbService.set('/mentor/:id', 'Uday') should update 'Uday' as label for '/mentor/2' OR 'mentor/ada'
*/
buildRegex(path) {
return this.ensureLeadingSlash(path).replace(new RegExp(PATH_PARAM.REGEX_IDENTIFIER, 'g'), PATH_PARAM.REGEX_REPLACER);
}
ensureLeadingSlash(path) {
return path.startsWith('/') ? path : `/${path}`;
}
/**
* In App's RouteConfig, breadcrumb can be defined as a string OR a function OR an object
*
* string: simple static breadcrumb label for a path
* function: callback that gets invoked with resolved path param
* object: additional data defined along with breadcrumb label that gets passed to *xngBreadcrumbItem directive
*/
extractLabel(config, resolvedParam) {
const label = typeof config === 'object' ? config.label : config;
if (typeof label === 'function') {
return label(resolvedParam);
}
return label;
}
extractObject(config) {
// don't include {label} if config is undefined. This is important since we merge the configs
if (config &&
(typeof config === 'string' || typeof config === 'function')) {
return { label: config };
}
return config || {};
}
}
BreadcrumbService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: BreadcrumbService, deps: [{ token: i1.ActivatedRoute }, { token: i1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
BreadcrumbService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: BreadcrumbService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.0", ngImport: i0, type: BreadcrumbService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: i1.ActivatedRoute }, { type: i1.Router }]; } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJlYWRjcnVtYi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vbGlicy94bmctYnJlYWRjcnVtYi9zcmMvbGliL2JyZWFkY3J1bWIuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzNDLE9BQU8sRUFDTCxjQUFjLEVBRWQsY0FBYyxFQUNkLE1BQU0sR0FDUCxNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDdkMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGdCQUFnQixDQUFDOzs7QUFVeEMsTUFBTSxVQUFVLEdBQUc7SUFDakIsTUFBTSxFQUFFLEdBQUc7SUFDWCxnQkFBZ0IsRUFBRSxTQUFTO0lBQzNCLGNBQWMsRUFBRSxRQUFRO0NBQ3pCLENBQUM7QUFDRixNQUFNLFlBQVksR0FBRyxHQUFHLENBQUM7QUFDekIsTUFBTSxVQUFVLEdBQUcsQ0FBQyxHQUFZLEVBQVcsRUFBRTtJQUMzQyxPQUFPLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUM7QUFDNUMsQ0FBQyxDQUFDO0FBS0YsTUFBTSxPQUFPLGlCQUFpQjtJQXlCNUIsWUFBb0IsY0FBOEIsRUFBVSxNQUFjO1FBQXRELG1CQUFjLEdBQWQsY0FBYyxDQUFnQjtRQUFVLFdBQU0sR0FBTixNQUFNLENBQVE7UUF4QmxFLGFBQVEsR0FBRyxHQUFHLENBQUM7UUFFdkI7Ozs7V0FJRztRQUNLLDJCQUFzQixHQUEyQixFQUFFLENBQUM7UUFFNUQ7Ozs7V0FJRztRQUNLLHVCQUFrQixHQUEyQixFQUFFLENBQUM7UUFDaEQsd0JBQW1CLEdBQTJCLEVBQUUsQ0FBQztRQUV6RDs7O1dBR0c7UUFDSyxnQkFBVyxHQUFHLElBQUksZUFBZSxDQUF5QixF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Y0FBYyxDQUMxQixDQUFDO0lBQ0osQ0FBQztJQUVPLGtCQUFrQixDQUFDLElBQVk7UUFDckMsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7SUFDbEQsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNLLFlBQVksQ0FBQyxNQUF3QixFQUFFLGFBQXNCO1FBQ25FLE1BQU0sS0FBSyxHQUFHLE9BQU8sTUFBTSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDO1FBQ2pFLElBQUksT0FBTyxLQUFLLEtBQUssVUFBVSxFQUFFO1lBQy9CLE9BQU8sS0FBSyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1NBQzdCO1FBQ0QsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRU8sYUFBYSxDQUFDLE1BQXdCO1FBQzVDLDZGQUE2RjtRQUM3RixJQUNFLE1BQU07WUFDTixDQUFDLE9BQU8sTUFBTSxLQUFLLFFBQVEsSUFBSSxPQUFPLE1BQU0sS0FBSyxVQUFVLENBQUMsRUFDNUQ7WUFDQSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxDQUFDO1NBQzFCO1FBQ0QsT0FBUSxNQUEyQixJQUFJLEVBQUUsQ0FBQztJQUM1QyxDQUFDOzs4R0FsWVUsaUJBQWlCO2tIQUFqQixpQkFBaUIsY0FGaEIsTUFBTTsyRkFFUCxpQkFBaUI7a0JBSDdCLFVBQVU7bUJBQUM7b0JBQ1YsVUFBVSxFQUFFLE1BQU07aUJBQ25CIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHtcbiAgQWN0aXZhdGVkUm91dGUsXG4gIEFjdGl2YXRlZFJvdXRlU25hcHNob3QsXG4gIEd1YXJkc0NoZWNrRW5kLFxuICBSb3V0ZXIsXG59IGZyb20gJ0Bhbmd1bGFyL3JvdXRlcic7XG5pbXBvcnQgeyBCZWhhdmlvclN1YmplY3QgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IGZpbHRlciB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7IEJyZWFkY3J1bWIgfSBmcm9tICcuL3R5cGVzL2JyZWFkY3J1bWInO1xuaW1wb3J0IHtcbiAgQnJlYWRjcnVtYkZ1bmN0aW9uLFxuICBCcmVhZGNydW1iT2JqZWN0LFxufSBmcm9tICcuL3R5cGVzL2JyZWFkY3J1bWIuY29uZmlnJztcblxudHlwZSBCcmVhZGNydW1iQ29uZmlnID0gQnJlYWRjcnVtYk9iamVjdCB8IEJyZWFkY3J1bWJGdW5jdGlvbiB8IHN0cmluZztcbnR5cGUgU3RvcmVNYXRjaGVyS2V5ID0gJ3JvdXRlTGluaycgfCAncm91dGVSZWdleCcgfCAnYWxpYXMnO1xuZXhwb3J0IHR5cGUgQnJlYWRjcnVtYkRlZmluaXRpb24gPSBCcmVhZGNydW1iICYgQnJlYWRjcnVtYk9iamVjdDtcbmNvbnN0IFBBVEhfUEFSQU0gPSB7XG4gIFBSRUZJWDogJzonLFxuICBSRUdFWF9JREVOVElGSUVSOiAnLzpbXi9dKycsXG4gIFJFR0VYX1JFUExBQ0VSOiAnL1teL10rJyxcbn07XG5jb25zdCBBTElBU19QUkVGSVggPSAnQCc7XG5jb25zdCBpc05vbkVtcHR5ID0gKG9iajogdW5rbm93bik6IGJvb2xlYW4gPT4ge1xuICByZXR1cm4gb2JqICYmIE9iamVjdC5rZXlzKG9iaikubGVuZ3RoID4gMDtcbn07XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnLFxufSlcbmV4cG9ydCBjbGFzcyBCcmVhZGNydW1iU2VydmljZSB7XG4gIHByaXZhdGUgYmFzZUhyZWYgPSAnLyc7XG5cbiAgLyoqXG4gICAqIGR5bmFtaWNCcmVhZGNydW1iU3RvcmUgaG9sZHMgaW5mb3JtYXRpb24gYWJvdXQgZHluYW1pY2FsbHkgdXBkYXRlZCBicmVhZGNydW1icy5cbiAgICogQnJlYWRjcnVtYnMgY2FuIGJlIHNldCBmcm9tIGFueXdoZXJlIChjb21wb25lbnQsIHNlcnZpY2UpIGluIHRoZSBhcHAuXG4gICAqIE9uIGV2ZXJ5IGJyZWFkY3J1bWIgdXBkYXRlIGNoZWNrIHRoaXMgc3RvcmUgYW5kIHVzZSB0aGUgaW5mbyBpZiBhdmFpbGFibGUuXG4gICAqL1xuICBwcml2YXRlIGR5bmFtaWNCcmVhZGNydW1iU3RvcmU6IEJyZWFkY3J1bWJEZWZpbml0aW9uW10gPSBbXTtcblxuICAvKipcbiAgICogYnJlYWRjcnVtYkxpc3QgZm9yIHRoZSBjdXJyZW50IHJvdXRlXG4gICAqIFdoZW4gYnJlYWRjcnVtYiBpbmZvIGlzIGNoYW5nZWQgZHluYW1pY2FsbHksIGNoZWNrIGlmIHRoZSBjdXJyZW50QnJlYWRjcnVtYnMgaXMgZWZmZWN0ZWRcbiAgICogSWYgZWZmZWN0ZWQsIHVwZGF0ZSB0aGUgY2hhbmdlIGFuZCBlbWl0IGEgbmV3IHN0cmVhbVxuICAgKi9cbiAgcHJpdmF0ZSBjdXJyZW50QnJlYWRjcnVtYnM6IEJyZWFkY3J1bWJEZWZpbml0aW9uW10gPSBbXTtcbiAgcHJpdmF0ZSBwcmV2aW91c0JyZWFkY3J1bWJzOiBCcmVhZGNydW1iRGVmaW5pdGlvbltdID0gW107XG5cbiAgLyoqXG4gICAqIEJyZWFkY3J1bWJzIG9ic2VydmFibGUgdG8gYmUgc3Vic2NyaWJlZCBieSBCcmVhZGNydW1iQ29tcG9uZW50XG4gICAqIEVtaXRzIG9uIGV2ZXJ5IHJvdXRlIGNoYW5nZSBPUiBkeW5hbWljIHVwZGF0ZSBvZiBicmVhZGNydW1iXG4gICAqL1xuICBwcml2YXRlIGJyZWFkY3J1bWJzID0gbmV3IEJlaGF2aW9yU3ViamVjdDxCcmVhZGNydW1iRGVmaW5pdGlvbltdPihbXSk7XG4gIHB1YmxpYyBicmVhZGNydW1icyQgPSB0aGlzLmJyZWFkY3J1bWJzLmFzT2JzZXJ2YWJsZSgpO1xuXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgYWN0aXZhdGVkUm91dGU6IEFjdGl2YXRlZFJvdXRlLCBwcml2YXRlIHJvdXRlcjogUm91dGVyKSB7XG4gICAgdGhpcy5kZXRlY3RSb3V0ZUNoYW5nZXMoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBXaGVuZXZlciByb3V0ZSBjaGFuZ2VzIGJ1aWxkIGJyZWFkY3J1bWIgbGlzdCBhZ2FpblxuICAgKi9cbiAgcHJpdmF0ZSBkZXRlY3RSb3V0ZUNoYW5nZXMoKSB7XG4gICAgLy8gU3BlY2lhbCBjYXNlIHdoZXJlIGJyZWFkY3J1bWIgc2VydmljZSAmIGNvbXBvbmVudCBpbnN0YW50aWF0ZXMgYWZ0ZXIgYSByb3V0ZSBpcyBuYXZpZ2F0ZWQuXG4gICAgLy8gRXg6IHB1dCBicmVhZGNydW1icyB3aXRoaW4gKm5nSWYgYW5kIHRoaXMucm91dGVyLmV2ZW50cyB3b3VsZCBiZSBlbXB0eVxuICAgIC8vIFRoaXMgY2hlY2sgaXMgYWxzbyByZXF1aXJlZCB3aGVyZSAgeyBpbml0aWFsTmF2aWdhdGlvbjogJ2VuYWJsZWRCbG9ja2luZycgfSBpcyBhcHBsaWVkIHRvIHJvdXRlc1xuICAgIHRoaXMuc2V0dXBCcmVhZGNydW1icyh0aGlzLmFjdGl2YXRlZFJvdXRlLnNuYXBzaG90KTtcblxuICAgIHRoaXMucm91dGVyLmV2ZW50c1xuICAgICAgLnBpcGUoXG4gICAgICAgIGZpbHRlcihcbiAgICAgICAgICAoZXZlbnQpOiBldmVudCBpcyBHdWFyZHNDaGVja0VuZCA9PiBldmVudCBpbnN0YW5jZW9mIEd1YXJkc0NoZWNrRW5kXG4gICAgICAgIClcbiAgICAgIClcbiAgICAgIC5zdWJzY3JpYmUoKGV2ZW50KSA9PiB7XG4gICAgICAgIC8vIGFjdGl2YXRlZFJvdXRlIGRvZXNuJ3QgY2FycnkgZGF0YSB3aGVuIHNob3VsZFJldXNlUm91dGUgcmV0dXJucyBmYWxzZVxuICAgICAgICAvLyB1c2UgdGhlIGV2ZW50IGRhdGEgd2l0aCBHdWFyZHNDaGVja0VuZCBhcyB3b3JrYXJvdW5kXG4gICAgICAgIC8vIENoZWNrIGZvciBzaG91bGRBY3RpdmF0ZSBpbiBjYXNlIHdoZXJlIHRoZSBhdXRoR3VhcmQgcmV0dXJucyBmYWxzZSB0aGUgYnJlYWRjcnVtYnMgc2hvdWxkbid0IGJlIGNoYW5nZWRcbiAgICAgICAgaWYgKGV2ZW50LnNob3VsZEFjdGl2YXRlKSB7XG4gICAgICAgICAgdGhpcy5zZXR1cEJyZWFkY3J1bWJzKGV2ZW50LnN0YXRlLnJvb3QpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgc2V0dXBCcmVhZGNydW1icyhhY3RpdmF0ZWRSb3V0ZVNuYXBzaG90OiBBY3RpdmF0ZWRSb3V0ZVNuYXBzaG90KSB7XG4gICAgdGhpcy5wcmV2aW91c0JyZWFkY3J1bWJzID0gdGhpcy5jdXJyZW50QnJlYWRjcnVtYnM7XG4gICAgLy8gYnJlYWRjcnVtYiBsYWJlbCBmb3IgYmFzZSBPUiByb290IHBhdGguIFVzdWFsbHksIHRoaXMgY2FuIGJlIHNldCBhcyAnSG9tZSdcbiAgICBjb25zdCByb290QnJlYWRjcnVtYiA9IHRoaXMuZ2V0Um9vdEJyZWFkY3J1bWIoKTtcbiAgICB0aGlzLmN1cnJlbnRCcmVhZGNydW1icyA9IHJvb3RCcmVhZGNydW1iID8gW3Jvb3RCcmVhZGNydW1iXSA6IFtdO1xuICAgIHRoaXMucHJlcGFyZUJyZWFkY3J1bWJMaXN0KGFjdGl2YXRlZFJvdXRlU25hcHNob3QsIHRoaXMuYmFzZUhyZWYpO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXRSb290QnJlYWRjcnVtYigpOiBCcmVhZGNydW1iIHwgdm9pZCB7XG4gICAgY29uc3Qgcm9vdENvbmZpZyA9IHRoaXMucm91dGVyLmNvbmZpZy5maW5kKChjb25maWcpID0+IGNvbmZpZy5wYXRoID09PSAnJyk7XG4gICAgY29uc3Qgcm9vdEJyZWFkY3J1bWIgPSB0aGlzLmV4dHJhY3RPYmplY3Qocm9vdENvbmZpZz8uZGF0YT8uYnJlYWRjcnVtYik7XG4gICAgY29uc3Qgc3RvcmVJdGVtID0gdGhpcy5nZXRGcm9tU3RvcmUocm9vdEJyZWFkY3J1bWIuYWxpYXMsICcvJyk7XG5cbiAgICBpZiAoaXNOb25FbXB0eShyb290QnJlYWRjcnVtYikgfHwgaXNOb25FbXB0eShzdG9yZUl0ZW0pKSB7XG4gICAgICByZXR1cm4ge1xuICAgICAgICAuLi5zdG9yZUl0ZW0sXG4gICAgICAgIC4uLnJvb3RCcmVhZGNydW1iLFxuICAgICAgICByb3V0ZUxpbms6IHRoaXMuYmFzZUhyZWYsXG4gICAgICAgIC4uLnRoaXMuZ2V0UXVlcnlQYXJhbXNGcm9tUHJldmlvdXNMaXN0KCcvJyksXG4gICAgICB9O1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgcHJlcGFyZUJyZWFkY3J1bWJJdGVtKFxuICAgIGFjdGl2YXRlZFJvdXRlU25hcHNob3Q6IEFjdGl2YXRlZFJvdXRlU25hcHNob3QsXG4gICAgcm91dGVMaW5rUHJlZml4OiBzdHJpbmdcbiAgKTogQnJlYWRjcnVtYkRlZmluaXRpb24ge1xuICAgIGNvbnN0IHsgcGF0aCwgYnJlYWRjcnVtYiB9ID0gdGhpcy5wYXJzZVJvdXRlRGF0YShcbiAgICAgIGFjdGl2YXRlZFJvdXRlU25hcHNob3Qucm91dGVDb25maWdcbiAgICApO1xuICAgIGNvbnN0IHJlc29sdmVkU2VnbWVudCA9IHRoaXMucmVzb2x2ZVBhdGhTZWdtZW50KFxuICAgICAgcGF0aCxcbiAgICAgIGFjdGl2YXRlZFJvdXRlU25hcHNob3RcbiAgICApO1xuICAgIGNvbnN0IHJvdXRlTGluayA9IGAke3JvdXRlTGlua1ByZWZpeH0ke3Jlc29sdmVkU2VnbWVudH1gO1xuICAgIGNvbnN0IHN0b3JlSXRlbSA9IHRoaXMuZ2V0RnJvbVN0b3JlKGJyZWFkY3J1bWIuYWxpYXMsIHJvdXRlTGluayk7XG5cbiAgICBjb25zdCBsYWJlbCA9IHRoaXMuZXh0cmFjdExhYmVsKFxuICAgICAgc3RvcmVJdGVtPy5sYWJlbCB8fCBicmVhZGNydW1iPy5sYWJlbCxcbiAgICAgIHJlc29sdmVkU2VnbWVudFxuICAgICk7XG4gICAgbGV0IGlzQXV0b0dlbmVyYXRlZExhYmVsID0gZmFsc2U7XG4gICAgbGV0IGF1dG9HZW5lcmF0ZWRMYWJlbCA9ICcnO1xuICAgIGlmICghbGFiZWwpIHtcbiAgICAgIGlzQXV0b0dlbmVyYXRlZExhYmVsID0gdHJ1ZTtcbiAgICAgIGF1dG9HZW5lcmF0ZWRMYWJlbCA9IHJlc29sdmVkU2VnbWVudDtcbiAgICB9XG5cbiAgICByZXR1cm4ge1xuICAgICAgLi4uc3RvcmVJdGVtLFxuICAgICAgLi4uYnJlYWRjcnVtYixcbiAgICAgIGxhYmVsOiBpc0F1dG9HZW5lcmF0ZWRMYWJlbCA/IGF1dG9HZW5lcmF0ZWRMYWJlbCA6IGxhYmVsLFxuICAgICAgcm91dGVMaW5rLFxuICAgICAgaXNBdXRvR2VuZXJhdGVkTGFiZWwsXG4gICAgICAuLi50aGlzLmdldFF1ZXJ5UGFyYW1zRnJvbVByZXZpb3VzTGlzdChyb3V0ZUxpbmspLFxuICAgIH07XG4gIH1cblxuICBwcml2YXRlIHByZXBhcmVCcmVhZGNydW1iTGlzdChcbiAgICBhY3RpdmF0ZWRSb3V0ZVNuYXBzaG90OiBBY3RpdmF0ZWRSb3V0ZVNuYXBzaG90LFxuICAgIHJvdXRlTGlua1ByZWZpeDogc3RyaW5nXG4gICk6IEJyZWFkY3J1bWJbXSB8IHZvaWQge1xuICAgIGlmIChhY3RpdmF0ZWRSb3V0ZVNuYXBzaG90LnJvdXRlQ29uZmlnPy5wYXRoKSB7XG4gICAgICBjb25zdCBicmVhZGNydW1iSXRlbSA9IHRoaXMucHJlcGFyZUJyZWFkY3J1bWJJdGVtKFxuICAgICAgICBhY3RpdmF0ZWRSb3V0ZVNuYXBzaG90LFxuICAgICAgICByb3V0ZUxpbmtQcmVmaXhcbiAgICAgICk7XG4gICAgICB0aGlzLmN1cnJlbnRCcmVhZGNydW1icy5wdXNoKGJyZWFkY3J1bWJJdGVtKTtcblxuICAgICAgaWYgKGFjdGl2YXRlZFJvdXRlU25hcHNob3QuZmlyc3RDaGlsZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5wcmVwYXJlQnJlYWRjcnVtYkxpc3QoXG4gICAgICAgICAgYWN0aXZhdGVkUm91dGVTbmFwc2hvdC5maXJzdENoaWxkLFxuICAgICAgICAgIGJyZWFkY3J1bWJJdGVtLnJvdXRlTGluayArICcvJ1xuICAgICAgICApO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoYWN0aXZhdGVkUm91dGVTbmFwc2hvdC5maXJzdENoaWxkKSB7XG4gICAgICByZXR1cm4gdGhpcy5wcmVwYXJlQnJlYWRjcnVtYkxpc3QoXG4gICAgICAgIGFjdGl2YXRlZFJvdXRlU25hcHNob3QuZmlyc3RDaGlsZCxcbiAgICAgICAgcm91dGVMaW5rUHJlZml4XG4gICAgICApO1xuICAgIH1cbiAgICBjb25zdCBsYXN0Q3J1bWIgPVxuICAgICAgdGhpcy5jdXJyZW50QnJlYWRjcnVtYnNbdGhpcy5jdXJyZW50QnJlYWRjcnVtYnMubGVuZ3RoIC0gMV07XG4gICAgdGhpcy5zZXRRdWVyeVBhcmFtc0ZvckFjdGl2ZUJyZWFkY3J1bWIobGFzdENydW1iLCBhY3RpdmF0ZWRSb3V0ZVNuYXBzaG90KTtcblxuICAgIC8vIHJlbW92ZSBicmVhZGNydW1iIGl0ZW1zIHRoYXQgbmVlZHMgdG8gYmUgaGlkZGVuXG4gICAgY29uc3QgYnJlYWRjcnVtYnNUb1Nob3cgPSB0aGlzLmN1cnJlbnRCcmVhZGNydW1icy5maWx0ZXIoXG4gICAgICAoaXRlbSkgPT4gIWl0ZW0uc2tpcFxuICAgICk7XG5cbiAgICB0aGlzLmJyZWFkY3J1bWJzLm5leHQoYnJlYWRjcnVtYnNUb1Nob3cpO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXRGcm9tU3RvcmUoYWxpYXM6IHN0cmluZywgcm91dGVMaW5rOiBzdHJpbmcpOiBCcmVhZGNydW1iRGVmaW5pdGlvbiB7XG4gICAgcmV0dXJuIHRoaXMuZHluYW1pY0JyZWFkY3J1bWJTdG9yZS5maW5kKChpdGVtKSA9PiB7XG4gICAgICByZXR1cm4gKFxuICAgICAgICAoYWxpYXMgJiYgYWxpYXMgPT09IGl0ZW0uYWxpYXMpIHx8XG4gICAgICAgIChyb3V0ZUxpbmsgJiYgcm91dGVMaW5rID09PSBpdGVtLnJvdXRlTGluaykgfHxcbiAgICAgICAgdGhpcy5tYXRjaFJlZ2V4KHJvdXRlTGluaywgaXRlbS5yb3V0ZVJlZ2V4KVxuICAgICAgKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiB1c2UgZXhhY3QgbWF0Y2ggaW5zdGVhZCBvZiByZWdleHAudGVzdFxuICAgKiBmb3IgL21lbnRvci9bXi9dKyB3ZSBzaG91bGQgbWF0Y2ggJy9tZW50b3IvMTInIGJ1dCBub3QgJy9tZW50b3IvMTIvYWJjJ1xuICAgKi9cbiAgcHJpdmF0ZSBtYXRjaFJlZ2V4KHJvdXRlTGluazogc3RyaW5nLCByb3V0ZVJlZ2V4OiBzdHJpbmcpIHtcbiAgICBjb25zdCBtYXRjaCA9IHJvdXRlTGluay5tYXRjaChuZXcgUmVnRXhwKHJvdXRlUmVnZXgpKTtcbiAgICByZXR1cm4gbWF0Y2g/LlswXSA9PT0gcm91dGVMaW5rO1xuICB9XG5cbiAgLyoqXG4gICAqIGlmIHRoZSBwYXRoIHNlZ21lbnQgaGFzIHJvdXRlIHBhcmFtcywgcmVhZCB0aGUgcGFyYW0gdmFsdWUgZnJvbSB1cmxcbiAgICogZm9yIGVhY2ggc2VnbWVudCBvZiByb3V0ZSB0aGlzIGdldHMgY2FsbGVkXG4gICAqXG4gICAqIGZvciBtZW50b3IvOmlkL3ZpZXcgLSBpdCBnZXRzIGNhbGxlZCB3aXRoIG1lbnRvciwgOmlkLCB2aWV3IDMgdGltZXNcbiAgICovXG4gIHByaXZhdGUgcmVzb2x2ZVBhdGhTZWdtZW50KFxuICAgIHNlZ21lbnQ6IHN0cmluZyxcbiAgICBhY3RpdmF0ZWRSb3V0ZVNuYXBzaG90OiBBY3RpdmF0ZWRSb3V0ZVNuYXBzaG90XG4gICkge1xuICAgIC8vcXVpcmsgLXNlZ21lbnQgY2FuIGJlIGRlZmluZWQgYXMgdmlldy86aWQgaW4gcm91dGUgY29uZmlnIGluIHdoaWNoIGNhc2UgeW91IG5lZWQgdG8gbWFrZSBpdCB2aWV3LzxyZXNvbHZlZC1wYXJhbT5cbiAgICBpZiAoc2VnbWVudC5pbmNsdWRlcyhQQVRIX1BBUkFNLlBSRUZJWCkpIHtcbiAgICAgIE9iamVjdC5lbnRyaWVzKGFjdGl2YXRlZFJvdXRlU25hcHNob3QucGFyYW1zKS5mb3JFYWNoKChba2V5LCB2YWx1ZV0pID0+IHtcbiAgICAgICAgc2VnbWVudCA9IHNlZ21lbnQucmVwbGFjZShgOiR7a2V5fWAsIGAke3ZhbHVlfWApO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBzZWdtZW50O1xuICB9XG5cbiAgLyoqXG4gICAqIHF1ZXJ5UGFyYW1zICYgZnJhZ21lbnRzIGZvciBwcmV2aW91cyBicmVhZGNydW1iIHBhdGggYXJlIGNvcGllZCBvdmVyIHRvIG5ldyBsaXN0XG4gICAqL1xuICBwcml2YXRlIGdldFF1ZXJ5UGFyYW1zRnJvbVByZXZpb3VzTGlzdChyb3V0ZUxpbms6IHN0cmluZyk6IEJyZWFkY3J1bWIge1xuICAgIGNvbnN0IHsgcXVlcnlQYXJhbXMsIGZyYWdtZW50IH0gPVxuICAgICAgdGhpcy5wcmV2aW91c0JyZWFkY3J1bWJzLmZpbmQoKGl0ZW0pID0+IGl0ZW0ucm91dGVMaW5rID09PSByb3V0ZUxpbmspIHx8XG4gICAgICB7fTtcbiAgICByZXR1cm4geyBxdWVyeVBhcmFtcywgZnJhZ21lbnQgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBzZXQgY3VycmVudCBhY3RpdmF0ZWQgcm91dGUgcXVlcnkgcGFyYW1zIHRvIHRoZSBsYXN0IGJyZWFkY3J1bWIgaXRlbVxuICAgKi9cbiAgcHJpdmF0ZSBzZXRRdWVyeVBhcmFtc0ZvckFjdGl2ZUJyZWFkY3J1bWIoXG4gICAgbGFzdEl0ZW06IEJyZWFkY3J1bWIsXG4gICAgYWN0aXZhdGVkUm91dGVTbmFwc2hvdDogQWN0aXZhdGVkUm91dGVTbmFwc2hvdFxuICApIHtcbiAgICBpZiAobGFzdEl0ZW0pIHtcbiAgICAgIGNvbnN0IHsgcXVlcnlQYXJhbXMsIGZyYWdtZW50IH0gPSBhY3RpdmF0ZWRSb3V0ZVNuYXBzaG90O1xuICAgICAgbGFzdEl0ZW0ucXVlcnlQYXJhbXMgPSBxdWVyeVBhcmFtcyA/IHsgLi4ucXVlcnlQYXJhbXMgfSA6IHVuZGVmaW5lZDtcbiAgICAgIGxhc3RJdGVtLmZyYWdtZW50ID0gZnJhZ21lbnQ7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEZvciBhIHNwZWNpZmljIHJvdXRlLCBicmVhZGNydW1iIGNhbiBiZSBkZWZpbmVkIGVpdGhlciBvbiBwYXJlbnQgT1IgaXQncyBjaGlsZCh3aGljaCBoYXMgZW1wdHkgcGF0aClcbiAgICogV2hlbiBib3RoIGFyZSBkZWZpbmVkLCBjaGlsZCB0YWtlcyBwcmVjZWRlbmNlXG4gICAqXG4gICAqIEV4OiBCZWxvdyB3ZSBhcmUgc2V0dGluZyBicmVhZGNydW1iIG9uIGJvdGggcGFyZW50IGFuZCBjaGlsZC5cbiAgICogU28sIGNoaWxkIHRha2VzIHByZWNlZGVuY2UgYW5kIFwiRGVmaW5lZCBPbiBDaGlsZFwiIGlzIGRpc3BsYXllZCBmb3IgdGhlIHJvdXRlICdob21lJ1xuICAgKiB7IHBhdGg6ICdob21lJywgbG9hZENoaWxkcmVuOiAoKSA9PiBpbXBvcnQoJy4vaG9tZS9ob21lLm1vZHVsZScpLnRoZW4oKG0pID0+IG0uSG9tZU1vZHVsZSkgLCBkYXRhOiB7YnJlYWRjcnVtYjogXCJEZWZpbmVkIE9uIE1vZHVsZVwifX1cbiAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBTkRcbiAgICogY2hpbGRyZW46IFtcbiAgICogICB7IHBhdGg6ICcnLCBjb21wb25lbnQ6IFNob3dVc2VyQ29tcG9uZW50LCBkYXRhOiB7YnJlYWRjcnVtYjogXCJEZWZpbmVkIE9uIENoaWxkXCIgfVxuICAgKiBdXG4gICAqL1xuICBwcml2YXRlIHBhcnNlUm91dGVEYXRhKHJvdXRlQ29uZmlnKSB7XG4gICAgY29uc3QgeyBwYXRoLCBkYXRhIH0gPSByb3V0ZUNvbmZpZztcbiAgICBjb25zdCBicmVhZGNydW1iID0gdGhpcy5tZXJnZVdpdGhCYXNlQ2hpbGREYXRhKFxuICAgICAgcm91dGVDb25maWcsXG4gICAgICBkYXRhPy5icmVhZGNydW1iXG4gICAgKTtcblxuICAgIHJldHVybiB7IHBhdGgsIGJyZWFkY3J1bWIgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBnZXQgZW1wdHkgY2hpbGRyZW4gb2YgYSBtb2R1bGUgb3IgQ29tcG9uZW50LiBFbXB0eSBjaGlsZCBpcyB0aGUgb25lIHdpdGggcGF0aDogJydcbiAgICogV2hlbiBwYXJlbnQgYW5kIGl0J3MgY2hpbGRyZW4gKHRoYXQgaGFzIGVtcHR5IHJvdXRlIHBhdGgpIGRlZmluZSBkYXRhIG1lcmdlIHRoZW0gYm90aCB3aXRoIGNoaWxkIHRha2luZyBwcmVjZWRlbmNlXG4gICAqL1xuICBwcml2YXRlIG1lcmdlV2l0aEJhc2VDaGlsZERhdGEoXG4gICAgcm91dGVDb25maWcsXG4gICAgY29uZmlnOiBCcmVhZGNydW1iQ29uZmlnXG4gICk6IEJyZWFkY3J1bWJPYmplY3Qge1xuICAgIGlmICghcm91dGVDb25maWcpIHtcbiAgICAgIHJldHVybiB0aGlzLmV4dHJhY3RPYmplY3QoY29uZmlnKTtcbiAgICB9XG5cbiAgICBsZXQgYmFzZUNoaWxkO1xuICAgIGlmIChyb3V0ZUNvbmZpZy5sb2FkQ2hpbGRyZW4pIHtcbiAgICAgIC8vIFRvIGhhbmRsZSBhIG1vZHVsZSB3aXRoIGVtcHR5IGNoaWxkIHJvdXRlXG4gICAgICBiYXNlQ2hpbGQgPSByb3V0ZUNvbmZpZy5fbG9hZGVkUm91dGVzLmZpbmQoKHJvdXRlKSA9PiByb3V0ZS5wYXRoID09PSAnJyk7XG4gICAgfSBlbHNlIGlmIChyb3V0ZUNvbmZpZy5jaGlsZHJlbikge1xuICAgICAgLy8gVG8gaGFuZGxlIGEgY29tcG9uZW50IHdpdGggZW1wdHkgY2hpbGQgcm91dGVcbiAgICAgIGJhc2VDaGlsZCA9IHJvdXRlQ29uZmlnLmNoaWxkcmVuLmZpbmQoKHJvdXRlKSA9PiByb3V0ZS5wYXRoID09PSAnJyk7XG4gICAgfVxuXG4gICAgY29uc3QgY2hpbGRDb25maWcgPSBiYXNlQ2hpbGQ/LmRhdGE/LmJyZWFkY3J1bWI7XG4gICAgcmV0dXJuIGNoaWxkQ29uZmlnXG4gICAgICA/IHRoaXMubWVyZ2VXaXRoQmFzZUNoaWxkRGF0YShiYXNlQ2hpbGQsIHtcbiAgICAgICAgICAuLi50aGlzLmV4dHJhY3RPYmplY3QoY29uZmlnKSxcbiAgICAgICAgICAuLi50aGlzLmV4dHJhY3RPYmplY3QoY2hpbGRDb25maWcpLFxuICAgICAgICB9KVxuICAgICAgOiB0aGlzLmV4dHJhY3RPYmplY3QoY29uZmlnKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBVcGRhdGUgYnJlYWRjcnVtYiBkeW5hbWljYWxseVxuICAgKlxuICAgKiBrZXkgY2FuIGJlIGEgcGF0aCB8IGFsaWFzXG4gICAqXG4gICAqIDEpIFVzaW5nIGNvbXBsZXRlIHJvdXRlIHBhdGguIHJvdXRlIGNhbiBiZSBwYXNzZWQgdGhlIHNhbWUgd2F5IHlvdSBkZWZpbmUgYW5ndWxhciByb3V0ZXNcbiAgICogLSBwYXRoIGNhbiBiZSBwYXNzZWQgYXMgJ2V4YWN0IHBhdGgocm91dGVMaW5rKScgb3IgJ3BhdGggd2l0aCBwYXJhbXMocm91dGVSZWdleCknXG4gICAqIC0gdXBkYXRlIGxhYmVsIEV4OiBzZXQoJy9tZW50b3InLCAnTWVudG9yJyksIHNldCgnL21lbnRvci86aWQnLCAnTWVudG9yIERldGFpbHMnKVxuICAgKiAtIGNoYW5nZSB2aXNpYmlsaXR5IEV4OiBzZXQoJy9tZW50b3IvOmlkL2VkaXQnLCB7IHNraXA6IHRydWUgfSlcbiAgICogLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE9SIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgKiAyKSBVc2luZyByb3V0ZSBhbGlhcyAocHJlZml4ZWQgd2l0aCAnQCcpLiBhbGlhcyBzaG91bGQgYmUgdW5pcXVlIGZvciBhIHJvdXRlXG4gICAqIC0gdXBkYXRlIGxhYmVsIEV4OiBzZXQoJ0BtZW50b3InLCAnRW5hYmxlcicpXG4gICAqIC0gY2hhbmdlIHZpc2liaWxpdHkgRXg6IHNldCgnQG1lbnRvckVkaXQnLCB7IHNraXA6IHRydWUgfSlcbiAgICpcbiAgICpcbiAgICogdmFsdWUgY2FuIGJlIHN0cmluZyB8IEJyZWFkY3J1bWJPYmplY3QgfCBCcmVhZGNydW1iRnVuY3Rpb25cbiAgICovXG4gIHNldChrZXk6IHN0cmluZywgYnJlYWRjcnVtYjogc3RyaW5nIHwgQnJlYWRjcnVtYk9iamVjdCkge1xuICAgIGNvbnN0IGJyZWFkY3J1bWJPYmplY3QgPSB0aGlzLmV4dHJhY3RPYmplY3QoYnJlYWRjcnVtYik7XG4gICAgbGV0IHVwZGF0ZUFyZ3M6IFtTdG9yZU1hdGNoZXJLZXksIEJyZWFkY3J1bWJEZWZpbml0aW9uXTtcblxuICAgIGlmIChrZXkuc3RhcnRzV2l0aChBTElBU19QUkVGSVgpKSB7XG4gICAgICB1cGRhdGVBcmdzID0gWydhbGlhcycsIHsgLi4uYnJlYWRjcnVtYk9iamVjdCwgYWxpYXM6IGtleS5zbGljZSgxKSB9XTtcbiAgICB9IGVsc2UgaWYgKGtleS5pbmNsdWRlcyhQQVRIX1BBUkFNLlBSRUZJWCkpIHtcbiAgICAgIHVwZGF0ZUFyZ3MgPSBbXG4gICAgICAgICdyb3V0ZVJlZ2V4JyxcbiAgICAgICAgeyAuLi5icmVhZGNydW1iT2JqZWN0LCByb3V0ZVJlZ2V4OiB0aGlzLmJ1aWxkUmVnZXgoa2V5KSB9LFxuICAgICAgXTtcbiAgICB9IGVsc2Uge1xuICAgICAgdXBkYXRlQXJncyA9IFtcbiAgICAgICAgJ3JvdXRlTGluaycsXG4gICAgICAgIHsgLi4uYnJlYWRjcnVtYk9iamVjdCwgcm91dGVMaW5rOiB0aGlzLmVuc3VyZUxlYWRpbmdTbGFzaChrZXkpIH0sXG4gICAgICBdO1xuICAgIH1cblxuICAgIC8vIEZvciB0aGlzIHJvdXRlIGlmIHByZXZpb3VzbHkgYSBicmVhZGNydW1iIGlzIG5vdCBkZWZpbmVkIHRoYXQgc2V0cyBpc0F1dG9HZW5lcmF0ZWRMYWJlbDogdHJ1ZVxuICAgIC8vIGNoYW5nZSBpdCB0byBmYWxzZSBzaW5jZSB0aGlzIGlzIHVzZXIgc3VwcGxpZWQgdmFsdWVcbiAgICB1cGRhdGVBcmdzWzFdLmlzQXV0b0dlbmVyYXRlZExhYmVsID0gZmFsc2U7XG5cbiAgICB0aGlzLnVwZGF0ZVN0b3JlKC4uLnVwZGF0ZUFyZ3MpO1xuICAgIHRoaXMudXBkYXRlQ3VycmVudEJyZWFkY3J1bWJzKC4uLnVwZGF0ZUFyZ3MpO1xuICB9XG5cbiAgLyoqXG4gICAqIFVwZGF0ZSB0aGUgc3RvcmUgdG8gcmV1c2UgZm9yIGR5bmFtaWMgZGVjbGFyYXRpb25zXG4gICAqIElmIHRoZSBzdG9yZSBhbHJlYWR5IGhhcyB0aGlzIHJvdXRlIGRlZmluaXRpb24gdXBkYXRlIGl0LCBlbHNlIGFkZFxuICAgKi9cbiAgcHJpdmF0ZSB1cGRhdGVTdG9yZShrZXk6IHN0cmluZywgYnJlYWRjcnVtYjogQnJlYWRjcnVtYkRlZmluaXRpb24pIHtcbiAgICBjb25zdCBzdG9yZUl0ZW1JbmRleCA9IHRoaXMuZHluYW1pY0JyZWFkY3J1bWJTdG9yZS5maW5kSW5kZXgoKGl0ZW0pID0+IHtcbiAgICAgIHJldHVybiBicmVhZGNydW1iW2tleV0gPT09IGl0ZW1ba2V5XTtcbiAgICB9KTtcbiAgICBpZiAoc3RvcmVJdGVtSW5kZXggPiAtMSkge1xuICAgICAgdGhpcy5keW5hbWljQnJlYWRjcnVtYlN0b3JlW3N0b3JlSXRlbUluZGV4XSA9IHtcbiAgICAgICAgLi4udGhpcy5keW5hbWljQnJlYWRjcnVtYlN0b3JlW3N0b3JlSXRlbUluZGV4XSxcbiAgICAgICAgLi4uYnJlYWRjcnVtYixcbiAgICAgIH07XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuZHluYW1pY0JyZWFkY3J1bWJTdG9yZS5wdXNoKHsgLi4uYnJlYWRjcnVtYiB9KTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSWYgYnJlYWRjcnVtYiBpcyBwcmVzZW50IGluIGN1cnJlbnQgYnJlYWRjcnVtYnMgdXBkYXRlIGl0IGFuZCBlbWl0IG5ldyBzdHJlYW1cbiAgICovXG4gIHByaXZhdGUgdXBkYXRlQ3VycmVudEJyZWFkY3J1bWJzKFxuICAgIGtleTogc3RyaW5nLFxuICAgIGJyZWFkY3J1bWI6IEJyZWFkY3J1bWJEZWZpbml0aW9uXG4gICkge1xuICAgIGNvbnN0IGl0ZW1JbmRleCA9IHRoaXMuY3VycmVudEJyZWFkY3J1bWJzLmZpbmRJbmRleCgoaXRlbSkgPT4ge1xuICAgICAgcmV0dXJuIGtleSA9PT0gJ3JvdXRlUmVnZXgnXG4gICAgICAgID8gdGhpcy5tYXRjaFJlZ2V4KGl0ZW0ucm91dGVMaW5rLCBicmVhZGNydW1iW2tleV0pXG4gICAgICAgIDogYnJlYWRjcnVtYltrZXldID09PSBpdGVtW2tleV07XG4gICAgfSk7XG4gICAgaWYgKGl0ZW1JbmRleCA+IC0xKSB7XG4gICAgICB0aGlzLmN1cnJlbnRCcmVhZGNydW1ic1tpdGVtSW5kZXhdID0ge1xuICAgICAgICAuLi50aGlzLmN1cnJlbnRCcmVhZGNydW1ic1tpdGVtSW5kZXhdLFxuICAgICAgICAuLi5icmVhZGNydW1iLFxuICAgICAgfTtcbiAgICAgIGNvbnN0IGJyZWFkY3J1bWJzVG9TaG93ID0gdGhpcy5jdXJyZW50QnJlYWRjcnVtYnMuZmlsdGVyKFxuICAgICAgICAoaXRlbSkgPT4gIWl0ZW0uc2tpcFxuICAgICAgKTtcbiAgICAgIHRoaXMuYnJlYWRjcnVtYnMubmV4dChbLi4uYnJlYWRjcnVtYnNUb1Nob3ddKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRm9yIGEgcm91dGUgd2l0aCBwYXRoIHBhcmFtLCB3ZSBjcmVhdGUgcmVnZXggZHluYW1pY2FsbHkgZnJvbSBhbmd1bGFyIHJvdXRlIHN5bnRheFxuICAgKiAnL21lbnRvci86aWQnIGJlY29tZXMgJy9tZW50b3IvW14vXScsXG4gICAqIGJyZWFkY3J1bWJTZXJ2aWNlLnNldCgnL21lbnRvci86aWQnLCAnVWRheScpIHNob3VsZCB1cGRhdGUgJ1VkYXknIGFzIGxhYmVsIGZvciAnL21lbnRvci8yJyBPUiAnbWVudG9yL2FkYSdcbiAgICovXG4gIHByaXZhdGUgYnVpbGRSZWdleChwYXRoOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdGhpcy5lbnN1cmVMZWFkaW5nU2xhc2gocGF0aCkucmVwbGFjZShcbiAgICAgIG5ldyBSZWdFeHAoUEFUSF9QQVJBTS5SRUdFWF9JREVOVElGSUVSLCAnZycpLFxuICAgICAgUEFUSF9QQVJBTS5SRUdFWF9SRVBMQUNFUlxuICAgICk7XG4gIH1cblxuICBwcml2YXRlIGVuc3VyZUxlYWRpbmdTbGFzaChwYXRoOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gcGF0aC5zdGFydHNXaXRoKCcvJykgPyBwYXRoIDogYC8ke3BhdGh9YDtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbiBBcHAncyBSb3V0ZUNvbmZpZywgYnJlYWRjcnVtYiBjYW4gYmUgZGVmaW5lZCBhcyBhIHN0cmluZyBPUiBhIGZ1bmN0aW9uIE9SIGFuIG9iamVjdFxuICAgKlxuICAgKiBzdHJpbmc6IHNpbXBsZSBzdGF0aWMgYnJlYWRjcnVtYiBsYWJlbCBmb3IgYSBwYXRoXG4gICAqIGZ1bmN0aW9uOiBjYWxsYmFjayB0aGF0IGdldHMgaW52b2tlZCB3aXRoIHJlc29sdmVkIHBhdGggcGFyYW1cbiAgICogb2JqZWN0OiBhZGRpdGlvbmFsIGRhdGEgZGVmaW5lZCBhbG9uZyB3aXRoIGJyZWFkY3J1bWIgbGFiZWwgdGhhdCBnZXRzIHBhc3NlZCB0byAqeG5nQnJlYWRjcnVtYkl0ZW0gZGlyZWN0aXZlXG4gICAqL1xuICBwcml2YXRlIGV4dHJhY3RMYWJlbChjb25maWc6IEJyZWFkY3J1bWJDb25maWcsIHJlc29sdmVkUGFyYW0/OiBzdHJpbmcpIHtcbiAgICBjb25zdCBsYWJlbCA9IHR5cGVvZiBjb25maWcgPT09ICdvYmplY3QnID8gY29uZmlnLmxhYmVsIDogY29uZmlnO1xuICAgIGlmICh0eXBlb2YgbGFiZWwgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgIHJldHVybiBsYWJlbChyZXNvbHZlZFBhcmFtKTtcbiAgICB9XG4gICAgcmV0dXJuIGxhYmVsO1xuICB9XG5cbiAgcHJpdmF0ZSBleHRyYWN0T2JqZWN0KGNvbmZpZzogQnJlYWRjcnVtYkNvbmZpZyk6IEJyZWFkY3J1bWJPYmplY3Qge1xuICAgIC8vIGRvbid0IGluY2x1ZGUge2xhYmVsfSBpZiBjb25maWcgaXMgdW5kZWZpbmVkLiBUaGlzIGlzIGltcG9ydGFudCBzaW5jZSB3ZSBtZXJnZSB0aGUgY29uZmlnc1xuICAgIGlmIChcbiAgICAgIGNvbmZpZyAmJlxuICAgICAgKHR5cGVvZiBjb25maWcgPT09ICdzdHJpbmcnIHx8IHR5cGVvZiBjb25maWcgPT09ICdmdW5jdGlvbicpXG4gICAgKSB7XG4gICAgICByZXR1cm4geyBsYWJlbDogY29uZmlnIH07XG4gICAgfVxuICAgIHJldHVybiAoY29uZmlnIGFzIEJyZWFkY3J1bWJPYmplY3QpIHx8IHt9O1xuICB9XG59XG4iXX0=