// TODO: add timeouts to the file offers // TODO: Check if user is online first/do proper disco var sys = require("sys"), net = require("net"), XMLNS = require("./xmlns.js"), xmpp = require("node-xmpp"), colors = require("colors"), hash = require("../../../node-hash/lib/hash"); var Bytestream = function(parent, file, to, idIq) { var bytestream = this; this.parent = parent; this.server = parent.server || null; this.client = parent.client; this.idIq = idIq || (new Date().getTime()); this.to = to; this.sidId = "s5b_"+ this.idIq +"_"+ (new Date().getTime()); this.sidHash = parent.buildHash(this.sidId, this.client.jid, 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(); }; 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") { jabber.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 var params = { type: "set", id: self.idIq, to: self.to }; var xmlIQ = new xmpp.Element('iq', params) .c("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() .up() .tree(); jabber.xmpp.send(xmlIQ); }; 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 === jabber.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.params.proxyPort); } } }; jabber._iqCallback[self.idIq].error = function(error) { console.log(error); delete jabber._iqCallback[self.idIq]; }; var params = { type: "set", id: self.idIq, to: self.to }; if (self.server !== null || ("proxyHost" in self.client.params) === true) { var xmlIQ = new xmpp.Element('iq', params).c('query', {xmlns: XMLNS.BYTESTREAMS, sid: self.sidId}); // S5B Direct if (self.server !== null) { self.server.addHandler(self.idIq, self.sidHash, self.file.data); xmlIQ.c("streamhost", {jid: jabber.jid.toString(), host: self.server.host, port: self.server.port}).up(); } // S5B Proxy if (("proxyHost" in self.client.params) === true) { var proxyParams = { jid: self.client.params.proxyJid, host: self.client.params.proxyHost, port: self.client.params.proxyPort }; xmlIQ.c("streamhost", proxyParams).up(); } jabber.xmpp.send(xmlIQ.up().tree()); } else { console.log("S5B Error: no available transports"); } }; Bytestream.prototype.sendProxy = function(streamProxy, streamPort) { var self = this; var jabber = this.client; var cbAcknowledged = function(client, streamHost, sid) { 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[iqId]; } }; jabber._iqCallback[iqId].error = function(error) { console.log("Activation error"); console.log(error.toString()); delete jabber._iqCallback[iqId]; }; var params = { type: "set", id: iqId, to: streamProxy }; var xmlIQ = new xmpp.Element('iq', params) .c('query', {xmlns: XMLNS.BYTESTREAMS, sid: self.sidId}) .c("activate").t(self.to) .up() .up() .tree(); jabber.xmpp.send(xmlIQ); }; var cbFailure = function(error) { console.log("Error with S5B proxy connection"); console.log(error); }; var streamHosts = [{host: streamProxy, port: streamPort}]; self.parent.createS5BClient(self.sidHash, streamHosts, self.sidId, cbAcknowledged, undefined, cbFailure); };