@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.
118 lines • 17.4 kB
JavaScript
/**
* GitHub API client for collection integration
*/
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { SECURITY_LIMITS } from '../security/constants.js';
import { TokenManager } from '../security/tokenManager.js';
export class GitHubClient {
apiCache;
rateLimitTracker;
constructor(apiCache, rateLimitTracker) {
this.apiCache = apiCache;
this.rateLimitTracker = rateLimitTracker;
}
/**
* Check rate limit for API calls
*/
checkRateLimit(key = 'default') {
const now = Date.now();
const requests = this.rateLimitTracker.get(key) || [];
// Remove requests outside the window
const validRequests = requests.filter(time => now - time < SECURITY_LIMITS.RATE_LIMIT_WINDOW_MS);
if (validRequests.length >= SECURITY_LIMITS.RATE_LIMIT_REQUESTS) {
throw new Error(`Rate limit exceeded. Max ${SECURITY_LIMITS.RATE_LIMIT_REQUESTS} requests per minute.`);
}
validRequests.push(now);
this.rateLimitTracker.set(key, validRequests);
}
/**
* Fetch data from GitHub API with caching and rate limiting
*/
async fetchFromGitHub(url, requireAuth = false) {
try {
// Check rate limit
this.checkRateLimit('github_api');
// Check cache first
const cached = this.apiCache.get(url);
if (cached) {
return cached;
}
// Add GitHub token if available for higher rate limits
const headers = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'DollhouseMCP/1.0'
};
// Use TokenManager for secure token handling
// CRITICAL FIX #471: Must use async method to retrieve OAuth tokens from secure storage
// The OAuth flow (setup_github_auth) stores tokens in secure OS keychain/credential store
// which requires async access. The sync method only checks environment variables,
// missing tokens stored by the OAuth authentication flow.
const token = await TokenManager.getGitHubTokenAsync();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
else if (requireAuth) {
throw new Error('GitHub authentication required but no valid token available. Please use setup_github_auth or set GITHUB_TOKEN environment variable.');
}
// Create fetch with timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
const response = await fetch(url, {
headers,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
if (response.status === 403) {
const errorMsg = token
? 'GitHub API rate limit exceeded or token lacks required permissions.'
: 'GitHub API rate limit exceeded. Consider using setup_github_auth or setting GITHUB_TOKEN environment variable.';
throw new Error(errorMsg);
}
if (response.status === 401) {
throw new Error('GitHub API authentication failed. Please check your GITHUB_TOKEN.');
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// Cache the successful response
this.apiCache.set(url, data);
return data;
}
catch (error) {
// Use TokenManager for safe error handling
const errorMessage = error instanceof Error ? error.message : String(error);
const safeMessage = TokenManager.createSafeErrorMessage(errorMessage);
const errorDetails = {
originalMessage: safeMessage,
url
};
// Preserve stack trace and error type information
if (error instanceof Error) {
errorDetails.errorType = error.constructor.name;
errorDetails.stack = error.stack;
// Special handling for common error types
if (error.name === 'AbortError') {
errorDetails.timeout = true;
}
}
const mcpError = new McpError(ErrorCode.InternalError, `Failed to fetch from GitHub: ${safeMessage}`, errorDetails);
// Also preserve original error for debugging
mcpError.cause = error;
throw mcpError;
}
}
/**
* Validate token permissions for collection operations
*/
async validateCollectionPermissions() {
// NOTE: Using 'marketplace' scope for backward compatibility with TokenManager.
// This is an internal implementation detail that doesn't affect functionality. (PR #280)
const validation = await TokenManager.ensureTokenPermissions('marketplace');
if (!validation.isValid) {
const safeMessage = TokenManager.createSafeErrorMessage(validation.error || 'Unknown validation error');
throw new Error(`GitHub token validation failed: ${safeMessage}`);
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2l0SHViQ2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbGxlY3Rpb24vR2l0SHViQ2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBRUgsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUV6RSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDM0QsT0FBTyxFQUFFLFlBQVksRUFBZSxNQUFNLDZCQUE2QixDQUFDO0FBR3hFLE1BQU0sT0FBTyxZQUFZO0lBQ2YsUUFBUSxDQUFXO0lBQ25CLGdCQUFnQixDQUF3QjtJQUVoRCxZQUFZLFFBQWtCLEVBQUUsZ0JBQXVDO1FBQ3JFLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQztJQUMzQyxDQUFDO0lBRUQ7O09BRUc7SUFDSyxjQUFjLENBQUMsTUFBYyxTQUFTO1FBQzVDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUN2QixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUV0RCxxQ0FBcUM7UUFDckMsTUFBTSxhQUFhLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEdBQUcsR0FBRyxJQUFJLEdBQUcsZUFBZSxDQUFDLG9CQUFvQixDQUFDLENBQUM7UUFFakcsSUFBSSxhQUFhLENBQUMsTUFBTSxJQUFJLGVBQWUsQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO1lBQ2hFLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLGVBQWUsQ0FBQyxtQkFBbUIsdUJBQXVCLENBQUMsQ0FBQztRQUMxRyxDQUFDO1FBRUQsYUFBYSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN4QixJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxhQUFhLENBQUMsQ0FBQztJQUNoRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsZUFBZSxDQUFDLEdBQVcsRUFBRSxjQUF1QixLQUFLO1FBQzdELElBQUksQ0FBQztZQUNILG1CQUFtQjtZQUNuQixJQUFJLENBQUMsY0FBYyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBRWxDLG9CQUFvQjtZQUNwQixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUN0QyxJQUFJLE1BQU0sRUFBRSxDQUFDO2dCQUNYLE9BQU8sTUFBTSxDQUFDO1lBQ2hCLENBQUM7WUFFRCx1REFBdUQ7WUFDdkQsTUFBTSxPQUFPLEdBQTJCO2dCQUN0QyxRQUFRLEVBQUUsZ0NBQWdDO2dCQUMxQyxZQUFZLEVBQUUsa0JBQWtCO2FBQ2pDLENBQUM7WUFFRiw2Q0FBNkM7WUFDN0Msd0ZBQXdGO1lBQ3hGLDBGQUEwRjtZQUMxRixrRkFBa0Y7WUFDbEYsMERBQTBEO1lBQzFELE1BQU0sS0FBSyxHQUFHLE1BQU0sWUFBWSxDQUFDLG1CQUFtQixFQUFFLENBQUM7WUFDdkQsSUFBSSxLQUFLLEVBQUUsQ0FBQztnQkFDVixPQUFPLENBQUMsZUFBZSxDQUFDLEdBQUcsVUFBVSxLQUFLLEVBQUUsQ0FBQztZQUMvQyxDQUFDO2lCQUFNLElBQUksV0FBVyxFQUFFLENBQUM7Z0JBQ3ZCLE1BQU0sSUFBSSxLQUFLLENBQUMscUlBQXFJLENBQUMsQ0FBQztZQUN6SixDQUFDO1lBRUQsNEJBQTRCO1lBQzVCLE1BQU0sVUFBVSxHQUFHLElBQUksZUFBZSxFQUFFLENBQUM7WUFDekMsTUFBTSxTQUFTLEdBQUcsVUFBVSxDQUFDLEdBQUcsRUFBRSxDQUFDLFVBQVUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLG9CQUFvQjtZQUVuRixNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLEVBQUU7Z0JBQ2hDLE9BQU87Z0JBQ1AsTUFBTSxFQUFFLFVBQVUsQ0FBQyxNQUFNO2FBQzFCLENBQUMsQ0FBQztZQUVILFlBQVksQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUV4QixJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxDQUFDO2dCQUNqQixJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUM7b0JBQzVCLE1BQU0sUUFBUSxHQUFHLEtBQUs7d0JBQ3BCLENBQUMsQ0FBQyxxRUFBcUU7d0JBQ3ZFLENBQUMsQ0FBQyxnSEFBZ0gsQ0FBQztvQkFDckgsTUFBTSxJQUFJLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDNUIsQ0FBQztnQkFDRCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUM7b0JBQzVCLE1BQU0sSUFBSSxLQUFLLENBQUMsbUVBQW1FLENBQUMsQ0FBQztnQkFDdkYsQ0FBQztnQkFDRCxNQUFNLElBQUksS0FBSyxDQUFDLHFCQUFxQixRQUFRLENBQUMsTUFBTSxJQUFJLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO1lBQ2pGLENBQUM7WUFFRCxNQUFNLElBQUksR0FBRyxNQUFNLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUVuQyxnQ0FBZ0M7WUFDaEMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDO1lBRTdCLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZiwyQ0FBMkM7WUFDM0MsTUFBTSxZQUFZLEdBQUcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQzVFLE1BQU0sV0FBVyxHQUFHLFlBQVksQ0FBQyxzQkFBc0IsQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUV0RSxNQUFNLFlBQVksR0FBUTtnQkFDeEIsZUFBZSxFQUFFLFdBQVc7Z0JBQzVCLEdBQUc7YUFDSixDQUFDO1lBRUYsa0RBQWtEO1lBQ2xELElBQUksS0FBSyxZQUFZLEtBQUssRUFBRSxDQUFDO2dCQUMzQixZQUFZLENBQUMsU0FBUyxHQUFHLEtBQUssQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDO2dCQUNoRCxZQUFZLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUM7Z0JBRWpDLDBDQUEwQztnQkFDMUMsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLFlBQVksRUFBRSxDQUFDO29CQUNoQyxZQUFZLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztnQkFDOUIsQ0FBQztZQUNILENBQUM7WUFFRCxNQUFNLFFBQVEsR0FBRyxJQUFJLFFBQVEsQ0FDM0IsU0FBUyxDQUFDLGFBQWEsRUFDdkIsZ0NBQWdDLFdBQVcsRUFBRSxFQUM3QyxZQUFZLENBQ2IsQ0FBQztZQUVGLDZDQUE2QztZQUM1QyxRQUFnQixDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7WUFFaEMsTUFBTSxRQUFRLENBQUM7UUFDakIsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyw2QkFBNkI7UUFDakMsZ0ZBQWdGO1FBQ2hGLHlGQUF5RjtRQUN6RixNQUFNLFVBQVUsR0FBRyxNQUFNLFlBQVksQ0FBQyxzQkFBc0IsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUM1RSxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3hCLE1BQU0sV0FBVyxHQUFHLFlBQVksQ0FBQyxzQkFBc0IsQ0FBQyxVQUFVLENBQUMsS0FBSyxJQUFJLDBCQUEwQixDQUFDLENBQUM7WUFDeEcsTUFBTSxJQUFJLEtBQUssQ0FBQyxtQ0FBbUMsV0FBVyxFQUFFLENBQUMsQ0FBQztRQUNwRSxDQUFDO0lBQ0gsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHaXRIdWIgQVBJIGNsaWVudCBmb3IgY29sbGVjdGlvbiBpbnRlZ3JhdGlvblxuICovXG5cbmltcG9ydCB7IE1jcEVycm9yLCBFcnJvckNvZGUgfSBmcm9tICdAbW9kZWxjb250ZXh0cHJvdG9jb2wvc2RrL3R5cGVzLmpzJztcbmltcG9ydCB7IEFQSUNhY2hlIH0gZnJvbSAnLi4vY2FjaGUvQVBJQ2FjaGUuanMnO1xuaW1wb3J0IHsgU0VDVVJJVFlfTElNSVRTIH0gZnJvbSAnLi4vc2VjdXJpdHkvY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IFRva2VuTWFuYWdlciwgVG9rZW5TY29wZXMgfSBmcm9tICcuLi9zZWN1cml0eS90b2tlbk1hbmFnZXIuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuZXhwb3J0IGNsYXNzIEdpdEh1YkNsaWVudCB7XG4gIHByaXZhdGUgYXBpQ2FjaGU6IEFQSUNhY2hlO1xuICBwcml2YXRlIHJhdGVMaW1pdFRyYWNrZXI6IE1hcDxzdHJpbmcsIG51bWJlcltdPjtcbiAgXG4gIGNvbnN0cnVjdG9yKGFwaUNhY2hlOiBBUElDYWNoZSwgcmF0ZUxpbWl0VHJhY2tlcjogTWFwPHN0cmluZywgbnVtYmVyW10+KSB7XG4gICAgdGhpcy5hcGlDYWNoZSA9IGFwaUNhY2hlO1xuICAgIHRoaXMucmF0ZUxpbWl0VHJhY2tlciA9IHJhdGVMaW1pdFRyYWNrZXI7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBDaGVjayByYXRlIGxpbWl0IGZvciBBUEkgY2FsbHNcbiAgICovXG4gIHByaXZhdGUgY2hlY2tSYXRlTGltaXQoa2V5OiBzdHJpbmcgPSAnZGVmYXVsdCcpOiB2b2lkIHtcbiAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgIGNvbnN0IHJlcXVlc3RzID0gdGhpcy5yYXRlTGltaXRUcmFja2VyLmdldChrZXkpIHx8IFtdO1xuICAgIFxuICAgIC8vIFJlbW92ZSByZXF1ZXN0cyBvdXRzaWRlIHRoZSB3aW5kb3dcbiAgICBjb25zdCB2YWxpZFJlcXVlc3RzID0gcmVxdWVzdHMuZmlsdGVyKHRpbWUgPT4gbm93IC0gdGltZSA8IFNFQ1VSSVRZX0xJTUlUUy5SQVRFX0xJTUlUX1dJTkRPV19NUyk7XG4gICAgXG4gICAgaWYgKHZhbGlkUmVxdWVzdHMubGVuZ3RoID49IFNFQ1VSSVRZX0xJTUlUUy5SQVRFX0xJTUlUX1JFUVVFU1RTKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFJhdGUgbGltaXQgZXhjZWVkZWQuIE1heCAke1NFQ1VSSVRZX0xJTUlUUy5SQVRFX0xJTUlUX1JFUVVFU1RTfSByZXF1ZXN0cyBwZXIgbWludXRlLmApO1xuICAgIH1cbiAgICBcbiAgICB2YWxpZFJlcXVlc3RzLnB1c2gobm93KTtcbiAgICB0aGlzLnJhdGVMaW1pdFRyYWNrZXIuc2V0KGtleSwgdmFsaWRSZXF1ZXN0cyk7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBGZXRjaCBkYXRhIGZyb20gR2l0SHViIEFQSSB3aXRoIGNhY2hpbmcgYW5kIHJhdGUgbGltaXRpbmdcbiAgICovXG4gIGFzeW5jIGZldGNoRnJvbUdpdEh1Yih1cmw6IHN0cmluZywgcmVxdWlyZUF1dGg6IGJvb2xlYW4gPSBmYWxzZSk6IFByb21pc2U8YW55PiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIENoZWNrIHJhdGUgbGltaXRcbiAgICAgIHRoaXMuY2hlY2tSYXRlTGltaXQoJ2dpdGh1Yl9hcGknKTtcbiAgICAgIFxuICAgICAgLy8gQ2hlY2sgY2FjaGUgZmlyc3RcbiAgICAgIGNvbnN0IGNhY2hlZCA9IHRoaXMuYXBpQ2FjaGUuZ2V0KHVybCk7XG4gICAgICBpZiAoY2FjaGVkKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWQ7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIEFkZCBHaXRIdWIgdG9rZW4gaWYgYXZhaWxhYmxlIGZvciBoaWdoZXIgcmF0ZSBsaW1pdHNcbiAgICAgIGNvbnN0IGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7XG4gICAgICAgICdBY2NlcHQnOiAnYXBwbGljYXRpb24vdm5kLmdpdGh1Yi52Mytqc29uJyxcbiAgICAgICAgJ1VzZXItQWdlbnQnOiAnRG9sbGhvdXNlTUNQLzEuMCdcbiAgICAgIH07XG4gICAgICBcbiAgICAgIC8vIFVzZSBUb2tlbk1hbmFnZXIgZm9yIHNlY3VyZSB0b2tlbiBoYW5kbGluZ1xuICAgICAgLy8gQ1JJVElDQUwgRklYICM0NzE6IE11c3QgdXNlIGFzeW5jIG1ldGhvZCB0byByZXRyaWV2ZSBPQXV0aCB0b2tlbnMgZnJvbSBzZWN1cmUgc3RvcmFnZVxuICAgICAgLy8gVGhlIE9BdXRoIGZsb3cgKHNldHVwX2dpdGh1Yl9hdXRoKSBzdG9yZXMgdG9rZW5zIGluIHNlY3VyZSBPUyBrZXljaGFpbi9jcmVkZW50aWFsIHN0b3JlXG4gICAgICAvLyB3aGljaCByZXF1aXJlcyBhc3luYyBhY2Nlc3MuIFRoZSBzeW5jIG1ldGhvZCBvbmx5IGNoZWNrcyBlbnZpcm9ubWVudCB2YXJpYWJsZXMsXG4gICAgICAvLyBtaXNzaW5nIHRva2VucyBzdG9yZWQgYnkgdGhlIE9BdXRoIGF1dGhlbnRpY2F0aW9uIGZsb3cuXG4gICAgICBjb25zdCB0b2tlbiA9IGF3YWl0IFRva2VuTWFuYWdlci5nZXRHaXRIdWJUb2tlbkFzeW5jKCk7XG4gICAgICBpZiAodG9rZW4pIHtcbiAgICAgICAgaGVhZGVyc1snQXV0aG9yaXphdGlvbiddID0gYEJlYXJlciAke3Rva2VufWA7XG4gICAgICB9IGVsc2UgaWYgKHJlcXVpcmVBdXRoKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignR2l0SHViIGF1dGhlbnRpY2F0aW9uIHJlcXVpcmVkIGJ1dCBubyB2YWxpZCB0b2tlbiBhdmFpbGFibGUuIFBsZWFzZSB1c2Ugc2V0dXBfZ2l0aHViX2F1dGggb3Igc2V0IEdJVEhVQl9UT0tFTiBlbnZpcm9ubWVudCB2YXJpYWJsZS4nKTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgLy8gQ3JlYXRlIGZldGNoIHdpdGggdGltZW91dFxuICAgICAgY29uc3QgY29udHJvbGxlciA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICAgIGNvbnN0IHRpbWVvdXRJZCA9IHNldFRpbWVvdXQoKCkgPT4gY29udHJvbGxlci5hYm9ydCgpLCAxMDAwMCk7IC8vIDEwIHNlY29uZCB0aW1lb3V0XG4gICAgICBcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsLCB7XG4gICAgICAgIGhlYWRlcnMsXG4gICAgICAgIHNpZ25hbDogY29udHJvbGxlci5zaWduYWxcbiAgICAgIH0pO1xuICAgICAgXG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dElkKTtcbiAgICAgIFxuICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICBpZiAocmVzcG9uc2Uuc3RhdHVzID09PSA0MDMpIHtcbiAgICAgICAgICBjb25zdCBlcnJvck1zZyA9IHRva2VuIFxuICAgICAgICAgICAgPyAnR2l0SHViIEFQSSByYXRlIGxpbWl0IGV4Y2VlZGVkIG9yIHRva2VuIGxhY2tzIHJlcXVpcmVkIHBlcm1pc3Npb25zLidcbiAgICAgICAgICAgIDogJ0dpdEh1YiBBUEkgcmF0ZSBsaW1pdCBleGNlZWRlZC4gQ29uc2lkZXIgdXNpbmcgc2V0dXBfZ2l0aHViX2F1dGggb3Igc2V0dGluZyBHSVRIVUJfVE9LRU4gZW52aXJvbm1lbnQgdmFyaWFibGUuJztcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyb3JNc2cpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQwMSkge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcignR2l0SHViIEFQSSBhdXRoZW50aWNhdGlvbiBmYWlsZWQuIFBsZWFzZSBjaGVjayB5b3VyIEdJVEhVQl9UT0tFTi4nKTtcbiAgICAgICAgfVxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEdpdEh1YiBBUEkgZXJyb3I6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICBcbiAgICAgIC8vIENhY2hlIHRoZSBzdWNjZXNzZnVsIHJlc3BvbnNlXG4gICAgICB0aGlzLmFwaUNhY2hlLnNldCh1cmwsIGRhdGEpO1xuICAgICAgXG4gICAgICByZXR1cm4gZGF0YTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgLy8gVXNlIFRva2VuTWFuYWdlciBmb3Igc2FmZSBlcnJvciBoYW5kbGluZ1xuICAgICAgY29uc3QgZXJyb3JNZXNzYWdlID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpO1xuICAgICAgY29uc3Qgc2FmZU1lc3NhZ2UgPSBUb2tlbk1hbmFnZXIuY3JlYXRlU2FmZUVycm9yTWVzc2FnZShlcnJvck1lc3NhZ2UpO1xuICAgICAgXG4gICAgICBjb25zdCBlcnJvckRldGFpbHM6IGFueSA9IHtcbiAgICAgICAgb3JpZ2luYWxNZXNzYWdlOiBzYWZlTWVzc2FnZSxcbiAgICAgICAgdXJsXG4gICAgICB9O1xuICAgICAgXG4gICAgICAvLyBQcmVzZXJ2ZSBzdGFjayB0cmFjZSBhbmQgZXJyb3IgdHlwZSBpbmZvcm1hdGlvblxuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgZXJyb3JEZXRhaWxzLmVycm9yVHlwZSA9IGVycm9yLmNvbnN0cnVjdG9yLm5hbWU7XG4gICAgICAgIGVycm9yRGV0YWlscy5zdGFjayA9IGVycm9yLnN0YWNrO1xuICAgICAgICBcbiAgICAgICAgLy8gU3BlY2lhbCBoYW5kbGluZyBmb3IgY29tbW9uIGVycm9yIHR5cGVzXG4gICAgICAgIGlmIChlcnJvci5uYW1lID09PSAnQWJvcnRFcnJvcicpIHtcbiAgICAgICAgICBlcnJvckRldGFpbHMudGltZW91dCA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIFxuICAgICAgY29uc3QgbWNwRXJyb3IgPSBuZXcgTWNwRXJyb3IoXG4gICAgICAgIEVycm9yQ29kZS5JbnRlcm5hbEVycm9yLFxuICAgICAgICBgRmFpbGVkIHRvIGZldGNoIGZyb20gR2l0SHViOiAke3NhZmVNZXNzYWdlfWAsXG4gICAgICAgIGVycm9yRGV0YWlsc1xuICAgICAgKTtcbiAgICAgIFxuICAgICAgLy8gQWxzbyBwcmVzZXJ2ZSBvcmlnaW5hbCBlcnJvciBmb3IgZGVidWdnaW5nXG4gICAgICAobWNwRXJyb3IgYXMgYW55KS5jYXVzZSA9IGVycm9yO1xuICAgICAgXG4gICAgICB0aHJvdyBtY3BFcnJvcjtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVmFsaWRhdGUgdG9rZW4gcGVybWlzc2lvbnMgZm9yIGNvbGxlY3Rpb24gb3BlcmF0aW9uc1xuICAgKi9cbiAgYXN5bmMgdmFsaWRhdGVDb2xsZWN0aW9uUGVybWlzc2lvbnMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gTk9URTogVXNpbmcgJ21hcmtldHBsYWNlJyBzY29wZSBmb3IgYmFja3dhcmQgY29tcGF0aWJpbGl0eSB3aXRoIFRva2VuTWFuYWdlci5cbiAgICAvLyBUaGlzIGlzIGFuIGludGVybmFsIGltcGxlbWVudGF0aW9uIGRldGFpbCB0aGF0IGRvZXNuJ3QgYWZmZWN0IGZ1bmN0aW9uYWxpdHkuIChQUiAjMjgwKVxuICAgIGNvbnN0IHZhbGlkYXRpb24gPSBhd2FpdCBUb2tlbk1hbmFnZXIuZW5zdXJlVG9rZW5QZXJtaXNzaW9ucygnbWFya2V0cGxhY2UnKTtcbiAgICBpZiAoIXZhbGlkYXRpb24uaXNWYWxpZCkge1xuICAgICAgY29uc3Qgc2FmZU1lc3NhZ2UgPSBUb2tlbk1hbmFnZXIuY3JlYXRlU2FmZUVycm9yTWVzc2FnZSh2YWxpZGF0aW9uLmVycm9yIHx8ICdVbmtub3duIHZhbGlkYXRpb24gZXJyb3InKTtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgR2l0SHViIHRva2VuIHZhbGlkYXRpb24gZmFpbGVkOiAke3NhZmVNZXNzYWdlfWApO1xuICAgIH1cbiAgfVxufSJdfQ==