Initial engine.io/websocketrpc port
[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.kiwi.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 util.inherits(Client, events.EventEmitter);
54
55 module.exports.Client = Client;
56
57 // Callback API:
58 // Callbacks SHALL accept 2 arguments, error and response, in that order.
59 // error MUST be null where the command is successul.
60 // error MUST otherwise be a truthy value and SHOULD be a string where the cause of the error is known.
61 // response MAY be given even if error is truthy
62
63 Client.prototype.sendIrcCommand = function (command, data, callback) {
64 var c = {command: command, data: data};
65 this.rpc.call('irc', c, callback);
66 };
67
68 Client.prototype.sendKiwiCommand = function (command, data, callback) {
69 var c = {command: command, data: data};
70 this.rpc.call('kiwi', c, callback);
71 };
72
73 Client.prototype.dispose = function () {
74 this.disposed = true;
75 this.emit('dispose');
76 this.removeAllListeners();
77 };
78
79 function handleClientMessage(msg, callback) {
80 var server;
81
82 // Make sure we have a server number specified
83 if ((msg.server === null) || (typeof msg.server !== 'number')) {
84 return (typeof callback === 'function') ? callback('server not specified') : undefined;
85 } else if (!this.state.irc_connections[msg.server]) {
86 return (typeof callback === 'function') ? callback('not connected to server') : undefined;
87 }
88
89 // The server this command is directed to
90 server = this.state.irc_connections[msg.server];
91
92 if (typeof callback !== 'function') {
93 callback = null;
94 }
95
96 try {
97 msg.data = JSON.parse(msg.data);
98 } catch (e) {
99 kiwi.log('[handleClientMessage] JSON parsing error ' + msg.data);
100 return;
101 }
102
103 // Run the client command
104 this.client_commands.run(msg.data.method, msg.data.args, server, callback);
105 }
106
107
108
109
110 function kiwiCommand(command, callback) {
111 if (typeof callback !== 'function') {
112 callback = function () {};
113 }
114
115 switch (command.command) {
116 case 'connect':
117 if (command.hostname && command.port && command.nick) {
118 var options = {};
119
120 // Get any optional parameters that may have been passed
121 if (command.encoding)
122 options.encoding = command.encoding;
123
124 options.password = global.config.restrict_server_password || command.password;
125
126 this.state.connect(
127 (global.config.restrict_server || command.hostname),
128 (global.config.restrict_server_port || command.port),
129 (typeof global.config.restrict_server_ssl !== 'undefined' ?
130 global.config.restrict_server_ssl :
131 command.ssl),
132 command.nick,
133 {hostname: this.websocket.kiwi.revdns, address: this.websocket.kiwi.real_address},
134 options,
135 callback);
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 this.emit('disconnect');
149
150 this.dispose();
151 }
152
153
154 // TODO: Should this close all the websocket connections too?
155 function websocketError() {
156 this.dispose();
157 }