@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.
195 lines • 24.8 kB
JavaScript
import { logger } from '../utils/logger.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import { randomBytes } from 'crypto';
import { getValidatedLockTimeout } from '../config/performance-constants.js';
/**
* FileLockManager - Prevents race conditions in concurrent file operations
*
* Features:
* - Resource-based locking with automatic cleanup
* - Configurable timeouts to prevent deadlocks
* - Atomic file operations with write-rename pattern
* - Lock queueing for concurrent requests
* - Comprehensive error handling and logging
* - Performance metrics tracking
*/
export class FileLockManager {
// Map of resource identifiers to their lock promises
locks = new Map();
// Lock acquisition metrics for monitoring
metrics = {
totalLockRequests: 0,
lockWaitTime: new Map(),
lockTimeouts: 0,
concurrentWaits: 0
};
logListener;
addLogListener(fn) {
this.logListener = fn;
return () => { this.logListener = undefined; };
}
// Default timeout for lock operations - using centralized configuration
DEFAULT_TIMEOUT_MS = getValidatedLockTimeout();
// Temporary file directory
TEMP_DIR = '.tmp';
/**
* Execute an operation with exclusive lock on a resource
* @param resource - Unique identifier for the resource (e.g., 'persona:name')
* @param operation - Async function to execute while holding the lock
* @param options - Lock options including timeout
* @returns Result of the operation
*/
async withLock(resource, operation, options = {}) {
const startTime = Date.now();
this.metrics.totalLockRequests++;
// Wait for any existing operation on this resource
const existingLock = this.locks.get(resource);
if (existingLock) {
this.metrics.concurrentWaits++;
const shortResource = path.basename(resource);
logger.debug(`Lock contention on: ${shortResource}`, { resource });
this.logListener?.('debug', 'Detect lock contention', { resource });
try {
await existingLock;
}
catch {
// Previous operation failed, but we can proceed
logger.debug(`Previous operation on ${resource} failed, proceeding`);
}
}
// Create new lock for this operation
const timeout = options.timeout || this.DEFAULT_TIMEOUT_MS;
const lockPromise = this.executeWithTimeout(operation, timeout, resource);
this.locks.set(resource, lockPromise);
try {
const result = await lockPromise;
// Record metrics
const waitTime = Date.now() - startTime;
if (!this.metrics.lockWaitTime.has(resource)) {
this.metrics.lockWaitTime.set(resource, []);
}
this.metrics.lockWaitTime.get(resource).push(waitTime);
// Only log locks that actually waited (>5ms indicates real contention)
if (waitTime > 5) {
const shortResource = path.basename(resource);
logger.debug(`Slow lock: ${shortResource} (${waitTime}ms)`);
}
return result;
}
finally {
// Clean up lock - unconditional delete is safe and race-free
// This operation completed, so remove its lock from the map
this.locks.delete(resource);
}
}
/**
* Execute operation with timeout protection
*/
async executeWithTimeout(operation, timeoutMs, resource) {
let timeoutHandle;
const timeoutPromise = new Promise((_, reject) => {
timeoutHandle = setTimeout(() => {
this.metrics.lockTimeouts++;
this.logListener?.('warn', 'Lock acquisition times out', { resource, timeoutMs });
reject(new Error(`Lock operation timeout for resource: ${resource}`));
}, timeoutMs);
});
try {
const result = await Promise.race([operation(), timeoutPromise]);
if (timeoutHandle)
clearTimeout(timeoutHandle);
return result;
}
catch (error) {
if (timeoutHandle)
clearTimeout(timeoutHandle);
throw error;
}
}
/**
* Perform atomic file write operation
* Writes to temporary file then renames to ensure atomicity
*/
async atomicWriteFile(filePath, content, options) {
const tempPath = await this.getTempFilePath(filePath);
const dir = path.dirname(tempPath);
try {
// Ensure temp directory exists
await fs.mkdir(dir, { recursive: true });
// Write to temporary file
await fs.writeFile(tempPath, content, options);
// Atomic rename (on same filesystem)
await fs.rename(tempPath, filePath);
}
catch (error) {
// Clean up temp file on error
try {
await fs.unlink(tempPath);
logger.debug(`Cleaned up temp file after error: ${tempPath}`);
}
catch (unlinkError) {
// Log cleanup failure but don't throw - original error is more important
logger.warn(`Failed to clean up temp file ${tempPath}: ${unlinkError}`);
}
throw error;
}
}
/**
* Perform atomic file read with lock
*/
async atomicReadFile(filePath, options) {
return this.withLock(`file:${filePath}`, async () => {
const content = await fs.readFile(filePath, options);
return content.toString();
});
}
/**
* Generate temporary file path for atomic operations
*/
async getTempFilePath(originalPath) {
const dir = path.dirname(originalPath);
const basename = path.basename(originalPath);
const random = randomBytes(8).toString('hex');
return path.join(dir, this.TEMP_DIR, `${basename}.${random}.tmp`);
}
/**
* Get lock metrics for monitoring
*/
getMetrics() {
const avgWaitTimes = new Map();
for (const [resource, times] of this.metrics.lockWaitTime.entries()) {
if (times.length > 0) {
const avg = times.reduce((a, b) => a + b, 0) / times.length;
avgWaitTimes.set(resource, Math.round(avg));
}
}
return {
totalRequests: this.metrics.totalLockRequests,
activeLocksCount: this.locks.size,
timeouts: this.metrics.lockTimeouts,
concurrentWaits: this.metrics.concurrentWaits,
avgWaitTimeByResource: Object.fromEntries(avgWaitTimes),
activeLocks: Array.from(this.locks.keys())
};
}
/**
* Clear all locks (use with caution - mainly for testing)
*/
clearAllLocks() {
this.locks.clear();
logger.warn('All file locks cleared - use only for testing/recovery');
}
/**
* Reset metrics
*/
resetMetrics() {
this.metrics = {
totalLockRequests: 0,
lockWaitTime: new Map(),
lockTimeouts: 0,
concurrentWaits: 0
};
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsZUxvY2tNYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlY3VyaXR5L2ZpbGVMb2NrTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDNUMsT0FBTyxLQUFLLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUNyQyxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUU3RTs7Ozs7Ozs7OztHQVVHO0FBQ0gsTUFBTSxPQUFPLGVBQWU7SUFDMUIscURBQXFEO0lBQzdDLEtBQUssR0FBRyxJQUFJLEdBQUcsRUFBd0IsQ0FBQztJQUVoRCwwQ0FBMEM7SUFDbEMsT0FBTyxHQUFHO1FBQ2hCLGlCQUFpQixFQUFFLENBQUM7UUFDcEIsWUFBWSxFQUFFLElBQUksR0FBRyxFQUFvQjtRQUN6QyxZQUFZLEVBQUUsQ0FBQztRQUNmLGVBQWUsRUFBRSxDQUFDO0tBQ25CLENBQUM7SUFFTSxXQUFXLENBQXlHO0lBRTVILGNBQWMsQ0FBQyxFQUF5RztRQUN0SCxJQUFJLENBQUMsV0FBVyxHQUFHLEVBQUUsQ0FBQztRQUN0QixPQUFPLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxXQUFXLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pELENBQUM7SUFFRCx3RUFBd0U7SUFDdkQsa0JBQWtCLEdBQUcsdUJBQXVCLEVBQUUsQ0FBQztJQUVoRSwyQkFBMkI7SUFDVixRQUFRLEdBQUcsTUFBTSxDQUFDO0lBRW5DOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxRQUFRLENBQ1osUUFBZ0IsRUFDaEIsU0FBMkIsRUFDM0IsVUFBZ0MsRUFBRTtRQUVsQyxNQUFNLFNBQVMsR0FBRyxJQUFJ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RSxJQUFJLEdBQUcsRUFBb0I7WUFDekMsWUFBWSxFQUFFLENBQUM7WUFDZixlQUFlLEVBQUUsQ0FBQztTQUNuQixDQUFDO0lBQ0osQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzL3Byb21pc2VzJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyByYW5kb21CeXRlcyB9IGZyb20gJ2NyeXB0byc7XG5pbXBvcnQgeyBnZXRWYWxpZGF0ZWRMb2NrVGltZW91dCB9IGZyb20gJy4uL2NvbmZpZy9wZXJmb3JtYW5jZS1jb25zdGFudHMuanMnO1xuXG4vKipcbiAqIEZpbGVMb2NrTWFuYWdlciAtIFByZXZlbnRzIHJhY2UgY29uZGl0aW9ucyBpbiBjb25jdXJyZW50IGZpbGUgb3BlcmF0aW9uc1xuICogXG4gKiBGZWF0dXJlczpcbiAqIC0gUmVzb3VyY2UtYmFzZWQgbG9ja2luZyB3aXRoIGF1dG9tYXRpYyBjbGVhbnVwXG4gKiAtIENvbmZpZ3VyYWJsZSB0aW1lb3V0cyB0byBwcmV2ZW50IGRlYWRsb2Nrc1xuICogLSBBdG9taWMgZmlsZSBvcGVyYXRpb25zIHdpdGggd3JpdGUtcmVuYW1lIHBhdHRlcm5cbiAqIC0gTG9jayBxdWV1ZWluZyBmb3IgY29uY3VycmVudCByZXF1ZXN0c1xuICogLSBDb21wcmVoZW5zaXZlIGVycm9yIGhhbmRsaW5nIGFuZCBsb2dnaW5nXG4gKiAtIFBlcmZvcm1hbmNlIG1ldHJpY3MgdHJhY2tpbmdcbiAqL1xuZXhwb3J0IGNsYXNzIEZpbGVMb2NrTWFuYWdlciB7XG4gIC8vIE1hcCBvZiByZXNvdXJjZSBpZGVudGlmaWVycyB0byB0aGVpciBsb2NrIHByb21pc2VzXG4gIHByaXZhdGUgbG9ja3MgPSBuZXcgTWFwPHN0cmluZywgUHJvbWlzZTxhbnk+PigpO1xuXG4gIC8vIExvY2sgYWNxdWlzaXRpb24gbWV0cmljcyBmb3IgbW9uaXRvcmluZ1xuICBwcml2YXRlIG1ldHJpY3MgPSB7XG4gICAgdG90YWxMb2NrUmVxdWVzdHM6IDAsXG4gICAgbG9ja1dhaXRUaW1lOiBuZXcgTWFwPHN0cmluZywgbnVtYmVyW10+KCksXG4gICAgbG9ja1RpbWVvdXRzOiAwLFxuICAgIGNvbmN1cnJlbnRXYWl0czogMFxuICB9O1xuXG4gIHByaXZhdGUgbG9nTGlzdGVuZXI/OiAobGV2ZWw6ICdkZWJ1ZycgfCAnaW5mbycgfCAnd2FybicgfCAnZXJyb3InLCBtZXNzYWdlOiBzdHJpbmcsIGRhdGE/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikgPT4gdm9pZDtcblxuICBhZGRMb2dMaXN0ZW5lcihmbjogKGxldmVsOiAnZGVidWcnIHwgJ2luZm8nIHwgJ3dhcm4nIHwgJ2Vycm9yJywgbWVzc2FnZTogc3RyaW5nLCBkYXRhPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pID0+IHZvaWQpOiAoKSA9PiB2b2lkIHtcbiAgICB0aGlzLmxvZ0xpc3RlbmVyID0gZm47XG4gICAgcmV0dXJuICgpID0+IHsgdGhpcy5sb2dMaXN0ZW5lciA9IHVuZGVmaW5lZDsgfTtcbiAgfVxuXG4gIC8vIERlZmF1bHQgdGltZW91dCBmb3IgbG9jayBvcGVyYXRpb25zIC0gdXNpbmcgY2VudHJhbGl6ZWQgY29uZmlndXJhdGlvblxuICBwcml2YXRlIHJlYWRvbmx5IERFRkFVTFRfVElNRU9VVF9NUyA9IGdldFZhbGlkYXRlZExvY2tUaW1lb3V0KCk7XG5cbiAgLy8gVGVtcG9yYXJ5IGZpbGUgZGlyZWN0b3J5XG4gIHByaXZhdGUgcmVhZG9ubHkgVEVNUF9ESVIgPSAnLnRtcCc7XG5cbiAgLyoqXG4gICAqIEV4ZWN1dGUgYW4gb3BlcmF0aW9uIHdpdGggZXhjbHVzaXZlIGxvY2sgb24gYSByZXNvdXJjZVxuICAgKiBAcGFyYW0gcmVzb3VyY2UgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIHJlc291cmNlIChlLmcuLCAncGVyc29uYTpuYW1lJylcbiAgICogQHBhcmFtIG9wZXJhdGlvbiAtIEFzeW5jIGZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hpbGUgaG9sZGluZyB0aGUgbG9ja1xuICAgKiBAcGFyYW0gb3B0aW9ucyAtIExvY2sgb3B0aW9ucyBpbmNsdWRpbmcgdGltZW91dFxuICAgKiBAcmV0dXJucyBSZXN1bHQgb2YgdGhlIG9wZXJhdGlvblxuICAgKi9cbiAgYXN5bmMgd2l0aExvY2s8VD4oXG4gICAgcmVzb3VyY2U6IHN0cmluZyxcbiAgICBvcGVyYXRpb246ICgpID0+IFByb21pc2U8VD4sXG4gICAgb3B0aW9uczogeyB0aW1lb3V0PzogbnVtYmVyIH0gPSB7fVxuICApOiBQcm9taXNlPFQ+IHtcbiAgICBjb25zdCBzdGFydFRpbWUgPSBEYXRlLm5vdygpO1xuICAgIHRoaXMubWV0cmljcy50b3RhbExvY2tSZXF1ZXN0cysrO1xuICAgIFxuICAgIC8vIFdhaXQgZm9yIGFueSBleGlzdGluZyBvcGVyYXRpb24gb24gdGhpcyByZXNvdXJjZVxuICAgIGNvbnN0IGV4aXN0aW5nTG9jayA9IHRoaXMubG9ja3MuZ2V0KHJlc291cmNlKTtcbiAgICBpZiAoZXhpc3RpbmdMb2NrKSB7XG4gICAgICB0aGlzLm1ldHJpY3MuY29uY3VycmVudFdhaXRzKys7XG4gICAgICBjb25zdCBzaG9ydFJlc291cmNlID0gcGF0aC5iYXNlbmFtZShyZXNvdXJjZSk7XG4gICAgICBsb2dnZXIuZGVidWcoYExvY2sgY29udGVudGlvbiBvbjogJHtzaG9ydFJlc291cmNlfWAsIHsgcmVzb3VyY2UgfSk7XG4gICAgICB0aGlzLmxvZ0xpc3RlbmVyPy4oJ2RlYnVnJywgJ0RldGVjdCBsb2NrIGNvbnRlbnRpb24nLCB7IHJlc291cmNlIH0pO1xuICAgICAgXG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCBleGlzdGluZ0xvY2s7XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gUHJldmlvdXMgb3BlcmF0aW9uIGZhaWxlZCwgYnV0IHdlIGNhbiBwcm9jZWVkXG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgUHJldmlvdXMgb3BlcmF0aW9uIG9uICR7cmVzb3VyY2V9IGZhaWxlZCwgcHJvY2VlZGluZ2ApO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICAvLyBDcmVhdGUgbmV3IGxvY2sgZm9yIHRoaXMgb3BlcmF0aW9uXG4gICAgY29uc3QgdGltZW91dCA9IG9wdGlvbnMudGltZW91dCB8fCB0aGlzLkRFRkFVTFRfVElNRU9VVF9NUztcbiAgICBjb25zdCBsb2NrUHJvbWlzZSA9IHRoaXMuZXhlY3V0ZVdpdGhUaW1lb3V0KG9wZXJhdGlvbiwgdGltZW91dCwgcmVzb3VyY2UpO1xuICAgIHRoaXMubG9ja3Muc2V0KHJlc291cmNlLCBsb2NrUHJvbWlzZSk7XG4gICAgXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGxvY2tQcm9taXNlO1xuICAgICAgXG4gICAgICAvLyBSZWNvcmQgbWV0cmljc1xuICAgICAgY29uc3Qgd2FpdFRpbWUgPSBEYXRlLm5vdygpIC0gc3RhcnRUaW1lO1xuICAgICAgaWYgKCF0aGlzLm1ldHJpY3MubG9ja1dhaXRUaW1lLmhhcyhyZXNvdXJjZSkpIHtcbiAgICAgICAgdGhpcy5tZXRyaWNzLmxvY2tXYWl0VGltZS5zZXQocmVzb3VyY2UsIFtdKTtcbiAgICAgIH1cbiAgICAgIHRoaXMubWV0cmljcy5sb2NrV2FpdFRpbWUuZ2V0KHJlc291cmNlKSEucHVzaCh3YWl0VGltZSk7XG4gICAgICBcbiAgICAgIC8vIE9ubHkgbG9nIGxvY2tzIHRoYXQgYWN0dWFsbHkgd2FpdGVkICg+NW1zIGluZGljYXRlcyByZWFsIGNvbnRlbnRpb24pXG4gICAgICBpZiAod2FpdFRpbWUgPiA1KSB7XG4gICAgICAgIGNvbnN0IHNob3J0UmVzb3VyY2UgPSBwYXRoLmJhc2VuYW1lKHJlc291cmNlKTtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKGBTbG93IGxvY2s6ICR7c2hvcnRSZXNvdXJjZX0gKCR7d2FpdFRpbWV9bXMpYCk7XG4gICAgICB9XG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH0gZmluYWxseSB7XG4gICAgICAvLyBDbGVhbiB1cCBsb2NrIC0gdW5jb25kaXRpb25hbCBkZWxldGUgaXMgc2FmZSBhbmQgcmFjZS1mcmVlXG4gICAgICAvLyBUaGlzIG9wZXJhdGlvbiBjb21wbGV0ZWQsIHNvIHJlbW92ZSBpdHMgbG9jayBmcm9tIHRoZSBtYXBcbiAgICAgIHRoaXMubG9ja3MuZGVsZXRlKHJlc291cmNlKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRXhlY3V0ZSBvcGVyYXRpb24gd2l0aCB0aW1lb3V0IHByb3RlY3Rpb25cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZXhlY3V0ZVdpdGhUaW1lb3V0PFQ+KFxuICAgIG9wZXJhdGlvbjogKCkgPT4gUHJvbWlzZTxUPixcbiAgICB0aW1lb3V0TXM6IG51bWJlcixcbiAgICByZXNvdXJjZTogc3RyaW5nXG4gICk6IFByb21pc2U8VD4ge1xuICAgIGxldCB0aW1lb3V0SGFuZGxlOiBOb2RlSlMuVGltZW91dCB8IHVuZGVmaW5lZDtcbiAgICBcbiAgICBjb25zdCB0aW1lb3V0UHJvbWlzZSA9IG5ldyBQcm9taXNlPG5ldmVyPigoXywgcmVqZWN0KSA9PiB7XG4gICAgICB0aW1lb3V0SGFuZGxlID0gc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgIHRoaXMubWV0cmljcy5sb2NrVGltZW91dHMrKztcbiAgICAgICAgdGhpcy5sb2dMaXN0ZW5lcj8uKCd3YXJuJywgJ0xvY2sgYWNxdWlzaXRpb24gdGltZXMgb3V0JywgeyByZXNvdXJjZSwgdGltZW91dE1zIH0pO1xuICAgICAgICByZWplY3QobmV3IEVycm9yKGBMb2NrIG9wZXJhdGlvbiB0aW1lb3V0IGZvciByZXNvdXJjZTogJHtyZXNvdXJjZX1gKSk7XG4gICAgICB9LCB0aW1lb3V0TXMpO1xuICAgIH0pO1xuICAgIFxuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBQcm9taXNlLnJhY2UoW29wZXJhdGlvbigpLCB0aW1lb3V0UHJvbWlzZV0pO1xuICAgICAgaWYgKHRpbWVvdXRIYW5kbGUpIGNsZWFyVGltZW91dCh0aW1lb3V0SGFuZGxlKTtcbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmICh0aW1lb3V0SGFuZGxlKSBjbGVhclRpbWVvdXQodGltZW91dEhhbmRsZSk7XG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUGVyZm9ybSBhdG9taWMgZmlsZSB3cml0ZSBvcGVyYXRpb25cbiAgICogV3JpdGVzIHRvIHRlbXBvcmFyeSBmaWxlIHRoZW4gcmVuYW1lcyB0byBlbnN1cmUgYXRvbWljaXR5XG4gICAqL1xuICBhc3luYyBhdG9taWNXcml0ZUZpbGUoXG4gICAgZmlsZVBhdGg6IHN0cmluZyxcbiAgICBjb250ZW50OiBzdHJpbmcsXG4gICAgb3B0aW9ucz86IHsgZW5jb2Rpbmc/OiBCdWZmZXJFbmNvZGluZyB9XG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHRlbXBQYXRoID0gYXdhaXQgdGhpcy5nZXRUZW1wRmlsZVBhdGgoZmlsZVBhdGgpO1xuICAgIGNvbnN0IGRpciA9IHBhdGguZGlybmFtZSh0ZW1wUGF0aCk7XG4gICAgXG4gICAgdHJ5IHtcbiAgICAgIC8vIEVuc3VyZSB0ZW1wIGRpcmVjdG9yeSBleGlzdHNcbiAgICAgIGF3YWl0IGZzLm1rZGlyKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgICBcbiAgICAgIC8vIFdyaXRlIHRvIHRlbXBvcmFyeSBmaWxlXG4gICAgICBhd2FpdCBmcy53cml0ZUZpbGUodGVtcFBhdGgsIGNvbnRlbnQsIG9wdGlvbnMpO1xuICAgICAgXG4gICAgICAvLyBBdG9taWMgcmVuYW1lIChvbiBzYW1lIGZpbGVzeXN0ZW0pXG4gICAgICBhd2FpdCBmcy5yZW5hbWUodGVtcFBhdGgsIGZpbGVQYXRoKTtcbiAgICAgIFxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAvLyBDbGVhbiB1cCB0ZW1wIGZpbGUgb24gZXJyb3JcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IGZzLnVubGluayh0ZW1wUGF0aCk7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgQ2xlYW5lZCB1cCB0ZW1wIGZpbGUgYWZ0ZXIgZXJyb3I6ICR7dGVtcFBhdGh9YCk7XG4gICAgICB9IGNhdGNoICh1bmxpbmtFcnJvcikge1xuICAgICAgICAvLyBMb2cgY2xlYW51cCBmYWlsdXJlIGJ1dCBkb24ndCB0aHJvdyAtIG9yaWdpbmFsIGVycm9yIGlzIG1vcmUgaW1wb3J0YW50XG4gICAgICAgIGxvZ2dlci53YXJuKGBGYWlsZWQgdG8gY2xlYW4gdXAgdGVtcCBmaWxlICR7dGVtcFBhdGh9OiAke3VubGlua0Vycm9yfWApO1xuICAgICAgfVxuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFBlcmZvcm0gYXRvbWljIGZpbGUgcmVhZCB3aXRoIGxvY2tcbiAgICovXG4gIGFzeW5jIGF0b21pY1JlYWRGaWxlKFxuICAgIGZpbGVQYXRoOiBzdHJpbmcsXG4gICAgb3B0aW9ucz86IHsgZW5jb2Rpbmc/OiBCdWZmZXJFbmNvZGluZyB9XG4gICk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgcmV0dXJuIHRoaXMud2l0aExvY2soYGZpbGU6JHtmaWxlUGF0aH1gLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgZnMucmVhZEZpbGUoZmlsZVBhdGgsIG9wdGlvbnMpO1xuICAgICAgcmV0dXJuIGNvbnRlbnQudG9TdHJpbmcoKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZW5lcmF0ZSB0ZW1wb3JhcnkgZmlsZSBwYXRoIGZvciBhdG9taWMgb3BlcmF0aW9uc1xuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBnZXRUZW1wRmlsZVBhdGgob3JpZ2luYWxQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IGRpciA9IHBhdGguZGlybmFtZShvcmlnaW5hbFBhdGgpO1xuICAgIGNvbnN0IGJhc2VuYW1lID0gcGF0aC5iYXNlbmFtZShvcmlnaW5hbFBhdGgpO1xuICAgIGNvbnN0IHJhbmRvbSA9IHJhbmRvbUJ5dGVzKDgpLnRvU3RyaW5nKCdoZXgnKTtcbiAgICByZXR1cm4gcGF0aC5qb2luKGRpciwgdGhpcy5URU1QX0RJUiwgYCR7YmFzZW5hbWV9LiR7cmFuZG9tfS50bXBgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgbG9jayBtZXRyaWNzIGZvciBtb25pdG9yaW5nXG4gICAqL1xuICBnZXRNZXRyaWNzKCkge1xuICAgIGNvbnN0IGF2Z1dhaXRUaW1lcyA9IG5ldyBNYXA8c3RyaW5nLCBudW1iZXI+KCk7XG4gICAgZm9yIChjb25zdCBbcmVzb3VyY2UsIHRpbWVzXSBvZiB0aGlzLm1ldHJpY3MubG9ja1dhaXRUaW1lLmVudHJpZXMoKSkge1xuICAgICAgaWYgKHRpbWVzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgY29uc3QgYXZnID0gdGltZXMucmVkdWNlKChhLCBiKSA9PiBhICsgYiwgMCkgLyB0aW1lcy5sZW5ndGg7XG4gICAgICAgIGF2Z1dhaXRUaW1lcy5zZXQocmVzb3VyY2UsIE1hdGgucm91bmQoYXZnKSk7XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIHJldHVybiB7XG4gICAgICB0b3RhbFJlcXVlc3RzOiB0aGlzLm1ldHJpY3MudG90YWxMb2NrUmVxdWVzdHMsXG4gICAgICBhY3RpdmVMb2Nrc0NvdW50OiB0aGlzLmxvY2tzLnNpemUsXG4gICAgICB0aW1lb3V0czogdGhpcy5tZXRyaWNzLmxvY2tUaW1lb3V0cyxcbiAgICAgIGNvbmN1cnJlbnRXYWl0czogdGhpcy5tZXRyaWNzLmNvbmN1cnJlbnRXYWl0cyxcbiAgICAgIGF2Z1dhaXRUaW1lQnlSZXNvdXJjZTogT2JqZWN0LmZyb21FbnRyaWVzKGF2Z1dhaXRUaW1lcyksXG4gICAgICBhY3RpdmVMb2NrczogQXJyYXkuZnJvbSh0aGlzLmxvY2tzLmtleXMoKSlcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFyIGFsbCBsb2NrcyAodXNlIHdpdGggY2F1dGlvbiAtIG1haW5seSBmb3IgdGVzdGluZylcbiAgICovXG4gIGNsZWFyQWxsTG9ja3MoKTogdm9pZCB7XG4gICAgdGhpcy5sb2Nrcy5jbGVhcigpO1xuICAgIGxvZ2dlci53YXJuKCdBbGwgZmlsZSBsb2NrcyBjbGVhcmVkIC0gdXNlIG9ubHkgZm9yIHRlc3RpbmcvcmVjb3ZlcnknKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXNldCBtZXRyaWNzXG4gICAqL1xuICByZXNldE1ldHJpY3MoKTogdm9pZCB7XG4gICAgdGhpcy5tZXRyaWNzID0ge1xuICAgICAgdG90YWxMb2NrUmVxdWVzdHM6IDAsXG4gICAgICBsb2NrV2FpdFRpbWU6IG5ldyBNYXA8c3RyaW5nLCBudW1iZXJbXT4oKSxcbiAgICAgIGxvY2tUaW1lb3V0czogMCxcbiAgICAgIGNvbmN1cnJlbnRXYWl0czogMFxuICAgIH07XG4gIH1cbn0iXX0=