Merge pull request #480 from M2Ys4U/config_commandline
[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 WebsocketRpc = require('./websocketrpc.js');
9
10
11 var Client = function (websocket) {
12 var that = this;
13
14 events.EventEmitter.call(this);
15 this.websocket = websocket;
16 this.rpc = new WebsocketRpc(this.websocket);
17
18 // Clients address
19 this.real_address = this.websocket.meta.real_address;
20
21 // A hash to identify this client instance
22 this.hash = crypto.createHash('sha256')
23 .update(this.real_address)
24 .update('' + Date.now())
25 .update(Math.floor(Math.random() * 100000).toString())
26 .digest('hex');
27
28 this.state = new State(this);
29
30 this.buffer = {
31 list: [],
32 motd: ''
33 };
34
35 // Handler for any commands sent from the client
36 this.client_commands = new ClientCommands(this);
37
38 this.rpc.on('irc', function (response, data) {
39 handleClientMessage.call(that, data, response);
40 });
41 this.rpc.on('kiwi', function (response, data) {
42 kiwiCommand.call(that, data, response);
43 });
44 websocket.on('close', function () {
45 websocketDisconnect.apply(that, arguments);
46 });
47 websocket.on('error', function () {
48 websocketError.apply(that, arguments);
49 });
50
51 this.disposed = false;
52
53 // Let the client know it's finished connecting
54 this.sendKiwiCommand('connected');
55 };
56 util.inherits(Client, events.EventEmitter);
57
58 module.exports.Client = Client;
59
60 // Callback API:
61 // Callbacks SHALL accept 2 arguments, error and response, in that order.
62 // error MUST be null where the command is successul.
63 // error MUST otherwise be a truthy value and SHOULD be a string where the cause of the error is known.
64 // response MAY be given even if error is truthy
65
66 Client.prototype.sendIrcCommand = function (command, data, callback) {
67 var c = {command: command, data: data};
68 this.rpc.call('irc', c, callback);
69 };
70
71 Client.prototype.sendKiwiCommand = function (command, data, callback) {
72 var c = {command: command, data: data};
73 this.rpc.call('kiwi', c, callback);
74 };
75
76 Client.prototype.dispose = function () {
77 this.disposed = true;
78 this.rpc.dispose();
79 this.emit('dispose');
80 this.removeAllListeners();
81 };
82
83 function handleClientMessage(msg, callback) {
84 var that = this,
85 server;
86
87 // Make sure we have a server number specified
88 if ((msg.server === null) || (typeof msg.server !== 'number')) {
89 return (typeof callback === 'function') ? callback('server not specified') : undefined;
90 } else if (!this.state.irc_connections[msg.server]) {
91 return (typeof callback === 'function') ? callback('not connected to server') : undefined;
92 }
93
94 // The server this command is directed to
95 server = this.state.irc_connections[msg.server];
96
97 if (typeof callback !== 'function') {
98 callback = null;
99 }
100
101 try {
102 msg.data = JSON.parse(msg.data);
103 } catch (e) {
104 kiwi.log('[handleClientMessage] JSON parsing error ' + msg.data);
105 return;
106 }
107
108 // Run the client command
109 global.modules.emit('client command', {
110 command: msg.data,
111 server: server
112 })
113 .done(function() {
114 that.client_commands.run(msg.data.method, msg.data.args, server, callback);
115 });
116 }
117
118
119
120
121 function kiwiCommand(command, callback) {
122 if (typeof callback !== 'function') {
123 callback = function () {};
124 }
125
126 switch (command.command) {
127 case 'connect':
128 if (command.hostname && command.port && command.nick) {
129 var options = {};
130
131 // Get any optional parameters that may have been passed
132 if (command.encoding)
133 options.encoding = command.encoding;
134
135 options.password = global.config.restrict_server_password || command.password;
136
137 this.state.connect(
138 (global.config.restrict_server || command.hostname),
139 (global.config.restrict_server_port || command.port),
140 (typeof global.config.restrict_server_ssl !== 'undefined' ?
141 global.config.restrict_server_ssl :
142 command.ssl),
143 command.nick,
144 {hostname: this.websocket.meta.revdns, address: this.websocket.meta.real_address},
145 options,
146 callback);
147 } else {
148 return callback('Hostname, port and nickname must be specified');
149 }
150 break;
151 default:
152 callback();
153 }
154 }
155
156
157 // Websocket has disconnected, so quit all the IRC connections
158 function websocketDisconnect() {
159 this.emit('disconnect');
160
161 this.dispose();
162 }
163
164
165 // TODO: Should this close all the websocket connections too?
166 function websocketError() {
167 this.dispose();
168 }