X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=server%2Fclient.js;h=609c906713e30e15d794d13aafc0e6a1ab745178;hb=b2bb27e71bb1c72a3fd55984d3817d02607d4a2a;hp=4276043c77eb1e5110b25d6650cde673fb4a4ba2;hpb=265f976c6813b76f9b4842b344fcb3c483a50c27;p=KiwiIRC.git diff --git a/server/client.js b/server/client.js index 4276043..609c906 100755 --- a/server/client.js +++ b/server/client.js @@ -1,34 +1,73 @@ -var util = require('util'), - events = require('events'), - IRCConnection = require('./irc-connection.js').IRCConnection; - IRCCommands = require('./irc-commands.js'); - -var Client = function (websocket) { - var c = this; - +var util = require('util'), + events = require('events'), + crypto = require('crypto'), + _ = require('lodash'), + State = require('./irc/state.js'), + IrcConnection = require('./irc/connection.js').IrcConnection, + ClientCommands = require('./clientcommands.js'), + WebsocketRpc = require('./websocketrpc.js'), + Stats = require('./stats.js'); + + +var Client = function (websocket, opts) { + var that = this; + + Stats.incr('client.created'); + events.EventEmitter.call(this); this.websocket = websocket; - - this.IRC_connections = []; - this.next_connection = 0; - + + // Keep a record of how this client connected + this.server_config = opts.server_config; + + this.rpc = new WebsocketRpc(this.websocket); + this.rpc.on('all', function(func_name, return_fn) { + if (typeof func_name === 'string' && typeof return_fn === 'function') { + Stats.incr('client.command'); + Stats.incr('client.command.' + func_name); + } + }); + + // Clients address + this.real_address = this.websocket.meta.real_address; + + // A hash to identify this client instance + this.hash = crypto.createHash('sha256') + .update(this.real_address) + .update('' + Date.now()) + .update(Math.floor(Math.random() * 100000).toString()) + .digest('hex'); + + this.state = new State(this); + this.buffer = { list: [], motd: '' }; - - websocket.on('irc', function () { - IRC_command.apply(c, arguments); - }); - websocket.on('kiwi', function () { - kiwi_command.apply(c, arguments); + + // Handler for any commands sent from the client + this.client_commands = new ClientCommands(this); + this.client_commands.addRpcEvents(this, this.rpc); + + // Handles the kiwi.* RPC functions + this.attachKiwiCommands(); + + websocket.on('message', function() { + // A message from the client is a sure sign the client is still alive, so consider it a heartbeat + that.heartbeat(); }); - websocket.on('disconnect', function () { - disconnect.apply(c, arguments); + + websocket.on('close', function () { + websocketDisconnect.apply(that, arguments); }); websocket.on('error', function () { - error.apply(c, arguments); + websocketError.apply(that, arguments); }); + + this.disposed = false; + + // Let the client know it's finished connecting + this.sendKiwiCommand('connected'); }; util.inherits(Client, events.EventEmitter); @@ -40,111 +79,107 @@ module.exports.Client = Client; // error MUST otherwise be a truthy value and SHOULD be a string where the cause of the error is known. // response MAY be given even if error is truthy -Client.prototype.sendIRCCommand = function (command, data, callback) { +Client.prototype.sendIrcCommand = function (command, data, callback) { var c = {command: command, data: data}; - console.log('C<--', c); - this.websocket.emit('irc', c, callback); + this.rpc('irc', c, callback); }; -Client.prototype.sendKiwiCommand = function (command, callback) { - this.websocket.emit('kiwi', command, callback); +Client.prototype.sendKiwiCommand = function (command, data, callback) { + var c = {command: command, data: data}; + this.rpc('kiwi', c, callback); }; -var IRC_command = function (command, callback) { - console.log('C-->', command); - var method, str = ''; - if (typeof callback !== 'function') { - callback = function () {}; - } - if ((command.server === null) || (typeof command.server !== 'number')) { - return callback('server not specified'); - } else if (!this.IRC_connections[command.server]) { - return callback('not connected to server'); - } - - command.data = JSON.parse(command.data); - - if (!_.isArray(command.data.args.params)){ - command.data.args.params = [command.data.args.params]; - } - - if (command.data.method === 'ctcp') { - if (command.data.args.request) { - str += 'PRIVMSG '; - } else { - str += 'NOTICE '; - } - str += command.data.args.target + ' :' - str += String.fromCharCode(1) + command.data.args.type + ' '; - str += command.data.args.params + String.fromCharCode(1); - this.IRC_connections[command.server].write(str); - } else if (command.data.method === 'raw') { - this.IRC_connections[command.server].write(command.data.args.data); - } else if (command.data.method === 'kiwi') { - // do special Kiwi stuff here - } else { - method = command.data.method; - command.data = command.data.args; - this.IRC_connections[command.server].write(method + ((command.data.params) ? ' ' + command.data.params.join(' ') : '') + ((command.data.trailing) ? ' :' + command.data.trailing : ''), callback); +Client.prototype.dispose = function () { + Stats.incr('client.disposed'); + + if (this._heartbeat_tmr) { + clearTimeout(this._heartbeat_tmr); } + + this.rpc.dispose(); + this.websocket.removeAllListeners(); + + this.disposed = true; + this.emit('dispose'); + + this.removeAllListeners(); }; -var kiwi_command = function (command, callback) { - var that = this; - console.log(typeof callback); - if (typeof callback !== 'function') { - callback = function () {}; - } - switch (command.command) { - case 'connect': - if ((command.hostname) && (command.port) && (command.nick)) { - var con = new IRCConnection(command.hostname, command.port, command.ssl, - command.nick, {hostname: this.websocket.handshake.revdns, address: this.websocket.handshake.address.address}, - command.password, null); - - var con_num = this.next_connection++; - this.IRC_connections[con_num] = con; - - var binder = new IRCCommands.Binder(con, con_num, this); - binder.bind_irc_commands(); - - con.on('connected', function () { - console.log("con.on('connected')"); - return callback(null, con_num); - }); - - con.on('error', function (err) { - this.websocket.sendKiwiCommand('error', {server: con_num, error: err}); - }); - - con.on('close', function () { - that.IRC_connections[con_num] = null; - }); - } else { - return callback('Hostname, port and nickname must be specified'); - } - break; - default: - callback(); + + +Client.prototype.heartbeat = function() { + if (this._heartbeat_tmr) { + clearTimeout(this._heartbeat_tmr); } + + // After 2 minutes of this heartbeat not being called again, assume the client has disconnected + console.log('resetting heartbeat'); + this._heartbeat_tmr = setTimeout(_.bind(this._heartbeat_timeout, this), 120000); }; -var extension_command = function (command, callback) { - if (typeof callback === 'function') { - callback('not implemented'); - } + +Client.prototype._heartbeat_timeout = function() { + console.log('heartbeat stopped'); + Stats.incr('client.timeout'); + this.dispose(); }; -var disconnect = function () { - _.each(this.IRC_connections, function (irc_connection, i, cons) { - if (irc_connection) { - irc_connection.end('QUIT :Kiwi IRC'); - cons[i] = null; + + +Client.prototype.attachKiwiCommands = function() { + var that = this; + + this.rpc.on('kiwi.connect_irc', function(callback, command) { + if (command.hostname && command.port && command.nick) { + var options = {}; + + // Get any optional parameters that may have been passed + if (command.encoding) + options.encoding = command.encoding; + + options.password = global.config.restrict_server_password || command.password; + + that.state.connect( + (global.config.restrict_server || command.hostname), + (global.config.restrict_server_port || command.port), + (typeof global.config.restrict_server_ssl !== 'undefined' ? + global.config.restrict_server_ssl : + command.ssl), + command.nick, + {hostname: that.websocket.meta.revdns, address: that.websocket.meta.real_address}, + options, + callback); + } else { + return callback('Hostname, port and nickname must be specified'); } }); - this.emit('destroy'); -}; -var error = function () { - this.emit('destroy'); + + this.rpc.on('kiwi.client_info', function(callback, args) { + // keep hold of selected parts of the client_info + that.client_info = { + build_version: args.build_version.toString() || undefined + }; + }); + + + // Just to let us know the client is still there + this.rpc.on('kiwi.heartbeat', function(callback, args) { + that.heartbeat(); + }); }; + + + +// Websocket has disconnected, so quit all the IRC connections +function websocketDisconnect() { + this.emit('disconnect'); + + this.dispose(); +} + + +// TODO: Should this close all the websocket connections too? +function websocketError() { + this.dispose(); +}