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