UNPKG

biometry-angular-components

Version:

Angular UI component library for capturing biometric data

115 lines 24.5 kB
import { Component, EventEmitter, Input, Output, ViewChild, ChangeDetectionStrategy } 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 "@angular/common"; export class FaceCaptureComponent { perms; ngZone; cdr; rectWidth = 360; rectHeight = 576; noShadow = false; capture = new EventEmitter(); videoEl; isConfirming = false; capturedUrl = null; capturedFile = null; stream; QUALITY_MULTIPLIER = 3; constructor(perms, ngZone, cdr) { this.perms = perms; this.ngZone = ngZone; this.cdr = cdr; } async ngOnInit() { await this.initStream(); } ngOnDestroy() { this.stopStream(); if (this.capturedUrl) URL.revokeObjectURL(this.capturedUrl); } async initStream() { try { const { stream, granted } = await this.perms.requestCamera({ width: this.rectWidth, height: this.rectHeight, }); if (!granted) { return; } this.stream = stream; console.log('this.stream = stream'); this.ngZone.runOutsideAngular(() => { if (this.videoEl?.nativeElement) { console.log('nativeElement'); this.videoEl.nativeElement.srcObject = this.stream; this.videoEl.nativeElement.onloadedmetadata = () => this.videoEl.nativeElement.play(); } }); } catch (err) { console.error('Camera access error:', err); } } stopStream() { this.stream?.getTracks().forEach(track => track.stop()); this.stream = undefined; } handleCapture() { const video = this.videoEl?.nativeElement; if (!video) return; const canvas = document.createElement('canvas'); canvas.width = this.rectWidth * this.QUALITY_MULTIPLIER; canvas.height = this.rectHeight * this.QUALITY_MULTIPLIER; const ctx = canvas.getContext('2d'); if (ctx) { ctx.drawImage(video, 0, 0, this.rectWidth, this.rectHeight, 0, 0, this.rectWidth * this.QUALITY_MULTIPLIER, this.rectHeight * this.QUALITY_MULTIPLIER); canvas.toBlob(blob => { if (blob) { const file = new File([blob], 'face.jpg', { type: 'image/jpeg' }); this.capturedFile = file; this.capturedUrl = URL.createObjectURL(blob); this.isConfirming = true; this.stopStream(); this.cdr.markForCheck(); } }, 'image/jpeg', 0.98); } } handleConfirm() { if (this.capturedFile) { this.capture.emit(this.capturedFile); } } handleDecline() { if (this.capturedUrl) { URL.revokeObjectURL(this.capturedUrl); } this.capturedUrl = null; this.capturedFile = null; this.isConfirming = false; this.initStream(); this.cdr.markForCheck(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FaceCaptureComponent, deps: [{ token: i1.PermissionsService }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FaceCaptureComponent, isStandalone: true, selector: "bio-face-capture", inputs: { rectWidth: "rectWidth", rectHeight: "rectHeight", noShadow: "noShadow" }, outputs: { capture: "capture" }, viewQueries: [{ propertyName: "videoEl", first: true, predicate: ["videoEl"], descendants: true }], ngImport: i0, template: "<div class=\"facecapture-wrapper\" [ngClass]=\"{ 'no-shadow': noShadow }\" [style.width.px]=\"rectWidth\"\n [style.height.px]=\"rectHeight + 64\">\n <ng-container *ngIf=\"!isConfirming; else confirmTpl\">\n <div class=\"facecapture-camera-area\" [style.width.px]=\"rectWidth - 40\" [style.height.px]=\"rectHeight - 40\">\n <video #videoEl class=\"facecapture-video\" [style.width.px]=\"rectWidth - 40\" [style.height.px]=\"rectHeight - 40\"\n playsinline muted autoplay></video>\n <div class=\"facecapture-dark-overlay\" [ngClass]=\"{ 'no-shadow': noShadow }\"></div>\n <div class=\"facecapture-oval-area\">\n <span class=\"facecapture-guidance-text\">Place your face in the oval</span>\n <div class=\"facecapture-oval-dashed\"></div>\n </div>\n </div>\n <button type=\"button\" class=\"facecapture-capture-btn\" (click)=\"handleCapture()\">Capture</button>\n </ng-container>\n\n <ng-template #confirmTpl>\n <div class=\"facecapture-preview\" [style.width.px]=\"rectWidth - 25\" [style.height.px]=\"rectHeight - 25\">\n <img *ngIf=\"capturedUrl\" [src]=\"capturedUrl\" alt=\"Captured face\" class=\"facecapture-preview-img\" />\n </div>\n <div class=\"facecapture-confirm-text\">Do you want to use this photo?</div>\n <div class=\"facecapture-confirm-btns\">\n <button type=\"button\" aria-label=\"Retake\" class=\"facecapture-icon-btn decline\" (click)=\"handleDecline()\">\n \u2716\n </button>\n <button type=\"button\" aria-label=\"Confirm\" class=\"facecapture-icon-btn confirm\" (click)=\"handleConfirm()\">\n \u2714\n </button>\n </div>\n </ng-template>\n</div>", styles: [".facecapture-wrapper{background:#fff;border-radius:10px;box-shadow:0 4px 24px #0000001f;padding:20px;display:flex;flex-direction:column;align-items:center;justify-content:center}.facecapture-wrapper.no-shadow{border-radius:0;box-shadow:none}.facecapture-camera-area{position:relative;margin-bottom:20px;display:flex;align-items:center;justify-content:center}.facecapture-video{object-fit:cover;border-radius:8px;z-index:0}.facecapture-dark-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#0006;z-index:1;border-radius:8px}.facecapture-dark-overlay.no-shadow{border-radius:0}.facecapture-oval-area{position:absolute;top:18%;left:50%;transform:translate(-50%);width:72%;height:68%;z-index:2;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;pointer-events:none}.facecapture-guidance-text{color:#fff;font-size:12px;font-weight:400;margin-bottom:4px;letter-spacing:.2px;text-shadow:0 1px 4px rgba(0,0,0,.25);-webkit-user-select:none;user-select:none}.facecapture-oval-dashed{width:100%;height:100%;border:2.5px dashed #fff;border-radius:50%;box-sizing:border-box;background:transparent}.facecapture-capture-btn{padding:12px 32px;font-size:18px;border-radius:8px;border:none;background:#1976d2;color:#fff;cursor:pointer;box-shadow:0 2px 8px #0000001a;transition:background .2s}.facecapture-capture-btn:hover{background:#125a9e}.facecapture-preview{display:flex;align-items:center;justify-content:center;background:#111;border-radius:8px;margin:0 auto}.facecapture-preview .facecapture-preview-img{max-width:100%;max-height:100%;border-radius:8px}.facecapture-confirm-text{text-align:center;margin-top:16px;color:#111;font-size:17px;font-weight:500;text-shadow:0 1px 4px rgba(255,255,255,.12)}.facecapture-confirm-btns{display:flex;justify-content:center;gap:32px;margin-top:16px;width:100%}.facecapture-icon-btn{background:none;border:none;cursor:pointer;padding:8px;border-radius:50%;transition:background .2s}.facecapture-icon-btn.decline:hover{background:#d32f2f1f}.facecapture-icon-btn.confirm:hover{background:#388e3c1f}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FaceCaptureComponent, decorators: [{ type: Component, args: [{ selector: 'bio-face-capture', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"facecapture-wrapper\" [ngClass]=\"{ 'no-shadow': noShadow }\" [style.width.px]=\"rectWidth\"\n [style.height.px]=\"rectHeight + 64\">\n <ng-container *ngIf=\"!isConfirming; else confirmTpl\">\n <div class=\"facecapture-camera-area\" [style.width.px]=\"rectWidth - 40\" [style.height.px]=\"rectHeight - 40\">\n <video #videoEl class=\"facecapture-video\" [style.width.px]=\"rectWidth - 40\" [style.height.px]=\"rectHeight - 40\"\n playsinline muted autoplay></video>\n <div class=\"facecapture-dark-overlay\" [ngClass]=\"{ 'no-shadow': noShadow }\"></div>\n <div class=\"facecapture-oval-area\">\n <span class=\"facecapture-guidance-text\">Place your face in the oval</span>\n <div class=\"facecapture-oval-dashed\"></div>\n </div>\n </div>\n <button type=\"button\" class=\"facecapture-capture-btn\" (click)=\"handleCapture()\">Capture</button>\n </ng-container>\n\n <ng-template #confirmTpl>\n <div class=\"facecapture-preview\" [style.width.px]=\"rectWidth - 25\" [style.height.px]=\"rectHeight - 25\">\n <img *ngIf=\"capturedUrl\" [src]=\"capturedUrl\" alt=\"Captured face\" class=\"facecapture-preview-img\" />\n </div>\n <div class=\"facecapture-confirm-text\">Do you want to use this photo?</div>\n <div class=\"facecapture-confirm-btns\">\n <button type=\"button\" aria-label=\"Retake\" class=\"facecapture-icon-btn decline\" (click)=\"handleDecline()\">\n \u2716\n </button>\n <button type=\"button\" aria-label=\"Confirm\" class=\"facecapture-icon-btn confirm\" (click)=\"handleConfirm()\">\n \u2714\n </button>\n </div>\n </ng-template>\n</div>", styles: [".facecapture-wrapper{background:#fff;border-radius:10px;box-shadow:0 4px 24px #0000001f;padding:20px;display:flex;flex-direction:column;align-items:center;justify-content:center}.facecapture-wrapper.no-shadow{border-radius:0;box-shadow:none}.facecapture-camera-area{position:relative;margin-bottom:20px;display:flex;align-items:center;justify-content:center}.facecapture-video{object-fit:cover;border-radius:8px;z-index:0}.facecapture-dark-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:#0006;z-index:1;border-radius:8px}.facecapture-dark-overlay.no-shadow{border-radius:0}.facecapture-oval-area{position:absolute;top:18%;left:50%;transform:translate(-50%);width:72%;height:68%;z-index:2;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;pointer-events:none}.facecapture-guidance-text{color:#fff;font-size:12px;font-weight:400;margin-bottom:4px;letter-spacing:.2px;text-shadow:0 1px 4px rgba(0,0,0,.25);-webkit-user-select:none;user-select:none}.facecapture-oval-dashed{width:100%;height:100%;border:2.5px dashed #fff;border-radius:50%;box-sizing:border-box;background:transparent}.facecapture-capture-btn{padding:12px 32px;font-size:18px;border-radius:8px;border:none;background:#1976d2;color:#fff;cursor:pointer;box-shadow:0 2px 8px #0000001a;transition:background .2s}.facecapture-capture-btn:hover{background:#125a9e}.facecapture-preview{display:flex;align-items:center;justify-content:center;background:#111;border-radius:8px;margin:0 auto}.facecapture-preview .facecapture-preview-img{max-width:100%;max-height:100%;border-radius:8px}.facecapture-confirm-text{text-align:center;margin-top:16px;color:#111;font-size:17px;font-weight:500;text-shadow:0 1px 4px rgba(255,255,255,.12)}.facecapture-confirm-btns{display:flex;justify-content:center;gap:32px;margin-top:16px;width:100%}.facecapture-icon-btn{background:none;border:none;cursor:pointer;padding:8px;border-radius:50%;transition:background .2s}.facecapture-icon-btn.decline:hover{background:#d32f2f1f}.facecapture-icon-btn.confirm:hover{background:#388e3c1f}\n"] }] }], ctorParameters: () => [{ type: i1.PermissionsService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }], propDecorators: { rectWidth: [{ type: Input }], rectHeight: [{ type: Input }], noShadow: [{ type: Input }], capture: [{ type: Output }], videoEl: [{ type: ViewChild, args: ['videoEl', { static: false }] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjZS1jYXB0dXJlLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9mYWNlLWNhcHR1cmUvZmFjZS1jYXB0dXJlLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9mYWNlLWNhcHR1cmUvZmFjZS1jYXB0dXJlLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCxTQUFTLEVBRVQsWUFBWSxFQUNaLEtBQUssRUFJTCxNQUFNLEVBQ04sU0FBUyxFQUVULHVCQUF1QixFQUN4QixNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7Ozs7QUFXL0MsTUFBTSxPQUFPLG9CQUFvQjtJQWtCckI7SUFDQTtJQUNBO0lBbkJELFNBQVMsR0FBRyxHQUFHLENBQUM7SUFDaEIsVUFBVSxHQUFHLEdBQUcsQ0FBQztJQUNqQixRQUFRLEdBQUcsS0FBSyxDQUFDO0lBRWhCLE9BQU8sR0FBRyxJQUFJLFlBQVksRUFBUSxDQUFDO0lBRUosT0FBTyxDQUFnQztJQUVoRixZQUFZLEdBQUcsS0FBSyxDQUFDO0lBQ3JCLFdBQVcsR0FBa0IsSUFBSSxDQUFDO0lBQ2xDLFlBQVksR0FBZ0IsSUFBSSxDQUFDO0lBRXpCLE1BQU0sQ0FBZTtJQUVwQixrQkFBa0IsR0FBRyxDQUFDLENBQUM7SUFFaEMsWUFDVSxLQUF5QixFQUN6QixNQUFjLEVBQ2QsR0FBc0I7UUFGdEIsVUFBSyxHQUFMLEtBQUssQ0FBb0I7UUFDekIsV0FBTSxHQUFOLE1BQU0sQ0FBUTtRQUNkLFFBQUcsR0FBSCxHQUFHLENBQW1CO0lBQzVCLENBQUM7SUFFTCxLQUFLLENBQUMsUUFBUTtRQUNaLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFRCxXQUFXO1FBQ1QsSUFBSSxD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QUs7Z0JBRUksT0FBTztzQkFBaEIsTUFBTTtnQkFFa0MsT0FBTztzQkFBL0MsU0FBUzt1QkFBQyxTQUFTLEVBQUUsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgQ29tcG9uZW50LFxuICBFbGVtZW50UmVmLFxuICBFdmVudEVtaXR0ZXIsXG4gIElucHV0LFxuICBOZ1pvbmUsXG4gIE9uRGVzdHJveSxcbiAgT25Jbml0LFxuICBPdXRwdXQsXG4gIFZpZXdDaGlsZCxcbiAgQ2hhbmdlRGV0ZWN0b3JSZWYsXG4gIENoYW5nZURldGVjdGlvblN0cmF0ZWd5XG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQ29tbW9uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcbmltcG9ydCB7IFBlcm1pc3Npb25zU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3Blcm1pc3Npb25zLnNlcnZpY2UnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdiaW8tZmFjZS1jYXB0dXJlJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW0NvbW1vbk1vZHVsZV0sXG4gIHRlbXBsYXRlVXJsOiAnLi9mYWNlLWNhcHR1cmUuY29tcG9uZW50Lmh0bWwnLFxuICBzdHlsZVVybHM6IFsnLi9mYWNlLWNhcHR1cmUuY29tcG9uZW50LnNjc3MnXSxcbiAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG59KVxuZXhwb3J0IGNsYXNzIEZhY2VDYXB0dXJlQ29tcG9uZW50IGltcGxlbWVudHMgT25Jbml0LCBPbkRlc3Ryb3kge1xuICBASW5wdXQoKSByZWN0V2lkdGggPSAzNjA7XG4gIEBJbnB1dCgpIHJlY3RIZWlnaHQgPSA1NzY7XG4gIEBJbnB1dCgpIG5vU2hhZG93ID0gZmFsc2U7XG5cbiAgQE91dHB1dCgpIGNhcHR1cmUgPSBuZXcgRXZlbnRFbWl0dGVyPEZpbGU+KCk7XG5cbiAgQFZpZXdDaGlsZCgndmlkZW9FbCcsIHsgc3RhdGljOiBmYWxzZSB9KSB2aWRlb0VsITogRWxlbWVudFJlZjxIVE1MVmlkZW9FbGVtZW50PjtcblxuICBpc0NvbmZpcm1pbmcgPSBmYWxzZTtcbiAgY2FwdHVyZWRVcmw6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuICBjYXB0dXJlZEZpbGU6IEZpbGUgfCBudWxsID0gbnVsbDtcblxuICBwcml2YXRlIHN0cmVhbT86IE1lZGlhU3RyZWFtO1xuXG4gIHJlYWRvbmx5IFFVQUxJVFlfTVVMVElQTElFUiA9IDM7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSBwZXJtczogUGVybWlzc2lvbnNTZXJ2aWNlLFxuICAgIHByaXZhdGUgbmdab25lOiBOZ1pvbmUsXG4gICAgcHJpdmF0ZSBjZHI6IENoYW5nZURldGVjdG9yUmVmLFxuICApIHsgfVxuXG4gIGFzeW5jIG5nT25Jbml0KCkge1xuICAgIGF3YWl0IHRoaXMuaW5pdFN0cmVhbSgpO1xuICB9XG5cbiAgbmdPbkRlc3Ryb3koKSB7XG4gICAgdGhpcy5zdG9wU3RyZWFtKCk7XG4gICAgaWYgKHRoaXMuY2FwdHVyZWRVcmwpIFVSTC5yZXZva2VPYmplY3RVUkwodGhpcy5jYXB0dXJlZFVybCk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGluaXRTdHJlYW0oKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHsgc3RyZWFtLCBncmFudGVkIH0gPSBhd2FpdCB0aGlzLnBlcm1zLnJlcXVlc3RDYW1lcmEoe1xuICAgICAgICB3aWR0aDogdGhpcy5yZWN0V2lkdGgsXG4gICAgICAgIGhlaWdodDogdGhpcy5yZWN0SGVpZ2h0LFxuICAgICAgfSk7XG4gICAgICBpZiAoIWdyYW50ZWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy5zdHJlYW0gPSBzdHJlYW07XG4gICAgICBjb25zb2xlLmxvZygndGhpcy5zdHJlYW0gPSBzdHJlYW0nKTtcbiAgICAgIHRoaXMubmdab25lLnJ1bk91dHNpZGVBbmd1bGFyKCgpID0+IHtcbiAgICAgICAgaWYgKHRoaXMudmlkZW9FbD8ubmF0aXZlRWxlbWVudCkge1xuICAgICAgICAgIGNvbnNvbGUubG9nKCduYXRpdmVFbGVtZW50Jyk7XG4gICAgICAgICAgdGhpcy52aWRlb0VsLm5hdGl2ZUVsZW1lbnQuc3JjT2JqZWN0ID0gdGhpcy5zdHJlYW0hO1xuICAgICAgICAgIHRoaXMudmlkZW9FbC5uYXRpdmVFbGVtZW50Lm9ubG9hZGVkbWV0YWRhdGEgPSAoKSA9PlxuICAgICAgICAgICAgdGhpcy52aWRlb0VsLm5hdGl2ZUVsZW1lbnQucGxheSgpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ0NhbWVyYSBhY2Nlc3MgZXJyb3I6JywgZXJyKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIHN0b3BTdHJlYW0oKSB7XG4gICAgdGhpcy5zdHJlYW0/LmdldFRyYWNrcygpLmZvckVhY2godHJhY2sgPT4gdHJhY2suc3RvcCgpKTtcbiAgICB0aGlzLnN0cmVhbSA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGhhbmRsZUNhcHR1cmUoKSB7XG4gICAgY29uc3QgdmlkZW8gPSB0aGlzLnZpZGVvRWw/Lm5hdGl2ZUVsZW1lbnQ7XG4gICAgaWYgKCF2aWRlbykgcmV0dXJuO1xuXG4gICAgY29uc3QgY2FudmFzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnY2FudmFzJyk7XG4gICAgY2FudmFzLndpZHRoID0gdGhpcy5yZWN0V2lkdGggKiB0aGlzLlFVQUxJVFlfTVVMVElQTElFUjtcbiAgICBjYW52YXMuaGVpZ2h0ID0gdGhpcy5yZWN0SGVpZ2h0ICogdGhpcy5RVUFMSVRZX01VTFRJUExJRVI7XG4gICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoJzJkJyk7XG5cbiAgICBpZiAoY3R4KSB7XG4gICAgICBjdHguZHJhd0ltYWdlKFxuICAgICAgICB2aWRlbyxcbiAgICAgICAgMCxcbiAgICAgICAgMCxcbiAgICAgICAgdGhpcy5yZWN0V2lkdGgsXG4gICAgICAgIHRoaXMucmVjdEhlaWdodCxcbiAgICAgICAgMCxcbiAgICAgICAgMCxcbiAgICAgICAgdGhpcy5yZWN0V2lkdGggKiB0aGlzLlFVQUxJVFlfTVVMVElQTElFUixcbiAgICAgICAgdGhpcy5yZWN0SGVpZ2h0ICogdGhpcy5RVUFMSVRZX01VTFRJUExJRVIsXG4gICAgICApO1xuICAgICAgY2FudmFzLnRvQmxvYihibG9iID0+IHtcbiAgICAgICAgaWYgKGJsb2IpIHtcbiAgICAgICAgICBjb25zdCBmaWxlID0gbmV3IEZpbGUoW2Jsb2JdLCAnZmFjZS5qcGcnLCB7IHR5cGU6ICdpbWFnZS9qcGVnJyB9KTtcbiAgICAgICAgICB0aGlzLmNhcHR1cmVkRmlsZSA9IGZpbGU7XG4gICAgICAgICAgdGhpcy5jYXB0dXJlZFVybCA9IFVSTC5jcmVhdGVPYmplY3RVUkwoYmxvYik7XG4gICAgICAgICAgdGhpcy5pc0NvbmZpcm1pbmcgPSB0cnVlO1xuICAgICAgICAgIHRoaXMuc3RvcFN0cmVhbSgpO1xuICAgICAgICAgIHRoaXMuY2RyLm1hcmtGb3JDaGVjaygpO1xuICAgICAgICB9XG4gICAgICB9LCAnaW1hZ2UvanBlZycsIDAuOTgpO1xuICAgIH1cbiAgfVxuXG4gIGhhbmRsZUNvbmZpcm0oKSB7XG4gICAgaWYgKHRoaXMuY2FwdHVyZWRGaWxlKSB7XG4gICAgICB0aGlzLmNhcHR1cmUuZW1pdCh0aGlzLmNhcHR1cmVkRmlsZSk7XG4gICAgfVxuICB9XG5cbiAgaGFuZGxlRGVjbGluZSgpIHtcbiAgICBpZiAodGhpcy5jYXB0dXJlZFVybCkge1xuICAgICAgVVJMLnJldm9rZU9iamVjdFVSTCh0aGlzLmNhcHR1cmVkVXJsKTtcbiAgICB9XG4gICAgdGhpcy5jYXB0dXJlZFVybCA9IG51bGw7XG4gICAgdGhpcy5jYXB0dXJlZEZpbGUgPSBudWxsO1xuICAgIHRoaXMuaXNDb25maXJtaW5nID0gZmFsc2U7XG4gICAgdGhpcy5pbml0U3RyZWFtKCk7XG4gICAgdGhpcy5jZHIubWFya0ZvckNoZWNrKCk7XG4gIH1cbn0iLCI8ZGl2IGNsYXNzPVwiZmFjZWNhcHR1cmUtd3JhcHBlclwiIFtuZ0NsYXNzXT1cInsgJ25vLXNoYWRvdyc6IG5vU2hhZG93IH1cIiBbc3R5bGUud2lkdGgucHhdPVwicmVjdFdpZHRoXCJcbiAgW3N0eWxlLmhlaWdodC5weF09XCJyZWN0SGVpZ2h0ICsgNjRcIj5cbiAgPG5nLWNvbnRhaW5lciAqbmdJZj1cIiFpc0NvbmZpcm1pbmc7IGVsc2UgY29uZmlybVRwbFwiPlxuICAgIDxkaXYgY2xhc3M9XCJmYWNlY2FwdHVyZS1jYW1lcmEtYXJlYVwiIFtzdHlsZS53aWR0aC5weF09XCJyZWN0V2lkdGggLSA0MFwiIFtzdHlsZS5oZWlnaHQucHhdPVwicmVjdEhlaWdodCAtIDQwXCI+XG4gICAgICA8dmlkZW8gI3ZpZGVvRWwgY2xhc3M9XCJmYWNlY2FwdHVyZS12aWRlb1wiIFtzdHlsZS53aWR0aC5weF09XCJyZWN0V2lkdGggLSA0MFwiIFtzdHlsZS5oZWlnaHQucHhdPVwicmVjdEhlaWdodCAtIDQwXCJcbiAgICAgICAgcGxheXNpbmxpbmUgbXV0ZWQgYXV0b3BsYXk+PC92aWRlbz5cbiAgICAgIDxkaXYgY2xhc3M9XCJmYWNlY2FwdHVyZS1kYXJrLW92ZXJsYXlcIiBbbmdDbGFzc109XCJ7ICduby1zaGFkb3cnOiBub1NoYWRvdyB9XCI+PC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwiZmFjZWNhcHR1cmUtb3ZhbC1hcmVhXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmFjZWNhcHR1cmUtZ3VpZGFuY2UtdGV4dFwiPlBsYWNlIHlvdXIgZmFjZSBpbiB0aGUgb3ZhbDwvc3Bhbj5cbiAgICAgICAgPGRpdiBjbGFzcz1cImZhY2VjYXB0dXJlLW92YWwtZGFzaGVkXCI+PC9kaXY+XG4gICAgICA8L2Rpdj5cbiAgICA8L2Rpdj5cbiAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiBjbGFzcz1cImZhY2VjYXB0dXJlLWNhcHR1cmUtYnRuXCIgKGNsaWNrKT1cImhhbmRsZUNhcHR1cmUoKVwiPkNhcHR1cmU8L2J1dHRvbj5cbiAgPC9uZy1jb250YWluZXI+XG5cbiAgPG5nLXRlbXBsYXRlICNjb25maXJtVHBsPlxuICAgIDxkaXYgY2xhc3M9XCJmYWNlY2FwdHVyZS1wcmV2aWV3XCIgW3N0eWxlLndpZHRoLnB4XT1cInJlY3RXaWR0aCAtIDI1XCIgW3N0eWxlLmhlaWdodC5weF09XCJyZWN0SGVpZ2h0IC0gMjVcIj5cbiAgICAgIDxpbWcgKm5nSWY9XCJjYXB0dXJlZFVybFwiIFtzcmNdPVwiY2FwdHVyZWRVcmxcIiBhbHQ9XCJDYXB0dXJlZCBmYWNlXCIgY2xhc3M9XCJmYWNlY2FwdHVyZS1wcmV2aWV3LWltZ1wiIC8+XG4gICAgPC9kaXY+XG4gICAgPGRpdiBjbGFzcz1cImZhY2VjYXB0dXJlLWNvbmZpcm0tdGV4dFwiPkRvIHlvdSB3YW50IHRvIHVzZSB0aGlzIHBob3RvPzwvZGl2PlxuICAgIDxkaXYgY2xhc3M9XCJmYWNlY2FwdHVyZS1jb25maXJtLWJ0bnNcIj5cbiAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGFyaWEtbGFiZWw9XCJSZXRha2VcIiBjbGFzcz1cImZhY2VjYXB0dXJlLWljb24tYnRuIGRlY2xpbmVcIiAoY2xpY2spPVwiaGFuZGxlRGVjbGluZSgpXCI+XG4gICAgICAgIOKcllxuICAgICAgPC9idXR0b24+XG4gICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiBhcmlhLWxhYmVsPVwiQ29uZmlybVwiIGNsYXNzPVwiZmFjZWNhcHR1cmUtaWNvbi1idG4gY29uZmlybVwiIChjbGljayk9XCJoYW5kbGVDb25maXJtKClcIj5cbiAgICAgICAg4pyUXG4gICAgICA8L2J1dHRvbj5cbiAgICA8L2Rpdj5cbiAgPC9uZy10ZW1wbGF0ZT5cbjwvZGl2PiJdfQ==