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.

510 lines 67.2 kB
/** * Collection Index Manager with Background Refresh and Robust Caching * * This manager implements: * - 1-hour TTL with local file caching * - Background refresh without blocking operations * - Exponential backoff retry logic * - Configurable timeouts via environment variables * - Return stale cache while refreshing in background * - Comprehensive error handling for production use */ import * as path from 'path'; import * as os from 'os'; import { logger } from '../utils/logger.js'; export class CollectionIndexManager { // Use GitHub Pages index which is automatically updated by workflow // Old URL was stale: https://raw.githubusercontent.com/DollhouseMCP/collection/main/public/collection-index.json INDEX_URL = 'https://dollhousemcp.github.io/collection/collection-index.json'; TTL_MS; FETCH_TIMEOUT_MS; MAX_RETRIES; BASE_RETRY_DELAY_MS; MAX_RETRY_DELAY_MS; CACHE_FILE; cachedIndex = null; backgroundRefreshPromise = null; isRefreshing = false; circuitBreakerFailures = 0; circuitBreakerLastFailure = 0; CIRCUIT_BREAKER_THRESHOLD = 5; CIRCUIT_BREAKER_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes REFRESH_THRESHOLD = 0.8; // Refresh when 80% of TTL has passed JITTER_FACTOR = 0.25; // ±25% randomness for jitter // File operations service for secure file I/O fileOperations; // Default configuration constants DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour DEFAULT_MAX_RETRIES = 3; DEFAULT_BASE_RETRY_DELAY_MS = 1000; DEFAULT_MAX_RETRY_DELAY_MS = 30000; DEFAULT_FETCH_TIMEOUT_MS = 5000; // 5 seconds CHECKSUM_LENGTH = 8; JSON_INDENT = 2; constructor(config) { // Configuration with environment variable overrides this.TTL_MS = config.ttlMs || this.DEFAULT_TTL_MS; this.FETCH_TIMEOUT_MS = this.parseFetchTimeout(config.fetchTimeoutMs); this.MAX_RETRIES = config.maxRetries || this.DEFAULT_MAX_RETRIES; this.BASE_RETRY_DELAY_MS = config.baseRetryDelayMs || this.DEFAULT_BASE_RETRY_DELAY_MS; this.MAX_RETRY_DELAY_MS = config.maxRetryDelayMs || this.DEFAULT_MAX_RETRY_DELAY_MS; // Initialize file operations service this.fileOperations = config.fileOperations; // Cache directory - use ~/.dollhouse/cache/collection-index.json as specified const cacheDir = config.cacheDir || path.join(os.homedir(), '.dollhouse', 'cache'); this.CACHE_FILE = path.join(cacheDir, 'collection-index.json'); logger.debug('CollectionIndexManager initialized', { ttlMs: this.TTL_MS, fetchTimeoutMs: this.FETCH_TIMEOUT_MS, cacheFile: this.CACHE_FILE, maxRetries: this.MAX_RETRIES }); } /** * Parse fetch timeout from config or environment variable */ parseFetchTimeout(configValue) { // Check environment variable first const envTimeout = process.env.COLLECTION_FETCH_TIMEOUT; if (envTimeout) { const parsed = Number.parseInt(envTimeout, 10); if (!Number.isNaN(parsed) && parsed > 0) { logger.debug(`Using COLLECTION_FETCH_TIMEOUT from environment: ${parsed}ms`); return parsed; } logger.warn(`Invalid COLLECTION_FETCH_TIMEOUT value: ${envTimeout}, using default`); } // Fall back to config value or default return configValue || this.DEFAULT_FETCH_TIMEOUT_MS; } /** * Get collection index with stale-while-revalidate pattern * Returns cached data immediately if available, refreshes in background */ async getIndex() { try { // Load from memory cache first if (!this.cachedIndex) { await this.loadFromDisk(); } // Check if we should return stale cache while refreshing const shouldRefresh = this.shouldRefreshCache(); if (this.cachedIndex && !this.isCacheExpired()) { // Cache is valid, return immediately logger.debug('Returning valid cached collection index'); // Start background refresh if needed but not already running if (shouldRefresh && !this.isRefreshing) { this.startBackgroundRefresh(); } return this.cachedIndex.data; } // If we have stale cache, return it while refreshing in background if (this.cachedIndex && this.isCacheExpired()) { logger.debug('Returning stale cache while refreshing in background'); // Start background refresh if not already running if (!this.isRefreshing) { this.startBackgroundRefresh(); } return this.cachedIndex.data; } // No cache available, must fetch synchronously logger.debug('No cache available, fetching collection index synchronously'); const freshIndex = await this.fetchWithRetry(); await this.updateCache(freshIndex); return freshIndex.data; } catch (error) { logger.error('Failed to get collection index', { error: this.getErrorMessage(error) }); // If we have any cached data (even expired), return it as last resort if (this.cachedIndex) { logger.warn('Returning expired cache as last resort'); return this.cachedIndex.data; } throw new Error(`Collection index not available: ${this.getErrorMessage(error)}`); } } /** * Force refresh the collection index */ async forceRefresh() { logger.debug('Force refreshing collection index'); try { const freshIndex = await this.fetchWithRetry(); await this.updateCache(freshIndex); return freshIndex.data; } catch (error) { logger.error('Force refresh failed', { error: this.getErrorMessage(error) }); // If we have cached data, return it if (this.cachedIndex) { logger.warn('Force refresh failed, returning cached data'); return this.cachedIndex.data; } throw error; } } /** * Check if cache should be refreshed (within TTL but getting close to expiry) */ shouldRefreshCache() { if (!this.cachedIndex) return true; const age = Date.now() - this.cachedIndex.timestamp; const refreshThreshold = this.TTL_MS * this.REFRESH_THRESHOLD; return age > refreshThreshold; } /** * Check if cache is expired */ isCacheExpired() { if (!this.cachedIndex) return true; const age = Date.now() - this.cachedIndex.timestamp; return age > this.TTL_MS; } /** * Start background refresh without blocking */ startBackgroundRefresh() { if (this.isRefreshing) { logger.debug('Background refresh already in progress'); return; } // Check circuit breaker if (this.isCircuitBreakerOpen()) { logger.debug('Circuit breaker open, skipping background refresh'); return; } this.isRefreshing = true; this.backgroundRefreshPromise = this.performBackgroundRefresh() .catch(error => { logger.debug('Background refresh failed', { error: this.getErrorMessage(error) }); this.recordCircuitBreakerFailure(); }) .finally(() => { this.isRefreshing = false; this.backgroundRefreshPromise = null; }); } /** * Perform background refresh */ async performBackgroundRefresh() { logger.debug('Starting background refresh of collection index'); try { const freshIndex = await this.fetchWithRetry(); await this.updateCache(freshIndex); // Reset circuit breaker on success this.circuitBreakerFailures = 0; logger.debug('Background refresh completed successfully'); } catch (error) { logger.debug('Background refresh failed', { error: this.getErrorMessage(error) }); throw error; } } /** * Fetch collection index with retry logic and exponential backoff */ async fetchWithRetry() { let lastError = null; for (let attempt = 0; attempt <= this.MAX_RETRIES; attempt++) { try { if (attempt > 0) { const delay = this.calculateRetryDelay(attempt); logger.debug(`Retrying fetch in ${delay}ms (attempt ${attempt + 1}/${this.MAX_RETRIES + 1})`); await this.sleep(delay); } return await this.fetchCollectionIndex(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); logger.debug(`Fetch attempt ${attempt + 1} failed`, { error: this.getErrorMessage(lastError), willRetry: attempt < this.MAX_RETRIES }); } } throw lastError || new Error('All fetch attempts failed'); } /** * Calculate retry delay with exponential backoff and jitter */ calculateRetryDelay(attempt) { // Exponential backoff: baseDelay * (2 ^ attempt) const exponentialDelay = this.BASE_RETRY_DELAY_MS * Math.pow(2, attempt - 1); // Cap at maximum delay const cappedDelay = Math.min(exponentialDelay, this.MAX_RETRY_DELAY_MS); // Add jitter to prevent thundering herd return this.addJitter(cappedDelay); } /** * Add jitter (±25% randomness) to a delay to prevent thundering herd problems */ addJitter(delay) { const jitter = delay * this.JITTER_FACTOR * (Math.random() - 0.5); return Math.max(0, delay + jitter); } /** * Fetch collection index from GitHub */ async fetchCollectionIndex() { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.FETCH_TIMEOUT_MS); try { // Build headers for conditional requests const headers = { 'Accept': 'application/json', 'User-Agent': 'DollhouseMCP/1.0', 'Cache-Control': 'no-cache' }; // Add conditional headers if we have cached data if (this.cachedIndex?.etag) { headers['If-None-Match'] = this.cachedIndex.etag; } if (this.cachedIndex?.lastModified) { headers['If-Modified-Since'] = this.cachedIndex.lastModified; } logger.debug('Fetching collection index', { url: this.INDEX_URL, timeout: this.FETCH_TIMEOUT_MS, hasEtag: !!this.cachedIndex?.etag }); const response = await fetch(this.INDEX_URL, { headers, signal: controller.signal }); clearTimeout(timeoutId); // Handle 304 Not Modified if (response.status === 304 && this.cachedIndex) { logger.debug('Collection index not modified (304), updating cache timestamp'); this.cachedIndex.timestamp = Date.now(); await this.saveToDisk(); return { data: this.cachedIndex.data, etag: this.cachedIndex.etag, lastModified: this.cachedIndex.lastModified }; } if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const indexData = await response.json(); // Validate the index structure this.validateIndexStructure(indexData); const etag = response.headers.get('etag') || undefined; const lastModified = response.headers.get('last-modified') || undefined; logger.debug('Collection index fetched successfully', { totalElements: indexData.total_elements, version: indexData.version, hasEtag: !!etag }); return { data: indexData, etag, lastModified }; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error && error.name === 'AbortError') { throw new Error(`Fetch timeout after ${this.FETCH_TIMEOUT_MS}ms`); } throw error; } } /** * Validate collection index structure */ validateIndexStructure(index) { if (!index || typeof index !== 'object') { throw new Error('Invalid index: not an object'); } if (typeof index.version !== 'string') { throw new Error('Invalid index: missing or invalid version'); } if (typeof index.generated !== 'string') { throw new Error('Invalid index: missing or invalid generated timestamp'); } if (typeof index.total_elements !== 'number') { throw new Error('Invalid index: missing or invalid total_elements'); } if (!index.index || typeof index.index !== 'object') { throw new Error('Invalid index: missing or invalid index object'); } if (!index.metadata || typeof index.metadata !== 'object') { throw new Error('Invalid index: missing or invalid metadata'); } } /** * Update cache with new data */ async updateCache(fetchResult) { const checksum = this.calculateChecksum(fetchResult.data); this.cachedIndex = { data: fetchResult.data, timestamp: Date.now(), etag: fetchResult.etag, lastModified: fetchResult.lastModified, version: fetchResult.data.version, checksum }; await this.saveToDisk(); logger.debug('Collection index cache updated', { version: fetchResult.data.version, totalElements: fetchResult.data.total_elements, checksum }); } /** * Calculate checksum for data integrity verification */ calculateChecksum(data) { // Simple checksum based on key properties const checksumData = { version: data.version, generated: data.generated, total_elements: data.total_elements }; return Buffer.from(JSON.stringify(checksumData)).toString('base64').substring(0, this.CHECKSUM_LENGTH); } /** * Load cache from disk */ async loadFromDisk() { try { const data = await this.fileOperations.readFile(this.CACHE_FILE, { source: 'CollectionIndexManager.loadFromDisk' }); const cached = JSON.parse(data); // Validate cache structure if (!cached.data || !cached.timestamp || !cached.version) { logger.debug('Invalid cache structure, ignoring'); return; } // Verify checksum if available if (cached.checksum) { const expectedChecksum = this.calculateChecksum(cached.data); if (cached.checksum !== expectedChecksum) { logger.debug('Cache checksum mismatch, ignoring cached data'); return; } } this.cachedIndex = cached; const age = Date.now() - cached.timestamp; const isExpired = age > this.TTL_MS; logger.debug('Loaded collection index from disk cache', { version: cached.version, age: Math.round(age / 1000), isExpired, totalElements: cached.data.total_elements }); } catch (error) { if (error.code !== 'ENOENT') { logger.debug('Failed to load cache from disk', { error: this.getErrorMessage(error) }); } } } /** * Save cache to disk */ async saveToDisk() { if (!this.cachedIndex) return; try { // Ensure cache directory exists await this.fileOperations.createDirectory(path.dirname(this.CACHE_FILE)); const cacheData = JSON.stringify(this.cachedIndex, null, this.JSON_INDENT); await this.fileOperations.writeFile(this.CACHE_FILE, cacheData, { source: 'CollectionIndexManager.saveToDisk' }); logger.debug('Collection index cache saved to disk'); } catch (error) { logger.debug('Failed to save cache to disk', { error: this.getErrorMessage(error) }); // Don't throw - cache persistence failures shouldn't break functionality } } /** * Circuit breaker logic */ isCircuitBreakerOpen() { if (this.circuitBreakerFailures < this.CIRCUIT_BREAKER_THRESHOLD) { return false; } const timeSinceLastFailure = Date.now() - this.circuitBreakerLastFailure; return timeSinceLastFailure < this.CIRCUIT_BREAKER_TIMEOUT_MS; } recordCircuitBreakerFailure() { this.circuitBreakerFailures++; this.circuitBreakerLastFailure = Date.now(); if (this.circuitBreakerFailures >= this.CIRCUIT_BREAKER_THRESHOLD) { logger.warn('Circuit breaker opened due to repeated failures', { failures: this.circuitBreakerFailures, timeoutMs: this.CIRCUIT_BREAKER_TIMEOUT_MS }); } } /** * Utility methods */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } getErrorMessage(error) { if (error instanceof Error) { return error.message; } return String(error); } /** * Get cache statistics for monitoring */ getCacheStats() { if (!this.cachedIndex) { return { isValid: false, age: 0, hasCache: false, isRefreshing: this.isRefreshing, circuitBreakerFailures: this.circuitBreakerFailures, circuitBreakerOpen: this.isCircuitBreakerOpen() }; } const age = Date.now() - this.cachedIndex.timestamp; return { isValid: !this.isCacheExpired(), age: Math.round(age / 1000), // age in seconds hasCache: true, version: this.cachedIndex.version, totalElements: this.cachedIndex.data.total_elements, isRefreshing: this.isRefreshing, circuitBreakerFailures: this.circuitBreakerFailures, circuitBreakerOpen: this.isCircuitBreakerOpen() }; } /** * Clear all cache data */ async clearCache() { this.cachedIndex = null; this.circuitBreakerFailures = 0; try { await this.fileOperations.deleteFile(this.CACHE_FILE, undefined, { source: 'CollectionIndexManager.clearCache' }); logger.debug('Collection index cache file deleted'); } catch (error) { if (error.code !== 'ENOENT') { logger.debug('Failed to delete cache file', { error: this.getErrorMessage(error) }); } } } /** * Wait for any ongoing background refresh to complete */ async waitForBackgroundRefresh() { if (this.backgroundRefreshPromise) { await this.backgroundRefreshPromise; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29sbGVjdGlvbkluZGV4TWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb2xsZWN0aW9uL0NvbGxlY3Rpb25JbmRleE1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7R0FVRztBQUVILE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBRXpCLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQXNCNUMsTUFBTSxPQUFPLHNCQUFzQjtJQUNqQyxvRUFBb0U7SUFDcEUsaUhBQWlIO0lBQ2hHLFNBQVMsR0FBRyxpRUFBaUUsQ0FBQztJQUM5RSxNQUFNLENBQVM7SUFDZixnQkFBZ0IsQ0FBUztJQUN6QixXQUFXLENBQVM7SUFDcEIsbUJBQW1CLENBQVM7SUFDNUIsa0JBQWtCLENBQVM7SUFDM0IsVUFBVSxDQUFTO0lBRTVCLFdBQVcsR0FBcUMsSUFBSSxDQUFDO0lBQ3JELHdCQUF3QixHQUF5QixJQUFJLENBQUM7SUFDdEQsWUFBWSxHQUFHLEtBQUssQ0FBQztJQUNyQixzQkFBc0IsR0FBRyxDQUFDLENBQUM7SUFDM0IseUJBQXlCLEdBQUcsQ0FBQyxDQUFDO0lBQ3JCLHlCQUF5QixHQUFHLENBQUMsQ0FBQztJQUM5QiwwQkFBMEIsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLFlBQVk7SUFDeEQsaUJBQWlCLEdBQUcsR0FBRyxDQUFDLENBQUMscUNBQXFDO0lBQzlELGFBQWEsR0FBRyxJQUFJLENBQUMsQ0FBQyw2QkFBNkI7SUFFcEUsOENBQThDO0lBQzdCLGNBQWMsQ0FBeUI7SUFFeEQsa0NBQWtDO0lBQ2pCLGNBQWMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLFNBQVM7SUFDMUMsbUJBQW1CLEdBQUcsQ0FBQyxDQUFDO0lBQ3hCLDJCQUEy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LEVBQUUsQ0FBQztZQUNmLElBQUssS0FBYSxDQUFDLElBQUksS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDckMsTUFBTSxDQUFDLEtBQUssQ0FBQyw2QkFBNkIsRUFBRSxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUN0RixDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyx3QkFBd0I7UUFDNUIsSUFBSSxJQUFJLENBQUMsd0JBQXdCLEVBQUUsQ0FBQztZQUNsQyxNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FBQztRQUN0QyxDQUFDO0lBQ0gsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb2xsZWN0aW9uIEluZGV4IE1hbmFnZXIgd2l0aCBCYWNrZ3JvdW5kIFJlZnJlc2ggYW5kIFJvYnVzdCBDYWNoaW5nXG4gKiBcbiAqIFRoaXMgbWFuYWdlciBpbXBsZW1lbnRzOlxuICogLSAxLWhvdXIgVFRMIHdpdGggbG9jYWwgZmlsZSBjYWNoaW5nXG4gKiAtIEJhY2tncm91bmQgcmVmcmVzaCB3aXRob3V0IGJsb2NraW5nIG9wZXJhdGlvbnNcbiAqIC0gRXhwb25lbnRpYWwgYmFja29mZiByZXRyeSBsb2dpY1xuICogLSBDb25maWd1cmFibGUgdGltZW91dHMgdmlhIGVudmlyb25tZW50IHZhcmlhYmxlc1xuICogLSBSZXR1cm4gc3RhbGUgY2FjaGUgd2hpbGUgcmVmcmVzaGluZyBpbiBiYWNrZ3JvdW5kXG4gKiAtIENvbXByZWhlbnNpdmUgZXJyb3IgaGFuZGxpbmcgZm9yIHByb2R1Y3Rpb24gdXNlXG4gKi9cblxuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCAqIGFzIG9zIGZyb20gJ29zJztcbmltcG9ydCB7IENvbGxlY3Rpb25JbmRleCB9IGZyb20gJy4uL3R5cGVzL2NvbGxlY3Rpb24nO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IElGaWxlT3BlcmF0aW9uc1NlcnZpY2UgfSBmcm9tICcuLi9zZXJ2aWNlcy9GaWxlT3BlcmF0aW9uc1NlcnZpY2UuanMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIENvbGxlY3Rpb25JbmRleENhY2hlRW50cnkge1xuICBkYXRhOiBDb2xsZWN0aW9uSW5kZXg7XG4gIHRpbWVzdGFtcDogbnVtYmVyO1xuICBldGFnPzogc3RyaW5nO1xuICBsYXN0TW9kaWZpZWQ/OiBzdHJpbmc7XG4gIHZlcnNpb246IHN0cmluZztcbiAgY2hlY2tzdW06IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBDb2xsZWN0aW9uSW5kZXhNYW5hZ2VyQ29uZmlnIHtcbiAgdHRsTXM/OiBudW1iZXI7XG4gIGZldGNoVGltZW91dE1zPzogbnVtYmVyO1xuICBtYXhSZXRyaWVzPzogbnVtYmVyO1xuICBiYXNlUmV0cnlEZWxheU1zPzogbnVtYmVyO1xuICBtYXhSZXRyeURlbGF5TXM/OiBudW1iZXI7XG4gIGNhY2hlRGlyPzogc3RyaW5nO1xuICBmaWxlT3BlcmF0aW9uczogSUZpbGVPcGVyYXRpb25zU2VydmljZTtcbn1cblxuZXhwb3J0IGNsYXNzIENvbGxlY3Rpb25JbmRleE1hbmFnZXIge1xuICAvLyBVc2UgR2l0SHViIFBhZ2VzIGluZGV4IHdoaWNoIGlzIGF1dG9tYXRpY2FsbHkgdXBkYXRlZCBieSB3b3JrZmxvd1xuICAvLyBPbGQgVVJMIHdhcyBzdGFsZTogaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0RvbGxob3VzZU1DUC9jb2xsZWN0aW9uL21haW4vcHVibGljL2NvbGxlY3Rpb24taW5kZXguanNvblxuICBwcml2YXRlIHJlYWRvbmx5IElOREVYX1VSTCA9ICdodHRwczovL2RvbGxob3VzZW1jcC5naXRodWIuaW8vY29sbGVjdGlvbi9jb2xsZWN0aW9uLWluZGV4Lmpzb24nO1xuICBwcml2YXRlIHJlYWRvbmx5IFRUTF9NUzogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IEZFVENIX1RJTUVPVVRfTVM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBNQVhfUkVUUklFUzogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IEJBU0VfUkVUUllfREVMQVlfTVM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBNQVhfUkVUUllfREVMQVlfTVM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBDQUNIRV9GSUxFOiBzdHJpbmc7XG4gIFxuICBwcml2YXRlIGNhY2hlZEluZGV4OiBDb2xsZWN0aW9uSW5kZXhDYWNoZUVudHJ5IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgYmFja2dyb3VuZFJlZnJlc2hQcm9taXNlOiBQcm9taXNlPHZvaWQ+IHwgbnVsbCA9IG51bGw7XG4gIHByaXZhdGUgaXNSZWZyZXNoaW5nID0gZmFsc2U7XG4gIHByaXZhdGUgY2lyY3VpdEJyZWFrZXJGYWlsdXJlcyA9IDA7XG4gIHByaXZhdGUgY2lyY3VpdEJyZWFrZXJMYXN0RmFpbHVyZSA9IDA7XG4gIHByaXZhdGUgcmVhZG9ubHkgQ0lSQ1VJVF9CUkVBS0VSX1RIUkVTSE9MRCA9IDU7XG4gIHByaXZhdGUgcmVhZG9ubHkgQ0lSQ1VJVF9CUkVBS0VSX1RJTUVPVVRfTVMgPSA1ICogNjAgKiAxMDAwOyAvLyA1IG1pbnV0ZXNcbiAgcHJpdmF0ZSByZWFkb25seSBSRUZSRVNIX1RIUkVTSE9MRCA9IDAuODsgLy8gUmVmcmVzaCB3aGVuIDgwJSBvZiBUVEwgaGFzIHBhc3NlZFxuICBwcml2YXRlIHJlYWRvbmx5IEpJVFRFUl9GQUNUT1IgPSAwLjI1OyAvLyDCsTI1JSByYW5kb21uZXNzIGZvciBqaXR0ZXJcblxuICAvLyBGaWxlIG9wZXJhdGlvbnMgc2VydmljZSBmb3Igc2VjdXJlIGZpbGUgSS9PXG4gIHByaXZhdGUgcmVhZG9ubHkgZmlsZU9wZXJhdGlvbnM6IElGaWxlT3BlcmF0aW9uc1NlcnZpY2U7XG5cbiAgLy8gRGVmYXVsdCBjb25maWd1cmF0aW9uIGNvbnN0YW50c1xuICBwcml2YXRlIHJlYWRvbmx5IERFRkFVTFRfVFRMX01TID0gNjAgKiA2MCAqIDEwMDA7IC8vIDEgaG91clxuICBwcml2YXRlIHJlYWRvbmx5IERFRkFVTFRfTUFYX1JFVFJJRVMgPSAzO1xuICBwcml2YXRlIHJlYWRvbmx5IERFRkFVTFRfQkFTRV9SRVRSWV9ERUxBWV9NUyA9IDEwMDA7XG4gIHByaXZhdGUgcmVhZG9ubHkgREVGQVVMVF9NQVhfUkVUUllfREVMQVlfTVMgPSAzMDAwMDtcbiAgcHJpdmF0ZSByZWFkb25seSBERUZBVUxUX0ZFVENIX1RJTUVPVVRfTVMgPSA1MDAwOyAvLyA1IHNlY29uZHNcbiAgcHJpdmF0ZSByZWFkb25seSBDSEVDS1NVTV9MRU5HVEggPSA4O1xuICBwcml2YXRlIHJlYWRvbmx5IEpTT05fSU5ERU5UID0gMjtcbiAgXG4gIGNvbnN0cnVjdG9yKGNvbmZpZzogQ29sbGVjdGlvbkluZGV4TWFuYWdlckNvbmZpZykge1xuICAgIC8vIENvbmZpZ3VyYXRpb24gd2l0aCBlbnZpcm9ubWVudCB2YXJpYWJsZSBvdmVycmlkZXNcbiAgICB0aGlzLlRUTF9NUyA9IGNvbmZpZy50dGxNcyB8fCB0aGlzLkRFRkFVTFRfVFRMX01TO1xuICAgIHRoaXMuRkVUQ0hfVElNRU9VVF9NUyA9IHRoaXMucGFyc2VGZXRjaFRpbWVvdXQoY29uZmlnLmZldGNoVGltZW91dE1zKTtcbiAgICB0aGlzLk1BWF9SRVRSSUVTID0gY29uZmlnLm1heFJldHJpZXMgfHwgdGhpcy5ERUZBVUxUX01BWF9SRVRSSUVTO1xuICAgIHRoaXMuQkFTRV9SRVRSWV9ERUxBWV9NUyA9IGNvbmZpZy5iYXNlUmV0cnlEZWxheU1zIHx8IHRoaXMuREVGQVVMVF9CQVNFX1JFVFJZX0RFTEFZX01TO1xuICAgIHRoaXMuTUFYX1JFVFJZX0RFTEFZX01TID0gY29uZmlnLm1heFJldHJ5RGVsYXlNcyB8fCB0aGlzLkRFRkFVTFRfTUFYX1JFVFJZX0RFTEFZX01TO1xuXG4gICAgLy8gSW5pdGlhbGl6ZSBmaWxlIG9wZXJhdGlvbnMgc2VydmljZVxuICAgIHRoaXMuZmlsZU9wZXJhdGlvbnMgPSBjb25maWcuZmlsZU9wZXJhdGlvbnM7XG5cbiAgICAvLyBDYWNoZSBkaXJlY3RvcnkgLSB1c2Ugfi8uZG9sbGhvdXNlL2NhY2hlL2NvbGxlY3Rpb24taW5kZXguanNvbiBhcyBzcGVjaWZpZWRcbiAgICBjb25zdCBjYWNoZURpciA9IGNvbmZpZy5jYWNoZURpciB8fCBwYXRoLmpvaW4ob3MuaG9tZWRpcigpLCAnLmRvbGxob3VzZScsICdjYWNoZScpO1xuICAgIHRoaXMuQ0FDSEVfRklMRSA9IHBhdGguam9pbihjYWNoZURpciwgJ2NvbGxlY3Rpb24taW5kZXguanNvbicpO1xuXG4gICAgbG9nZ2VyLmRlYnVnKCdDb2xsZWN0aW9uSW5kZXhNYW5hZ2VyIGluaXRpYWxpemVkJywge1xuICAgICAgdHRsTXM6IHRoaXMuVFRMX01TLFxuICAgICAgZmV0Y2hUaW1lb3V0TXM6IHRoaXMuRkVUQ0hfVElNRU9VVF9NUyxcbiAgICAgIGNhY2hlRmlsZTogdGhpcy5DQUNIRV9GSUxFLFxuICAgICAgbWF4UmV0cmllczogdGhpcy5NQVhfUkVUUklFU1xuICAgIH0pO1xuICB9XG4gIFxuICAvKipcbiAgICogUGFyc2UgZmV0Y2ggdGltZW91dCBmcm9tIGNvbmZpZyBvciBlbnZpcm9ubWVudCB2YXJpYWJsZVxuICAgKi9cbiAgcHJpdmF0ZSBwYXJzZUZldGNoVGltZW91dChjb25maWdWYWx1ZT86IG51bWJlcik6IG51bWJlciB7XG4gICAgLy8gQ2hlY2sgZW52aXJvbm1lbnQgdmFyaWFibGUgZmlyc3RcbiAgICBjb25zdCBlbnZUaW1lb3V0ID0gcHJvY2Vzcy5lbnYuQ09MTEVDVElPTl9GRVRDSF9USU1FT1VUO1xuICAgIGlmIChlbnZUaW1lb3V0KSB7XG4gICAgICBjb25zdCBwYXJzZWQgPSBOdW1iZXIucGFyc2VJbnQoZW52VGltZW91dCwgMTApO1xuICAgICAgaWYgKCFOdW1iZXIuaXNOYU4ocGFyc2VkKSAmJiBwYXJzZWQgPiAwKSB7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgVXNpbmcgQ09MTEVDVElPTl9GRVRDSF9USU1FT1VUIGZyb20gZW52aXJvbm1lbnQ6ICR7cGFyc2VkfW1zYCk7XG4gICAgICAgIHJldHVybiBwYXJzZWQ7XG4gICAgICB9XG4gICAgICBsb2dnZXIud2FybihgSW52YWxpZCBDT0xMRUNUSU9OX0ZFVENIX1RJTUVPVVQgdmFsdWU6ICR7ZW52VGltZW91dH0sIHVzaW5nIGRlZmF1bHRgKTtcbiAgICB9XG4gICAgXG4gICAgLy8gRmFsbCBiYWNrIHRvIGNvbmZpZyB2YWx1ZSBvciBkZWZhdWx0XG4gICAgcmV0dXJuIGNvbmZpZ1ZhbHVlIHx8IHRoaXMuREVGQVVMVF9GRVRDSF9USU1FT1VUX01TO1xuICB9XG4gIFxuICAvKipcbiAgICogR2V0IGNvbGxlY3Rpb24gaW5kZXggd2l0aCBzdGFsZS13aGlsZS1yZXZhbGlkYXRlIHBhdHRlcm5cbiAgICogUmV0dXJucyBjYWNoZWQgZGF0YSBpbW1lZGlhdGVseSBpZiBhdmFpbGFibGUsIHJlZnJlc2hlcyBpbiBiYWNrZ3JvdW5kXG4gICAqL1xuICBhc3luYyBnZXRJbmRleCgpOiBQcm9taXNlPENvbGxlY3Rpb25JbmRleD4ge1xuICAgIHRyeSB7XG4gICAgICAvLyBMb2FkIGZyb20gbWVtb3J5IGNhY2hlIGZpcnN0XG4gICAgICBpZiAoIXRoaXMuY2FjaGVkSW5kZXgpIHtcbiAgICAgICAgYXdhaXQgdGhpcy5sb2FkRnJvbURpc2soKTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgLy8gQ2hlY2sgaWYgd2Ugc2hvdWxkIHJldHVybiBzdGFsZSBjYWNoZSB3aGlsZSByZWZyZXNoaW5nXG4gICAgICBjb25zdCBzaG91bGRSZWZyZXNoID0gdGhpcy5zaG91bGRSZWZyZXNoQ2FjaGUoKTtcbiAgICAgIFxuICAgICAgaWYgKHRoaXMuY2FjaGVkSW5kZXggJiYgIXRoaXMuaXNDYWNoZUV4cGlyZWQoKSkge1xuICAgICAgICAvLyBDYWNoZSBpcyB2YWxpZCwgcmV0dXJuIGltbWVkaWF0ZWx5XG4gICAgICAgIGxvZ2dlci5kZWJ1ZygnUmV0dXJuaW5nIHZhbGlkIGNhY2hlZCBjb2xsZWN0aW9uIGluZGV4Jyk7XG4gICAgICAgIFxuICAgICAgICAvLyBTdGFydCBiYWNrZ3JvdW5kIHJlZnJlc2ggaWYgbmVlZGVkIGJ1dCBub3QgYWxyZWFkeSBydW5uaW5nXG4gICAgICAgIGlmIChzaG91bGRSZWZyZXNoICYmICF0aGlzLmlzUmVmcmVzaGluZykge1xuICAgICAgICAgIHRoaXMuc3RhcnRCYWNrZ3JvdW5kUmVmcmVzaCgpO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICByZXR1cm4gdGhpcy5jYWNoZWRJbmRleC5kYXRhO1xuICAgICAgfVxuICAgICAgXG4gICAgICAvLyBJZiB3ZSBoYXZlIHN0YWxlIGNhY2hlLCByZXR1cm4gaXQgd2hpbGUgcmVmcmVzaGluZyBpbiBiYWNrZ3JvdW5kXG4gICAgICBpZiAodGhpcy5jYWNoZWRJbmRleCAmJiB0aGlzLmlzQ2FjaGVFeHBpcmVkKCkpIHtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdSZXR1cm5pbmcgc3RhbGUgY2FjaGUgd2hpbGUgcmVmcmVzaGluZyBpbiBiYWNrZ3JvdW5kJyk7XG4gICAgICAgIFxuICAgICAgICAvLyBTdGFydCBiYWNrZ3JvdW5kIHJlZnJlc2ggaWYgbm90IGFscmVhZHkgcnVubmluZ1xuICAgICAgICBpZiAoIXRoaXMuaXNSZWZyZXNoaW5nKSB7XG4gICAgICAgICAgdGhpcy5zdGFydEJhY2tncm91bmRSZWZyZXNoKCk7XG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICAgIHJldHVybiB0aGlzLmNhY2hlZEluZGV4LmRhdGE7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIE5vIGNhY2hlIGF2YWlsYWJsZSwgbXVzdCBmZXRjaCBzeW5jaHJvbm91c2x5XG4gICAgICBsb2dnZXIuZGVidWcoJ05vIGNhY2hlIGF2YWlsYWJsZSwgZmV0Y2hpbmcgY29sbGVjdGlvbiBpbmRleCBzeW5jaHJvbm91c2x5Jyk7XG4gICAgICBjb25zdCBmcmVzaEluZGV4ID0gYXdhaXQgdGhpcy5mZXRjaFdpdGhSZXRyeSgpO1xuICAgICAgYXdhaXQgdGhpcy51cGRhdGVDYWNoZShmcmVzaEluZGV4KTtcbiAgICAgIHJldHVybiBmcmVzaEluZGV4LmRhdGE7XG4gICAgICBcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmVycm9yKCdGYWlsZWQgdG8gZ2V0IGNvbGxlY3Rpb24gaW5kZXgnLCB7IGVycm9yOiB0aGlzLmdldEVycm9yTWVzc2FnZShlcnJvcikgfSk7XG4gICAgICBcbiAgICAgIC8vIElmIHdlIGhhdmUgYW55IGNhY2hlZCBkYXRhIChldmVuIGV4cGlyZWQpLCByZXR1cm4gaXQgYXMgbGFzdCByZXNvcnRcbiAgICAgIGlmICh0aGlzLmNhY2hlZEluZGV4KSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdSZXR1cm5pbmcgZXhwaXJlZCBjYWNoZSBhcyBsYXN0IHJlc29ydCcpO1xuICAgICAgICByZXR1cm4gdGhpcy5jYWNoZWRJbmRleC5kYXRhO1xuICAgICAgfVxuICAgICAgXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYENvbGxlY3Rpb24gaW5kZXggbm90IGF2YWlsYWJsZTogJHt0aGlzLmdldEVycm9yTWVzc2FnZShlcnJvcil9YCk7XG4gICAgfVxuICB9XG4gIFxuICAvKipcbiAgICogRm9yY2UgcmVmcmVzaCB0aGUgY29sbGVjdGlvbiBpbmRleFxuICAgKi9cbiAgYXN5bmMgZm9yY2VSZWZyZXNoKCk6IFByb21pc2U8Q29sbGVjdGlvbkluZGV4PiB7XG4gICAgbG9nZ2VyLmRlYnVnKCdGb3JjZSByZWZyZXNoaW5nIGNvbGxlY3Rpb24gaW5kZXgnKTtcbiAgICBcbiAgICB0cnkge1xuICAgICAgY29uc3QgZnJlc2hJbmRleCA9IGF3YWl0IHRoaXMuZmV0Y2hXaXRoUmV0cnkoKTtcbiAgICAgIGF3YWl0IHRoaXMudXBkYXRlQ2FjaGUoZnJlc2hJbmRleCk7XG4gICAgICByZXR1cm4gZnJlc2hJbmRleC5kYXRhO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoJ0ZvcmNlIHJlZnJlc2ggZmFpbGVkJywgeyBlcnJvcjogdGhpcy5nZXRFcnJvck1lc3NhZ2UoZXJyb3IpIH0pO1xuICAgICAgXG4gICAgICAvLyBJZiB3ZSBoYXZlIGNhY2hlZCBkYXRhLCByZXR1cm4gaXRcbiAgICAgIGlmICh0aGlzLmNhY2hlZEluZGV4KSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdGb3JjZSByZWZyZXNoIGZhaWxlZCwgcmV0dXJuaW5nIGNhY2hlZCBkYXRhJyk7XG4gICAgICAgIHJldHVybiB0aGlzLmNhY2hlZEluZGV4LmRhdGE7XG4gICAgICB9XG4gICAgICBcbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgfVxuICBcbiAgLyoqXG4gICAqIENoZWNrIGlmIGNhY2hlIHNob3VsZCBiZSByZWZyZXNoZWQgKHdpdGhpbiBUVEwgYnV0IGdldHRpbmcgY2xvc2UgdG8gZXhwaXJ5KVxuICAgKi9cbiAgcHJpdmF0ZSBzaG91bGRSZWZyZXNoQ2FjaGUoKTogYm9vbGVhbiB7XG4gICAgaWYgKCF0aGlzLmNhY2hlZEluZGV4KSByZXR1cm4gdHJ1ZTtcbiAgICBcbiAgICBjb25zdCBhZ2UgPSBEYXRlLm5vdygpIC0gdGhpcy5jYWNoZWRJbmRleC50aW1lc3RhbXA7XG4gICAgY29uc3QgcmVmcmVzaFRocmVzaG9sZCA9IHRoaXMuVFRMX01TICogdGhpcy5SRUZSRVNIX1RIUkVTSE9MRDtcbiAgICBcbiAgICByZXR1cm4gYWdlID4gcmVmcmVzaFRocmVzaG9sZDtcbiAgfVxuICBcbiAgLyoqXG4gICAqIENoZWNrIGlmIGNhY2hlIGlzIGV4cGlyZWRcbiAgICovXG4gIHByaXZhdGUgaXNDYWNoZUV4cGlyZWQoKTogYm9vbGVhbiB7XG4gICAgaWYgKCF0aGlzLmNhY2hlZEluZGV4KSByZXR1cm4gdHJ1ZTtcbiAgICBcbiAgICBjb25zdCBhZ2UgPSBEYXRlLm5vdygpIC0gdGhpcy5jYWNoZWRJbmRleC50aW1lc3RhbXA7XG4gICAgcmV0dXJuIGF