TransportSession.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // wrapped by build app
  2. define("dojox/xmpp/TransportSession", ["dijit","dojo","dojox","dojo/require!dojox/xmpp/bosh,dojox/xmpp/util,dojox/data/dom"], function(dijit,dojo,dojox){
  3. dojo.provide("dojox.xmpp.TransportSession");
  4. dojo.require("dojox.xmpp.bosh");
  5. dojo.require("dojox.xmpp.util");
  6. dojo.require("dojox.data.dom");
  7. dojox.xmpp.TransportSession = function(props) {
  8. // we have to set this here because "this" doesn't work
  9. // in the dojo.extend call.
  10. this.sendTimeout = (this.wait+20)*1000;
  11. //mixin any options that we want to provide to this service
  12. if (props && dojo.isObject(props)) {
  13. dojo.mixin(this, props);
  14. if(this.useScriptSrcTransport){
  15. this.transportIframes = [];
  16. }
  17. }
  18. };
  19. dojo.extend(dojox.xmpp.TransportSession, {
  20. /* options/defaults */
  21. rid: 0,
  22. hold: 1,
  23. polling:1000,
  24. secure: false,
  25. wait: 60,
  26. lang: 'en',
  27. submitContentType: 'text/xml; charset=utf=8',
  28. serviceUrl: '/httpbind',
  29. defaultResource: "dojoIm",
  30. domain: 'imserver.com',
  31. sendTimeout: 0, //(this.wait+20)*1000
  32. useScriptSrcTransport:false,
  33. keepAliveTimer:null,
  34. //status
  35. state: "NotReady",
  36. transmitState: "Idle",
  37. protocolPacketQueue: [],
  38. outboundQueue: [],
  39. outboundRequests: {},
  40. inboundQueue: [],
  41. deferredRequests: {},
  42. matchTypeIdAttribute: {},
  43. open: function() {
  44. this.status = "notReady";
  45. this.rid = Math.round(Math.random() * 1000000000);
  46. this.protocolPacketQueue = [];
  47. this.outboundQueue = [];
  48. this.outboundRequests = {};
  49. this.inboundQueue = [];
  50. this.deferredRequests = {};
  51. this.matchTypeIdAttribute = {};
  52. this.keepAliveTimer = setTimeout(dojo.hitch(this, "_keepAlive"), 10000);
  53. if(this.useScriptSrcTransport){
  54. dojox.xmpp.bosh.initialize({
  55. iframes: this.hold+1,
  56. load: dojo.hitch(this, function(){
  57. this._sendLogin();
  58. })
  59. });
  60. } else {
  61. this._sendLogin();
  62. }
  63. },
  64. _sendLogin: function() {
  65. var rid = this.rid++;
  66. var req = {
  67. content: this.submitContentType,
  68. hold: this.hold,
  69. rid: rid,
  70. to: this.domain,
  71. secure: this.secure,
  72. wait: this.wait,
  73. "xml:lang": this.lang,
  74. "xmpp:version": "1.0",
  75. xmlns: dojox.xmpp.xmpp.BODY_NS,
  76. "xmlns:xmpp": "urn:xmpp:xbosh"
  77. };
  78. var msg = dojox.xmpp.util.createElement("body", req, true);
  79. this.addToOutboundQueue(msg, rid);
  80. },
  81. _sendRestart: function(){
  82. var rid = this.rid++;
  83. var req = {
  84. rid: rid,
  85. sid: this.sid,
  86. to: this.domain,
  87. "xmpp:restart": "true",
  88. "xml:lang": this.lang,
  89. xmlns: dojox.xmpp.xmpp.BODY_NS,
  90. "xmlns:xmpp": "urn:xmpp:xbosh"
  91. };
  92. var msg = dojox.xmpp.util.createElement("body", req, true);
  93. this.addToOutboundQueue(msg, rid);
  94. },
  95. processScriptSrc: function(msg, rid) {
  96. //console.log("processScriptSrc::", rid, msg);
  97. // var msgDom = dojox.xml.DomParser.parse(msg);
  98. var msgDom = dojox.xml.parser.parse(msg, "text/xml");
  99. //console.log("parsed mgs", msgDom);
  100. //console.log("Queue", this.outboundQueue);
  101. if(msgDom) {
  102. this.processDocument(msgDom, rid);
  103. } else {
  104. //console.log("Recived bad document from server",msg);
  105. }
  106. },
  107. _keepAlive: function(){
  108. if (this.state=="wait" || this.isTerminated()) {
  109. return;
  110. }
  111. this._dispatchPacket();
  112. this.keepAliveTimer = setTimeout(dojo.hitch(this, "_keepAlive"), 10000);
  113. },
  114. close: function(protocolMsg){
  115. var rid = this.rid++;
  116. var req = {
  117. sid: this.sid,
  118. rid: rid,
  119. type: "terminate"
  120. };
  121. var envelope = null;
  122. if (protocolMsg) {
  123. envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false));
  124. envelope.append(protocolMsg);
  125. envelope.append("</body>");
  126. } else {
  127. envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false));
  128. }
  129. // this.sendXml(envelope,rid);
  130. this.addToOutboundQueue(envelope.toString(), rid);
  131. this.state=="Terminate";
  132. },
  133. dispatchPacket: function(msg, protocolMatchType, matchId, matchProperty){
  134. // summary
  135. // Main Packet dispatcher, most calls should be made with this other
  136. // than a few setup calls which use add items to the queue directly
  137. //protocolMatchType, matchId, and matchProperty are optional params
  138. //that allow a deferred to be tied to a protocol response instad of the whole
  139. //rid
  140. // //console.log("In dispatchPacket ", msg, protocolMatchType, matchId, matchProperty);
  141. if (msg){
  142. this.protocolPacketQueue.push(msg);
  143. }
  144. var def = new dojo.Deferred();
  145. //def.rid = req.rid;
  146. if (protocolMatchType && matchId){
  147. def.protocolMatchType = protocolMatchType;
  148. def.matchId = matchId;
  149. def.matchProperty = matchProperty || "id";
  150. if(def.matchProperty != "id") {
  151. this.matchTypeIdAttribute[protocolMatchType] = def.matchProperty;
  152. }
  153. }
  154. this.deferredRequests[def.protocolMatchType + "-" +def.matchId]=def;
  155. if(!this.dispatchTimer) {
  156. this.dispatchTimer = setTimeout(dojo.hitch(this, "_dispatchPacket"), 600);
  157. }
  158. return def;
  159. },
  160. _dispatchPacket: function(){
  161. clearTimeout(this.dispatchTimer);
  162. delete this.dispatchTimer;
  163. if (!this.sid){
  164. console.debug("TransportSession::dispatchPacket() No SID, packet dropped.")
  165. return;
  166. }
  167. if (!this.authId){
  168. //FIXME according to original nodes, this should wait a little while and try
  169. // again up to three times to see if we get this data.
  170. console.debug("TransportSession::dispatchPacket() No authId, packet dropped [FIXME]")
  171. return;
  172. }
  173. //if there is a pending request with the server, don't poll
  174. if (this.transmitState != "error" && (this.protocolPacketQueue.length == 0) && (this.outboundQueue.length > 0)) {
  175. return;
  176. }
  177. if (this.state=="wait" || this.isTerminated()) {
  178. return;
  179. }
  180. var req = {
  181. sid: this.sid,
  182. xmlns: dojox.xmpp.xmpp.BODY_NS
  183. }
  184. var envelope
  185. if (this.protocolPacketQueue.length > 0){
  186. req.rid= this.rid++;
  187. envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false));
  188. envelope.append(this.processProtocolPacketQueue());
  189. envelope.append("</body>");
  190. delete this.lastPollTime;
  191. } else {
  192. //console.log("Nothing to send, I'm just polling.");
  193. if(this.lastPollTime) {
  194. var now = new Date().getTime();
  195. if(now - this.lastPollTime < this.polling) {
  196. //console.log("Waiting to poll ", this.polling - (now - this.lastPollTime)+10);
  197. this.dispatchTimer = setTimeout(dojo.hitch(this, "_dispatchPacket"), this.polling - (now - this.lastPollTime)+10);
  198. return;
  199. }
  200. }
  201. req.rid= this.rid++;
  202. this.lastPollTime = new Date().getTime();
  203. envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, true));
  204. }
  205. this.addToOutboundQueue(envelope.toString(),req.rid);
  206. },
  207. redispatchPacket: function(rid){
  208. var env = this.outboundRequests[rid];
  209. this.sendXml(env, rid);
  210. },
  211. addToOutboundQueue: function(msg, rid){
  212. this.outboundQueue.push({msg: msg,rid: rid});
  213. this.outboundRequests[rid]=msg;
  214. this.sendXml(msg, rid);
  215. },
  216. removeFromOutboundQueue: function(rid){
  217. for(var i=0; i<this.outboundQueue.length;i++){
  218. if (rid == this.outboundQueue[i]["rid"]){
  219. this.outboundQueue.splice(i, 1);
  220. break;
  221. }
  222. }
  223. delete this.outboundRequests[rid];
  224. },
  225. processProtocolPacketQueue: function(){
  226. var packets = new dojox.string.Builder();
  227. for(var i=0; i<this.protocolPacketQueue.length;i++){
  228. packets.append(this.protocolPacketQueue[i]);
  229. }
  230. this.protocolPacketQueue=[];
  231. return packets.toString();
  232. },
  233. sendXml: function(message, rid){
  234. if(this.isTerminated()) {
  235. return false;
  236. }
  237. //console.log("TransportSession::sendXml()"+ new Date().getTime() + " RID: ", rid, " MSG: ", message);
  238. this.transmitState = "transmitting";
  239. var def = null;
  240. if(this.useScriptSrcTransport) {
  241. //console.log("using script src to transmit");
  242. def = dojox.xmpp.bosh.get({
  243. rid: rid,
  244. url: this.serviceUrl+'?'+encodeURIComponent(message),
  245. error: dojo.hitch(this, function(res, io){
  246. this.setState("Terminate", "error");
  247. return false;
  248. }),
  249. timeout: this.sendTimeout
  250. });
  251. } else {
  252. def = dojo.rawXhrPost({
  253. contentType: "text/xml",
  254. url: this.serviceUrl,
  255. postData: message,
  256. handleAs: "xml",
  257. error: dojo.hitch(this, function(res, io) {
  258. ////console.log("foo", res, io.xhr.responseXML, io.xhr.status);
  259. return this.processError(io.xhr.responseXML, io.xhr.status , rid);
  260. }),
  261. timeout: this.sendTimeout
  262. });
  263. }
  264. //process the result document
  265. def.addCallback(this, function(res){
  266. return this.processDocument(res, rid);
  267. });
  268. return def;
  269. },
  270. processDocument: function(doc, rid){
  271. if(this.isTerminated() || !doc.firstChild) {
  272. return false;
  273. }
  274. //console.log("TransportSession:processDocument() ", doc, rid);
  275. this.transmitState = "idle";
  276. var body = doc.firstChild;
  277. if (body.nodeName != 'body'){
  278. //console.log("TransportSession::processDocument() firstChild is not <body> element ", doc, " RID: ", rid);
  279. }
  280. if (this.outboundQueue.length<1){return false;}
  281. var expectedId = this.outboundQueue[0]["rid"];
  282. //console.log("expectedId", expectedId);
  283. if (rid==expectedId){
  284. this.removeFromOutboundQueue(rid);
  285. this.processResponse(body, rid);
  286. this.processInboundQueue();
  287. }else{
  288. //console.log("TransportSession::processDocument() rid: ", rid, " expected: ", expectedId);
  289. var gap = rid-expectedId;
  290. if (gap < this.hold + 2){
  291. this.addToInboundQueue(doc,rid);
  292. }else{
  293. //console.log("TransportSession::processDocument() RID is outside of the expected response window");
  294. }
  295. }
  296. return doc;
  297. },
  298. processInboundQueue: function(){
  299. while (this.inboundQueue.length > 0) {
  300. var item = this.inboundQueue.shift();
  301. this.processDocument(item["doc"], item["rid"]);
  302. }
  303. },
  304. addToInboundQueue: function(doc,rid){
  305. for (var i=0; i<this.inboundQueue.length;i++){
  306. if (rid < this.inboundQueue[i]["rid"]){continue;}
  307. this.inboundQueue.splice(i,0,{doc: doc, rid: rid});
  308. }
  309. },
  310. processResponse: function(body,rid){
  311. ////console.log("TransportSession:processResponse() ", body, " RID: ", rid);
  312. if (body.getAttribute("type")=='terminate'){
  313. var reasonNode = body.firstChild.firstChild;
  314. var errorMessage = "";
  315. if(reasonNode.nodeName == "conflict") {
  316. errorMessage = "conflict"
  317. }
  318. this.setState("Terminate", errorMessage);
  319. return;
  320. }
  321. if ((this.state != 'Ready')&&(this.state != 'Terminate')) {
  322. var sid=body.getAttribute("sid");
  323. if (sid){
  324. this.sid=sid;
  325. } else {
  326. throw new Error("No sid returned during xmpp session startup");
  327. }
  328. this.authId = body.getAttribute("authid");
  329. if (this.authId == "") {
  330. if (this.authRetries-- < 1) {
  331. console.error("Unable to obtain Authorization ID");
  332. this.terminateSession();
  333. }
  334. }
  335. this.wait= body.getAttribute("wait");
  336. if( body.getAttribute("polling")){
  337. this.polling= parseInt(body.getAttribute("polling"))*1000;
  338. }
  339. //console.log("Polling value ", this.polling);
  340. this.inactivity = body.getAttribute("inactivity");
  341. this.setState("Ready");
  342. }
  343. dojo.forEach(body.childNodes, function(node){
  344. this.processProtocolResponse(node, rid);
  345. }, this);
  346. //need to make sure, since if you use sendXml directly instead of using
  347. //dispatch packets, there wont' be a call back function here
  348. //normally the deferred will get fired by a child message at the protocol level
  349. //but if it hasn't fired by now, go ahead and fire it with the full body
  350. /*if (this.deferredRequests[rid] && this.deferredRequests[rid].fired==-1){
  351. this.deferredRequests[rid].callback(body);
  352. }*/
  353. //delete from the list of outstanding requests
  354. //delete this.deferredRequests[rid];
  355. if (this.transmitState == "idle"){
  356. this.dispatchPacket();
  357. }
  358. },
  359. processProtocolResponse: function(msg, rid){
  360. //summary
  361. //process the individual protocol messages and if there
  362. //is a matching set of protocolMatchType, matchId, and matchPropery
  363. //fire off the deferred
  364. this.onProcessProtocolResponse(msg);
  365. var key = msg.nodeName + "-" +msg.getAttribute("id");
  366. var def = this.deferredRequests[key];
  367. if (def){
  368. def.callback(msg);
  369. delete this.deferredRequests[key];
  370. }
  371. },
  372. setState: function(state, message){
  373. if (this.state != state) {
  374. if (this["on"+state]){
  375. this["on"+state](state, this.state, message);
  376. }
  377. this.state=state;
  378. }
  379. },
  380. isTerminated: function() {
  381. return this.state=="Terminate";
  382. },
  383. processError: function(err, httpStatusCode,rid){
  384. //console.log("Processing server error ", err, httpStatusCode,rid);
  385. if(this.isTerminated()) {
  386. return false;
  387. }
  388. if(httpStatusCode != 200) {
  389. if(httpStatusCode >= 400 && httpStatusCode < 500){
  390. /* Any status code between 400 and 500 should terminate
  391. * the connection */
  392. this.setState("Terminate", errorMessage);
  393. return false;
  394. }else{
  395. this.removeFromOutboundQueue(rid);
  396. setTimeout(dojo.hitch(this, function(){ this.dispatchPacket(); }), 200);
  397. return true;
  398. }
  399. return false;
  400. }
  401. if (err && err.dojoType && err.dojoType=="timeout"){
  402. //console.log("Wait timeout");
  403. }
  404. this.removeFromOutboundQueue(rid);
  405. //FIXME conditional processing if request will be needed based on type of error.
  406. if(err && err.firstChild) {
  407. //console.log("Error ", err.firstChild.getAttribute("type") + " status code " + httpStatusCode);
  408. if (err.firstChild.getAttribute("type")=='terminate'){
  409. var reasonNode = err.firstChild.firstChild;
  410. var errorMessage = "";
  411. if(reasonNode && reasonNode.nodeName == "conflict") {
  412. errorMessage = "conflict"
  413. }
  414. this.setState("Terminate", errorMessage);
  415. return false;
  416. }
  417. }
  418. this.transmitState = "error";
  419. setTimeout(dojo.hitch(this, function(){ this.dispatchPacket(); }), 200);
  420. //console.log("Error: ", arguments);
  421. return true;
  422. },
  423. //events
  424. onTerminate: function(newState, oldState, message){ },
  425. onProcessProtocolResponse: function(msg){},
  426. onReady: function(newState, oldState){}
  427. });
  428. });