UNPKG

ngx-link-preview

Version:

The Open Graph link preview component for Angular 6+

230 lines 39.2 kB
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { NgxLinkPreviewLoadingSpinner } from './ngx-link-preview-loading-spinner'; import { NgxLinkPreviewLoadingManager } from '../../services/ngx-link-preview-loading.manager'; import { map } from 'rxjs/operators'; import * as i0 from "@angular/core"; import * as i1 from "@angular/platform-browser"; import * as i2 from "../../services/ngx-link-preview-cache.service"; import * as i3 from "@angular/common"; export class NgxLinkPreviewComponent { constructor(sanitizer, cacheSvc) { this.sanitizer = sanitizer; this.cacheSvc = cacheSvc; /** Plain links string array */ this.links = []; /** Target url will be attached as encodeURI(btoa(url)), so it must be decoded on the server */ this.queryParamName = 'url'; /** boolean: show image in preview */ this.showImage = true; /** boolean: show site name in preview */ this.showSiteName = true; /** boolean: show title in preview */ this.showTitle = true; /** boolean: show description in preview */ this.showDescription = true; /** boolean: show link url in preview */ this.showLinkUrl = false; /** boolean: use cache to display previews faster on next rendering */ this.useCache = true; /** number: max age the data cache of a link preview should be used */ this.maxCacheAgeMs = 1000 * 60 * 60 * 24 * 7; // 7 days /** boolean: show loading indicator */ this.showLoadingIndicator = true; /** * boolean: whether the <a href="..."></a> link should be clickable. * This is a question of context security. Otherwise use (previewClick) event. */ this.useHtmlLinkDefaultClickEvent = false; /** * HtmlLinkTarget: where the HTML link should be opened on click. * Only has an effect if [useHtmlLinkDefaultClickEvent]="true" */ this.htmlLinkTarget = '_blank'; /** Event emitter: on click to handle the click event, emits the clicked URL */ this.previewClicked = new EventEmitter(); /** Scanned links[] from @Input() links & @Input() parseForLinks */ this.scannedLinks = []; /** Array of metadata objects where the preview is rendered from */ this.previews = []; this.loadingMgr = new NgxLinkPreviewLoadingManager(); this.loadingSpinner = this.sanitizer.bypassSecurityTrustHtml(NgxLinkPreviewLoadingSpinner); this.showLoadingSpinner = false; this.subscriptions = []; this.subscribeLoadingMgrHasJobs(); } ngOnDestroy() { this.unsubscribeAll(); } /** * Preview will be refreshed every time a change is recognized */ ngOnChanges(changes) { this.init(); } /** * Subscribe to loading manager has jobs stream */ subscribeLoadingMgrHasJobs() { this.subscriptions.push(this.loadingMgr.hasPendingJobs$.subscribe(hasJobs => { this.showLoadingSpinner = hasJobs; })); } /** * Unsubscribe all subscriptions */ unsubscribeAll() { this.subscriptions.forEach((sub) => { sub.unsubscribe(); }); } /** * Init preview */ init() { this.scannedLinks = []; this.previews = []; this.checkInputParameters(); // Find links in string if (this.parseForLinksStr) { // Parse for links and push to links const links = this.parseStringForLinks(this.parseForLinksStr); this.scannedLinks = this.scannedLinks.concat(links); } // Add links passed as string[] if (this.links && this.links.length) { this.scannedLinks = this.scannedLinks.concat(this.links); } for (const link of this.scannedLinks) { this.loadCacheOrGet(link); } } /** * Load cache or get from api */ loadCacheOrGet(link) { const encodedLink = this.encodeUrlSafe(link); const requestUrl = this.apiRoute + '?' + this.queryParamName + '=' + encodedLink; // Try to load from cache, use encodedLink as key const cacheItem = this.cacheSvc.getCacheItem(encodedLink); if (this.useCache && cacheItem && !this.isCacheOutdated(cacheItem)) { this.previews.push(cacheItem); } else { this.loadingMgr.addTask(encodedLink); this.getApiEndpoint$(requestUrl) .pipe(map((resp) => { resp.timestampMs = new Date().valueOf(); return resp; })) .subscribe((resp) => { this.cacheSvc.updateCacheItem(encodedLink, resp); this.loadingMgr.removeTask(encodedLink); this.previews.push(resp); }); } } /** * Check if cache item is outdated by max-cache-age */ isCacheOutdated(item) { const now = new Date().valueOf(); const maxValidTimestamp = now - this.maxCacheAgeMs; return maxValidTimestamp > item.timestampMs; } /** * On link click emit to EventEmitter */ onLinkClick(url) { this.previewClicked.emit(url); } disableDefaultLink(event) { if (!this.useHtmlLinkDefaultClickEvent) { event.preventDefault(); } } /** * Encode string url safe */ encodeUrlSafe(url) { return encodeURI(btoa(url)); } /** * Check required input parameters */ checkInputParameters() { if (!this.apiRoute) { throw new Error('<ngx-link-preview></ngx-link-preview> Missing [apiRoute] input parameter'); } if (!this.getApiEndpoint$) { throw new Error('<ngx-link-preview></ngx-link-preview> Missing [getApiEndpoint$] input parameter'); } } /** * Parse string for links */ parseStringForLinks(msg) { const links = msg.match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&\/=]*)/gim); if (links) { return links; } else { return []; } } /** * Get sanitized image url due data often contains url errors */ getSanitizedImageUrl(p) { if (p['og:image'].startsWith('http')) { return p['og:image']; } else if (p['og:image'].startsWith('www')) { return p['og:image']; } else if (p['og:image'].startsWith('//www')) { return 'https://' + p['og:image'].slice(2); } else if (p['og:image'].startsWith('/yts/')) { return 'https://' + p.source + p['og:image']; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.6", ngImport: i0, type: NgxLinkPreviewComponent, deps: [{ token: i1.DomSanitizer }, { token: i2.NgxLinkPreviewCacheService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.6", type: NgxLinkPreviewComponent, selector: "ngx-link-preview", inputs: { links: "links", parseForLinksStr: "parseForLinksStr", getApiEndpoint$: "getApiEndpoint$", apiRoute: "apiRoute", queryParamName: "queryParamName", showImage: "showImage", showSiteName: "showSiteName", showTitle: "showTitle", showDescription: "showDescription", showLinkUrl: "showLinkUrl", useCache: "useCache", maxCacheAgeMs: "maxCacheAgeMs", showLoadingIndicator: "showLoadingIndicator", useHtmlLinkDefaultClickEvent: "useHtmlLinkDefaultClickEvent", htmlLinkTarget: "htmlLinkTarget" }, outputs: { previewClicked: "previewClicked" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-link-preview-container\">\n\n <ng-container *ngFor=\"let p of previews\">\n\n <div class=\"og-link-preview\" *ngIf=\"p.url\" (click)=\"onLinkClick(p.url)\">\n <a class=\"row\" [href]=\"p.url\" (click)=\"disableDefaultLink($event)\" [target]=\"htmlLinkTarget\">\n\n <div class=\"col preview-image\" [ngStyle]=\"{backgroundImage: 'url('+getSanitizedImageUrl(p)+')'}\">\n <div class=\"image\" *ngIf=\"showImage && (p['og:image'] || p.image)\">\n <img [src]=\"getSanitizedImageUrl(p)\" alt=\"\">\n </div>\n </div>\n\n <div class=\"col text-data\">\n <div class=\"header\" *ngIf=\"showSiteName\">\n <div class=\"site-name\" *ngIf=\"p['og:site_name']\">\n {{ p['og:site_name'] }}\n </div>\n </div>\n\n <div class=\"title\" *ngIf=\"showTitle && (p['og:title'] || p.title)\">\n {{ p['og:title'] || p.title }}\n </div>\n\n <div class=\"description\" *ngIf=\"showDescription && (p['og:description'] || p.description)\">\n {{ p['og:description'] || p.description }}\n </div>\n\n <div class=\"footer\">\n <div class=\"url\" *ngIf=\"showLinkUrl && (p['og:url'] || p.url)\">\n {{ p['og:url'] || p.url }}\n </div>\n </div>\n </div>\n\n </a><!-- a.row -->\n </div><!-- og-link-preview -->\n\n </ng-container><!-- *ngFor -->\n</div>\n\n<div class=\"loading-spinner\" *ngIf=\"showLoadingIndicator && showLoadingSpinner\">\n <!-- Custom loading spinner -->\n <ng-container>\n <div class=\"wrapper-custom\" #ngContentLoading>\n <ng-content></ng-content>\n </div>\n </ng-container>\n\n <!-- Default loading spinner -->\n <ng-container *ngIf=\"ngContentLoading.childNodes.length === 0\">\n <div class=\"wrapper-default\" [innerHTML]=\"loadingSpinner\"></div>\n </ng-container>\n</div>\n", styles: [".ngx-link-preview-container .og-link-preview{border-radius:4px;background-color:#c8c8c840}.ngx-link-preview-container .og-link-preview:hover{background-color:#c8c8c866}.ngx-link-preview-container .og-link-preview .row{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;text-decoration:none;color:unset;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ngx-link-preview-container .og-link-preview .row .col{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;background-position:-9999px;background-repeat:no-repeat}.ngx-link-preview-container .og-link-preview .row .col.preview-image{padding:5px 0 5px 5px;margin-right:6px}.ngx-link-preview-container .og-link-preview .row .col.text-data{padding:6px 6px 4px 0}.ngx-link-preview-container .og-link-preview .row .col .image{border-radius:3px}.ngx-link-preview-container .og-link-preview .row .col .image img{max-height:70px;height:13vw;border-radius:3px;vertical-align:middle}.ngx-link-preview-container .og-link-preview .row .col .title{font-weight:500;font-size:14px}.ngx-link-preview-container .og-link-preview .row .col .description{margin-top:4px;max-height:40px;font-size:12px;filter:contrast(.8);overflow:hidden;text-overflow:ellipsis}.ngx-link-preview-container .og-link-preview .row .col .header{display:flex;flex-direction:row;align-items:center;margin-bottom:4px;font-size:12px;filter:contrast(.7)}.ngx-link-preview-container .og-link-preview .row .col .footer{display:flex;flex-direction:row;align-items:center;font-size:12px;filter:contrast(.7);margin-top:4px}.loading-spinner .wrapper-custom{text-align:center}.loading-spinner .wrapper-default{height:40px;display:flex;justify-content:center;align-items:center}:host.modern .og-link-preview .row{align-items:stretch}:host.modern .og-link-preview .col{justify-content:center!important}:host.modern .og-link-preview .image{width:150px}:host.modern .og-link-preview .image img{opacity:0}:host.modern .og-link-preview .col.preview-image{background-size:cover;background-position:center center!important;border-bottom-left-radius:4px;border-top-left-radius:4px}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.6", ngImport: i0, type: NgxLinkPreviewComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-link-preview', template: "<div class=\"ngx-link-preview-container\">\n\n <ng-container *ngFor=\"let p of previews\">\n\n <div class=\"og-link-preview\" *ngIf=\"p.url\" (click)=\"onLinkClick(p.url)\">\n <a class=\"row\" [href]=\"p.url\" (click)=\"disableDefaultLink($event)\" [target]=\"htmlLinkTarget\">\n\n <div class=\"col preview-image\" [ngStyle]=\"{backgroundImage: 'url('+getSanitizedImageUrl(p)+')'}\">\n <div class=\"image\" *ngIf=\"showImage && (p['og:image'] || p.image)\">\n <img [src]=\"getSanitizedImageUrl(p)\" alt=\"\">\n </div>\n </div>\n\n <div class=\"col text-data\">\n <div class=\"header\" *ngIf=\"showSiteName\">\n <div class=\"site-name\" *ngIf=\"p['og:site_name']\">\n {{ p['og:site_name'] }}\n </div>\n </div>\n\n <div class=\"title\" *ngIf=\"showTitle && (p['og:title'] || p.title)\">\n {{ p['og:title'] || p.title }}\n </div>\n\n <div class=\"description\" *ngIf=\"showDescription && (p['og:description'] || p.description)\">\n {{ p['og:description'] || p.description }}\n </div>\n\n <div class=\"footer\">\n <div class=\"url\" *ngIf=\"showLinkUrl && (p['og:url'] || p.url)\">\n {{ p['og:url'] || p.url }}\n </div>\n </div>\n </div>\n\n </a><!-- a.row -->\n </div><!-- og-link-preview -->\n\n </ng-container><!-- *ngFor -->\n</div>\n\n<div class=\"loading-spinner\" *ngIf=\"showLoadingIndicator && showLoadingSpinner\">\n <!-- Custom loading spinner -->\n <ng-container>\n <div class=\"wrapper-custom\" #ngContentLoading>\n <ng-content></ng-content>\n </div>\n </ng-container>\n\n <!-- Default loading spinner -->\n <ng-container *ngIf=\"ngContentLoading.childNodes.length === 0\">\n <div class=\"wrapper-default\" [innerHTML]=\"loadingSpinner\"></div>\n </ng-container>\n</div>\n", styles: [".ngx-link-preview-container .og-link-preview{border-radius:4px;background-color:#c8c8c840}.ngx-link-preview-container .og-link-preview:hover{background-color:#c8c8c866}.ngx-link-preview-container .og-link-preview .row{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;text-decoration:none;color:unset;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ngx-link-preview-container .og-link-preview .row .col{display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;background-position:-9999px;background-repeat:no-repeat}.ngx-link-preview-container .og-link-preview .row .col.preview-image{padding:5px 0 5px 5px;margin-right:6px}.ngx-link-preview-container .og-link-preview .row .col.text-data{padding:6px 6px 4px 0}.ngx-link-preview-container .og-link-preview .row .col .image{border-radius:3px}.ngx-link-preview-container .og-link-preview .row .col .image img{max-height:70px;height:13vw;border-radius:3px;vertical-align:middle}.ngx-link-preview-container .og-link-preview .row .col .title{font-weight:500;font-size:14px}.ngx-link-preview-container .og-link-preview .row .col .description{margin-top:4px;max-height:40px;font-size:12px;filter:contrast(.8);overflow:hidden;text-overflow:ellipsis}.ngx-link-preview-container .og-link-preview .row .col .header{display:flex;flex-direction:row;align-items:center;margin-bottom:4px;font-size:12px;filter:contrast(.7)}.ngx-link-preview-container .og-link-preview .row .col .footer{display:flex;flex-direction:row;align-items:center;font-size:12px;filter:contrast(.7);margin-top:4px}.loading-spinner .wrapper-custom{text-align:center}.loading-spinner .wrapper-default{height:40px;display:flex;justify-content:center;align-items:center}:host.modern .og-link-preview .row{align-items:stretch}:host.modern .og-link-preview .col{justify-content:center!important}:host.modern .og-link-preview .image{width:150px}:host.modern .og-link-preview .image img{opacity:0}:host.modern .og-link-preview .col.preview-image{background-size:cover;background-position:center center!important;border-bottom-left-radius:4px;border-top-left-radius:4px}\n"] }] }], ctorParameters: function () { return [{ type: i1.DomSanitizer }, { type: i2.NgxLinkPreviewCacheService }]; }, propDecorators: { links: [{ type: Input }], parseForLinksStr: [{ type: Input }], getApiEndpoint$: [{ type: Input }], apiRoute: [{ type: Input }], queryParamName: [{ type: Input }], showImage: [{ type: Input }], showSiteName: [{ type: Input }], showTitle: [{ type: Input }], showDescription: [{ type: Input }], showLinkUrl: [{ type: Input }], useCache: [{ type: Input }], maxCacheAgeMs: [{ type: Input }], showLoadingIndicator: [{ type: Input }], useHtmlLinkDefaultClickEvent: [{ type: Input }], htmlLinkTarget: [{ type: Input }], previewClicked: [{ type: Output }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWxpbmstcHJldmlldy5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtbGluay1wcmV2aWV3L3NyYy9saWIvY29tcG9uZW50cy9uZ3gtbGluay1wcmV2aWV3L25neC1saW5rLXByZXZpZXcuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LWxpbmstcHJldmlldy9zcmMvbGliL2NvbXBvbmVudHMvbmd4LWxpbmstcHJldmlldy9uZ3gtbGluay1wcmV2aWV3LmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBd0IsTUFBTSxFQUFpQixNQUFNLGVBQWUsQ0FBQztBQUk1RyxPQUFPLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVsRixPQUFPLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSxpREFBaUQsQ0FBQztBQUMvRixPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7Ozs7O0FBVXJDLE1BQU0sT0FBTyx1QkFBdUI7SUFxRmpDLFlBQW9CLFNBQXVCLEVBQVUsUUFBb0M7UUFBckUsY0FBUyxHQUFULFNBQVMsQ0FBYztRQUFVLGFBQVEsR0FBUixRQUFRLENBQTRCO1FBcEZ6RiwrQkFBK0I7UUFFeEIsVUFBSyxHQUFhLEVBQUUsQ0FBQztRQWdCNUIsK0ZBQStGO1FBRXhGLG1CQUFjLEdBQUcsS0FBSyxDQUFDO1FBRTlCLHFDQUFxQztRQUU5QixjQUFTLEdBQUcsSUFBSSxDQUFDO1FBRXhCLHlDQUF5QztRQUVsQyxpQkFBWSxHQUFHLElBQUksQ0FBQztRQUUzQixxQ0FBcUM7UUFFOUIsY0FBUyxHQUFHLElBQUksQ0FBQztRQUV4QiwyQ0FBMkM7UUFFcEMsb0JBQWUsR0FBRyxJQUFJLENBQUM7UUFFOUIsd0NBQXdDO1FBRWpD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LE9BQU8sQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1NBQ3ZCO2FBQU0sSUFBSSxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQzNDLE9BQU8sVUFBVSxHQUFHLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDN0M7YUFBTSxJQUFJLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEVBQUU7WUFDM0MsT0FBTyxVQUFVLEdBQUcsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUM7U0FDL0M7SUFDSixDQUFDOzhHQWhQUyx1QkFBdUI7a0dBQXZCLHVCQUF1Qiw0bUJDakJwQywyaUVBc0RBOzsyRkRyQ2EsdUJBQXVCO2tCQUxuQyxTQUFTOytCQUNHLGtCQUFrQjs0SUFPckIsS0FBSztzQkFEWCxLQUFLO2dCQUtDLGdCQUFnQjtzQkFEdEIsS0FBSztnQkFLQyxlQUFlO3NCQURyQixLQUFLO2dCQU9DLFFBQVE7c0JBRGQsS0FBSztnQkFLQyxjQUFjO3NCQURwQixLQUFLO2dCQUtDLFNBQVM7c0JBRGYsS0FBSztnQkFLQyxZQUFZO3NCQURsQixLQUFLO2dCQUtDLFNBQVM7c0JBRGYsS0FBSztnQkFLQyxlQUFlO3NCQURyQixLQUFLO2dCQUtDLFdBQVc7c0JBRGpCLEtBQUs7Z0JBS0MsUUFBUTtzQkFEZCxLQUFLO2dCQUtDLGFBQWE7c0JBRG5CLEtBQUs7Z0JBS0Msb0JBQW9CO3NCQUQxQixLQUFLO2dCQVFDLDRCQUE0QjtzQkFEbEMsS0FBSztnQkFRQyxjQUFjO3NCQURwQixLQUFLO2dCQUtDLGNBQWM7c0JBRHBCLE1BQU0iLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIEV2ZW50RW1pdHRlciwgSW5wdXQsIE9uQ2hhbmdlcywgT25EZXN0cm95LCBPdXRwdXQsIFNpbXBsZUNoYW5nZXMgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IE9wZW5HcmFwaE1ldGFEYXRhIH0gZnJvbSAnLi4vLi4vaW50ZXJmYWNlcy9vcGVuLWdyYXBoLW1ldGEtZGF0YSc7XG5pbXBvcnQgeyBOZ3hMaW5rUHJldmlld0NhY2hlU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL25neC1saW5rLXByZXZpZXctY2FjaGUuc2VydmljZSc7XG5pbXBvcnQgeyBPYnNlcnZhYmxlLCBTdWJqZWN0LCBTdWJzY3JpcHRpb24gfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IE5neExpbmtQcmV2aWV3TG9hZGluZ1NwaW5uZXIgfSBmcm9tICcuL25neC1saW5rLXByZXZpZXctbG9hZGluZy1zcGlubmVyJztcbmltcG9ydCB7IERvbVNhbml0aXplciB9IGZyb20gJ0Bhbmd1bGFyL3BsYXRmb3JtLWJyb3dzZXInO1xuaW1wb3J0IHsgTmd4TGlua1ByZXZpZXdMb2FkaW5nTWFuYWdlciB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL25neC1saW5rLXByZXZpZXctbG9hZGluZy5tYW5hZ2VyJztcbmltcG9ydCB7IG1hcCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcblxudHlwZSBIdG1sTGlua1RhcmdldCA9ICdfYmxhbmsnIHwgJ19zZWxmJyB8ICdfcGFyZW50JyB8ICdfdG9wJyB8IEZyYW1lTmFtZTtcbnR5cGUgRnJhbWVOYW1lID0gc3RyaW5nO1xuXG5AQ29tcG9uZW50KHtcbiAgIHNlbGVjdG9yOiAnbmd4LWxpbmstcHJldmlldycsXG4gICB0ZW1wbGF0ZVVybDogJy4vbmd4LWxpbmstcHJldmlldy5jb21wb25lbnQuaHRtbCcsXG4gICBzdHlsZVVybHM6IFsnLi9uZ3gtbGluay1wcmV2aWV3LmNvbXBvbmVudC5zY3NzJ11cbn0pXG5leHBvcnQgY2xhc3MgTmd4TGlua1ByZXZpZXdDb21wb25lbnQgaW1wbGVtZW50cyBPbkNoYW5nZXMsIE9uRGVzdHJveSB7XG4gICAvKiogUGxhaW4gbGlua3Mgc3RyaW5nIGFycmF5ICovXG4gICBASW5wdXQoKVxuICAgcHVibGljIGxpbmtzOiBzdHJpbmdbXSA9IFtdO1xuXG4gICAvKiogSW5wdXQgc3RyaW5nIHRvIHBhcnNlIGZvciBsaW5rcyAqL1xuICAgQElucHV0KClcbiAgIHB1YmxpYyBwYXJzZUZvckxpbmtzU3RyOiBzdHJpbmc7XG5cbiAgIC8qKiBNZXRob2QgdGhhdCBkb2VzIHRoZSBBUEkgcmVxdWVzdCwgcHJvdmlkZSBhcyBjbGFzcyBtZW1iZXIgYXJyb3cgZnVuY3Rpb24gZnJvbSBwYXJlbnQgKi9cbiAgIEBJbnB1dCgpXG4gICBwdWJsaWMgZ2V0QXBpRW5kcG9pbnQkOiAocmVxdWVzdFVybDogc3RyaW5nKSA9PiBPYnNlcnZhYmxlPGFueT47XG5cbiAgIC8qKiBBUEkgcm91dGUgd2hlcmUgdG8gZ2V0IHRoZSBtZXRhIGRhdGEgZnJvbSwgY29tcG9uZW50IHdpbGwgYnVpbGQgdGhlIGZ1bGwgcmVxdWVzdCB1cmxcbiAgICAqIFNjaGVtYTogYXBpLmV4YW1wbGUuY29tL2FwaS9nZXQtbWV0YS1kYXRhP3VybD1kM2QzTG1WNFlXMXdiR1V1WTI5dFxuICAgICovXG4gICBASW5wdXQoKVxuICAgcHVibGljIGFwaVJvdXRlOiBzdHJpbmc7XG5cbiAgIC8qKiBUYXJnZXQgdXJsIHdpbGwgYmUgYXR0YWNoZWQgYXMgZW5jb2RlVVJJKGJ0b2EodXJsKSksIHNvIGl0IG11c3QgYmUgZGVjb2RlZCBvbiB0aGUgc2VydmVyICovXG4gICBASW5wdXQoKVxuICAgcHVibGljIHF1ZXJ5UGFyYW1OYW1lID0gJ3VybCc7XG5cbiAgIC8qKiBib29sZWFuOiBzaG93IGltYWdlIGluIHByZXZpZXcgKi9cbiAgIEBJbnB1dCgpXG4gICBwdWJsaWMgc2hvd0ltYWdlID0gdHJ1ZTtcblxuICAgLyoqIGJvb2xlYW46IHNob3cgc2l0ZSBuYW1lIGluIHByZXZpZXcgKi9cbiAgIEBJbnB1dCgpXG4gICBwdWJsaWMgc2hvd1NpdGVOYW1lID0gdHJ1ZTtcblxuICAgLyoqIGJvb2xlYW46IHNob3cgdGl0bGUgaW4gcHJldmlldyAqL1xuICAgQElucHV0KClcbiAgIHB1YmxpYyBzaG93VGl0bGUgPSB0cnVlO1xuXG4gICAvKiogYm9vbGVhbjogc2hvdyBkZXNjcmlwdGlvbiBpbiBwcmV2aWV3ICovXG4gICBASW5wdXQoKVxuICAgcHVibGljIHNob3dEZXNjcmlwdGlvbiA9IHRydWU7XG5cbiAgIC8qKiBib29sZWFuOiBzaG93IGxpbmsgdXJsIGluIHByZXZpZXcgKi9cbiAgIEBJbnB1dCgpXG4gICBwdWJsaWMgc2hvd0xpbmtVcmwgPSBmYWxzZTtcblxuICAgLyoqIGJvb2xlYW46IHVzZSBjYWNoZSB0byBkaXNwbGF5IHByZXZpZXdzIGZhc3RlciBvbiBuZXh0IHJlbmRlcmluZyAqL1xuICAgQElucHV0KClcbiAgIHB1YmxpYyB1c2VDYWNoZSA9IHRydWU7XG5cbiAgIC8qKiBudW1iZXI6IG1heCBhZ2UgdGhlIGRhdGEgY2FjaGUgb2YgYSBsaW5rIHByZXZpZXcgc2hvdWxkIGJlIHVzZWQgKi9cbiAgIEBJbnB1dCgpXG4gICBwdWJsaWMgbWF4Q2FjaGVBZ2VNcyA9IDEwMDAgKiA2MCAqIDYwICogMjQgKiA3OyAvLyA3IGRheXNcblxuICAgLyoqIGJvb2xlYW46IHNob3cgbG9hZGluZyBpbmRpY2F0b3IgKi9cbiAgIEBJbnB1dCgpXG4gICBwdWJsaWMgc2hvd0xvYWRpbmdJbmRpY2F0b3IgPSB0cnVlO1xuXG4gICAvKipcbiAgICAqIGJvb2xlYW46IHdoZXRoZXIgdGhlIDxhIGhyZWY9XCIuLi5cIj48L2E+IGxpbmsgc2hvdWxkIGJlIGNsaWNrYWJsZS5cbiAgICAqIFRoaXMgaXMgYSBxdWVzdGlvbiBvZiBjb250ZXh0IHNlY3VyaXR5LiBPdGhlcndpc2UgdXNlIChwcmV2aWV3Q2xpY2spIGV2ZW50LlxuICAgICovXG4gICBASW5wdXQoKVxuICAgcHVibGljIHVzZUh0bWxMaW5rRGVmYXVsdENsaWNrRXZlbnQgPSBmYWxzZTtcblxuICAgLyoqXG4gICAgKiBIdG1sTGlua1RhcmdldDogd2hlcmUgdGhlIEhUTUwgbGluayBzaG91bGQgYmUgb3BlbmVkIG9uIGNsaWNrLlxuICAgICogT25seSBoYXMgYW4gZWZmZWN0IGlmIFt1c2VIdG1sTGlua0RlZmF1bHRDbGlja0V2ZW50XT1cInRydWVcIlxuICAgICovXG4gICBASW5wdXQoKVxuICAgcHVibGljIGh0bWxMaW5rVGFyZ2V0OiBIdG1sTGlua1RhcmdldCA9ICdfYmxhbmsnO1xuXG4gICAvKiogRXZlbnQgZW1pdHRlcjogb24gY2xpY2sgdG8gaGFuZGxlIHRoZSBjbGljayBldmVudCwgZW1pdHMgdGhlIGNsaWNrZWQgVVJMICovXG4gICBAT3V0cHV0KClcbiAgIHB1YmxpYyBwcmV2aWV3Q2xpY2tlZCA9IG5ldyBFdmVudEVtaXR0ZXI8c3RyaW5nPigpO1xuXG4gICAvKiogU2Nhbm5lZCBsaW5rc1tdIGZyb20gQElucHV0KCkgbGlua3MgJiBASW5wdXQoKSBwYXJzZUZvckxpbmtzICovXG4gICBwcml2YXRlIHNjYW5uZWRMaW5rczogc3RyaW5nW10gPSBbXTtcblxuICAgLyoqIEFycmF5IG9mIG1ldGFkYXRhIG9iamVjdHMgd2hlcmUgdGhlIHByZXZpZXcgaXMgcmVuZGVyZWQgZnJvbSAqL1xuICAgcHVibGljIHByZXZpZXdzOiBPcGVuR3JhcGhNZXRhRGF0YVtdID0gW107XG5cbiAgIHByaXZhdGUgbG9hZGluZ01nciA9IG5ldyBOZ3hMaW5rUHJldmlld0xvYWRpbmdNYW5hZ2VyKCk7XG4gICBwdWJsaWMgbG9hZGluZ1NwaW5uZXIgPSB0aGlzLnNhbml0aXplci5ieXBhc3NTZWN1cml0eVRydXN0SHRtbChOZ3hMaW5rUHJldmlld0xvYWRpbmdTcGlubmVyKTtcbiAgIHB1YmxpYyBzaG93TG9hZGluZ1NwaW5uZXIgPSBmYWxzZTtcblxuICAgcHJpdmF0ZSBzdWJzY3JpcHRpb25zOiBTdWJzY3JpcHRpb25bXSA9IFtdO1xuXG4gICBjb25zdHJ1Y3Rvcihwcml2YXRlIHNhbml0aXplcjogRG9tU2FuaXRpemVyLCBwcml2YXRlIGNhY2hlU3ZjOiBOZ3hMaW5rUHJldmlld0NhY2hlU2VydmljZSkge1xuICAgICAgdGhpcy5zdWJzY3JpYmVMb2FkaW5nTWdySGFzSm9icygpO1xuICAgfVxuXG4gICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICAgIHRoaXMudW5zdWJzY3JpYmVBbGwoKTtcbiAgIH1cblxuICAgLyoqXG4gICAgKiBQcmV2aWV3IHdpbGwgYmUgcmVmcmVzaGVkIGV2ZXJ5IHRpbWUgYSBjaGFuZ2UgaXMgcmVjb2duaXplZFxuICAgICovXG4gICBuZ09uQ2hhbmdlcyhjaGFuZ2VzOiBTaW1wbGVDaGFuZ2VzKTogdm9pZCB7XG4gICAgICB0aGlzLmluaXQoKTtcbiAgIH1cblxuICAgLyoqXG4gICAgKiBTdWJzY3JpYmUgdG8gbG9hZGluZyBtYW5hZ2VyIGhhcyBqb2JzIHN0cmVhbVxuICAgICovXG4gICBwcml2YXRlIHN1YnNjcmliZUxvYWRpbmdNZ3JIYXNKb2JzKCk6IHZvaWQge1xuICAgICAgdGhpcy5zdWJzY3JpcHRpb25zLnB1c2goXG4gICAgICAgICB0aGlzLmxvYWRpbmdNZ3IuaGFzUGVuZGluZ0pvYnMkLnN1YnNjcmliZShoYXNKb2JzID0+IHtcbiAgICAgICAgICAgIHRoaXMuc2hvd0xvYWRpbmdTcGlubmVyID0gaGFzSm9icztcbiAgICAgICAgIH0pXG4gICAgICApO1xuICAgfVxuXG4gICAvKipcbiAgICAqIFVuc3Vic2NyaWJlIGFsbCBzdWJzY3JpcHRpb25zXG4gICAgKi9cbiAgIHByaXZhdGUgdW5zdWJzY3JpYmVBbGwoKTogdm9pZCB7XG4gICAgICB0aGlzLnN1YnNjcmlwdGlvbnMuZm9yRWFjaCgoc3ViOiBTdWJzY3JpcHRpb24pID0+IHtcbiAgICAgICAgIHN1Yi51bnN1YnNjcmliZSgpO1xuICAgICAgfSk7XG4gICB9XG5cbiAgIC8qKlxuICAgICogSW5pdCBwcmV2aWV3XG4gICAgKi9cbiAgIHByaXZhdGUgaW5pdCgpOiB2b2lkIHtcbiAgICAgIHRoaXMuc2Nhbm5lZExpbmtzID0gW107XG4gICAgICB0aGlzLnByZXZpZXdzID0gW107XG4gICAgICB0aGlzLmNoZWNrSW5wdXRQYXJhbWV0ZXJzKCk7XG5cbiAgICAgIC8vIEZpbmQgbGlua3MgaW4gc3RyaW5nXG4gICAgICBpZiAodGhpcy5wYXJzZUZvckxpbmtzU3RyKSB7XG4gICAgICAgICAvLyBQYXJzZSBmb3IgbGlua3MgYW5kIHB1c2ggdG8gbGlua3NcbiAgICAgICAgIGNvbnN0IGxpbmtzID0gdGhpcy5wYXJzZVN0cmluZ0ZvckxpbmtzKHRoaXMucGFyc2VGb3JMaW5rc1N0cik7XG4gICAgICAgICB0aGlzLnNjYW5uZWRMaW5rcyA9IHRoaXMuc2Nhbm5lZExpbmtzLmNvbmNhdChsaW5rcyk7XG4gICAgICB9XG4gICAgICAvLyBBZGQgbGlua3MgcGFzc2VkIGFzIHN0cmluZ1tdXG4gICAgICBpZiAodGhpcy5saW5rcyAmJiB0aGlzLmxpbmtzLmxlbmd0aCkge1xuICAgICAgICAgdGhpcy5zY2FubmVkTGlua3MgPSB0aGlzLnNjYW5uZWRMaW5rcy5jb25jYXQodGhpcy5saW5rcyk7XG4gICAgICB9XG5cbiAgICAgIGZvciAoY29uc3QgbGluayBvZiB0aGlzLnNjYW5uZWRMaW5rcykge1xuICAgICAgICAgdGhpcy5sb2FkQ2FjaGVPckdldChsaW5rKTtcbiAgICAgIH1cbiAgIH1cblxuICAgLyoqXG4gICAgKiBMb2FkIGNhY2hlIG9yIGdldCBmcm9tIGFwaVxuICAgICovXG4gICBwcml2YXRlIGxvYWRDYWNoZU9yR2V0KGxpbms6IHN0cmluZyk6IHZvaWQge1xuICAgICAgY29uc3QgZW5jb2RlZExpbmsgPSB0aGlzLmVuY29kZVVybFNhZmUobGluayk7XG4gICAgICBjb25zdCByZXF1ZXN0VXJsID0gdGhpcy5hcGlSb3V0ZSArICc/JyArIHRoaXMucXVlcnlQYXJhbU5hbWUgKyAnPScgKyBlbmNvZGVkTGluaztcblxuICAgICAgLy8gVHJ5IHRvIGxvYWQgZnJvbSBjYWNoZSwgdXNlIGVuY29kZWRMaW5rIGFzIGtleVxuICAgICAgY29uc3QgY2FjaGVJdGVtID0gdGhpcy5jYWNoZVN2Yy5nZXRDYWNoZUl0ZW0oZW5jb2RlZExpbmspO1xuICAgICAgaWYgKHRoaXMudXNlQ2FjaGUgJiYgY2FjaGVJdGVtICYmICF0aGlzLmlzQ2FjaGVPdXRkYXRlZChjYWNoZUl0ZW0pKSB7XG4gICAgICAgICB0aGlzLnByZXZpZXdzLnB1c2goY2FjaGVJdGVtKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgICB0aGlzLmxvYWRpbmdNZ3IuYWRkVGFzayhlbmNvZGVkTGluayk7XG4gICAgICAgICB0aGlzLmdldEFwaUVuZHBvaW50JChyZXF1ZXN0VXJsKVxuICAgICAgICAgICAgLnBpcGUoXG4gICAgICAgICAgICAgICBtYXAoKHJlc3A6IE9wZW5HcmFwaE1ldGFEYXRhKSA9PiB7XG4gICAgICAgICAgICAgICAgICByZXNwLnRpbWVzdGFtcE1zID0gbmV3IERhdGUoKS52YWx1ZU9mKCk7XG4gICAgICAgICAgICAgICAgICByZXR1cm4gcmVzcDtcbiAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICApXG4gICAgICAgICAgICAuc3Vic2NyaWJlKChyZXNwOiBPcGVuR3JhcGhNZXRhRGF0YSkgPT4ge1xuICAgICAgICAgICAgICAgdGhpcy5jYWNoZVN2Yy51cGRhdGVDYWNoZUl0ZW0oZW5jb2RlZExpbmssIHJlc3ApO1xuICAgICAgICAgICAgICAgdGhpcy5sb2FkaW5nTWdyLnJlbW92ZVRhc2soZW5jb2RlZExpbmspO1xuICAgICAgICAgICAgICAgdGhpcy5wcmV2aWV3cy5wdXNoKHJlc3ApO1xuICAgICAgICAgICAgfSk7XG4gICAgICB9XG4gICB9XG5cbiAgIC8qKlxuICAgICogQ2hlY2sgaWYgY2FjaGUgaXRlbSBpcyBvdXRkYXRlZCBieSBtYXgtY2FjaGUtYWdlXG4gICAgKi9cbiAgIHByaXZhdGUgaXNDYWNoZU91dGRhdGVkKGl0ZW06IE9wZW5HcmFwaE1ldGFEYXRhKTogYm9vbGVhbiB7XG4gICAgICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpLnZhbHVlT2YoKTtcbiAgICAgIGNvbnN0IG1heFZhbGlkVGltZXN0YW1wID0gbm93IC0gdGhpcy5tYXhDYWNoZUFnZU1zO1xuICAgICAgcmV0dXJuIG1heFZhbGlkVGltZXN0YW1wID4gaXRlbS50aW1lc3RhbXBNcztcbiAgIH1cblxuICAgLyoqXG4gICAgKiBPbiBsaW5rIGNsaWNrIGVtaXQgdG8gRXZlbnRFbWl0dGVyXG4gICAgKi9cbiAgIHB1YmxpYyBvbkxpbmtDbGljayh1cmw6IHN0cmluZyk6IHZvaWQge1xuICAgICAgdGhpcy5wcmV2aWV3Q2xpY2tlZC5lbWl0KHVybCk7XG4gICB9XG4gICBwdWJsaWMgZGlzYWJsZURlZmF1bHRMaW5rKGV2ZW50OiBNb3VzZUV2ZW50KTogdm9pZCB7XG4gICAgICBpZiAoIXRoaXMudXNlSHRtbExpbmtEZWZhdWx0Q2xpY2tFdmVudCkge1xuICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgIH1cbiAgIH1cblxuICAgLyoqXG4gICAgKiBFbmNvZGUgc3RyaW5nIHVybCBzYWZlXG4gICAgKi9cbiAgIHByaXZhdGUgZW5jb2RlVXJsU2FmZSh1cmw6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgICByZXR1cm4gZW5jb2RlVVJJKGJ0b2EodXJsKSk7XG4gICB9XG5cbiAgIC8qKlxuICAgICogQ2hlY2sgcmVxdWlyZWQgaW5wdXQgcGFyYW1ldGVyc1xuICAgICovXG4gICBwcml2YXRlIGNoZWNrSW5wdXRQYXJhbWV0ZXJzKCk6IHZvaWQge1xuICAgICAgaWYgKCF0aGlzLmFwaVJvdXRlKSB7XG4gICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJzxuZ3gtbGluay1wcmV2aWV3Pjwvbmd4LWxpbmstcHJldmlldz4gTWlzc2luZyBbYXBpUm91dGVdIGlucHV0IHBhcmFtZXRlcicpO1xuICAgICAgfVxuICAgICAgaWYgKCF0aGlzLmdldEFwaUVuZHBvaW50JCkge1xuICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCc8bmd4LWxpbmstcHJldmlldz48L25neC1saW5rLXByZXZpZXc+IE1pc3NpbmcgW2dldEFwaUVuZHBvaW50JF0gaW5wdXQgcGFyYW1ldGVyJyk7XG4gICAgICB9XG4gICB9XG5cbiAgIC8qKlxuICAgICogUGFyc2Ugc3RyaW5nIGZvciBsaW5rc1xuICAgICovXG4gICBwcml2YXRlIHBhcnNlU3RyaW5nRm9yTGlua3MobXNnOiBzdHJpbmcpOiBzdHJpbmdbXSB7XG4gICAgICBjb25zdCBsaW5rcyA9IG1zZy5tYXRjaChcbiAgICAgICAgIC9odHRwcz86XFwvXFwvKHd3d1xcLik/Wy1hLXpBLVowLTlAOiUuXyt+Iz1dezEsMjU2fVxcLlthLXpBLVowLTkoKV17MSw2fVxcYihbLWEtekEtWjAtOSgpQDolXysufiM/JlxcLz1dKikvZ2ltXG4gICAgICApO1xuXG4gICAgICBpZiAobGlua3MpIHtcbiAgICAgICAgIHJldHVybiBsaW5rcztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgICByZXR1cm4gW107XG4gICAgICB9XG4gICB9XG5cbiAgIC8qKlxuICAgICogR2V0IHNhbml0aXplZCBpbWFnZSB1cmwgZHVlIGRhdGEgb2Z0ZW4gY29udGFpbnMgdXJsIGVycm9yc1xuICAgICovXG4gICBwdWJsaWMgZ2V0U2FuaXRpemVkSW1hZ2VVcmwocDogT3BlbkdyYXBoTWV0YURhdGEpOiBzdHJpbmcge1xuICAgICAgaWYgKHBbJ29nOmltYWdlJ10uc3RhcnRzV2l0aCgnaHR0cCcpKSB7XG4gICAgICAgICByZXR1cm4gcFsnb2c6aW1hZ2UnXTtcbiAgICAgIH0gZWxzZSBpZiAocFsnb2c6aW1hZ2UnXS5zdGFydHNXaXRoKCd3d3cnKSkge1xuICAgICAgICAgcmV0dXJuIHBbJ29nOmltYWdlJ107XG4gICAgICB9IGVsc2UgaWYgKHBbJ29nOmltYWdlJ10uc3RhcnRzV2l0aCgnLy93d3cnKSkge1xuICAgICAgICAgcmV0dXJuICdodHRwczovLycgKyBwWydvZzppbWFnZSddLnNsaWNlKDIpO1xuICAgICAgfSBlbHNlIGlmIChwWydvZzppbWFnZSddLnN0YXJ0c1dpdGgoJy95dHMvJykpIHtcbiAgICAgICAgIHJldHVybiAnaHR0cHM6Ly8nICsgcC5zb3VyY2UgKyBwWydvZzppbWFnZSddO1xuICAgICAgfVxuICAgfVxufVxuIiwiPGRpdiBjbGFzcz1cIm5neC1saW5rLXByZXZpZXctY29udGFpbmVyXCI+XG5cbiAgIDxuZy1jb250YWluZXIgKm5nRm9yPVwibGV0IHAgb2YgcHJldmlld3NcIj5cblxuICAgICAgPGRpdiBjbGFzcz1cIm9nLWxpbmstcHJldmlld1wiICpuZ0lmPVwicC51cmxcIiAoY2xpY2spPVwib25MaW5rQ2xpY2socC51cmwpXCI+XG4gICAgICAgICA8YSBjbGFzcz1cInJvd1wiIFtocmVmXT1cInAudXJsXCIgKGNsaWNrKT1cImRpc2FibGVEZWZhdWx0TGluaygkZXZlbnQpXCIgW3RhcmdldF09XCJodG1sTGlua1RhcmdldFwiPlxuXG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwiY29sIHByZXZpZXctaW1hZ2VcIiBbbmdTdHlsZV09XCJ7YmFja2dyb3VuZEltYWdlOiAndXJsKCcrZ2V0U2FuaXRpemVkSW1hZ2VVcmwocCkrJyknfVwiPlxuICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImltYWdlXCIgKm5nSWY9XCJzaG93SW1hZ2UgJiYgKHBbJ29nOmltYWdlJ10gfHwgcC5pbWFnZSlcIj5cbiAgICAgICAgICAgICAgICAgIDxpbWcgW3NyY109XCJnZXRTYW5pdGl6ZWRJbWFnZVVybChwKVwiIGFsdD1cIlwiPlxuICAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICA8L2Rpdj5cblxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImNvbCB0ZXh0LWRhdGFcIj5cbiAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJoZWFkZXJcIiAqbmdJZj1cInNob3dTaXRlTmFtZVwiPlxuICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cInNpdGUtbmFtZVwiICpuZ0lmPVwicFsnb2c6c2l0ZV9uYW1lJ11cIj5cbiAgICAgICAgICAgICAgICAgICAgIHt7IHBbJ29nOnNpdGVfbmFtZSddIH19XG4gICAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgIDwvZGl2PlxuXG4gICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwidGl0bGVcIiAqbmdJZj1cInNob3dUaXRsZSAmJiAocFsnb2c6dGl0bGUnXSB8fCBwLnRpdGxlKVwiPlxuICAgICAgICAgICAgICAgICAge3sgcFsnb2c6dGl0bGUnXSB8fCBwLnRpdGxlIH19XG4gICAgICAgICAgICAgICA8L2Rpdj5cblxuICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImRlc2NyaXB0aW9uXCIgKm5nSWY9XCJzaG93RGVzY3JpcHRpb24gJiYgKHBbJ29nOmRlc2NyaXB0aW9uJ10gfHwgcC5kZXNjcmlwdGlvbilcIj5cbiAgICAgICAgICAgICAgICAgIHt7IHBbJ29nOmRlc2NyaXB0aW9uJ10gfHwgcC5kZXNjcmlwdGlvbiB9fVxuICAgICAgICAgICAgICAgPC9kaXY+XG5cbiAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJmb290ZXJcIj5cbiAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJ1cmxcIiAqbmdJZj1cInNob3dMaW5rVXJsICYmIChwWydvZzp1cmwnXSB8fCBwLnVybClcIj5cbiAgICAgICAgICAgICAgICAgICAgIHt7IHBbJ29nOnVybCddIHx8IHAudXJsIH19XG4gICAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgPC9kaXY+XG5cbiAgICAgICAgIDwvYT48IS0tIGEucm93IC0tPlxuICAgICAgPC9kaXY+PCEtLSBvZy1saW5rLXByZXZpZXcgLS0+XG5cbiAgIDwvbmctY29udGFpbmVyPjwhLS0gKm5nRm9yIC0tPlxuPC9kaXY+XG5cbjxkaXYgY2xhc3M9XCJsb2FkaW5nLXNwaW5uZXJcIiAqbmdJZj1cInNob3dMb2FkaW5nSW5kaWNhdG9yICYmIHNob3dMb2FkaW5nU3Bpbm5lclwiPlxuICAgPCEtLSBDdXN0b20gbG9hZGluZyBzcGlubmVyIC0tPlxuICAgPG5nLWNvbnRhaW5lcj5cbiAgICAgIDxkaXYgY2xhc3M9XCJ3cmFwcGVyLWN1c3RvbVwiICNuZ0NvbnRlbnRMb2FkaW5nPlxuICAgICAgICAgPG5nLWNvbnRlbnQ+PC9uZy1jb250ZW50PlxuICAgICAgPC9kaXY+XG4gICA8L25nLWNvbnRhaW5lcj5cblxuICAgPCEtLSBEZWZhdWx0IGxvYWRpbmcgc3Bpbm5lciAtLT5cbiAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJuZ0NvbnRlbnRMb2FkaW5nLmNoaWxkTm9kZXMubGVuZ3RoID09PSAwXCI+XG4gICAgICA8ZGl2IGNsYXNzPVwid3JhcHBlci1kZWZhdWx0XCIgW2lubmVySFRNTF09XCJsb2FkaW5nU3Bpbm5lclwiPjwvZGl2PlxuICAgPC9uZy1jb250YWluZXI+XG48L2Rpdj5cbiJdfQ==