@benmalka/foxdriver
Version:
Foxdriver is a Node library which provides a high-level API to control Firefox over the Remote Debugging Protocol
297 lines (236 loc) • 26.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _net = require("net");
var _net2 = _interopRequireDefault(_net);
var _events = require("events");
var _safeBuffer = require("safe-buffer");
var _logger = require("./logger");
var _logger2 = _interopRequireDefault(_logger);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const UNSOLICITED_EVENTS = ['tabNavigated', 'styleApplied', 'propertyChange', 'networkEventUpdate', 'networkEvent', 'propertyChange', 'newMutations', 'appOpen', 'appClose', 'appInstall', 'appUninstall', 'frameUpdate', 'tabListChanged'];
/**
* a Client object handles connecting with a Firefox remote debugging
* server instance (e.g. a Firefox instance), plus sending and receiving
* packets on that conection using the Firefox remote debugging protocol.
*
* Important methods:
* connect - Create the connection to the server.
* makeRequest - Make a request to the server with a JSON message,
* and a callback to call with the response.
*
* Important events:
* 'message' - An unsolicited (e.g. not a response to a prior request)
* packet has been received. These packets usually describe events.
*
* This code was adapted from https://github.com/harthur/firefox-client
*/
class Client extends _events.EventEmitter {
constructor(host, port) {
super();
this.host = host;
this.port = port;
this.incoming = _safeBuffer.Buffer.from('');
this.log = (0, _logger2.default)('Client');
this.supportedDomains = [];
this._pendingRequests = [];
this._activeRequests = {};
}
/**
* create socket connection
*
* @return {Promise} resolves once connected to socket
*/
connect() {
this.socket = _net2.default.createConnection({
host: this.host,
port: this.port
});
this.socket.on('data', this.onData.bind(this));
this.socket.on('error', this.onError.bind(this));
this.socket.on('end', this.onEnd.bind(this));
this.socket.on('timeout', this.onTimeout.bind(this));
return new Promise(resolve => this.socket.on('connect', resolve));
}
/**
* end socket connection
*/
disconnect() {
if (!this.socket) {
return;
}
this.socket.destroy();
this.socket.unref();
}
/**
* Called when a new data chunk is received on the connection.
* Parse data into message(s) and call message handler for any full
* messages that are read in.
*/
onData(data) {
this.incoming = _safeBuffer.Buffer.concat([this.incoming, data]);
while (this.readMessage()) {}
}
/**
* Parse out and process the next message from the data read from
* the connection. Returns true if a full meassage was parsed, false
* otherwise.
*/
readMessage() {
var sep = this.incoming.toString().indexOf(':');
if (sep < 0) {
return false;
}
/**
* beginning of a message is preceded by byteLength(message) + ":"
*/
const count = parseInt(this.incoming.slice(0, sep));
/**
* check if response is complete
*/
if (this.incoming.length - (sep + 1) < count) {
return false;
}
this.incoming = this.incoming.slice(sep + 1);
const packet = this.incoming.slice(0, count);
this.incoming = this.incoming.slice(count);
this.handleMessage(packet);
return true;
}
/**
* Handler for a new message coming in. It's either an unsolicited event
* from the server, or a response to a previous request from the client.
*/
handleMessage(packet) {
let message;
try {
message = JSON.parse(packet.toString());
} catch (e) {
return this.log.error(`Couldn't parse packet from server as JSON ${e}, message:\n${packet}`);
}
if (!message.from) {
if (message.error) {
return this.log.error(message.message);
}
return this.log.error(`Server didn't specify an actor: ${packet}`);
}
/**
* respond to request
*/
if (!UNSOLICITED_EVENTS.includes(message.type) && this._activeRequests[message.from]) {
this.emit('message', message);
this.log.info(`response: ${packet}`);
const callback = this._activeRequests[message.from];
delete this._activeRequests[message.from];
callback(message);
return this._flushRequests();
}
/**
* handle unsolicited event from server
*/
if (message.type) {
// this is an unsolicited event from the server
this.log.info(`unsolicited event: ${packet}`);
return this.emit('message', message);
}
this.log.error(`Unhandled message: ${JSON.stringify(message)}`);
}
/**
* Send a JSON message over the connection to the server.
*/
sendMessage(message) {
if (!message.to) {
throw new Error('No actor specified in request');
}
if (!this.socket) {
throw new Error('Not connected, connect() before sending requests');
}
let str = JSON.stringify(message);
this.emit('send', message);
/**
* message is preceded by byteLength(message):
*/
str = `${_safeBuffer.Buffer.from(str).length}:${str}`;
try {
this.socket.write(str);
} catch (e) {
this.log.error(`Couldn't set socket message: ${e.message}`);
}
}
/**
* Set a request to be sent to an actor on the server. If the actor
* is already handling a request, queue this request until the actor
* has responded to the previous request.
*
* @param {object} request
* Message to be JSON-ified and sent to server.
* @param {function} callback
* Function that's called with the response from the server.
*/
makeRequest(request) {
this.log.info(`request: ${JSON.stringify(request)}`);
if (!request.to) {
throw new Error(`${request.type || ''} request packet has no destination.`);
}
let resolveCb;
const resp = new Promise(resolve => {
resolveCb = resolve;
});
this._pendingRequests.push({
to: request.to,
message: request,
callback: resolveCb
});
this._flushRequests();
return resp;
}
/**
* Activate (send) any pending requests to actors that don't have an
* active request.
*/
_flushRequests() {
this._pendingRequests = this._pendingRequests.filter(request => {
/**
* only one active request per actor at a time
*/
if (this._activeRequests[request.to]) {
return true;
}
/**
* no active requests for this actor, so activate this one
*/
this.sendMessage(request.message);
this.expectReply(request.to, request.callback);
/**
* remove from pending requests
*/
return false;
});
}
/**
* Arrange to hand the next reply from |actor| to |handler|.
*/
expectReply(actor, handler) {
if (this._activeRequests[actor]) {
throw Error(`clashing handlers for next reply from ${actor}`);
}
this._activeRequests[actor] = handler;
}
onError(error) {
var code = error.code ? error.code : error;
this.log.error(`connection error: ${code}`);
this.emit('error', error);
}
onEnd() {
this.log.info('connection closed by server');
this.emit('end');
}
onTimeout() {
this.log.info('connection timeout');
this.emit('timeout');
}
}
exports.default = Client;
module.exports = exports["default"];
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL2xpYi9jbGllbnQuanMiXSwibmFtZXMiOlsiVU5TT0xJQ0lURURfRVZFTlRTIiwiQ2xpZW50IiwiRXZlbnRFbWl0dGVyIiwiY29uc3RydWN0b3IiLCJob3N0IiwicG9ydCIsImluY29taW5nIiwiQnVmZmVyIiwiZnJvbSIsImxvZyIsInN1cHBvcnRlZERvbWFpbnMiLCJfcGVuZGluZ1JlcXVlc3RzIiwiX2FjdGl2ZVJlcXVlc3RzIiwiY29ubmVjdCIsInNvY2tldCIsIm5ldCIsImNyZWF0ZUNvbm5lY3Rpb24iLCJvbiIsIm9uRGF0YSIsIm9uRXJyb3IiLCJvbkVuZCIsIm9uVGltZW91dCIsIlByb21pc2UiLCJyZXNvbHZlIiwiZGlzY29ubmVjdCIsImRlc3Ryb3kiLCJ1bnJlZiIsImRhdGEiLCJjb25jYXQiLCJyZWFkTWVzc2FnZSIsInNlcCIsInRvU3RyaW5nIiwiaW5kZXhPZiIsImNvdW50IiwicGFyc2VJbnQiLCJzbGljZSIsImxlbmd0aCIsInBhY2tldCIsImhhbmRsZU1lc3NhZ2UiLCJtZXNzYWdlIiwiSlNPTiIsInBhcnNlIiwiZSIsImVycm9yIiwiaW5jbHVkZXMiLCJ0eXBlIiwiZW1pdCIsImluZm8iLCJjYWxsYmFjayIsIl9mbHVzaFJlcXVlc3RzIiwic3RyaW5naWZ5Iiwic2VuZE1lc3NhZ2UiLCJ0byIsIkVycm9yIiwic3RyIiwid3JpdGUiLCJtYWtlUmVxdWVzdCIsInJlcXVlc3QiLCJyZXNvbHZlQ2IiLCJyZXNwIiwicHVzaCIsImZpbHRlciIsImV4cGVjdFJlcGx5IiwiYWN0b3IiLCJoYW5kbGVyIiwiY29kZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7Ozs7QUFDQTs7QUFDQTs7QUFFQTs7Ozs7O0FBRUEsTUFBTUEsa0JBQWtCLEdBQUcsQ0FDdkIsY0FEdUIsRUFDUCxjQURPLEVBQ1MsZ0JBRFQsRUFDMkIsb0JBRDNCLEVBQ2lELGNBRGpELEVBRXZCLGdCQUZ1QixFQUVMLGNBRkssRUFFVyxTQUZYLEVBRXNCLFVBRnRCLEVBRWtD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QzdCLFlBQU1WLEtBQUssQ0FBRSx5Q0FBd0NVLEtBQU0sRUFBaEQsQ0FBWDtBQUNIOztBQUNELFNBQUtuRCxlQUFMLENBQXFCbUQsS0FBckIsSUFBOEJDLE9BQTlCO0FBQ0g7O0FBRUQ3QyxFQUFBQSxPQUFPLENBQUV3QixLQUFGLEVBQVM7QUFDWixRQUFJc0IsSUFBSSxHQUFHdEIsS0FBSyxDQUFDc0IsSUFBTixHQUFhdEIsS0FBSyxDQUFDc0IsSUFBbkIsR0FBMEJ0QixLQUFyQztBQUNBLFNBQUtsQyxHQUFMLENBQVNrQyxLQUFULENBQWdCLHFCQUFvQnNCLElBQUssRUFBekM7QUFDQSxTQUFLbkIsSUFBTCxDQUFVLE9BQVYsRUFBbUJILEtBQW5CO0FBQ0g7O0FBRUR2QixFQUFBQSxLQUFLLEdBQUk7QUFDTCxTQUFLWCxHQUFMLENBQVNzQyxJQUFULENBQWMsNkJBQWQ7QUFDQSxTQUFLRCxJQUFMLENBQVUsS0FBVjtBQUNIOztBQUVEekIsRUFBQUEsU0FBUyxHQUFJO0FBQ1QsU0FBS1osR0FBTCxDQUFTc0MsSUFBVCxDQUFjLG9CQUFkO0FBQ0EsU0FBS0QsSUFBTCxDQUFVLFNBQVY7QUFDSDs7QUF2TzRDOztrQkFBNUI3QyxNIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IG5ldCBmcm9tICduZXQnXHJcbmltcG9ydCB7IEV2ZW50RW1pdHRlciB9IGZyb20gJ2V2ZW50cydcclxuaW1wb3J0IHsgQnVmZmVyIH0gZnJvbSAnc2FmZS1idWZmZXInXHJcblxyXG5pbXBvcnQgbG9nZ2VyIGZyb20gJy4vbG9nZ2VyJ1xyXG5cclxuY29uc3QgVU5TT0xJQ0lURURfRVZFTlRTID0gW1xyXG4gICAgJ3RhYk5hdmlnYXRlZCcsICdzdHlsZUFwcGxpZWQnLCAncHJvcGVydHlDaGFuZ2UnLCAnbmV0d29ya0V2ZW50VXBkYXRlJywgJ25ldHdvcmtFdmVudCcsXHJcbiAgICAncHJvcGVydHlDaGFuZ2UnLCAnbmV3TXV0YXRpb25zJywgJ2FwcE9wZW4nLCAnYXBwQ2xvc2UnLCAnYXBwSW5zdGFsbCcsICdhcHBVbmluc3RhbGwnLFxyXG4gICAgJ2ZyYW1lVXBkYXRlJywgJ3RhYkxpc3RDaGFuZ2VkJ1xyXG5dXHJcblxyXG4vKipcclxuICogYSBDbGllbnQgb2JqZWN0IGhhbmRsZXMgY29ubmVjdGluZyB3aXRoIGEgRmlyZWZveCByZW1vdGUgZGVidWdnaW5nXHJcbiAqIHNlcnZlciBpbnN0YW5jZSAoZS5nLiBhIEZpcmVmb3ggaW5zdGFuY2UpLCBwbHVzIHNlbmRpbmcgYW5kIHJlY2VpdmluZ1xyXG4gKiBwYWNrZXRzIG9uIHRoYXQgY29uZWN0aW9uIHVzaW5nIHRoZSBGaXJlZm94IHJlbW90ZSBkZWJ1Z2dpbmcgcHJvdG9jb2wuXHJcbiAqXHJcbiAqIEltcG9ydGFudCBtZXRob2RzOlxyXG4gKiBjb25uZWN0IC0gQ3JlYXRlIHRoZSBjb25uZWN0aW9uIHRvIHRoZSBzZXJ2ZXIuXHJcbiAqIG1ha2VSZXF1ZXN0IC0gTWFrZSBhIHJlcXVlc3QgdG8gdGhlIHNlcnZlciB3aXRoIGEgSlNPTiBtZXNzYWdlLFxyXG4gKiAgIGFuZCBhIGNhbGxiYWNrIHRvIGNhbGwgd2l0aCB0aGUgcmVzcG9uc2UuXHJcbiAqXHJcbiAqIEltcG9ydGFudCBldmVudHM6XHJcbiAqICdtZXNzYWdlJyAtIEFuIHVuc29saWNpdGVkIChlLmcuIG5vdCBhIHJlc3BvbnNlIHRvIGEgcHJpb3IgcmVxdWVzdClcclxuICogICAgcGFja2V0IGhhcyBiZWVuIHJlY2VpdmVkLiBUaGVzZSBwYWNrZXRzIHVzdWFsbHkgZGVzY3JpYmUgZXZlbnRzLlxyXG4gKlxyXG4gKiBUaGlzIGNvZGUgd2FzIGFkYXB0ZWQgZnJvbSBodHRwczovL2dpdGh1Yi5jb20vaGFydGh1ci9maXJlZm94LWNsaWVudFxyXG4gKi9cclxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQ2xpZW50IGV4dGVuZHMgRXZlbnRFbWl0dGVyIHtcclxuICAgIGNvbnN0cnVjdG9yIChob3N0LCBwb3J0KSB7XHJcbiAgICAgICAgc3VwZXIoKVxyXG4gICAgICAgIHRoaXMuaG9zdCA9IGhvc3RcclxuICAgICAgICB0aGlzLnBvcnQgPSBwb3J0XHJcbiAgICAgICAgdGhpcy5pbmNvbWluZyA9IEJ1ZmZlci5mcm9tKCcnKVxyXG4gICAgICAgIHRoaXMubG9nID0gbG9nZ2VyKCdDbGllbnQnKVxyXG4gICAgICAgIHRoaXMuc3VwcG9ydGVkRG9tYWlucyA9IFtdXHJcblxyXG4gICAgICAgIHRoaXMuX3BlbmRpbmdSZXF1ZXN0cyA9IFtdXHJcbiAgICAgICAgdGhpcy5fYWN0aXZlUmVxdWVzdHMgPSB7fVxyXG4gICAgfVxyXG5cclxuICAgIC8qKlxyXG4gICAgICogY3JlYXRlIHNvY2tldCBjb25uZWN0aW9uXHJcbiAgICAgKlxyXG4gICAgICogQHJldHVybiB7UHJvbWlzZX0gIHJlc29sdmVzIG9uY2UgY29ubmVjdGVkIHRvIHNvY2tldFxyXG4gICAgICovXHJcbiAgICBjb25uZWN0ICgpIHtcclxuICAgICAgICB0aGlzLnNvY2tldCA9IG5ldC5jcmVhdGVDb25uZWN0aW9uKHtcclxuICAgICAgICAgICAgaG9zdDogdGhpcy5ob3N0LFxyXG4gICAgICAgICAgICBwb3J0OiB0aGlzLnBvcnRcclxuICAgICAgICB9KVxyXG5cclxuICAgICAgICB0aGlzLnNvY2tldC5vbignZGF0YScsIDo6dGhpcy5vbkRhdGEpXHJcbiAgICAgICAgdGhpcy5zb2NrZXQub24oJ2Vycm9yJywgOjp0aGlzLm9uRXJyb3IpXHJcbiAgICAgICAgdGhpcy5zb2NrZXQub24oJ2VuZCcsIDo6dGhpcy5vbkVuZClcclxuICAgICAgICB0aGlzLnNvY2tldC5vbigndGltZW91dCcsIDo6dGhpcy5vblRpbWVvdXQpXHJcblxyXG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gdGhpcy5zb2NrZXQub24oJ2Nvbm5lY3QnLCByZXNvbHZlKSlcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIGVuZCBzb2NrZXQgY29ubmVjdGlvblxyXG4gICAgICovXHJcbiAgICBkaXNjb25uZWN0ICgpIHtcclxuICAgICAgICBpZiAoIXRoaXMuc29ja2V0KSB7XHJcbiAgICAgICAgICAgIHJldHVyblxyXG4gICAgICAgIH1cclxuICAgICAgICB0aGlzLnNvY2tldC5kZXN0cm95KClcclxuICAgICAgICB0aGlzLnNvY2tldC51bnJlZigpXHJcbiAgICB9XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBDYWxsZWQgd2hlbiBhIG5ldyBkYXRhIGNodW5rIGlzIHJlY2VpdmVkIG9uIHRoZSBjb25uZWN0aW9uLlxyXG4gICAgICogUGFyc2UgZGF0YSBpbnRvIG1lc3NhZ2UocykgYW5kIGNhbGwgbWVzc2FnZSBoYW5kbGVyIGZvciBhbnkgZnVsbFxyXG4gICAgICogbWVzc2FnZXMgdGhhdCBhcmUgcmVhZCBpbi5cclxuICAgICAqL1xyXG4gICAgb25EYXRhIChkYXRhKSB7XHJcbiAgICAgICAgdGhpcy5pbmNvbWluZyA9IEJ1ZmZlci5jb25jYXQoW3RoaXMuaW5jb21pbmcsIGRhdGFdKVxyXG4gICAgICAgIHdoaWxlICh0aGlzLnJlYWRNZXNzYWdlKCkpIHt9XHJcbiAgICB9XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBQYXJzZSBvdXQgYW5kIHByb2Nlc3MgdGhlIG5leHQgbWVzc2FnZSBmcm9tIHRoZSBkYXRhIHJlYWQgZnJvbVxyXG4gICAgICogdGhlIGNvbm5lY3Rpb24uIFJldHVybnMgdHJ1ZSBpZiBhIGZ1bGwgbWVhc3NhZ2Ugd2FzIHBhcnNlZCwgZmFsc2VcclxuICAgICAqIG90aGVyd2lzZS5cclxuICAgICAqL1xyXG4gICAgcmVhZE1lc3NhZ2UgKCkge1xyXG4gICAgICAgIHZhciBzZXAgPSB0aGlzLmluY29taW5nLnRvU3RyaW5nKCkuaW5kZXhPZignOicpXHJcblxyXG4gICAgICAgIGlmIChzZXAgPCAwKSB7XHJcbiAgICAgICAgICAgIHJldHVybiBmYWxzZVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogYmVnaW5uaW5nIG9mIGEgbWVzc2FnZSBpcyBwcmVjZWRlZCBieSBieXRlTGVuZ3RoKG1lc3NhZ2UpICsgXCI6XCJcclxuICAgICAgICAgKi9cclxuICAgICAgICBjb25zdCBjb3VudCA9IHBhcnNlSW50KHRoaXMuaW5jb21pbmcuc2xpY2UoMCwgc2VwKSlcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogY2hlY2sgaWYgcmVzcG9uc2UgaXMgY29tcGxldGVcclxuICAgICAgICAgKi9cclxuICAgICAgICBpZiAodGhpcy5pbmNvbWluZy5sZW5ndGggLSAoc2VwICsgMSkgPCBjb3VudCkge1xyXG4gICAgICAgICAgICByZXR1cm4gZmFsc2VcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIHRoaXMuaW5jb21pbmcgPSB0aGlzLmluY29taW5nLnNsaWNlKHNlcCArIDEpXHJcbiAgICAgICAgY29uc3QgcGFja2V0ID0gdGhpcy5pbmNvbWluZy5zbGljZSgwLCBjb3VudClcclxuICAgICAgICB0aGlzLmluY29taW5nID0gdGhpcy5pbmNvbWluZy5zbGljZShjb3VudClcclxuICAgICAgICB0aGlzLmhhbmRsZU1lc3NhZ2UocGFja2V0KVxyXG4gICAgICAgIHJldHVybiB0cnVlXHJcbiAgICB9XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBIYW5kbGVyIGZvciBhIG5ldyBtZXNzYWdlIGNvbWluZyBpbi4gSXQncyBlaXRoZXIgYW4gdW5zb2xpY2l0ZWQgZXZlbnRcclxuICAgICAqIGZyb20gdGhlIHNlcnZlciwgb3IgYSByZXNwb25zZSB0byBhIHByZXZpb3VzIHJlcXVlc3QgZnJvbSB0aGUgY2xpZW50LlxyXG4gICAgICovXHJcbiAgICBoYW5kbGVNZXNzYWdlIChwYWNrZXQpIHtcclxuICAgICAgICBsZXQgbWVzc2FnZVxyXG5cclxuICAgICAgICB0cnkge1xyXG4gICAgICAgICAgICBtZXNzYWdlID0gSlNPTi5wYXJzZShwYWNrZXQudG9TdHJpbmcoKSlcclxuICAgICAgICB9IGNhdGNoIChlKSB7XHJcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmxvZy5lcnJvcihgQ291bGRuJ3QgcGFyc2UgcGFja2V0IGZyb20gc2VydmVyIGFzIEpTT04gJHtlfSwgbWVzc2FnZTpcXG4ke3BhY2tldH1gKVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgaWYgKCFtZXNzYWdlLmZyb20pIHtcclxuICAgICAgICAgICAgaWYgKG1lc3NhZ2UuZXJyb3IpIHtcclxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmxvZy5lcnJvcihtZXNzYWdlLm1lc3NhZ2UpXHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmxvZy5lcnJvcihgU2VydmVyIGRpZG4ndCBzcGVjaWZ5IGFuIGFjdG9yOiAke3BhY2tldH1gKVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogcmVzcG9uZCB0byByZXF1ZXN0XHJcbiAgICAgICAgICovXHJcbiAgICAgICAgaWYgKCFVTlNPTElDSVRFRF9FVkVOVFMuaW5jbHVkZXMobWVzc2FnZS50eXBlKSAmJiB0aGlzLl9hY3RpdmVSZXF1ZXN0c1ttZXNzYWdlLmZyb21dKSB7XHJcbiAgICAgICAgICAgIHRoaXMuZW1pdCgnbWVzc2FnZScsIG1lc3NhZ2UpXHJcbiAgICAgICAgICAgIHRoaXMubG9nLmluZm8oYHJlc3BvbnNlOiAke3BhY2tldH1gKVxyXG4gICAgICAgICAgICBjb25zdCBjYWxsYmFjayA9IHRoaXMuX2FjdGl2ZVJlcXVlc3RzW21lc3NhZ2UuZnJvbV1cclxuICAgICAgICAgICAgZGVsZXRlIHRoaXMuX2FjdGl2ZVJlcXVlc3RzW21lc3NhZ2UuZnJvbV1cclxuICAgICAgICAgICAgY2FsbGJhY2sobWVzc2FnZSlcclxuICAgICAgICAgICAgcmV0dXJuIHRoaXMuX2ZsdXNoUmVxdWVzdHMoKVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogaGFuZGxlIHVuc29saWNpdGVkIGV2ZW50IGZyb20gc2VydmVyXHJcbiAgICAgICAgICovXHJcbiAgICAgICAgaWYgKG1lc3NhZ2UudHlwZSkge1xyXG4gICAgICAgICAgICAvLyB0aGlzIGlzIGFuIHVuc29saWNpdGVkIGV2ZW50IGZyb20gdGhlIHNlcnZlclxyXG4gICAgICAgICAgICB0aGlzLmxvZy5pbmZvKGB1bnNvbGljaXRlZCBldmVudDogJHtwYWNrZXR9YClcclxuICAgICAgICAgICAgcmV0dXJuIHRoaXMuZW1pdCgnbWVzc2FnZScsIG1lc3NhZ2UpXHJcbiAgICAgICAgfVxyXG5cclxuICAgICAgICB0aGlzLmxvZy5lcnJvcihgVW5oYW5kbGVkIG1lc3NhZ2U6ICR7SlNPTi5zdHJpbmdpZnkobWVzc2FnZSl9YClcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIFNlbmQgYSBKU09OIG1lc3NhZ2Ugb3ZlciB0aGUgY29ubmVjdGlvbiB0byB0aGUgc2VydmVyLlxyXG4gICAgICovXHJcbiAgICBzZW5kTWVzc2FnZSAobWVzc2FnZSkge1xyXG4gICAgICAgIGlmICghbWVzc2FnZS50bykge1xyXG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ05vIGFjdG9yIHNwZWNpZmllZCBpbiByZXF1ZXN0JylcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIGlmICghdGhpcy5zb2NrZXQpIHtcclxuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdOb3QgY29ubmVjdGVkLCBjb25uZWN0KCkgYmVmb3JlIHNlbmRpbmcgcmVxdWVzdHMnKVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgbGV0IHN0ciA9IEpTT04uc3RyaW5naWZ5KG1lc3NhZ2UpXHJcbiAgICAgICAgdGhpcy5lbWl0KCdzZW5kJywgbWVzc2FnZSlcclxuXHJcbiAgICAgICAgLyoqXHJcbiAgICAgICAgICogbWVzc2FnZSBpcyBwcmVjZWRlZCBieSBieXRlTGVuZ3RoKG1lc3NhZ2UpOlxyXG4gICAgICAgICAqL1xyXG4gICAgICAgIHN0ciA9IGAkeyhCdWZmZXIuZnJvbShzdHIpKS5sZW5ndGh9OiR7c3RyfWBcclxuXHJcbiAgICAgICAgdHJ5IHtcclxuICAgICAgICAgICAgdGhpcy5zb2NrZXQud3JpdGUoc3RyKVxyXG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcclxuICAgICAgICAgICAgdGhpcy5sb2cuZXJyb3IoYENvdWxkbid0IHNldCBzb2NrZXQgbWVzc2FnZTogJHtlLm1lc3NhZ2V9YClcclxuICAgICAgICB9XHJcbiAgICB9XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBTZXQgYSByZXF1ZXN0IHRvIGJlIHNlbnQgdG8gYW4gYWN0b3Igb24gdGhlIHNlcnZlci4gSWYgdGhlIGFjdG9yXHJcbiAgICAgKiBpcyBhbHJlYWR5IGhhbmRsaW5nIGEgcmVxdWVzdCwgcXVldWUgdGhpcyByZXF1ZXN0IHVudGlsIHRoZSBhY3RvclxyXG4gICAgICogaGFzIHJlc3BvbmRlZCB0byB0aGUgcHJldmlvdXMgcmVxdWVzdC5cclxuICAgICAqXHJcbiAgICAgKiBAcGFyYW0ge29iamVjdH0gcmVxdWVzdFxyXG4gICAgICogICAgICAgIE1lc3NhZ2UgdG8gYmUgSlNPTi1pZmllZCBhbmQgc2VudCB0byBzZXJ2ZXIuXHJcbiAgICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xyXG4gICAgICogICAgICAgIEZ1bmN0aW9uIHRoYXQncyBjYWxsZWQgd2l0aCB0aGUgcmVzcG9uc2UgZnJvbSB0aGUgc2VydmVyLlxyXG4gICAgICovXHJcbiAgICBtYWtlUmVxdWVzdCAocmVxdWVzdCkge1xyXG4gICAgICAgIHRoaXMubG9nLmluZm8oYHJlcXVlc3Q6ICR7SlNPTi5zdHJpbmdpZnkocmVxdWVzdCl9YClcclxuXHJcbiAgICAgICAgaWYgKCFyZXF1ZXN0LnRvKSB7XHJcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgJHtyZXF1ZXN0LnR5cGUgfHwgJyd9IHJlcXVlc3QgcGFja2V0IGhhcyBubyBkZXN0aW5hdGlvbi5gKVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgbGV0IHJlc29sdmVDYlxyXG4gICAgICAgIGNvbnN0IHJlc3AgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4geyByZXNvbHZlQ2IgPSByZXNvbHZlIH0pXHJcbiAgICAgICAgdGhpcy5fcGVuZGluZ1JlcXVlc3RzLnB1c2goeyB0bzogcmVxdWVzdC50bywgbWVzc2FnZTogcmVxdWVzdCwgY2FsbGJhY2s6IHJlc29sdmVDYiB9KVxyXG4gICAgICAgIHRoaXMuX2ZsdXNoUmVxdWVzdHMoKVxyXG5cclxuICAgICAgICByZXR1cm4gcmVzcFxyXG4gICAgfVxyXG5cclxuICAgIC8qKlxyXG4gICAgICogQWN0aXZhdGUgKHNlbmQpIGFueSBwZW5kaW5nIHJlcXVlc3RzIHRvIGFjdG9ycyB0aGF0IGRvbid0IGhhdmUgYW5cclxuICAgICAqIGFjdGl2ZSByZXF1ZXN0LlxyXG4gICAgICovXHJcbiAgICBfZmx1c2hSZXF1ZXN0cyAoKSB7XHJcbiAgICAgICAgdGhpcy5fcGVuZGluZ1JlcXVlc3RzID0gdGhpcy5fcGVuZGluZ1JlcXVlc3RzLmZpbHRlcigocmVxdWVzdCkgPT4ge1xyXG4gICAgICAgICAgICAvKipcclxuICAgICAgICAgICAgICogb25seSBvbmUgYWN0aXZlIHJlcXVlc3QgcGVyIGFjdG9yIGF0IGEgdGltZVxyXG4gICAgICAgICAgICAgKi9cclxuICAgICAgICAgICAgaWYgKHRoaXMuX2FjdGl2ZVJlcXVlc3RzW3JlcXVlc3QudG9dKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZVxyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgICAgICAvKipcclxuICAgICAgICAgICAgICogbm8gYWN0aXZlIHJlcXVlc3RzIGZvciB0aGlzIGFjdG9yLCBzbyBhY3RpdmF0ZSB0aGlzIG9uZVxyXG4gICAgICAgICAgICAgKi9cclxuICAgICAgICAgICAgdGhpcy5zZW5kTWVzc2FnZShyZXF1ZXN0Lm1lc3NhZ2UpXHJcbiAgICAgICAgICAgIHRoaXMuZXhwZWN0UmVwbHkocmVxdWVzdC50bywgcmVxdWVzdC5jYWxsYmFjaylcclxuXHJcbiAgICAgICAgICAgIC8qKlxyXG4gICAgICAgICAgICAgKiByZW1vdmUgZnJvbSBwZW5kaW5nIHJlcXVlc3RzXHJcbiAgICAgICAgICAgICAqL1xyXG4gICAgICAgICAgICByZXR1cm4gZmFsc2VcclxuICAgICAgICB9KVxyXG4gICAgfVxyXG5cclxuICAgIC8qKlxyXG4gICAgICogQXJyYW5nZSB0byBoYW5kIHRoZSBuZXh0IHJlcGx5IGZyb20gfGFjdG9yfCB0byB8aGFuZGxlcnwuXHJcbiAgICAgKi9cclxuICAgIGV4cGVjdFJlcGx5IChhY3RvciwgaGFuZGxlcikge1xyXG4gICAgICAgIGlmICh0aGlzLl9hY3RpdmVSZXF1ZXN0c1thY3Rvcl0pIHtcclxuICAgICAgICAgICAgdGhyb3cgRXJyb3IoYGNsYXNoaW5nIGhhbmRsZXJzIGZvciBuZXh0IHJlcGx5IGZyb20gJHthY3Rvcn1gKVxyXG4gICAgICAgIH1cclxuICAgICAgICB0aGlzLl9hY3RpdmVSZXF1ZXN0c1thY3Rvcl0gPSBoYW5kbGVyXHJcbiAgICB9XHJcblxyXG4gICAgb25FcnJvciAoZXJyb3IpIHtcclxuICAgICAgICB2YXIgY29kZSA9IGVycm9yLmNvZGUgPyBlcnJvci5jb2RlIDogZXJyb3JcclxuICAgICAgICB0aGlzLmxvZy5lcnJvcihgY29ubmVjdGlvbiBlcnJvcjogJHtjb2RlfWApXHJcbiAgICAgICAgdGhpcy5lbWl0KCdlcnJvcicsIGVycm9yKVxyXG4gICAgfVxyXG5cclxuICAgIG9uRW5kICgpIHtcclxuICAgICAgICB0aGlzLmxvZy5pbmZvKCdjb25uZWN0aW9uIGNsb3NlZCBieSBzZXJ2ZXInKVxyXG4gICAgICAgIHRoaXMuZW1pdCgnZW5kJylcclxuICAgIH1cclxuXHJcbiAgICBvblRpbWVvdXQgKCkge1xyXG4gICAgICAgIHRoaXMubG9nLmluZm8oJ2Nvbm5lY3Rpb24gdGltZW91dCcpXHJcbiAgICAgICAgdGhpcy5lbWl0KCd0aW1lb3V0JylcclxuICAgIH1cclxufVxyXG4iXX0=