Kiwi client/server RPC namespaces
[KiwiIRC.git] / client / src / app.js
1 // Holds anything kiwi client specific (ie. front, gateway, _kiwi.plugs..)
2 /**
3 * @namespace
4 */
5 var _kiwi = {};
6
7 _kiwi.misc = {};
8 _kiwi.model = {};
9 _kiwi.view = {};
10 _kiwi.applets = {};
11
12
13 /**
14 * A global container for third party access
15 * Will be used to access a limited subset of kiwi functionality
16 * and data (think: plugins)
17 */
18 _kiwi.global = {
19 build_version: '', // Kiwi IRC version this is built from (Set from index.html)
20 settings: undefined, // Instance of _kiwi.model.DataStore
21 plugins: undefined, // Instance of _kiwi.model.PluginManager
22 events: undefined, // Instance of PluginInterface
23 utils: {}, // References to misc. re-usable helpers / functions
24
25 initUtils: function() {
26 this.utils.randomString = randomString;
27 this.utils.secondsToTime = secondsToTime;
28 this.utils.parseISO8601 = parseISO8601;
29 this.utils.escapeRegex = escapeRegex;
30 this.utils.formatIRCMsg = formatIRCMsg;
31 this.utils.styleText = styleText;
32 this.utils.hsl2rgb = hsl2rgb;
33 },
34
35 rpc: function() {
36 if (!_kiwi.gateway.rpc) {
37 throw 'RPC unavailable. Is Kiwi connected to the server yet?';
38 }
39
40 return _kiwi.gateway.rpc.apply(_kiwi.gateway.rpc, arguments);
41 },
42
43 rpcNamespace: function(namespace) {
44 if (!_kiwi.gateway.rpc) {
45 throw 'RPC unavailable. Is Kiwi connected to the server yet?';
46 }
47
48 return _kiwi.gateway.rpc.namespace(namespace);
49 },
50
51 addMediaMessageType: function(match, buildHtml) {
52 _kiwi.view.MediaMessage.addType(match, buildHtml);
53 },
54
55 // Event managers for plugins
56 components: {
57 EventComponent: function(event_source, proxy_event_name) {
58 /*
59 * proxyEvent() listens for events then re-triggers them on its own
60 * event emitter. Why? So we can .off() on this emitter without
61 * effecting the source of events. Handy for plugins that we don't
62 * trust meddling with the core events.
63 *
64 * If listening for 'all' events the arguments are as follows:
65 * 1. Name of the triggered event
66 * 2. The event data
67 * For all other events, we only have one argument:
68 * 1. The event data
69 *
70 * When this is used via `new kiwi.components.Network()`, this listens
71 * for 'all' events so the first argument is the event name which is
72 * the connection ID. We don't want to re-trigger this event name so
73 * we need to juggle the arguments to find the real event name we want
74 * to emit.
75 */
76 function proxyEvent(event_name, event_data) {
77 if (proxy_event_name == 'all') {
78 } else {
79 event_data = event_name.event_data;
80 event_name = event_name.event_name;
81 }
82
83 this.trigger(event_name, event_data);
84 }
85
86 // The event we are to proxy
87 proxy_event_name = proxy_event_name || 'all';
88
89 _.extend(this, Backbone.Events);
90 this._source = event_source;
91
92 // Proxy the events to this dispatcher
93 event_source.on(proxy_event_name, proxyEvent, this);
94
95 // Clean up this object
96 this.dispose = function () {
97 event_source.off(proxy_event_name, proxyEvent);
98 this.off();
99 delete this.event_source;
100 };
101 },
102
103 Network: function(connection_id) {
104 var connection_event;
105
106 // If no connection id given, use all connections
107 if (typeof connection_id !== 'undefined') {
108 connection_event = 'connection:' + connection_id.toString();
109 } else {
110 connection_event = 'connection';
111 }
112
113 // Helper to get the network object
114 var getNetwork = function() {
115 var network = typeof connection_id === 'undefined' ?
116 _kiwi.app.connections.active_connection :
117 _kiwi.app.connections.getByConnectionId(connection_id);
118
119 return network ?
120 network :
121 undefined;
122 };
123
124 // Create the return object (events proxy from the gateway)
125 var obj = new this.EventComponent(_kiwi.gateway, connection_event);
126
127 // Proxy several gateway functions onto the return object
128 var funcs = {
129 kiwi: 'kiwi', raw: 'raw', kick: 'kick', topic: 'topic',
130 part: 'part', join: 'join', action: 'action', ctcp: 'ctcp',
131 ctcpRequest: 'ctcpRequest', ctcpResponse: 'ctcpResponse',
132 notice: 'notice', msg: 'privmsg', changeNick: 'changeNick',
133 channelInfo: 'channelInfo', mode: 'mode', quit: 'quit'
134 };
135
136 _.each(funcs, function(gateway_fn, func_name) {
137 obj[func_name] = function() {
138 var fn_name = gateway_fn;
139
140 // Add connection_id to the argument list
141 var args = Array.prototype.slice.call(arguments, 0);
142 args.unshift(connection_id);
143
144 // Call the gateway function on behalf of this connection
145 return _kiwi.gateway[fn_name].apply(_kiwi.gateway, args);
146 };
147 });
148
149 // Add the networks getters/setters
150 obj.get = function(name) {
151 var network, restricted_keys;
152
153 network = getNetwork();
154 if (!network) {
155 return;
156 }
157
158 restricted_keys = [
159 'password'
160 ];
161 if (restricted_keys.indexOf(name) > -1) {
162 return undefined;
163 }
164
165 return network.get(name);
166 };
167
168 obj.set = function() {
169 var network = getNetwork();
170 if (!network) {
171 return;
172 }
173
174 return network.set.apply(network, arguments);
175 };
176
177 return obj;
178 },
179
180 ControlInput: function() {
181 var obj = new this.EventComponent(_kiwi.app.controlbox);
182 var funcs = {
183 run: 'processInput', addPluginIcon: 'addPluginIcon'
184 };
185
186 _.each(funcs, function(controlbox_fn, func_name) {
187 obj[func_name] = function() {
188 var fn_name = controlbox_fn;
189 return _kiwi.app.controlbox[fn_name].apply(_kiwi.app.controlbox, arguments);
190 };
191 });
192
193 return obj;
194 }
195 },
196
197 // Entry point to start the kiwi application
198 init: function (opts, callback) {
199 var jobs, locale, localeLoaded, textThemeLoaded, text_theme;
200 opts = opts || {};
201
202 this.initUtils();
203
204 jobs = new JobManager();
205 jobs.onFinish(function(locale, s, xhr) {
206 _kiwi.app = new _kiwi.model.Application(opts);
207
208 // Start the client up
209 _kiwi.app.initializeInterfaces();
210
211 // Event emitter to let plugins interface with parts of kiwi
212 _kiwi.global.events = new PluginInterface();
213
214 // Now everything has started up, load the plugin manager for third party plugins
215 _kiwi.global.plugins = new _kiwi.model.PluginManager();
216
217 callback();
218 });
219
220 textThemeLoaded = function(text_theme, s, xhr) {
221 opts.text_theme = text_theme;
222
223 jobs.finishJob('load_text_theme');
224 };
225
226 localeLoaded = function(locale, s, xhr) {
227 if (locale) {
228 _kiwi.global.i18n = new Jed(locale);
229 } else {
230 _kiwi.global.i18n = new Jed();
231 }
232
233 jobs.finishJob('load_locale');
234 };
235
236 // Set up the settings datastore
237 _kiwi.global.settings = _kiwi.model.DataStore.instance('kiwi.settings');
238 _kiwi.global.settings.load();
239
240 // Set the window title
241 window.document.title = opts.server_settings.client.window_title || 'Kiwi IRC';
242
243 jobs.registerJob('load_locale');
244 locale = _kiwi.global.settings.get('locale');
245 if (!locale) {
246 $.getJSON(opts.base_path + '/assets/locales/magic.json', localeLoaded);
247 } else {
248 $.getJSON(opts.base_path + '/assets/locales/' + locale + '.json', localeLoaded);
249 }
250
251 jobs.registerJob('load_text_theme');
252 text_theme = opts.server_settings.client.settings.text_theme || 'default';
253 $.getJSON(opts.base_path + '/assets/text_themes/' + text_theme + '.json', textThemeLoaded);
254 },
255
256 start: function() {
257 _kiwi.app.showStartup();
258 },
259
260 // Allow plugins to change the startup applet
261 registerStartupApplet: function(startup_applet_name) {
262 _kiwi.app.startup_applet_name = startup_applet_name;
263 },
264
265 /**
266 * Open a new IRC connection
267 * @param {Object} connection_details {nick, host, port, ssl, password, options}
268 * @param {Function} callback function(err, network){}
269 */
270 newIrcConnection: function(connection_details, callback) {
271 _kiwi.gateway.newConnection(connection_details, callback);
272 },
273
274
275 /**
276 * Taking settings from the server and URL, extract the default server/channel/nick settings
277 */
278 defaultServerSettings: function () {
279 var parts;
280 var defaults = {
281 nick: '',
282 server: '',
283 port: 6667,
284 ssl: false,
285 channel: '',
286 channel_key: ''
287 };
288 var uricheck;
289
290
291 /**
292 * Get any settings set by the server
293 * These settings may be changed in the server selection dialog or via URL parameters
294 */
295 if (_kiwi.app.server_settings.client) {
296 if (_kiwi.app.server_settings.client.nick)
297 defaults.nick = _kiwi.app.server_settings.client.nick;
298
299 if (_kiwi.app.server_settings.client.server)
300 defaults.server = _kiwi.app.server_settings.client.server;
301
302 if (_kiwi.app.server_settings.client.port)
303 defaults.port = _kiwi.app.server_settings.client.port;
304
305 if (_kiwi.app.server_settings.client.ssl)
306 defaults.ssl = _kiwi.app.server_settings.client.ssl;
307
308 if (_kiwi.app.server_settings.client.channel)
309 defaults.channel = _kiwi.app.server_settings.client.channel;
310
311 if (_kiwi.app.server_settings.client.channel_key)
312 defaults.channel_key = _kiwi.app.server_settings.client.channel_key;
313 }
314
315
316
317 /**
318 * Get any settings passed in the URL
319 * These settings may be changed in the server selection dialog
320 */
321
322 // Any query parameters first
323 if (getQueryVariable('nick'))
324 defaults.nick = getQueryVariable('nick');
325
326 if (window.location.hash)
327 defaults.channel = window.location.hash;
328
329
330 // Process the URL part by part, extracting as we go
331 parts = window.location.pathname.toString().replace(_kiwi.app.get('base_path'), '').split('/');
332
333 if (parts.length > 0) {
334 parts.shift();
335
336 if (parts.length > 0 && parts[0]) {
337 // Check to see if we're dealing with an irc: uri, or whether we need to extract the server/channel info from the HTTP URL path.
338 uricheck = parts[0].substr(0, 7).toLowerCase();
339 if ((uricheck === 'ircs%3a') || (uricheck.substr(0,6) === 'irc%3a')) {
340 parts[0] = decodeURIComponent(parts[0]);
341 // irc[s]://<host>[:<port>]/[<channel>[?<password>]]
342 uricheck = /^irc(s)?:(?:\/\/?)?([^:\/]+)(?::([0-9]+))?(?:(?:\/)([^\?]*)(?:(?:\?)(.*))?)?$/.exec(parts[0]);
343 /*
344 uricheck[1] = ssl (optional)
345 uricheck[2] = host
346 uricheck[3] = port (optional)
347 uricheck[4] = channel (optional)
348 uricheck[5] = channel key (optional, channel must also be set)
349 */
350 if (uricheck) {
351 if (typeof uricheck[1] !== 'undefined') {
352 defaults.ssl = true;
353 if (defaults.port === 6667) {
354 defaults.port = 6697;
355 }
356 }
357 defaults.server = uricheck[2];
358 if (typeof uricheck[3] !== 'undefined') {
359 defaults.port = uricheck[3];
360 }
361 if (typeof uricheck[4] !== 'undefined') {
362 defaults.channel = '#' + uricheck[4];
363 if (typeof uricheck[5] !== 'undefined') {
364 defaults.channel_key = uricheck[5];
365 }
366 }
367 }
368 parts = [];
369 } else {
370 // Extract the port+ssl if we find one
371 if (parts[0].search(/:/) > 0) {
372 defaults.port = parts[0].substring(parts[0].search(/:/) + 1);
373 defaults.server = parts[0].substring(0, parts[0].search(/:/));
374 if (defaults.port[0] === '+') {
375 defaults.port = parseInt(defaults.port.substring(1), 10);
376 defaults.ssl = true;
377 } else {
378 defaults.ssl = false;
379 }
380
381 } else {
382 defaults.server = parts[0];
383 }
384
385 parts.shift();
386 }
387 }
388
389 if (parts.length > 0 && parts[0]) {
390 defaults.channel = '#' + parts[0];
391 parts.shift();
392 }
393 }
394
395 // If any settings have been given by the server.. override any auto detected settings
396 /**
397 * Get any server restrictions as set in the server config
398 * These settings can not be changed in the server selection dialog
399 */
400 if (_kiwi.app.server_settings && _kiwi.app.server_settings.connection) {
401 if (_kiwi.app.server_settings.connection.server) {
402 defaults.server = _kiwi.app.server_settings.connection.server;
403 }
404
405 if (_kiwi.app.server_settings.connection.port) {
406 defaults.port = _kiwi.app.server_settings.connection.port;
407 }
408
409 if (_kiwi.app.server_settings.connection.ssl) {
410 defaults.ssl = _kiwi.app.server_settings.connection.ssl;
411 }
412
413 if (_kiwi.app.server_settings.connection.channel) {
414 defaults.channel = _kiwi.app.server_settings.connection.channel;
415 }
416
417 if (_kiwi.app.server_settings.connection.channel_key) {
418 defaults.channel_key = _kiwi.app.server_settings.connection.channel_key;
419 }
420
421 if (_kiwi.app.server_settings.connection.nick) {
422 defaults.nick = _kiwi.app.server_settings.connection.nick;
423 }
424 }
425
426 // Set any random numbers if needed
427 defaults.nick = defaults.nick.replace('?', Math.floor(Math.random() * 100000).toString());
428
429 if (getQueryVariable('encoding'))
430 defaults.encoding = getQueryVariable('encoding');
431
432 return defaults;
433 },
434 };
435
436
437
438 // If within a closure, expose the kiwi globals
439 if (typeof global !== 'undefined') {
440 global.kiwi = _kiwi.global;
441 } else {
442 // Not within a closure so set a var in the current scope
443 var kiwi = _kiwi.global;
444 }