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