UNPKG

vibetunnel

Version:

Terminal sharing server with web interface - supports macOS, Linux, and headless environments

436 lines 60 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BufferAggregator = void 0; const chalk_1 = __importDefault(require("chalk")); const ws_1 = require("ws"); const logger_js_1 = require("../utils/logger.js"); const logger = (0, logger_js_1.createLogger)('buffer-aggregator'); /** * Aggregates and distributes terminal buffer updates across local and remote sessions. * * The BufferAggregator acts as a central hub for WebSocket-based terminal buffer streaming, * managing connections between clients and terminal sessions. In HQ (headquarters) mode, * it also handles connections to remote VibeTunnel servers, enabling cross-server terminal * session access. * * Key features: * - WebSocket-based real-time buffer streaming * - Support for both local and remote terminal sessions * - Efficient binary protocol for buffer updates * - Automatic connection management and reconnection * - Session subscription/unsubscription handling * - Remote server connection pooling in HQ mode * - Graceful cleanup on disconnection * * The aggregator uses a binary protocol for buffer updates: * - Magic byte (0xBF) to identify binary messages * - 4-byte session ID length (little-endian) * - UTF-8 encoded session ID * - Binary terminal buffer data * * @example * ```typescript * // Create aggregator for local-only mode * const aggregator = new BufferAggregator({ * terminalManager, * remoteRegistry: null, * isHQMode: false * }); * * // Handle client WebSocket connection * wss.on('connection', (ws) => { * aggregator.handleClientConnection(ws); * }); * * // In HQ mode with remote registry * const hqAggregator = new BufferAggregator({ * terminalManager, * remoteRegistry, * isHQMode: true * }); * * // Register remote server * await hqAggregator.onRemoteRegistered(remoteId); * ``` * * @see TerminalManager - Manages local terminal instances * @see RemoteRegistry - Tracks remote VibeTunnel servers in HQ mode * @see web/src/server/routes/buffer.ts - WebSocket endpoint setup */ class BufferAggregator { constructor(config) { this.remoteConnections = new Map(); this.clientSubscriptions = new Map(); this.config = config; logger.log(`BufferAggregator initialized (HQ mode: ${config.isHQMode})`); } /** * Handle a new client WebSocket connection */ async handleClientConnection(ws) { logger.log(chalk_1.default.blue('New client connected')); const clientId = `client-${Date.now()}`; logger.debug(`Assigned client ID: ${clientId}`); // Initialize subscription map for this client this.clientSubscriptions.set(ws, new Map()); // Send welcome message ws.send(JSON.stringify({ type: 'connected', version: '1.0' })); logger.debug('Sent welcome message to client'); // Handle messages from client ws.on('message', async (message) => { try { const data = JSON.parse(message.toString()); await this.handleClientMessage(ws, data); } catch (error) { logger.error('Error handling client message:', error); ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format', })); } }); // Handle disconnection ws.on('close', () => { this.handleClientDisconnect(ws); }); ws.on('error', (error) => { logger.error('Client WebSocket error:', error); }); } /** * Handle messages from a client */ async handleClientMessage(clientWs, data) { const subscriptions = this.clientSubscriptions.get(clientWs); if (!subscriptions) return; if (data.type === 'subscribe' && data.sessionId) { const sessionId = data.sessionId; // Unsubscribe if already subscribed if (subscriptions.has(sessionId)) { const existingUnsubscribe = subscriptions.get(sessionId); if (existingUnsubscribe) { existingUnsubscribe(); } subscriptions.delete(sessionId); } // Check if this is a local or remote session const isRemoteSession = this.config.isHQMode && this.config.remoteRegistry && this.config.remoteRegistry.getRemoteBySessionId(sessionId); if (isRemoteSession) { // Subscribe to remote session logger.debug(`Subscribing to remote session ${sessionId} on remote ${isRemoteSession.id}`); await this.subscribeToRemoteSession(clientWs, sessionId, isRemoteSession.id); } else { // Subscribe to local session logger.debug(`Subscribing to local session ${sessionId}`); await this.subscribeToLocalSession(clientWs, sessionId); } clientWs.send(JSON.stringify({ type: 'subscribed', sessionId })); logger.log(chalk_1.default.green(`Client subscribed to session ${sessionId}`)); } else if (data.type === 'unsubscribe' && data.sessionId) { const sessionId = data.sessionId; const unsubscribe = subscriptions.get(sessionId); if (unsubscribe) { unsubscribe(); subscriptions.delete(sessionId); logger.log(chalk_1.default.yellow(`Client unsubscribed from session ${sessionId}`)); } // Also unsubscribe from remote if applicable if (this.config.isHQMode && this.config.remoteRegistry) { const remote = this.config.remoteRegistry.getRemoteBySessionId(sessionId); if (remote) { const remoteConn = this.remoteConnections.get(remote.id); if (remoteConn) { remoteConn.subscriptions.delete(sessionId); if (remoteConn.ws.readyState === ws_1.WebSocket.OPEN) { remoteConn.ws.send(JSON.stringify({ type: 'unsubscribe', sessionId })); logger.debug(`Sent unsubscribe request to remote ${remoteConn.remoteName} for session ${sessionId}`); } else { logger.debug(`Cannot unsubscribe from remote ${remoteConn.remoteName} - WebSocket not open`); } } } } } else if (data.type === 'ping') { clientWs.send(JSON.stringify({ type: 'pong', timestamp: Date.now() })); } } /** * Subscribe a client to a local session */ async subscribeToLocalSession(clientWs, sessionId) { const subscriptions = this.clientSubscriptions.get(clientWs); if (!subscriptions) return; try { const unsubscribe = await this.config.terminalManager.subscribeToBufferChanges(sessionId, (sessionId, snapshot) => { try { const buffer = this.config.terminalManager.encodeSnapshot(snapshot); const sessionIdBuffer = Buffer.from(sessionId, 'utf8'); const totalLength = 1 + 4 + sessionIdBuffer.length + buffer.length; const fullBuffer = Buffer.allocUnsafe(totalLength); let offset = 0; fullBuffer.writeUInt8(0xbf, offset); // Magic byte for binary message offset += 1; fullBuffer.writeUInt32LE(sessionIdBuffer.length, offset); offset += 4; sessionIdBuffer.copy(fullBuffer, offset); offset += sessionIdBuffer.length; buffer.copy(fullBuffer, offset); if (clientWs.readyState === ws_1.WebSocket.OPEN) { clientWs.send(fullBuffer); } else { logger.debug(`Skipping buffer update - client WebSocket not open`); } } catch (error) { logger.error('Error encoding buffer update:', error); } }); subscriptions.set(sessionId, unsubscribe); logger.debug(`Created subscription for local session ${sessionId}`); // Send initial buffer logger.debug(`Sending initial buffer for session ${sessionId}`); const initialSnapshot = await this.config.terminalManager.getBufferSnapshot(sessionId); const buffer = this.config.terminalManager.encodeSnapshot(initialSnapshot); const sessionIdBuffer = Buffer.from(sessionId, 'utf8'); const totalLength = 1 + 4 + sessionIdBuffer.length + buffer.length; const fullBuffer = Buffer.allocUnsafe(totalLength); let offset = 0; fullBuffer.writeUInt8(0xbf, offset); offset += 1; fullBuffer.writeUInt32LE(sessionIdBuffer.length, offset); offset += 4; sessionIdBuffer.copy(fullBuffer, offset); offset += sessionIdBuffer.length; buffer.copy(fullBuffer, offset); if (clientWs.readyState === ws_1.WebSocket.OPEN) { clientWs.send(fullBuffer); logger.debug(`Sent initial buffer (${fullBuffer.length} bytes) for session ${sessionId}`); } else { logger.warn(`Cannot send initial buffer - client WebSocket not open`); } } catch (error) { logger.error(`Error subscribing to local session ${sessionId}:`, error); clientWs.send(JSON.stringify({ type: 'error', message: 'Failed to subscribe to session' })); } } /** * Subscribe a client to a remote session */ async subscribeToRemoteSession(clientWs, sessionId, remoteId) { // Ensure we have a connection to this remote let remoteConn = this.remoteConnections.get(remoteId); if (!remoteConn || remoteConn.ws.readyState !== ws_1.WebSocket.OPEN) { logger.debug(`No active connection to remote ${remoteId}, establishing new connection`); // Need to connect to remote const connected = await this.connectToRemote(remoteId); if (!connected) { logger.warn(`Failed to connect to remote ${remoteId} for session ${sessionId}`); clientWs.send(JSON.stringify({ type: 'error', message: 'Failed to connect to remote server' })); return; } remoteConn = this.remoteConnections.get(remoteId); } if (!remoteConn) return; // Subscribe to the session on the remote remoteConn.subscriptions.add(sessionId); remoteConn.ws.send(JSON.stringify({ type: 'subscribe', sessionId })); logger.debug(`Sent subscription request to remote ${remoteConn.remoteName} for session ${sessionId}`); // Store an unsubscribe function for the client const subscriptions = this.clientSubscriptions.get(clientWs); if (subscriptions) { subscriptions.set(sessionId, () => { // Will be handled in the unsubscribe message handler }); } } /** * Connect to a remote server's WebSocket */ async connectToRemote(remoteId) { logger.log(`Connecting to remote ${remoteId}`); if (!this.config.remoteRegistry) { logger.warn('No remote registry available'); return false; } const remote = this.config.remoteRegistry.getRemote(remoteId); if (!remote) { logger.warn(`Remote ${remoteId} not found in registry`); return false; } try { // Convert HTTP URL to WebSocket URL and add /buffers path const wsUrl = `${remote.url.replace(/^http/, 'ws')}/buffers`; const ws = new ws_1.WebSocket(wsUrl, { headers: { Authorization: `Bearer ${remote.token}`, }, }); logger.debug(`Attempting WebSocket connection to ${wsUrl}`); await new Promise((resolve, reject) => { const timeout = setTimeout(() => { logger.warn(`Connection to remote ${remote.name} timed out after 5s`); reject(new Error('Connection timeout')); }, 5000); ws.on('open', () => { clearTimeout(timeout); resolve(); }); ws.on('error', (error) => { clearTimeout(timeout); reject(error); }); }); const remoteConn = { ws, remoteId: remote.id, remoteName: remote.name, subscriptions: new Set(), }; this.remoteConnections.set(remoteId, remoteConn); // Handle messages from remote ws.on('message', (data) => { this.handleRemoteMessage(remoteId, data); }); logger.debug(`Remote ${remote.name} connection established with ${remoteConn.subscriptions.size} initial subscriptions`); // Handle disconnection ws.on('close', () => { logger.log(chalk_1.default.yellow(`Disconnected from remote ${remote.name}`)); this.remoteConnections.delete(remoteId); }); ws.on('error', (error) => { logger.error(`Remote ${remote.name} WebSocket error:`, error); }); logger.log(chalk_1.default.green(`Connected to remote ${remote.name}`)); return true; } catch (error) { logger.error(`Failed to connect to remote ${remoteId}:`, error); return false; } } /** * Handle messages from a remote server */ handleRemoteMessage(remoteId, data) { // Check if this is a binary buffer update if (data.length > 0 && data[0] === 0xbf) { // Forward to all clients subscribed to sessions from this remote this.forwardBufferToClients(data); } else { // JSON message try { const message = JSON.parse(data.toString()); logger.debug(`Remote ${remoteId} message:`, message.type); } catch (error) { logger.error(`Failed to parse remote message:`, error); } } } /** * Forward a buffer update to all subscribed clients */ forwardBufferToClients(buffer) { // Extract session ID from buffer if (buffer.length < 5) return; const sessionIdLength = buffer.readUInt32LE(1); if (buffer.length < 5 + sessionIdLength) return; const sessionId = buffer.subarray(5, 5 + sessionIdLength).toString('utf8'); // Forward to all clients subscribed to this session let forwardedCount = 0; for (const [clientWs, subscriptions] of this.clientSubscriptions) { if (subscriptions.has(sessionId) && clientWs.readyState === ws_1.WebSocket.OPEN) { clientWs.send(buffer); forwardedCount++; } } if (forwardedCount > 0) { logger.debug(`Forwarded buffer update for session ${sessionId} to ${forwardedCount} clients`); } } /** * Handle client disconnection */ handleClientDisconnect(ws) { const subscriptions = this.clientSubscriptions.get(ws); if (subscriptions) { const subscriptionCount = subscriptions.size; // Unsubscribe from all sessions for (const [sessionId, unsubscribe] of subscriptions) { logger.debug(`Cleaning up subscription for session ${sessionId}`); unsubscribe(); } subscriptions.clear(); logger.debug(`Cleaned up ${subscriptionCount} subscriptions`); } this.clientSubscriptions.delete(ws); logger.log(chalk_1.default.yellow('Client disconnected')); } /** * Register a new remote server (called when a remote registers with HQ) */ async onRemoteRegistered(remoteId) { logger.log(`Remote ${remoteId} registered, establishing connection`); // Optionally pre-connect to the remote const connected = await this.connectToRemote(remoteId); if (!connected) { logger.warn(`Failed to establish connection to newly registered remote ${remoteId}`); } } /** * Handle remote server unregistration */ onRemoteUnregistered(remoteId) { logger.log(`Remote ${remoteId} unregistered, closing connection`); const remoteConn = this.remoteConnections.get(remoteId); if (remoteConn) { logger.debug(`Closing connection to remote ${remoteConn.remoteName} with ${remoteConn.subscriptions.size} active subscriptions`); remoteConn.ws.close(); this.remoteConnections.delete(remoteId); } else { logger.debug(`No active connection found for unregistered remote ${remoteId}`); } } /** * Clean up all connections */ destroy() { logger.log(chalk_1.default.yellow('Shutting down BufferAggregator')); // Close all client connections const clientCount = this.clientSubscriptions.size; for (const [ws] of this.clientSubscriptions) { ws.close(); } this.clientSubscriptions.clear(); logger.debug(`Closed ${clientCount} client connections`); // Close all remote connections const remoteCount = this.remoteConnections.size; for (const [_, remoteConn] of this.remoteConnections) { remoteConn.ws.close(); } this.remoteConnections.clear(); logger.debug(`Closed ${remoteCount} remote connections`); } } exports.BufferAggregator = BufferAggregator; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVmZmVyLWFnZ3JlZ2F0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmVyL3NlcnZpY2VzL2J1ZmZlci1hZ2dyZWdhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLGtEQUEwQjtBQUMxQiwyQkFBK0I7QUFDL0Isa0RBQWtEO0FBSWxELE1BQU0sTUFBTSxHQUFHLElBQUEsd0JBQVksRUFBQyxtQkFBbUIsQ0FBQyxDQUFDO0FBZWpEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtREc7QUFDSCxNQUFhLGdCQUFnQjtJQUszQixZQUFZLE1BQThCO1FBSGxDLHNCQUFpQixHQUEyQyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQ3RFLHdCQUFtQixHQUE0QyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBRy9FLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLE1BQU0sQ0FBQyxHQUFHLENBQUMsMENBQTBDLE1BQU0sQ0FBQyxRQUFRLEdBQUcsQ0FBQyxDQUFDO0lBQzNFLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxzQkFBc0IsQ0FBQyxFQUFhO1FBQ3hDLE1BQU0sQ0FBQyxHQUFHLENBQUMsZUFBSyxDQUFDLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDLENBQUM7UUFDL0MsTUFBTSxRQUFRLEdBQUcsVUFBVSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQztRQUN4QyxNQUFNLENBQUMsS0FBSyxDQUFDLHVCQUF1QixRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBRWhELDhDQUE4QztRQUM5QyxJQUFJLENBQUMsbUJBQW1CLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxJQUFJLEdBQUcsRUFBRSxDQUFDLENBQUM7UUFFNUMsdUJBQXVCO1FBQ3ZCLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLElB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Q0FBQzthQUFNLENBQUM7WUFDTixNQUFNLENBQUMsS0FBSyxDQUFDLHNEQUFzRCxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ2pGLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxPQUFPO1FBQ0wsTUFBTSxDQUFDLEdBQUcsQ0FBQyxlQUFLLENBQUMsTUFBTSxDQUFDLGdDQUFnQyxDQUFDLENBQUMsQ0FBQztRQUUzRCwrQkFBK0I7UUFDL0IsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLG1CQUFtQixDQUFDLElBQUksQ0FBQztRQUNsRCxLQUFLLE1BQU0sQ0FBQyxFQUFFLENBQUMsSUFBSSxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztZQUM1QyxFQUFFLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDYixDQUFDO1FBQ0QsSUFBSSxDQUFDLG1CQUFtQixDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2pDLE1BQU0sQ0FBQyxLQUFLLENBQUMsVUFBVSxXQUFXLHFCQUFxQixDQUFDLENBQUM7UUFFekQsK0JBQStCO1FBQy9CLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUM7UUFDaEQsS0FBSyxNQUFNLENBQUMsQ0FBQyxFQUFFLFVBQVUsQ0FBQyxJQUFJLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1lBQ3JELFVBQVUsQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDeEIsQ0FBQztRQUNELElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMvQixNQUFNLENBQUMsS0FBSyxDQUFDLFVBQVUsV0FBVyxxQkFBcUIsQ0FBQyxDQUFDO0lBQzNELENBQUM7Q0FDRjtBQXZiRCw0Q0F1YkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgY2hhbGsgZnJvbSAnY2hhbGsnO1xuaW1wb3J0IHsgV2ViU29ja2V0IH0gZnJvbSAnd3MnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB0eXBlIHsgUmVtb3RlUmVnaXN0cnkgfSBmcm9tICcuL3JlbW90ZS1yZWdpc3RyeS5qcyc7XG5pbXBvcnQgdHlwZSB7IFRlcm1pbmFsTWFuYWdlciB9IGZyb20gJy4vdGVybWluYWwtbWFuYWdlci5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignYnVmZmVyLWFnZ3JlZ2F0b3InKTtcblxuaW50ZXJmYWNlIEJ1ZmZlckFnZ3JlZ2F0b3JDb25maWcge1xuICB0ZXJtaW5hbE1hbmFnZXI6IFRlcm1pbmFsTWFuYWdlcjtcbiAgcmVtb3RlUmVnaXN0cnk6IFJlbW90ZVJlZ2lzdHJ5IHwgbnVsbDtcbiAgaXNIUU1vZGU6IGJvb2xlYW47XG59XG5cbmludGVyZmFjZSBSZW1vdGVXZWJTb2NrZXRDb25uZWN0aW9uIHtcbiAgd3M6IFdlYlNvY2tldDtcbiAgcmVtb3RlSWQ6IHN0cmluZztcbiAgcmVtb3RlTmFtZTogc3RyaW5nO1xuICBzdWJzY3JpcHRpb25zOiBTZXQ8c3RyaW5nPjtcbn1cblxuLyoqXG4gKiBBZ2dyZWdhdGVzIGFuZCBkaXN0cmlidXRlcyB0ZXJtaW5hbCBidWZmZXIgdXBkYXRlcyBhY3Jvc3MgbG9jYWwgYW5kIHJlbW90ZSBzZXNzaW9ucy5cbiAqXG4gKiBUaGUgQnVmZmVyQWdncmVnYXRvciBhY3RzIGFzIGEgY2VudHJhbCBodWIgZm9yIFdlYlNvY2tldC1iYXNlZCB0ZXJtaW5hbCBidWZmZXIgc3RyZWFtaW5nLFxuICogbWFuYWdpbmcgY29ubmVjdGlvbnMgYmV0d2VlbiBjbGllbnRzIGFuZCB0ZXJtaW5hbCBzZXNzaW9ucy4gSW4gSFEgKGhlYWRxdWFydGVycykgbW9kZSxcbiAqIGl0IGFsc28gaGFuZGxlcyBjb25uZWN0aW9ucyB0byByZW1vdGUgVmliZVR1bm5lbCBzZXJ2ZXJzLCBlbmFibGluZyBjcm9zcy1zZXJ2ZXIgdGVybWluYWxcbiAqIHNlc3Npb24gYWNjZXNzLlxuICpcbiAqIEtleSBmZWF0dXJlczpcbiAqIC0gV2ViU29ja2V0LWJhc2VkIHJlYWwtdGltZSBidWZmZXIgc3RyZWFtaW5nXG4gKiAtIFN1cHBvcnQgZm9yIGJvdGggbG9jYWwgYW5kIHJlbW90ZSB0ZXJtaW5hbCBzZXNzaW9uc1xuICogLSBFZmZpY2llbnQgYmluYXJ5IHByb3RvY29sIGZvciBidWZmZXIgdXBkYXRlc1xuICogLSBBdXRvbWF0aWMgY29ubmVjdGlvbiBtYW5hZ2VtZW50IGFuZCByZWNvbm5lY3Rpb25cbiAqIC0gU2Vzc2lvbiBzdWJzY3JpcHRpb24vdW5zdWJzY3JpcHRpb24gaGFuZGxpbmdcbiAqIC0gUmVtb3RlIHNlcnZlciBjb25uZWN0aW9uIHBvb2xpbmcgaW4gSFEgbW9kZVxuICogLSBHcmFjZWZ1bCBjbGVhbnVwIG9uIGRpc2Nvbm5lY3Rpb25cbiAqXG4gKiBUaGUgYWdncmVnYXRvciB1c2VzIGEgYmluYXJ5IHByb3RvY29sIGZvciBidWZmZXIgdXBkYXRlczpcbiAqIC0gTWFnaWMgYnl0ZSAoMHhCRikgdG8gaWRlbnRpZnkgYmluYXJ5IG1lc3NhZ2VzXG4gKiAtIDQtYnl0ZSBzZXNzaW9uIElEIGxlbmd0aCAobGl0dGxlLWVuZGlhbilcbiAqIC0gVVRGLTggZW5jb2RlZCBzZXNzaW9uIElEXG4gKiAtIEJpbmFyeSB0ZXJtaW5hbCBidWZmZXIgZGF0YVxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDcmVhdGUgYWdncmVnYXRvciBmb3IgbG9jYWwtb25seSBtb2RlXG4gKiBjb25zdCBhZ2dyZWdhdG9yID0gbmV3IEJ1ZmZlckFnZ3JlZ2F0b3Ioe1xuICogICB0ZXJtaW5hbE1hbmFnZXIsXG4gKiAgIHJlbW90ZVJlZ2lzdHJ5OiBudWxsLFxuICogICBpc0hRTW9kZTogZmFsc2VcbiAqIH0pO1xuICpcbiAqIC8vIEhhbmRsZSBjbGllbnQgV2ViU29ja2V0IGNvbm5lY3Rpb25cbiAqIHdzcy5vbignY29ubmVjdGlvbicsICh3cykgPT4ge1xuICogICBhZ2dyZWdhdG9yLmhhbmRsZUNsaWVudENvbm5lY3Rpb24od3MpO1xuICogfSk7XG4gKlxuICogLy8gSW4gSFEgbW9kZSB3aXRoIHJlbW90ZSByZWdpc3RyeVxuICogY29uc3QgaHFBZ2dyZWdhdG9yID0gbmV3IEJ1ZmZlckFnZ3JlZ2F0b3Ioe1xuICogICB0ZXJtaW5hbE1hbmFnZXIsXG4gKiAgIHJlbW90ZVJlZ2lzdHJ5LFxuICogICBpc0hRTW9kZTogdHJ1ZVxuICogfSk7XG4gKlxuICogLy8gUmVnaXN0ZXIgcmVtb3RlIHNlcnZlclxuICogYXdhaXQgaHFBZ2dyZWdhdG9yLm9uUmVtb3RlUmVnaXN0ZXJlZChyZW1vdGVJZCk7XG4gKiBgYGBcbiAqXG4gKiBAc2VlIFRlcm1pbmFsTWFuYWdlciAtIE1hbmFnZXMgbG9jYWwgdGVybWluYWwgaW5zdGFuY2VzXG4gKiBAc2VlIFJlbW90ZVJlZ2lzdHJ5IC0gVHJhY2tzIHJlbW90ZSBWaWJlVHVubmVsIHNlcnZlcnMgaW4gSFEgbW9kZVxuICogQHNlZSB3ZWIvc3JjL3NlcnZlci9yb3V0ZXMvYnVmZmVyLnRzIC0gV2ViU29ja2V0IGVuZHBvaW50IHNldHVwXG4gKi9cbmV4cG9ydCBjbGFzcyBCdWZmZXJBZ2dyZWdhdG9yIHtcbiAgcHJpdmF0ZSBjb25maWc6IEJ1ZmZlckFnZ3JlZ2F0b3JDb25maWc7XG4gIHByaXZhdGUgcmVtb3RlQ29ubmVjdGlvbnM6IE1hcDxzdHJpbmcsIFJlbW90ZVdlYlNvY2tldENvbm5lY3Rpb24+ID0gbmV3IE1hcCgpO1xuICBwcml2YXRlIGNsaWVudFN1YnNjcmlwdGlvbnM6IE1hcDxXZWJTb2NrZXQsIE1hcDxzdHJpbmcsICgpID0+IHZvaWQ+PiA9IG5ldyBNYXAoKTtcblxuICBjb25zdHJ1Y3Rvcihjb25maWc6IEJ1ZmZlckFnZ3JlZ2F0b3JDb25maWcpIHtcbiAgICB0aGlzLmNvbmZpZyA9IGNvbmZpZztcbiAgICBsb2dnZXIubG9nKGBCdWZmZXJBZ2dyZWdhdG9yIGluaXRpYWxpemVkIChIUSBtb2RlOiAke2NvbmZpZy5pc0hRTW9kZX0pYCk7XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlIGEgbmV3IGNsaWVudCBXZWJTb2NrZXQgY29ubmVjdGlvblxuICAgKi9cbiAgYXN5bmMgaGFuZGxlQ2xpZW50Q29ubmVjdGlvbih3czogV2ViU29ja2V0KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgbG9nZ2VyLmxvZyhjaGFsay5ibHVlKCdOZXcgY2xpZW50IGNvbm5lY3RlZCcpKTtcbiAgICBjb25zdCBjbGllbnRJZCA9IGBjbGllbnQtJHtEYXRlLm5vdygpfWA7XG4gICAgbG9nZ2VyLmRlYnVnKGBBc3NpZ25lZCBjbGllbnQgSUQ6ICR7Y2xpZW50SWR9YCk7XG5cbiAgICAvLyBJbml0aWFsaXplIHN1YnNjcmlwdGlvbiBtYXAgZm9yIHRoaXMgY2xpZW50XG4gICAgdGhpcy5jbGllbnRTdWJzY3JpcHRpb25zLnNldCh3cywgbmV3IE1hcCgpKTtcblxuICAgIC8vIFNlbmQgd2VsY29tZSBtZXNzYWdlXG4gICAgd3Muc2VuZChKU09OLnN0cmluZ2lmeSh7IHR5cGU6ICdjb25uZWN0ZWQnLCB2ZXJzaW9uOiAnMS4wJyB9KSk7XG4gICAgbG9nZ2VyLmRlYnVnKCdTZW50IHdlbGNvbWUgbWVzc2FnZSB0byBjbGllbnQnKTtcblxuICAgIC8vIEhhbmRsZSBtZXNzYWdlcyBmcm9tIGNsaWVudFxuICAgIHdzLm9uKCdtZXNzYWdlJywgYXN5bmMgKG1lc3NhZ2U6IEJ1ZmZlcikgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgZGF0YSA9IEpTT04ucGFyc2UobWVzc2FnZS50b1N0cmluZygpKTtcbiAgICAgICAgYXdhaXQgdGhpcy5oYW5kbGVDbGllbnRNZXNzYWdlKHdzLCBkYXRhKTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignRXJyb3IgaGFuZGxpbmcgY2xpZW50IG1lc3NhZ2U6JywgZXJyb3IpO1xuICAgICAgICB3cy5zZW5kKFxuICAgICAgICAgIEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICAgIHR5cGU6ICdlcnJvcicsXG4gICAgICAgICAgICBtZXNzYWdlOiAnSW52YWxpZCBtZXNzYWdlIGZvcm1hdCcsXG4gICAgICAgICAgfSlcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIC8vIEhhbmRsZSBkaXNjb25uZWN0aW9uXG4gICAgd3Mub24oJ2Nsb3NlJywgKCkgPT4ge1xuICAgICAgdGhpcy5oYW5kbGVDbGllbnREaXNjb25uZWN0KHdzKTtcbiAgICB9KTtcblxuICAgIHdzLm9uKCdlcnJvcicsIChlcnJvcikgPT4ge1xuICAgICAgbG9nZ2VyLmVycm9yKCdDbGllbnQgV2ViU29ja2V0IGVycm9yOicsIGVycm9yKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGUgbWVzc2FnZXMgZnJvbSBhIGNsaWVudFxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBoYW5kbGVDbGllbnRNZXNzYWdlKFxuICAgIGNsaWVudFdzOiBXZWJTb2NrZXQsXG4gICAgZGF0YTogeyB0eXBlOiBzdHJpbmc7IHNlc3Npb25JZD86IHN0cmluZyB9XG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHN1YnNjcmlwdGlvbnMgPSB0aGlzLmNsaWVudFN1YnNjcmlwdGlvbnMuZ2V0KGNsaWVudFdzKTtcbiAgICBpZiAoIXN1YnNjcmlwdGlvbnMpIHJldHVybjtcblxuICAgIGlmIChkYXRhLnR5cGUgPT09ICdzdWJzY3JpYmUnICYmIGRhdGEuc2Vzc2lvbklkKSB7XG4gICAgICBjb25zdCBzZXNzaW9uSWQgPSBkYXRhLnNlc3Npb25JZDtcblxuICAgICAgLy8gVW5zdWJzY3JpYmUgaWYgYWxyZWFkeSBzdWJzY3JpYmVkXG4gICAgICBpZiAoc3Vic2NyaXB0aW9ucy5oYXMoc2Vzc2lvbklkKSkge1xuICAgICAgICBjb25zdCBleGlzdGluZ1Vuc3Vic2NyaWJlID0gc3Vic2NyaXB0aW9ucy5nZXQoc2Vzc2lvbklkKTtcbiAgICAgICAgaWYgKGV4aXN0aW5nVW5zdWJzY3JpYmUpIHtcbiAgICAgICAgICBleGlzdGluZ1Vuc3Vic2NyaWJlKCk7XG4gICAgICAgIH1cbiAgICAgICAgc3Vic2NyaXB0aW9ucy5kZWxldGUoc2Vzc2lvbklkKTtcbiAgICAgIH1cblxuICAgICAgLy8gQ2hlY2sgaWYgdGhpcyBpcyBhIGxvY2FsIG9yIHJlbW90ZSBzZXNzaW9uXG4gICAgICBjb25zdCBpc1JlbW90ZVNlc3Npb24gPVxuICAgICAgICB0aGlzLmNvbmZpZy5pc0hRTW9kZSAmJlxuICAgICAgICB0aGlzLmNvbmZpZy5yZW1vdGVSZWdpc3RyeSAmJlxuICAgICAgICB0aGlzLmNvbmZpZy5yZW1vdGVSZWdpc3RyeS5nZXRSZW1vdGVCeVNlc3Npb25JZChzZXNzaW9uSWQpO1xuXG4gICAgICBpZiAoaXNSZW1vdGVTZXNzaW9uKSB7XG4gICAgICAgIC8vIFN1YnNjcmliZSB0byByZW1vdGUgc2Vzc2lvblxuICAgICAgICBsb2dnZXIuZGVidWcoYFN1YnNjcmliaW5nIHRvIHJlbW90ZSBzZXNzaW9uICR7c2Vzc2lvbklkfSBvbiByZW1vdGUgJHtpc1JlbW90ZVNlc3Npb24uaWR9YCk7XG4gICAgICAgIGF3YWl0IHRoaXMuc3Vic2NyaWJlVG9SZW1vdGVTZXNzaW9uKGNsaWVudFdzLCBzZXNzaW9uSWQsIGlzUmVtb3RlU2Vzc2lvbi5pZCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBTdWJzY3JpYmUgdG8gbG9jYWwgc2Vzc2lvblxuICAgICAgICBsb2dnZXIuZGVidWcoYFN1YnNjcmliaW5nIHRvIGxvY2FsIHNlc3Npb24gJHtzZXNzaW9uSWR9YCk7XG4gICAgICAgIGF3YWl0IHRoaXMuc3Vic2NyaWJlVG9Mb2NhbFNlc3Npb24oY2xpZW50V3MsIHNlc3Npb25JZCk7XG4gICAgICB9XG5cbiAgICAgIGNsaWVudFdzLnNlbmQoSlNPTi5zdHJpbmdpZnkoeyB0eXBlOiAnc3Vic2NyaWJlZCcsIHNlc3Npb25JZCB9KSk7XG4gICAgICBsb2dnZXIubG9nKGNoYWxrLmdyZWVuKGBDbGllbnQgc3Vic2NyaWJlZCB0byBzZXNzaW9uICR7c2Vzc2lvbklkfWApKTtcbiAgICB9IGVsc2UgaWYgKGRhdGEudHlwZSA9PT0gJ3Vuc3Vic2NyaWJlJyAmJiBkYXRhLnNlc3Npb25JZCkge1xuICAgICAgY29uc3Qgc2Vzc2lvbklkID0gZGF0YS5zZXNzaW9uSWQ7XG4gICAgICBjb25zdCB1bnN1YnNjcmliZSA9IHN1YnNjcmlwdGlvbnMuZ2V0KHNlc3Npb25JZCk7XG4gICAgICBpZiAodW5zdWJzY3JpYmUpIHtcbiAgICAgICAgdW5zdWJzY3JpYmUoKTtcbiAgICAgICAgc3Vic2NyaXB0aW9ucy5kZWxldGUoc2Vzc2lvbklkKTtcbiAgICAgICAgbG9nZ2VyLmxvZyhjaGFsay55ZWxsb3coYENsaWVudCB1bnN1YnNjcmliZWQgZnJvbSBzZXNzaW9uICR7c2Vzc2lvbklkfWApKTtcbiAgICAgIH1cblxuICAgICAgLy8gQWxzbyB1bnN1YnNjcmliZSBmcm9tIHJlbW90ZSBpZiBhcHBsaWNhYmxlXG4gICAgICBpZiAodGhpcy5jb25maWcuaXNIUU1vZGUgJiYgdGhpcy5jb25maWcucmVtb3RlUmVnaXN0cnkpIHtcbiAgICAgICAgY29uc3QgcmVtb3RlID0gdGhpcy5jb25maWcucmVtb3RlUmVnaXN0cnkuZ2V0UmVtb3RlQnlTZXNzaW9uSWQoc2Vzc2lvbklkKTtcbiAgICAgICAgaWYgKHJlbW90ZSkge1xuICAgICAgICAgIGNvbnN0IHJlbW90ZUNvbm4gPSB0aGlzLnJlbW90ZUNvbm5lY3Rpb25zLmdldChyZW1vdGUuaWQpO1xuICAgICAgICAgIGlmIChyZW1vdGVDb25uKSB7XG4gICAgICAgICAgICByZW1vdGVDb25uLnN1YnNjcmlwdGlvbnMuZGVsZXRlKHNlc3Npb25JZCk7XG4gICAgICAgICAgICBpZiAocmVtb3RlQ29ubi53cy5yZWFkeVN0YXRlID09PSBXZWJTb2NrZXQuT1BFTikge1xuICAgICAgICAgICAgICByZW1vdGVDb25uLndzLnNlbmQoSlNPTi5zdHJpbmdpZnkoeyB0eXBlOiAndW5zdWJzY3JpYmUnLCBzZXNzaW9uSWQgfSkpO1xuICAgICAgICAgICAgICBsb2dnZXIuZGVidWcoXG4gICAgICAgICAgICAgICAgYFNlbnQgdW5zdWJzY3JpYmUgcmVxdWVzdCB0byByZW1vdGUgJHtyZW1vdGVDb25uLnJlbW90ZU5hbWV9IGZvciBzZXNzaW9uICR7c2Vzc2lvbklkfWBcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIGxvZ2dlci5kZWJ1ZyhcbiAgICAgICAgICAgICAgICBgQ2Fubm90IHVuc3Vic2NyaWJlIGZyb20gcmVtb3RlICR7cmVtb3RlQ29ubi5yZW1vdGVOYW1lfSAtIFdlYlNvY2tldCBub3Qgb3BlbmBcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKGRhdGEudHlwZSA9PT0gJ3BpbmcnKSB7XG4gICAgICBjbGllbnRXcy5zZW5kKEpTT04uc3RyaW5naWZ5KHsgdHlwZTogJ3BvbmcnLCB0aW1lc3RhbXA6IERhdGUubm93KCkgfSkpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTdWJzY3JpYmUgYSBjbGllbnQgdG8gYSBsb2NhbCBzZXNzaW9uXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHN1YnNjcmliZVRvTG9jYWxTZXNzaW9uKGNsaWVudFdzOiBXZWJTb2NrZXQsIHNlc3Npb25JZDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3Qgc3Vic2NyaXB0aW9ucyA9IHRoaXMuY2xpZW50U3Vic2NyaXB0aW9ucy5nZXQoY2xpZW50V3MpO1xuICAgIGlmICghc3Vic2NyaXB0aW9ucykgcmV0dXJuO1xuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHVuc3Vic2NyaWJlID0gYXdhaXQgdGhpcy5jb25maWcudGVybWluYWxNYW5hZ2VyLnN1YnNjcmliZVRvQnVmZmVyQ2hhbmdlcyhcbiAgICAgICAgc2Vzc2lvbklkLFxuICAgICAgICAoc2Vzc2lvbklkOiBzdHJpbmcsIHNuYXBzaG90OiBQYXJhbWV0ZXJzPFRlcm1pbmFsTWFuYWdlclsnZW5jb2RlU25hcHNob3QnXT5bMF0pID0+IHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgYnVmZmVyID0gdGhpcy5jb25maWcudGVybWluYWxNYW5hZ2VyLmVuY29kZVNuYXBzaG90KHNuYXBzaG90KTtcbiAgICAgICAgICAgIGNvbnN0IHNlc3Npb25JZEJ1ZmZlciA9IEJ1ZmZlci5mcm9tKHNlc3Npb25JZCwgJ3V0ZjgnKTtcbiAgICAgICAgICAgIGNvbnN0IHRvdGFsTGVuZ3RoID0gMSArIDQgKyBzZXNzaW9uSWRCdWZmZXIubGVuZ3RoICsgYnVmZmVyLmxlbmd0aDtcbiAgICAgICAgICAgIGNvbnN0IGZ1bGxCdWZmZXIgPSBCdWZmZXIuYWxsb2NVbnNhZmUodG90YWxMZW5ndGgpO1xuXG4gICAgICAgICAgICBsZXQgb2Zmc2V0ID0gMDtcbiAgICAgICAgICAgIGZ1bGxCdWZmZXIud3JpdGVVSW50OCgweGJmLCBvZmZzZXQpOyAvLyBNYWdpYyBieXRlIGZvciBiaW5hcnkgbWVzc2FnZVxuICAgICAgICAgICAgb2Zmc2V0ICs9IDE7XG5cbiAgICAgICAgICAgIGZ1bGxCdWZmZXIud3JpdGVVSW50MzJMRShzZXNzaW9uSWRCdWZmZXIubGVuZ3RoLCBvZmZzZXQpO1xuICAgICAgICAgICAgb2Zmc2V0ICs9IDQ7XG5cbiAgICAgICAgICAgIHNlc3Npb25JZEJ1ZmZlci5jb3B5KGZ1bGxCdWZmZXIsIG9mZnNldCk7XG4gICAgICAgICAgICBvZmZzZXQgKz0gc2Vzc2lvbklkQnVmZmVyLmxlbmd0aDtcblxuICAgICAgICAgICAgYnVmZmVyLmNvcHkoZnVsbEJ1ZmZlciwgb2Zmc2V0KTtcblxuICAgICAgICAgICAgaWYgKGNsaWVudFdzLnJlYWR5U3RhdGUgPT09IFdlYlNvY2tldC5PUEVOKSB7XG4gICAgICAgICAgICAgIGNsaWVudFdzLnNlbmQoZnVsbEJ1ZmZlcik7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICBsb2dnZXIuZGVidWcoYFNraXBwaW5nIGJ1ZmZlciB1cGRhdGUgLSBjbGllbnQgV2ViU29ja2V0IG5vdCBvcGVuYCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIGxvZ2dlci5lcnJvcignRXJyb3IgZW5jb2RpbmcgYnVmZmVyIHVwZGF0ZTonLCBlcnJvcik7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICApO1xuXG4gICAgICBzdWJzY3JpcHRpb25zLnNldChzZXNzaW9uSWQsIHVuc3Vic2NyaWJlKTtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgQ3JlYXRlZCBzdWJzY3JpcHRpb24gZm9yIGxvY2FsIHNlc3Npb24gJHtzZXNzaW9uSWR9YCk7XG5cbiAgICAgIC8vIFNlbmQgaW5pdGlhbCBidWZmZXJcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgU2VuZGluZyBpbml0aWFsIGJ1ZmZlciBmb3Igc2Vzc2lvbiAke3Nlc3Npb25JZH1gKTtcbiAgICAgIGNvbnN0IGluaXRpYWxTbmFwc2hvdCA9IGF3YWl0IHRoaXMuY29uZmlnLnRlcm1pbmFsTWFuYWdlci5nZXRCdWZmZXJTbmFwc2hvdChzZXNzaW9uSWQpO1xuICAgICAgY29uc3QgYnVmZmVyID0gdGhpcy5jb25maWcudGVybWluYWxNYW5hZ2VyLmVuY29kZVNuYXBzaG90KGluaXRpYWxTbmFwc2hvdCk7XG5cbiAgICAgIGNvbnN0IHNlc3Npb25JZEJ1ZmZlciA9IEJ1ZmZlci5mcm9tKHNlc3Npb25JZCwgJ3V0ZjgnKTtcbiAgICAgIGNvbnN0IHRvdGFsTGVuZ3RoID0gMSArIDQgKyBzZXNzaW9uSWRCdWZmZXIubGVuZ3RoICsgYnVmZmVyLmxlbmd0aDtcbiAgICAgIGNvbnN0IGZ1bGxCdWZmZXIgPSBCdWZmZXIuYWxsb2NVbnNhZmUodG90YWxMZW5ndGgpO1xuXG4gICAgICBsZXQgb2Zmc2V0ID0gMDtcbiAgICAgIGZ1bGxCdWZmZXIud3JpdGVVSW50OCgweGJmLCBvZmZzZXQpO1xuICAgICAgb2Zmc2V0ICs9IDE7XG5cbiAgICAgIGZ1bGxCdWZmZXIud3JpdGVVSW50MzJMRShzZXNzaW9uSWRCdWZmZXIubGVuZ3RoLCBvZmZzZXQpO1xuICAgICAgb2Zmc2V0ICs9IDQ7XG5cbiAgICAgIHNlc3Npb25JZEJ1ZmZlci5jb3B5KGZ1bGxCdWZmZXIsIG9mZnNldCk7XG4gICAgICBvZmZzZXQgKz0gc2Vzc2lvbklkQnVmZmVyLmxlbmd0aDtcblxuICAgICAgYnVmZmVyLmNvcHkoZnVsbEJ1ZmZlciwgb2Zmc2V0KTtcblxuICAgICAgaWYgKGNsaWVudFdzLnJlYWR5U3RhdGUgPT09IFdlYlNvY2tldC5PUEVOKSB7XG4gICAgICAgIGNsaWVudFdzLnNlbmQoZnVsbEJ1ZmZlcik7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgU2VudCBpbml0aWFsIGJ1ZmZlciAoJHtmdWxsQnVmZmVyLmxlbmd0aH0gYnl0ZXMpIGZvciBzZXNzaW9uICR7c2Vzc2lvbklkfWApO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oYENhbm5vdCBzZW5kIGluaXRpYWwgYnVmZmVyIC0gY2xpZW50IFdlYlNvY2tldCBub3Qgb3BlbmApO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoYEVycm9yIHN1YnNjcmliaW5nIHRvIGxvY2FsIHNlc3Npb24gJHtzZXNzaW9uSWR9OmAsIGVycm9yKTtcbiAgICAgIGNsaWVudFdzLnNlbmQoSlNPTi5zdHJpbmdpZnkoeyB0eXBlOiAnZXJyb3InLCBtZXNzYWdlOiAnRmFpbGVkIHRvIHN1YnNjcmliZSB0byBzZXNzaW9uJyB9KSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFN1YnNjcmliZSBhIGNsaWVudCB0byBhIHJlbW90ZSBzZXNzaW9uXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHN1YnNjcmliZVRvUmVtb3RlU2Vzc2lvbihcbiAgICBjbGllbnRXczogV2ViU29ja2V0LFxuICAgIHNlc3Npb25JZDogc3RyaW5nLFxuICAgIHJlbW90ZUlkOiBzdHJpbmdcbiAgKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgLy8gRW5zdXJlIHdlIGhhdmUgYSBjb25uZWN0aW9uIHRvIHRoaXMgcmVtb3RlXG4gICAgbGV0IHJlbW90ZUNvbm4gPSB0aGlzLnJlbW90ZUNvbm5lY3Rpb25zLmdldChyZW1vdGVJZCk7XG4gICAgaWYgKCFyZW1vdGVDb25uIHx8IHJlbW90ZUNvbm4ud3MucmVhZHlTdGF0ZSAhPT0gV2ViU29ja2V0Lk9QRU4p