vibetunnel
Version:
Terminal sharing server with web interface - supports macOS, Linux, and headless environments
586 lines • 77.4 kB
JavaScript
"use strict";
/**
* SessionManager - Centralized management for terminal session lifecycle and persistence
*
* This class provides a comprehensive solution for managing terminal sessions in VibeTunnel.
* It handles session directory structure, metadata persistence, process tracking, and
* file operations while maintaining compatibility with the tty-fwd format.
*
* ## Key Features:
* - **Session Lifecycle Management**: Create, track, and cleanup terminal sessions
* - **Persistent Storage**: Store session metadata and I/O streams in filesystem
* - **Process Tracking**: Monitor running processes and detect zombie sessions
* - **Version Management**: Handle cleanup across VibeTunnel version upgrades
* - **Unique Naming**: Ensure session names are unique with automatic suffix handling
* - **Atomic Operations**: Use temp files and rename for safe metadata updates
*
* ## Directory Structure:
* ```
* ~/.vibetunnel/control/
* ├── .version # VibeTunnel version tracking
* └── [session-id]/ # Per-session directory
* ├── session.json # Session metadata
* ├── stdout # Process output stream
* └── stdin # Process input (FIFO or file)
* ```
*
* ## Session States:
* - `starting`: Session is being initialized
* - `running`: Process is active and accepting input
* - `exited`: Process has terminated
*
* @example
* ```typescript
* // Initialize session manager
* const manager = new SessionManager();
*
* // Create a new session
* const paths = manager.createSessionDirectory('session-123');
*
* // Save session metadata
* manager.saveSessionInfo('session-123', {
* name: 'Development Server',
* status: 'starting',
* pid: 12345,
* startedAt: new Date().toISOString()
* });
*
* // Update session status when process starts
* manager.updateSessionStatus('session-123', 'running', 12345);
*
* // List all sessions
* const sessions = manager.listSessions();
* console.log(`Found ${sessions.length} sessions`);
*
* // Cleanup when done
* manager.updateSessionStatus('session-123', 'exited', undefined, 0);
* manager.cleanupSession('session-123');
* ```
*/
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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionManager = void 0;
const chalk_1 = __importDefault(require("chalk"));
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const logger_js_1 = require("../utils/logger.js");
const version_js_1 = require("../version.js");
const process_utils_js_1 = require("./process-utils.js");
const types_js_1 = require("./types.js");
const logger = (0, logger_js_1.createLogger)('session-manager');
class SessionManager {
constructor(controlPath) {
this.controlPath = controlPath || path.join(os.homedir(), '.vibetunnel', 'control');
logger.debug(`initializing session manager with control path: ${this.controlPath}`);
this.ensureControlDirectory();
}
/**
* Validate session ID format for security
*/
validateSessionId(sessionId) {
if (!SessionManager.SESSION_ID_REGEX.test(sessionId)) {
throw new types_js_1.PtyError(`Invalid session ID format: "${sessionId}". Session IDs must only contain letters, numbers, hyphens (-), and underscores (_).`, 'INVALID_SESSION_ID');
}
}
/**
* Ensure the control directory exists
*/
ensureControlDirectory() {
if (!fs.existsSync(this.controlPath)) {
fs.mkdirSync(this.controlPath, { recursive: true });
logger.debug(chalk_1.default.green(`control directory created: ${this.controlPath}`));
}
}
/**
* Get the path to the version tracking file
*/
getVersionFilePath() {
return path.join(this.controlPath, '.version');
}
/**
* Read the last known version from the version file
*/
readLastVersion() {
try {
const versionFile = this.getVersionFilePath();
if (fs.existsSync(versionFile)) {
const content = fs.readFileSync(versionFile, 'utf8').trim();
logger.debug(`read last version from file: ${content}`);
return content;
}
return null;
}
catch (error) {
logger.warn(`failed to read version file: ${error}`);
return null;
}
}
/**
* Write the current version to the version file
*/
writeCurrentVersion() {
try {
const versionFile = this.getVersionFilePath();
fs.writeFileSync(versionFile, version_js_1.VERSION, 'utf8');
logger.debug(`wrote current version to file: ${version_js_1.VERSION}`);
}
catch (error) {
logger.warn(`failed to write version file: ${error}`);
}
}
/**
* Create a new session directory structure
*/
createSessionDirectory(sessionId) {
this.validateSessionId(sessionId);
const controlDir = path.join(this.controlPath, sessionId);
// Create session directory
if (!fs.existsSync(controlDir)) {
fs.mkdirSync(controlDir, { recursive: true });
}
const paths = this.getSessionPaths(sessionId, true);
if (!paths) {
throw new Error(`Session ${sessionId} not found`);
}
// Create FIFO pipe for stdin (or regular file on systems without mkfifo)
this.createStdinPipe(paths.stdinPath);
logger.debug(chalk_1.default.green(`session directory created for ${sessionId}`));
return paths;
}
/**
* Create stdin pipe (FIFO if possible, regular file otherwise)
*/
createStdinPipe(stdinPath) {
try {
// Try to create FIFO pipe (Unix-like systems)
if (process.platform !== 'win32') {
const result = (0, child_process_1.spawnSync)('mkfifo', [stdinPath], { stdio: 'ignore' });
if (result.status === 0) {
logger.debug(`FIFO pipe created: ${stdinPath}`);
return; // Successfully created FIFO
}
}
// Fallback to regular file
if (!fs.existsSync(stdinPath)) {
fs.writeFileSync(stdinPath, '');
}
}
catch (error) {
// If mkfifo fails, create regular file
logger.debug(`mkfifo failed (${error instanceof Error ? error.message : 'unknown error'}), creating regular file: ${stdinPath}`);
if (!fs.existsSync(stdinPath)) {
fs.writeFileSync(stdinPath, '');
}
}
}
/**
* Save session info to JSON file
*/
saveSessionInfo(sessionId, sessionInfo) {
this.validateSessionId(sessionId);
try {
const sessionDir = path.join(this.controlPath, sessionId);
const sessionJsonPath = path.join(sessionDir, 'session.json');
const tempPath = `${sessionJsonPath}.tmp`;
// Ensure session directory exists before writing
if (!fs.existsSync(sessionDir)) {
logger.warn(`Session directory ${sessionDir} does not exist, creating it`);
fs.mkdirSync(sessionDir, { recursive: true });
}
const sessionInfoStr = JSON.stringify(sessionInfo, null, 2);
// Write to temporary file first, then move to final location (atomic write)
fs.writeFileSync(tempPath, sessionInfoStr, 'utf8');
// Double-check directory still exists before rename (handle race conditions)
if (!fs.existsSync(sessionDir)) {
logger.error(`Session directory ${sessionDir} was deleted during save operation`);
// Clean up temp file if it exists
if (fs.existsSync(tempPath)) {
fs.unlinkSync(tempPath);
}
throw new types_js_1.PtyError(`Session directory was deleted during save operation`, 'SESSION_DIR_DELETED');
}
fs.renameSync(tempPath, sessionJsonPath);
logger.debug(`session.json file saved for session ${sessionId} with name: ${sessionInfo.name}`);
}
catch (error) {
if (error instanceof types_js_1.PtyError) {
throw error;
}
throw new types_js_1.PtyError(`Failed to save session info: ${error instanceof Error ? error.message : String(error)}`, 'SAVE_SESSION_FAILED');
}
}
/**
* Load session info from JSON file
*/
loadSessionInfo(sessionId) {
const sessionJsonPath = path.join(this.controlPath, sessionId, 'session.json');
try {
if (!fs.existsSync(sessionJsonPath)) {
return null;
}
const content = fs.readFileSync(sessionJsonPath, 'utf8');
return JSON.parse(content);
}
catch (error) {
logger.warn(`failed to load session info for ${sessionId}:`, error);
return null;
}
}
/**
* Update session status
*/
updateSessionStatus(sessionId, status, pid, exitCode) {
const sessionInfo = this.loadSessionInfo(sessionId);
if (!sessionInfo) {
throw new types_js_1.PtyError('Session info not found', 'SESSION_NOT_FOUND');
}
if (pid !== undefined) {
sessionInfo.pid = pid;
}
sessionInfo.status = status;
if (exitCode !== undefined) {
sessionInfo.exitCode = exitCode;
}
this.saveSessionInfo(sessionId, sessionInfo);
logger.debug(`session ${sessionId} status updated to ${status}${pid ? ` (pid: ${pid})` : ''}${exitCode !== undefined ? ` (exit code: ${exitCode})` : ''}`);
}
/**
* Ensure a session name is unique by adding a suffix if necessary
*/
ensureUniqueName(desiredName, excludeSessionId) {
const sessions = this.listSessions();
let finalName = desiredName;
let suffix = 2;
// Keep checking until we find a unique name
while (true) {
const nameExists = sessions.some((session) => session.name === finalName && session.id !== excludeSessionId);
if (!nameExists) {
break;
}
// Add or increment suffix
finalName = `${desiredName} (${suffix})`;
suffix++;
}
return finalName;
}
/**
* Update session name
*/
updateSessionName(sessionId, name) {
logger.debug(`[SessionManager] updateSessionName called for session ${sessionId} with name: ${name}`);
const sessionInfo = this.loadSessionInfo(sessionId);
if (!sessionInfo) {
logger.error(`[SessionManager] Session info not found for ${sessionId}`);
throw new types_js_1.PtyError('Session info not found', 'SESSION_NOT_FOUND');
}
logger.debug(`[SessionManager] Current session info: ${JSON.stringify(sessionInfo)}`);
// Ensure the name is unique
const uniqueName = this.ensureUniqueName(name, sessionId);
if (uniqueName !== name) {
logger.debug(`[SessionManager] Name "${name}" already exists, using "${uniqueName}" instead`);
}
sessionInfo.name = uniqueName;
logger.debug(`[SessionManager] Updated session info: ${JSON.stringify(sessionInfo)}`);
logger.debug(`[SessionManager] Calling saveSessionInfo`);
this.saveSessionInfo(sessionId, sessionInfo);
logger.debug(`[SessionManager] session ${sessionId} name updated to: ${uniqueName}`);
return uniqueName;
}
/**
* List all sessions
*/
listSessions() {
try {
if (!fs.existsSync(this.controlPath)) {
return [];
}
const sessions = [];
const entries = fs.readdirSync(this.controlPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const sessionId = entry.name;
const sessionDir = path.join(this.controlPath, sessionId);
const stdoutPath = path.join(sessionDir, 'stdout');
const sessionInfo = this.loadSessionInfo(sessionId);
if (sessionInfo) {
// Determine active state for running processes
if (sessionInfo.status === 'running' && sessionInfo.pid) {
// Update status if process is no longer alive
if (!process_utils_js_1.ProcessUtils.isProcessRunning(sessionInfo.pid)) {
logger.debug(chalk_1.default.yellow(`process ${sessionInfo.pid} no longer running for session ${sessionId}`));
sessionInfo.status = 'exited';
if (sessionInfo.exitCode === undefined) {
sessionInfo.exitCode = 1; // Default exit code for dead processes
}
this.saveSessionInfo(sessionId, sessionInfo);
}
}
if (fs.existsSync(stdoutPath)) {
const lastModified = fs.statSync(stdoutPath).mtime.toISOString();
sessions.push({ ...sessionInfo, id: sessionId, lastModified });
}
else {
sessions.push({ ...sessionInfo, id: sessionId, lastModified: sessionInfo.startedAt });
}
}
}
}
// Sort by startedAt timestamp (newest first)
sessions.sort((a, b) => {
const aTime = a.startedAt ? new Date(a.startedAt).getTime() : 0;
const bTime = b.startedAt ? new Date(b.startedAt).getTime() : 0;
return bTime - aTime;
});
logger.debug(`listSessions found ${sessions.length} sessions`);
sessions.forEach((session) => {
logger.debug(` - Session ${session.id}: name="${session.name}", status="${session.status}"`);
});
return sessions;
}
catch (error) {
throw new types_js_1.PtyError(`Failed to list sessions: ${error instanceof Error ? error.message : String(error)}`, 'LIST_SESSIONS_FAILED');
}
}
/**
* Check if a session exists
*/
sessionExists(sessionId) {
const sessionDir = path.join(this.controlPath, sessionId);
const sessionJsonPath = path.join(sessionDir, 'session.json');
return fs.existsSync(sessionJsonPath);
}
/**
* Cleanup a specific session
*/
cleanupSession(sessionId) {
if (!sessionId) {
throw new types_js_1.PtyError('Session ID is required for cleanup', 'INVALID_SESSION_ID');
}
try {
const sessionDir = path.join(this.controlPath, sessionId);
if (fs.existsSync(sessionDir)) {
logger.debug(`Cleaning up session directory: ${sessionDir}`);
// Log session info before cleanup for debugging
const sessionInfo = this.loadSessionInfo(sessionId);
if (sessionInfo) {
logger.debug(`Cleaning up session ${sessionId} with status: ${sessionInfo.status}`);
}
// Remove directory and all contents
fs.rmSync(sessionDir, { recursive: true, force: true });
logger.debug(chalk_1.default.green(`session ${sessionId} cleaned up`));
}
else {
logger.debug(`Session directory ${sessionDir} does not exist, nothing to clean up`);
}
}
catch (error) {
throw new types_js_1.PtyError(`Failed to cleanup session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`, 'CLEANUP_FAILED', sessionId);
}
}
/**
* Cleanup all exited sessions
*/
cleanupExitedSessions() {
const cleanedSessions = [];
try {
const sessions = this.listSessions();
for (const session of sessions) {
if (session.status === 'exited' && session.id) {
this.cleanupSession(session.id);
cleanedSessions.push(session.id);
}
}
if (cleanedSessions.length > 0) {
logger.debug(chalk_1.default.green(`cleaned up ${cleanedSessions.length} exited sessions`));
}
return cleanedSessions;
}
catch (error) {
throw new types_js_1.PtyError(`Failed to cleanup exited sessions: ${error instanceof Error ? error.message : String(error)}`, 'CLEANUP_EXITED_FAILED');
}
}
/**
* Cleanup sessions from old VibeTunnel versions
* This is called during server startup to clean sessions when version changes
*/
cleanupOldVersionSessions() {
const lastVersion = this.readLastVersion();
const currentVersion = version_js_1.VERSION;
// If no version file exists, this is likely a fresh install or first time with version tracking
if (!lastVersion) {
logger.debug('no previous version found, checking for legacy sessions');
// First update zombie sessions to mark dead processes
this.updateZombieSessions();
// Clean up any sessions without version field that are also not active
let cleanedCount = 0;
const sessions = this.listSessions();
for (const session of sessions) {
if (!session.version) {
// Only clean if the session is not actively running
if (session.status === 'exited' ||
(session.pid && !process_utils_js_1.ProcessUtils.isProcessRunning(session.pid))) {
logger.debug(`cleaning up legacy zombie session ${session.id} (no version field)`);
this.cleanupSession(session.id);
cleanedCount++;
}
else {
logger.debug(`preserving active legacy session ${session.id}`);
}
}
}
this.writeCurrentVersion();
return { versionChanged: false, cleanedCount };
}
// If version hasn't changed, nothing to do
if (lastVersion === currentVersion) {
logger.debug(`version unchanged (${currentVersion}), skipping cleanup`);
return { versionChanged: false, cleanedCount: 0 };
}
logger.log(chalk_1.default.yellow(`VibeTunnel version changed from ${lastVersion} to ${currentVersion}`));
logger.log(chalk_1.default.yellow('cleaning up zombie sessions from old version...'));
// First update zombie sessions to mark dead processes
this.updateZombieSessions();
let cleanedCount = 0;
try {
const sessions = this.listSessions();
for (const session of sessions) {
// Only clean sessions that don't match the current version AND are not active
if (!session.version || session.version !== currentVersion) {
// Check if session is actually dead/zombie
if (session.status === 'exited' ||
(session.pid && !process_utils_js_1.ProcessUtils.isProcessRunning(session.pid))) {
logger.debug(`cleaning up zombie session ${session.id} (version: ${session.version || 'unknown'})`);
this.cleanupSession(session.id);
cleanedCount++;
}
else {
logger.debug(`preserving active session ${session.id} (version: ${session.version || 'unknown'})`);
}
}
}
// Update the version file to current version
this.writeCurrentVersion();
if (cleanedCount > 0) {
logger.log(chalk_1.default.green(`cleaned up ${cleanedCount} zombie sessions from previous version`));
}
else {
logger.log(chalk_1.default.gray('no zombie sessions to clean up (active sessions preserved)'));
}
return { versionChanged: true, cleanedCount };
}
catch (error) {
logger.error(`failed to cleanup old version sessions: ${error}`);
// Still update version file to prevent repeated cleanup attempts
this.writeCurrentVersion();
return { versionChanged: true, cleanedCount };
}
}
/**
* Get session paths for a given session ID
*/
getSessionPaths(sessionId, checkExists = false) {
const sessionDir = path.join(this.controlPath, sessionId);
logger.debug(`[SessionManager] getSessionPaths for ${sessionId}, sessionDir: ${sessionDir}, checkExists: ${checkExists}`);
if (checkExists && !fs.existsSync(sessionDir)) {
logger.debug(`[SessionManager] Session directory does not exist: ${sessionDir}`);
return null;
}
return {
controlDir: sessionDir,
stdoutPath: path.join(sessionDir, 'stdout'),
stdinPath: path.join(sessionDir, 'stdin'),
sessionJsonPath: path.join(sessionDir, 'session.json'),
};
}
/**
* Write to stdin pipe/file
*/
writeToStdin(sessionId, data) {
const paths = this.getSessionPaths(sessionId);
if (!paths) {
throw new types_js_1.PtyError(`Session ${sessionId} not found`, 'SESSION_NOT_FOUND', sessionId);
}
try {
// For FIFO pipes, we need to open in append mode
// For regular files, we also use append mode to avoid conflicts
fs.appendFileSync(paths.stdinPath, data);
logger.debug(`wrote ${data.length} bytes to stdin for session ${sessionId}`);
}
catch (error) {
throw new types_js_1.PtyError(`Failed to write to stdin for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`, 'STDIN_WRITE_FAILED', sessionId);
}
}
/**
* Update sessions that have zombie processes
*/
updateZombieSessions() {
const updatedSessions = [];
try {
const sessions = this.listSessions();
for (const session of sessions) {
if (session.status === 'running' && session.pid) {
if (!process_utils_js_1.ProcessUtils.isProcessRunning(session.pid)) {
// Process is dead, update status
const paths = this.getSessionPaths(session.id);
if (paths) {
logger.debug(chalk_1.default.yellow(`marking zombie process ${session.pid} as exited for session ${session.id}`));
this.updateSessionStatus(session.id, 'exited', undefined, 1);
updatedSessions.push(session.id);
}
}
}
}
return updatedSessions;
}
catch (error) {
logger.warn('failed to update zombie sessions:', error);
return [];
}
}
/**
* Get control path
*/
getControlPath() {
return this.controlPath;
}
}
exports.SessionManager = SessionManager;
SessionManager.SESSION_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vzc2lvbi1tYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlcnZlci9wdHkvc2Vzc2lvbi1tYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBeURHOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFSCxrREFBMEI7QUFDMUIsaURBQTBDO0FBQzFDLHVDQUF5QjtBQUN6Qix1Q0FBeUI7QUFDekIsMkNBQTZCO0FBRTdCLGtEQUFrRDtBQUNsRCw4Q0FBd0M7QUFDeEMseURBQWtEO0FBQ2xELHlDQUFzQztBQUV0QyxNQUFNLE1BQU0sR0FBRyxJQUFBLHdCQUFZLEVBQUMsaUJBQWlCLENBQUMsQ0FBQztBQUUvQyxNQUFhLGNBQWM7SUFJekIsWUFBWSxXQUFvQjtRQUM5QixJQUFJLENBQUMsV0FBVyxHQUFHLFdBQVcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRSxhQUFhLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDcEYsTUFBTSxDQUFDLEtBQUssQ0FBQyxtREFBbUQsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7UUFDcEYsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7SUFDaEMsQ0FBQztJQUVEOztPQUVHO0lBQ0ssaUJBQWlCLENBQUMsU0FBaUI7UUFDekMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQztZQUNyRCxNQUFNLElBQUksbUJBQVEsQ0FDaEIsK0JBQStCLFNBQVMsc0ZBQXNGLEVBQzlILG9CQUFvQixDQUNyQixDQUFDO1FBQ0osQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNLLHNCQUFzQjtRQUM1QixJQUFJLENBQUMsRUFBRSxDQUFD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aW9uTWFuYWdlciAtIENlbnRyYWxpemVkIG1hbmFnZW1lbnQgZm9yIHRlcm1pbmFsIHNlc3Npb24gbGlmZWN5Y2xlIGFuZCBwZXJzaXN0ZW5jZVxuICpcbiAqIFRoaXMgY2xhc3MgcHJvdmlkZXMgYSBjb21wcmVoZW5zaXZlIHNvbHV0aW9uIGZvciBtYW5hZ2luZyB0ZXJtaW5hbCBzZXNzaW9ucyBpbiBWaWJlVHVubmVsLlxuICogSXQgaGFuZGxlcyBzZXNzaW9uIGRpcmVjdG9yeSBzdHJ1Y3R1cmUsIG1ldGFkYXRhIHBlcnNpc3RlbmNlLCBwcm9jZXNzIHRyYWNraW5nLCBhbmRcbiAqIGZpbGUgb3BlcmF0aW9ucyB3aGlsZSBtYWludGFpbmluZyBjb21wYXRpYmlsaXR5IHdpdGggdGhlIHR0eS1md2QgZm9ybWF0LlxuICpcbiAqICMjIEtleSBGZWF0dXJlczpcbiAqIC0gKipTZXNzaW9uIExpZmVjeWNsZSBNYW5hZ2VtZW50Kio6IENyZWF0ZSwgdHJhY2ssIGFuZCBjbGVhbnVwIHRlcm1pbmFsIHNlc3Npb25zXG4gKiAtICoqUGVyc2lzdGVudCBTdG9yYWdlKio6IFN0b3JlIHNlc3Npb24gbWV0YWRhdGEgYW5kIEkvTyBzdHJlYW1zIGluIGZpbGVzeXN0ZW1cbiAqIC0gKipQcm9jZXNzIFRyYWNraW5nKio6IE1vbml0b3IgcnVubmluZyBwcm9jZXNzZXMgYW5kIGRldGVjdCB6b21iaWUgc2Vzc2lvbnNcbiAqIC0gKipWZXJzaW9uIE1hbmFnZW1lbnQqKjogSGFuZGxlIGNsZWFudXAgYWNyb3NzIFZpYmVUdW5uZWwgdmVyc2lvbiB1cGdyYWRlc1xuICogLSAqKlVuaXF1ZSBOYW1pbmcqKjogRW5zdXJlIHNlc3Npb24gbmFtZXMgYXJlIHVuaXF1ZSB3aXRoIGF1dG9tYXRpYyBzdWZmaXggaGFuZGxpbmdcbiAqIC0gKipBdG9taWMgT3BlcmF0aW9ucyoqOiBVc2UgdGVtcCBmaWxlcyBhbmQgcmVuYW1lIGZvciBzYWZlIG1ldGFkYXRhIHVwZGF0ZXNcbiAqXG4gKiAjIyBEaXJlY3RvcnkgU3RydWN0dXJlOlxuICogYGBgXG4gKiB+Ly52aWJldHVubmVsL2NvbnRyb2wvXG4gKiDilJzilIDilIAgLnZlcnNpb24gICAgICAgICAgICAgICAgICAgICMgVmliZVR1bm5lbCB2ZXJzaW9uIHRyYWNraW5nXG4gKiDilJTilIDilIAgW3Nlc3Npb24taWRdLyAgICAgICAgICAgICAgIyBQZXItc2Vzc2lvbiBkaXJlY3RvcnlcbiAqICAgICDilJzilIDilIAgc2Vzc2lvbi5qc29uICAgICAgICAgICAjIFNlc3Npb24gbWV0YWRhdGFcbiAqICAgICDilJzilIDilIAgc3Rkb3V0ICAgICAgICAgICAgICAgICAjIFByb2Nlc3Mgb3V0cHV0IHN0cmVhbVxuICogICAgIOKUlOKUgOKUgCBzdGRpbiAgICAgICAgICAgICAgICAgICMgUHJvY2VzcyBpbnB1dCAoRklGTyBvciBmaWxlKVxuICogYGBgXG4gKlxuICogIyMgU2Vzc2lvbiBTdGF0ZXM6XG4gKiAtIGBzdGFydGluZ2A6IFNlc3Npb24gaXMgYmVpbmcgaW5pdGlhbGl6ZWRcbiAqIC0gYHJ1bm5pbmdgOiBQcm9jZXNzIGlzIGFjdGl2ZSBhbmQgYWNjZXB0aW5nIGlucHV0XG4gKiAtIGBleGl0ZWRgOiBQcm9jZXNzIGhhcyB0ZXJtaW5hdGVkXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIEluaXRpYWxpemUgc2Vzc2lvbiBtYW5hZ2VyXG4gKiBjb25zdCBtYW5hZ2VyID0gbmV3IFNlc3Npb25NYW5hZ2VyKCk7XG4gKlxuICogLy8gQ3JlYXRlIGEgbmV3IHNlc3Npb25cbiAqIGNvbnN0IHBhdGhzID0gbWFuYWdlci5jcmVhdGVTZXNzaW9uRGlyZWN0b3J5KCdzZXNzaW9uLTEyMycpO1xuICpcbiAqIC8vIFNhdmUgc2Vzc2lvbiBtZXRhZGF0YVxuICogbWFuYWdlci5zYXZlU2Vzc2lvbkluZm8oJ3Nlc3Npb24tMTIzJywge1xuICogICBuYW1lOiAnRGV2ZWxvcG1lbnQgU2VydmVyJyxcbiAqICAgc3RhdHVzOiAnc3RhcnRpbmcnLFxuICogICBwaWQ6IDEyMzQ1LFxuICogICBzdGFydGVkQXQ6IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKVxuICogfSk7XG4gKlxuICogLy8gVXBkYXRlIHNlc3Npb24gc3RhdHVzIHdoZW4gcHJvY2VzcyBzdGFydHNcbiAqIG1hbmFnZXIudXBkYXRlU2Vzc2lvblN0YXR1cygnc2Vzc2lvbi0xMjMnLCAncnVubmluZycsIDEyMzQ1KTtcbiAqXG4gKiAvLyBMaXN0IGFsbCBzZXNzaW9uc1xuICogY29uc3Qgc2Vzc2lvbnMgPSBtYW5hZ2VyLmxpc3RTZXNzaW9ucygpO1xuICogY29uc29sZS5sb2coYEZvdW5kICR7c2Vzc2lvbnMubGVuZ3RofSBzZXNzaW9uc2ApO1xuICpcbiAqIC8vIENsZWFudXAgd2hlbiBkb25lXG4gKiBtYW5hZ2VyLnVwZGF0ZVNlc3Npb25TdGF0dXMoJ3Nlc3Npb24tMTIzJywgJ2V4aXRlZCcsIHVuZGVmaW5lZCwgMCk7XG4gKiBtYW5hZ2VyLmNsZWFudXBTZXNzaW9u