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