UNPKG

@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.

428 lines 54.9 kB
/** * Leader election for the unified web console. * * When multiple MCP server instances run concurrently, only one should host * the web console (the "leader"). Others become "followers" that forward * events to the leader. This module handles: * * 1. Reading/writing a leader lock file at ~/.dollhouse/run/console-leader.lock * 2. Atomic claim via temp+rename to prevent TOCTOU races * 3. PID-based stale detection (signal-0 liveness check) * 4. Heartbeat updates (10s interval) so followers can detect hung leaders * 5. Cleanup on process exit * * The configured port binding is the ultimate tiebreaker: even if two * processes both write the lock file, only one can bind the port (see * `DOLLHOUSE_WEB_CONSOLE_PORT` in `src/config/env.ts`). * * @since v2.1.0 — Issue #1700 */ import { homedir } from 'node:os'; import { join } from 'node:path'; import { mkdir, readFile, writeFile, rename, unlink } from 'node:fs/promises'; import { UnicodeValidator } from '../../security/validators/unicodeValidator.js'; import { env } from '../../config/env.js'; import { PACKAGE_VERSION } from '../../generated/version.js'; import { logger } from '../../utils/logger.js'; import { compareVersions } from '../../utils/version.js'; /** Directory for runtime state files */ const RUN_DIR = join(homedir(), '.dollhouse', 'run'); /** * Built-in default filename for the authenticated console's leader lock. * * The `.auth` suffix isolates this from any legacy no-authentication * DollhouseMCP installation that may also be running on the same * machine. Those older installs use `console-leader.lock` (no suffix); * the authenticated console uses `console-leader.auth.lock`. Combined * with the port separation, this means the two generations of the * console can coexist with zero interference — different port, different * lock file, different token file, independent leader election spaces. */ const DEFAULT_LOCK_FILENAME = 'console-leader.auth.lock'; /** Legacy lock filename from the pre-authentication console. Used only for detection. */ const LEGACY_LOCK_FILENAME = 'console-leader.lock'; /** * Path to the leader lock file. Prefers the `DOLLHOUSE_CONSOLE_LEADER_LOCK_FILE` * env var when set, otherwise uses `DEFAULT_LOCK_FILENAME` under RUN_DIR. * The env var is the single source of truth when present, so a deployment * can relocate the lock without code changes (see `src/config/env.ts`). */ const LOCK_FILE = env.DOLLHOUSE_CONSOLE_LEADER_LOCK_FILE ?? join(RUN_DIR, DEFAULT_LOCK_FILENAME); /** Path to the legacy pre-auth lock file (used by `detectLegacyLeader` only). */ const LEGACY_LOCK_FILE = join(RUN_DIR, LEGACY_LOCK_FILENAME); /** How often the leader updates its heartbeat (ms) */ const HEARTBEAT_INTERVAL_MS = 10_000; /** How long before a heartbeat is considered stale (ms) */ const HEARTBEAT_STALE_MS = 30_000; /** Current lock file schema version */ export const LOCK_VERSION = 1; /** * Version of the leader-election/session metadata contract used by the * authenticated web console. Older leaders will not have this field. */ export const CONSOLE_PROTOCOL_VERSION = 1; /** Missing protocol metadata means the leader predates version-aware election. */ export const LEGACY_CONSOLE_PROTOCOL_VERSION = 0; /** Old lock files do not carry package version metadata. Treat them as oldest. */ export const LEGACY_SERVER_VERSION = '0.0.0'; /** * Check whether a process with the given PID is alive. * Uses signal 0 which checks existence without sending a signal. */ export function isProcessAlive(pid) { try { process.kill(pid, 0); return true; } catch (err) { // EPERM = process exists but owned by another user — still alive return err?.code === 'EPERM'; } } /** * Normalize the server version present in the leader lock. * Missing metadata means "legacy leader" for election purposes. */ export function getLeaderServerVersion(info) { if (typeof info.serverVersion === 'string' && info.serverVersion.trim().length > 0) { return info.serverVersion.trim(); } return LEGACY_SERVER_VERSION; } /** * Normalize the console protocol version present in the leader lock. * Missing metadata means a leader from before version-aware election. */ export function getLeaderConsoleProtocolVersion(info) { const raw = info.consoleProtocolVersion; if (typeof raw === 'number' && Number.isInteger(raw) && raw >= 0) { return raw; } return LEGACY_CONSOLE_PROTOCOL_VERSION; } /** * Create this process's leader metadata in one place so all leadership paths * publish the same version and protocol information. */ export function createLeaderInfo(sessionId, port) { const now = new Date().toISOString(); return { version: LOCK_VERSION, pid: process.pid, port, sessionId: UnicodeValidator.normalize(sessionId).normalizedContent, startedAt: now, heartbeat: now, serverVersion: PACKAGE_VERSION, consoleProtocolVersion: CONSOLE_PROTOCOL_VERSION, }; } /** * Decide whether this process should replace the current live leader based on * compatibility first, then package version. */ export function evaluateLeaderPreference(candidate, existing) { const candidateVersion = getLeaderServerVersion(candidate); const existingVersion = getLeaderServerVersion(existing); const candidateProtocolVersion = getLeaderConsoleProtocolVersion(candidate); const existingProtocolVersion = getLeaderConsoleProtocolVersion(existing); const compatible = existingProtocolVersion === candidateProtocolVersion || existingProtocolVersion === LEGACY_CONSOLE_PROTOCOL_VERSION; if (!compatible) { return { shouldReplace: false, reason: 'incompatible-protocol', candidateVersion, existingVersion, candidateProtocolVersion, existingProtocolVersion, }; } const versionComparison = compareVersions(candidateVersion, existingVersion); if (versionComparison > 0) { return { shouldReplace: true, reason: 'newer-compatible-version', candidateVersion, existingVersion, candidateProtocolVersion, existingProtocolVersion, }; } if (versionComparison === 0) { return { shouldReplace: false, reason: 'same-version', candidateVersion, existingVersion, candidateProtocolVersion, existingProtocolVersion, }; } return { shouldReplace: false, reason: 'older-version', candidateVersion, existingVersion, candidateProtocolVersion, existingProtocolVersion, }; } /** * Detect whether a legacy (pre-authentication) DollhouseMCP console is * currently running on this machine (#1794). * * The pre-authentication console writes its lock to * `~/.dollhouse/run/console-leader.lock` (no `.auth` suffix). An * authenticated console on a different port will not interfere with * it — they have fully independent ports, lock files, and token files — * but the user probably wants to know the two exist simultaneously * because the security posture of each console is different. * * Returns info about the legacy leader if one is detected, or * `{ legacyRunning: false }` otherwise. * * @param lockPath - Optional override for the legacy lock file path. * Defaults to the built-in legacy location. Primarily * used by tests to point at a temp directory. */ export async function detectLegacyLeader(lockPath = LEGACY_LOCK_FILE) { try { const content = await readFile(lockPath, 'utf-8'); const data = JSON.parse(content); if (!data.pid || !isProcessAlive(data.pid)) { return { legacyRunning: false, lockPath }; } return { legacyRunning: true, pid: data.pid, port: data.port, lockPath, }; } catch { // File missing, unreadable, or invalid JSON — no legacy leader detected return { legacyRunning: false, lockPath }; } } /** * Read and parse the leader lock file. * Returns null if the file doesn't exist, is unreadable, or has invalid content. */ export async function readLeaderLock() { try { const content = await readFile(LOCK_FILE, 'utf-8'); const data = JSON.parse(content); if (data.version !== LOCK_VERSION || !data.pid || !data.port || !data.sessionId) { return null; } return data; } catch (error) { if (error.code !== 'ENOENT') { logger.debug('[LeaderElection] Ignoring unreadable or invalid leader lock', { lockFile: LOCK_FILE, error: error instanceof Error ? error.message : String(error), }); } return null; } } /** * Check if a leader lock is stale (dead process or expired heartbeat). */ export function isLockStale(info) { if (!isProcessAlive(info.pid)) { return true; } const heartbeatAge = Date.now() - new Date(info.heartbeat).getTime(); return heartbeatAge > HEARTBEAT_STALE_MS; } /** * Attempt to atomically claim leadership. * * Writes to a temp file then renames to the lock path. On POSIX systems * rename is atomic, so only one writer wins. After renaming, re-reads the * lock to verify our PID won. * * @returns true if this process successfully claimed leadership */ export async function claimLeadership(info) { await mkdir(RUN_DIR, { recursive: true }); const tmpFile = join(RUN_DIR, `console-leader.lock.${process.pid}.tmp`); try { await writeFile(tmpFile, JSON.stringify(info, null, 2), 'utf-8'); await rename(tmpFile, LOCK_FILE); // Verify we won the race const written = await readLeaderLock(); return written !== null && written.pid === info.pid; } catch { // Clean up temp file on failure try { await unlink(tmpFile); } catch { /* ignore */ } return false; } } /** * Delete the leader lock file (for cleanup or takeover). */ export async function deleteLeaderLock() { try { await unlink(LOCK_FILE); } catch { /* already gone */ } } /** * Run the leader election protocol. * * 1. If no lock exists or lock is stale → claim leadership * 2. If lock exists with a live, responsive leader → become follower * * @param sessionId - This process's unique session identifier * @param port - The port this process would use as leader (see `DOLLHOUSE_WEB_CONSOLE_PORT`) * @returns Election result with role and leader info */ export async function electLeader(sessionId, port) { const myInfo = createLeaderInfo(sessionId, port); sessionId = myInfo.sessionId; const existingLock = await readLeaderLock(); if (existingLock && !isLockStale(existingLock)) { const preference = evaluateLeaderPreference(myInfo, existingLock); if (preference.shouldReplace) { logger.info('[LeaderElection] Replacing leader with newer compatible version', { staleSession: existingLock.sessionId, stalePid: existingLock.pid, stalePort: existingLock.port, staleVersion: preference.existingVersion, staleProtocolVersion: preference.existingProtocolVersion, mySession: sessionId, myPid: process.pid, myVersion: preference.candidateVersion, myProtocolVersion: preference.candidateProtocolVersion, }); await deleteLeaderLock(); } else { logger.info('[LeaderElection] Existing leader found — becoming follower', { leaderSession: existingLock.sessionId, leaderPid: existingLock.pid, leaderPort: existingLock.port, leaderVersion: preference.existingVersion, leaderProtocolVersion: preference.existingProtocolVersion, mySession: sessionId, myPid: process.pid, myVersion: preference.candidateVersion, myProtocolVersion: preference.candidateProtocolVersion, reason: preference.reason, }); return { role: 'follower', leaderInfo: existingLock }; } } if (existingLock && !isLockStale(existingLock)) { // Leader was intentionally replaced above. Continue to the claim path. } else if (existingLock) { // No valid leader — try to claim const alive = isProcessAlive(existingLock.pid); const heartbeatAge = Date.now() - new Date(existingLock.heartbeat).getTime(); logger.info('[LeaderElection] Stale leader lock — taking over', { stalePid: existingLock.pid, alive, heartbeatAgeMs: heartbeatAge, staleSession: existingLock.sessionId, mySession: sessionId, }); await deleteLeaderLock(); } const claimed = await claimLeadership(myInfo); if (claimed) { logger.info('[LeaderElection] Claimed leadership', { sessionId, port, pid: process.pid }); return { role: 'leader', leaderInfo: myInfo }; } // Another process won the race — re-read and become follower const winner = await readLeaderLock(); if (winner) { logger.info('[LeaderElection] Lost election — becoming follower', { winnerPid: winner.pid, winnerSession: winner.sessionId, mySession: sessionId, myPid: process.pid, }); return { role: 'follower', leaderInfo: winner }; } // Extremely unlikely: lock disappeared between our claim and re-read. Retry once. logger.warn('[LeaderElection] Lock vanished after failed claim. Retrying.'); const retryInfo = { ...createLeaderInfo(sessionId, port) }; const retryClaimed = await claimLeadership(retryInfo); if (retryClaimed) { return { role: 'leader', leaderInfo: retryInfo }; } const actualLeader = await readLeaderLock(); return { role: 'follower', leaderInfo: actualLeader ?? retryInfo }; } /** * Probe whether the leader's web console is reachable. * Returns true if the leader's ingest endpoint responds, false otherwise. */ export async function isLeaderWebConsoleReachable(leaderInfo) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 2_000); const res = await fetch(`http://127.0.0.1:${leaderInfo.port}/api/logs/stats`, { signal: controller.signal, }); clearTimeout(timeout); return res.ok; } catch { return false; } } /** * Force claim leadership by deleting the existing lock and claiming. * Used when the existing leader is alive but not running a web console. */ export async function forceClaimLeadership(sessionId, port) { logger.info('[LeaderElection] Forcing leadership takeover — existing leader has no web console'); await deleteLeaderLock(); const myInfo = createLeaderInfo(sessionId, port); const claimed = await claimLeadership(myInfo); if (claimed) { logger.info('[LeaderElection] Forced leadership claimed', { sessionId, port, pid: process.pid }); return { role: 'leader', leaderInfo: myInfo }; } // Failed — fall back to follower const winner = await readLeaderLock(); return { role: 'follower', leaderInfo: winner ?? myInfo }; } /** * Start the leader heartbeat loop. * Updates the lock file every HEARTBEAT_INTERVAL_MS so followers know the leader is alive. * * @returns A stop function to clear the interval */ export function startHeartbeat(info) { const interval = setInterval(async () => { try { const updated = { ...info, heartbeat: new Date().toISOString() }; const tmpFile = join(RUN_DIR, `console-leader.lock.${process.pid}.tmp`); await writeFile(tmpFile, JSON.stringify(updated, null, 2), 'utf-8'); await rename(tmpFile, LOCK_FILE); } catch (err) { logger.debug('[LeaderElection] Heartbeat write failed:', err); } }, HEARTBEAT_INTERVAL_MS); // Don't let the heartbeat interval keep the process alive interval.unref(); return () => clearInterval(interval); } /** * Register cleanup handlers to remove the leader lock on process exit. * Should only be called by the leader. */ export function registerLeaderCleanup() { const cleanup = () => { deleteLeaderLock().catch(() => { }); }; process.once('exit', cleanup); process.once('SIGTERM', cleanup); process.once('SIGINT', cleanup); process.once('SIGHUP', cleanup); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTGVhZGVyRWxlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvd2ViL2NvbnNvbGUvTGVhZGVyRWxlY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUVILE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDbEMsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNqQyxPQUFPLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzlFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLCtDQUErQyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUMxQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDN0QsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQy9DLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUV6RCx3Q0FBd0M7QUFDeEMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLFlBQVksRUFBRSxLQUFLLENBQUMsQ0FBQztBQUVyRDs7Ozs7Ozs7OztHQVVHO0FBQ0gsTUFBTSxxQkFBcUIsR0FBRywwQkFBMEIsQ0FBQztBQUV6RCx5RkFBeUY7QUFDekYsTUFBTSxvQkFBb0IsR0FBRyxxQkFBcUIsQ0FBQztBQUVuRDs7Ozs7R0FLRztBQUNILE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxrQ0FBa0MsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLHFCQUFxQixDQUFDLENBQUM7QUFFakcsaUZBQWlGO0FBQ2pGLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLE9BQU8sRUFBRSxvQkFBb0IsQ0FBQyxDQUFDO0FB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QyxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLHFCQUFxQjtJQUNuQyxNQUFNLE9BQU8sR0FBRyxHQUFHLEVBQUUsR0FBRyxnQkFBZ0IsRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsR0FBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5RCxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM5QixPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNqQyxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNoQyxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUNsQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBMZWFkZXIgZWxlY3Rpb24gZm9yIHRoZSB1bmlmaWVkIHdlYiBjb25zb2xlLlxuICpcbiAqIFdoZW4gbXVsdGlwbGUgTUNQIHNlcnZlciBpbnN0YW5jZXMgcnVuIGNvbmN1cnJlbnRseSwgb25seSBvbmUgc2hvdWxkIGhvc3RcbiAqIHRoZSB3ZWIgY29uc29sZSAodGhlIFwibGVhZGVyXCIpLiBPdGhlcnMgYmVjb21lIFwiZm9sbG93ZXJzXCIgdGhhdCBmb3J3YXJkXG4gKiBldmVudHMgdG8gdGhlIGxlYWRlci4gVGhpcyBtb2R1bGUgaGFuZGxlczpcbiAqXG4gKiAxLiBSZWFkaW5nL3dyaXRpbmcgYSBsZWFkZXIgbG9jayBmaWxlIGF0IH4vLmRvbGxob3VzZS9ydW4vY29uc29sZS1sZWFkZXIubG9ja1xuICogMi4gQXRvbWljIGNsYWltIHZpYSB0ZW1wK3JlbmFtZSB0byBwcmV2ZW50IFRPQ1RPVSByYWNlc1xuICogMy4gUElELWJhc2VkIHN0YWxlIGRldGVjdGlvbiAoc2lnbmFsLTAgbGl2ZW5lc3MgY2hlY2spXG4gKiA0LiBIZWFydGJlYXQgdXBkYXRlcyAoMTBzIGludGVydmFsKSBzbyBmb2xsb3dlcnMgY2FuIGRldGVjdCBodW5nIGxlYWRlcnNcbiAqIDUuIENsZWFudXAgb24gcHJvY2VzcyBleGl0XG4gKlxuICogVGhlIGNvbmZpZ3VyZWQgcG9ydCBiaW5kaW5nIGlzIHRoZSB1bHRpbWF0ZSB0aWVicmVha2VyOiBldmVuIGlmIHR3b1xuICogcHJvY2Vzc2VzIGJvdGggd3JpdGUgdGhlIGxvY2sgZmlsZSwgb25seSBvbmUgY2FuIGJpbmQgdGhlIHBvcnQgKHNlZVxuICogYERPTExIT1VTRV9XRUJfQ09OU09MRV9QT1JUYCBpbiBgc3JjL2NvbmZpZy9lbnYudHNgKS5cbiAqXG4gKiBAc2luY2UgdjIuMS4wIOKAlCBJc3N1ZSAjMTcwMFxuICovXG5cbmltcG9ydCB7IGhvbWVkaXIgfSBmcm9tICdub2RlOm9zJztcbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgbWtkaXIsIHJlYWRGaWxlLCB3cml0ZUZpbGUsIHJlbmFtZSwgdW5saW5rIH0gZnJvbSAnbm9kZTpmcy9wcm9taXNlcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IGVudiB9IGZyb20gJy4uLy4uL2NvbmZpZy9lbnYuanMnO1xuaW1wb3J0IHsgUEFDS0FHRV9WRVJTSU9OIH0gZnJvbSAnLi4vLi4vZ2VuZXJhdGVkL3ZlcnNpb24uanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IGNvbXBhcmVWZXJzaW9ucyB9IGZyb20gJy4uLy4uL3V0aWxzL3ZlcnNpb24uanMnO1xuXG4vKiogRGlyZWN0b3J5IGZvciBydW50aW1lIHN0YXRlIGZpbGVzICovXG5jb25zdCBSVU5fRElSID0gam9pbihob21lZGlyKCksICcuZG9sbGhvdXNlJywgJ3J1bicpO1xuXG4vKipcbiAqIEJ1aWx0LWluIGRlZmF1bHQgZmlsZW5hbWUgZm9yIHRoZSBhdXRoZW50aWNhdGVkIGNvbnNvbGUncyBsZWFkZXIgbG9jay5cbiAqXG4gKiBUaGUgYC5hdXRoYCBzdWZmaXggaXNvbGF0ZXMgdGhpcyBmcm9tIGFueSBsZWdhY3kgbm8tYXV0aGVudGljYXRpb25cbiAqIERvbGxob3VzZU1DUCBpbnN0YWxsYXRpb24gdGhhdCBtYXkgYWxzbyBiZSBydW5uaW5nIG9uIHRoZSBzYW1lXG4gKiBtYWNoaW5lLiBUaG9zZSBvbGRlciBpbnN0YWxscyB1c2UgYGNvbnNvbGUtbGVhZGVyLmxvY2tgIChubyBzdWZmaXgpO1xuICogdGhlIGF1dGhlbnRpY2F0ZWQgY29uc29sZSB1c2VzIGBjb25zb2xlLWxlYWRlci5hdXRoLmxvY2tgLiBDb21iaW5lZFxuICogd2l0aCB0aGUgcG9ydCBzZXBhcmF0aW9uLCB0aGlzIG1lYW5zIHRoZSB0d28gZ2VuZXJhdGlvbnMgb2YgdGhlXG4gKiBjb25zb2xlIGNhbiBjb2V4aXN0IHdpdGggemVybyBpbnRlcmZlcmVuY2Ug4oCUIGRpZmZlcmVudCBwb3J0LCBkaWZmZXJlbnRcbiAqIGxvY2sgZmlsZSwgZGlmZmVyZW50IHRva2VuIGZpbGUsIGluZGVwZW5kZW50IGxlYWRlciBlbGVjdGlvbiBzcGFjZXMuXG4gKi9cbmNvbnN0IERFRkFVTFRfTE9DS19GSUxFTkFNRSA9ICdjb25zb2xlLWxlYWRlci5hdXRoLmxvY2snO1xuXG4vKiogTGVnYWN5IGxvY2sgZmlsZW5hbWUgZnJvbSB0aGUgcHJlLWF1dGhlbnRpY2F0aW9uIGNvbnNvbGUuIFVzZWQgb25seSBmb3IgZGV0ZWN0aW9uLiAqL1xuY29uc3QgTEVHQUNZX0xPQ0tfRklMRU5BTUUgPSAnY29uc29sZS1sZWFkZXIubG9jayc7XG5cbi8qKlxuICogUGF0aCB0byB0aGUgbGVhZGVyIGxvY2sgZmlsZS4gUHJlZmVycyB0aGUgYERPTExIT1VTRV9DT05TT0xFX0xFQURFUl9MT0NLX0ZJTEVgXG4gKiBlbnYgdmFyIHdoZW4gc2V0LCBvdGhlcndpc2UgdXNlcyBgREVGQVVMVF9MT0NLX0ZJTEVOQU1FYCB1bmRlciBSVU5fRElSLlxuICogVGhlIGVudiB2YXIgaXMgdGhlIHNpbmdsZSBzb3VyY2Ugb2YgdHJ1dGggd2hlbiBwcmVzZW50LCBzbyBhIGRlcGxveW1lbnRcbiAqIGNhbiByZWxvY2F0ZSB0aGUgbG9jayB3aXRob3V0IGNvZGUgY2hhbmdlcyAoc2VlIGBzcmMvY29uZmlnL2Vudi50c2ApLlxuICovXG5jb25zdCBMT0NLX0ZJTEUgPSBlbnYuRE9MTEhPVVNFX0NPTlNPTEVfTEVBREVSX0xPQ0tfRklMRSA/PyBqb2luKFJVTl9ESVIsIERFRkFVTFRfTE9DS19GSUxFTkFNRSk7XG5cbi8qKiBQYXRoIHRvIHRoZSBsZWdhY3kgcHJlLWF1dGggbG9jayBmaWxlICh1c2VkIGJ5IGBkZXRlY3RMZWdhY3lMZWFkZXJgIG9ubHkpLiAqL1xuY29uc3QgTEVHQUNZX0xPQ0tfRklMRSA9IGpvaW4oUlVOX0RJUiwgTEVHQUNZX0xPQ0tfRklMRU5BTUUpO1xuXG4vKiogSG93IG9mdGVuIHRoZSBsZWFkZXIgdXBkYXRlcyBpdHMgaGVhcnRiZWF0IChtcykgKi9cbmNvbnN0IEhFQVJUQkVBVF9JTlRFUlZBTF9NUyA9IDEwXzAwMDtcblxuLyoqIEhvdyBsb25nIGJlZm9yZSBhIGhlYXJ0YmVhdCBpcyBjb25zaWRlcmVkIHN0YWxlIChtcykgKi9cbmNvbnN0IEhFQVJUQkVBVF9TVEFMRV9NUyA9IDMwXzAwMDtcblxuLyoqIEN1cnJlbnQgbG9jayBmaWxlIHNjaGVtYSB2ZXJzaW9uICovXG5leHBvcnQgY29uc3QgTE9DS19WRVJTSU9OID0gMTtcblxuLyoqXG4gKiBWZXJzaW9uIG9mIHRoZSBsZWFkZXItZWxlY3Rpb24vc2Vzc2lvbiBtZXRhZGF0YSBjb250cmFjdCB1c2VkIGJ5IHRoZVxuICogYXV0aGVudGljYXRlZCB3ZWIgY29uc29sZS4gT2xkZXIgbGVhZGVycyB3aWxsIG5vdCBoYXZlIHRoaXMgZmllbGQuXG4gKi9cbmV4cG9ydCBjb25zdCBDT05TT0xFX1BST1RPQ09MX1ZFUlNJT04gPSAxO1xuXG4vKiogTWlzc2luZyBwcm90b2NvbCBtZXRhZGF0YSBtZWFucyB0aGUgbGVhZGVyIHByZWRhdGVzIHZlcnNpb24tYXdhcmUgZWxlY3Rpb24uICovXG5leHBvcnQgY29uc3QgTEVHQUNZX0NPTlNPTEVfUFJPVE9DT0xfVkVSU0lPTiA9IDA7XG5cbi8qKiBPbGQgbG9jayBmaWxlcyBkbyBub3QgY2FycnkgcGFja2FnZSB2ZXJzaW9uIG1ldGFkYXRhLiBUcmVhdCB0aGVtIGFzIG9sZGVzdC4gKi9cbmV4cG9ydCBjb25zdCBMRUdBQ1lfU0VSVkVSX1ZFUlNJT04gPSAnMC4wLjAnO1xuXG4vKipcbiAqIEluZm9ybWF0aW9uIHN0b3JlZCBpbiB0aGUgbGVhZGVyIGxvY2sgZmlsZS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDb25zb2xlTGVhZGVySW5mbyB7XG4gIHZlcnNpb246IG51bWJlcjtcbiAgcGlkOiBudW1iZXI7XG4gIHBvcnQ6IG51bWJlcjtcbiAgc2Vzc2lvbklkOiBzdHJpbmc7XG4gIHN0YXJ0ZWRBdDogc3RyaW5nO1xuICBoZWFydGJlYXQ6IHN0cmluZztcbiAgc2VydmVyVmVyc2lvbj86IHN0cmluZztcbiAgY29uc29sZVByb3RvY29sVmVyc2lvbj86IG51bWJlcjtcbn1cblxuLyoqXG4gKiBSZXN1bHQgb2YgYSBsZWFkZXIgZWxlY3Rpb24gYXR0ZW1wdC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBFbGVjdGlvblJlc3VsdCB7XG4gIHJvbGU6ICdsZWFkZXInIHwgJ2ZvbGxvd2VyJztcbiAgLyoqIExlYWRlciBpbmZvIOKAlCBmb3IgZm9sbG93ZXJzLCB0aGlzIGlzIHRoZSBleGlzdGluZyBsZWFkZXIncyBpbmZvICovXG4gIGxlYWRlckluZm86IENvbnNvbGVMZWFkZXJJbmZvO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIExlYWRlclByZWZlcmVuY2VEZWNpc2lvbiB7XG4gIHNob3VsZFJlcGxhY2U6IGJvb2xlYW47XG4gIHJlYXNvbjogJ25ld2VyLWNvbXBhdGlibGUtdmVyc2lvbicgfCAnc2FtZS12ZXJzaW9uJyB8ICdvbGRlci12ZXJzaW9uJyB8ICdpbmNvbXBhdGlibGUtcHJvdG9jb2wnO1xuICBjYW5kaWRhdGVWZXJzaW9uOiBzdHJpbmc7XG4gIGV4aXN0aW5nVmVyc2lvbjogc3RyaW5nO1xuICBjYW5kaWRhdGVQcm90b2NvbFZlcnNpb246IG51bWJlcjtcbiAgZXhpc3RpbmdQcm90b2NvbFZlcnNpb246IG51bWJlcjtcbn1cblxuLyoqXG4gKiBDaGVjayB3aGV0aGVyIGEgcHJvY2VzcyB3aXRoIHRoZSBnaXZlbiBQSUQgaXMgYWxpdmUuXG4gKiBVc2VzIHNpZ25hbCAwIHdoaWNoIGNoZWNrcyBleGlzdGVuY2Ugd2l0aG91dCBzZW5kaW5nIGEgc2lnbmFsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNQcm9jZXNzQWxpdmUocGlkOiBudW1iZXIpOiBib29sZWFuIHtcbiAgdHJ5IHtcbiAgICBwcm9jZXNzLmtpbGwocGlkLCAwKTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSBjYXRjaCAoZXJyOiBhbnkpIHtcbiAgICAvLyBFUEVSTSA9IHByb2Nlc3MgZXhpc3RzIGJ1dCBvd25lZCBieSBhbm90aGVyIHVzZXIg4oCUIHN0aWxsIGFsaXZlXG4gICAgcmV0dXJuIGVycj8uY29kZSA9PT0gJ0VQRVJNJztcbiAgfVxufVxuXG4vKipcbiAqIE5vcm1hbGl6ZSB0aGUgc2VydmVyIHZlcnNpb24gcHJlc2VudCBpbiB0aGUgbGVhZGVyIGxvY2suXG4gKiBNaXNzaW5nIG1ldGFkYXRhIG1lYW5zIFwibGVnYWN5IGxlYWRlclwiIGZvciBlbGVjdGlvbiBwdXJwb3Nlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldExlYWRlclNlcnZlclZlcnNpb24oaW5mbzogQ29uc29sZUxlYWRlckluZm8pOiBzdHJpbmcge1xuICBpZiAodHlwZW9mIGluZm8uc2VydmVyVmVyc2lvbiA9PT0gJ3N0cmluZycgJiYgaW5mby5zZXJ2ZXJWZXJzaW9uLnRyaW0oKS5sZW5ndGggPiAwKSB7XG4gICAgcmV0dXJuIGluZm8uc2VydmVyVmVyc2lvbi50cmltKCk7XG4gIH1cbiAgcmV0dXJuIExFR0FDWV9TRVJWRVJfVkVSU0lPTjtcbn1cblxuLyoqXG4gKiBOb3JtYWxpemUgdGhlIGNvbnNvbGUgcHJvdG9jb2wgdmVyc2lvbiBwcmVzZW50IGluIHRoZSBsZWFkZXIgbG9jay5cbiAqIE1pc3NpbmcgbWV0YWRhdGEgbWVhbnMgYSBsZWFkZXIgZnJvbSBiZWZvcmUgdmVyc2lvbi1hd2FyZSBlbGVjdGlvbi5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldExlYWRlckNvbnNvbGVQcm90b2NvbFZlcnNpb24oaW5mbzogQ29uc29sZUxlYWRlckluZm8pOiBudW1iZXIge1xuICBjb25zdCByYXcgPSBpbmZvLmNvbnNvbGVQcm90b2NvbFZlcnNpb247XG4gIGlmICh0eXBlb2YgcmF3ID09PSAnbnVtYmVyJyAmJiBOdW1iZXIuaXNJbnRlZ2VyKHJhdykgJiYgcmF3ID49IDApIHtcbiAgICByZXR1cm4gcmF3O1xuICB9XG4gIHJldHVybiBMRUdBQ1lfQ09OU09MRV9QUk9UT0NPTF9WRVJTSU9OO1xufVxuXG4vKipcbiAqIENyZWF0ZSB0aGlzIHByb2Nlc3MncyBsZWFkZXIgbWV0YWRhdGEgaW4gb25lIHBsYWNlIHNvIGFsbCBsZWFkZXJzaGlwIHBhdGhzXG4gKiBwdWJsaXNoIHRoZSBzYW1lIHZlcnNpb24gYW5kIHByb3RvY29sIGluZm9ybWF0aW9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlTGVhZGVySW5mbyhzZXNzaW9uSWQ6IHN0cmluZywgcG9ydDogbnVtYmVyKTogQ29uc29sZUxlYWRlckluZm8ge1xuICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCk7XG4gIHJldHVybiB7XG4gICAgdmVyc2lvbjogTE9DS19WRVJTSU9OLFxuICAgIHBpZDogcHJvY2Vzcy5waWQsXG4gICAgcG9ydCxcbiAgICBzZXNzaW9uSWQ6IFVuaWNvZGVWYWxpZGF0b3Iubm9ybWFsaXplKHNlc3Npb25JZCkubm9ybWFsaXplZENvbnRlbnQsXG4gICAgc3RhcnRlZEF0OiBub3csXG4gICAgaGVhcnRiZWF0OiBub3csXG4gICAgc2VydmVyVmVyc2lvbjogUEFDS0FHRV9WRVJTSU9OLFxuICAgIGNvbnNvbGVQcm90b2NvbFZlcnNpb246IENPTlNPTEVfUFJPVE9DT0xfVkVSU0lPTixcbiAgfTtcbn1cblxuLyoqXG4gKiBEZWNpZGUgd2hldGhlciB0aGlzIHByb2Nlc3Mgc2hvdWxkIHJlcGxhY2UgdGhlIGN1cnJlbnQgbGl2ZSBsZWFkZXIgYmFzZWQgb25cbiAqIGNvbXBhdGliaWxpdHkgZmlyc3QsIHRoZW4gcGFja2FnZSB2ZXJzaW9uLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZXZhbHVhdGVMZWFkZXJQcmVmZXJlbmNlKFxuICBjYW5kaWRhdGU6IENvbnNvbGVMZWFkZXJJbmZvLFxuICBleGlzdGluZzogQ29uc29sZUxlYWRlckluZm8sXG4pOiBMZWFkZXJQcmVmZXJlbmNlRGVjaXNpb24ge1xuICBjb25zdCBjYW5kaWRhdGVWZXJzaW9uID0gZ2V0TGVhZGVyU2VydmVyVmVyc2lvbihjYW5kaWRhdGUpO1xuICBjb25zdCBleGlzdGluZ1ZlcnNpb24gPSBnZXRMZWFkZXJTZXJ2ZXJWZXJzaW9uKGV4aXN0aW5nKTtcbiAgY29uc3QgY2FuZGlkYXRlUHJvdG9jb2xWZXJzaW9uID0gZ2V0TGVhZGVyQ29uc29sZVByb3RvY29sVmVyc2lvbihjYW5kaWRhdGUpO1xuICBjb25zdCBleGlzdGluZ1Byb3RvY29sVmVyc2lvbiA9IGdldExlYWRlckNvbnNvbGVQcm90b2NvbFZlcnNpb24oZXhpc3RpbmcpO1xuXG4gIGNvbnN0IGNvbXBhdGlibGUgPVxuICAgIGV4aXN0aW5nUHJvdG9jb2xWZXJzaW9uID09PSBjYW5kaWRhdGVQcm90b2NvbFZlcnNpb24gfHxcbiAgICBleGlzdGluZ1Byb3RvY29sVmVyc2lvbiA9PT0gTEVHQUNZX0NPTlNPTEVfUFJPVE9DT0xfVkVSU0lPTjtcblxuICBpZiAoIWNvbXBhdGlibGUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgc2hvdWxkUmVwbGFjZTogZmFsc2UsXG4gICAgICByZWFzb246ICdpbmNvbXBhdGlibGUtcHJvdG9jb2wnLFxuICAgICAgY2FuZGlkYXRlVmVyc2lvbixcbiAgICAgIGV4aXN0aW5nVmVyc2lvbixcbiAgICAgIGNhbmRpZGF0ZVByb3RvY29sVmVyc2lvbixcbiAgICAgIGV4aXN0aW5nUHJvdG9jb2xWZXJzaW9uLFxuICAgIH07XG4gIH1cblxuICBjb25zdCB2ZXJzaW9uQ29tcGFyaXNvbiA9IGNvbXBhcmVWZXJzaW9ucyhjYW5kaWRhdGVWZXJzaW9uLCBleGlzdGluZ1ZlcnNpb24pO1xuICBpZiAodmVyc2lvbkNvbXBhcmlzb24gPiAwKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHNob3VsZFJlcGxhY2U6IHRydWUsXG4gICAgICByZWFzb246ICduZXdlci1jb21wYXRpYmxlLXZlcnNpb24nLFxuICAgICAgY2FuZGlkYXRlVmVyc2lvbixcbiAgICAgIGV4aXN0aW5nVmVyc2lvbixcbiAgICAgIGNhbmRpZGF0ZVByb3RvY29sVmVyc2lvbixcbiAgICAgIGV4aXN0aW5nUHJvdG9jb2xWZXJzaW9uLFxuICAgIH07XG4gIH1cbiAgaWYgKHZlcnNpb25Db21wYXJpc29uID09PSAwKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHNob3VsZFJlcGxhY2U6IGZhbHNlLFxuICAgICAgcmVhc29uOiAnc2FtZS12ZXJzaW9uJyxcbiAgICAgIGNhbmRpZGF0ZVZlcnNpb24sXG4gICAgICBleGlzdGluZ1ZlcnNpb24sXG4gICAgICBjYW5kaWRhdGVQcm90b2NvbFZlcnNpb24sXG4gICAgICBleGlzdGluZ1Byb3RvY29sVmVyc2lvbixcbiAgICB9O1xuICB9XG4gIHJldHVybiB7XG4gICAgc2hvdWxkUmVwbGFjZTogZmFsc2UsXG4gICAgcmVhc29uOiAnb2xkZXItdmVyc2lvbicsXG4gICAgY2FuZGlkYXRlVmVyc2lvbixcbiAgICBleGlzdGluZ1ZlcnNpb24sXG4gICAgY2FuZGlkYXRlUHJvdG9jb2xWZXJzaW9uLFxuICAgIGV4aXN0aW5nUHJvdG9jb2xWZXJzaW9uLFxuICB9O1xufVxuXG4vKipcbiAqIFJlc3VsdCBvZiBhIGxlZ2FjeS1sZWFkZXIgZGV0ZWN0aW9uIHNjYW4uXG4gKiBgbGVnYWN5UnVubmluZyA9PT0gdHJ1ZWAgbWVhbnMgYSBwcmUtYXV0aGVudGljYXRpb24gRG9sbGhvdXNlTUNQIGNvbnNvbGVcbiAqIGlzIGN1cnJlbnRseSBydW5uaW5nIG9uIHRoaXMgbWFjaGluZSAoaXRzIGxvY2sgZmlsZSBleGlzdHMgYW5kIGl0cyBwaWRcbiAqIGlzIGFsaXZlKS4gQ2FsbGVycyBjYW4gc3VyZmFjZSB0aGlzIHRvIHRoZSB1c2VyIGFzIGEgd2FybmluZy5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBMZWdhY3lMZWFkZXJJbmZvIHtcbiAgbGVnYWN5UnVubmluZzogYm9vbGVhbjtcbiAgcGlkPzogbnVtYmVyO1xuICBwb3J0PzogbnVtYmVyO1xuICBsb2NrUGF0aDogc3RyaW5nO1xufVxuXG4vKipcbiAqIERldGVjdCB3aGV0aGVyIGEgbGVnYWN5IChwcmUtYXV0aGVudGljYXRpb24pIERvbGxob3VzZU1DUCBjb25zb2xlIGlzXG4gKiBjdXJyZW50bHkgcnVubmluZyBvbiB0aGlzIG1hY2hpbmUgKCMxNzk0KS5cbiAqXG4gKiBUaGUgcHJlLWF1dGhlbnRpY2F0aW9uIGNvbnNvbGUgd3JpdGVzIGl0cyBsb2NrIHRvXG4gKiBgfi8uZG9sbGhvdXNlL3J1bi9jb25zb2xlLWxlYWRlci5sb2NrYCAobm8gYC5hdXRoYCBzdWZmaXgpLiBBblxuICogYXV0aGVudGljYXRlZCBjb25zb2xlIG9uIGEgZGlmZmVyZW50IHBvcnQgd2lsbCBub3QgaW50ZXJmZXJlIHdpdGhcbiAqIGl0IOKAlCB0aGV5IGhhdmUgZnVsbHkgaW5kZXBlbmRlbnQgcG9ydHMsIGxvY2sgZmlsZXMsIGFuZCB0b2tlbiBmaWxlcyDigJRcbiAqIGJ1dCB0aGUgdXNlciBwcm9iYWJseSB3YW50cyB0byBrbm93IHRoZSB0d28gZXhpc3Qgc2ltdWx0YW5lb3VzbHlcbiAqIGJlY2F1c2UgdGhlIHNlY3VyaXR5IHBvc3R1cmUgb2YgZWFjaCBjb25zb2xlIGlzIGRpZmZlcmVudC5cbiAqXG4gKiBSZXR1cm5zIGluZm8gYWJvdXQgdGhlIGxlZ2FjeSBsZWFkZXIgaWYgb25lIGlzIGRldGVjdGVkLCBvclxuICogYHsgbGVnYWN5UnVubmluZzogZmFsc2UgfWAgb3RoZXJ3aXNlLlxuICpcbiAqIEBwYXJhbSBsb2NrUGF0aCAtIE9wdGlvbmFsIG92ZXJyaWRlIGZvciB0aGUgbGVnYWN5IGxvY2sgZmlsZSBwYXRoLlxuICogICAgICAgICAgICAgICAgICAgRGVmYXVsdHMgdG8gdGhlIGJ1aWx0LWluIGxlZ2FjeSBsb2NhdGlvbi4gUHJpbWFyaWx5XG4gKiAgICAgICAgICAgICAgICAgICB1c2VkIGJ5IHRlc3RzIHRvIHBvaW50IGF0IGEgdGVtcCBkaXJlY3RvcnkuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBkZXRlY3RMZWdhY3lMZWFkZXIobG9ja1BhdGg6IHN0cmluZyA9IExFR0FDWV9MT0NLX0ZJTEUpOiBQcm9taXNlPExlZ2FjeUxlYWRlckluZm8+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgcmVhZEZpbGUobG9ja1BhdGgsICd1dGYtOCcpO1xuICAgIGNvbnN0IGRhdGEgPSBKU09OLnBhcnNlKGNvbnRlbnQpIGFzIENvbnNvbGVMZWFkZXJJbmZvO1xuICAgIGlmICghZGF0YS5waWQgfHwgIWlzUHJvY2Vzc0FsaXZlKGRhdGEucGlkKSkge1xuICAgICAgcmV0dXJuIHsgbGVnYWN5UnVubmluZzogZmFsc2UsIGxvY2tQYXRoIH07XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBsZWdhY3lSdW5uaW5nOiB0cnVlLFxuICAgICAgcGlkOiBkYXRhLnBpZCxcbiAgICAgIHBvcnQ6IGRhdGEucG9ydCxcbiAgICAgIGxvY2tQYXRoLFxuICAgIH07XG4gIH0gY2F0Y2gge1xuICAgIC8vIEZpbGUgbWlzc2luZywgdW5yZWFkYWJsZSwgb3IgaW52YWxpZCBKU09OIOKAlCBubyBsZWdhY3kgbGVhZGVyIGRldGVjdGVkXG4gICAgcmV0dXJuIHsgbGVnYWN5UnVubmluZzogZmFsc2UsIGxvY2tQYXRoIH07XG4gIH1cbn1cblxuLyoqXG4gKiBSZWFkIGFuZCBwYXJzZSB0aGUgbGVhZGVyIGxvY2sgZmlsZS5cbiAqIFJldHVybnMgbnVsbCBpZiB0aGUgZmlsZSBkb2Vzbid0IGV4aXN0LCBpcyB1bnJlYWRhYmxlLCBvciBoYXMgaW52YWxpZCBjb250ZW50LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVhZExlYWRlckxvY2soKTogUHJvbWlzZTxDb25zb2xlTGVhZGVySW5mbyB8IG51bGw+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgcmVhZEZpbGUoTE9DS19GSUxFLCAndXRmLTgnKTtcbiAgICBjb25zdCBkYXRhID0gSlNPTi5wYXJzZShjb250ZW50KSBhcyBDb25zb2xlTGVhZGVySW5mbztcbiAgICBpZiAoZGF0YS52ZXJzaW9uICE9PSBMT0NLX1ZFUlNJT04gfHwgIWRhdGEucGlkIHx8ICFkYXRhLnBvcnQgfHwgIWRhdGEuc2Vzc2lvbklkKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIGRhdGE7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgaWYgKChlcnJvciBhcyBOb2RlSlMuRXJybm9FeGNlcHRpb24pLmNvZGUgIT09ICdFTk9FTlQnKSB7XG4gICAgICBsb2dnZXIuZGVidWcoJ1tMZWFkZXJFbGVjdGlvbl0gSWdub3JpbmcgdW5yZWFkYWJsZSBvciBpbnZhbGlkIGxlYWRlciBsb2NrJywge1xuICAgICAgICBsb2NrRmlsZTogTE9DS19GSUxFLFxuICAgICAgICBlcnJvcjogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpLFxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG59XG5cbi8qKlxuICogQ2hlY2sgaWYgYSBsZWFkZXIgbG9jayBpcyBzdGFsZSAoZGVhZCBwcm9jZXNzIG9yIGV4cGlyZWQgaGVhcnRiZWF0KS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzTG9ja1N0YWxlKGluZm86IENvbnNvbGVMZWFkZXJJbmZvKTogYm9vbGVhbiB7XG4gIGlmICghaXNQcm9jZXNzQWxpdmUoaW5mby5waWQpKSB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cbiAgY29uc3QgaGVhcnRiZWF0QWdlID0gRGF0ZS5ub3coKSAtIG5ldyBEYXRlKGluZm8uaGVhcnRiZWF0KS5nZXRUaW1lKCk7XG4gIHJldHVybiBoZWFydGJlYXRBZ2UgPiBIRUFSVEJFQVRfU1RBTEVfTVM7XG59XG5cbi8qKlxuICogQXR0ZW1wdCB0byBhdG9taWNhbGx5IGNsYWltIGxlYWRlcnNoaXAuXG4gKlxuICogV3JpdGVzIHRvIGEgdGVtcCBmaWxlIHRoZW4gcmVuYW1lcyB0byB0aGUgbG9jayBwYXRoLiBPbiBQT1NJWCBzeXN0ZW1zXG4gKiByZW5hbWUgaXMgYXRvbWljLCBzbyBvbmx5IG9uZSB3cml0ZXIgd2lucy4gQWZ0ZXIgcmVuYW1pbmcsIHJlLXJlYWRzIHRoZVxuICogbG9jayB0byB2ZXJpZnkgb3VyIFBJRCB3b24uXG4gKlxuICogQHJldHVybnMgdHJ1ZSBpZiB0aGlzIHByb2Nlc3Mgc3VjY2Vzc2Z1bGx5IGNsYWltZWQgbGVhZGVyc2hpcFxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2xhaW1MZWFkZXJzaGlwKGluZm86IENvbnNvbGVMZWFkZXJJbmZvKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGF3YWl0IG1rZGlyKFJVTl9ESVIsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICBjb25zdCB0bXBGaWxlID0gam9pbihSVU5fRElSLCBgY29uc29sZS1sZWFkZXIubG9jay4ke3Byb2Nlc3MucGlkfS50bXBgKTtcbiAgdHJ5IHtcbiAgICBhd2FpdCB3cml0ZUZpbGUodG1wRmlsZSwgSlNPTi5zdHJpbmdpZnkoaW5mbywgbnVsbCwgMiksICd1dGYtOCcpO1xuICAgIGF3YWl0IHJlbmFtZSh0bXBGaWxlLCBMT0NLX0ZJTEUpO1xuXG4gICAgLy8gVmVyaWZ5IHdlIHdvbiB0aGUgcmFjZVxuICAgIGNvbnN0IHdyaXR0ZW4gPSBhd2FpdCByZWFkTGVhZGVyTG9jaygpO1xuICAgIHJldHVybiB3cml0dGVuICE9PSBudWxsICYmIHdyaXR0ZW4ucGlkID09PSBpbmZvLnBpZDtcbiAgfSBjYXRjaCB7XG4gICAgLy8gQ2xlYW4gdXAgdGVtcCBmaWxlIG9uIGZhaWx1cmVcbiAgICB0cnkgeyBhd2FpdCB1bmxpbmsodG1wRmlsZSk7IH0gY2F0Y2ggeyAvKiBpZ25vcmUgKi8gfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxufVxuXG4vKipcbiAqIERlbGV0ZSB0aGUgbGVhZGVyIGxvY2sgZmlsZSAoZm9yIGNsZWFudXAgb3IgdGFrZW92ZXIpLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZGVsZXRlTGVhZGVyTG9jaygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgdHJ5IHsgYXdhaXQgdW5saW5rKExPQ0tfRklMRSk7IH0gY2F0Y2ggeyAvKiBhbHJlYWR5IGdvbmUgKi8gfVxufVxuXG4vKipcbiAqIFJ1biB0aGUgbGVhZGVyIGVsZWN0aW9uIHByb3RvY29sLlxuICpcbiAqIDEuIElmIG5vIGxvY2sgZXhpc3RzIG9yIGxvY2sgaXMgc3RhbGUg4oaSIGNsYWltIGxlYWRlcnNoaXBcbiAqIDIuIElmIGxvY2sgZXhpc3RzIHdpdGggYSBsaXZlLCByZXNwb25zaXZlIGxlYWRlciDihpIgYmVjb21lIGZvbGxvd2VyXG4gKlxuICogQHBhcmFtIHNlc3Npb25JZCAtIFRoaXMgcHJvY2VzcydzIHVuaXF1ZSBzZXNzaW9uIGlkZW50aWZpZXJcbiAqIEBwYXJhbSBwb3J0IC0gVGhlIHBvcnQgdGhpcyBwcm9jZXNzIHdvdWxkIHVzZSBhcyBsZWFkZXIgKHNlZSBgRE9MTEhPVVNFX1dFQl9DT05TT0xFX1BPUlRgKVxuICogQHJldHVybnMgRWxlY3Rpb24gcmVzdWx0IHdpdGggcm9sZSBhbmQgbGVhZGVyIGluZm9cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGVsZWN0TGVhZGVyKHNlc3Npb25JZDogc3RyaW5nLCBwb3J0OiBudW1iZXIpOiBQcm9taXNlPEVsZWN0aW9uUmVzdWx0PiB7XG4gIGNvbnN0IG15SW5mbyA9IGNyZWF0ZUxlYWRlckluZm8oc2Vzc2lvbklkLCBwb3J0KTtcbiAgc2Vzc2lvbklkID0gbXlJbmZvLnNlc3Npb25JZDtcbiAgY29uc3QgZXhpc3RpbmdMb2NrID0gYXdhaXQgcmVhZExlYWRlckxvY2soKTtcblxuICBpZiAoZXhpc3RpbmdMb2NrICYmICFpc0xvY2tTdGFsZShleGlzdGluZ0xvY2spKSB7XG4gICAgY29uc3QgcHJlZmVyZW5jZSA9IGV2YWx1YXRlTGVhZGVyUHJlZmVyZW5jZShteUluZm8sIGV4aXN0aW5nTG9jayk7XG4gICAgaWYgKHByZWZlcmVuY2Uuc2hvdWxkUmVwbGFjZSkge1xuICAgICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gUmVwbGFjaW5nIGxlYWRlciB3aXRoIG5ld2VyIGNvbXBhdGlibGUgdmVyc2lvbicsIHtcbiAgICAgICAgc3RhbGVTZXNzaW9uOiBleGlzdGluZ0xvY2suc2Vzc2lvbklkLFxuICAgICAgICBzdGFsZVBpZDogZXhpc3RpbmdMb2NrLnBpZCxcbiAgICAgICAgc3RhbGVQb3J0OiBleGlzdGluZ0xvY2sucG9ydCxcbiAgICAgICAgc3RhbGVWZXJzaW9uOiBwcmVmZXJlbmNlLmV4aXN0aW5nVmVyc2lvbixcbiAgICAgICAgc3RhbGVQcm90b2NvbFZlcnNpb246IHByZWZlcmVuY2UuZXhpc3RpbmdQcm90b2NvbFZlcnNpb24sXG4gICAgICAgIG15U2Vzc2lvbjogc2Vzc2lvbklkLFxuICAgICAgICBteVBpZDogcHJvY2Vzcy5waWQsXG4gICAgICAgIG15VmVyc2lvbjogcHJlZmVyZW5jZS5jYW5kaWRhdGVWZXJzaW9uLFxuICAgICAgICBteVByb3RvY29sVmVyc2lvbjogcHJlZmVyZW5jZS5jYW5kaWRhdGVQcm90b2NvbFZlcnNpb24sXG4gICAgICB9KTtcbiAgICAgIGF3YWl0IGRlbGV0ZUxlYWRlckxvY2soKTtcbiAgICB9IGVsc2Uge1xuICAgICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gRXhpc3RpbmcgbGVhZGVyIGZvdW5kIOKAlCBiZWNvbWluZyBmb2xsb3dlcicsIHtcbiAgICAgICAgbGVhZGVyU2Vzc2lvbjogZXhpc3RpbmdMb2NrLnNlc3Npb25JZCxcbiAgICAgICAgbGVhZGVyUGlkOiBleGlzdGluZ0xvY2sucGlkLFxuICAgICAgICBsZWFkZXJQb3J0OiBleGlzdGluZ0xvY2sucG9ydCxcbiAgICAgICAgbGVhZGVyVmVyc2lvbjogcHJlZmVyZW5jZS5leGlzdGluZ1ZlcnNpb24sXG4gICAgICAgIGxlYWRlclByb3RvY29sVmVyc2lvbjogcHJlZmVyZW5jZS5leGlzdGluZ1Byb3RvY29sVmVyc2lvbixcbiAgICAgICAgbXlTZXNzaW9uOiBzZXNzaW9uSWQsXG4gICAgICAgIG15UGlkOiBwcm9jZXNzLnBpZCxcbiAgICAgICAgbXlWZXJzaW9uOiBwcmVmZXJlbmNlLmNhbmRpZGF0ZVZlcnNpb24sXG4gICAgICAgIG15UHJvdG9jb2xWZXJzaW9uOiBwcmVmZXJlbmNlLmNhbmRpZGF0ZVByb3RvY29sVmVyc2lvbixcbiAgICAgICAgcmVhc29uOiBwcmVmZXJlbmNlLnJlYXNvbixcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHsgcm9sZTogJ2ZvbGxvd2VyJywgbGVhZGVySW5mbzogZXhpc3RpbmdMb2NrIH07XG4gICAgfVxuICB9XG5cbiAgaWYgKGV4aXN0aW5nTG9jayAmJiAhaXNMb2NrU3RhbGUoZXhpc3RpbmdMb2NrKSkge1xuICAgIC8vIExlYWRlciB3YXMgaW50ZW50aW9uYWxseSByZXBsYWNlZCBhYm92ZS4gQ29udGludWUgdG8gdGhlIGNsYWltIHBhdGguXG4gIH0gZWxzZSBpZiAoZXhpc3RpbmdMb2NrKSB7XG4gICAgLy8gTm8gdmFsaWQgbGVhZGVyIOKAlCB0cnkgdG8gY2xhaW1cbiAgICBjb25zdCBhbGl2ZSA9IGlzUHJvY2Vzc0FsaXZlKGV4aXN0aW5nTG9jay5waWQpO1xuICAgIGNvbnN0IGhlYXJ0YmVhdEFnZSA9IERhdGUubm93KCkgLSBuZXcgRGF0ZShleGlzdGluZ0xvY2suaGVhcnRiZWF0KS5nZXRUaW1lKCk7XG4gICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gU3RhbGUgbGVhZGVyIGxvY2sg4oCUIHRha2luZyBvdmVyJywge1xuICAgICAgc3RhbGVQaWQ6IGV4aXN0aW5nTG9jay5waWQsIGFsaXZlLCBoZWFydGJlYXRBZ2VNczogaGVhcnRiZWF0QWdlLFxuICAgICAgc3RhbGVTZXNzaW9uOiBleGlzdGluZ0xvY2suc2Vzc2lvbklkLCBteVNlc3Npb246IHNlc3Npb25JZCxcbiAgICB9KTtcbiAgICBhd2FpdCBkZWxldGVMZWFkZXJMb2NrKCk7XG4gIH1cblxuICBjb25zdCBjbGFpbWVkID0gYXdhaXQgY2xhaW1MZWFkZXJzaGlwKG15SW5mbyk7XG4gIGlmIChjbGFpbWVkKSB7XG4gICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gQ2xhaW1lZCBsZWF