JunctionXMPP middleware framework | |
| lib/junction/client.js |
Module dependencies.
|
var xmpp = require('node-xmpp')
, uuid = require('node-uuid')
, util = require('util')
, assert = require('assert')
, NullStream = require('./nullstream');
|
Initialize a new Client .
|
function Client(options) {
options = options || {}
this._disableStream = options.disableStream || false;
xmpp.Client.call(this, options);
this._stack = [];
this._filters = [];
this.addListener('stanza', this.handle);
}
|
Inherit from xmpp.Client .
|
util.inherits(Client, xmpp.Client);
|
Utilize the given middleware handle .
|
Client.prototype.use = function(ns, handle) {
if ('string' != typeof ns) {
handle = ns;
ns = null;
}
if ('function' == typeof handle.handle) {
var connection = handle;
connection.send = this.send.bind(this);
connection.generateID = this.generateID.bind(this);
handle = function(stanza, next){
connection.handle(stanza, next);
};
}
this._stack.push({ ns: ns, handle: handle });
return this;
}
|
Utilize the given filter handle .
|
Client.prototype.filter = function(handle) {
this._filters.push({ handle: handle });
return this;
}
|
Send stanza, running it through the filter chain.
|
Client.prototype.send = function(stanza) {
if (stanza.toXML) {
stanza = stanza.toXML();
}
if (stanza.root) {
console.log('SEND: ' + stanza.root().toString() + '\n');
} else {
console.log('SEND: ' + stanza + '\n');
}
var filters = this._filters;
var idx = 0;
function next() {
var filter = filters[idx++];
if (!filter) { return; }
try {
filter.handle(stanza, next);
} catch (e) {
if (e instanceof assert.AssertionError) {
console.error(e.stack + '\n');
next();
} else {
console.error(e.stack + '\n');
next();
}
}
}
if (stanza instanceof xmpp.Element) { next(); }
xmpp.Client.prototype.send.call(this, stanza);
}
|
Generate a unique identifier, for use in a stanza's id attribute.
|
Client.prototype.generateID = function() {
return uuid();
}
|
Expose Client .
|
module.exports = Client;
|
| lib/junction/component.js |
Module dependencies.
|
var xmpp = require('node-xmpp')
, util = require('util')
, Client = require('./client');
|
Initialize a new Component .
param: Object options api: public
|
function Component(options) {
options = options || {}
this._disableStream = options.disableStream || false;
xmpp.Component.call(this, options);
this._stack = [];
this._filters = [];
this.addListener('stanza', this.handle);
}
|
Inherit from xmpp.Component .
|
util.inherits(Component, xmpp.Component);
|
Mixin Client methods.
|
Object.keys(Client.prototype).forEach(function(method){
Component.prototype[method] = Client.prototype[method];
});
|
Expose Component .
|
module.exports = Component;
|
| lib/junction/elements/element.js |
Module dependencies.
|
var util = require('util')
, XMLElement = require('node-xmpp').Element;
|
Initialize a new Element .
param: String name param: String xmlns api: public
|
function Element(name, xmlns) {
if (!name) { throw new Error('Element requires a name'); }
this.parent = null;
this.name = name;
this.xmlns = xmlns;
this.children = [];
}
|
Add a child element.
|
Element.prototype.c = function(child, attrs) {
if (attrs) {
child = new XMLElement(child, attrs);
}
this.children.push(child);
child.parent = this;
return child;
}
|
Set the text value.
param: String text api: public
|
Element.prototype.t = function(text) {
this.children.push(text);
return this;
};
|
Return the parent element, or this if root.
|
Element.prototype.up = function() {
if (this.parent) {
return this.parent;
} else {
return this;
}
};
|
Serialize to XML.
|
Element.prototype.toXML = function() {
var attrs = this.xmlAttributes();
attrs['xmlns'] = this.xmlns;
var xml = new XMLElement(this.name, attrs);
this.children.forEach(function(child) {
if (child.toXML) {
xml.children.push(child.toXML());
} else if (child.toString) {
xml.children.push(child);
} else if (typeof child === 'string') {
xml.children.push(child);
}
});
return xml;
}
|
Build XML attributes.
This function is intended to be overrriden by subclasses, in order to
serialize attributes.
|
Element.prototype.xmlAttributes = function() {
return {};
}
|
Expose Element .
|
exports = module.exports = Element;
|
| lib/junction/elements/iq.js |
Module dependencies.
|
var util = require('util')
, Element = require('./element');
|
Initialize a new IQ element.
param: String to param: String from param: String type api: public
|
function IQ(to, from, type) {
if ('string' != typeof type) {
type = from;
from = null;
}
type = type || 'get';
Element.call(this, 'iq');
this.id = null;
this.to = to;
this.from = from;
this.type = type;
}
|
Inherit from Element .
|
util.inherits(IQ, Element);
|
Expose IQ .
|
exports = module.exports = IQ;
|
| lib/junction/elements/message.js |
Module dependencies.
|
var util = require('util')
, Element = require('./element');
|
Initialize a new Message element.
param: String to param: String from param: String type api: public
|
function Message(to, from, type) {
if ('string' != typeof type) {
type = from;
from = null;
}
Element.call(this, 'message');
this.to = to || null;
this.from = from || null;
this.type = type || null;
}
|
Inherit from Element .
|
util.inherits(Message, Element);
|
Expose Message .
|
exports = module.exports = Message;
|
| lib/junction/elements/presence.js |
Module dependencies.
|
var util = require('util')
, Element = require('./element');
|
Initialize a new Presence element.
param: String to param: String from param: String type api: public
|
function Presence(to, from, type) {
if ('string' != typeof type) {
type = from;
from = null;
}
Element.call(this, 'presence');
this.to = to || null;
this.from = from || null;
this.type = type || null;
}
|
Inherit from Element .
|
util.inherits(Presence, Element);
|
Expose Presence .
|
exports = module.exports = Presence;
|
| lib/junction/filters/pending.js |
Module dependencies.
|
var util = require('util');
|
Save pending data to pending store.
This filter processes outgoing stanzas, saving any data attached to the
pending property to the pending store. Pending data is typically set by
applying additional filter() 's to the connection. Such filters process
outgoing stanzas and record any data necessary to interpret the eventual
response.
When used in conjunction with the pending middleware, the data saved in the
pending store will be loaded when the corresponding response stanza arrives.
This allows a stateless, shared-nothing architecture to be utilized in
XMPP-based systems. This is particularly advantageous in systems employing
XMPP component connections with round-robin load balancing strategies. In
such a scenario, requests can be sent via one component instance, while the
result can be received and processed by an entirely separate component
instance.
Options
store pending store instance
Examples
var store = new junction.pending.MemoryStore();
connection.filter(disco.filters.infoQuery());
connection.filter(junction.filters.pending({ store: store }));
connection.use(junction.pending({ store: store }));
connection.use(function(stanza, next) {
if (stanza.inResponseTo) {
console.log('response received!');
return;
}
next();
});
param: Object options return: Function api: public
|
module.exports = function pending(options) {
options = options || {};
var store = options.store;
if (!store) throw new Error('pending filter requires a store');
return function pending(stanza, next) {
if (!stanza.attrs.id || !stanza.pending) {
return next();
}
var key = stanza.attrs.to + ':' + stanza.attrs.id;
store.set(key, stanza.pending, function(err) {
next(err);
});
}
}
|
| lib/junction/index.js |
Module dependencies.
|
var fs = require('fs')
, xmpp = require('node-xmpp')
, Client = require('./client')
, Component = require('./component')
, StanzaError = require('./stanzaerror');
|
Framework version.
|
exports.version = '0.1.0';
|
Create a new Junction connection.
Options
jid JIDpassword Password, for authenticationhost port type Type of connection, see below for typesdisableStream Disable underlying stream, defaults to false
Connection Types
client XMPP client connectioncomponent XMPP component connection
Examples
var client = junction.createConnection({
type: 'client',
jid: 'user@example.com',
password: 'secret',
host: 'example.com',
port: 5222
});
param: Object options return: Connection api: public
|
exports.createConnection = function (options) {
if (options.type == 'component') {
return new Component(options);
}
return new Client(options);
};
|
Expose constructors.
|
exports.Client = Client;
exports.Component = Component;
exports.JID = xmpp.JID;
exports.XMLElement = xmpp.Element;
exports.StanzaError = StanzaError;
|
Expose element constructors.
|
exports.elements = {};
exports.elements.Element = require('./elements/element');
exports.elements.IQ = require('./elements/iq');
exports.elements.Message = require('./elements/message');
exports.elements.Presence = require('./elements/presence');
|
Expose bundled filters.
|
exports.filters = {};
exports.filters.pending = require('./filters/pending');
|
Auto-load bundled middleware.
|
exports.middleware = {};
fs.readdirSync(__dirname + '/middleware').forEach(function(filename) {
if (/\.js$/.test(filename)) {
var name = filename.substr(0, filename.lastIndexOf('.'));
exports.middleware.__defineGetter__(name, function(){
return require('./middleware/' + name);
});
}
});
|
Expose middleware as first-class exports.
|
exports.__proto__ = exports.middleware;
|
| lib/junction/middleware/attentionParser.js |
Parse elements intended to get the attention of a user.
This middleware parses elements within the urn:xmpp:attention:0 namespace.
If attention is requested, stanza.attention will be set to true .
Examples
connection.use(junction.attentionParser());
References
return: Function api: public
|
module.exports = function attentionParser() {
return function attentionParser(stanza, next) {
if (!stanza.is('message')) { return next(); }
var attention = stanza.getChild('attention', 'urn:xmpp:attention:0');
if (!attention) { return next(); }
stanza.attention = true;
next();
}
}
|
| lib/junction/middleware/capabilitiesParser.js |
Parse entity capabilities broadcast in presence stanzas.
This middleware parses entity capabilities present in presence stanzas.
stanza.capabilities.node will be set to a URI that uniquely identifies a
software application. stanza.capabilities.verification will be set to a
string used to verify the identity and supported features of the entity.
stanza.capabilities.hash will indicate the hashing algorithm used to
generate the verification string
Examples
connection.use(junction.capabilitiesParser());
References
return: Function api: public
|
module.exports = function capabilitiesParser() {
return function capabilitiesParser(stanza, next) {
if (!stanza.is('presence')) { return next(); }
var c = stanza.getChild('c', 'http://jabber.org/protocol/caps');
if (!c) { return next(); }
stanza.capabilities = {};
stanza.capabilities.node = c.attrs.node;
stanza.capabilities.verification = c.attrs.ver;
stanza.capabilities.hash = c.attrs.hash;
next();
}
}
|
| lib/junction/middleware/delayParser.js |
Module dependencies.
|
var JID = require('node-xmpp').JID;
|
Parse information indicating an XMPP stanza has been delivered with a delay.
This middleware parses delay information contained within message and
presence stanzas. stanza.delayedBy indicates the Jabber ID of the entity
that delayed the delivery of the stana. stanza.originallySentAt indicates
the time the stanza was originally sent.
Examples
connection.use(junction.delayParser());
References
return: Function api: public
|
module.exports = function delayParser() {
return function delayParser(stanza, next) {
if (!(stanza.is('message') || stanza.is('presence'))) { return next(); }
var delay = stanza.getChild('delay', 'urn:xmpp:delay');
if (!delay) { return next(); }
stanza.delayedBy = new JID(delay.attrs.from);
stanza.originallySentAt = new Date(delay.attrs.stamp);
next();
}
}
|
| lib/junction/middleware/errorHandler.js |
Module dependencies.
|
var StanzaError = require('../stanzaerror');
|
Flexible error handler, providing error responses containing a message,
application-specific error conditions, and optional stack traces.
Options
includeStanza include the original stanza in the error response. Defaults to false (not implemented)showStack respond with both the error message and stack trace. Defaults to false dumpExceptions dump exceptions to stderr (without terminating the process). Defaults to false
Examples
connection.use(junction.errorHandler());
connection.use(
junction.errorHandler({ showStack: true, dumpExceptions: true })
);
Middleware can then next() with an error:
next(new Error("You're doing it wrong!"));
next(new StanzaError("Administrator privileges required.", "auth", "forbidden"));
var err = new StanzaError('', 'modify', 'bad-request');
err.specificCondition = { name: 'invalid-jid', xmlns: 'http://jabber.org/protocol/pubsub#errors' };
next(err);
param: Object options return: Function api: public
|
module.exports = function errorHandler(options) {
options = options || {};
var includeStanza = options.includeStanza || false;
var showStack = options.showStack || false;
var dumpExceptions = options.dumpExceptions || false;
return function errorHandler(err, req, res, next) {
if (dumpExceptions) { console.error(err.stack); }
if (!res) { return next(err); }
var type = err.type || 'wait';
var condition = err.condition || 'internal-server-error';
var message = err.message || '';
if (showStack) {
message += '\n';
message += err.stack;
}
res.attrs.type = 'error';
if (includeStanza) {
}
var errorEl = res.c('error', { type: type }).c(condition, { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' }).up();
if (message && message.length) {
errorEl.c('text', { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' }).t(message)
}
if (err.specificCondition && err.specificCondition.name) {
errorEl.c(err.specificCondition.name, { xmlns: err.specificCondition.xmlns })
}
res.send();
}
}
|
| lib/junction/middleware/lastActivity.js |
Module dependencies.
|
var StanzaError = require('../stanzaerror');
|
Handle requests for the last activity associated with an XMPP entity.
This middleware handles IQ-get requests within the jabber:iq:last XML
namespace. By default, the middleware responds with the uptime of the
entity, measured in seconds.
Examples
connection.use(junction.lastActivity());
connection.use(
junction.lastActivity(function() {
return 31556926;
})
);
References
param: Function callback return: Function api: public
|
module.exports = function lastActivity(callback) {
callback = callback || uptime;
var start = Date.now();
function uptime() {
return Math.round((Date.now() - start) / 1000);
}
return function lastActivity(req, res, next) {
if (!req.is('iq')) { return next(); }
if (req.type == 'result' || req.type == 'error') { return next(); }
var query = req.getChild('query', 'jabber:iq:last');
if (!query) { return next(); }
if (req.type != 'get') {
return next(new StanzaError("Query must be an IQ-get stanza.", 'modify', 'bad-request'));
}
var now = Date.now();
var q = res.c('query', { xmlns: 'jabber:iq:last', seconds: callback() });
res.send();
}
}
|
| lib/junction/middleware/lastActivityResultParser.js |
Parse information about the last activity associated with an XMPP entity.
This middleware parses last activity information contained within IQ-result
stanzas. stanza.lastActivity indicates the time of last activity of the
entity, in seconds. stanza.lastStatus indicates last status of the entity.
Examples
connection.use(junction.lastActivityResultParser());
References
return: Function api: public
|
module.exports = function lastActivityResultParser() {
return function lastActivityResultParser(stanza, next) {
if (!stanza.is('iq')) { return next(); }
if (stanza.type != 'result') { return next(); }
var query = stanza.getChild('query', 'jabber:iq:last');
if (!query) { return next(); }
stanza.lastActivity = query.attrs.seconds;
stanza.lastStatus = query.getText();
next();
}
}
|
| lib/junction/middleware/legacyDelayParser.js |
Module dependencies.
|
var JID = require('node-xmpp').JID;
|
Parse legacy information indicating an XMPP stanza has been delivered with a
delay.
This middleware parses legacy delay information contained within message and
presence stanzas. stanza.delayedBy indicates the Jabber ID of the entity
that delayed the delivery of the stana. stanza.originallySentAt indicates
the time the stanza was originally sent.
Examples
connection.use(junction.legacyDelayParser());
References
return: Function api: public
|
module.exports = function legacyDelayParser() {
return function legacyDelayParser(stanza, next) {
if (!(stanza.is('message') || stanza.is('presence'))) { return next(); }
var delay = stanza.getChild('x', 'jabber:x:delay');
if (!delay) { return next(); }
stanza.delayedBy = new JID(delay.attrs.from);
var match = /(\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})/.exec(delay.attrs.stamp);
if (match) {
stanza.originallySentAt = new Date(Date.UTC(match[1], match[2] - 1, match[3], match[4], match[5], match[6]));
}
next();
}
}
|
| lib/junction/middleware/legacyTime.js |
Module dependencies.
|
var StanzaError = require('../stanzaerror');
|
Handle legacy requests for the local time of an XMPP entity.
This middleware handles IQ-get requests within the jabber:iq:time XML
namespace. The middleware responds with the UTC time of the entity, and
optionally the time zone and a human-readable display string.
Options
timezone a string containing the time zone, typically a three-letter acronymdisplay a string containing the time in a human-readable format
Examples
connection.use(junction.legacyTime());
connection.use(
junction.legacyTime({ timezone: 'MDT', display: 'Tue Sep 10 12:58:35 2002' })
);
References
param: Object options return: Function api: public
|
module.exports = function legacyTime(options) {
options = options || {};
return function legacyTime(req, res, next) {
if (!req.is('iq')) { return next(); }
if (req.type == 'result' || req.type == 'error') { return next(); }
var query = req.getChild('query', 'jabber:iq:time');
if (!query) { return next(); }
if (req.type != 'get') {
return next(new StanzaError("Query must be an IQ-get stanza.", 'modify', 'bad-request'));
}
var now = options.date || new Date();
var t = res.c('query', { xmlns: 'jabber:iq:time' });
t.c('utc').t(LegacyXMPPDateTimeString(now));
if (options.timezone) { t.c('tz').t(options.timezone); }
if (options.display) { t.c('display').t(options.display); }
res.send();
}
}
function LegacyXMPPDateTimeString(d) {
function pad(n) { return n < 10 ? '0' + n : n.toString() }
return d.getUTCFullYear()
+ pad(d.getUTCMonth() + 1)
+ pad(d.getUTCDate()) + 'T'
+ pad(d.getUTCHours()) + ':'
+ pad(d.getUTCMinutes()) + ':'
+ pad(d.getUTCSeconds())
}
|
| lib/junction/middleware/legacyTimeResultParser.js |
Parse legacy information about the local time of an XMPP entity.
This middleware parses legacy local time information contained within
IQ-result stanzas. stanza.utcDate indicates the UTC time according to the
responding entity. stanza.timezone indicates the time zone in which the
responding entity is located. stanza.display contains the time in a
human-readable format.
Examples
connection.use(junction.legacyTimeResultParser());
References
return: Function api: public
|
module.exports = function legacyTimeResultParser() {
return function legacyTimeResultParser(stanza, next) {
if (!stanza.is('iq')) { return next(); }
if (stanza.type != 'result') { return next(); }
var query = stanza.getChild('query', 'jabber:iq:time');
if (!query) { return next(); }
var utcEl = query.getChild('utc');
if (utcEl) {
var match = /(\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})/.exec(utcEl.getText());
if (match) {
stanza.utcDate = new Date(Date.UTC(match[1], match[2] - 1, match[3], match[4], match[5], match[6]));
}
}
var tzEl = query.getChild('tz');
if (tzEl) {
stanza.timezone = tzEl.getText();
}
var displayEl = query.getChild('display');
if (displayEl) {
stanza.display = displayEl.getText();
}
next();
}
}
|
| lib/junction/middleware/logger.js |
module.exports = function logger(options) {
options = options || {};
var stream = options.stream || process.stdout;
// @todo: Determine if there is a standard format for XMPP stanza logging.
return function logger(stanza, next) {
stream.write('RECV: ' + stanza.toString() + '\n');
next();
}
}
|
|
| lib/junction/middleware/message.js |
Module dependencies.
|
var events = require('events');
var util = require('util');
require('../../node-xmpp/element_ext');
|
Handle message stanzas.
This middleware allows applications to handle message stanzas. Applications
provide a callback(handler) which the middleware calls with an instance of
EventEmitter . Listeners can be attached to handler in order to process
presence stanza.
Events
chat the message is sent in the context of a one-to-one chat sessiongroupchat the message is sent in the context of a multi-user chat environmentnormal the message is a standalone message that is sent outside the context of a one-to-one conversation or multi-user chat environment, and to which it is expected that the recipient will replyheadline the message provides an alert, a notification, or other transient information to which no reply is expectederr an error has occurred regarding processing of a previously sent message stanza
Examples
connection.use(
junction.message(function(handler) {
handler.on('chat', function(stanza) {
console.log('someone is chatting!');
});
handler.on('groupchat', function(stanza) {
console.log('someone is group chatting!');
});
})
);
References
param: Function fn return: Function api: public
|
module.exports = function message(fn) {
if (!fn) throw new Error('message middleware requires a callback function');
var handler = new Handler();
fn.call(this, handler);
return function message(stanza, next) {
if (!stanza.isMessage()) { return next(); }
handler._handle(stanza);
next();
}
}
|
Inherit from EventEmitter .
|
util.inherits(Handler, events.EventEmitter);
|
| lib/junction/middleware/messageParser.js |
Parse message stanzas.
This middleware parses the standard elements contained in message stanzas.
stanza.subject indicates the topic of the message. stanza.body contains
the contents of the message. stanza.thread is used to identify a
conversation thread. stanza.parentThread is used to identify another
thread of which the current thread is an offshoot.
Examples
connection.use(junction.messageParser());
References
return: Function api: public
|
module.exports = function messageParser() {
return function messageParser(stanza, next) {
if (!stanza.is('message')) { return next(); }
var subject = stanza.getChild('subject');
if (subject) { stanza.subject = subject.getText(); }
var body = stanza.getChild('body');
if (body) { stanza.body = body.getText(); }
var thread = stanza.getChild('thread');
if (thread) {
stanza.thread = thread.getText();
stanza.parentThread = thread.attrs.parent;
}
next();
}
}
|
| lib/junction/middleware/nicknameParser.js |
Parse user nicknames.
This middleware parses user nicknames contained within presence subscription
requests and messages. stanza.nickname indicates the nickname, asserted by
the sending XMPP user.
Examples
connection.use(junction.nicknameParser());
References
return: Function api: public
|
module.exports = function nicknameParser() {
return function nicknameParser(stanza, next) {
if (!(stanza.is('message') || stanza.is('presence'))) { return next(); }
var nick = stanza.getChild('nick', 'http://jabber.org/protocol/nick');
if (!nick) { return next(); }
stanza.nickname = nick.getText();
next();
}
}
|
| lib/junction/middleware/pending/memorystore.js |
Module dependencies.
|
var util = require('util')
, Store = require('./store');
|
Initialize a new MemoryStore .
Options
timeout expire keys in the store after timeout milliseconds
|
function MemoryStore(options) {
options = options || {};
this._hash = {};
this._timeout = options.timeout;
}
|
Inherit from Store .
|
util.inherits(MemoryStore, Store);
|
Fetch data with the given key from the pending store.
param: String key param: Function fn api: public
|
MemoryStore.prototype.get = function(key, fn) {
var self = this;
process.nextTick(function(){
var data = self._hash[key];
if (data) {
data = JSON.parse(data);
fn(null, data);
} else {
fn();
}
});
}
|
Commit data associated with the given key to the pending store.
param: String key param: Object data param: Function fn api: public
|
MemoryStore.prototype.set = function(key, data, fn) {
var self = this;
process.nextTick(function(){
self._hash[key] = JSON.stringify(data);
fn && fn();
});
if (this._timeout) {
setTimeout(function() {
self.remove(key);
}, this._timeout);
}
};
|
Remove data associated with the given key from the pending store.
param: String key param: Function fn api: public
|
MemoryStore.prototype.remove = function(key, fn) {
var self = this;
process.nextTick(function(){
delete self._hash[key];
fn && fn();
});
};
|
Expose MemoryStore .
|
module.exports = MemoryStore;
|
| lib/junction/middleware/pending/store.js |
Initialize abstract Store .
|
function Store() {
}
|
Get data from the pending store.
This function must be overridden by subclasses. In abstract form, it always
throws an exception.
param: String key param: Function callback api: protected
|
Store.prototype.get = function(key, callback) {
throw new Error('Store#get must be overridden by subclass');
}
|
Set data to the pending store.
This function must be overridden by subclasses. In abstract form, it always
throws an exception.
param: String key param: Object data param: Function callback api: protected
|
Store.prototype.set = function(key, data, callback) {
throw new Error('Store#set must be overridden by subclass');
}
|
Remove data from the pending store.
This function must be overridden by subclasses. In abstract form, it always
throws an exception.
param: String key param: Function callback api: protected
|
Store.prototype.remove = function(key, callback) {
throw new Error('Store#remove must be overridden by subclass');
}
|
Expose Store .
|
module.exports = Store;
|
| lib/junction/middleware/pending.js |
Module dependencies.
|
var util = require('util')
, Store = require('./pending/store')
, MemoryStore = require('./pending/memorystore');
|
Setup pending store with the given options .
This middleware processes incoming response stanzas, restoring any pending
data set when the corresponding request was sent. Pending data is typically
set by applying filter() 's to the connection. The pending data can be
accessed via the stanza.irt property, also aliased to stanza.inReplyTo ,
stanza.inResponseTo and stanza.regarding . Data is (generally) serialized
as JSON by the store.
This allows a stateless, shared-nothing architecture to be utilized in
XMPP-based systems. This is particularly advantageous in systems employing
XMPP component connections with round-robin load balancing strategies. In
such a scenario, requests can be sent via one component instance, while the
result can be received and processed by an entirely separate component
instance.
Options
store pending store instanceautoRemove automatically remove data when the response is received. Defaults to true
Examples
connection.use(junction.pending({ store: new junction.pending.MemoryStore() }));
var store = new junction.pending.MemoryStore();
connection.filter(disco.filters.infoQuery());
connection.filter(junction.filters.pending({ store: store }));
connection.use(junction.pending({ store: store }));
connection.use(function(stanza, next) {
if (stanza.inResponseTo) {
console.log('response received!');
return;
}
next();
});
param: Object options return: Function api: public
|
function pending(options) {
options = options || {};
var store = options.store;
var autoRemove = (typeof options.autoRemove === 'undefined') ? true : options.autoRemove;
if (!store) throw new Error('pending middleware requires a store');
return function pending(stanza, next) {
if (!stanza.id || !(stanza.type == 'result' || stanza.type == 'error')) {
return next();
}
var key = stanza.from + ':' + stanza.id;
store.get(key, function(err, data) {
if (err) {
next(err);
} else if (!data) {
next();
} else {
stanza.irt =
stanza.inReplyTo =
stanza.inResponseTo =
stanza.regarding = data;
if (autoRemove) { store.remove(key); }
next();
}
});
}
}
|
Expose the middleware.
|
exports = module.exports = pending;
|
Expose constructors.
|
exports.Store = Store;
exports.MemoryStore = MemoryStore;
|
| lib/junction/middleware/ping.js |
Module dependencies.
|
var StanzaError = require('../stanzaerror');
|
Handle application-level pings sent over XMPP stanzas.
Examples
connection.use(junction.ping());
References
return: Function api: public
|
module.exports = function ping() {
return function ping(req, res, next) {
if (!req.is('iq')) { return next(); }
var ping = req.getChild('ping', 'urn:xmpp:ping');
if (!ping) { return next(); }
if (req.type != 'get') {
return next(new StanzaError("Ping must be an IQ-get stanza.", 'modify', 'bad-request'));
}
res.send();
}
}
|
| lib/junction/middleware/presence.js |
Module dependencies.
|
var events = require('events');
var util = require('util');
require('../../node-xmpp/element_ext');
|
Handle presence stanzas.
This middleware allows applications to handle presence stanzas. Applications
provide a callback(handler) which the middleware calls with an instance of
EventEmitter . Listeners can be attached to handler in order to process
presence stanza.
Events
available the sending entity is available for communicationunavailable the sending entity is no longer available for communicationprobe the sending entity requests the recipient's current presencesubscribe the sending entity wishes to subscribe to the recipient's presencesubscribed the sending entity has allowed the recipient to receive their presenceunsubscribe the sending entity is unsubscribing from the recipient's presenceunsubscribed the sending entity has denied a subscription request or has canceled a previously granted subscriptionerr an error has occurred regarding processing of a previously sent presence stanza
Examples
connection.use(
junction.presence(function(handler) {
handler.on('available', function(stanza) {
console.log('someone is available!');
});
handler.on('unavailable', function(stanza) {
console.log('someone is unavailable.');
});
})
);
References
param: Function fn return: Function api: public
|
module.exports = function presence(fn) {
if (!fn) throw new Error('presence middleware requires a callback function');
var handler = new Handler();
fn.call(this, handler);
return function presence(stanza, next) {
if (!stanza.isPresence()) { return next(); }
handler._handle(stanza);
next();
}
}
|
Inherit from EventEmitter .
|
util.inherits(Handler, events.EventEmitter);
|
| lib/junction/middleware/presenceParser.js |
Parse presence stanzas.
This middleware parses the standard elements contained in presence stanzas.
stanza.show indicates the availability sub-state of an entity.
stanza.status contains a human-readable description of an entity's
availability. stanza.priority specifies the priority level of the
resource.
Examples
connection.use(junction.presenceParser());
References
return: Function api: public
|
module.exports = function presenceParser() {
return function presenceParser(stanza, next) {
if (!stanza.is('presence')) { return next(); }
var show = stanza.getChild('show');
if (show) {
stanza.show = show.getText();
} else if (!stanza.attrs.type) {
stanza.show = 'online';
}
var status = stanza.getChild('status');
if (status) { stanza.status = status.getText(); }
var priority = stanza.getChild('priority');
if (priority) {
stanza.priority = parseInt(priority.getText());
} else {
stanza.priority = 0;
}
next();
}
}
|
| lib/junction/middleware/serviceDiscovery.js |
Module dependencies.
|
var util = require('util');
var StanzaError = require('../stanzaerror');
|
Handle requests for discovering information about an XMPP entity
This middleware handles IQ-get requests for information about the identity
and capabilities of an XMPP entity. These requests are contained within the
http://jabber.org/protocol/disco#info XML namespace.
This middleware is intentionally simple, providing only the most basic, yet
widely used, features of the service discovery protocol. Specifically, it
has no support for item discovery or for querying nodes. For a
feature-complete implementation of service discovery, Junction/Disco
should be used.
Examples
connection.use(
junction.serviceDiscovery([ { category: 'conference', type: 'text', name: 'Play-Specific Chatrooms' },
{ category: 'directory', type: 'chatroom' } ],
[ 'http://jabber.org/protocol/disco#info',
'http://jabber.org/protocol/muc' ])
);
connection.use(
junction.serviceDiscovery( { category: 'client', type: 'pc' },
'http://jabber.org/protocol/disco#info' )
);
References
|
module.exports = function serviceDiscovery(identities, features) {
identities = identities || [];
features = features || [];
if (!Array.isArray(identities)) { identities = [ identities ]; }
if (!Array.isArray(features)) { features = [ features ]; }
return function serviceDiscovery(req, res, next) {
if (!req.is('iq')) { return next(); }
if (req.type == 'result' || req.type == 'error') { return next(); }
var query = req.getChild('query', 'http://jabber.org/protocol/disco#info');
if (!query) { return next(); }
if (req.type != 'get') {
return next(new StanzaError("Query must be an IQ-get stanza.", 'modify', 'bad-request'));
}
if (query.attrs.node) {
return next(new StanzaError("", 'cancel', 'item-not-found'));
}
var info = res.c('query', { xmlns: 'http://jabber.org/protocol/disco#info' });
identities.forEach(function(identity) {
var ide = info.c('identity', { category: identity.category,
type: identity.type });
if (identity.name) { ide.attrs.name = identity.name };
});
features.forEach(function(feature) {
info.c('feature', { var: feature });
});
res.send();
}
}
|
| lib/junction/middleware/serviceUnavailable.js |
Module dependencies.
|
var StanzaError = require('../stanzaerror');
require('../../node-xmpp/element_ext');
|
Respond with service-unavailable error to IQ request stanzas.
This middleware responds with a service-unavailable stanza error to any
IQ-get or IQ-set stanza. This error is used to indicate that an entity does
not provide the requested service.
This middleware should be use() -ed at the lowest priority level. This
allows higher-priority middleware an opportunity to process the stanza. If
all higher-priority middleware pass on that opportunity, the stanza will be
handled by this middleware and a service-unavailable error will be sent to
the requesting entity.
Examples
connection.use(junction.ping());
// TODO: Use other application-specific middleware here.
connection.use(junction.serviceUnavailable());
References
return: Function api: public
|
module.exports = function serviceUnavailable() {
return function serviceUnavailable(req, res, next) {
if (!req.isIQ()) { return next(); }
res.attrs.type = 'error';
res.c('error', { type: 'cancel' }).c('service-unavailable', { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' });
res.send();
}
}
|
| lib/junction/middleware/softwareVersion.js |
Module dependencies.
|
var StanzaError = require('../stanzaerror');
|
Handle requests for information about the software application associated
with an XMPP entity.
This middleware handles IQ-get requests within the jabber:iq:version XML
namespace. The middleware responds with the application name, version, and
operating system.
Examples
connection.use(junction.softwareVersion('IMster', '1.0', 'Linux'));
References
param: String name param: String version param: String os return: Function api: public
|
module.exports = function softwareVersion(name, version, os) {
return function softwareVersion(req, res, next) {
if (!req.is('iq')) { return next(); }
if (req.type == 'result' || req.type == 'error') { return next(); }
var query = req.getChild('query', 'jabber:iq:version');
if (!query) { return next(); }
if (req.type != 'get') {
return next(new StanzaError("Query must be an IQ-get stanza.", 'modify', 'bad-request'));
}
var ver = res.c('query', { xmlns: 'jabber:iq:version' });
if (name) { ver.c('name').t(name); }
if (version) { ver.c('version').t(version); }
if (os) { ver.c('os').t(os); }
res.send();
}
}
|
| lib/junction/middleware/softwareVersionResultParser.js |
Parse information about the software application associated with an XMPP
entity.
This middleware parses software application information contained within
IQ-result stanzas. stanza.application indicates the name of the software
application. stanza.version indicates the specific version of the
application. stanza.os indicates the operating system on which the
application is executing.
Examples
connection.use(junction.softwareVersionResultParser());
References
return: Function api: public
|
module.exports = function softwareVersionResultParser() {
return function softwareVersionResultParser(stanza, next) {
if (!stanza.is('iq')) { return next(); }
if (stanza.type != 'result') { return next(); }
var query = stanza.getChild('query', 'jabber:iq:version');
if (!query) { return next(); }
var nameEl = query.getChild('name');
if (nameEl) {
stanza.application = nameEl.getText();
}
var versionEl = query.getChild('version');
if (versionEl) {
stanza.version = versionEl.getText();
}
var osEl = query.getChild('os');
if (osEl) {
stanza.os = osEl.getText();
}
next();
}
}
|
| lib/junction/middleware/time.js |
Module dependencies.
|
var StanzaError = require('../stanzaerror');
|
Handle requests for the local time of an XMPP entity.
This middleware handles IQ-get requests within the urn:xmpp:time XML
namespace. The middleware responds with the UTC time of the entity, as well
as the offset from UTC.
Examples
connection.use(junction.time());
References
param: Object options return: Function api: public
|
module.exports = function time(options) {
options = options || {};
return function time(req, res, next) {
if (!req.is('iq')) { return next(); }
if (req.type == 'result' || req.type == 'error') { return next(); }
var te = req.getChild('time', 'urn:xmpp:time');
if (!te) { return next(); }
if (req.type != 'get') {
return next(new StanzaError("Time must be an IQ-get stanza.", 'modify', 'bad-request'));
}
var now = options.date || new Date();
var tzo = (typeof options.timezoneOffset !== 'undefined') ? options.timezoneOffset : now.getTimezoneOffset();
var t = res.c('time', { xmlns: 'urn:xmpp:time' });
t.c('utc').t(XSDDateTimeString(now));
t.c('tzo').t(XSDTimeZoneString(tzo));
res.send();
}
}
function XSDDateTimeString(d) {
function pad(n) { return n < 10 ? '0' + n : n }
return d.getUTCFullYear() + '-'
+ pad(d.getUTCMonth() + 1) + '-'
+ pad(d.getUTCDate()) + 'T'
+ pad(d.getUTCHours()) + ':'
+ pad(d.getUTCMinutes()) + ':'
+ pad(d.getUTCSeconds()) + 'Z'
}
function XSDTimeZoneString(tzo) {
function sign(n) { return n > 0 ? '+' : '-' }
function pad(n) { return n < 10 ? '0' + n : n.toString() }
return sign(0 - tzo)
+ pad(Math.floor(Math.abs(tzo) / 60)) + ':'
+ pad(Math.abs(tzo) % 60);
}
|
| lib/junction/middleware/timeResultParser.js |
Parse information about the local time of an XMPP entity.
This middleware parses local time information contained within IQ-result
stanzas. stanza.utcDate indicates the UTC time according to the responding
entity. stanza.timezoneOffset indicates the timezone offset from UTC, in
minutes, for the responding entity.
The time-zone offset is the difference, in minutes, between UTC and local
time. Note that this means that the offset is positive if the local timezone
is behind UTC and negative if it is ahead. For example, if your time zone is
UTC+10 (Australian Eastern Standard Time), -600 will be returned. This
matches the semantics of getTimezoneOffset() provided by JavaScript's
built-in Date class.
Examples
connection.use(junction.timeResultParser());
References
return: Function api: public
|
module.exports = function timeResultParser() {
return function timeResultParser(stanza, next) {
if (!stanza.is('iq')) { return next(); }
if (stanza.type != 'result') { return next(); }
var time = stanza.getChild('time', 'urn:xmpp:time');
if (!time) { return next(); }
var utcEl = time.getChild('utc');
if (utcEl) {
stanza.utcDate = new Date(utcEl.getText());
}
var tzoEl = time.getChild('tzo');
if (tzoEl) {
stanza.timezoneOffset = timezoneOffsetFromUTC(tzoEl.getText());
}
next();
}
}
function timezoneOffsetFromUTC(tzo) {
function parse(s) {
var match = /([+\-])([0-9]{2}):(\d{2})/.exec(s);
if (match) {
var min = (parseInt(match[2], 10) * 60) + parseInt(match[3], 10);
return match[1] == '+' ? min : 0 - min;
}
return 0;
}
return 0 - parse(tzo);
}
|
| lib/junction/nullstream.js |
Module dependencies.
|
var util = require('util')
, EventEmitter = require('events').EventEmitter;
|
Inherit from EventEmitter .
|
util.inherits(NullStream, EventEmitter);
|
Expose NullStream .
|
module.exports = NullStream;
|
| lib/junction/stanzaerror.js |
Module dependencies.
|
var util = require('util');
|
Initialize a new StanzaError .
param: String message param: String type param: String condition api: public
|
function StanzaError(message, type, condition) {
Error.apply(this, arguments);
Error.captureStackTrace(this, arguments.callee);
this.name = 'StanzaError';
this.message = message || null;
this.type = type || 'wait';
this.condition = condition || 'internal-server-error';
};
|
Inherit from Error .
|
util.inherits(StanzaError, Error);
|
Expose StanzaError .
|
exports = module.exports = StanzaError;
|