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.

465 lines (460 loc) • 59.3 kB
/** * GitHub authentication manager using OAuth device flow * Handles authentication for MCP servers without requiring client secrets */ import { TokenManager } from '../security/tokenManager.js'; import { logger } from '../utils/logger.js'; import { UnicodeValidator } from '../security/validators/unicodeValidator.js'; import { SecurityMonitor } from '../security/securityMonitor.js'; /** * Manages GitHub authentication using the OAuth device flow * This is the recommended approach for CLI/desktop applications */ export class GitHubAuthManager { // GitHub OAuth App Client ID for DollhouseMCP // Must be configured via environment variable static CLIENT_ID = process.env.DOLLHOUSE_GITHUB_CLIENT_ID; // GitHub OAuth endpoints static DEVICE_CODE_URL = 'https://github.com/login/device/code'; static TOKEN_URL = 'https://github.com/login/oauth/access_token'; static USER_URL = 'https://api.github.com/user'; // Polling configuration static DEFAULT_POLL_INTERVAL = 5000; // 5 seconds static MAX_POLL_ATTEMPTS = 180; // 15 minutes total apiCache; activePolling = null; constructor(apiCache) { this.apiCache = apiCache; } /** * Execute a network request with retry logic */ async fetchWithRetry(url, options, maxRetries = 3, retryDelay = 1000) { let lastError = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch(url, options); return response; } catch (error) { lastError = error; // Check if it's a network error that should be retried const isNetworkError = error instanceof Error && (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT') || error.message.includes('ENOTFOUND') || error.message.includes('network')); if (isNetworkError && attempt < maxRetries) { logger.debug(`Network request failed, retrying (${attempt}/${maxRetries})`, { url, error: error.message, nextRetryIn: retryDelay * attempt }); // Exponential backoff await new Promise(resolve => setTimeout(resolve, retryDelay * attempt)); } else { // Not a network error or last attempt, throw immediately throw error; } } } throw lastError || new Error('Network request failed after all retries'); } /** * Check current authentication status */ async getAuthStatus() { const token = await TokenManager.getGitHubTokenAsync(); if (!token) { return { isAuthenticated: false, hasToken: false }; } try { // Try to get user info to validate token const userInfo = await this.fetchUserInfo(token); return { isAuthenticated: true, hasToken: true, username: userInfo.login, scopes: userInfo.scopes }; } catch (error) { // Token might be invalid or expired logger.debug('Token validation failed', { error }); return { isAuthenticated: false, hasToken: true // Has token but it's invalid }; } } /** * Initiate the device flow authentication process */ async initiateDeviceFlow() { if (!GitHubAuthManager.CLIENT_ID) { throw new Error('GitHub OAuth is not configured. Please set DOLLHOUSE_GITHUB_CLIENT_ID environment variable. ' + 'For setup instructions, visit: https://github.com/DollhouseMCP/mcp-server#github-authentication'); } try { const response = await this.fetchWithRetry(GitHubAuthManager.DEVICE_CODE_URL, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: GitHubAuthManager.CLIENT_ID, scope: 'public_repo read:user' }) }); if (!response.ok) { // Provide user-friendly error messages based on status codes const errorMessage = this.getErrorMessageForStatus(response.status, 'device flow initialization'); logger.debug('Device flow initiation failed', { status: response.status, statusText: response.statusText }); throw new Error(errorMessage); } const data = await response.json(); // Validate response if (!data.device_code || !data.user_code || !data.verification_uri) { throw new Error('Invalid device flow response from GitHub'); } // Log security event for audit trail SecurityMonitor.logSecurityEvent({ type: 'TOKEN_VALIDATION_SUCCESS', severity: 'LOW', source: 'GitHubAuthManager.initiateDeviceFlow', details: 'GitHub OAuth device flow initiated', metadata: { userCode: data.user_code, expiresIn: data.expires_in, interval: data.interval } }); return data; } catch (error) { logger.error('Failed to initiate device flow', { error }); throw new Error('Failed to start GitHub authentication. Please check your internet connection.'); } } /** * Poll for token after user has authorized the device */ async pollForToken(deviceCode, interval = GitHubAuthManager.DEFAULT_POLL_INTERVAL) { // Create new abort controller for this polling session this.activePolling = new AbortController(); const signal = this.activePolling.signal; let attempts = 0; try { while (attempts < GitHubAuthManager.MAX_POLL_ATTEMPTS) { // Check if polling was aborted if (signal.aborted) { throw new Error('Authentication polling was cancelled'); } attempts++; try { const response = await fetch(GitHubAuthManager.TOKEN_URL, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: GitHubAuthManager.CLIENT_ID, device_code: deviceCode, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }) }); const data = await response.json(); // Check for various response states if (data.error) { switch (data.error) { case 'authorization_pending': // User hasn't authorized yet, keep polling break; case 'slow_down': // Increase polling interval interval = Math.min(interval * 1.5, 30000); // Max 30 seconds logger.debug('Slowing down polling interval', { newInterval: interval }); break; case 'expired_token': throw new Error('The authorization code has expired. Please start over.'); case 'access_denied': throw new Error('Authorization was denied. Please try again.'); default: // Log the actual error for debugging logger.debug('OAuth device flow error', { error: data.error, description: data.error_description }); throw new Error('Authentication failed. Please try starting the process again.'); } } else if (data.access_token) { // Success! return data; } // Wait before next poll // Wait for interval with abort support await this.waitWithAbort(interval, signal); } catch (error) { // Network errors shouldn't stop polling logger.debug('Poll attempt failed', { attempt: attempts, error }); await this.waitWithAbort(interval, signal); } } throw new Error('Authentication timed out. Please try again.'); } finally { // Clear active polling reference this.activePolling = null; } } /** * Complete the authentication flow and store the token */ async completeAuthentication(tokenResponse) { // Store token securely await this.storeToken(tokenResponse.access_token); // Get user info const userInfo = await this.fetchUserInfo(tokenResponse.access_token); // Log successful authentication completion SecurityMonitor.logSecurityEvent({ type: 'TOKEN_VALIDATION_SUCCESS', severity: 'LOW', source: 'GitHubAuthManager.completeAuthentication', details: 'GitHub OAuth device flow completed successfully', metadata: { username: userInfo.login, scopes: tokenResponse.scope.split(' '), tokenType: TokenManager.getTokenType(tokenResponse.access_token) } }); return { isAuthenticated: true, hasToken: true, username: userInfo.login, scopes: tokenResponse.scope.split(' ') }; } /** * Clear stored authentication and revoke token */ async clearAuthentication() { try { // Get the token before clearing it const token = await TokenManager.getGitHubTokenAsync(); if (token) { // Attempt to revoke the token on GitHub // Note: GitHub OAuth tokens don't have a revocation endpoint for device flow tokens // But we'll clear the cache and remove from storage // Clear cached user info this.apiCache.clear(); // Log security event for audit trail SecurityMonitor.logSecurityEvent({ type: 'TOKEN_CACHE_CLEARED', severity: 'LOW', source: 'GitHubAuthManager.clearAuthentication', details: 'GitHub authentication cleared by user request', metadata: { hadToken: true, tokenPrefix: TokenManager.getTokenPrefix(token) } }); } // Remove from secure storage await TokenManager.removeStoredToken(); logger.info('GitHub authentication cleared successfully'); } catch (error) { logger.error('Error clearing authentication', { error }); throw new Error('Failed to clear authentication'); } } /** * Store token securely using encrypted file storage */ async storeToken(token) { try { await TokenManager.storeGitHubToken(token); logger.info('GitHub token stored securely. You are now authenticated!'); } catch (error) { logger.error('Failed to store token securely', { error }); // Fallback to environment variable instructions logger.info('For manual setup, you can set GITHUB_TOKEN environment variable.'); throw error; } } /** * Fetch user information from GitHub */ async fetchUserInfo(token) { // Check cache first const cached = this.apiCache.get(GitHubAuthManager.USER_URL); if (cached) { return cached; } const response = await this.fetchWithRetry(GitHubAuthManager.USER_URL, { headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/vnd.github.v3+json' } }); if (!response.ok) { const errorMessage = this.getErrorMessageForStatus(response.status, 'user information fetch'); logger.debug('Failed to fetch user info', { status: response.status }); throw new Error(errorMessage); } const data = await response.json(); // Normalize username and other text fields to prevent Unicode attacks if (data.login) { const validation = UnicodeValidator.normalize(data.login); if (!validation.isValid) { SecurityMonitor.logSecurityEvent({ type: 'UNICODE_VALIDATION_ERROR', severity: 'MEDIUM', source: 'GitHubAuthManager.fetchUserInfo', details: 'GitHub username contains invalid Unicode', metadata: { originalLength: data.login.length, detectedIssues: validation.detectedIssues } }); throw new Error('Invalid username format from GitHub'); } data.login = validation.normalizedContent; } // Normalize display name if present if (data.name) { const nameValidation = UnicodeValidator.normalize(data.name); if (nameValidation.isValid) { data.name = nameValidation.normalizedContent; } else { // Don't fail on display name, just remove it delete data.name; } } // Add scopes from response headers const scopeHeader = response.headers.get('x-oauth-scopes'); if (scopeHeader) { data.scopes = scopeHeader.split(',').map(s => s.trim()); } // Log successful authentication for audit trail SecurityMonitor.logSecurityEvent({ type: 'TOKEN_VALIDATION_SUCCESS', severity: 'LOW', source: 'GitHubAuthManager.fetchUserInfo', details: 'GitHub user authenticated successfully', metadata: { username: data.login, hasEmail: !!data.email, scopes: data.scopes || [] } }); // Cache the result this.apiCache.set(GitHubAuthManager.USER_URL, data); return data; } /** * Format authentication instructions for users */ formatAuthInstructions(deviceResponse) { return `šŸ” **GitHub Authentication Required** To access all DollhouseMCP features, please authenticate with GitHub: 1. Visit: **${deviceResponse.verification_uri}** 2. Enter code: **${deviceResponse.user_code}** 3. Authorize 'DollhouseMCP Collection' This will grant access to: āœ… Browse the public collection āœ… Install community content āœ… Submit your own creations Don't have a GitHub account? You'll be prompted to create one (it's free!) ā±ļø This code expires in ${Math.floor(deviceResponse.expires_in / 60)} minutes.`; } /** * Check if user needs to authenticate for a specific action */ needsAuthForAction(action) { const authRequiredActions = ['submit', 'create_pr', 'manage_content']; return authRequiredActions.includes(action.toLowerCase()); } /** * Wait with abort signal support */ async waitWithAbort(ms, signal) { return new Promise((resolve, reject) => { const timeout = setTimeout(resolve, ms); // Listen for abort signal const abortHandler = () => { clearTimeout(timeout); reject(new Error('Wait aborted')); }; signal.addEventListener('abort', abortHandler, { once: true }); // Clean up after timeout setTimeout(() => { signal.removeEventListener('abort', abortHandler); }, ms); }); } /** * Clean up any active operations (called on server shutdown) */ async cleanup() { // Abort any active polling if (this.activePolling) { this.activePolling.abort(); this.activePolling = null; SecurityMonitor.logSecurityEvent({ type: 'TOKEN_CACHE_CLEARED', severity: 'LOW', source: 'GitHubAuthManager.cleanup', details: 'GitHub auth manager cleaned up on shutdown', metadata: { hadActivePolling: true } }); logger.info('GitHub authentication polling cancelled due to shutdown'); } // Clear API cache this.apiCache.clear(); } /** * Get user-friendly error message based on HTTP status code * Avoids exposing sensitive information while providing helpful guidance */ getErrorMessageForStatus(status, operation) { switch (status) { case 400: return `Invalid request to GitHub. Please ensure the OAuth app is properly configured.`; case 401: return `Authentication failed. The OAuth app credentials may be invalid.`; case 403: return `Access denied by GitHub. The OAuth app may lack required permissions.`; case 404: return `GitHub service not found. This may indicate an API change.`; case 422: return `Invalid parameters sent to GitHub. Please check your configuration.`; case 429: return `Too many requests to GitHub. Please wait a moment and try again.`; case 500: case 502: case 503: case 504: return `GitHub service temporarily unavailable. Please try again in a few moments.`; default: // Log the actual status for debugging, but don't expose it to users logger.debug(`Unexpected status code during ${operation}`, { status }); return `Failed to complete ${operation}. Please check your internet connection and try again.`; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2l0SHViQXV0aE1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXV0aC9HaXRIdWJBdXRoTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDM0QsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRTVDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDRDQUE0QyxDQUFDO0FBQzlFLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQXdCakU7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLGlCQUFpQjtJQUM1Qiw4Q0FBOEM7SUFDOUMsOENBQThDO0lBQ3RDLE1BQU0sQ0FBVSxTQUFTLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQywwQkFBMEIsQ0FBQztJQUUzRSx5QkFBeUI7SUFDakIsTUFBTSxDQUFVLGVBQWUsR0FBRyxzQ0FBc0MsQ0FBQztJQUN6RSxNQUFNLENBQVUsU0FBUyxHQUFHLDZDQUE2QyxDQUFDO0lBQzFFLE1BQU0sQ0FBVSxRQUFRLEdBQUcsNkJBQTZCLENBQUM7SUFFakUsd0JBQXdCO0lBQ2hCLE1BQU0sQ0FBVSxxQkFBcUIsR0FBRyxJQUFJLENBQUMsQ0FBQyxZQUFZO0lBQzFELE1BQU0sQ0FBVSxpQkFBaUIsR0FBRyxHQUFHLENBQUMsQ0FBQyxtQkFBbUI7SUFFNUQsUUFBUSxDQUFXO0lBQ25CLGFBQWEsR0FBMkIsSUFBSSxDQUFDO0lBRXJELFlBQVksUUFBa0I7UUFDNUIsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDM0IsQ0FBQztJQUVEOztPQUVHO0lBQ0ssS0FBSyxDQUFDLGNBQWMsQ0FDMUIsR0FBVyxFQUNYLE9BQW9CLEVBQ3BCLGFBQXFCLENBQUMsRUFDdEIsYUFBcUIsSUFBSTtRQUV6QixJQUFJLFNBQVMsR0FBaUIsSUFBSSxDQUFD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QUFPLEVBQUUsNENBQTRDO2dCQUNyRCxRQUFRLEVBQUU7b0JBQ1IsZ0JBQWdCLEVBQUUsSUFBSTtpQkFDdkI7YUFDRixDQUFDLENBQUM7WUFFSCxNQUFNLENBQUMsSUFBSSxDQUFDLHlEQUF5RCxDQUFDLENBQUM7UUFDekUsQ0FBQztRQUVELGtCQUFrQjtRQUNsQixJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFFRDs7O09BR0c7SUFDSyx3QkFBd0IsQ0FBQyxNQUFjLEVBQUUsU0FBaUI7UUFDaEUsUUFBUSxNQUFNLEVBQUUsQ0FBQztZQUNmLEtBQUssR0FBRztnQkFDTixPQUFPLGdGQUFnRixDQUFDO1lBQzFGLEtBQUssR0FBRztnQkFDTixPQUFPLGtFQUFrRSxDQUFDO1lBQzVFLEtBQUssR0FBRztnQkFDTixPQUFPLHVFQUF1RSxDQUFDO1lBQ2pGLEtBQUssR0FBRztnQkFDTixPQUFPLDREQUE0RCxDQUFDO1lBQ3RFLEtBQUssR0FBRztnQkFDTixPQUFPLHFFQUFxRSxDQUFDO1lBQy9FLEtBQUssR0FBRztnQkFDTixPQUFPLGtFQUFrRSxDQUFDO1lBQzVFLEtBQUssR0FBRyxDQUFDO1lBQ1QsS0FBSyxHQUFHLENBQUM7WUFDVCxLQUFLLEdBQUcsQ0FBQztZQUNULEtBQUssR0FBRztnQkFDTixPQUFPLDRFQUE0RSxDQUFDO1lBQ3RGO2dCQUNFLG9FQUFvRTtnQkFDcEUsTUFBTSxDQUFDLEtBQUssQ0FBQyxpQ0FBaUMsU0FBUyxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO2dCQUN2RSxPQUFPLHNCQUFzQixTQUFTLHdEQUF3RCxDQUFDO1FBQ25HLENBQUM7SUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHaXRIdWIgYXV0aGVudGljYXRpb24gbWFuYWdlciB1c2luZyBPQXV0aCBkZXZpY2UgZmxvd1xuICogSGFuZGxlcyBhdXRoZW50aWNhdGlvbiBmb3IgTUNQIHNlcnZlcnMgd2l0aG91dCByZXF1aXJpbmcgY2xpZW50IHNlY3JldHNcbiAqL1xuXG5pbXBvcnQgeyBUb2tlbk1hbmFnZXIgfSBmcm9tICcuLi9zZWN1cml0eS90b2tlbk1hbmFnZXIuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IEFQSUNhY2hlIH0gZnJvbSAnLi4vY2FjaGUvQVBJQ2FjaGUuanMnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuLi9zZWN1cml0eS9zZWN1cml0eU1vbml0b3IuanMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIERldmljZUNvZGVSZXNwb25zZSB7XG4gIGRldmljZV9jb2RlOiBzdHJpbmc7XG4gIHVzZXJfY29kZTogc3RyaW5nO1xuICB2ZXJpZmljYXRpb25fdXJpOiBzdHJpbmc7XG4gIGV4cGlyZXNfaW46IG51bWJlcjtcbiAgaW50ZXJ2YWw6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBUb2tlblJlc3BvbnNlIHtcbiAgYWNjZXNzX3Rva2VuOiBzdHJpbmc7XG4gIHRva2VuX3R5cGU6IHN0cmluZztcbiAgc2NvcGU6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBBdXRoU3RhdHVzIHtcbiAgaXNBdXRoZW50aWNhdGVkOiBib29sZWFuO1xuICBoYXNUb2tlbjogYm9vbGVhbjtcbiAgdXNlcm5hbWU/OiBzdHJpbmc7XG4gIHNjb3Blcz86IHN0cmluZ1tdO1xuICBleHBpcmVzQXQ/OiBEYXRlO1xufVxuXG4vKipcbiAqIE1hbmFnZXMgR2l0SHViIGF1dGhlbnRpY2F0aW9uIHVzaW5nIHRoZSBPQXV0aCBkZXZpY2UgZmxvd1xuICogVGhpcyBpcyB0aGUgcmVjb21tZW5kZWQgYXBwcm9hY2ggZm9yIENMSS9kZXNrdG9wIGFwcGxpY2F0aW9uc1xuICovXG5leHBvcnQgY2xhc3MgR2l0SHViQXV0aE1hbmFnZXIge1xuICAvLyBHaXRIdWIgT0F1dGggQXBwIENsaWVudCBJRCBmb3IgRG9sbGhvdXNlTUNQXG4gIC8vIE11c3QgYmUgY29uZmlndXJlZCB2aWEgZW52aXJvbm1lbnQgdmFyaWFibGVcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgQ0xJRU5UX0lEID0gcHJvY2Vzcy5lbnYuRE9MTEhPVVNFX0dJVEhVQl9DTElFTlRfSUQ7XG4gIFxuICAvLyBHaXRIdWIgT0F1dGggZW5kcG9pbnRzXG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IERFVklDRV9DT0RFX1VSTCA9ICdodHRwczovL2dpdGh1Yi5jb20vbG9naW4vZGV2aWNlL2NvZGUnO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBUT0tFTl9VUkwgPSAnaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoL2FjY2Vzc190b2tlbic7XG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IFVTRVJfVVJMID0gJ2h0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcic7XG4gIFxuICAvLyBQb2xsaW5nIGNvbmZpZ3VyYXRpb25cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgREVGQVVMVF9QT0xMX0lOVEVSVkFMID0gNTAwMDsgLy8gNSBzZWNvbmRzXG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IE1BWF9QT0xMX0FUVEVNUFRTID0gMTgwOyAvLyAxNSBtaW51dGVzIHRvdGFsXG4gIFxuICBwcml2YXRlIGFwaUNhY2hlOiBBUElDYWNoZTtcbiAgcHJpdmF0ZSBhY3RpdmVQb2xsaW5nOiBBYm9ydENvbnRyb2xsZXIgfCBudWxsID0gbnVsbDtcbiAgXG4gIGNvbnN0cnVjdG9yKGFwaUNhY2hlOiBBUElDYWNoZSkge1xuICAgIHRoaXMuYXBpQ2FjaGUgPSBhcGlDYWNoZTtcbiAgfVxuICBcbiAgLyoqXG4gICAqIEV4ZWN1dGUgYSBuZXR3b3JrIHJlcXVlc3Qgd2l0aCByZXRyeSBsb2dpY1xuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBmZXRjaFdpdGhSZXRyeShcbiAgICB1cmw6IHN0cmluZywgXG4gICAgb3B0aW9uczogUmVxdWVzdEluaXQsIFxuICAgIG1heFJldHJpZXM6IG51bWJlciA9IDMsXG4gICAgcmV0cnlEZWxheTogbnVtYmVyID0gMTAwMFxuICApOiBQcm9taXNlPFJlc3BvbnNlPiB7XG4gICAgbGV0IGxhc3RFcnJvcjogRXJyb3IgfCBudWxsID0gbnVsbDtcbiAgICBcbiAgICBmb3IgKGxldCBhdHRlbXB0ID0gMTsgYXR0ZW1wdCA8PSBtYXhSZXRyaWVzOyBhdHRlbXB0KyspIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsLCBvcHRpb25zKTtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbGFzdEVycm9yID0gZXJyb3IgYXMgRXJyb3I7XG4gICAgICAgIFxuICAgICAgICAvLyBDaGVjayBpZiBpdCdzIGEgbmV0d29yayBlcnJvciB0aGF0IHNob3VsZCBiZSByZXRyaWVkXG4gICAgICAgIGNvbnN0IGlzTmV0d29ya0Vycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciAmJiAoXG4gICAgICAgICAgZXJyb3IubWVzc2FnZS5pbmNsdWRlcygnRUNPTk5SRUZVU0VEJykgfHxcbiAgICAgICAgICBlcnJvci5tZXNzYWdlLmluY2x1ZGVzKCdFVElNRURPVVQnKSB8fFxuICAgICAgICAgIGVycm9yLm1lc3NhZ2UuaW5jbHVkZXMoJ0VOT1RGT1VORCcpIHx8XG4gICAgICAgICAgZXJyb3IubWVzc2FnZS5pbmNsdWRlcygnbmV0d29yaycpXG4gICAgICAgICk7XG4gICAgICAgIFxuICAgICAgICBpZiAoaXNOZXR3b3JrRXJyb3IgJiYgYXR0ZW1wdCA8IG1heFJldHJpZXMpIHtcbiAgICAgICAgICBsb2dnZXIuZGVidWcoYE5ldHdvcmsgcmVxdWVzdCBmYWlsZWQsIHJldHJ5aW5nICgke2F0dGVtcHR9LyR7bWF4UmV0cmllc30pYCwge1xuICAgICAgICAgICAgdXJsLFxuICAgICAgICAgICAgZXJyb3I6IGVycm9yLm1lc3NhZ2UsXG4gICAgICAgICAgICBuZXh0UmV0cnlJbjogcmV0cnlEZWxheSAqIGF0dGVtcHRcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBcbiAgICAgICAgICAvLyBFeHBvbmVudGlhbCBiYWNrb2ZmXG4gICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UocmVzb2x2ZSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIHJldHJ5RGVsYXkgKiBhdHRlbXB0KSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gTm90IGEgbmV0d29yayBlcnJvciBvciBsYXN0IGF0dGVtcHQsIHRocm93IGltbWVkaWF0ZWx5XG4gICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgXG4gICAgdGhyb3cgbGFzdEVycm9yIHx8IG5ldyBFcnJvcignTmV0d29yayByZXF1ZXN0IGZhaWxlZCBhZnRlciBhbGwgcmV0cmllcycpO1xuICB9XG4gIFxuICAvKipcbiAgICogQ2hlY2sgY3VycmVudCBhdXRoZW50aWNhdGlvbiBzdGF0dXNcbiAgICovXG4gIGFzeW5jIGdldEF1dGhTdGF0dXMoKTogUHJvbWlzZTxBdXRoU3RhdHVzPiB7XG4gICAgY29uc3QgdG9rZW4gPSBhd2FpdCBUb2tlbk1hbmFnZXIuZ2V0R2l0SHViVG9rZW5Bc3luYygpO1xuICAgIFxuICAgIGlmICghdG9rZW4pIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGlzQXV0aGVudGljYXRlZDogZmFsc2UsXG4gICAgICAgIGhhc1Rva2VuOiBmYWxzZVxuICAgICAgfTtcbiAgICB9XG4gICAgXG4gICAgdHJ5IHtcbiAgICAgIC8vIFRyeSB0byBnZXQgdXNlciBpbmZvIHRvIHZhbGlkYXRlIHRva2VuXG4gICAgICBjb25zdCB1c2VySW5mbyA9IGF3YWl0IHRoaXMuZmV0Y2hVc2VySW5mbyh0b2tlbik7XG4gICAgICBcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGlzQXV0aGVudGljYXRlZDogdHJ1ZSxcbiAgICAgICAgaGFzVG9rZW46IHRydWUsXG4gICAgICAgIHVzZXJuYW1lOiB1c2VySW5mby5sb2dpbixcbiAgICAgICAgc2NvcGVzOiB1c2VySW5mby5zY29wZXNcbiAgICAgIH07XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIC8vIFRva2VuIG1pZ2h0IGJlIGludmFsaWQgb3IgZXhwaXJlZFxuICAgICAgbG9nZ2VyLmRlYnVnKCdUb2tlbiB2YWxpZGF0aW9uIGZhaWxlZCcsIHsgZXJyb3IgfSk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBpc0F1dGhlbnRpY2F0ZWQ6IGZhbHNlLFxuICAgICAgICBoYXNUb2tlbjogdHJ1ZSAvLyBIYXMgdG9rZW4gYnV0IGl0J3MgaW52YWxpZFxuICAgICAgfTtcbiAgICB9XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBJbml0aWF0ZSB0aGUgZGV2aWNlIGZsb3cgYXV0aGVudGljYXRpb24gcHJvY2Vzc1xuICAgKi9cbiAgYXN5bmMgaW5pdGlhdGVEZXZpY2VGbG93KCk6IFByb21pc2U8RGV2aWNlQ29kZVJlc3BvbnNlPiB7XG4gICAgaWYgKCFHaXRIdWJBdXRoTWFuYWdlci5DTElFTlRfSUQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgJ0dpdEh1YiBPQXV0aCBpcyBub3QgY29uZmlndXJlZC4gUGxlYXNlIHNldCBET0xMSE9VU0VfR0lUSFVCX0NMSUVOVF9JRCBlbnZpcm9ubWVudCB2YXJpYWJsZS4gJyArXG4gICAgICAgICdGb3Igc2V0dXAgaW5zdHJ1Y3Rpb25zLCB2aXNpdDogaHR0cHM6Ly9naXRodWIuY29tL0RvbGxob3VzZU1DUC9tY3Atc2VydmVyI2dpdGh1Yi1hdXRoZW50aWNhdGlvbidcbiAgICAgICk7XG4gICAgfVxuICAgIFxuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRoaXMuZmV0Y2hXaXRoUmV0cnkoR2l0SHViQXV0aE1hbmFnZXIuREVWSUNFX0NPREVfVVJMLCB7XG4gICAgICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgICAgICBoZWFkZXJzOiB7XG4gICAgICAgICAgJ0FjY2VwdCc6ICdhcHBsaWNhdGlvbi9qc29uJyxcbiAgICAgICAgICAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nXG4gICAgICAgIH0sXG4gICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICBjbGllbnRfaWQ6IEdpdEh1YkF1dGhNYW5hZ2VyLkNMSUVOVF9JRCxcbiAgICAgICAgICBzY29wZTogJ3B1YmxpY19yZXBvIHJlYWQ6dXNlcidcbiAgICAgICAgfSlcbiAgICAgIH0pO1xuICAgICAgXG4gICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgIC8vIFByb3ZpZGUgdXNlci1mcmllbmRseSBlcnJvciBtZXNzYWdlcyBiYXNlZCBvbiBzdGF0dXMgY29kZXNcbiAgICAgICAgY29uc3QgZXJyb3JNZXNzYWdlID0gdGhpcy5nZXRFcnJvck1lc3NhZ2VGb3JTdGF0dXMocmVzcG9uc2Uuc3RhdHVzLCAnZGV2aWNlIGZsb3cgaW5pdGlhbGl6YXRpb24nKTtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdEZXZpY2UgZmxvdyBpbml0aWF0aW9uIGZhaWxlZCcsIHsgXG4gICAgICAgICAgc3RhdHVzOiByZXNwb25zZS5zdGF0dXMsIFxuICAgICAgICAgIHN0YXR1c1RleHQ6IHJlc3BvbnNlLnN0YXR1c1RleHQgXG4gICAgICAgIH0pO1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyb3JNZXNzYWdlKTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTtcbiAgICAgIFxuICAgICAgLy8gVmFsaWRhdGUgcmVzcG9uc2VcbiAgICAgIGlmICghZGF0YS5kZXZpY2VfY29kZSB8fCAhZGF0YS51c2VyX2NvZGUgfHwgIWRhdGEudmVyaWZpY2F0aW9uX3VyaSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgZGV2aWNlIGZsb3cgcmVzcG9uc2UgZnJvbSBHaXRIdWInKTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgLy8gTG9nIHNlY3VyaXR5IGV2ZW50IGZvciBhdWRpdCB0cmFpbFxuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnVE9LRU5fVkFMSURBVElPTl9TVUNDRVNTJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdMT1cnLFxuICAgICAgICBzb3VyY2U6ICdHaXRIdWJBdXRoTWFuYWdlci5pbml0aWF0ZURldmljZUZsb3cnLFxuICAgICAgICBkZXRhaWxzOiAnR2l0SHViIE9BdXRoIGRldmljZSBmbG93IGluaXRpYXRlZCcsXG4gICAgICAgIG1ldGFkYXRhOiB7XG4gICAgICAgICAgdXNlckNvZGU6IGRhdGEudXNlcl9jb2RlLFxuICAgICAgICAgIGV4cGlyZXNJbjogZGF0YS5leHBpcmVzX2luLFxuICAgICAgICAgIGludGVydmFsOiBkYXRhLmludGVydmFsXG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgICAgXG4gICAgICByZXR1cm4gZGF0YSBhcyBEZXZpY2VDb2RlUmVzcG9uc2U7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIGluaXRpYXRlIGRldmljZSBmbG93JywgeyBlcnJvciB9KTtcbiAgICAgIHRocm93IG5ldyBFcnJvcignRmFpbGVkIHRvIHN0YXJ0IEdpdEh1YiBhdXRoZW50aWNhdGlvbi4gUGxlYXNlIGNoZWNrIHlvdXIgaW50ZXJuZXQgY29ubmVjdGlvbi4nKTtcbiAgICB9XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBQb2xsIGZvciB0b2tlbiBhZnRlciB1c2VyIGhhcyBhdXRob3JpemVkIHRoZSBkZXZpY2VcbiAgICovXG4gIGFzeW5jIHBvbGxGb3JUb2tlbihkZXZpY2VDb2RlOiBzdHJpbmcsIGludGVydmFsOiBudW1iZXIgPSBHaXRIdWJBdXRoTWFuYWdlci5ERUZBVUxUX1BPTExfSU5URVJWQUwpOiBQcm9taXNlPFRva2VuUmVzcG9uc2U+IHtcbiAgICAvLyBDcmVhdGUgbmV3IGFib3J0IGNvbnRyb2xsZXIgZm9yIHRoaXMgcG9sbGluZyBzZXNzaW9uXG4gICAgdGhpcy5hY3RpdmVQb2xsaW5nID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgIGNvbnN0IHNpZ25hbCA9IHRoaXMuYWN0aXZlUG9sbGluZy5zaWduYWw7XG4gICAgXG4gICAgbGV0IGF0dGVtcHRzID0gMDtcbiAgICBcbiAgICB0cnkge1xuICAgICAgd2hpbGUgKGF0dGVtcHRzIDwgR2l0SHViQXV0aE1hbmFnZXIuTUFYX1BPTExfQVRURU1QVFMpIHtcbiAgICAgICAgLy8gQ2hlY2sgaWYgcG9sbGluZyB3YXMgYWJvcnRlZFxuICAgICAgICBpZiAoc2lnbmFsLmFib3J0ZWQpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0F1dGhlbnRpY2F0aW9uIHBvbGxpbmcgd2FzIGNhbmNlbGxlZCcpO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICBhdHRlbXB0cysrO1xuICAgICAgICBcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChHaXRIdWJBdXRoTWFuYWdlci5UT0tFTl9VUkwsIHtcbiAgICAgICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgICAgICBoZWFkZXJzOiB7XG4gICAgICAgICAgICAnQWNjZXB0JzogJ2FwcGxpY2F0aW9uL2pzb24nLFxuICAgICAgICAgICAgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJ1xuICAgICAgICAgIH0sXG4gICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgICAgICAgY2xpZW50X2lkOiBHaXRIdWJBdXRoTWFuYWdlci5DTElFTlRfSUQsXG4gICAgICAgICAgICBkZXZpY2VfY29kZTogZGV2aWNlQ29kZSxcbiAgICAgICAgICAgIGdyYW50X3R5cGU6ICd1cm46aWV0ZjpwYXJhbXM6b2F1dGg6Z3JhbnQtdHlwZTpkZXZpY2VfY29kZSdcbiAgICAgICAgICB9KVxuICAgICAgICB9KTtcbiAgICAgICAgXG4gICAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgIFxuICAgICAgICAvLyBDaGVjayBmb3IgdmFyaW91cyByZXNwb25zZSBzdGF0ZXNcbiAgICAgICAgaWYgKGRhdGEuZXJyb3IpIHtcbiAgICAgICAgICBzd2l0Y2ggKGRhdGEuZXJyb3IpIHtcbiAgICAgICAgICAgIGNhc2UgJ2F1dGhvcml6YXRpb25fcGVuZGluZyc6XG4gICAgICAgICAgICAgIC8vIFVzZXIgaGFzbid0IGF1dGhvcml6ZWQgeWV0LCBrZWVwIHBvbGxpbmdcbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgIFxuICAgICAgICAgICAgY2FzZSAnc2xvd19kb3duJzpcbiAgICAgICAgICAgICAgLy8gSW5jcmVhc2UgcG9sbGluZyBpbnRlcnZhbFxuICAgICAgICAgICAgICBpbnRlcnZhbCA9IE1hdGgubWluKGludGVydmFsICogMS41LCAzMDAwMCk7IC8vIE1heCAzMCBzZWNvbmRzXG4gICAgICAgICAgICAgIGxvZ2dlci5kZWJ1ZygnU2xvd2luZyBkb3duIHBvbGxpbmcgaW50ZXJ2YWwnLCB7IG5ld0ludGVydmFsOiBpbnRlcnZhbCB9KTtcbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgIFxuICAgICAgICAgICAgY2FzZSAnZXhwaXJlZF90b2tlbic6XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignVGhlIGF1dGhvcml6YXRpb24gY29kZSBoYXMgZXhwaXJlZC4gUGxlYXNlIHN0YXJ0IG92ZXIuJyk7XG4gICAgICAgICAgICAgIFxuICAgICAgICAgICAgY2FzZSAnYWNjZXNzX2RlbmllZCc6XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignQXV0aG9yaXphdGlvbiB3YXMgZGVuaWVkLiBQbGVhc2UgdHJ5IGFnYWluLicpO1xuICAgICAgICAgICAgICBcbiAgICAgICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICAgIC8vIExvZyB0aGUgYWN0dWFsIGVycm9yIGZvciBkZWJ1Z2dpbmdcbiAgICAgICAgICAgICAgbG9nZ2VyLmRlYnVnKCdPQXV0aCBkZXZpY2UgZmxvdyBlcnJvcicsIHsgXG4gICAgICAgICAgICAgICAgZXJyb3I6IGRhdGEuZXJyb3IsIFxuICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uOiBkYXRhLmVycm9yX2Rlc2NyaXB0aW9uIFxuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdBdXRoZW50aWNhdGlvbiBmYWlsZWQuIFBsZWFzZSB0cnkgc3RhcnRpbmcgdGhlIHByb2Nlc3MgYWdhaW4uJyk7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKGRhdGEuYWNjZXNzX3Rva2VuKSB7XG4gICAgICAgICAgLy8gU3VjY2VzcyFcbiAgICAgICAgICByZXR1cm4gZGF0YSBhcyBUb2tlblJlc3BvbnNlO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBXYWl0IGJlZm9yZSBuZXh0IHBvbGxcbiAgICAgICAgLy8gV2FpdCBmb3IgaW50ZXJ2YWwgd2l0aCBhYm9ydCBzdXBwb3J0XG4gICAgICAgIGF3YWl0IHRoaXMud2FpdFdpdGhBYm9ydChpbnRlcnZhbCwgc2lnbmFsKTtcbiAgICAgICAgXG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAvLyBOZXR3b3JrIGVycm9ycyBzaG91bGRuJ3Qgc3RvcCBwb2xsaW5nXG4gICAgICAgIGxvZ2dlci5kZWJ1ZygnUG9sbCBhdHRlbXB0IGZhaWxlZCcsIHsgYXR0ZW1wdDogYXR0ZW1wdHMsIGVycm9yIH0pO1xuICAgICAgICBhd2FpdCB0aGlzLndhaXRXaXRoQWJvcnQoaW50ZXJ2YWwsIHNpZ25hbCk7XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIHRocm93IG5ldyBFcnJvcignQXV0aGVudGljYXRpb24gdGltZWQgb3V0LiBQbGVhc2UgdHJ5IGFnYWluLicpO1xuICAgIH0gZmluYWxseSB7XG4gICAgICAvLyBDbGVhciBhY3RpdmUgcG9sbGluZyByZWZlcmVuY2VcbiAgICAgIHRoaXMuYWN0aXZlUG9sbGluZyA9IG51bGw7XG4gICAgfVxuICB9XG4gIFxuICAvKipcbiAgICogQ29tcGxldGUgdGhlIGF1dGhlbnRpY2F0aW9uIGZsb3cgYW5kIHN0b3JlIHRoZSB0b2tlblxuICAgKi9cbiAgYXN5bmMgY29tcGxldGVBdXRoZW50aWNhdGlvbih0b2tlblJlc3BvbnNlOiBUb2tlblJlc3BvbnNlKTogUHJvbWlzZTxBdXRoU3RhdHVzPiB7XG4gICAgLy8gU3RvcmUgdG9rZW4gc2VjdXJlbHlcbiAgICBhd2FpdCB0aGlzLnN0b3JlVG9rZW4odG9rZW5SZXNwb25zZS5hY2Nlc3NfdG9rZW4pO1xuICAgIFxuICAgIC8vIEdldCB1c2VyIGluZm9cbiAgICBjb25zdCB1c2VySW5mbyA9IGF3YWl0IHRoaXMuZmV0Y2hVc2VySW5mbyh0b2tlblJlc3BvbnNlLmFjY2Vzc190b2tlbik7XG4gICAgXG4gICAgLy8gTG9nIHN1Y2Nlc3NmdWwgYXV0aGVudGljYXRpb24gY29tcGxldGlvblxuICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgIHR5cGU6ICdUT0tFTl9WQUxJREFUSU9OX1NVQ0NFU1MnLFxuICAgICAgc2V2ZXJpdHk6ICdMT1cnLFxuICAgICAgc291cmNlOiAnR2l0SHViQXV0aE1hbmFnZXIuY29tcGxldGVBdXRoZW50aWNhdGlvbicsXG4gICAgICBkZXRhaWxzOiAnR2l0SHViIE9BdXRoIGRldmljZSBmbG93IGNvbXBsZXRlZCBzdWNjZXNzZnVsbHknLFxuICAgICAgbWV0YWRhdGE6IHtcbiAgICAgICAgdXNlcm5hbWU6IHVzZXJJbmZvLmxvZ2luLFxuICAgICAgICBzY29wZXM6IHRva2VuUmVzcG9uc2Uuc2NvcGUuc3BsaXQoJyAnKSxcbiAgICAgICAgdG9rZW5UeXBlOiBUb2tlbk1hbmFnZXIuZ2V0VG9rZW5UeXBlKHRva2VuUmVzcG9uc2UuYWNjZXNzX3Rva2VuKVxuICAgICAgfVxuICAgIH0pO1xuICAgIFxuICAgIHJldHVybiB7XG4gICAgICBpc0F1dGhlbnRpY2F0ZWQ6IHRydWUsXG4gICAgICBoYXNUb2tlbjogdHJ1ZSxcbiAgICAgIHVzZXJuYW1lOiB1c2VySW5mby5sb2dpbixcbiAgICAgIHNjb3BlczogdG9rZW5SZXNwb25zZS5zY29wZS5zcGxpdCgnICcpXG4gICAgfTtcbiAgfVxuICBcbiAgLyoqXG4gICAqIENsZWFyIHN0b3JlZCBhdXRoZW50aWNhdGlvbiBhbmQgcmV2b2tlIHRva2VuXG4gICAqL1xuICBhc3luYyBjbGVhckF1dGhlbnRpY2F0aW9uKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRyeSB7XG4gICAgICAvLyBHZXQgdGhlIHRva2VuIGJlZm9yZSBjbGVhcmluZyBpdFxuICAgICAgY29uc3QgdG9rZW4gPSBhd2FpdCBUb2tlbk1hbmFnZXIuZ2V0R2l0SHViVG9rZW5Bc3luYygpO1xuICAgICAgXG4gICAgICBpZiAodG9rZW4pIHtcbiAgICAgICAgLy8gQXR0ZW1wdCB0byByZXZva2UgdGhlIHRva2VuIG9uIEdpdEh1YlxuICAgICAgICAvLyBOb3RlOiBHaXRIdWIgT0F1dGggdG9rZW5zIGRvbid0IGhhdmUgYSByZXZvY2F0aW9uIGVuZHBvaW50IGZvciBkZXZpY2UgZmxvdyB0b2tlbnNcbiAgICAgICAgLy8gQnV0IHdlJ2xsIGNsZWFyIHRoZSBjYWNoZSBhbmQgcmVtb3ZlIGZyb20gc3RvcmFnZVxuICAgICAgICBcbiAgICAgICAgLy8gQ2xlYXIgY2FjaGVkIHVzZXIgaW5mb1xuICAgICAgICB0aGlzLmFwaUNhY2hlLmNsZWFyKCk7XG4gICAgICAgIFxuICAgICAgICAvLyBMb2cgc2VjdXJpdHkgZXZlbnQgZm9yIGF1ZGl0IHRyYWlsXG4gICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICB0eXBlOiAnVE9LRU5fQ0FDSEVfQ0xFQVJFRCcsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdMT1cnLFxuICAgICAgICAgIHNvdXJjZTogJ0dpdEh1YkF1dGhNYW5hZ2VyLmNsZWFyQXV0aGVudGljYXRpb24nLFxuICAgICAgICAgIGRldGFpbHM6ICdHaXRIdWIgYXV0aGVudGljYXRpb24gY2xlYXJlZCBieSB1c2VyIHJlcXVlc3QnLFxuICAgICAgICAgIG1ldGFkYXRhOiB7XG4gICAgICAgICAgICBoYWRUb2tlbjogdHJ1ZSxcbiAgICAgICAgICAgIHRva2VuUHJlZml4OiBUb2tlbk1hbmFnZXIuZ2V0VG9rZW5QcmVmaXgodG9rZW4pXG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICAgIFxuICAgICAgLy8gUmVtb3ZlIGZyb20gc2VjdXJlIHN0b3JhZ2VcbiAgICAgIGF3YWl0IFRva2VuTWFuYWdlci5yZW1vdmVTdG9yZWRUb2tlbigpO1xuICAgICAgXG4gICAgICBsb2dnZXIuaW5mbygnR2l0SHViIGF1dGhlbnRpY2F0aW9uIGNsZWFyZWQgc3VjY2Vzc2Z1bGx5Jyk7X