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