Mercurial > eldonilo > blog
view server.js @ 13:161d4ea1c3f8
Migration of the client-side to XMPP.js instead of Strophe.js.
Drop BOSH support and add WebSockets support.
The server-side is untested, may be broken.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Thu, 03 Nov 2011 14:23:10 -0700 |
parents | 360186772aa3 |
children | 03be0717d3f8 |
line wrap: on
line source
#!/usr/bin/env node 'use strict'; var config = require('./configuration'); var util = require('util'); var http = require('http'); var parseurl = require('url').parse; var fs = require('fs'); var xmpp = require('node-xmpp'); var Element = xmpp.Element; var JID = require('./jid'); var ns = require('./ns'); var forms = require('./forms'); require('./date'); var received = {}; var cl = new xmpp.Client(config); (function() { var send = cl.send; cl.send = function(s) { util.log('Sent: [1;32m' + s + '[0m'); send.call(cl, s); } })(); cl.on('online', function() { util.log('Connected.'); cl.send(new Element('presence')); }); var getUniqId = (function() { var id = 0; return function() { return ++id; } })(); var getNodeInfo = function(jid) { var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()}) .c('query', {xmlns: ns.info, node: jid.resource}) .up(); cl.send(iq); }; var getNodeItems = function(jid) { var iq = new Element('iq', {to: jid.bare, type: 'get', id: getUniqId()}) .c('pubsub', {xmlns: ns.ps}) .c('items', {node: jid.resource}) .up() .up(); cl.send(iq); }; var makeError = function(response) { response.attrs.type = 'error'; response.c('error', {type: 'cancel'}) .c('feature-not-implemented', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}); return cl.send(response); }; var handleInfo = function(query, from) { var x = query.getChild('x', ns.data); if (!x) return; var jid = new JID; jid.bare = from; jid.resource = query.attrs.node; var form = forms.parse(x, true).fields; received[jid].form = form; generatePage(jid); }; var handlePubSub = function(pubsub, from) { var items = pubsub.getChild('items'); if (!items) return; var itemsNS = items.getNS(); if (itemsNS !== ns.ps && itemsNS !== ns.pse) return; var jid = new JID; jid.bare = from; jid.resource = items.attrs.node; items = items.getChildren('item', itemsNS); if (!items) return; if (!received[jid]) received[jid] = {data: null, form: null, wait: {}}; var data = received[jid].data || {}; for (var i in items) { var item = items[i]; var id = item.attrs.id; var payload = item.children[0]; delete payload.parent; data[id] = payload; } received[jid].data = data; generatePage(jid); }; cl.on('stanza', function(stanza) { util.log('Recv: [1;34m' + stanza + '[0m'); if (stanza.is('iq', ns.j)) { var type = stanza.attrs.type; if (type === 'error') return; var result = new Element('iq', {to: stanza.attrs.from, from: stanza.attrs.to, type: 'result'}); if (type === 'get' || type === 'set') return makeError(result); var payload = stanza.getChild('query', ns.info); if (payload) return handleInfo(payload, stanza.attrs.from); payload = stanza.getChild('pubsub', ns.ps); if (payload) return handlePubSub(payload, stanza.attrs.from); } else if (stanza.is('message', ns.j)) { var type = stanza.attrs.type; if (type === 'error') return; if (type !== 'headline') return; var payload = stanza.getChild('event', ns.pse); if (payload) return handlePubSub(payload, stanza.attrs.from); } }); var parseAtom = function(atom, id, jid) { var article = new Element('article', {'e:id': id}); var avatar = article.c('aside').c('img') article.up(); try { var title = atom.getChild('title', ns.atom).getText(); if (title) article.c('h2').t(title).up(); } catch (e) { } var footer = article.c('footer'); article.up(); var author = atom.getChild('author', ns.atom); if (author) { footer.t('By '); var name = author.getChild('name', ns.atom).getText(); try{ var uri = author.getChild('uri', ns.atom).getText(); footer.c('cite').c('a', {href: uri}).t(name).up(); avatar.attrs.src = config.avatarRoot + uri.substring(5); avatar.attrs.alt = uri.substring(5); } catch (e) { footer.c('cite').t(name); } try { var email = author.getChild('email', ns.atom).getText(); footer.t(' (').c('a', {href: 'mailto:' + email}).t('email').up().t(')'); } catch (e) { } footer.up(); } var published = (function() { try { var elem = atom.getChild('published', ns.atom); var iso8601 = elem.getText(); var d = (new Date).set8601(iso8601); var relative = d.getRelative(); return {iso8601: iso8601, relative: relative}; } catch (e) { var d = new Date; return {iso8601: d.to8601(), relative: d.getRelative()}; } })(); if (author) footer.t(', '); footer.c('time', {datetime: published.iso8601}).t(published.relative).up(); article.attrs['e:date'] = published.iso8601; try { var summary = atom.getChild('summary', ns.atom).getText(); if (summary) article.c('p').t(summary).up(); } catch (e) { } try { var links = atom.getChildren('link'); for (var i in links) { var link = links[i]; if (link.attrs.rel !== 'replies') continue; if (link.attrs.title !== 'comments') continue; var href = new JID; href.uri = link.attrs.href; article.c('a', {href: '?jid=' + href.bare + ';node=' + href.query.node + ';origjid=' + jid.bare + ';orignode=' + jid.resource + ';origitem=' + id}).t('Comments !'); break; } } catch (e) { } return article; }; var generatePage = function(jid) { var r = received[jid.full]; var s = r.wait; var form = r.form; var data = r.data; if (!form || !data) return; for (var i in s) { r = s[i]; delete s[i]; makePage(r.res, jid, form, data, r.noscript); } }; var makePage = function(res, jid, form, data, noscript) { var body = '</div>'; for (var id in data) { var item = data[id]; var article = parseAtom(item, id, jid); body = article + body; } body = '<div e:jid="' + jid.full + '">' + body; 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); }; var servePage = function(url, res) { util.log(url.href); var query = require('querystring').parse(url.query, ';'); var page = new JID(config.defaultNode); if (query.jid) page.bare = query.jid; if (query.node) page.resource = query.node; if (query.type === 'atom' && config.atomRoot) { res.writeHead(301, {'Location': config.atomRoot + page.resource}); return res.end(); } res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); var noscript = false; if (query.no === 'server') return fs.readFile('index.xhtml', function(err, data) { res.end(data); }); else if (query.no === 'client') noscript = true; var jid = page.full; if (!received[jid]) { getNodeInfo(page); getNodeItems(page); received[jid] = {data: null, form: null, wait: {}}; received[jid].wait[getUniqId()] = {res: res, noscript: noscript}; } else { received[jid].wait[getUniqId()] = {res: res, noscript: noscript}; generatePage(page); } } var home = function(res, title, desc, body, footer, noscript) { res.writeHead(200, {'Content-Type': 'application/xhtml+xml'}); res.write('<?xml version="1.0" encoding="utf-8"?>\n'); res.write('<?xml-stylesheet type="text/css" href="theme.css" media="screen"?>\n'); res.write('<!DOCTYPE html>\n'); res.write('<html xmlns="http://www.w3.org/1999/xhtml">\n'); res.write(' <head>\n'); res.write(' <title>' + (title? title: 'Eldonilo blog') + '</title>\n'); if (!noscript) { res.write('\n'); res.write(' <script type="application/ecmascript" src="configuration.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="xml2json.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="strophe.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="strophe.pubsub.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="date.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="ns.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="jid.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="atom.js" defer=""/>\n'); res.write(' <script type="application/ecmascript" src="nothing.js" defer=""/>\n'); } if (config.atomRoot) { res.write('\n'); res.write(' <link rel="alternate" type="application/atom+xml" title="Atom feed" href="?type=atom"/>\n'); } res.write(' </head>\n'); res.write('\n'); res.write(' <body>\n'); res.write(' <header>\n'); res.write(' <h1>' + (title? title: 'Eldonilo blog') + '</h1>\n'); res.write(' <p>' + (desc? desc: 'Displaying your nodes.') + '</p>\n'); res.write('\n'); res.write(' <nav>\n'); res.write(' <ul>\n'); res.write(' <li><a href="?no=client">Without client-side</a></li>\n'); res.write(' <li><a href="?no=server">Without server-side</a></li>\n'); res.write(' <li><a href="?">Hybrid mode</a></li>\n'); res.write(' </ul>\n'); res.write(' </nav>\n'); res.write(' </header>\n'); res.write('\n'); if (!body) res.write(' <section id="messages"/>\n'); else { res.write(' <section id="messages" xmlns:e="' + ns.e + '">\n'); res.write(body); res.write(' </section>\n'); } res.write('\n'); if (!footer) res.write(' <footer/>\n'); else { res.write(' <footer>\n'); res.write(footer); res.write(' </footer>\n'); } if (!noscript) { res.write('\n'); res.write(' <script type="application/ecmascript" src="blog.js"/>\n'); } res.write(' </body>\n'); res.end('</html>\n'); }; http.createServer(function(req, res) { util.log('Connection from ' + (req.headers['x-forwarded-for'] || req.client.remoteAddress) + ' (' + req.headers['user-agent'] + ') to ' + req.method.toLocaleLowerCase() + ' “' + req.url + '” from ' + req.headers['referer'] + '.'); var re = new RegExp('^' + config.webRoot); var url = parseurl(req.url.replace(re, '')); var pathname = url.pathname || ''; var ext = pathname.substring(pathname.lastIndexOf('.')+1); if (pathname === '') return servePage(url, res); fs.readFile(pathname, function(err, data) { if (err || pathname === 'index.xhtml') return servePage(url, res); res.writeHead(200, {'Content-Type': config.types[ext] || 'application/octet-stream'}); res.end(data); }); }).listen(config.webPort, config.webHost);