diff 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
line wrap: on
line diff
new file mode 100755
--- /dev/null
+++ b/server.js
@@ -0,0 +1,382 @@
+#!/usr/bin/env node
+
+'use strict';
+
+var config = require('./configuration');
+
+var util = require('util');
+var http = require('http');
+var fs = require('fs');
+var xmpp = require('node-xmpp');
+var Element = xmpp.Element;
+var JID = require('./jid').JID;
+var ns = require('./ns').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: ' + s + '');
+		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: ' + stanza + '');
+	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')) {
+		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) {
+	var article = new Element('article', {'e:id': id, 'e:date': '2011-06-02T10:59:39Z'});
+
+	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 = '/avatar/' + uri.substring(5);
+		} catch (e) {
+			footer.c('cite').t(name);
+		}
+
+		try {
+			var email = author.getChild('email', ns.atom).getText();
+			footer.t(', (').c('a', {href: email}).t('email').up().t(')');
+		} catch (e) { }
+		footer.up();
+	}
+
+	var published = atom.getChild('published', ns.atom).getText();
+	if (published) {
+		if (author)
+			footer.t(', ');
+		footer.c('time', {datetime: published}).t((new Date).set8601(published).getRelative()).up();
+	}
+
+	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/* + ';comments=' + params.jid + '/' + params.node*/}).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.full, 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);
+		body = article + body;
+	}
+	body = '<div e:jid="' + jid + '">' + 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('		</header>\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('\n');
+	res.write('		<hr/>\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');
+	res.write('		<section hidden="">\n');
+	res.write('			<h2>logs</h2>\n');
+	res.write('			<div id="log"></div>\n');
+	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) {
+	var re = new RegExp('^' + config.webRoot);
+	req.url = req.url.replace(re, '');
+	var ext = req.url.substring(req.url.lastIndexOf('.')+1);
+	var url = require('url').parse(req.url);
+
+	if (url.pathname === '')
+		return servePage(url, res);
+
+	fs.readFile(req.url, function(err, data) {
+		if (err)
+			return servePage(url, res);
+
+		res.writeHead(200, {'Content-Type': config.types[ext] || 'application/octet-stream'});
+
+		res.end(data);
+	});
+}).listen(config.webPort, config.webHost);