wal
Version:
An implementation of write-ahead logging (WAL) for nodejs.
443 lines (384 loc) • 37.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _assertPlus = require('assert-plus');
var _assertPlus2 = _interopRequireDefault(_assertPlus);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _stream = require('stream');
var _ranfile = require('ranfile');
var _ranfile2 = _interopRequireDefault(_ranfile);
var _queue = require('./queue');
var _queue2 = _interopRequireDefault(_queue);
var _logIndexFile = require('./log-index-file');
var _logIndexFile2 = _interopRequireDefault(_logIndexFile);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var DEFAULT_INDEX_EXT = '.lix';
var $index = Symbol('index');
var $file = Symbol('file');
/**
* An encapsulation of write-ahead logging behavior.
*
* Write-ahead logging (WAL) is a building block used to improve automicity and durability in distributed systems. WAL improves these properties by providing persistent, sequenced storage for _Log Entries_ as well as a record of which _Log Entries_ have been committed. Write-ahead logging enables systems to recover from failures, whether those failures are in software, storage, or the network.
*
* This implementation provides a basic semantic on which reliable systems can be built.
*/
var WriteAheadLog = function () {
function WriteAheadLog(file, index) {
_classCallCheck(this, WriteAheadLog);
_assertPlus2.default.object(file, 'file');
_assertPlus2.default.object(index, 'index');
this[$file] = file;
this[$index] = index;
}
/**
* @type {string} - The log's file name (fully qualified path).
*/
_createClass(WriteAheadLog, [{
key: 'isCommitted',
/**
* Determines if the specified LSN/index has been committed.
*
* ```javascript
* wal.isCommitted(lsn)
* .then(committed => {
* if (committed) console.log(lsn + ' is committed');
* });
* ```
* @param {number} index - The LSN/index to check.
* @returns {Promise} - A promise that upon success is resolved with boolean indicating whether the LSN/index has been committed.
*/
value: function isCommitted(index) {
_assertPlus2.default.number(index, 'index');
return this[$index].isCommitted(index);
}
/**
* Commits the specified, uncommitted LSN/index.
*
* ```javascript
* wal.commit(lsn)
* .then(lsn => {
* console.log(lsn + ' successfully committed');
* })
* .catch(err => {
* console.log(`Unexpected error during commit of ${lsn}: ${''+err.stack}.`);
* })
* ```
* @param {number} index - The LSN/index to commit.
* @returns {Promise} - A promise that upon success is resolved with the specified, committed LSN/index.
*/
}, {
key: 'commit',
value: function commit(index) {
_assertPlus2.default.number(index, 'index');
return this[$index].commit(index);
}
/**
* Writes a log entry.
*
* The entire contents of the specified `data` buffer is written as the log entry, the actual binary data is treated as opaque in the log. Since the log is not involved in the interpretation of the data, concerns such as encryption and data security is entirely the caller's responsibility.
*
* ```javascript
* wal.write(data)
* .then(lsn => {
* console.log(`Data written as Log Entry ${lsn}.`);
* })
* .catch(err => {
* console.log(`Unexpected error: ${''+err.stack}.`);
* })
* ```
* @param {Buffer} data - The log entry as an opaque data buffer.
* @returns {Promise} A promise that upon success is resolved with the new entry's LSN/index.
* @throws {AssertionError} thrown when `data` is unspecified or not a `Buffer`.
*/
}, {
key: 'write',
value: function write(data) {
var log = this[$file];
var idx = this[$index];
var head = idx.head;
return idx.offset(head).then(function (firstByte) {
return log.write(firstByte, data);
}).then(function (subsequentByte) {
return idx.increment(subsequentByte);
});
}
/**
* Reads the log entry at the specified LSN/index.
*
* ```javascript
* wal.read(lsn)
* .then(data => {
* console.log(`Log Entry ${lsn} has ${data.length} bytes of data.`);
* })
* .catch(err => {
* console.log(`Unexpected error: ${''+err.stack}.`);
* })
* ```
* @param {number} index - Specifies the LSN/index of the log entry to read.
* @returns {Promise} A promise that upon success is resolved with a buffer containing the log entry
* @throws {AssertionError} Thrown when the LSN/index is out of range.
*/
}, {
key: 'read',
value: function read(index) {
var log = this[$file];
var idx = this[$index];
return idx.get(index).then(function (rec) {
return log.read(rec.offset, rec.length);
});
}
/**
* Reads a range of log entries beginning at the specified `first` LSN/index. If count is not specified all subsequent log entries are returned.
*
* ```javascript
* wal.readRange(first)
* .then(stream => new Promise((resolve, reject) => {
* let acc = [];
* stream.on('data', data => {
* acc.push(data);
* })
* .on('end', () => resolve(acc))
* .on('error', reject);
* }))
* .then(entries => {
* console.log(`There are ${entries.length} log entries after LSN ${first}.`);
* })
* .catch(err => {
* console.log(`Unexpected error: ${''+err.stack}.`);
* })
* ```
* @param {number} first - Specifies the index of the first log entry to read.
* @param {number} count - Specifies the number of log entries to read.
* @returns {Readable} A readable stream where the log entries are streamed to the caller.
*/
}, {
key: 'readRange',
value: function readRange(first, count) {
_assertPlus2.default.number(first, 'first');
_assertPlus2.default.optionalNumber(count, 'count');
var idx = this[$index];
var log = this[$file];
if (count === undefined) {
// head points at the
count = idx.head - first;
}
// respect the flow-control specified streaming api.
var sending = false;
var ended = false;
var ready = new _queue2.default();
var stream = void 0;
function sendUntilPaused() {
while (sending && ready.length) {
sending = stream.push(ready.dequeue());
}
if (ended && ready.isEmpty) {
stream.push(null);
stream.emit('end');
}
}
function readyToSend(data) {
ready.enqueue(data);
sendUntilPaused();
}
function clearToSend() {
sending = true;
return sendUntilPaused();
}
// read the entries from the logfile and possibly buffer them
// for the readable stream.
idx.getRange(first, count).then(function (range) {
return range.reduce(function (acc, rec) {
return acc.then(function () {
return log.read(rec.offset, rec.length).then(readyToSend);
});
}, Promise.resolve());
}).then(function () {
if (ready.isEmpty) {
stream.push(null);
stream.emit('end');
} else {
ended = true;
sendUntilPaused();
}
}).catch(function (err) {
return stream.emit('error', err);
});
return stream = new _stream.Readable({
read: clearToSend
});
}
/**
* Truncates the log from the specified, uncommitted LSN/index.
*
* Since the `.next` property is usually ahead of the `.commitHead`, when this call succeeds it resets the write head to the newly truncated LSN/index position. Be aware that the very next `.write()` will re-use this most-recent truncated LSN/index.
*
* ```javascript
* wal.truncate(first)
* .then(size => {
* console.log(`There are ${bytes} committed to the log.`);
* })
* .catch(err => {
* console.log(`Unexpected error: ${''+err.stack}.`);
* })
* ```
* @param {number} from - Specifies the LSN/index where the log entries should be truncated.
* @returns {Promise} A promise that upon success is resolved with the new write head.
*/
}, {
key: 'truncate',
value: function truncate(from) {
_assertPlus2.default.number(from, 'from');
var idx = this[$index];
var log = this[$file];
return from < this.next ? idx.truncate(from).then(function (next) {
return log.truncate(next);
}) : idx.offset(from).then(function (offset) {
return log.truncate(offset);
});
}
}, {
key: 'recover',
value: function recover(handler) {
if (handler === false) {
handler = function handler() {
return false;
};
}
_assertPlus2.default.ok(typeof handler === 'function', 'handler (function) is required');
var head = this.next;
var committed = this.commitHead;
var self = this;
function truncAfterCommitHead() {
return self.truncate(self.commitHead + 1).then(function () {
return self;
});
}
function possiblyRecover(index) {
return self.read(index).then(function (data) {
var commit = handler(index, data);
if ((typeof commit === 'undefined' ? 'undefined' : _typeof(commit)) === 'object' && typeof commit.then === 'function') {
return commit.then(function (commit) {
if (commit) {
return self.commit(index).then(function (committed) {
return committed + 1 < head ? possiblyRecover(committed + 1) : truncAfterCommitHead();
});
}
return truncAfterCommitHead();
});
}
if (commit) {
return self.commit(index).then(function (committed) {
return committed + 1 < head ? possiblyRecover(committed + 1) : truncAfterCommitHead();
});
}
return truncAfterCommitHead();
});
}
return committed + 1 < head ? possiblyRecover(committed + 1) : Promise.resolve(self);
}
}, {
key: 'close',
value: function close() {
return Promise.all([this[$file].close(), this[$index].close()]);
}
}, {
key: 'name',
get: function get() {
return this[$file].name;
}
/**
* @type {string} - The log's index file name (fully qualified path).
*/
}, {
key: 'index',
get: function get() {
return this[$index].name;
}
/**
* @type {boolean} - Indicates whether the log was opened in a writable mode.
*/
}, {
key: 'writable',
get: function get() {
return this[$file].writable;
}
/**
* @type {number} - The log's size in bytes.
*/
}, {
key: 'size',
get: function get() {
return this[$file].size;
}
/**
* @type {number} - The next LSN/index (the write head).
*/
}, {
key: 'next',
get: function get() {
return this[$index].head;
}
/**
* @type {number} - The most recently commited LSN/index.
*/
}, {
key: 'commitHead',
get: function get() {
return this[$index].commitHead;
}
}]);
return WriteAheadLog;
}();
function open(options) {
_assertPlus2.default.object(options, 'options');
_assertPlus2.default.string(options.path, 'options.path');
_assertPlus2.default.optionalString(options.index, 'options.index');
_assertPlus2.default.optionalBool(options.writable, 'options.writable');
_assertPlus2.default.optionalBool(options.init, 'options.init');
var mode = _fs2.default.R_OK;
if (options.writable) {
mode |= _fs2.default.W_OK;
}
var idxFile = options.index || options.path + DEFAULT_INDEX_EXT;
var log = void 0;
// ensure existence/access to files before continuing...
return _ranfile2.default.open(options.path, options.writable).then(function (file) {
log = file;
return _logIndexFile2.default.open(idxFile, options.writable);
}).then(function (index) {
return new WriteAheadLog(log, index);
});
}
function create(options) {
_assertPlus2.default.object(options, 'options');
_assertPlus2.default.string(options.path, 'options.path');
_assertPlus2.default.optionalString(options.index, 'options.index');
var idxFile = options.index || options.path + DEFAULT_INDEX_EXT;
var log = void 0;
return _ranfile2.default.create(options.path).then(function (file) {
log = file;
return _logIndexFile2.default.create(idxFile, 0);
}).then(function (index) {
return new WriteAheadLog(log, index);
});
}
function openOrCreate(options) {
return WriteAheadLog.open(options).catch(function (err) {
// if it doesn't exist we'll create it if writable
if (err.code === 'ENOENT' && options.writable) {
return WriteAheadLog.create(options);
}
throw err;
});
}
WriteAheadLog.create = create;
WriteAheadLog.open = open;
WriteAheadLog.openOrCreate = openOrCreate;
WriteAheadLog.DEFAULT_INDEX_EXT = DEFAULT_INDEX_EXT;
exports.default = WriteAheadLog;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy93cml0ZS1haGVhZC1sb2cuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7QUFFQTs7OztBQUNBOzs7O0FBQ0E7O0FBQ0E7Ozs7QUFFQTs7OztBQUNBOzs7Ozs7OztBQUVBLElBQU0sb0JBQW9CLE1BQTFCOztBQUVBLElBQU0sU0FBUyxPQUFPLE9BQVAsQ0FBZjtBQUNBLElBQU0sUUFBUSxPQUFPLE1BQVAsQ0FBZDs7Ozs7Ozs7OztJQVNNLGE7QUFFSix5QkFBWSxJQUFaLEVBQWtCLEtBQWxCLEVBQXlCO0FBQUE7O0FBQ3ZCLHlCQUFPLE1BQVAsQ0FBYyxJQUFkLEVBQW9CLE1BQXBCO0FBQ0EseUJBQU8sTUFBUCxDQUFjLEtBQWQsRUFBcUIsT0FBckI7QUFDQSxTQUFLLEtBQUwsSUFBYyxJQUFkO0FBQ0EsU0FBSyxNQUFMLElBQWUsS0FBZjtBQUNEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztnQ0F3RFcsSyxFQUFPO0FBQ2pCLDJCQUFPLE1BQVAsQ0FBYyxLQUFkLEVBQXFCLE9BQXJCO0FBQ0EsYUFBTyxLQUFLLE1BQUwsRUFBYSxXQUFiLENBQXlCLEtBQXpCLENBQVA7QUFDRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7MkJBaUJNLEssRUFBTztBQUNaLDJCQUFPLE1BQVAsQ0FBYyxLQUFkLEVBQXFCLE9BQXJCO0FBQ0EsYUFBTyxLQUFLLE1BQUwsRUFBYSxNQUFiLENBQW9CLEtBQXBCLENBQVA7QUFDRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7MEJBb0JLLEksRUFBTTtBQUNWLFVBQUksTUFBTSxLQUFLLEtBQUwsQ0FBVjtBQUNBLFVBQUksTUFBTSxLQUFLLE1BQUwsQ0FBVjtBQUNBLFVBQUksT0FBTyxJQUFJLElBQWY7O0FBRUEsYUFBTyxJQUFJLE1BQUosQ0FBVyxJQUFYLEVBQ0osSUFESSxDQUNDLHFCQUFhO0FBQ2pCLGVBQU8sSUFBSSxLQUFKLENBQVUsU0FBVixFQUFxQixJQUFyQixDQUFQO0FBQ0QsT0FISSxFQUlKLElB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QUksSUFBSSxJQUFKLEtBQWEsUUFBYixJQUF5QixRQUFRLFFBQXJDLEVBQStDO0FBQzdDLGFBQU8sY0FBYyxNQUFkLENBQXFCLE9BQXJCLENBQVA7QUFDRDtBQUNELFVBQU0sR0FBTjtBQUNELEdBUEksQ0FBUDtBQVFEOztBQUVELGNBQWMsTUFBZCxHQUF1QixNQUF2QjtBQUNBLGNBQWMsSUFBZCxHQUFxQixJQUFyQjtBQUNBLGNBQWMsWUFBZCxHQUE2QixZQUE3QjtBQUNBLGNBQWMsaUJBQWQsR0FBa0MsaUJBQWxDOztrQkFFZSxhIiwiZmlsZSI6IndyaXRlLWFoZWFkLWxvZy5qcyIsInNvdXJjZXNDb250ZW50IjpbIid1c2Ugc3RyaWN0JztcblxuaW1wb3J0IGFzc2VydCBmcm9tICdhc3NlcnQtcGx1cyc7XG5pbXBvcnQgZnMgZnJvbSAnZnMnO1xuaW1wb3J0IHsgUmVhZGFibGUgfSBmcm9tICdzdHJlYW0nO1xuaW1wb3J0IFJhbmRvbUFjY2Vzc0ZpbGUgZnJvbSAncmFuZmlsZSc7XG5cbmltcG9ydCBRdWV1ZSBmcm9tICcuL3F1ZXVlJztcbmltcG9ydCBMb2dJbmRleEZpbGUgZnJvbSAnLi9sb2ctaW5kZXgtZmlsZSc7XG5cbmNvbnN0IERFRkFVTFRfSU5ERVhfRVhUID0gJy5saXgnO1xuXG5jb25zdCAkaW5kZXggPSBTeW1ib2woJ2luZGV4Jyk7XG5jb25zdCAkZmlsZSA9IFN5bWJvbCgnZmlsZScpO1xuXG4vKipcbiAqIEFuIGVuY2Fwc3VsYXRpb24gb2Ygd3JpdGUtYWhlYWQgbG9nZ2luZyBiZWhhdmlvci5cbiAqXG4gKiBXcml0ZS1haGVhZCBsb2dnaW5nIChXQUwpIGlzIGEgYnVpbGRpbmcgYmxvY2sgdXNlZCB0byBpbXByb3ZlIGF1dG9taWNpdHkgYW5kIGR1cmFiaWxpdHkgaW4gZGlzdHJpYnV0ZWQgc3lzdGVtcy4gV0FMIGltcHJvdmVzIHRoZXNlIHByb3BlcnRpZXMgYnkgcHJvdmlkaW5nIHBlcnNpc3RlbnQsIHNlcXVlbmNlZCBzdG9yYWdlIGZvciBfTG9nIEVudHJpZXNfIGFzIHdlbGwgYXMgYSByZWNvcmQgb2Ygd2hpY2ggX0xvZyBFbnRyaWVzXyBoYXZlIGJlZW4gY29tbWl0dGVkLiBXcml0ZS1haGVhZCBsb2dnaW5nIGVuYWJsZXMgc3lzdGVtcyB0byByZWNvdmVyIGZyb20gZmFpbHVyZXMsIHdoZXRoZXIgdGhvc2UgZmFpbHVyZXMgYXJlIGluIHNvZnR3YXJlLCBzdG9yYWdlLCBvciB0aGUgbmV0d29yay5cbiAqXG4gKiBUaGlzIGltcGxlbWVudGF0aW9uIHByb3ZpZGVzIGEgYmFzaWMgc2VtYW50aWMgb24gd2hpY2ggcmVsaWFibGUgc3lzdGVtcyBjYW4gYmUgYnVpbHQuXG4gKi9cbmNsYXNzIFdyaXRlQWhlYWRMb2cge1xuXG4gIGNvbnN0cnVjdG9yKGZpbGUsIGluZGV4KSB7XG4gICAgYXNzZXJ0Lm9iamVjdChmaWxlLCAnZmlsZScpO1xuICAgIGFzc2VydC5vYmplY3QoaW5kZXgsICdpbmRleCcpO1xuICAgIHRoaXNbJGZpbGVdID0gZmlsZTtcbiAgICB0aGlzWyRpbmRleF0gPSBpbmRleDtcbiAgfVxuXG4gIC8qKlxuICAgKiBAdHlwZSB7c3RyaW5nfSAtIFRoZSBsb2cncyBmaWxlIG5hbWUgKGZ1bGx5IHF1YWxpZmllZCBwYXRoKS5cbiAgICovXG4gIGdldCBuYW1lKCkge1xuICAgIHJldHVybiB0aGlzWyRmaWxlXS5uYW1lO1xuICB9XG5cbiAgLyoqXG4gICAqIEB0eXBlIHtzdHJpbmd9IC0gVGhlIGxvZydzIGluZGV4IGZpbGUgbmFtZSAoZnVsbHkgcXVhbGlmaWVkIHBhdGgpLlxuICAgKi9cbiAgZ2V0IGluZGV4KCkge1xuICAgIHJldHVybiB0aGlzWyRpbmRleF0ubmFtZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAdHlwZSB7Ym9vbGVhbn0gLSBJbmRpY2F0ZXMgd2hldGhlciB0aGUgbG9nIHdhcyBvcGVuZWQgaW4gYSB3cml0YWJsZSBtb2RlLlxuICAgKi9cbiAgZ2V0IHdyaXRhYmxlKCkge1xuICAgIHJldHVybiB0aGlzWyRmaWxlXS53cml0YWJsZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAdHlwZSB7bnVtYmVyfSAtIFRoZSBsb2cncyBzaXplIGluIGJ5dGVzLlxuICAgKi9cbiAgZ2V0IHNpemUoKSB7XG4gICAgcmV0dXJuIHRoaXNbJGZpbGVdLnNpemU7XG4gIH1cblxuICAvKipcbiAgICogQHR5cGUge251bWJlcn0gLSBUaGUgbmV4dCBMU04vaW5kZXggKHRoZSB3cml0ZSBoZWFkKS5cbiAgICovXG4gIGdldCBuZXh0KCkge1xuICAgIHJldHVybiB0aGlzWyRpbmRleF0uaGVhZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBAdHlwZSB7bnVtYmVyfSAtIFRoZSBtb3N0IHJlY2VudGx5IGNvbW1pdGVkIExTTi9pbmRleC5cbiAgICovXG4gIGdldCBjb21taXRIZWFkKCkge1xuICAgIHJldHVybiB0aGlzWyRpbmRleF0uY29tbWl0SGVhZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBEZXRlcm1pbmVzIGlmIHRoZSBzcGVjaWZpZWQgTFNOL2luZGV4IGhhcyBiZWVuIGNvbW1pdHRlZC5cbiAgICpcbiAgICogYGBgamF2YXNjcmlwdFxuICAgKiB3YWwuaXNDb21taXR0ZWQobHNuKVxuICAgKiAgIC50aGVuKGNvbW1pdHRlZCA9PiB7XG4gICAqICAgICAgaWYgKGNvbW1pdHRlZCkgY29uc29sZS5sb2cobHNuICsgJyBpcyBjb21taXR0ZWQnKTtcbiAgICogICB9KTtcbiAgICogYGBgXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBpbmRleCAtIFRoZSBMU04vaW5kZXggdG8gY2hlY2suXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSAtIEEgcHJvbWlzZSB0aGF0IHVwb24gc3VjY2VzcyBpcyByZXNvbHZlZCB3aXRoIGJvb2xlYW4gaW5kaWNhdGluZyB3aGV0aGVyIHRoZSBMU04vaW5kZXggaGFzIGJlZW4gY29tbWl0dGVkLlxuICAgKi9cbiAgaXNDb21taXR0ZWQoaW5kZXgpIHtcbiAgICBhc3NlcnQubnVtYmVyKGluZGV4LCAnaW5kZXgnKTtcbiAgICByZXR1cm4gdGhpc1skaW5kZXhdLmlzQ29tbWl0dGVkKGluZGV4KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb21taXRzIHRoZSBzcGVjaWZpZWQsIHVuY29tbWl0dGVkIExTTi9pbmRleC5cbiAgICpcbiAgICogYGBgamF2YXNjcmlwdFxuICAgKiB3YWwuY29tbWl0KGxzbilcbiAgICogICAudGhlbihsc24gPT4ge1xuICAgKiAgICAgIGNvbnNvbGUubG9nKGxzbiArICcgc3VjY2Vzc2Z1bGx5IGNvbW1pdHRlZCcpO1xuICAgKiAgIH0pXG4gICAqICAgLmNhdGNoKGVyciA9PiB7XG4gICAqICAgICAgY29uc29sZS5sb2coYFVuZXhwZWN0ZWQgZXJyb3IgZHVyaW5nIGNvbW1pdCBvZiAke2xzbn06ICR7JycrZXJyLnN0YWNrfS5gKTtcbiAgICogICB9KVxuICAgKiBgYGBcbiAgICogQHBhcmFtIHtudW1iZXJ9IGluZGV4IC0gVGhlIExTTi9pbmRleCB0byBjb21taXQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSAtIEEgcHJvbWlzZSB0aGF0IHVwb24gc3VjY2VzcyBpcyByZXNvbHZlZCB3aXRoIHRoZSBzcGVjaWZpZWQsIGNvbW1pdHRlZCBMU04vaW5kZXguXG4gICAqL1xuICBjb21taXQoaW5kZXgpIHtcbiAgICBhc3NlcnQubnVtYmVyKGluZGV4LCAnaW5kZXgnKTtcbiAgICByZXR1cm4gdGhpc1skaW5kZXhdLmNvbW1pdChpbmRleCk7XG4gIH1cblxuICAvKipcbiAgICogV3JpdGVzIGEgbG9nIGVudHJ5LlxuICAgKlxuICAgKiBUaGUgZW50aXJlIGNvbnRlbnRzIG9mIHRoZSBzcGVjaWZpZWQgYGRhdGFgIGJ1ZmZlciBpcyB3cml0dGVuIGFzIHRoZSBsb2cgZW50cnksIHRoZSBhY3R1YWwgYmluYXJ5IGRhdGEgaXMgdHJlYXRlZCBhcyBvcGFxdWUgaW4gdGhlIGxvZy4gU2luY2UgdGhlIGxvZyBpcyBub3QgaW52b2x2ZWQgaW4gdGhlIGludGVycHJldGF0aW9uIG9mIHRoZSBkYXRhLCBjb25jZXJucyBzdWNoIGFzIGVuY3J5cHRpb24gYW5kIGRhdGEgc2VjdXJpdHkgaXMgZW50aXJlbHkgdGhlIGNhbGxlcidzIHJlc3BvbnNpYmlsaXR5LlxuICAgKlxuICAgKiBgYGBqYXZhc2NyaXB0XG4gICAqIHdhbC53cml0ZShkYXRhKVxuICAgKiAgIC50aGVuKGxzbiA9PiB7XG4gICAqICAgICAgY29uc29sZS5sb2coYERhdGEgd3JpdHRlbiBhcyBMb2cgRW50cnkgJHtsc259LmApO1xuICAgKiAgIH0pXG4gICAqICAgLmNhdGNoKGVyciA9PiB7XG4gICAqICAgICAgY29uc29sZS5sb2coYFVuZXhwZWN0ZWQgZXJyb3I6ICR7JycrZXJyLnN0YWNrfS5gKTtcbiAgICogICB9KVxuICAgKiBgYGBcbiAgICogQHBhcmFtIHtCdWZmZXJ9IGRhdGEgLSBUaGUgbG9nIGVudHJ5IGFzIGFuIG9wYXF1ZSBkYXRhIGJ1ZmZlci5cbiAgICogQHJldHVybnMge1Byb21pc2V9IEEgcHJvbWlzZSB0aGF0IHVwb24gc3VjY2VzcyBpcyByZXNvbHZlZCB3aXRoIHRoZSBuZXcgZW50cnkncyBMU04vaW5kZXguXG4gICAqIEB0aHJvd3Mge0Fzc2VydGlvbkVycm9yfSB0aHJvd24gd2hlbiBgZGF0YWAgaXMgdW5zcGVjaWZpZWQgb3Igbm90IGEgYEJ1ZmZlcmAuXG4gICAqL1xuICB3cml0ZShkYXRhKSB7XG4gICAgbGV0IGxvZyA9IHRoaXNbJGZpbGVdO1xuICAgIGxldCBpZHggPSB0aGlzWyRpbmRleF07XG4gICAgbGV0IGhlYWQgPSBpZHguaGVhZDtcblxuICAgIHJldHVybiBpZHgub2Zmc2V0KGhlYWQpXG4gICAgICAudGhlbihmaXJzdEJ5dGUgPT4ge1xuICAgICAgICByZXR1cm4gbG9nLndyaXRlKGZpcnN0Qnl0ZSwgZGF0YSk7XG4gICAgICB9KVxuICAgICAgLnRoZW4oc3Vic2VxdWVudEJ5dGUgPT4gaWR4LmluY3JlbWVudChzdWJzZXF1ZW50Qnl0ZSkpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlYWRzIHRoZSBsb2cgZW50cnkgYXQgdGhlIHNwZWNpZmllZCBMU04vaW5kZXguXG4gICAqXG4gICAqIGBgYGphdmFzY3JpcHRcbiAgICogd2FsLnJlYWQobHNuKVxuICAgKiAgIC50aGVuKGRhdGEgPT4ge1xuICAgKiAgICAgY29uc29sZS5sb2coYExvZyBFbnRyeSAke2xzbn0gaGFzICR7ZGF0YS5sZW5ndGh9IGJ5dGVzIG9mIGRhdGEuYCk7XG4gICAqICAgfSlcbiAgICogICAuY2F0Y2goZXJyID0+IHtcbiAgICogICAgICBjb25zb2xlLmxvZyhgVW5leHBlY3RlZCBlcnJvcjogJHsnJytlcnIuc3RhY2t9LmApO1xuICAgKiAgIH0pXG4gICAqIGBgYFxuICAgKiBAcGFyYW0ge251bWJlcn0gaW5kZXggLSBTcGVjaWZpZXMgdGhlIExTTi9pbmRleCBvZiB0aGUgbG9nIGVudHJ5IHRvIHJlYWQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBBIHByb21pc2UgdGhhdCB1cG9uIHN1Y2Nlc3MgaXMgcmVzb2x2ZWQgd2l0aCBhIGJ1ZmZlciBjb250YWluaW5nIHRoZSBsb2cgZW50cnlcbiAgICogQHRocm93cyB7QXNzZXJ0aW9uRXJyb3J9IFRocm93biB3aGVuIHRoZSBMU04vaW5kZXggaXMgb3V0IG9mIHJhbmdlLlxuICAgKi9cbiAgcmVhZChpbmRleCkge1xuICAgIGxldCBsb2cgPSB0aGlzWyRmaWxlXTtcbiAgICBsZXQgaWR4ID0gdGhpc1skaW5kZXhdO1xuICAgIHJldHVybiBpZHguZ2V0KGluZGV4KVxuICAgICAgLnRoZW4ocmVjID0+IGxvZy5yZWFkKHJlYy5vZmZzZXQsIHJlYy5sZW5ndGgpKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWFkcyBhIHJhbmdlIG9mIGxvZyBlbnRyaWVzIGJlZ2lubmluZyBhdCB0aGUgc3BlY2lmaWVkIGBmaXJzdGAgTFNOL2luZGV4LiBJZiBjb3VudCBpcyBub3Qgc3BlY2lmaWVkIGFsbCBzdWJzZXF1ZW50IGxvZyBlbnRyaWVzIGFyZSByZXR1cm5lZC5cbiAgICpcbiAgICogYGBgamF2YXNjcmlwdFxuICAgKiB3YWwucmVhZFJhbmdlKGZpcnN0KVxuICAgKiAgIC50aGVuKHN0cmVhbSA9PiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAqICAgICAgIGxldCBhY2MgPSBbXTtcbiAgICogICAgICAgc3RyZWFtLm9uKCdkYXRhJywgZGF0YSA9PiB7XG4gICAqICAgICAgICAgIGFjYy5wdXNoKGRhdGEpO1xuICAgKiAgICAgICAgfSlcbiAgICogICAgICAgIC5vbignZW5kJywgKCkgPT4gcmVzb2x2ZShhY2MpKVxuICAgKiAgICAgICAgLm9uKCdlcnJvcicsIHJlamVjdCk7XG4gICAqICAgICAgfSkpXG4gICAqICAgLnRoZW4oZW50cmllcyA9PiB7XG4gICAqICAgICBjb25zb2xlLmxvZyhgVGhlcmUgYXJlICR7ZW50cmllcy5sZW5ndGh9IGxvZyBlbnRyaWVzIGFmdGVyIExTTiAke2ZpcnN0fS5gKTtcbiAgICogICB9KVxuICAgKiAgIC5jYXRjaChlcnIgPT4ge1xuICAgKiAgICAgIGNvbnNvbGUubG9nKGBVbmV4cGVjdGVkIGVycm9yOiAkeycnK2Vyci5zdGFja30uYCk7XG4gICAqICAgfSlcbiAgICogYGBgXG4gICAqIEBwYXJhbSB7bnVtYmVyfSBmaXJzdCAtIFNwZWNpZmllcyB0aGUgaW5kZXggb2YgdGhlIGZpcnN0IGxvZyBlbnRyeSB0byByZWFkLlxuICAgKiBAcGFyYW0ge251bWJlcn0gY291bnQgLSBTcGVjaWZpZXMgdGhlIG51bWJlciBvZiBsb2cgZW50cmllcyB0byByZWFkLlxuICAgKiBAcmV0dXJucyB7UmVhZGFibGV9ICBBIHJlYWRhYmxlIHN0cmVhbSB3aGVyZSB0aGUgbG9nIGVudHJpZXMgYXJlIHN0cmVhbWVkIHRvIHRoZSBjYWxsZXIuXG4gICAqL1xuICByZWFkUmFuZ2UoZmlyc3QsIGNvdW50KSB7XG4gICAgYXNzZXJ0Lm51bWJlcihmaXJzdCwgJ2ZpcnN0Jyk7XG4gICAgYXNzZXJ0Lm9wdGlvbmFsTnVtYmVyKGNvdW50LCAnY291bnQnKTtcbiAgICBsZXQgaWR4ID0gdGhpc1skaW5kZXhdO1xuICAgIGxldCBsb2cgPSB0aGlzWyRmaWxlXTtcbiAgICBpZiAoY291bnQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgLy8gaGVhZCBwb2ludHMgYXQgdGhlXG4gICAgICBjb3VudCA9IChpZHguaGVhZCAtIGZpcnN0KTtcbiAgICB9XG4gICAgLy8gcmVzcGVjdCB0aGUgZmxvdy1jb250cm9sIHNwZWNpZmllZCBzdHJlYW1pbmcgYXBpLlxuICAgIGxldCBzZW5kaW5nID0gZmFsc2U7XG4gICAgbGV0IGVuZGVkID0gZmFsc2U7XG4gICAgbGV0IHJlYWR5ID0gbmV3IFF1ZXVlKCk7XG4gICAgbGV0IHN0cmVhbTtcblxuICAgIGZ1bmN0aW9uIHNlbmRVbnRpbFBhdXNlZCgpIHtcbiAgICAgIHdoaWxlIChzZW5kaW5nICYmIHJlYWR5Lmxlbmd0aCkge1xuICAgICAgICBzZW5kaW5nID0gc3RyZWFtLnB1c2gocmVhZHkuZGVxdWV1ZSgpKTtcbiAgICAgIH1cbiAgICAgIGlmIChlbmRlZCAmJiByZWFkeS5pc0VtcHR5KSB7XG4gICAgICAgIHN0cmVhbS5wdXNoKG51bGwpO1xuICAgICAgICBzdHJlYW0uZW1pdCgnZW5kJyk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gcmVhZHlUb1NlbmQoZGF0YSkge1xuICAgICAgcmVhZHkuZW5xdWV1ZShkYXRhKTtcbiAgICAgIHNlbmRVbnRpbFBhdXNlZCgpO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIGNsZWFyVG9TZW5kKCkge1xuICAgICAgc2VuZGluZyA9IHRydWU7XG4gICAgICByZXR1cm4gc2VuZFVudGlsUGF1c2VkKCk7XG4gICAgfVxuICAgIC8vIHJlYWQgdGhlIGVudHJpZXMgZnJvbSB0aGUgbG9nZmlsZSBhbmQgcG9zc2libHkgYnVmZmVyIHRoZW1cbiAgICAvLyBmb3IgdGhlIHJlYWRhYmxlIHN0cmVhbS5cbiAgICBpZHguZ2V0UmFuZ2UoZmlyc3QsIGNvdW50KVxuICAgICAgLnRoZW4ocmFuZ2UgPT4gcmFuZ2UucmVkdWNlKChhY2MsIHJlYykgPT5cbiAgICAgICAgYWNjLnRoZW4oKCkgPT4gbG9nLnJlYWQocmVjLm9mZnNldCwgcmVjLmxlbmd0aCkudGhlbihyZWFkeVRvU2VuZCkpLFxuICAgICAgICBQcm9taXNlLnJlc29sdmUoKSkpXG4gICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgIGlmIChyZWFkeS5pc0VtcHR5KSB7XG4gICAgICAgICAgc3RyZWFtLnB1c2gobnVsbCk7XG4gICAgICAgICAgc3RyZWFtLmVtaXQoJ2VuZCcpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGVuZGVkID0gdHJ1ZTtcbiAgICAgICAgICBzZW5kVW50aWxQYXVzZWQoKTtcbiAgICAgICAgfVxuICAgICAgfSlcbiAgICAgIC5jYXRjaChlcnIgPT4gc3RyZWFtLmVtaXQoJ2Vycm9yJywgZXJyKSk7XG5cbiAgICByZXR1cm4gc3RyZWFtID0gbmV3IFJlYWRhYmxlKHtcbiAgICAgIHJlYWQ6IGNsZWFyVG9TZW5kXG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogVHJ1bmNhdGVzIHRoZSBsb2cgZnJvbSB0aGUgc3BlY2lmaWVkLCB1bmNvbW1pdHRlZCBMU04vaW5kZXguXG4gICAqXG4gICAqIFNpbmNlIHRoZSBgLm5leHRgIHByb3BlcnR5IGlzIHVzdWFsbHkgYWhlYWQgb2YgdGhlIGAuY29tbWl0SGVhZGAsIHdoZW4gdGhpcyBjYWxsIHN1Y2NlZWRzIGl0IHJlc2V0cyB0aGUgd3JpdGUgaGVhZCB0byB0aGUgbmV3bHkgdHJ1bmNhdGVkIExTTi9pbmRleCBwb3NpdGlvbi4gQmUgYXdhcmUgdGhhdCB0aGUgdmVyeSBuZXh0IGAud3JpdGUoKWAgd2lsbCByZS11c2UgdGhpcyBtb3N0LXJlY2VudCB0cnVuY2F0ZWQgTFNOL2luZGV4LlxuICAgKlxuICAgKiBgYGBqYXZhc2NyaXB0XG4gICAqIHdhbC50cnVuY2F0ZShmaXJzdClcbiAgICogICAudGhlbihzaXplID0+IHtcbiAgICogICAgIGNvbnNvbGUubG9nKGBUaGVyZSBhcmUgJHtieXRlc30gY29tbWl0dGVkIHRvIHRoZSBsb2cuYCk7XG4gICAqICAgfSlcbiAgICogICAuY2F0Y2goZXJyID0+IHtcbiAgICogICAgICBjb25zb2xlLmxvZyhgVW5leHBlY3RlZCBlcnJvcjogJHsnJytlcnIuc3RhY2t9LmApO1xuICAgKiAgIH0pXG4gICAqIGBgYFxuICAgKiBAcGFyYW0ge251bWJlcn0gZnJvbSAtIFNwZWNpZmllcyB0aGUgTFNOL2luZGV4IHdoZXJlIHRoZSBsb2cgZW50cmllcyBzaG91bGQgYmUgdHJ1bmNhdGVkLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gIEEgcHJvbWlzZSB0aGF0IHVwb24gc3VjY2VzcyBpcyByZXNvbHZlZCB3aXRoIHRoZSBuZXcgd3JpdGUgaGVhZC5cbiAgICovXG4gIHRydW5jYXRlKGZyb20pIHtcbiAgICBhc3NlcnQubnVtYmVyKGZyb20sICdmcm9tJyk7XG4gICAgbGV0IGlkeCA9IHRoaXNbJGluZGV4XTtcbiAgICBsZXQgbG9nID0gdGhpc1skZmlsZV07XG4gICAgcmV0dXJuIChmcm9tIDwgdGhpcy5uZXh0KSA/XG4gICAgICBpZHgudHJ1bmNhdGUoZnJvbSkudGhlbihuZXh0ID0+IGxvZy50cnVuY2F0ZShuZXh0KSkgOlxuICAgICAgaWR4Lm9mZnNldChmcm9tKS50aGVuKG9mZnNldCA9PiBsb2cudHJ1bmNhdGUob2Zmc2V0KSk7XG4gIH1cblxuICByZWNvdmVyKGhhbmRsZXIpIHtcbiAgICBpZiAoaGFuZGxlciA9PT0gZmFsc2UpIHtcbiAgICAgIGhhbmRsZXIgPSAoKSA9PiBmYWxzZTtcbiAgICB9XG4gICAgYXNzZXJ0Lm9rKHR5cGVvZihoYW5kbGVyKSA9PT0gJ2Z1bmN0aW9uJywgJ2hhbmRsZXIgKGZ1bmN0aW9uKSBpcyByZXF1aXJlZCcpO1xuICAgIGxldCBoZWFkID0gdGhpcy5uZXh0O1xuICAgIGxldCBjb21taXR0ZWQgPSB0aGlzLmNvbW1pdEhlYWQ7XG4gICAgbGV0IHNlbGYgPSB0aGlzO1xuXG4gICAgZnVuY3Rpb24gdHJ1bmNBZnRlckNvbW1pdEhlYWQoKSB7XG4gICAgICByZXR1cm4gc2VsZi50cnVuY2F0ZShzZWxmLmNvbW1pdEhlYWQgKyAxKVxuICAgICAgICAudGhlbigoKSA9PiBzZWxmKTtcbiAgICB9XG5cbiAgICBmdW5jdGlvbiBwb3NzaWJseVJlY292ZXIoaW5kZXgpIHtcbiAgICAgIHJldHVybiBzZWxmLnJlYWQoaW5kZXgpXG4gICAgICAgIC50aGVuKGRhdGEgPT4ge1xuICAgICAgICAgIGxldCBjb21taXQgPSBoYW5kbGVyKGluZGV4LCBkYXRhKTtcbiAgICAgICAgICBpZiAodHlwZW9mKGNvbW1pdCkgPT09ICdvYmplY3QnICYmIHR5cGVvZihjb21taXQudGhlbikgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgICAgIHJldHVybiBjb21taXQudGhlbihjb21taXQgPT4ge1xuICAgICAgICAgICAgICBpZiAoY29tbWl0KSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHNlbGYuY29tbWl0KGluZGV4KVxuICAgICAgICAgICAgICAgICAgLnRoZW4oY29tbWl0dGVkID0+XG4gICAgICAgICAgICAgICAgICAgICgoY29tbWl0dGVkICsgMSkgPCBoZWFkKSA/XG4gICAgICAgICAgICAgICAgICAgIHBvc3NpYmx5UmVjb3Zlcihjb21taXR0ZWQgKyAxKSA6XG4gICAgICAgICAgICAgICAgICAgIHRydW5jQWZ0ZXJDb21taXRIZWFkKCkpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIHJldHVybiB0cnVuY0FmdGVyQ29tbWl0SGVhZCgpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmIChjb21taXQpIHtcbiAgICAgICAgICAgIHJldHVybiBzZWxmLmNvbW1pdChpbmRleClcbiAgICAgICAgICAgICAgLnRoZW4oY29tbWl0dGVkID0+XG4gICAgICAgICAgICAgICAgKChjb21taXR0ZWQgKyAxKSA8IGhlYWQpID9cbiAgICAgICAgICAgICAgICBwb3NzaWJseVJlY292ZXIoY29tbWl0dGVkICsgMSkgOlxuICAgICAgICAgICAgICAgIHRydW5jQWZ0ZXJDb21taXRIZWFkKCkpO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4gdHJ1bmNBZnRlckNvbW1pdEhlYWQoKTtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiAoKGNvbW1pdHRlZCArIDEpIDwgaGVhZCkgP1xuICAgICAgcG9zc2libHlSZWNvdmVyKGNvbW1pdHRlZCArIDEpIDpcbiAgICAgIFByb21pc2UucmVzb2x2ZShzZWxmKTtcbiAgfVxuXG4gIGNsb3NlKCkge1xuICAgIHJldHVybiBQcm9taXNlLmFsbChbXG4gICAgICB0aGlzWyRmaWxlXS5jbG9zZSgpLFxuICAgICAgdGhpc1skaW5kZXhdLmNsb3NlKClcbiAgICBdKTtcbiAgfVxufVxuXG5mdW5jdGlvbiBvcGVuKG9wdGlvbnMpIHtcbiAgYXNzZXJ0Lm9iamVjdChvcHRpb25zLCAnb3B0aW9ucycpO1xuICBhc3NlcnQuc3RyaW5nKG9wdGlvbnMucGF0aCwgJ29wdGlvbnMucGF0aCcpO1xuICBhc3NlcnQub3B0aW9uYWxTdHJpbmcob3B0aW9ucy5pbmRleCwgJ29wdGlvbnMuaW5kZXgnKTtcbiAgYXNzZXJ0Lm9wdGlvbmFsQm9vbChvcHRpb25zLndyaXRhYmxlLCAnb3B0aW9ucy53cml0YWJsZScpO1xuICBhc3NlcnQub3B0aW9uYWxCb29sKG9wdGlvbnMuaW5pdCwgJ29wdGlvbnMuaW5pdCcpO1xuICBsZXQgbW9kZSA9IGZzLlJfT0s7XG4gIGlmIChvcHRpb25zLndyaXRhYmxlKSB7XG4gICAgbW9kZSB8PSBmcy5XX09LO1xuICB9XG4gIGxldCBpZHhGaWxlID0gb3B0aW9ucy5pbmRleCB8fCBvcHRpb25zLnBhdGggKyBERUZBVUxUX0lOREVYX0VYVDtcbiAgbGV0IGxvZztcbiAgLy8gZW5zdXJlIGV4aXN0ZW5jZS9hY2Nlc3MgdG8gZmlsZXMgYmVmb3JlIGNvbnRpbnVpbmcuLi5cbiAgcmV0dXJuIFJhbmRvbUFjY2Vzc0ZpbGUub3BlbihvcHRpb25zLnBhdGgsIG9wdGlvbnMud3JpdGFibGUpXG4gICAgLnRoZW4oZmlsZSA9PiB7XG4gICAgICBsb2cgPSBmaWxlO1xuICAgICAgcmV0dXJuIExvZ0luZGV4RmlsZS5vcGVuKGlkeEZpbGUsIG9wdGlvbnMud3JpdGFibGUpO1xuICAgIH0pXG4gICAgLnRoZW4oaW5kZXggPT4gbmV3IFdyaXRlQWhlYWRMb2cobG9nLCBpbmRleCkpO1xufVxuXG5mdW5jdGlvbiBjcmVhdGUob3B0aW9ucykge1xuICBhc3NlcnQub2JqZWN0KG9wdGlvbnMsICdvcHRpb25zJyk7XG4gIGFzc2VydC5zdHJpbmcob3B0aW9ucy5wYXRoLCAnb3B0aW9ucy5wYXRoJyk7XG4gIGFzc2VydC5vcHRpb25hbFN0cmluZyhvcHRpb25zLmluZGV4LCAnb3B0aW9ucy5pbmRleCcpO1xuICBsZXQgaWR4RmlsZSA9IG9wdGlvbnMuaW5kZXggfHwgb3B0aW9ucy5wYXRoICsgREVGQVVMVF9JTkRFWF9FWFQ7XG4gIGxldCBsb2c7XG4gIHJldHVybiBSYW5kb21BY2Nlc3NGaWxlLmNyZWF0ZShvcHRpb25zLnBhdGgpXG4gICAgLnRoZW4oZmlsZSA9PiB7XG4gICAgICBsb2cgPSBmaWxlO1xuICAgICAgcmV0dXJuIExvZ0luZGV4RmlsZS5jcmVhdGUoaWR4RmlsZSwgMCk7XG4gICAgfSlcbiAgICAudGhlbihpbmRleCA9PiBuZXcgV3JpdGVBaGVhZExvZyhsb2csIGluZGV4KSk7XG59XG5cbmZ1bmN0aW9uIG9wZW5PckNyZWF0ZShvcHRpb25zKSB7XG4gIHJldHVybiBXcml0ZUFoZWFkTG9nLm9wZW4ob3B0aW9ucylcbiAgICAuY2F0Y2goZXJyID0+IHtcbiAgICAgIC8vIGlmIGl0IGRvZXNuJ3QgZXhpc3Qgd2UnbGwgY3JlYXRlIGl0IGlmIHdyaXRhYmxlXG4gICAgICBpZiAoZXJyLmNvZGUgPT09ICdFTk9FTlQnICYmIG9wdGlvbnMud3JpdGFibGUpIHtcbiAgICAgICAgcmV0dXJuIFdyaXRlQWhlYWRMb2cuY3JlYXRlKG9wdGlvbnMpO1xuICAgICAgfVxuICAgICAgdGhyb3cgZXJyO1xuICAgIH0pO1xufVxuXG5Xcml0ZUFoZWFkTG9nLmNyZWF0ZSA9IGNyZWF0ZTtcbldyaXRlQWhlYWRMb2cub3BlbiA9IG9wZW47XG5Xcml0ZUFoZWFkTG9nLm9wZW5PckNyZWF0ZSA9IG9wZW5PckNyZWF0ZTtcbldyaXRlQWhlYWRMb2cuREVGQVVMVF9JTkRFWF9FWFQgPSBERUZBVUxUX0lOREVYX0VYVDtcblxuZXhwb3J0IGRlZmF1bHQgV3JpdGVBaGVhZExvZztcbiJdfQ==