UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

406 lines 54.2 kB
/** * GitHubRateLimiter - Specialized rate limiter for GitHub API calls * * Features: * - Respects GitHub's authenticated (5000/hour) and unauthenticated (60/hour) limits * - Client-side queuing when approaching limits * - Request prioritization for critical operations * - Comprehensive logging for quota management * - Early termination when exact matches are found */ import { RateLimiter } from './RateLimiter.js'; import { GITHUB_API_RATE_LIMITS } from '../config/portfolio-constants.js'; import { logger } from './logger.js'; import { SecurityMonitor } from '../security/securityMonitor.js'; import { UnicodeValidator } from '../security/validators/unicodeValidator.js'; import { randomBytes } from 'node:crypto'; export class GitHubRateLimiter { rateLimiter; requestQueue = []; processing = false; lastRateLimitInfo; isAuthenticated = false; initialized = false; initializationPromise; tokenManager; constructor(tokenManager) { // FIX (SonarCloud S7059): Removed async operations from constructor // Previously: Called async updateLimitsForAuthStatus() directly // Now: Using lazy initialization pattern - async work deferred to first use this.tokenManager = tokenManager; // Initialize with conservative defaults synchronously this.rateLimiter = new RateLimiter({ maxRequests: Math.floor(GITHUB_API_RATE_LIMITS.UNAUTHENTICATED_LIMIT * GITHUB_API_RATE_LIMITS.BUFFER_PERCENTAGE), windowMs: GITHUB_API_RATE_LIMITS.WINDOW_MS, minDelayMs: GITHUB_API_RATE_LIMITS.MIN_DELAY_MS }); // Setup periodic check immediately (synchronous) this.setupPeriodicStatusCheck(); } /** * Ensure rate limiter is initialized with proper auth status */ async ensureInitialized() { // FIX: Check promise first to address potential race condition if (this.initializationPromise) { return this.initializationPromise; } if (this.initialized) return; // Prevent multiple concurrent initializations this.initializationPromise = this.updateLimitsForAuthStatus() .then(() => { // FIX: Only set initialized to true on success this.initialized = true; }) .catch((error) => { // FIX: Better error recovery - don't mark as initialized on failure logger.warn('Failed to initialize with auth status, using defaults', { error }); // Don't set initialized to true, allow retry on next call // Re-throw to maintain promise chain behavior throw error; }) .finally(() => { this.initializationPromise = undefined; }); return this.initializationPromise; } /** * Update rate limits based on current authentication status */ async updateLimitsForAuthStatus() { try { const token = await this.tokenManager.getGitHubTokenAsync(); const newIsAuthenticated = !!token; // Only recreate rate limiter if auth status changed if (newIsAuthenticated !== this.isAuthenticated) { this.isAuthenticated = newIsAuthenticated; const limit = this.isAuthenticated ? GITHUB_API_RATE_LIMITS.AUTHENTICATED_LIMIT : GITHUB_API_RATE_LIMITS.UNAUTHENTICATED_LIMIT; // Apply buffer to stay below actual limits const bufferedLimit = Math.floor(limit * GITHUB_API_RATE_LIMITS.BUFFER_PERCENTAGE); const config = { maxRequests: bufferedLimit, windowMs: GITHUB_API_RATE_LIMITS.WINDOW_MS, minDelayMs: GITHUB_API_RATE_LIMITS.MIN_DELAY_MS }; this.rateLimiter = new RateLimiter(config); logger.info('GitHub rate limiter updated', { authenticated: this.isAuthenticated, limit: bufferedLimit, originalLimit: limit, bufferPercentage: GITHUB_API_RATE_LIMITS.BUFFER_PERCENTAGE }); } } catch (error) { logger.warn('Failed to check authentication status for rate limiting', { error }); // Fall back to unauthenticated limits this.isAuthenticated = false; this.rateLimiter = new RateLimiter({ maxRequests: Math.floor(GITHUB_API_RATE_LIMITS.UNAUTHENTICATED_LIMIT * GITHUB_API_RATE_LIMITS.BUFFER_PERCENTAGE), windowMs: GITHUB_API_RATE_LIMITS.WINDOW_MS, minDelayMs: GITHUB_API_RATE_LIMITS.MIN_DELAY_MS }); // Re-throw to allow retry mechanism in ensureInitialized() throw error; } } statusCheckInterval; /** * Setup periodic check for rate limit status * FIX: Store interval reference to allow cleanup in tests */ setupPeriodicStatusCheck() { // Clear any existing interval if (this.statusCheckInterval) { clearInterval(this.statusCheckInterval); } // Check auth status every 5 minutes this.statusCheckInterval = setInterval(() => { this.updateLimitsForAuthStatus().catch(error => { logger.warn('Periodic auth status check failed', { error }); }); }, 5 * 60 * 1000); // Prevent the interval from keeping the event loop alive if (typeof this.statusCheckInterval.unref === 'function') { this.statusCheckInterval.unref(); } } /** * Cleanup method for tests */ cleanup() { if (this.statusCheckInterval) { clearInterval(this.statusCheckInterval); this.statusCheckInterval = undefined; } } /** * Queue a GitHub API request with rate limiting * @param operation Description of the operation * @param apiCall Function that makes the actual API call * @param priority Request priority (high, normal, low) * @returns Promise that resolves with the API response */ async queueRequest(operation, apiCall, priority = 'normal') { // SECURITY FIX (DMCP-SEC-004): Normalize Unicode in operation name to prevent injection attacks const normalizedOperation = UnicodeValidator.normalize(operation); if (!normalizedOperation.isValid) { SecurityMonitor.logSecurityEvent({ type: 'UNICODE_VALIDATION_ERROR', severity: 'MEDIUM', source: 'GitHubRateLimiter.queueRequest', details: `Invalid Unicode in operation name: ${normalizedOperation.detectedIssues?.[0] || 'unknown error'}` }); // Use a safe fallback for the operation name operation = 'github-api-request'; } else { operation = normalizedOperation.normalizedContent; } // FIX: Use crypto.randomBytes instead of Math.random() for secure ID generation // SonarCloud: Math.random() is not cryptographically secure const randomPart = randomBytes(6).toString('hex'); const requestId = `${operation}-${Date.now()}-${randomPart}`; return new Promise((resolve, reject) => { const request = { id: requestId, operation, priority, timestamp: Date.now(), resolve: () => { // Wrap in async IIFE to handle async operations without returning a Promise (async () => { try { logger.debug('Executing GitHub API request', { operation, requestId, queueWaitTime: Date.now() - request.timestamp }); const result = await apiCall(); resolve(result); // Log successful API usage for quota tracking this.logApiUsage(operation, 'success'); } catch (error) { // Check if this is a rate limit error from GitHub if (this.isGitHubRateLimitError(error)) { this.handleGitHubRateLimit(error); } // Ensure rejection reason is an Error object reject(error instanceof Error ? error : new Error(String(error))); this.logApiUsage(operation, 'error', error); } })().catch((err) => { // Catch any synchronous errors from the IIFE itself // Ensure the rejection reason is an Error object reject(err instanceof Error ? err : new Error(String(err))); }); }, reject }; // Add to queue with priority ordering this.addToQueue(request); this.processQueue(); }); } /** * Add request to queue with priority ordering */ addToQueue(request) { // Insert based on priority: high > normal > low // Within same priority, maintain FIFO order const priorityOrder = { high: 0, normal: 1, low: 2 }; let insertIndex = this.requestQueue.length; for (let i = 0; i < this.requestQueue.length; i++) { if (priorityOrder[request.priority] < priorityOrder[this.requestQueue[i].priority]) { insertIndex = i; break; } } this.requestQueue.splice(insertIndex, 0, request); logger.debug('GitHub API request queued', { operation: request.operation, priority: request.priority, queuePosition: insertIndex, totalQueued: this.requestQueue.length }); } /** * Process the request queue */ async processQueue() { if (this.processing || this.requestQueue.length === 0) { return; } this.processing = true; try { // Ensure initialization before processing // If initialization fails, we continue with default rate limiter try { await this.ensureInitialized(); } catch (initError) { // Initialization failed but we have fallback defaults, continue processing logger.debug('Continuing with default rate limits after init failure', { error: initError }); } while (this.requestQueue.length > 0) { // Update auth status periodically // Use crypto for consistency, though this is not security-sensitive const shouldUpdate = randomBytes(1)[0] < 26; // ~10% chance (26/256) if (shouldUpdate) { await this.updateLimitsForAuthStatus(); } const rateLimitStatus = this.rateLimiter.checkLimit(); if (!rateLimitStatus.allowed) { // Log rate limit wait logger.info('GitHub API rate limit reached, waiting', { retryAfterMs: rateLimitStatus.retryAfterMs, remainingTokens: rateLimitStatus.remainingTokens, queueLength: this.requestQueue.length, resetTime: rateLimitStatus.resetTime }); // Wait for the specified time await new Promise(resolve => setTimeout(resolve, rateLimitStatus.retryAfterMs || 1000)); continue; } // Process the next request const request = this.requestQueue.shift(); this.rateLimiter.consumeToken(); // Execute the request request.resolve(null); // This will trigger the actual API call } } finally { this.processing = false; } } /** * Get current rate limit status */ getStatus() { const baseStatus = this.rateLimiter.getStatus(); return { ...baseStatus, queueLength: this.requestQueue.length, currentLimit: this.isAuthenticated ? GITHUB_API_RATE_LIMITS.AUTHENTICATED_LIMIT : GITHUB_API_RATE_LIMITS.UNAUTHENTICATED_LIMIT, rateLimitInfo: this.lastRateLimitInfo }; } /** * Check if an error is a GitHub rate limit error */ isGitHubRateLimitError(error) { return error?.status === 429 || error?.response?.status === 429 || (typeof error?.message === 'string' && error.message.toLowerCase().includes('rate limit')); } /** * Handle GitHub rate limit error response */ handleGitHubRateLimit(error) { let resetTime; let remainingRequests = 0; // Parse rate limit headers if available if (error?.response?.headers) { const headers = error.response.headers; const resetTimestamp = Number.parseInt(headers['x-ratelimit-reset'] || '0'); const remaining = Number.parseInt(headers['x-ratelimit-remaining'] || '0'); const limit = Number.parseInt(headers['x-ratelimit-limit'] || '0'); if (resetTimestamp > 0) { resetTime = new Date(resetTimestamp * 1000); } this.lastRateLimitInfo = { limit, remaining, reset: resetTime || new Date(Date.now() + 60 * 60 * 1000), // Default to 1 hour used: limit - remaining }; remainingRequests = remaining; } logger.warn('GitHub API rate limit hit from server', { remaining: remainingRequests, resetTime, queueLength: this.requestQueue.length, errorMessage: error?.message }); // Log as a security event for monitoring SecurityMonitor.logSecurityEvent({ type: 'RATE_LIMIT_EXCEEDED', severity: 'MEDIUM', source: 'GitHubRateLimiter.handleGitHubRateLimit', details: `GitHub API rate limit exceeded. Remaining: ${remainingRequests}, Queue: ${this.requestQueue.length}`, metadata: { rateLimitInfo: this.lastRateLimitInfo, authenticated: this.isAuthenticated } }); } /** * Log API usage for monitoring and diagnostics */ logApiUsage(operation, result, error) { const status = this.getStatus(); logger.debug('GitHub API usage logged', { operation, result, remainingTokens: status.remainingTokens, queueLength: status.queueLength, authenticated: this.isAuthenticated, error: error?.message }); // Log warning if getting close to rate limits if (status.remainingTokens < 100 && this.isAuthenticated) { logger.warn('Approaching GitHub API rate limit', { operation, remainingTokens: status.remainingTokens, currentLimit: status.currentLimit, recommendation: 'Consider reducing API usage frequency' }); } else if (status.remainingTokens < 10 && !this.isAuthenticated) { logger.warn('Approaching GitHub API rate limit (unauthenticated)', { operation, remainingTokens: status.remainingTokens, currentLimit: status.currentLimit, recommendation: 'Consider authenticating for higher rate limits' }); } } /** * Clear the request queue (for testing or emergency situations) */ clearQueue() { const clearedCount = this.requestQueue.length; // Reject all pending requests this.requestQueue.forEach(request => { request.reject(new Error('Request queue cleared')); }); this.requestQueue = []; logger.info('GitHub API request queue cleared', { clearedCount }); } /** * Reset the rate limiter (for testing) */ reset() { this.rateLimiter.reset(); this.clearQueue(); this.processing = false; logger.info('GitHub rate limiter reset'); } /** * Dispose of background timers and queued requests. */ dispose() { if (this.statusCheckInterval) { clearInterval(this.statusCheckInterval); this.statusCheckInterval = undefined; } this.clearQueue(); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2l0SHViUmF0ZUxpbWl0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvR2l0SHViUmF0ZUxpbWl0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7OztHQVNHO0FBRUgsT0FBTyxFQUFFLFdBQVcsRUFBc0MsTUFBTSxrQkFBa0IsQ0FBQztBQUNuRixPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNqRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQztBQUM5RSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBeUMxQyxNQUFNLE9BQU8saUJBQWlCO0lBQ3BCLFdBQVcsQ0FBZTtJQUMxQixZQUFZLEdBQXVCLEVBQUUsQ0FBQztJQUN0QyxVQUFVLEdBQUcsS0FBSyxDQUFDO0lBQ25CLGlCQUFpQixDQUF1QjtJQUN4QyxlQUFlLEdBQUcsS0FBSyxDQUFDO0lBQ3hCLFdBQVcsR0FBRyxLQUFLLENBQUM7SUFDcEIscUJBQXFCLENBQWlCO0lBQ3RDLFlBQVksQ0FBZTtJQUVuQyxZQUFZLFlBQTBCO1FBQ3BDLG9FQUFvRTtRQUNwRSxnRUFBZ0U7UUFDaEUsNEVBQTRFO1FBRTVFLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO1FBRWpDLHNEQUFzRDtRQUN0RCxJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksV0FBVyxDQUFDO1lBQ2pDLFdBQVcsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLHNCQUFzQixDQUFDLHFCQUFxQixHQUFHLHNCQUFzQixDQUFDLGlCQUFpQixDQUFDO1lBQ2hILFFBQVEsRUFBRSxzQkFBc0IsQ0FBQyxTQUFTO1lBQzFDLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQyxZ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YlJhdGVMaW1pdGVyIC0gU3BlY2lhbGl6ZWQgcmF0ZSBsaW1pdGVyIGZvciBHaXRIdWIgQVBJIGNhbGxzXG4gKiBcbiAqIEZlYXR1cmVzOlxuICogLSBSZXNwZWN0cyBHaXRIdWIncyBhdXRoZW50aWNhdGVkICg1MDAwL2hvdXIpIGFuZCB1bmF1dGhlbnRpY2F0ZWQgKDYwL2hvdXIpIGxpbWl0c1xuICogLSBDbGllbnQtc2lkZSBxdWV1aW5nIHdoZW4gYXBwcm9hY2hpbmcgbGltaXRzXG4gKiAtIFJlcXVlc3QgcHJpb3JpdGl6YXRpb24gZm9yIGNyaXRpY2FsIG9wZXJhdGlvbnNcbiAqIC0gQ29tcHJlaGVuc2l2ZSBsb2dnaW5nIGZvciBxdW90YSBtYW5hZ2VtZW50XG4gKiAtIEVhcmx5IHRlcm1pbmF0aW9uIHdoZW4gZXhhY3QgbWF0Y2hlcyBhcmUgZm91bmRcbiAqL1xuXG5pbXBvcnQgeyBSYXRlTGltaXRlciwgUmF0ZUxpbWl0ZXJDb25maWcsIFJhdGVMaW1pdFN0YXR1cyB9IGZyb20gJy4vUmF0ZUxpbWl0ZXIuanMnO1xuaW1wb3J0IHsgR0lUSFVCX0FQSV9SQVRFX0xJTUlUUyB9IGZyb20gJy4uL2NvbmZpZy9wb3J0Zm9saW8tY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IFRva2VuTWFuYWdlciB9IGZyb20gJy4uL3NlY3VyaXR5L3Rva2VuTWFuYWdlci5qcyc7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tICcuL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuLi9zZWN1cml0eS9zZWN1cml0eU1vbml0b3IuanMnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyByYW5kb21CeXRlcyB9IGZyb20gJ25vZGU6Y3J5cHRvJztcblxuZXhwb3J0IGludGVyZmFjZSBHaXRIdWJSYXRlTGltaXRJbmZvIHtcbiAgbGltaXQ6IG51bWJlcjtcbiAgcmVtYWluaW5nOiBudW1iZXI7XG4gIHJlc2V0OiBEYXRlO1xuICB1c2VkOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgR2l0SHViQXBpUmVxdWVzdCB7XG4gIGlkOiBzdHJpbmc7XG4gIG9wZXJhdGlvbjogc3RyaW5nO1xuICBwcmlvcml0eTogJ2hpZ2gnIHwgJ25vcm1hbCcgfCAnbG93JztcbiAgdGltZXN0YW1wOiBudW1iZXI7XG4gIHJlc29sdmU6ICh2YWx1ZTogYW55KSA9PiB2b2lkO1xuICByZWplY3Q6IChlcnJvcjogYW55KSA9PiB2b2lkO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEdpdEh1YlJhdGVTdGF0dXMgZXh0ZW5kcyBSYXRlTGltaXRTdGF0dXMge1xuICBxdWV1ZUxlbmd0aDogbnVtYmVyO1xuICBjdXJyZW50TGltaXQ6IG51bWJlcjtcbiAgcmF0ZUxpbWl0SW5mbz86IEdpdEh1YlJhdGVMaW1pdEluZm87XG59XG5cbi8qKlxuICogSW50ZXJmYWNlIGZvciByYXRlIGxpbWl0aW5nIEdpdEh1YiBBUEkgcmVxdWVzdHNcbiAqIFVzZWQgZm9yIGRlcGVuZGVuY3kgaW5qZWN0aW9uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSVJhdGVMaW1pdGVyIHtcbiAgcXVldWVSZXF1ZXN0PFQ+KFxuICAgIG9wZXJhdGlvbjogc3RyaW5nLFxuICAgIGFwaUNhbGw6ICgpID0+IFByb21pc2U8VD4sXG4gICAgcHJpb3JpdHk/OiAnaGlnaCcgfCAnbm9ybWFsJyB8ICdsb3cnXG4gICk6IFByb21pc2U8VD47XG4gIGdldFN0YXR1cygpOiBHaXRIdWJSYXRlU3RhdHVzO1xuICBjbGVhclF1ZXVlKCk6IHZvaWQ7XG4gIHJlc2V0KCk6IHZvaWQ7XG4gIGNsZWFudXAoKTogdm9pZDtcbiAgZGlzcG9zZSgpOiB2b2lkO1xufVxuXG5leHBvcnQgY2xhc3MgR2l0SHViUmF0ZUxpbWl0ZXIgaW1wbGVtZW50cyBJUmF0ZUxpbWl0ZXIge1xuICBwcml2YXRlIHJhdGVMaW1pdGVyITogUmF0ZUxpbWl0ZXI7XG4gIHByaXZhdGUgcmVxdWVzdFF1ZXVlOiBHaXRIdWJBcGlSZXF1ZXN0W10gPSBbXTtcbiAgcHJpdmF0ZSBwcm9jZXNzaW5nID0gZmFsc2U7XG4gIHByaXZhdGUgbGFzdFJhdGVMaW1pdEluZm8/OiBHaXRIdWJSYXRlTGltaXRJbmZvO1xuICBwcml2YXRlIGlzQXV0aGVudGljYXRlZCA9IGZhbHNlO1xuICBwcml2YXRlIGluaXRpYWxpemVkID0gZmFsc2U7XG4gIHByaXZhdGUgaW5pdGlhbGl6YXRpb25Qcm9taXNlPzogUHJvbWlzZTx2b2lkPjtcbiAgcHJpdmF0ZSB0b2tlbk1hbmFnZXI6IFRva2VuTWFuYWdlcjtcblxuICBjb25zdHJ1Y3Rvcih0b2tlbk1hbmFnZXI6IFRva2VuTWFuYWdlcikge1xuICAgIC8vIEZJWCAoU29uYXJDbG91ZCBTNzA1OSk6IFJlbW92ZWQgYXN5bmMgb3BlcmF0aW9ucyBmcm9tIGNvbnN0cnVjdG9yXG4gICAgLy8gUHJldmlvdXNseTogQ2FsbGVkIGFzeW5jIHVwZGF0ZUxpbWl0c0ZvckF1dGhTdGF0dXMoKSBkaXJlY3RseVxuICAgIC8vIE5vdzogVXNpbmcgbGF6eSBpbml0aWFsaXphdGlvbiBwYXR0ZXJuIC0gYXN5bmMgd29yayBkZWZlcnJlZCB0byBmaXJzdCB1c2VcblxuICAgIHRoaXMudG9rZW5NYW5hZ2VyID0gdG9rZW5NYW5hZ2VyO1xuXG4gICAgLy8gSW5pdGlhbGl6ZSB3aXRoIGNvbnNlcnZhdGl2ZSBkZWZhdWx0cyBzeW5jaHJvbm91c2x5XG4gICAgdGhpcy5yYXRlTGltaXRlciA9IG5ldyBSYXRlTGltaXRlcih7XG4gICAgICBtYXhSZXF1ZXN0czogTWF0aC5mbG9vcihHSVRIVUJfQVBJX1JBVEVfTElNSVRTLlVOQVVUSEVOVElDQVRFRF9MSU1JVCAqIEdJVEhVQl9BUElfUkFURV9MSU1JVFMuQlVGRkVSX1BFUkNFTlRBR0UpLFxuICAgICAgd2luZG93TXM6IEdJVEhVQl9BUElfUkFURV9MSU1JVFMuV0lORE9XX01TLFxuICAgICAgbWluRGVsYXlNczogR0lUSFVCX0FQSV9SQVRFX0xJTUlUUy5NSU5fREVMQVlfTVNcbiAgICB9KTtcblxuICAgIC8vIFNldHVwIHBlcmlvZGljIGNoZWNrIGltbWVkaWF0ZWx5IChzeW5jaHJvbm91cylcbiAgICB0aGlzLnNldHVwUGVyaW9kaWNTdGF0dXNDaGVjaygpO1xuICB9XG5cbiAgLyoqXG4gICAqIEVuc3VyZSByYXRlIGxpbWl0ZXIgaXMgaW5pdGlhbGl6ZWQgd2l0aCBwcm9wZXIgYXV0aCBzdGF0dXNcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZW5zdXJlSW5pdGlhbGl6ZWQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gRklYOiBDaGVjayBwcm9taXNlIGZpcnN0IHRvIGFkZHJlc3MgcG90ZW50aWFsIHJhY2UgY29uZGl0aW9uXG4gICAgaWYgKHRoaXMuaW5pdGlhbGl6YXRpb25Qcm9taXNlKSB7XG4gICAgICByZXR1cm4gdGhpcy5pbml0aWFsaXphdGlvblByb21pc2U7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuaW5pdGlhbGl6ZWQpIHJldHVybjtcblxuICAgIC8vIFByZXZlbnQgbXVsdGlwbGUgY29uY3VycmVudCBpbml0aWFsaXphdGlvbnNcbiAgICB0aGlzLmluaXRpYWxpemF0aW9uUHJvbWlzZSA9IHRoaXMudXBkYXRlTGltaXRzRm9yQXV0aFN0YXR1cygpXG4gICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgIC8vIEZJWDogT25seSBzZXQgaW5pdGlhbGl6ZWQgdG8gdHJ1ZSBvbiBzdWNjZXNzXG4gICAgICAgIHRoaXMuaW5pdGlhbGl6ZWQgPSB0cnVlO1xuICAgICAgfSlcbiAgICAgIC5jYXRjaCgoZXJyb3IpID0+IHtcbiAgICAgICAgLy8gRklYOiBCZXR0ZXIgZXJyb3IgcmVjb3ZlcnkgLSBkb24ndCBtYXJrIGFzIGluaXRpYWxpemVkIG9uIGZhaWx1cmVcbiAgICAgICAgbG9nZ2VyLndhcm4oJ0ZhaWxlZCB0byBpbml0aWFsaXplIHdpdGggYXV0aCBzdGF0dXMsIHVzaW5nIGRlZmF1bHRzJywgeyBlcnJvciB9KTtcbiAgICAgICAgLy8gRG9uJ3Qgc2V0IGluaXRpYWxpemVkIHRvIHRydWUsIGFsbG93IHJldHJ5IG9uIG5leHQgY2FsbFxuICAgICAgICAvLyBSZS10aHJvdyB0byBtYWludGFpbiBwcm9taXNlIGNoYWluIGJlaGF2aW9yXG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfSlcbiAgICAgIC5maW5hbGx5KCgpID0+IHtcbiAgICAgICAgdGhpcy5pbml0aWFsaXphdGlvblByb21pc2UgPSB1bmRlZmluZWQ7XG4gICAgICB9KTtcblxuICAgIHJldHVybiB0aGlzLmluaXRpYWxpemF0aW9uUHJvbWlzZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBVcGRhdGUgcmF0ZSBsaW1pdHMgYmFzZWQgb24gY3VycmVudCBhdXRoZW50aWNhdGlvbiBzdGF0dXNcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgdXBkYXRlTGltaXRzRm9yQXV0aFN0YXR1cygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgdG9rZW4gPSBhd2FpdCB0aGlzLnRva2VuTWFuYWdlci5nZXRHaXRIdWJUb2tlbkFzeW5jKCk7XG4gICAgICBjb25zdCBuZXdJc0F1dGhlbnRpY2F0ZWQgPSAhIXRva2VuO1xuICAgICAgXG4gICAgICAvLyBPbmx5IHJlY3JlYXRlIHJhdGUgbGltaXRlciBpZiBhdXRoIHN0YXR1cyBjaGFuZ2VkXG4gICAgICBpZiAobmV3SXNBdXRoZW50aWNhdGVkICE9PSB0aGlzLmlzQXV0aGVudGljYXRlZCkge1xuICAgICAgICB0aGlzLmlzQXV0aGVudGljYXRlZCA9IG5ld0lzQXV0aGVudGljYXRlZDtcbiAgICAgICAgXG4gICAgICAgIGNvbnN0IGxpbWl0ID0gdGhpcy5pc0F1dGhlbnRpY2F0ZWQgXG4gICAgICAgICAgPyBHSVRIVUJfQVBJX1JBVEVfTElNSVRTLkFVVEhFTlRJQ0FURURfTElNSVQgXG4gICAgICAgICAgOiBHSVRIVUJfQVBJX1JBVEVfTElNSVRTLlVOQVVUSEVOVElDQVRFRF9MSU1JVDtcbiAgICAgICAgICBcbiAgICAgICAgLy8gQXBwbHkgYnVmZmVyIHRvIHN0YXkgYmVsb3cgYWN0dWFsIGxpbWl0c1xuICAgICAgICBjb25zdCBidWZmZXJlZExpbWl0ID0gTWF0aC5mbG9vcihsaW1pdCAqIEdJVEhVQl9BUElfUkFURV9MSU1JVFMuQlVGRkVSX1BFUkNFTlRBR0UpO1xuICAgICAgICBcbiAgICAgICAgY29uc3QgY29uZmlnOiBSYXRlTGltaXRlckNvbmZpZyA9IHtcbiAgICAgICAgICBtYXhSZXF1ZXN0czogYnVmZmVyZWRMaW1pdCxcbiAgICAgICAgICB3aW5kb3dNczogR0lUSFVCX0FQSV9SQVRFX0xJTUlUUy5XSU5ET1dfTVMsXG4gICAgICAgICAgbWluRGVsYXlNczogR0lUSFVCX0FQSV9SQVRFX0xJTUlUUy5NSU5fREVMQVlfTVNcbiAgICAgICAgfTtcbiAgICAgICAgXG4gICAgICAgIHRoaXMucmF0ZUxpbWl0ZXIgPSBuZXcgUmF0ZUxpbWl0ZXIoY29uZmlnKTtcbiAgICAgICAgXG4gICAgICAgIGxvZ2dlci5pbmZvKCdHaXRIdWIgcmF0ZSBsaW1pdGVyIHVwZGF0ZWQnLCB7XG4gICAgICAgICAgYXV0aGVudGljYXRlZDogdGhpcy5pc0F1dGhlbnRpY2F0ZWQsXG4gICAgICAgICAgbGltaXQ6IGJ1ZmZlcmVkTGltaXQsXG4gICAgICAgICAgb3JpZ2luYWxMaW1pdDogbGltaXQsXG4gICAgICAgICAgYnVmZmVyUGVyY2VudGFnZTogR0lUSFVCX0FQSV9SQVRFX0xJTUlUUy5CVUZGRVJfUEVSQ0VOVEFHRVxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLndhcm4oJ0ZhaWxlZCB0byBjaGVjayBhdXRoZW50aWNhdGlvbiBzdGF0dXMgZm9yIHJhdGUgbGltaXRpbmcnLCB7IGVycm9yIH0pO1xuICAgICAgLy8gRmFsbCBiYWNrIHRvIHVuYXV0aGVudGljYXRlZCBsaW1pdHNcbiAgICAgIHRoaXMuaXNBdXRoZW50aWNhdGVkID0gZmFsc2U7XG4gICAgICB0aGlzLnJhdGVMaW1pdGVyID0gbmV3IFJhdGVMaW1pdGVyKHtcbiAgICAgICAgbWF4UmVxdWVzdHM6IE1hdGguZmxvb3IoR0lUSFVCX0FQSV9SQVRFX0xJTUlUUy5VTkFVVEhFTlRJQ0FURURfTElNSVQgKiBHSVRIVUJfQVBJX1JBVEVfTElNSVRTLkJVRkZFUl9QRVJDRU5UQUdFKSxcbiAgICAgICAgd2luZG93TXM6IEdJVEhVQl9BUElfUkFURV9MSU1JVFMuV0lORE9XX01TLFxuICAgICAgICBtaW5EZWxheU1zOiBHSVRIVUJfQVBJX1JBVEVfTElNSVRTLk1JTl9ERUxBWV9NU1xuICAgICAgfSk7XG4gICAgICAvLyBSZS10aHJvdyB0byBhbGxvdyByZXRyeSBtZWNoYW5pc20gaW4gZW5zdXJlSW5pdGlhbGl6ZWQoKVxuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBzdGF0dXNDaGVja0ludGVydmFsPzogTm9kZUpTLlRpbWVvdXQ7XG5cbiAgLyoqXG4gICAqIFNldHVwIHBlcmlvZGljIGNoZWNrIGZvciByYXRlIGxpbWl0IHN0YXR1c1xuICAgKiBGSVg6IFN0b3JlIGludGVydmFsIHJlZmVyZW5jZSB0byBhbGxvdyBjbGVhbnVwIGluIHRlc3RzXG4gICAqL1xuICBwcml2YXRlIHNldHVwUGVyaW9kaWNTdGF0dXNDaGVjaygpOiB2b2lkIHtcbiAgICAvLyBDbGVhciBhbnkgZXhpc3RpbmcgaW50ZXJ2YWxcbiAgICBpZiAodGhpcy5zdGF0dXNDaGVja0ludGVydmFsKSB7XG4gICAgICBjbGVhckludGVydmFsKHRoaXMuc3RhdHVzQ2hlY2tJbnRlcnZhbCk7XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgYXV0aCBzdGF0dXMgZXZlcnkgNSBtaW51dGVzXG4gICAgdGhpcy5zdGF0dXNDaGVja0ludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgdGhpcy51cGRhdGVMaW1pdHNGb3JBdXRoU3RhdHVzKCkuY2F0Y2goZXJyb3IgPT4ge1xuICAgICAgICBsb2dnZXIud2FybignUGVyaW9kaWMgYXV0aCBzdGF0dXMgY2hlY2sgZmFpbGVkJywgeyBlcnJvciB9KTtcbiAgICAgIH0pO1xuICAgIH0sIDUgKiA2MCAqIDEwMDApO1xuXG4gICAgLy8gUHJldmVudCB0aGUgaW50ZXJ2YWwgZnJvbSBrZWVwaW5nIHRoZSBldmVudCBsb29wIGFsaXZlXG4gICAgaWYgKHR5cGVvZiB0aGlzLnN0YXR1c0NoZWNrSW50ZXJ2YWwudW5yZWYgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgIHRoaXMuc3RhdHVzQ2hlY2tJbnRlcnZhbC51bnJlZigpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhbnVwIG1ldGhvZCBmb3IgdGVzdHNcbiAgICovXG4gIHB1YmxpYyBjbGVhbnVwKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLnN0YXR1c0NoZWNrSW50ZXJ2YWwpIHtcbiAgICAgIGNsZWFySW50ZXJ2YWwodGhpcy5zdGF0dXNDaGVja0ludGVydmFsKTtcbiAgICAgIHRoaXMuc3RhdHVzQ2hlY2tJbnRlcnZhbCA9IHVuZGVmaW5lZDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUXVldWUgYSBHaXRIdWIgQVBJIHJlcXVlc3Qgd2l0aCByYXRlIGxpbWl0aW5nXG4gICAqIEBwYXJhbSBvcGVyYXRpb24gRGVzY3JpcHRpb24gb2YgdGhlIG9wZXJhdGlvblxuICAgKiBAcGFyYW0gYXBpQ2FsbCBGdW5jdGlvbiB0aGF0IG1ha2VzIHRoZSBhY3R1YWwgQVBJIGNhbGxcbiAgICogQHBhcmFtIHByaW9yaXR5IFJlcXVlc3QgcHJpb3JpdHkgKGhpZ2gsIG5vcm1hbCwgbG93KVxuICAgKiBAcmV0dXJucyBQcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2l0aCB0aGUgQVBJIHJlc3BvbnNlXG4gICAqL1xuICBhc3luYyBxdWV1ZVJlcXVlc3Q8VD4oXG4gICAgb3BlcmF0aW9uOiBzdHJpbmcsXG4gICAgYXBpQ2FsbDogKCkgPT4gUHJvbWlzZTxUPixcbiAgICBwcmlvcml0eTogJ2hpZ2gnIHwgJ25vcm1hbCcgfCAnbG93JyA9ICdub3JtYWwnXG4gICk6IFByb21pc2U8VD4ge1xuICAgIC8vIFNFQ1VSSVRZIEZJWCAoRE1DUC1TRUMtMDA0KTogTm9ybWFsaXplIFVuaWNvZGUgaW4gb3BlcmF0aW9uIG5hbWUgdG8gcHJldmVudCBpbmplY3Rpb24gYXR0YWNrc1xuICAgIGNvbnN0IG5vcm1hbGl6ZWRPcGVyYXRpb24gPSBVbmljb2RlVmFsaWRhdG9yLm5vcm1hbGl6ZShvcGVyYXRpb24pO1xuICAgIGlmICghbm9ybWFsaXplZE9wZXJhdGlvbi5pc1ZhbGlkKSB7XG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdVTklDT0RFX1ZBTElEQVRJT05fRVJST1InLFxuICAgICAgICBzZXZlcml0eTogJ01FRElVTScsXG4gICAgICAgIHNvdXJjZTogJ0dpdEh1YlJhdGVMaW1pdGVyLnF1ZXVlUmVxdWVzdCcsXG4gICAgICAgIGRldGFpbHM6IGBJbnZhbGlkIFVuaWNvZGUgaW4gb3BlcmF0aW9uIG5hbWU6ICR7bm9ybWFsaXplZE9wZXJhdGlvbi5kZXRlY3RlZElzc3Vlcz8uWzBdIHx8ICd1bmtub3duIGVycm9yJ31gXG4gICAgICB9KTtcbiAgICAgIC8vIFVzZSBhIHNhZmUgZmFsbGJhY2sgZm9yIHRoZSBvcGVyYXRpb24gbmFtZVxuICAgICAgb3BlcmF0aW9uID0gJ2dpdGh1Yi1hcGktcmVxdWVzdCc7XG4gICAgfSBlbHNlIHtcbiAgICAgIG9wZXJhdGlvbiA9IG5vcm1hbGl6ZWRPcGVyYXRpb24ubm9ybWFsaXplZENvbnRlbnQ7XG4gICAgfVxuICAgIFxuICAgIC8vIEZJWDogVXNlIGNyeXB0by5yYW5kb21CeXRlcyBpbnN0ZWFkIG9mIE1hdGgucmFuZG9tKCkgZm9yIHNlY3VyZSBJRCBnZW5lcmF0aW9uXG4gICAgLy8gU29uYXJDbG91ZDogTWF0aC5yYW5kb20oKSBpcyBub3QgY3J5cHRvZ3JhcGhpY2FsbHkgc2VjdXJlXG4gICAgY29uc3QgcmFuZG9tUGFydCA9IHJhbmRvbUJ5dGVzKDYpLnRvU3RyaW5nKCdoZXgnKTtcbiAgICBjb25zdCByZXF1ZXN0SWQgPSBgJHtvcGVyYXRpb259LSR7RGF0ZS5ub3coKX0tJHtyYW5kb21QYXJ0fWA7XG4gICAgXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlPFQ+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIGNvbnN0IHJlcXVlc3Q6IEdpdEh1YkFwaVJlcXVlc3QgPSB7XG4gICAgICAgIGlkOiByZXF1ZXN0SWQsXG4gICAgICAgIG9wZXJhdGlvbixcbiAgICAgICAgcHJpb3JpdHksXG4gICAgICAgIHRpbWVzdGFtcDogRGF0ZS5ub3coKSxcbiAgICAgICAgcmVzb2x2ZTogKCkgPT4ge1xuICAgICAgICAgIC8vIFdyYXAgaW4gYXN5bmMgSUlGRSB0byBoYW5kbGUgYXN5bmMgb3BlcmF0aW9ucyB3aXRob3V0IHJldHVybmluZyBhIFByb21pc2VcbiAgICAgICAgICAoYXN5bmMgKCkgPT4ge1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgbG9nZ2VyLmRlYnVnKCdFeGVjdXRpbmcgR2l0SHViIEFQSSByZXF1ZXN0Jywge1xuICAgICAgICAgICAgICAgIG9wZXJhdGlvbixcbiAgICAgICAgICAgICAgICByZXF1ZXN0SWQsXG4gICAgICAgICAgICAgICAgcXVldWVXYWl0VGltZTogRGF0ZS5ub3coKSAtIHJlcXVlc3QudGltZXN0YW1wXG4gICAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGFwaUNhbGwoKTtcbiAgICAgICAgICAgICAgcmVzb2x2ZShyZXN1bHQpO1xuXG4gICAgICAgICAgICAgIC8vIExvZyBzdWNjZXNzZnVsIEFQSSB1c2FnZSBmb3IgcXVvdGEgdHJhY2tpbmdcbiAgICAgICAgICAgICAgdGhpcy5sb2dBcGlVc2FnZShvcGVyYXRpb24sICdzdWNjZXNzJyk7XG5cbiAgICAgICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICAgIC8vIENoZWNrIGlmIHRoaXMgaXMgYSByYXRlIGxpbWl0IGVycm9yIGZyb20gR2l0SHViXG4gICAgICAgICAgICAgIGlmICh0aGlzLmlzR2l0SHViUmF0ZUxpbWl0RXJyb3IoZXJyb3IpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5oYW5kbGVHaXRIdWJSYXRlTGltaXQoZXJyb3IpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIC8vIEVuc3VyZSByZWplY3Rpb24gcmVhc29uIGlzIGFuIEVycm9yIG9iamVjdFxuICAgICAgICAgICAgICByZWplY3QoZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFN0cmluZyhlcnJvcikpKTtcbiAgICAgICAgICAgICAgdGhpcy5sb2dBcGlVc2FnZShvcGVyYXRpb24sICdlcnJvcicsIGVycm9yKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9KSgpLmNhdGNoKChlcnIpID0+IHtcbiAgICAgICAgICAgIC8vIENhdGNoIGFueSBzeW5jaHJvbm91cyBlcnJvcnMgZnJvbSB0aGUgSUlGRSBpdHNlbGZcbiAgICAgICAgICAgIC8vIEVuc3VyZSB0aGUgcmVqZWN0aW9uIHJlYXNvbiBpcyBhbiBFcnJvciBvYmplY3RcbiAgICAgICAgICAgIHJlamVjdChlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyciA6IG5ldyBFcnJvcihTdHJpbmcoZXJyKSkpO1xuICAgICAgICAgIH0pO1xuICAgICAgICB9LFxuICAgICAgICByZWplY3RcbiAgICAgIH07XG5cbiAgICAgIC8vIEFkZCB0byBxdWV1ZSB3aXRoIHByaW9yaXR5IG9yZGVyaW5nXG4gICAgICB0aGlzLmFkZFRvUXVldWUocmVxdWVzdCk7XG4gICAgICB0aGlzLnByb2Nlc3NRdWV1ZSgpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIEFkZCByZXF1ZXN0IHRvIHF1ZXVlIHdpdGggcHJpb3JpdHkgb3JkZXJpbmdcbiAgICovXG4gIHByaXZhdGUgYWRkVG9RdWV1ZShyZXF1ZXN0OiBHaXRIdWJBcGlSZXF1ZXN0KTogdm9pZCB7XG4gICAgLy8gSW5zZXJ0IGJhc2VkIG9uIHByaW9yaXR5OiBoaWdoID4gbm9ybWFsID4gbG93XG4gICAgLy8gV2l0aGluIHNhbWUgcHJpb3JpdHksIG1haW50YWluIEZJRk8gb3JkZXJcbiAgICBjb25zdCBwcmlvcml0eU9yZGVyID0geyBoaWdoOiAwLCBub3JtYWw6IDEsIGxvdzogMiB9O1xuICAgIFxuICAgIGxldCBpbnNlcnRJbmRleCA9IHRoaXMucmVxdWVzdFF1ZXVlLmxlbmd0aDtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRoaXMucmVxdWVzdFF1ZXVlLmxlbmd0aDsgaSsrKSB7XG4gICAgICBpZiAocHJpb3JpdHlPcmRlcltyZXF1ZXN0LnByaW9yaXR5XSA8IHByaW9yaXR5T3JkZXJbdGhpcy5yZXF1ZXN0UXVldWVbaV0ucHJpb3JpdHldKSB7XG4gICAgICAgIGluc2VydEluZGV4ID0gaTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIHRoaXMucmVxdWVzdFF1ZXVlLnNwbGljZShpbnNlcnRJbmRleCwgMCwgcmVxdWVzdCk7XG4gICAgXG4gICAgbG9nZ2VyLmRlYnVnKCdHaXRIdWIgQVBJIHJlcXVlc3QgcXVldWVkJywge1xuICAgICAgb3BlcmF0aW9uOiByZXF1ZXN0Lm9wZXJhdGlvbixcbiAgICAgIHByaW9yaXR5OiByZXF1ZXN0LnByaW9yaXR5LFxuICAgICAgcXVldWVQb3NpdGlvbjogaW5zZXJ0SW5kZXgsXG4gICAgICB0b3RhbFF1ZXVlZDogdGhpcy5yZXF1ZXN0UXVldWUubGVuZ3RoXG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogUHJvY2VzcyB0aGUgcmVxdWVzdCBxdWV1ZVxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBwcm9jZXNzUXVldWUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMucHJvY2Vzc2luZyB8fCB0aGlzLnJlcXVlc3RRdWV1ZS5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLnByb2Nlc3NpbmcgPSB0cnVlO1xuXG4gICAgdHJ5IHtcbiAgICAgIC8vIEVuc3VyZSBpbml0aWFsaXphdGlvbiBiZWZvcmUgcHJvY2Vzc2luZ1xuICAgICAgLy8gSWYgaW5pdGlhbGl6YXRpb24gZmFpbHMsIHdlIGNvbnRpbnVlIHdpdGggZGVmYXVsdCByYXRlIGxpbWl0ZXJcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMuZW5zdXJlSW5pdGlhbGl6ZWQoKTtcbiAgICAgIH0gY2F0Y2ggKGluaXRFcnJvcikge1xuICAgICAgICAvLyBJbml0aWFsaXphdGlvbiBmYWlsZWQgYnV0IHdlIGhhdmUgZmFsbGJhY2sgZGVmYXVsdHMsIGNvbnRpbnVlIHByb2Nlc3NpbmdcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdDb250aW51aW5nIHdpdGggZGVmYXVsdCByYXRlIGxpbWl0cyBhZnRlciBpbml0IGZhaWx1cmUnLCB7IGVycm9yOiBpbml0RXJyb3IgfSk7XG4gICAgICB9XG5cbiAgICAgIHdoaWxlICh0aGlzLnJlcXVlc3RRdWV1ZS5sZW5ndGggPiAwKSB7XG4gICAgICAgIC8vIFVwZGF0ZSBhdXRoIHN0YXR1cyBwZXJpb2RpY2FsbHlcbiAgICAgICAgLy8gVXNlIGNyeXB0byBmb3IgY29uc2lzdGVuY3ksIHRob3VnaCB0aGlzIGlzIG5vdCBzZWN1cml0eS1zZW5zaXRpdmVcbiAgICAgICAgY29uc3Qgc2hvdWxkVXBkYXRlID0gcmFuZG9tQnl0ZXMoMSlbMF0gPCAyNjsgLy8gfjEwJSBjaGFuY2UgKDI2LzI1NilcbiAgICAgICAgaWYgKHNob3VsZFVwZGF0ZSkge1xuICAgICAgICAgIGF3YWl0IHRoaXMudXBkYXRlTGltaXRzRm9yQXV0aFN0YXR1cygpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcmF0ZUxpbWl0U3RhdHVzID0gdGhpcy5yYXRlTGltaXRlci5jaGVja0xpbWl0KCk7XG4gICAgICAgIFxuICAgICAgICBpZiAoIXJhdGVMaW1pdFN0YXR1cy5hbGxvd2VkKSB7XG4gICAgICAgICAgLy8gTG9nIHJhdGUgbGltaXQgd2FpdFxuICAgICAgICAgIGxvZ2dlci5pbmZvKCdHaXRIdWIgQVBJIHJhdGUgbGltaXQgcmVhY2hlZCwgd2FpdGluZycsIHtcbiAgICAgICAgICAgIHJldHJ5QWZ0ZXJNczogcmF0ZUxpbWl0U3RhdHVzLnJldHJ5QWZ0ZXJNcyxcbiAgICAgICAgICAgIHJlbWFpbmluZ1Rva2VuczogcmF0ZUxpbWl0U3RhdHVzLnJlbWFpbmluZ1Rva2VucyxcbiAgICAgICAgICAgIHF1ZXVlTGVuZ3RoOiB0aGlzLnJlcXVlc3RRdWV1ZS5sZW5ndGgsXG4gICAgICAgICAgICByZXNldFRpbWU6IHJhdGVMaW1pdFN0YXR1cy5yZXNldFRpbWVcbiAgICAgICAgICB9KTtcblxuICAgICAgICAgIC8vIFdhaXQgZm9yIHRoZSBzcGVjaWZpZWQgdGltZVxuICAgICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKHJlc29sdmUgPT4gc2V0VGltZW91dChyZXNvbHZlLCByYXRlTGltaXRTdGF0dXMucmV0cnlBZnRlck1zIHx8IDEwMDApKTtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFByb2Nlc3MgdGhlIG5leHQgcmVxdWVzdFxuICAgICAgICBjb25zdCByZXF1ZXN0ID0gdGhpcy5yZXF1ZXN0UXVldWUuc2hpZnQoKSE7XG4gICAgICAgIHRoaXMucmF0ZUxpbWl0ZXIuY29uc3VtZVRva2VuKCk7XG5cbiAgICAgICAgLy8gRXhlY3V0ZSB0aGUgcmVxdWVzdFxuICAgICAgICByZXF1ZXN0LnJlc29sdmUobnVsbCk7IC8vIFRoaXMgd2lsbCB0cmlnZ2VyIHRoZSBhY3R1YWwgQVBJIGNhbGxcbiAgICAgIH1cbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5wcm9jZXNzaW5nID0gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEdldCBjdXJyZW50IHJhdGUgbGltaXQgc3RhdHVzXG4gICAqL1xuICBnZXRTdGF0dXMoKTogR2l0SHViUmF0ZVN0YXR1cyB7XG4gICAgY29uc3QgYmFzZVN0YXR1cyA9IHRoaXMucmF0ZUxpbWl0ZXIuZ2V0U3RhdHVzKCk7XG4gICAgXG4gICAgcmV0dXJuIHtcbiAgICAgIC4uLmJhc2VTdGF0dXMsXG4gICAgICBxdWV1ZUxlbmd0aDogdGhpcy5yZXF1ZXN0UXVldWUubGVuZ3RoLFxuICAgICAgY3VycmVudExpbWl0OiB0aGlzLmlzQXV0aGVudGljYXRlZCBcbiAgICAgICAgPyBHSVRIVUJfQVBJX1JBVEVfTElNSVRTLkFVVEhFTlRJQ0FURURfTElNSVQgXG4gICAgICAgIDogR0lUSFVCX0FQSV9SQVRFX0xJTUlUUy5VTkFVVEhFTlRJQ0FURURfTElNSVQsXG4gICAgICByYXRlTGltaXRJbmZvOiB0aGlzLmxhc3RSYXRlTGltaXRJbmZvXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBhbiBlcnJvciBpcyBhIEdpdEh1YiByYXRlIGxpbWl0IGVycm9yXG4gICAqL1xuICBwcml2YXRlIGlzR2l0SHViUmF0ZUxpbWl0RXJyb3IoZXJyb3I6IGFueSk6IGJvb2xlYW4ge1xuICAgIHJldHVybiBlcnJvcj8uc3RhdHVzID09PSA0MjkgfHwgXG4gICAgICAgICAgIGVycm9yPy5yZXNwb25zZT8uc3RhdHVzID09PSA0MjkgfHxcbiAgICAgICAgICAgKHR5cGVvZiBlcnJvcj8ubWVzc2FnZSA9PT0gJ3N0cmluZycgJiYgZXJyb3IubWVzc2FnZS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKCdyYXRlIGxpbWl0JykpO1xuICB9XG5cbiAgLyoqXG4gICAqIEhhbmRsZSBHaXRIdWIgcmF0ZSBsaW1pdCBlcnJvciByZXNwb25zZVxuICAgKi9cbiAgcHJpdmF0ZSBoYW5kbGVHaXRIdWJSYXRlTGltaXQoZXJyb3I6IGFueSk6IHZvaWQge1xuICAgIGxldCByZXNldFRpbWU6IERhdGUgfCB1bmRlZmluZWQ7XG4gICAgbGV0IHJlbWFpbmluZ1JlcXVlc3RzID0gMDtcbiAgICBcbiAgICAvLyBQYXJzZSByYXRlIGxpbWl0IGhlYWRlcnMgaWYgYXZhaWxhYmxlXG4gICAgaWYgKGVycm9yPy5yZXNwb25zZT8uaGVhZGVycykge1xuICAgICAgY29uc3QgaGVhZGVycyA9IGVycm9yLnJlc3BvbnNlLmhlYWRlcnM7XG4gICAgICBjb25zdCByZXNldFRpbWVzdGFtcCA9IE51bWJlci5wYXJzZUludChoZWFkZXJzWyd4LXJhdGVsaW1pdC1yZXNldCddIHx8ICcwJyk7XG4gICAgICBjb25zdCByZW1haW5pbmcgPSBOdW1iZXIucGFyc2VJbnQoaGVhZGVyc1sneC1yYXRlbGltaXQtcmVtYWluaW5nJ10gfHwgJzAnKTtcbiAgICAgIGNvbnN0IGxpbWl0ID0gTnVtYmVyLnBhcnNlSW50KGhlYWRlcnNbJ3gtcmF0ZWxpbWl0LWxpbWl0J10gfHwgJzAnKTtcbiAgICA