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