diff --git a/lib/xmpp-client.js b/lib/xmpp-client.js
index c3dbcbb..614117c 100644
--- a/lib/xmpp-client.js
+++ b/lib/xmpp-client.js
@@ -1,3 +1,8 @@
exports.BasicClient = require('./xmpp-client/basic-client').BasicClient;
exports.Client = require('./xmpp-client/client').Client;
-exports.Jid = require('./xmpp-client/client').Jid;
\ No newline at end of file
+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');
\ No newline at end of file
diff --git a/lib/xmpp-client/basic-client.js b/lib/xmpp-client/basic-client.js
index 34b0795..babf2af 100644
--- a/lib/xmpp-client/basic-client.js
+++ b/lib/xmpp-client/basic-client.js
@@ -3,46 +3,78 @@
*/
-var sys = require('sys'),
- xmpp = require('node-xmpp'),
- colors = require('colors'),
- events = require('events');
+var sys = require('sys'),
+ xmpp = require('node-xmpp'),
+ colors = require('colors'),
+ events = require('events');
var BasicClient = function(params, callback) {
events.EventEmitter.call(this);
+ this.idle = (new Date().getTime());
this._iq = 0;
this._iqCallback = {};
this._iqHandler = {};
var jabber = this;
+ this.params = params;
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.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");
- process.exit(1);
+ */
+ this.xmpp.addListener('authFail', function(e) {
+ jabber.emit('authFail', e);
});
this.xmpp.addListener('error', function(e) {
- sys.error(e);
- process.exit(1);
+ jabber.emit('error', e);
});
+
this.xmpp.addListener('stanza', function(stanza) {
switch(stanza.name) {
case 'iq':
switch(stanza.attrs.type) {
case 'error':
- jabber._iqCallback[stanza.attrs.id].error.apply(jabber, [stanza]);
+ 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]);
+ }
break;
case 'result':
- jabber._iqCallback[stanza.attrs.id].success.apply(jabber, [stanza]);
+ 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]);
+ }
break;
default:
jabber.emit('iq', stanza);
var q = stanza.getChild('query');
if(q == undefined) {
- jabber.emit('iq:unknow', stanza);
+ 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);
+ }
} else {
if(q.attrs.xmlns != null && jabber._iqHandler[q.attrs.xmlns] != null) {
jabber._iqHandler[q.attrs.xmlns].call(jabber, stanza);
@@ -54,17 +86,120 @@ var BasicClient = function(params, callback) {
}
break;
case 'presence':
+ var newPresence = false;
if(stanza.attrs.type == 'error') {
jabber.emit('presence:error', stanza);
} 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;
case 'message':
jabber.emit('message', stanza);
break;
+ default:
+ console.log(stanza.toString());
+ break;
}
});
+
this.xmpp.addListener('online', function() {
jabber.jid = this.jid;
jabber.emit('online');
@@ -75,25 +210,49 @@ var BasicClient = function(params, callback) {
sys.inherits(BasicClient, events.EventEmitter);
exports.BasicClient = BasicClient;
+BasicClient.prototype.unidle = function() {
+ this.idle = (new Date().getTime());
+};
+
BasicClient.prototype.message = function(to, message) {
this.xmpp.send(new xmpp.Element('message', {
to: to,
type: 'chat'})
.c('body').t(message));
+
+ this.unidle();
};
-BasicClient.prototype.presence = function(type) {
- this.xmpp.send(new xmpp.Element('presence', (type != null) ? {type: type} : {}).tree());
+BasicClient.prototype.presence = function(status, message)
+{
+ 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);};
var n = 'node' + this._iq++;
this._iqCallback[n] = {};
this._iqCallback[n].success = callback;
this._iqCallback[n].error = error;
+ if (typeof type === "undefined") {
+ var type = "get";
+ }
+
var attrs = {
- type: 'get',
+ type: type,
id: n
};
if(to != null) {
@@ -107,12 +266,16 @@ BasicClient.prototype.iq = function(to, query, callback, error) {
Answer an iq query
*/
BasicClient.prototype.resultIq = function(iqGet, result) {
- this.xmpp.send(new xmpp.Element('iq', {
- type: 'result',
- from: iqGet.attrs.to,
- to: iqGet.attrs.from,
- id: iqGet.attrs.id
- }).cnode(result).tree());
+ if (typeof iqGet === "undefined" || typeof result === "string") {
+ this.xmpp.send(result);
+ } else {
+ this.xmpp.send(new xmpp.Element('iq', {
+ type: 'result',
+ from: iqGet.attrs.to,
+ to: iqGet.attrs.from,
+ id: iqGet.attrs.id
+ }).cnode(result).tree());
+ }
};
BasicClient.prototype.registerIqHandler = function(xmlns, action) {
diff --git a/lib/xmpp-client/bytestream-server.js b/lib/xmpp-client/bytestream-server.js
new file mode 100644
index 0000000..40f461d
--- /dev/null
+++ b/lib/xmpp-client/bytestream-server.js
@@ -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];
+ };
+};
diff --git a/lib/xmpp-client/bytestream.js b/lib/xmpp-client/bytestream.js
new file mode 100644
index 0000000..5b97da7
--- /dev/null
+++ b/lib/xmpp-client/bytestream.js
@@ -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");
+ });
+ });
+};
+
diff --git a/lib/xmpp-client/client-old.js b/lib/xmpp-client/client-old.js
index 66d870d..d48eabf 100644
--- a/lib/xmpp-client/client-old.js
+++ b/lib/xmpp-client/client-old.js
@@ -67,7 +67,7 @@ var Client = function(params, callback) {
} else {
jabber._debug('MESSAGE: ' + stanza);
var event_ = stanza.getChild('event', 'http://jabber.org/protocol/pubsub#event');
- sys.debug(event_.toString().green);
+ // sys.debug(event_.toString().green);
if(event_ != null) {
jabber.emit('pubsub:event', from, event_.getChild('items').attrs.node, event_, stanza);
} else {
@@ -111,7 +111,7 @@ var Client = function(params, callback) {
}
});
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'})
.c('feature', {'var': 'http://jabber.org/protocol/disco#info'}).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) {
- sys.debug((stanza.attrs.from + ' wonts last')[jabber.color]);
+ // sys.debug((stanza.attrs.from + ' wonts last')[jabber.color]);
//[FIXME] giving a good last time
jabber.resultIq(stanza, new xmpp.Element('query', {
xmlns: 'jabber:iq:last', seconds:'1'})
@@ -141,7 +141,7 @@ var Client = function(params, callback) {
);
});
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._debug(stanza.toString().red.invert);
@@ -153,7 +153,7 @@ exports.Client = Client;
Client.prototype._debug = function(txt) {
if(this.debug) {
- sys.debug(txt);
+ // sys.debug(txt);
}
};
diff --git a/lib/xmpp-client/client.js b/lib/xmpp-client/client.js
index cd91b53..2b58478 100644
--- a/lib/xmpp-client/client.js
+++ b/lib/xmpp-client/client.js
@@ -1,54 +1,62 @@
-var sys = require('sys'),
- xmpp = require('node-xmpp'),
- colors = require('colors'),
- BasicClient = require('./basic-client').BasicClient;
+var sys = require('sys'),
+ util = require('util'),
+ colors = require('colors'),
+ dateFormat = require("../../../date.js").dateFormat,
+ xmpp = require('node-xmpp'),
+ BasicClient = require('./basic-client').BasicClient;
var Client = function(params, callback) {
var jabber = this;
+ var params = params;
+ var xmpp = this.xmpp;
this.roster = {};
- this.presences = {};
+ this.presences = {};
+ this.presences.users = {};
+ this.presences.muc = {};
+
BasicClient.call(this, params, function() {
- this.presence();
+ this.presence("dnd", "Loading...");
this.getRoster(function(roster) {
- callback.apply(this);
+ if (typeof callback === "function")
+ {
+ callback.apply(this);
+ }
+ else
+ {
+ jabber.emit('binded', this);
+ }
});
});
+
this.registerIqHandler('http://jabber.org/protocol/disco#info', function(stanza) {
- sys.debug((stanza.attrs.from + " wont to disco!")[jabber.color]);
- 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()
- );
+ jabber.sendDisco(stanza);
});
+
this.registerIqHandler('jabber:iq:last', function(stanza) {
- sys.debug((stanza.attrs.from + ' wonts last')[jabber.color]);
- //[FIXME] giving a good last time
- jabber.resultIq(stanza, new xmpp.Element('query', {
- xmlns: 'jabber:iq:last', seconds:'1'})
- .tree()
- );
+ jabber.sendLast(stanza);
});
- this.registerIqHandler('jabber:iq:version', function(stanza) {
- jabber.resultIq(stanza, new xmpp.Element('query', {xmlns:'jabber:iq:version'})
- .c('name').t('node-xmpp-client').up()
- .c('version').t('0.0.2').up()
- .c('os').t(process.platform).up()
- .tree()
- );
+
+ this.registerIqHandler('urn:xmpp:time', function(stanza) {
+ jabber.sendTime(stanza);
});
+ 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);
exports.Client = Client;
+Client.prototype.getPresences = function()
+{
+ return this.presences.users;
+};
+
Client.prototype.getRoster = function(callback) {
var jabber = this;
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, ""
+ + ""
+ + ""
+ + ""
+ + ""
+ );
+};
+
+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,
+ ""
+ +""
+ + ""
+ );
+};
+
+// 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
*/
diff --git a/lib/xmpp-client/pubsub.js b/lib/xmpp-client/pubsub.js
index 0f572f8..7646dc4 100644
--- a/lib/xmpp-client/pubsub.js
+++ b/lib/xmpp-client/pubsub.js
@@ -13,7 +13,7 @@ function Pubsub(client, to) {
var pubsub = this;
this.client.addListener('pubsub:event', function(from, node, event_, stanza) {
if(from == pubsub.to) {
- sys.debug('a pubsub event'.yellow);
+ // sys.debug('a pubsub event'.yellow);
var cb = pubsub._eventCallback[node];
if(cb != null) {
cb.call(pubsub, event_, stanza);
@@ -27,17 +27,17 @@ sys.inherits(Pubsub, events.EventEmitter);
exports.Pubsub = Pubsub;
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('configure')
- .tree());
+ .tree());*/
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('configure')
.tree(),
function(stanza) {
- sys.debug(stanza.toString().yellow);
- sys.debug(stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub').toString.yellow);
+ // sys.debug(stanza.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) {
var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub');
if(pubsub != null) {
- sys.debug(pubsub.toString().yellow);
+ // sys.debug(pubsub.toString().yellow);
callback.call(jabber);
}
}
@@ -147,8 +147,8 @@ Pubsub.prototype.publish = function(node, content) {
.cnode(content)
.tree(),
function(iq) {
- sys.debug('PUBLISH : ' + iq);
- sys.debug('just published'.yellow);
+ // sys.debug('PUBLISH : ' + iq);
+ // sys.debug('just published'.yellow);
}
);
};
@@ -163,7 +163,7 @@ Pubsub.prototype.suscribe = function(node, onMessage, onSuscribed) {
})
.tree(),
function(iq) {
- sys.debug(('Suscribe to ' + node).yellow);
+ // sys.debug(('Suscribe to ' + node).yellow);
pubsub._eventCallback[node] = onMessage;
var s = iq.getChild('pubsub', 'http://jabber.org/protocol/pubsub').attrs;
onSuscribed.call(jabber, s.subscription, s.subid);
diff --git a/lib/xmpp-client/room.js b/lib/xmpp-client/room.js
index a2b4940..e58d676 100644
--- a/lib/xmpp-client/room.js
+++ b/lib/xmpp-client/room.js
@@ -1,59 +1,95 @@
-var sys = require('sys'),
- xmpp = require('node-xmpp'),
- colors = require('colors'),
- events = require('events');
+var sys = require("sys"),
+ xmpp = require("node-xmpp"),
+ colors = require("colors"),
+ events = require("events");
+
+var NS_MUC = "http://jabber.org/protocol/muc";
var Room = function(client, name, callback) {
events.EventEmitter.call(this);
this._isReady = false;
this.client = client;
this.room = name;
- this.to = this.room + '/' + this.client.jid.user;
+ this.to = this.room + "/" + this.client.params.nickname;
+
var room = this;
- this.addListener('presence', function(from, stanza) {
+ this.addListener("presence", function(from, stanza) {
var jfrom = new xmpp.JID(from);
- if(name == jfrom.user + '@' + jfrom.domain) {
- var x = stanza.getChild('x', 'http://jabber.org/protocol/muc#user');
+ if(name == jfrom.user + "@" + jfrom.domain) {
+ var x = stanza.getChild("x", NS_MUC + "#user");
if(x != null) {
- var item = x.getChild('item');
+ var item = x.getChild("item");
if(item != null) {
room.affiliation = item.attrs.affiliation;
room.role = item.attrs.role;
}
- var status = x.getChild('status');
+ var status = x.getChild("status");
if(! room._isReady) {
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);
exports.Room = Room;
-Room.prototype.presence = function() {
- sys.debug(('presence: ' + this.room)[this.client.color]);
- this.client.xmpp.send(new xmpp.Element('presence', {
+Room.prototype.getPresences = function()
+{
+ 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
})
- .c('priority').t("5").up()
- .c('x', {xmlns:"http://jabber.org/protocol/muc"})
+ .c("priority").t("5").up()
+ .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()
);
};
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,
- type: 'groupchat',
+ type: "groupchat",
id: this.client._iq++
})
-// .c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(this.client.jid.username).up()
- .c('body').t(message).up()
+// .c("nick", {xmlns: "http://jabber.org/protocol/nick"}).t(this.client.jid.username).up()
+ .c("body").t(message).up()
.tree()
);
};
diff --git a/lib/xmpp-client/xmlns.js b/lib/xmpp-client/xmlns.js
new file mode 100644
index 0000000..9ae07f1
--- /dev/null
+++ b/lib/xmpp-client/xmlns.js
@@ -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";
\ No newline at end of file