You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
507 lines
14 KiB
507 lines
14 KiB
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("Received 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){} |
|
});
|
|
|