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.

260 lines 31.9 kB
/** * Forwarding sinks for follower MCP servers. * * When a server becomes a follower in the unified console election, it * registers these sinks with its LogManager and MetricsManager. Instead * of broadcasting to local SSE clients, entries are batch-POSTed to * the leader's ingestion endpoints. * * Features: * - Batch buffering (50 entries or 1s flush, whichever comes first) * - In-memory buffer up to 10,000 entries on leader failure * - Exponential backoff on POST failure (1s → 2s → 4s, max 30s) * - Automatic drain on leader recovery * * @since v2.1.0 — Issue #1700 */ import { UnicodeValidator } from '../../security/validators/unicodeValidator.js'; import { PACKAGE_VERSION } from '../../generated/version.js'; import { logger } from '../../utils/logger.js'; import { env } from '../../config/env.js'; import { CONSOLE_PROTOCOL_VERSION } from './LeaderElection.js'; import { detectSessionClientPlatformId, } from './sessionClientPlatform.js'; /** Maximum entries to buffer when leader is unreachable */ const MAX_BUFFER_SIZE = 10_000; /** Batch size before flushing */ const BATCH_SIZE = 50; /** Time-based flush interval (ms) */ const FLUSH_INTERVAL_MS = 1_000; /** Initial backoff delay on failure (ms) */ const INITIAL_BACKOFF_MS = 1_000; /** Maximum backoff delay (ms) */ const MAX_BACKOFF_MS = 30_000; /** Give up forwarding after this many consecutive failures (#1850: configurable via env) */ const MAX_CONSECUTIVE_FAILURES = env.DOLLHOUSE_CONSOLE_MAX_FORWARD_FAILURES; /** HTTP request timeout (ms) */ const REQUEST_TIMEOUT_MS = 5_000; /** * Build the HTTP headers for ingest POSTs, including the console auth token * if one was provided (#1780). Followers read the token from the shared token * file on startup and pass it to each sink; when the leader has auth disabled * or the token file is missing, this is a no-op that sends only Content-Type. */ function buildIngestHeaders(token) { const headers = { 'Content-Type': 'application/json' }; if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } /** * ILogSink that batch-POSTs entries to the leader's /api/ingest/logs. */ export class LeaderForwardingLogSink { leaderUrl; sessionId; authToken; onLeaderDeath; buffer = []; flushTimer = null; backoffMs = INITIAL_BACKOFF_MS; flushing = false; consecutiveFailures = 0; gaveUp = false; constructor(leaderUrl, sessionId, /** Optional console auth token (#1780). Included as Bearer header on ingest POSTs. */ authToken = null, /** Callback invoked when the leader is presumed dead after MAX_CONSECUTIVE_FAILURES (#1850). */ onLeaderDeath) { this.leaderUrl = leaderUrl; this.sessionId = sessionId; this.authToken = authToken; this.onLeaderDeath = onLeaderDeath; this.sessionId = UnicodeValidator.normalize(sessionId).normalizedContent; this.flushTimer = setInterval(() => this.flushBuffer(), FLUSH_INTERVAL_MS); this.flushTimer.unref(); } write(entry) { // Stamp session ID before buffering const stamped = { ...entry, data: { ...entry.data, _sessionId: this.sessionId }, }; if (this.buffer.length >= MAX_BUFFER_SIZE) { // Evict oldest entry (FIFO) this.buffer.shift(); } this.buffer.push(stamped); if (this.buffer.length >= BATCH_SIZE) { this.flushBuffer(); } } async flush() { await this.flushBuffer(); } async close() { if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; } await this.flushBuffer(); } async flushBuffer() { if (this.flushing || this.buffer.length === 0 || this.gaveUp) return; this.flushing = true; const batch = this.buffer.splice(0, BATCH_SIZE); try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); const response = await fetch(`${this.leaderUrl}/api/ingest/logs`, { method: 'POST', headers: buildIngestHeaders(this.authToken), body: JSON.stringify({ sessionId: this.sessionId, entries: batch }), signal: controller.signal, }); clearTimeout(timeout); if (response.ok) { this.backoffMs = INITIAL_BACKOFF_MS; this.consecutiveFailures = 0; } else { this.requeueBatch(batch); this.handleFailure(); } } catch { this.requeueBatch(batch); this.handleFailure(); } finally { this.flushing = false; } } requeueBatch(batch) { const spaceAvailable = MAX_BUFFER_SIZE - this.buffer.length; if (spaceAvailable > 0) { const toRequeue = batch.slice(0, spaceAvailable); this.buffer.unshift(...toRequeue); } else { logger.warn(`[ForwardingSink] Buffer full (${MAX_BUFFER_SIZE}), dropping ${batch.length} entries`); } } handleFailure() { this.consecutiveFailures++; if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) { if (!this.gaveUp) { this.gaveUp = true; logger.info(`[ForwardingSink] Leader not running web console — log forwarding disabled after ${this.consecutiveFailures} failed attempts. Buffered ${this.buffer.length} entries discarded.`); this.buffer.length = 0; if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; } // Notify the orchestrator so it can attempt follower-to-leader promotion (#1850). // Fired asynchronously so handleFailure completes cleanly before promotion runs. if (this.onLeaderDeath) { queueMicrotask(() => this.onLeaderDeath()); } } return; } logger.debug(`[ForwardingSink] Leader unreachable, backoff ${this.backoffMs}ms (attempt ${this.consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}, buffered: ${this.buffer.length})`); this.backoffMs = Math.min(this.backoffMs * 2, MAX_BACKOFF_MS); } } /** * Forwards metric snapshots to the leader's /api/ingest/metrics. */ export class LeaderForwardingMetricsSink { leaderUrl; sessionId; authToken; constructor(leaderUrl, sessionId, /** Optional console auth token (#1780). Included as Bearer header on ingest POSTs. */ authToken = null) { this.leaderUrl = leaderUrl; this.sessionId = sessionId; this.authToken = authToken; } async onSnapshot(snapshot) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); await fetch(`${this.leaderUrl}/api/ingest/metrics`, { method: 'POST', headers: buildIngestHeaders(this.authToken), body: JSON.stringify({ sessionId: this.sessionId, snapshot }), signal: controller.signal, }); clearTimeout(timeout); } catch { logger.debug('[ForwardingSink] Failed to forward metrics snapshot'); } } } /** * Sends session lifecycle events to the leader. */ export class SessionHeartbeat { leaderUrl; sessionId; pid; authToken; clientPlatform; heartbeatTimer = null; constructor(leaderUrl, sessionId, pid, /** Optional console auth token (#1780). Included as Bearer header on ingest POSTs. */ authToken = null, /** Explicit MCP host platform metadata for this runtime. */ clientPlatform = detectSessionClientPlatformId()) { this.leaderUrl = leaderUrl; this.sessionId = sessionId; this.pid = pid; this.authToken = authToken; this.clientPlatform = clientPlatform; } /** Notify the leader that this session has started */ async start() { await this.sendEvent('started'); this.heartbeatTimer = setInterval(() => { this.sendEvent('heartbeat').catch(() => { }); }, 10_000); this.heartbeatTimer.unref(); } /** Notify the leader that this session is stopping */ async stop() { if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; } await this.sendEvent('stopped'); } async sendEvent(event) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); await fetch(`${this.leaderUrl}/api/ingest/session`, { method: 'POST', headers: buildIngestHeaders(this.authToken), body: JSON.stringify({ sessionId: this.sessionId, event, pid: this.pid, startedAt: new Date().toISOString(), serverVersion: PACKAGE_VERSION, consoleProtocolVersion: CONSOLE_PROTOCOL_VERSION, clientPlatform: this.clientPlatform ?? undefined, }), signal: controller.signal, }); clearTimeout(timeout); } catch { logger.debug(`[SessionHeartbeat] Failed to send ${event} event`); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTGVhZGVyRm9yd2FyZGluZ1NpbmsuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvd2ViL2NvbnNvbGUvTGVhZGVyRm9yd2FyZGluZ1NpbmsudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBSUgsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sK0NBQStDLENBQUM7QUFDakYsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzdELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUMvQyxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDMUMsT0FBTyxFQUFFLHdCQUF3QixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDL0QsT0FBTyxFQUNMLDZCQUE2QixHQUU5QixNQUFNLDRCQUE0QixDQUFDO0FBRXBDLDJEQUEyRDtBQUMzRCxNQUFNLGVBQWUsR0FBRyxNQUFNLENBQUM7QUFFL0IsaUNBQWlDO0FBQ2pDLE1BQU0sVUFBVSxHQUFHLEVBQUUsQ0FBQztBQUV0QixxQ0FBcUM7QUFDckMsTUFBTSxpQkFBaUIsR0FBRyxLQUFLLENBQUM7QUFFaEMsNENBQTRDO0FBQzVDLE1BQU0sa0JBQWtCLEdBQUcsS0FBSyxDQUFDO0FBRWpDLGlDQUFpQztBQUNqQyxNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUM7QUFFOUIsNEZBQTRGO0FBQzVGLE1BQU0sd0JBQXdCLEdBQUcsR0FBRyxDQUFDLHNDQUFzQyxDQUFDO0FBRTVFLGdDQUFnQztBQUNoQyxNQUFNLGtCQUFrQixHQUFHLEtBQUssQ0FBQztBQUVqQzs7Ozs7R0FLRztBQUNILFNBQVMsa0JBQWtCLENBQUMsS0FBb0I7SUFDOUMsTUFBTSxPQUFPLEdBQTJCLEVBQUUsY0FBYyxFQUFFLGtCQUFrQixFQUFFLENBQUM7SUFDL0UsSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUNWLE9BQU8sQ0FB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QUFVLENBQUMsS0FBSyxFQUFFLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztZQUV6RSxNQUFNLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxTQUFTLHFCQUFxQixFQUFFO2dCQUNsRCxNQUFNLEVBQUUsTUFBTTtnQkFDZCxPQUFPLEVBQUUsa0JBQWtCLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztnQkFDM0MsSUFBSSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUM7b0JBQ25CLFNBQVMsRUFBRSxJQUFJLENBQUMsU0FBUztvQkFDekIsS0FBSztvQkFDTCxHQUFHLEVBQUUsSUFBSSxDQUFDLEdBQUc7b0JBQ2IsU0FBUyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFO29CQUNuQyxhQUFhLEVBQUUsZUFBZTtvQkFDOUIsc0JBQXNCLEVBQUUsd0JBQXdCO29CQUNoRCxjQUFjLEVBQUUsSUFBSSxDQUFDLGNBQWMsSUFBSSxTQUFTO2lCQUNqRCxDQUFDO2dCQUNGLE1BQU0sRUFBRSxVQUFVLENBQUMsTUFBTTthQUMxQixDQUFDLENBQUM7WUFDSCxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDeEIsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLE1BQU0sQ0FBQyxLQUFLLENBQUMscUNBQXFDLEtBQUssUUFBUSxDQUFDLENBQUM7UUFDbkUsQ0FBQztJQUNILENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogRm9yd2FyZGluZyBzaW5rcyBmb3IgZm9sbG93ZXIgTUNQIHNlcnZlcnMuXG4gKlxuICogV2hlbiBhIHNlcnZlciBiZWNvbWVzIGEgZm9sbG93ZXIgaW4gdGhlIHVuaWZpZWQgY29uc29sZSBlbGVjdGlvbiwgaXRcbiAqIHJlZ2lzdGVycyB0aGVzZSBzaW5rcyB3aXRoIGl0cyBMb2dNYW5hZ2VyIGFuZCBNZXRyaWNzTWFuYWdlci4gSW5zdGVhZFxuICogb2YgYnJvYWRjYXN0aW5nIHRvIGxvY2FsIFNTRSBjbGllbnRzLCBlbnRyaWVzIGFyZSBiYXRjaC1QT1NUZWQgdG9cbiAqIHRoZSBsZWFkZXIncyBpbmdlc3Rpb24gZW5kcG9pbnRzLlxuICpcbiAqIEZlYXR1cmVzOlxuICogLSBCYXRjaCBidWZmZXJpbmcgKDUwIGVudHJpZXMgb3IgMXMgZmx1c2gsIHdoaWNoZXZlciBjb21lcyBmaXJzdClcbiAqIC0gSW4tbWVtb3J5IGJ1ZmZlciB1cCB0byAxMCwwMDAgZW50cmllcyBvbiBsZWFkZXIgZmFpbHVyZVxuICogLSBFeHBvbmVudGlhbCBiYWNrb2ZmIG9uIFBPU1QgZmFpbHVyZSAoMXMg4oaSIDJzIOKGkiA0cywgbWF4IDMwcylcbiAqIC0gQXV0b21hdGljIGRyYWluIG9uIGxlYWRlciByZWNvdmVyeVxuICpcbiAqIEBzaW5jZSB2Mi4xLjAg4oCUIElzc3VlICMxNzAwXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBJTG9nU2luaywgVW5pZmllZExvZ0VudHJ5IH0gZnJvbSAnLi4vLi4vbG9nZ2luZy90eXBlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IE1ldHJpY1NuYXBzaG90IH0gZnJvbSAnLi4vLi4vbWV0cmljcy90eXBlcy5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IFBBQ0tBR0VfVkVSU0lPTiB9IGZyb20gJy4uLy4uL2dlbmVyYXRlZC92ZXJzaW9uLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBlbnYgfSBmcm9tICcuLi8uLi9jb25maWcvZW52LmpzJztcbmltcG9ydCB7IENPTlNPTEVfUFJPVE9DT0xfVkVSU0lPTiB9IGZyb20gJy4vTGVhZGVyRWxlY3Rpb24uanMnO1xuaW1wb3J0IHtcbiAgZGV0ZWN0U2Vzc2lvbkNsaWVudFBsYXRmb3JtSWQsXG4gIHR5cGUgU2Vzc2lvbkNsaWVudFBsYXRmb3JtSWQsXG59IGZyb20gJy4vc2Vzc2lvbkNsaWVudFBsYXRmb3JtLmpzJztcblxuLyoqIE1heGltdW0gZW50cmllcyB0byBidWZmZXIgd2hlbiBsZWFkZXIgaXMgdW5yZWFjaGFibGUgKi9cbmNvbnN0IE1BWF9CVUZGRVJfU0laRSA9IDEwXzAwMDtcblxuLyoqIEJhdGNoIHNpemUgYmVmb3JlIGZsdXNoaW5nICovXG5jb25zdCBCQVRDSF9TSVpFID0gNTA7XG5cbi8qKiBUaW1lLWJhc2VkIGZsdXNoIGludGVydmFsIChtcykgKi9cbmNvbnN0IEZMVVNIX0lOVEVSVkFMX01TID0gMV8wMDA7XG5cbi8qKiBJbml0aWFsIGJhY2tvZmYgZGVsYXkgb24gZmFpbHVyZSAobXMpICovXG5jb25zdCBJTklUSUFMX0JBQ0tPRkZfTVMgPSAxXzAwMDtcblxuLyoqIE1heGltdW0gYmFja29mZiBkZWxheSAobXMpICovXG5jb25zdCBNQVhfQkFDS09GRl9NUyA9IDMwXzAwMDtcblxuLyoqIEdpdmUgdXAgZm9yd2FyZGluZyBhZnRlciB0aGlzIG1hbnkgY29uc2VjdXRpdmUgZmFpbHVyZXMgKCMxODUwOiBjb25maWd1cmFibGUgdmlhIGVudikgKi9cbmNvbnN0IE1BWF9DT05TRUNVVElWRV9GQUlMVVJFUyA9IGVudi5ET0xMSE9VU0VfQ09OU09MRV9NQVhfRk9SV0FSRF9GQUlMVVJFUztcblxuLyoqIEhUVFAgcmVxdWVzdCB0aW1lb3V0IChtcykgKi9cbmNvbnN0IFJFUVVFU1RfVElNRU9VVF9NUyA9IDVfMDAwO1xuXG4vKipcbiAqIEJ1aWxkIHRoZSBIVFRQIGhlYWRlcnMgZm9yIGluZ2VzdCBQT1NUcywgaW5jbHVkaW5nIHRoZSBjb25zb2xlIGF1dGggdG9rZW5cbiAqIGlmIG9uZSB3YXMgcHJvdmlkZWQgKCMxNzgwKS4gRm9sbG93ZXJzIHJlYWQgdGhlIHRva2VuIGZyb20gdGhlIHNoYXJlZCB0b2tlblxuICogZmlsZSBvbiBzdGFydHVwIGFuZCBwYXNzIGl0IHRvIGVhY2ggc2luazsgd2hlbiB0aGUgbGVhZGVyIGhhcyBhdXRoIGRpc2FibGVkXG4gKiBvciB0aGUgdG9rZW4gZmlsZSBpcyBtaXNzaW5nLCB0aGlzIGlzIGEgbm8tb3AgdGhhdCBzZW5kcyBvbmx5IENvbnRlbnQtVHlwZS5cbiAqL1xuZnVuY3Rpb24gYnVpbGRJbmdlc3RIZWFkZXJzKHRva2VuOiBzdHJpbmcgfCBudWxsKTogUmVjb3JkPHN0cmluZywgc3RyaW5nPiB7XG4gIGNvbnN0IGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicgfTtcbiAgaWYgKHRva2VuKSB7XG4gICAgaGVhZGVyc1snQXV0aG9yaXphdGlvbiddID0gYEJlYXJlciAke3Rva2VufWA7XG4gIH1cbiAgcmV0dXJuIGhlYWRlcnM7XG59XG5cbi8qKlxuICogSUxvZ1NpbmsgdGhhdCBiYXRjaC1QT1NUcyBlbnRyaWVzIHRvIHRoZSBsZWFkZXIncyAvYXBpL2luZ2VzdC9sb2dzLlxuICovXG5leHBvcnQgY2xhc3MgTGVhZGVyRm9yd2FyZGluZ0xvZ1NpbmsgaW1wbGVtZW50cyBJTG9nU2luayB7XG4gIHByaXZhdGUgcmVhZG9ubHkgYnVmZmVyOiBVbmlmaWVkTG9nRW50cnlbXSA9IFtdO1xuICBwcml2YXRlIGZsdXNoVGltZXI6IFJldHVyblR5cGU8dHlwZW9mIHNldEludGVydmFsPiB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIGJhY2tvZmZNcyA9IElOSVRJQUxfQkFDS09GRl9NUztcbiAgcHJpdmF0ZSBmbHVzaGluZyA9IGZhbHNlO1xuICBwcml2YXRlIGNvbnNlY3V0aXZlRmFpbHVyZXMgPSAwO1xuICBwcml2YXRlIGdhdmVVcCA9IGZhbHNlO1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgcmVhZG9ubHkgbGVhZGVyVXJsOiBzdHJpbmcsXG4gICAgcHJpdmF0ZSByZWFkb25seSBzZXNzaW9uSWQ6IHN0cmluZyxcbiAgICAvKiogT3B0aW9uYWwgY29uc29sZSBhdXRoIHRva2VuICgjMTc4MCkuIEluY2x1ZGVkIGFzIEJlYXJlciBoZWFkZXIgb24gaW5nZXN0IFBPU1RzLiAqL1xuICAgIHByaXZhdGUgcmVhZG9ubHkgYXV0aFRva2VuOiBzdHJpbmcgfCBudWxsID0gbnVsbCxcbiAgICAvKiogQ2FsbGJhY2sgaW52b2tlZCB3aGVuIHRoZSBsZWFkZXIgaXMgcHJlc3VtZWQgZGVhZCBhZnRlciBNQVhfQ09OU0VDVVRJVkVfRkFJTFVSRVMgKCMxODUwKS4gKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IG9uTGVhZGVyRGVhdGg/OiAoKSA9PiB2b2lkLFxuICApIHtcbiAgICB0aGlzLnNlc3Npb25JZCA9IFVuaWNvZGVWYWxpZGF0b3Iubm9ybWFsaXplKHNlc3Npb25JZCkubm9ybWFsaXplZENvbnRlbnQ7XG4gICAgdGhpcy5mbHVzaFRpbWVyID0gc2V0SW50ZXJ2YWwoKCkgPT4gdGhpcy5mbHVzaEJ1ZmZlcigpLCBGTFVTSF9JTlRFUlZBTF9NUyk7XG4gICAgdGhpcy5mbHVzaFRpbWVyLnVucmVmKCk7XG4gIH1cblxuICB3cml0ZShlbnRyeTogVW5pZmllZExvZ0VudHJ5KTogdm9pZCB7XG4gICAgLy8gU3RhbXAgc2Vzc2lvbiBJRCBiZWZvcmUgYnVmZmVyaW5nXG4gICAgY29uc3Qgc3RhbXBlZDogVW5pZmllZExvZ0VudHJ5ID0ge1xuICAgICAgLi4uZW50cnksXG4gICAgICBkYXRhOiB7IC4uLmVudHJ5LmRhdGEsIF9zZXNzaW9uSWQ6IHRoaXMuc2Vzc2lvbklkIH0sXG4gICAgfTtcblxuICAgIGlmICh0aGlzLmJ1ZmZlci5sZW5ndGggPj0gTUFYX0JVRkZFUl9TSVpFKSB7XG4gICAgICAvLyBFdmljdCBvbGRlc3QgZW50cnkgKEZJRk8pXG4gICAgICB0aGlzLmJ1ZmZlci5zaGlmdCgpO1xuICAgIH1cbiAgICB0aGlzLmJ1ZmZlci5wdXNoKHN0YW1wZWQpO1xuXG4gICAgaWYgKHRoaXMuYnVmZmVyLmxlbmd0aCA+PSBCQVRDSF9TSVpFKSB7XG4gICAgICB0aGlzLmZsdXNoQnVmZmVyKCk7XG4gICAgfVxuICB9XG5cbiAgYXN5bmMgZmx1c2goKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgYXdhaXQgdGhpcy5mbHVzaEJ1ZmZlcigpO1xuICB9XG5cbiAgYXN5bmMgY2xvc2UoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMuZmx1c2hUaW1lcikge1xuICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLmZsdXNoVGltZXIpO1xuICAgICAgdGhpcy5mbHVzaFRpbWVyID0gbnVsbDtcbiAgICB9XG4gICAgYXdhaXQgdGhpcy5mbHVzaEJ1ZmZlcigpO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBmbHVzaEJ1ZmZlcigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy5mbHVzaGluZyB8fCB0aGlzLmJ1ZmZlci5sZW5ndGggPT09IDAgfHwgdGhpcy5nYXZlVXApIHJldHVybjtcbiAgICB0aGlzLmZsdXNoaW5nID0gdHJ1ZTtcblxuICAgIGNvbnN0IGJhdGNoID0gdGhpcy5idWZmZXIuc3BsaWNlKDAsIEJBVENIX1NJWkUpO1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgICAgY29uc3QgdGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4gY29udHJvbGxlci5hYm9ydCgpLCBSRVFVRVNUX1RJTUVPVVRfTVMpO1xuXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKGAke3RoaXMubGVhZGVyVXJsfS9hcGkvaW5nZXN0L2xvZ3NgLCB7XG4gICAgICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgICAgICBoZWFkZXJzOiBidWlsZEluZ2VzdEhlYWRlcnModGhpcy5hdXRoVG9rZW4pLFxuICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7IHNlc3Npb25JZDogdGhpcy5zZXNzaW9uSWQsIGVudHJpZXM6IGJhdGNoIH0pLFxuICAgICAgICBzaWduYWw6IGNvbnRyb2xsZXIuc2lnbmFsLFxuICAgICAgfSk7XG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG5cbiAgICAgIGlmIChyZXNwb25zZS5vaykge1xuICAgICAgICB0aGlzLmJhY2tvZmZNcyA9IElOSVRJQUxfQkFDS09GRl9NUztcbiAgICAgICAgdGhpcy5jb25zZWN1dGl2ZUZhaWx1cmVzID0gMDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMucmVxdWV1ZUJhdGNoKGJhdGNoKTtcbiAgICAgICAgdGhpcy5oYW5kbGVGYWlsdXJlKCk7XG4gICAgICB9XG4gICAgfSBjYXRjaCB7XG4gICAgICB0aGlzLnJlcXVldWVCYXRjaChiYXRjaCk7XG4gICAgICB0aGlzLmhhbmRsZUZhaWx1cmUoKTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5mbHVzaGluZyA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgcmVxdWV1ZUJhdGNoKGJhdGNoOiBVbmlmaWVkTG9nRW50cnlbXSk6IHZvaWQge1xuICAgIGNvbnN0IHNwYWNlQXZhaWxhYmxlID0gTUFYX0JVRkZFUl9TSVpFIC0gdGhpcy5idWZmZXIubGVuZ3RoO1xuICAgIGlmIChzcGFjZUF2YWlsYWJsZSA+IDApIHtcbiAgICAgIGNvbnN0IHRvUmVxdWV1ZSA9IGJhdGNoLnNsaWNlKDAsIHNwYWNlQXZhaWxhYmxlKTtcbiAgICAgIHRoaXMuYnVmZmVyLnVuc2hpZnQoLi4udG9SZXF1ZXVlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgbG9nZ2VyLndhcm4oYFtGb3J3YXJkaW5nU2lua10gQnVmZmVyIGZ1bGwgKCR7TUFYX0JVRkZFUl9TSVpFfSksIGRyb3BwaW5nICR7YmF0Y2gubGVuZ3RofSBlbnRyaWVzYCk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBoYW5kbGVGYWlsdXJlKCk6IHZvaWQge1xuICAgIHRoaXMuY29uc2VjdXRpdmVGYWlsdXJlcysrO1xuXG4gICAgaWYgKHRoaXMuY29uc2VjdXRpdmVGYWlsdXJlcyA+PSBNQVhfQ09OU0VDVVRJVkVfRkFJTFVSRVMpIHtcbiAgICAgIGlmICghdGhpcy5nYXZlVXApIHtcbiAgICAgICAgdGhpcy5nYXZlVXAgPSB0cnVlO1xuICAgICAgICBsb2dnZXIuaW5mbyhgW0ZvcndhcmRpbmdTaW5rXSBMZWFkZXIgbm90IHJ1bm5pbmcgd2ViIGNvbnNvbGUg4oCUIGxvZyBmb3J3YXJkaW5nIGRpc2FibGVkIGFmdGVyICR7dGhpcy5jb25zZWN1dGl2ZUZhaWx1cmVzfSBmYWlsZWQgYXR0ZW1wdHMuIEJ1ZmZlcmVkICR7dGhpcy5idWZmZXIubGVuZ3RofSBlbnRyaWVzIGRpc2NhcmRlZC5gKTtcbiAgICAgICAgdGhpcy5idWZmZXIubGVuZ3RoID0gMDtcbiAgICAgICAgaWYgKHRoaXMuZmx1c2hUaW1lcikge1xuICAgICAgICAgIGNsZWFySW50ZXJ2YWwodGhpcy5mbHVzaFRpbWVyKTtcbiAgICAgICAgICB0aGlzLmZsdXNoVGltZXIgPSBudWxsO1xuICAgICAgICB9XG4gICAgICAgIC8vIE5vdGlmeSB0aGUgb3JjaGVzdHJhdG9yIHNvIGl0IGNhbiBhdHRlbXB0IGZvbGxvd2VyLXRvLWxlYWRlciBwcm9tb3Rpb24gKCMxODUwKS5cbiAgICAgICAgLy8gRmlyZWQgYXN5bmNocm9ub3VzbHkgc28gaGFuZGxlRmFpbHVyZSBjb21wbGV0ZXMgY2xlYW5seSBiZWZvcmUgcHJvbW90aW9uIHJ1bnMuXG4gICAgICAgIGlmICh0aGlzLm9uTGVhZGVyRGVhdGgpIHtcbiAgICAgICAgICBxdWV1ZU1pY3JvdGFzaygoKSA9PiB0aGlzLm9uTGVhZGVyRGVhdGghKCkpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbG9nZ2VyLmRlYnVnKGBbRm9yd2FyZGluZ1NpbmtdIExlYWRlciB1bnJlYWNoYWJsZSwgYmFja29mZiAke3RoaXMuYmFja29mZk1zfW1zIChhdHRlbXB0ICR7dGhpcy5jb25zZWN1dGl2ZUZhaWx1cmVzfS8ke01BWF9DT05TRUNVVElWRV9GQUlMVVJFU30sIGJ1ZmZlcmVkOiAke3RoaXMuYnVmZmVyLmxlbmd0aH0pYCk7XG4gICAgdGhpcy5iYWNrb2ZmTXMgPSBNYXRoLm1pbih0aGlzLmJhY2tvZmZNcyAqIDIsIE1BWF9CQUNLT0ZGX01TKTtcbiAgfVxufVxuXG4vKipcbiAqIEZvcndhcmRzIG1ldHJpYyBzbmFwc2hvdHMgdG8gdGhlIGxlYWRlcidzIC9hcGkvaW5nZXN0L21ldHJpY3MuXG4gKi9cbmV4cG9ydCBjbGFzcyBMZWFkZXJGb3J3YXJkaW5nTWV0cmljc1Npbmsge1xuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHJlYWRvbmx5IGxlYWRlclVybDogc3RyaW5nLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgc2Vzc2lvbklkOiBzdHJpbmcsXG4gICAgLyoqIE9wdGlvbmFsIGNvbnNvbGUgYXV0aCB0b2tlbiAoIzE3ODApLiBJbmNsdWRlZCBhcyBCZWFyZXIgaGVhZGVyIG9uIGluZ2VzdCBQT1NUcy4gKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IGF1dGhUb2tlbjogc3RyaW5nIHwgbnVsbCA9IG51bGwsXG4gICkge31cblxuICBhc3luYyBvblNuYXBzaG90KHNuYXBzaG90OiBNZXRyaWNTbmFwc2hvdCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgICAgY29uc3QgdGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4gY29udHJvbGxlci5hYm9ydCgpLCBSRVFVRVNUX1RJTUVPVVRfTVMpO1xuXG4gICAgICBhd2FpdCBmZXRjaChgJHt0aGlzLmxlYWRlclVybH0vYXBpL2luZ2VzdC9tZXRyaWNzYCwge1xuICAgICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgICAgaGVhZGVyczogYnVpbGRJbmdlc3RIZWFkZXJzKHRoaXMuYXV0aFRva2VuKSxcbiAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBzZXNzaW9uSWQ6IHRoaXMuc2Vzc2lvbklkLCBzbmFwc2hvdCB9KSxcbiAgICAgICAgc2lnbmFsOiBjb250cm9sbGVyLnNpZ25hbCxcbiAgICAgIH0pO1xuICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpO1xuICAgIH0gY2F0Y2gge1xuICAgICAgbG9nZ2VyLmRlYnVnKCdbRm9yd2FyZGluZ1NpbmtdIEZhaWxlZCB0byBmb3J3YXJkIG1ldHJpY3Mgc25hcHNob3QnKTtcbiAgICB9XG4gIH1cbn1cblxuLyoqXG4gKiBTZW5kcyBzZXNzaW9uIGxpZmVjeWNsZSBldmVudHMgdG8gdGhlIGxlYWRlci5cbiAqL1xuZXhwb3J0IGNsYXNzIFNlc3Npb25IZWFydGJlYXQge1xuICBwcml2YXRlIGhlYXJ0YmVhdFRpbWVyOiBSZXR1cm5UeXBlPHR5cGVvZiBzZXRJbnRlcnZhbD4gfCBudWxsID0gbnVsbDtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHJlYWRvbmx5IGxlYWRlclVybDogc3RyaW5nLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgc2Vzc2lvbklkOiBzdHJpbmcsXG4gICAgcHJpdmF0ZSByZWFkb25seSBwaWQ6IG51bWJlcixcbiAgICAvKiogT3B0aW9uYWwgY29uc29sZSBhdXRoIHRva2VuICgjMTc4MCkuIEluY2x1ZGVkIGFzIEJlYXJlciBoZWFkZXIgb24gaW5nZXN0IFBPU1RzLiAqL1xuICAgIHByaXZhdGUgcmVhZG9ubHkgYXV0aFRva2VuOiBzdHJpbmcgfCBudWxsID0gbnVsbCxcbiAgICAvKiogRXhwbGljaXQgTUNQIGhvc3QgcGxhdGZvcm0gbWV0YWRhdGEgZm9yIHRoaXMgcnVudGltZS4gKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IGNsaWVudFBsYXRmb3JtOiBTZXNzaW9uQ2xpZW50UGxhdGZvcm1JZCB8IG51bGwgPSBkZXRlY3RTZXNzaW9uQ2xpZW50UGxhdGZvcm1JZCgpLFxuICApIHt9XG5cbiAgLyoqIE5vdGlmeSB0aGUgbGVhZGVyIHRoYXQgdGhpcyBzZXNzaW9uIGhhcyBzdGFydGVkICovXG4gIGFzeW5jIHN0YXJ0KCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IHRoaXMuc2VuZEV2ZW50KCdzdGFydGVkJyk7XG5cbiAgICB0aGlzLmhlYXJ0YmVhdFRpbWVyID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgdGhpcy5zZW5kRXZlbnQoJ2hlYXJ0YmVhdCcpLmNhdGNoKCgpID0+IHt9KTtcbiAgICB9LCAxMF8wMDApO1xuICAgIHRoaXMuaGVhcnRiZWF0VGltZXIudW5yZWYoKTtcbiAgfVxuXG4gIC8qKiBOb3RpZnkgdGhlIGxlYWRlciB0aGF0IHRoaXMgc2Vzc2lvbiBpcyBzdG9wcGluZyAqL1xuICBhc3luYyBzdG9wKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICh0aGlzLmhlYXJ0YmVhdFRpbWVyKSB7XG4gICAgICBjbGVhckludGVydmFsKHRoaXMuaGVhcnRiZWF0VGltZXIpO1xuICAgICAgdGhpcy5oZWFydGJlYXRUaW1lciA9IG51bGw7XG4gICAgfVxuICAgIGF3YWl0IHRoaXMuc2VuZEV2ZW50KCdzdG9wcGVkJyk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHNlbmRFdmVudChldmVudDogJ3N0YXJ0ZWQnIHwgJ3N0b3BwZWQnIHwgJ2hlYXJ0YmVhdCcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgY29udHJvbGxlciA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgICAgIGNvbnN0IHRpbWVvdXQgPSBzZXRUaW1lb3V0KCgpID0+IGNvbnRyb2xsZXIuYWJvcnQoKSwgUkVRVUVTVF9USU1FT1VUX01TKTtcblxuICAgICAgYXdhaXQgZmV0Y2goYCR7dGhpcy5sZWFkZXJVcmx9L2FwaS9pbmdlc3Qvc2Vzc2lvbmAsIHtcbiAgICAgICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgICAgIGhlYWRlcnM6IGJ1aWxkSW5nZXN0SGVhZGVycyh0aGlzLmF1dGhUb2tlbiksXG4gICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICBzZXNzaW9uSWQ6IHRoaXMuc2Vzc2lvbklkLFxuICAgICAgICAgIGV2ZW50LFxuICAgICAgICAgIHBpZDogdGhpcy5waWQsXG4gICAgICAgICAgc3RhcnRlZEF0OiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgICAgICAgc2VydmVyVmVyc2lvbjogUEFDS0FHRV9WRVJTSU9OLFxuICAgICAgICAgIGNvbnNvbGVQcm90b2NvbFZlcnNpb246IENPTlNPTEVfUFJPVE9DT0xfVkVSU0lPTixcbiAgICAgICAgICBjbGllbnRQbGF0Zm9ybTogdGhpcy5jbGllbnRQbGF0Zm9ybSA/PyB1bmRlZmluZWQsXG4gICAgICAgIH0pLFxuICAgICAgICBzaWduYWw6IGNvbnRyb2xsZXIuc2lnbmFsLFxuICAgICAgfSk7XG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgfSBjYXRjaCB7XG4gICAgICBsb2dnZXIuZGVidWcoYFtTZXNzaW9uSGVhcnRiZWF0XSBGYWlsZWQgdG8gc2VuZCAke2V2ZW50fSBldmVudGApO1xuICAgIH1cbiAgfVxufVxuIl19