@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.
233 lines • 29 kB
JavaScript
/**
* Dynamic port allocation and port file discovery for the web console.
*
* When multiple DollhouseMCP sessions run simultaneously (e.g., multiple
* Claude Code windows, IDE instances, or agent sessions), each needs its
* own web console port. This module handles:
*
* 1. Finding an available port starting from the configured default
* (see `DOLLHOUSE_WEB_CONSOLE_PORT` in `src/config/env.ts`)
* 2. Writing port discovery files so external tools know which port to use
* 3. Cleaning up port files on process exit
*
* Port files are written to ~/.dollhouse/run/:
* - permission-server-{pid}.port — per-process file (cleaned on exit)
* - permission-server.port — latest port (convenience for scripts)
*/
import { createServer } from 'node:net';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { mkdir, writeFile, unlink, readdir, readFile } from 'node:fs/promises';
import { env } from '../config/env.js';
import { logger } from '../utils/logger.js';
/** Default maximum port attempts before giving up */
const DEFAULT_MAX_PORT_ATTEMPTS = 10;
/** Directory for runtime state files (port discovery, PID files) */
const RUN_DIR = join(homedir(), '.dollhouse', 'run');
const LATEST_PORT_FILENAME = 'permission-server.port';
function pidPortFilePath(dir = RUN_DIR, pid = process.pid) {
return join(dir, `permission-server-${pid}.port`);
}
function latestPortFilePath(dir = RUN_DIR) {
return join(dir, LATEST_PORT_FILENAME);
}
/** Track port file path for cleanup */
let portFilePath = null;
/**
* Attempt to bind to a single port. Returns the port if successful, null if in use.
* Keeps the server listening to prevent TOCTOU race conditions — caller must
* close the returned server after their own server binds.
*/
function tryBindPort(port) {
return new Promise((resolve) => {
const server = createServer();
server.once('error', (err) => {
if (err.code === 'EADDRINUSE') {
resolve(null);
}
else {
resolve(null);
logger.debug(`[PortDiscovery] Unexpected error on port ${port}: ${err.code}`);
}
});
server.once('listening', () => {
resolve({ port, server });
});
server.listen(port, '127.0.0.1');
});
}
/**
* Find an available port starting from the given port.
*
* Tries sequential ports, logging each attempt. Returns both the port number
* and a held server to prevent TOCTOU race conditions. The caller should
* close the held server after binding their own Express app to the port.
*
* @param startPort - Port to try first (see `DOLLHOUSE_WEB_CONSOLE_PORT` for the default)
* @param maxAttempts - Maximum ports to try (default: 10, configurable via DOLLHOUSE_MAX_PORT_ATTEMPTS)
* @returns Object with port number and held server, or throws if all attempts fail
*/
export async function findAvailablePort(startPort, maxAttempts = Number(process.env.DOLLHOUSE_MAX_PORT_ATTEMPTS) || DEFAULT_MAX_PORT_ATTEMPTS) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const port = startPort + attempt;
logger.debug(`[PortDiscovery] Trying port ${port} (attempt ${attempt + 1}/${maxAttempts})`);
const result = await tryBindPort(port);
if (result) {
// Close the probe server — there's a brief TOCTOU window here between
// closing and the caller binding, but it's negligible on localhost.
// A future improvement could return the server for the caller to adopt.
result.server.close();
if (attempt > 0) {
logger.info(`[PortDiscovery] Port ${startPort} in use, using ${port} (after ${attempt} skip${attempt > 1 ? 's' : ''})`);
}
return port;
}
logger.debug(`[PortDiscovery] Port ${port} in use, trying next`);
}
throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1} ` +
`after ${maxAttempts} attempts. Set DOLLHOUSE_MAX_PORT_ATTEMPTS to increase the range.`);
}
/**
* Write the active server port to a discoverable file.
*
* Creates two files:
* - PID-keyed file for per-process cleanup
* - Latest file for convenience (scripts can read this without knowing the PID)
*
* @param port - The port the web server is listening on
* @returns Path to the PID-keyed port file
*/
export async function writePortFile(port) {
await mkdir(RUN_DIR, { recursive: true });
const pidFile = pidPortFilePath();
const latestFile = latestPortFilePath();
await writeFile(pidFile, String(port), 'utf-8');
await writeFile(latestFile, String(port), 'utf-8');
portFilePath = pidFile;
logger.debug(`[PortDiscovery] Port file written: ${pidFile}`);
return pidFile;
}
/**
* Restore or update the shared latest port file for the active listener.
*
* Returns true when the file had to be written or updated, false when it
* already matched the active port.
*/
export async function ensureLatestPortFile(port, customDir) {
const dir = customDir || RUN_DIR;
const latestFile = latestPortFilePath(dir);
const desiredValue = String(port);
await mkdir(dir, { recursive: true });
try {
const existingValue = (await readFile(latestFile, 'utf-8')).trim();
if (existingValue === desiredValue) {
return false;
}
}
catch {
// Missing/unreadable file — rewrite below.
}
await writeFile(latestFile, desiredValue, 'utf-8');
logger.debug(`[PortDiscovery] Shared latest port file refreshed: ${latestFile}`);
return true;
}
/**
* Clean up port file on shutdown.
*/
export async function cleanupPortFile() {
if (portFilePath) {
try {
await unlink(portFilePath);
}
catch { /* already gone */ }
}
}
/**
* Register process exit handlers to clean up port files.
* Should be called once after the web server successfully starts.
*/
export function registerPortCleanup() {
const exitCleanup = () => { cleanupPortFile().catch(() => { }); };
process.once('exit', exitCleanup);
process.once('SIGTERM', exitCleanup);
process.once('SIGINT', exitCleanup);
process.once('SIGHUP', exitCleanup);
}
/**
* Sweep stale port files from ~/.dollhouse/run/ on startup (#1856).
*
* Scans for permission-server-{pid}.port files and removes any whose PID
* is no longer alive. This prevents unbounded accumulation of port files
* from crashed or abandoned sessions.
*
* Note: This only removes metadata files, not processes or ports. The port
* binding itself (in bindAndListen) is atomic via listen(). There is no race
* between sweeping files and binding — they operate on independent resources.
*
* Safe to call on every startup — only removes files for dead processes.
*/
export async function sweepStalePortFiles(customDir) {
try {
const dir = customDir || RUN_DIR;
await mkdir(dir, { recursive: true });
const files = await readdir(dir);
const PORT_FILE_RE = /^permission-server-(\d+)\.port$/;
let removed = 0;
for (const file of files) {
const match = PORT_FILE_RE.exec(file);
if (!match)
continue;
const pid = Number(match[1]);
// Check if process is alive via signal-0.
// ESRCH = process doesn't exist (dead). EPERM = process exists but
// owned by another user (alive — don't touch their port file).
let alive = false;
try {
process.kill(pid, 0);
alive = true;
}
catch (err) {
alive = err?.code === 'EPERM'; // EPERM = alive but different user
}
if (!alive) {
try {
await unlink(join(dir, file));
removed++;
}
catch { /* already gone */ }
}
}
if (removed > 0) {
logger.info(`[PortDiscovery] Swept ${removed} stale port files from ${dir}`);
}
return removed;
}
catch (err) {
logger.debug('[PortDiscovery] Stale port file sweep failed', {
error: err instanceof Error ? err.message : String(err),
});
return 0;
}
}
/**
* Discover an available port, write discovery files, and register cleanup.
*
* This is the recommended entry point for Container startup. Combines
* port discovery, file writing, and cleanup registration in one call.
*
* @param defaultPort - Port to try first (see `DOLLHOUSE_WEB_CONSOLE_PORT` for the default)
* @returns The port the server should bind to, or undefined if discovery failed
*/
export async function discoverAndBindPort(defaultPort = env.DOLLHOUSE_WEB_CONSOLE_PORT) {
try {
const port = await findAvailablePort(defaultPort);
await writePortFile(port);
registerPortCleanup();
return port;
}
catch (err) {
logger.warn('[PortDiscovery] Port discovery failed:', err);
return undefined;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9ydERpc2NvdmVyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93ZWIvcG9ydERpc2NvdmVyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFlLE1BQU0sVUFBVSxDQUFDO0FBQ3JELE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDbEMsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNqQyxPQUFPLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQy9FLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN2QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFNUMscURBQXFEO0FBQ3JELE1BQU0seUJBQXlCLEdBQUcsRUFBRSxDQUFDO0FBRXJDLG9FQUFvRTtBQUNwRSxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsWUFBWSxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBQ3JELE1BQU0sb0JBQW9CLEdBQUcsd0JBQXdCLENBQUM7QUFFdEQsU0FBUyxlQUFlLENBQUMsTUFBYyxPQUFPLEVBQUUsTUFBYyxPQUFPLENBQUMsR0FBRztJQUN2RSxPQUFPLElBQUksQ0FBQyxHQUFHLEVBQUUscUJBQXFCLEdBQUcsT0FBTyxDQUFDLENBQUM7QUFDcEQsQ0FBQztBQUVELFNBQVMsa0JBQWtCLENBQUMsTUFBYyxPQUFPO0lBQy9DLE9BQU8sSUFBSSxDQUFDLEdBQUcsRUFBRSxvQkFBb0IsQ0FBQyxDQUFDO0FBQ3pDLENBQUM7QUFFRCx1Q0FBdUM7QUFDdkMsSUFBSSxZQUFZLEdBQWtCLElBQUksQ0FBQztBQUV2Qzs7OztHQUlHO0FBQ0gsU0FBUyxXQUFXLENBQUMsSUFB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TSxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUVsRCxNQUFNLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMxQixtQkFBbUIsRUFBRSxDQUFDO1FBRXRCLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7UUFDYixNQUFNLENBQUMsSUFBSSxDQUFDLHdDQUF3QyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQzNELE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBEeW5hbWljIHBvcnQgYWxsb2NhdGlvbiBhbmQgcG9ydCBmaWxlIGRpc2NvdmVyeSBmb3IgdGhlIHdlYiBjb25zb2xlLlxuICpcbiAqIFdoZW4gbXVsdGlwbGUgRG9sbGhvdXNlTUNQIHNlc3Npb25zIHJ1biBzaW11bHRhbmVvdXNseSAoZS5nLiwgbXVsdGlwbGVcbiAqIENsYXVkZSBDb2RlIHdpbmRvd3MsIElERSBpbnN0YW5jZXMsIG9yIGFnZW50IHNlc3Npb25zKSwgZWFjaCBuZWVkcyBpdHNcbiAqIG93biB3ZWIgY29uc29sZSBwb3J0LiBUaGlzIG1vZHVsZSBoYW5kbGVzOlxuICpcbiAqIDEuIEZpbmRpbmcgYW4gYXZhaWxhYmxlIHBvcnQgc3RhcnRpbmcgZnJvbSB0aGUgY29uZmlndXJlZCBkZWZhdWx0XG4gKiAgICAoc2VlIGBET0xMSE9VU0VfV0VCX0NPTlNPTEVfUE9SVGAgaW4gYHNyYy9jb25maWcvZW52LnRzYClcbiAqIDIuIFdyaXRpbmcgcG9ydCBkaXNjb3ZlcnkgZmlsZXMgc28gZXh0ZXJuYWwgdG9vbHMga25vdyB3aGljaCBwb3J0IHRvIHVzZVxuICogMy4gQ2xlYW5pbmcgdXAgcG9ydCBmaWxlcyBvbiBwcm9jZXNzIGV4aXRcbiAqXG4gKiBQb3J0IGZpbGVzIGFyZSB3cml0dGVuIHRvIH4vLmRvbGxob3VzZS9ydW4vOlxuICogLSBwZXJtaXNzaW9uLXNlcnZlci17cGlkfS5wb3J0IOKAlCBwZXItcHJvY2VzcyBmaWxlIChjbGVhbmVkIG9uIGV4aXQpXG4gKiAtIHBlcm1pc3Npb24tc2VydmVyLnBvcnQg4oCUIGxhdGVzdCBwb3J0IChjb252ZW5pZW5jZSBmb3Igc2NyaXB0cylcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVTZXJ2ZXIsIHR5cGUgU2VydmVyIH0gZnJvbSAnbm9kZTpuZXQnO1xuaW1wb3J0IHsgaG9tZWRpciB9IGZyb20gJ25vZGU6b3MnO1xuaW1wb3J0IHsgam9pbiB9IGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBta2Rpciwgd3JpdGVGaWxlLCB1bmxpbmssIHJlYWRkaXIsIHJlYWRGaWxlIH0gZnJvbSAnbm9kZTpmcy9wcm9taXNlcyc7XG5pbXBvcnQgeyBlbnYgfSBmcm9tICcuLi9jb25maWcvZW52LmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKiBEZWZhdWx0IG1heGltdW0gcG9ydCBhdHRlbXB0cyBiZWZvcmUgZ2l2aW5nIHVwICovXG5jb25zdCBERUZBVUxUX01BWF9QT1JUX0FUVEVNUFRTID0gMTA7XG5cbi8qKiBEaXJlY3RvcnkgZm9yIHJ1bnRpbWUgc3RhdGUgZmlsZXMgKHBvcnQgZGlzY292ZXJ5LCBQSUQgZmlsZXMpICovXG5jb25zdCBSVU5fRElSID0gam9pbihob21lZGlyKCksICcuZG9sbGhvdXNlJywgJ3J1bicpO1xuY29uc3QgTEFURVNUX1BPUlRfRklMRU5BTUUgPSAncGVybWlzc2lvbi1zZXJ2ZXIucG9ydCc7XG5cbmZ1bmN0aW9uIHBpZFBvcnRGaWxlUGF0aChkaXI6IHN0cmluZyA9IFJVTl9ESVIsIHBpZDogbnVtYmVyID0gcHJvY2Vzcy5waWQpOiBzdHJpbmcge1xuICByZXR1cm4gam9pbihkaXIsIGBwZXJtaXNzaW9uLXNlcnZlci0ke3BpZH0ucG9ydGApO1xufVxuXG5mdW5jdGlvbiBsYXRlc3RQb3J0RmlsZVBhdGgoZGlyOiBzdHJpbmcgPSBSVU5fRElSKTogc3RyaW5nIHtcbiAgcmV0dXJuIGpvaW4oZGlyLCBMQVRFU1RfUE9SVF9GSUxFTkFNRSk7XG59XG5cbi8qKiBUcmFjayBwb3J0IGZpbGUgcGF0aCBmb3IgY2xlYW51cCAqL1xubGV0IHBvcnRGaWxlUGF0aDogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG5cbi8qKlxuICogQXR0ZW1wdCB0byBiaW5kIHRvIGEgc2luZ2xlIHBvcnQuIFJldHVybnMgdGhlIHBvcnQgaWYgc3VjY2Vzc2Z1bCwgbnVsbCBpZiBpbiB1c2UuXG4gKiBLZWVwcyB0aGUgc2VydmVyIGxpc3RlbmluZyB0byBwcmV2ZW50IFRPQ1RPVSByYWNlIGNvbmRpdGlvbnMg4oCUIGNhbGxlciBtdXN0XG4gKiBjbG9zZSB0aGUgcmV0dXJuZWQgc2VydmVyIGFmdGVyIHRoZWlyIG93biBzZXJ2ZXIgYmluZHMuXG4gKi9cbmZ1bmN0aW9uIHRyeUJpbmRQb3J0KHBvcnQ6IG51bWJlcik6IFByb21pc2U8eyBwb3J0OiBudW1iZXI7IHNlcnZlcjogU2VydmVyIH0gfCBudWxsPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuICAgIGNvbnN0IHNlcnZlciA9IGNyZWF0ZVNlcnZlcigpO1xuICAgIHNlcnZlci5vbmNlKCdlcnJvcicsIChlcnI6IE5vZGVKUy5FcnJub0V4Y2VwdGlvbikgPT4ge1xuICAgICAgaWYgKGVyci5jb2RlID09PSAnRUFERFJJTlVTRScpIHtcbiAgICAgICAgcmVzb2x2ZShudWxsKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlc29sdmUobnVsbCk7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgW1BvcnREaXNjb3ZlcnldIFVuZXhwZWN0ZWQgZXJyb3Igb24gcG9ydCAke3BvcnR9OiAke2Vyci5jb2RlfWApO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHNlcnZlci5vbmNlKCdsaXN0ZW5pbmcnLCAoKSA9PiB7XG4gICAgICByZXNvbHZlKHsgcG9ydCwgc2VydmVyIH0pO1xuICAgIH0pO1xuICAgIHNlcnZlci5saXN0ZW4ocG9ydCwgJzEyNy4wLjAuMScpO1xuICB9KTtcbn1cblxuLyoqXG4gKiBGaW5kIGFuIGF2YWlsYWJsZSBwb3J0IHN0YXJ0aW5nIGZyb20gdGhlIGdpdmVuIHBvcnQuXG4gKlxuICogVHJpZXMgc2VxdWVudGlhbCBwb3J0cywgbG9nZ2luZyBlYWNoIGF0dGVtcHQuIFJldHVybnMgYm90aCB0aGUgcG9ydCBudW1iZXJcbiAqIGFuZCBhIGhlbGQgc2VydmVyIHRvIHByZXZlbnQgVE9DVE9VIHJhY2UgY29uZGl0aW9ucy4gVGhlIGNhbGxlciBzaG91bGRcbiAqIGNsb3NlIHRoZSBoZWxkIHNlcnZlciBhZnRlciBiaW5kaW5nIHRoZWlyIG93biBFeHByZXNzIGFwcCB0byB0aGUgcG9ydC5cbiAqXG4gKiBAcGFyYW0gc3RhcnRQb3J0IC0gUG9ydCB0byB0cnkgZmlyc3QgKHNlZSBgRE9MTEhPVVNFX1dFQl9DT05TT0xFX1BPUlRgIGZvciB0aGUgZGVmYXVsdClcbiAqIEBwYXJhbSBtYXhBdHRlbXB0cyAtIE1heGltdW0gcG9ydHMgdG8gdHJ5IChkZWZhdWx0OiAxMCwgY29uZmlndXJhYmxlIHZpYSBET0xMSE9VU0VfTUFYX1BPUlRfQVRURU1QVFMpXG4gKiBAcmV0dXJucyBPYmplY3Qgd2l0aCBwb3J0IG51bWJlciBhbmQgaGVsZCBzZXJ2ZXIsIG9yIHRocm93cyBpZiBhbGwgYXR0ZW1wdHMgZmFpbFxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZmluZEF2YWlsYWJsZVBvcnQoXG4gIHN0YXJ0UG9ydDogbnVtYmVyLFxuICBtYXhBdHRlbXB0czogbnVtYmVyID0gTnVtYmVyKHByb2Nlc3MuZW52LkRPTExIT1VTRV9NQVhfUE9SVF9BVFRFTVBUUykgfHwgREVGQVVMVF9NQVhfUE9SVF9BVFRFTVBUUyxcbik6IFByb21pc2U8bnVtYmVyPiB7XG4gIGZvciAobGV0IGF0dGVtcHQgPSAwOyBhdHRlbXB0IDwgbWF4QXR0ZW1wdHM7IGF0dGVtcHQrKykge1xuICAgIGNvbnN0IHBvcnQgPSBzdGFydFBvcnQgKyBhdHRlbXB0O1xuICAgIGxvZ2dlci5kZWJ1ZyhgW1BvcnREaXNjb3ZlcnldIFRyeWluZyBwb3J0ICR7cG9ydH0gKGF0dGVtcHQgJHthdHRlbXB0ICsgMX0vJHttYXhBdHRlbXB0c30pYCk7XG5cbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0cnlCaW5kUG9ydChwb3J0KTtcbiAgICBpZiAocmVzdWx0KSB7XG4gICAgICAvLyBDbG9zZSB0aGUgcHJvYmUgc2VydmVyIOKAlCB0aGVyZSdzIGEgYnJpZWYgVE9DVE9VIHdpbmRvdyBoZXJlIGJldHdlZW5cbiAgICAgIC8vIGNsb3NpbmcgYW5kIHRoZSBjYWxsZXIgYmluZGluZywgYnV0IGl0J3MgbmVnbGlnaWJsZSBvbiBsb2NhbGhvc3QuXG4gICAgICAvLyBBIGZ1dHVyZSBpbXByb3ZlbWVudCBjb3VsZCByZXR1cm4gdGhlIHNlcnZlciBmb3IgdGhlIGNhbGxlciB0byBhZG9wdC5cbiAgICAgIHJlc3VsdC5zZXJ2ZXIuY2xvc2UoKTtcbiAgICAgIGlmIChhdHRlbXB0ID4gMCkge1xuICAgICAgICBsb2dnZXIuaW5mbyhgW1BvcnREaXNjb3ZlcnldIFBvcnQgJHtzdGFydFBvcnR9IGluIHVzZSwgdXNpbmcgJHtwb3J0fSAoYWZ0ZXIgJHthdHRlbXB0fSBza2lwJHthdHRlbXB0ID4gMSA/ICdzJyA6ICcnfSlgKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBwb3J0O1xuICAgIH1cbiAgICBsb2dnZXIuZGVidWcoYFtQb3J0RGlzY292ZXJ5XSBQb3J0ICR7cG9ydH0gaW4gdXNlLCB0cnlpbmcgbmV4dGApO1xuICB9XG5cbiAgdGhyb3cgbmV3IEVycm9yKFxuICAgIGBObyBhdmFpbGFibGUgcG9ydCBmb3VuZCBpbiByYW5nZSAke3N0YXJ0UG9ydH0tJHtzdGFydFBvcnQgKyBtYXhBdHRlbXB0cyAtIDF9IGAgK1xuICAgIGBhZnRlciAke21heEF0dGVtcHRzfSBhdHRlbXB0cy4gU2V0IERPTExIT1VTRV9NQVhfUE9SVF9BVFRFTVBUUyB0byBpbmNyZWFzZSB0aGUgcmFuZ2UuYFxuICApO1xufVxuXG4vKipcbiAqIFdyaXRlIHRoZSBhY3RpdmUgc2VydmVyIHBvcnQgdG8gYSBkaXNjb3ZlcmFibGUgZmlsZS5cbiAqXG4gKiBDcmVhdGVzIHR3byBmaWxlczpcbiAqIC0gUElELWtleWVkIGZpbGUgZm9yIHBlci1wcm9jZXNzIGNsZWFudXBcbiAqIC0gTGF0ZXN0IGZpbGUgZm9yIGNvbnZlbmllbmNlIChzY3JpcHRzIGNhbiByZWFkIHRoaXMgd2l0aG91dCBrbm93aW5nIHRoZSBQSUQpXG4gKlxuICogQHBhcmFtIHBvcnQgLSBUaGUgcG9ydCB0aGUgd2ViIHNlcnZlciBpcyBsaXN0ZW5pbmcgb25cbiAqIEByZXR1cm5zIFBhdGggdG8gdGhlIFBJRC1rZXllZCBwb3J0IGZpbGVcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHdyaXRlUG9ydEZpbGUocG9ydDogbnVtYmVyKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgYXdhaXQgbWtkaXIoUlVOX0RJUiwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gIGNvbnN0IHBpZEZpbGUgPSBwaWRQb3J0RmlsZVBhdGgoKTtcbiAgY29uc3QgbGF0ZXN0RmlsZSA9IGxhdGVzdFBvcnRGaWxlUGF0aCgpO1xuICBhd2FpdCB3cml0ZUZpbGUocGlkRmlsZSwgU3RyaW5nKHBvcnQpLCAndXRmLTgnKTtcbiAgYXdhaXQgd3JpdGVGaWxlKGxhdGVzdEZpbGUsIFN0cmluZyhwb3J0KSwgJ3V0Zi04Jyk7XG4gIHBvcnRGaWxlUGF0aCA9IHBpZEZpbGU7XG4gIGxvZ2dlci5kZWJ1ZyhgW1BvcnREaXNjb3ZlcnldIFBvcnQgZmlsZSB3cml0dGVuOiAke3BpZEZpbGV9YCk7XG4gIHJldHVybiBwaWRGaWxlO1xufVxuXG4vKipcbiAqIFJlc3RvcmUgb3IgdXBkYXRlIHRoZSBzaGFyZWQgbGF0ZXN0IHBvcnQgZmlsZSBmb3IgdGhlIGFjdGl2ZSBsaXN0ZW5lci5cbiAqXG4gKiBSZXR1cm5zIHRydWUgd2hlbiB0aGUgZmlsZSBoYWQgdG8gYmUgd3JpdHRlbiBvciB1cGRhdGVkLCBmYWxzZSB3aGVuIGl0XG4gKiBhbHJlYWR5IG1hdGNoZWQgdGhlIGFjdGl2ZSBwb3J0LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZW5zdXJlTGF0ZXN0UG9ydEZpbGUocG9ydDogbnVtYmVyLCBjdXN0b21EaXI/OiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgY29uc3QgZGlyID0gY3VzdG9tRGlyIHx8IFJVTl9ESVI7XG4gIGNvbnN0IGxhdGVzdEZpbGUgPSBsYXRlc3RQb3J0RmlsZVBhdGgoZGlyKTtcbiAgY29uc3QgZGVzaXJlZFZhbHVlID0gU3RyaW5nKHBvcnQpO1xuXG4gIGF3YWl0IG1rZGlyKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG5cbiAgdHJ5IHtcbiAgICBjb25zdCBleGlzdGluZ1ZhbHVlID0gKGF3YWl0IHJlYWRGaWxlKGxhdGVzdEZpbGUsICd1dGYtOCcpKS50cmltKCk7XG4gICAgaWYgKGV4aXN0aW5nVmFsdWUgPT09IGRlc2lyZWRWYWx1ZSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfSBjYXRjaCB7XG4gICAgLy8gTWlzc2luZy91bnJlYWRhYmxlIGZpbGUg4oCUIHJld3JpdGUgYmVsb3cuXG4gIH1cblxuICBhd2FpdCB3cml0ZUZpbGUobGF0ZXN0RmlsZSwgZGVzaXJlZFZhbHVlLCAndXRmLTgnKTtcbiAgbG9nZ2VyLmRlYnVnKGBbUG9ydERpc2NvdmVyeV0gU2hhcmVkIGxhdGVzdCBwb3J0IGZpbGUgcmVmcmVzaGVkOiAke2xhdGVzdEZpbGV9YCk7XG4gIHJldHVybiB0cnVlO1xufVxuXG4vKipcbiAqIENsZWFuIHVwIHBvcnQgZmlsZSBvbiBzaHV0ZG93bi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNsZWFudXBQb3J0RmlsZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgaWYgKHBvcnRGaWxlUGF0aCkge1xuICAgIHRyeSB7IGF3YWl0IHVubGluayhwb3J0RmlsZVBhdGgpOyB9IGNhdGNoIHsgLyogYWxyZWFkeSBnb25lICovIH1cbiAgfVxufVxuXG4vKipcbiAqIFJlZ2lzdGVyIHByb2Nlc3MgZXhpdCBoYW5kbGVycyB0byBjbGVhbiB1cCBwb3J0IGZpbGVzLlxuICogU2hvdWxkIGJlIGNhbGxlZCBvbmNlIGFmdGVyIHRoZSB3ZWIgc2VydmVyIHN1Y2Nlc3NmdWxseSBzdGFydHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWdpc3RlclBvcnRDbGVhbnVwKCk6IHZvaWQge1xuICBjb25zdCBleGl0Q2xlYW51cCA9ICgpID0+IHsgY2xlYW51cFBvcnRGaWxlKCkuY2F0Y2goKCkgPT4ge30pOyB9O1xuICBwcm9jZXNzLm9uY2UoJ2V4aXQnLCBleGl0Q2xlYW51cCk7XG4gIHByb2Nlc3Mub25jZSgnU0lHVEVSTScsIGV4aXRDbGVhbnVwKTtcbiAgcHJvY2Vzcy5vbmNlKCdTSUdJTlQnLCBleGl0Q2xlYW51cCk7XG4gIHByb2Nlc3Mub25jZSgnU0lHSFVQJywgZXhpdENsZWFudXApO1xufVxuXG4vKipcbiAqIFN3ZWVwIHN0YWxlIHBvcnQgZmlsZXMgZnJvbSB+Ly5kb2xsaG91c2UvcnVuLyBvbiBzdGFydHVwICgjMTg1NikuXG4gKlxuICogU2NhbnMgZm9yIHBlcm1pc3Npb24tc2VydmVyLXtwaWR9LnBvcnQgZmlsZXMgYW5kIHJlbW92ZXMgYW55IHdob3NlIFBJRFxuICogaXMgbm8gbG9uZ2VyIGFsaXZlLiBUaGlzIHByZXZlbnRzIHVuYm91bmRlZCBhY2N1bXVsYXRpb24gb2YgcG9ydCBmaWxlc1xuICogZnJvbSBjcmFzaGVkIG9yIGFiYW5kb25lZCBzZXNzaW9ucy5cbiAqXG4gKiBOb3RlOiBUaGlzIG9ubHkgcmVtb3ZlcyBtZXRhZGF0YSBmaWxlcywgbm90IHByb2Nlc3NlcyBvciBwb3J0cy4gVGhlIHBvcnRcbiAqIGJpbmRpbmcgaXRzZWxmIChpbiBiaW5kQW5kTGlzdGVuKSBpcyBhdG9taWMgdmlhIGxpc3RlbigpLiBUaGVyZSBpcyBubyByYWNlXG4gKiBiZXR3ZWVuIHN3ZWVwaW5nIGZpbGVzIGFuZCBiaW5kaW5nIOKAlCB0aGV5IG9wZXJhdGUgb24gaW5kZXBlbmRlbnQgcmVzb3VyY2VzLlxuICpcbiAqIFNhZmUgdG8gY2FsbCBvbiBldmVyeSBzdGFydHVwIOKAlCBvbmx5IHJlbW92ZXMgZmlsZXMgZm9yIGRlYWQgcHJvY2Vzc2VzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc3dlZXBTdGFsZVBvcnRGaWxlcyhjdXN0b21EaXI/OiBzdHJpbmcpOiBQcm9taXNlPG51bWJlcj4ge1xuICB0cnkge1xuICAgIGNvbnN0IGRpciA9IGN1c3RvbURpciB8fCBSVU5fRElSO1xuICAgIGF3YWl0IG1rZGlyKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgY29uc3QgZmlsZXMgPSBhd2FpdCByZWFkZGlyKGRpcik7XG4gICAgY29uc3QgUE9SVF9GSUxFX1JFID0gL15wZXJtaXNzaW9uLXNlcnZlci0oXFxkKylcXC5wb3J0JC87XG4gICAgbGV0IHJlbW92ZWQgPSAwO1xuXG4gICAgZm9yIChjb25zdCBmaWxlIG9mIGZpbGVzKSB7XG4gICAgICBjb25zdCBtYXRjaCA9IFBPUlRfRklMRV9SRS5leGVjKGZpbGUpO1xuICAgICAgaWYgKCFtYXRjaCkgY29udGludWU7XG4gICAgICBjb25zdCBwaWQgPSBOdW1iZXIobWF0Y2hbMV0pO1xuXG4gICAgICAvLyBDaGVjayBpZiBwcm9jZXNzIGlzIGFsaXZlIHZpYSBzaWduYWwtMC5cbiAgICAgIC8vIEVTUkNIID0gcHJvY2VzcyBkb2Vzbid0IGV4aXN0IChkZWFkKS4gRVBFUk0gPSBwcm9jZXNzIGV4aXN0cyBidXRcbiAgICAgIC8vIG93bmVkIGJ5IGFub3RoZXIgdXNlciAoYWxpdmUg4oCUIGRvbid0IHRvdWNoIHRoZWlyIHBvcnQgZmlsZSkuXG4gICAgICBsZXQgYWxpdmUgPSBmYWxzZTtcbiAgICAgIHRyeSB7XG4gICAgICAgIHByb2Nlc3Mua2lsbChwaWQsIDApO1xuICAgICAgICBhbGl2ZSA9IHRydWU7XG4gICAgICB9IGNhdGNoIChlcnI6IGFueSkge1xuICAgICAgICBhbGl2ZSA9IGVycj8uY29kZSA9PT0gJ0VQRVJNJzsgLy8gRVBFUk0gPSBhbGl2ZSBidXQgZGlmZmVyZW50IHVzZXJcbiAgICAgIH1cblxuICAgICAgaWYgKCFhbGl2ZSkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGF3YWl0IHVubGluayhqb2luKGRpciwgZmlsZSkpO1xuICAgICAgICAgIHJlbW92ZWQrKztcbiAgICAgICAgfSBjYXRjaCB7IC8qIGFscmVhZHkgZ29uZSAqLyB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHJlbW92ZWQgPiAwKSB7XG4gICAgICBsb2dnZXIuaW5mbyhgW1BvcnREaXNjb3ZlcnldIFN3ZXB0ICR7cmVtb3ZlZH0gc3RhbGUgcG9ydCBmaWxlcyBmcm9tICR7ZGlyfWApO1xuICAgIH1cbiAgICByZXR1cm4gcmVtb3ZlZDtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgbG9nZ2VyLmRlYnVnKCdbUG9ydERpc2NvdmVyeV0gU3RhbGUgcG9ydCBmaWxlIHN3ZWVwIGZhaWxlZCcsIHtcbiAgICAgIGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVyciksXG4gICAgfSk7XG4gICAgcmV0dXJuIDA7XG4gIH1cbn1cblxuLyoqXG4gKiBEaXNjb3ZlciBhbiBhdmFpbGFibGUgcG9ydCwgd3JpdGUgZGlzY292ZXJ5IGZpbGVzLCBhbmQgcmVnaXN0ZXIgY2xlYW51cC5cbiAqXG4gKiBUaGlzIGlzIHRoZSByZWNvbW1lbmRlZCBlbnRyeSBwb2ludCBmb3IgQ29udGFpbmVyIHN0YXJ0dXAuIENvbWJpbmVzXG4gKiBwb3J0IGRpc2NvdmVyeSwgZmlsZSB3cml0aW5nLCBhbmQgY2xlYW51cCByZWdpc3RyYXRpb24gaW4gb25lIGNhbGwuXG4gKlxuICogQHBhcmFtIGRlZmF1bHRQb3J0IC0gUG9ydCB0byB0cnkgZmlyc3QgKHNlZSBgRE9MTEhPVVNFX1dFQl9DT05TT0xFX1BPUlRgIGZvciB0aGUgZGVmYXVsdClcbiAqIEByZXR1cm5zIFRoZSBwb3J0IHRoZSBzZXJ2ZXIgc2hvdWxkIGJpbmQgdG8sIG9yIHVuZGVmaW5lZCBpZiBkaXNjb3ZlcnkgZmFpbGVkXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBkaXNjb3ZlckFuZEJpbmRQb3J0KFxuICBkZWZhdWx0UG9ydDogbnVtYmVyID0gZW52LkRPTExIT1VTRV9XRUJfQ09OU09MRV9QT1JULFxuKTogUHJvbWlzZTxudW1iZXIgfCB1bmRlZmluZWQ+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBwb3J0ID0gYXdhaXQgZmluZEF2YWlsYWJsZVBvcnQoZGVmYXVsdFBvcnQpO1xuXG4gICAgYXdhaXQgd3JpdGVQb3J0RmlsZShwb3J0KTtcbiAgICByZWdpc3RlclBvcnRDbGVhbnVwKCk7XG5cbiAgICByZXR1cm4gcG9ydDtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgbG9nZ2VyLndhcm4oJ1tQb3J0RGlzY292ZXJ5XSBQb3J0IGRpc2NvdmVyeSBmYWlsZWQ6JywgZXJyKTtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG59XG4iXX0=