Merge branch 'settings' of https://github.com/M2Ys4U/KiwiIRC into settings
[KiwiIRC.git] / server / httphandler.js
CommitLineData
bd299b17 1var url = require('url'),
0fa2ca42 2 fs = require('fs'),
cbcd1a23 3 crypto = require('crypto'),
0fa2ca42 4 node_static = require('node-static'),
cbcd1a23
JA
5 _ = require('lodash'),
6 config = require('./configuration.js');
a8bf3ea4 7
a8bf3ea4 8
a8bf3ea4 9
186531ed
D
10var HttpHandler = function (config) {
11 var public_html = config.public_html || 'client/';
12 this.file_server = new node_static.Server(public_html);
a8bf3ea4
JA
13};
14
186531ed
D
15module.exports.HttpHandler = HttpHandler;
16
17
18
19HttpHandler.prototype.serve = function (request, response) {
b65ad8f1 20 // The incoming requests base path (ie. /kiwiclient)
b737610b 21 var base_path = global.config.http_base_path || '/kiwi',
b65ad8f1
D
22 base_path_regex;
23
24 // Trim of any trailing slashes
25 if (base_path.substr(base_path.length - 1) === '/') {
26 base_path = base_path.substr(0, base_path.length - 1);
27 }
0fa2ca42 28
b65ad8f1
D
29 // Build the regex to match the base_path
30 base_path_regex = base_path.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
0ae87edb
D
31
32 // Any asset request to head into the asset dir
b65ad8f1 33 request.url = request.url.replace(base_path + '/assets/', '/assets/');
0ae87edb 34
b075a0d6 35 // Any requests for /client to load the index file
b65ad8f1 36 if (request.url.match(new RegExp('^' + base_path_regex + '([/$]|$)', 'i'))) {
b075a0d6
D
37 request.url = '/';
38 }
39
0fa2ca42 40 // If the 'magic' translation is requested, figure out the best language to use from
b722f3c6 41 // the Accept-Language HTTP header. If nothing is suitible, fallback to our en-gb default translation
28cde487
JA
42 if (request.url.substr(0, 16) === '/assets/locales/') {
43 if (request.url === '/assets/locales/magic.json') {
44 return serveMagicLocale.call(this, request, response);
45 } else {
46 response.setHeader('Content-Language', request.url.substr(16, request.url.indexOf('.') - 16));
47 }
cbcd1a23
JA
48 } else if (request.url.substr(0, 21) === '/assets/settings.json') {
49 return serveSettings.call(this, request, response);
0fa2ca42 50 }
0ae87edb 51
186531ed 52 this.file_server.serve(request, response, function (err) {
a8bf3ea4 53 if (err) {
bd299b17
JA
54 response.writeHead(err.status, err.headers);
55 response.end();
a8bf3ea4
JA
56 }
57 });
0fa2ca42
JA
58};
59
60var serveMagicLocale = function (request, response) {
cbc8feae 61 var that = this;
8e1ab29d 62
0fa2ca42 63 if (request.headers['accept-language']) {
28cde487
JA
64 fs.readdir('client/assets/locales', function (err, files) {
65 var available = [],
66 i = 0,
cbc8feae 67 langs = request.headers['accept-language'].split(','); // Example: en-gb,en;q=0.5
28cde487 68
8e1ab29d 69 // Get a list of the available translations we have
0fa2ca42 70 files.forEach(function (file) {
28cde487
JA
71 if (file.substr(-5) === '.json') {
72 available.push(file.slice(0, -5));
0fa2ca42
JA
73 }
74 });
0fa2ca42 75
8e1ab29d 76 // Sanitise the browsers accepted languages and the qualities
28cde487
JA
77 for (i = 0; i < langs.length; i++) {
78 langs[i]= langs[i].split(';q=');
8e1ab29d 79 langs[i][0] = langs[i][0].toLowerCase();
28cde487 80 langs[i][1] = (typeof langs[i][1] === 'string') ? parseFloat(langs[i][1]) : 1.0;
0fa2ca42 81 }
8e1ab29d
D
82
83 // Sort the accepted languages by quality
28cde487
JA
84 langs.sort(function (a, b) {
85 return b[1] - a[1];
86 });
8e1ab29d
D
87
88 // Serve the first language we have a translation for
28cde487
JA
89 for (i = 0; i < langs.length; i++) {
90 if (langs[i][0] === '*') {
91 break;
92 } else if (_.contains(available, langs[i][0])) {
cbc8feae 93 return that.file_server.serveFile('/assets/locales/' + langs[i][0] + '.json', 200, {Vary: 'Accept-Language', 'Content-Language': langs[i][0]}, request, response);
28cde487
JA
94 }
95 }
8e1ab29d
D
96
97 serveFallbackLocale.call(that, request, response);
28cde487 98 });
d2831ddd 99 } else {
8e1ab29d 100 serveFallbackLocale.call(that, request, response);
0fa2ca42 101 }
d2831ddd 102};
cbc8feae 103
d2831ddd 104var serveFallbackLocale = function (request, response) {
b722f3c6 105 //en-gb is our default language, so we serve this as the last possible answer for everything
d2831ddd 106 this.file_server.serveFile('/assets/locales/en-gb.json', 200, {Vary: 'Accept-Language', 'Content-Language': 'en-gb'}, request, response);
0fa2ca42 107};
cbcd1a23 108
cbe80532 109var cached_settings = {
498afd32
JA
110 debug: {
111 hash: '',
112 settings: ''
113 },
114 production: {
115 hash: '',
116 settings: ''
117 }
cbe80532
JA
118};
119
120config.on('loaded', function () {
498afd32
JA
121 cached_settings.debug.settings = cached_settings.production.settings = '';
122 cached_settings.debug.hash = cached_settings.production.hash = '';
cbe80532
JA
123});
124
498afd32 125function generateSettings(request, debug, callback) {
cbcd1a23
JA
126 var vars = {
127 server_settings: {},
128 client_plugins: [],
129 translations: [],
130 scripts: [
131 [
132 'libs/lodash.min.js'
133 ],
134 'libs/backbone.min.js',
135 'libs/jed.js'
136 ]
498afd32 137 };
cbcd1a23 138
498afd32 139 if (debug) {
cbcd1a23
JA
140 vars.scripts = vars.scripts.concat([
141 'src/app.js',
142 [
143 'src/models/application.js',
144 'src/models/gateway.js'
145 ],
146 [
147 'src/models/newconnection.js',
148 'src/models/panellist.js',
149 'src/models/networkpanellist.js',
150 'src/models/panel.js',
151 'src/models/member.js',
152 'src/models/memberlist.js',
153 'src/models/network.js'
154 ],
155
156 [
157 'src/models/query.js',
158 'src/models/channel.js',
159 'src/models/server.js',
160 'src/models/applet.js'
161 ],
162
163 [
164 'src/applets/settings.js',
165 'src/applets/chanlist.js',
166 'src/applets/scripteditor.js'
167 ],
168
169 [
170 'src/models/pluginmanager.js',
171 'src/models/datastore.js',
172 'src/helpers/utils.js'
173 ],
174
175 // Some views extend these, so make sure they're loaded beforehand
176 [
177 'src/views/panel.js'
178 ],
179
180 [
181 'src/views/channel.js',
182 'src/views/applet.js',
183 'src/views/application.js',
184 'src/views/apptoolbar.js',
185 'src/views/controlbox.js',
186 'src/views/favicon.js',
187 'src/views/mediamessage.js',
188 'src/views/member.js',
189 'src/views/memberlist.js',
190 'src/views/menubox.js',
191 'src/views/networktabs.js',
192 'src/views/nickchangebox.js',
193 'src/views/resizehandler.js',
194 'src/views/serverselect.js',
195 'src/views/statusmessage.js',
196 'src/views/tabs.js',
197 'src/views/topicbar.js',
198 'src/views/userbox.js'
199 ]
200 ]);
201 } else {
202 vars.scripts.push('kiwi.min.js');
203 }
204
205 // Any restricted server mode set?
206 if (config.get().restrict_server) {
207 vars.server_settings = {
208 connection: {
209 server: config.get().restrict_server,
210 port: config.get().restrict_server_port || 6667,
211 ssl: config.get().restrict_server_ssl,
212 channel: config.get().restrict_server_channel,
213 nick: config.get().restrict_server_nick,
214 allow_change: false
215 }
216 };
217 }
218
219 // Any client default settings?
220 if (config.get().client) {
221 vars.server_settings.client = config.get().client;
222 }
223
224 // Any client plugins?
225 if (config.get().client_plugins && config.get().client_plugins.length > 0) {
226 vars.client_plugins = config.get().client_plugins;
227 }
228
229 fs.readFile(__dirname + '/../client/assets/src/translations/translations.json', function (err, translations) {
230 if (err) {
cbe80532 231 return callback(err);
cbcd1a23
JA
232 }
233
234 var translation_files;
235 translations = JSON.parse(translations);
236 fs.readdir(__dirname + '/../client/assets/src/translations/', function (err, pofiles) {
498afd32 237 var hash, settings;
cbcd1a23 238 if (err) {
cbe80532 239 return callback(err);
cbcd1a23
JA
240 }
241
242 pofiles.forEach(function (file) {
243 var locale = file.slice(0, -3);
244 if ((file.slice(-3) === '.po') && (locale !== 'template')) {
245 vars.translations.push({tag: locale, language: translations[locale]});
246 }
247 });
248
498afd32
JA
249 settings = cached_settings[debug?'debug':'production'];
250 settings.settings = JSON.stringify(vars);
251 settings.hash = crypto.createHash('md5').update(cached_settings.settings).digest('hex');
cbcd1a23 252
498afd32 253 return callback(null, settings);
cbe80532
JA
254 });
255 });
256}
cbcd1a23 257
cbe80532 258function serveSettings(request, response) {
498afd32
JA
259 var referrer_url,
260 debug = false,
261 settings;
262
263 if (request.headers['referer']) {
264 referrer_url = url.parse(request.headers['referer'], true);
265 if (referrer_url.query && referrer_url.query.debug) {
266 debug = true;
267 }
268 }
269
270 settings = cached_settings[debug?'debug':'production'];
271 if (settings.settings === '') {
272 generateSettings(request, debug, function (err, settings) {
cbe80532
JA
273 if (err) {
274 response.statusCode = 500;
275 response.end();
276 } else {
277 sendSettings.call(this, request, response, settings);
278 }
cbcd1a23 279 });
cbe80532 280 } else {
498afd32 281 sendSettings.call(this, request, response, settings);
cbe80532
JA
282 }
283}
284
285function sendSettings(request, response, settings) {
286 if (request.headers['if-none-match'] && request.headers['if-none-match'] === settings.hash) {
287 response.writeHead(304, 'Not Modified');
288 return response.end();
289 }
290
291 response.writeHead(200, {
292 'ETag': settings.hash,
293 'Content-Type': 'application/json'
cbcd1a23 294 });
cbe80532 295 response.end(settings.settings);
cbcd1a23 296}