UNPKG

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
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