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