UNPKG

dnode-tarantula

Version:

asynchronous rpc system for node.js, bi-direction and poly-direction communication...

214 lines (196 loc) 6.14 kB
var net = require('net'); var proto = require('dnode-protocol'); var EventEmitter = require('events').EventEmitter; var util = require('util'); var parser = require('./parser'); var shortid = require('shortid'); var logger = require('./logger'); var DEFAULT = { PORT: 5000, DELIMITER: "\0", RECONNECT_INTERVAL: 1000, }; var Client = function(api, options) { var self = this; var options = options || {}; var api = api || {}; var auth = options.auth; if('function' == typeof api) { api.prototype.$ = {}; api.prototype.$._setId = this._setId.bind(this); api.prototype.$.auth = auth || null; } else { api.$ = {}; api.$._setId = this._setId.bind(this); api.$.updateId = this.updateId.bind(this); api.$.auth = auth || null; api.$.update = this._update.bind(this); } this.auth = auth || null; this.client = new net.Socket(); this.buffer = ''; this.id = options.nodeId || shortid.generate(); this.remote = null; this.dnode = proto(api); this.log = new logger(options.log || true); this._inited = false; this._closed = false; this.started = false; this.dnode.on('request', this._write.bind(this)); this.dnode.on('remote', this._onRemote.bind(this)); this.client.on('connect', this.onConnect.bind(this)); this.client.on('close', this._reconnect.bind(this, options, api)); this.client.on('end', this._reconnect.bind(this, options)); this.client.on('error', function(error) {}); this.client.on('data', this.onData.bind(this)); this.client.connect(options.port || DEFAULT.PORT, options.host); } util.inherits(Client, EventEmitter); Client.prototype.onConnect = function(client) { var self = this; if(this.auth) { this.on('auth:success', function() { self.dnode.start(); self.started = true; }); } else { this.dnode.start(); this.started = true; } } Client.prototype.disconnect = function(callback) { this.client.destroy(); this._closed = true; this.emit('disconnect', this); (callback && callback()); this.log.info('disconnected from server'); } Client.prototype.onData = function(chunk) { var self = this; parser.chunk(this, chunk, function(data) { this.parse(data, function(err, isEvent, data, isString) { if(isString) { self.handleStringData(data); } if(isEvent) { self._event(data.name, data.args); } else { self.dnode.handle(data); } }); }.bind(this)); this.log.debug('got data from client: ', ""+chunk); } Client.prototype.handleStringData = function(data, callback) { var self = this; if(data == "PING") { this.pingBack(); return; } parser.string(data, function(err, parsed) { if(err) { throw new Error(err); } if(parsed.type == 'err') { if(parsed.data == 'disconnected') { self._closed = true; self.emit('disconnect', self); } else { throw new Error(parsed.data); } } else if(parsed.type == 'auth') { if(parsed.data == 'require') { self._write("AUTH:"+self.auth); } else if(parsed.data == 'success') { self.emit('auth:success'); } else if(parsed.data == 'failure') { self.emit('auth:failure'); } } }); callback && callback(); } Client.prototype.pingBack = function() { this._write("PONG"); } Client.prototype.event = function(name) { this._write({ type: 'event', name: name, arguments: Array.prototype.slice.call(arguments, 1), }); } Client.prototype.parse = function(data, callback) { try { data = JSON.parse(data); } catch (e) { callback(null, false, data, true); return; } if(data && data.type && data.type === 'event') { callback(null, true, data, false) } else { callback(null, false, data, false); } } Client.prototype._event = function(name, args, callback) { args = args || []; if(name == 'ready') { this.emit(name, this.remote); } else { this.emit(name, args); } callback && callback() } Client.prototype._reconnect = function(options, api) { if(!this._closed) { setTimeout(function() { this.client.connect(options.port || DEFAULT.PORT, options.host); this.log.debug('reconnecting to server'); }.bind(this), DEFAULT.RECONNECT_INTERVAL); } } Client.prototype._onRemote = function(remote) { this.remote = remote; } Client.prototype._write = function(data) { if('object' == typeof data) { data = JSON.stringify(data); } if(this.client.writable) { this.client.write(data+DEFAULT.DELIMITER); this.log.debug("writing data to server: ", data); } } Client.prototype.update = function() { this.remote.$.update(this.id); } Client.prototype._update = function() { this.dnode.start(); this.log.debug("pushing API update to remote"); } Client.prototype.updateId = function(id, callback) { if(id !== this.id) { this.id = id; } callback && callback(); } Client.prototype._setId = function(id) { if(!this._inited) { eventName = 'connection'; this.log.info('Connected to server'); } else { eventName = 'reconnection'; this.log.info('Reconnected to server'); } if (!this.id) { this.id = id; this.emit(eventName, this.remote); } else { this.remote.$._changeId(id, this.id, this.emit.bind(this, eventName, this.remote)); } if (!this._inited) { this._inited = true; } } module.exports = Client;