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