UNPKG

ngx-scroll-position-restoration

Version:
350 lines 53 kB
import { isPlatformServer } from '@angular/common'; import { Inject, Injectable, NgZone, PLATFORM_ID } from '@angular/core'; import { NavigationStart, Router, NavigationEnd } from '@angular/router'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import * as DomUtils from './dom-utils'; import { NGX_SCROLL_POSITION_RESTORATION_CONFIG_INJECTION_TOKEN } from './ngx-scroll-position-restoration-config-injection-token'; export class NgxScrollPositionRestorationService { constructor(router, zone, platformId, config) { this.router = router; this.zone = zone; this.platformId = platformId; this.config = config; this.applyStateToDomTimer = 0; this.currentPageState = {}; this.lastNavigationStartAt = 0; this.navigationIDs = []; this.pageStates = {}; this.scrolledElements = new Set(); this.maximumNumberOfCachedPageStates = 20; this.serviceDestroyed$ = new Subject(); } /** * Initialize NgxScrollPositionRestorationService. */ initialize() { if (isPlatformServer(this.platformId)) { return; } this.setupScrollBinding(); // this.setupRouterBinding(); // I bind to the router events and perform to primary actions: // -- // NAVIGATION START: When the user is about to navigate away from the current view, // I inspect the current DOM state and commit any scrolled-element offsets to the // in-memory cache of the page state (scroll events were recorded during the lifetime // of the current router state). // -- // NAVIGATION END: When the user completes a navigation to a new view, I check to see // if the new view is really the restoration of a previously cached page state; and, // if so, I try to reinstate the old scrolled-element offsets in the rendered DOM. this.router.events.pipe(takeUntil(this.serviceDestroyed$)).subscribe((event) => { // Filter navigation event streams to the appropriate event handlers. if (event instanceof NavigationStart) { this.handleNavigationStart(event); } else if (event instanceof NavigationEnd) { this.handleNavigationEnd(); } }); // Since we're going to be implementing a custom scroll retention algorithm, // let's disable the one that is provided by the browser. This will keep our // polyfill the source of truth. this.disableBrowserDefaultScrollRestoration(); } ngOnDestroy() { this.serviceDestroyed$.next(); this.serviceDestroyed$.complete(); } clearSavedWindowScrollTopInLastNavigation() { const lastNavigationId = this.navigationIDs[this.navigationIDs.length - 1]; if (lastNavigationId) { if (this.config.debug && this.pageStates[lastNavigationId][DomUtils.WINDOW_SELECTOR]) { console.log('Navigation in a "secondary" router-outlet - Remove window scroll position from recorded scroll positions.'); } delete (this.pageStates[lastNavigationId][DomUtils.WINDOW_SELECTOR]); } } /** * I attempt to apply the given page-state to the rendered DOM. I will continue to poll the document until all states have been reinstated; or, until the poll duration has been exceeded; or, until a subsequent navigation takes place. */ applyPageStateToDom(pageState) { if (this.config.debug) { this.debugPageState(pageState, 'Attempting to reapply scroll positions after a popstate navigation (backward or forward).'); } if (this.objectIsEmpty(pageState)) { return; } // Let's create a copy of the page state so that we can safely delete keys from // it as we successfully apply them to the rendered DOM. const pendingPageState = Object.assign({}, pageState); // Setup the scroll retention timer outside of the Angular Zone so that it // doesn't trigger any additional change-detection digests. this.zone.runOutsideAngular(() => { const startedAt = Date.now(); this.applyStateToDomTimer = setInterval(() => { for (const selector in pendingPageState) { const target = DomUtils.select(selector); // If the target element doesn't exist in the DOM yet, it // could be an indication of asynchronous loading and // rendering. Move onto the next selector while we still // have time. if (!target) { continue; } // If the element in question has been scrolled (by the user) // while we're attempting to reinstate the previous scroll // offsets, then ignore this state - the user's action should // take precedence. if (this.scrolledElements.has(target)) { delete (pendingPageState[selector]); // Otherwise, let's try to restore the scroll for the target. } else { const scrollTop = pendingPageState[selector]; const resultantScrollTop = DomUtils.scrollTo(target, scrollTop); // If the attempt to restore the element to its previous // offset resulted in a match, then stop tracking this // element. Otherwise, we'll continue to try and scroll // it in the subsequent tick. // -- // NOTE: We continue to try and update it because the // target element may exist in the DOM but also be // loading asynchronous data that is required for the // previous scroll offset. if (resultantScrollTop === scrollTop) { delete (pendingPageState[selector]); } } } // If there are no more elements to scroll or, we've exceeded our // poll duration, then stop watching the DOM. if (this.objectIsEmpty(pendingPageState) || ((Date.now() - startedAt) >= this.config.pollDuration)) { clearTimeout(this.applyStateToDomTimer); if (this.config.debug) { if (this.objectIsEmpty(pendingPageState)) { console.log('%c Successfully reapplied all recorded scroll positions to the DOM.', 'color: #2ecc71'); } else { console.warn(`Could not reapply following recorded scroll positions to the DOM after a poll duration of: ${this.config.pollDuration} milliseconds:`); this.debugPageState(pendingPageState); } } } }, this.config.pollCadence); }); } /** * I get the page state from the given set of nodes. This extracts the CSS selectors and offsets from the recorded elements. */ getPageStateFromNodes(nodes) { const pageState = {}; nodes.forEach(target => { // Generate a CSS selector from the given target. // -- // TODO: Right now, this algorithm creates the selector by walking up the // DOM tree and using the simulated encapsulation attributes. But, it // would be cool to have a configuration option that tells this algorithm // to look for a specific id-prefix or attribute or something. This would // require the developer to provide those; but it would be optimal. const selector = DomUtils.getSelector(target); // If the given Target is no longer part of the active DOM, the selector // will be null. if (selector) { pageState[selector] = DomUtils.getScrollTop(target); } }); return pageState; } /** * I determine if the given object is empty (ie, has no keys). */ objectIsEmpty(object) { for (const key in object) { return false; } return true; } // The goal of the NavigationStart event is to take changes that have been made // to the current DOM and store them in the render-state tree so they can be // reinstated at a future date. handleNavigationStart(event) { this.lastNavigationStartAt = Date.now(); // Get the navigation ID and the restored navigation ID for use in the // NavigationEnd event handler. this.navigationID = event.id; /** * Maybe in future update @todo: use ngx-navigation-trigger here, like: * (event.restoredState && this.whenShouldScrollPositionBeRestored.has(this.navigationTrigger)) */ this.restoredNavigationID = event.restoredState ? event.restoredState.navigationId : null; // If the user is navigating away from the current view, kill any timers that // may be trying to reinstate a page-state. clearTimeout(this.applyStateToDomTimer); // Before we navigate away from the current page state, let's commit any // scroll-elements to the current page state. Object.assign(this.currentPageState, this.getPageStateFromNodes(this.scrolledElements)); this.scrolledElements.clear(); if (this.config.debug) { this.debugPageState(this.currentPageState, 'Recorded scroll positions.'); } } ; // The primary goal of the NavigationEnd event is to reinstate a cached page // state in the event that the navigation is restoring a previously rendered page // as the result of a popstate event (ex, the user hit the Back or Forward // buttons). handleNavigationEnd() { const previousPageState = this.currentPageState; // Now that we know the navigation was successful, let's start and store a // new page state to track future scrolling. this.currentPageState = this.pageStates[this.navigationID] = {}; // While we are going to track elements that will be scrolled during the // current page rendering, it is possible that there are elements that were // scrolled during a prior page rendering that still exist on the page, but // were not scrolled recently (such as a secondary router-outlet). As such, // let's look at the previous page state and 'pull forward' any state that // still pertains to the current page. if (!this.restoredNavigationID) { for (const selector in previousPageState) { const target = DomUtils.select(selector); // Only pull the selector forward if it corresponds to an element // that still exists in the rendered page. if (!target) { continue; } // Only pull the selector forward if the target is still at the same // offset after the navigation has taken place. In other words, if // the offset has somehow changed in between the NavigationStart and // NavigationEnd events, then ignore it. To be honest, this really // only applies to the WINDOW, which can change in offset due to the // change in what the Router is actively rendering in the DOM. if (DomUtils.getScrollTop(target) !== previousPageState[selector]) { continue; } this.currentPageState[selector] = previousPageState[selector]; if (this.config.debug) { console.group('Pulling scroll position from previous page state in current page state.'); console.log({ selector, scrollPosition: this.currentPageState[selector] }); console.groupEnd(); } } // If we're restoring a previous page state AND we have that previous page // state cached in-memory, let's copy the previous state and then restore the // offsets in the DOM. } else if (this.restoredNavigationID && this.pageStates[this.restoredNavigationID]) { // NOTE: We're copying the offsets from the restored state into the // current state instead of just swapping the references because these // navigations are different in the Router history. Since each navigation // - imperative or popstate - gets a unique ID, we never truly 'go back' // in history; the Router only 'goes forward', with the notion that we're // recreating a previous state sometimes. this.applyPageStateToDom(Object.assign(this.currentPageState, this.pageStates[this.restoredNavigationID])); } // Keep track of the navigation event so we can limit the size of our // in-memory page state cache. this.navigationIDs.push(this.navigationID); // Trim the oldest page states as we go so that the in-memory cache doesn't // grow, unbounded. while (this.navigationIDs.length > this.maximumNumberOfCachedPageStates) { delete (this.pageStates[this.navigationIDs.shift()]); } } ; /** * I bind to the scroll event and keep track of any elements that are scrolled in the rendered document. */ setupScrollBinding() { /** * Maybe @todo: You should try to find a way to get scrollable (scrolled) elements only during NavigationStart. * Advantages: * - Better performance: no need to listen to the scroll event the whole time. * - Some elements might be added to the `scrolledElements` are not part of the DOM any more. * Disavantages: * - during NavigationStart scrollable elements that are maybe present after the intialization of page (before any user-interactions that can remove them) might be not part DOM any more. * */ // Add scroll-binding outside of the Angular Zone so it doesn't trigger any // additional change-detection digests. this.zone.runOutsideAngular(() => { // When navigating, the browser emits some scroll events as the DOM // (Document Object Model) changes shape in a way that forces the various // scroll offsets to change. Since these scroll events are not indicative // of a user's actual scrolling intent, we're going to ignore them. This // needs to be done on both sides of the navigation event (for reasons // that are not fully obvious or logical -- basically, the window's // scroll changes at a time that is not easy to tap into). Ignoring these // scroll events is important because the polyfilly stops trying to // reinstate a scroll-offset if it sees that the given element has // already been scrolled during the current rendering. const scrollBufferWindow = 100; let target; window.addEventListener('scroll', event => { // If the scroll event happens immediately following a // navigation event, then ignore it - it is likely a scroll that // was forced by the browser's native behavior. if ((Date.now() - this.lastNavigationStartAt) < scrollBufferWindow) { return; } // The target will return NULL for elements that have irrelevant // scroll behaviors (like textarea inputs). As such, we have to // check to see if the domUtils returned anything. target = DomUtils.getTargetFromScrollEvent(event); if (target) { this.scrolledElements.add(target); } }, // We have to use the CAPTURING phase. Scroll events DO NOT BUBBLE. // As such, if we want to listen for all scroll events in the // document, we have to use the capturing phase (as the event travels // down through the DOM tree). true); }); } debugPageState(pageState, message) { if (this.objectIsEmpty(pageState)) { return; } console.group(message || ''); for (const [selector, scrollPosition] of Object.entries(pageState)) { console.log({ selector, scrollPosition }); } console.groupEnd(); } /** * Disable browser default scroll restoration. * * Documentation: * - https://developer.mozilla.org/en-US/docs/Web/API/History/scrollRestoration */ disableBrowserDefaultScrollRestoration() { if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; } } } NgxScrollPositionRestorationService.decorators = [ { type: Injectable } ]; NgxScrollPositionRestorationService.ctorParameters = () => [ { type: Router }, { type: NgZone }, { type: String, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }, { type: undefined, decorators: [{ type: Inject, args: [NGX_SCROLL_POSITION_RESTORATION_CONFIG_INJECTION_TOKEN,] }] } ]; /** * Source: * - https://www.bennadel.com/blog/3534-restoring-and-resetting-the-scroll-position-using-the-navigationstart-event-in-angular-7-0-4.htm * - http://bennadel.github.io/JavaScript-Demos/demos/router-retain-scroll-polyfill-angular7/ * - https://github.com/bennadel/JavaScript-Demos/tree/master/demos/router-retain-scroll-polyfill-angular7 */ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LXNjcm9sbC1wb3NpdGlvbi1yZXN0b3JhdGlvbi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LXNjcm9sbC1wb3NpdGlvbi1yZXN0b3JhdGlvbi9zcmMvbGliL25neC1zY3JvbGwtcG9zaXRpb24tcmVzdG9yYXRpb24uc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNuRCxPQUFPLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQWEsV0FBVyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ25GLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxFQUFrQyxhQUFhLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6RyxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQy9CLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssUUFBUSxNQUFNLGFBQWEsQ0FBQztBQUV4QyxPQUFPLEVBQUUsc0RBQXNELEVBQUUsTUFBTSwwREFBMEQsQ0FBQztBQUdsSSxNQUFNLE9BQU8sbUNBQW1DO0lBaUI5QyxZQUNVLE1BQWMsRUFDZCxJQUFZLEVBQ1MsVUFBa0IsRUFDeUIsTUFBMEM7UUFIMUcsV0FBTSxHQUFOLE1BQU0sQ0FBUTtRQUNkLFNBQUksR0FBSixJQUFJLENBQVE7UUFDUyxlQUFVLEdBQVYsVUFBVSxDQUFRO1FBQ3lCLFdBQU0sR0FBTixNQUFNLENBQW9DO1FBbkI1Ryx5QkFBb0IsR0FBVyxDQUFDLENBQUM7UUFDakMscUJBQWdCLEdBQWMsRUFBRSxDQUFDO1FBQ2pDLDBCQUFxQixHQUFXLENBQUMsQ0FBQztRQUNsQyxrQkFBYSxHQUFhLEVBQUUsQ0FBQztRQUM3QixlQUFVLEdBQWUsRUFBRSxDQUFDO1FBQzVCLHFCQUFnQixHQUFnQixJ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QWtEO2dCQUNsRCxNQUFNLEdBQUcsUUFBUSxDQUFDLHdCQUF3QixDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNsRCxJQUFJLE1BQU0sRUFBRTtvQkFDVixJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO2lCQUNuQztZQUNILENBQUM7WUFDRCxtRUFBbUU7WUFDbkUsOERBQThEO1lBQzlELHFFQUFxRTtZQUNyRSw4QkFBOEI7WUFDOUIsSUFBSSxDQUNMLENBQUM7UUFDSixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTyxjQUFjLENBQUMsU0FBb0IsRUFBRSxPQUFnQjtRQUMzRCxJQUFJLElBQUksQ0FBQyxhQUFhLENBQUMsU0FBUyxDQUFDLEVBQUU7WUFDakMsT0FBTztTQUNSO1FBQ0QsT0FBTyxDQUFDLEtBQUssQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDLENBQUM7UUFDN0IsS0FBSyxNQUFNLENBQUMsUUFBUSxFQUFFLGNBQWMsQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUU7WUFDbEUsT0FBTyxDQUFDLEdBQUcsQ0FBQztnQkFDVixRQUFRO2dCQUNSLGNBQWM7YUFDZixDQUFDLENBQUM7U0FDSjtRQUNELE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztJQUNyQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSyxzQ0FBc0M7UUFDNUMsSUFBSSxtQkFBbUIsSUFBSSxPQUFPLEVBQUU7WUFDbEMsT0FBTyxDQUFDLGlCQUFpQixHQUFHLFFBQVEsQ0FBQztTQUN0QztJQUNILENBQUM7OztZQTNZRixVQUFVOzs7WUFQZSxNQUFNO1lBREgsTUFBTTt5Q0E2QjlCLE1BQU0sU0FBQyxXQUFXOzRDQUNsQixNQUFNLFNBQUMsc0RBQXNEOztBQXFZbEU7Ozs7O0dBS0ciLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBpc1BsYXRmb3JtU2VydmVyIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IEluamVjdCwgSW5qZWN0YWJsZSwgTmdab25lLCBPbkRlc3Ryb3ksIFBMQVRGT1JNX0lEIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBOYXZpZ2F0aW9uU3RhcnQsIFJvdXRlciwgRXZlbnQgYXMgUm91dGVyTmF2aWdhdGlvbkV2ZW50LCBOYXZpZ2F0aW9uRW5kIH0gZnJvbSAnQGFuZ3VsYXIvcm91dGVyJztcbmltcG9ydCB7IFN1YmplY3QgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IHRha2VVbnRpbCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCAqIGFzIERvbVV0aWxzIGZyb20gJy4vZG9tLXV0aWxzJztcbmltcG9ydCB7IE5neFNjcm9sbFBvc2l0aW9uUmVzdG9yYXRpb25Db25maWcgfSBmcm9tICcuL25neC1zY3JvbGwtcG9zaXRpb24tcmVzdG9yYXRpb24tY29uZmlnJztcbmltcG9ydCB7IE5HWF9TQ1JPTExfUE9TSVRJT05fUkVTVE9SQVRJT05fQ09ORklHX0lOSkVDVElPTl9UT0tFTiB9IGZyb20gJy4vbmd4LXNjcm9sbC1wb3NpdGlvbi1yZXN0b3JhdGlvbi1jb25maWctaW5qZWN0aW9uLXRva2VuJztcblxuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIE5neFNjcm9sbFBvc2l0aW9uUmVzdG9yYXRpb25TZXJ2aWNlIGltcGxlbWVudHMgT25EZXN0cm95IHtcblxuICBwcml2YXRlIGFwcGx5U3RhdGVUb0RvbVRpbWVyOiBudW1iZXIgPSAwO1xuICBwcml2YXRlIGN1cnJlbnRQYWdlU3RhdGU6IFBhZ2VTdGF0ZSA9IHt9O1xuICBwcml2YXRlIGxhc3ROYXZpZ2F0aW9uU3RhcnRBdDogbnVtYmVyID0gMDtcbiAgcHJpdmF0ZSBuYXZpZ2F0aW9uSURzOiBudW1iZXJbXSA9IFtdO1xuICBwcml2YXRlIHBhZ2VTdGF0ZXM6IFBhZ2VTdGF0ZXMgPSB7fTtcbiAgcHJpdmF0ZSBzY3JvbGxlZEVsZW1lbnRzOiBTZXQ8VGFyZ2V0PiA9IG5ldyBTZXQoKTtcblxuICAvLyBXZSBuZWVkIHRvIGtlZXAgdHJhY2sgb2YgdGhlc2UgdmFsdWVzIGFjcm9zcyB0aGUgU3RhcnQgLyBFbmQgZXZlbnRzLlxuICBwcml2YXRlIG5hdmlnYXRpb25JRCE6IG51bWJlcjtcbiAgcHJpdmF0ZSByZXN0b3JlZE5hdmlnYXRpb25JRCE6IG51bWJlciB8IG51bGw7XG5cbiAgcHJpdmF0ZSBtYXhpbXVtTnVtYmVyT2ZDYWNoZWRQYWdlU3RhdGVzOiBudW1iZXIgPSAyMDtcblxuICBwcml2YXRlIHNlcnZpY2VEZXN0cm95ZWQkID0gbmV3IFN1YmplY3Q8dm9pZD4oKTtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHJvdXRlcjogUm91dGVyLFxuICAgIHByaXZhdGUgem9uZTogTmdab25lLFxuICAgIEBJbmplY3QoUExBVEZPUk1fSUQpIHByaXZhdGUgcGxhdGZvcm1JZDogc3RyaW5nLFxuICAgIEBJbmplY3QoTkdYX1NDUk9MTF9QT1NJVElPTl9SRVNUT1JBVElPTl9DT05GSUdfSU5KRUNUSU9OX1RPS0VOKSBwcml2YXRlIGNvbmZpZzogTmd4U2Nyb2xsUG9zaXRpb25SZXN0b3JhdGlvbkNvbmZpZ1xuICApIHsgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplIE5neFNjcm9sbFBvc2l0aW9uUmVzdG9yYXRpb25TZXJ2aWNlLlxuICAgKi9cbiAgaW5pdGlhbGl6ZSgpOiB2b2lkIHtcbiAgICBpZiAoaXNQbGF0Zm9ybVNlcnZlcih0aGlzLnBsYXRmb3JtSWQpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdGhpcy5zZXR1cFNjcm9sbEJpbmRpbmcoKTtcblxuICAgIC8vIHRoaXMuc2V0dXBSb3V0ZXJCaW5kaW5nKCk7XG4gICAgLy8gSSBiaW5kIHRvIHRoZSByb3V0ZXIgZXZlbnRzIGFuZCBwZXJmb3JtIHRvIHByaW1hcnkgYWN0aW9uczpcbiAgICAvLyAtLVxuICAgIC8vIE5BVklHQVRJT04gU1RBUlQ6IFdoZW4gdGhlIHVzZXIgaXMgYWJvdXQgdG8gbmF2aWdhdGUgYXdheSBmcm9tIHRoZSBjdXJyZW50IHZpZXcsXG4gICAgLy8gSSBpbnNwZWN0IHRoZSBjdXJyZW50IERPTSBzdGF0ZSBhbmQgY29tbWl0IGFueSBzY3JvbGxlZC1lbGVtZW50IG9mZnNldHMgdG8gdGhlXG4gICAgLy8gaW4tbWVtb3J5IGNhY2hlIG9mIHRoZSBwYWdlIHN0YXRlIChzY3JvbGwgZXZlbnRzIHdlcmUgcmVjb3JkZWQgZHVyaW5nIHRoZSBsaWZldGltZVxuICAgIC8vIG9mIHRoZSBjdXJyZW50IHJvdXRlciBzdGF0ZSkuXG4gICAgLy8gLS1cbiAgICAvLyBOQVZJR0FUSU9OIEVORDogV2hlbiB0aGUgdXNlciBjb21wbGV0ZXMgYSBuYXZpZ2F0aW9uIHRvIGEgbmV3IHZpZXcsIEkgY2hlY2sgdG8gc2VlXG4gICAgLy8gaWYgdGhlIG5ldyB2aWV3IGlzIHJlYWxseSB0aGUgcmVzdG9yYXRpb24gb2YgYSBwcmV2aW91c2x5IGNhY2hlZCBwYWdlIHN0YXRlOyBhbmQsXG4gICAgLy8gaWYgc28sIEkgdHJ5IHRvIHJlaW5zdGF0ZSB0aGUgb2xkIHNjcm9sbGVkLWVsZW1lbnQgb2Zmc2V0cyBpbiB0aGUgcmVuZGVyZWQgRE9NLlxuICAgIHRoaXMucm91dGVyLmV2ZW50cy5waXBlKFxuICAgICAgdGFrZVVudGlsKHRoaXMuc2VydmljZURlc3Ryb3llZCQpXG4gICAgKS5zdWJzY3JpYmUoXG4gICAgICAoZXZlbnQ6IFJvdXRlck5hdmlnYXRpb25FdmVudCkgPT4ge1xuICAgICAgICAvLyBGaWx0ZXIgbmF2aWdhdGlvbiBldmVudCBzdHJlYW1zIHRvIHRoZSBhcHByb3ByaWF0ZSBldmVudCBoYW5kbGVycy5cbiAgICAgICAgaWYgKGV2ZW50IGluc3RhbmNlb2YgTmF2aWdhdGlvblN0YXJ0KSB7XG4gICAgICAgICAgdGhpcy5oYW5kbGVOYXZpZ2F0aW9uU3RhcnQoZXZlbnQpO1xuICAgICAgICB9IGVsc2UgaWYgKGV2ZW50IGluc3RhbmNlb2YgTmF2aWdhdGlvbkVuZCkge1xuICAgICAgICAgIHRoaXMuaGFuZGxlTmF2aWdhdGlvbkVuZCgpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgKTtcblxuICAgIC8vIFNpbmNlIHdlJ3JlIGdvaW5nIHRvIGJlIGltcGxlbWVudGluZyBhIGN1c3RvbSBzY3JvbGwgcmV0ZW50aW9uIGFsZ29yaXRobSxcbiAgICAvLyBsZXQncyBkaXNhYmxlIHRoZSBvbmUgdGhhdCBpcyBwcm92aWRlZCBieSB0aGUgYnJvd3Nlci4gVGhpcyB3aWxsIGtlZXAgb3VyXG4gICAgLy8gcG9seWZpbGwgdGhlIHNvdXJjZSBvZiB0cnV0aC5cbiAgICB0aGlzLmRpc2FibGVCcm93c2VyRGVmYXVsdFNjcm9sbFJlc3RvcmF0aW9uKCk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICB0aGlzLnNlcnZpY2VEZXN0cm95ZWQkLm5leHQoKTtcbiAgICB0aGlzLnNlcnZpY2VEZXN0cm95ZWQkLmNvbXBsZXRlKCk7XG4gIH1cblxuICBjbGVhclNhdmVkV2luZG93U2Nyb2xsVG9wSW5MYXN0TmF2aWdhdGlvbigpOiB2b2lkIHtcbiAgICBjb25zdCBsYXN0TmF2aWdhdGlvbklkID0gdGhpcy5uYXZpZ2F0aW9uSURzW3RoaXMubmF2aWdhdGlvbklEcy5sZW5ndGggLSAxXTtcbiAgICBpZiAobGFzdE5hdmlnYXRpb25JZCkge1xuICAgICAgaWYgKHRoaXMuY29uZmlnLmRlYnVnICYmIHRoaXMucGFnZVN0YXRlc1tsYXN0TmF2aWdhdGlvbklkXVtEb21VdGlscy5XSU5ET1dfU0VMRUNUT1JdKSB7XG4gICAgICAgIGNvbnNvbGUubG9nKCdOYXZpZ2F0aW9uIGluIGEgXCJzZWNvbmRhcnlcIiByb3V0ZXItb3V0bGV0IC0gUmVtb3ZlIHdpbmRvdyBzY3JvbGwgcG9zaXRpb24gZnJvbSByZWNvcmRlZCBzY3JvbGwgcG9zaXRpb25zLicpO1xuICAgICAgfVxuICAgICAgZGVsZXRlICh0aGlzLnBhZ2VTdGF0ZXNbbGFzdE5hdmlnYXRpb25JZF1bRG9tVXRpbHMuV0lORE9XX1NFTEVDVE9SXSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEkgYXR0ZW1wdCB0byBhcHBseSB0aGUgZ2l2ZW4gcGFnZS1zdGF0ZSB0byB0aGUgcmVuZGVyZWQgRE9NLiBJIHdpbGwgY29udGludWUgdG8gcG9sbCB0aGUgZG9jdW1lbnQgdW50aWwgYWxsIHN0YXRlcyBoYXZlIGJlZW4gcmVpbnN0YXRlZDsgb3IsIHVudGlsIHRoZSBwb2xsIGR1cmF0aW9uIGhhcyBiZWVuIGV4Y2VlZGVkOyBvciwgdW50aWwgYSBzdWJzZXF1ZW50IG5hdmlnYXRpb24gdGFrZXMgcGxhY2UuXG4gICAqL1xuICBwcml2YXRlIGFwcGx5UGFnZVN0YXRlVG9Eb20ocGFnZVN0YXRlOiBQYWdlU3RhdGUpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5jb25maWcuZGVidWcpIHtcbiAgICAgIHRoaXMuZGVidWdQYWdlU3RhdGUocGFnZVN0YXRlLCAnQXR0ZW1wdGluZyB0byByZWFwcGx5IHNjcm9sbCBwb3NpdGlvbnMgYWZ0ZXIgYSBwb3BzdGF0ZSBuYXZpZ2F0aW9uIChiYWNrd2FyZCBvciBmb3J3YXJkKS4nKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5vYmplY3RJc0VtcHR5KHBhZ2VTdGF0ZSkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBMZXQncyBjcmVhdGUgYSBjb3B5IG9mIHRoZSBwYWdlIHN0YXRlIHNvIHRoYXQgd2UgY2FuIHNhZmVseSBkZWxldGUga2V5cyBmcm9tXG4gICAgLy8gaXQgYXMgd2Ugc3VjY2Vzc2Z1bGx5IGFwcGx5IHRoZW0gdG8gdGhlIHJlbmRlcmVkIERPTS5cbiAgICBjb25zdCBwZW5kaW5nUGFnZVN0YXRlID0geyAuLi5wYWdlU3RhdGUgfTtcblxuICAgIC8vIFNldHVwIHRoZSBzY3JvbGwgcmV0ZW50aW9uIHRpbWVyIG91dHNpZGUgb2YgdGhlIEFuZ3VsYXIgWm9uZSBzbyB0aGF0IGl0XG4gICAgLy8gZG9lc24ndCB0cmlnZ2VyIGFueSBhZGRpdGlvbmFsIGNoYW5nZS1kZXRlY3Rpb24gZGlnZXN0cy5cbiAgICB0aGlzLnpvbmUucnVuT3V0c2lkZUFuZ3VsYXIoXG4gICAgICAoKTogdm9pZCA9PiB7XG4gICAgICAgIGNvbnN0IHN0YXJ0ZWRBdCA9IERhdGUubm93KCk7XG5cbiAgICAgICAgdGhpcy5hcHBseVN0YXRlVG9Eb21UaW1lciA9IHNldEludGVydmFsKFxuICAgICAgICAgICgpID0+IHtcbiAgICAgICAgICAgIGZvciAoY29uc3Qgc2VsZWN0b3IgaW4gcGVuZGluZ1BhZ2VTdGF0ZSkge1xuICAgICAgICAgICAgICBjb25zdCB0YXJnZXQgPSBEb21VdGlscy5zZWxlY3Qoc2VsZWN0b3IpO1xuXG4gICAgICAgICAgICAgIC8vIElmIHRoZSB0YXJnZXQgZWxlbWVudCBkb2Vzbid0IGV4aXN0IGluIHRoZSBET00geWV0LCBpdFxuICAgICAgICAgICAgICAvLyBjb3VsZCBiZSBhbiBpbmRpY2F0aW9uIG9mIGFzeW5jaHJvbm91cyBsb2FkaW5nIGFuZFxuICAgICAgICAgICAgICAvLyByZW5kZXJpbmcuIE1vdmUgb250byB0aGUgbmV4dCBzZWxlY3RvciB3aGlsZSB3ZSBzdGlsbFxuICAgICAgICAgICAgICAvLyBoYXZlIHRpbWUuXG4gICAgICAgICAgICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAvLyBJZiB0aGUgZWxlbWVudCBpbiBxdWVzdGlvbiBoYXMgYmVlbiBzY3JvbGxlZCAoYnkgdGhlIHVzZXIpXG4gICAgICAgICAgICAgIC8vIHdoaWxlIHdlJ3JlIGF0dGVtcHRpbmcgdG8gcmVpbnN0YXRlIHRoZSBwcmV2aW91cyBzY3JvbGxcbiAgICAgICAgICAgICAgLy8gb2Zmc2V0cywgdGhlbiBpZ25vcmUgdGhpcyBzdGF0ZSAtIHRoZSB1c2VyJ3MgYWN0aW9uIHNob3VsZFxuICAgICAgICAgICAgICAvLyB0YWtlIHByZWNlZGVuY2UuXG4gICAgICAgICAgICAgIGlmICh0aGlzLnNjcm9sbGVkRWxlbWVudHMuaGFzKHRhcmdldCkpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgKHBlbmRpbmdQYWdlU3RhdGVbc2VsZWN0b3JdKTtcbiAgICAgICAgICAgICAgICAvLyBPdGhlcndpc2UsIGxldCdzIHRyeSB0byByZXN0b3JlIHRoZSBzY3JvbGwgZm9yIHRoZSB0YXJnZXQuXG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgY29uc3Qgc2Nyb2xsVG9wID0gcGVuZGluZ1BhZ2VTdGF0ZVtzZWxlY3Rvcl07XG4gICAgICAgICAgICAgICAgY29uc3QgcmVzdWx0YW50U2Nyb2xsVG9wID0gRG9tVXRpbHMuc2Nyb2xsVG8odGFyZ2V0LCBzY3JvbGxUb3ApO1xuXG4gICAgICAgICAgICAgICAgLy8gSWYgdGhlIGF0dGVtcHQgdG8gcmVzdG9yZSB0aGUgZWxlbWVudCB0byBpdHMgcHJldmlvdXNcbiAgICAgICAgICAgICAgICAvLyBvZmZzZXQgcmVzdWx0ZWQgaW4gYSBtYXRjaCwgdGhlbiBzdG9wIHRyYWNraW5nIHRoaXNcbiAgICAgICAgICAgICAgICAvLyBlbGVtZW50LiBPdGhlcndpc2UsIHdlJ2xsIGNvbnRpbnVlIHRvIHRyeSBhbmQgc2Nyb2xsXG4gICAgICAgICAgICAgICAgLy8gaXQgaW4gdGhlIHN1YnNlcXVlbnQgdGljay5cbiAgICAgICAgICAgICAgICAvLyAtLVxuICAgICAgICAgICAgICAgIC8vIE5PVEU6IFdlIGNvbnRpbnVlIHRvIHRyeSBhbmQgdXBkYXRlIGl0IGJlY2F1c2UgdGhlXG4gICAgICAgICAgICAgICAgLy8gdGFyZ2V0IGVsZW1lbnQgbWF5IGV4aXN0IGluIHRoZSBET00gYnV0IGFsc28gYmVcbiAgICAgICAgICAgICAgICAvLyBsb2FkaW5nIGFzeW5jaHJvbm91cyBkYXRhIHRoYXQgaXMgcmVxdWlyZWQgZm9yIHRoZVxuICAgICAgICAgICAgICAgIC8vIHByZXZpb3VzIHNjcm9sbCBvZmZzZXQuXG4gICAgICAgICAgICAgICAgaWYgKHJlc3VsdGFudFNjcm9sbFRvcCA9PT0gc2Nyb2xsVG9wKSB7XG4gICAgICAgICAgICAgICAgICBkZWxldGUgKHBlbmRpbmdQYWdlU3RhdGVbc2VsZWN0b3JdKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gSWYgdGhlcmUgYXJlIG5vIG1vcmUgZWxlbWVudHMgdG8gc2Nyb2xsIG9yLCB3ZSd2ZSBleGNlZWRlZCBvdXJcbiAgICAgICAgICAgIC8vIHBvbGwgZHVyYXRpb24sIHRoZW4gc3RvcCB3YXRjaGluZyB0aGUgRE9NLlxuICAgICAgICAgICAgaWYgKHRoaXMub2JqZWN0SXNFbXB0eShwZW5kaW5nUGFnZVN0YXRlKVxuICAgICAgICAgICAgICB8fCAoKERhdGUubm93KCkgLSBzdGFydGVkQXQpID49IHRoaXMuY29uZmlnLnBvbGxEdXJhdGlvbiEpXG4gICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuYXBwbHlTdGF0ZVRvRG9tVGltZXIpO1xuICAgICAgICAgICAgICBpZiAodGhpcy5jb25maWcuZGVidWcpIHtcbiAgICAgICAgICAgICAgICBpZiAodGhpcy5vYmplY3RJc0VtcHR5KHBlbmRpbmdQYWdlU3RhdGUpKSB7XG4gICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygnJWMgU3VjY2Vzc2Z1bGx5IHJlYXBwbGllZCBhbGwgcmVjb3JkZWQgc2Nyb2xsIHBvc2l0aW9ucyB0byB0aGUgRE9NLicsICdjb2xvcjogIzJlY2M3MScpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oYENvdWxkIG5vdCByZWFwcGx5IGZvbGxvd2luZyByZWNvcmRlZCBzY3JvbGwgcG9zaXRpb25zIHRvIHRoZSBET00gYWZ0ZXIgYSBwb2xsIGR1cmF0aW9uIG9mOiAke3RoaXMuY29uZmlnLnBvbGxEdXJhdGlvbn0gbWlsbGlzZWNvbmRzOmApO1xuICAgICAgICAgICAgICAgICAgdGhpcy5kZWJ1Z1BhZ2VTdGF0ZShwZW5kaW5nUGFnZVN0YXRlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIHRoaXMuY29uZmlnLnBvbGxDYWRlbmNlXG4gICAgICAgICk7XG4gICAgICB9XG4gICAgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJIGdldCB0aGUgcGFnZSBzdGF0ZSBmcm9tIHRoZSBnaXZlbiBzZXQgb2Ygbm9kZXMuIFRoaXMgZXh0cmFjdHMgdGhlIENTUyBzZWxlY3RvcnMgYW5kIG9mZnNldHMgZnJvbSB0aGUgcmVjb3JkZWQgZWxlbWVudHMuXG4gICAqL1xuICBwcml2YXRlIGdldFBhZ2VTdGF0ZUZyb21Ob2Rlcyhub2RlczogU2V0PFRhcmdldD4pOiBQYWdlU3RhdGUge1xuICAgIGNvbnN0IHBhZ2VTdGF0ZTogUGFnZVN0YXRlID0ge307XG5cbiAgICBub2Rlcy5mb3JFYWNoKFxuICAgICAgdGFyZ2V0ID0+IHtcbiAgICAgICAgLy8gR2VuZXJhdGUgYSBDU1Mgc2VsZWN0b3IgZnJvbSB0aGUgZ2l2ZW4gdGFyZ2V0LlxuICAgICAgICAvLyAtLVxuICAgICAgICAvLyBUT0RPOiBSaWdodCBub3csIHRoaXMgYWxnb3JpdGhtIGNyZWF0ZXMgdGhlIHNlbGVjdG9yIGJ5IHdhbGtpbmcgdXAgdGhlXG4gICAgICAgIC8vIERPTSB0cmVlIGFuZCB1c2luZyB0aGUgc2ltdWxhdGVkIGVuY2Fwc3VsYXRpb24gYXR0cmlidXRlcy4gQnV0LCBpdFxuICAgICAgICAvLyB3b3VsZCBiZSBjb29sIHRvIGhhdmUgYSBjb25maWd1cmF0aW9uIG9wdGlvbiB0aGF0IHRlbGxzIHRoaXMgYWxnb3JpdGhtXG4gICAgICAgIC8vIHRvIGxvb2sgZm9yIGEgc3BlY2lmaWMgaWQtcHJlZml4IG9yIGF0dHJpYnV0ZSBvciBzb21ldGhpbmcuIFRoaXMgd291bGRcbiAgICAgICAgLy8gcmVxdWlyZSB0aGUgZGV2ZWxvcGVyIHRvIHByb3ZpZGUgdGhvc2U7IGJ1dCBpdCB3b3VsZCBiZSBvcHRpbWFsLlxuICAgICAgICBjb25zdCBzZWxlY3RvciA9IERvbVV0aWxzLmdldFNlbGVjdG9yKHRhcmdldCk7XG5cbiAgICAgICAgLy8gSWYgdGhlIGdpdmVuIFRhcmdldCBpcyBubyBsb25nZXIgcGFydCBvZiB0aGUgYWN0aXZlIERPTSwgdGhlIHNlbGVjdG9yXG4gICAgICAgIC8vIHdpbGwgYmUgbnVsbC5cbiAgICAgICAgaWYgKHNlbGVjdG9yKSB7XG4gICAgICAgICAgcGFnZVN0YXRlW3NlbGVjdG9yXSA9IERvbVV0aWxzLmdldFNjcm9sbFRvcCh0YXJnZXQpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgKTtcbiAgICByZXR1cm4gcGFnZVN0YXRlO1xuICB9XG5cbiAgLyoqXG4gICAqIEkgZGV0ZXJtaW5lIGlmIHRoZSBnaXZlbiBvYmplY3QgaXMgZW1wdHkgKGllLCBoYXMgbm8ga2V5cykuXG4gICAqL1xuICBwcml2YXRlIG9iamVjdElzRW1wdHkob2JqZWN0OiBPYmplY3QpOiBib29sZWFuIHtcbiAgICBmb3IgKGNvbnN0IGtleSBpbiBvYmplY3QpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICAvLyBUaGUgZ29hbCBvZiB0aGUgTmF2aWdhdGlvblN0YXJ0IGV2ZW50IGlzIHRvIHRha2UgY2hhbmdlcyB0aGF0IGhhdmUgYmVlbiBtYWRlXG4gIC8vIHRvIHRoZSBjdXJyZW50IERPTSBhbmQgc3RvcmUgdGhlbSBpbiB0aGUgcmVuZGVyLXN0YXRlIHRyZWUgc28gdGhleSBjYW4gYmVcbiAgLy8gcmVpbnN0YXRlZCBhdCBhIGZ1dHVyZSBkYXRlLlxuICBwcml2YXRlIGhhbmRsZU5hdmlnYXRpb25TdGFydChldmVudDogTmF2aWdhdGlvblN0YXJ0KTogdm9pZCB7XG4gICAgdGhpcy5sYXN0TmF2aWdhdGlvblN0YXJ0QXQgPSBEYXRlLm5vdygpO1xuXG4gICAgLy8gR2V0IHRoZSBuYXZpZ2F0aW9uIElEIGFuZCB0aGUgcmVzdG9yZWQgbmF2aWdhdGlvbiBJRCBmb3IgdXNlIGluIHRoZVxuICAgIC8vIE5hdmlnYXRpb25FbmQgZXZlbnQgaGFuZGxlci5cbiAgICB0aGlzLm5hdmlnYXRpb25JRCA9IGV2ZW50LmlkO1xuICAgIC8qKlxuICAgICAqIE1heWJlIGluIGZ1dHVyZSB1cGRhdGUgQHRvZG86IHVzZSBuZ3gtbmF2aWdhdGlvbi10cmlnZ2VyIGhlcmUsIGxpa2U6IFxuICAgICAqIChldmVudC5yZXN0b3JlZFN0YXRlICYmIHRoaXMud2hlblNob3VsZFNjcm9sbFBvc2l0aW9uQmVSZXN0b3JlZC5oYXModGhpcy5uYXZpZ2F0aW9uVHJpZ2dlcikpXG4gICAgICovXG4gICAgdGhpcy5yZXN0b3JlZE5hdmlnYXRpb25JRCA9IGV2ZW50LnJlc3RvcmVkU3RhdGUgPyBldmVudC5yZXN0b3JlZFN0YXRlLm5hdmlnYXRpb25JZCA6IG51bGw7XG5cbiAgICAvLyBJZiB0aGUgdXNlciBpcyBuYXZpZ2F0aW5nIGF3YXkgZnJvbSB0aGUgY3VycmVudCB2aWV3LCBraWxsIGFueSB0aW1lcnMgdGhhdFxuICAgIC8vIG1heSBiZSB0cnlpbmcgdG8gcmVpbnN0YXRlIGEgcGFnZS1zdGF0ZS5cbiAgICBjbGVhclRpbWVvdXQodGhpcy5hcHBseVN0YXRlVG9Eb21UaW1lcik7XG5cbiAgICAvLyBCZWZvcmUgd2UgbmF2aWdhdGUgYXdheSBmcm9tIHRoZSBjdXJyZW50IHBhZ2Ugc3RhdGUsIGxldCdzIGNvbW1pdCBhbnlcbiAgICAvLyBzY3JvbGwtZWxlbWVudHMgdG8gdGhlIGN1cnJlbnQgcGFnZSBzdGF0ZS5cbiAgICBPYmplY3QuYXNzaWduKFxuICAgICAgdGhpcy5jdXJyZW50UGFnZVN0YXRlLFxuICAgICAgdGhpcy5nZXRQYWdlU3RhdGVGcm9tTm9kZXModGhpcy5zY3JvbGxlZEVsZW1lbnRzKVxuICAgICk7XG5cbiAgICB0aGlzLnNjcm9sbGVkRWxlbWVudHMuY2xlYXIoKTtcblxuICAgIGlmICh0aGlzLmNvbmZpZy5kZWJ1Zykge1xuICAgICAgdGhpcy5kZWJ1Z1BhZ2VTdGF0ZSh0aGlzLmN1cnJlbnRQYWdlU3RhdGUsICdSZWNvcmRlZCBzY3JvbGwgcG9zaXRpb25zLicpO1xuICAgIH1cbiAgfTtcblxuICAvLyBUaGUgcHJpbWFyeSBnb2FsIG9mIHRoZSBOYXZpZ2F0aW9uRW5kIGV2ZW50IGlzIHRvIHJlaW5zdGF0ZSBhIGNhY2hlZCBwYWdlXG4gIC8vIHN0YXRlIGluIHRoZSBldmVudCB0aGF0IHRoZSBuYXZpZ2F0aW9uIGlzIHJlc3RvcmluZyBhIHByZXZpb3VzbHkgcmVuZGVyZWQgcGFnZVxuICAvLyBhcyB0aGUgcmVzdWx0IG9mIGEgcG9wc3RhdGUgZXZlbnQgKGV4LCB0aGUgdXNlciBoaXQgdGhlIEJhY2sgb3IgRm9yd2FyZFxuICAvLyBidXR0b25zKS5cbiAgcHJpdmF0ZSBoYW5kbGVOYXZpZ2F0aW9uRW5kKCk6IHZvaWQge1xuXG4gICAgY29uc3QgcHJldmlvdXNQYWdlU3RhdGUgPSB0aGlzLmN1cnJlbnRQYWdlU3RhdGU7XG5cbiAgICAvLyBOb3cgdGhhdCB3ZSBrbm93IHRoZSBuYXZpZ2F0aW9uIHdhcyBzdWNjZXNzZnVsLCBsZXQncyBzdGFydCBhbmQgc3RvcmUgYVxuICAgIC8vIG5ldyBwYWdlIHN0YXRlIHRvIHRyYWNrIGZ1dHVyZSBzY3JvbGxpbmcuXG4gICAgdGhpcy5jdXJyZW50UGFnZVN0YXRlID0gdGhpcy5wYWdlU3RhdGVzW3RoaXMubmF2aWdhdGlvbklEXSA9IHt9O1xuXG4gICAgLy8gV2hpbGUgd2UgYXJlIGdvaW5nIHRvIHRyYWNrIGVsZW1lbnRzIHRoYXQgd2lsbCBiZSBzY3JvbGxlZCBkdXJpbmcgdGhlXG4gICAgLy8gY3VycmVudCBwYWdlIHJlbmRlcmluZywgaXQgaXMgcG9zc2libGUgdGhhdCB0aGVyZSBhcmUgZWxlbWVudHMgdGhhdCB3ZXJlXG4gICAgLy8gc2Nyb2xsZWQgZHVyaW5nIGEgcHJpb3IgcGFnZSByZW5kZXJpbmcgdGhhdCBzdGlsbCBleGlzdCBvbiB0aGUgcGFnZSwgYnV0XG4gICAgLy8gd2VyZSBub3Qgc2Nyb2xsZWQgcmVjZW50bHkgKHN1Y2ggYXMgYSBzZWNvbmRhcnkgcm91dGVyLW91dGxldCkuIEFzIHN1Y2gsXG4gICAgLy8gbGV0J3MgbG9vayBhdCB0aGUgcHJldmlvdXMgcGFnZSBzdGF0ZSBhbmQgJ3B1bGwgZm9yd2FyZCcgYW55IHN0YXRlIHRoYXRcbiAgICAvLyBzdGlsbCBwZXJ0YWlucyB0byB0aGUgY3VycmVudCBwYWdlLlxuICAgIGlmICghdGhpcy5yZXN0b3JlZE5hdmlnYXRpb25JRCkge1xuICAgICAgZm9yIChjb25zdCBzZWxlY3RvciBpbiBwcmV2aW91c1BhZ2VTdGF0ZSkge1xuICAgICAgICBjb25zdCB0YXJnZXQgPSBEb21VdGlscy5zZWxlY3Qoc2VsZWN0b3IpO1xuXG4gICAgICAgIC8vIE9ubHkgcHVsbCB0aGUgc2VsZWN0b3IgZm9yd2FyZCBpZiBpdCBjb3JyZXNwb25kcyB0byBhbiBlbGVtZW50XG4gICAgICAgIC8vIHRoYXQgc3RpbGwgZXhpc3RzIGluIHRoZSByZW5kZXJlZCBwYWdlLlxuICAgICAgICBpZiAoIXRhcmdldCkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gT25seSBwdWxsIHRoZSBzZWxlY3RvciBmb3J3YXJkIGlmIHRoZSB0YXJnZXQgaXMgc3RpbGwgYXQgdGhlIHNhbWVcbiAgICAgICAgLy8gb2Zmc2V0IGFmdGVyIHRoZSBuYXZpZ2F0aW9uIGhhcyB0YWtlbiBwbGFjZS4gSW4gb3RoZXIgd29yZHMsIGlmXG4gICAgICAgIC8vIHRoZSBvZmZzZXQgaGFzIHNvbWVob3cgY2hhbmdlZCBpbiBiZXR3ZWVuIHRoZSBOYXZpZ2F0aW9uU3RhcnQgYW5kXG4gICAgICAgIC8vIE5hdmlnYXRpb25FbmQgZXZlbnRzLCB0aGVuIGlnbm9yZSBpdC4gVG8gYmUgaG9uZXN0LCB0aGlzIHJlYWxseVxuICAgICAgICAvLyBvbmx5IGFwcGxpZXMgdG8gdGhlIFdJTkRPVywgd2hpY2ggY2FuIGNoYW5nZSBpbiBvZmZzZXQgZHVlIHRvIHRoZVxuICAgICAgICAvLyBjaGFuZ2UgaW4gd2hhdCB0aGUgUm91dGVyIGlzIGFjdGl2ZWx5IHJlbmRlcmluZyBpbiB0aGUgRE9NLlxuICAgICAgICBpZiAoRG9tVXRpbHMuZ2V0U2Nyb2xsVG9wKHRhcmdldCkgIT09IHByZXZpb3VzUGFnZVN0YXRlW3NlbGVjdG9yXSkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5jdXJyZW50UGFnZVN0YXRlW3NlbGVjdG9yXSA9IHByZXZpb3VzUGFnZVN0YXRlW3NlbGVjdG9yXTtcblxuICAgICAgICBpZiAodGhpcy5jb25maWcuZGVidWcpIHtcbiAgICAgICAgICBjb25zb2xlLmdyb3VwKCdQdWxsaW5nIHNjcm9sbCBwb3NpdGlvbiBmcm9tIHByZXZpb3VzIHBhZ2Ugc3RhdGUgaW4gY3VycmVudCBwYWdlIHN0YXRlLicpO1xuICAgICAgICAgIGNvbnNvbGUubG9nKHtcbiAgICAgICAgICAgIHNlbGVjdG9yLFxuICAgICAgICAgICAgc2Nyb2xsUG9zaXRpb246IHRoaXMuY3VycmVudFBhZ2VTdGF0ZVtzZWxlY3Rvcl1cbiAgICAgICAgICB9KTtcbiAgICAgICAgICBjb25zb2xlLmdyb3VwRW5kKCk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gSWYgd2UncmUgcmVzdG9yaW5nIGEgcHJldmlvdXMgcGFnZSBzdGF0ZSBBTkQgd2UgaGF2ZSB0aGF0IHByZXZpb3VzIHBhZ2VcbiAgICAgIC8vIHN0YXRlIGNhY2hlZCBpbi1tZW1vcnksIGxldCdzIGNvcHkgdGhlIHByZXZpb3VzIHN0YXRlIGFuZCB0aGVuIHJlc3RvcmUgdGhlXG4gICAgICAvLyBvZmZzZXRzIGluIHRoZSBET00uXG4gICAgfSBlbHNlIGlmICh0aGlzLnJlc3RvcmVkTmF2aWdhdGlvbklEICYmIHRoaXMucGFnZVN0YXRlc1t0aGlzLnJlc3RvcmVkTmF2aWdhdGlvbklEXSkge1xuXG4gICAgICAvLyBOT1RFOiBXZSdyZSBjb3B5aW5nIHRoZSBvZmZzZXRzIGZyb20gdGhlIHJlc3RvcmVkIHN0YXRlIGludG8gdGhlXG4gICAgICAvLyBjdXJyZW50IHN0YXRlIGluc3RlYWQgb2YganVzdCBzd2FwcGluZyB0aGUgcmVmZXJlbmNlcyBiZWNhdXNlIHRoZXNlXG4gICAgICAvLyBuYXZpZ2F0aW9ucyBhcmUgZGlmZmVyZW50IGluIHRoZSBSb3V0ZXIgaGlzdG9yeS4gU2luY2UgZWFjaCBuYXZpZ2F0aW9uXG4gICAgICAvLyAtIGltcGVyYXRpdmUgb3IgcG9wc3RhdGUgLSBnZXRzIGEgdW5pcXVlIElELCB3ZSBuZXZlciB0cnVseSAnZ28gYmFjaydcbiAgICAgIC8vIGluIGhpc3Rvcnk7IHRoZSBSb3V0ZXIgb25seSAnZ29lcyBmb3J3YXJkJywgd2l0aCB0aGUgbm90aW9uIHRoYXQgd2UncmVcbiAgICAgIC8vIHJlY3JlYXRpbmcgYSBwcmV2aW91cyBzdGF0ZSBzb21ldGltZXMuXG4gICAgICB0aGlzLmFwcGx5UGFnZVN0YXRlVG9Eb20oXG4gICAgICAgIE9iamVjdC5hc3NpZ24oXG4gICAgICAgICAgdGhpcy5jdXJyZW50UGFnZVN0YXRlLFxuICAgICAgICAgIHRoaXMucGFnZVN0YXRlc1t0aGlzLnJlc3RvcmVkTmF2aWdhdGlvbklEXVxuICAgICAgICApXG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIEtlZXAgdHJhY2sgb2YgdGhlIG5hdmlnYXRpb24gZXZlbnQgc28gd2UgY2FuIGxpbWl0IHRoZSBzaXplIG9mIG91clxuICAgIC8vIGluLW1lbW9yeSBwYWdlIHN0YXRlIGNhY2hlLlxuICAgIHRoaXMubmF2aWdhdGlvbklEcy5wdXNoKHRoaXMubmF2aWdhdGlvbklEKTtcblxuICAgIC8vIFRyaW0gdGhlIG9sZGVzdCBwYWdlIHN0YXRlcyBhcyB3ZSBnbyBzbyB0aGF0IHRoZSBpbi1tZW1vcnkgY2FjaGUgZG9lc24ndFxuICAgIC8vIGdyb3csIHVuYm91bmRlZC5cbiAgICB3aGlsZSAodGhpcy5uYXZpZ2F0aW9uSURzLmxlbmd0aCA+IHRoaXMubWF4aW11bU51bWJlck9mQ2FjaGVkUGFnZVN0YXRlcykge1xuICAgICAgZGVsZXRlICh0aGlzLnBhZ2VTdGF0ZXNbdGhpcy5uYXZpZ2F0aW9uSURzLnNoaWZ0KCkgYXMgbnVtYmVyXSk7XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBJIGJpbmQgdG8gdGhlIHNjcm9sbCBldmVudCBhbmQga2VlcCB0cmFjayBvZiBhbnkgZWxlbWVudHMgdGhhdCBhcmUgc2Nyb2xsZWQgaW4gdGhlIHJlbmRlcmVkIGRvY3VtZW50LlxuICAgKi9cbiAgcHJpdmF0ZSBzZXR1cFNjcm9sbEJpbmRpbmcoKTogdm9pZCB7XG5cbiAgICAvKipcbiAgICAgKiBNYXliZSBAdG9kbzogWW91IHNob3VsZCB0cnkgdG8gZmluZCBhIHdheSB0byBnZXQgc2Nyb2xsYWJsZSAoc2Nyb2xsZWQpIGVsZW1lbnRzIG9ubHkgZHVyaW5nIE5hdmlnYXRpb25TdGFydC4gXG4gICAgICogQWR2YW50YWdlczpcbiAgICAgKiAtIEJldHRlciBwZXJmb3JtYW5jZTogbm8gbmVlZCB0byBsaXN0ZW4gdG8gdGhlIHNjcm9sbCBldmVudCB0aGUgd2hvbGUgdGltZS5cbiAgICAgKiAtIFNvbWUgZWxlbWVudHMgbWlnaHQgYmUgYWRkZWQgdG8gdGhlIGBzY3JvbGxlZEVsZW1lbnRzYCBhcmUgbm90IHBhcnQgb2YgdGhlIERPTSBhbnkgbW9yZS5cbiAgICAgKiBEaXNhdmFudGFnZXM6XG4gICAgICogLSBkdXJpbmcgTmF2aWdhdGlvblN0YXJ0IHNjcm9sbGFibGUgZWxlbWVudHMgdGhhdCBhcmUgbWF5YmUgcHJlc2VudCBhZnRlciB0aGUgaW50aWFsaXphdGlvbiBvZiBwYWdlIChiZWZvcmUgYW55IHVzZXItaW50ZXJhY3Rpb25zIHRoYXQgY2FuIHJlbW92ZSB0aGVtKSBtaWdodCBiZSBub3QgcGFydCBET00gYW55IG1vcmUuXG4gICAgICogXG4gICAgICovXG4gICAgLy8gQWRkIHNjcm9sbC1iaW5kaW5nIG91dHNpZGUgb2YgdGhlIEFuZ3VsYXIgWm9uZSBzbyBpdCBkb2Vzbid0IHRyaWdnZXIgYW55XG4gICAgLy8gYWRkaXRpb25hbCBjaGFuZ2UtZGV0ZWN0aW9uIGRpZ2VzdHMuXG4gICAgdGhpcy56b25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpID0+IHtcbiAgICAgIC8vIFdoZW4gbmF2aWdhdGluZywgdGhlIGJyb3dzZXIgZW1pdHMgc29tZSBzY3JvbGwgZXZlbnRzIGFzIHRoZSBET00gXG4gICAgICAvLyAoRG9jdW1lbnQgT2JqZWN0IE1vZGVsKSBjaGFuZ2VzIHNoYXBlIGluIGEgd2F5IHRoYXQgZm9yY2VzIHRoZSB2YXJpb3VzXG4gICAgICAvLyBzY3JvbGwgb2Zmc2V0cyB0byBjaGFuZ2UuIFNpbmNlIHRoZXNlIHNjcm9sbCBldmVudHMgYXJlIG5vdCBpbmRpY2F0aXZlXG4gICAgICAvLyBvZiBhIHVzZXIncyBhY3R1YWwgc2Nyb2xsaW5nIGludGVudCwgd2UncmUgZ29pbmcgdG8gaWdub3JlIHRoZW0uIFRoaXNcbiAgICAgIC8vIG5lZWRzIHRvIGJlIGRvbmUgb24gYm90aCBzaWRlcyBvZiB0aGUgbmF2aWdhdGlvbiBldmVudCAoZm9yIHJlYXNvbnNcbiAgICAgIC8vIHRoYXQgYXJlIG5vdCBmdWxseSBvYnZpb3VzIG9yIGxvZ2ljYWwgLS0gYmFzaWNhbGx5LCB0aGUgd2luZG93J3NcbiAgICAgIC8vIHNjcm9sbCBjaGFuZ2VzIGF0IGEgdGltZSB0aGF0IGlzIG5vdCBlYXN5IHRvIHRhcCBpbnRvKS4gSWdub3JpbmcgdGhlc2VcbiAgICAgIC8vIHNjcm9sbCBldmVudHMgaXMgaW1wb3J0YW50IGJlY2F1c2UgdGhlIHBvbHlmaWxseSBzdG9wcyB0cnlpbmcgdG9cbiAgICAgIC8vIHJlaW5zdGF0ZSBhIHNjcm9sbC1vZmZzZXQgaWYgaXQgc2VlcyB0aGF0IHRoZSBnaXZlbiBlbGVtZW50IGhhc1xuICAgICAgLy8gYWxyZWFkeSBiZWVuIHNjcm9sbGVkIGR1cmluZyB0aGUgY3VycmVudCByZW5kZXJpbmcuXG4gICAgICBjb25zdCBzY3JvbGxCdWZmZXJXaW5kb3cgPSAxMDA7XG4gICAgICBsZXQgdGFyZ2V0OiBUYXJnZXQgfCBudWxsO1xuXG4gICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihcbiAgICAgICAgJ3Njcm9sbCcsXG4gICAgICAgIGV2ZW50ID0+IHtcblxuICAgICAgICAgIC8vIElmIHRoZSBzY3JvbGw