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