UNPKG

@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
"use strict"; 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=