ngx-dynamic-hooks
Version:
Automatically insert live Angular components into a dynamic string of content (based on their selector or any pattern of your choice) and render the result in the DOM.
368 lines • 65.5 kB
JavaScript
import { Inject, PLATFORM_ID, Injectable, createComponent, reflectComponentType } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { combineLatest, ReplaySubject, of } from 'rxjs';
import { first, mergeMap, tap, catchError } from 'rxjs/operators';
import { anchorAttrHookId, anchorAttrParseToken, anchorElementTag, voidElementTags } from '../../constants/core';
import * as i0 from "@angular/core";
import * as i1 from "./componentUpdater";
import * as i2 from "../platform/autoPlatformService";
import * as i3 from "../utils/logger";
/**
* The service responsible for dynamically creating components for all found Hooks
*/
export class ComponentCreator {
constructor(platformId, appRef, componentUpdater, platformService, logger) {
this.platformId = platformId;
this.appRef = appRef;
this.componentUpdater = componentUpdater;
this.platformService = platformService;
this.logger = logger;
}
/**
* The main entry function to start the dynamic component initialization process
*
* @param contentElement - The main content element
* @param hookIndex - The current hookIndex
* @param token - The current parse token
* @param context - The current context object
* @param options - The current ParseOptions
* @param environmentInjector - The environment injector to use for the dynamically-created components
* @param injector - The injector to use for the dynamically-created components
*/
init(contentElement, hookIndex, token, context, options, environmentInjector, injector) {
const allComponentsLoaded = new ReplaySubject(1);
const componentLoadSubjects = [];
const anchorElements = {};
// If no hooks found, no need to progress further
if (Object.keys(hookIndex).length === 0) {
allComponentsLoaded.next(true);
return allComponentsLoaded;
}
// Check anchor elements prepare loading components
// Note: Loop queried anchor elements instead of hookIndex entries as it will be in order of appearance, allowing for subsequent ones
// to potentially be skipped when overwritten by custom ng-content
for (let anchorElement of this.platformService.querySelectorAll(contentElement, `[${anchorAttrHookId}][${anchorAttrParseToken}]`)) {
const hookId = parseInt(this.platformService.getAttribute(anchorElement, anchorAttrHookId));
// Discard hook if anchor element was removed by previous hook in loop via ng-content replacement
if (this.platformService.querySelectorAll(contentElement, `[${anchorAttrHookId}="${hookId}"][${anchorAttrParseToken}="${token}"]`).length === 0) {
delete hookIndex[hookId];
continue;
}
const hook = hookIndex[hookId];
hook.data = hook.parser.loadComponent(hook.id, hook.value, context, this.platformService.getChildNodes(anchorElement), options);
hook.isLazy = hook.data.component.hasOwnProperty('importPromise') && hook.data.component.hasOwnProperty('importName');
// Skip loading lazy components during SSR
if (!isPlatformBrowser(this.platformId) && hook.isLazy) {
delete hookIndex[hookId];
continue;
}
// If anchor element is a void element and no custom host element specified, fallback to default anchor element
if (!hook.data.hostElementTag && voidElementTags.includes(this.platformService.getTagName(anchorElement).toLowerCase())) {
hook.data.hostElementTag = anchorElementTag;
}
/*
* Replace anchor element with custom one, if desired. Do this before loading any components.
*
* Explanation:
* When a component is created, one of its projectableNodes happens to be another components anchor element, but the parent component doesn't render the anchor right away
* (due to *ngIf, for example), you can't replace that anchor anymore as it is now tracked in Angular's memory as a reference. And that exact reference will
* be rendered when the component's *ngIf eventually resolved to true. So need to process all custom host element requests before loading components.
*/
if (hook.data.hostElementTag) {
anchorElement = this.useCustomHostElement(anchorElement, hook.data.hostElementTag);
hook.value.element = anchorElement;
}
// Insert child content according to hook.data immediately
// This has the benefit that if the child content is custom, the next iterations of this loop will throw out all hooks whose placeholder elements
// can no longer be found (b/c they were in the discarded child content) so their component won't be unnecessarily loaded.
this.createContentSlotElements(anchorElement, hook, token);
anchorElements[hookId] = anchorElement;
}
// For safety: Remove hooks from index whose anchor element for whatever reason could no longer be found
const foundHookIds = Object.keys(anchorElements).map(hookId => parseInt(hookId));
for (const [hookId, hook] of Object.entries(hookIndex)) {
if (!foundHookIds.includes(parseInt(hookId))) {
this.logger.warn(['Error when trying to load components - The anchor element for the following hook was found initially, but could not be found again for loading the component. Ignoring.', hook], options);
delete hookIndex[hookId];
}
}
// Load components
for (const [hookId, anchorElement] of Object.entries(anchorElements)) {
const hook = hookIndex[hookId];
// Start with of(true) to catch errors from loadComponentClass in the observable stream as well
componentLoadSubjects.push(of(true)
// Load component class first (might be lazy-loaded)
.pipe(mergeMap(() => this.loadComponentClass(hook.data.component)))
.pipe(tap((compClass) => {
// Get projectableNodes from the content slots
const projectableNodes = this.extractContentSlotElements(anchorElement, token);
// Instantiate component
this.createComponent(hook, context, anchorElement, projectableNodes, options, compClass, environmentInjector, injector);
}))
// If could not be created, remove from hookIndex
.pipe(catchError((e) => {
this.logger.error([e.stack], options);
delete hookIndex[hook.id];
return of(null);
})));
}
// If no components in text, no need to progress further
if (componentLoadSubjects.length === 0) {
allComponentsLoaded.next(true);
return allComponentsLoaded;
}
// Once all normal and lazy components have loaded
combineLatest([...componentLoadSubjects]).pipe(first()).subscribe(() => {
// Call dynamic lifecycle methods for all created components
for (const hook of Object.values(hookIndex)) {
// Find all content children components
const contentChildren = [];
if (typeof hook.componentRef.instance['onDynamicMount'] === 'function' || typeof hook.componentRef.instance['onDynamicChanges'] === 'function') {
this.findContentChildren(hook.componentRef.location.nativeElement, contentChildren, hookIndex, token);
}
// OnDynamicChanges
if (typeof hook.componentRef.instance['onDynamicChanges'] === 'function') {
hook.componentRef.instance['onDynamicChanges']({ contentChildren });
}
// OnDynamicMount
if (typeof hook.componentRef.instance['onDynamicMount'] === 'function') {
hook.componentRef.instance['onDynamicMount']({ context, contentChildren });
}
}
// Remove now redundant attributes from component elements
for (const anchorElement of Object.values(anchorElements)) {
this.platformService.removeAttribute(anchorElement, anchorAttrHookId);
this.platformService.removeAttribute(anchorElement, anchorAttrParseToken);
this.platformService.removeAttribute(anchorElement, 'ng-version');
}
// Done!
allComponentsLoaded.next(true);
});
return allComponentsLoaded;
}
// DOM manipulation
// ----------------------------------------------------------------------------------------------------------------
/**
* Replaces a default anchor element with a custom element
*
* @param anchorElement - The default component anchor element
* @param customTagName - The custom tag that should be used instead
*/
useCustomHostElement(anchorElement, customTagName) {
const customHostElement = this.platformService.createElement(customTagName);
// Move attributes to selector
this.platformService.setAttribute(customHostElement, anchorAttrHookId, this.platformService.getAttribute(anchorElement, anchorAttrHookId));
this.platformService.setAttribute(customHostElement, anchorAttrParseToken, this.platformService.getAttribute(anchorElement, anchorAttrParseToken));
this.platformService.removeAttribute(anchorElement, anchorAttrHookId);
this.platformService.removeAttribute(anchorElement, anchorAttrParseToken);
// Move child nodes to selector
const childNodes = this.platformService.getChildNodes(anchorElement);
for (const node of childNodes) {
this.platformService.appendChild(customHostElement, node);
}
// Replace anchorElement
this.platformService.insertBefore(this.platformService.getParentNode(anchorElement), customHostElement, anchorElement);
this.platformService.removeChild(this.platformService.getParentNode(anchorElement), anchorElement);
return customHostElement;
}
/**
* Creates a content slot dom element for each ng-content tag of the dynamically loaded component.
*
* This is to create a direct dom-representation of each entry in the projectableNodes array returned
* by parser.loadComponent, so it can be cleanly resolved back into projectableNodes later on. Without these
* content slots for separation, you wouldn't know which child nodes go into which ng-content slot.
*
* @param hostElement - The dom element to create the content slots in
* @param hook - The hook of the component
* @param token - The current parse token
*/
createContentSlotElements(hostElement, hook, token) {
let content;
// If content property is defined, use the submitted content slots
if (hook.data.hasOwnProperty('content') && Array.isArray(hook.data.content)) {
content = hook.data.content;
// Otherwise just wrap existing content into single content slot
}
else {
content = [this.platformService.getChildNodes(hostElement)];
}
// Empty child nodes
this.platformService.clearChildNodes(hostElement);
// Insert new ones
for (const [index, contentSlot] of content.entries()) {
if (contentSlot !== undefined && contentSlot !== null) {
const contentSlotElement = this.platformService.createElement('dynamic-component-contentslot');
this.platformService.setAttribute(contentSlotElement, 'slotIndex', index.toString());
this.platformService.setAttribute(contentSlotElement, 'parsetoken', token);
for (const node of contentSlot) {
this.platformService.appendChild(contentSlotElement, node);
}
this.platformService.appendChild(hostElement, contentSlotElement);
}
}
}
/**
* Returns all previously created content slots for a component element as a projectableNodes[][] array
*
* @param componentHostElement - The dom element with the content slots
* @param token - The current parse token
*/
extractContentSlotElements(componentHostElement, token) {
// Resolve ng-content from content slots
const projectableNodes = [];
const contentSlotElements = this.platformService.getChildNodes(componentHostElement)
.filter(entry => this.platformService.getTagName(entry) === 'DYNAMIC-COMPONENT-CONTENTSLOT' && this.platformService.getAttribute(entry, 'parsetoken') === token);
for (const contentSlotElement of contentSlotElements) {
const slotIndex = this.platformService.getAttribute(contentSlotElement, 'slotIndex');
projectableNodes[parseInt(slotIndex)] = this.platformService.getChildNodes(contentSlotElement);
}
// Bugfix: Make sure to manually remove the content slots and not just rely on createComponent() to do so.
// Otherwise they will persist with SSR due to hydration bug.
this.platformService.clearChildNodes(componentHostElement);
return projectableNodes;
}
// Component creation
// ----------------------------------------------------------------------------------------------------------------
/**
* Loads the component class from a ComponentConfig. Returns a subject the emits the class when ready.
*
* @param componentConfig - The componentConfig from HookData
*/
loadComponentClass(componentConfig) {
const componentClassLoaded = new ReplaySubject(1);
// a) If is component class
if (componentConfig.hasOwnProperty('prototype')) {
componentClassLoaded.next(componentConfig);
// c) If is function that returns promise with component class
}
else if (typeof componentConfig === 'function') {
componentConfig().then(compClass => {
componentClassLoaded.next(compClass);
});
// c) If is LazyLoadComponentConfig
}
else if (componentConfig.hasOwnProperty('importPromise') && componentConfig.hasOwnProperty('importName')) {
// Catch typical importPromise error
if (componentConfig.importPromise instanceof Promise) {
throw Error(`When lazy-loading a component, the "importPromise"-field must contain a function returning the import-promise, but it contained the promise itself.`);
}
componentConfig.importPromise().then((m) => {
const importName = componentConfig.importName;
const compClass = Object.prototype.hasOwnProperty.call(m, importName) ? m[importName] : m['default'];
componentClassLoaded.next(compClass);
});
}
else {
throw Error('The "component" property of a returned HookData object must either contain the component class, a function that returns a promise with the component class or an explicit LazyLoadComponentConfig');
}
return componentClassLoaded;
}
/**
* Dynamically creates the component with Angular methods
*
* @param hook - The hook for this component
* @param context - The current context
* @param componentHostElement - The hostElement for the component
* @param projectableNodes - The nodes to inject as ng-content
* @param options - The current ParseOptions
* @param compClass - The component class
* @param environmentInjector - The default environmentInjector
* @param injector - The default injector
*/
createComponent(hook, context, componentHostElement, projectableNodes, options, compClass, environmentInjector, injector) {
// Dynamically create component
// Note: Transcluded content (including components) for ng-content can simply be added here in the form of the projectableNodes-argument.
// The order of component creation or injection via projectableNodes does not seem to matter.
const dynamicComponentRef = createComponent(compClass, {
hostElement: componentHostElement,
environmentInjector: hook.data.environmentInjector || environmentInjector,
elementInjector: hook.data.injector || injector,
projectableNodes: projectableNodes
});
// Track component
hook.componentRef = dynamicComponentRef;
// Optionally trigger HTML events when outputs emit
this.mapOutputsToHTMLEvents(hook, options);
// Pass in initial bindings
this.componentUpdater.updateBindings(hook, context, options);
// Call initial OnDynamicChanges with context (if not undefined)
if (typeof hook.componentRef.instance['onDynamicChanges'] === 'function' && context !== undefined) {
hook.componentRef.instance['onDynamicChanges']({ context });
}
// Activate change detection
this.appRef.attachView(dynamicComponentRef.hostView);
// Trigger an Initial cd call to:
// - have Angular automatically invoke ngOnInit(), which happens the first time the change detector runs for a component
// - prevent ExpressionHasChangedErrors in Angular<8
dynamicComponentRef.changeDetectorRef.detectChanges();
}
// Other
// ----------------------------------------------------------------------------------------------------------------
/**
* Register DOM events to trigger when component outputs emit
*
* @param hook - The component hook
* @param options - The current ParseOptions
*/
mapOutputsToHTMLEvents(hook, options) {
const compMeta = reflectComponentType(hook.componentRef.componentType);
for (const outputObject of compMeta.outputs) {
const outputName = options.ignoreOutputAliases ? outputObject.propName : outputObject.templateName;
// Trigger events, if requested
hook.htmlEventSubscriptions[outputName] = hook.componentRef.instance[outputObject.propName].subscribe((event) => {
if (options.triggerDOMEvents) {
this.platformService.dispatchEvent(hook.componentRef?.location.nativeElement, outputName, event);
}
});
}
}
/**
* Find all components that would be the ContentChildren of a dynamic component and returns them in a hierarchical tree object
* Important: This function depends on the anchor attributes not being removed yet
*
* @param node - The HTML node to parse
* @param treeLevel - The current tree level of DynamicContentChildren (for recursiveness)
* @param hookIndex - The current hookIndex
* @param token - The current parseToken
*/
findContentChildren(node, treeLevel = [], hookIndex, token) {
const childNodes = this.platformService.getChildNodes(node);
if (childNodes != undefined && childNodes.length > 0) {
childNodes.forEach((childNode, key) => {
let componentFound = false;
// If element has a parsetoken and hookid, it is a dynamic component
const parseToken = this.platformService.getAttribute(childNode, anchorAttrParseToken);
if (parseToken !== null &&
parseToken === token &&
this.platformService.getAttribute(childNode, anchorAttrHookId)) {
const hookId = parseInt(this.platformService.getAttribute(childNode, anchorAttrHookId), 10);
if (hookIndex.hasOwnProperty(hookId)) {
treeLevel.push({
componentRef: hookIndex[hookId].componentRef,
contentChildren: [],
hookValue: hookIndex[hookId].value
});
componentFound = true;
}
}
// The hierarchical structure of the result is solely built on found components. It DOES NOT reflect the actual HTML structure.
// E.g. two components returned on the same array level in the result may be on completely different nesting levels in the HTML,
// as the only reason to 'go a level deeper' in the result is when a component was found.
const treeLevelForNested = componentFound ? treeLevel[treeLevel.length - 1].contentChildren : treeLevel;
this.findContentChildren(childNode, treeLevelForNested, hookIndex, token);
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ComponentCreator, deps: [{ token: PLATFORM_ID }, { token: i0.ApplicationRef }, { token: i1.ComponentUpdater }, { token: i2.AutoPlatformService }, { token: i3.Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ComponentCreator, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ComponentCreator, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }, { type: i0.ApplicationRef }, { type: i1.ComponentUpdater }, { type: i2.AutoPlatformService }, { type: i3.Logger }] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcG9uZW50Q3JlYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL25neC1keW5hbWljLWhvb2tzL3NyYy9saWIvc2VydmljZXMvY29yZS9jb21wb25lbnRDcmVhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQVksV0FBVyxFQUFrQixVQUFVLEVBQUUsZUFBZSxFQUF1QixvQkFBb0IsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN0SixPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxFQUFFLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDeEQsT0FBTyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsR0FBRyxFQUFFLFVBQVUsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBTWxFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxvQkFBb0IsRUFBRSxnQkFBZ0IsRUFBRSxlQUFlLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQzs7Ozs7QUFJakg7O0dBRUc7QUFJSCxNQUFNLE9BQU8sZ0JBQWdCO0lBRTNCLFlBQytCLFVBQWtCLEVBQ3ZDLE1BQXNCLEVBQ3RCLGdCQUFrQyxFQUNsQyxlQUFvQyxFQUNwQyxNQUFjO1FBSk8sZUFBVSxHQUFWLFVBQVUsQ0FBUTtRQUN2QyxXQUFNLEdBQU4sTUFBTSxDQUFnQjtRQUN0QixxQkFBZ0IsR0FBaEIsZ0JBQWdCLENBQWtCO1FBQ2xDLG9CQUFlLEdBQWYsZUFBZSxDQUFxQjtRQUNwQyxXQUFNLEdBQU4sTUFBTSxDQUFRO0lBRXhCLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsSUFBSSxDQUFDLGNBQW1CLEVBQUUsU0FBb0IsRUFBRSxLQUFhLEVBQUUsT0FBWSxFQUFFLE9BQXFCLEVBQUUsbUJBQXdDLEVBQUUsUUFBa0I7UUFDOUosTUFBTSxtQkFBbUIs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QUFtQixDQUFDLFNBQVMsRUFBRSxrQkFBa0IsRUFBRSxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDNUUsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQzsrR0FqWlUsZ0JBQWdCLGtCQUdqQixXQUFXO21IQUhWLGdCQUFnQixjQUZmLE1BQU07OzRGQUVQLGdCQUFnQjtrQkFINUIsVUFBVTttQkFBQztvQkFDVixVQUFVLEVBQUUsTUFBTTtpQkFDbkI7OzBCQUlJLE1BQU07MkJBQUMsV0FBVyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdCwgSW5qZWN0b3IsIFBMQVRGT1JNX0lELCBBcHBsaWNhdGlvblJlZiwgSW5qZWN0YWJsZSwgY3JlYXRlQ29tcG9uZW50LCBFbnZpcm9ubWVudEluamVjdG9yLCByZWZsZWN0Q29tcG9uZW50VHlwZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgaXNQbGF0Zm9ybUJyb3dzZXIgfSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xuaW1wb3J0IHsgY29tYmluZUxhdGVzdCwgUmVwbGF5U3ViamVjdCwgb2YgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IGZpcnN0LCBtZXJnZU1hcCwgdGFwLCBjYXRjaEVycm9yIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuXG5pbXBvcnQgeyBIb29rLCBIb29rSW5kZXggfSBmcm9tICcuLi8uLi9pbnRlcmZhY2VzUHVibGljJztcbmltcG9ydCB7IER5bmFtaWNDb250ZW50Q2hpbGQsIENvbXBvbmVudENvbmZpZywgTGF6eUxvYWRDb21wb25lbnRDb25maWcgfSBmcm9tICcuLi8uLi9pbnRlcmZhY2VzUHVibGljJztcbmltcG9ydCB7IENvbXBvbmVudFVwZGF0ZXIgfSBmcm9tICcuL2NvbXBvbmVudFVwZGF0ZXInO1xuaW1wb3J0IHsgQXV0b1BsYXRmb3JtU2VydmljZSB9IGZyb20gJy4uL3BsYXRmb3JtL2F1dG9QbGF0Zm9ybVNlcnZpY2UnO1xuaW1wb3J0IHsgYW5jaG9yQXR0ckhvb2tJZCwgYW5jaG9yQXR0clBhcnNlVG9rZW4sIGFuY2hvckVsZW1lbnRUYWcsIHZvaWRFbGVtZW50VGFncyB9IGZyb20gJy4uLy4uL2NvbnN0YW50cy9jb3JlJztcbmltcG9ydCB7IFBhcnNlT3B0aW9ucyB9IGZyb20gJy4uL3NldHRpbmdzL29wdGlvbnMnO1xuaW1wb3J0IHsgTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyJztcblxuLyoqXG4gKiBUaGUgc2VydmljZSByZXNwb25zaWJsZSBmb3IgZHluYW1pY2FsbHkgY3JlYXRpbmcgY29tcG9uZW50cyBmb3IgYWxsIGZvdW5kIEhvb2tzXG4gKi9cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnXG59KVxuZXhwb3J0IGNsYXNzIENvbXBvbmVudENyZWF0b3Ige1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIEBJbmplY3QoUExBVEZPUk1fSUQpIHByaXZhdGUgcGxhdGZvcm1JZDogc3RyaW5nLFxuICAgIHByaXZhdGUgYXBwUmVmOiBBcHBsaWNhdGlvblJlZixcbiAgICBwcml2YXRlIGNvbXBvbmVudFVwZGF0ZXI6IENvbXBvbmVudFVwZGF0ZXIsIFxuICAgIHByaXZhdGUgcGxhdGZvcm1TZXJ2aWNlOiBBdXRvUGxhdGZvcm1TZXJ2aWNlLFxuICAgIHByaXZhdGUgbG9nZ2VyOiBMb2dnZXJcbiAgKSB7XG4gIH1cblxuICAvKipcbiAgICogVGhlIG1haW4gZW50cnkgZnVuY3Rpb24gdG8gc3RhcnQgdGhlIGR5bmFtaWMgY29tcG9uZW50IGluaXRpYWxpemF0aW9uIHByb2Nlc3NcbiAgICpcbiAgICogQHBhcmFtIGNvbnRlbnRFbGVtZW50IC0gVGhlIG1haW4gY29udGVudCBlbGVtZW50XG4gICAqIEBwYXJhbSBob29rSW5kZXggLSBUaGUgY3VycmVudCBob29rSW5kZXhcbiAgICogQHBhcmFtIHRva2VuIC0gVGhlIGN1cnJlbnQgcGFyc2UgdG9rZW5cbiAgICogQHBhcmFtIGNvbnRleHQgLSBUaGUgY3VycmVudCBjb250ZXh0IG9iamVjdFxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIFRoZSBjdXJyZW50IFBhcnNlT3B0aW9uc1xuICAgKiBAcGFyYW0gZW52aXJvbm1lbnRJbmplY3RvciAtIFRoZSBlbnZpcm9ubWVudCBpbmplY3RvciB0byB1c2UgZm9yIHRoZSBkeW5hbWljYWxseS1jcmVhdGVkIGNvbXBvbmVudHNcbiAgICogQHBhcmFtIGluamVjdG9yIC0gVGhlIGluamVjdG9yIHRvIHVzZSBmb3IgdGhlIGR5bmFtaWNhbGx5LWNyZWF0ZWQgY29tcG9uZW50c1xuICAgKi9cbiAgaW5pdChjb250ZW50RWxlbWVudDogYW55LCBob29rSW5kZXg6IEhvb2tJbmRleCwgdG9rZW46IHN0cmluZywgY29udGV4dDogYW55LCBvcHRpb25zOiBQYXJzZU9wdGlvbnMsIGVudmlyb25tZW50SW5qZWN0b3I6IEVudmlyb25tZW50SW5qZWN0b3IsIGluamVjdG9yOiBJbmplY3Rvcik6IFJlcGxheVN1YmplY3Q8Ym9vbGVhbj4ge1xuICAgIGNvbnN0IGFsbENvbXBvbmVudHNMb2FkZWQ6IFJlcGxheVN1YmplY3Q8Ym9vbGVhbj4gPSBuZXcgUmVwbGF5U3ViamVjdCgxKTtcbiAgICBjb25zdCBjb21wb25lbnRMb2FkU3ViamVjdHMgPSBbXTtcbiAgICBjb25zdCBhbmNob3JFbGVtZW50czoge1trZXk6IHN0cmluZ106IGFueX0gPSB7fTtcblxuICAgIC8vIElmIG5vIGhvb2tzIGZvdW5kLCBubyBuZWVkIHRvIHByb2dyZXNzIGZ1cnRoZXJcbiAgICBpZiAoT2JqZWN0LmtleXMoaG9va0luZGV4KS5sZW5ndGggPT09IDApIHtcbiAgICAgIGFsbENvbXBvbmVudHNMb2FkZWQubmV4dCh0cnVlKTtcbiAgICAgIHJldHVybiBhbGxDb21wb25lbnRzTG9hZGVkO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGFuY2hvciBlbGVtZW50cyBwcmVwYXJlIGxvYWRpbmcgY29tcG9uZW50c1xuICAgIC8vIE5vdGU6IExvb3AgcXVlcmllZCBhbmNob3IgZWxlbWVudHMgaW5zdGVhZCBvZiBob29rSW5kZXggZW50cmllcyBhcyBpdCB3aWxsIGJlIGluIG9yZGVyIG9mIGFwcGVhcmFuY2UsIGFsbG93aW5nIGZvciBzdWJzZXF1ZW50IG9uZXMgXG4gICAgLy8gdG8gcG90ZW50aWFsbHkgYmUgc2tpcHBlZCB3aGVuIG92ZXJ3cml0dGVuIGJ5IGN1c3RvbSBuZy1jb250ZW50XG4gICAgZm9yIChsZXQgYW5jaG9yRWxlbWVudCBvZiB0aGlzLnBsYXRmb3JtU2VydmljZS5xdWVyeVNlbGVjdG9yQWxsKGNvbnRlbnRFbGVtZW50LCBgWyR7YW5jaG9yQXR0ckhvb2tJZH1dWyR7YW5jaG9yQXR0clBhcnNlVG9rZW59XWApKSB7XG4gICAgICBjb25zdCBob29rSWQgPSBwYXJzZUludCh0aGlzLnBsYXRmb3JtU2VydmljZS5nZXRBdHRyaWJ1dGUoYW5jaG9yRWxlbWVudCwgYW5jaG9yQXR0ckhvb2tJZCkhKTtcbiAgICAgIFxuICAgICAgLy8gRGlzY2FyZCBob29rIGlmIGFuY2hvciBlbGVtZW50IHdhcyByZW1vdmVkIGJ5IHByZXZpb3VzIGhvb2sgaW4gbG9vcCB2aWEgbmctY29udGVudCByZXBsYWNlbWVudFxuICAgICAgaWYgKHRoaXMucGxhdGZvcm1TZXJ2aWNlLnF1ZXJ5U2VsZWN0b3JBbGwoY29udGVudEVsZW1lbnQsIGBbJHthbmNob3JBdHRySG9va0lkfT1cIiR7aG9va0lkfVwiXVske2FuY2hvckF0dHJQYXJzZVRva2VufT1cIiR7dG9rZW59XCJdYCkubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGRlbGV0ZSBob29rSW5kZXhbaG9va0lkXTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IGhvb2sgPSBob29rSW5kZXhbaG9va0lkXTtcbiAgICAgIGhvb2suZGF0YSA9IGhvb2sucGFyc2VyLmxvYWRDb21wb25lbnQoaG9vay5pZCwgaG9vay52YWx1ZSwgY29udGV4dCwgdGhpcy5wbGF0Zm9ybVNlcnZpY2UuZ2V0Q2hpbGROb2RlcyhhbmNob3JFbGVtZW50KSwgb3B0aW9ucyk7XG4gICAgICBob29rLmlzTGF6eSA9IGhvb2suZGF0YS5jb21wb25lbnQuaGFzT3duUHJvcGVydHkoJ2ltcG9ydFByb21pc2UnKSAmJiBob29rLmRhdGEuY29tcG9uZW50Lmhhc093blByb3BlcnR5KCdpbXBvcnROYW1lJyk7XG5cbiAgICAgIC8vIFNraXAgbG9hZGluZyBsYXp5IGNvbXBvbmVudHMgZHVyaW5nIFNTUlxuICAgICAgaWYgKCFpc1BsYXRmb3JtQnJvd3Nlcih0aGlzLnBsYXRmb3JtSWQpICYmIGhvb2suaXNMYXp5KSB7XG4gICAgICAgIGRlbGV0ZSBob29rSW5kZXhbaG9va0lkXTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG5cbiAgICAgIC8vIElmIGFuY2hvciBlbGVtZW50IGlzIGEgdm9pZCBlbGVtZW50IGFuZCBubyBjdXN0b20gaG9zdCBlbGVtZW50IHNwZWNpZmllZCwgZmFsbGJhY2sgdG8gZGVmYXVsdCBhbmNob3IgZWxlbWVudFxuICAgICAgaWYgKCFob29rLmRhdGEuaG9zdEVsZW1lbnRUYWcgJiYgdm9pZEVsZW1lbnRUYWdzLmluY2x1ZGVzKHRoaXMucGxhdGZvcm1TZXJ2aWNlLmdldFRhZ05hbWUoYW5jaG9yRWxlbWVudCkudG9Mb3dlckNhc2UoKSkpIHtcbiAgICAgICAgaG9vay5kYXRhLmhvc3RFbGVtZW50VGFnID0gYW5jaG9yRWxlbWVudFRhZztcbiAgICAgIH1cblxuICAgICAgLypcbiAgICAgICogUmVwbGFjZSBhbmNob3IgZWxlbWVudCB3aXRoIGN1c3RvbSBvbmUsIGlmIGRlc2lyZWQuIERvIHRoaXMgYmVmb3JlIGxvYWRpbmcgYW55IGNvbXBvbmVudHMuXG4gICAgICAqIFxuICAgICAgKiBFeHBsYW5hdGlvbjogXG4gICAgICAqIFdoZW4gYSBjb21wb25lbnQgaXMgY3JlYXRlZCwgb25lIG9mIGl0cyBwcm9qZWN0YWJsZU5vZGVzIGhhcHBlbnMgdG8gYmUgYW5vdGhlciBjb21wb25lbnRzIGFuY2hvciBlbGVtZW50LCBidXQgdGhlIHBhcmVudCBjb21wb25lbnQgZG9lc24ndCByZW5kZXIgdGhlIGFuY2hvciByaWdodCBhd2F5XG4gICAgICAqIChkdWUgdG8gKm5nSWYsIGZvciBleGFtcGxlKSwgeW91IGNhbid0IHJlcGxhY2UgdGhhdCBhbmNob3IgYW55bW9yZSBhcyBpdCBpcyBub3cgdHJhY2tlZCBpbiBBbmd1bGFyJ3MgbWVtb3J5IGFzIGEgcmVmZXJlbmNlLiBBbmQgdGhhdCBleGFjdCByZWZlcmVuY2Ugd2lsbCBcbiAgICAgICogYmUgcmVuZGVyZWQgd2hlbiB0aGUgY29tcG9uZW50J3MgKm5nSWYgZXZlbnR1YWxseSByZXNvbHZlZCB0byB0cnVlLiBTbyBuZWVkIHRvIHByb2Nlc3MgYWxsIGN1c3RvbSBob3N0IGVsZW1lbnQgcmVxdWVzdHMgYmVmb3JlIGxvYWRpbmcgY29tcG9uZW50cy5cbiAgICAgICovXG4gICAgICBpZiAoaG9vay5kYXRhLmhvc3RFbGVtZW50VGFnKSB7XG4gICAgICAgIGFuY2hvckVsZW1lbnQgPSB0aGlzLnVzZUN1c3RvbUhvc3RFbGVtZW50KGFuY2hvckVsZW1lbnQsIGhvb2suZGF0YS5ob3N0RWxlbWVudFRhZyk7XG4gICAgICAgIGhvb2sudmFsdWUuZWxlbWVudCA9IGFuY2hvckVsZW1lbnQ7XG4gICAgICB9XG5cbiAgICAgIC8vIEluc2VydCBjaGlsZCBjb250ZW50IGFjY29yZGluZyB0byBob29rLmRhdGEgaW1tZWRpYXRlbHlcbiAgICAgIC8vIFRoaXMgaGFzIHRoZSBiZW5lZml0IHRoYXQgaWYgdGhlIGNoaWxkIGNvbnRlbnQgaXMgY3VzdG9tLCB0aGUgbmV4dCBpdGVyYXRpb25zIG9mIHRoaXMgbG9vcCB3aWxsIHRocm93IG91dCBhbGwgaG9va3Mgd2hvc2UgcGxhY2Vob2xkZXIgZWxlbWVudHMgXG4gICAgICAvLyBjYW4gbm8gbG9uZ2VyIGJlIGZvdW5kIChiL2MgdGhleSB3ZXJlIGluIHRoZSBkaXNjYXJkZWQgY2hpbGQgY29udGVudCkgc28gdGhlaXIgY29tcG9uZW50IHdvbid0IGJlIHVubmVjZXNzYXJpbHkgbG9hZGVkLlxuICAgICAgdGhpcy5jcmVhdGVDb250ZW50U2xvdEVsZW1lbnRzKGFuY2hvckVsZW1lbnQsIGhvb2ssIHRva2VuKTtcblxuICAgICAgYW5jaG9yRWxlbWVudHNbaG9va0lkXSA9IGFuY2hvckVsZW1lbnQ7XG4gICAgfVxuXG4gICAgLy8gRm9yIHNhZmV0eTogUmVtb3ZlIGhvb2tzIGZyb20gaW5kZXggd2hvc2UgYW5jaG9yIGVsZW1lbnQgZm9yIHdoYXRldmVyIHJlYXNvbiBjb3VsZCBubyBsb25nZXIgYmUgZm91bmRcbiAgICBjb25zdCBmb3VuZEhvb2tJZHMgPSBPYmplY3Qua2V5cyhhbmNob3JFbGVtZW50cykubWFwKGhvb2tJZCA9PiBwYXJzZUludChob29rSWQpKTtcbiAgICBmb3IgKGNvbnN0IFtob29rSWQsIGhvb2tdIG9mIE9iamVjdC5lbnRyaWVzKGhvb2tJbmRleCkpIHtcbiAgICAgIGlmICghZm91bmRIb29rSWRzLmluY2x1ZGVzKHBhcnNlSW50KGhvb2tJZCkpKSB7XG4gICAgICAgIHRoaXMubG9nZ2VyLndhcm4oWydFcnJvciB3aGVuIHRyeWluZyB0byBsb2FkIGNvbXBvbmVudHMgLSBUaGUgYW5jaG9yIGVsZW1lbnQgZm9yIHRoZSBmb2xsb3dpbmcgaG9vayB3YXMgZm91bmQgaW5pdGlhbGx5LCBidXQgY291bGQgbm90IGJlIGZvdW5kIGFnYWluIGZvciBsb2FkaW5nIHRoZSBjb21wb25lbnQuIElnbm9yaW5nLicsIGhvb2tdLCBvcHRpb25zKTtcbiAgICAgICAgZGVsZXRlIGhvb2tJbmRleFtob29rSWRdO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIExvYWQgY29tcG9uZW50c1xuICAgIGZvciAoY29uc3QgW2hvb2tJZCwgYW5jaG9yRWxlbWVudF0gb2YgT2JqZWN0LmVudHJpZXMoYW5jaG9yRWxlbWVudHMpKSB7XG4gICAgICBjb25zdCBob29rID0gaG9va0luZGV4W2hvb2tJZF07XG5cbiAgICAgIC8vIFN0YXJ0IHdpdGggb2YodHJ1ZSkgdG8gY2F0Y2ggZXJyb3JzIGZyb20gbG9hZENvbXBvbmVudENsYXNzIGluIHRoZSBvYnNlcnZhYmxlIHN0cmVhbSBhcyB3ZWxsXG4gICAgICBjb21wb25lbnRMb2FkU3ViamVjdHMucHVzaChvZih0cnVlKVxuICAgICAgICAvLyBMb2FkIGNvbXBvbmVudCBjbGFzcyBmaXJzdCAobWlnaHQgYmUgbGF6eS1sb2FkZWQpXG4gICAgICAgIC5waXBlKG1lcmdlTWFwKCgpID0+IHRoaXMubG9hZENvbXBvbmVudENsYXNzKGhvb2suZGF0YSEuY29tcG9uZW50KSkpXG4gICAgICAgIC5waXBlKHRhcCgoY29tcENsYXNzKSA9PiB7XG4gICAgICAgICAgLy8gR2V0IHByb2plY3RhYmxlTm9kZXMgZnJvbSB0aGUgY29udGVudCBzbG90c1xuICAgICAgICAgIGNvbnN0IHByb2plY3RhYmxlTm9kZXMgPSB0aGlzLmV4dHJhY3RDb250ZW50U2xvdEVsZW1lbnRzKGFuY2hvckVsZW1lbnQsIHRva2VuKTtcbiAgICAgICAgICAvLyBJbnN0YW50aWF0ZSBjb21wb25lbnRcbiAgICAgICAgICB0aGlzLmNyZWF0ZUNvbXBvbmVudChob29rLCBjb250ZXh0LCBhbmNob3JFbGVtZW50LCBwcm9qZWN0YWJsZU5vZGVzLCBvcHRpb25zLCBjb21wQ2xhc3MsIGVudmlyb25tZW50SW5qZWN0b3IsIGluamVjdG9yKTtcbiAgICAgICAgfSkpXG4gICAgICAgIC8vIElmIGNvdWxkIG5vdCBiZSBjcmVhdGVkLCByZW1vdmUgZnJvbSBob29rSW5kZXhcbiAgICAgICAgLnBpcGUoY2F0Y2hFcnJvcigoZSkgPT4ge1xuICAgICAgICAgIHRoaXMubG9nZ2VyLmVycm9yKFtlLnN0YWNrXSwgb3B0aW9ucyk7XG4gICAgICAgICAgZGVsZXRlIGhvb2tJbmRleFtob29rLmlkXTtcbiAgICAgICAgICByZXR1cm4gb2YobnVsbCk7XG4gICAgICAgIH0pKSk7XG4gICAgfVxuXG4gICAgLy8gSWYgbm8gY29tcG9uZW50cyBpbiB0ZXh0LCBubyBuZWVkIHRvIHByb2dyZXNzIGZ1cnRoZXJcbiAgICBpZiAoY29tcG9uZW50TG9hZFN1YmplY3RzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgYWxsQ29tcG9uZW50c0xvYWRlZC5uZXh0KHRydWUpO1xuICAgICAgcmV0dXJuIGFsbENvbXBvbmVudHNMb2FkZWQ7XG4gICAgfVxuXG4gICAgLy8gT25jZSBhbGwgbm9ybWFsIGFuZCBsYXp5IGNvbXBvbmVudHMgaGF2ZSBsb2FkZWRcbiAgICBjb21iaW5lTGF0ZXN0KFsuLi5jb21wb25lbnRMb2FkU3ViamVjdHNdKS5waXBlKGZpcnN0KCkpLnN1YnNjcmliZSgoKSA9PiB7XG5cbiAgICAgIC8vIENhbGwgZHluYW1pYyBsaWZlY3ljbGUgbWV0aG9kcyBmb3IgYWxsIGNyZWF0ZWQgY29tcG9uZW50c1xuICAgICAgZm9yIChjb25zdCBob29rIG9mIE9iamVjdC52YWx1ZXMoaG9va0luZGV4KSkge1xuICAgICAgICAvLyBGaW5kIGFsbCBjb250ZW50IGNoaWxkcmVuIGNvbXBvbmVudHNcbiAgICAgICAgY29uc3QgY29udGVudENoaWxkcmVuOiBEeW5hbWljQ29udGVudENoaWxkW10gPSBbXTtcbiAgICAgICAgaWYgKHR5cGVvZiBob29rLmNvbXBvbmVudFJlZiEuaW5zdGFuY2VbJ29uRHluYW1pY01vdW50J10gPT09ICdmdW5jdGlvbicgfHwgdHlwZW9mIGhvb2suY29tcG9uZW50UmVmIS5pbnN0YW5jZVsnb25EeW5hbWljQ2hhbmdlcyddID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgdGhpcy5maW5kQ29udGVudENoaWxkcmVuKGhvb2suY29tcG9uZW50UmVmIS5sb2NhdGlvbi5uYXRpdmVFbGVtZW50LCBjb250ZW50Q2hpbGRyZW4sIGhvb2tJbmRleCwgdG9rZW4pO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gT25EeW5hbWljQ2hhbmdlc1xuICAgICAgICBpZiAodHlwZW9mIGhvb2suY29tcG9uZW50UmVmIS5pbnN0YW5jZVsnb25EeW5hbWljQ2hhbmdlcyddID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgaG9vay5jb21wb25lbnRSZWYhLmluc3RhbmNlWydvbkR5bmFtaWNDaGFuZ2VzJ10oe2NvbnRlbnRDaGlsZHJlbn0pO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gT25EeW5hbWljTW91bnRcbiAgICAgICAgaWYgKHR5cGVvZiBob29rLmNvbXBvbmVudFJlZiEuaW5zdGFuY2VbJ29uRHluYW1pY01vdW50J10gPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgICBob29rLmNvbXBvbmVudFJlZiEuaW5zdGFuY2VbJ29uRHluYW1pY01vdW50J10oe2NvbnRleHQsIGNvbnRlbnRDaGlsZHJlbn0pO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIFJlbW92ZSBub3cgcmVkdW5kYW50IGF0dHJpYnV0ZXMgZnJvbSBjb21wb25lbnQgZWxlbWVudHNcbiAgICAgIGZvciAoY29uc3QgYW5jaG9yRWxlbWVudCBvZiBPYmplY3QudmFsdWVzKGFuY2hvckVsZW1lbnRzKSkge1xuICAgICAgICB0aGlzLnBsYXRmb3JtU2VydmljZS5yZW1vdmVBdHRyaWJ1dGUoYW5jaG9yRWxlbWVudCwgYW5jaG9yQXR0ckhvb2tJZCk7XG4gICAgICAgIHRoaXMucGxhdGZvcm1TZXJ2aWNlLnJlbW92ZUF0dHJpYnV0ZShhbmNob3JFbGVtZW50LCBhbmNob3JBdHRyUGFyc2VUb2tlbik7XG4gICAgICAgIHRoaXMucGxhdGZvcm1TZXJ2aWNlLnJlbW92ZUF0dHJpYnV0ZShhbmNob3JFbGVtZW50LCAnbmctdmVyc2lvbicpO1xuICAgICAgfVxuXG4gICAgICAvLyBEb25lIVxuICAgICAgYWxsQ29tcG9uZW50c0xvYWRlZC5uZXh0KHRydWUpO1xuICAgIH0pO1xuXG4gICAgcmV0dXJuIGFsbENvbXBvbmVudHNMb2FkZWQ7XG4gIH1cblxuICAvLyBET00gbWFuaXB1bGF0aW9uXG4gIC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuICAvKipcbiAgICogUmVwbGFjZXMgYSBkZWZhdWx0IGFuY2hvciBlbGVtZW50IHdpdGggYSBjdXN0b20gZWxlbWVudCBcbiAgICogXG4gICAqIEBwYXJhbSBhbmNob3JFbGVtZW50IC0gVGhlIGRlZmF1bHQgY29tcG9uZW50IGFuY2hvciBlbGVtZW50XG4gICAqIEBwYXJhbSBjdXN0b21UYWdOYW1lIC0gVGhlIGN1c3RvbSB0YWcgdGhhdCBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkXG4gICAqL1xuICB1c2VDdXN0b21Ib3N0RWxlbWVudChhbmNob3JFbGVtZW50OiBhbnksIGN1c3RvbVRhZ05