/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dojox.xmpp.TransportSession"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.xmpp.TransportSession"] = true;
dojo.provide("dojox.xmpp.TransportSession");
dojo.require("dojox.xmpp.bosh");
dojo.require("dojox.xmpp.util");
dojo.require("dojox.data.dom");
dojox.xmpp.TransportSession = function(props) {
// we have to set this here because "this" doesn't work
// in the dojo.extend call.
this.sendTimeout = (this.wait+20)*1000;
//mixin any options that we want to provide to this service
if (props && dojo.isObject(props)) {
dojo.mixin(this, props);
if(this.useScriptSrcTransport){
this.transportIframes = [];
}
}
};
dojo.extend(dojox.xmpp.TransportSession, {
/* options/defaults */
rid: 0,
hold: 1,
polling:1000,
secure: false,
wait: 60,
lang: 'en',
submitContentType: 'text/xml; charset=utf=8',
serviceUrl: '/httpbind',
defaultResource: "dojoIm",
domain: 'imserver.com',
sendTimeout: 0, //(this.wait+20)*1000
useScriptSrcTransport:false,
keepAliveTimer:null,
//status
state: "NotReady",
transmitState: "Idle",
protocolPacketQueue: [],
outboundQueue: [],
outboundRequests: {},
inboundQueue: [],
deferredRequests: {},
matchTypeIdAttribute: {},
open: function() {
this.status = "notReady";
this.rid = Math.round(Math.random() * 1000000000);
this.protocolPacketQueue = [];
this.outboundQueue = [];
this.outboundRequests = {};
this.inboundQueue = [];
this.deferredRequests = {};
this.matchTypeIdAttribute = {};
this.keepAliveTimer = setTimeout(dojo.hitch(this, "_keepAlive"), 10000);
if(this.useScriptSrcTransport){
dojox.xmpp.bosh.initialize({
iframes: this.hold+1,
load: dojo.hitch(this, function(){
this._sendLogin();
})
});
} else {
this._sendLogin();
}
},
_sendLogin: function() {
var rid = this.rid++;
var req = {
content: this.submitContentType,
hold: this.hold,
rid: rid,
to: this.domain,
secure: this.secure,
wait: this.wait,
"xml:lang": this.lang,
"xmpp:version": "1.0",
xmlns: dojox.xmpp.xmpp.BODY_NS,
"xmlns:xmpp": "urn:xmpp:xbosh"
};
var msg = dojox.xmpp.util.createElement("body", req, true);
this.addToOutboundQueue(msg, rid);
},
_sendRestart: function(){
var rid = this.rid++;
var req = {
rid: rid,
sid: this.sid,
to: this.domain,
"xmpp:restart": "true",
"xml:lang": this.lang,
xmlns: dojox.xmpp.xmpp.BODY_NS,
"xmlns:xmpp": "urn:xmpp:xbosh"
};
var msg = dojox.xmpp.util.createElement("body", req, true);
this.addToOutboundQueue(msg, rid);
},
processScriptSrc: function(msg, rid) {
//console.log("processScriptSrc::", rid, msg);
// var msgDom = dojox.xml.DomParser.parse(msg);
var msgDom = dojox.xml.parser.parse(msg, "text/xml");
//console.log("parsed mgs", msgDom);
//console.log("Queue", this.outboundQueue);
if(msgDom) {
this.processDocument(msgDom, rid);
} else {
//console.log("Recived bad document from server",msg);
}
},
_keepAlive: function(){
if (this.state=="wait" || this.isTerminated()) {
return;
}
this._dispatchPacket();
this.keepAliveTimer = setTimeout(dojo.hitch(this, "_keepAlive"), 10000);
},
close: function(protocolMsg){
var rid = this.rid++;
var req = {
sid: this.sid,
rid: rid,
type: "terminate"
};
var envelope = null;
if (protocolMsg) {
envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false));
envelope.append(protocolMsg);
envelope.append("</body>");
} else {
envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false));
}
// this.sendXml(envelope,rid);
this.addToOutboundQueue(envelope.toString(), rid);
this.state=="Terminate";
},
dispatchPacket: function(msg, protocolMatchType, matchId, matchProperty){
// summary
// Main Packet dispatcher, most calls should be made with this other
// than a few setup calls which use add items to the queue directly
//protocolMatchType, matchId, and matchProperty are optional params
//that allow a deferred to be tied to a protocol response instad of the whole
//rid
// //console.log("In dispatchPacket ", msg, protocolMatchType, matchId, matchProperty);
if (msg){
this.protocolPacketQueue.push(msg);
}
var def = new dojo.Deferred();
//def.rid = req.rid;
if (protocolMatchType && matchId){
def.protocolMatchType = protocolMatchType;
def.matchId = matchId;
def.matchProperty = matchProperty || "id";
if(def.matchProperty != "id") {
this.matchTypeIdAttribute[protocolMatchType] = def.matchProperty;
}
}
this.deferredRequests[def.protocolMatchType + "-" +def.matchId]=def;
if(!this.dispatchTimer) {
this.dispatchTimer = setTimeout(dojo.hitch(this, "_dispatchPacket"), 600);
}
return def;
},
_dispatchPacket: function(){
clearTimeout(this.dispatchTimer);
delete this.dispatchTimer;
if (!this.sid){
console.debug("TransportSession::dispatchPacket() No SID, packet dropped.")
return;
}
if (!this.authId){
//FIXME according to original nodes, this should wait a little while and try
// again up to three times to see if we get this data.
console.debug("TransportSession::dispatchPacket() No authId, packet dropped [FIXME]")
return;
}
//if there is a pending request with the server, don't poll
if (this.transmitState != "error" && (this.protocolPacketQueue.length == 0) && (this.outboundQueue.length > 0)) {
return;
}
if (this.state=="wait" || this.isTerminated()) {
return;
}
var req = {
sid: this.sid,
xmlns: dojox.xmpp.xmpp.BODY_NS
}
var envelope
if (this.protocolPacketQueue.length > 0){
req.rid= this.rid++;
envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false));
envelope.append(this.processProtocolPacketQueue());
envelope.append("</body>");
delete this.lastPollTime;
} else {
//console.log("Nothing to send, I'm just polling.");
if(this.lastPollTime) {
var now = new Date().getTime();
if(now - this.lastPollTime < this.polling) {
//console.log("Waiting to poll ", this.polling - (now - this.lastPollTime)+10);
this.dispatchTimer = setTimeout(dojo.hitch(this, "_dispatchPacket"), this.polling - (now - this.lastPollTime)+10);
return;
}
}
req.rid= this.rid++;
this.lastPollTime = new Date().getTime();
envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, true));
}
this.addToOutboundQueue(envelope.toString(),req.rid);
},
redispatchPacket: function(rid){
var env = this.outboundRequests[rid];
this.sendXml(env, rid);
},
addToOutboundQueue: function(msg, rid){
this.outboundQueue.push({msg: msg,rid: rid});
this.outboundRequests[rid]=msg;
this.sendXml(msg, rid);
},
removeFromOutboundQueue: function(rid){
for(var i=0; i<this.outboundQueue.length;i++){
if (rid == this.outboundQueue[i]["rid"]){
this.outboundQueue.splice(i, 1);
break;
}
}
delete this.outboundRequests[rid];
},
processProtocolPacketQueue: function(){
var packets = new dojox.string.Builder();
for(var i=0; i<this.protocolPacketQueue.length;i++){
packets.append(this.protocolPacketQueue[i]);
}
this.protocolPacketQueue=[];
return packets.toString();
},
sendXml: function(message, rid){
if(this.isTerminated()) {
return false;
}
//console.log("TransportSession::sendXml()"+ new Date().getTime() + " RID: ", rid, " MSG: ", message);
this.transmitState = "transmitting";
var def = null;
if(this.useScriptSrcTransport) {
//console.log("using script src to transmit");
def = dojox.xmpp.bosh.get({
rid: rid,
url: this.serviceUrl+'?'+encodeURIComponent(message),
error: dojo.hitch(this, function(res, io){
this.setState("Terminate", "error");
return false;
}),
timeout: this.sendTimeout
});
} else {
def = dojo.rawXhrPost({
contentType: "text/xml",
url: this.serviceUrl,
postData: message,
handleAs: "xml",
error: dojo.hitch(this, function(res, io) {
////console.log("foo", res, io.xhr.responseXML, io.xhr.status);
return this.processError(io.xhr.responseXML, io.xhr.status , rid);
}),
timeout: this.sendTimeout
});
}
//process the result document
def.addCallback(this, function(res){
return this.processDocument(res, rid);
});
return def;
},
processDocument: function(doc, rid){
if(this.isTerminated() || !doc.firstChild) {
return false;
}
//console.log("TransportSession:processDocument() ", doc, rid);
this.transmitState = "idle";
var body = doc.firstChild;
if (body.nodeName != 'body'){
//console.log("TransportSession::processDocument() firstChild is not <body> element ", doc, " RID: ", rid);
}
if (this.outboundQueue.length<1){return false;}
var expectedId = this.outboundQueue[0]["rid"];
//console.log("expectedId", expectedId);
if (rid==expectedId){
this.removeFromOutboundQueue(rid);
this.processResponse(body, rid);
this.processInboundQueue();
}else{
//console.log("TransportSession::processDocument() rid: ", rid, " expected: ", expectedId);
var gap = rid-expectedId;
if (gap < this.hold + 2){
this.addToInboundQueue(doc,rid);
}else{
//console.log("TransportSession::processDocument() RID is outside of the expected response window");
}
}
return doc;
},
processInboundQueue: function(){
while (this.inboundQueue.length > 0) {
var item = this.inboundQueue.shift();
this.processDocument(item["doc"], item["rid"]);
}
},
addToInboundQueue: function(doc,rid){
for (var i=0; i<this.inboundQueue.length;i++){
if (rid < this.inboundQueue[i]["rid"]){continue;}
this.inboundQueue.splice(i,0,{doc: doc, rid: rid});
}
},
processResponse: function(body,rid){
////console.log("TransportSession:processResponse() ", body, " RID: ", rid);
if (body.getAttribute("type")=='terminate'){
var reasonNode = body.firstChild.firstChild;
var errorMessage = "";
if(reasonNode.nodeName == "conflict") {
errorMessage = "conflict"
}
this.setState("Terminate", errorMessage);
return;
}
if ((this.state != 'Ready')&&(this.state != 'Terminate')) {
var sid=body.getAttribute("sid");
if (sid){
this.sid=sid;
} else {
throw new Error("No sid returned during xmpp session startup");
}
this.authId = body.getAttribute("authid");
if (this.authId == "") {
if (this.authRetries-- < 1) {
console.error("Unable to obtain Authorization ID");
this.terminateSession();
}
}
this.wait= body.getAttribute("wait");
if( body.getAttribute("polling")){
this.polling= parseInt(body.getAttribute("polling"))*1000;
}
//console.log("Polling value ", this.polling);
this.inactivity = body.getAttribute("inactivity");
this.setState("Ready");
}
dojo.forEach(body.childNodes, function(node){
this.processProtocolResponse(node, rid);
}, this);
//need to make sure, since if you use sendXml directly instead of using
//dispatch packets, there wont' be a call back function here
//normally the deferred will get fired by a child message at the protocol level
//but if it hasn't fired by now, go ahead and fire it with the full body
/*if (this.deferredRequests[rid] && this.deferredRequests[rid].fired==-1){
this.deferredRequests[rid].callback(body);
}*/
//delete from the list of outstanding requests
//delete this.deferredRequests[rid];
if (this.transmitState == "idle"){
this.dispatchPacket();
}
},
processProtocolResponse: function(msg, rid){
//summary
//process the individual protocol messages and if there
//is a matching set of protocolMatchType, matchId, and matchPropery
//fire off the deferred
this.onProcessProtocolResponse(msg);
var key = msg.nodeName + "-" +msg.getAttribute("id");
var def = this.deferredRequests[key];
if (def){
def.callback(msg);
delete this.deferredRequests[key];
}
},
setState: function(state, message){
if (this.state != state) {
if (this["on"+state]){
this["on"+state](state, this.state, message);
}
this.state=state;
}
},
isTerminated: function() {
return this.state=="Terminate";
},
processError: function(err, httpStatusCode,rid){
//console.log("Processing server error ", err, httpStatusCode,rid);
if(this.isTerminated()) {
return false;
}
if(httpStatusCode != 200) {
if(httpStatusCode >= 400 && httpStatusCode < 500){
/* Any status code between 400 and 500 should terminate
* the connection */
this.setState("Terminate", errorMessage);
return false;
}else{
this.removeFromOutboundQueue(rid);
setTimeout(dojo.hitch(this, function(){ this.dispatchPacket(); }), 200);
return true;
}
return false;
}
if (err && err.dojoType && err.dojoType=="timeout"){
//console.log("Wait timeout");
}
this.removeFromOutboundQueue(rid);
//FIXME conditional processing if request will be needed based on type of error.
if(err && err.firstChild) {
//console.log("Error ", err.firstChild.getAttribute("type") + " status code " + httpStatusCode);
if (err.firstChild.getAttribute("type")=='terminate'){
var reasonNode = err.firstChild.firstChild;
var errorMessage = "";
if(reasonNode && reasonNode.nodeName == "conflict") {
errorMessage = "conflict"
}
this.setState("Terminate", errorMessage);
return false;
}
}
this.transmitState = "error";
setTimeout(dojo.hitch(this, function(){ this.dispatchPacket(); }), 200);
//console.log("Error: ", arguments);
return true;
},
//events
onTerminate: function(newState, oldState, message){ },
onProcessProtocolResponse: function(msg){},
onReady: function(newState, oldState){}
});
}