TransportSession.js 14 KB

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