UNPKG

ng-speed-test

Version:

Angular library for testing internet connection speed

283 lines 43.7 kB
import { Injectable } from '@angular/core'; import { fromEvent, merge, Observable, of, throwError } from 'rxjs'; import { map, mergeMap, catchError, timeout, switchMap, startWith } from 'rxjs/operators'; import { SpeedTestFileModel } from '../models/speed-test-file.model'; import { SpeedTestSettingsModel } from '../models/speed-test-settings.model'; import { SpeedTestResultsModel } from '../models/speed-test-results.model'; import * as i0 from "@angular/core"; export class SpeedTestService { constructor() { this.DEFAULT_TIMEOUT = 15000; // Reduced from 30s to 15s this.OFFLINE_CHECK_TIMEOUT = 3000; // Quick offline check } applyCacheBuster(path) { const separator = path.includes('?') ? '&' : '?'; return `${path}${separator}cache_bust=${Date.now()}_${Math.random()}`; } /** * Quick connectivity check before running speed test */ checkConnectivity() { // First check navigator.onLine if (!navigator.onLine) { return of(false); } // Then do a quick network request to verify actual connectivity return new Observable(observer => { const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); observer.next(false); observer.complete(); }, this.OFFLINE_CHECK_TIMEOUT); // Use a small, fast endpoint for connectivity check fetch('https://httpbin.org/get?minimal=true', { method: 'HEAD', mode: 'no-cors', signal: controller.signal, cache: 'no-cache' }) .then(() => { clearTimeout(timeoutId); observer.next(true); observer.complete(); }) .catch(() => { clearTimeout(timeoutId); observer.next(false); observer.complete(); }); return () => { clearTimeout(timeoutId); controller.abort(); }; }); } downloadTest(settings, allResults = []) { // Quick connectivity check first return this.checkConnectivity().pipe(switchMap(isConnected => { if (!isConnected) { return throwError(() => new Error('No internet connection available')); } return new Observable(observer => { const testResult = new SpeedTestResultsModel(settings.file.size); const abortController = new AbortController(); let filePath = settings.file.path; if (settings.file.shouldBustCache) { filePath = this.applyCacheBuster(filePath); } testResult.start(); // Set a more aggressive timeout for the fetch request const fetchTimeout = setTimeout(() => { abortController.abort(); testResult.error(); observer.next(testResult); observer.complete(); }, this.DEFAULT_TIMEOUT); fetch(filePath, { method: 'GET', signal: abortController.signal, cache: 'no-cache' }) .then(response => { clearTimeout(fetchTimeout); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.blob(); }) .then(() => { testResult.end(); observer.next(testResult); observer.complete(); }) .catch(error => { clearTimeout(fetchTimeout); console.warn('Speed test download failed:', error); testResult.error(); const delay = settings.iterations !== 1 ? settings.retryDelay : 0; setTimeout(() => { observer.next(testResult); observer.complete(); }, delay); }); // Cleanup function return () => { clearTimeout(fetchTimeout); abortController.abort(); }; }); }), mergeMap((testResult) => { allResults.push(testResult); if (settings.iterations === 1) { // Calculate average speed from all valid results const validResults = allResults.filter(result => result.speedBps > 0); if (validResults.length === 0) { return throwError(() => new Error('All speed test iterations failed - no internet connection or server unreachable')); } const totalSpeed = validResults.reduce((sum, result) => sum + result.speedBps, 0); const averageSpeed = totalSpeed / validResults.length; return of(averageSpeed); } else { settings.iterations--; return this.downloadTest(settings, allResults); } })); } validateSettings(settings) { if (!settings.file?.path) { throw new Error('ng-speed-test: File path is required'); } if (!settings.file?.size || settings.file.size <= 0) { throw new Error('ng-speed-test: Valid file size is required'); } if (settings.iterations && settings.iterations < 1) { throw new Error('ng-speed-test: Iterations must be at least 1'); } } /** * Get internet speed in bits per second (bps) * Fails fast if no internet connection is available */ getBps(customSettings) { return new Observable(observer => { // Check connectivity immediately if (!navigator.onLine) { observer.error(new Error('No internet connection - browser reports offline')); return; } // Small delay to ensure proper initialization setTimeout(() => { // Create settings with proper merging const defaultSettings = new SpeedTestSettingsModel(); const settings = this.mergeSettings(defaultSettings, customSettings); try { this.validateSettings(settings); this.downloadTest(settings).subscribe({ next: (speedBps) => { observer.next(speedBps); observer.complete(); }, error: (error) => { observer.error(error); } }); } catch (error) { observer.error(error); } }, 1); }); } /** * Properly merge custom settings with defaults */ mergeSettings(defaultSettings, customSettings) { if (!customSettings) { return defaultSettings; } const mergedSettings = new SpeedTestSettingsModel(); // Merge iterations mergedSettings.iterations = customSettings.iterations !== undefined ? customSettings.iterations : defaultSettings.iterations; // Merge retryDelay mergedSettings.retryDelay = customSettings.retryDelay !== undefined ? customSettings.retryDelay : defaultSettings.retryDelay; // Merge file settings if (customSettings.file) { mergedSettings.file = new SpeedTestFileModel(); // Merge file path mergedSettings.file.path = customSettings.file.path !== undefined ? customSettings.file.path : defaultSettings.file.path; // Merge file size mergedSettings.file.size = customSettings.file.size !== undefined ? customSettings.file.size : defaultSettings.file.size; // Merge shouldBustCache mergedSettings.file.shouldBustCache = customSettings.file.shouldBustCache !== undefined ? customSettings.file.shouldBustCache : defaultSettings.file.shouldBustCache; } else { mergedSettings.file = defaultSettings.file; } return mergedSettings; } /** * Get internet speed in kilobits per second (Kbps) */ getKbps(settings) { return this.getBps(settings).pipe(map(bps => bps / 1024)); } /** * Get internet speed in megabits per second (Mbps) */ getMbps(settings) { return this.getKbps(settings).pipe(map(kbps => kbps / 1024)); } /** * Get comprehensive speed test results with fast failure for offline scenarios */ getSpeedTestResult(settings) { const startTime = Date.now(); return this.getBps(settings).pipe(map(bps => ({ bps, kbps: bps / 1024, mbps: bps / (1024 * 1024), duration: (Date.now() - startTime) / 1000 })), timeout(this.DEFAULT_TIMEOUT + 5000), // Overall timeout slightly longer than individual request timeout catchError(error => { if (error.name === 'TimeoutError') { return throwError(() => new Error('Speed test timed out - please check your internet connection')); } return throwError(() => error); })); } /** * Check if the browser is online with enhanced detection */ isOnline() { return merge(fromEvent(window, 'offline').pipe(map(() => false)), fromEvent(window, 'online').pipe(map(() => true)), of(navigator.onLine)).pipe(startWith(navigator.onLine), // Verify actual connectivity for online state switchMap(browserOnline => { if (!browserOnline) { return of(false); } // Quick connectivity verification return this.checkConnectivity(); })); } /** * Monitor network connection status with enhanced detection */ getNetworkStatus() { const getConnectionInfo = () => { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; return { isOnline: navigator.onLine, effectiveType: connection?.effectiveType, downlink: connection?.downlink }; }; return merge(fromEvent(window, 'offline').pipe(map(() => ({ ...getConnectionInfo(), isOnline: false }))), fromEvent(window, 'online').pipe(map(() => getConnectionInfo()), // Verify actual connectivity when browser reports online switchMap(info => this.checkConnectivity().pipe(map(actuallyOnline => ({ ...info, isOnline: actuallyOnline }))))), of(getConnectionInfo()).pipe(switchMap(info => info.isOnline ? this.checkConnectivity().pipe(map(actuallyOnline => ({ ...info, isOnline: actuallyOnline }))) : of(info)))); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SpeedTestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SpeedTestService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SpeedTestService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BlZWQtdGVzdC5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3NlcnZpY2VzL3NwZWVkLXRlc3Quc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzNDLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxFQUFFLEVBQUUsVUFBVSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3BFLE9BQU8sRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRTFGLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBQzdFLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLG9DQUFvQyxDQUFDOztBQVkzRSxNQUFNLE9BQU8sZ0JBQWdCO0lBSXpCO1FBSGlCLG9CQUFlLEdBQUcsS0FBSyxDQUFDLENBQUMsMEJBQTBCO1FBQ25ELDBCQUFxQixHQUFHLElBQUksQ0FBQyxDQUFDLHNCQUFzQjtJQUV0RCxDQUFDO0lBRVIsZ0JBQWdCLENBQUMsSUFBWTtRQUNqQyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztRQUNqRCxPQUFPLEdBQUcsSUFBSSxHQUFHLFNBQVMsY0FBYyxJQUFJLENBQUMsR0FBRyxFQUFFLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUM7SUFDMUUsQ0FBQztJQUVEOztPQUVHO0lBQ0ssaUJBQWlCO1FBQ3JCLCtCQUErQjtRQUMvQixJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0s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RUFBRSxjQUFjLEVBQUUsQ0FBQyxDQUFDLENBQ2pFLENBQ0osQ0FDSixFQUNELEVBQUUsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUN4QixTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FDYixJQUFJLENBQUMsUUFBUTtZQUNULENBQUMsQ0FBQyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQyxJQUFJLENBQzNCLEdBQUcsQ0FBQyxjQUFjLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxHQUFHLElBQUksRUFBRSxRQUFRLEVBQUUsY0FBYyxFQUFFLENBQUMsQ0FBQyxDQUNqRTtZQUNELENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLENBQ2pCLENBQ0osQ0FDSixDQUFDO0lBQ04sQ0FBQzsrR0FsVlEsZ0JBQWdCO21IQUFoQixnQkFBZ0IsY0FGYixNQUFNOzs0RkFFVCxnQkFBZ0I7a0JBSDVCLFVBQVU7bUJBQUM7b0JBQ1IsVUFBVSxFQUFFLE1BQU07aUJBQ3JCIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBmcm9tRXZlbnQsIG1lcmdlLCBPYnNlcnZhYmxlLCBvZiwgdGhyb3dFcnJvciB9IGZyb20gJ3J4anMnO1xyXG5pbXBvcnQgeyBtYXAsIG1lcmdlTWFwLCBjYXRjaEVycm9yLCB0aW1lb3V0LCBzd2l0Y2hNYXAsIHN0YXJ0V2l0aCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcclxuXHJcbmltcG9ydCB7IFNwZWVkVGVzdEZpbGVNb2RlbCB9IGZyb20gJy4uL21vZGVscy9zcGVlZC10ZXN0LWZpbGUubW9kZWwnO1xyXG5pbXBvcnQgeyBTcGVlZFRlc3RTZXR0aW5nc01vZGVsIH0gZnJvbSAnLi4vbW9kZWxzL3NwZWVkLXRlc3Qtc2V0dGluZ3MubW9kZWwnO1xyXG5pbXBvcnQgeyBTcGVlZFRlc3RSZXN1bHRzTW9kZWwgfSBmcm9tICcuLi9tb2RlbHMvc3BlZWQtdGVzdC1yZXN1bHRzLm1vZGVsJztcclxuXHJcbmV4cG9ydCBpbnRlcmZhY2UgU3BlZWRUZXN0UmVzdWx0IHtcclxuICAgIGJwczogbnVtYmVyO1xyXG4gICAga2JwczogbnVtYmVyO1xyXG4gICAgbWJwczogbnVtYmVyO1xyXG4gICAgZHVyYXRpb246IG51bWJlcjtcclxufVxyXG5cclxuQEluamVjdGFibGUoe1xyXG4gICAgcHJvdmlkZWRJbjogJ3Jvb3QnXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBTcGVlZFRlc3RTZXJ2aWNlIHtcclxuICAgIHByaXZhdGUgcmVhZG9ubHkgREVGQVVMVF9USU1FT1VUID0gMTUwMDA7IC8vIFJlZHVjZWQgZnJvbSAzMHMgdG8gMTVzXHJcbiAgICBwcml2YXRlIHJlYWRvbmx5IE9GRkxJTkVfQ0hFQ0tfVElNRU9VVCA9IDMwMDA7IC8vIFF1aWNrIG9mZmxpbmUgY2hlY2tcclxuXHJcbiAgICBjb25zdHJ1Y3RvcigpIHt9XHJcblxyXG4gICAgcHJpdmF0ZSBhcHBseUNhY2hlQnVzdGVyKHBhdGg6IHN0cmluZyk6IHN0cmluZyB7XHJcbiAgICAgICAgY29uc3Qgc2VwYXJhdG9yID0gcGF0aC5pbmNsdWRlcygnPycpID8gJyYnIDogJz8nO1xyXG4gICAgICAgIHJldHVybiBgJHtwYXRofSR7c2VwYXJhdG9yfWNhY2hlX2J1c3Q9JHtEYXRlLm5vdygpfV8ke01hdGgucmFuZG9tKCl9YDtcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIFF1aWNrIGNvbm5lY3Rpdml0eSBjaGVjayBiZWZvcmUgcnVubmluZyBzcGVlZCB0ZXN0XHJcbiAgICAgKi9cclxuICAgIHByaXZhdGUgY2hlY2tDb25uZWN0aXZpdHkoKTogT2JzZXJ2YWJsZTxib29sZWFuPiB7XHJcbiAgICAgICAgLy8gRmlyc3QgY2hlY2sgbmF2aWdhdG9yLm9uTGluZVxyXG4gICAgICAgIGlmICghbmF2aWdhdG9yLm9uTGluZSkge1xyXG4gICAgICAgICAgICByZXR1cm4gb2YoZmFsc2UpO1xyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgLy8gVGhlbiBkbyBhIHF1aWNrIG5ldHdvcmsgcmVxdWVzdCB0byB2ZXJpZnkgYWN0dWFsIGNvbm5lY3Rpdml0eVxyXG4gICAgICAgIHJldHVybiBuZXcgT2JzZXJ2YWJsZTxib29sZWFuPihvYnNlcnZlciA9PiB7XHJcbiAgICAgICAgICAgIGNvbnN0IGNvbnRyb2xsZXIgPSBuZXcgQWJvcnRDb250cm9sbGVyKCk7XHJcbiAgICAgICAgICAgIGNvbnN0IHRpbWVvdXRJZCA9IHNldFRpbWVvdXQoKCkgPT4ge1xyXG4gICAgICAgICAgICAgICAgY29udHJvbGxlci5hYm9ydCgpO1xyXG4gICAgICAgICAgICAgICAgb2JzZXJ2ZXIubmV4dChmYWxzZSk7XHJcbiAgICAgICAgICAgICAgICBvYnNlcnZlci5jb21wbGV0ZSgpO1xyXG4gICAgICAgICAgICB9LCB0aGlzLk9GRkxJTkVfQ0hFQ0tfVElNRU9VVCk7XHJcblxyXG4gICAgICAgICAgICAvLyBVc2UgYSBzbWFsbCwgZmFzdCBlbmRwb2ludCBmb3IgY29ubmVjdGl2aXR5IGNoZWNrXHJcbiAgICAgICAgICAgIGZldGNoKCdodHRwczovL2h0dHBiaW4ub3JnL2dldD9taW5pbWFsPXRydWUnLCB7XHJcbiAgICAgICAgICAgICAgICBtZXRob2Q6ICdIRUFEJyxcclxuICAgICAgICAgICAgICAgIG1vZGU6ICduby1jb3JzJyxcclxuICAgICAgICAgICAgICAgIHNpZ25hbDogY29udHJvbGxlci5zaWduYWwsXHJcbiAgICAgICAgICAgICAgICBjYWNoZTogJ25vLWNhY2hlJ1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICAgICAgLnRoZW4oKCkgPT4ge1xyXG4gICAgICAgICAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpO1xyXG4gICAgICAgICAgICAgICAgICAgIG9ic2VydmVyLm5leHQodHJ1ZSk7XHJcbiAgICAgICAgICAgICAgICAgICAgb2JzZXJ2ZXIuY29tcGxldGUoKTtcclxuICAgICAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgICAgICAuY2F0Y2goKCkgPT4ge1xyXG4gICAgICAgICAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpO1xyXG4gICAgICAgICAgICAgICAgICAgIG9ic2VydmVyLm5leHQoZmFsc2UpO1xyXG4gICAgICAgICAgICAgICAgICAgIG9ic2VydmVyLmNvbXBsZXRlKCk7XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuXHJcbiAgICAgICAgICAgIHJldHVybiAoKSA9PiB7XHJcbiAgICAgICAgICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dElkKTtcclxuICAgICAgICAgICAgICAgIGNvbnRyb2xsZXIuYWJvcnQoKTtcclxuICAgICAgICAgICAgfTtcclxuICAgICAgICB9KTtcclxuICAgIH1cclxuXHJcbiAgICBwcml2YXRlIGRvd25sb2FkVGVzdChzZXR0aW5nczogU3BlZWRUZXN0U2V0dGluZ3NNb2RlbCwgYWxsUmVzdWx0czogU3BlZWRUZXN0UmVzdWx0c01vZGVsW10gPSBbXSk6IE9ic2VydmFibGU8bnVtYmVyPiB7XHJcbiAgICAgICAgLy8gUXVpY2sgY29ubmVjdGl2aXR5IGNoZWNrIGZpcnN0XHJcbiAgICAgICAgcmV0dXJuIHRoaXMuY2hlY2tDb25uZWN0aXZpdHkoKS5waXBlKFxyXG4gICAgICAgICAgICBzd2l0Y2hNYXAoaXNDb25uZWN0ZWQgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKCFpc0Nvbm5lY3RlZCkge1xyXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB0aHJvd0Vycm9yKCgpID0+IG5ldyBFcnJvcignTm8gaW50ZXJuZXQgY29ubmVjdGlvbiBhdmFpbGFibGUnKSk7XHJcbiAgICAgICAgICAgICAgICB9XHJcblxyXG4gICAgICAgICAgICAgICAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlPFNwZWVkVGVzdFJlc3VsdHNNb2RlbD4ob2JzZXJ2ZXIgPT4ge1xyXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHRlc3RSZXN1bHQgPSBuZXcgU3BlZWRUZXN0UmVzdWx0c01vZGVsKHNldHRpbmdzLmZpbGUhLnNpemUpO1xyXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGFib3J0Q29udHJvbGxlciA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcclxuXHJcbiAgICAgICAgICAgICAgICAgICAgbGV0IGZpbGVQYXRoID0gc2V0dGluZ3MuZmlsZSEucGF0aDtcclxuICAgICAgICAgICAgICAgICAgICBpZiAoc2V0dGluZ3MuZmlsZSEuc2hvdWxkQnVzdENhY2hlKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVQYXRoID0gdGhpcy5hcHBseUNhY2hlQnVzdGVyKGZpbGVQYXRoKTtcclxuICAgICAgICAgICAgICAgICAgICB9XHJcblxyXG4gICAgICAgICAgICAgICAgICAgIHRlc3RSZXN1bHQuc3RhcnQoKTtcclxuXHJcbiAgICAgICAgICAgICAgICAgICAgLy8gU2V0IGEgbW9yZSBhZ2dyZXNzaXZlIHRpbWVvdXQgZm9yIHRoZSBmZXRjaCByZXF1ZXN0XHJcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgZmV0Y2hUaW1lb3V0ID0gc2V0VGltZW91dCgoKSA9PiB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGFib3J0Q29udHJvbGxlci5hYm9ydCgpO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB0ZXN0UmVzdWx0LmVycm9yKCk7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIG9ic2VydmVyLm5leHQodGVzdFJlc3VsdCk7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIG9ic2VydmVyLmNvbXBsZXRlKCk7XHJcbiAgICAgICAgICAgICAgICAgICAgfSwgdGhpcy5ERUZBVUxUX1RJTUVPVVQpO1xyXG5cclxuICAgICAgICAgICAgICAgICAgICBmZXRjaChmaWxlUGF0aCwge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q6ICdHRVQnLFxyXG4gICAgICAgICAgICAgICAgICAgICAgICBzaWduYWw6IGFib3J0Q29udHJvbGxlci5zaWduYWwsXHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGNhY2hlOiAnbm8tY2FjaGUnXHJcbiAgICAgICAgICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KGZldGNoVGltZW91dCk7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBIVFRQICR7cmVzcG9uc2Uuc3RhdHVzfTogJHtyZXNwb25zZS5zdGF0dXNUZXh0fWApO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmJsb2IoKTtcclxuICAgICAgICAgICAgICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgICAgICAgICAgICAgLnRoZW4oKCkgPT4ge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdFJlc3VsdC5lbmQoKTtcclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9ic2VydmVyLm5leHQodGVzdFJlc3VsdCk7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBvYnNlcnZlci5jb21wbGV0ZSgpO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICAgICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4ge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KGZldGNoVGltZW91dCk7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oJ1NwZWVkIHRlc3QgZG93bmxvYWQgZmFpbGVkOicsIGVycm9yKTtcclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RSZXN1bHQuZXJyb3IoKTtcclxuXHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBkZWxheSA9IHNldHRpbmdzLml0ZXJhdGlvbnMgIT09IDEgPyBzZXR0aW5ncy5yZXRyeURlbGF5ISA6IDA7XHJcblxyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2JzZXJ2ZXIubmV4dCh0ZXN0UmVzdWx0KTtcclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvYnNlcnZlci5jb21wbGV0ZSgpO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwgZGVsYXkpO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB9KTtcclxuXHJcbiAgICAgICAgICAgICAgICAgICAgLy8gQ2xlYW51cCBmdW5jdGlvblxyXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiAoKSA9PiB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIGNsZWFyVGltZW91dChmZXRjaFRpbWVvdXQpO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICBhYm9ydENvbnRyb2xsZXIuYWJvcnQoKTtcclxuICAgICAgICAgICAgICAgICAgICB9O1xyXG4gICAgICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgICAgIH0pLFxyXG4gICAgICAgICAgICBtZXJnZU1hcCgodGVzdFJlc3VsdDogU3BlZWRUZXN0UmVzdWx0c01vZGVsKSA9PiB7XHJcbiAgICAgICAgICAgICAgICBhbGxSZXN1bHRzLnB1c2godGVzdFJlc3VsdCk7XHJcblxyXG4gICAgICAgICAgICAgICAgaWYgKHNldHRpbmdzLml0ZXJhdGlvbnMgPT09IDEpIHtcclxuICAgICAgICAgICAgICAgICAgICAvLyBDYWxjdWxhdGUgYXZlcmFnZSBzcGVlZCBmcm9tIGFsbCB2YWxpZCByZXN1bHRzXHJcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgdmFsaWRSZXN1bHRzID0gYWxsUmVzdWx0cy5maWx0ZXIocmVzdWx0ID0+IHJlc3VsdC5zcGVlZEJwcyA+IDApO1xyXG5cclxuICAgICAgICAgICAgICAgICAgICBpZiAodmFsaWRSZXN1bHRzLmxlbmd0aCA9PT0gMCkge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGhyb3dFcnJvcigoKSA9PiBuZXcgRXJyb3IoJ0FsbCBzcGVlZCB0ZXN0IGl0ZXJhdGlvbnMgZmFpbGVkIC0gbm8gaW50ZXJuZXQgY29ubmVjdGlvbiBvciBzZXJ2ZXIgdW5yZWFjaGFibGUnKSk7XHJcbiAgICAgICAgICAgICAgICAgICAgfVxyXG5cclxuICAgICAgICAgICAgICAgICAgICBjb25zdCB0b3RhbFNwZWVkID0gdmFsaWRSZXN1bHRzLnJlZHVjZSgoc3VtLCByZXN1bHQpID0+IHN1bSArIHJlc3VsdC5zcGVlZEJwcywgMCk7XHJcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgYXZlcmFnZVNwZWVkID0gdG90YWxTcGVlZCAvIHZhbGlkUmVzdWx0cy5sZW5ndGg7XHJcblxyXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBvZihhdmVyYWdlU3BlZWQpO1xyXG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgICAgICAgICBzZXR0aW5ncy5pdGVyYXRpb25zIS0tO1xyXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmRvd25sb2FkVGVzdChzZXR0aW5ncywgYWxsUmVzdWx0cyk7XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgKTtcclxuICAgIH1cclxuXHJcbiAgICBwcml2YXRlIHZhbGlkYXRlU2V0dGluZ3Moc2V0dGluZ3M6IFNwZWVkVGVzdFNldHRpbmdzTW9kZWwpOiB2b2lkIHtcclxuICAgICAgICBpZiAoIXNldHRpbmdzLmZpbGU/LnBhdGgpIHtcclxuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCduZy1zcGVlZC10ZXN0OiBGaWxlIHBhdGggaXMgcmVxdWlyZWQnKTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIGlmICghc2V0dGluZ3MuZmlsZT8uc2l6ZSB8fCBzZXR0aW5ncy5maWxlLnNpemUgPD0gMCkge1xyXG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ25nLXNwZWVkLXRlc3Q6IFZhbGlkIGZpbGUgc2l6ZSBpcyByZXF1aXJlZCcpO1xyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgaWYgKHNldHRpbmdzLml0ZXJhdGlvbnMgJiYgc2V0dGluZ3MuaXRlcmF0aW9ucyA8IDEpIHtcclxuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCduZy1zcGVlZC10ZXN0OiBJdGVyYXRpb25zIG11c3QgYmUgYXQgbGVhc3QgMScpO1xyXG4gICAgICAgIH1cclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIEdldCBpbnRlcm5ldCBzcGVlZCBpbiBiaXRzIHBlciBzZWNvbmQgKGJwcylcclxuICAgICAqIEZhaWxzIGZhc3QgaWYgbm8gaW50ZXJuZXQgY29ubmVjdGlvbiBpcyBhdmFpbGFibGVcclxuICAgICAqL1xyXG4gICAgZ2V0QnBzKGN1c3RvbVNldHRpbmdzPzogUGFydGlhbDxTcGVlZFRlc3RTZXR0aW5nc01vZGVsPik6IE9ic2VydmFibGU8bnVtYmVyPiB7XHJcbiAgICAgICAgcmV0dXJuIG5ldyBPYnNlcnZhYmxlKG9ic2VydmVyID0+IHtcclxuICAgICAgICAgICAgLy8gQ2hlY2sgY29ubmVjdGl2aXR5IGltbWVkaWF0ZWx5XHJcbiAgICAgICAgICAgIGlmICghbmF2aWdhdG9yLm9uTGluZSkge1xyXG4gICAgICAgICAgICAgICAgb2JzZXJ2ZXIuZXJyb3IobmV3IEVycm9yKCdObyBpbnRlcm5ldCBjb25uZWN0aW9uIC0gYnJvd3NlciByZXBvcnRzIG9mZmxpbmUnKSk7XHJcbiAgICAgICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgICAgIC8vIFNtYWxsIGRlbGF5IHRvIGVuc3VyZSBwcm9wZXIgaW5pdGlhbGl6YXRpb25cclxuICAgICAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XHJcbiAgICAgICAgICAgICAgICAvLyBDcmVhdGUgc2V0dGluZ3Mgd2l0aCBwcm9wZXIgbWVyZ2luZ1xyXG4gICAgICAgICAgICAgICAgY29uc3QgZGVmYXVsdFNldHRpbmdzID0gbmV3IFNwZWVkVGVzdFNldHRpbmdzTW9kZWwoKTtcclxuICAgICAgICAgICAgICAgIGNvbnN0IHNldHRpbmdzID0gdGhpcy5tZXJnZVNldHRpbmdzKGRlZmF1bHRTZXR0aW5ncywgY3VzdG9tU2V0dGluZ3MpO1xyXG5cclxuICAgICAgICAgICAgICAgIHRyeSB7XHJcbiAgICAgICAgICAgICAgICAgICAgdGhpcy52YWxpZGF0ZVNldHRpbmdzKHNldHRpbmdzKTtcclxuXHJcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5kb3dubG9hZFRlc3Qoc2V0dGluZ3MpLnN1YnNjcmliZSh7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIG5leHQ6IChzcGVlZEJwcykgPT4ge1xyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgb2JzZXJ2ZXIubmV4dChzcGVlZEJwcyk7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBvYnNlcnZlci5jb21wbGV0ZSgpO1xyXG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxyXG4gICAgICAgICAgICAgICAgICAgICAgICBlcnJvcjogKGVycm9yKSA9PiB7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBvYnNlcnZlci5lcnJvcihlcnJvcik7XHJcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgb2JzZXJ2ZXIuZXJyb3IoZXJyb3IpO1xyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICB9LCAxKTtcclxuICAgICAgICB9KTtcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIFByb3Blcmx5IG1lcmdlIGN1c3RvbSBzZXR0aW5ncyB3aXRoIGRlZmF1bHRzXHJcbiAgICAgKi9cclxuICAgIHByaXZhdGUgbWVyZ2VTZXR0aW5ncyhkZWZhdWx0U2V0dGluZ3M6IFNwZWVkVGVzdFNldHRpbmdzTW9kZWwsIGN1c3RvbVNldHRpbmdzPzogUGFydGlhbDxTcGVlZFRlc3RTZXR0aW5nc01vZGVsPik6IFNwZWVkVGVzdFNldHRpbmdzTW9kZWwge1xyXG4gICAgICAgIGlmICghY3VzdG9tU2V0dGluZ3MpIHtcclxuICAgICAgICAgICAgcmV0dXJuIGRlZmF1bHRTZXR0aW5ncztcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIGNvbnN0IG1lcmdlZFNldHRpbmdzID0gbmV3IFNwZWVkVGVzdFNldHRpbmdzTW9kZWwoKTtcclxuXHJcbiAgICAgICAgLy8gTWVyZ2UgaXRlcmF0aW9uc1xyXG4gICAgICAgIG1lcmdlZFNldHRpbmdzLml0ZXJhdGlvbnMgPSBjdXN0b21TZXR0aW5ncy5pdGVyYXRpb25zICE9PSB1bmRlZmluZWRcclxuICAgICAgICAgICAgPyBjdXN0b21TZXR0aW5ncy5pdGVyYXRpb25zXHJcbiAgICAgICAgICAgIDogZGVmYXVsdFNldHRpbmdzLml0ZXJhdGlvbnM7XHJcblxyXG4gICAgICAgIC8vIE1lcmdlIHJldHJ5RGVsYXlcclxuICAgICAgICBtZXJnZWRTZXR0aW5ncy5yZXRyeURlbGF5ID0gY3VzdG9tU2V0dGluZ3MucmV0cnlEZWxheSAhPT0gdW5kZWZpbmVkXHJcbiAgICAgICAgICAgID8gY3VzdG9tU2V0dGluZ3MucmV0cnlEZWxheVxyXG4gICAgICAgICAgICA6IGRlZmF1bHRTZXR0aW5ncy5yZXRyeURlbGF5O1xyXG5cclxuICAgICAgICAvLyBNZXJnZSBmaWxlIHNldHRpbmdzXHJcbiAgICAgICAgaWYgKGN1c3RvbVNldHRpbmdzLmZpbGUpIHtcclxuICAgICAgICAgICAgbWVyZ2VkU2V0dGluZ3MuZmlsZSA9IG5ldyBTcGVlZFRlc3RGaWxlTW9kZWwoKTtcclxuXHJcbiAgICAgICAgICAgIC8vIE1lcmdlIGZpbGUgcGF0aFxyXG4gICAgICAgICAgICBtZXJnZWRTZXR0aW5ncy5maWxlLnBhdGggPSBjdXN0b21TZXR0aW5ncy5maWxlLnBhdGggIT09IHVuZGVmaW5lZFxyXG4gICAgICAgICAgICAgICAgPyBjdXN0b21TZXR0aW5ncy5maWxlLnBhdGhcclxuICAgICAgICAgICAgICAgIDogZGVmYXVsdFNldHRpbmdzLmZpbGUhLnBhdGg7XHJcblxyXG4gICAgICAgICAgICAvLyBNZXJnZSBmaWxlIHNpemVcclxuICAgICAgICAgICAgbWVyZ2VkU2V0dGluZ3MuZmlsZS5zaXplID0gY3VzdG9tU2V0dGluZ3MuZmlsZS5zaXplICE9PSB1bmRlZmluZWRcclxuICAgICAgICAgICAgICAgID8gY3VzdG9tU2V0dGluZ3MuZmlsZS5zaXplXHJcbiAgICAgICAgICAgICAgICA6IGRlZmF1bHRTZXR0aW5ncy5maWxlIS5zaXplO1xyXG5cclxuICAgICAgICAgICAgLy8gTWVyZ2Ugc2hvdWxkQnVzdENhY2hlXHJcbiAgICAgICAgICAgIG1lcmdlZFNldHRpbmdzLmZpbGUuc2hvdWxkQnVzdENhY2hlID0gY3VzdG9tU2V0dGluZ3MuZmlsZS5zaG91bGRCdXN0Q2FjaGUgIT09IHVuZGVmaW5lZFxyXG4gICAgICAgICAgICAgICAgPyBjdXN0b21TZXR0aW5ncy5maWxlLnNob3VsZEJ1c3RDYWNoZVxyXG4gICAgICAgICAgICAgICAgOiBkZWZhdWx0U2V0dGluZ3MuZmlsZSEuc2hvdWxkQnVzdENhY2hlO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIG1lcmdlZFNldHRpbmdzLmZpbGUgPSBkZWZhdWx0U2V0dGluZ3MuZmlsZTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIHJldHVybiBtZXJnZWRTZXR0aW5ncztcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIEdldCBpbnRlcm5ldCBzcGVlZCBpbiBraWxvYml0cyBwZXIgc2Vjb25kIChLYnBzKVxyXG4gICAgICovXHJcbiAgICBnZXRLYnBzKHNldHRpbmdzPzogUGFydGlhbDxTcGVlZFRlc3RTZXR0aW5nc01vZGVsPik6IE9ic2VydmFibGU8bnVtYmVyPiB7XHJcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0QnBzKHNldHRpbmdzKS5waXBlKFxyXG4gICAgICAgICAgICBtYXAoYnBzID0+IGJwcyAvIDEwMjQpXHJcbiAgICAgICAgKTtcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIEdldCBpbnRlcm5ldCBzcGVlZCBpbiBtZWdhYml0cyBwZXIgc2Vjb25kIChNYnBzKVxyXG4gICAgICovXHJcbiAgICBnZXRNYnBzKHNldHRpbmdzPzogUGFydGlhbDxTcGVlZFRlc3RTZXR0aW5nc01vZGVsPik6IE9ic2VydmFibGU8bnVtYmVyPiB7XHJcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0S2JwcyhzZXR0aW5ncykucGlwZShcclxuICAgICAgICAgICAgbWFwKGticHMgPT4ga2JwcyAvIDEwMjQpXHJcbiAgICAgICAgKTtcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIEdldCBjb21wcmVoZW5zaXZlIHNwZWVkIHRlc3QgcmVzdWx0cyB3aXRoIGZhc3QgZmFpbHVyZSBmb3Igb2ZmbGluZSBzY2VuYXJpb3NcclxuICAgICAqL1xyXG4gICAgZ2V0U3BlZWRUZXN0UmVzdWx0KHNldHRpbmdzPzogUGFydGlhbDxTcGVlZFRlc3RTZXR0aW5nc01vZGVsPik6IE9ic2VydmFibGU8U3BlZWRUZXN0UmVzdWx0PiB7XHJcbiAgICAgICAgY29uc3Qgc3RhcnRUaW1lID0gRGF0ZS5ub3coKTtcclxuXHJcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0QnBzKHNldHRpbmdzKS5waXBlKFxyXG4gICAgICAgICAgICBtYXAoYnBzID0+ICh7XHJcbiAgICAgICAgICAgICAgICBicHMsXHJcbiAgICAgICAgICAgICAgICBrYnBzOiBicHMgLyAxMDI0LFxyXG4gICAgICAgICAgICAgICAgbWJwczogYnBzIC8gKDEwMjQgKiAxMDI0KSxcclxuICAgICAgICAgICAgICAgIGR1cmF0aW9uOiAoRGF0ZS5ub3coKSAtIHN0YXJ0VGltZSkgLyAxMDAwXHJcbiAgICAgICAgICAgIH0pKSxcclxuICAgICAgICAgICAgdGltZW91dCh0aGlzLkRFRkFVTFRfVElNRU9VVCArIDUwMDApLCAvLyBPdmVyYWxsIHRpbWVvdXQgc2xpZ2h0bHkgbG9uZ2VyIHRoYW4gaW5kaXZpZHVhbCByZXF1ZXN0IHRpbWVvdXRcclxuICAgICAgICAgICAgY2F0Y2hFcnJvcihlcnJvciA9PiB7XHJcbiAgICAgICAgICAgICAgICBpZiAoZXJyb3IubmFtZSA9PT0gJ1RpbWVvdXRFcnJvcicpIHtcclxuICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGhyb3dFcnJvcigoKSA9PiBuZXcgRXJyb3IoJ1NwZWVkIHRlc3QgdGltZWQgb3V0IC0gcGxlYXNlIGNoZWNrIHlvdXIgaW50ZXJuZXQgY29ubmVjdGlvbicpKTtcclxuICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgIHJldHVybiB0aHJvd0Vycm9yKCgpID0+IGVycm9yKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICApO1xyXG4gICAgfVxyXG5cclxuICAgIC8qKlxyXG4gICAgICogQ2hlY2sgaWYgdGhlIGJyb3dzZXIgaXMgb25saW5lIHdpdGggZW5oYW5jZWQgZGV0ZWN0aW9uXHJcbiAgICAgKi9cclxuICAgIGlzT25saW5lKCk6IE9ic2VydmFibGU8Ym9vbGVhbj4ge1xyXG4gICAgICAgIHJldHVybiBtZXJnZShcclxuICAgICAgICAgICAgZnJvbUV2ZW50KHdpbmRvdywgJ29mZmxpbmUnKS5waXBlKG1hcCgoKSA9PiBmYWxzZSkpLFxyXG4gICAgICAgICAgICBmcm9tRXZlbnQod2luZG93LCAnb25saW5lJykucGlwZShtYXAoKCkgPT4gdHJ1ZSkpLFxyXG4gICAgICAgICAgICBvZihuYXZpZ2F0b3Iub25MaW5lKVxyXG4gICAgICAgICkucGlwZShcclxuICAgICAgICAgICAgc3RhcnRXaXRoKG5hdmlnYXRvci5vbkxpbmUpLFxyXG4gICAgICAgICAgICAvLyBWZXJpZnkgYWN0dWFsIGNvbm5lY3Rpdml0eSBmb3Igb25saW5lIHN0YXRlXHJcbiAgICAgICAgICAgIHN3aXRjaE1hcChicm93c2VyT25saW5lID0+IHtcclxuICAgICAgICAgICAgICAgIGlmICghYnJvd3Nlck9ubGluZSkge1xyXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBvZihmYWxzZSk7XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICAvLyBRdWljayBjb25uZWN0aXZpdHkgdmVyaWZpY2F0aW9uXHJcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5jaGVja0Nvbm5lY3Rpdml0eSgpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICk7XHJcbiAgICB9XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBNb25pdG9yIG5ldHdvcmsgY29ubmVjdGlvbiBzdGF0dXMgd2l0aCBlbmhhbmNlZCBkZXRlY3Rpb25cclxuICAgICAqL1xyXG4gICAgZ2V0TmV0d29ya1N0YXR1cygpOiBPYnNlcnZhYmxlPHsgaXNPbmxpbmU6IGJvb2xlYW47IGVmZmVjdGl2ZVR5cGU/OiBzdHJpbmc7IGRvd25saW5rPzogbnVtYmVyIH0+IHtcclxuICAgICAgICBjb25zdCBnZXRDb25uZWN0aW9uSW5mbyA9ICgpID0+IHtcclxuICAgICAgICAgICAgY29uc3QgY29ubmVjdGlvbiA9IChuYXZpZ2F0b3IgYXMgYW55KS5jb25uZWN0aW9uIHx8XHJcbiAgICAgICAgICAgICAgICAobmF2aWdhdG9yIGFzIGFueSkubW96Q29ubmVjdGlvbiB8fFxyXG4gICAgICAgICAgICAgICAgKG5hdmlnYXRvciBhcyBhbnkpLndlYmtpdENvbm5lY3Rpb247XHJcblxyXG4gICAgICAgICAgICByZXR1cm4ge1xyXG4gICAgICAgICAgICAgICAgaXNPbmxpbmU6IG5hdmlnYXRvci5vbkxpbmUsXHJcbiAgICAgICAgICAgICAgICBlZmZlY3RpdmVUeXBlOiBjb25uZWN0aW9uPy5lZmZlY3RpdmVUeXBlLFxyXG4gICAgICAgICAgICAgICAgZG93bmxpbms6IGNvbm5lY3Rpb24/LmRvd25saW5rXHJcbiAgICAgICAgICAgIH07XHJcbiAgICAgICAgfTtcclxuXHJcbiAgICAgICAgcmV0dXJuIG1lcmdlKFxyXG4gICAgICAgICAgICBmcm9tRXZlbnQod2luZG93LCAnb2ZmbGluZScpLnBpcGUoXHJcbiAgICAgICAgICAgICAgICBtYXAoKCkgPT4gKHsgLi4uZ2V0Q29ubmVjdGlvbkluZm8oKSwgaXNPbmxpbmU6IGZhbHNlIH0pKVxyXG4gICAgICAgICAgICApLFxyXG4gICAgICAgICAgICBmcm9tRXZlbnQod2luZG93LCAnb25saW5lJykucGlwZShcclxuICAgICAgICAgICAgICAgIG1hcCgoKSA9PiBnZXRDb25uZWN0aW9uSW5mbygpKSxcclxuICAgICAgICAgICAgICAgIC8vIFZlcmlmeSBhY3R1YWwgY29ubmVjdGl2aXR5IHdoZW4gYnJvd3NlciByZXBvcnRzIG9ubGluZVxyXG4gICAgICAgICAgICAgICAgc3dpdGNoTWFwKGluZm8gPT5cclxuICAgICAgICAgICAgICAgICAgICB0aGlzLmNoZWNrQ29ubmVjdGl2aXR5KCkucGlwZShcclxuICAgICAgICAgICAgICAgICAgICAgICAgbWFwKGFjdHVhbGx5T25saW5lID0+ICh7IC4uLmluZm8sIGlzT25saW5lOiBhY3R1YWxseU9ubGluZSB9KSlcclxuICAgICAgICAgICAgICAgICAgICApXHJcbiAgICAgICAgICAgICAgICApXHJcbiAgICAgICAgICAgICksXHJcbiAgICAgICAgICAgIG9mKGdldENvbm5lY3Rpb25JbmZvKCkpLnBpcGUoXHJcbiAgICAgICAgICAgICAgICBzd2l0Y2hNYXAoaW5mbyA9PlxyXG4gICAgICAgICAgICAgICAgICAgIGluZm8uaXNPbmxpbmVcclxuICAgICAgICAgICAgICAgICAgICAgICAgPyB0aGlzLmNoZWNrQ29ubmVjdGl2aXR5KCkucGlwZShcclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcChhY3R1YWxseU9ubGluZSA9PiAoeyAuLi5pbmZvLCBpc09ubGluZTogYWN0dWFsbHlPbmxpbmUgfSkpXHJcbiAgICAgICAgICAgICAgICAgICAgICAgIClcclxuICAgICAgICAgICAgICAgICAgICAgICAgOiBvZihpbmZvKVxyXG4gICAgICAgICAgICAgICAgKVxyXG4gICAgICAgICAgICApXHJcbiAgICAgICAgKTtcclxuICAgIH1cclxufVxyXG4iXX0=