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.

496 lines 65.1 kB
/** * GitHub Portfolio Indexer - Fetches and indexes user's GitHub portfolio for fast searching * * Features: * - Singleton pattern for efficient resource usage * - Smart caching with TTL and invalidation after user actions * - GraphQL/REST API integration for efficient fetching * - Rate limiting and authentication handling * - Fallback strategy for resilient operation * - Performance optimized for 1000+ portfolio elements */ import { ElementType } from './types.js'; import { logger } from '../utils/logger.js'; import { SecurityMonitor } from '../security/securityMonitor.js'; import { UnicodeValidator } from '../security/validators/unicodeValidator.js'; import { ErrorHandler, ErrorCategory } from '../utils/ErrorHandler.js'; import { APICache } from '../cache/APICache.js'; export class GitHubPortfolioIndexer { cache = null; lastFetch = null; ttl = 15 * 60 * 1000; // 15 minutes recentUserAction = false; actionTimestamp = null; actionGracePeriod = 2 * 60 * 1000; // 2 minutes after action portfolioRepoManager; apiCache; rateLimitTracker; graphQLFeatureEnabled; constructor(portfolioRepoManager) { this.apiCache = new APICache(); // Uses default settings this.rateLimitTracker = new Map(); this.portfolioRepoManager = portfolioRepoManager; const graphQLEnv = process.env.DOLLHOUSE_GITHUB_GRAPHQL?.toLowerCase(); this.graphQLFeatureEnabled = graphQLEnv === 'true' || graphQLEnv === '1'; if (this.graphQLFeatureEnabled) { logger.info('GitHubPortfolioIndexer: GraphQL fetch enabled via DOLLHOUSE_GITHUB_GRAPHQL'); } logger.debug('GitHubPortfolioIndexer created'); } /** * Main method to get GitHub portfolio index */ async getIndex(force = false) { try { // Check if we need fresh data if (force || this.shouldFetchFresh()) { return await this.fetchFresh(); } // Return cached data if available and valid if (this.cache && this.isCacheValid()) { logger.debug('Returning cached GitHub portfolio index', { username: this.cache.username, totalElements: this.cache.totalElements, age: this.lastFetch ? Date.now() - this.lastFetch.getTime() : 'unknown' }); return this.cache; } // Try to fetch fresh, fall back to stale cache on failure try { return await this.fetchFresh(); } catch (error) { logger.warn('Failed to fetch fresh GitHub portfolio index, checking for stale cache', { error: error instanceof Error ? error.message : String(error) }); // Return stale cache if available if (this.cache) { logger.info('Returning stale GitHub portfolio cache as fallback', { username: this.cache.username, age: this.lastFetch ? Date.now() - this.lastFetch.getTime() : 'unknown' }); return this.cache; } // Return empty index as last resort return this.createEmptyIndex(); } } catch (error) { ErrorHandler.logError('GitHubPortfolioIndexer.getIndex', error); // Return stale cache or empty index if (this.cache) { return this.cache; } return this.createEmptyIndex(); } } /** * Invalidate cache after user actions */ invalidateAfterAction(action) { logger.info('Invalidating GitHub portfolio cache after user action', { action }); this.recentUserAction = true; this.actionTimestamp = new Date(); // Log security event for audit trail SecurityMonitor.logSecurityEvent({ type: 'PORTFOLIO_CACHE_INVALIDATION', severity: 'LOW', source: 'GitHubPortfolioIndexer.invalidateAfterAction', details: `Cache invalidated after user action: ${action}`, metadata: { action } }); } /** * Clear all cached data */ clearCache() { this.cache = null; this.lastFetch = null; this.recentUserAction = false; this.actionTimestamp = null; this.apiCache.clear(); logger.info('GitHub portfolio cache cleared'); } /** * Get cache statistics */ getCacheStats() { return { hasCachedData: this.cache !== null, lastFetch: this.lastFetch, isStale: !this.isCacheValid(), recentUserAction: this.recentUserAction, totalElements: this.cache?.totalElements || 0 }; } /** * Fetch fresh data from GitHub */ async fetchFresh() { const startTime = Date.now(); logger.info('Fetching fresh GitHub portfolio index...'); try { // Get GitHub username from token const username = await this.getGitHubUsername(); const repository = this.portfolioRepoManager.getRepositoryName(); // Check if portfolio repository exists const repoExists = await this.portfolioRepoManager.checkPortfolioExists(username); if (!repoExists) { logger.info('GitHub portfolio repository does not exist', { username }); return this.createEmptyIndex(username, repository); } // Fetch repository content using GitHub API const index = await this.fetchRepositoryContent(username, repository); // Update cache this.cache = index; this.lastFetch = new Date(); this.recentUserAction = false; this.actionTimestamp = null; const duration = Date.now() - startTime; logger.info('GitHub portfolio index fetched successfully', { username, totalElements: index.totalElements, duration: `${duration}ms`, rateLimitRemaining: index.rateLimitInfo?.remaining }); // Log security event SecurityMonitor.logSecurityEvent({ type: 'PORTFOLIO_FETCH_SUCCESS', severity: 'LOW', source: 'GitHubPortfolioIndexer.fetchFresh', details: `Fetched GitHub portfolio with ${index.totalElements} elements in ${duration}ms`, metadata: { username, duration, totalElements: index.totalElements } }); return index; } catch (error) { const duration = Date.now() - startTime; ErrorHandler.logError('GitHubPortfolioIndexer.fetchFresh', error, { duration }); throw ErrorHandler.wrapError(error, 'Failed to fetch GitHub portfolio index', ErrorCategory.NETWORK_ERROR); } } /** * Fetch repository content from GitHub API */ async fetchRepositoryContent(username, repository) { if (this.graphQLFeatureEnabled) { try { return await this.fetchWithGraphQL(username, repository); } catch (graphqlError) { logger.debug('GraphQL fetch failed, falling back to REST API', { error: graphqlError instanceof Error ? graphqlError.message : String(graphqlError) }); } } else { logger.debug('GraphQL portfolio fetch disabled; using REST API'); } return await this.fetchWithREST(username, repository); } /** * Fetch using GraphQL for better performance * TODO: Implement GraphQL endpoint for portfolio operations */ async fetchWithGraphQL(username, repository) { // GraphQL query stub - will be used when GraphQL endpoint is implemented const _query = ` query GetPortfolioContent($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { defaultBranchRef { target { ... on Commit { oid history(first: 1) { nodes { committedDate } } } } } object(expression: "HEAD:") { ... on Tree { entries { name type object { ... on Tree { entries { name type oid object { ... on Blob { byteSize text } } } } } } } } } rateLimit { remaining resetAt } } `; // GraphQL variables stub - will be used when GraphQL endpoint is implemented const _variables = { owner: username, name: repository }; // GraphQL endpoint not yet implemented with PortfolioRepoManager // For now, just throw to fall back to REST // Note: In a real implementation, you would send POST request with _query and _variables throw new Error('GraphQL not yet implemented for portfolio operations'); } /** * Fetch using REST API with pagination */ async fetchWithREST(username, repository) { const normalizedUsername = UnicodeValidator.normalize(username).normalizedContent; // Construct repository path (repository parameter is always just the repo name) const repoPath = `${normalizedUsername}/${repository}`; // Get repository info and latest commit await this.portfolioRepoManager.githubRequest(`/repos/${repoPath}`); const latestCommit = await this.portfolioRepoManager.githubRequest(`/repos/${repoPath}/commits/HEAD`); // Initialize index const index = { username: normalizedUsername, repository, lastUpdated: new Date(latestCommit.commit.committer.date), elements: new Map(), totalElements: 0, sha: latestCommit.sha }; // Initialize element type maps for (const elementType of Object.values(ElementType)) { index.elements.set(elementType, []); } // Fetch content for each element type logger.info(`Fetching content for all element types from GitHub portfolio...`); for (const elementType of Object.values(ElementType)) { try { logger.debug(`Fetching ${elementType} from GitHub...`); const entries = await this.fetchElementTypeContent(repoPath, elementType); index.elements.set(elementType, entries); index.totalElements += entries.length; if (entries.length > 0) { logger.info(`✅ Found ${entries.length} ${elementType} in GitHub portfolio`); } else { logger.debug(`📁 No ${elementType} found (directory may not exist yet)`); } } catch (error) { logger.warn(`❌ Failed to fetch ${elementType} from GitHub portfolio`, { error: error instanceof Error ? error.message : String(error), elementType }); // Continue with other element types } } return index; } /** * Fetch content for a specific element type */ async fetchElementTypeContent(repoPath, elementType) { try { // Get directory listing using PortfolioRepoManager const contents = await this.portfolioRepoManager.githubRequest(`/repos/${repoPath}/contents/${elementType}`); if (!Array.isArray(contents)) { return []; } const entries = []; const maxConcurrent = 5; // Limit concurrent requests // Process files in batches to avoid rate limiting for (let i = 0; i < contents.length; i += maxConcurrent) { const batch = contents.slice(i, i + maxConcurrent); const batchPromises = batch .filter(item => item.type === 'file' && item.name.endsWith('.md')) .map(item => this.createGitHubIndexEntry(elementType, item)); const batchResults = await Promise.allSettled(batchPromises); for (const result of batchResults) { if (result.status === 'fulfilled' && result.value) { entries.push(result.value); } } // Add delay between batches to respect rate limits if (i + maxConcurrent < contents.length) { await new Promise(resolve => setTimeout(resolve, 100)); } } return entries; } catch (error) { // Directory might not exist - check for 404 errors // PortfolioRepoManager will throw standard GitHub API errors if (error instanceof Error) { // Check for 404 Not Found errors from GitHub API if (error.message.includes('404') || error.message.includes('Not Found')) { logger.debug(`Directory ${elementType} not found in GitHub repository (this is normal if not yet created)`); return []; } // Log the actual error for debugging logger.debug(`Error fetching ${elementType}: ${error.message}`); } // Re-throw other errors logger.warn(`Unexpected error fetching ${elementType}:`, error); throw error; } } /** * Create GitHub index entry from API response */ async createGitHubIndexEntry(elementType, fileInfo) { try { // Parse metadata from filename or fetch content if needed // FIX: Keep original filename format to match local file expectations // Previously: .replaceAll(/-/g, ' ') converted hyphens to spaces causing sync mismatch const name = fileInfo.name.replace('.md', ''); const entry = { path: fileInfo.path, name, elementType, sha: fileInfo.sha, htmlUrl: fileInfo.html_url, downloadUrl: fileInfo.download_url, lastModified: new Date(), // GitHub API doesn't provide file modification time directly size: fileInfo.size || 0 }; // Optionally fetch content to extract metadata // This is expensive, so only do it for small files or when specifically needed if (fileInfo.size && fileInfo.size < 10000) { // Only for files < 10KB try { // For download URLs, we need to fetch directly, not through API const response = await fetch(fileInfo.download_url); const content = await response.text(); const metadata = this.parseMetadataFromContent(content); if (metadata.name) entry.name = metadata.name; if (metadata.description) entry.description = metadata.description; if (metadata.version) entry.version = metadata.version; if (metadata.author) entry.author = metadata.author; } catch (metadataError) { // Non-critical error, continue without metadata logger.debug('Failed to fetch metadata for file', { path: fileInfo.path, error: metadataError instanceof Error ? metadataError.message : String(metadataError) }); } } return entry; } catch (error) { logger.debug('Failed to create GitHub index entry', { path: fileInfo.path, error: error instanceof Error ? error.message : String(error) }); return null; } } /** * Parse metadata from file content (frontmatter) */ parseMetadataFromContent(content) { const metadata = {}; // Simple frontmatter parsing (could use a proper YAML parser) const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); if (frontmatterMatch) { const frontmatter = frontmatterMatch[1]; const nameMatch = frontmatter.match(/^name:\s*(.+)$/m); if (nameMatch) metadata.name = nameMatch[1].trim(); const descMatch = frontmatter.match(/^description:\s*(.+)$/m); if (descMatch) metadata.description = descMatch[1].trim(); const versionMatch = frontmatter.match(/^version:\s*(.+)$/m); if (versionMatch) metadata.version = versionMatch[1].trim(); const authorMatch = frontmatter.match(/^author:\s*(.+)$/m); if (authorMatch) metadata.author = authorMatch[1].trim(); } return metadata; } /** * Get GitHub username from authenticated token */ async getGitHubUsername() { try { const userInfo = await this.portfolioRepoManager.githubRequest('/user'); return userInfo.login; } catch { throw new Error('Failed to get GitHub username. Please ensure you are authenticated with GitHub.'); } } /** * Check if cache is valid */ isCacheValid() { if (!this.cache || !this.lastFetch) { return false; } const age = Date.now() - this.lastFetch.getTime(); return age < this.ttl; } /** * Determine if we should fetch fresh data */ shouldFetchFresh() { // Always fetch if no cache if (!this.cache || !this.lastFetch) { return true; } // Check for recent user actions if (this.recentUserAction && this.actionTimestamp) { const actionAge = Date.now() - this.actionTimestamp.getTime(); if (actionAge < this.actionGracePeriod) { logger.debug('Fetching fresh due to recent user action', { actionAge }); return true; } else { // Grace period expired, clear action flag this.recentUserAction = false; this.actionTimestamp = null; } } // Check TTL return !this.isCacheValid(); } /** * Create empty index when no portfolio exists */ createEmptyIndex(username, repository) { const index = { username: username || 'unknown', repository: repository || this.portfolioRepoManager.getRepositoryName(), lastUpdated: new Date(), elements: new Map(), totalElements: 0, sha: '' }; // Initialize empty element type maps for (const elementType of Object.values(ElementType)) { index.elements.set(elementType, []); } return index; } dispose() { this.clearCache(); this.apiCache.clear(); this.rateLimitTracker.clear(); this.recentUserAction = false; this.actionTimestamp = null; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2l0SHViUG9ydGZvbGlvSW5kZXhlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wb3J0Zm9saW8vR2l0SHViUG9ydGZvbGlvSW5kZXhlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7OztHQVVHO0FBR0gsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN6QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDNUMsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDRDQUE0QyxDQUFDO0FBQzlFLE9BQU8sRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBb0NoRCxNQUFNLE9BQU8sc0JBQXNCO0lBQ3pCLEtBQUssR0FBZ0MsSUFBSSxDQUFDO0lBQzFDLFNBQVMsR0FBZ0IsSUFBSSxDQUFDO0lBQ3JCLEdBQUcsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLGFBQWE7SUFDNUMsZ0JBQWdCLEdBQUcsS0FBSyxDQUFDO0lBQ3pCLGVBQWUsR0FBZ0IsSUFBSSxDQUFDO0lBQzNCLGlCQUFpQixHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUMseUJBQXlCO0lBRXJFLG9CQUFvQixDQUF1QjtJQUMzQyxRQUFRLENBQVc7SUFDbkIsZ0JBQWdCLENBQXdCO0lBQy9CLHFCQUFxQixDQUFVO0lBRWhELFlBQVksb0JBQTJDO1FBQ3JELElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxRQUFRLEVBQUUsQ0FBQyxDQUFDLHdCQUF3QjtRQUN4RCxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUNsQyxJQUFJLENBQUMsb0JBQW9C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R0FBRyxJQUFJLENBQUM7WUFDOUIsQ0FBQztRQUNILENBQUM7UUFFRCxZQUFZO1FBQ1osT0FBTyxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztJQUM5QixDQUFDO0lBRUQ7O09BRUc7SUFDSyxnQkFBZ0IsQ0FBQyxRQUFpQixFQUFFLFVBQW1CO1FBQzdELE1BQU0sS0FBSyxHQUF5QjtZQUNsQyxRQUFRLEVBQUUsUUFBUSxJQUFJLFNBQVM7WUFDL0IsVUFBVSxFQUFFLFVBQVUsSUFBSSxJQUFJLENBQUMsb0JBQW9CLENBQUMsaUJBQWlCLEVBQUU7WUFDdkUsV0FBVyxFQUFFLElBQUksSUFBSSxFQUFFO1lBQ3ZCLFFBQVEsRUFBRSxJQUFJLEdBQUcsRUFBRTtZQUNuQixhQUFhLEVBQUUsQ0FBQztZQUNoQixHQUFHLEVBQUUsRUFBRTtTQUNSLENBQUM7UUFFRixxQ0FBcUM7UUFDckMsS0FBSyxNQUFNLFdBQVcsSUFBSSxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7WUFDckQsS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ3RDLENBQUM7UUFFRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFTSxPQUFPO1FBQ1osSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2xCLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDdEIsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzlCLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxLQUFLLENBQUM7UUFDOUIsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUM7SUFDOUIsQ0FBQztDQUdGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHaXRIdWIgUG9ydGZvbGlvIEluZGV4ZXIgLSBGZXRjaGVzIGFuZCBpbmRleGVzIHVzZXIncyBHaXRIdWIgcG9ydGZvbGlvIGZvciBmYXN0IHNlYXJjaGluZ1xuICogXG4gKiBGZWF0dXJlczpcbiAqIC0gU2luZ2xldG9uIHBhdHRlcm4gZm9yIGVmZmljaWVudCByZXNvdXJjZSB1c2FnZVxuICogLSBTbWFydCBjYWNoaW5nIHdpdGggVFRMIGFuZCBpbnZhbGlkYXRpb24gYWZ0ZXIgdXNlciBhY3Rpb25zXG4gKiAtIEdyYXBoUUwvUkVTVCBBUEkgaW50ZWdyYXRpb24gZm9yIGVmZmljaWVudCBmZXRjaGluZ1xuICogLSBSYXRlIGxpbWl0aW5nIGFuZCBhdXRoZW50aWNhdGlvbiBoYW5kbGluZ1xuICogLSBGYWxsYmFjayBzdHJhdGVneSBmb3IgcmVzaWxpZW50IG9wZXJhdGlvblxuICogLSBQZXJmb3JtYW5jZSBvcHRpbWl6ZWQgZm9yIDEwMDArIHBvcnRmb2xpbyBlbGVtZW50c1xuICovXG5cbmltcG9ydCB7IFBvcnRmb2xpb1JlcG9NYW5hZ2VyIH0gZnJvbSAnLi9Qb3J0Zm9saW9SZXBvTWFuYWdlci5qcyc7XG5pbXBvcnQgeyBFbGVtZW50VHlwZSB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IFNlY3VyaXR5TW9uaXRvciB9IGZyb20gJy4uL3NlY3VyaXR5L3NlY3VyaXR5TW9uaXRvci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IEVycm9ySGFuZGxlciwgRXJyb3JDYXRlZ29yeSB9IGZyb20gJy4uL3V0aWxzL0Vycm9ySGFuZGxlci5qcyc7XG5pbXBvcnQgeyBBUElDYWNoZSB9IGZyb20gJy4uL2NhY2hlL0FQSUNhY2hlLmpzJztcblxuZXhwb3J0IGludGVyZmFjZSBHaXRIdWJJbmRleEVudHJ5IHtcbiAgcGF0aDogc3RyaW5nO1xuICBuYW1lOiBzdHJpbmc7XG4gIGRlc2NyaXB0aW9uPzogc3RyaW5nO1xuICB2ZXJzaW9uPzogc3RyaW5nO1xuICBhdXRob3I/OiBzdHJpbmc7XG4gIGVsZW1lbnRUeXBlOiBFbGVtZW50VHlwZTtcbiAgc2hhOiBzdHJpbmc7IC8vIEZpbGUgU0hBIGZvciBjaGFuZ2UgZGV0ZWN0aW9uXG4gIGh0bWxVcmw6IHN0cmluZzsgLy8gTGluayB0byBHaXRIdWJcbiAgZG93bmxvYWRVcmw6IHN0cmluZztcbiAgbGFzdE1vZGlmaWVkOiBEYXRlO1xuICBzaXplOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgR2l0SHViUG9ydGZvbGlvSW5kZXgge1xuICB1c2VybmFtZTogc3RyaW5nO1xuICByZXBvc2l0b3J5OiBzdHJpbmc7XG4gIGxhc3RVcGRhdGVkOiBEYXRlO1xuICBlbGVtZW50czogTWFwPEVsZW1lbnRUeXBlLCBHaXRIdWJJbmRleEVudHJ5W10+O1xuICB0b3RhbEVsZW1lbnRzOiBudW1iZXI7XG4gIHNoYTogc3RyaW5nOyAvLyBMYXRlc3QgY29tbWl0IFNIQVxuICByYXRlTGltaXRJbmZvPzoge1xuICAgIHJlbWFpbmluZzogbnVtYmVyO1xuICAgIHJlc2V0VGltZTogRGF0ZTtcbiAgfTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBHaXRIdWJGZXRjaE9wdGlvbnMge1xuICBmb3JjZT86IGJvb2xlYW47XG4gIG1heEVsZW1lbnRzPzogbnVtYmVyO1xuICBlbGVtZW50VHlwZXM/OiBFbGVtZW50VHlwZVtdO1xuICB1c2VHcmFwaFFMPzogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGNsYXNzIEdpdEh1YlBvcnRmb2xpb0luZGV4ZXIge1xuICBwcml2YXRlIGNhY2hlOiBHaXRIdWJQb3J0Zm9saW9JbmRleCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIGxhc3RGZXRjaDogRGF0ZSB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIHJlYWRvbmx5IHR0bCA9IDE1ICogNjAgKiAxMDAwOyAvLyAxNSBtaW51dGVzXG4gIHByaXZhdGUgcmVjZW50VXNlckFjdGlvbiA9IGZhbHNlO1xuICBwcml2YXRlIGFjdGlvblRpbWVzdGFtcDogRGF0ZSB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIHJlYWRvbmx5IGFjdGlvbkdyYWNlUGVyaW9kID0gMiAqIDYwICogMTAwMDsgLy8gMiBtaW51dGVzIGFmdGVyIGFjdGlvblxuICBcbiAgcHJpdmF0ZSBwb3J0Zm9saW9SZXBvTWFuYWdlcjogUG9ydGZvbGlvUmVwb01hbmFnZXI7XG4gIHByaXZhdGUgYXBpQ2FjaGU6IEFQSUNhY2hlO1xuICBwcml2YXRlIHJhdGVMaW1pdFRyYWNrZXI6IE1hcDxzdHJpbmcsIG51bWJlcltdPjtcbiAgcHJpdmF0ZSByZWFkb25seSBncmFwaFFMRmVhdHVyZUVuYWJsZWQ6IGJvb2xlYW47XG4gIFxuICBjb25zdHJ1Y3Rvcihwb3J0Zm9saW9SZXBvTWFuYWdlcj86IFBvcnRmb2xpb1JlcG9NYW5hZ2VyKSB7XG4gICAgdGhpcy5hcGlDYWNoZSA9IG5ldyBBUElDYWNoZSgpOyAvLyBVc2VzIGRlZmF1bHQgc2V0dGluZ3NcbiAgICB0aGlzLnJhdGVMaW1pdFRyYWNrZXIgPSBuZXcgTWFwKCk7XG4gICAgdGhpcy5wb3J0Zm9saW9SZXBvTWFuYWdlciA9IHBvcnRmb2xpb1JlcG9NYW5hZ2VyITtcbiAgICBjb25zdCBncmFwaFFMRW52ID0gcHJvY2Vzcy5lbnYuRE9MTEhPVVNFX0dJVEhVQl9HUkFQSFFMPy50b0xvd2VyQ2FzZSgpO1xuICAgIHRoaXMuZ3JhcGhRTEZlYXR1cmVFbmFibGVkID0gZ3JhcGhRTEVudiA9PT0gJ3RydWUnIHx8IGdyYXBoUUxFbnYgPT09ICcxJztcbiAgICBpZiAodGhpcy5ncmFwaFFMRmVhdHVyZUVuYWJsZWQpIHtcbiAgICAgIGxvZ2dlci5pbmZvKCdHaXRIdWJQb3J0Zm9saW9JbmRleGVyOiBHcmFwaFFMIGZldGNoIGVuYWJsZWQgdmlhIERPTExIT1VTRV9HSVRIVUJfR1JBUEhRTCcpO1xuICAgIH1cblxuICAgIGxvZ2dlci5kZWJ1ZygnR2l0SHViUG9ydGZvbGlvSW5kZXhlciBjcmVhdGVkJyk7XG4gIH1cblxuICAvKipcbiAgICogTWFpbiBtZXRob2QgdG8gZ2V0IEdpdEh1YiBwb3J0Zm9saW8gaW5kZXhcbiAgICovXG4gIHB1YmxpYyBhc3luYyBnZXRJbmRleChmb3JjZSA9IGZhbHNlKTogUHJvbWlzZTxHaXRIdWJQb3J0Zm9saW9JbmRleD4ge1xuICAgIHRyeSB7XG4gICAgICAvLyBDaGVjayBpZiB3ZSBuZWVkIGZyZXNoIGRhdGFcbiAgICAgIGlmIChmb3JjZSB8fCB0aGlzLnNob3VsZEZldGNoRnJlc2goKSkge1xuICAgICAgICByZXR1cm4gYXdhaXQgdGhpcy5mZXRjaEZyZXNoKCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIFJldHVybiBjYWNoZWQgZGF0YSBpZiBhdmFpbGFibGUgYW5kIHZhbGlkXG4gICAgICBpZiAodGhpcy5jYWNoZSAmJiB0aGlzLmlzQ2FjaGVWYWxpZCgpKSB7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZygnUmV0dXJuaW5nIGNhY2hlZCBHaXRIdWIgcG9ydGZvbGlvIGluZGV4Jywge1xuICAgICAgICAgIHVzZXJuYW1lOiB0aGlzLmNhY2hlLnVzZXJuYW1lLFxuICAgICAgICAgIHRvdGFsRWxlbWVudHM6IHRoaXMuY2FjaGUudG90YWxFbGVtZW50cyxcbiAgICAgICAgICBhZ2U6IHRoaXMubGFzdEZldGNoID8gRGF0ZS5ub3coKSAtIHRoaXMubGFzdEZldGNoLmdldFRpbWUoKSA6ICd1bmtub3duJ1xuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIHRoaXMuY2FjaGU7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIFRyeSB0byBmZXRjaCBmcmVzaCwgZmFsbCBiYWNrIHRvIHN0YWxlIGNhY2hlIG9uIGZhaWx1cmVcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCB0aGlzLmZldGNoRnJlc2goKTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdGYWlsZWQgdG8gZmV0Y2ggZnJlc2ggR2l0SHViIHBvcnRmb2xpbyBpbmRleCwgY2hlY2tpbmcgZm9yIHN0YWxlIGNhY2hlJywge1xuICAgICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcilcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICAvLyBSZXR1cm4gc3RhbGUgY2FjaGUgaWYgYXZhaWxhYmxlXG4gICAgICAgIGlmICh0aGlzLmNhY2hlKSB7XG4gICAgICAgICAgbG9nZ2VyLmluZm8oJ1JldHVybmluZyBzdGFsZSBHaXRIdWIgcG9ydGZvbGlvIGNhY2hlIGFzIGZhbGxiYWNrJywge1xuICAgICAgICAgICAgdXNlcm5hbWU6IHRoaXMuY2FjaGUudXNlcm5hbWUsXG4gICAgICAgICAgICBhZ2U6IHRoaXMubGFzdEZldGNoID8gRGF0ZS5ub3coKSAtIHRoaXMubGFzdEZldGNoLmdldFRpbWUoKSA6ICd1bmtub3duJ1xuICAgICAgICAgIH0pO1xuICAgICAgICAgIHJldHVybiB0aGlzLmNhY2hlO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBSZXR1cm4gZW1wdHkgaW5kZXggYXMgbGFzdCByZXNvcnRcbiAgICAgICAgcmV0dXJuIHRoaXMuY3JlYXRlRW1wdHlJbmRleCgpO1xuICAgICAgfVxuICAgICAgXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIEVycm9ySGFuZGxlci5sb2dFcnJvcignR2l0SHViUG9ydGZvbGlvSW5kZXhlci5nZXRJbmRleCcsIGVycm9yKTtcbiAgICAgIFxuICAgICAgLy8gUmV0dXJuIHN0YWxlIGNhY2hlIG9yIGVtcHR5IGluZGV4XG4gICAgICBpZiAodGhpcy5jYWNoZSkge1xuICAgICAgICByZXR1cm4gdGhpcy5jYWNoZTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgcmV0dXJuIHRoaXMuY3JlYXRlRW1wdHlJbmRleCgpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZhbGlkYXRlIGNhY2hlIGFmdGVyIHVzZXIgYWN0aW9uc1xuICAgKi9cbiAgcHVibGljIGludmFsaWRhdGVBZnRlckFjdGlvbihhY3Rpb246IHN0cmluZyk6IHZvaWQge1xuICAgIGxvZ2dlci5pbmZvKCdJbnZhbGlkYXRpbmcgR2l0SHViIHBvcnRmb2xpbyBjYWNoZSBhZnRlciB1c2VyIGFjdGlvbicsIHsgYWN0aW9uIH0pO1xuICAgIFxuICAgIHRoaXMucmVjZW50VXNlckFjdGlvbiA9IHRydWU7XG4gICAgdGhpcy5hY3Rpb25UaW1lc3RhbXAgPSBuZXcgRGF0ZSgpO1xuICAgIFxuICAgIC8vIExvZyBzZWN1cml0eSBldmVudCBmb3IgYXVkaXQgdHJhaWxcbiAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICB0eXBlOiAnUE9SVEZPTElPX0NBQ0hFX0lOVkFMSURBVElPTicsXG4gICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICBzb3VyY2U6ICdHaXRIdWJQb3J0Zm9saW9JbmRleGVyLmludmFsaWRhdGVBZnRlckFjdGlvbicsXG4gICAgICBkZXRhaWxzOiBgQ2FjaGUgaW52YWxpZGF0ZWQgYWZ0ZXIgdXNlciBhY3Rpb246ICR7YWN0aW9ufWAsXG4gICAgICBtZXRhZGF0YTogeyBhY3Rpb24gfVxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFyIGFsbCBjYWNoZWQgZGF0YVxuICAgKi9cbiAgcHVibGljIGNsZWFyQ2FjaGUoKTogdm9pZCB7XG4gICAgdGhpcy5jYWNoZSA9IG51bGw7XG4gICAgdGhpcy5sYXN0RmV0Y2ggPSBudWxsO1xuICAgIHRoaXMucmVjZW50VXNlckFjdGlvbiA9IGZhbHNlO1xuICAgIHRoaXMuYWN0aW9uVGltZXN0YW1wID0gbnVsbDtcbiAgICB0aGlzLmFwaUNhY2hlLmNsZWFyKCk7XG4gICAgXG4gICAgbG9nZ2VyLmluZm8oJ0dpdEh1YiBwb3J0Zm9saW8gY2FjaGUgY2xlYXJlZCcpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBjYWNoZSBzdGF0aXN0aWNzXG4gICAqL1xuICBwdWJsaWMgZ2V0Q2FjaGVTdGF0cygpOiB7XG4gICAgaGFzQ2FjaGVkRGF0YTogYm9vbGVhbjtcbiAgICBsYXN0RmV0Y2g6IERhdGUgfCBudWxsO1xuICAgIGlzU3RhbGU6IGJvb2xlYW47XG4gICAgcmVjZW50VXNlckFjdGlvbjogYm9vbGVhbjtcbiAgICB0b3RhbEVsZW1lbnRzOiBudW1iZXI7XG4gIH0ge1xuICAgIHJldHVybiB7XG4gICAgICBoYXNDYWNoZWREYXRhOiB0aGlzLmNhY2hlICE9PSBudWxsLFxuICAgICAgbGFzdEZldGNoOiB0aGlzLmxhc3RGZXRjaCxcbiAgICAgIGlzU3RhbGU6ICF0aGlzLmlzQ2FjaGVWYWxpZCgpLFxuICAgICAgcmVjZW50VXNlckFjdGlvbjogdGhpcy5yZWNlbnRVc2VyQWN0aW9uLFxuICAgICAgdG90YWxFbGVtZW50czogdGhpcy5jYWNoZT8udG90YWxFbGVtZW50cyB8fCAwXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBGZXRjaCBmcmVzaCBkYXRhIGZyb20gR2l0SHViXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGZldGNoRnJlc2goKTogUHJvbWlzZTxHaXRIdWJQb3J0Zm9saW9JbmRleD4ge1xuICAgIGNvbnN0IHN0YXJ0VGltZSA9IERhdGUubm93KCk7XG4gICAgbG9nZ2VyLmluZm8oJ0ZldGNoaW5nIGZyZXNoIEdpdEh1YiBwb3J0Zm9saW8gaW5kZXguLi4nKTtcblxuICAgIHRyeSB7XG4gICAgICAvLyBHZXQgR2l0SHViIHVzZXJuYW1lIGZyb20gdG9rZW5cbiAgICAgIGNvbnN0IHVzZXJuYW1lID0gYXdhaXQgdGhpcy5nZXRHaXRIdWJVc2VybmFtZSgpO1xuICAgICAgY29uc3QgcmVwb3NpdG9yeSA9IHRoaXMucG9ydGZvbGlvUmVwb01hbmFnZXIuZ2V0UmVwb3NpdG9yeU5hbWUoKTtcbiAgICAgIFxuICAgICAgLy8gQ2hlY2sgaWYgcG9ydGZvbGlvIHJlcG9zaXRvcnkgZXhpc3RzXG4gICAgICBjb25zdCByZXBvRXhpc3RzID0gYXdhaXQgdGhpcy5wb3J0Zm9saW9SZXBvTWFuYWdlci5jaGVja1BvcnRmb2xpb0V4aXN0cyh1c2VybmFtZSk7XG4gICAgICBpZiAoIXJlcG9FeGlzdHMpIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ0dpdEh1YiBwb3J0Zm9saW8gcmVwb3NpdG9yeSBkb2VzIG5vdCBleGlzdCcsIHsgdXNlcm5hbWUgfSk7XG4gICAgICAgIHJldHVybiB0aGlzLmNyZWF0ZUVtcHR5SW5kZXgodXNlcm5hbWUsIHJlcG9zaXRvcnkpO1xuICAgICAgfVxuICAgICAgXG4gICAgICAvLyBGZXRjaCByZXBvc2l0b3J5IGNvbnRlbnQgdXNpbmcgR2l0SHViIEFQSVxuICAgICAgY29uc3QgaW5kZXggPSBhd2FpdCB0aGlzLmZldGNoUmVwb3NpdG9yeUNvbnRlbnQodXNlcm5hbWUsIHJlcG9zaXRvcnkpO1xuICAgICAgXG4gICAgICAvLyBVcGRhdGUgY2FjaGVcbiAgICAgIHRoaXMuY2FjaGUgPSBpbmRleDtcbiAgICAgIHRoaXMubGFzdEZldGNoID0gbmV3IERhdGUoKTtcbiAgICAgIHRoaXMucmVjZW50VXNlckFjdGlvbiA9IGZhbHNlO1xuICAgICAgdGhpcy5hY3Rpb25UaW1lc3RhbXAgPSBudWxsO1xuICAgICAgXG4gICAgICBjb25zdCBkdXJhdGlvbiA9IERhdGUubm93KCkgLSBzdGFydFRpbWU7XG4gICAgICBsb2dnZXIuaW5mbygnR2l0SHViIHBvcnRmb2xpbyBpbmRleCBmZXRjaGVkIHN1Y2Nlc3NmdWxseScsIHtcbiAgICAgICAgdXNlcm5hbWUsXG4gICAgICAgIHRvdGFsRWxlbWVudHM6IGluZGV4LnRvdGFsRWxlbWVudHMsXG4gICAgICAgIGR1cmF0aW9uOiBgJHtkdXJhdGlvbn1tc2AsXG4gICAgICAgIHJhdGVMaW1pdFJlbWFpbmluZzogaW5kZXgucmF0ZUxpbWl0SW5mbz8ucmVtYWluaW5nXG4gICAgICB9KTtcbiAgICAgIFxuICAgICAgLy8gTG9nIHNlY3VyaXR5IGV2ZW50XG4gICAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICAgIHR5cGU6ICdQT1JURk9MSU9fRkVUQ0hfU1VDQ0VTUycsXG4gICAgICAgIHNldmVyaXR5OiAnTE9XJyxcbiAgICAgICAgc291cmNlOiAnR2l0SHViUG9ydGZvbGlvSW5kZXhlci5mZXRjaEZyZXNoJyxcbiAgICAgICAgZGV0YWlsczogYEZldGNoZWQgR2l0SHViIHBvcnRmb2xpbyB3aXRoICR7aW5kZXgudG90YWxFbGVtZW50c30gZWxlbWVudHMgaW4gJHtkdXJhdGlvbn1tc2AsXG4gICAgICAgIG1ldGFkYXRhOiB7IHVzZXJuYW1lLCBkdXJhdGlvbiwgdG90YWxFbGVtZW50czogaW5kZXgudG90YWxFbGVtZW50cyB9XG4gICAgICB9KTtcbiAgICAgIFxuICAgICAgcmV0dXJuIGluZGV4O1xuICAgICAgXG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGNvbnN0IGR1cmF0aW9uID0gRGF0ZS5ub3coKSAtIHN0YXJ0VGltZTtcbiAgICAgIEVycm9ySGFuZGxlci5sb2dFcnJvcignR2l0SHViUG9ydGZvbGlvSW5kZXhlci5mZXRjaEZyZXNoJywgZXJyb3IsIHsgZHVyYXRpb24gfSk7XG4gICAgICB0aHJvdyBFcnJvckhhbmRsZXIud3JhcEVycm9yKGVycm9yLCAnRmFpbGVkIHRvIGZldGNoIEdpdEh1YiBwb3J0Zm9saW8gaW5kZXgnLCBFcnJvckNhdGVnb3J5Lk5FVFdPUktfRVJST1IpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBGZXRjaCByZXBvc2l0b3J5IGNvbnRlbnQgZnJvbSBHaXRIdWIgQVBJXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGZldGNoUmVwb3NpdG9yeUNvbnRlbnQodXNlcm5hbWU6IHN0cmluZywgcmVwb3NpdG9yeTogc3RyaW5nKTogUHJvbWlzZTxHaXRIdWJQb3J0Zm9saW9JbmRleD4ge1xuICAgIGlmICh0aGlzLmdyYXBoUUxGZWF0dXJlRW5hYmxlZCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIGF3YWl0IHRoaXMuZmV0Y2hXaXRoR3JhcGhRTCh1c2VybmFtZSwgcmVwb3NpdG9yeSk7XG4gICAgICB9IGNhdGNoIChncmFwaHFsRXJyb3IpIHtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdHcmFwaFFMIGZldGNoIGZhaWxlZCwgZmFsbGluZyBiYWNrIHRvIFJFU1QgQVBJJywge1xuICAgICAgICAgIGVycm9yOiBncmFwaHFsRXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGdyYXBocWxFcnJvci5tZXNzYWdlIDogU3RyaW5nKGdyYXBocWxFcnJvcilcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnR3JhcGhRTCBwb3J0Zm9saW8gZmV0Y2ggZGlzYWJsZWQ7IHVzaW5nIFJFU1QgQVBJJyk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZmV0Y2hXaXRoUkVTVCh1c2VybmFtZSwgcmVwb3NpdG9yeSk7XG4gIH1cblxuICAvKipcbiAgICogRmV0Y2ggdXNpbmcgR3JhcGhRTCBmb3IgYmV0dGVyIHBlcmZvcm1hbmNlXG4gICAqIFRPRE86IEltcGxlbWVudCBHcmFwaFFMIGVuZHBvaW50IGZvciBwb3J0Zm9saW8gb3BlcmF0aW9uc1xuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBmZXRjaFdpdGhHcmFwaFFMKHVzZXJuYW1lOiBzdHJpbmcsIHJlcG9zaXRvcnk6IHN0cmluZyk6IFByb21pc2U8R2l0SHViUG9ydGZvbGlvSW5kZXg+IHtcbiAgICAvLyBHcmFwaFFMIHF1ZXJ5IHN0dWIgLSB3aWxsIGJlIHVzZWQgd2hlbiBHcmFwaFFMIGVuZHBvaW50IGlzIGltcGxlbWVudGVkXG4gICAgY29uc3QgX3F1ZXJ5ID0gYFxuICAgICAgcXVlcnkgR2V0UG9ydGZvbGlvQ29udGVudCgkb3duZXI6IFN0cmluZyEsICRuYW1lOiBTdHJpbmchKSB7XG4gICAgICAgIHJlcG9zaXRvcnkob3duZXI6ICRvd25lciwgbmFtZTogJG5hbWUpIHtcbiAgICAgICAgICBkZWZhdWx0QnJhbmNoUmVmIHtcbiAgICAgICAgICAgIHRhcmdldCB7XG4gICAgICAgICAgIC