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