source: Dev/branches/rest-dojo-ui/client/dojox/xmpp/TransportSession.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 13.8 KB
Line 
1dojo.provide("dojox.xmpp.TransportSession");
2dojo.require("dojox.xmpp.bosh");
3dojo.require("dojox.xmpp.util");
4dojo.require("dojox.data.dom");
5
6dojox.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
21dojo.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});
Note: See TracBrowser for help on using the repository browser.