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