1
0
Fork 0

ADDED - Inbout Bytestream support (S5b XEP-00065)

ADDED - Presence tracking for chat and muc
FIXED - MUC now works properly
ADDED - Time and Last IQ
ADDED - Idle time
master
Matthieu Lalonde 14 years ago committed by xSmurf
parent d5b97a21b0
commit 027dd2fee5

@ -1,3 +1,8 @@
exports.BasicClient = require('./xmpp-client/basic-client').BasicClient; exports.BasicClient = require('./xmpp-client/basic-client').BasicClient;
exports.Client = require('./xmpp-client/client').Client; exports.Client = require('./xmpp-client/client').Client;
exports.Jid = require('./xmpp-client/client').Jid; exports.Jid = require('./xmpp-client/client').Jid;
exports.Room = require('./xmpp-client/room').Room;
exports.BytestreamServer = require('./xmpp-client/bytestream-server').BytestreamServer;
exports.Bytestream = require('./xmpp-client/bytestream').Bytestream;
exports.Pubsub = require('./xmpp-client/bytestream').Pubsub;
exports.XMLNS = require('./xmpp-client/xmlns');

@ -10,39 +10,71 @@ var sys = require('sys'),
var BasicClient = function(params, callback) { var BasicClient = function(params, callback) {
events.EventEmitter.call(this); events.EventEmitter.call(this);
this.idle = (new Date().getTime());
this._iq = 0; this._iq = 0;
this._iqCallback = {}; this._iqCallback = {};
this._iqHandler = {}; this._iqHandler = {};
var jabber = this; var jabber = this;
this.params = params;
var jid = new xmpp.JID(params.jid); var jid = new xmpp.JID(params.jid);
this.host = (params.host == null) ? jid.domain : params.host; this.params.host = (params.host == null) ? jid.domain : params.host;
this.params.nickname = (params.resource == null) ? jid.user : params.resource;
this.xmpp = new xmpp.Client(params); this.xmpp = new xmpp.Client(params);
/*
this.xmpp.addListener('rawStanza', function(stanza) { this.xmpp.addListener('rawStanza', function(stanza) {
//sys.debug("RAW: "[jabber.color] + stanza.toString().white); sys.debug("RAW: "[jabber.color] + stanza.toString().white);
}); });
this.xmpp.addListener('authFail', function() { */
sys.error("[Error] Jabber : Authentication failure"); this.xmpp.addListener('authFail', function(e) {
process.exit(1); jabber.emit('authFail', e);
}); });
this.xmpp.addListener('error', function(e) { this.xmpp.addListener('error', function(e) {
sys.error(e); jabber.emit('error', e);
process.exit(1);
}); });
this.xmpp.addListener('stanza', function(stanza) { this.xmpp.addListener('stanza', function(stanza) {
switch(stanza.name) { switch(stanza.name) {
case 'iq': case 'iq':
switch(stanza.attrs.type) { switch(stanza.attrs.type) {
case 'error': case 'error':
var q = stanza.getChild('query');
if(q !== undefined) {
if(q.attrs.xmlns != null && jabber._iqHandler[q.attrs.xmlns] != null) {
return jabber._iqHandler[q.attrs.xmlns].call(jabber, stanza);
}
}
if (typeof jabber._iqCallback[stanza.attrs.id] !== "undefined") {
jabber._iqCallback[stanza.attrs.id].error.apply(jabber, [stanza]); jabber._iqCallback[stanza.attrs.id].error.apply(jabber, [stanza]);
}
break; break;
case 'result': case 'result':
if (typeof jabber._iqCallback[stanza.attrs.id] === "undefined") {
var q = stanza.getChild('query');
if(q !== undefined) {
if(q.attrs.xmlns != null && jabber._iqHandler[q.attrs.xmlns] != null) {
return jabber._iqHandler[q.attrs.xmlns].call(jabber, stanza);
}
}
} else if (typeof jabber._iqCallback[stanza.attrs.id] !== "undefined") {
jabber._iqCallback[stanza.attrs.id].success.apply(jabber, [stanza]); jabber._iqCallback[stanza.attrs.id].success.apply(jabber, [stanza]);
}
break; break;
default: default:
jabber.emit('iq', stanza); jabber.emit('iq', stanza);
var q = stanza.getChild('query'); var q = stanza.getChild('query');
if(q == undefined) { if(q == undefined) {
var q = stanza.getChild('time');
if(q !== undefined) {
if(q.attrs.xmlns != null && jabber._iqHandler[q.attrs.xmlns] != null) {
jabber._iqHandler[q.attrs.xmlns].call(jabber, stanza);
} else {
jabber.emit('iq:unknow', stanza);
}
} else {
jabber.emit('iq:unknow', stanza); jabber.emit('iq:unknow', stanza);
}
} else { } else {
if(q.attrs.xmlns != null && jabber._iqHandler[q.attrs.xmlns] != null) { if(q.attrs.xmlns != null && jabber._iqHandler[q.attrs.xmlns] != null) {
jabber._iqHandler[q.attrs.xmlns].call(jabber, stanza); jabber._iqHandler[q.attrs.xmlns].call(jabber, stanza);
@ -54,17 +86,120 @@ var BasicClient = function(params, callback) {
} }
break; break;
case 'presence': case 'presence':
var newPresence = false;
if(stanza.attrs.type == 'error') { if(stanza.attrs.type == 'error') {
jabber.emit('presence:error', stanza); jabber.emit('presence:error', stanza);
} else { } else {
jabber.emit('presence', stanza); // Skip self presence
if (jabber.jid.user + "@" + jabber.jid.domain + "/" + jabber.jid.resource === stanza.attrs.from) {
return false;
}
try {
// This is a presences for a conference
if (stanza.attrs.from.indexOf('conference') === -1 || stanza.attrs.from.indexOf('conference') < stanza.attrs.from.indexOf('@')) {
if (stanza.attrs.from.toString().indexOf('/') > -1) {
var user = stanza.attrs.from.split('/')[0];
var resource = stanza.attrs.from.substr(stanza.attrs.from.indexOf('/')+1);
} else {
var user = stanza.attrs.from.toString();
var resource = "unknown";
}
if (typeof jabber.presences.users === "undefined" || jabber.presences.users === null) {
jabber.presences.users = {};
}
if (stanza.attrs.type === "unavailable") {
if (typeof jabber.presences.users[user] !== "undefined" && jabber.presences[user] !== null) {
delete jabber.presences[user][resource];
if (Object.keys(jabber.presences[user]).length === 0) {
delete jabber.presences[user];
}
}
} else {
if (typeof jabber.presences.users[user] === "undefined") {
jabber.presences.users[user] = {};
}
if (typeof jabber.presences.users[user][resource] === "undefined") {
jabber.presences.users[user][resource] = {};
newPresence = true;
}
jabber.presences.users[user][resource] = {
user: user,
resource: resource,
jid: stanza.attrs.from,
priority: stanza.getChildText('priority'),
show: stanza.getChildText('show'),
status: stanza.getChildText('status')
};
}
} else {
var conference = stanza.attrs.from.split('/')[0];
var nickname = stanza.attrs.from.substr(stanza.attrs.from.indexOf('/')+1);
// Kill self references
if (nickname === jabber.params.nickname) {
return false;
}
if (stanza.attrs.type === "unavailable") {
delete jabber.presences.muc[conference][nickname];
} else {
if (typeof jabber.presences.muc[conference] === "undefined") {
jabber.presences.muc[conference] = {};
}
if (typeof jabber.presences.muc[conference][nickname] === "undefined") {
jabber.presences.muc[conference][nickname] = {};
newPresence = true;
}
jabber.presences.muc[conference][nickname] = {
priority: stanza.getChildText("priority"),
show: stanza.getChildText("show"),
status: stanza.getChildText("status")
};
// Look to see if we have access to the user"s full details or if this is an anonymous room
if (typeof stanza.getChild("x", "http://jabber.org/protocol/muc#user") !== "undefined") {
var userDetails = stanza.getChild("x", "http://jabber.org/protocol/muc#user").getChild("item");
jabber.presences.muc[conference][nickname].affiliation = userDetails.attrs.affiliation;
jabber.presences.muc[conference][nickname].role = userDetails.attrs.role;
if (typeof userDetails.attrs.jid !== "undefined") {
var user = userDetails.attrs.jid.split("/")[0];
var resource = userDetails.attrs.jid.split("/")[1];
jabber.presences.muc[conference][nickname].jid = user;
jabber.presences.muc[conference][nickname].resource = resource;
}
}
}
}
jabber.emit('presence', stanza, newPresence);
} catch (err) {
console.log("Something went wrong parsing presences: " + err.toString());
console.log(stanza.toString());
console.log(err.stack);
}
} }
break; break;
case 'message': case 'message':
jabber.emit('message', stanza); jabber.emit('message', stanza);
break; break;
default:
console.log(stanza.toString());
break;
} }
}); });
this.xmpp.addListener('online', function() { this.xmpp.addListener('online', function() {
jabber.jid = this.jid; jabber.jid = this.jid;
jabber.emit('online'); jabber.emit('online');
@ -75,25 +210,49 @@ var BasicClient = function(params, callback) {
sys.inherits(BasicClient, events.EventEmitter); sys.inherits(BasicClient, events.EventEmitter);
exports.BasicClient = BasicClient; exports.BasicClient = BasicClient;
BasicClient.prototype.unidle = function() {
this.idle = (new Date().getTime());
};
BasicClient.prototype.message = function(to, message) { BasicClient.prototype.message = function(to, message) {
this.xmpp.send(new xmpp.Element('message', { this.xmpp.send(new xmpp.Element('message', {
to: to, to: to,
type: 'chat'}) type: 'chat'})
.c('body').t(message)); .c('body').t(message));
this.unidle();
}; };
BasicClient.prototype.presence = function(type) { BasicClient.prototype.presence = function(status, message)
this.xmpp.send(new xmpp.Element('presence', (type != null) ? {type: type} : {}).tree()); {
if (typeof status === "undefined")
{
var status = "";
}
if (typeof message === "undefined")
{
var message = "";
}
this.xmpp.send(new xmpp.Element('presence', { type: 'chat'}).
c('show').t(status).up().
c('status').t(message)
);
}; };
BasicClient.prototype.iq = function(to, query, callback, error) { BasicClient.prototype.iq = function(to, query, callback, error, type) {
if(error == undefined) error = function(stanza) { sys.error((this.jid + " : " + stanza.toString()).red);}; if(error == undefined) error = function(stanza) { sys.error((this.jid + " : " + stanza.toString()).red);};
var n = 'node' + this._iq++; var n = 'node' + this._iq++;
this._iqCallback[n] = {}; this._iqCallback[n] = {};
this._iqCallback[n].success = callback; this._iqCallback[n].success = callback;
this._iqCallback[n].error = error; this._iqCallback[n].error = error;
if (typeof type === "undefined") {
var type = "get";
}
var attrs = { var attrs = {
type: 'get', type: type,
id: n id: n
}; };
if(to != null) { if(to != null) {
@ -107,12 +266,16 @@ BasicClient.prototype.iq = function(to, query, callback, error) {
Answer an iq query Answer an iq query
*/ */
BasicClient.prototype.resultIq = function(iqGet, result) { BasicClient.prototype.resultIq = function(iqGet, result) {
if (typeof iqGet === "undefined" || typeof result === "string") {
this.xmpp.send(result);
} else {
this.xmpp.send(new xmpp.Element('iq', { this.xmpp.send(new xmpp.Element('iq', {
type: 'result', type: 'result',
from: iqGet.attrs.to, from: iqGet.attrs.to,
to: iqGet.attrs.from, to: iqGet.attrs.from,
id: iqGet.attrs.id id: iqGet.attrs.id
}).cnode(result).tree()); }).cnode(result).tree());
}
}; };
BasicClient.prototype.registerIqHandler = function(xmlns, action) { BasicClient.prototype.registerIqHandler = function(xmlns, action) {

@ -0,0 +1,141 @@
var sys = require("sys"),
net = require("net"),
XMLNS = require("./xmlns.js"),
xmpp = require("node-xmpp"),
colors = require("colors"),
events = require("events");
var BytestreamServer = function(client) {
var self = this;
this.client = client;
this.port = (client.params.s5bPort == null) ? 8010 : client.params.s5bPort;
this.host = (client.params.s5bHost == null) ? "proxy." + client.params.host : client.params.s5bHost;
this._clients = {};
this._handlers = {};
this.tcpSocket = net.createServer(function(stream) {self.handleConnection(stream);});
this.tcpSocket.on("error", function(error) {
console.log("Error with S5B server!");
console.log(error);
});
this.tcpSocket.listen(this.port, "0.0.0.0");
};
exports.BytestreamServer = BytestreamServer;
BytestreamServer.prototype.close = function() {
for (ii in this._clients) {
this._clients[ii].end();
}
this.tcpSocket.close();
};
BytestreamServer.prototype.addHandler = function(iqId, sidHash, data, cbSuccess, cbFailure) {
this._handlers[sidHash] = {id: iqId, data: data, cbSuccess:cbSuccess, cbFailure:cbFailure};
};
BytestreamServer.prototype.handleConnection = function(stream) {
var self = this;
this._clients[stream.remoteAddress] = new BytestreamHandler(stream, self);
this._clients[stream.remoteAddress].established = false;
this._clients[stream.remoteAddress].transfered = false;
stream.on("error", function(error) {
console.log("S5B stream error from "+ stream.remoteAddress);
console.log(error);
stream.end();
delete self._clients[stream.remoteAddress];
});
stream.on("connect", this._clients[stream.remoteAddress].onConnect);
stream.on("data", this._clients[stream.remoteAddress].onData);
stream.on("end", this._clients[stream.remoteAddress].onEnd);
};
BytestreamServer.prototype.iq = function(bytestream, query, to, id) {
var self = this;
var jabber = self.client;
if (typeof to === "undefined") {
var to = bytestream.to;
}
if (typeof id === "undefined") {
var id = bytestream.idIq;
}
var params = {
type: "set",
id: id,
to: to
};
jabber.xmpp.send(new xmpp.Element('iq', params).cnode(query).tree());
};
var BytestreamHandler = function(self, parent) {
self.parent = parent;
self.iqId = null;
this.onConnect = function(data) {};
this.onData = function(data) {
// TODO: Disconnect client when BS data is entered
// just a start
if (data[0] !== 0x05 && data[1] > 0x0F) {
console.log("Received erroneous data from " + self.remoteAddress);
self.end();
}
if (self.parent._clients[self.remoteAddress].established === false
&& data.length >= 1 && data[0] === 0x05) {
if (data.length >= 3 && (data[1] === 0x01 || data[1] === 0x02) && data[2] === 0x00) {
if (data[1] === 0x01 || (data.length >= 4 && data[3] === 0x02)) {
self.parent._clients[self.remoteAddress].established = true;
self.write(new Buffer([0x05,0x00])); // Ack
}
}
} else if (self.parent._clients[self.remoteAddress].established === true && data.length == 47
&& data[data.length - 1] === 0x00 && data[data.length - 2] === 0x00) {
var reqHash = data.toString("ascii", 5, 45);
if ((reqHash in self.parent._handlers) === true) {
var buff = [0x05,0x00,0x00,0x03]; // Request header
buff.push(reqHash.length); // Announce data length
// Push our reqHash in the buffer
reqHash.split("").forEach(function(val) {
buff.push(val.charCodeAt(0));
});
// DST.PORT is two bytes
buff.push(0x00, 0x00);
// Add iq handler for receiving the target's ack
self.iqId = self.parent._handlers[reqHash].id;
self.parent.client._iqCallback[self.iqId] = {};
self.parent.client._iqCallback[self.iqId].success = function(data) {
if (typeof self.parent._handlers[reqHash].data === "function") {
self.parent._handlers[reqHash].data(self);
} else {
self.write(self.parent._handlers[reqHash].data);
}
self.parent._clients[self.remoteAddress].transfered = true;
delete self.parent.client._iqCallback[self.iqId];
};
self.parent.client._iqCallback[self.iqId].error = function(error) {
console.log(error);
};
self.write(new Buffer(buff));
}
}
};
this.onEnd = function(data) {
delete self.parent._clients[self.remoteAddress];
};
};

@ -0,0 +1,214 @@
var sys = require("sys"),
net = require("net"),
XMLNS = require("./xmlns.js"),
xmpp = require("node-xmpp"),
colors = require("colors"),
events = require("events"),
hash = require("../../../node_hash/lib/hash");
var Bytestream = function(server, file, to, idIq) {
var bytestream = this;
this.server = server;
this.client = server.client;
this.idIq = idIq;
this.to = to;
this.sidId = this.idIq + "_" + (new Date().getTime());
this.sidHash = hash.sha1(this.sidId + this.client.jid.toString() + this.to);
this.file = file;
/*
// FIXME
if (("mimeType" in this.file) === false) {
this.file.mimeType = "plain/text";
}
if (("length" in this.file) === false || typeof this.file.data.length !== "undefined") {
this.file.length = this.file.data.length;
}
*/
this.sendInitiate();
};
sys.inherits(Bytestream, events.EventEmitter);
exports.Bytestream = Bytestream;
Bytestream.prototype.sendInitiate = function() {
var self = this;
var jabber = self.client;
// Define the iqCallback for the negotiation stream
jabber._iqCallback[self.idIq] = {};
jabber._iqCallback[self.idIq].success = function(jabber) {
self.sendFile();
};
jabber._iqCallback[self.idIq].error = function(error) {
var declined = false;
if (typeof error !== "undefined") {
var error = error.getChild("error");
if (typeof error.getChild("forbidden") !== "undefined") {
self.client.message(self.to, "Ok, maybe some other time!");
declined = true;
}
}
if (declined !== true) {
console.log(error.toString());
}
delete jabber._iqCallback[self.idIq];
};
// Build and send the data negotiation request
self.server.iq(self,
new xmpp.Element
("si", {xmlns: XMLNS.SI, profile: XMLNS.SI_PROFILE, id: self.sidId, "mime-type": self.file.mimeType})
.c("file", {xmlns: XMLNS.SI_PROFILE, size: self.file.length, name: self.file.name})
.c("desc").t((typeof self.file.desc !== "undefined" ? self.file.desc : "")).up()
.c("range").up()
.up()
.c("feature", {xmlns: XMLNS.FEATURE_NEG})
.c("x", {xmlns: XMLNS.DATA, type: "form"})
.c("field", {type: "list-single", "var": "stream-method"})
.c("option")
.c("value").t(XMLNS.BYTESTREAMS)
.up()
.up()
.up()
.up()
.up()
.up()
.tree()
);
};
Bytestream.prototype.sendFile = function() {
var self = this;
var jabber = self.client;
// Define the callback for streamhost negotiation
// This might get overwritten is the internal bytestream server is contacted by the client
jabber._iqCallback[self.idIq] = {};
jabber._iqCallback[self.idIq].success = function(stanza) {
if (typeof stanza.getChild("query") !== "undefined"
&& typeof stanza.getChild("query").getChild("streamhost-used") !== "undefined") {
var usedStream = stanza.getChild("query").getChild("streamhost-used").attrs.jid.toString();
// We connected through the local S5B
if (usedStream === self.client.jid.toString()) {
// Nothing here really, we only got this after the session is ready and this has been overwritten
// Connected through the server's S5B proxy
} else {
self.sendProxy(usedStream);
}
}
};
jabber._iqCallback[self.idIq].error = function(error) {
console.log(error);
delete jabber._iqCallback[self.idIq];
};
self.server.iq(self,
new xmpp.Element
('query', {xmlns: XMLNS.BYTESTREAMS, sid: self.sidId})
// S5B Direct
.c("streamhost", {jid: jabber.jid.toString(), host: self.server.host, port: self.server.port}).up()
// S5B Proxy
.c("streamhost", {jid: self.client.params.proxyJid, host: self.client.params.proxyHost, port: self.client.params.proxyPort}).up()
.tree()
);
self.server.addHandler(self.idIq, self.sidHash, self.file.data);
};
Bytestream.prototype.sendProxy = function(streamProxy) {
var self = this;
var jabber = this.client;
var client = net.createConnection(7777, streamProxy);
client.addListener("error", function(error) {
console.log("Error with S5B proxy connection for "+ self.to +" "+ client.remoteAddress);
console.log(error);
});
client.addListener("connect", function() {
var connected = false;
client.write(new Buffer([0x05,0x01,0x00])); // CONNECT
client.addListener("data", function(data) {
if (data[0] !== 0x05) {
return;
}
// Ack
if (connected === false && data.length === 0x02 && data[1] === 0x00) {
var buff = [0x05,0x01,0x0,0x03]; // Request header
buff.push(self.sidHash.length); // Announce data length
// Push our sidHash in the buffer
self.sidHash.split("").forEach(function(val) {
buff.push(val.charCodeAt(0));
});
// DST.PORT is two bytes
buff.push(0x00, 0x00);
client.write(new Buffer(buff));
connected = true;
} else if (connected === true && data.length == 47 && data[1] === 0x00) {
// Request Activate
var reqHash = data.toString("ascii", 5, 5 + self.sidHash.length);
if (reqHash === self.sidHash) {
var iqId = "S5B_"+ (new Date().getTime()).toString();
// Register activation callback
jabber._iqCallback[iqId] = {};
jabber._iqCallback[iqId].success = function(stanza) {
if (stanza.attrs.from.toString() === streamProxy
&& stanza.attrs.type.toString() === "result") {
// Send data! Finally!
if (typeof self.file.data === "function") {
self.file.data(client);
} else {
client.write(self.file.data);
}
delete jabber._iqCallback[self.iqId];
}
};
jabber._iqCallback[iqId].error = function(error) {
console.log("Activation error");
console.log(error.toString());
delete jabber._iqCallback[self.idIq];
};
self.server.iq(self,
new xmpp.Element('query', {xmlns: XMLNS.BYTESTREAMS, sid: self.sidId})
.c("activate").t(self.to)
.up()
.tree(),
streamProxy,
iqId
);
}
} else {
console.log(data);
}
});
client.addListener("close", function(data) {
sys.puts("Disconnected from server");
});
});
};

@ -67,7 +67,7 @@ var Client = function(params, callback) {
} else { } else {
jabber._debug('MESSAGE: ' + stanza); jabber._debug('MESSAGE: ' + stanza);
var event_ = stanza.getChild('event', 'http://jabber.org/protocol/pubsub#event'); var event_ = stanza.getChild('event', 'http://jabber.org/protocol/pubsub#event');
sys.debug(event_.toString().green); // sys.debug(event_.toString().green);
if(event_ != null) { if(event_ != null) {
jabber.emit('pubsub:event', from, event_.getChild('items').attrs.node, event_, stanza); jabber.emit('pubsub:event', from, event_.getChild('items').attrs.node, event_, stanza);
} else { } else {
@ -111,7 +111,7 @@ var Client = function(params, callback) {
} }
}); });
this.registerIqHandler('http://jabber.org/protocol/disco#info', function(stanza) { this.registerIqHandler('http://jabber.org/protocol/disco#info', function(stanza) {
sys.debug((stanza.attrs.from + " wont to disco!")[jabber.color]); // sys.debug((stanza.attrs.from + " wont to disco!")[jabber.color]);
jabber.resultIq(stanza, new xmpp.Element('query', {xmlns: 'http://jabber.org/protocol/disco#info'}) jabber.resultIq(stanza, new xmpp.Element('query', {xmlns: 'http://jabber.org/protocol/disco#info'})
.c('feature', {'var': 'http://jabber.org/protocol/disco#info'}).up() .c('feature', {'var': 'http://jabber.org/protocol/disco#info'}).up()
.c('feature', {'var': 'http://jabber.org/protocol/disco#items'}).up() .c('feature', {'var': 'http://jabber.org/protocol/disco#items'}).up()
@ -125,7 +125,7 @@ var Client = function(params, callback) {
); );
}); });
this.registerIqHandler('jabber:iq:last', function(stanza) { this.registerIqHandler('jabber:iq:last', function(stanza) {
sys.debug((stanza.attrs.from + ' wonts last')[jabber.color]); // sys.debug((stanza.attrs.from + ' wonts last')[jabber.color]);
//[FIXME] giving a good last time //[FIXME] giving a good last time
jabber.resultIq(stanza, new xmpp.Element('query', { jabber.resultIq(stanza, new xmpp.Element('query', {
xmlns: 'jabber:iq:last', seconds:'1'}) xmlns: 'jabber:iq:last', seconds:'1'})
@ -141,7 +141,7 @@ var Client = function(params, callback) {
); );
}); });
this.addListener('iq', function(stanza) { this.addListener('iq', function(stanza) {
sys.debug(stanza.getChild('query').toString().yellow); // sys.debug(stanza.getChild('query').toString().yellow);
}); });
this.addListener('iq:error', function(id, stanza) { this.addListener('iq:error', function(id, stanza) {
this._debug(stanza.toString().red.invert); this._debug(stanza.toString().red.invert);
@ -153,7 +153,7 @@ exports.Client = Client;
Client.prototype._debug = function(txt) { Client.prototype._debug = function(txt) {
if(this.debug) { if(this.debug) {
sys.debug(txt); // sys.debug(txt);
} }
}; };

@ -1,54 +1,62 @@
var sys = require('sys'), var sys = require('sys'),
xmpp = require('node-xmpp'), util = require('util'),
colors = require('colors'), colors = require('colors'),
dateFormat = require("../../../date.js").dateFormat,
xmpp = require('node-xmpp'),
BasicClient = require('./basic-client').BasicClient; BasicClient = require('./basic-client').BasicClient;
var Client = function(params, callback) { var Client = function(params, callback) {
var jabber = this; var jabber = this;
var params = params;
var xmpp = this.xmpp;
this.roster = {}; this.roster = {};
this.presences = {}; this.presences = {};
this.presences.users = {};
this.presences.muc = {};
BasicClient.call(this, params, function() { BasicClient.call(this, params, function() {
this.presence(); this.presence("dnd", "Loading...");
this.getRoster(function(roster) { this.getRoster(function(roster) {
if (typeof callback === "function")
{
callback.apply(this); callback.apply(this);
}
else
{
jabber.emit('binded', this);
}
}); });
}); });
this.registerIqHandler('http://jabber.org/protocol/disco#info', function(stanza) { this.registerIqHandler('http://jabber.org/protocol/disco#info', function(stanza) {
sys.debug((stanza.attrs.from + " wont to disco!")[jabber.color]); jabber.sendDisco(stanza);
jabber.resultIq(stanza, new xmpp.Element('query', {xmlns: 'http://jabber.org/protocol/disco#info'})
.c('feature', {'var': 'http://jabber.org/protocol/disco#info'}).up()
.c('feature', {'var': 'http://jabber.org/protocol/disco#items'}).up()
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
.c('identity', {
category: 'conference',
type: 'text',
name: 'Play-Specific Chatrooms'
}).up()
.tree()
);
}); });
this.registerIqHandler('jabber:iq:last', function(stanza) { this.registerIqHandler('jabber:iq:last', function(stanza) {
sys.debug((stanza.attrs.from + ' wonts last')[jabber.color]); jabber.sendLast(stanza);
//[FIXME] giving a good last time
jabber.resultIq(stanza, new xmpp.Element('query', {
xmlns: 'jabber:iq:last', seconds:'1'})
.tree()
);
}); });
this.registerIqHandler('jabber:iq:version', function(stanza) {
jabber.resultIq(stanza, new xmpp.Element('query', {xmlns:'jabber:iq:version'}) this.registerIqHandler('urn:xmpp:time', function(stanza) {
.c('name').t('node-xmpp-client').up() jabber.sendTime(stanza);
.c('version').t('0.0.2').up()
.c('os').t(process.platform).up()
.tree()
);
}); });
this.registerIqHandler('jabber:iq:version', function(stanza) {
if (typeof jabber.cbVersion === "function") {
jabber.resultIq(stanza, jabber.cbVersion(stanza));
} else {
jabber.sendUnimplemented(stanza, "version");
}
});
}; };
sys.inherits(Client, BasicClient); sys.inherits(Client, BasicClient);
exports.Client = Client; exports.Client = Client;
Client.prototype.getPresences = function()
{
return this.presences.users;
};
Client.prototype.getRoster = function(callback) { Client.prototype.getRoster = function(callback) {
var jabber = this; var jabber = this;
this.iq(null, new xmpp.Element('query', {xmlns: 'jabber:iq:roster'}), function(iq) { this.iq(null, new xmpp.Element('query', {xmlns: 'jabber:iq:roster'}), function(iq) {
@ -62,6 +70,58 @@ Client.prototype.getRoster = function(callback) {
}); });
}; };
Client.prototype.sendUnimplemented = function(stanza, iqName) {
this.resultIq(stanza, "<query xmlns=\"jabber:iq:"+ iqName +"\"/>"
+ "<error type=\"cancel\" >"
+ "<feature-not-implemented xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+ "</error>"
+ "</iq>"
);
};
Client.prototype.sendTime = function(stanza) {
var now = new Date();
var tzo = dateFormat(now, "o").toString();
if (tzo !== "0") {
tzo = tzo.substr(0, (tzo.length - 2)) + ":" + tzo.substr((tzo.toString().length - 2));
}
this.resultIq(stanza,
"<iq xmlns=\"jabber:client\" type=\"result\" to=\""+ stanza.attrs.from +"\" from=\""+stanza.attrs.to+"\" id=\""+stanza.attrs.id+"\">"
+"<time xmlns=\"urn:xmpp:time\">"
+ "<tzo>"+ tzo +"</tzo>"
+ "<utc>"+ dateFormat(now, "isoUtcDateTime") +"</utc>"
+ "</time>"
+ "</iq>"
);
};
// FIXME: For some reason it seems like the result of this only arrives once you request is again?!?!
Client.prototype.sendLast = function(stanza) {
var last = (new Date().getTime() - this.idle);
if (last < 0) last = 0;
else last = Math.ceil(last / 1000);
this.resultIq(stanza, new xmpp.Element('query', {
xmlns: 'jabber:iq:last', seconds:last})
.tree()
);
};
Client.prototype.sendDisco = function(stanza) {
this.resultIq(stanza, new xmpp.Element('query', {xmlns: 'http://jabber.org/protocol/disco#info'})
.c('feature', {'var': 'http://jabber.org/protocol/disco#info'}).up()
.c('feature', {'var': 'http://jabber.org/protocol/disco#items'}).up()
.c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
.c('identity', {
category: 'conference',
type: 'text',
name: 'Play-Specific Chatrooms'
}).up()
.tree()
);
};
/* /*
http://xmpp.org/extensions/xep-0092.html http://xmpp.org/extensions/xep-0092.html
*/ */

@ -13,7 +13,7 @@ function Pubsub(client, to) {
var pubsub = this; var pubsub = this;
this.client.addListener('pubsub:event', function(from, node, event_, stanza) { this.client.addListener('pubsub:event', function(from, node, event_, stanza) {
if(from == pubsub.to) { if(from == pubsub.to) {
sys.debug('a pubsub event'.yellow); // sys.debug('a pubsub event'.yellow);
var cb = pubsub._eventCallback[node]; var cb = pubsub._eventCallback[node];
if(cb != null) { if(cb != null) {
cb.call(pubsub, event_, stanza); cb.call(pubsub, event_, stanza);
@ -27,17 +27,17 @@ sys.inherits(Pubsub, events.EventEmitter);
exports.Pubsub = Pubsub; exports.Pubsub = Pubsub;
Pubsub.prototype.createTree = function() { Pubsub.prototype.createTree = function() {
sys.debug(new xmpp.Element('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) /*sys.debug(new xmpp.Element('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
.c('create', {node: '/home/' + this.client.jid.domain + '/user'}).up() .c('create', {node: '/home/' + this.client.jid.domain + '/user'}).up()
.c('configure') .c('configure')
.tree()); .tree());*/
this.client.iqSet(this.to, new xmpp.Element('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) this.client.iqSet(this.to, new xmpp.Element('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'})
.c('create', {node: '/home/' + this.client.jid.domain + '/user'}).up() .c('create', {node: '/home/' + this.client.jid.domain + '/user'}).up()
.c('configure') .c('configure')
.tree(), .tree(),
function(stanza) { function(stanza) {
sys.debug(stanza.toString().yellow); // sys.debug(stanza.toString().yellow);
sys.debug(stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub').toString.yellow); // sys.debug(stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub').toString.yellow);
} }
); );
}; };
@ -133,7 +133,7 @@ Pubsub.prototype.createNode = function(node, callback) {
function(stanza) { function(stanza) {
var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub'); var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
if(pubsub != null) { if(pubsub != null) {
sys.debug(pubsub.toString().yellow); // sys.debug(pubsub.toString().yellow);
callback.call(jabber); callback.call(jabber);
} }
} }
@ -147,8 +147,8 @@ Pubsub.prototype.publish = function(node, content) {
.cnode(content) .cnode(content)
.tree(), .tree(),
function(iq) { function(iq) {
sys.debug('PUBLISH : ' + iq); // sys.debug('PUBLISH : ' + iq);
sys.debug('just published'.yellow); // sys.debug('just published'.yellow);
} }
); );
}; };
@ -163,7 +163,7 @@ Pubsub.prototype.suscribe = function(node, onMessage, onSuscribed) {
}) })
.tree(), .tree(),
function(iq) { function(iq) {
sys.debug(('Suscribe to ' + node).yellow); // sys.debug(('Suscribe to ' + node).yellow);
pubsub._eventCallback[node] = onMessage; pubsub._eventCallback[node] = onMessage;
var s = iq.getChild('pubsub', 'http://jabber.org/protocol/pubsub').attrs; var s = iq.getChild('pubsub', 'http://jabber.org/protocol/pubsub').attrs;
onSuscribed.call(jabber, s.subscription, s.subid); onSuscribed.call(jabber, s.subscription, s.subid);

@ -1,59 +1,95 @@
var sys = require('sys'), var sys = require("sys"),
xmpp = require('node-xmpp'), xmpp = require("node-xmpp"),
colors = require('colors'), colors = require("colors"),
events = require('events'); events = require("events");
var NS_MUC = "http://jabber.org/protocol/muc";
var Room = function(client, name, callback) { var Room = function(client, name, callback) {
events.EventEmitter.call(this); events.EventEmitter.call(this);
this._isReady = false; this._isReady = false;
this.client = client; this.client = client;
this.room = name; this.room = name;
this.to = this.room + '/' + this.client.jid.user; this.to = this.room + "/" + this.client.params.nickname;
var room = this; var room = this;
this.addListener('presence', function(from, stanza) { this.addListener("presence", function(from, stanza) {
var jfrom = new xmpp.JID(from); var jfrom = new xmpp.JID(from);
if(name == jfrom.user + '@' + jfrom.domain) { if(name == jfrom.user + "@" + jfrom.domain) {
var x = stanza.getChild('x', 'http://jabber.org/protocol/muc#user'); var x = stanza.getChild("x", NS_MUC + "#user");
if(x != null) { if(x != null) {
var item = x.getChild('item'); var item = x.getChild("item");
if(item != null) { if(item != null) {
room.affiliation = item.attrs.affiliation; room.affiliation = item.attrs.affiliation;
room.role = item.attrs.role; room.role = item.attrs.role;
} }
var status = x.getChild('status'); var status = x.getChild("status");
if(! room._isReady) { if(! room._isReady) {
room._isReady = true; room._isReady = true;
callback.call(room, (status != null) ? status.attrs.code : '200'); callback.call(room, (status != null) ? status.attrs.code : "200");
} }
} }
} }
}); });
this.presence();
this.presence("dnd", "Loading...");
this.client.emit("muc:binded");
}; };
sys.inherits(Room, events.EventEmitter); sys.inherits(Room, events.EventEmitter);
exports.Room = Room; exports.Room = Room;
Room.prototype.presence = function() { Room.prototype.getPresences = function()
sys.debug(('presence: ' + this.room)[this.client.color]); {
this.client.xmpp.send(new xmpp.Element('presence', { return this.client.presences.muc;
};
Room.prototype.presence = function(status, message)
{
if (typeof status === "undefined")
{
var status = "";
}
if (typeof message === "undefined")
{
var message = "";
}
this.client.xmpp.send(new xmpp.Element("presence", {
to: this.to to: this.to
}) })
.c('priority').t("5").up() .c("priority").t("5").up()
.c('x', {xmlns:"http://jabber.org/protocol/muc"}) .c("show").t(status).up()
.c("status").t(message).up()
.c("x", {xmlns: NS_MUC})
.tree()
);
};
Room.prototype.changenick = function(newnick)
{
this.client.params.nickname = newnick;
this.client.unidle();
this.client.xmpp.send(new xmpp.Element("presence", {
to: this.to.split("/")[0]+"/"+this.client.params.nickname
})
.c("x", {xmlns: NS_MUC})
.tree() .tree()
); );
}; };
Room.prototype.message = function(message) { Room.prototype.message = function(message) {
this.client.xmpp.send(new xmpp.Element('message', { this.client.unidle();
this.client.xmpp.send(new xmpp.Element("message", {
to: this.room, to: this.room,
type: 'groupchat', type: "groupchat",
id: this.client._iq++ id: this.client._iq++
}) })
// .c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(this.client.jid.username).up() // .c("nick", {xmlns: "http://jabber.org/protocol/nick"}).t(this.client.jid.username).up()
.c('body').t(message).up() .c("body").t(message).up()
.tree() .tree()
); );
}; };

@ -0,0 +1,22 @@
/**
* XMPP XMLNS List
*/
exports.SASL = "urn:ietf:params:xml:ns:xmpp-sasl";
exports.BIND = "urn:ietf:params:xml:ns:xmpp-bind";
exports.SESSION = "urn:ietf:params:xml:ns:xmpp-session";
exports.CLIENT = "jabber:client";
exports.XHTML = "http://www.w3.org/1999/xhtml";
exports.XHTML_IM = "http://jabber.org/protocol/xhtml-im";
exports.TLS = "urn:ietf:params:xml:ns:xmpp-tls";
exports.STREAMS = "http://etherx.jabber.org/streams";
exports.BYTESTREAMS = "http://jabber.org/protocol/bytestreams";
exports.ROASTER = "jabber:iq:roster";
exports.VCARD = "vcard-temp";
exports.VCARD_UPDATE = "vcard-temp:x:update";
exports.SI = "http://jabber.org/protocol/si";
exports.SI_PROFILE = "http://jabber.org/protocol/si/profile/file-transfer";
exports.FEATURE_NEG = "http://jabber.org/protocol/feature-neg";
exports.DATA = "jabber:x:data";
exports.AFFINIX_STREAM = "http://affinix.com/jabber/stream";
exports.CHAT_STATE = "http://jabber.org/protocol/chatstates";
exports.IQ_LAST = "jabber:iq:last";
Loading…
Cancel
Save