diff psgxs.js @ 23:5fc4ee90c1bc

A lot of refactorization. First attempt to modularize the server.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sun, 31 Oct 2010 23:58:07 +0100
parents 0f42c9c8085a
children c774f2ffb271
line wrap: on
line diff
--- a/psgxs.js
+++ b/psgxs.js
@@ -24,12 +24,18 @@ var sha1 = require('sha1');
 require('./iso8601');
 var storage = require('./storage');
 var errors = require('./errors');
+var makeError = errors.makeError;
 var utils = require('./util');
 var toBareJID = utils.toBareJID;
 var config = require('./configuration');
 var forms = require('./forms');
 var conn = new xmpp.Connection();
 
+var notifs = require('./notifs');
+notifs.setConnection(conn);
+
+var modules = require('./modules');
+
 var service_configuration = config.service_configuration;
 var componentJID = config.jid;
 var componentPassword = config.password;
@@ -51,9 +57,10 @@ process.addListener('uncaughtException',
 if (typeof xmpp.StanzaBuilder.cnode != 'function' || typeof xmpp.StanzaBuilder.prototype.cnode != 'function') {
 	xmpp.StanzaBuilder.prototype.cnode = function (stanza)
 	{
-		var parent = this;
+		var parent = this.last_node[this.last_node.length-1];
 		parent.tags.push(stanza);
 		parent.children.push(stanza);
+		this.last_node.push(stanza);
 		return this;
 	};
 }
@@ -70,898 +77,40 @@ function onIq(stanza) {
 	else
 		response = xmpp.iq({to: to, from: from, type: 'result'});
 
-	if (type == 'get') {
-
-		// XEP-0092: Software Version
-		if (stanza.getChild('query', 'jabber:iq:version')) {
-			var query = xmpp.stanza('query', {xmlns: 'jabber:iq:version'})
-				.s('name').t('PSĜS')
-				.s('version').t(config.version)
-				.s('os').t(config.os);
-			response.cnode(query);
-
-		// SECTION 5.1
-		} else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#info')) {
-			var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#info');
-			var nodeID = query.getAttribute('node');
-
-			// SECTION 5.3
-			if (nodeID && nodeID != '') {
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
+	var sent = false;
 
-				var conf = storage.getConfiguration(nodeID);
-				if (typeof conf == 'number')
-					return makeError(response, conf);
-
-				var type = 'leaf'
-				if (conf['pubsub#node_type'])
-					type = conf['pubsub#node_type'];
-
-				var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info', node: nodeID})
-				q.s('identity', {category: 'pubsub', type: type});
-				q.s('feature', {'var': 'http://jabber.org/protocol/pubsub'});
-
-				// SECTION 5.4
-				if (config.enabled('meta-data')) {
-					var x = forms.build('result', 'node_metadata', storage.getMetadata(nodeID), true);
-					if (x)
-						q.cnode(x);
-				}
-				response.cnode(q);
+	for (var i in modules) {
+		var module = modules[i];
+		if (module.type && (type != module.type))
+			continue;
 
-			// SECTION 5.1
-			} else {
-				var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info'})
-					.s('identity', {category: 'pubsub', type: 'service', name: 'PubSub JavaScript Server'})
-					.s('feature', {'var': 'http://jabber.org/protocol/disco#info'})
-					.s('feature', {'var': 'http://jabber.org/protocol/disco#items'})
-					.s('feature', {'var': 'http://jabber.org/protocol/pubsub'})
-					.s('feature', {'var': 'jabber:iq:version'})
-					.s('feature', {'var': 'http://jabber.org/protocol/commands'});
+		for (var j in stanza.tags) {
+			var child = stanza.tags[j];
+			if (module.child && (child.name != module.child))
+				continue;
 
-				for (var i in config.activated)
-					if (typeof i == 'string')
-						q.s('feature', {'var': 'http://jabber.org/protocol/pubsub#' + config.activated[i]});
-
-				response.cnode(q);
-			}
+			if (module.ns && (child.attr.xmlns != module.ns))
+				continue;
 
-		// SECTION 5.2
-		} else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#items')) {
-			var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#items');
-			var q;
-			var children;
-			var nodeID = query.getAttribute('node');
-			if (nodeID && nodeID != '') {
-				if (nodeID == 'http://jabber.org/protocol/commands') {
-					// XEP-0050: Ad-Hoc Commands
-
-					q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items', node: nodeID})
-						.s('item', {jid: componentJID, node: 'ping', name: 'Ping'})
-						.s('item', {jid: componentJID, node: 'reload', name: 'Reload'});
+			if (module.child == 'pubsub') {
+				var child2 = child.getChild(module.pschild, child.attr.xmlns);
+				if (child2)
+					child = child2;
 
-					response.cnode(q);
-					return conn.send(response);
-				} else {
-					if (!storage.existsNode(nodeID))
-						return makeError(response, errors.node_does_not_exist.n);
-
-					q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items', node: nodeID});
-
-					children = storage.getChildren(nodeID);
-					if (typeof children == 'number')
-						return makeError(response, children);
-				}
-			} else {
-				q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items'});
-
-				children = storage.getChildren();
-				if (typeof children == 'number')
-					return makeError(response, children);
+				if (module.pschild && (!child || module.pschild != child2.name))
+					continue;
 			}
 
-			for (var i in children) {
-				var attr = {jid: componentJID};
-				if (children[i] == 'node') {
-					if (config.enabled('meta-data')) {
-						var metadata = storage.getMetadata(i);
-						if (metadata['pubsub#title'])
-							attr.name = metadata['pubsub#title'];
-					}
-					attr.node = i;
-
-				// SECTION 5.5
-				} else
-					attr.name = i;
-
-				q.s('item', attr);
+			var toSend = module.func(response, stanza, child, to);
+			if (toSend) {
+				conn.send(toSend);
+				sent = true;
 			}
-			response.cnode(q);
-		} else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) {
-			var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
-
-			// SECTION 5.6
-			if (pubsub.getChild('subscriptions')) {
-				if (!config.enabled('retrieve-subscriptions'))
-					return makeError(response, errors.subscriptions_retrieval_not_supported.n);
-
-				var subscriptions = pubsub.getChild('subscriptions');
-				var subs;
-
-				var nodeID = subscriptions.getAttribute('node');
-				if (nodeID && nodeID != '') {
-					if (!storage.existsNode(nodeID))
-						return makeError(response, errors.node_does_not_exist.n);
-					subs = storage.getSubscription(toBareJID(to), node);
-				} else
-					subs = storage.getSubscription(toBareJID(to));
-
-				var s = xmpp.stanza('subscriptions');
-				for (i in subs)
-					s.s('subscription', {node: i, jid: to, subscription: subs[i].type, subid: subs[i].subid});
-
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
-				p.cnode(s);
-				response.cnode(p);
-
-			// SECTION 5.7
-			} else if (pubsub.getChild('affiliations')) {
-				if (!config.enabled('retrieve-affiliations'))
-					return makeError(response, errors.affiliations_retrieval_not_supported.n);
-
-				var affiliations = pubsub.getChild('affiliations');
-				var nodeID = affiliations.getAttribute('node');
-				var affils;
-				if (nodeID && nodeID != '') {
-					if (!storage.existsNode(nodeID))
-						return makeError(response, errors.node_does_not_exist.n);
-					affils = {};
-					affils[nodeID] = storage.getAffiliation(toBareJID(to), nodeID);
-				} else
-					affils = storage.getAffiliationsFromJID(toBareJID(to));
-
-				var s = xmpp.stanza('affiliations');
-				for (i in affils)
-					s.s('affiliation', {node: i, affiliation: affils[i]});
-
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
-				p.cnode(s);
-				response.cnode(p);
-
-			// SECTION 6.3.2
-			} else if (pubsub.getChild('options')) {
-				if (!config.enabled('subscription-options'))
-					return makeError(response, errors.sub.configure.subscription_options_not_supported.n);
-
-				var options = pubsub.getChild('options');
-
-				var nodeID = options.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var jid = options.getAttribute('jid');
-				if (!jid)
-					return makeError(response, errors.sub.configure.subscriber_jid_required.n);
-				if (toBareJID(jid) != toBareJID(to))
-					return makeError(response, errors.sub.configure.insufficient_privileges.n);
-
-				var subs = storage.getSubscription(jid, nodeID);
-				if (!subs.subid) // FIXME: better test for empty object.
-					return makeError(response, errors.sub.configure.no_such_subscriber.n);
-
-				var s = xmpp.stanza('options', {node: nodeID, jid: jid});
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
-				var form = forms.build('form', 'subscribe_options', subs.options, true);
-				s.cnode(form);
-				p.cnode(s);
-				response.cnode(p);
-
-			// SECTION 6.4
-			} else if (pubsub.getChild('default')) {
-				if (!config.enabled('retrieve-default-sub'))
-					return makeError(response, errors.sub.default_options.default_subscription_configuration_retrieval_not_supported.n);
-
-				var def = pubsub.getChild('default');
-
-				var nodeID = def.getAttribute('node');
-				if (nodeID && !storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
-				var s;
-				if (nodeID)
-					s = xmpp.stanza('default', {node: nodeID});
-				else
-					s = xmpp.stanza('default');
-
-				var form = forms.build('form', 'subscribe_options', 'default', false);
-				s.cnode(form);
-				p.cnode(s);
-				response.cnode(p);
-
-			// SECTION 6.5
-			} else if (pubsub.getChild('items')) {
-				if (!config.enabled('retrieve-items'))
-					return makeError(response, errors.sub.default_options.node_configuration_not_supported.n);
-
-				var items = pubsub.getChild('items');
-
-				var nodeID = items.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var configuration = storage.getConfiguration(nodeID);
-				if (configuration['pubsub#access_model'] == 'whitelist') {
-					var affil = storage.getAffiliation(toBareJID(to), nodeID);
-					if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member')
-						return makeError(response, errors.pub.publish.insufficient_privileges.n);
-				}
-
-				var item = [];
-				for (var i=0; i<items.children.length; i++) {
-					var j = items.children[i];
-					if (j.name == 'item' && j.attr['id'] && j.attr['id'] != '')
-						item.push(j.attr['id']);
-				}
-
-				var max_items = items.getAttribute('max_items');
-				if (max_items)
-					max_items = Number (max_items);
-
-				if (item.length) {
-					var s = xmpp.stanza('items', {node: nodeID});
-
-					for (var i=0; i<item.length; i++) {
-						var j = storage.getItem(nodeID, item[i]);
-						if (typeof j == 'number')
-							return makeError(response, j);
-						if (j == errors.success)
-							continue;
-
-						var k = xmpp.stanza('item', {id: item[i]})
-						k.cnode(j);
-						s.cnode(k);
-					}
-				} else {
-					var s = xmpp.stanza('items', {node: nodeID});
-
-					var j;
-					if (max_items)
-						j = storage.getLastItem(nodeID, max_items);
-					else
-						j = storage.getItems(nodeID);
-					if (typeof j == 'number')
-						return makeError(response, j);
-
-					var k = 0;
-					for (var i in j) {
-						var contentItem = xmpp.stanza('item', {id: i}).t(j[i].content);
-						s.cnode(contentItem);
-					}
-				}
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'});
-				p.cnode(s);
-				response.cnode(p);
-			} else
-				return makeError(response, errors.feature_not_implemented.n);
-		} else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
-			var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
-
-			// SECTION 8.2
-			if (pubsub.getChild('configure')) {
-				if (!config.enabled('config-node'))
-					return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
-
-				var nodeID = pubsub.getChild('configure').getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
-					return makeError(response, errors.pub.publish.insufficient_privileges.n);
-
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
-				var s = xmpp.stanza('configure', {node: nodeID});
-				var form = forms.build('form', 'node_config', 'default', true);
-				s.cnode(form);
-				p.cnode(s);
-				response.cnode(p);
-
-			// SECTION 8.3
-			} else if (pubsub.getChild('default')) {
-				if (!config.enabled('config-node'))
-					return makeError(response, errors.owner.default_options.node_configuration_not_supported.n);
-
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
-				var s = xmpp.stanza('default');
-				var form = forms.build('node_config', service_configuration.node_config, null, true);
-				s.cnode(form);
-				p.cnode(s);
-				response.cnode(p);
-
-			// SECTION 8.8
-			} else if (pubsub.getChild('subscriptions')) {
-				if (!config.enabled('manage-subscriptions'))
-					return makeError(response, errors.owner.manage_subscriptions.not_supported.n);
-
-				var subscriptions = pubsub.getChild('subscriptions');
-
-				var nodeID = subscriptions.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner')
-					return makeError(response, errors.forbidden.n);
-
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
-				var s = xmpp.stanza('subscriptions', {node: nodeID});
-
-				var subs = storage.getSubscriptionsFromNodeID(nodeID)
-				for (var jid in subs)
-					s.s('subscription', {jid: jid, subscription: subs[jid].type, subid: subs[jid].subid})
-
-				p.cnode(s);
-				response.cnode(p);
-
-			// SECTION 8.9
-			} else if (pubsub.getChild('affiliations')) {
-				if (!config.enabled('modify-affiliations'))
-					return makeError(response, errors.owner.manage_affiliations.not_supported.n);
-
-				var affiliations = pubsub.getChild('affiliations');
-
-				var nodeID = affiliations.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affils = storage.getAffiliationsFromNodeID(nodeID);
-				var affil = affils[toBareJID(to)];
-				if (affil != 'super-owner' && affil != 'owner')
-					return makeError(response, errors.owner.manage_affiliations.retrieve_list.entity_is_not_an_owner.n);
-
-				var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'});
-				var s = xmpp.stanza('affiliations', {node: nodeID});
-
-				for (var jid in affils)
-					s.s('affiliation', {jid: jid, affiliation: affils[jid]})
-
-				p.cnode(s);
-				response.cnode(p);
-			} else
-				return makeError(response, errors.feature_not_implemented.n);
-		} else
-			return makeError(response, errors.feature_not_implemented.n);
-	} else if (type == 'set') {
-		if (stanza.getChild('command', 'http://jabber.org/protocol/commands')) {
-			// XEP-0050: Ad-Hoc Commands
-			var command = stanza.getChild('command', 'http://jabber.org/protocol/commands');
-
-			var action = command.getAttribute('action');
-			if (action != 'execute')
-				return makeError(response, errors.bad_request.n);
-
-			var node = command.getAttribute('node');
-			if (node == 'ping') {
-				var cmd = xmpp.stanza('command', {xmlns: 'http://jabber.org/protocol/commands',
-//						      sessionid: 'list:20020923T213616Z-700',
-						      node: node,
-						      'status': 'completed'})
-					.c('note', {type: 'info'}).t('pong');
-				response.cnode(cmd);
-			} else if (node == 'reload') {
-				storage.load();
-				response.c('command', {xmlns: 'http://jabber.org/protocol/commands',
-						       node: node,
-						       'status': 'completed'})
-					.c('note', {type: 'info'}).t('The server has correctly reloaded.');
-			} else
-				return makeError(response, errors.bad_request.n);
-		} else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) {
-			var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
-
-			// SECTION 6.1
-			if (pubsub.getChild('subscribe')) {
-				if (!config.enabled('subscribe'))
-					return makeError(response, errors.sub.subscribe.not_supported.n);
-
-				var subscribe = pubsub.getChild('subscribe');
-
-				var nodeID = subscribe.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var configuration = storage.getConfiguration(nodeID);
-				if (!configuration['pubsub#subscribe'])
-					return makeError(response, errors.sub.subscribe.not_supported.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil == 'publish-only' || affil == 'outcast')
-					return makeError(response, errors.pub.publish.insufficient_privileges.n);
-
-				var jid = subscribe.getAttribute('jid');
-				if (!jid || toBareJID(jid) != toBareJID(to))
-					return makeError(response, errors.sub.subscribe.jids_do_not_match.n);
-
-				// SECTION 6.3.7
-				var options = pubsub.getChild('options');
-				if (options && config.enabled('subscription-options')) {
-					if (options.getAttribute('node') || options.getAttribute('jid'))
-						return makeError(response, errors.bad_request.n);
-
-					var x = options.getChild('x', 'jabber:x:data');
-					if (!x || x.getAttribute('type') != 'submit')
-						return makeError(response, errors.bad_request.n);
-
-					var form = forms.parse(x, true);
-					if (typeof form == 'number')
-						return makeError(response, form);
-
-					var conf = form;
-				}
-
-				var subID;
-				if (configuration['pubsub#access_model'] == 'open') {
-					subID = storage.subscribe(nodeID, jid, 'subscribe', conf);
-					if (typeof subID == 'number')
-						return makeError(response, subID);
-				} else if (configuration['pubsub#access_model'] == 'authorize') {
-					subID = storage.subscribe(nodeID, jid, 'pending', conf);
-					if (typeof subID == 'number')
-						return makeError(response, subID);
-
-					var affiliates = storage.getAffiliationsFromNodeID(nodeID);
-					var form = forms.build('form', 'subscribe_authorization', {allow: false, node: nodeID, subscriber_jid: jid}, true); //168
-
-					for (var i in affiliates) {
-						if (affiliates[i] == 'super-owner' || affiliates[i] == 'owner') {
-							var message = xmpp.message({to: i}).cnode(form);
-							conn.send(message);
-						}
-					}
-				} else if (configuration['pubsub#access_model'] == 'whitelist') {
-					var affil = storage.getAffiliation(jid, nodeID);
-					if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member')
-						return makeError(response, errors.sub.subscribe.not_on_whitelist.n);
-
-					subID = storage.subscribe(nodeID, jid, conf);
-					if (typeof subID == 'number')
-						return makeError(response, subID);
-				}
-
-				response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
-					.c('subscription', {node: nodeID, jid: jid, subid: subID.subid, subscription: subID.type});
-
-				if (conf)
-					response.cnode(options);
-
-				if (config.enabled('last-published')) {
-					var last = storage.getLastItem(nodeID);
-					if (typeof last != 'number') {
-						var item = storage.getItem(nodeID, last);
-						if (typeof item != 'number') {
-							var attr = {};
-							attr[last] = {content: item};
-							sendNotifs(jid, 'items', nodeID, attr);
-						}
-					}
-				}
+		}
+	}
 
-			// SECTION 6.2
-			} else if (pubsub.getChild('unsubscribe')) {
-				if (!config.enabled('subscribe'))
-					return makeError(response, errors.sub.subscribe.not_supported.n);
-
-				var unsubscribe = pubsub.getChild('unsubscribe');
-				var nodeID = unsubscribe.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var jid = unsubscribe.getAttribute('jid');
-				if (!jid || toBareJID(jid) != toBareJID(to))
-					return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n);
-
-				var subID = storage.subscribe(nodeID, jid, 'none');
-				if (typeof subID == 'number')
-					return makeError(response, subID);
-
-			// SECTIONS 6.3.5
-			} else if (pubsub.getChild('options')) {
-				if (!config.enabled('subscription-options'))
-					return makeError(response, errors.sub.subscribe.not_supported.n);
-
-				var options = pubsub.getChild('options');
-
-				var nodeID = unsubscribe.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var jid = unsubscribe.getAttribute('jid');
-				if (!jid || toBareJID(jid) != toBareJID(to))
-					return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n);
-
-				var x = options.getChild('x', 'jabber:x:data');
-				if (!x || x.getAttribute(type) != 'submit')
-					return makeError(response, errors.bad_request);
-
-				var form = forms.parse(x, true);
-				if (typeof form == 'number')
-					return makeError(response, form);
-
-				var set = storage.configureSubscription(nodeID, jid, form);
-				if (typeof form == 'number')
-					return makeError(response, form);
-
-			// SECTION 7.1
-			} else if (pubsub.getChild('publish')) {
-				if (!config.enabled('publish'))
-					return makeError(response, errors.pub.publish.item_publication_not_supported.n);
-
-				var publish = pubsub.getChild('publish');
-				var nodeID = publish.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-
-				var autocreate = false;
-				if (!storage.existsNode(nodeID)) {
-					if (config.enabled('auto-create'))
-						autocreate = true;
-					else
-						return makeError(response, errors.node_does_not_exist.n);
-				}
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (typeof affil == 'number' && affil != errors.node_does_not_exist.n)
-					return makeError(response, affil);
-				if (!autocreate && affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'publish-only')
-					return makeError(response, errors.forbidden.n);
-
-				var item = publish.getChild('item');
-				var itemID = item.getAttribute('id');
-				if (!config.enabled('item-ids') && itemID)
-					return makeError(response, errors.itemid_required.n);
-				itemID = itemID? itemID: utils.makeRandomId();
-
-				if (item.tags.length != 1)
-					return makeError(response, errors.pub.publish.bad_payload.n);
-
-				var conf = storage.getConfiguration(nodeID);
-				var publishOptions = pubsub.getChild('publish-options');
-				if (publishOptions && config.enabled('publish-options')) {
-					var x = publishOptions.getChild('x', 'jabber:x:data');
-					if (!x || x.getAttribute('type') != 'submit')
-						return makeError(response, errors.bad_request.n);
-
-					var form = forms.parse(x, true);
-					if (form.access_model != conf['pubsub#access_model'] && !autocreate)
-						return makeError(response, errors.pub.configuration.precondition.n);
-				}
-
-				if (!config.enabled('persistent-items')) {
-					var notifs = storage.purgeNode(nodeID);
-					if (typeof notifs == 'number')
-						return makeError(response, r);
-				}
-
-				if (autocreate) {
-					if (!form)
-						form = {};
-					form['pubsub#creator'] = toBareJID(to);
-
-					var r = storage.createNode(nodeID, form);
-					if (typeof r == 'number')
-						return makeError(response, r);
-				}
-
-				var content = item.getChild();
-
-				subscribers = storage.setItem(nodeID, itemID, content);
-				if (typeof subscribers == 'number')
-					return makeError(response, subscribers);
-
-				var attrs = {};
-				if (content)
-					attrs[itemID] = {content: content};
-				else
-					attrs[itemID] = {};
-				sendNotifs(subscribers, 'items', nodeID, attrs);
-
-				response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
-					.c('publish', {node: nodeID})
-						.c('item', {id: itemID});
-
-			// SECTION 7.2
-			} else if (pubsub.getChild('retract')) {
-				if (!config.enabled('retract-items'))
-					return makeError(response, errors.pub.retract.item_deletion_not_supported.n);
-
-				var retract = pubsub.getChild('retract');
-
-				var nodeID = retract.getAttribute('node');
-				if (!nodeID || nodeID == '')
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var item = retract.getChild('item');
-				if (!item)
-					return makeError(response, errors.pub.retract.item_or_itemid_required.n);
-
-				var itemID = item.getAttribute('id')
-				if (!itemID || itemID == '')
-					return makeError(response, errors.pub.retract.item_or_itemid_required.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
-					return makeError(response, errors.forbidden.n);
-
-				var subscribers = storage.deleteItem(nodeID, itemID);
-				if (typeof subscribers == 'number')
-					return makeError(response, subscribers);
-
-				var attrs = {};
-				attrs[itemID] = {};
-				sendNotifs(subscribers, 'items', nodeID, attrs, 'retract')
-
-			// SECTION 8.1
-			} else if (pubsub.getChild('create')) {
-				if (!config.enabled('create-nodes'))
-					return makeError(response, errors.owner.create.node_creation_not_supported.n);
-
-				var instant = false;
-
-				var nodeID = pubsub.getChild('create').getAttribute('node');
-				if (!nodeID || nodeID == '') {
-					if (!config.enabled('instant-nodes'))
-						return makeError(response, errors.owner.create.instant_nodes_not_supported.n);
-					nodeID = utils.makeRandomId();
-					instant = true;
-				}
-				if (storage.existsNode(nodeID))
-					return makeError(response, errors.owner.create.nodeid_already_exists.n);
-
-				var bare = toBareJID(to);
-				var right = false;
-
-				// Check for super-owner
-				for (var i in config.owner)
-					if (config.owner[i] == bare)
-						right = true;
-
-				// Check for authorized user
-				for (var i in config.allowCreateNode)
-					if (config.allowCreateNode[i].exec(bare))
-						right = true;
-
-				if (!right)
-					return makeError(response, errors.forbidden.n);
-
-				var configure = pubsub.getChild('configure');
-				if (configure && config.enabled('create-and-configure')) {
-					if (!config.enabled('config-node'))
-						return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
-
-					if (configure.getAttribute('node'))
-						return makeError(response, errors.bad_request.n);
-
-					var x = configure.getChild('x', 'jabber:x:data');
-					if (!x || x.getAttribute('type') != 'submit')
-						return makeError(response, errors.bad_request.n);
-
-					var form = forms.parse(x, true);
-					if (typeof form == 'number')
-						return makeError(response, form);
-
-					var conf = form;
-				}
-
-				if (!conf)
-					conf = {};
-				conf['pubsub#creator'] = bare;
-
-				var r = storage.createNode(nodeID, conf);
-				if (typeof r == 'number')
-					return makeError(response, r);
-
-				if (instant)
-					response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
-						.c('create', {node: nodeID});
-			} else
-				return makeError(response, errors.feature_not_implemented.n);
-		} else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) {
-			var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner');
-
-			// SECTION 8.2.4
-			if (pubsub.getChild('configure')) {
-				if (!config.enabled('config-node'))
-					return makeError(response, errors.owner.configure.node_configuration_not_supported.n);
-
-				var configure = pubsub.getChild('configure');
-
-				var nodeID = configure.getAttribute('node');
-				if (!nodeID)
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only')
-					return makeError(response, errors.forbidden.n);
-
-				var x = configure.getChild('x', 'jabber:x:data');
-				if (!x)
-					return makeError(response, errors.bad_request.n);
-
-				var type = x.getAttribute('type');
-				if (type == 'cancel') {
-					conn.send(response);
-					return;
-				}
-				if (type != 'submit')
-					return makeError(response, errors.bad_request.n);
-
-				var form = forms.parse(x, true);
-				if (typeof form == 'number')
-					return makeError(response, form);
-
-				var conf = form;
-
-				var set = storage.configure(nodeID, conf);
-				if (typeof set == 'number')
-					return makeError(response, set);
-
-			// SECTION 8.4
-			} else if (pubsub.getChild('delete')) {
-				if (!config.enabled('delete-nodes'))
-					return makeError(response, errors.feature_not_implemented.n); //XXX
-
-				var del = pubsub.getChild('delete');
-
-				var nodeID = del.getAttribute('node');
-				if (!nodeID)
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner')
-					return makeError(response, errors.forbidden.n);
-
-				var notifs = storage.deleteNode(nodeID);
-				if (typeof notifs == 'number')
-					return makeError(response, r);
-
-				sendNotifs(notifs, 'delete', nodeID);
-
-			// SECTION 8.5
-			} else if (pubsub.getChild('purge')) {
-				if (!config.enabled('purge-nodes'))
-					return makeError(response, errors.owner.purge.node_purging_not_supported.n); //XXX
-
-				var purge = pubsub.getChild('purge');
-
-				var nodeID = purge.getAttribute('node');
-				if (!nodeID)
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner')
-					return makeError(response, errors.forbidden.n);
-
-				if (!config.enabled('persistent-items')) //FIXME: autre condition, supporté par le node
-					return makeError(response, errors.owner.purge.node_does_not_persist_items.n);
-
-				var notifs = storage.purgeNode(nodeID);
-				if (typeof notifs == 'number')
-					return makeError(response, r);
-
-				sendNotifs(notifs, 'purge', nodeID);
-
-			// SECTION 8.8.2
-			} else if (pubsub.getChild('subscriptions')) {
-				if (!config.enabled('manage-subscriptions'))
-					return makeError(response, errors.owner.manage_subscriptions.not_supported.n); //XXX
-
-				var subscriptions = pubsub.getChild('subscriptions');
-
-				var nodeID = subscriptions.getAttribute('node');
-				if (!nodeID)
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner')
-					return makeError(response, errors.forbidden.n);
-
-				var e = false;
-				var tags2 = [];
-				for (i in subscriptions.tags) {
-					var tag = subscriptions.tags[i];
-					var jid = tag.getAttribute('jid');
-					var sub = tag.getAttribute('subscription');
-
-					if (sub == 'none' || sub == 'pending' || sub == 'subscribed' || sub == 'unconfigured') {
-						var set = storage.subscribe(nodeID, jid, sub);
-
-						if (typeof set == 'number') {
-							e = true;
-							tags2.push(tag);
-						} else {
-							// SECTION 8.8.4
-							sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: sub});
-						}
-					} else {
-						e = true;
-						tags2.push(tag);
-					}
-				}
-
-				subscriptions.tags = tags2;
-
-				if (e)
-					return makeError(response, errors.owner.manage_subscriptions.modify.multiple_simultaneous_modifications.n, pubsub);
-
-			// SECTION 8.9.2
-			} else if (pubsub.getChild('affiliations')) {
-				if (!config.enabled('modify-affiliations'))
-					return makeError(response, errors.owner.manage_affiliations.not_supported.n); //XXX
-
-				var affiliations = pubsub.getChild('affiliations');
-
-				var nodeID = affiliations.getAttribute('node');
-				if (!nodeID)
-					return makeError(response, errors.nodeid_required.n);
-				if (!storage.existsNode(nodeID))
-					return makeError(response, errors.node_does_not_exist.n);
-
-				var affil = storage.getAffiliation(toBareJID(to), nodeID);
-				if (affil != 'super-owner' && affil != 'owner')
-					return makeError(response, errors.forbidden.n);
-
-				var e = false;
-				for (i in affiliations.children) {
-					var jid = affiliations.children[i].getAttribute('jid');
-					var affiliation = affiliations.children[i].getAttribute('affiliation');
-
-					var set = storage.setAffiliation(nodeID, jid, affiliation);
-					if (typeof set == 'number')
-						e = true;
-					else {
-						// SECTION 8.9.4
-						sendNotifs(jid, 'affiliations', nodeID, {jid: jid, affiliation: affiliation});
-						affiliations.children.splice(i, 1);
-					}
-				}
-
-				if (e)
-					return makeError(response, errors.owner.manage_affiliations.modify.multiple_simultaneous_modifications.n, pubsub);
-			} else
-				return makeError(response, errors.feature_not_implemented.n);
-		} else
-			return makeError(response, errors.feature_not_implemented.n);
-	} else
-		return makeError(response, errors.feature_not_implemented.n);
-	conn.send(response);
+	if (!sent)
+		conn.send(makeError(response, errors.feature_not_implemented.n));
 }
 
 function onMessage(stanza) {
@@ -991,7 +140,7 @@ function onMessage(stanza) {
 			var type = allow? 'subscribed': 'none';
 			var set = storage.subscribe(nodeID, jid, type)
 			//if (set.subid != subID) //TODO: support the multi-subscribe feature
-			sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: type});
+			notifs.send(jid, 'subscription', nodeID, {jid: jid, subscription: type});
 		} else
 			return makeError(response, errors.feature_not_implemented.n);
 	} else
@@ -1013,177 +162,6 @@ function onPresence(stanza) {
 	makeError(response, errors.feature_not_implemented.n);
 }
 
-function makeError(response, errorNumber, payload) {
-	response.attr.type = 'error';
-	if (payload)
-		response.cnode(payload);
-
-	var e = errors.reverse[errorNumber];
-	var error = xmpp.stanza('error', {type: e.type});
-
-	error.s(e.error, {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'})
-
-	if (e.reason) {
-		if (e.feature)
-			error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors', feature: e.feature});
-		else
-			error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors'});
-	}
-
-	response.cnode(error);
-	conn.send(response);
-}
-
-function sendNotifs(notifs, type, nodeID, a1, a2) {
-	var ev = xmpp.stanza('event', {xmlns: 'http://jabber.org/protocol/pubsub#event'});
-
-	if (type == 'affiliations') {
-		ev.attr.xmlns = 'http://jabber.org/protocol/pubsub';
-
-		var args = {};
-		for (i in a1) {
-			var attr = a1[i];
-			if (i == 'affiliation')
-				args.affiliation = attr;
-			else if (i == 'jid')
-				args.jid = attr;
-		}
-		var affiliations = xmpp.stanza('affiliations', {node: nodeID})
-			.c('affiliation', args);
-		ev.cnode(affiliations);
-	} else if (type == 'collection') {
-		var collection = xmpp.stanza('collection', {node: nodeID});
-		if (a1 == 'associate')
-			collection.cnode('associate', {node: nodeID});
-		else
-			collection.cnode('disassociate', {node: nodeID});
-		ev.cnode(collection);
-	} else if (type == 'configuration') {
-		if (!config.enabled('config-node')) {
-			_('Error #4', 41)
-			return;
-		}
-
-		var configuration = xmpp.stanza('configuration', {node: nodeID});
-		if (a1) {
-			var x = forms.build('node_config', service_configuration.node_config, storage.getConfiguration(nodeID));
-			if (x)
-				configuration.cnode(x); //TODO: voir exemple 150
-		}
-		ev.cnode(configuration);
-	} else if (type == 'delete') {
-		var del = xmpp.stanza('delete', {node: nodeID});
-		if (a1)
-			del.c('redirect', {uri: a1});
-		ev.cnode(del);
-	} else if (type == 'items') {
-		var items = xmpp.stanza(type, {node: nodeID});
-		if (a2 == 'retract')
-			for (var i in a1)
-				items.s('retract', {id: i});
-		else {
-			for (var i in a1) {
-				var item = a1[i];
-				var args = {};
-				if (i != '')
-					args.id = i;
-				if (item.node)
-					args.node = item.node;
-				if (item.publisher)
-					args.publisher = item.publisher;
-				var it = xmpp.stanza('item', args);
-				if (item.content)
-					it.cnode(item.content);
-				items.cnode(it);
-			}
-		}
-		ev.cnode(items);
-	} else if (type == 'purge') {
-		ev.c('purge', {node: nodeID});
-	} else if (type == 'subscription') {
-		if (!config.enabled('subscription-notifications'))
-			return;
-
-		var args = {node: nodeID};
-		for (i in a1) {
-			var attr = a1[i];
-			if (i == 'subscription') {
-				if (attr == 'none' || attr == 'pending' || attr == 'subscribed' || attr == 'unconfigured')
-					args[i] = attr;
-				else {
-					_('Error #3', 41)
-					return;
-				}
-			} else if (i == 'jid' || i == 'subid')
-				args[i] = attr;
-			else if (i == 'expiry')
-				args[i] = attr.toString();
-		}
-		if (!args.jid || args.jid == '') {
-			_('Error #2', 41)
-			return;
-		}
-		var sub = xmpp.stanza('subscription', args);
-		ev.cnode(sub);
-	} else {
-		_('Error #1', 41)
-		return;
-	}
-
-	var subs;
-	if (typeof notifs == 'string') {
-		subs = {};
-		subs[notifs] = storage.getSubscription(notifs, nodeID);
-	} else
-		subs = notifs;
-
-	for (var i in subs) {
-		var sub = subs[i];
-
-		if (sub.options) {
-			if (typeof sub.options['pubsub#deliver'] != 'undefined' && !sub.options['pubsub#deliver'])
-				continue;
-
-			if (typeof sub.options['pubsub#digest'] != 'undefined' && sub.options['pubsub#digest']) {
-				if (!sub.digest)
-					sub.digest = [];
-				sub.digest.push(ev)
-
-				if (sub.digestTimeout)
-					continue;
-
-				var freq;
-				if (typeof sub.options['pubsub#digest_frequency'] == 'undefined')
-					freq = 0;
-				else
-					freq = parseInt(sub.options['pubsub#digest_frequency']);
-
-				if (freq == 0)
-					freq = 24*60*60*1000;
-
-				setTimeout(sendDigest, freq, notifs[i], nodeID);
-				sub.digestTimeout = true;
-				continue;
-			}
-		}
-
-		var message = xmpp.message({to: i, from: componentJID, id: conn.getUniqueId()});
-		message.cnode(ev);
-		conn.send(message);
-	}
-}
-
-function sendDigest(jid, nodeID) {
-	var sub = storage.getSubscription(jid, nodeID);
-	if (sub.digestTimeout)
-		sub.digestTimeout = false;
-
-	var message = xmpp.message({to: jid, from: componentJID, id: conn.getUniqueId()});
-	for (var i in sub.digest)
-		message.cnode(sub.digest[i]);
-	conn.send(message);
-}
-
 conn.connect(componentJID, componentPassword, function (status, condition) {
 	if (status == xmpp.Status.CONNECTED) {
 		conn.addHandler(onMessage, null, 'message', null, null,  null);