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