@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.
208 lines (203 loc) • 24.7 kB
JavaScript
/**
* UnifiedEndpoint - Single unified MCP-AQL endpoint handler (Issue #196)
*
* This handler provides a single `mcp_aql` endpoint as an alternative to the
* 4 CRUD endpoints for users who want minimal token footprint (~300-400 tokens
* vs ~943 tokens for 4 endpoints).
*
* ARCHITECTURE:
* - Single entry point for all MCP-AQL operations
* - Routes internally to appropriate MCPAQLHandler methods
* - Uses Gatekeeper (PermissionGuard) for all permission checks
* - Maintains full security with minimal client-side footprint
*
* USE CASES:
* - Multi-server deployments where token budget is constrained
* - Clients that prefer simpler tool interfaces
* - Integration scenarios where 4 endpoints create complexity
*
* SECURITY:
* - All operations pass through PermissionGuard.validate()
* - Operation routing is determined server-side, not client-side
* - Full audit logging for all operations
*/
import { getRoute } from './OperationRouter.js';
import { isOperationInput, parseOperationInput, describeInvalidInput } from './types.js';
import { logger } from '../../utils/logger.js';
import { SecurityMonitor } from '../../security/securityMonitor.js';
/**
* UnifiedEndpoint - Single entry point for all MCP-AQL operations
*
* This class wraps MCPAQLHandler to provide a unified endpoint that
* automatically routes operations to the correct CRUD handler based
* on the operation name.
*
* SECURITY NOTE:
* Unlike the 4-endpoint mode where the client chooses which endpoint to call,
* in single-endpoint mode the server determines the appropriate handler
* based on the operation name. This means:
* 1. The client cannot bypass security by calling the wrong endpoint
* 2. All routing is enforced server-side via PermissionGuard
* 3. The operation-to-endpoint mapping is authoritative
*/
export class UnifiedEndpoint {
mcpAqlHandler;
constructor(mcpAqlHandler) {
this.mcpAqlHandler = mcpAqlHandler;
}
/**
* Handle any MCP-AQL operation through the unified endpoint.
*
* This method:
* 1. Validates input structure
* 2. Determines the correct CRUD endpoint for the operation
* 3. Routes to the appropriate handler method
* 4. Returns standardized OperationResult or BatchResult
*
* @param input - Operation input with operation name and params, or BatchRequest
* @returns OperationResult or BatchResult with success/failure status
*/
async handle(input) {
// Issue #301: Capture start time for response timing metadata
const startTime = performance.now();
// Extract operation name for logging (safe access before validation)
const operationName = isOperationInput(input) ? input.operation : 'unknown';
try {
// Step 1: Validate and normalize input structure (supports silent JSON fallback)
const parsedInput = parseOperationInput(input);
if (!parsedInput) {
return this.failure('Invalid input: expected OperationInput with operation name and optional params. ' +
describeInvalidInput(input), startTime);
}
const { operation } = parsedInput;
// Step 2: Determine the correct CRUD endpoint for this operation
const route = getRoute(operation);
if (!route) {
SecurityMonitor.logSecurityEvent({
type: 'UPDATE_SECURITY_VIOLATION',
severity: 'MEDIUM',
source: 'UnifiedEndpoint.handle',
details: `Unknown operation: "${operation}"`,
additionalData: { operation }
});
return this.failure(`Unknown operation: "${operation}". See tool description for available operations.`, startTime);
}
// Step 3: Route to the appropriate handler method
// The handler will perform its own PermissionGuard validation
// Pass parsedInput to ensure normalized format is used
const result = await this.routeToHandler(route.endpoint, parsedInput);
// Log successful routing
SecurityMonitor.logSecurityEvent({
type: 'ELEMENT_CREATED',
severity: 'LOW',
source: 'UnifiedEndpoint.handle',
details: `Operation '${operation}' routed to ${route.endpoint} handler`,
additionalData: { operation, endpoint: route.endpoint }
});
return result;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
SecurityMonitor.logSecurityEvent({
type: 'UPDATE_SECURITY_VIOLATION',
severity: 'MEDIUM',
source: 'UnifiedEndpoint.handle',
details: `Operation '${operationName}' failed: ${message}`,
additionalData: { operation: operationName, error: message }
});
logger.error('UnifiedEndpoint operation failed', {
operation: operationName,
error: message,
stack: error instanceof Error ? error.stack : undefined,
});
return this.failure(message, startTime);
}
}
/**
* Route input to the appropriate CRUD handler based on endpoint type.
*
* @param endpoint - The CRUD endpoint determined from operation routing
* @param input - The validated operation input
* @returns OperationResult or BatchResult from the handler
*/
async routeToHandler(endpoint, input) {
switch (endpoint) {
case 'CREATE':
return this.mcpAqlHandler.handleCreate(input);
case 'READ':
return this.mcpAqlHandler.handleRead(input);
case 'UPDATE':
return this.mcpAqlHandler.handleUpdate(input);
case 'DELETE':
return this.mcpAqlHandler.handleDelete(input);
case 'EXECUTE':
return this.mcpAqlHandler.handleExecute(input);
default: {
// Exhaustive check - TypeScript will error if a case is missing
const _exhaustive = endpoint;
return _exhaustive;
}
}
}
/**
* Build response metadata with correlation ID and timing.
* Issue #301: Request correlation support.
*/
buildMeta(startTime) {
return {
requestId: this.mcpAqlHandler.getCorrelationId() ?? 'unknown',
durationMs: parseFloat((performance.now() - startTime).toFixed(2)),
timestamp: new Date().toISOString(),
};
}
/**
* Create a failed operation result
*/
failure(error, startTime) {
return {
success: false,
error,
_meta: this.buildMeta(startTime),
};
}
}
/**
* Get all available operations with their endpoint mappings.
* This is useful for documentation and help text.
*
* @returns Map of operation names to their CRUDE endpoints
*/
export function getOperationHelp() {
return `
## MCP-AQL Unified Endpoint Operations (CRUDE)
### CREATE Operations (additive, non-destructive)
- create_element: Create a new element
- import_element: Import an element from exported data
- addEntry: Add an entry to a memory element
- activate_element: Activate an element for use in session
### READ Operations (safe, read-only)
- list_elements: List elements with filtering and pagination
- get_element: Retrieve a single element by name
- get_element_details: Get detailed information about an element
- search_elements: Full-text search across elements
- query_elements: Query elements with advanced filters
- get_active_elements: Get currently active elements
- validate_element: Validate an element's structure
- render: Render a template with variables
- export_element: Export an element to portable format
- deactivate_element: Deactivate an active element
### UPDATE Operations (modifying)
- edit_element: Modify an existing element's fields
### DELETE Operations (destructive)
- delete_element: Permanently delete an element
- clear: Clear entries from a memory element
### EXECUTE Operations (runtime lifecycle, potentially destructive)
- execute_agent: Start execution of an agent
- get_execution_state: Query current execution state
- record_execution_step: Record execution progress
- complete_execution: Signal successful completion
- continue_execution: Resume from saved state
- abort_execution: Abort a running execution
`.trim();
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVW5pZmllZEVuZHBvaW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2hhbmRsZXJzL21jcC1hcWwvVW5pZmllZEVuZHBvaW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0JHO0FBR0gsT0FBTyxFQUFFLFFBQVEsRUFBZ0IsTUFBTSxzQkFBc0IsQ0FBQztBQUM5RCxPQUFPLEVBQThDLGdCQUFnQixFQUFFLG1CQUFtQixFQUFFLG9CQUFvQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3JJLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUMvQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFFcEU7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxNQUFNLE9BQU8sZUFBZTtJQUNHO0lBQTdCLFlBQTZCLGFBQTRCO1FBQTVCLGtCQUFhLEdBQWIsYUFBYSxDQUFlO0lBQUcsQ0FBQztJQUU3RDs7Ozs7Ozs7Ozs7T0FXRztJQUNILEtBQUssQ0FBQyxNQUFNLENBQUMsS0FBYztRQUN6Qiw4REFBOEQ7UUFDOUQsTUFBTSxTQUFTLEdBQUcsV0FBVyxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRXBDLHFFQUFxRTtRQUNyRSxNQUFNLGFBQWEsR0FBRyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1FBRTVFLElBQUksQ0FBQztZQUNILGlGQUFpRjtZQUNqRixNQUFNLFdBQVcsR0FBRyxtQkFBbUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUMvQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7Z0JBQ2pCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FDakIsa0ZBQWtGO29CQUNsRixvQkFBb0IsQ0FBQyxLQUFLLENBQUMsRUFDM0IsU0FBUyxDQUNWLENBQUM7WUFDSixD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YSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNqRCxPQUFPLENBQUMsQ0FBQyxDQUFDO2dCQUNSLGdFQUFnRTtnQkFDaEUsTUFBTSxXQUFXLEdBQVUsUUFBUSxDQUFDO2dCQUNwQyxPQUFPLFdBQVcsQ0FBQztZQUNyQixDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRDs7O09BR0c7SUFDSyxTQUFTLENBQUMsU0FBaUI7UUFDakMsT0FBTztZQUNMLFNBQVMsRUFBRSxJQUFJLENBQUMsYUFBYSxDQUFDLGdCQUFnQixFQUFFLElBQUksU0FBUztZQUM3RCxVQUFVLEVBQUUsVUFBVSxDQUFDLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRSxHQUFHLFNBQVMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNsRSxTQUFTLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUU7U0FDcEMsQ0FBQztJQUNKLENBQUM7SUFFRDs7T0FFRztJQUNLLE9BQU8sQ0FBQyxLQUFhLEVBQUUsU0FBaUI7UUFDOUMsT0FBTztZQUNMLE9BQU8sRUFBRSxLQUFLO1lBQ2QsS0FBSztZQUNMLEtBQUssRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQztTQUNqQyxDQUFDO0lBQ0osQ0FBQztDQUNGO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCO0lBQzlCLE9BQU87Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0NBbUNSLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDVCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBVbmlmaWVkRW5kcG9pbnQgLSBTaW5nbGUgdW5pZmllZCBNQ1AtQVFMIGVuZHBvaW50IGhhbmRsZXIgKElzc3VlICMxOTYpXG4gKlxuICogVGhpcyBoYW5kbGVyIHByb3ZpZGVzIGEgc2luZ2xlIGBtY3BfYXFsYCBlbmRwb2ludCBhcyBhbiBhbHRlcm5hdGl2ZSB0byB0aGVcbiAqIDQgQ1JVRCBlbmRwb2ludHMgZm9yIHVzZXJzIHdobyB3YW50IG1pbmltYWwgdG9rZW4gZm9vdHByaW50ICh+MzAwLTQwMCB0b2tlbnNcbiAqIHZzIH45NDMgdG9rZW5zIGZvciA0IGVuZHBvaW50cykuXG4gKlxuICogQVJDSElURUNUVVJFOlxuICogLSBTaW5nbGUgZW50cnkgcG9pbnQgZm9yIGFsbCBNQ1AtQVFMIG9wZXJhdGlvbnNcbiAqIC0gUm91dGVzIGludGVybmFsbHkgdG8gYXBwcm9wcmlhdGUgTUNQQVFMSGFuZGxlciBtZXRob2RzXG4gKiAtIFVzZXMgR2F0ZWtlZXBlciAoUGVybWlzc2lvbkd1YXJkKSBmb3IgYWxsIHBlcm1pc3Npb24gY2hlY2tzXG4gKiAtIE1haW50YWlucyBmdWxsIHNlY3VyaXR5IHdpdGggbWluaW1hbCBjbGllbnQtc2lkZSBmb290cHJpbnRcbiAqXG4gKiBVU0UgQ0FTRVM6XG4gKiAtIE11bHRpLXNlcnZlciBkZXBsb3ltZW50cyB3aGVyZSB0b2tlbiBidWRnZXQgaXMgY29uc3RyYWluZWRcbiAqIC0gQ2xpZW50cyB0aGF0IHByZWZlciBzaW1wbGVyIHRvb2wgaW50ZXJmYWNlc1xuICogLSBJbnRlZ3JhdGlvbiBzY2VuYXJpb3Mgd2hlcmUgNCBlbmRwb2ludHMgY3JlYXRlIGNvbXBsZXhpdHlcbiAqXG4gKiBTRUNVUklUWTpcbiAqIC0gQWxsIG9wZXJhdGlvbnMgcGFzcyB0aHJvdWdoIFBlcm1pc3Npb25HdWFyZC52YWxpZGF0ZSgpXG4gKiAtIE9wZXJhdGlvbiByb3V0aW5nIGlzIGRldGVybWluZWQgc2VydmVyLXNpZGUsIG5vdCBjbGllbnQtc2lkZVxuICogLSBGdWxsIGF1ZGl0IGxvZ2dpbmcgZm9yIGFsbCBvcGVyYXRpb25zXG4gKi9cblxuaW1wb3J0IHsgTUNQQVFMSGFuZGxlciB9IGZyb20gJy4vTUNQQVFMSGFuZGxlci5qcyc7XG5pbXBvcnQgeyBnZXRSb3V0ZSwgQ1JVREVuZHBvaW50IH0gZnJvbSAnLi9PcGVyYXRpb25Sb3V0ZXIuanMnO1xuaW1wb3J0IHsgT3BlcmF0aW9uUmVzdWx0LCBCYXRjaFJlc3VsdCwgUmVzcG9uc2VNZXRhLCBpc09wZXJhdGlvbklucHV0LCBwYXJzZU9wZXJhdGlvbklucHV0LCBkZXNjcmliZUludmFsaWRJbnB1dCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IFNlY3VyaXR5TW9uaXRvciB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L3NlY3VyaXR5TW9uaXRvci5qcyc7XG5cbi8qKlxuICogVW5pZmllZEVuZHBvaW50IC0gU2luZ2xlIGVudHJ5IHBvaW50IGZvciBhbGwgTUNQLUFRTCBvcGVyYXRpb25zXG4gKlxuICogVGhpcyBjbGFzcyB3cmFwcyBNQ1BBUUxIYW5kbGVyIHRvIHByb3ZpZGUgYSB1bmlmaWVkIGVuZHBvaW50IHRoYXRcbiAqIGF1dG9tYXRpY2FsbHkgcm91dGVzIG9wZXJhdGlvbnMgdG8gdGhlIGNvcnJlY3QgQ1JVRCBoYW5kbGVyIGJhc2VkXG4gKiBvbiB0aGUgb3BlcmF0aW9uIG5hbWUuXG4gKlxuICogU0VDVVJJVFkgTk9URTpcbiAqIFVubGlrZSB0aGUgNC1lbmRwb2ludCBtb2RlIHdoZXJlIHRoZSBjbGllbnQgY2hvb3NlcyB3aGljaCBlbmRwb2ludCB0byBjYWxsLFxuICogaW4gc2luZ2xlLWVuZHBvaW50IG1vZGUgdGhlIHNlcnZlciBkZXRlcm1pbmVzIHRoZSBhcHByb3ByaWF0ZSBoYW5kbGVyXG4gKiBiYXNlZCBvbiB0aGUgb3BlcmF0aW9uIG5hbWUuIFRoaXMgbWVhbnM6XG4gKiAxLiBUaGUgY2xpZW50IGNhbm5vdCBieXBhc3Mgc2VjdXJpdHkgYnkgY2FsbGluZyB0aGUgd3JvbmcgZW5kcG9pbnRcbiAqIDIuIEFsbCByb3V0aW5nIGlzIGVuZm9yY2VkIHNlcnZlci1zaWRlIHZpYSBQZXJtaXNzaW9uR3VhcmRcbiAqIDMuIFRoZSBvcGVyYXRpb24tdG8tZW5kcG9pbnQgbWFwcGluZyBpcyBhdXRob3JpdGF0aXZlXG4gKi9cbmV4cG9ydCBjbGFzcyBVbmlmaWVkRW5kcG9pbnQge1xuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IG1jcEFxbEhhbmRsZXI6IE1DUEFRTEhhbmRsZXIpIHt9XG5cbiAgLyoqXG4gICAqIEhhbmRsZSBhbnkgTUNQLUFRTCBvcGVyYXRpb24gdGhyb3VnaCB0aGUgdW5pZmllZCBlbmRwb2ludC5cbiAgICpcbiAgICogVGhpcyBtZXRob2Q6XG4gICAqIDEuIFZhbGlkYXRlcyBpbnB1dCBzdHJ1Y3R1cmVcbiAgICogMi4gRGV0ZXJtaW5lcyB0aGUgY29ycmVjdCBDUlVEIGVuZHBvaW50IGZvciB0aGUgb3BlcmF0aW9uXG4gICAqIDMuIFJvdXRlcyB0byB0aGUgYXBwcm9wcmlhdGUgaGFuZGxlciBtZXRob2RcbiAgICogNC4gUmV0dXJucyBzdGFuZGFyZGl6ZWQgT3BlcmF0aW9uUmVzdWx0IG9yIEJhdGNoUmVzdWx0XG4gICAqXG4gICAqIEBwYXJhbSBpbnB1dCAtIE9wZXJhdGlvbiBpbnB1dCB3aXRoIG9wZXJhdGlvbiBuYW1lIGFuZCBwYXJhbXMsIG9yIEJhdGNoUmVxdWVzdFxuICAgKiBAcmV0dXJucyBPcGVyYXRpb25SZXN1bHQgb3IgQmF0Y2hSZXN1bHQgd2l0aCBzdWNjZXNzL2ZhaWx1cmUgc3RhdHVzXG4gICAqL1xuICBhc3luYyBoYW5kbGUoaW5wdXQ6IHVua25vd24pOiBQcm9taXNlPE9wZXJhdGlvblJlc3VsdCB8IEJhdGNoUmVzdWx0PiB7XG4gICAgLy8gSXNzdWUgIzMwMTogQ2FwdHVyZSBzdGFydCB0aW1lIGZvciByZXNwb25zZSB0aW1pbmcgbWV0YWRhdGFcbiAgICBjb25zdCBzdGFydFRpbWUgPSBwZXJmb3JtYW5jZS5ub3coKTtcblxuICAgIC8vIEV4dHJhY3Qgb3BlcmF0aW9uIG5hbWUgZm9yIGxvZ2dpbmcgKHNhZmUgYWNjZXNzIGJlZm9yZSB2YWxpZGF0aW9uKVxuICAgIGNvbnN0IG9wZXJhdGlvbk5hbWUgPSBpc09wZXJhdGlvbklucHV0KGlucHV0KSA/IGlucHV0Lm9wZXJhdGlvbiA6ICd1bmtub3duJztcblxuICAgIHRyeSB7XG4gICAgICAvLyBTdGVwIDE6IFZhbGlkYXRlIGFuZCBub3JtYWxpemUgaW5wdXQgc3RydWN0dXJlIChzdXBwb3J0cyBzaWxlbnQgSlNPTiBmYWxsYmFjaylcbiAgICAgIGNvbnN0IHBhcnNlZElucHV0ID0gcGFyc2VPcGVyYXRpb25JbnB1dChpbnB1dCk7XG4gICAgICBpZiAoIXBhcnNlZElucHV0KSB7XG4gICAgICAgIHJldHVybiB0aGlzLmZhaWx1cmUoXG4gICAgICAgICAgJ0ludmFsaWQgaW5wdXQ6IGV4cGVjdGVkIE9wZXJhdGlvbklucHV0IHdpdGggb3BlcmF0aW9uIG5hbWUgYW5kIG9wdGlvbmFsIHBhcmFtcy4gJyArXG4gICAgICAgICAgZGVzY3JpYmVJbnZhbGlkSW5wdXQoaW5wdXQpLFxuICAgICAgICAgIHN0YXJ0VGltZVxuICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICBjb25zdCB7IG9wZXJhdGlvbiB9ID0gcGFyc2VkSW5wdXQ7XG5cbiAgICAgIC8vIFN0ZXAgMjogRGV0ZXJtaW5lIHRoZSBjb3JyZWN0IENSVUQgZW5kcG9pbnQgZm9yIHRoaXMgb3BlcmF0aW9uXG4gICAgICBjb25zdCByb3V0ZSA9IGdldFJvdXRlKG9wZXJhdGlvbik7XG4gICAgICBpZiAoIXJvdXRlKSB7XG4gICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICB0eXBlOiAnVVBEQVRFX1NFQ1VSSVRZX1ZJT0xBVElPTicsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICAgIHNvdXJjZTogJ1VuaWZpZWRFbmRwb2ludC5oYW5kbGUnLFxuICAgICAgICAgIGRldGFpbHM6IGBVbmtub3duIG9wZXJhdGlvbjogXCIke29wZXJhdGlvbn1cImAsXG4gICAgICAgICAgYWRkaXRpb25hbERhdGE6IHsgb3BlcmF0aW9uIH1cbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiB0aGlzLmZhaWx1cmUoXG4gICAgICAgICAgYFVua25vd24gb3BlcmF0aW9uOiBcIiR7b3BlcmF0aW9ufVwiLiBTZWUgdG9vbCBkZXNjcmlwdGlvbiBmb3IgYXZhaWxhYmxlIG9wZXJhdGlvbnMuYCxcbiAgICAgICAgICBzdGFydFRpbWVcbiAgICAgICAgKTtcbiAgICAgIH1cblxuICAgICAgLy8gU3RlcCAzOiBSb3V0ZSB0byB0aGUgYXBwcm9wcmlhdGUgaGFuZGxlciBtZXRob2RcbiAgICAgIC8vIFRoZSBoYW5kbGVyIHdpbGwgcGVyZm9ybSBpdHMgb3duIFBlcm1pc3Npb25HdWFyZCB2YWxpZGF0aW9uXG4gICAgICAvLyBQYXNzIHBhcnNlZElucHV0IHRvIGVuc3VyZSBub3JtYWxpemVkIGZvcm1hdCBpcyB1c2VkXG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLnJvdXRlVG9IYW5kbGVyKHJvdXRlLmVuZHBvaW50LCBwYXJzZWRJbnB1dCk7XG5cbiAgICAgIC8vIExvZyBzdWNjZXNzZnVsIHJvdXRpbmdcbiAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgdHlwZTogJ0VMRU1FTlRfQ1JFQVRFRCcsXG4gICAgICAgIHNldmVyaXR5OiAnTE9XJyxcbiAgICAgICAgc291cmNlOiAnVW5pZmllZEVuZHBvaW50LmhhbmRsZScsXG4gICAgICAgIGRldGFpbHM6IGBPcGVyYXRpb24gJyR7b3BlcmF0aW9ufScgcm91dGVkIHRvICR7cm91dGUuZW5kcG9pbnR9IGhhbmRsZXJgLFxuICAgICAgICBhZGRpdGlvbmFsRGF0YTogeyBvcGVyYXRpb24sIGVuZHBvaW50OiByb3V0ZS5lbmRwb2ludCB9XG4gICAgICB9KTtcblxuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKTtcblxuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnVVBEQVRFX1NFQ1VSSVRZX1ZJT0xBVElPTicsXG4gICAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgICAgc291cmNlOiAnVW5pZmllZEVuZHBvaW50LmhhbmRsZScsXG4gICAgICAgIGRldGFpbHM6IGBPcGVyYXRpb24gJyR7b3BlcmF0aW9uTmFtZX0nIGZhaWxlZDogJHttZXNzYWdlfWAsXG4gICAgICAgIGFkZGl0aW9uYWxEYXRhOiB7IG9wZXJhdGlvbjogb3BlcmF0aW9uTmFtZSwgZXJyb3I6IG1lc3NhZ2UgfVxuICAgICAgfSk7XG5cbiAgICAgIGxvZ2dlci5lcnJvcignVW5pZmllZEVuZHBvaW50IG9wZXJhdGlvbiBmYWlsZWQnLCB7XG4gICAgICAgIG9wZXJhdGlvbjogb3BlcmF0aW9uTmFtZSxcbiAgICAgICAgZXJyb3I6IG1lc3NhZ2UsXG4gICAgICAgIHN0YWNrOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3Iuc3RhY2sgOiB1bmRlZmluZWQsXG4gICAgICB9KTtcblxuICAgICAgcmV0dXJuIHRoaXMuZmFpbHVyZShtZXNzYWdlLCBzdGFydFRpbWUpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSb3V0ZSBpbnB1dCB0byB0aGUgYXBwcm9wcmlhdGUgQ1JVRCBoYW5kbGVyIGJhc2VkIG9uIGVuZHBvaW50IHR5cGUuXG4gICAqXG4gICAqIEBwYXJhbSBlbmRwb2ludCAtIFRoZSBDUlVEIGVuZHBvaW50IGRldGVybWluZWQgZnJvbSBvcGVyYXRpb24gcm91dGluZ1xuICAgKiBAcGFyYW0gaW5wdXQgLSBUaGUgdmFsaWRhdGVkIG9wZXJhdGlvbiBpbnB1dFxuICAgKiBAcmV0dXJucyBPcGVyYXRpb25SZXN1bHQgb3IgQmF0Y2hSZXN1bHQgZnJvbSB0aGUgaGFuZGxlclxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyByb3V0ZVRvSGFuZGxlcihcbiAgICBlbmRwb2ludDogQ1JVREVuZHBvaW50LFxuICAgIGlucHV0OiB1bmtub3duXG4gICk6IFByb21pc2U8T3BlcmF0aW9uUmVzdWx0IHwgQmF0Y2hSZXN1bHQ+IHtcbiAgICBzd2l0Y2ggKGVuZHBvaW50KSB7XG4gICAgICBjYXNlICdDUkVBVEUnOlxuICAgICAgICByZXR1cm4gdGhpcy5tY3BBcWxIYW5kbGVyLmhhbmRsZUNyZWF0ZShpbnB1dCk7XG4gICAgICBjYXNlICdSRUFEJzpcbiAgICAgICAgcmV0dXJuIHRoaXMubWNwQXFsSGFuZGxlci5oYW5kbGVSZWFkKGlucHV0KTtcbiAgICAgIGNhc2UgJ1VQREFURSc6XG4gICAgICAgIHJldHVybiB0aGlzLm1jcEFxbEhhbmRsZXIuaGFuZGxlVXBkYXRlKGlucHV0KTtcbiAgICAgIGNhc2UgJ0RFTEVURSc6XG4gICAgICAgIHJldHVybiB0aGlzLm1jcEFxbEhhbmRsZXIuaGFuZGxlRGVsZXRlKGlucHV0KTtcbiAgICAgIGNhc2UgJ0VYRUNVVEUnOlxuICAgICAgICByZXR1cm4gdGhpcy5tY3BBcWxIYW5kbGVyLmhhbmRsZUV4ZWN1dGUoaW5wdXQpO1xuICAgICAgZGVmYXVsdDoge1xuICAgICAgICAvLyBFeGhhdXN0aXZlIGNoZWNrIC0gVHlwZVNjcmlwdCB3aWxsIGVycm9yIGlmIGEgY2FzZSBpcyBtaXNzaW5nXG4gICAgICAgIGNvbnN0IF9leGhhdXN0aXZlOiBuZXZlciA9IGVuZHBvaW50O1xuICAgICAgICByZXR1cm4gX2V4aGF1c3RpdmU7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEJ1aWxkIHJlc3BvbnNlIG1ldGFkYXRhIHdpdGggY29ycmVsYXRpb24gSUQgYW5kIHRpbWluZy5cbiAgICogSXNzdWUgIzMwMTogUmVxdWVzdCBjb3JyZWxhdGlvbiBzdXBwb3J0LlxuICAgKi9cbiAgcHJpdmF0ZSBidWlsZE1ldGEoc3RhcnRUaW1lOiBudW1iZXIpOiBSZXNwb25zZU1ldGEge1xuICAgIHJldHVybiB7XG4gICAgICByZXF1ZXN0SWQ6IHRoaXMubWNwQXFsSGFuZGxlci5nZXRDb3JyZWxhdGlvbklkKCkgPz8gJ3Vua25vd24nLFxuICAgICAgZHVyYXRpb25NczogcGFyc2VGbG9hdCgocGVyZm9ybWFuY2Uubm93KCkgLSBzdGFydFRpbWUpLnRvRml4ZWQoMikpLFxuICAgICAgdGltZXN0YW1wOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBmYWlsZWQgb3BlcmF0aW9uIHJlc3VsdFxuICAgKi9cbiAgcHJpdmF0ZSBmYWlsdXJlKGVycm9yOiBzdHJpbmcsIHN0YXJ0VGltZTogbnVtYmVyKTogT3BlcmF0aW9uUmVzdWx0IHtcbiAgICByZXR1cm4ge1xuICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICBlcnJvcixcbiAgICAgIF9tZXRhOiB0aGlzLmJ1aWxkTWV0YShzdGFydFRpbWUpLFxuICAgIH07XG4gIH1cbn1cblxuLyoqXG4gKiBHZXQgYWxsIGF2YWlsYWJsZSBvcGVyYXRpb25zIHdpdGggdGhlaXIgZW5kcG9pbnQgbWFwcGluZ3MuXG4gKiBUaGlzIGlzIHVzZWZ1bCBmb3IgZG9jdW1lbnRhdGlvbiBhbmQgaGVscCB0ZXh0LlxuICpcbiAqIEByZXR1cm5zIE1hcCBvZiBvcGVyYXRpb24gbmFtZXMgdG8gdGhlaXIgQ1JVREUgZW5kcG9pbnRzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRPcGVyYXRpb25IZWxwKCk6IHN0cmluZyB7XG4gIHJldHVybiBgXG4jIyBNQ1AtQVFMIFVuaWZpZWQgRW5kcG9pbnQgT3BlcmF0aW9ucyAoQ1JVREUpXG5cbiMjIyBDUkVBVEUgT3BlcmF0aW9ucyAoYWRkaXRpdmUsIG5vbi1kZXN0cnVjdGl2ZSlcbi0gY3JlYXRlX2VsZW1lbnQ6IENyZWF0ZSBhIG5ldyBlbGVtZW50XG4tIGltcG9ydF9lbGVtZW50OiBJbXBvcnQgYW4gZWxlbWVudCBmcm9tIGV4cG9ydGVkIGRhdGFcbi0gYWRkRW50cnk6IEFkZCBhbiBlbnRyeSB0byBhIG1lbW9yeSBlbGVtZW50XG4tIGFjdGl2YXRlX2VsZW1lbnQ6IEFjdGl2YXRlIGFuIGVsZW1lbnQgZm9yIHVzZSBpbiBzZXNzaW9uXG5cbiMjIyBSRUFEIE9wZXJhdGlvbnMgKHNhZmUsIHJlYWQtb25seSlcbi0gbGlzdF9lbGVtZW50czogTGlzdCBlbGVtZW50cyB3aXRoIGZpbHRlcmluZyBhbmQgcGFnaW5hdGlvblxuLSBnZXRfZWxlbWVudDogUmV0cmlldmUgYSBzaW5nbGUgZWxlbWVudCBieSBuYW1lXG4tIGdldF9lbGVtZW50X2RldGFpbHM6IEdldCBkZXRhaWxlZCBpbmZvcm1hdGlvbiBhYm91dCBhbiBlbGVtZW50XG4tIHNlYXJjaF9lbGVtZW50czogRnVsbC10ZXh0IHNlYXJjaCBhY3Jvc3MgZWxlbWVudHNcbi0gcXVlcnlfZWxlbWVudHM6IFF1ZXJ5IGVsZW1lbnRzIHdpdGggYWR2YW5jZWQgZmlsdGVyc1xuLSBnZXRfYWN0aXZlX2VsZW1lbnRzOiBHZXQgY3VycmVudGx5IGFjdGl2ZSBlbGVtZW50c1xuLSB2YWxpZGF0ZV9lbGVtZW50OiBWYWxpZGF0ZSBhbiBlbGVtZW50J3Mgc3RydWN0dXJlXG4tIHJlbmRlcjogUmVuZGVyIGEgdGVtcGxhdGUgd2l0aCB2YXJpYWJsZXNcbi0gZXhwb3J0X2VsZW1lbnQ6IEV4cG9ydCBhbiBlbGVtZW50IHRvIHBvcnRhYmxlIGZvcm1hdFxuLSBkZWFjdGl2YXRlX2VsZW1lbnQ6IERlYWN0aXZhdGUgYW4gYWN0aXZlIGVsZW1lbnRcblxuIyMjIFVQREFURSBPcGVyYXRpb25zIChtb2RpZnlpbmcpXG4tIGVkaXRfZWxlbWVudDogTW9kaWZ5IGFuIGV4aXN0aW5nIGVsZW1lbnQncyBmaWVsZHNcblxuIyMjIERFTEVURSBPcGVyYXRpb25zIChkZXN0cnVjdGl2ZSlcbi0gZGVsZXRlX2VsZW1lbnQ6IFBlcm1hbmVudGx5IGRlbGV0ZSBhbiBlbGVtZW50XG4tIGNsZWFyOiBDbGVhciBlbnRyaWVzIGZyb20gYSBtZW1vcnkgZWxlbWVudFxuXG4jIyMgRVhFQ1VURSBPcGVyYXRpb25zIChydW50aW1lIGxpZmVjeWNsZSwgcG90ZW50aWFsbHkgZGVzdHJ1Y3RpdmUpXG4tIGV4ZWN1dGVfYWdlbnQ6IFN0YXJ0IGV4ZWN1dGlvbiBvZiBhbiBhZ2VudFxuLSBnZXRfZXhlY3V0aW9uX3N0YXRlOiBRdWVyeSBjdXJyZW50IGV4ZWN1dGlvbiBzdGF0ZVxuLSByZWNvcmRfZXhlY3V0aW9uX3N0ZXA6IFJlY29yZCBleGVjdXRpb24gcHJvZ3Jlc3Ncbi0gY29tcGxldGVfZXhlY3V0aW9uOiBTaWduYWwgc3VjY2Vzc2Z1bCBjb21wbGV0aW9uXG4tIGNvbnRpbnVlX2V4ZWN1dGlvbjogUmVzdW1lIGZyb20gc2F2ZWQgc3RhdGVcbi0gYWJvcnRfZXhlY3V0aW9uOiBBYm9ydCBhIHJ1bm5pbmcgZXhlY3V0aW9uXG5gLnRyaW0oKTtcbn1cbiJdfQ==