Client: Encoding within the URL
[KiwiIRC.git] / client / assets / src / models / gateway.js
1 _kiwi.model.Gateway = function () {
2
3 // Set to a reference to this object within initialize()
4 var that = null;
5
6 this.defaults = {
7 /**
8 * The name of the network
9 * @type String
10 */
11 name: 'Server',
12
13 /**
14 * The address (URL) of the network
15 * @type String
16 */
17 address: '',
18
19 /**
20 * The current nickname
21 * @type String
22 */
23 nick: '',
24
25 /**
26 * The channel prefix for this network
27 * @type String
28 */
29 channel_prefix: '#',
30
31 /**
32 * The user prefixes for channel owner/admin/op/voice etc. on this network
33 * @type Array
34 */
35 user_prefixes: ['~', '&', '@', '+'],
36
37 /**
38 * The URL to the Kiwi server
39 * @type String
40 */
41 kiwi_server: '//kiwi',
42
43 /**
44 * List of nicks we are ignoring
45 * @type Array
46 */
47 ignore_list: []
48 };
49
50
51 this.initialize = function () {
52 that = this;
53
54 // For ease of access. The socket.io object
55 this.socket = this.get('socket');
56
57 this.applyEventHandlers();
58
59 // Used to check if a disconnection was unplanned
60 this.disconnect_requested = false;
61 };
62
63
64 this.applyEventHandlers = function () {
65 /*
66 kiwi.gateway.on('message:#channel', my_function);
67 kiwi.gateway.on('message:somenick', my_function);
68
69 kiwi.gateway.on('notice:#channel', my_function);
70 kiwi.gateway.on('action:somenick', my_function);
71
72 kiwi.gateway.on('join:#channel', my_function);
73 kiwi.gateway.on('part:#channel', my_function);
74 kiwi.gateway.on('quit', my_function);
75 */
76 var that = this;
77
78 // Some easier handler events
79 this.on('onmsg', function (event) {
80 var source,
81 connection = _kiwi.app.connections.getByConnectionId(event.server),
82 is_pm = (event.channel == connection.get('nick'));
83
84 source = is_pm ? event.nick : event.channel;
85
86 that.trigger('message:' + source, event);
87 that.trigger('message', event);
88
89 if (is_pm) {
90 that.trigger('pm:' + source, event);
91 that.trigger('pm', event);
92 }
93 }, this);
94
95
96 this.on('onnotice', function (event) {
97 // The notice towards a channel or a query window?
98 var source = event.target || event.nick;
99
100 this.trigger('notice:' + source, event);
101 this.trigger('notice', event);
102 }, this);
103
104
105 this.on('onaction', function (event) {
106 var source,
107 connection = _kiwi.app.connections.getByConnectionId(event.server),
108 is_pm = (event.channel == connection.get('nick'));
109
110 source = is_pm ? event.nick : event.channel;
111
112 that.trigger('action:' + source, event);
113
114 if (is_pm) {
115 that.trigger('action:' + source, event);
116 that.trigger('action', event);
117 }
118 }, this);
119
120
121 this.on('ontopic', function (event) {
122 that.trigger('topic:' + event.channel, event);
123 that.trigger('topic', event);
124 });
125
126
127 this.on('onjoin', function (event) {
128 that.trigger('join:' + event.channel, event);
129 that.trigger('join', event);
130 });
131
132 };
133
134
135
136 this.reconnect = function (callback) {
137 var that = this,
138 transport_path;
139
140 this.disconnect_requested = true;
141 this.socket.disconnect();
142
143 // To get around the allow-origin issues for requests, completely reload the
144 // transport source from the new server
145 window.io = null;
146
147 // Path to get the socket.io transport code
148 transport_path = _kiwi.app.kiwi_server + _kiwi.app.get('base_path') + '/transport/socket.io.js?ts='+(new Date().getTime());
149 $script(transport_path, function() {
150 if (!window.io) {
151 return callback('err_kiwi_server_not_found');
152 }
153
154 that.set('kiwi_server', _kiwi.app.kiwi_server + '/kiwi');
155 that.connect(callback);
156 });
157 };
158
159
160
161 /**
162 * Connects to the server
163 * @param {Function} callback A callback function to be invoked once Kiwi's server has connected to the IRC server
164 */
165 this.connect = function (callback) {
166 var resource;
167
168 // Work out the resource URL for socket.io
169 if (_kiwi.app.get('base_path').substr(0, 1) === '/') {
170 resource = _kiwi.app.get('base_path');
171 resource = resource.substr(1, resource.length-1);
172 resource += '/transport';
173 } else {
174 resource = _kiwi.app.get('base_path') + '/transport';
175 }
176
177 this.socket = io.connect(this.get('kiwi_server'), {
178 'resource': resource,
179
180 'try multiple transports': true,
181 'connect timeout': 3000,
182 'max reconnection attempts': 7,
183 'reconnection delay': 2000,
184 'sync disconnect on unload': false
185 });
186 this.socket.on('connect_failed', function (reason) {
187 this.socket.disconnect();
188 this.trigger("connect_fail", {reason: reason});
189 });
190
191 this.socket.on('error', function (e) {
192 console.log("_kiwi.gateway.socket.on('error')", {reason: e});
193 that.trigger("connect_fail", {reason: e});
194 });
195
196 this.socket.on('connecting', function (transport_type) {
197 console.log("_kiwi.gateway.socket.on('connecting')");
198 that.trigger("connecting");
199 });
200
201 /**
202 * Once connected to the kiwi server send the IRC connect command along
203 * with the IRC server details.
204 * A `connect` event is sent from the kiwi server once connected to the
205 * IRCD and the nick has been accepted.
206 */
207 this.socket.on('connect', function () {
208 // Reset the disconnect_requested flag
209 that.disconnect_requested = false;
210
211 callback && callback();
212 });
213
214 this.socket.on('too_many_connections', function () {
215 that.trigger("connect_fail", {reason: 'too_many_connections'});
216 });
217
218 this.socket.on('irc', function (data, callback) {
219 that.parse(data.command, data.data);
220 });
221
222 this.socket.on('kiwi', function (data, callback) {
223 that.parseKiwi(data.command, data.data);
224 });
225
226 this.socket.on('disconnect', function () {
227 that.trigger("disconnect", {});
228 console.log("_kiwi.gateway.socket.on('disconnect')");
229 });
230
231 this.socket.on('close', function () {
232 console.log("_kiwi.gateway.socket.on('close')");
233 });
234
235 this.socket.on('reconnecting', function (reconnectionDelay, reconnectionAttempts) {
236 console.log("_kiwi.gateway.socket.on('reconnecting')");
237 that.trigger("reconnecting", {delay: reconnectionDelay, attempts: reconnectionAttempts});
238 });
239
240 this.socket.on('reconnect_failed', function () {
241 console.log("_kiwi.gateway.socket.on('reconnect_failed')");
242 });
243 };
244
245
246 /**
247 * Return a new network object with the new connection details
248 */
249 this.newConnection = function(connection_info, callback_fn) {
250 var that = this;
251
252 this.makeIrcConnection(connection_info, function(err, server_num) {
253 var connection;
254
255 if (!err) {
256 if (!_kiwi.app.connections.getByConnectionId(server_num)){
257 var inf = {
258 connection_id: server_num,
259 nick: connection_info.nick,
260 address: connection_info.host,
261 port: connection_info.port,
262 ssl: connection_info.ssl,
263 password: connection_info.password
264 };
265 connection = new _kiwi.model.Network(inf);
266 _kiwi.app.connections.add(connection);
267 }
268
269 console.log("_kiwi.gateway.socket.on('connect')", connection);
270 callback_fn && callback_fn(err, connection);
271
272 } else {
273 console.log("_kiwi.gateway.socket.on('error')", {reason: err});
274 callback_fn && callback_fn(err);
275 }
276 });
277 };
278
279
280 /**
281 * Make a new IRC connection and return its connection ID
282 */
283 this.makeIrcConnection = function(connection_info, callback_fn) {
284 var server_info = {
285 command: 'connect',
286 nick: connection_info.nick,
287 hostname: connection_info.host,
288 port: connection_info.port,
289 ssl: connection_info.ssl,
290 password: connection_info.password
291 };
292
293 // A few optional parameters
294 if (connection_info.options.encoding)
295 server_info.encoding = connection_info.options.encoding;
296
297 this.socket.emit('kiwi', server_info, function (err, server_num) {
298 if (!err) {
299 callback_fn && callback_fn(err, server_num);
300
301 } else {
302 callback_fn && callback_fn(err);
303 }
304 });
305 };
306
307
308 this.isConnected = function () {
309 return this.socket.socket.connected;
310 };
311
312
313
314 this.parseKiwi = function (command, data) {
315 this.trigger('kiwi:' + command, data);
316 this.trigger('kiwi', data);
317 };
318 /*
319 Events:
320 msg
321 action
322 server_connect
323 options
324 motd
325 notice
326 userlist
327 nick
328 join
329 topic
330 part
331 kick
332 quit
333 whois
334 syncchannel_redirect
335 debug
336 */
337 /**
338 * Parses the response from the server
339 */
340 this.parse = function (command, data) {
341 //console.log('gateway event', command, data);
342
343 if (command !== undefined) {
344 switch (command) {
345 case 'options':
346 $.each(data.options, function (name, value) {
347 switch (name) {
348 case 'CHANTYPES':
349 that.set('channel_prefix', value.join(''));
350 break;
351 case 'NETWORK':
352 that.set('name', value);
353 break;
354 case 'PREFIX':
355 that.set('user_prefixes', value);
356 break;
357 }
358 });
359 that.set('cap', data.cap);
360 break;
361
362 /*
363 case 'sync':
364 if (_kiwi.gateway.onSync && _kiwi.gateway.syncing) {
365 _kiwi.gateway.syncing = false;
366 _kiwi.gateway.onSync(item);
367 }
368 break;
369 */
370
371 case 'kiwi':
372 this.emit('_kiwi.' + data.namespace, data.data);
373 break;
374 }
375 }
376
377
378 if (typeof data.server !== 'undefined') {
379 that.trigger('connection:' + data.server.toString(), {
380 event_name: command,
381 event_data: data
382 });
383 }
384
385 // Trigger the global events (Mainly legacy now)
386 that.trigger('on' + command, data);
387 };
388
389 /**
390 * Sends data to the server
391 * @private
392 * @param {Object} data The data to send
393 * @param {Function} callback A callback function
394 */
395 this.sendData = function (connection_id, data, callback) {
396 if (typeof connection_id === 'undefined' || connection_id === null)
397 connection_id = _kiwi.app.connections.active_connection.get('connection_id');
398
399 var data_buffer = {
400 server: connection_id,
401 data: JSON.stringify(data)
402 };
403 this.socket.emit('irc', data_buffer, callback);
404 };
405
406 /**
407 * Sends a PRIVMSG message
408 * @param {String} target The target of the message (e.g. a channel or nick)
409 * @param {String} msg The message to send
410 * @param {Function} callback A callback function
411 */
412 this.privmsg = function (connection_id, target, msg, callback) {
413 var data = {
414 method: 'privmsg',
415 args: {
416 target: target,
417 msg: msg
418 }
419 };
420
421 this.sendData(connection_id, data, callback);
422 };
423
424 /**
425 * Sends a NOTICE message
426 * @param {String} target The target of the message (e.g. a channel or nick)
427 * @param {String} msg The message to send
428 * @param {Function} callback A callback function
429 */
430 this.notice = function (connection_id, target, msg, callback) {
431 var data = {
432 method: 'notice',
433 args: {
434 target: target,
435 msg: msg
436 }
437 };
438
439 this.sendData(connection_id, data, callback);
440 };
441
442 /**
443 * Sends a CTCP message
444 * @param {Boolean} request Indicates whether this is a CTCP request (true) or reply (false)
445 * @param {String} type The type of CTCP message, e.g. 'VERSION', 'TIME', 'PING' etc.
446 * @param {String} target The target of the message, e.g a channel or nick
447 * @param {String} params Additional paramaters
448 * @param {Function} callback A callback function
449 */
450 this.ctcp = function (connection_id, request, type, target, params, callback) {
451 var data = {
452 method: 'ctcp',
453 args: {
454 request: request,
455 type: type,
456 target: target,
457 params: params
458 }
459 };
460
461 this.sendData(connection_id, data, callback);
462 };
463
464 /**
465 * @param {String} target The target of the message (e.g. a channel or nick)
466 * @param {String} msg The message to send
467 * @param {Function} callback A callback function
468 */
469 this.action = function (connection_id, target, msg, callback) {
470 this.ctcp(connection_id, true, 'ACTION', target, msg, callback);
471 };
472
473 /**
474 * Joins a channel
475 * @param {String} channel The channel to join
476 * @param {String} key The key to the channel
477 * @param {Function} callback A callback function
478 */
479 this.join = function (connection_id, channel, key, callback) {
480 var data = {
481 method: 'join',
482 args: {
483 channel: channel,
484 key: key
485 }
486 };
487
488 this.sendData(connection_id, data, callback);
489 };
490
491 /**
492 * Leaves a channel
493 * @param {String} channel The channel to part
494 * @param {Function} callback A callback function
495 */
496 this.part = function (connection_id, channel, callback) {
497 var data = {
498 method: 'part',
499 args: {
500 channel: channel
501 }
502 };
503
504 this.sendData(connection_id, data, callback);
505 };
506
507 /**
508 * Queries or modifies a channell topic
509 * @param {String} channel The channel to query or modify
510 * @param {String} new_topic The new topic to set
511 * @param {Function} callback A callback function
512 */
513 this.topic = function (connection_id, channel, new_topic, callback) {
514 var data = {
515 method: 'topic',
516 args: {
517 channel: channel,
518 topic: new_topic
519 }
520 };
521
522 this.sendData(connection_id, data, callback);
523 };
524
525 /**
526 * Kicks a user from a channel
527 * @param {String} channel The channel to kick the user from
528 * @param {String} nick The nick of the user to kick
529 * @param {String} reason The reason for kicking the user
530 * @param {Function} callback A callback function
531 */
532 this.kick = function (connection_id, channel, nick, reason, callback) {
533 var data = {
534 method: 'kick',
535 args: {
536 channel: channel,
537 nick: nick,
538 reason: reason
539 }
540 };
541
542 this.sendData(connection_id, data, callback);
543 };
544
545 /**
546 * Disconnects us from the server
547 * @param {String} msg The quit message to send to the IRC server
548 * @param {Function} callback A callback function
549 */
550 this.quit = function (connection_id, msg, callback) {
551 msg = msg || "";
552 var data = {
553 method: 'quit',
554 args: {
555 message: msg
556 }
557 };
558
559 this.sendData(connection_id, data, callback);
560 };
561
562 /**
563 * Sends a string unmodified to the IRC server
564 * @param {String} data The data to send to the IRC server
565 * @param {Function} callback A callback function
566 */
567 this.raw = function (connection_id, data, callback) {
568 data = {
569 method: 'raw',
570 args: {
571 data: data
572 }
573 };
574
575 this.sendData(connection_id, data, callback);
576 };
577
578 /**
579 * Changes our nickname
580 * @param {String} new_nick Our new nickname
581 * @param {Function} callback A callback function
582 */
583 this.changeNick = function (connection_id, new_nick, callback) {
584 var data = {
585 method: 'nick',
586 args: {
587 nick: new_nick
588 }
589 };
590
591 this.sendData(connection_id, data, callback);
592 };
593
594 /**
595 * Sends ENCODING change request to server.
596 * @param {String} new_encoding The new proposed encode
597 * @param {Fucntion} callback A callback function
598 */
599 this.setEncoding = function (connection_id, new_encoding, callback) {
600 var data = {
601 method: 'encoding',
602 args: {
603 encoding: new_encoding
604 }
605 };
606 this.sendData(connection_id, data, callback);
607 };
608
609 /**
610 * Sends data to a fellow Kiwi IRC user
611 * @param {String} target The nick of the Kiwi IRC user to send to
612 * @param {String} data The data to send
613 * @param {Function} callback A callback function
614 */
615 this.kiwi = function (target, data, callback) {
616 data = {
617 method: 'kiwi',
618 args: {
619 target: target,
620 data: data
621 }
622 };
623
624 this.sendData(data, callback);
625 };
626
627 // Check a nick alongside our ignore list
628 this.isNickIgnored = function (nick) {
629 var idx, list = this.get('ignore_list');
630 var pattern, regex;
631
632 for (idx = 0; idx < list.length; idx++) {
633 pattern = list[idx].replace(/([.+^$[\]\\(){}|-])/g, "\\$1")
634 .replace('*', '.*')
635 .replace('?', '.');
636
637 regex = new RegExp(pattern, 'i');
638 if (regex.test(nick)) return true;
639 }
640
641 return false;
642 };
643
644
645 return new (Backbone.Model.extend(this))(arguments);
646 };