@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
JavaScript
/**
* 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