ADDED - Presence tracking for chat and muc FIXED - MUC now works properly ADDED - Time and Last IQ ADDED - Idle timemaster
parent
d5b97a21b0
commit
027dd2fee5
@ -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;
|
||||
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');
|
@ -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");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -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()
|
||||
);
|
||||
};
|
||||
|
@ -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…
Reference in new issue