ngx-link-preview
Version:
The Open Graph link preview component for Angular 6+
230 lines • 39.2 kB
JavaScript
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==