client + ircconnection cleanup
[KiwiIRC.git] / server / client.js
1 var util = require('util'),
2 events = require('events'),
3 _ = require('underscore'),
4 IrcConnection = require('./ircconnection.js').IrcConnection,
5 IrcCommands = require('./irc-commands.js'),
6 ClientCommandset = require('./client-commands.js').ClientCommandset;
7
8
9 var Client = function (websocket) {
10 var that = this;
11
12 events.EventEmitter.call(this);
13 this.websocket = websocket;
14
15 this.irc_connections = [];
16 this.next_connection = 0;
17
18 this.buffer = {
19 list: [],
20 motd: ''
21 };
22
23 // Handler for any commands sent from the client
24 this.client_commands = new ClientCommandset(this);
25
26 websocket.on('irc', function () {
27 handleClientMessage.apply(that, arguments);
28 });
29 websocket.on('kiwi', function () {
30 kiwiCommand.apply(that, arguments);
31 });
32 websocket.on('disconnect', function () {
33 websocketDisconnect.apply(that, arguments);
34 });
35 websocket.on('error', function () {
36 websocketError.apply(that, arguments);
37 });
38 };
39 util.inherits(Client, events.EventEmitter);
40
41 module.exports.Client = Client;
42
43 // Callback API:
44 // Callbacks SHALL accept 2 arguments, error and response, in that order.
45 // error MUST be null where the command is successul.
46 // error MUST otherwise be a truthy value and SHOULD be a string where the cause of the error is known.
47 // response MAY be given even if error is truthy
48
49 Client.prototype.sendIRCCommand = function (command, data, callback) {
50 var c = {command: command, data: data};
51 //console.log('C<--', c);
52 this.websocket.emit('irc', c, callback);
53 };
54
55 Client.prototype.sendKiwiCommand = function (command, callback) {
56 this.websocket.emit('kiwi', command, callback);
57 };
58
59 function handleClientMessage(msg, callback) {
60 var server, args, obj, channels, keys;
61
62 // Make sure we have a server number specified
63 if ((msg.server === null) || (typeof msg.server !== 'number')) {
64 return (typeof callback === 'function') ? callback('server not specified') : undefined;
65 } else if (!this.irc_connections[msg.server]) {
66 return (typeof callback === 'function') ? callback('not connected to server') : undefined;
67 }
68
69 // The server this command is directed to
70 server = this.irc_connections[msg.server];
71
72 if (typeof callback !== 'function') {
73 callback = null;
74 }
75
76 try {
77 msg.data = JSON.parse(msg.data);
78 } catch (e) {
79 kiwi.log('[handleClientMessage] JSON parsing error ' + msg.data);
80 return;
81 }
82
83 // Run the client command
84 this.client_commands.run(msg.data.method, msg.data.args, server, callback);
85 };
86
87
88
89
90 function kiwiCommand(command, callback) {
91 var that = this;
92
93 if (typeof callback !== 'function') {
94 callback = function () {};
95 }
96 switch (command.command) {
97 case 'connect':
98 if ((command.hostname) && (command.port) && (command.nick)) {
99 var con = new IrcConnection(command.hostname, command.port, command.ssl,
100 command.nick, {hostname: this.websocket.handshake.revdns, address: this.websocket.handshake.address.address},
101 command.password, null);
102
103 var con_num = this.next_connection++;
104 this.irc_connections[con_num] = con;
105
106 var binder = new IrcCommands.Binder(con, con_num, this);
107 binder.bind_irc_commands();
108
109 con.on('connected', function () {
110 console.log("con.on('connected')");
111 return callback(null, con_num);
112 });
113
114 con.on('error', function (err) {
115 this.websocket.sendKiwiCommand('error', {server: con_num, error: err});
116 });
117
118 con.on('close', function () {
119 that.irc_connections[con_num] = null;
120 });
121 } else {
122 return callback('Hostname, port and nickname must be specified');
123 }
124 break;
125 default:
126 callback();
127 }
128 };
129
130
131 // Websocket has disconnected, so quit all the IRC connections
132 function websocketDisconnect() {
133 _.each(this.irc_connections, function (irc_connection, i, cons) {
134 if (irc_connection) {
135 irc_connection.end('QUIT :Kiwi IRC');
136 cons[i] = null;
137 }
138 });
139 this.emit('destroy');
140 };
141
142
143 // TODO: Should this close all the websocket connections too?
144 function websocketError() {
145 this.emit('destroy');
146 };