1 var url
= require('url'),
3 crypto
= require('crypto'),
4 node_static
= require('node-static'),
6 config
= require('./configuration.js');
9 // Cache for settings.json
10 var cached_settings
= {
21 // Clear the settings cache when the settings change
22 config
.on('loaded', function () {
23 cached_settings
.debug
.settings
= cached_settings
.production
.settings
= '';
24 cached_settings
.debug
.hash
= cached_settings
.production
.hash
= '';
30 var HttpHandler = function (config
) {
31 var public_html
= config
.public_html
|| 'client/';
32 this.file_server
= new node_static
.Server(public_html
);
35 module
.exports
.HttpHandler
= HttpHandler
;
39 HttpHandler
.prototype.serve = function (request
, response
) {
40 // The incoming requests base path (ie. /kiwiclient)
41 var base_path
= global
.config
.http_base_path
|| '/kiwi',
44 // Trim of any trailing slashes
45 if (base_path
.substr(base_path
.length
- 1) === '/') {
46 base_path
= base_path
.substr(0, base_path
.length
- 1);
49 // Build the regex to match the base_path
50 base_path_regex
= base_path
.replace(/[-/\\^$*+?.()|[\]{}]/g
, '\\$&');
52 // Any asset request to head into the asset dir
53 request
.url
= request
.url
.replace(base_path
+ '/assets/', '/assets/');
55 // Any requests for /client to load the index file
56 if (request
.url
.match(new RegExp('^' + base_path_regex
+ '([/$]|$)', 'i'))) {
60 // If the 'magic' translation is requested, figure out the best language to use from
61 // the Accept-Language HTTP header. If nothing is suitible, fallback to our en-gb default translation
62 if (request
.url
.substr(0, 16) === '/assets/locales/') {
63 if (request
.url
=== '/assets/locales/magic.json') {
64 return serveMagicLocale
.call(this, request
, response
);
66 response
.setHeader('Content-Language', request
.url
.substr(16, request
.url
.indexOf('.') - 16));
68 } else if (request
.url
.substr(0, 21) === '/assets/settings.json') {
69 return serveSettings
.call(this, request
, response
);
72 this.file_server
.serve(request
, response
, function (err
) {
74 response
.writeHead(err
.status
, err
.headers
);
82 * Handle the /assets/locales/magic.json request
83 * Find the closest translation we have for the language
86 var serveMagicLocale = function (request
, response
) {
88 default_locale_id
= 'en-gb';
90 if (request
.headers
['accept-language']) {
91 fs
.readdir('client/assets/locales', function (err
, files
) {
94 langs
= request
.headers
['accept-language'].split(','), // Example: en-gb,en;q=0.5
95 found_locale
= default_locale_id
;
97 // Get a list of the available translations we have
98 files
.forEach(function (file
) {
99 if (file
.substr(-5) === '.json') {
100 available
.push(file
.slice(0, -5));
104 // Sanitise the browsers accepted languages and the qualities
105 for (i
= 0; i
< langs
.length
; i
++) {
106 langs
[i
]= langs
[i
].split(';q=');
107 langs
[i
][0] = langs
[i
][0].toLowerCase();
108 langs
[i
][1] = (typeof langs
[i
][1] === 'string') ? parseFloat(langs
[i
][1]) : 1.0;
111 // Sort the accepted languages by quality
112 langs
.sort(function (a
, b
) {
116 // Serve the first language we have a translation for
117 for (i
= 0; i
< langs
.length
; i
++) {
118 if (langs
[i
][0] === '*') {
120 } else if (_
.contains(available
, langs
[i
][0])) {
121 found_locale
= langs
[i
][0];
126 return serveLocale
.call(that
, request
, response
, found_locale
);
130 // No accept-language specified in the request so send the default
131 return serveLocale
.call(this, request
, response
, default_locale_id
);
138 * Send a locale to the browser
140 var serveLocale = function (request
, response
, locale_id
) {
141 this.file_server
.serveFile('/assets/locales/' + locale_id
+ '.json', 200, {
142 Vary
: 'Accept-Language',
143 'Content-Language': locale_id
144 }, request
, response
);
149 * Handle the settings.json request
151 function serveSettings(request
, response
) {
156 // Check the referrer for a debug option
157 if (request
.headers
['referer']) {
158 referrer_url
= url
.parse(request
.headers
['referer'], true);
159 if (referrer_url
.query
&& referrer_url
.query
.debug
) {
164 settings
= cached_settings
[debug
? 'debug' : 'production'];
166 // Generate the settings if we don't have them cached as yet
167 if (settings
.settings
=== '') {
168 generateSettings(request
, debug
, function (err
, settings
) {
170 response
.statusCode
= 500;
174 sendSettings
.call(this, request
, response
, settings
);
179 sendSettings
.call(this, request
, response
, settings
);
185 * Send the the settings to the browser
187 function sendSettings(request
, response
, settings
) {
188 if (request
.headers
['if-none-match'] && request
.headers
['if-none-match'] === settings
.hash
) {
189 response
.writeHead(304, 'Not Modified');
190 return response
.end();
193 response
.writeHead(200, {
194 'ETag': settings
.hash
,
195 'Content-Type': 'application/json'
197 response
.end(settings
.settings
);
202 * Generate a settings object for the client.
203 * Settings include available translations, default client config, etc
205 function generateSettings(request
, debug
, callback
) {
214 ['libs/backbone.min.js', 'libs/jed.js']
219 vars
.scripts
= vars
.scripts
.concat([
223 'libs/engine.io.tools.js'
226 'src/models/application.js',
227 'src/models/gateway.js'
230 'src/models/newconnection.js',
231 'src/models/panellist.js',
232 'src/models/networkpanellist.js',
233 'src/models/panel.js',
234 'src/models/member.js',
235 'src/models/memberlist.js',
236 'src/models/network.js'
240 'src/models/channel.js',
241 'src/models/applet.js'
245 'src/models/query.js',
246 'src/models/server.js', // Depends on models/channel.js
247 'src/applets/settings.js',
248 'src/applets/chanlist.js',
249 'src/applets/scripteditor.js'
253 'src/models/pluginmanager.js',
254 'src/models/datastore.js',
255 'src/helpers/utils.js'
258 // Some views extend these, so make sure they're loaded beforehand
264 'src/views/channel.js',
265 'src/views/applet.js',
266 'src/views/application.js',
267 'src/views/apptoolbar.js',
268 'src/views/controlbox.js',
269 'src/views/favicon.js',
270 'src/views/mediamessage.js',
271 'src/views/member.js',
272 'src/views/memberlist.js',
273 'src/views/menubox.js',
274 'src/views/networktabs.js',
275 'src/views/nickchangebox.js',
276 'src/views/resizehandler.js',
277 'src/views/serverselect.js',
278 'src/views/statusmessage.js',
280 'src/views/topicbar.js',
281 'src/views/userbox.js'
285 vars
.scripts
.push(['kiwi.min.js', 'libs/engine.io.bundle.min.js']);
288 // Any restricted server mode set?
289 if (config
.get().restrict_server
) {
290 vars
.server_settings
= {
292 server
: config
.get().restrict_server
,
293 port
: config
.get().restrict_server_port
|| 6667,
294 ssl
: config
.get().restrict_server_ssl
,
295 channel
: config
.get().restrict_server_channel
,
296 nick
: config
.get().restrict_server_nick
,
302 // Any client default settings?
303 if (config
.get().client
) {
304 vars
.server_settings
.client
= config
.get().client
;
307 // Any client plugins?
308 if (config
.get().client_plugins
&& config
.get().client_plugins
.length
> 0) {
309 vars
.client_plugins
= config
.get().client_plugins
;
312 // Read theme information
313 readThemeInfo(config
.get().client_themes
, function (err
, themes
) {
315 return callback(err
);
318 vars
.themes
= themes
;
320 // Get a list of available translations
321 fs
.readFile(__dirname
+ '/../client/assets/src/translations/translations.json', function (err
, translations
) {
323 return callback(err
);
326 translations
= JSON
.parse(translations
);
327 fs
.readdir(__dirname
+ '/../client/assets/src/translations/', function (err
, pofiles
) {
330 return callback(err
);
333 pofiles
.forEach(function (file
) {
334 var locale
= file
.slice(0, -3);
335 if ((file
.slice(-3) === '.po') && (locale
!== 'template')) {
336 vars
.translations
.push({tag
: locale
, language
: translations
[locale
]});
340 settings
= cached_settings
[debug
?'debug':'production'];
341 settings
.settings
= JSON
.stringify(vars
);
342 settings
.hash
= crypto
.createHash('md5').update(settings
.settings
).digest('hex');
344 return callback(null, settings
);
350 function readThemeInfo(themes
, prev
, callback
) {
352 var theme
= themes
[0];
354 if (typeof prev
=== 'function') {
359 fs
.readFile(__dirname
+ '/../client/assets/themes/' + theme
.toLowerCase() + '/theme.json', function (err
, theme_json
) {
361 return callback(err
);
365 theme_json
= JSON
.parse(theme_json
);
370 prev
.push(theme_json
);
372 if (themes
.length
> 1) {
373 return readThemeInfo(themes
.slice(1), prev
, callback
);
376 callback(null, prev
);