@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
JavaScript
/**
* 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