UNPKG

queen-remote

Version:

Rule browsers remotely, remotely

305 lines (245 loc) 8.04 kB
var EventEmitter = require("events").EventEmitter, net = require('net'), jot = require('json-over-tcp'), generateId = require('node-uuid').v4, its = require('its'); var Workforce = require('./workforce.js').Workforce, createWorkforce = require('./workforce.js').create, createWorkerProvider = require('./workerProvider.js').create, utils = require('../utils.js'), MESSAGE_TYPE = require('../protocol.js').QUEEN_MESSAGE_TYPE; var create = module.exports = function(options){ options = options || {}; var socket = options.socket || new jot.Socket(), callback = options.callback || utils.noop, queen = new Queen(socket), hostArr = options.host && options.host.split(":") || [], hostname = hostArr[0], port = hostArr[1]; if(options.log) queen.log = options.log; if(options.debug) queen.debug = options.debug; if(options.trackWorkerProviders) queen.trackWorkerProviders = options.trackWorkerProviders === true; if(port) queen.port = port; if(hostname && hostname !== "*") queen.hostname = hostname; var callbackCalled = false; function onReady(queen){ if(callbackCalled) return; callbackCalled = true; if(queen instanceof Error){ return callback(queen); } else { callback(getApi(queen)); } } queen.onReady = onReady; queen.emitter.on('dead', function(reason){ if(!callbackCalled){ if(!(reason instanceof Error)) reason = new Error(reason) onReady(reason); } }); queen.connect(); }; var Queen = exports.Queen = function(socket){ var self = this; its.object(socket, "Socket must be defined"); this.socket = socket; this.emitter = new EventEmitter(); this.workforceEmitters = {}; this.workerProviders = {}; this.workerProviderEmitters = {}; this.continuousWorkforces = {}; this.kill = utils.once(this.kill.bind(this)); this.getWorkerProvider = this.getWorkerProvider.bind(this); // used by workforces socket.on('data', this.messageHandler.bind(this)); socket.on('close', function(){ self.log('[Queen Remote Client] Connection to remote server closed\n'); self.kill('socket closed'); }); socket.on('end', function(){ self.log('[Queen Remote Client] Connection to remote server ended\n'); self.kill('socket ended'); }); socket.on('error', function(e){ self.log('[Queen Remote Client] Connection to remote server encountered error: ' + e + "\n"); self.kill(e); }); }; var getApi = function(queen){ var api = queen.getWorkforce.bind(queen); api.on = queen.emitter.on.bind(queen.emitter); api.removeListener = queen.emitter.removeListener.bind(queen.emitter); api.kill = queen.kill; api.getWorkerProvider = queen.getWorkerProvider.bind(queen); Object.defineProperty(api, 'log', { enumerable: true, get: function(){ return queen.log; } }); Object.defineProperty(api, 'debug', { enumerable: true, get: function(){ return queen.debug; } }); Object.defineProperty(api, 'workerProviders', { enumerable: true, get: function(){ return utils.values(queen.workerProviders); } }); return api; }; Queen.prototype.port = 9200; Queen.prototype.hostname = "localhost"; Queen.prototype.trackWorkerProviders = false; Queen.prototype.log = utils.noop; Queen.prototype.debug = utils.noop; Queen.prototype.onReady = utils.noop; Queen.prototype.sendToSocket = function(message){ this.socket.write(message); }; Queen.prototype.connect = function(){ this.log("[Queen Remote Client] Connecting to " + this.hostname + ":" + this.port + "\n"); this.socket.connect(this.port, this.hostname, this.connectionHandler.bind(this)); }; Queen.prototype.connectionHandler = function(){ if(this.trackWorkerProviders){ this.sendToSocket([ MESSAGE_TYPE['track worker providers'], trackWorkerProviders ]); } }; Queen.prototype.kill = function(reason){ utils.each(this.workforces, function(workforce){ workforce.kill(); }); this.emitter.emit('dead', reason); this.emitter.removeAllListeners(); this.socket.end(); }; Queen.prototype.messageHandler = function(message){ switch(message[0]){ case MESSAGE_TYPE['workforce message']: this.workforceMessage(message[1], message[2]); break; case MESSAGE_TYPE['worker provider message']: this.workerProviderMessage(message[1], message[2]); break; case MESSAGE_TYPE['create worker provider']: this.createWorkerProvider(message[1], message[2]); break; case MESSAGE_TYPE['worker provider dead']: this.removeWorkerProvider(message[1]); break; case MESSAGE_TYPE['ready']: this.ready = true; this.onReady(this); break; default: break; } }; Queen.prototype.workforceMessage = function(workforceId, message){ var workforceEmitter = this.workforceEmitters[workforceId]; if(workforceEmitter === void 0) return; workforceEmitter.emit('message', message); }; Queen.prototype.workerProviderMessage = function(workerProviderId, message){ var workerProviderEmitter = this.workerProviderEmitters[workerProviderId]; if(workerProviderEmitter === void 0) return; workerProviderEmitter.emit("message", message); }; Queen.prototype.getWorkerProvider = function(id){ return this.workerProviders[id]; }; Queen.prototype.getWorkerProviders = function(filter){ if(!filter) return utils.values(this.workerProviders); return utils.filter(this.workerProviders, function(workerProvider){ return filter(workerProvider.attributes); }); }; Queen.prototype.createWorkerProvider = function(id, attributes){ var self = this, workerProviderEmitter = new EventEmitter(), workerProvider = createWorkerProvider(id, workerProviderEmitter, attributes); this.workerProviderEmitters[id] = workerProviderEmitter; this.workerProviders[id] = workerProvider; workerProvider.on('dead', function(){ self.log('[Queen Remote Client] Worker provider dead: ' + workerProvider + "\n"); delete self.workerProviderEmitters[id]; delete self.workerProviders[id]; }); this.log('[Queen Remote Client] New worker provider: ' + workerProvider + "\n"); this.emitter.emit('workerProvider', workerProvider); utils.each(this.continuousWorkforces, function(workforce){ workforce.populate(workerProvider); }); }; Queen.prototype.removeWorkerProvider = function(id){ if(id in this.workerProviders){ this.workerProviders[id].kill(); } }; Queen.prototype.getWorkforce = function(config){ var self = this, populate = config.populate, autoStart = config.autoStart, workforceId = generateId(), workforceEmitter = new EventEmitter(), workforce, onSendToSocket; onSendToSocket = function(message){ self.sendToSocket([ MESSAGE_TYPE['workforce message'], workforceId, message ]); }; workforce = createWorkforce(this.getWorkerProvider, workforceEmitter, onSendToSocket, { workerHandler: config.handler, stopHandler: config.stop, providerFilter: config.filter, uniquenessFilter: config.uniqueness, killOnStop: config.killOnStop }); if(config.workforceTimeout){ timeout = setTimeout(function(){ workforce.kill("timeout"); }, config.workforceTimeout); workforce.api.on('dead', function(){ clearTimeout(timeout); }); delete config.workforceTimeout; } workforce.api.on('dead', function(){ self.debug('[Queen Remote Client] Workforce dead\n'); delete self.workforceEmitters[workforceId]; }); workforce.api.on('start', function(){ workforce.populate(self.getWorkerProviders()); if(populate === "continuous"){ self.continuousWorkforces[workforceId] = workforce; workforce.api.on('dead', function(){ delete self.continuousWorkforces[workforceId]; }); } }); this.workforceEmitters[workforceId] = workforceEmitter; config.populate = "manual"; // population is done on this side of the connection config.killOnStop = false; // killing is controlled on this side config.autoStart = false; this.sendToSocket([ MESSAGE_TYPE['create workforce'], workforceId, config ]); if(autoStart !== false){ workforce.start(); } this.debug('[Queen Remote Client] New workforce\n'); this.emitter.emit('workforce', workforce.api); return workforce.api; };