@rudderstack/integrations-lib
Version:
A comprehensive TypeScript library providing shared utilities, SDKs, and tools for RudderStack integrations and destinations.
479 lines • 58.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClusterManager = void 0;
const events_1 = require("events");
const logger = __importStar(require("../logger"));
const utils_1 = require("./utils");
/**
* ClusterManager - A cluster lifecycle management system
*
* The manager supports the following features:
* - Graceful shutdown with configurable timeout
* - Worker health monitoring with ping/pong mechanism
* - Automatic worker restart with configurable limits
* - Signal handling for shutdown triggers
* - Flexible primary and worker function handlers
*/
class ClusterManager extends events_1.EventEmitter {
constructor(options = {}) {
super();
this.workers = new Map();
this.started = false;
this.isShuttingDown = false;
this.shutdownPromise = null;
this.healthCheckInterval = null;
this.signalHandlers = new Map();
this.options = (0, utils_1.validateAndDefaultOptions)(options);
this.initialize();
}
/**
* Initialize the cluster manager with primary process configuration
*
* @see {@link https://nodejs.org/docs/latest/api/cluster.html#clustersetupprimaryoptions Node.js cluster.setupPrimary()}
*/
initialize() {
// Only call setupPrimary in the primary process
if (this.options.cluster.isPrimary) {
this.options.cluster.setupPrimary({
serialization: this.options.serialization,
});
}
this.setupSignalHandlers();
}
/**
* Starts the cluster manager
* In primary process: starts workers and health monitoring
* In worker process: executes worker function
*/
async start() {
if (this.options.cluster.isPrimary) {
await this.startPrimary();
}
else {
await this.startWorker();
}
this.started = true;
}
/**
* Initiates graceful shutdown of the cluster
*/
shutdown(signal) {
if (!this.started) {
return Promise.resolve();
}
if (this.shutdownPromise) {
return this.shutdownPromise;
}
this.isShuttingDown = true;
this.emit('shutdown:started', signal);
if (this.options.cluster.isPrimary) {
this.shutdownPromise = this.shutdownPrimary(signal);
}
else {
this.shutdownPromise = this.shutdownWorker(signal);
}
return this.shutdownPromise;
}
/**
* Sets up signal handlers for graceful shutdown
*/
setupSignalHandlers() {
this.options.shutdownSignals.forEach((signal) => {
const handler = () => {
this.shutdown(signal);
};
this.signalHandlers.set(signal, handler);
process.on(signal, handler);
});
}
/**
* Removes signal handlers
*/
removeSignalHandlers() {
Array.from(this.signalHandlers.entries()).forEach(([signal, handler]) => {
process.removeListener(signal, handler);
});
this.signalHandlers.clear();
}
/**
* Starts the primary process
*/
async startPrimary() {
logger.info(`Primary process (pid: ${process.pid}) starting with ${this.options.numWorkers} workers`);
// Execute primary initialization function, any error will stop the cluster from starting
await this.options.primaryFn();
// Set up cluster event handlers
this.setupClusterEventHandlers();
// Spawn initial workers
for (let i = 0; i < this.options.numWorkers; i += 1) {
const workerId = i + 1; // Worker IDs start from 1
this.spawnWorker(workerId);
}
// Start health monitoring
this.startHealthMonitoring();
}
/**
* Starts a worker process
*/
async startWorker() {
logger.info(`Worker ${this.getCurrentWorkerId()} (pid: ${process.pid}) starting`);
// Set up IPC message handlers
this.setupWorkerMessageHandlers();
// Execute worker initialization function, any error will be propagated
await this.options.workerFn();
}
/**
* Sets up cluster event handlers for the primary process
*/
setupClusterEventHandlers() {
this.options.cluster.on('exit', (worker, code, signal) => {
this.handleWorkerExit(worker, code, signal);
});
this.options.cluster.on('online', (worker) => {
logger.info(`Worker ${this.getWorkerId(worker)} (pid: ${worker.process.pid}) is online`);
this.emit('worker:started', worker);
});
}
/**
* Sets up IPC message handlers for worker processes
*/
setupWorkerMessageHandlers() {
process.on('message', (message) => {
switch (message.type) {
case 'ping':
this.handleWorkerPing(message);
break;
case 'shutdown':
this.shutdown(message.signal);
break;
default:
if (!message.type.includes('Metrics')) {
logger.warn(`ignoring unknown message type in worker ${process.pid}: ${message.type}`);
}
break;
}
});
}
/**
* Spawns a new worker and sets up its state
*/
spawnWorker(id) {
const worker = this.options.cluster.fork({
WORKER_ID: id,
});
const workerState = {
id,
worker,
restartCount: 0,
lastPing: Date.now(),
pendingPing: false,
isShuttingDown: false,
};
this.workers.set(worker.id, workerState);
// Set up worker message handler
worker.on('message', (message) => {
this.handleWorkerMessage(worker, message);
});
return worker;
}
/**
* Handles messages from workers
*/
handleWorkerMessage(worker, message) {
const workerState = this.workers.get(worker.id);
if (!workerState)
return;
switch (message.type) {
case 'pong':
workerState.lastPing = Date.now();
workerState.pendingPing = false;
break;
default:
if (!message.type.includes('Metrics')) {
logger.warn(`Received unknown message type from worker ${this.getWorkerId(worker)} (pid: ${worker.process.pid}): ${message.type}`);
}
break;
}
}
/**
* Handles ping messages in worker processes
*/
handleWorkerPing(message) {
const pongMessage = {
type: 'pong',
timestamp: message.timestamp,
};
if (process.send) {
process.send(pongMessage);
}
}
/**
* Starts health monitoring for all workers
*/
startHealthMonitoring() {
this.healthCheckInterval = setInterval(() => {
this.performHealthCheck();
}, this.options.pingFrequency);
}
/**
* Stops health monitoring
*/
stopHealthMonitoring() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = null;
}
}
/**
* Performs health check on all workers
*/
performHealthCheck() {
if (this.isShuttingDown)
return;
Array.from(this.workers.values())
.filter((workerState) => !workerState.isShuttingDown)
.forEach((workerState) => {
const timeSinceLastPing = Date.now() - workerState.lastPing;
if (workerState.pendingPing && timeSinceLastPing > this.options.pingTimeout) {
// Worker is stuck, handle it
this.handleStuckWorker(workerState);
}
else if (!workerState.pendingPing) {
// Send ping to worker
this.sendPingToWorker(workerState);
}
});
}
/**
* Sends a ping message to a worker
*/
sendPingToWorker(workerState) {
const pingMessage = {
type: 'ping',
timestamp: Date.now(),
};
workerState.pendingPing = true;
try {
workerState.worker.send(pingMessage);
}
catch (error) {
logger.error(`Failed to send ping to worker ${workerState.id} (pid: ${workerState.worker.process.pid}): ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Handles a stuck worker
*/
handleStuckWorker(workerState) {
logger.error(`Worker ${workerState.id} (pid: ${workerState.worker.process.pid}) is stuck, killing it`);
this.emit('worker:stuck', workerState.worker);
// Remove worker state
this.workers.delete(workerState.worker.id);
// Kill the worker
workerState.worker.kill('SIGKILL');
// Determine if we should spawn a replacement
const shouldSpawn = this.options.stuckWorkerRespawnFunc(workerState.worker);
if (shouldSpawn && !this.isShuttingDown) {
logger.info(`Spawning replacement worker for stuck worker ${workerState.id} (pid: ${workerState.worker.process.pid})`);
const { restartCount } = workerState; // Preserve restart count
const newWorker = this.spawnWorker(workerState.id);
const newWorkerState = this.workers.get(newWorker.id);
if (newWorkerState) {
newWorkerState.restartCount = restartCount;
}
}
else {
logger.error(`Triggering cluster shutdown due to stuck worker ${workerState.id} (pid: ${workerState.worker.process.pid})`);
this.shutdown('STUCK_WORKER');
}
}
/**
* Handles worker exit events
*/
handleWorkerExit(worker, code, signal) {
const workerId = this.getWorkerId(worker);
this.emit('worker:died', worker, code, signal);
const workerState = this.workers.get(worker.id);
if (!workerState) {
logger.warn(`Unknown worker ${workerId} (pid: ${worker.process.pid}) exited with code ${code} and signal ${signal}`);
return;
}
this.workers.delete(worker.id);
// If we're shutting down or worker was killed intentionally, don't restart
if (this.isShuttingDown || workerState.isShuttingDown) {
logger.info(`Worker ${workerId} (pid: ${worker.process.pid}) exited with code ${code} and signal ${signal}`);
return;
}
logger.error(`Worker ${workerId} (pid: ${worker.process.pid}) exited unexpectedly with code ${code} and signal ${signal}`);
// Handle unexpected exit
this.handleUnexpectedWorkerExit(workerState);
}
getWorkerId(worker) {
return this.workers.get(worker.id)?.id ?? worker.id;
}
getCurrentWorkerId() {
const envWorkerId = process.env.WORKER_ID !== undefined ? Number(process.env.WORKER_ID) : undefined;
return envWorkerId ?? this.options.cluster.worker?.id ?? -1;
}
/**
* Handles unexpected worker exits with restart logic
*/
handleUnexpectedWorkerExit(workerState) {
const restartCount = workerState.restartCount + 1;
if (restartCount <= this.options.restartMaxTimes) {
logger.error(`Restarting worker ${workerState.id} (pid: ${workerState.worker.process.pid}) (attempt ${restartCount}/${this.options.restartMaxTimes})`);
const newWorker = this.spawnWorker(workerState.id);
const newWorkerState = this.workers.get(newWorker.id);
if (newWorkerState) {
newWorkerState.restartCount = restartCount;
}
this.emit('worker:restarted', newWorker, workerState.restartCount);
}
else {
logger.error(`Restart limit (${this.options.restartMaxTimes}) exceeded for worker ${workerState.id} (pid: ${workerState.worker.process.pid}), shutting down cluster`);
this.emit('worker:restart-limit-exceeded', workerState.worker, workerState.restartCount);
this.shutdown('RESTART_LIMIT_EXCEEDED');
}
}
/**
* Shuts down the primary process
*/
async shutdownPrimary(signal) {
logger.warn(`Primary process (pid: ${process.pid}) shutting down (signal: ${signal ?? 'manual'})`);
this.stopHealthMonitoring();
this.removeSignalHandlers();
// Shutdown all workers
await this.shutdownAllWorkers(signal);
// Execute primary shutdown function
await (0, utils_1.safeExecute)(() => (0, utils_1.timeout)(this.options.primaryShutdownFn(signal), this.options.shutdownTimeout, 'primary shutdown timeout'), 'Error in primary shutdown function');
logger.info(`Primary process (pid: ${process.pid}) shutdown completed`);
this.emit('shutdown:completed');
process.exit(0);
}
/**
* Shuts down all workers gracefully
*/
async shutdownAllWorkers(signal) {
if (this.workers.size === 0)
return;
logger.info(`Shutting down ${this.workers.size} workers...`);
// Mark all workers as shutting down and send shutdown message
Array.from(this.workers.values()).forEach((workerState) => {
workerState.isShuttingDown = true;
// Only send shutdown message if worker is still connected
if (!workerState.worker.isDead() && workerState.worker.process.connected) {
const shutdownMessage = {
type: 'shutdown',
signal,
};
try {
workerState.worker.send(shutdownMessage);
}
catch (error) {
// Worker IPC channel is already closed, which is fine
logger.warn(`Failed to send shutdown message to worker ${workerState.id} (pid: ${workerState.worker.process.pid}): ${error instanceof Error ? error.message : String(error)}`);
}
}
});
// Wait for workers to exit gracefully or timeout
try {
await (0, utils_1.timeout)(this.waitForAllWorkersToExit(), this.options.shutdownTimeout, 'Worker shutdown timeout');
}
catch (error) {
logger.error('Graceful shutdown for workers timed out, forcing shutdown');
this.forceKillAllWorkers();
}
}
/**
* Waits for all workers to exit
*/
async waitForAllWorkersToExit() {
while (this.workers.size > 0) {
// eslint-disable-next-line no-await-in-loop
await (0, utils_1.delay)(100);
}
}
/**
* Force kills all remaining workers
*/
forceKillAllWorkers() {
Array.from(this.workers.values()).forEach((workerState) => {
logger.error(`Force killing worker ${workerState.id} (pid: ${workerState.worker.process.pid})`);
workerState.worker.kill('SIGKILL');
});
this.workers.clear();
}
/**
* Shuts down a worker process
*/
async shutdownWorker(signal) {
logger.warn(`Worker ${this.getCurrentWorkerId()} (pid: ${process.pid}) shutting down (signal: ${signal ?? 'manual'})`);
this.removeSignalHandlers();
// Execute worker shutdown function
await (0, utils_1.safeExecute)(() => (0, utils_1.timeout)(this.options.workerShutdownFn(signal), this.options.shutdownTimeout, 'worker shutdown timeout'), `Error in worker ${this.getCurrentWorkerId()} (pid: ${process.pid}) shutdown function`);
logger.info(`Worker ${this.getCurrentWorkerId()} (pid: ${process.pid}) shutdown completed`);
process.exit(0);
}
/**
* Gets the current number of active workers
*/
getWorkerCount() {
return this.workers.size;
}
/**
* Gets information about all workers
*/
getWorkerInfo() {
return Array.from(this.workers.values()).map((state) => ({
id: state.id,
pid: state.worker.process.pid ?? -1,
restartCount: state.restartCount,
}));
}
/**
* Checks if the cluster is currently started
*/
isStarted() {
return this.started;
}
/**
* Checks if the cluster is currently shutting down
*/
isShutdown() {
return this.isShuttingDown;
}
}
exports.ClusterManager = ClusterManager;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jbHVzdGVyL21hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBR0EsbUNBQXNDO0FBQ3RDLGtEQUFvQztBQVVwQyxtQ0FBaUY7QUFFakY7Ozs7Ozs7OztHQVNHO0FBQ0gsTUFBYSxjQUFlLFNBQVEscUJBQVk7SUFlOUMsWUFBWSxVQUFpQyxFQUFFO1FBQzdDLEtBQUssRUFBRSxDQUFDO1FBYk8sWUFBTyxHQUFHLElBQUksR0FBRyxFQUF1QixDQUFDO1FBRWxELFlBQU8sR0FBRyxLQUFLLENBQUM7UUFFaEIsbUJBQWMsR0FBRyxLQUFLLENBQUM7UUFFdkIsb0JBQWUsR0FBeUIsSUFBSSxDQUFDO1FBRTdDLHdCQUFtQixHQUEwQixJQUFJLENBQUM7UUFFbEQsbUJBQWMsR0FBRyxJQUFJLEdBQUcsRUFBOEIsQ0FBQztRQUk3RCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUEsaUNBQXlCLEVBQUMsT0FBTyxDQUFDLENBQUM7UUFDbEQsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQ3BCLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ssVUFBVTtRQUNoQixnREFBZ0Q7UUFDaEQsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNuQyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUM7Z0JBQ2hDLGFBQWEsRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLGFBQWE7YUFDMUMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUNELElBQUksQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO0lBQzdCLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksS0FBSyxDQUFDLEtBQUs7UUFDaEIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZ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S0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQ3ZELEVBQUUsRUFBRSxLQUFLLENBQUMsRUFBRTtZQUNaLEdBQUcsRUFBRSxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO1lBQ25DLFlBQVksRUFBRSxLQUFLLENBQUMsWUFBWTtTQUNqQyxDQUFDLENBQUMsQ0FBQztJQUNOLENBQUM7SUFFRDs7T0FFRztJQUNJLFNBQVM7UUFDZCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDdEIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksVUFBVTtRQUNmLE9BQU8sSUFBSSxDQUFDLGNBQWMsQ0FBQztJQUM3QixDQUFDO0NBQ0Y7QUF0aUJELHdDQXNpQkMiLCJzb3VyY2VzQ29udGVudCI6WyIvKiBlc2xpbnQtZGlzYWJsZSBjbGFzcy1tZXRob2RzLXVzZS10aGlzICovXG4vKiBlc2xpbnQtZGlzYWJsZSBuby1wYXJhbS1yZWFzc2lnbiAqL1xuaW1wb3J0IHsgV29ya2VyIH0gZnJvbSAnY2x1c3Rlcic7XG5pbXBvcnQgeyBFdmVudEVtaXR0ZXIgfSBmcm9tICdldmVudHMnO1xuaW1wb3J0ICogYXMgbG9nZ2VyIGZyb20gJy4uL2xvZ2dlcic7XG5pbXBvcnQge1xuICBDbHVzdGVyTWFuYWdlck9wdGlvbnMsXG4gIENsdXN0ZXJNYW5hZ2VyRXZlbnRzLFxuICBXb3JrZXJTdGF0ZSxcbiAgSVBDTWVzc2FnZSxcbiAgUGluZ01lc3NhZ2UsXG4gIFBvbmdNZXNzYWdlLFxuICBTaHV0ZG93bk1lc3NhZ2UsXG59IGZyb20gJy4vdHlwZXMnO1xuaW1wb3J0IHsgZGVsYXksIHRpbWVvdXQsIHNhZmVFeGVjdXRlLCB2YWxpZGF0ZUFuZERlZmF1bHRPcHRpb25zIH0gZnJvbSAnLi91dGlscyc7XG5cbi8qKlxuICogQ2x1c3Rlck1hbmFnZXIgLSBBIGNsdXN0ZXIgbGlmZWN5Y2xlIG1hbmFnZW1lbnQgc3lzdGVtXG4gKlxuICogVGhlIG1hbmFnZXIgc3VwcG9ydHMgdGhlIGZvbGxvd2luZyBmZWF0dXJlczpcbiAqIC0gR3JhY2VmdWwgc2h1dGRvd24gd2l0aCBjb25maWd1cmFibGUgdGltZW91dFxuICogLSBXb3JrZXIgaGVhbHRoIG1vbml0b3Jpbmcgd2l0aCBwaW5nL3BvbmcgbWVjaGFuaXNtXG4gKiAtIEF1dG9tYXRpYyB3b3JrZXIgcmVzdGFydCB3aXRoIGNvbmZpZ3VyYWJsZSBsaW1pdHNcbiAqIC0gU2lnbmFsIGhhbmRsaW5nIGZvciBzaHV0ZG93biB0cmlnZ2Vyc1xuICogLSBGbGV4aWJsZSBwcmltYXJ5IGFuZCB3b3JrZXIgZnVuY3Rpb24gaGFuZGxlcnNcbiAqL1xuZXhwb3J0IGNsYXNzIENsdXN0ZXJNYW5hZ2VyIGV4dGVuZHMgRXZlbnRFbWl0dGVyIHtcbiAgcHJpdmF0ZSByZWFkb25seSBvcHRpb25zOiBSZXF1aXJlZDxDbHVzdGVyTWFuYWdlck9wdGlvbnM+O1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgd29ya2VycyA9IG5ldyBNYXA8bnVtYmVyLCBXb3JrZXJTdGF0ZT4oKTtcblxuICBwcml2YXRlIHN0YXJ0ZWQgPSBmYWxzZTtcblxuICBwcml2YXRlIGlzU2h1dHRpbmdEb3duID0gZmFsc2U7XG5cbiAgcHJpdmF0ZSBzaHV0ZG93blByb21pc2U6IFByb21pc2U8dm9pZD4gfCBudWxsID0gbnVsbDtcblxuICBwcml2YXRlIGhlYWx0aENoZWNrSW50ZXJ2YWw6IE5vZGVKUy5UaW1lb3V0IHwgbnVsbCA9IG51bGw7XG5cbiAgcHJpdmF0ZSBzaWduYWxIYW5kbGVycyA9IG5ldyBNYXA8Tm9kZUpTLlNpZ25hbHMsICgpID0+IHZvaWQ+KCk7XG5cbiAgY29uc3RydWN0b3Iob3B0aW9uczogQ2x1c3Rlck1hbmFnZXJPcHRpb25zID0ge30pIHtcbiAgICBzdXBlcigpO1xuICAgIHRoaXMub3B0aW9ucyA9IHZhbGlkYXRlQW5kRGVmYXVsdE9wdGlvbnMob3B0aW9ucyk7XG4gICAgdGhpcy5pbml0aWFsaXplKCk7XG4gIH1cblxuICAvKipcbiAgICogSW5pdGlhbGl6ZSB0aGUgY2x1c3RlciBtYW5hZ2VyIHdpdGggcHJpbWFyeSBwcm9jZXNzIGNvbmZpZ3VyYXRpb25cbiAgICpcbiAgICogQHNlZSB7QGxpbmsgaHR0cHM6Ly9ub2RlanMub3JnL2RvY3MvbGF0ZXN0L2FwaS9jbHVzdGVyLmh0bWwjY2x1c3RlcnNldHVwcHJpbWFyeW9wdGlvbnMgTm9kZS5qcyBjbHVzdGVyLnNldHVwUHJpbWFyeSgpfVxuICAgKi9cbiAgcHJpdmF0ZSBpbml0aWFsaXplKCk6IHZvaWQge1xuICAgIC8vIE9ubHkgY2FsbCBzZXR1cFByaW1hcnkgaW4gdGhlIHByaW1hcnkgcHJvY2Vzc1xuICAgIGlmICh0aGlzLm9wdGlvbnMuY2x1c3Rlci5pc1ByaW1hcnkpIHtcbiAgICAgIHRoaXMub3B0aW9ucy5jbHVzdGVyLnNldHVwUHJpbWFyeSh7XG4gICAgICAgIHNlcmlhbGl6YXRpb246IHRoaXMub3B0aW9ucy5zZXJpYWxpemF0aW9uLFxuICAgICAgfSk7XG4gICAgfVxuICAgIHRoaXMuc2V0dXBTaWduYWxIYW5kbGVycygpO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0YXJ0cyB0aGUgY2x1c3RlciBtYW5hZ2VyXG4gICAqIEluIHByaW1hcnkgcHJvY2Vzczogc3RhcnRzIHdvcmtlcnMgYW5kIGhlYWx0aCBtb25pdG9yaW5nXG4gICAqIEluIHdvcmtlciBwcm9jZXNzOiBleGVjdXRlcyB3b3JrZXIgZnVuY3Rpb25cbiAgICovXG4gIHB1YmxpYyBhc3luYyBzdGFydCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy5vcHRpb25zLmNsdXN0ZXIuaXNQcmltYXJ5KSB7XG4gICAgICBhd2FpdCB0aGlzLnN0YXJ0UHJpbWFyeSgpO1xuICAgIH0gZWxzZSB7XG4gICAgICBhd2FpdCB0aGlzLnN0YXJ0V29ya2VyKCk7XG4gICAgfVxuICAgIHRoaXMuc3RhcnRlZCA9IHRydWU7XG4gIH1cblxuICAvKipcbiAgICogSW5pdGlhdGVzIGdyYWNlZnVsIHNodXRkb3duIG9mIHRoZSBjbHVzdGVyXG4gICAqL1xuICBwdWJsaWMgc2h1dGRvd24oc2lnbmFsPzogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKCF0aGlzLnN0YXJ0ZWQpIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5zaHV0ZG93blByb21pc2UpIHtcbiAgICAgIHJldHVybiB0aGlzLnNodXRkb3duUHJvbWlzZTtcbiAgICB9XG5cbiAgICB0aGlzLmlzU2h1dHRpbmdEb3duID0gdHJ1ZTtcbiAgICB0aGlzLmVtaXQoJ3NodXRkb3duOnN0YXJ0ZWQnLCBzaWduYWwpO1xuXG4gICAgaWYgKHRoaXMub3B0aW9ucy5jbHVzdGVyLmlzUHJpbWFyeSkge1xuICAgICAgdGhpcy5zaHV0ZG93blByb21pc2UgPSB0aGlzLnNodXRkb3duUHJpbWFyeShzaWduYWwpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLnNodXRkb3duUHJvbWlzZSA9IHRoaXMuc2h1dGRvd25Xb3JrZXIoc2lnbmFsKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5zaHV0ZG93blByb21pc2U7XG4gIH1cblxuICAvKipcbiAgICogU2V0cyB1cCBzaWduYWwgaGFuZGxlcnMgZm9yIGdyYWNlZnVsIHNodXRkb3duXG4gICAqL1xuICBwcml2YXRlIHNldHVwU2lnbmFsSGFuZGxlcnMoKTogdm9pZCB7XG4gICAgdGhpcy5vcHRpb25zLnNodXRkb3duU2lnbmFscy5mb3JFYWNoKChzaWduYWwpID0+IHtcbiAgICAgIGNvbnN0IGhhbmRsZXIgPSAoKSA9PiB7XG4gICAgICAgIHRoaXMuc2h1dGRvd24oc2lnbmFsKTtcbiAgICAgIH07XG4gICAgICB0aGlzLnNpZ25hbEhhbmRsZXJzLnNldChzaWduYWwsIGhhbmRsZXIpO1xuICAgICAgcHJvY2Vzcy5vbihzaWduYWwsIGhhbmRsZXIpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgc2lnbmFsIGhhbmRsZXJzXG4gICAqL1xuICBwcml2YXRlIHJlbW92ZVNpZ25hbEhhbmRsZXJzKCk6IHZvaWQge1xuICAgIEFycmF5LmZyb20odGhpcy5zaWduYWxIYW5kbGVycy5lbnRyaWVzKCkpLmZvckVhY2goKFtzaWduYWwsIGhhbmRsZXJdKSA9PiB7XG4gICAgICBwcm9jZXNzLnJlbW92ZUxpc3RlbmVyKHNpZ25hbCwgaGFuZGxlcik7XG4gICAgfSk7XG4gICAgdGhpcy5zaWduYWxIYW5kbGVycy5jbGVhcigpO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0YXJ0cyB0aGUgcHJpbWFyeSBwcm9jZXNzXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHN0YXJ0UHJpbWFyeSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsb2dnZXIuaW5mbyhcbiAgICAgIGBQcmltYXJ5IHByb2Nlc3MgKHBpZDogJHtwcm9jZXNzLnBpZH0pIHN0YXJ0aW5nIHdpdGggJHt0aGlzLm9wdGlvbnMubnVtV29ya2Vyc30gd29ya2Vyc2AsXG4gICAgKTtcblxuICAgIC8vIEV4ZWN1dGUgcHJpbWFyeSBpbml0aWFsaXphdGlvbiBmdW5jdGlvbiwgYW55IGVycm9yIHdpbGwgc3RvcCB0aGUgY2x1c3RlciBmcm9tIHN0YXJ0aW5nXG4gICAgYXdhaXQgdGhpcy5vcHRpb25zLnByaW1hcnlGbigpO1xuXG4gICAgLy8gU2V0IHVwIGNsdXN0ZXIgZXZlbnQgaGFuZGxlcnNcbiAgICB0aGlzLnNldHVwQ2x1c3RlckV2ZW50SGFuZGxlcnMoKTtcblxuICAgIC8vIFNwYXduIGluaXRpYWwgd29ya2Vyc1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdGhpcy5vcHRpb25zLm51bVdvcmtlcnM7IGkgKz0gMSkge1xuICAgICAgY29uc3Qgd29ya2VySWQgPSBpICsgMTsgLy8gV29ya2VyIElEcyBzdGFydCBmcm9tIDFcbiAgICAgIHRoaXMuc3Bhd25Xb3JrZXIod29ya2VySWQpO1xuICAgIH1cblxuICAgIC8vIFN0YXJ0IGhlYWx0aCBtb25pdG9yaW5nXG4gICAgdGhpcy5zdGFydEhlYWx0aE1vbml0b3JpbmcoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdGFydHMgYSB3b3JrZXIgcHJvY2Vzc1xuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBzdGFydFdvcmtlcigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBsb2dnZXIuaW5mbyhgV29ya2VyICR7dGhpcy5nZXRDdXJyZW50V29ya2VySWQoKX0gKHBpZDogJHtwcm9jZXNzLnBpZH0pIHN0YXJ0aW5nYCk7XG5cbiAgICAvLyBTZXQgdXAgSVBDIG1lc3NhZ2UgaGFuZGxlcnNcbiAgICB0aGlzLnNldHVwV29ya2VyTWVzc2FnZUhhbmRsZXJzKCk7XG5cbiAgICAvLyBFeGVjdXRlIHdvcmtlciBpbml0aWFsaXphdGlvbiBmdW5jdGlvbiwgYW55IGVycm9yIHdpbGwgYmUgcHJvcGFnYXRlZFxuICAgIGF3YWl0IHRoaXMub3B0aW9ucy53b3JrZXJGbigpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNldHMgdXAgY2x1c3RlciBldmVudCBoYW5kbGVycyBmb3IgdGhlIHByaW1hcnkgcHJvY2Vzc1xuICAgKi9cbiAgcHJpdmF0ZSBzZXR1cENsdXN0ZXJFdmVudEhhbmRsZXJzKCk6IHZvaWQge1xuICAgIHRoaXMub3B0aW9ucy5jbHVzdGVyLm9uKCdleGl0JywgKHdvcmtlcjogV29ya2VyLCBjb2RlOiBudW1iZXIsIHNpZ25hbDogc3RyaW5nKSA9PiB7XG4gICAgICB0aGlzLmhhbmRsZVdvcmtlckV4aXQod29ya2VyLCBjb2RlLCBzaWduYWwpO1xuICAgIH0pO1xuXG4gICAgdGhpcy5vcHRpb25zLmNsdXN0ZXIub24oJ29ubGluZScsICh3b3JrZXI6IFdvcmtlcikgPT4ge1xuICAgICAgbG9nZ2VyLmluZm8oYFdvcmtlciAke3RoaXMuZ2V0V29ya2VySWQod29ya2VyKX0gKHBpZDogJHt3b3JrZXIucHJvY2Vzcy5waWR9KSBpcyBvbmxpbmVgKTtcbiAgICAgIHRoaXMuZW1pdCgnd29ya2VyOnN0YXJ0ZWQnLCB3b3JrZXIpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFNldHMgdXAgSVBDIG1lc3NhZ2UgaGFuZGxlcnMgZm9yIHdvcmtlciBwcm9jZXNzZXNcbiAgICovXG4gIHByaXZhdGUgc2V0dXBXb3JrZXJNZXNzYWdlSGFuZGxlcnMoKTogdm9pZCB7XG4gICAgcHJvY2Vzcy5vbignbWVzc2FnZScsIChtZXNzYWdlOiBJUENNZXNzYWdlKSA9PiB7XG4gICAgICBzd2l0Y2ggKG1lc3NhZ2UudHlwZSkge1xuICAgICAgICBjYXNlICdwaW5nJzpcbiAgICAgICAgICB0aGlzLmhhbmRsZVdvcmtlclBpbmcobWVzc2FnZSk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJ3NodXRkb3duJzpcbiAgICAgICAgICB0aGlzLnNodXRkb3duKG1lc3NhZ2Uuc2lnbmFsKTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICBpZiAoIW1lc3NhZ2UudHlwZS5pbmNsdWRlcygnTWV0cmljcycpKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihgaWdub3JpbmcgdW5rbm93biBtZXNzYWdlIHR5cGUgaW4gd29ya2VyICR7cHJvY2Vzcy5waWR9OiAke21lc3NhZ2UudHlwZX1gKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogU3Bhd25zIGEgbmV3IHdvcmtlciBhbmQgc2V0cyB1cCBpdHMgc3RhdGVcbiAgICovXG4gIHByaXZhdGUgc3Bhd25Xb3JrZXIoaWQ6IG51bWJlcik6IFdvcmtlciB7XG4gICAgY29uc3Qgd29ya2VyID0gdGhpcy5vcHRpb25zLmNsdXN0ZXIuZm9yayh7XG4gICAgICBXT1JLRVJfSUQ6IGlkLFxuICAgIH0pO1xuICAgIGNvbnN0IHdvcmtlclN0YXRlOiBXb3JrZXJTdGF0ZSA9IHtcbiAgICAgIGlkLFxuICAgICAgd29ya2VyLFxuICAgICAgcmVzdGFydENvdW50OiAwLFxuICAgICAgbGFzdFBpbmc6IERhdGUubm93KCksXG4gICAgICBwZW5kaW5nUGluZzogZmFsc2UsXG4gICAgICBpc1NodXR0aW5nRG93bjogZmFsc2UsXG4gICAgfTtcblxuICAgIHRoaXMud29ya2Vycy5zZXQod29ya2VyLmlkLCB3b3JrZXJTdGF0ZSk7XG5cbiAgICAvLyBTZXQgdXAgd29ya2VyIG1lc3NhZ2UgaGFuZGxlclxuICAgIHdvcmtlci5vbignbWVzc2FnZScsIChtZXNzYWdlOiBJUENNZXNzYWdlKSA9PiB7XG4gICAgICB0aGlzLmhhbmRsZVdvcmtlck1lc3NhZ2Uod29ya2VyLCBtZXNzYWdlKTtcbiAgICB9KTtcblxuICAgIHJldHVybiB3b3JrZXI7XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyBtZXNzYWdlcyBmcm9tIHdvcmtlcnNcbiAgICovXG4gIHByaXZhdGUgaGFuZGxlV29ya2VyTWVzc2FnZSh3b3JrZXI6IFdvcmtlciwgbWVzc2FnZTogSVBDTWVzc2FnZSk6IHZvaWQge1xuICAgIGNvbnN0IHdvcmtlclN0YXRlID0gdGhpcy53b3JrZXJzLmdldCh3b3JrZXIuaWQpO1xuICAgIGlmICghd29ya2VyU3RhdGUpIHJldHVybjtcblxuICAgIHN3aXRjaCAobWVzc2FnZS50eXBlKSB7XG4gICAgICBjYXNlICdwb25nJzpcbiAgICAgICAgd29ya2VyU3RhdGUubGFzdFBpbmcgPSBEYXRlLm5vdygpO1xuICAgICAgICB3b3JrZXJTdGF0ZS5wZW5kaW5nUGluZyA9IGZhbHNlO1xuICAgICAgICBicmVhaztcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGlmICghbWVzc2FnZS50eXBlLmluY2x1ZGVzKCdNZXRyaWNzJykpIHtcbiAgICAgICAgICBsb2dnZXIud2FybihcbiAgICAgICAgICAgIGBSZWNlaXZlZCB1bmtub3duIG1lc3NhZ2UgdHlwZSBmcm9tIHdvcmtlciAke3RoaXMuZ2V0V29ya2VySWQod29ya2VyKX0gKHBpZDogJHtcbiAgICAgICAgICAgICAgd29ya2VyLnByb2Nlc3MucGlkXG4gICAgICAgICAgICB9KTogJHttZXNzYWdlLnR5cGV9YCxcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIGJyZWFrO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGVzIHBpbmcgbWVzc2FnZXMgaW4gd29ya2VyIHByb2Nlc3Nlc1xuICAgKi9cbiAgcHJpdmF0ZSBoYW5kbGVXb3JrZXJQaW5nKG1lc3NhZ2U6IFBpbmdNZXNzYWdlKTogdm9pZCB7XG4gICAgY29uc3QgcG9uZ01lc3NhZ2U6IFBvbmdNZXNzYWdlID0ge1xuICAgICAgdHlwZTogJ3BvbmcnLFxuICAgICAgdGltZXN0YW1wOiBtZXNzYWdlLnRpbWVzdGFtcCxcbiAgICB9O1xuXG4gICAgaWYgKHByb2Nlc3Muc2VuZCkge1xuICAgICAgcHJvY2Vzcy5zZW5kKHBvbmdNZXNzYWdlKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU3RhcnRzIGhlYWx0aCBtb25pdG9yaW5nIGZvciBhbGwgd29ya2Vyc1xuICAgKi9cbiAgcHJpdmF0ZSBzdGFydEhlYWx0aE1vbml0b3JpbmcoKTogdm9pZCB7XG4gICAgdGhpcy5oZWFsdGhDaGVja0ludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgdGhpcy5wZXJmb3JtSGVhbHRoQ2hlY2soKTtcbiAgICB9LCB0aGlzLm9wdGlvbnMucGluZ0ZyZXF1ZW5jeSk7XG4gIH1cblxuICAvKipcbiAgICogU3RvcHMgaGVhbHRoIG1vbml0b3JpbmdcbiAgICovXG4gIHByaXZhdGUgc3RvcEhlYWx0aE1vbml0b3JpbmcoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuaGVhbHRoQ2hlY2tJbnRlcnZhbCkge1xuICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLmhlYWx0aENoZWNrSW50ZXJ2YWwpO1xuICAgICAgdGhpcy5oZWFsdGhDaGVja0ludGVydmFsID0gbnVsbDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUGVyZm9ybXMgaGVhbHRoIGNoZWNrIG9uIGFsbCB3b3JrZXJzXG4gICAqL1xuICBwcml2YXRlIHBlcmZvcm1IZWFsdGhDaGVjaygpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5pc1NodXR0aW5nRG93bikgcmV0dXJuO1xuXG4gICAgQXJyYXkuZnJvbSh0aGlzLndvcmtlcnMudmFsdWVzKCkpXG4gICAgICAuZmlsdGVyKCh3b3JrZXJTdGF0ZSkgPT4gIXdvcmtlclN0YXRlLmlzU2h1dHRpbmdEb3duKVxuICAgICAgLmZvckVhY2goKHdvcmtlclN0YXRlKSA9PiB7XG4gICAgICAgIGNvbnN0IHRpbWVTaW5jZUxhc3RQaW5nID0gRGF0ZS5ub3coKSAtIHdvcmtlclN0YXRlLmxhc3RQaW5nO1xuXG4gICAgICAgIGlmICh3b3JrZXJTdGF0ZS5wZW5kaW5nUGluZyAmJiB0aW1lU2luY2VMYXN0UGluZyA+IHRoaXMub3B0aW9ucy5waW5nVGltZW91dCkge1xuICAgICAgICAgIC8vIFdvcmtlciBpcyBzdHVjaywgaGFuZGxlIGl0XG4gICAgICAgICAgdGhpcy5oYW5kbGVTdHVja1dvcmtlcih3b3JrZXJTdGF0ZSk7XG4gICAgICAgIH0gZWxzZSBpZiAoIXdvcmtlclN0YXRlLnBlbmRpbmdQaW5nKSB7XG4gICAgICAgICAgLy8gU2VuZCBwaW5nIHRvIHdvcmtlclxuICAgICAgICAgIHRoaXMuc2VuZFBpbmdUb1dvcmtlcih3b3JrZXJTdGF0ZSk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFNlbmRzIGEgcGluZyBtZXNzYWdlIHRvIGEgd29ya2VyXG4gICAqL1xuICBwcml2YXRlIHNlbmRQaW5nVG9Xb3JrZXIod29ya2VyU3RhdGU6IFdvcmtlclN0YXRlKTogdm9pZCB7XG4gICAgY29uc3QgcGluZ01lc3NhZ2U6IFBpbmdNZXNzYWdlID0ge1xuICAgICAgdHlwZTogJ3BpbmcnLFxuICAgICAgdGltZXN0YW1wOiBEYXRlLm5vdygpLFxuICAgIH07XG5cbiAgICB3b3JrZXJTdGF0ZS5wZW5kaW5nUGluZyA9IHRydWU7XG4gICAgdHJ5IHtcbiAgICAgIHdvcmtlclN0YXRlLndvcmtlci5zZW5kKHBpbmdNZXNzYWdlKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmVycm9yKFxuICAgICAgICBgRmFpbGVkIHRvIHNlbmQgcGluZyB0byB3b3JrZXIgJHt3b3JrZXJTdGF0ZS5pZH0gKHBpZDogJHtcbiAgICAgICAgICB3b3JrZXJTdGF0ZS53b3JrZXIucHJvY2Vzcy5waWRcbiAgICAgICAgfSk6ICR7ZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpfWAsXG4gICAgICApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGVzIGEgc3R1Y2sgd29ya2VyXG4gICAqL1xuICBwcml2YXRlIGhhbmRsZVN0dWNrV29ya2VyKHdvcmtlclN0YXRlOiBXb3JrZXJTdGF0ZSk6IHZvaWQge1xuICAgIGxvZ2dlci5lcnJvcihcbiAgICAgIGBXb3JrZXIgJHt3b3JrZXJTdGF0ZS5pZH0gKHBpZDogJHt3b3JrZXJTdGF0ZS53b3JrZXIucHJvY2Vzcy5waWR9KSBpcyBzdHVjaywga2lsbGluZyBpdGAsXG4gICAgKTtcbiAgICB0aGlzLmVtaXQoJ3dvcmtlcjpzdHVjaycsIHdvcmtlclN0YXRlLndvcmtlcik7XG5cbiAgICAvLyBSZW1vdmUgd29ya2VyIHN0YXRlXG4gICAgdGhpcy53b3JrZXJzLmRlbGV0ZSh3b3JrZXJTdGF0ZS53b3JrZXIuaWQpO1xuXG4gICAgLy8gS2lsbCB0aGUgd29ya2VyXG4gICAgd29ya2VyU3RhdGUud29ya2VyLmtpbGwoJ1NJR0tJTEwnKTtcblxuICAgIC8vIERldGVybWluZSBpZiB3ZSBzaG91bGQgc3Bhd24gYSByZXBsYWNlbWVudFxuICAgIGNvbnN0IHNob3VsZFNwYXduID0gdGhpcy5vcHRpb25zLnN0dWNrV29ya2VyUmVzcGF3bkZ1bmMod29ya2VyU3RhdGUud29ya2VyKTtcblxuICAgIGlmIChzaG91bGRTcGF3biAmJiAhdGhpcy5pc1NodXR0aW5nRG93bikge1xuICAgICAgbG9nZ2VyLmluZm8oXG4gICAgICAgIGBTcGF3bmluZyByZXBsYWNlbWVudCB3b3JrZXIgZm9yIHN0dWNrIHdvcmtlciAke3dvcmtlclN0YXRlLmlkfSAocGlkOiAke3dvcmtlclN0YXRlLndvcmtlci5wcm9jZXNzLnBpZH0pYCxcbiAgICAgICk7XG4gICAgICBjb25zdCB7IHJlc3RhcnRDb3VudCB9ID0gd29ya2VyU3RhdGU7IC8vIFByZXNlcnZlIHJlc3RhcnQgY291bnRcbiAgICAgIGNvbnN0IG5ld1dvcmtlciA9IHRoaXMuc3Bhd25Xb3JrZXIod29ya2VyU3RhdGUuaWQpO1xuICAgICAgY29uc3QgbmV3V29ya2VyU3RhdGUgPSB0aGlzLndvcmtlcnMuZ2V0KG5ld1dvcmtlci5pZCk7XG4gICAgICBpZiAobmV3V29ya2VyU3RhdGUpIHtcbiAgICAgICAgbmV3V29ya2VyU3RhdGUucmVzdGFydENvdW50ID0gcmVzdGFydENvdW50O1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBsb2dnZXIuZXJyb3IoXG4gICAgICAgIGBUcmlnZ2VyaW5nIGNsdXN0ZXIgc2h1dGRvd24gZHVlIHRvIHN0dWNrIHdvcmtlciAke3dvcmtlclN0YXRlLmlkfSAocGlkOiAke3dvcmtlclN0YXRlLndvcmtlci5wcm9jZXNzLnBpZH0pYCxcbiAgICAgICk7XG4gICAgICB0aGlzLnNodXRkb3duKCdTVFVDS19XT1JLRVInKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlcyB3b3JrZXIgZXhpdCBldmVudHNcbiAgICovXG4gIHByaXZhdGUgaGFuZGxlV29ya2VyRXhpdCh3b3JrZXI6IFdvcmtlciwgY29kZTogbnVtYmVyIHwgbnVsbCwgc2lnbmFsOiBzdHJpbmcgfCBudWxsKTogdm9pZCB7XG4gICAgY29uc3Qgd29ya2VySWQgPSB0aGlzLmdldFdvcmtlcklkKHdvcmtlcik7XG4gICAgdGhpcy5lbWl0KCd3b3JrZXI6ZGllZCcsIHdvcmtlciwgY29kZSwgc2lnbmFsKTtcblxuICAgIGNvbnN0IHdvcmtlclN0YXRlID0gdGhpcy53b3JrZXJzLmdldCh3b3JrZXIuaWQpO1xuICAgIGlmICghd29ya2VyU3RhdGUpIHtcbiAgICAgIGxvZ2dlci53YXJuKFxuICAgICAgICBgVW5rbm93biB3b3JrZXIgJHt3b3JrZXJJZH0gKHBpZDogJHt3b3JrZXIucHJvY2Vzcy5waWR9KSBleGl0ZWQgd2l0aCBjb2RlICR7Y29kZX0gYW5kIHNpZ25hbCAke3NpZ25hbH1gLFxuICAgICAgKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB0aGlzLndvcmtlcnMuZGVsZXRlKHdvcmtlci5pZCk7XG5cbiAgICAvLyBJZiB3ZSdyZSBzaHV0dGluZyBkb3duIG9yIHdvcmtlciB3YXMga2lsbGVkIGludGVudGlvbmFsbHksIGRvbid0IHJlc3RhcnRcbiAgICBpZiAodGhpcy5pc1NodXR0aW5nRG93biB8fCB3b3JrZXJTdGF0ZS5pc1NodXR0aW5nRG93bikge1xuICAgICAgbG9nZ2VyLmluZm8oXG4gICAgICAgIGBXb3JrZXIgJHt3b3JrZXJJZH0gKHBpZDogJHt3b3JrZXIucHJvY2Vzcy5waWR9KSBleGl0ZWQgd2l0aCBjb2RlICR7Y29kZX0gYW5kIHNpZ25hbCAke3NpZ25hbH1gLFxuICAgICAgKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbG9nZ2VyLmVycm9yKFxuICAgICAgYFdvcmtlciAke3dvcmtlcklkfSAocGlkOiAke