@ux-aspects/ux-aspects
Version:
Open source user interface framework for building modern, responsive, mobile big data applications
180 lines • 47.2 kB
JavaScript
import { Component, ElementRef, inject, Input, ViewChild } from '@angular/core';
import { fromEvent, Subject } from 'rxjs';
import { debounceTime, takeUntil, tap } from 'rxjs/operators';
import { AudioService } from '../../services/audio/index';
import { MediaPlayerService } from './media-player.service';
import * as i0 from "@angular/core";
import * as i1 from "@angular/cdk/a11y";
import * as i2 from "../../directives/accessibility/focus-indicator/focus-indicator.directive";
import * as i3 from "../../directives/accessibility/focus-within/focus-within.directive";
import * as i4 from "./extensions/timeline/timeline.component";
import * as i5 from "./extensions/controls/controls.component";
import * as i6 from "@angular/common";
import * as i7 from "../../pipes/file-size/file-size.pipe";
export class MediaPlayerComponent {
get source() {
return this.mediaPlayerService.source;
}
/** The url to the media file to be loaded by the media player. */
set source(value) {
this.mediaPlayerService.source = value;
}
get type() {
return this.mediaPlayerService.type;
}
/**
* Defines the appearance of the media player. The two possible values are `video` and `audio`.
* The media player will adapt it's appearance to best suit the type specified.
*/
set type(value) {
this.mediaPlayerService.type = value;
}
get quietMode() {
return this.mediaPlayerService.quietMode;
}
/**
* If enabled, the controls in the media player will be hidden unless the mouse is over the player and will appear in a darker style.
* Dark mode is automatically enabled in full screen mode. Quiet mode is only available for videos.
*/
set quietMode(value) {
this.mediaPlayerService.quietMode = value;
}
/**
* If specified the function will be called passing the current volume as an argument.
* It should return an appropriate aria-label for the mute/unmute button.
*/
set muteAriaLabel(fn) {
this.mediaPlayerService.muteAriaLabel = fn;
}
/**
* If specified the function will be called passing the current playing state as an argument.
* It should return an appropriate aria-label for the play/pause button.
*/
set playAriaLabel(fn) {
this.mediaPlayerService.playAriaLabel = fn;
}
/**
* If specified the function will be called passing the current fullscreen state as an argument.
* It should return an appropriate aria-label for the fullscreen toggle button.
*/
set fullscreenAriaLabel(fn) {
this.mediaPlayerService.fullscreenAriaLabel = fn;
}
/**
* If specified the function will be called passing the current track as an argument.
* It should return an appropriate aria-label for the subtitle selection button.
*/
set selectSubtitlesAriaLabel(fn) {
this.mediaPlayerService.selectSubtitlesAriaLabel = fn;
}
/** Defines an aria-label for the go to start button. */
set goToStartAriaLabel(ariaLabel) {
this.mediaPlayerService.goToStartAriaLabel = ariaLabel;
}
/** Defines an aria-label for the go to end button. */
set goToEndAriaLabel(ariaLabel) {
this.mediaPlayerService.goToEndAriaLabel = ariaLabel;
}
/** Defines an aria-label for the title displayed in the subtitle selection popover. */
set subtitlesTitleAriaLabel(ariaLabel) {
this.mediaPlayerService.subtitlesTitleAriaLabel = ariaLabel;
}
/** Defines an aria-label to indicate subtitle are not currently enabled. */
set subtitlesOffAriaLabel(ariaLabel) {
this.mediaPlayerService.subtitlesOffAriaLabel = ariaLabel;
}
/** Define an aria-label to indicate there are no subtitles available. */
set noSubtitlesAriaLabel(ariaLabel) {
this.mediaPlayerService.noSubtitlesAriaLabel = ariaLabel;
}
/** Define an aria-label for the media player. */
set mediaPlayerAriaLabel(ariaLabel) {
this.mediaPlayerService.mediaPlayerAriaLabel = ariaLabel;
}
/** Define an aria-label for the the seek element. */
set seekAriaLabel(ariaLabel) {
this.mediaPlayerService.seekAriaLabel = ariaLabel;
}
constructor() {
this.mediaPlayerService = inject(MediaPlayerService);
this._audioService = inject(AudioService);
this._elementRef = inject(ElementRef);
this.hovering = false;
this.focused = false;
/** The `anonymous` keyword means that there will be no exchange of user credentials when the media source is fetched. */
this.crossorigin = 'use-credentials';
this._onDestroy = new Subject();
// show controls when hovering and in quiet mode
fromEvent(this._elementRef.nativeElement, 'mousemove').pipe(tap(() => this.hovering = true), debounceTime(2000), takeUntil(this._onDestroy)).subscribe(() => this.hovering = false);
}
ngAfterViewInit() {
this.mediaPlayerService.setMediaPlayer(this._elementRef.nativeElement, this._playerRef.nativeElement);
this.audioMetadata = this._audioService.getAudioFileMetadata(this._playerRef.nativeElement);
this.mediaPlayerService.playingEvent.pipe(takeUntil(this._onDestroy)).subscribe(() => this.mediaPlayerService.playing.next(true));
this.mediaPlayerService.pauseEvent.pipe(takeUntil(this._onDestroy)).subscribe(() => this.mediaPlayerService.playing.next(false));
this.mediaPlayerService.mediaClickEvent.pipe(takeUntil(this._onDestroy)).subscribe(() => this.mediaPlayerService.togglePlay());
this.mediaPlayerService.loadedMetadataEvent.pipe(takeUntil(this._onDestroy)).subscribe(() => this.mediaPlayerService.loaded = true);
// initially hide all text tracks
this.mediaPlayerService.hideSubtitleTracks();
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: MediaPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.12", type: MediaPlayerComponent, selector: "ux-media-player", inputs: { crossorigin: "crossorigin", filename: "filename", source: "source", type: "type", quietMode: "quietMode", muteAriaLabel: "muteAriaLabel", playAriaLabel: "playAriaLabel", fullscreenAriaLabel: "fullscreenAriaLabel", selectSubtitlesAriaLabel: "selectSubtitlesAriaLabel", goToStartAriaLabel: "goToStartAriaLabel", goToEndAriaLabel: "goToEndAriaLabel", subtitlesTitleAriaLabel: "subtitlesTitleAriaLabel", subtitlesOffAriaLabel: "subtitlesOffAriaLabel", noSubtitlesAriaLabel: "noSubtitlesAriaLabel", mediaPlayerAriaLabel: "mediaPlayerAriaLabel", seekAriaLabel: "seekAriaLabel" }, host: { listeners: { "keydown.Space": "mediaPlayerService.togglePlay(); $event.preventDefault()", "mouseenter": "hovering = true", "mouseleave": "hovering = false", "document:fullscreenchange": "mediaPlayerService.fullscreenChange()", "document:webkitfullscreenchange": "mediaPlayerService.fullscreenChange()", "document:mozfullscreenchange": "mediaPlayerService.fullscreenChange()", "document:MSFullscreenChange": "mediaPlayerService.fullscreenChange()" }, properties: { "class.standard": "!mediaPlayerService.fullscreen", "class.fullscreen": "mediaPlayerService.fullscreen", "class.quiet": "quietMode && type === \"video\" || mediaPlayerService.fullscreen", "class.hover": "hovering || focused", "class.video": "type === \"video\"", "class.audio": "type === \"audio\"" } }, providers: [MediaPlayerService], viewQueries: [{ propertyName: "_playerRef", first: true, predicate: ["player"], descendants: true }], ngImport: i0, template: "<div class=\"player-container\"\n uxFocusIndicator\n tabindex=\"0\"\n [attr.aria-label]=\"mediaPlayerService.mediaPlayerAriaLabel\"\n [cdkTrapFocus]=\"mediaPlayerService.fullscreen\">\n\n @if (type === 'video') {\n <div class=\"video-player-container\">\n <video class=\"video-player\"\n #player\n tabindex=\"-1\"\n [src]=\"source\"\n [crossOrigin]=\"crossorigin\"\n (abort)=\"mediaPlayerService.abortEvent.next()\"\n (canplay)=\"mediaPlayerService.canPlayEvent.next(true)\"\n (canplaythrough)=\"mediaPlayerService.canPlayThroughEvent.next(true)\"\n (durationchange)=\"mediaPlayerService.durationChangeEvent.next(player.duration)\"\n (ended)=\"mediaPlayerService.endedEvent.next()\"\n (error)=\"mediaPlayerService.errorEvent.next($event)\"\n (loadeddata)=\"mediaPlayerService.loadedDataEvent.next($event)\"\n (loadedmetadata)=\"mediaPlayerService.loadedMetadataEvent.next($event)\"\n (loadstart)=\"mediaPlayerService.loadStartEvent.next()\"\n (pause)=\"mediaPlayerService.pauseEvent.next()\"\n (play)=\"mediaPlayerService.playEvent.next()\"\n (playing)=\"mediaPlayerService.playingEvent.next(!player.paused)\"\n (ratechange)=\"mediaPlayerService.rateChangeEvent.next(player.playbackRate)\"\n (seeked)=\"mediaPlayerService.seekedEvent.next(player.currentTime)\"\n (seeking)=\"mediaPlayerService.seekingEvent.next(player.currentTime)\"\n (stalled)=\"mediaPlayerService.stalledEvent.next()\"\n (suspend)=\"mediaPlayerService.suspendEvent.next()\"\n (timeupdate)=\"mediaPlayerService.timeUpdateEvent.next(player.currentTime)\"\n (volumechange)=\"mediaPlayerService.volumeChangeEvent.next(player.volume)\"\n (waiting)=\"mediaPlayerService.waitingEvent.next()\"\n (click)=\"mediaPlayerService.mediaClickEvent.next($event)\">\n <ng-content select=\"track\"></ng-content>\n </video>\n <div class=\"video-overlay\" [class.playing]=\"mediaPlayerService.playing | async\">\n <svg class=\"play-graphic\" x=\"0px\" y=\"0px\" viewBox=\"0 0 64 64\">\n <circle class=\"play-circle\" cx=\"32.2\" cy=\"31.8\" r=\"31.8\" />\n <polygon class=\"play-triangle\" points=\"23,14.1 23,50.8 48.3,32.5\" />\n </svg>\n </div>\n </div>\n }\n\n @if (type === 'audio') {\n <div class=\"audio-player\">\n <svg width=\"24px\" height=\"24px\" viewBox=\"0 0 24 24\">\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <g transform=\"translate(-98.000000, -458.000000)\">\n <g transform=\"translate(98.000000, 458.000000)\">\n <path d=\"M4.5,0.5 L18.0435308,0.5 L23.5,6.22251502 L23.5,23.5 L4.5,23.5 L4.5,0.5 Z\" fill=\"#CCEAE2\"></path>\n <path d=\"M4.5,8 L4.5,0.5 L18,0.5 L23.5,6 L23.5,23.5 L18,23.5\" stroke=\"#60798D\" fill=\"#CCEAE2\"></path>\n <path d=\"M4,13.5 L0.5,13.5 L0.5,18.5 L4,18.5 L9.5,22.5 L9.5,9.5 L4,13.5 Z\" stroke=\"#60798D\" fill=\"#85D2BE\"></path>\n <path d=\"M11.5,12.5137939 C13.7576225,12.5137939 14.5,14.3709236 14.5,16 C14.5,17.6849236 13.7089152,19.5420532 11.5,19.5420532\"\n stroke=\"#60798D\"></path>\n <path d=\"M11.5,9 C15.8037643,9.04168701 18.5,11.6604805 18.5,16 C18.5,20.3395195 15.8804302,23.0079956 11.5,23\" stroke=\"#60798D\"></path>\n <path d=\"M17.5219116,0.761413574 L17.5219116,6 L23,6\" stroke=\"#60798D\" fill=\"#85D2BE\"></path>\n </g>\n </g>\n </g>\n </svg>\n <p class=\"audio-file-name\">{{ this.filename ? this.filename : (audioMetadata | async)?.filename }}</p>\n <p class=\"audio-file-format\">{{ (audioMetadata | async)?.description }}</p>\n <p class=\"audio-file-size\">{{ (audioMetadata | async)?.size | fileSize }}</p>\n <audio #player\n [src]=\"source\"\n (abort)=\"mediaPlayerService.abortEvent.next()\"\n (canplay)=\"mediaPlayerService.canPlayEvent.next(true)\"\n (canplaythrough)=\"mediaPlayerService.canPlayThroughEvent.next(true)\"\n (durationchange)=\"mediaPlayerService.durationChangeEvent.next(player.duration)\"\n (ended)=\"mediaPlayerService.endedEvent.next()\"\n (error)=\"mediaPlayerService.errorEvent.next($event)\"\n (loadeddata)=\"mediaPlayerService.loadedDataEvent.next($event)\"\n (loadedmetadata)=\"mediaPlayerService.loadedMetadataEvent.next($event)\"\n (loadstart)=\"mediaPlayerService.loadStartEvent.next()\"\n (pause)=\"mediaPlayerService.pauseEvent.next()\"\n (play)=\"mediaPlayerService.playEvent.next()\"\n (playing)=\"mediaPlayerService.playingEvent.next(!player.paused)\"\n (ratechange)=\"mediaPlayerService.rateChangeEvent.next(player.playbackRate)\"\n (seeked)=\"mediaPlayerService.seekedEvent.next(player.currentTime)\"\n (seeking)=\"mediaPlayerService.seekingEvent.next(player.currentTime)\"\n (stalled)=\"mediaPlayerService.stalledEvent.next()\"\n (suspend)=\"mediaPlayerService.suspendEvent.next()\"\n (timeupdate)=\"mediaPlayerService.timeUpdateEvent.next(player.currentTime)\"\n (volumechange)=\"mediaPlayerService.volumeChangeEvent.next(player.volume)\"\n (waiting)=\"mediaPlayerService.waitingEvent.next()\"\n (click)=\"mediaPlayerService.mediaClickEvent.next($event)\">\n </audio>\n </div>\n }\n\n <div class=\"control-bar\"\n (uxFocusWithin)=\"focused = true\"\n (uxBlurWithin)=\"focused = false\">\n\n <ux-media-player-timeline></ux-media-player-timeline>\n <ux-media-player-controls>\n <ng-content select=\"[uxMediaPlayerCustomControl]\"></ng-content>\n </ux-media-player-controls>\n </div>\n</div>", dependencies: [{ kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i2.FocusIndicatorDirective, selector: "[uxFocusIndicator]", inputs: ["checkChildren", "mouseFocusIndicator", "touchFocusIndicator", "keyboardFocusIndicator", "programmaticFocusIndicator"], outputs: ["indicator"], exportAs: ["ux-focus-indicator"] }, { kind: "directive", type: i3.FocusWithinDirective, selector: "[uxFocusWithin],[uxBlurWithin]", outputs: ["uxFocusWithin", "uxBlurWithin"] }, { kind: "component", type: i4.MediaPlayerTimelineExtensionComponent, selector: "ux-media-player-timeline" }, { kind: "component", type: i5.MediaPlayerControlsExtensionComponent, selector: "ux-media-player-controls" }, { kind: "pipe", type: i6.AsyncPipe, name: "async" }, { kind: "pipe", type: i7.FileSizePipe, name: "fileSize" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: MediaPlayerComponent, decorators: [{
type: Component,
args: [{ selector: 'ux-media-player', providers: [MediaPlayerService], host: {
'(keydown.Space)': 'mediaPlayerService.togglePlay(); $event.preventDefault()',
'[class.standard]': '!mediaPlayerService.fullscreen',
'[class.fullscreen]': 'mediaPlayerService.fullscreen',
'[class.quiet]': 'quietMode && type === "video" || mediaPlayerService.fullscreen',
'[class.hover]': 'hovering || focused',
'[class.video]': 'type === "video"',
'[class.audio]': 'type === "audio"',
'(mouseenter)': 'hovering = true',
'(mouseleave)': 'hovering = false',
'(document:fullscreenchange)': 'mediaPlayerService.fullscreenChange()',
'(document:webkitfullscreenchange)': 'mediaPlayerService.fullscreenChange()',
'(document:mozfullscreenchange)': 'mediaPlayerService.fullscreenChange()',
'(document:MSFullscreenChange)': 'mediaPlayerService.fullscreenChange()'
}, template: "<div class=\"player-container\"\n uxFocusIndicator\n tabindex=\"0\"\n [attr.aria-label]=\"mediaPlayerService.mediaPlayerAriaLabel\"\n [cdkTrapFocus]=\"mediaPlayerService.fullscreen\">\n\n @if (type === 'video') {\n <div class=\"video-player-container\">\n <video class=\"video-player\"\n #player\n tabindex=\"-1\"\n [src]=\"source\"\n [crossOrigin]=\"crossorigin\"\n (abort)=\"mediaPlayerService.abortEvent.next()\"\n (canplay)=\"mediaPlayerService.canPlayEvent.next(true)\"\n (canplaythrough)=\"mediaPlayerService.canPlayThroughEvent.next(true)\"\n (durationchange)=\"mediaPlayerService.durationChangeEvent.next(player.duration)\"\n (ended)=\"mediaPlayerService.endedEvent.next()\"\n (error)=\"mediaPlayerService.errorEvent.next($event)\"\n (loadeddata)=\"mediaPlayerService.loadedDataEvent.next($event)\"\n (loadedmetadata)=\"mediaPlayerService.loadedMetadataEvent.next($event)\"\n (loadstart)=\"mediaPlayerService.loadStartEvent.next()\"\n (pause)=\"mediaPlayerService.pauseEvent.next()\"\n (play)=\"mediaPlayerService.playEvent.next()\"\n (playing)=\"mediaPlayerService.playingEvent.next(!player.paused)\"\n (ratechange)=\"mediaPlayerService.rateChangeEvent.next(player.playbackRate)\"\n (seeked)=\"mediaPlayerService.seekedEvent.next(player.currentTime)\"\n (seeking)=\"mediaPlayerService.seekingEvent.next(player.currentTime)\"\n (stalled)=\"mediaPlayerService.stalledEvent.next()\"\n (suspend)=\"mediaPlayerService.suspendEvent.next()\"\n (timeupdate)=\"mediaPlayerService.timeUpdateEvent.next(player.currentTime)\"\n (volumechange)=\"mediaPlayerService.volumeChangeEvent.next(player.volume)\"\n (waiting)=\"mediaPlayerService.waitingEvent.next()\"\n (click)=\"mediaPlayerService.mediaClickEvent.next($event)\">\n <ng-content select=\"track\"></ng-content>\n </video>\n <div class=\"video-overlay\" [class.playing]=\"mediaPlayerService.playing | async\">\n <svg class=\"play-graphic\" x=\"0px\" y=\"0px\" viewBox=\"0 0 64 64\">\n <circle class=\"play-circle\" cx=\"32.2\" cy=\"31.8\" r=\"31.8\" />\n <polygon class=\"play-triangle\" points=\"23,14.1 23,50.8 48.3,32.5\" />\n </svg>\n </div>\n </div>\n }\n\n @if (type === 'audio') {\n <div class=\"audio-player\">\n <svg width=\"24px\" height=\"24px\" viewBox=\"0 0 24 24\">\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <g transform=\"translate(-98.000000, -458.000000)\">\n <g transform=\"translate(98.000000, 458.000000)\">\n <path d=\"M4.5,0.5 L18.0435308,0.5 L23.5,6.22251502 L23.5,23.5 L4.5,23.5 L4.5,0.5 Z\" fill=\"#CCEAE2\"></path>\n <path d=\"M4.5,8 L4.5,0.5 L18,0.5 L23.5,6 L23.5,23.5 L18,23.5\" stroke=\"#60798D\" fill=\"#CCEAE2\"></path>\n <path d=\"M4,13.5 L0.5,13.5 L0.5,18.5 L4,18.5 L9.5,22.5 L9.5,9.5 L4,13.5 Z\" stroke=\"#60798D\" fill=\"#85D2BE\"></path>\n <path d=\"M11.5,12.5137939 C13.7576225,12.5137939 14.5,14.3709236 14.5,16 C14.5,17.6849236 13.7089152,19.5420532 11.5,19.5420532\"\n stroke=\"#60798D\"></path>\n <path d=\"M11.5,9 C15.8037643,9.04168701 18.5,11.6604805 18.5,16 C18.5,20.3395195 15.8804302,23.0079956 11.5,23\" stroke=\"#60798D\"></path>\n <path d=\"M17.5219116,0.761413574 L17.5219116,6 L23,6\" stroke=\"#60798D\" fill=\"#85D2BE\"></path>\n </g>\n </g>\n </g>\n </svg>\n <p class=\"audio-file-name\">{{ this.filename ? this.filename : (audioMetadata | async)?.filename }}</p>\n <p class=\"audio-file-format\">{{ (audioMetadata | async)?.description }}</p>\n <p class=\"audio-file-size\">{{ (audioMetadata | async)?.size | fileSize }}</p>\n <audio #player\n [src]=\"source\"\n (abort)=\"mediaPlayerService.abortEvent.next()\"\n (canplay)=\"mediaPlayerService.canPlayEvent.next(true)\"\n (canplaythrough)=\"mediaPlayerService.canPlayThroughEvent.next(true)\"\n (durationchange)=\"mediaPlayerService.durationChangeEvent.next(player.duration)\"\n (ended)=\"mediaPlayerService.endedEvent.next()\"\n (error)=\"mediaPlayerService.errorEvent.next($event)\"\n (loadeddata)=\"mediaPlayerService.loadedDataEvent.next($event)\"\n (loadedmetadata)=\"mediaPlayerService.loadedMetadataEvent.next($event)\"\n (loadstart)=\"mediaPlayerService.loadStartEvent.next()\"\n (pause)=\"mediaPlayerService.pauseEvent.next()\"\n (play)=\"mediaPlayerService.playEvent.next()\"\n (playing)=\"mediaPlayerService.playingEvent.next(!player.paused)\"\n (ratechange)=\"mediaPlayerService.rateChangeEvent.next(player.playbackRate)\"\n (seeked)=\"mediaPlayerService.seekedEvent.next(player.currentTime)\"\n (seeking)=\"mediaPlayerService.seekingEvent.next(player.currentTime)\"\n (stalled)=\"mediaPlayerService.stalledEvent.next()\"\n (suspend)=\"mediaPlayerService.suspendEvent.next()\"\n (timeupdate)=\"mediaPlayerService.timeUpdateEvent.next(player.currentTime)\"\n (volumechange)=\"mediaPlayerService.volumeChangeEvent.next(player.volume)\"\n (waiting)=\"mediaPlayerService.waitingEvent.next()\"\n (click)=\"mediaPlayerService.mediaClickEvent.next($event)\">\n </audio>\n </div>\n }\n\n <div class=\"control-bar\"\n (uxFocusWithin)=\"focused = true\"\n (uxBlurWithin)=\"focused = false\">\n\n <ux-media-player-timeline></ux-media-player-timeline>\n <ux-media-player-controls>\n <ng-content select=\"[uxMediaPlayerCustomControl]\"></ng-content>\n </ux-media-player-controls>\n </div>\n</div>" }]
}], ctorParameters: () => [], propDecorators: { _playerRef: [{
type: ViewChild,
args: ['player', { static: false }]
}], crossorigin: [{
type: Input
}], filename: [{
type: Input
}], source: [{
type: Input
}], type: [{
type: Input
}], quietMode: [{
type: Input
}], muteAriaLabel: [{
type: Input
}], playAriaLabel: [{
type: Input
}], fullscreenAriaLabel: [{
type: Input
}], selectSubtitlesAriaLabel: [{
type: Input
}], goToStartAriaLabel: [{
type: Input
}], goToEndAriaLabel: [{
type: Input
}], subtitlesTitleAriaLabel: [{
type: Input
}], subtitlesOffAriaLabel: [{
type: Input
}], noSubtitlesAriaLabel: [{
type: Input
}], mediaPlayerAriaLabel: [{
type: Input
}], seekAriaLabel: [{
type: Input
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVkaWEtcGxheWVyLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jb21wb25lbnRzL21lZGlhLXBsYXllci9tZWRpYS1wbGF5ZXIuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvbWVkaWEtcGxheWVyL21lZGlhLXBsYXllci5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQWlCLFNBQVMsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBYSxTQUFTLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDMUcsT0FBTyxFQUFFLFNBQVMsRUFBYyxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDdEQsT0FBTyxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDOUQsT0FBTyxFQUFpQixZQUFZLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUN6RSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQzs7Ozs7Ozs7O0FBc0I1RCxNQUFNLE9BQU8sb0JBQW9CO0lBb0I3QixJQUFJLE1BQU07UUFDTixPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUM7SUFDMUMsQ0FBQztJQUVELGtFQUFrRTtJQUNsRSxJQUNJLE1BQU0sQ0FBQyxLQUFhO1FBQ3BCLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDO0lBQzNDLENBQUM7SUFFRCxJQUFJLElBQUk7UUFDSixPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLENBQUM7SUFDeEMsQ0FBQztJQUVEOzs7T0FHRztJQUNILElBQ0ksSUFBSSxDQUFDLEtBQXNCO1FBQzNCLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLEdBQUcsS0FBSyxDQUFDO0lBQ3pDLENBQUM7SUFFRCxJQUFJ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QU9GLGtCQUFrQjtzQkFEckIsS0FBSztnQkFPRixnQkFBZ0I7c0JBRG5CLEtBQUs7Z0JBT0YsdUJBQXVCO3NCQUQxQixLQUFLO2dCQU9GLHFCQUFxQjtzQkFEeEIsS0FBSztnQkFPRixvQkFBb0I7c0JBRHZCLEtBQUs7Z0JBT0Ysb0JBQW9CO3NCQUR2QixLQUFLO2dCQU9GLGFBQWE7c0JBRGhCLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBZnRlclZpZXdJbml0LCBDb21wb25lbnQsIEVsZW1lbnRSZWYsIGluamVjdCwgSW5wdXQsIE9uRGVzdHJveSwgVmlld0NoaWxkIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBmcm9tRXZlbnQsIE9ic2VydmFibGUsIFN1YmplY3QgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IGRlYm91bmNlVGltZSwgdGFrZVVudGlsLCB0YXAgfSBmcm9tICdyeGpzL29wZXJhdG9ycyc7XG5pbXBvcnQgeyBBdWRpb01ldGFkYXRhLCBBdWRpb1NlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9hdWRpby9pbmRleCc7XG5pbXBvcnQgeyBNZWRpYVBsYXllclNlcnZpY2UgfSBmcm9tICcuL21lZGlhLXBsYXllci5zZXJ2aWNlJztcblxuQENvbXBvbmVudCh7XG4gICAgc2VsZWN0b3I6ICd1eC1tZWRpYS1wbGF5ZXInLFxuICAgIHRlbXBsYXRlVXJsOiAnLi9tZWRpYS1wbGF5ZXIuY29tcG9uZW50Lmh0bWwnLFxuICAgIHByb3ZpZGVyczogW01lZGlhUGxheWVyU2VydmljZV0sXG4gICAgaG9zdDoge1xuICAgICAgICAnKGtleWRvd24uU3BhY2UpJzogJ21lZGlhUGxheWVyU2VydmljZS50b2dnbGVQbGF5KCk7ICRldmVudC5wcmV2ZW50RGVmYXVsdCgpJyxcbiAgICAgICAgJ1tjbGFzcy5zdGFuZGFyZF0nOiAnIW1lZGlhUGxheWVyU2VydmljZS5mdWxsc2NyZWVuJyxcbiAgICAgICAgJ1tjbGFzcy5mdWxsc2NyZWVuXSc6ICdtZWRpYVBsYXllclNlcnZpY2UuZnVsbHNjcmVlbicsXG4gICAgICAgICdbY2xhc3MucXVpZXRdJzogJ3F1aWV0TW9kZSAmJiB0eXBlID09PSBcInZpZGVvXCIgfHwgbWVkaWFQbGF5ZXJTZXJ2aWNlLmZ1bGxzY3JlZW4nLFxuICAgICAgICAnW2NsYXNzLmhvdmVyXSc6ICdob3ZlcmluZyB8fCBmb2N1c2VkJyxcbiAgICAgICAgJ1tjbGFzcy52aWRlb10nOiAndHlwZSA9PT0gXCJ2aWRlb1wiJyxcbiAgICAgICAgJ1tjbGFzcy5hdWRpb10nOiAndHlwZSA9PT0gXCJhdWRpb1wiJyxcbiAgICAgICAgJyhtb3VzZWVudGVyKSc6ICdob3ZlcmluZyA9IHRydWUnLFxuICAgICAgICAnKG1vdXNlbGVhdmUpJzogJ2hvdmVyaW5nID0gZmFsc2UnLFxuICAgICAgICAnKGRvY3VtZW50OmZ1bGxzY3JlZW5jaGFuZ2UpJzogJ21lZGlhUGxheWVyU2VydmljZS5mdWxsc2NyZWVuQ2hhbmdlKCknLFxuICAgICAgICAnKGRvY3VtZW50OndlYmtpdGZ1bGxzY3JlZW5jaGFuZ2UpJzogJ21lZGlhUGxheWVyU2VydmljZS5mdWxsc2NyZWVuQ2hhbmdlKCknLFxuICAgICAgICAnKGRvY3VtZW50Om1vemZ1bGxzY3JlZW5jaGFuZ2UpJzogJ21lZGlhUGxheWVyU2VydmljZS5mdWxsc2NyZWVuQ2hhbmdlKCknLFxuICAgICAgICAnKGRvY3VtZW50Ok1TRnVsbHNjcmVlbkNoYW5nZSknOiAnbWVkaWFQbGF5ZXJTZXJ2aWNlLmZ1bGxzY3JlZW5DaGFuZ2UoKSdcbiAgICB9XG59KVxuZXhwb3J0IGNsYXNzIE1lZGlhUGxheWVyQ29tcG9uZW50IGltcGxlbWVudHMgQWZ0ZXJWaWV3SW5pdCwgT25EZXN0cm95IHtcbiAgICByZWFkb25seSBtZWRpYVBsYXllclNlcnZpY2UgPSBpbmplY3QoTWVkaWFQbGF5ZXJTZXJ2aWNlKTtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2F1ZGlvU2VydmljZSA9IGluamVjdChBdWRpb1NlcnZpY2UpO1xuXG4gICAgcHJpdmF0ZSByZWFkb25seSBfZWxlbWVudFJlZiA9IGluamVjdChFbGVtZW50UmVmKTtcblxuICAgIEBWaWV3Q2hpbGQoJ3BsYXllcicsIHsgc3RhdGljOiBmYWxzZSB9KSBwcml2YXRlIHJlYWRvbmx5IF9wbGF5ZXJSZWY6IEVsZW1lbnRSZWY7XG5cbiAgICBob3ZlcmluZzogYm9vbGVhbiA9IGZhbHNlO1xuICAgIGZvY3VzZWQ6IGJvb2xlYW4gPSBmYWxzZTtcbiAgICBhdWRpb01ldGFkYXRhOiBPYnNlcnZhYmxlPEF1ZGlvTWV0YWRhdGE+O1xuXG4gICAgLyoqIFRoZSBgYW5vbnltb3VzYCBrZXl3b3JkIG1lYW5zIHRoYXQgdGhlcmUgd2lsbCBiZSBubyBleGNoYW5nZSBvZiB1c2VyIGNyZWRlbnRpYWxzIHdoZW4gdGhlIG1lZGlhIHNvdXJjZSBpcyBmZXRjaGVkLiAqL1xuICAgIEBJbnB1dCgpIGNyb3Nzb3JpZ2luOiAndXNlLWNyZWRlbnRpYWxzJyB8ICdhbm9ueW1vdXMnID0gJ3VzZS1jcmVkZW50aWFscyc7XG5cbiAgICAvKiogT3ZlcndyaXRlIHRoZSBmaWxlbmFtZSBkaXNwbGF5ZWQgaW4gdGhlIGF1ZGlvIG1lZGlhIHBsYXllciAqL1xuICAgIEBJbnB1dCgpXG4gICAgZmlsZW5hbWU6IHN0cmluZztcblxuICAgIGdldCBzb3VyY2UoKTogc3RyaW5nIHtcbiAgICAgICAgcmV0dXJuIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLnNvdXJjZTtcbiAgICB9XG5cbiAgICAvKiogVGhlIHVybCB0byB0aGUgbWVkaWEgZmlsZSB0byBiZSBsb2FkZWQgYnkgdGhlIG1lZGlhIHBsYXllci4gKi9cbiAgICBASW5wdXQoKVxuICAgIHNldCBzb3VyY2UodmFsdWU6IHN0cmluZykge1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5zb3VyY2UgPSB2YWx1ZTtcbiAgICB9XG5cbiAgICBnZXQgdHlwZSgpOiBNZWRpYVBsYXllclR5cGUge1xuICAgICAgICByZXR1cm4gdGhpcy5tZWRpYVBsYXllclNlcnZpY2UudHlwZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEZWZpbmVzIHRoZSBhcHBlYXJhbmNlIG9mIHRoZSBtZWRpYSBwbGF5ZXIuIFRoZSB0d28gcG9zc2libGUgdmFsdWVzIGFyZSBgdmlkZW9gIGFuZCBgYXVkaW9gLlxuICAgICAqIFRoZSBtZWRpYSBwbGF5ZXIgd2lsbCBhZGFwdCBpdCdzIGFwcGVhcmFuY2UgdG8gYmVzdCBzdWl0IHRoZSB0eXBlIHNwZWNpZmllZC5cbiAgICAgKi9cbiAgICBASW5wdXQoKVxuICAgIHNldCB0eXBlKHZhbHVlOiBNZWRpYVBsYXllclR5cGUpIHtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2UudHlwZSA9IHZhbHVlO1xuICAgIH1cblxuICAgIGdldCBxdWlldE1vZGUoKTogYm9vbGVhbiB7XG4gICAgICAgIHJldHVybiB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5xdWlldE1vZGU7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogSWYgZW5hYmxlZCwgdGhlIGNvbnRyb2xzIGluIHRoZSBtZWRpYSBwbGF5ZXIgd2lsbCBiZSBoaWRkZW4gdW5sZXNzIHRoZSBtb3VzZSBpcyBvdmVyIHRoZSBwbGF5ZXIgYW5kIHdpbGwgYXBwZWFyIGluIGEgZGFya2VyIHN0eWxlLlxuICAgICAqIERhcmsgbW9kZSBpcyBhdXRvbWF0aWNhbGx5IGVuYWJsZWQgaW4gZnVsbCBzY3JlZW4gbW9kZS4gUXVpZXQgbW9kZSBpcyBvbmx5IGF2YWlsYWJsZSBmb3IgdmlkZW9zLlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgc2V0IHF1aWV0TW9kZSh2YWx1ZTogYm9vbGVhbikge1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5xdWlldE1vZGUgPSB2YWx1ZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBJZiBzcGVjaWZpZWQgdGhlIGZ1bmN0aW9uIHdpbGwgYmUgY2FsbGVkIHBhc3NpbmcgdGhlIGN1cnJlbnQgdm9sdW1lIGFzIGFuIGFyZ3VtZW50LlxuICAgICAqIEl0IHNob3VsZCByZXR1cm4gYW4gYXBwcm9wcmlhdGUgYXJpYS1sYWJlbCBmb3IgdGhlIG11dGUvdW5tdXRlIGJ1dHRvbi5cbiAgICAgKi9cbiAgICBASW5wdXQoKVxuICAgIHNldCBtdXRlQXJpYUxhYmVsKGZuOiAodm9sdW1lOiBudW1iZXIpID0+IHN0cmluZykge1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5tdXRlQXJpYUxhYmVsID0gZm47XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogSWYgc3BlY2lmaWVkIHRoZSBmdW5jdGlvbiB3aWxsIGJlIGNhbGxlZCBwYXNzaW5nIHRoZSBjdXJyZW50IHBsYXlpbmcgc3RhdGUgYXMgYW4gYXJndW1lbnQuXG4gICAgICogSXQgc2hvdWxkIHJldHVybiBhbiBhcHByb3ByaWF0ZSBhcmlhLWxhYmVsIGZvciB0aGUgcGxheS9wYXVzZSBidXR0b24uXG4gICAgICovXG4gICAgQElucHV0KClcbiAgICBzZXQgcGxheUFyaWFMYWJlbChmbjogKGlzUGxheWluZzogYm9vbGVhbikgPT4gc3RyaW5nKSB7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLnBsYXlBcmlhTGFiZWwgPSBmbjtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBJZiBzcGVjaWZpZWQgdGhlIGZ1bmN0aW9uIHdpbGwgYmUgY2FsbGVkIHBhc3NpbmcgdGhlIGN1cnJlbnQgZnVsbHNjcmVlbiBzdGF0ZSBhcyBhbiBhcmd1bWVudC5cbiAgICAgKiBJdCBzaG91bGQgcmV0dXJuIGFuIGFwcHJvcHJpYXRlIGFyaWEtbGFiZWwgZm9yIHRoZSBmdWxsc2NyZWVuIHRvZ2dsZSBidXR0b24uXG4gICAgICovXG4gICAgQElucHV0KClcbiAgICBzZXQgZnVsbHNjcmVlbkFyaWFMYWJlbChmbjogKGlzRnVsbHNjcmVlbjogYm9vbGVhbikgPT4gc3RyaW5nKSB7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLmZ1bGxzY3JlZW5BcmlhTGFiZWwgPSBmbjtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBJZiBzcGVjaWZpZWQgdGhlIGZ1bmN0aW9uIHdpbGwgYmUgY2FsbGVkIHBhc3NpbmcgdGhlIGN1cnJlbnQgdHJhY2sgYXMgYW4gYXJndW1lbnQuXG4gICAgICogSXQgc2hvdWxkIHJldHVybiBhbiBhcHByb3ByaWF0ZSBhcmlhLWxhYmVsIGZvciB0aGUgc3VidGl0bGUgc2VsZWN0aW9uIGJ1dHRvbi5cbiAgICAgKi9cbiAgICBASW5wdXQoKVxuICAgIHNldCBzZWxlY3RTdWJ0aXRsZXNBcmlhTGFiZWwoZm46ICh0cmFjazogc3RyaW5nKSA9PiBzdHJpbmcpIHtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2Uuc2VsZWN0U3VidGl0bGVzQXJpYUxhYmVsID0gZm47XG4gICAgfVxuXG4gICAgLyoqIERlZmluZXMgYW4gYXJpYS1sYWJlbCBmb3IgdGhlIGdvIHRvIHN0YXJ0IGJ1dHRvbi4gKi9cbiAgICBASW5wdXQoKVxuICAgIHNldCBnb1RvU3RhcnRBcmlhTGFiZWwoYXJpYUxhYmVsOiBzdHJpbmcpIHtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2UuZ29Ub1N0YXJ0QXJpYUxhYmVsID0gYXJpYUxhYmVsO1xuICAgIH1cblxuICAgIC8qKiBEZWZpbmVzIGFuIGFyaWEtbGFiZWwgZm9yIHRoZSBnbyB0byBlbmQgYnV0dG9uLiAqL1xuICAgIEBJbnB1dCgpXG4gICAgc2V0IGdvVG9FbmRBcmlhTGFiZWwoYXJpYUxhYmVsOiBzdHJpbmcpIHtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2UuZ29Ub0VuZEFyaWFMYWJlbCA9IGFyaWFMYWJlbDtcbiAgICB9XG5cbiAgICAvKiogRGVmaW5lcyBhbiBhcmlhLWxhYmVsIGZvciB0aGUgdGl0bGUgZGlzcGxheWVkIGluIHRoZSBzdWJ0aXRsZSBzZWxlY3Rpb24gcG9wb3Zlci4gKi9cbiAgICBASW5wdXQoKVxuICAgIHNldCBzdWJ0aXRsZXNUaXRsZUFyaWFMYWJlbChhcmlhTGFiZWw6IHN0cmluZykge1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5zdWJ0aXRsZXNUaXRsZUFyaWFMYWJlbCA9IGFyaWFMYWJlbDtcbiAgICB9XG5cbiAgICAvKiogRGVmaW5lcyBhbiBhcmlhLWxhYmVsIHRvIGluZGljYXRlIHN1YnRpdGxlIGFyZSBub3QgY3VycmVudGx5IGVuYWJsZWQuICovXG4gICAgQElucHV0KClcbiAgICBzZXQgc3VidGl0bGVzT2ZmQXJpYUxhYmVsKGFyaWFMYWJlbDogc3RyaW5nKSB7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLnN1YnRpdGxlc09mZkFyaWFMYWJlbCA9IGFyaWFMYWJlbDtcbiAgICB9XG5cbiAgICAvKiogRGVmaW5lIGFuIGFyaWEtbGFiZWwgdG8gaW5kaWNhdGUgdGhlcmUgYXJlIG5vIHN1YnRpdGxlcyBhdmFpbGFibGUuICovXG4gICAgQElucHV0KClcbiAgICBzZXQgbm9TdWJ0aXRsZXNBcmlhTGFiZWwoYXJpYUxhYmVsOiBzdHJpbmcpIHtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2Uubm9TdWJ0aXRsZXNBcmlhTGFiZWwgPSBhcmlhTGFiZWw7XG4gICAgfVxuXG4gICAgLyoqIERlZmluZSBhbiBhcmlhLWxhYmVsIGZvciB0aGUgbWVkaWEgcGxheWVyLiAqL1xuICAgIEBJbnB1dCgpXG4gICAgc2V0IG1lZGlhUGxheWVyQXJpYUxhYmVsKGFyaWFMYWJlbDogc3RyaW5nKSB7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLm1lZGlhUGxheWVyQXJpYUxhYmVsID0gYXJpYUxhYmVsO1xuICAgIH1cblxuICAgIC8qKiBEZWZpbmUgYW4gYXJpYS1sYWJlbCBmb3IgdGhlIHRoZSBzZWVrIGVsZW1lbnQuICovXG4gICAgQElucHV0KClcbiAgICBzZXQgc2Vla0FyaWFMYWJlbChhcmlhTGFiZWw6IHN0cmluZykge1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5zZWVrQXJpYUxhYmVsID0gYXJpYUxhYmVsO1xuICAgIH1cblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX29uRGVzdHJveSA9IG5ldyBTdWJqZWN0PHZvaWQ+KCk7XG5cbiAgICBjb25zdHJ1Y3RvcigpIHtcblxuICAgICAgICAvLyBzaG93IGNvbnRyb2xzIHdoZW4gaG92ZXJpbmcgYW5kIGluIHF1aWV0IG1vZGVcbiAgICAgICAgZnJvbUV2ZW50KHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudCwgJ21vdXNlbW92ZScpLnBpcGUoXG4gICAgICAgICAgICB0YXAoKCkgPT4gdGhpcy5ob3ZlcmluZyA9IHRydWUpLFxuICAgICAgICAgICAgZGVib3VuY2VUaW1lKDIwMDApLFxuICAgICAgICAgICAgdGFrZVVudGlsKHRoaXMuX29uRGVzdHJveSlcbiAgICAgICAgKS5zdWJzY3JpYmUoKCkgPT4gdGhpcy5ob3ZlcmluZyA9IGZhbHNlKTtcbiAgICB9XG5cbiAgICBuZ0FmdGVyVmlld0luaXQoKTogdm9pZCB7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLnNldE1lZGlhUGxheWVyKHRoaXMuX2VsZW1lbnRSZWYubmF0aXZlRWxlbWVudCwgdGhpcy5fcGxheWVyUmVmLm5hdGl2ZUVsZW1lbnQpO1xuXG4gICAgICAgIHRoaXMuYXVkaW9NZXRhZGF0YSA9IHRoaXMuX2F1ZGlvU2VydmljZS5nZXRBdWRpb0ZpbGVNZXRhZGF0YSh0aGlzLl9wbGF5ZXJSZWYubmF0aXZlRWxlbWVudCk7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLnBsYXlpbmdFdmVudC5waXBlKHRha2VVbnRpbCh0aGlzLl9vbkRlc3Ryb3kpKS5zdWJzY3JpYmUoKCkgPT4gdGhpcy5tZWRpYVBsYXllclNlcnZpY2UucGxheWluZy5uZXh0KHRydWUpKTtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2UucGF1c2VFdmVudC5waXBlKHRha2VVbnRpbCh0aGlzLl9vbkRlc3Ryb3kpKS5zdWJzY3JpYmUoKCkgPT4gdGhpcy5tZWRpYVBsYXllclNlcnZpY2UucGxheWluZy5uZXh0KGZhbHNlKSk7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLm1lZGlhQ2xpY2tFdmVudC5waXBlKHRha2VVbnRpbCh0aGlzLl9vbkRlc3Ryb3kpKS5zdWJzY3JpYmUoKCkgPT4gdGhpcy5tZWRpYVBsYXllclNlcnZpY2UudG9nZ2xlUGxheSgpKTtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2UubG9hZGVkTWV0YWRhdGFFdmVudC5waXBlKHRha2VVbnRpbCh0aGlzLl9vbkRlc3Ryb3kpKS5zdWJzY3JpYmUoKCkgPT4gdGhpcy5tZWRpYVBsYXllclNlcnZpY2UubG9hZGVkID0gdHJ1ZSk7XG5cbiAgICAgICAgLy8gaW5pdGlhbGx5IGhpZGUgYWxsIHRleHQgdHJhY2tzXG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLmhpZGVTdWJ0aXRsZVRyYWNrcygpO1xuICAgIH1cblxuICAgIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgICAgICB0aGlzLl9vbkRlc3Ryb3kubmV4dCgpO1xuICAgICAgICB0aGlzLl9vbkRlc3Ryb3kuY29tcGxldGUoKTtcbiAgICB9XG59XG5cbmV4cG9ydCB0eXBlIE1lZGlhUGxheWVyVHlwZSA9ICd2aWRlbycgfCAnYXVkaW8nO1xuXG5leHBvcnQgaW50ZXJmYWNlIE1lZGlhUGxheWVyQnVmZmVyIHtcbiAgICBzdGFydDogbnVtYmVyO1xuICAgIGVuZDogbnVtYmVyO1xufVxuIiwiPGRpdiBjbGFzcz1cInBsYXllci1jb250YWluZXJcIlxuICB1eEZvY3VzSW5kaWNhdG9yXG4gIHRhYmluZGV4PVwiMFwiXG4gIFthdHRyLmFyaWEtbGFiZWxdPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLm1lZGlhUGxheWVyQXJpYUxhYmVsXCJcbiAgW2Nka1RyYXBGb2N1c109XCJtZWRpYVBsYXllclNlcnZpY2UuZnVsbHNjcmVlblwiPlxuXG4gIEBpZiAodHlwZSA9PT0gJ3ZpZGVvJykge1xuICAgIDxkaXYgY2xhc3M9XCJ2aWRlby1wbGF5ZXItY29udGFpbmVyXCI+XG4gICAgICA8dmlkZW8gY2xhc3M9XCJ2aWRlby1wbGF5ZXJcIlxuICAgICAgICAjcGxheWVyXG4gICAgICAgIHRhYmluZGV4PVwiLTFcIlxuICAgICAgICBbc3JjXT1cInNvdXJjZVwiXG4gICAgICAgIFtjcm9zc09yaWdpbl09XCJjcm9zc29yaWdpblwiXG4gICAgICAgIChhYm9ydCk9XCJtZWRpYVBsYXllclNlcnZpY2UuYWJvcnRFdmVudC5uZXh0KClcIlxuICAgICAgICAoY2FucGxheSk9XCJtZWRpYVBsYXllclNlcnZpY2UuY2FuUGxheUV2ZW50Lm5leHQodHJ1ZSlcIlxuICAgICAgICAoY2FucGxheXRocm91Z2gpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLmNhblBsYXlUaHJvdWdoRXZlbnQubmV4dCh0cnVlKVwiXG4gICAgICAgIChkdXJhdGlvbmNoYW5nZSk9XCJtZWRpYVBsYXllclNlcnZpY2UuZHVyYXRpb25DaGFuZ2VFdmVudC5uZXh0KHBsYXllci5kdXJhdGlvbilcIlxuICAgICAgICAoZW5kZWQpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLmVuZGVkRXZlbnQubmV4dCgpXCJcbiAgICAgICAgKGVycm9yKT1cIm1lZGlhUGxheWVyU2VydmljZS5lcnJvckV2ZW50Lm5leHQoJGV2ZW50KVwiXG4gICAgICAgIChsb2FkZWRkYXRhKT1cIm1lZGlhUGxheWVyU2VydmljZS5sb2FkZWREYXRhRXZlbnQubmV4dCgkZXZlbnQpXCJcbiAgICAgICAgKGxvYWRlZG1ldGFkYXRhKT1cIm1lZGlhUGxheWVyU2VydmljZS5sb2FkZWRNZXRhZGF0YUV2ZW50Lm5leHQoJGV2ZW50KVwiXG4gICAgICAgIChsb2Fkc3RhcnQpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLmxvYWRTdGFydEV2ZW50Lm5leHQoKVwiXG4gICAgICAgIChwYXVzZSk9XCJtZWRpYVBsYXllclNlcnZpY2UucGF1c2VFdmVudC5uZXh0KClcIlxuICAgICAgICAocGxheSk9XCJtZWRpYVBsYXllclNlcnZpY2UucGxheUV2ZW50Lm5leHQoKVwiXG4gICAgICAgIChwbGF5aW5nKT1cIm1lZGlhUGxheWVyU2VydmljZS5wbGF5aW5nRXZlbnQubmV4dCghcGxheWVyLnBhdXNlZClcIlxuICAgICAgICAocmF0ZWNoYW5nZSk9XCJtZWRpYVBsYXllclNlcnZpY2UucmF0ZUNoYW5nZUV2ZW50Lm5leHQocGxheWVyLnBsYXliYWNrUmF0ZSlcIlxuICAgICAgICAoc2Vla2VkKT1cIm1lZGlhUGxheWVyU2VydmljZS5zZWVrZWRFdmVudC5uZXh0KHBsYXllci5jdXJyZW50VGltZSlcIlxuICAgICAgICAoc2Vla2luZyk9XCJtZWRpYVBsYXllclNlcnZpY2Uuc2Vla2luZ0V2ZW50Lm5leHQocGxheWVyLmN1cnJlbnRUaW1lKVwiXG4gICAgICAgIChzdGFsbGVkKT1cIm1lZGlhUGxheWVyU2VydmljZS5zdGFsbGVkRXZlbnQubmV4dCgpXCJcbiAgICAgICAgKHN1c3BlbmQpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnN1c3BlbmRFdmVudC5uZXh0KClcIlxuICAgICAgICAodGltZXVwZGF0ZSk9XCJtZWRpYVBsYXllclNlcnZpY2UudGltZVVwZGF0ZUV2ZW50Lm5leHQocGxheWVyLmN1cnJlbnRUaW1lKVwiXG4gICAgICAgICh2b2x1bWVjaGFuZ2UpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnZvbHVtZUNoYW5nZUV2ZW50Lm5leHQocGxheWVyLnZvbHVtZSlcIlxuICAgICAgICAod2FpdGluZyk9XCJtZWRpYVBsYXllclNlcnZpY2Uud2FpdGluZ0V2ZW50Lm5leHQoKVwiXG4gICAgICAgIChjbGljayk9XCJtZWRpYVBsYXllclNlcnZpY2UubWVkaWFDbGlja0V2ZW50Lm5leHQoJGV2ZW50KVwiPlxuICAgICAgICA8bmctY29udGVudCBzZWxlY3Q9XCJ0cmFja1wiPjwvbmctY29udGVudD5cbiAgICAgIDwvdmlkZW8+XG4gICAgICA8ZGl2IGNsYXNzPVwidmlkZW8tb3ZlcmxheVwiIFtjbGFzcy5wbGF5aW5nXT1cIm1lZGlhUGxheWVyU2VydmljZS5wbGF5aW5nIHwgYXN5bmNcIj5cbiAgICAgICAgPHN2ZyBjbGFzcz1cInBsYXktZ3JhcGhpY1wiIHg9XCIwcHhcIiB5PVwiMHB4XCIgdmlld0JveD1cIjAgMCA2NCA2NFwiPlxuICAgICAgICAgIDxjaXJjbGUgY2xhc3M9XCJwbGF5LWNpcmNsZVwiIGN4PVwiMzIuMlwiIGN5PVwiMzEuOFwiIHI9XCIzMS44XCIgLz5cbiAgICAgICAgICA8cG9seWdvbiBjbGFzcz1cInBsYXktdHJpYW5nbGVcIiBwb2ludHM9XCIyMywxNC4xIDIzLDUwLjggNDguMywzMi41XCIgLz5cbiAgICAgICAgPC9zdmc+XG4gICAgICA8L2Rpdj5cbiAgICA8L2Rpdj5cbiAgfVxuXG4gIEBpZiAodHlwZSA9PT0gJ2F1ZGlvJykge1xuICAgIDxkaXYgY2xhc3M9XCJhdWRpby1wbGF5ZXJcIj5cbiAgICAgIDxzdmcgd2lkdGg9XCIyNHB4XCIgaGVpZ2h0PVwiMjRweFwiIHZpZXdCb3g9XCIwIDAgMjQgMjRcIj5cbiAgICAgICAgPGcgc3Ryb2tlPVwibm9uZVwiIHN0cm9rZS13aWR0aD1cIjFcIiBmaWxsPVwibm9uZVwiIGZpbGwtcnVsZT1cImV2ZW5vZGRcIj5cbiAgICAgICAgICA8ZyB0cmFuc2Zvcm09XCJ0cmFuc2xhdGUoLTk4LjAwMDAwMCwgLTQ1OC4wMDAwMDApXCI+XG4gICAgICAgICAgICA8ZyB0cmFuc2Zvcm09XCJ0cmFuc2xhdGUoOTguMDAwMDAwLCA0NTguMDAwMDAwKVwiPlxuICAgICAgICAgICAgICA8cGF0aCBkPVwiTTQuNSwwLjUgTDE4LjA0MzUzMDgsMC41IEwyMy41LDYuMjIyNTE1MDIgTDIzLjUsMjMuNSBMNC41LDIzLjUgTDQuNSwwLjUgWlwiIGZpbGw9XCIjQ0NFQUUyXCI+PC9wYXRoPlxuICAgICAgICAgICAgICA8cGF0aCBkPVwiTTQuNSw4IEw0LjUsMC41IEwxOCwwLjUgTDIzLjUsNiBMMjMuNSwyMy41IEwxOCwyMy41XCIgc3Ryb2tlPVwiIzYwNzk4RFwiIGZpbGw9XCIjQ0NFQUUyXCI+PC9wYXRoPlxuICAgICAgICAgICAgICA8cGF0aCBkPVwiTTQsMTMuNSBMMC41LDEzLjUgTDAuNSwxOC41IEw0LDE4LjUgTDkuNSwyMi41IEw5LjUsOS41IEw0LDEzLjUgWlwiIHN0cm9rZT1cIiM2MDc5OERcIiBmaWxsPVwiIzg1RDJCRVwiPjwvcGF0aD5cbiAgICAgICAgICAgICAgPHBhdGggZD1cIk0xMS41LDEyLjUxMzc5MzkgQzEzLjc1NzYyMjUsMTIuNTEzNzkzOSAxNC41LDE0LjM3MDkyMzYgMTQuNSwxNiBDMTQuNSwxNy42ODQ5MjM2IDEzLjcwODkxNTIsMTkuNTQyMDUzMiAxMS41LDE5LjU0MjA1MzJcIlxuICAgICAgICAgICAgICBzdHJva2U9XCIjNjA3OThEXCI+PC9wYXRoPlxuICAgICAgICAgICAgICA8cGF0aCBkPVwiTTExLjUsOSBDMTUuODAzNzY0Myw5LjA0MTY4NzAxIDE4LjUsMTEuNjYwNDgwNSAxOC41LDE2IEMxOC41LDIwLjMzOTUxOTUgMTUuODgwNDMwMiwyMy4wMDc5OTU2IDExLjUsMjNcIiBzdHJva2U9XCIjNjA3OThEXCI+PC9wYXRoPlxuICAgICAgICAgICAgICA8cGF0aCBkPVwiTTE3LjUyMTkxMTYsMC43NjE0MTM1NzQgTDE3LjUyMTkxMTYsNiBMMjMsNlwiIHN0cm9rZT1cIiM2MDc5OERcIiBmaWxsPVwiIzg1RDJCRVwiPjwvcGF0aD5cbiAgICAgICAgICAgIDwvZz5cbiAgICAgICAgICA8L2c+XG4gICAgICAgIDwvZz5cbiAgICAgIDwvc3ZnPlxuICAgICAgPHAgY2xhc3M9XCJhdWRpby1maWxlLW5hbWVcIj57eyB0aGlzLmZpbGVuYW1lID8gdGhpcy5maWxlbmFtZSA6ICAoYXVkaW9NZXRhZGF0YSB8IGFzeW5jKT8uZmlsZW5hbWUgfX08L3A+XG4gICAgICA8cCBjbGFzcz1cImF1ZGlvLWZpbGUtZm9ybWF0XCI+e3sgKGF1ZGlvTWV0YWRhdGEgfCBhc3luYyk/LmRlc2NyaXB0aW9uIH19PC9wPlxuICAgICAgPHAgY2xhc3M9XCJhdWRpby1maWxlLXNpemVcIj57eyAoYXVkaW9NZXRhZGF0YSB8IGFzeW5jKT8uc2l6ZSB8IGZpbGVTaXplIH19PC9wPlxuICAgICAgPGF1ZGlvICNwbGF5ZXJcbiAgICAgICAgW3NyY109XCJzb3VyY2VcIlxuICAgICAgICAoYWJvcnQpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLmFib3J0RXZlbnQubmV4dCgpXCJcbiAgICAgICAgKGNhbnBsYXkpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLmNhblBsYXlFdmVudC5uZXh0KHRydWUpXCJcbiAgICAgICAgKGNhbnBsYXl0aHJvdWdoKT1cIm1lZGlhUGxheWVyU2VydmljZS5jYW5QbGF5VGhyb3VnaEV2ZW50Lm5leHQodHJ1ZSlcIlxuICAgICAgICAoZHVyYXRpb25jaGFuZ2UpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLmR1cmF0aW9uQ2hhbmdlRXZlbnQubmV4dChwbGF5ZXIuZHVyYXRpb24pXCJcbiAgICAgICAgKGVuZGVkKT1cIm1lZGlhUGxheWVyU2VydmljZS5lbmRlZEV2ZW50Lm5leHQoKVwiXG4gICAgICAgIChlcnJvcik9XCJtZWRpYVBsYXllclNlcnZpY2UuZXJyb3JFdmVudC5uZXh0KCRldmVudClcIlxuICAgICAgICAobG9hZGVkZGF0YSk9XCJtZWRpYVBsYXllclNlcnZpY2UubG9hZGVkRGF0YUV2ZW50Lm5leHQoJGV2ZW50KVwiXG4gICAgICAgIChsb2FkZWRtZXRhZGF0YSk9XCJtZWRpYVBsYXllclNlcnZpY2UubG9hZGVkTWV0YWRhdGFFdmVudC5uZXh0KCRldmVudClcIlxuICAgICAgICAobG9hZHN0YXJ0KT1cIm1lZGlhUGxheWVyU2VydmljZS5sb2FkU3RhcnRFdmVudC5uZXh0KClcIlxuICAgICAgICAocGF1c2UpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnBhdXNlRXZlbnQubmV4dCgpXCJcbiAgICAgICAgKHBsYXkpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnBsYXlFdmVudC5uZXh0KClcIlxuICAgICAgICAocGxheWluZyk9XCJtZWRpYVBsYXllclNlcnZpY2UucGxheWluZ0V2ZW50Lm5leHQoIXBsYXllci5wYXVzZWQpXCJcbiAgICAgICAgKHJhdGVjaGFuZ2UpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnJhdGVDaGFuZ2VFdmVudC5uZXh0KHBsYXllci5wbGF5YmFja1JhdGUpXCJcbiAgICAgICAgKHNlZWtlZCk9XCJtZWRpYVBsYXllclNlcnZpY2Uuc2Vla2VkRXZlbnQubmV4dChwbGF5ZXIuY3VycmVudFRpbWUpXCJcbiAgICAgICAgKHNlZWtpbmcpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnNlZWtpbmdFdmVudC5uZXh0KHBsYXllci5jdXJyZW50VGltZSlcIlxuICAgICAgICAoc3RhbGxlZCk9XCJtZWRpYVBsYXllclNlcnZpY2Uuc3RhbGxlZEV2ZW50Lm5leHQoKVwiXG4gICAgICAgIChzdXNwZW5kKT1cIm1lZGlhUGxheWVyU2VydmljZS5zdXNwZW5kRXZlbnQubmV4dCgpXCJcbiAgICAgICAgKHRpbWV1cGRhdGUpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnRpbWVVcGRhdGVFdmVudC5uZXh0KHBsYXllci5jdXJyZW50VGltZSlcIlxuICAgICAgICAodm9sdW1lY2hhbmdlKT1cIm1lZGlhUGxheWVyU2VydmljZS52b2x1bWVDaGFuZ2VFdmVudC5uZXh0KHBsYXllci52b2x1bWUpXCJcbiAgICAgICAgKHdhaXRpbmcpPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLndhaXRpbmdFdmVudC5uZXh0KClcIlxuICAgICAgICAoY2xpY2spPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLm1lZGlhQ2xpY2tFdmVudC5uZXh0KCRldmVudClcIj5cbiAgICAgIDwvYXVkaW8+XG4gICAgPC9kaXY+XG4gIH1cblxuICA8ZGl2IGNsYXNzPVwiY29udHJvbC1iYXJcIlxuICAgICh1eEZvY3VzV2l0aGluKT1cImZvY3VzZWQgPSB0cnVlXCJcbiAgICAodXhCbHVyV2l0aGluKT1cImZvY3VzZWQgPSBmYWxzZVwiPlxuXG4gICAgPHV4LW1lZGlhLXBsYXllci10aW1lbGluZT48L3V4LW1lZGlhLXBsYXllci10aW1lbGluZT5cbiAgICA8dXgtbWVkaWEtcGxheWVyLWNvbnRyb2xzPlxuICAgICAgPG5nLWNvbnRlbnQgc2VsZWN0PVwiW3V4TWVkaWFQbGF5ZXJDdXN0b21Db250cm9sXVwiPjwvbmctY29udGVudD5cbiAgICA8L3V4LW1lZGlhLXBsYXllci1jb250cm9scz5cbiAgPC9kaXY+XG48L2Rpdj4iXX0=