comparison server.js @ 0:f62b5c395a48

Initial commit.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 04 Jun 2011 05:02:47 +0200
parents
children 82905edac9d8
comparison
equal deleted inserted replaced
-1:000000000000 0:f62b5c395a48
1 #!/usr/bin/env node
2
3 'use strict';
4
5 var config = require('./configuration');
6
7 var util = require('util');
8 var http = require('http');
9 var fs = require('fs');
10 var xmpp = require('node-xmpp');
11 var Element = xmpp.Element;
12 var JID = require('./jid').JID;
13 var ns = require('./ns').ns;
14 var forms = require('./forms');
15 require('./date');
16
17 var received = {};
18
19 var cl = new xmpp.Client(config);
20
21 (function() {
22 var send = cl.send;
23 cl.send = function(s) {
24 util.log('Sent: ' + s + '');
25 send.call(cl, s);
26 }
27 })();
28
29 cl.on('online', function() {
30 util.log('Connected.');
31 cl.send(new Element('presence'));
32 });
33
34 var getUniqId = (function() {
35 var id = 0;
36 return function() {
37 return ++id;
38 }
39 })();
40
41 var getNodeInfo = function(jid) {
42 var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()})
43 .c('query', {xmlns: ns.info, node: jid.resource})
44 .up();
45
46 cl.send(iq);
47 };
48
49 var getNodeItems = function(jid) {
50 var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()})
51 .c('pubsub', {xmlns: ns.ps})
52 .c('items', {node: jid.resource})
53 .up()
54 .up();
55
56 cl.send(iq);
57 };
58
59 var makeError = function(response) {
60 response.attrs.type = 'error';
61
62 response.c('error', {type: 'cancel'})
63 .c('feature-not-implemented', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'});
64
65 return cl.send(response);
66 };
67
68 var handleInfo = function(query, from) {
69 var x = query.getChild('x', ns.data);
70 if (!x)
71 return;
72
73 var jid = new JID;
74 jid.bare = from;
75 jid.resource = query.attrs.node;
76
77 var form = forms.parse(x, true).fields;
78 received[jid].form = form;
79 generatePage(jid);
80 };
81
82 var handlePubSub = function(pubsub, from) {
83 var items = pubsub.getChild('items');
84 if (!items)
85 return;
86
87 var itemsNS = items.getNS();
88 if (itemsNS !== ns.ps && itemsNS !== ns.pse)
89 return;
90
91 var jid = new JID;
92 jid.bare = from;
93 jid.resource = items.attrs.node;
94
95 items = items.getChildren('item', itemsNS);
96 if (!items)
97 return;
98
99 if (!received[jid])
100 received[jid] = {data: null, form: null, wait: {}};
101
102 var data = received[jid].data || {};
103
104 for (var i in items) {
105 var item = items[i];
106 var id = item.attrs.id;
107
108 var payload = item.children[0];
109 delete payload.parent;
110 data[id] = payload;
111 }
112
113 received[jid].data = data;
114 generatePage(jid);
115 };
116
117 cl.on('stanza', function(stanza) {
118 util.log('Recv: ' + stanza + '');
119 if (stanza.is('iq', ns.j)) {
120 var type = stanza.attrs.type;
121 if (type === 'error')
122 return;
123
124 var result = new Element('iq', {to: stanza.attrs.from, from: stanza.attrs.to, type: 'result'});
125 if (type === 'get' || type === 'set')
126 return makeError(result);
127
128 var payload = stanza.getChild('query', ns.info);
129 if (payload)
130 return handleInfo(payload, stanza.attrs.from);
131
132 payload = stanza.getChild('pubsub', ns.ps);
133 if (payload)
134 return handlePubSub(payload, stanza.attrs.from);
135
136 } else if (stanza.is('message')) {
137 var type = stanza.attrs.type;
138 if (type === 'error')
139 return;
140
141 if (type !== 'headline')
142 return;
143
144 var payload = stanza.getChild('event', ns.pse);
145 if (payload)
146 return handlePubSub(payload, stanza.attrs.from);
147 }
148 });
149
150 var parseAtom = function(atom, id) {
151 var article = new Element('article', {'e:id': id, 'e:date': '2011-06-02T10:59:39Z'});
152
153 var avatar = article.c('aside').c('img')
154 article.up();
155
156 try {
157 var title = atom.getChild('title', ns.atom).getText();
158 if (title)
159 article.c('h2').t(title).up();
160 } catch (e) { }
161
162 var footer = article.c('footer');
163 article.up();
164
165 var author = atom.getChild('author', ns.atom);
166 if (author) {
167 footer.t('By ');
168 var name = author.getChild('name', ns.atom).getText();
169
170 try{
171 var uri = author.getChild('uri', ns.atom).getText();
172 footer.c('cite').c('a', {href: uri}).t(name).up();
173 avatar.attrs.src = '/avatar/' + uri.substring(5);
174 } catch (e) {
175 footer.c('cite').t(name);
176 }
177
178 try {
179 var email = author.getChild('email', ns.atom).getText();
180 footer.t(', (').c('a', {href: email}).t('email').up().t(')');
181 } catch (e) { }
182 footer.up();
183 }
184
185 var published = atom.getChild('published', ns.atom).getText();
186 if (published) {
187 if (author)
188 footer.t(', ');
189 footer.c('time', {datetime: published}).t((new Date).set8601(published).getRelative()).up();
190 }
191
192 try {
193 var summary = atom.getChild('summary', ns.atom).getText();
194 if (summary)
195 article.c('p').t(summary).up();
196 } catch (e) { }
197
198 try {
199 var links = atom.getChildren('link');
200 for (var i in links) {
201 var link = links[i];
202
203 if (link.attrs.rel !== 'replies')
204 continue;
205
206 if (link.attrs.title !== 'comments')
207 continue;
208
209 var href = new JID;
210 href.uri = link.attrs.href;
211
212 article.c('a', {href: '?jid=' + href.bare + ';node=' + href.query.node/* + ';comments=' + params.jid + '/' + params.node*/}).t('Comments !');
213 break;
214 }
215 } catch (e) { }
216
217 return article;
218 };
219
220 var generatePage = function(jid) {
221 var r = received[jid.full];
222 var s = r.wait;
223 var form = r.form;
224 var data = r.data;
225
226 if (!form || !data)
227 return;
228
229 for (var i in s) {
230 r = s[i];
231 delete s[i];
232 makePage(r.res, jid.full, form, data, r.noscript);
233 }
234 };
235
236 var makePage = function(res, jid, form, data, noscript) {
237 var body = '</div>';
238
239 for (var id in data) {
240 var item = data[id];
241 var article = parseAtom(item, id);
242 body = article + body;
243 }
244 body = '<div e:jid="' + jid + '">' + body;
245
246 home(res, form['pubsub#title'], form['pubsub#description'], body, 'Node created the <time>' + form['pubsub#creation_date'] + '</time> by <cite>' + form['pubsub#creator'] + '</cite> with <a href="http://linkmauve.fr/dev/eldonilo/blog">Eldonilo blog</a>.', noscript);
247 };
248
249 var servePage = function(url, res) {
250 util.log(url.href);
251
252 var query = require('querystring').parse(url.query, ';');
253
254 var page = new JID(config.defaultNode);
255 if (query.jid)
256 page.bare = query.jid;
257 if (query.node)
258 page.resource = query.node;
259
260 if (query.type === 'atom' && config.atomRoot) {
261 res.writeHead(301, {'Location': config.atomRoot + page.resource});
262 return res.end();
263 }
264
265 res.writeHead(200, {'Content-Type': 'application/xhtml+xml'});
266
267 var noscript = false;
268 if (query.no === 'server')
269 return fs.readFile('index.xhtml', function(err, data) {
270 res.end(data);
271 });
272 else if (query.no === 'client')
273 noscript = true;
274
275 var jid = page.full;
276 if (!received[jid]) {
277 getNodeInfo(page);
278 getNodeItems(page);
279 received[jid] = {data: null, form: null, wait: {}};
280 received[jid].wait[getUniqId()] = {res: res, noscript: noscript};
281 } else {
282 received[jid].wait[getUniqId()] = {res: res, noscript: noscript};
283 generatePage(page);
284 }
285 }
286
287 var home = function(res, title, desc, body, footer, noscript) {
288 res.writeHead(200, {'Content-Type': 'application/xhtml+xml'});
289 res.write('<?xml version="1.0" encoding="utf-8"?>\n');
290 res.write('<?xml-stylesheet type="text/css" href="theme.css" media="screen"?>\n');
291 res.write('<!DOCTYPE html>\n');
292 res.write('<html xmlns="http://www.w3.org/1999/xhtml">\n');
293 res.write(' <head>\n');
294 res.write(' <title>' + (title? title: 'Eldonilo blog') + '</title>\n');
295
296 if (!noscript) {
297 res.write('\n');
298 res.write(' <script type="application/ecmascript" src="configuration.js" defer=""/>\n');
299 res.write(' <script type="application/ecmascript" src="xml2json.js" defer=""/>\n');
300 res.write(' <script type="application/ecmascript" src="strophe.js" defer=""/>\n');
301 res.write(' <script type="application/ecmascript" src="strophe.pubsub.js" defer=""/>\n');
302 res.write(' <script type="application/ecmascript" src="date.js" defer=""/>\n');
303 res.write(' <script type="application/ecmascript" src="ns.js" defer=""/>\n');
304 res.write(' <script type="application/ecmascript" src="jid.js" defer=""/>\n');
305 res.write(' <script type="application/ecmascript" src="atom.js" defer=""/>\n');
306 res.write(' <script type="application/ecmascript" src="nothing.js" defer=""/>\n');
307 }
308
309 if (config.atomRoot) {
310 res.write('\n');
311 res.write(' <link rel="alternate" type="application/atom+xml" title="Atom feed" href="?type=atom"/>\n');
312 }
313
314 res.write(' </head>\n');
315 res.write('\n');
316 res.write(' <body>\n');
317 res.write(' <header>\n');
318 res.write(' <h1>' + (title? title: 'Eldonilo blog') + '</h1>\n');
319 res.write(' <p>' + (desc? desc: 'Displaying your nodes.') + '</p>\n');
320 res.write(' </header>\n');
321 res.write('\n');
322 res.write(' <nav>\n');
323 res.write(' <ul>\n');
324 res.write(' <li><a href="?no=client">Without client-side</a></li>\n');
325 res.write(' <li><a href="?no=server">Without server-side</a></li>\n');
326 res.write(' <li><a href="?">Hybrid mode</a></li>\n');
327 res.write(' </ul>\n');
328 res.write(' </nav>\n');
329 res.write('\n');
330 res.write(' <hr/>\n');
331 res.write('\n');
332
333 if (!body)
334 res.write(' <section id="messages"/>\n');
335 else {
336 res.write(' <section id="messages" xmlns:e="' + ns.e + '">\n');
337 res.write(body);
338 res.write(' </section>\n');
339 }
340
341 res.write('\n');
342 res.write(' <section hidden="">\n');
343 res.write(' <h2>logs</h2>\n');
344 res.write(' <div id="log"></div>\n');
345 res.write(' </section>\n');
346 res.write('\n');
347
348 if (!footer)
349 res.write(' <footer/>\n');
350 else {
351 res.write(' <footer>\n');
352 res.write(footer);
353 res.write(' </footer>\n');
354 }
355
356 if (!noscript) {
357 res.write('\n');
358 res.write(' <script type="application/ecmascript" src="blog.js"/>\n');
359 }
360
361 res.write(' </body>\n');
362 res.end('</html>\n');
363 };
364
365 http.createServer(function(req, res) {
366 var re = new RegExp('^' + config.webRoot);
367 req.url = req.url.replace(re, '');
368 var ext = req.url.substring(req.url.lastIndexOf('.')+1);
369 var url = require('url').parse(req.url);
370
371 if (url.pathname === '')
372 return servePage(url, res);
373
374 fs.readFile(req.url, function(err, data) {
375 if (err)
376 return servePage(url, res);
377
378 res.writeHead(200, {'Content-Type': config.types[ext] || 'application/octet-stream'});
379
380 res.end(data);
381 });
382 }).listen(config.webPort, config.webHost);