UNPKG

biometry-angular-components

Version:

Angular UI component library for capturing biometric data

275 lines 47 kB
import { Component, ChangeDetectionStrategy, EventEmitter, Input, Output, ViewChild, } from '@angular/core'; import { CommonModule } from '@angular/common'; import * as i0 from "@angular/core"; import * as i1 from "../../services/permissions.service"; import * as i2 from "../../services/recorder.service"; import * as i3 from "@angular/common"; function numbersToPhrase(digits) { const words = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']; return digits.map(d => words[d]).join(' '); } function pickSupportedMimeType() { const candidates = [ 'video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/webm', 'video/mp4;codecs=h264,aac', 'video/mp4', ]; for (const type of candidates) { // @ts-ignore - isTypeSupported exists in browsers that implement MediaRecorder if (typeof MediaRecorder !== 'undefined' && MediaRecorder.isTypeSupported?.(type)) { return type; } } return 'video/webm'; } export class FaceRecorderComponent { permissions; recorder; cdr; videoRef; countdownSeconds = 3; recordingSeconds = 10; /** Prefer vertical 9:16; request 720x1280 from camera */ videoWidth = 720; videoHeight = 1280; capture = new EventEmitter(); state = 'preparation'; digits = []; maskedDigits = []; countdownLeft = 0; recordLeft = 0; countdownTimer; recordTimer; stream; videoBlob = null; videoUrl = null; isConfirming = false; permissionsGranted = null; mimeType = pickSupportedMimeType(); constructor(permissions, recorder, cdr) { this.permissions = permissions; this.recorder = recorder; this.cdr = cdr; } ngOnInit() { this.generateDigits(); this.requestPermissions(); } ngAfterViewInit() { if (this.stream) { this.attachStream(); } } ngOnDestroy() { this.clearAllTimers(); this.cleanupPlayback(); this.stopTracks(); this.recorder.cancel(); } /** === Flow control === */ async requestPermissions() { this.permissionsGranted = null; this.cdr.markForCheck(); const { stream, granted } = await this.permissions.requestCamera({ width: this.videoWidth, height: this.videoHeight, audio: true, }); this.permissionsGranted = granted; if (granted && stream) { this.stream = stream; this.attachStream(); } else { this.stream = undefined; } this.cdr.markForCheck(); } attachStream() { const video = this.videoRef.nativeElement; video.srcObject = this.stream ?? null; video.src = ''; video.controls = false; video.muted = true; video.play().catch(() => { }); } startFlow() { if (!this.stream || this.state !== 'preparation') return; this.startCountdown(); } startCountdown() { this.state = 'countdown'; this.countdownLeft = this.countdownSeconds; this.cdr.markForCheck(); this.clearCountdown(); this.countdownTimer = setInterval(() => { this.countdownLeft -= 1; this.cdr.markForCheck(); if (this.countdownLeft <= 0) { this.clearCountdown(); this.startRecording(); } }, 1000); } startRecording() { if (!this.stream) return; // Keep preview running; MediaRecorder records from the same stream this.state = 'recording'; this.recordLeft = this.recordingSeconds; try { this.recorder.start(this.stream, this.mimeType); } catch (e) { console.error('Failed to start recorder', e); this.cancelRecording(); return; } this.clearRecordTimer(); this.recordTimer = setInterval(async () => { this.recordLeft -= 1; this.cdr.markForCheck(); if (this.recordLeft <= 0) { await this.finishRecording(); } }, 1000); this.cdr.markForCheck(); } async finishRecording() { if (this.state !== 'recording') return; this.state = 'processing'; this.clearRecordTimer(); this.cdr.markForCheck(); try { this.videoBlob = await this.recorder.stop(); } catch { this.videoBlob = null; } if (this.videoBlob && this.videoBlob.size) { if (this.videoUrl) URL.revokeObjectURL(this.videoUrl); this.videoUrl = URL.createObjectURL(this.videoBlob); // Switch the element from live preview to the recorded video const video = this.videoRef.nativeElement; video.srcObject = null; video.src = this.videoUrl; video.controls = true; video.muted = false; await video.play().catch(() => { }); this.state = 'result'; } else { // No data recorded: return to preparation with live preview this.state = 'preparation'; this.attachStream(); } this.cdr.markForCheck(); } cancelRecording() { if (this.state !== 'countdown' && this.state !== 'recording') return; this.clearCountdown(); this.clearRecordTimer(); this.recorder.cancel(); if (this.videoUrl) URL.revokeObjectURL(this.videoUrl); this.videoUrl = null; this.videoBlob = null; // Return to preparation and keep preview alive this.state = 'preparation'; this.attachStream(); this.cdr.markForCheck(); } handleDecline() { if (this.videoUrl) URL.revokeObjectURL(this.videoUrl); this.videoUrl = null; this.videoBlob = null; this.state = 'preparation'; this.generateDigits(); this.requestPermissions(); this.cdr.markForCheck(); } handleConfirm() { if (!this.videoBlob) return; const file = new File([this.videoBlob], 'face-video.webm', { type: this.videoBlob.type, }); this.capture.emit({ file, phrase: numbersToPhrase(this.digits) }); if (this.videoUrl) URL.revokeObjectURL(this.videoUrl); this.videoUrl = null; this.videoBlob = null; this.isConfirming = false; this.state = 'preparation'; this.requestPermissions(); this.cdr.markForCheck(); } /** === Helpers === */ generateDigits() { this.digits = Array.from({ length: 10 }, () => Math.floor(Math.random() * 10)); this.maskedDigits = Array(this.digits.length).fill('*'); } clearCountdown() { if (this.countdownTimer) { clearInterval(this.countdownTimer); this.countdownTimer = null; } } clearRecordTimer() { if (this.recordTimer) { clearInterval(this.recordTimer); this.recordTimer = null; } } clearAllTimers() { this.clearCountdown(); this.clearRecordTimer(); } cleanupPlayback() { const v = this.videoRef?.nativeElement; if (v) { v.pause(); v.srcObject = null; v.src = ''; v.removeAttribute('src'); } if (this.videoUrl) URL.revokeObjectURL(this.videoUrl); this.videoUrl = null; this.videoBlob = null; } stopTracks() { this.stream?.getTracks().forEach(t => t.stop()); this.stream = undefined; } get displayDigits() { return (this.state === 'recording' ? this.digits : this.maskedDigits).map(d => d.toString()); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FaceRecorderComponent, deps: [{ token: i1.PermissionsService }, { token: i2.RecorderService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FaceRecorderComponent, isStandalone: true, selector: "bio-face-recorder", inputs: { countdownSeconds: "countdownSeconds", recordingSeconds: "recordingSeconds", videoWidth: "videoWidth", videoHeight: "videoHeight" }, outputs: { capture: "capture" }, viewQueries: [{ propertyName: "videoRef", first: true, predicate: ["videoEl"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"recorder-wrapper\" [class.result]=\"state === 'result'\">\n <!-- Video frame (9:16) -->\n <div class=\"frame\">\n <video #videoEl class=\"preview\" autoplay playsinline muted preload=\"metadata\"></video>\n\n <!-- Oval dashed overlay -->\n <div class=\"oval-area\" *ngIf=\"state !== 'result' && state !== 'processing'\">\n <span class=\"guidance-text\">Place your face in the oval</span>\n <div class=\"oval-dashed\"></div>\n </div>\n\n <!-- HUD: digits to read -->\n <!-- <div class=\"digits\" *ngIf=\"state !== 'processing'\">\n <div class=\"digits-label\">Read these digits aloud</div>\n <div class=\"digits-row\">\n <div class=\"digit-box\" *ngFor=\"let d of displayDigits\">\n {{ d }}\n </div>\n </div>\n </div> -->\n\n <!-- HUD: countdown -->\n <div class=\"overlay center\" *ngIf=\"state === 'countdown'\">\n <div class=\"countdown\">{{ countdownLeft }}</div>\n <button class=\"btn ghost\" type=\"button\" (click)=\"cancelRecording()\">Cancel</button>\n </div>\n\n <!-- HUD: recording timer + cancel -->\n <div class=\"overlay top\" *ngIf=\"state === 'recording'\">\n <div class=\"timer\">\n <span class=\"dot\"></span>\n <span>{{ recordLeft }}s</span>\n </div>\n <button class=\"btn danger\" type=\"button\" (click)=\"cancelRecording()\">Cancel</button>\n </div>\n\n <!-- HUD: processing -->\n <div class=\"overlay center\" *ngIf=\"state === 'processing'\">\n <div class=\"spinner\"></div>\n <div class=\"processing-label\">Processing\u2026</div>\n </div>\n </div>\n\n <div class=\"digits\" *ngIf=\"state !== 'processing'\">\n <div class=\"digits-label\">Read these digits aloud</div>\n <div class=\"digits-row\">\n <div class=\"digit-box\" *ngFor=\"let d of displayDigits\">\n {{ d }}\n </div>\n </div>\n </div>\n\n <!-- Controls / CTA -->\n <div class=\"controls\">\n <!-- Preparation -->\n <ng-container *ngIf=\"state === 'preparation'\">\n <div class=\"hint\" *ngIf=\"permissionsGranted === false\">\n Camera access denied. Please allow camera & audio and try again.\n </div>\n <button class=\"btn primary\" type=\"button\" (click)=\"startFlow()\" [disabled]=\"!permissionsGranted\">\n Start recording\n </button>\n <button class=\"btn\" type=\"button\" (click)=\"requestPermissions()\">Retry camera</button>\n </ng-container>\n\n <!-- Result -->\n <ng-container *ngIf=\"state === 'result'\">\n <div class=\"result-actions\">\n <button class=\"btn success\" type=\"button\" [disabled]=\"isConfirming\" (click)=\"handleConfirm()\">\n {{ isConfirming ? 'Confirming\u2026' : 'Use this video' }}\n </button>\n <button class=\"btn\" type=\"button\" (click)=\"handleDecline()\">Retake</button>\n </div>\n </ng-container>\n </div>\n</div>", styles: [".recorder-wrapper{display:grid;gap:12px;justify-items:center;width:100%}.frame{position:relative;width:100%;max-width:320px;aspect-ratio:9/16;background:#000;border-radius:20px;overflow:hidden;box-shadow:0 6px 20px #00000040}.preview{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.oval-area{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}.guidance-text{position:absolute;top:13%;left:50%;transform:translate(-50%);text-wrap:nowrap;color:#fff;background:#00000059;padding:6px 10px;text-align:center;border-radius:30px;font-size:12px;letter-spacing:.2px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.oval-dashed{width:70%;height:58%;border:3px dashed rgba(255,255,255,.8);border-radius:50%;box-shadow:0 0 0 9999px #00000026 inset}.digits{width:min(100%,420px);display:grid;gap:8px;justify-items:center;margin-top:4px}.digits-label{font-size:13px;opacity:.8;text-align:center}.digits-row{display:flex;justify-content:center;gap:8px;flex-wrap:nowrap;overflow-x:auto}.digit-box{width:28px;height:36px;background:#f5f5f5;border:2px solid #333;border-radius:6px;font-size:18px;font-weight:600;color:#333;display:flex;align-items:center;justify-content:center;box-shadow:0 1px 3px #00000014;transition:background .3s;flex-shrink:0}.overlay{position:absolute;inset-inline:0;display:grid;justify-items:center;gap:10px}.overlay.center{inset-block:0;place-content:center}.overlay.top{top:10px}.countdown{font-size:72px;font-weight:800;color:#fff;text-shadow:0 2px 8px rgba(0,0,0,.35)}.timer{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:999px;background:#00000059;color:#fff;font-weight:600}.timer .dot{width:8px;height:8px;border-radius:50%;background:currentColor;animation:pulse 1s infinite}.processing-label{color:#fff;font-weight:600}.spinner{width:28px;height:28px;border-radius:50%;border:3px solid rgba(255,255,255,.4);border-top-color:#fff;animation:spin .9s linear infinite}.controls{width:min(100%,420px);display:grid;gap:10px}.result-actions{display:grid;grid-auto-flow:column;gap:10px}.hint{font-size:13px;color:#a00;text-align:center}.btn{appearance:none;border:none;padding:10px 14px;border-radius:12px;font-weight:600;cursor:pointer;background:#eee}.btn.primary{background:#2b6ef2;color:#fff}.btn.success{background:#16a34a;color:#fff}.btn.danger{background:#ef4444;color:#fff}.btn.ghost{background:#00000059;color:#fff}.btn:disabled{opacity:.6;cursor:not-allowed}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{0%,to{opacity:.5;transform:scale(1)}50%{opacity:1;transform:scale(1.2)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FaceRecorderComponent, decorators: [{ type: Component, args: [{ selector: 'bio-face-recorder', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"recorder-wrapper\" [class.result]=\"state === 'result'\">\n <!-- Video frame (9:16) -->\n <div class=\"frame\">\n <video #videoEl class=\"preview\" autoplay playsinline muted preload=\"metadata\"></video>\n\n <!-- Oval dashed overlay -->\n <div class=\"oval-area\" *ngIf=\"state !== 'result' && state !== 'processing'\">\n <span class=\"guidance-text\">Place your face in the oval</span>\n <div class=\"oval-dashed\"></div>\n </div>\n\n <!-- HUD: digits to read -->\n <!-- <div class=\"digits\" *ngIf=\"state !== 'processing'\">\n <div class=\"digits-label\">Read these digits aloud</div>\n <div class=\"digits-row\">\n <div class=\"digit-box\" *ngFor=\"let d of displayDigits\">\n {{ d }}\n </div>\n </div>\n </div> -->\n\n <!-- HUD: countdown -->\n <div class=\"overlay center\" *ngIf=\"state === 'countdown'\">\n <div class=\"countdown\">{{ countdownLeft }}</div>\n <button class=\"btn ghost\" type=\"button\" (click)=\"cancelRecording()\">Cancel</button>\n </div>\n\n <!-- HUD: recording timer + cancel -->\n <div class=\"overlay top\" *ngIf=\"state === 'recording'\">\n <div class=\"timer\">\n <span class=\"dot\"></span>\n <span>{{ recordLeft }}s</span>\n </div>\n <button class=\"btn danger\" type=\"button\" (click)=\"cancelRecording()\">Cancel</button>\n </div>\n\n <!-- HUD: processing -->\n <div class=\"overlay center\" *ngIf=\"state === 'processing'\">\n <div class=\"spinner\"></div>\n <div class=\"processing-label\">Processing\u2026</div>\n </div>\n </div>\n\n <div class=\"digits\" *ngIf=\"state !== 'processing'\">\n <div class=\"digits-label\">Read these digits aloud</div>\n <div class=\"digits-row\">\n <div class=\"digit-box\" *ngFor=\"let d of displayDigits\">\n {{ d }}\n </div>\n </div>\n </div>\n\n <!-- Controls / CTA -->\n <div class=\"controls\">\n <!-- Preparation -->\n <ng-container *ngIf=\"state === 'preparation'\">\n <div class=\"hint\" *ngIf=\"permissionsGranted === false\">\n Camera access denied. Please allow camera & audio and try again.\n </div>\n <button class=\"btn primary\" type=\"button\" (click)=\"startFlow()\" [disabled]=\"!permissionsGranted\">\n Start recording\n </button>\n <button class=\"btn\" type=\"button\" (click)=\"requestPermissions()\">Retry camera</button>\n </ng-container>\n\n <!-- Result -->\n <ng-container *ngIf=\"state === 'result'\">\n <div class=\"result-actions\">\n <button class=\"btn success\" type=\"button\" [disabled]=\"isConfirming\" (click)=\"handleConfirm()\">\n {{ isConfirming ? 'Confirming\u2026' : 'Use this video' }}\n </button>\n <button class=\"btn\" type=\"button\" (click)=\"handleDecline()\">Retake</button>\n </div>\n </ng-container>\n </div>\n</div>", styles: [".recorder-wrapper{display:grid;gap:12px;justify-items:center;width:100%}.frame{position:relative;width:100%;max-width:320px;aspect-ratio:9/16;background:#000;border-radius:20px;overflow:hidden;box-shadow:0 6px 20px #00000040}.preview{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}.oval-area{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}.guidance-text{position:absolute;top:13%;left:50%;transform:translate(-50%);text-wrap:nowrap;color:#fff;background:#00000059;padding:6px 10px;text-align:center;border-radius:30px;font-size:12px;letter-spacing:.2px;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.oval-dashed{width:70%;height:58%;border:3px dashed rgba(255,255,255,.8);border-radius:50%;box-shadow:0 0 0 9999px #00000026 inset}.digits{width:min(100%,420px);display:grid;gap:8px;justify-items:center;margin-top:4px}.digits-label{font-size:13px;opacity:.8;text-align:center}.digits-row{display:flex;justify-content:center;gap:8px;flex-wrap:nowrap;overflow-x:auto}.digit-box{width:28px;height:36px;background:#f5f5f5;border:2px solid #333;border-radius:6px;font-size:18px;font-weight:600;color:#333;display:flex;align-items:center;justify-content:center;box-shadow:0 1px 3px #00000014;transition:background .3s;flex-shrink:0}.overlay{position:absolute;inset-inline:0;display:grid;justify-items:center;gap:10px}.overlay.center{inset-block:0;place-content:center}.overlay.top{top:10px}.countdown{font-size:72px;font-weight:800;color:#fff;text-shadow:0 2px 8px rgba(0,0,0,.35)}.timer{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:999px;background:#00000059;color:#fff;font-weight:600}.timer .dot{width:8px;height:8px;border-radius:50%;background:currentColor;animation:pulse 1s infinite}.processing-label{color:#fff;font-weight:600}.spinner{width:28px;height:28px;border-radius:50%;border:3px solid rgba(255,255,255,.4);border-top-color:#fff;animation:spin .9s linear infinite}.controls{width:min(100%,420px);display:grid;gap:10px}.result-actions{display:grid;grid-auto-flow:column;gap:10px}.hint{font-size:13px;color:#a00;text-align:center}.btn{appearance:none;border:none;padding:10px 14px;border-radius:12px;font-weight:600;cursor:pointer;background:#eee}.btn.primary{background:#2b6ef2;color:#fff}.btn.success{background:#16a34a;color:#fff}.btn.danger{background:#ef4444;color:#fff}.btn.ghost{background:#00000059;color:#fff}.btn:disabled{opacity:.6;cursor:not-allowed}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{0%,to{opacity:.5;transform:scale(1)}50%{opacity:1;transform:scale(1.2)}}\n"] }] }], ctorParameters: () => [{ type: i1.PermissionsService }, { type: i2.RecorderService }, { type: i0.ChangeDetectorRef }], propDecorators: { videoRef: [{ type: ViewChild, args: ['videoEl', { static: true }] }], countdownSeconds: [{ type: Input }], recordingSeconds: [{ type: Input }], videoWidth: [{ type: Input }], videoHeight: [{ type: Input }], capture: [{ type: Output }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjZS1yZWNvcmRlci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbGliL2NvbXBvbmVudHMvZmFjZS1yZWNvcmRlci9mYWNlLXJlY29yZGVyLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9mYWNlLXJlY29yZGVyL2ZhY2UtcmVjb3JkZXIuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFDVCx1QkFBdUIsRUFHdkIsWUFBWSxFQUNaLEtBQUssRUFHTCxNQUFNLEVBQ04sU0FBUyxHQUVWLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQzs7Ozs7QUFNL0MsU0FBUyxlQUFlLENBQUMsTUFBZ0I7SUFDdkMsTUFBTSxLQUFLLEdBQUcsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUMvRixPQUFPLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMscUJBQXFCO0lBQzVCLE1BQU0sVUFBVSxHQUFHO1FBQ2pCLDRCQUE0QjtRQUM1Qiw0QkFBNEI7UUFDNUIsWUFBWTtRQUNaLDJCQUEyQjtRQUMzQixXQUFXO0tBQ1osQ0FBQztJQUNGLEtBQUssTUFBTSxJQUFJLElBQUksVUFBVSxFQUFFLENBQUM7UUFDOUIsK0VBQStFO1FBQy9FLElBQUksT0FBTyxhQUFhLEtBQUssV0FBVyxJQUFJLGFBQWEsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxJQUFJLENB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O3FKQUdQLFFBQVE7c0JBQS9DLFNBQVM7dUJBQUMsU0FBUyxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRTtnQkFFN0IsZ0JBQWdCO3NCQUF4QixLQUFLO2dCQUNHLGdCQUFnQjtzQkFBeEIsS0FBSztnQkFHRyxVQUFVO3NCQUFsQixLQUFLO2dCQUNHLFdBQVc7c0JBQW5CLEtBQUs7Z0JBRUksT0FBTztzQkFBaEIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENvbXBvbmVudCxcbiAgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksXG4gIENoYW5nZURldGVjdG9yUmVmLFxuICBFbGVtZW50UmVmLFxuICBFdmVudEVtaXR0ZXIsXG4gIElucHV0LFxuICBPbkRlc3Ryb3ksXG4gIE9uSW5pdCxcbiAgT3V0cHV0LFxuICBWaWV3Q2hpbGQsXG4gIEFmdGVyVmlld0luaXQsXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IFBlcm1pc3Npb25zU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3Blcm1pc3Npb25zLnNlcnZpY2UnO1xuaW1wb3J0IHsgUmVjb3JkZXJTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vc2VydmljZXMvcmVjb3JkZXIuc2VydmljZSc7XG5cbnR5cGUgRmFjZVN0YXRlID0gJ3ByZXBhcmF0aW9uJyB8ICdjb3VudGRvd24nIHwgJ3JlY29yZGluZycgfCAncHJvY2Vzc2luZycgfCAncmVzdWx0JztcblxuZnVuY3Rpb24gbnVtYmVyc1RvUGhyYXNlKGRpZ2l0czogbnVtYmVyW10pOiBzdHJpbmcge1xuICBjb25zdCB3b3JkcyA9IFsnemVybycsICdvbmUnLCAndHdvJywgJ3RocmVlJywgJ2ZvdXInLCAnZml2ZScsICdzaXgnLCAnc2V2ZW4nLCAnZWlnaHQnLCAnbmluZSddO1xuICByZXR1cm4gZGlnaXRzLm1hcChkID0+IHdvcmRzW2RdKS5qb2luKCcgJyk7XG59XG5cbmZ1bmN0aW9uIHBpY2tTdXBwb3J0ZWRNaW1lVHlwZSgpOiBzdHJpbmcge1xuICBjb25zdCBjYW5kaWRhdGVzID0gW1xuICAgICd2aWRlby93ZWJtO2NvZGVjcz12cDksb3B1cycsXG4gICAgJ3ZpZGVvL3dlYm07Y29kZWNzPXZwOCxvcHVzJyxcbiAgICAndmlkZW8vd2VibScsXG4gICAgJ3ZpZGVvL21wNDtjb2RlY3M9aDI2NCxhYWMnLFxuICAgICd2aWRlby9tcDQnLFxuICBdO1xuICBmb3IgKGNvbnN0IHR5cGUgb2YgY2FuZGlkYXRlcykge1xuICAgIC8vIEB0cy1pZ25vcmUgLSBpc1R5cGVTdXBwb3J0ZWQgZXhpc3RzIGluIGJyb3dzZXJzIHRoYXQgaW1wbGVtZW50IE1lZGlhUmVjb3JkZXJcbiAgICBpZiAodHlwZW9mIE1lZGlhUmVjb3JkZXIgIT09ICd1bmRlZmluZWQnICYmIE1lZGlhUmVjb3JkZXIuaXNUeXBlU3VwcG9ydGVkPy4odHlwZSkpIHtcbiAgICAgIHJldHVybiB0eXBlO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiAndmlkZW8vd2VibSc7XG59XG5cblxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnYmlvLWZhY2UtcmVjb3JkZXInLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICBpbXBvcnRzOiBbQ29tbW9uTW9kdWxlXSxcbiAgdGVtcGxhdGVVcmw6ICcuL2ZhY2UtcmVjb3JkZXIuY29tcG9uZW50Lmh0bWwnLFxuICBzdHlsZVVybHM6IFsnLi9mYWNlLXJlY29yZGVyLmNvbXBvbmVudC5zY3NzJ10sXG4gIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxufSlcbmV4cG9ydCBjbGFzcyBGYWNlUmVjb3JkZXJDb21wb25lbnQgaW1wbGVtZW50cyBPbkluaXQsIEFmdGVyVmlld0luaXQsIE9uRGVzdHJveSB7XG4gIEBWaWV3Q2hpbGQoJ3ZpZGVvRWwnLCB7IHN0YXRpYzogdHJ1ZSB9KSB2aWRlb1JlZiE6IEVsZW1lbnRSZWY8SFRNTFZpZGVvRWxlbWVudD47XG5cbiAgQElucHV0KCkgY291bnRkb3duU2Vjb25kcyA9IDM7XG4gIEBJbnB1dCgpIHJlY29yZGluZ1NlY29uZHMgPSAxMDtcblxuICAvKiogUHJlZmVyIHZlcnRpY2FsIDk6MTY7IHJlcXVlc3QgNzIweDEyODAgZnJvbSBjYW1lcmEgKi9cbiAgQElucHV0KCkgdmlkZW9XaWR0aCA9IDcyMDtcbiAgQElucHV0KCkgdmlkZW9IZWlnaHQgPSAxMjgwO1xuXG4gIEBPdXRwdXQoKSBjYXB0dXJlID0gbmV3IEV2ZW50RW1pdHRlcjx7IGZpbGU6IEZpbGU7IHBocmFzZTogc3RyaW5nIH0+KCk7XG5cbiAgc3RhdGU6IEZhY2VTdGF0ZSA9ICdwcmVwYXJhdGlvbic7XG4gIGRpZ2l0czogbnVtYmVyW10gPSBbXTtcbiAgbWFza2VkRGlnaXRzOiBzdHJpbmdbXSA9IFtdO1xuXG4gIGNvdW50ZG93bkxlZnQgPSAwO1xuICByZWNvcmRMZWZ0ID0gMDtcblxuICBwcml2YXRlIGNvdW50ZG93blRpbWVyOiBhbnk7XG4gIHByaXZhdGUgcmVjb3JkVGltZXI6IGFueTtcblxuICBwcml2YXRlIHN0cmVhbT86IE1lZGlhU3RyZWFtO1xuICB2aWRlb0Jsb2I6IEJsb2IgfCBudWxsID0gbnVsbDtcbiAgdmlkZW9Vcmw6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuXG4gIGlzQ29uZmlybWluZyA9IGZhbHNlO1xuICBwZXJtaXNzaW9uc0dyYW50ZWQ6IGJvb2xlYW4gfCBudWxsID0gbnVsbDtcblxuICBwcml2YXRlIG1pbWVUeXBlID0gcGlja1N1cHBvcnRlZE1pbWVUeXBlKCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSBwZXJtaXNzaW9uczogUGVybWlzc2lvbnNTZXJ2aWNlLFxuICAgIHByaXZhdGUgcmVjb3JkZXI6IFJlY29yZGVyU2VydmljZSxcbiAgICBwcml2YXRlIGNkcjogQ2hhbmdlRGV0ZWN0b3JSZWZcbiAgKSB7IH1cblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICB0aGlzLmdlbmVyYXRlRGlnaXRzKCk7XG4gICAgdGhpcy5yZXF1ZXN0UGVybWlzc2lvbnMoKTtcbiAgfVxuXG4gIG5nQWZ0ZXJWaWV3SW5pdCgpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5zdHJlYW0pIHtcbiAgICAgIHRoaXMuYXR0YWNoU3RyZWFtKCk7XG4gICAgfVxuICB9XG5cbiAgbmdPbkRlc3Ryb3koKTogdm9pZCB7XG4gICAgdGhpcy5jbGVhckFsbFRpbWVycygpO1xuICAgIHRoaXMuY2xlYW51cFBsYXliYWNrKCk7XG4gICAgdGhpcy5zdG9wVHJhY2tzKCk7XG4gICAgdGhpcy5yZWNvcmRlci5jYW5jZWwoKTtcbiAgfVxuXG4gIC8qKiA9PT0gRmxvdyBjb250cm9sID09PSAqL1xuXG4gIGFzeW5jIHJlcXVlc3RQZXJtaXNzaW9ucygpIHtcbiAgICB0aGlzLnBlcm1pc3Npb25zR3JhbnRlZCA9IG51bGw7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG5cbiAgICBjb25zdCB7IHN0cmVhbSwgZ3JhbnRlZCB9ID0gYXdhaXQgdGhpcy5wZXJtaXNzaW9ucy5yZXF1ZXN0Q2FtZXJhKHtcbiAgICAgIHdpZHRoOiB0aGlzLnZpZGVvV2lkdGgsXG4gICAgICBoZWlnaHQ6IHRoaXMudmlkZW9IZWlnaHQsXG4gICAgICBhdWRpbzogdHJ1ZSxcbiAgICB9KTtcblxuICAgIHRoaXMucGVybWlzc2lvbnNHcmFudGVkID0gZ3JhbnRlZDtcblxuICAgIGlmIChncmFudGVkICYmIHN0cmVhbSkge1xuICAgICAgdGhpcy5zdHJlYW0gPSBzdHJlYW07XG4gICAgICB0aGlzLmF0dGFjaFN0cmVhbSgpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLnN0cmVhbSA9IHVuZGVmaW5lZDtcbiAgICB9XG5cbiAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIHByaXZhdGUgYXR0YWNoU3RyZWFtKCkge1xuICAgIGNvbnN0IHZpZGVvID0gdGhpcy52aWRlb1JlZi5uYXRpdmVFbGVtZW50O1xuICAgIHZpZGVvLnNyY09iamVjdCA9IHRoaXMuc3RyZWFtID8/IG51bGw7XG4gICAgdmlkZW8uc3JjID0gJyc7XG4gICAgdmlkZW8uY29udHJvbHMgPSBmYWxzZTtcbiAgICB2aWRlby5tdXRlZCA9IHRydWU7XG4gICAgdmlkZW8ucGxheSgpLmNhdGNoKCgpID0+IHsvKiBpZ25vcmUgYXV0b3BsYXkgcmFjZSAqLyB9KTtcbiAgfVxuXG4gIHN0YXJ0RmxvdygpIHtcbiAgICBpZiAoIXRoaXMuc3RyZWFtIHx8IHRoaXMuc3RhdGUgIT09ICdwcmVwYXJhdGlvbicpIHJldHVybjtcbiAgICB0aGlzLnN0YXJ0Q291bnRkb3duKCk7XG4gIH1cblxuICBwcml2YXRlIHN0YXJ0Q291bnRkb3duKCkge1xuICAgIHRoaXMuc3RhdGUgPSAnY291bnRkb3duJztcbiAgICB0aGlzLmNvdW50ZG93bkxlZnQgPSB0aGlzLmNvdW50ZG93blNlY29uZHM7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG5cbiAgICB0aGlzLmNsZWFyQ291bnRkb3duKCk7XG4gICAgdGhpcy5jb3VudGRvd25UaW1lciA9IHNldEludGVydmFsKCgpID0+IHtcbiAgICAgIHRoaXMuY291bnRkb3duTGVmdCAtPSAxO1xuICAgICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gICAgICBpZiAodGhpcy5jb3VudGRvd25MZWZ0IDw9IDApIHtcbiAgICAgICAgdGhpcy5jbGVhckNvdW50ZG93bigpO1xuICAgICAgICB0aGlzLnN0YXJ0UmVjb3JkaW5nKCk7XG4gICAgICB9XG4gICAgfSwgMTAwMCk7XG4gIH1cblxuICBwcml2YXRlIHN0YXJ0UmVjb3JkaW5nKCkge1xuICAgIGlmICghdGhpcy5zdHJlYW0pIHJldHVybjtcblxuICAgIC8vIEtlZXAgcHJldmlldyBydW5uaW5nOyBNZWRpYVJlY29yZGVyIHJlY29yZHMgZnJvbSB0aGUgc2FtZSBzdHJlYW1cbiAgICB0aGlzLnN0YXRlID0gJ3JlY29yZGluZyc7XG4gICAgdGhpcy5yZWNvcmRMZWZ0ID0gdGhpcy5yZWNvcmRpbmdTZWNvbmRzO1xuXG4gICAgdHJ5IHtcbiAgICAgIHRoaXMucmVjb3JkZXIuc3RhcnQodGhpcy5zdHJlYW0sIHRoaXMubWltZVR5cGUpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ0ZhaWxlZCB0byBzdGFydCByZWNvcmRlcicsIGUpO1xuICAgICAgdGhpcy5jYW5jZWxSZWNvcmRpbmcoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLmNsZWFyUmVjb3JkVGltZXIoKTtcbiAgICB0aGlzLnJlY29yZFRpbWVyID0gc2V0SW50ZXJ2YWwoYXN5bmMgKCkgPT4ge1xuICAgICAgdGhpcy5yZWNvcmRMZWZ0IC09IDE7XG4gICAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcbiAgICAgIGlmICh0aGlzLnJlY29yZExlZnQgPD0gMCkge1xuICAgICAgICBhd2FpdCB0aGlzLmZpbmlzaFJlY29yZGluZygpO1xuICAgICAgfVxuICAgIH0sIDEwMDApO1xuICAgIHRoaXMuY2RyLm1hcmtGb3JDaGVjaygpO1xuICB9XG5cbiAgYXN5bmMgZmluaXNoUmVjb3JkaW5nKCkge1xuICAgIGlmICh0aGlzLnN0YXRlICE9PSAncmVjb3JkaW5nJykgcmV0dXJuO1xuXG4gICAgdGhpcy5zdGF0ZSA9ICdwcm9jZXNzaW5nJztcbiAgICB0aGlzLmNsZWFyUmVjb3JkVGltZXIoKTtcbiAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcblxuICAgIHRyeSB7XG4gICAgICB0aGlzLnZpZGVvQmxvYiA9IGF3YWl0IHRoaXMucmVjb3JkZXIuc3RvcCgpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgdGhpcy52aWRlb0Jsb2IgPSBudWxsO1xuICAgIH1cblxuICAgIGlmICh0aGlzLnZpZGVvQmxvYiAmJiB0aGlzLnZpZGVvQmxvYi5zaXplKSB7XG4gICAgICBpZiAodGhpcy52aWRlb1VybCkgVVJMLnJldm9rZU9iamVjdFVSTCh0aGlzLnZpZGVvVXJsKTtcbiAgICAgIHRoaXMudmlkZW9VcmwgPSBVUkwuY3JlYXRlT2JqZWN0VVJMKHRoaXMudmlkZW9CbG9iKTtcbiAgICAgIC8vIFN3aXRjaCB0aGUgZWxlbWVudCBmcm9tIGxpdmUgcHJldmlldyB0byB0aGUgcmVjb3JkZWQgdmlkZW9cbiAgICAgIGNvbnN0IHZpZGVvID0gdGhpcy52aWRlb1JlZi5uYXRpdmVFbGVtZW50O1xuICAgICAgdmlkZW8uc3JjT2JqZWN0ID0gbnVsbDtcbiAgICAgIHZpZGVvLnNyYyA9IHRoaXMudmlkZW9Vcmw7XG4gICAgICB2aWRlby5jb250cm9scyA9IHRydWU7XG4gICAgICB2aWRlby5tdXRlZCA9IGZhbHNlO1xuICAgICAgYXdhaXQgdmlkZW8ucGxheSgpLmNhdGNoKCgpID0+IHsgfSk7XG4gICAgICB0aGlzLnN0YXRlID0gJ3Jlc3VsdCc7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIE5vIGRhdGEgcmVjb3JkZWQ6IHJldHVybiB0byBwcmVwYXJhdGlvbiB3aXRoIGxpdmUgcHJldmlld1xuICAgICAgdGhpcy5zdGF0ZSA9ICdwcmVwYXJhdGlvbic7XG4gICAgICB0aGlzLmF0dGFjaFN0cmVhbSgpO1xuICAgIH1cblxuICAgIHRoaXMuY2RyLm1hcmtGb3JDaGVjaygpO1xuICB9XG5cbiAgY2FuY2VsUmVjb3JkaW5nKCkge1xuICAgIGlmICh0aGlzLnN0YXRlICE9PSAnY291bnRkb3duJyAmJiB0aGlzLnN0YXRlICE9PSAncmVjb3JkaW5nJykgcmV0dXJuO1xuXG4gICAgdGhpcy5jbGVhckNvdW50ZG93bigpO1xuICAgIHRoaXMuY2xlYXJSZWNvcmRUaW1lcigpO1xuICAgIHRoaXMucmVjb3JkZXIuY2FuY2VsKCk7XG5cbiAgICBpZiAodGhpcy52aWRlb1VybCkgVVJMLnJldm9rZU9iamVjdFVSTCh0aGlzLnZpZGVvVXJsKTtcbiAgICB0aGlzLnZpZGVvVXJsID0gbnVsbDtcbiAgICB0aGlzLnZpZGVvQmxvYiA9IG51bGw7XG5cbiAgICAvLyBSZXR1cm4gdG8gcHJlcGFyYXRpb24gYW5kIGtlZXAgcHJldmlldyBhbGl2ZVxuICAgIHRoaXMuc3RhdGUgPSAncHJlcGFyYXRpb24nO1xuICAgIHRoaXMuYXR0YWNoU3RyZWFtKCk7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gIH1cblxuICBoYW5kbGVEZWNsaW5lKCkge1xuICAgIGlmICh0aGlzLnZpZGVvVXJsKSBVUkwucmV2b2tlT2JqZWN0VVJMKHRoaXMudmlkZW9VcmwpO1xuICAgIHRoaXMudmlkZW9VcmwgPSBudWxsO1xuICAgIHRoaXMudmlkZW9CbG9iID0gbnVsbDtcblxuICAgIHRoaXMuc3RhdGUgPSAncHJlcGFyYXRpb24nO1xuICAgIHRoaXMuZ2VuZXJhdGVEaWdpdHMoKTtcbiAgICB0aGlzLnJlcXVlc3RQZXJtaXNzaW9ucygpO1xuICAgIHRoaXMuY2RyLm1hcmtGb3JDaGVjaygpO1xuICB9XG5cbiAgaGFuZGxlQ29uZmlybSgpIHtcbiAgICBpZiAoIXRoaXMudmlkZW9CbG9iKSByZXR1cm47XG5cbiAgICBjb25zdCBmaWxlID0gbmV3IEZpbGUoW3RoaXMudmlkZW9CbG9iXSwgJ2ZhY2UtdmlkZW8ud2VibScsIHtcbiAgICAgIHR5cGU6IHRoaXMudmlkZW9CbG9iLnR5cGUsXG4gICAgfSk7XG5cbiAgICB0aGlzLmNhcHR1cmUuZW1pdCh7IGZpbGUsIHBocmFzZTogbnVtYmVyc1RvUGhyYXNlKHRoaXMuZGlnaXRzKSB9KTtcblxuICAgIGlmICh0aGlzLnZpZGVvVXJsKSBVUkwucmV2b2tlT2JqZWN0VVJMKHRoaXMudmlkZW9VcmwpO1xuICAgIHRoaXMudmlkZW9VcmwgPSBudWxsO1xuICAgIHRoaXMudmlkZW9CbG9iID0gbnVsbDtcblxuICAgIHRoaXMuaXNDb25maXJtaW5nID0gZmFsc2U7XG4gICAgdGhpcy5zdGF0ZSA9ICdwcmVwYXJhdGlvbic7XG4gICAgdGhpcy5yZXF1ZXN0UGVybWlzc2lvbnMoKTtcbiAgICB0aGlzLmNkci5tYXJrRm9yQ2hlY2soKTtcbiAgfVxuXG4gIC8qKiA9PT0gSGVscGVycyA9PT0gKi9cblxuICBwcml2YXRlIGdlbmVyYXRlRGlnaXRzKCkge1xuICAgIHRoaXMuZGlnaXRzID0gQXJyYXkuZnJvbSh7IGxlbmd0aDogMTAgfSwgKCkgPT4gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogMTApKTtcbiAgICB0aGlzLm1hc2tlZERpZ2l0cyA9IEFycmF5KHRoaXMuZGlnaXRzLmxlbmd0aCkuZmlsbCgnKicpO1xuICB9XG5cbiAgcHJpdmF0ZSBjbGVhckNvdW50ZG93bigpIHtcbiAgICBpZiAodGhpcy5jb3VudGRvd25UaW1lcikge1xuICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLmNvdW50ZG93blRpbWVyKTtcbiAgICAgIHRoaXMuY291bnRkb3duVGltZXIgPSBudWxsO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgY2xlYXJSZWNvcmRUaW1lcigpIHtcbiAgICBpZiAodGhpcy5yZWNvcmRUaW1lcikge1xuICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLnJlY29yZFRpbWVyKTtcbiAgICAgIHRoaXMucmVjb3JkVGltZXIgPSBudWxsO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgY2xlYXJBbGxUaW1lcnMoKSB7XG4gICAgdGhpcy5jbGVhckNvdW50ZG93bigpO1xuICAgIHRoaXMuY2xlYXJSZWNvcmRUaW1lcigpO1xuICB9XG5cbiAgcHJpdmF0ZSBjbGVhbnVwUGxheWJhY2soKSB7XG4gICAgY29uc3QgdiA9IHRoaXMudmlkZW9SZWY/Lm5hdGl2ZUVsZW1lbnQ7XG4gICAgaWYgKHYpIHtcbiAgICAgIHYucGF1c2UoKTtcbiAgICAgIHYuc3JjT2JqZWN0ID0gbnVsbDtcbiAgICAgIHYuc3JjID0gJyc7XG4gICAgICB2LnJlbW92ZUF0dHJpYnV0ZSgnc3JjJyk7XG4gICAgfVxuICAgIGlmICh0aGlzLnZpZGVvVXJsKSBVUkwucmV2b2tlT2JqZWN0VVJMKHRoaXMudmlkZW9VcmwpO1xuICAgIHRoaXMudmlkZW9VcmwgPSBudWxsO1xuICAgIHRoaXMudmlkZW9CbG9iID0gbnVsbDtcbiAgfVxuXG4gIHByaXZhdGUgc3RvcFRyYWNrcygpIHtcbiAgICB0aGlzLnN0cmVhbT8uZ2V0VHJhY2tzKCkuZm9yRWFjaCh0ID0+IHQuc3RvcCgpKTtcbiAgICB0aGlzLnN0cmVhbSA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGdldCBkaXNwbGF5RGlnaXRzKCk6IHN0cmluZ1tdIHtcbiAgICByZXR1cm4gKHRoaXMuc3RhdGUgPT09ICdyZWNvcmRpbmcnID8gdGhpcy5kaWdpdHMgOiB0aGlzLm1hc2tlZERpZ2l0cykubWFwKGQgPT4gZC50b1N0cmluZygpKTtcbiAgfVxufVxuIiwiPGRpdiBjbGFzcz1cInJlY29yZGVyLXdyYXBwZXJcIiBbY2xhc3MucmVzdWx0XT1cInN0YXRlID09PSAncmVzdWx0J1wiPlxuICA8IS0tIFZpZGVvIGZyYW1lICg5OjE2KSAtLT5cbiAgPGRpdiBjbGFzcz1cImZyYW1lXCI+XG4gICAgPHZpZGVvICN2aWRlb0VsIGNsYXNzPVwicHJldmlld1wiIGF1dG9wbGF5IHBsYXlzaW5saW5lIG11dGVkIHByZWxvYWQ9XCJtZXRhZGF0YVwiPjwvdmlkZW8+XG5cbiAgICA8IS0tIE92YWwgZGFzaGVkIG92ZXJsYXkgLS0+XG4gICAgPGRpdiBjbGFzcz1cIm92YWwtYXJlYVwiICpuZ0lmPVwic3RhdGUgIT09ICdyZXN1bHQnICYmIHN0YXRlICE9PSAncHJvY2Vzc2luZydcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwiZ3VpZGFuY2UtdGV4dFwiPlBsYWNlIHlvdXIgZmFjZSBpbiB0aGUgb3ZhbDwvc3Bhbj5cbiAgICAgIDxkaXYgY2xhc3M9XCJvdmFsLWRhc2hlZFwiPjwvZGl2PlxuICAgIDwvZGl2PlxuXG4gICAgPCEtLSBIVUQ6IGRpZ2l0cyB0byByZWFkIC0tPlxuICAgIDwhLS0gPGRpdiBjbGFzcz1cImRpZ2l0c1wiICpuZ0lmPVwic3RhdGUgIT09ICdwcm9jZXNzaW5nJ1wiPlxuICAgICAgPGRpdiBjbGFzcz1cImRpZ2l0cy1sYWJlbFwiPlJlYWQgdGhlc2UgZGlnaXRzIGFsb3VkPC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwiZGlnaXRzLXJvd1wiPlxuICAgICAgICA8ZGl2IGNsYXNzPVwiZGlnaXQtYm94XCIgKm5nRm9yPVwibGV0IGQgb2YgZGlzcGxheURpZ2l0c1wiPlxuICAgICAgICAgIHt7IGQgfX1cbiAgICAgICAgPC9kaXY+XG4gICAgICA8L2Rpdj5cbiAgICA8L2Rpdj4gLS0+XG5cbiAgICA8IS0tIEhVRDogY291bnRkb3duIC0tPlxuICAgIDxkaXYgY2xhc3M9XCJvdmVybGF5IGNlbnRlclwiICpuZ0lmPVwic3RhdGUgPT09ICdjb3VudGRvd24nXCI+XG4gICAgICA8ZGl2IGNsYXNzPVwiY291bnRkb3duXCI+e3sgY291bnRkb3duTGVmdCB9fTwvZGl2PlxuICAgICAgPGJ1dHRvbiBjbGFzcz1cImJ0biBnaG9zdFwiIHR5cGU9XCJidXR0b25cIiAoY2xpY2spPVwiY2FuY2VsUmVjb3JkaW5nKClcIj5DYW5jZWw8L2J1dHRvbj5cbiAgICA8L2Rpdj5cblxuICAgIDwhLS0gSFVEOiByZWNvcmRpbmcgdGltZXIgKyBjYW5jZWwgLS0+XG4gICAgPGRpdiBjbGFzcz1cIm92ZXJsYXkgdG9wXCIgKm5nSWY9XCJzdGF0ZSA9PT0gJ3JlY29yZGluZydcIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJ0aW1lclwiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImRvdFwiPjwvc3Bhbj5cbiAgICAgICAgPHNwYW4+e3sgcmVjb3JkTGVmdCB9fXM8L3NwYW4+XG4gICAgICA8L2Rpdj5cbiAgICAgIDxidXR0b24gY2xhc3M9XCJidG4gZGFuZ2VyXCIgdHlwZT1cImJ1dHRvblwiIChjbGljayk9XCJjYW5jZWxSZWNvcmRpbmcoKVwiPkNhbmNlbDwvYnV0dG9uPlxuICAgIDwvZGl2PlxuXG4gICAgPCEtLSBIVUQ6IHByb2Nlc3NpbmcgLS0+XG4gICAgPGRpdiBjbGFzcz1cIm92ZXJsYXkgY2VudGVyXCIgKm5nSWY9XCJzdGF0ZSA9PT0gJ3Byb2Nlc3NpbmcnXCI+XG4gICAgICA8ZGl2IGNsYXNzPVwic3Bpbm5lclwiPjwvZGl2PlxuICAgICAgPGRpdiBjbGFzcz1cInByb2Nlc3NpbmctbGFiZWxcIj5Qcm9jZXNzaW5n4oCmPC9kaXY+XG4gICAgPC9kaXY+XG4gIDwvZGl2PlxuXG4gIDxkaXYgY2xhc3M9XCJkaWdpdHNcIiAqbmdJZj1cInN0YXRlICE9PSAncHJvY2Vzc2luZydcIj5cbiAgICA8ZGl2IGNsYXNzPVwiZGlnaXRzLWxhYmVsXCI+UmVhZCB0aGVzZSBkaWdpdHMgYWxvdWQ8L2Rpdj5cbiAgICA8ZGl2IGNsYXNzPVwiZGlnaXRzLXJvd1wiPlxuICAgICAgPGRpdiBjbGFzcz1cImRpZ2l0LWJveFwiICpuZ0Zvcj1cImxldCBkIG9mIGRpc3BsYXlEaWdpdHNcIj5cbiAgICAgICAge3sgZCB9fVxuICAgICAgPC9kaXY+XG4gICAgPC9kaXY+XG4gIDwvZGl2PlxuXG4gIDwhLS0gQ29udHJvbHMgLyBDVEEgLS0+XG4gIDxkaXYgY2xhc3M9XCJjb250cm9sc1wiPlxuICAgIDwhLS0gUHJlcGFyYXRpb24gLS0+XG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInN0YXRlID09PSAncHJlcGFyYXRpb24nXCI+XG4gICAgICA8ZGl2IGNsYXNzPVwiaGludFwiICpuZ0lmPVwicGVybWlzc2lvbnNHcmFudGVkID09PSBmYWxzZVwiPlxuICAgICAgICBDYW1lcmEgYWNjZXNzIGRlbmllZC4gUGxlYXNlIGFsbG93IGNhbWVyYSAmIGF1ZGlvIGFuZCB0cnkgYWdhaW4uXG4gICAgICA8L2Rpdj5cbiAgICAgIDxidXR0b24gY2xhc3M9XCJidG4gcHJpbWFyeVwiIHR5cGU9XCJidXR0b25cIiAoY2xpY2spPVwic3RhcnRGbG93KClcIiBbZGlzYWJsZWRdPVwiIXBlcm1pc3Npb25zR3JhbnRlZFwiPlxuICAgICAgICBTdGFydCByZWNvcmRpbmdcbiAgICAgIDwvYnV0dG9uPlxuICAgICAgPGJ1dHRvbiBjbGFzcz1cImJ0blwiIHR5cGU9XCJidXR0b25cIiAoY2xpY2spPVwicmVxdWVzdFBlcm1pc3Npb25zKClcIj5SZXRyeSBjYW1lcmE8L2J1dHRvbj5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDwhLS0gUmVzdWx0IC0tPlxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJzdGF0ZSA9PT0gJ3Jlc3VsdCdcIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJyZXN1bHQtYWN0aW9uc1wiPlxuICAgICAgICA8YnV0dG9uIGNsYXNzPVwiYnRuIHN1Y2Nlc3NcIiB0eXBlPVwiYnV0dG9uXCIgW2Rpc2FibGVkXT1cImlzQ29uZmlybWluZ1wiIChjbGljayk9XCJoYW5kbGVDb25maXJtKClcIj5cbiAgICAgICAgICB7eyBpc0NvbmZpcm1pbmcgPyAnQ29uZmlybWluZ+KApicgOiAnVXNlIHRoaXMgdmlkZW8nIH19XG4gICAgICAgIDwvYnV0dG9uPlxuICAgICAgICA8YnV0dG9uIGNsYXNzPVwiYnRuXCIgdHlwZT1cImJ1dHRvblwiIChjbGljayk9XCJoYW5kbGVEZWNsaW5lKClcIj5SZXRha2U8L2J1dHRvbj5cbiAgICAgIDwvZGl2PlxuICAgIDwvbmctY29udGFpbmVyPlxuICA8L2Rpdj5cbjwvZGl2PiJdfQ==