vibetunnel
Version:
Terminal sharing server with web interface - supports macOS, Linux, and headless environments
526 lines โข 72.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.controlUnixHandler = exports.ControlUnixHandler = void 0;
const child_process = __importStar(require("node:child_process"));
const fs = __importStar(require("node:fs"));
const net = __importStar(require("node:net"));
const path = __importStar(require("node:path"));
const uuid_1 = require("uuid");
const logger_js_1 = require("../utils/logger.js");
const control_protocol_js_1 = require("./control-protocol.js");
const logger = (0, logger_js_1.createLogger)('control-unix');
class TerminalHandler {
async handleMessage(message) {
logger.log(`Terminal handler: ${message.action}`);
if (message.action === 'spawn') {
const request = message.payload;
try {
// Build the command for launching terminal with VibeTunnel
const args = ['launch'];
if (request.workingDirectory) {
args.push('--working-directory', request.workingDirectory);
}
if (request.command) {
args.push('--command', request.command);
}
args.push('--session-id', request.sessionId);
if (request.terminalPreference) {
args.push('--terminal', request.terminalPreference);
}
// Execute vibetunnel command
logger.log(`Spawning terminal with args: ${args.join(' ')}`);
// Use spawn to avoid shell injection
const vt = child_process.spawn('vibetunnel', args, {
detached: true,
stdio: 'ignore',
});
vt.unref();
const response = {
success: true,
};
return (0, control_protocol_js_1.createControlResponse)(message, response);
}
catch (error) {
logger.error('Failed to spawn terminal:', error);
return (0, control_protocol_js_1.createControlResponse)(message, null, error instanceof Error ? error.message : 'Failed to spawn terminal');
}
}
return (0, control_protocol_js_1.createControlResponse)(message, null, `Unknown terminal action: ${message.action}`);
}
}
class SystemHandler {
async handleMessage(message) {
logger.log(`System handler: ${message.action}, type: ${message.type}, id: ${message.id}`);
switch (message.action) {
case 'ping':
// Already handled in handleMacMessage
return null;
case 'ready':
// Event, no response needed
return null;
default:
logger.warn(`Unknown system action: ${message.action}`);
return (0, control_protocol_js_1.createControlResponse)(message, null, `Unknown action: ${message.action}`);
}
}
}
/**
* Handles Unix domain socket communication between the VibeTunnel web server and macOS app.
*
* This class manages a Unix socket server that provides bidirectional communication
* between the web server and the native macOS application. It implements a message-based
* protocol with length-prefixed framing for reliable message delivery and supports
* multiple message categories including terminal control and system events.
*
* Key features:
* - Unix domain socket server with automatic cleanup on restart
* - Length-prefixed binary protocol for message framing
* - Message routing based on categories (terminal, system)
* - Request/response pattern with timeout support
* - WebSocket bridge for browser clients
* - Automatic socket permission management (0600)
*
* @example
* ```typescript
* // Create and start the handler
* const handler = new ControlUnixHandler();
* await handler.start();
*
* // Check if Mac app is connected
* if (handler.isMacAppConnected()) {
* // Send a control message
* const response = await handler.sendControlMessage({
* id: 'msg-123',
* type: 'request',
* category: 'terminal',
* action: 'spawn',
* payload: {
* sessionId: 'session-456',
* workingDirectory: '/Users/alice',
* command: 'vim'
* }
* });
* }
*
* // Handle browser WebSocket connections
* ws.on('connection', (socket) => {
* handler.handleBrowserConnection(socket, userId);
* });
* ```
*/
class ControlUnixHandler {
constructor() {
this.pendingRequests = new Map();
this.macSocket = null;
this.unixServer = null;
this.handlers = new Map();
this.messageBuffer = Buffer.alloc(0);
// Use control directory from environment or default
const home = process.env.HOME || '/tmp';
const controlDir = process.env.VIBETUNNEL_CONTROL_DIR || path.join(home, '.vibetunnel');
const socketDir = controlDir;
// Ensure directory exists
try {
fs.mkdirSync(socketDir, { recursive: true });
}
catch (_e) {
// Ignore if already exists
}
this.socketPath = path.join(socketDir, 'control.sock');
// Initialize handlers
this.handlers.set('terminal', new TerminalHandler());
this.handlers.set('system', new SystemHandler());
}
async start() {
logger.log('๐ Starting control Unix socket handler');
logger.log(`๐ Socket path: ${this.socketPath}`);
// Clean up any existing socket file to prevent EADDRINUSE errors on restart.
try {
if (fs.existsSync(this.socketPath)) {
fs.unlinkSync(this.socketPath);
logger.log('๐งน Removed existing stale socket file.');
}
else {
logger.log('โ
No existing socket file found');
}
}
catch (error) {
logger.warn('โ ๏ธ Failed to remove stale socket file:', error);
}
// Create UNIX socket server
this.unixServer = net.createServer((socket) => {
this.handleMacConnection(socket);
});
// Start listening
await new Promise((resolve, reject) => {
this.unixServer?.listen(this.socketPath, () => {
logger.log(`Control UNIX socket server listening at ${this.socketPath}`);
// Set restrictive permissions - only owner can read/write
fs.chmod(this.socketPath, 0o600, (err) => {
if (err) {
logger.error('Failed to set socket permissions:', err);
}
else {
logger.log('Socket permissions set to 0600 (owner read/write only)');
}
});
resolve();
});
this.unixServer?.on('error', (error) => {
logger.error('UNIX socket server error:', error);
reject(error);
});
});
}
stop() {
if (this.macSocket) {
this.macSocket.destroy();
this.macSocket = null;
}
if (this.unixServer) {
this.unixServer.close();
this.unixServer = null;
}
// Clean up socket file
try {
fs.unlinkSync(this.socketPath);
}
catch (_error) {
// Ignore
}
}
isMacAppConnected() {
return this.macSocket !== null && !this.macSocket.destroyed;
}
handleMacConnection(socket) {
logger.log('๐ New Mac connection via UNIX socket');
logger.log(`๐ Socket info: local=${socket.localAddress}, remote=${socket.remoteAddress}`);
// Close any existing Mac connection
if (this.macSocket) {
logger.log('โ ๏ธ Closing existing Mac connection');
this.macSocket.destroy();
}
this.macSocket = socket;
logger.log('โ
Mac socket stored');
// Set socket options for better handling of large messages
socket.setNoDelay(true); // Disable Nagle's algorithm for lower latency
logger.log('โ
Socket options set: NoDelay=true');
// Increase the buffer size for receiving large messages
const bufferSize = 1024 * 1024; // 1MB
try {
const socketWithState = socket;
if (socketWithState._readableState) {
socketWithState._readableState.highWaterMark = bufferSize;
logger.log(`Set socket receive buffer to ${bufferSize} bytes`);
}
}
catch (error) {
logger.warn('Failed to set socket buffer size:', error);
}
socket.on('data', (data) => {
// Append new data to our buffer
this.messageBuffer = Buffer.concat([this.messageBuffer, data]);
logger.log(`๐ฅ Received from Mac: ${data.length} bytes, buffer size: ${this.messageBuffer.length}`);
// Log first few bytes for debugging
if (data.length > 0) {
const preview = data.subarray(0, Math.min(data.length, 50));
logger.debug(`๐ Data preview (first ${preview.length} bytes):`, preview.toString('hex'));
}
// Process as many messages as we can from the buffer
while (true) {
// A message needs at least 4 bytes for the length header
if (this.messageBuffer.length < 4) {
break;
}
// Read the length of the message
const messageLength = this.messageBuffer.readUInt32BE(0);
// Validate message length
if (messageLength <= 0) {
logger.error(`Invalid message length: ${messageLength}`);
// Clear the buffer to recover from this error
this.messageBuffer = Buffer.alloc(0);
break;
}
// Sanity check: messages shouldn't be larger than 10MB
const maxMessageSize = 10 * 1024 * 1024; // 10MB
if (messageLength > maxMessageSize) {
logger.error(`Message too large: ${messageLength} bytes (max: ${maxMessageSize})`);
// Clear the buffer to recover from this error
this.messageBuffer = Buffer.alloc(0);
break;
}
// Check if we have the full message in the buffer
if (this.messageBuffer.length < 4 + messageLength) {
// Not enough data yet, wait for more
logger.debug(`Waiting for more data: have ${this.messageBuffer.length}, need ${4 + messageLength}`);
break;
}
// Extract the message data
const messageData = this.messageBuffer.subarray(4, 4 + messageLength);
// Remove the message (header + body) from the buffer
this.messageBuffer = this.messageBuffer.subarray(4 + messageLength);
try {
const messageStr = messageData.toString('utf-8');
logger.debug(`๐จ Parsing message (${messageLength} bytes): ${messageStr.substring(0, 100)}...`);
const message = JSON.parse(messageStr);
logger.log(`โ
Parsed Mac message: category=${message.category}, action=${message.action}, id=${message.id}`);
this.handleMacMessage(message);
}
catch (error) {
logger.error('โ Failed to parse Mac message:', error);
logger.error('Message length:', messageLength);
logger.error('Raw message buffer:', messageData.toString('utf-8'));
}
}
});
socket.on('error', (error) => {
logger.error('โ Mac socket error:', error);
const errorObj = error;
logger.error('Error details:', {
code: errorObj.code,
syscall: errorObj.syscall,
errno: errorObj.errno,
message: errorObj.message,
});
// Check if it's a write-related error
if (errorObj.code === 'EPIPE' || errorObj.code === 'ECONNRESET') {
logger.error('๐ด Connection broken - Mac app likely closed the connection');
}
});
socket.on('close', (hadError) => {
logger.log(`๐ Mac disconnected (hadError: ${hadError})`);
logger.log(`๐ Socket state: destroyed=${socket.destroyed}, readable=${socket.readable}, writable=${socket.writable}`);
if (socket === this.macSocket) {
this.macSocket = null;
logger.log('๐งน Cleared Mac socket reference');
}
});
// Handle drain event for backpressure
socket.on('drain', () => {
logger.log('Mac socket drained - ready for more data');
});
// Add event for socket end (clean close)
socket.on('end', () => {
logger.log('๐ด Mac socket received FIN packet (clean close)');
});
// Send ready event to Mac
logger.log('๐ค Sending initial system:ready event to Mac');
this.sendToMac((0, control_protocol_js_1.createControlEvent)('system', 'ready'));
logger.log('โ
system:ready event sent');
}
handleBrowserConnection(ws, userId) {
logger.log('๐ New browser WebSocket connection for control messages');
logger.log(`๐ค User ID: ${userId || 'unknown'}`);
logger.log(`๐ Mac socket status on browser connect: ${this.macSocket ? 'CONNECTED' : 'NOT CONNECTED'}`);
ws.on('message', async (data) => {
try {
const rawMessage = data.toString();
logger.log(`๐จ Browser message received (${rawMessage.length} chars): ${rawMessage.substring(0, 200)}...`);
const message = JSON.parse(rawMessage);
logger.log(`๐ฅ Parsed browser message - type: ${message.type}, category: ${message.category}, action: ${message.action}`);
// Handle browser -> Mac messages
logger.warn(`โ ๏ธ Browser sent message for category: ${message.category}`);
}
catch (error) {
logger.error('โ Failed to parse browser message:', error);
ws.send(JSON.stringify((0, control_protocol_js_1.createControlEvent)('system', 'error', {
error: error instanceof Error ? error.message : String(error),
})));
}
});
ws.on('close', () => {
logger.log('Browser disconnected');
});
ws.on('error', (error) => {
logger.error('Browser WebSocket error:', error);
});
}
async handleMacMessage(message) {
logger.log(`Mac message - category: ${message.category}, action: ${message.action}, type: ${message.type}, id: ${message.id}`);
// Handle ping keep-alive from Mac client
if (message.category === 'system' && message.action === 'ping') {
const pong = (0, control_protocol_js_1.createControlResponse)(message, { status: 'ok' });
this.sendToMac(pong);
return;
}
// Check if this is a response to a pending request
if (message.type === 'response' && this.pendingRequests.has(message.id)) {
const resolver = this.pendingRequests.get(message.id);
if (resolver) {
logger.debug(`Resolving pending request for id: ${message.id}`);
this.pendingRequests.delete(message.id);
resolver(message);
}
return;
}
// Skip processing for response messages that aren't pending requests
// This prevents response loops where error responses get processed again
if (message.type === 'response') {
logger.debug(`Ignoring response message that has no pending request: ${message.id}, action: ${message.action}`);
return;
}
const handler = this.handlers.get(message.category);
if (!handler) {
logger.warn(`No handler for category: ${message.category}`);
if (message.type === 'request') {
const response = (0, control_protocol_js_1.createControlResponse)(message, null, `Unknown category: ${message.category}`);
this.sendToMac(response);
}
return;
}
try {
const response = await handler.handleMessage(message);
if (response) {
this.sendToMac(response);
}
}
catch (error) {
logger.error(`Handler error for ${message.category}:${message.action}:`, error);
if (message.type === 'request') {
const response = (0, control_protocol_js_1.createControlResponse)(message, null, error instanceof Error ? error.message : 'Handler error');
this.sendToMac(response);
}
}
}
async sendControlMessage(message) {
// If Mac is not connected, return null immediately
if (!this.isMacAppConnected()) {
return null;
}
return new Promise((resolve) => {
// Store the pending request
this.pendingRequests.set(message.id, resolve);
// Send the message
this.sendToMac(message);
// Set a timeout
setTimeout(() => {
if (this.pendingRequests.has(message.id)) {
this.pendingRequests.delete(message.id);
resolve(null);
}
}, 10000); // 10 second timeout
});
}
/**
* Send a notification to the Mac app via the Unix socket
*/
sendNotification(title, body, options) {
if (!this.macSocket) {
logger.warn('[ControlUnixHandler] Cannot send notification - Mac app not connected');
return;
}
const message = {
id: (0, uuid_1.v4)(),
type: 'event',
category: 'notification',
action: 'show',
payload: {
title,
body,
...options,
},
};
this.sendToMac(message);
logger.info('[ControlUnixHandler] Sent notification:', { title, body, options });
}
sendToMac(message) {
if (!this.macSocket) {
logger.warn('โ ๏ธ Cannot send to Mac - no socket connection');
return;
}
if (this.macSocket.destroyed) {
logger.warn('โ ๏ธ Cannot send to Mac - socket is destroyed');
this.macSocket = null;
return;
}
try {
// Convert message to JSON
const jsonStr = JSON.stringify(message);
const jsonData = Buffer.from(jsonStr, 'utf-8');
// Create a buffer with 4-byte length header + JSON data
const lengthBuffer = Buffer.allocUnsafe(4);
lengthBuffer.writeUInt32BE(jsonData.length, 0);
// Combine length header and data
const fullData = Buffer.concat([lengthBuffer, jsonData]);
// Log message details
logger.log(`๐ค Sending to Mac: ${message.category}:${message.action}, header: 4 bytes, payload: ${jsonData.length} bytes, total: ${fullData.length} bytes`);
logger.log(`๐ Message ID being sent: ${message.id}`);
logger.debug(`๐ Message content: ${jsonStr.substring(0, 200)}...`);
// Log the actual bytes for the first few messages
if (message.category === 'system' || message.action === 'get-initial-data') {
logger.debug(`๐ Length header bytes: ${lengthBuffer.toString('hex')}`);
logger.debug(`๐ First 50 bytes of full data: ${fullData.subarray(0, Math.min(50, fullData.length)).toString('hex')}`);
}
if (jsonData.length > 65536) {
logger.warn(`โ ๏ธ Large message to Mac: ${jsonData.length} bytes`);
}
// Write with error handling
const result = this.macSocket.write(fullData, (error) => {
if (error) {
logger.error('โ Error writing to Mac socket:', error);
logger.error('Error details:', {
// biome-ignore lint/suspicious/noExplicitAny: error object has non-standard properties
code: error.code,
// biome-ignore lint/suspicious/noExplicitAny: error object has non-standard properties
syscall: error.syscall,
message: error.message,
});
// Close the connection on write error
this.macSocket?.destroy();
this.macSocket = null;
}
else {
logger.debug('โ
Write to Mac socket completed successfully');
}
});
// Check if write was buffered (backpressure)
if (!result) {
logger.warn('โ ๏ธ Socket write buffered - backpressure detected');
}
else {
logger.debug('โ
Write immediate - no backpressure');
}
}
catch (error) {
logger.error('โ Exception while sending to Mac:', error);
this.macSocket?.destroy();
this.macSocket = null;
}
}
}
exports.ControlUnixHandler = ControlUnixHandler;
exports.controlUnixHandler = new ControlUnixHandler();
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udHJvbC11bml4LWhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmVyL3dlYnNvY2tldC9jb250cm9sLXVuaXgtaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxrRUFBb0Q7QUFDcEQsNENBQThCO0FBQzlCLDhDQUFnQztBQUNoQyxnREFBa0M7QUFDbEMsK0JBQW9DO0FBRXBDLGtEQUFrRDtBQU9sRCwrREFBa0Y7QUFFbEYsTUFBTSxNQUFNLEdBQUcsSUFBQSx3QkFBWSxFQUFDLGNBQWMsQ0FBQyxDQUFDO0FBTTVDLE1BQU0sZUFBZTtJQUNuQixLQUFLLENBQUMsYUFBYSxDQUFDLE9BQXVCO1FBQ3pDLE1BQU0sQ0FBQyxHQUFHLENBQUMscUJBQXFCLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBRWxELElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxPQUFPLEVBQUUsQ0FBQztZQUMvQixNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBK0IsQ0FBQztZQUV4RCxJQUFJLENBQUM7Z0JBQ0gsMkRBQTJEO2dCQUMzRCxNQUFNLElBQUksR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO2dCQUV4QixJQUFJLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO29CQUM3QixJQUFJLENBQUMsSUFBSSxDQUFDLHFCQUFxQixFQUFFLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO2dCQUM3RCxDQUFDO2dCQUVELElBQUksT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO29CQUNwQixJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7Z0JBQzFDLENBQUM7Z0JBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO2dCQUU3QyxJQUFJ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QzFCLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDO2dCQUN4QixDQUFDO3FCQUFNLENBQUM7b0JBQ04sTUFBTSxDQUFDLEtBQUssQ0FBQyw4Q0FBOEMsQ0FBQyxDQUFDO2dCQUMvRCxDQUFDO1lBQ0gsQ0FBQyxDQUFDLENBQUM7WUFFSCw2Q0FBNkM7WUFDN0MsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUNaLE1BQU0sQ0FBQyxJQUFJLENBQUMsa0RBQWtELENBQUMsQ0FBQztZQUNsRSxDQUFDO2lCQUFNLENBQUM7Z0JBQ04sTUFBTSxDQUFDLEtBQUssQ0FBQyxxQ0FBcUMsQ0FBQyxDQUFDO1lBQ3RELENBQUM7UUFDSCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE1BQU0sQ0FBQyxLQUFLLENBQUMsbUNBQW1DLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDekQsSUFBSSxDQUFDLFNBQVMsRUFBRSxPQUFPLEVBQUUsQ0FBQztZQUMxQixJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQztRQUN4QixDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBbGVELGdEQWtlQztBQUVZLFFBQUEsa0JBQWtCLEdBQUcsSUFBSSxrQkFBa0IsRUFBRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgY2hpbGRfcHJvY2VzcyBmcm9tICdub2RlOmNoaWxkX3Byb2Nlc3MnO1xuaW1wb3J0ICogYXMgZnMgZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgKiBhcyBuZXQgZnJvbSAnbm9kZTpuZXQnO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgdjQgYXMgdXVpZHY0IH0gZnJvbSAndXVpZCc7XG5pbXBvcnQgdHlwZSB7IFdlYlNvY2tldCB9IGZyb20gJ3dzJztcbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgdHlwZSB7XG4gIENvbnRyb2xDYXRlZ29yeSxcbiAgQ29udHJvbE1lc3NhZ2UsXG4gIFRlcm1pbmFsU3Bhd25SZXF1ZXN0LFxuICBUZXJtaW5hbFNwYXduUmVzcG9uc2UsXG59IGZyb20gJy4vY29udHJvbC1wcm90b2NvbC5qcyc7XG5pbXBvcnQgeyBjcmVhdGVDb250cm9sRXZlbnQsIGNyZWF0ZUNvbnRyb2xSZXNwb25zZSB9IGZyb20gJy4vY29udHJvbC1wcm90b2NvbC5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignY29udHJvbC11bml4Jyk7XG5cbmludGVyZmFjZSBNZXNzYWdlSGFuZGxlciB7XG4gIGhhbmRsZU1lc3NhZ2UobWVzc2FnZTogQ29udHJvbE1lc3NhZ2UpOiBQcm9taXNlPENvbnRyb2xNZXNzYWdlIHwgbnVsbD47XG59XG5cbmNsYXNzIFRlcm1pbmFsSGFuZGxlciBpbXBsZW1lbnRzIE1lc3NhZ2VIYW5kbGVyIHtcbiAgYXN5bmMgaGFuZGxlTWVzc2FnZShtZXNzYWdlOiBDb250cm9sTWVzc2FnZSk6IFByb21pc2U8Q29udHJvbE1lc3NhZ2U+IHtcbiAgICBsb2dnZXIubG9nKGBUZXJtaW5hbCBoYW5kbGVyOiAke21lc3NhZ2UuYWN0aW9ufWApO1xuXG4gICAgaWYgKG1lc3NhZ2UuYWN0aW9uID09PSAnc3Bhd24nKSB7XG4gICAgICBjb25zdCByZXF1ZXN0ID0gbWVzc2FnZS5wYXlsb2FkIGFzIFRlcm1pbmFsU3Bhd25SZXF1ZXN0O1xuXG4gICAgICB0cnkge1xuICAgICAgICAvLyBCdWlsZCB0aGUgY29tbWFuZCBmb3IgbGF1bmNoaW5nIHRlcm1pbmFsIHdpdGggVmliZVR1bm5lbFxuICAgICAgICBjb25zdCBhcmdzID0gWydsYXVuY2gnXTtcblxuICAgICAgICBpZiAocmVxdWVzdC53b3JraW5nRGlyZWN0b3J5KSB7XG4gICAgICAgICAgYXJncy5wdXNoKCctLXdvcmtpbmctZGlyZWN0b3J5JywgcmVxdWVzdC53b3JraW5nRGlyZWN0b3J5KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChyZXF1ZXN0LmNvbW1hbmQpIHtcbiAgICAgICAgICBhcmdzLnB1c2goJy0tY29tbWFuZCcsIHJlcXVlc3QuY29tbWFuZCk7XG4gICAgICAgIH1cblxuICAgICAgICBhcmdzLnB1c2goJy0tc2Vzc2lvbi1pZCcsIHJlcXVlc3Quc2Vzc2lvbklkKTtcblxuICAgICAgICBpZiAocmVxdWVzdC50ZXJtaW5hbFByZWZlcmVuY2UpIHtcbiAgICAgICAgICBhcmdzLnB1c2goJy0tdGVybWluYWwnLCByZXF1ZXN0LnRlcm1pbmFsUHJlZmVyZW5jZSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBFeGVjdXRlIHZpYmV0dW5uZWwgY29tbWFuZFxuICAgICAgICBsb2dnZXIubG9nKGBTcGF3bmluZyB0ZXJtaW5hbCB3aXRoIGFyZ3M6ICR7YXJncy5qb2luKCcgJyl9YCk7XG5cbiAgICAgICAgLy8gVXNlIHNwYXduIHRvIGF2b2lkIHNoZWxsIGluamVjdGlvblxuICAgICAgICBjb25zdCB2dCA9IGNoaWxkX3Byb2Nlc3Muc3Bhd24oJ3ZpYmV0dW5uZWwnLCBhcmdzLCB7XG4gICAgICAgICAgZGV0YWNoZWQ6IHRydWUsXG4gICAgICAgICAgc3RkaW86ICdpZ25vcmUnLFxuICAgICAgICB9KTtcblxuICAgICAgICB2dC51bnJlZigpO1xuXG4gICAgICAgIGNvbnN0IHJlc3BvbnNlOiBUZXJtaW5hbFNwYXduUmVzcG9uc2UgPSB7XG4gICAgICAgICAgc3VjY2VzczogdHJ1ZSxcbiAgICAgICAgfTtcblxuICAgICAgICByZXR1cm4gY3JlYXRlQ29udHJvbFJlc3BvbnNlKG1lc3NhZ2UsIHJlc3BvbnNlKTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIHNwYXduIHRlcm1pbmFsOicsIGVycm9yKTtcbiAgICAgICAgcmV0dXJuIGNyZWF0ZUNvbnRyb2xSZXNwb25zZShcbiAgICAgICAgICBtZXNzYWdlLFxuICAgICAgICAgIG51bGwsXG4gICAgICAgICAgZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiAnRmFpbGVkIHRvIHNwYXduIHRlcm1pbmFsJ1xuICAgICAgICApO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBjcmVhdGVDb250cm9sUmVzcG9uc2UobWVzc2FnZSwgbnVsbCwgYFVua25vd24gdGVybWluYWwgYWN0aW9uOiAke21lc3NhZ2UuYWN0aW9ufWApO1xuICB9XG59XG5cbmNsYXNzIFN5c3RlbUhhbmRsZXIgaW1wbGVtZW50cyBNZXNzYWdlSGFuZGxlciB7XG4gIGFzeW5jIGhhbmRsZU1lc3NhZ2UobWVzc2FnZTogQ29udHJvbE1lc3NhZ2UpOiBQcm9taXNlPENvbnRyb2xNZXNzYWdlIHwgbnVsbD4ge1xuICAgIGxvZ2dlci5sb2coYFN5c3RlbSBoYW5kbGVyOiAke21lc3NhZ2UuYWN0aW9ufSwgdHlwZTogJHttZXNzYWdlLnR5cGV9LCBpZDogJHttZXNzYWdlLmlkfWApO1xuXG4gICAgc3dpdGNoIChtZXNzYWdlLmFjdGlvbikge1xuICAgICAgY2FzZSAncGluZyc6XG4gICAgICAgIC8vIEFscmVhZHkgaGFuZGxlZCBpbiBoYW5kbGVNYWNNZXNzYWdlXG4gICAgICAgIHJldHVybiBudWxsO1xuXG4gICAgICBjYXNlICdyZWFkeSc6XG4gICAgICAgIC8vIEV2ZW50LCBubyByZXNwb25zZSBuZWVkZWRcbiAgICAgICAgcmV0dXJuIG51bGw7XG5cbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGxvZ2dlci53YXJuKGBVbmtub3duIHN5c3RlbSBhY3Rpb246ICR7bWVzc2FnZS5hY3Rpb259YCk7XG4gICAgICAgIHJldHVybiBjcmVhdGVDb250cm9sUmVzcG9uc2UobWVzc2FnZSwgbnVsbCwgYFVua25vd24gYWN0aW9uOiAke21lc3NhZ2UuYWN0aW9ufWApO1xuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIEhhbmRsZXMgVW5peCBkb21haW4gc29ja2V0IGNvbW11bmljYXRpb24gYmV0d2VlbiB0aGUgVmliZVR1bm5lbCB3ZWIgc2VydmVyIGFuZCBtYWNPUyBhcHAuXG4gKlxuICogVGhpcyBjbGFzcyBtYW5hZ2VzIGEgVW5peCBzb2NrZXQgc2VydmVyIHRoYXQgcHJvdmlkZXMgYmlkaXJlY3Rpb25hbCBjb21tdW5pY2F0aW9uXG4gKiBiZXR3ZWVuIHRoZSB3ZWIgc2VydmVyIGFuZCB0aGUgbmF0aXZlIG1hY09TIGFwcGxpY2F0aW9uLiBJdCBpbXBsZW1lbnRzIGEgbWVzc2FnZS1iYXNlZFxuICogcHJvdG9jb2wgd2l0aCBsZW5ndGgtcHJlZml4ZWQgZnJhbWluZyBmb3IgcmVsaWFibGUgbWVzc2FnZSBkZWxpdmVyeSBhbmQgc3VwcG9ydHNcbiAqIG11bHRpcGxlIG1lc3NhZ2UgY2F0ZWdvcmllcyBpbmNsdWRpbmcgdGVybWluYWwgY29udHJvbCBhbmQgc3lzdGVtIGV2ZW50cy5cbiAqXG4gKiBLZXkgZmVhdHVyZXM6XG4gKiAtIFVuaXggZG9tYWluIHNvY2tldCBzZXJ2ZXIgd2l0aCBhdXRvbWF0aWMgY2xlYW51cCBvbiByZXN0YXJ0XG4gKiAtIExlbmd0aC1wcmVmaXhlZCBiaW5hcnkgcHJvdG9jb2wgZm9yIG1lc3NhZ2UgZnJhbWluZ1xuICogLSBNZXNzYWdlIHJvdXRpbmcgYmFzZWQgb24gY2F0ZWdvcmllcyAodGVybWluYWwsIHN5c3RlbSlcbiAqIC0gUmVxdWVzdC9yZXNwb25zZSBwYXR0ZXJuIHdpdGggdGltZW91dCBzdXBwb3J0XG4gKiAtIFdlYlNvY2tldCBicmlkZ2UgZm9yIGJyb3dzZXIgY2xpZW50c1xuICogLSBBdXRvbWF0aWMgc29ja2V0IHBlcm1pc3Npb24gbWFuYWdlbWVudCAoMDYwMClcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ3JlYXRlIGFuZCBzdGFydCB0aGUgaGFuZGxlclxuICogY29uc3QgaGFuZGxlciA9IG5ldyBDb250cm9sVW5peEhhbmRsZXIoKTtcbiAqIGF3YWl0IGhhbmRsZXIuc3RhcnQoKTtcbiAqXG4gKiAvLyBDaGVjayBpZiBNYWMgYXBwIGlzIGNvbm5lY3RlZFxuICogaWYgKGhhbmRsZXIuaXNNYWNBcHBDb25uZWN0ZWQoKSkge1xuICogICAvLyBTZW5kIGEgY29udHJvbCBtZXNzYWdlXG4gKiAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgaGFuZGxlci5zZW5kQ29udHJvbE1lc3NhZ2Uoe1xuICogICAgIGlkOiAnbXNnLTEyMycsXG4gKiAgICAgdHlwZTogJ3JlcXVlc3QnLFxuICogICAgIGNhdGVnb3J5OiAndGVybWluYWwnLFxuICogICAgIGFjdGlvbjogJ3NwYXduJyxcbiAqICAgICBwYXlsb2FkOiB7XG4gKiAgICAgICBzZXNzaW9uSWQ6ICdzZXNzaW9uLTQ1NicsXG4gKiAgICAgICB3b3JraW5nRGlyZWN0b3J5OiAnL1VzZXJzL2FsaWNlJyxcbiAqICAgICAgIGNvbW1hbmQ6ICd2aW0nXG