CLIENT GUI Framework
[vnfsdk/refrepo.git] / portal-common / src / main / webapp / common / thirdparty / cometd / cometd.js
1 /*\r
2  * Copyright (c) 2010 the original author or authors.\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 // Namespaces for the cometd implementation\r
18 this.org = this.org || {};\r
19 org.cometd = {};\r
20 \r
21 org.cometd.JSON = {};\r
22 org.cometd.JSON.toJSON = org.cometd.JSON.fromJSON = function(object)\r
23 {\r
24     throw 'Abstract';\r
25 };\r
26 \r
27 org.cometd.Utils = {};\r
28 \r
29 org.cometd.Utils.isString = function(value)\r
30 {\r
31     if (value === undefined || value === null)\r
32     {\r
33         return false;\r
34     }\r
35     return typeof value === 'string' ||  value instanceof String;\r
36 };\r
37 \r
38 org.cometd.Utils.isArray = function(value)\r
39 {\r
40     if (value === undefined || value === null)\r
41     {\r
42         return false;\r
43     }\r
44     return value instanceof Array;\r
45 };\r
46 \r
47 /**\r
48  * Returns whether the given element is contained into the given array.\r
49  * @param element the element to check presence for\r
50  * @param array the array to check for the element presence\r
51  * @return the index of the element, if present, or a negative index if the element is not present\r
52  */\r
53 org.cometd.Utils.inArray = function(element, array)\r
54 {\r
55     for (var i = 0; i < array.length; ++i)\r
56     {\r
57         if (element === array[i])\r
58         {\r
59             return i;\r
60         }\r
61     }\r
62     return -1;\r
63 };\r
64 \r
65 org.cometd.Utils.setTimeout = function(cometd, funktion, delay)\r
66 {\r
67     return window.setTimeout(function()\r
68     {\r
69         try\r
70         {\r
71             funktion();\r
72         }\r
73         catch (x)\r
74         {\r
75             cometd._debug('Exception invoking timed function', funktion, x);\r
76         }\r
77     }, delay);\r
78 };\r
79 \r
80 org.cometd.Utils.clearTimeout = function(timeoutHandle)\r
81 {\r
82     window.clearTimeout(timeoutHandle);\r
83 };\r
84 \r
85 /**\r
86  * A registry for transports used by the Cometd object.\r
87  */\r
88 org.cometd.TransportRegistry = function()\r
89 {\r
90     var _types = [];\r
91     var _transports = {};\r
92 \r
93     this.getTransportTypes = function()\r
94     {\r
95         return _types.slice(0);\r
96     };\r
97 \r
98     this.findTransportTypes = function(version, crossDomain, url)\r
99     {\r
100         var result = [];\r
101         for (var i = 0; i < _types.length; ++i)\r
102         {\r
103             var type = _types[i];\r
104             if (_transports[type].accept(version, crossDomain, url) === true)\r
105             {\r
106                 result.push(type);\r
107             }\r
108         }\r
109         return result;\r
110     };\r
111 \r
112     this.negotiateTransport = function(types, version, crossDomain, url)\r
113     {\r
114         for (var i = 0; i < _types.length; ++i)\r
115         {\r
116             var type = _types[i];\r
117             for (var j = 0; j < types.length; ++j)\r
118             {\r
119                 if (type === types[j])\r
120                 {\r
121                     var transport = _transports[type];\r
122                     if (transport.accept(version, crossDomain, url) === true)\r
123                     {\r
124                         return transport;\r
125                     }\r
126                 }\r
127             }\r
128         }\r
129         return null;\r
130     };\r
131 \r
132     this.add = function(type, transport, index)\r
133     {\r
134         var existing = false;\r
135         for (var i = 0; i < _types.length; ++i)\r
136         {\r
137             if (_types[i] === type)\r
138             {\r
139                 existing = true;\r
140                 break;\r
141             }\r
142         }\r
143 \r
144         if (!existing)\r
145         {\r
146             if (typeof index !== 'number')\r
147             {\r
148                 _types.push(type);\r
149             }\r
150             else\r
151             {\r
152                 _types.splice(index, 0, type);\r
153             }\r
154             _transports[type] = transport;\r
155         }\r
156 \r
157         return !existing;\r
158     };\r
159 \r
160     this.find = function(type)\r
161     {\r
162         for (var i = 0; i < _types.length; ++i)\r
163         {\r
164             if (_types[i] === type)\r
165             {\r
166                 return _transports[type];\r
167             }\r
168         }\r
169         return null;\r
170     };\r
171 \r
172     this.remove = function(type)\r
173     {\r
174         for (var i = 0; i < _types.length; ++i)\r
175         {\r
176             if (_types[i] === type)\r
177             {\r
178                 _types.splice(i, 1);\r
179                 var transport = _transports[type];\r
180                 delete _transports[type];\r
181                 return transport;\r
182             }\r
183         }\r
184         return null;\r
185     };\r
186 \r
187     this.clear = function()\r
188     {\r
189         _types = [];\r
190         _transports = {};\r
191     };\r
192 \r
193     this.reset = function()\r
194     {\r
195         for (var i = 0; i < _types.length; ++i)\r
196         {\r
197             _transports[_types[i]].reset();\r
198         }\r
199     };\r
200 };\r
201 \r
202 /**\r
203  * Base object with the common functionality for transports.\r
204  */\r
205 org.cometd.Transport = function()\r
206 {\r
207     var _type;\r
208     var _cometd;\r
209 \r
210     /**\r
211      * Function invoked just after a transport has been successfully registered.\r
212      * @param type the type of transport (for example 'long-polling')\r
213      * @param cometd the cometd object this transport has been registered to\r
214      * @see #unregistered()\r
215      */\r
216     this.registered = function(type, cometd)\r
217     {\r
218         _type = type;\r
219         _cometd = cometd;\r
220     };\r
221 \r
222     /**\r
223      * Function invoked just after a transport has been successfully unregistered.\r
224      * @see #registered(type, cometd)\r
225      */\r
226     this.unregistered = function()\r
227     {\r
228         _type = null;\r
229         _cometd = null;\r
230     };\r
231 \r
232     this._debug = function()\r
233     {\r
234         _cometd._debug.apply(_cometd, arguments);\r
235     };\r
236 \r
237     this._mixin = function()\r
238     {\r
239         return _cometd._mixin.apply(_cometd, arguments);\r
240     };\r
241 \r
242     this.getConfiguration = function()\r
243     {\r
244         return _cometd.getConfiguration();\r
245     };\r
246 \r
247     this.getAdvice = function()\r
248     {\r
249         return _cometd.getAdvice();\r
250     };\r
251 \r
252     this.setTimeout = function(funktion, delay)\r
253     {\r
254         return org.cometd.Utils.setTimeout(_cometd, funktion, delay);\r
255     };\r
256 \r
257     this.clearTimeout = function(handle)\r
258     {\r
259         org.cometd.Utils.clearTimeout(handle);\r
260     };\r
261 \r
262     /**\r
263      * Converts the given response into an array of bayeux messages\r
264      * @param response the response to convert\r
265      * @return an array of bayeux messages obtained by converting the response\r
266      */\r
267     this.convertToMessages = function (response)\r
268     {\r
269         if (org.cometd.Utils.isString(response))\r
270         {\r
271             try\r
272             {\r
273                 return org.cometd.JSON.fromJSON(response);\r
274             }\r
275             catch(x)\r
276             {\r
277                 this._debug('Could not convert to JSON the following string', '"' + response + '"');\r
278                 throw x;\r
279             }\r
280         }\r
281         if (org.cometd.Utils.isArray(response))\r
282         {\r
283             return response;\r
284         }\r
285         if (response === undefined || response === null)\r
286         {\r
287             return [];\r
288         }\r
289         if (response instanceof Object)\r
290         {\r
291             return [response];\r
292         }\r
293         throw 'Conversion Error ' + response + ', typeof ' + (typeof response);\r
294     };\r
295 \r
296     /**\r
297      * Returns whether this transport can work for the given version and cross domain communication case.\r
298      * @param version a string indicating the transport version\r
299      * @param crossDomain a boolean indicating whether the communication is cross domain\r
300      * @return true if this transport can work for the given version and cross domain communication case,\r
301      * false otherwise\r
302      */\r
303     this.accept = function(version, crossDomain, url)\r
304     {\r
305         throw 'Abstract';\r
306     };\r
307 \r
308     /**\r
309      * Returns the type of this transport.\r
310      * @see #registered(type, cometd)\r
311      */\r
312     this.getType = function()\r
313     {\r
314         return _type;\r
315     };\r
316 \r
317     this.send = function(envelope, metaConnect)\r
318     {\r
319         throw 'Abstract';\r
320     };\r
321 \r
322     this.reset = function()\r
323     {\r
324         this._debug('Transport', _type, 'reset');\r
325     };\r
326 \r
327     this.abort = function()\r
328     {\r
329         this._debug('Transport', _type, 'aborted');\r
330     };\r
331 \r
332     this.toString = function()\r
333     {\r
334         return this.getType();\r
335     };\r
336 };\r
337 \r
338 org.cometd.Transport.derive = function(baseObject)\r
339 {\r
340     function F() {}\r
341     F.prototype = baseObject;\r
342     return new F();\r
343 };\r
344 \r
345 /**\r
346  * Base object with the common functionality for transports based on requests.\r
347  * The key responsibility is to allow at most 2 outstanding requests to the server,\r
348  * to avoid that requests are sent behind a long poll.\r
349  * To achieve this, we have one reserved request for the long poll, and all other\r
350  * requests are serialized one after the other.\r
351  */\r
352 org.cometd.RequestTransport = function()\r
353 {\r
354     var _super = new org.cometd.Transport();\r
355     var _self = org.cometd.Transport.derive(_super);\r
356     var _requestIds = 0;\r
357     var _metaConnectRequest = null;\r
358     var _requests = [];\r
359     var _envelopes = [];\r
360 \r
361     function _coalesceEnvelopes(envelope)\r
362     {\r
363         while (_envelopes.length > 0)\r
364         {\r
365             var envelopeAndRequest = _envelopes[0];\r
366             var newEnvelope = envelopeAndRequest[0];\r
367             var newRequest = envelopeAndRequest[1];\r
368             if (newEnvelope.url === envelope.url &&\r
369                     newEnvelope.sync === envelope.sync)\r
370             {\r
371                 _envelopes.shift();\r
372                 envelope.messages = envelope.messages.concat(newEnvelope.messages);\r
373                 this._debug('Coalesced', newEnvelope.messages.length, 'messages from request', newRequest.id);\r
374                 continue;\r
375             }\r
376             break;\r
377         }\r
378     }\r
379 \r
380     function _transportSend(envelope, request)\r
381     {\r
382         this.transportSend(envelope, request);\r
383         request.expired = false;\r
384 \r
385         if (!envelope.sync)\r
386         {\r
387             var maxDelay = this.getConfiguration().maxNetworkDelay;\r
388             var delay = maxDelay;\r
389             if (request.metaConnect === true)\r
390             {\r
391                 delay += this.getAdvice().timeout;\r
392             }\r
393 \r
394             this._debug('Transport', this.getType(), 'waiting at most', delay, 'ms for the response, maxNetworkDelay', maxDelay);\r
395 \r
396             var self = this;\r
397             request.timeout = this.setTimeout(function()\r
398             {\r
399                 request.expired = true;\r
400                 if (request.xhr)\r
401                 {\r
402                     request.xhr.abort();\r
403                 }\r
404                 var errorMessage = 'Request ' + request.id + ' of transport ' + self.getType() + ' exceeded ' + delay + ' ms max network delay';\r
405                 self._debug(errorMessage);\r
406                 self.complete(request, false, request.metaConnect);\r
407                 envelope.onFailure(request.xhr, envelope.messages, 'timeout', errorMessage);\r
408             }, delay);\r
409         }\r
410     }\r
411 \r
412     function _queueSend(envelope)\r
413     {\r
414         var requestId = ++_requestIds;\r
415         var request = {\r
416             id: requestId,\r
417             metaConnect: false\r
418         };\r
419 \r
420         // Consider the metaConnect requests which should always be present\r
421         if (_requests.length < this.getConfiguration().maxConnections - 1)\r
422         {\r
423             _requests.push(request);\r
424             _transportSend.call(this, envelope, request);\r
425         }\r
426         else\r
427         {\r
428             this._debug('Transport', this.getType(), 'queueing request', requestId, 'envelope', envelope);\r
429             _envelopes.push([envelope, request]);\r
430         }\r
431     }\r
432 \r
433     function _metaConnectComplete(request)\r
434     {\r
435         var requestId = request.id;\r
436         this._debug('Transport', this.getType(), 'metaConnect complete, request', requestId);\r
437         if (_metaConnectRequest !== null && _metaConnectRequest.id !== requestId)\r
438         {\r
439             throw 'Longpoll request mismatch, completing request ' + requestId;\r
440         }\r
441 \r
442         // Reset metaConnect request\r
443         _metaConnectRequest = null;\r
444     }\r
445 \r
446     function _complete(request, success)\r
447     {\r
448         var index = org.cometd.Utils.inArray(request, _requests);\r
449         // The index can be negative if the request has been aborted\r
450         if (index >= 0)\r
451         {\r
452             _requests.splice(index, 1);\r
453         }\r
454 \r
455         if (_envelopes.length > 0)\r
456         {\r
457             var envelopeAndRequest = _envelopes.shift();\r
458             var nextEnvelope = envelopeAndRequest[0];\r
459             var nextRequest = envelopeAndRequest[1];\r
460             this._debug('Transport dequeued request', nextRequest.id);\r
461             if (success)\r
462             {\r
463                 if (this.getConfiguration().autoBatch)\r
464                 {\r
465                     _coalesceEnvelopes.call(this, nextEnvelope);\r
466                 }\r
467                 _queueSend.call(this, nextEnvelope);\r
468                 this._debug('Transport completed request', request.id, nextEnvelope);\r
469             }\r
470             else\r
471             {\r
472                 // Keep the semantic of calling response callbacks asynchronously after the request\r
473                 var self = this;\r
474                 this.setTimeout(function()\r
475                 {\r
476                     self.complete(nextRequest, false, nextRequest.metaConnect);\r
477                     nextEnvelope.onFailure(nextRequest.xhr, nextEnvelope.messages, 'error', 'Previous request failed');\r
478                 }, 0);\r
479             }\r
480         }\r
481     }\r
482 \r
483     _self.complete = function(request, success, metaConnect)\r
484     {\r
485         if (metaConnect)\r
486         {\r
487             _metaConnectComplete.call(this, request);\r
488         }\r
489         else\r
490         {\r
491             _complete.call(this, request, success);\r
492         }\r
493     };\r
494 \r
495     /**\r
496      * Performs the actual send depending on the transport type details.\r
497      * @param envelope the envelope to send\r
498      * @param request the request information\r
499      */\r
500     _self.transportSend = function(envelope, request)\r
501     {\r
502         throw 'Abstract';\r
503     };\r
504 \r
505     _self.transportSuccess = function(envelope, request, responses)\r
506     {\r
507         if (!request.expired)\r
508         {\r
509             this.clearTimeout(request.timeout);\r
510             this.complete(request, true, request.metaConnect);\r
511             if (responses && responses.length > 0)\r
512             {\r
513                 envelope.onSuccess(responses);\r
514             }\r
515             else\r
516             {\r
517                 envelope.onFailure(request.xhr, envelope.messages, 'Empty HTTP response');\r
518             }\r
519         }\r
520     };\r
521 \r
522     _self.transportFailure = function(envelope, request, reason, exception)\r
523     {\r
524         if (!request.expired)\r
525         {\r
526             this.clearTimeout(request.timeout);\r
527             this.complete(request, false, request.metaConnect);\r
528             envelope.onFailure(request.xhr, envelope.messages, reason, exception);\r
529         }\r
530     };\r
531 \r
532     function _metaConnectSend(envelope)\r
533     {\r
534         if (_metaConnectRequest !== null)\r
535         {\r
536             throw 'Concurrent metaConnect requests not allowed, request id=' + _metaConnectRequest.id + ' not yet completed';\r
537         }\r
538 \r
539         var requestId = ++_requestIds;\r
540         this._debug('Transport', this.getType(), 'metaConnect send, request', requestId, 'envelope', envelope);\r
541         var request = {\r
542             id: requestId,\r
543             metaConnect: true\r
544         };\r
545         _transportSend.call(this, envelope, request);\r
546         _metaConnectRequest = request;\r
547     }\r
548 \r
549     _self.send = function(envelope, metaConnect)\r
550     {\r
551         if (metaConnect)\r
552         {\r
553             _metaConnectSend.call(this, envelope);\r
554         }\r
555         else\r
556         {\r
557             _queueSend.call(this, envelope);\r
558         }\r
559     };\r
560 \r
561     _self.abort = function()\r
562     {\r
563         _super.abort();\r
564         for (var i = 0; i < _requests.length; ++i)\r
565         {\r
566             var request = _requests[i];\r
567             this._debug('Aborting request', request);\r
568             if (request.xhr)\r
569             {\r
570                 request.xhr.abort();\r
571             }\r
572         }\r
573         if (_metaConnectRequest)\r
574         {\r
575             this._debug('Aborting metaConnect request', _metaConnectRequest);\r
576             if (_metaConnectRequest.xhr)\r
577             {\r
578                 _metaConnectRequest.xhr.abort();\r
579             }\r
580         }\r
581         this.reset();\r
582     };\r
583 \r
584     _self.reset = function()\r
585     {\r
586         _super.reset();\r
587         _metaConnectRequest = null;\r
588         _requests = [];\r
589         _envelopes = [];\r
590     };\r
591 \r
592     return _self;\r
593 };\r
594 \r
595 org.cometd.LongPollingTransport = function()\r
596 {\r
597     var _super = new org.cometd.RequestTransport();\r
598     var _self = org.cometd.Transport.derive(_super);\r
599     // By default, support cross domain\r
600     var _supportsCrossDomain = true;\r
601 \r
602     _self.accept = function(version, crossDomain, url)\r
603     {\r
604         return _supportsCrossDomain || !crossDomain;\r
605     };\r
606 \r
607     _self.xhrSend = function(packet)\r
608     {\r
609         throw 'Abstract';\r
610     };\r
611 \r
612     _self.transportSend = function(envelope, request)\r
613     {\r
614         this._debug('Transport', this.getType(), 'sending request', request.id, 'envelope', envelope);\r
615 \r
616         var self = this;\r
617         try\r
618         {\r
619             var sameStack = true;\r
620             request.xhr = this.xhrSend({\r
621                 transport: this,\r
622                 url: envelope.url,\r
623                 sync: envelope.sync,\r
624                 headers: this.getConfiguration().requestHeaders,\r
625                 body: org.cometd.JSON.toJSON(envelope.messages),\r
626                 onSuccess: function(response)\r
627                 {\r
628                     self._debug('Transport', self.getType(), 'received response', response);\r
629                     var success = false;\r
630                     try\r
631                     {\r
632                         var received = self.convertToMessages(response);\r
633                         if (received.length === 0)\r
634                         {\r
635                             _supportsCrossDomain = false;\r
636                             self.transportFailure(envelope, request, 'no response', null);\r
637                         }\r
638                         else\r
639                         {\r
640                             success = true;\r
641                             self.transportSuccess(envelope, request, received);\r
642                         }\r
643                     }\r
644                     catch(x)\r
645                     {\r
646                         self._debug(x);\r
647                         if (!success)\r
648                         {\r
649                             _supportsCrossDomain = false;\r
650                             self.transportFailure(envelope, request, 'bad response', x);\r
651                         }\r
652                     }\r
653                 },\r
654                 onError: function(reason, exception)\r
655                 {\r
656                     _supportsCrossDomain = false;\r
657                     if (sameStack)\r
658                     {\r
659                         // Keep the semantic of calling response callbacks asynchronously after the request\r
660                         self.setTimeout(function()\r
661                         {\r
662                             self.transportFailure(envelope, request, reason, exception);\r
663                         }, 0);\r
664                     }\r
665                     else\r
666                     {\r
667                         self.transportFailure(envelope, request, reason, exception);\r
668                     }\r
669                 }\r
670             });\r
671             sameStack = false;\r
672         }\r
673         catch (x)\r
674         {\r
675             _supportsCrossDomain = false;\r
676             // Keep the semantic of calling response callbacks asynchronously after the request\r
677             this.setTimeout(function()\r
678             {\r
679                 self.transportFailure(envelope, request, 'error', x);\r
680             }, 0);\r
681         }\r
682     };\r
683 \r
684     _self.reset = function()\r
685     {\r
686         _super.reset();\r
687         _supportsCrossDomain = true;\r
688     };\r
689 \r
690     return _self;\r
691 };\r
692 \r
693 org.cometd.CallbackPollingTransport = function()\r
694 {\r
695     var _super = new org.cometd.RequestTransport();\r
696     var _self = org.cometd.Transport.derive(_super);\r
697     var _maxLength = 2000;\r
698 \r
699     _self.accept = function(version, crossDomain, url)\r
700     {\r
701         return true;\r
702     };\r
703 \r
704     _self.jsonpSend = function(packet)\r
705     {\r
706         throw 'Abstract';\r
707     };\r
708 \r
709     _self.transportSend = function(envelope, request)\r
710     {\r
711         var self = this;\r
712 \r
713         // Microsoft Internet Explorer has a 2083 URL max length\r
714         // We must ensure that we stay within that length\r
715         var start = 0;\r
716         var length = envelope.messages.length;\r
717         var lengths = [];\r
718         while (length > 0)\r
719         {\r
720             // Encode the messages because all brackets, quotes, commas, colons, etc\r
721             // present in the JSON will be URL encoded, taking many more characters\r
722             var json = org.cometd.JSON.toJSON(envelope.messages.slice(start, start + length));\r
723             var urlLength = envelope.url.length + encodeURI(json).length;\r
724 \r
725             // Let's stay on the safe side and use 2000 instead of 2083\r
726             // also because we did not count few characters among which\r
727             // the parameter name 'message' and the parameter 'jsonp',\r
728             // which sum up to about 50 chars\r
729             if (urlLength > _maxLength)\r
730             {\r
731                 if (length === 1)\r
732                 {\r
733                     var x = 'Bayeux message too big (' + urlLength + ' bytes, max is ' + _maxLength + ') ' +\r
734                             'for transport ' + this.getType();\r
735                     // Keep the semantic of calling response callbacks asynchronously after the request\r
736                     this.setTimeout(function()\r
737                     {\r
738                         self.transportFailure(envelope, request, 'error', x);\r
739                     }, 0);\r
740                     return;\r
741                 }\r
742 \r
743                 --length;\r
744                 continue;\r
745             }\r
746 \r
747             lengths.push(length);\r
748             start += length;\r
749             length = envelope.messages.length - start;\r
750         }\r
751 \r
752         // Here we are sure that the messages can be sent within the URL limit\r
753 \r
754         var envelopeToSend = envelope;\r
755         if (lengths.length > 1)\r
756         {\r
757             var begin = 0;\r
758             var end = lengths[0];\r
759             this._debug('Transport', this.getType(), 'split', envelope.messages.length, 'messages into', lengths.join(' + '));\r
760             envelopeToSend = this._mixin(false, {}, envelope);\r
761             envelopeToSend.messages = envelope.messages.slice(begin, end);\r
762             envelopeToSend.onSuccess = envelope.onSuccess;\r
763             envelopeToSend.onFailure = envelope.onFailure;\r
764 \r
765             for (var i = 1; i < lengths.length; ++i)\r
766             {\r
767                 var nextEnvelope = this._mixin(false, {}, envelope);\r
768                 begin = end;\r
769                 end += lengths[i];\r
770                 nextEnvelope.messages = envelope.messages.slice(begin, end);\r
771                 nextEnvelope.onSuccess = envelope.onSuccess;\r
772                 nextEnvelope.onFailure = envelope.onFailure;\r
773                 this.send(nextEnvelope, request.metaConnect);\r
774             }\r
775         }\r
776 \r
777         this._debug('Transport', this.getType(), 'sending request', request.id, 'envelope', envelopeToSend);\r
778 \r
779         try\r
780         {\r
781             var sameStack = true;\r
782             this.jsonpSend({\r
783                 transport: this,\r
784                 url: envelopeToSend.url,\r
785                 sync: envelopeToSend.sync,\r
786                 headers: this.getConfiguration().requestHeaders,\r
787                 body: org.cometd.JSON.toJSON(envelopeToSend.messages),\r
788                 onSuccess: function(responses)\r
789                 {\r
790                     var success = false;\r
791                     try\r
792                     {\r
793                         var received = self.convertToMessages(responses);\r
794                         if (received.length === 0)\r
795                         {\r
796                             self.transportFailure(envelopeToSend, request, 'no response');\r
797                         }\r
798                         else\r
799                         {\r
800                             success=true;\r
801                             self.transportSuccess(envelopeToSend, request, received);\r
802                         }\r
803                     }\r
804                     catch (x)\r
805                     {\r
806                         self._debug(x);\r
807                         if (!success)\r
808                         {\r
809                             self.transportFailure(envelopeToSend, request, 'bad response', x);\r
810                         }\r
811                     }\r
812                 },\r
813                 onError: function(reason, exception)\r
814                 {\r
815                     if (sameStack)\r
816                     {\r
817                         // Keep the semantic of calling response callbacks asynchronously after the request\r
818                         self.setTimeout(function()\r
819                         {\r
820                             self.transportFailure(envelopeToSend, request, reason, exception);\r
821                         }, 0);\r
822                     }\r
823                     else\r
824                     {\r
825                         self.transportFailure(envelopeToSend, request, reason, exception);\r
826                     }\r
827                 }\r
828             });\r
829             sameStack = false;\r
830         }\r
831         catch (xx)\r
832         {\r
833             // Keep the semantic of calling response callbacks asynchronously after the request\r
834             this.setTimeout(function()\r
835             {\r
836                 self.transportFailure(envelopeToSend, request, 'error', xx);\r
837             }, 0);\r
838         }\r
839     };\r
840 \r
841     return _self;\r
842 };\r
843 \r
844 org.cometd.WebSocketTransport = function()\r
845 {\r
846     var _super = new org.cometd.Transport();\r
847     var _self = org.cometd.Transport.derive(_super);\r
848     var _cometd;\r
849     // By default, support WebSocket\r
850     var _supportsWebSocket = true;\r
851     // Whether we were able to establish a WebSocket connection\r
852     var _webSocketSupported = false;\r
853     // Envelopes that have been sent\r
854     var _envelopes = {};\r
855     // Timeouts for messages that have been sent\r
856     var _timeouts = {};\r
857     var _webSocket = null;\r
858     var _opened = false;\r
859     var _connected = false;\r
860     var _successCallback;\r
861 \r
862     function _websocketConnect()\r
863     {\r
864         // Mangle the URL, changing the scheme from 'http' to 'ws'\r
865         var url = _cometd.getURL().replace(/^http/, 'ws');\r
866         this._debug('Transport', this.getType(), 'connecting to URL', url);\r
867 \r
868         var self = this;\r
869         var connectTimer = null;\r
870 \r
871         var connectTimeout = _cometd.getConfiguration().connectTimeout;\r
872         if (connectTimeout > 0)\r
873         {\r
874             connectTimer = this.setTimeout(function()\r
875             {\r
876                 connectTimer = null;\r
877                 if (!_opened)\r
878                 {\r
879                     self._debug('Transport', self.getType(), 'timed out while connecting to URL', url, ':', connectTimeout, 'ms');\r
880                     self.onClose(1002, 'Connect Timeout');\r
881                 }\r
882             }, connectTimeout);\r
883         }\r
884 \r
885         var webSocket = new org.cometd.WebSocket(url);\r
886         var onopen = function()\r
887         {\r
888             self._debug('WebSocket opened', webSocket);\r
889             if (connectTimer)\r
890             {\r
891                 self.clearTimeout(connectTimer);\r
892                 connectTimer = null;\r
893             }\r
894             if (webSocket !== _webSocket)\r
895             {\r
896                 // It's possible that the onopen callback is invoked\r
897                 // with a delay so that we have already reconnected\r
898                 self._debug('Ignoring open event, WebSocket', _webSocket);\r
899                 return;\r
900             }\r
901             self.onOpen();\r
902         };\r
903         var onclose = function(event)\r
904         {\r
905             var code = event ? event.code : 1000;\r
906             var reason = event ? event.reason : undefined;\r
907             self._debug('WebSocket closed', code, '/', reason, webSocket);\r
908             if (connectTimer)\r
909             {\r
910                 self.clearTimeout(connectTimer);\r
911                 connectTimer = null;\r
912             }\r
913             if (webSocket !== _webSocket)\r
914             {\r
915                 // The onclose callback may be invoked when the server sends\r
916                 // the close message reply, but after we have already reconnected\r
917                 self._debug('Ignoring close event, WebSocket', _webSocket);\r
918                 return;\r
919             }\r
920             self.onClose(code, reason);\r
921         };\r
922         var onmessage = function(message)\r
923         {\r
924             self._debug('WebSocket message', message, webSocket);\r
925             if (webSocket !== _webSocket)\r
926             {\r
927                 self._debug('Ignoring message event, WebSocket', _webSocket);\r
928                 return;\r
929             }\r
930             self.onMessage(message);\r
931         };\r
932 \r
933         webSocket.onopen = onopen;\r
934         webSocket.onclose = onclose;\r
935         webSocket.onerror = function()\r
936         {\r
937             onclose({ code: 1002 });\r
938         };\r
939         webSocket.onmessage = onmessage;\r
940 \r
941         _webSocket = webSocket;\r
942         this._debug('Transport', this.getType(), 'configured callbacks on', webSocket);\r
943     }\r
944 \r
945     function _webSocketSend(envelope, metaConnect)\r
946     {\r
947         var json = org.cometd.JSON.toJSON(envelope.messages);\r
948 \r
949         _webSocket.send(json);\r
950         this._debug('Transport', this.getType(), 'sent', envelope, 'metaConnect =', metaConnect);\r
951 \r
952         // Manage the timeout waiting for the response\r
953         var maxDelay = this.getConfiguration().maxNetworkDelay;\r
954         var delay = maxDelay;\r
955         if (metaConnect)\r
956         {\r
957             delay += this.getAdvice().timeout;\r
958             _connected = true;\r
959         }\r
960 \r
961         var messageIds = [];\r
962         for (var i = 0; i < envelope.messages.length; ++i)\r
963         {\r
964             var message = envelope.messages[i];\r
965             if (message.id)\r
966             {\r
967                 messageIds.push(message.id);\r
968                 var self = this;\r
969                 var webSocket = _webSocket;\r
970                 _timeouts[message.id] = this.setTimeout(function()\r
971                 {\r
972                     if (webSocket)\r
973                     {\r
974                         webSocket.close(1000, 'Timeout');\r
975                     }\r
976                 }, delay);\r
977             }\r
978         }\r
979 \r
980         this._debug('Transport', this.getType(), 'waiting at most', delay, 'ms for messages', messageIds, 'maxNetworkDelay', maxDelay, ', timeouts:', _timeouts);\r
981     }\r
982 \r
983     function _send(envelope, metaConnect)\r
984     {\r
985         try\r
986         {\r
987             if (_webSocket === null)\r
988             {\r
989                 _websocketConnect.call(this);\r
990             }\r
991             // We may have a non-null _webSocket, but not be open yet so\r
992             // to avoid out of order deliveries, we check if we are open\r
993             else if (_opened)\r
994             {\r
995                 _webSocketSend.call(this, envelope, metaConnect);\r
996             }\r
997         }\r
998         catch (x)\r
999         {\r
1000             // Keep the semantic of calling response callbacks asynchronously after the request\r
1001             var webSocket = _webSocket;\r
1002             this.setTimeout(function()\r
1003             {\r
1004                 envelope.onFailure(webSocket, envelope.messages, 'error', x);\r
1005             }, 0);\r
1006         }\r
1007     }\r
1008 \r
1009     _self.onOpen = function()\r
1010     {\r
1011         this._debug('Transport', this.getType(), 'opened', _webSocket);\r
1012         _opened = true;\r
1013         _webSocketSupported = true;\r
1014 \r
1015         this._debug('Sending pending messages', _envelopes);\r
1016         for (var key in _envelopes)\r
1017         {\r
1018             var element = _envelopes[key];\r
1019             var envelope = element[0];\r
1020             var metaConnect = element[1];\r
1021             // Store the success callback, which is independent from the envelope,\r
1022             // so that it can be used to notify arrival of messages.\r
1023             _successCallback = envelope.onSuccess;\r
1024             _webSocketSend.call(this, envelope, metaConnect);\r
1025         }\r
1026     };\r
1027 \r
1028     _self.onMessage = function(wsMessage)\r
1029     {\r
1030         this._debug('Transport', this.getType(), 'received websocket message', wsMessage, _webSocket);\r
1031 \r
1032         var close = false;\r
1033         var messages = this.convertToMessages(wsMessage.data);\r
1034         var messageIds = [];\r
1035         for (var i = 0; i < messages.length; ++i)\r
1036         {\r
1037             var message = messages[i];\r
1038 \r
1039             // Detect if the message is a response to a request we made.\r
1040             // If it's a meta message, for sure it's a response;\r
1041             // otherwise it's a publish message and publish responses lack the data field\r
1042             if (/^\/meta\//.test(message.channel) || message.data === undefined)\r
1043             {\r
1044                 if (message.id)\r
1045                 {\r
1046                     messageIds.push(message.id);\r
1047 \r
1048                     var timeout = _timeouts[message.id];\r
1049                     if (timeout)\r
1050                     {\r
1051                         this.clearTimeout(timeout);\r
1052                         delete _timeouts[message.id];\r
1053                         this._debug('Transport', this.getType(), 'removed timeout for message', message.id, ', timeouts', _timeouts);\r
1054                     }\r
1055                 }\r
1056             }\r
1057 \r
1058             if ('/meta/connect' === message.channel)\r
1059             {\r
1060                 _connected = false;\r
1061             }\r
1062             if ('/meta/disconnect' === message.channel && !_connected)\r
1063             {\r
1064                 close = true;\r
1065             }\r
1066         }\r
1067 \r
1068         // Remove the envelope corresponding to the messages\r
1069         var removed = false;\r
1070         for (var j = 0; j < messageIds.length; ++j)\r
1071         {\r
1072             var id = messageIds[j];\r
1073             for (var key in _envelopes)\r
1074             {\r
1075                 var ids = key.split(',');\r
1076                 var index = org.cometd.Utils.inArray(id, ids);\r
1077                 if (index >= 0)\r
1078                 {\r
1079                     removed = true;\r
1080                     ids.splice(index, 1);\r
1081                     var envelope = _envelopes[key][0];\r
1082                     var metaConnect = _envelopes[key][1];\r
1083                     delete _envelopes[key];\r
1084                     if (ids.length > 0)\r
1085                     {\r
1086                         _envelopes[ids.join(',')] = [envelope, metaConnect];\r
1087                     }\r
1088                     break;\r
1089                 }\r
1090             }\r
1091         }\r
1092         if (removed)\r
1093         {\r
1094             this._debug('Transport', this.getType(), 'removed envelope, envelopes', _envelopes);\r
1095         }\r
1096 \r
1097         _successCallback.call(this, messages);\r
1098 \r
1099         if (close)\r
1100         {\r
1101             _webSocket.close(1000, 'Disconnect');\r
1102         }\r
1103     };\r
1104 \r
1105     _self.onClose = function(code, reason)\r
1106     {\r
1107         this._debug('Transport', this.getType(), 'closed', code, reason, _webSocket);\r
1108 \r
1109         // Remember if we were able to connect\r
1110         // This close event could be due to server shutdown, and if it restarts we want to try websocket again\r
1111         _supportsWebSocket = _webSocketSupported;\r
1112 \r
1113         for (var id in _timeouts)\r
1114         {\r
1115             this.clearTimeout(_timeouts[id]);\r
1116         }\r
1117         _timeouts = {};\r
1118 \r
1119         for (var key in _envelopes)\r
1120         {\r
1121             var envelope = _envelopes[key][0];\r
1122             var metaConnect = _envelopes[key][1];\r
1123             if (metaConnect)\r
1124             {\r
1125                 _connected = false;\r
1126             }\r
1127             envelope.onFailure(_webSocket, envelope.messages, 'closed ' + code + '/' + reason);\r
1128         }\r
1129         _envelopes = {};\r
1130 \r
1131         if (_webSocket !== null && _opened)\r
1132         {\r
1133             _webSocket.close(1000, 'Close');\r
1134         }\r
1135         _opened = false;\r
1136         _webSocket = null;\r
1137     };\r
1138 \r
1139     _self.registered = function(type, cometd)\r
1140     {\r
1141         _super.registered(type, cometd);\r
1142         _cometd = cometd;\r
1143     };\r
1144 \r
1145     _self.accept = function(version, crossDomain, url)\r
1146     {\r
1147         // Using !! to return a boolean (and not the WebSocket object)\r
1148         return _supportsWebSocket && !!org.cometd.WebSocket && _cometd.websocketEnabled !== false;\r
1149     };\r
1150 \r
1151     _self.send = function(envelope, metaConnect)\r
1152     {\r
1153         this._debug('Transport', this.getType(), 'sending', envelope, 'metaConnect =', metaConnect);\r
1154 \r
1155         // Store the envelope in any case; if the websocket cannot be opened, we fail it in close()\r
1156         var messageIds = [];\r
1157         for (var i = 0; i < envelope.messages.length; ++i)\r
1158         {\r
1159             var message = envelope.messages[i];\r
1160             if (message.id)\r
1161             {\r
1162                 messageIds.push(message.id);\r
1163             }\r
1164         }\r
1165         _envelopes[messageIds.join(',')] = [envelope, metaConnect];\r
1166         this._debug('Transport', this.getType(), 'stored envelope, envelopes', _envelopes);\r
1167 \r
1168         _send.call(this, envelope, metaConnect);\r
1169     };\r
1170 \r
1171     _self.abort = function()\r
1172     {\r
1173         _super.abort();\r
1174         if (_webSocket !== null)\r
1175         {\r
1176             try\r
1177             {\r
1178                 _webSocket.close(1001);\r
1179             }\r
1180             catch (x)\r
1181             {\r
1182                 // Firefox may throw, just ignore\r
1183                 this._debug(x);\r
1184             }\r
1185         }\r
1186         this.reset();\r
1187     };\r
1188 \r
1189     _self.reset = function()\r
1190     {\r
1191         _super.reset();\r
1192         if (_webSocket !== null && _opened)\r
1193         {\r
1194             _webSocket.close(1000, 'Reset');\r
1195         }\r
1196         _supportsWebSocket = true;\r
1197         _webSocketSupported = false;\r
1198         _timeouts = {};\r
1199         _envelopes = {};\r
1200         _webSocket = null;\r
1201         _opened = false;\r
1202         _successCallback = null;\r
1203     };\r
1204 \r
1205     return _self;\r
1206 };\r
1207 \r
1208 /**\r
1209  * The constructor for a Cometd object, identified by an optional name.\r
1210  * The default name is the string 'default'.\r
1211  * In the rare case a page needs more than one Bayeux conversation,\r
1212  * a new instance can be created via:\r
1213  * <pre>\r
1214  * var bayeuxUrl2 = ...;\r
1215  *\r
1216  * // Dojo style\r
1217  * var cometd2 = new dojox.Cometd('another_optional_name');\r
1218  *\r
1219  * // jQuery style\r
1220  * var cometd2 = new $.Cometd('another_optional_name');\r
1221  *\r
1222  * cometd2.init({url: bayeuxUrl2});\r
1223  * </pre>\r
1224  * @param name the optional name of this cometd object\r
1225  */\r
1226 // IMPLEMENTATION NOTES:\r
1227 // Be very careful in not changing the function order and pass this file every time through JSLint (http://jslint.com)\r
1228 // The only implied globals must be "dojo", "org" and "window", and check that there are no "unused" warnings\r
1229 // Failing to pass JSLint may result in shrinkers/minifiers to create an unusable file.\r
1230 org.cometd.Cometd = function(name)\r
1231 {\r
1232     var _cometd = this;\r
1233     var _name = name || 'default';\r
1234     var _crossDomain = false;\r
1235     var _transports = new org.cometd.TransportRegistry();\r
1236     var _transport;\r
1237     var _status = 'disconnected';\r
1238     var _messageId = 0;\r
1239     var _clientId = null;\r
1240     var _batch = 0;\r
1241     var _messageQueue = [];\r
1242     var _internalBatch = false;\r
1243     var _listeners = {};\r
1244     var _backoff = 0;\r
1245     var _scheduledSend = null;\r
1246     var _extensions = [];\r
1247     var _advice = {};\r
1248     var _handshakeProps;\r
1249     var _publishCallbacks = {};\r
1250     var _reestablish = false;\r
1251     var _connected = false;\r
1252     var _config = {\r
1253         connectTimeout: 0,\r
1254         maxConnections: 2,\r
1255         backoffIncrement: 1000,\r
1256         maxBackoff: 60000,\r
1257         logLevel: 'info',\r
1258         reverseIncomingExtensions: true,\r
1259         maxNetworkDelay: 10000,\r
1260         requestHeaders: {},\r
1261         appendMessageTypeToURL: true,\r
1262         autoBatch: false,\r
1263         advice: {\r
1264             timeout: 60000,\r
1265             interval: 0,\r
1266             reconnect: 'retry'\r
1267         }\r
1268     };\r
1269 \r
1270     /**\r
1271      * Mixes in the given objects into the target object by copying the properties.\r
1272      * @param deep if the copy must be deep\r
1273      * @param target the target object\r
1274      * @param objects the objects whose properties are copied into the target\r
1275      */\r
1276     this._mixin = function(deep, target, objects)\r
1277     {\r
1278         var result = target || {};\r
1279 \r
1280         // Skip first 2 parameters (deep and target), and loop over the others\r
1281         for (var i = 2; i < arguments.length; ++i)\r
1282         {\r
1283             var object = arguments[i];\r
1284 \r
1285             if (object === undefined || object === null)\r
1286             {\r
1287                 continue;\r
1288             }\r
1289 \r
1290             for (var propName in object)\r
1291             {\r
1292                 var prop = object[propName];\r
1293                 var targ = result[propName];\r
1294 \r
1295                 // Avoid infinite loops\r
1296                 if (prop === target)\r
1297                 {\r
1298                     continue;\r
1299                 }\r
1300                 // Do not mixin undefined values\r
1301                 if (prop === undefined)\r
1302                 {\r
1303                     continue;\r
1304                 }\r
1305 \r
1306                 if (deep && typeof prop === 'object' && prop !== null)\r
1307                 {\r
1308                     if (prop instanceof Array)\r
1309                     {\r
1310                         result[propName] = this._mixin(deep, targ instanceof Array ? targ : [], prop);\r
1311                     }\r
1312                     else\r
1313                     {\r
1314                         var source = typeof targ === 'object' && !(targ instanceof Array) ? targ : {};\r
1315                         result[propName] = this._mixin(deep, source, prop);\r
1316                     }\r
1317                 }\r
1318                 else\r
1319                 {\r
1320                     result[propName] = prop;\r
1321                 }\r
1322             }\r
1323         }\r
1324 \r
1325         return result;\r
1326     };\r
1327 \r
1328     function _isString(value)\r
1329     {\r
1330         return org.cometd.Utils.isString(value);\r
1331     }\r
1332 \r
1333     function _isFunction(value)\r
1334     {\r
1335         if (value === undefined || value === null)\r
1336         {\r
1337             return false;\r
1338         }\r
1339         return typeof value === 'function';\r
1340     }\r
1341 \r
1342     function _log(level, args)\r
1343     {\r
1344         if (window.console)\r
1345         {\r
1346             var logger = window.console[level];\r
1347             if (_isFunction(logger))\r
1348             {\r
1349                 logger.apply(window.console, args);\r
1350             }\r
1351         }\r
1352     }\r
1353 \r
1354     this._warn = function()\r
1355     {\r
1356         _log('warn', arguments);\r
1357     };\r
1358 \r
1359     this._info = function()\r
1360     {\r
1361         if (_config.logLevel !== 'warn')\r
1362         {\r
1363             _log('info', arguments);\r
1364         }\r
1365     };\r
1366 \r
1367     this._debug = function()\r
1368     {\r
1369         if (_config.logLevel === 'debug')\r
1370         {\r
1371             _log('debug', arguments);\r
1372         }\r
1373     };\r
1374 \r
1375     /**\r
1376      * Returns whether the given hostAndPort is cross domain.\r
1377      * The default implementation checks against window.location.host\r
1378      * but this function can be overridden to make it work in non-browser\r
1379      * environments.\r
1380      *\r
1381      * @param hostAndPort the host and port in format host:port\r
1382      * @return whether the given hostAndPort is cross domain\r
1383      */\r
1384     this._isCrossDomain = function(hostAndPort)\r
1385     {\r
1386         return hostAndPort && hostAndPort !== window.location.host;\r
1387     };\r
1388 \r
1389     function _configure(configuration)\r
1390     {\r
1391         _cometd._debug('Configuring cometd object with', configuration);\r
1392         // Support old style param, where only the Bayeux server URL was passed\r
1393         if (_isString(configuration))\r
1394         {\r
1395             configuration = { url: configuration };\r
1396         }\r
1397         if (!configuration)\r
1398         {\r
1399             configuration = {};\r
1400         }\r
1401 \r
1402         _config = _cometd._mixin(false, _config, configuration);\r
1403 \r
1404         if (!_config.url)\r
1405         {\r
1406             throw 'Missing required configuration parameter \'url\' specifying the Bayeux server URL';\r
1407         }\r
1408 \r
1409         // Check if we're cross domain\r
1410         // [1] = protocol://, [2] = host:port, [3] = host, [4] = IPv6_host, [5] = IPv4_host, [6] = :port, [7] = port, [8] = uri, [9] = rest\r
1411         var urlParts = /(^https?:\/\/)?(((\[[^\]]+\])|([^:\/\?#]+))(:(\d+))?)?([^\?#]*)(.*)?/.exec(_config.url);\r
1412         var hostAndPort = urlParts[2];\r
1413         var uri = urlParts[8];\r
1414         var afterURI = urlParts[9];\r
1415         _crossDomain = _cometd._isCrossDomain(hostAndPort);\r
1416 \r
1417         // Check if appending extra path is supported\r
1418         if (_config.appendMessageTypeToURL)\r
1419         {\r
1420             if (afterURI !== undefined && afterURI.length > 0)\r
1421             {\r
1422                 _cometd._info('Appending message type to URI ' + uri + afterURI + ' is not supported, disabling \'appendMessageTypeToURL\' configuration');\r
1423                 _config.appendMessageTypeToURL = false;\r
1424             }\r
1425             else\r
1426             {\r
1427                 var uriSegments = uri.split('/');\r
1428                 var lastSegmentIndex = uriSegments.length - 1;\r
1429                 if (uri.match(/\/$/))\r
1430                 {\r
1431                     lastSegmentIndex -= 1;\r
1432                 }\r
1433                 if (uriSegments[lastSegmentIndex].indexOf('.') >= 0)\r
1434                 {\r
1435                     // Very likely the CometD servlet's URL pattern is mapped to an extension, such as *.cometd\r
1436                     // It will be difficult to add the extra path in this case\r
1437                     _cometd._info('Appending message type to URI ' + uri + ' is not supported, disabling \'appendMessageTypeToURL\' configuration');\r
1438                     _config.appendMessageTypeToURL = false;\r
1439                 }\r
1440             }\r
1441         }\r
1442     }\r
1443 \r
1444     function _clearSubscriptions()\r
1445     {\r
1446         for (var channel in _listeners)\r
1447         {\r
1448             var subscriptions = _listeners[channel];\r
1449             for (var i = 0; i < subscriptions.length; ++i)\r
1450             {\r
1451                 var subscription = subscriptions[i];\r
1452                 if (subscription && !subscription.listener)\r
1453                 {\r
1454                     delete subscriptions[i];\r
1455                     _cometd._debug('Removed subscription', subscription, 'for channel', channel);\r
1456                 }\r
1457             }\r
1458         }\r
1459     }\r
1460 \r
1461     function _setStatus(newStatus)\r
1462     {\r
1463         if (_status !== newStatus)\r
1464         {\r
1465             _cometd._debug('Status', _status, '->', newStatus);\r
1466             _status = newStatus;\r
1467         }\r
1468     }\r
1469 \r
1470     function _isDisconnected()\r
1471     {\r
1472         return _status === 'disconnecting' || _status === 'disconnected';\r
1473     }\r
1474 \r
1475     function _nextMessageId()\r
1476     {\r
1477         return ++_messageId;\r
1478     }\r
1479 \r
1480     function _applyExtension(scope, callback, name, message, outgoing)\r
1481     {\r
1482         try\r
1483         {\r
1484             return callback.call(scope, message);\r
1485         }\r
1486         catch (x)\r
1487         {\r
1488             _cometd._debug('Exception during execution of extension', name, x);\r
1489             var exceptionCallback = _cometd.onExtensionException;\r
1490             if (_isFunction(exceptionCallback))\r
1491             {\r
1492                 _cometd._debug('Invoking extension exception callback', name, x);\r
1493                 try\r
1494                 {\r
1495                     exceptionCallback.call(_cometd, x, name, outgoing, message);\r
1496                 }\r
1497                 catch(xx)\r
1498                 {\r
1499                     _cometd._info('Exception during execution of exception callback in extension', name, xx);\r
1500                 }\r
1501             }\r
1502             return message;\r
1503         }\r
1504     }\r
1505 \r
1506     function _applyIncomingExtensions(message)\r
1507     {\r
1508         for (var i = 0; i < _extensions.length; ++i)\r
1509         {\r
1510             if (message === undefined || message === null)\r
1511             {\r
1512                 break;\r
1513             }\r
1514 \r
1515             var index = _config.reverseIncomingExtensions ? _extensions.length - 1 - i : i;\r
1516             var extension = _extensions[index];\r
1517             var callback = extension.extension.incoming;\r
1518             if (_isFunction(callback))\r
1519             {\r
1520                 var result = _applyExtension(extension.extension, callback, extension.name, message, false);\r
1521                 message = result === undefined ? message : result;\r
1522             }\r
1523         }\r
1524         return message;\r
1525     }\r
1526 \r
1527     function _applyOutgoingExtensions(message)\r
1528     {\r
1529         for (var i = 0; i < _extensions.length; ++i)\r
1530         {\r
1531             if (message === undefined || message === null)\r
1532             {\r
1533                 break;\r
1534             }\r
1535 \r
1536             var extension = _extensions[i];\r
1537             var callback = extension.extension.outgoing;\r
1538             if (_isFunction(callback))\r
1539             {\r
1540                 var result = _applyExtension(extension.extension, callback, extension.name, message, true);\r
1541                 message = result === undefined ? message : result;\r
1542             }\r
1543         }\r
1544         return message;\r
1545     }\r
1546 \r
1547     function _notify(channel, message)\r
1548     {\r
1549         var subscriptions = _listeners[channel];\r
1550         if (subscriptions && subscriptions.length > 0)\r
1551         {\r
1552             for (var i = 0; i < subscriptions.length; ++i)\r
1553             {\r
1554                 var subscription = subscriptions[i];\r
1555                 // Subscriptions may come and go, so the array may have 'holes'\r
1556                 if (subscription)\r
1557                 {\r
1558                     try\r
1559                     {\r
1560                         subscription.callback.call(subscription.scope, message);\r
1561                     }\r
1562                     catch (x)\r
1563                     {\r
1564                         _cometd._debug('Exception during notification', subscription, message, x);\r
1565                         var listenerCallback = _cometd.onListenerException;\r
1566                         if (_isFunction(listenerCallback))\r
1567                         {\r
1568                             _cometd._debug('Invoking listener exception callback', subscription, x);\r
1569                             try\r
1570                             {\r
1571                                 listenerCallback.call(_cometd, x, subscription.handle, subscription.listener, message);\r
1572                             }\r
1573                             catch (xx)\r
1574                             {\r
1575                                 _cometd._info('Exception during execution of listener callback', subscription, xx);\r
1576                             }\r
1577                         }\r
1578                     }\r
1579                 }\r
1580             }\r
1581         }\r
1582     }\r
1583 \r
1584     function _notifyListeners(channel, message)\r
1585     {\r
1586         // Notify direct listeners\r
1587         _notify(channel, message);\r
1588 \r
1589         // Notify the globbing listeners\r
1590         var channelParts = channel.split('/');\r
1591         var last = channelParts.length - 1;\r
1592         for (var i = last; i > 0; --i)\r
1593         {\r
1594             var channelPart = channelParts.slice(0, i).join('/') + '/*';\r
1595             // We don't want to notify /foo/* if the channel is /foo/bar/baz,\r
1596             // so we stop at the first non recursive globbing\r
1597             if (i === last)\r
1598             {\r
1599                 _notify(channelPart, message);\r
1600             }\r
1601             // Add the recursive globber and notify\r
1602             channelPart += '*';\r
1603             _notify(channelPart, message);\r
1604         }\r
1605     }\r
1606 \r
1607     function _cancelDelayedSend()\r
1608     {\r
1609         if (_scheduledSend !== null)\r
1610         {\r
1611             org.cometd.Utils.clearTimeout(_scheduledSend);\r
1612         }\r
1613         _scheduledSend = null;\r
1614     }\r
1615 \r
1616     function _delayedSend(operation)\r
1617     {\r
1618         _cancelDelayedSend();\r
1619         var delay = _advice.interval + _backoff;\r
1620         _cometd._debug('Function scheduled in', delay, 'ms, interval =', _advice.interval, 'backoff =', _backoff, operation);\r
1621         _scheduledSend = org.cometd.Utils.setTimeout(_cometd, operation, delay);\r
1622     }\r
1623 \r
1624     // Needed to break cyclic dependencies between function definitions\r
1625     var _handleMessages;\r
1626     var _handleFailure;\r
1627 \r
1628     /**\r
1629      * Delivers the messages to the CometD server\r
1630      * @param messages the array of messages to send\r
1631      * @param longpoll true if this send is a long poll\r
1632      */\r
1633     function _send(sync, messages, longpoll, extraPath)\r
1634     {\r
1635         // We must be sure that the messages have a clientId.\r
1636         // This is not guaranteed since the handshake may take time to return\r
1637         // (and hence the clientId is not known yet) and the application\r
1638         // may create other messages.\r
1639         for (var i = 0; i < messages.length; ++i)\r
1640         {\r
1641             var message = messages[i];\r
1642             message.id = '' + _nextMessageId();\r
1643 \r
1644             if (_clientId)\r
1645             {\r
1646                 message.clientId = _clientId;\r
1647             }\r
1648 \r
1649             var callback = undefined;\r
1650             if (_isFunction(message._callback))\r
1651             {\r
1652                 callback = message._callback;\r
1653                 // Remove the publish callback before calling the extensions\r
1654                 delete message._callback;\r
1655             }\r
1656 \r
1657             message = _applyOutgoingExtensions(message);\r
1658             if (message !== undefined && message !== null)\r
1659             {\r
1660                 messages[i] = message;\r
1661                 if (callback)\r
1662                     _publishCallbacks[message.id] = callback;\r
1663             }\r
1664             else\r
1665             {\r
1666                 messages.splice(i--, 1);\r
1667             }\r
1668         }\r
1669 \r
1670         if (messages.length === 0)\r
1671         {\r
1672             return;\r
1673         }\r
1674 \r
1675         var url = _config.url;\r
1676         if (_config.appendMessageTypeToURL)\r
1677         {\r
1678             // If url does not end with '/', then append it\r
1679             if (!url.match(/\/$/))\r
1680             {\r
1681                 url = url + '/';\r
1682             }\r
1683             if (extraPath)\r
1684             {\r
1685                 url = url + extraPath;\r
1686             }\r
1687         }\r
1688 \r
1689         var envelope = {\r
1690             url: url,\r
1691             sync: sync,\r
1692             messages: messages,\r
1693             onSuccess: function(rcvdMessages)\r
1694             {\r
1695                 try\r
1696                 {\r
1697                     _handleMessages.call(_cometd, rcvdMessages);\r
1698                 }\r
1699                 catch (x)\r
1700                 {\r
1701                     _cometd._debug('Exception during handling of messages', x);\r
1702                 }\r
1703             },\r
1704             onFailure: function(conduit, messages, reason, exception)\r
1705             {\r
1706                 try\r
1707                 {\r
1708                     _handleFailure.call(_cometd, conduit, messages, reason, exception);\r
1709                 }\r
1710                 catch (x)\r
1711                 {\r
1712                     _cometd._debug('Exception during handling of failure', x);\r
1713                 }\r
1714             }\r
1715         };\r
1716         _cometd._debug('Send', envelope);\r
1717         _transport.send(envelope, longpoll);\r
1718     }\r
1719 \r
1720     function _queueSend(message)\r
1721     {\r
1722         if (_batch > 0 || _internalBatch === true)\r
1723         {\r
1724             _messageQueue.push(message);\r
1725         }\r
1726         else\r
1727         {\r
1728             _send(false, [message], false);\r
1729         }\r
1730     }\r
1731 \r
1732     /**\r
1733      * Sends a complete bayeux message.\r
1734      * This method is exposed as a public so that extensions may use it\r
1735      * to send bayeux message directly, for example in case of re-sending\r
1736      * messages that have already been sent but that for some reason must\r
1737      * be resent.\r
1738      */\r
1739     this.send = _queueSend;\r
1740 \r
1741     function _resetBackoff()\r
1742     {\r
1743         _backoff = 0;\r
1744     }\r
1745 \r
1746     function _increaseBackoff()\r
1747     {\r
1748         if (_backoff < _config.maxBackoff)\r
1749         {\r
1750             _backoff += _config.backoffIncrement;\r
1751         }\r
1752     }\r
1753 \r
1754     /**\r
1755      * Starts a the batch of messages to be sent in a single request.\r
1756      * @see #_endBatch(sendMessages)\r
1757      */\r
1758     function _startBatch()\r
1759     {\r
1760         ++_batch;\r
1761     }\r
1762 \r
1763     function _flushBatch()\r
1764     {\r
1765         var messages = _messageQueue;\r
1766         _messageQueue = [];\r
1767         if (messages.length > 0)\r
1768         {\r
1769             _send(false, messages, false);\r
1770         }\r
1771     }\r
1772 \r
1773     /**\r
1774      * Ends the batch of messages to be sent in a single request,\r
1775      * optionally sending messages present in the message queue depending\r
1776      * on the given argument.\r
1777      * @see #_startBatch()\r
1778      */\r
1779     function _endBatch()\r
1780     {\r
1781         --_batch;\r
1782         if (_batch < 0)\r
1783         {\r
1784             throw 'Calls to startBatch() and endBatch() are not paired';\r
1785         }\r
1786 \r
1787         if (_batch === 0 && !_isDisconnected() && !_internalBatch)\r
1788         {\r
1789             _flushBatch();\r
1790         }\r
1791     }\r
1792 \r
1793     /**\r
1794      * Sends the connect message\r
1795      */\r
1796     function _connect()\r
1797     {\r
1798         if (!_isDisconnected())\r
1799         {\r
1800             var message = {\r
1801                 channel: '/meta/connect',\r
1802                 connectionType: _transport.getType()\r
1803             };\r
1804 \r
1805             // In case of reload or temporary loss of connection\r
1806             // we want the next successful connect to return immediately\r
1807             // instead of being held by the server, so that connect listeners\r
1808             // can be notified that the connection has been re-established\r
1809             if (!_connected)\r
1810             {\r
1811                 message.advice = { timeout: 0 };\r
1812             }\r
1813 \r
1814             _setStatus('connecting');\r
1815             _cometd._debug('Connect sent', message);\r
1816             _send(false, [message], true, 'connect');\r
1817             _setStatus('connected');\r
1818         }\r
1819     }\r
1820 \r
1821     function _delayedConnect()\r
1822     {\r
1823         _setStatus('connecting');\r
1824         _delayedSend(function()\r
1825         {\r
1826             _connect();\r
1827         });\r
1828     }\r
1829 \r
1830     function _updateAdvice(newAdvice)\r
1831     {\r
1832         if (newAdvice)\r
1833         {\r
1834             _advice = _cometd._mixin(false, {}, _config.advice, newAdvice);\r
1835             _cometd._debug('New advice', _advice);\r
1836         }\r
1837     }\r
1838 \r
1839     function _disconnect(abort)\r
1840     {\r
1841         _cancelDelayedSend();\r
1842         if (abort)\r
1843         {\r
1844             _transport.abort();\r
1845         }\r
1846         _clientId = null;\r
1847         _setStatus('disconnected');\r
1848         _batch = 0;\r
1849         _resetBackoff();\r
1850 \r
1851         // Fail any existing queued message\r
1852         if (_messageQueue.length > 0)\r
1853         {\r
1854             _handleFailure.call(_cometd, undefined, _messageQueue, 'error', 'Disconnected');\r
1855             _messageQueue = [];\r
1856         }\r
1857     }\r
1858 \r
1859     /**\r
1860      * Sends the initial handshake message\r
1861      */\r
1862     function _handshake(handshakeProps)\r
1863     {\r
1864         _clientId = null;\r
1865 \r
1866         _clearSubscriptions();\r
1867 \r
1868         // Reset the transports if we're not retrying the handshake\r
1869         if (_isDisconnected())\r
1870         {\r
1871             _transports.reset();\r
1872             _updateAdvice(_config.advice);\r
1873         }\r
1874         else\r
1875         {\r
1876             // We are retrying the handshake, either because another handshake failed\r
1877             // and we're backing off, or because the server timed us out and asks us to\r
1878             // re-handshake: in both cases, make sure that if the handshake succeeds\r
1879             // the next action is a connect.\r
1880             _updateAdvice(_cometd._mixin(false, _advice, {reconnect: 'retry'}));\r
1881         }\r
1882 \r
1883         _batch = 0;\r
1884 \r
1885         // Mark the start of an internal batch.\r
1886         // This is needed because handshake and connect are async.\r
1887         // It may happen that the application calls init() then subscribe()\r
1888         // and the subscribe message is sent before the connect message, if\r
1889         // the subscribe message is not held until the connect message is sent.\r
1890         // So here we start a batch to hold temporarily any message until\r
1891         // the connection is fully established.\r
1892         _internalBatch = true;\r
1893 \r
1894         // Save the properties provided by the user, so that\r
1895         // we can reuse them during automatic re-handshake\r
1896         _handshakeProps = handshakeProps;\r
1897 \r
1898         var version = '1.0';\r
1899 \r
1900         // Figure out the transports to send to the server\r
1901         var transportTypes = _transports.findTransportTypes(version, _crossDomain, _config.url);\r
1902 \r
1903         var bayeuxMessage = {\r
1904             version: version,\r
1905             minimumVersion: '0.9',\r
1906             channel: '/meta/handshake',\r
1907             supportedConnectionTypes: transportTypes,\r
1908             advice: {\r
1909                 timeout: _advice.timeout,\r
1910                 interval: _advice.interval\r
1911             }\r
1912         };\r
1913         // Do not allow the user to mess with the required properties,\r
1914         // so merge first the user properties and *then* the bayeux message\r
1915         var message = _cometd._mixin(false, {}, _handshakeProps, bayeuxMessage);\r
1916 \r
1917         // Pick up the first available transport as initial transport\r
1918         // since we don't know if the server supports it\r
1919         _transport = _transports.negotiateTransport(transportTypes, version, _crossDomain, _config.url);\r
1920         _cometd._debug('Initial transport is', _transport.getType());\r
1921 \r
1922         // We started a batch to hold the application messages,\r
1923         // so here we must bypass it and send immediately.\r
1924         _setStatus('handshaking');\r
1925         _cometd._debug('Handshake sent', message);\r
1926         _send(false, [message], false, 'handshake');\r
1927     }\r
1928 \r
1929     function _delayedHandshake()\r
1930     {\r
1931         _setStatus('handshaking');\r
1932 \r
1933         // We will call _handshake() which will reset _clientId, but we want to avoid\r
1934         // that between the end of this method and the call to _handshake() someone may\r
1935         // call publish() (or other methods that call _queueSend()).\r
1936         _internalBatch = true;\r
1937 \r
1938         _delayedSend(function()\r
1939         {\r
1940             _handshake(_handshakeProps);\r
1941         });\r
1942     }\r
1943 \r
1944     function _failHandshake(message)\r
1945     {\r
1946         _notifyListeners('/meta/handshake', message);\r
1947         _notifyListeners('/meta/unsuccessful', message);\r
1948 \r
1949         // Only try again if we haven't been disconnected and\r
1950         // the advice permits us to retry the handshake\r
1951         var retry = !_isDisconnected() && _advice.reconnect !== 'none';\r
1952         if (retry)\r
1953         {\r
1954             _increaseBackoff();\r
1955             _delayedHandshake();\r
1956         }\r
1957         else\r
1958         {\r
1959             _disconnect(false);\r
1960         }\r
1961     }\r
1962 \r
1963     function _handshakeResponse(message)\r
1964     {\r
1965         if (message.successful)\r
1966         {\r
1967             // Save clientId, figure out transport, then follow the advice to connect\r
1968             _clientId = message.clientId;\r
1969 \r
1970             var newTransport = _transports.negotiateTransport(message.supportedConnectionTypes, message.version, _crossDomain, _config.url);\r
1971             if (newTransport === null)\r
1972             {\r
1973                 throw 'Could not negotiate transport with server; client ' +\r
1974                       _transports.findTransportTypes(message.version, _crossDomain, _config.url) +\r
1975                       ', server ' + message.supportedConnectionTypes;\r
1976             }\r
1977             else if (_transport !== newTransport)\r
1978             {\r
1979                 _cometd._debug('Transport', _transport, '->', newTransport);\r
1980                 _transport = newTransport;\r
1981             }\r
1982 \r
1983             // End the internal batch and allow held messages from the application\r
1984             // to go to the server (see _handshake() where we start the internal batch).\r
1985             _internalBatch = false;\r
1986             _flushBatch();\r
1987 \r
1988             // Here the new transport is in place, as well as the clientId, so\r
1989             // the listeners can perform a publish() if they want.\r
1990             // Notify the listeners before the connect below.\r
1991             message.reestablish = _reestablish;\r
1992             _reestablish = true;\r
1993             _notifyListeners('/meta/handshake', message);\r
1994 \r
1995             var action = _isDisconnected() ? 'none' : _advice.reconnect;\r
1996             switch (action)\r
1997             {\r
1998                 case 'retry':\r
1999                     _resetBackoff();\r
2000                     _delayedConnect();\r
2001                     break;\r
2002                 case 'none':\r
2003                     _disconnect(false);\r
2004                     break;\r
2005                 default:\r
2006                     throw 'Unrecognized advice action ' + action;\r
2007             }\r
2008         }\r
2009         else\r
2010         {\r
2011             _failHandshake(message);\r
2012         }\r
2013     }\r
2014 \r
2015     function _handshakeFailure(xhr, message)\r
2016     {\r
2017         _failHandshake({\r
2018             successful: false,\r
2019             failure: true,\r
2020             channel: '/meta/handshake',\r
2021             request: message,\r
2022             xhr: xhr,\r
2023             advice: {\r
2024                 reconnect: 'retry',\r
2025                 interval: _backoff\r
2026             }\r
2027         });\r
2028     }\r
2029 \r
2030     function _failConnect(message)\r
2031     {\r
2032         // Notify the listeners after the status change but before the next action\r
2033         _notifyListeners('/meta/connect', message);\r
2034         _notifyListeners('/meta/unsuccessful', message);\r
2035 \r
2036         // This may happen when the server crashed, the current clientId\r
2037         // will be invalid, and the server will ask to handshake again\r
2038         // Listeners can call disconnect(), so check the state after they run\r
2039         var action = _isDisconnected() ? 'none' : _advice.reconnect;\r
2040         switch (action)\r
2041         {\r
2042             case 'retry':\r
2043                 _delayedConnect();\r
2044                 _increaseBackoff();\r
2045                 break;\r
2046             case 'handshake':\r
2047                 // The current transport may be failed (e.g. network disconnection)\r
2048                 // Reset the transports so the new handshake picks up the right one\r
2049                 _transports.reset();\r
2050                 _resetBackoff();\r
2051                 _delayedHandshake();\r
2052                 break;\r
2053             case 'none':\r
2054                 _disconnect(false);\r
2055                 break;\r
2056             default:\r
2057                 throw 'Unrecognized advice action' + action;\r
2058         }\r
2059     }\r
2060 \r
2061     function _connectResponse(message)\r
2062     {\r
2063         _connected = message.successful;\r
2064 \r
2065         if (_connected)\r
2066         {\r
2067             _notifyListeners('/meta/connect', message);\r
2068 \r
2069             // Normally, the advice will say "reconnect: 'retry', interval: 0"\r
2070             // and the server will hold the request, so when a response returns\r
2071             // we immediately call the server again (long polling)\r
2072             // Listeners can call disconnect(), so check the state after they run\r
2073             var action = _isDisconnected() ? 'none' : _advice.reconnect;\r
2074             switch (action)\r
2075             {\r
2076                 case 'retry':\r
2077                     _resetBackoff();\r
2078                     _delayedConnect();\r
2079                     break;\r
2080                 case 'none':\r
2081                     _disconnect(false);\r
2082                     break;\r
2083                 default:\r
2084                     throw 'Unrecognized advice action ' + action;\r
2085             }\r
2086         }\r
2087         else\r
2088         {\r
2089             _failConnect(message);\r
2090         }\r
2091     }\r
2092 \r
2093     function _connectFailure(xhr, message)\r
2094     {\r
2095         _connected = false;\r
2096         _failConnect({\r
2097             successful: false,\r
2098             failure: true,\r
2099             channel: '/meta/connect',\r
2100             request: message,\r
2101             xhr: xhr,\r
2102             advice: {\r
2103                 reconnect: 'retry',\r
2104                 interval: _backoff\r
2105             }\r
2106         });\r
2107     }\r
2108 \r
2109     function _failDisconnect(message)\r
2110     {\r
2111         _disconnect(true);\r
2112         _notifyListeners('/meta/disconnect', message);\r
2113         _notifyListeners('/meta/unsuccessful', message);\r
2114     }\r
2115 \r
2116     function _disconnectResponse(message)\r
2117     {\r
2118         if (message.successful)\r
2119         {\r
2120             _disconnect(false);\r
2121             _notifyListeners('/meta/disconnect', message);\r
2122         }\r
2123         else\r
2124         {\r
2125             _failDisconnect(message);\r
2126         }\r
2127     }\r
2128 \r
2129     function _disconnectFailure(xhr, message)\r
2130     {\r
2131         _failDisconnect({\r
2132             successful: false,\r
2133             failure: true,\r
2134             channel: '/meta/disconnect',\r
2135             request: message,\r
2136             xhr: xhr,\r
2137             advice: {\r
2138                 reconnect: 'none',\r
2139                 interval: 0\r
2140             }\r
2141         });\r
2142     }\r
2143 \r
2144     function _failSubscribe(message)\r
2145     {\r
2146         _notifyListeners('/meta/subscribe', message);\r
2147         _notifyListeners('/meta/unsuccessful', message);\r
2148     }\r
2149 \r
2150     function _subscribeResponse(message)\r
2151     {\r
2152         if (message.successful)\r
2153         {\r
2154             _notifyListeners('/meta/subscribe', message);\r
2155         }\r
2156         else\r
2157         {\r
2158             _failSubscribe(message);\r
2159         }\r
2160     }\r
2161 \r
2162     function _subscribeFailure(xhr, message)\r
2163     {\r
2164         _failSubscribe({\r
2165             successful: false,\r
2166             failure: true,\r
2167             channel: '/meta/subscribe',\r
2168             request: message,\r
2169             xhr: xhr,\r
2170             advice: {\r
2171                 reconnect: 'none',\r
2172                 interval: 0\r
2173             }\r
2174         });\r
2175     }\r
2176 \r
2177     function _failUnsubscribe(message)\r
2178     {\r
2179         _notifyListeners('/meta/unsubscribe', message);\r
2180         _notifyListeners('/meta/unsuccessful', message);\r
2181     }\r
2182 \r
2183     function _unsubscribeResponse(message)\r
2184     {\r
2185         if (message.successful)\r
2186         {\r
2187             _notifyListeners('/meta/unsubscribe', message);\r
2188         }\r
2189         else\r
2190         {\r
2191             _failUnsubscribe(message);\r
2192         }\r
2193     }\r
2194 \r
2195     function _unsubscribeFailure(xhr, message)\r
2196     {\r
2197         _failUnsubscribe({\r
2198             successful: false,\r
2199             failure: true,\r
2200             channel: '/meta/unsubscribe',\r
2201             request: message,\r
2202             xhr: xhr,\r
2203             advice: {\r
2204                 reconnect: 'none',\r
2205                 interval: 0\r
2206             }\r
2207         });\r
2208     }\r
2209 \r
2210     function _handlePublishCallback(message)\r
2211     {\r
2212         var callback = _publishCallbacks[message.id];\r
2213         if (_isFunction(callback))\r
2214         {\r
2215             delete _publishCallbacks[message.id];\r
2216             callback.call(_cometd, message);\r
2217         }\r
2218     }\r
2219 \r
2220     function _failMessage(message)\r
2221     {\r
2222         _handlePublishCallback(message);\r
2223         _notifyListeners('/meta/publish', message);\r
2224         _notifyListeners('/meta/unsuccessful', message);\r
2225     }\r
2226 \r
2227     function _messageResponse(message)\r
2228     {\r
2229         if (message.successful === undefined)\r
2230         {\r
2231             if (message.data)\r
2232             {\r
2233                 // It is a plain message, and not a bayeux meta message\r
2234                 _notifyListeners(message.channel, message);\r
2235             }\r
2236             else\r
2237             {\r
2238                 _cometd._debug('Unknown message', message);\r
2239             }\r
2240         }\r
2241         else\r
2242         {\r
2243             if (message.successful)\r
2244             {\r
2245                 _handlePublishCallback(message);\r
2246                 _notifyListeners('/meta/publish', message);\r
2247             }\r
2248             else\r
2249             {\r
2250                 _failMessage(message);\r
2251             }\r
2252         }\r
2253     }\r
2254 \r
2255     function _messageFailure(xhr, message)\r
2256     {\r
2257         _failMessage({\r
2258             successful: false,\r
2259             failure: true,\r
2260             channel: message.channel,\r
2261             request: message,\r
2262             xhr: xhr,\r
2263             advice: {\r
2264                 reconnect: 'none',\r
2265                 interval: 0\r
2266             }\r
2267         });\r
2268     }\r
2269 \r
2270     function _receive(message)\r
2271     {\r
2272         message = _applyIncomingExtensions(message);\r
2273         if (message === undefined || message === null)\r
2274         {\r
2275             return;\r
2276         }\r
2277 \r
2278         _updateAdvice(message.advice);\r
2279 \r
2280         var channel = message.channel;\r
2281         switch (channel)\r
2282         {\r
2283             case '/meta/handshake':\r
2284                 _handshakeResponse(message);\r
2285                 break;\r
2286             case '/meta/connect':\r
2287                 _connectResponse(message);\r
2288                 break;\r
2289             case '/meta/disconnect':\r
2290                 _disconnectResponse(message);\r
2291                 break;\r
2292             case '/meta/subscribe':\r
2293                 _subscribeResponse(message);\r
2294                 break;\r
2295             case '/meta/unsubscribe':\r
2296                 _unsubscribeResponse(message);\r
2297                 break;\r
2298             default:\r
2299                 _messageResponse(message);\r
2300                 break;\r
2301         }\r
2302     }\r
2303 \r
2304     /**\r
2305      * Receives a message.\r
2306      * This method is exposed as a public so that extensions may inject\r
2307      * messages simulating that they had been received.\r
2308      */\r
2309     this.receive = _receive;\r
2310 \r
2311     _handleMessages = function(rcvdMessages)\r
2312     {\r
2313         _cometd._debug('Received', rcvdMessages);\r
2314 \r
2315         for (var i = 0; i < rcvdMessages.length; ++i)\r
2316         {\r
2317             var message = rcvdMessages[i];\r
2318             _receive(message);\r
2319         }\r
2320     };\r
2321 \r
2322     _handleFailure = function(conduit, messages, reason, exception)\r
2323     {\r
2324         _cometd._debug('handleFailure', conduit, messages, reason, exception);\r
2325 \r
2326         for (var i = 0; i < messages.length; ++i)\r
2327         {\r
2328             var message = messages[i];\r
2329             var channel = message.channel;\r
2330             switch (channel)\r
2331             {\r
2332                 case '/meta/handshake':\r
2333                     _handshakeFailure(conduit, message);\r
2334                     break;\r
2335                 case '/meta/connect':\r
2336                     _connectFailure(conduit, message);\r
2337                     break;\r
2338                 case '/meta/disconnect':\r
2339                     _disconnectFailure(conduit, message);\r
2340                     break;\r
2341                 case '/meta/subscribe':\r
2342                     _subscribeFailure(conduit, message);\r
2343                     break;\r
2344                 case '/meta/unsubscribe':\r
2345                     _unsubscribeFailure(conduit, message);\r
2346                     break;\r
2347                 default:\r
2348                     _messageFailure(conduit, message);\r
2349                     break;\r
2350             }\r
2351         }\r
2352     };\r
2353 \r
2354     function _hasSubscriptions(channel)\r
2355     {\r
2356         var subscriptions = _listeners[channel];\r
2357         if (subscriptions)\r
2358         {\r
2359             for (var i = 0; i < subscriptions.length; ++i)\r
2360             {\r
2361                 if (subscriptions[i])\r
2362                 {\r
2363                     return true;\r
2364                 }\r
2365             }\r
2366         }\r
2367         return false;\r
2368     }\r
2369 \r
2370     function _resolveScopedCallback(scope, callback)\r
2371     {\r
2372         var delegate = {\r
2373             scope: scope,\r
2374             method: callback\r
2375         };\r
2376         if (_isFunction(scope))\r
2377         {\r
2378             delegate.scope = undefined;\r
2379             delegate.method = scope;\r
2380         }\r
2381         else\r
2382         {\r
2383             if (_isString(callback))\r
2384             {\r
2385                 if (!scope)\r
2386                 {\r
2387                     throw 'Invalid scope ' + scope;\r
2388                 }\r
2389                 delegate.method = scope[callback];\r
2390                 if (!_isFunction(delegate.method))\r
2391                 {\r
2392                     throw 'Invalid callback ' + callback + ' for scope ' + scope;\r
2393                 }\r
2394             }\r
2395             else if (!_isFunction(callback))\r
2396             {\r
2397                 throw 'Invalid callback ' + callback;\r
2398             }\r
2399         }\r
2400         return delegate;\r
2401     }\r
2402 \r
2403     function _addListener(channel, scope, callback, isListener)\r
2404     {\r
2405         // The data structure is a map<channel, subscription[]>, where each subscription\r
2406         // holds the callback to be called and its scope.\r
2407 \r
2408         var delegate = _resolveScopedCallback(scope, callback);\r
2409         _cometd._debug('Adding listener on', channel, 'with scope', delegate.scope, 'and callback', delegate.method);\r
2410 \r
2411         var subscription = {\r
2412             channel: channel,\r
2413             scope: delegate.scope,\r
2414             callback: delegate.method,\r
2415             listener: isListener\r
2416         };\r
2417 \r
2418         var subscriptions = _listeners[channel];\r
2419         if (!subscriptions)\r
2420         {\r
2421             subscriptions = [];\r
2422             _listeners[channel] = subscriptions;\r
2423         }\r
2424 \r
2425         // Pushing onto an array appends at the end and returns the id associated with the element increased by 1.\r
2426         // Note that if:\r
2427         // a.push('a'); var hb=a.push('b'); delete a[hb-1]; var hc=a.push('c');\r
2428         // then:\r
2429         // hc==3, a.join()=='a',,'c', a.length==3\r
2430         var subscriptionID = subscriptions.push(subscription) - 1;\r
2431         subscription.id = subscriptionID;\r
2432         subscription.handle = [channel, subscriptionID];\r
2433 \r
2434         _cometd._debug('Added listener', subscription, 'for channel', channel, 'having id =', subscriptionID);\r
2435 \r
2436         // The subscription to allow removal of the listener is made of the channel and the index\r
2437         return subscription.handle;\r
2438     }\r
2439 \r
2440     function _removeListener(subscription)\r
2441     {\r
2442         var subscriptions = _listeners[subscription[0]];\r
2443         if (subscriptions)\r
2444         {\r
2445             delete subscriptions[subscription[1]];\r
2446             _cometd._debug('Removed listener', subscription);\r
2447         }\r
2448     }\r
2449 \r
2450     //\r
2451     // PUBLIC API\r
2452     //\r
2453 \r
2454     /**\r
2455      * Registers the given transport under the given transport type.\r
2456      * The optional index parameter specifies the "priority" at which the\r
2457      * transport is registered (where 0 is the max priority).\r
2458      * If a transport with the same type is already registered, this function\r
2459      * does nothing and returns false.\r
2460      * @param type the transport type\r
2461      * @param transport the transport object\r
2462      * @param index the index at which this transport is to be registered\r
2463      * @return true if the transport has been registered, false otherwise\r
2464      * @see #unregisterTransport(type)\r
2465      */\r
2466     this.registerTransport = function(type, transport, index)\r
2467     {\r
2468         var result = _transports.add(type, transport, index);\r
2469         if (result)\r
2470         {\r
2471             this._debug('Registered transport', type);\r
2472 \r
2473             if (_isFunction(transport.registered))\r
2474             {\r
2475                 transport.registered(type, this);\r
2476             }\r
2477         }\r
2478         return result;\r
2479     };\r
2480 \r
2481     /**\r
2482      * @return an array of all registered transport types\r
2483      */\r
2484     this.getTransportTypes = function()\r
2485     {\r
2486         return _transports.getTransportTypes();\r
2487     };\r
2488 \r
2489     /**\r
2490      * Unregisters the transport with the given transport type.\r
2491      * @param type the transport type to unregister\r
2492      * @return the transport that has been unregistered,\r
2493      * or null if no transport was previously registered under the given transport type\r
2494      */\r
2495     this.unregisterTransport = function(type)\r
2496     {\r
2497         var transport = _transports.remove(type);\r
2498         if (transport !== null)\r
2499         {\r
2500             this._debug('Unregistered transport', type);\r
2501 \r
2502             if (_isFunction(transport.unregistered))\r
2503             {\r
2504                 transport.unregistered();\r
2505             }\r
2506         }\r
2507         return transport;\r
2508     };\r
2509 \r
2510     this.unregisterTransports = function()\r
2511     {\r
2512         _transports.clear();\r
2513     };\r
2514 \r
2515     this.findTransport = function(name)\r
2516     {\r
2517         return _transports.find(name);\r
2518     };\r
2519 \r
2520     /**\r
2521      * Configures the initial Bayeux communication with the Bayeux server.\r
2522      * Configuration is passed via an object that must contain a mandatory field <code>url</code>\r
2523      * of type string containing the URL of the Bayeux server.\r
2524      * @param configuration the configuration object\r
2525      */\r
2526     this.configure = function(configuration)\r
2527     {\r
2528         _configure.call(this, configuration);\r
2529     };\r
2530 \r
2531     /**\r
2532      * Configures and establishes the Bayeux communication with the Bayeux server\r
2533      * via a handshake and a subsequent connect.\r
2534      * @param configuration the configuration object\r
2535      * @param handshakeProps an object to be merged with the handshake message\r
2536      * @see #configure(configuration)\r
2537      * @see #handshake(handshakeProps)\r
2538      */\r
2539     this.init = function(configuration, handshakeProps)\r
2540     {\r
2541         this.configure(configuration);\r
2542         this.handshake(handshakeProps);\r
2543     };\r
2544 \r
2545     /**\r
2546      * Establishes the Bayeux communication with the Bayeux server\r
2547      * via a handshake and a subsequent connect.\r
2548      * @param handshakeProps an object to be merged with the handshake message\r
2549      */\r
2550     this.handshake = function(handshakeProps)\r
2551     {\r
2552         _setStatus('disconnected');\r
2553         _reestablish = false;\r
2554         _handshake(handshakeProps);\r
2555     };\r
2556 \r
2557     /**\r
2558      * Disconnects from the Bayeux server.\r
2559      * It is possible to suggest to attempt a synchronous disconnect, but this feature\r
2560      * may only be available in certain transports (for example, long-polling may support\r
2561      * it, callback-polling certainly does not).\r
2562      * @param sync whether attempt to perform a synchronous disconnect\r
2563      * @param disconnectProps an object to be merged with the disconnect message\r
2564      */\r
2565     this.disconnect = function(sync, disconnectProps)\r
2566     {\r
2567         if (_isDisconnected())\r
2568         {\r
2569             return;\r
2570         }\r
2571 \r
2572         if (disconnectProps === undefined)\r
2573         {\r
2574             if (typeof sync !== 'boolean')\r
2575             {\r
2576                 disconnectProps = sync;\r
2577                 sync = false;\r
2578             }\r
2579         }\r
2580 \r
2581         var bayeuxMessage = {\r
2582             channel: '/meta/disconnect'\r
2583         };\r
2584         var message = this._mixin(false, {}, disconnectProps, bayeuxMessage);\r
2585         _setStatus('disconnecting');\r
2586         _send(sync === true, [message], false, 'disconnect');\r
2587     };\r
2588 \r
2589     /**\r
2590      * Marks the start of a batch of application messages to be sent to the server\r
2591      * in a single request, obtaining a single response containing (possibly) many\r
2592      * application reply messages.\r
2593      * Messages are held in a queue and not sent until {@link #endBatch()} is called.\r
2594      * If startBatch() is called multiple times, then an equal number of endBatch()\r
2595      * calls must be made to close and send the batch of messages.\r
2596      * @see #endBatch()\r
2597      */\r
2598     this.startBatch = function()\r
2599     {\r
2600         _startBatch();\r
2601     };\r
2602 \r
2603     /**\r
2604      * Marks the end of a batch of application messages to be sent to the server\r
2605      * in a single request.\r
2606      * @see #startBatch()\r
2607      */\r
2608     this.endBatch = function()\r
2609     {\r
2610         _endBatch();\r
2611     };\r
2612 \r
2613     /**\r
2614      * Executes the given callback in the given scope, surrounded by a {@link #startBatch()}\r
2615      * and {@link #endBatch()} calls.\r
2616      * @param scope the scope of the callback, may be omitted\r
2617      * @param callback the callback to be executed within {@link #startBatch()} and {@link #endBatch()} calls\r
2618      */\r
2619     this.batch = function(scope, callback)\r
2620     {\r
2621         var delegate = _resolveScopedCallback(scope, callback);\r
2622         this.startBatch();\r
2623         try\r
2624         {\r
2625             delegate.method.call(delegate.scope);\r
2626             this.endBatch();\r
2627         }\r
2628         catch (x)\r
2629         {\r
2630             this._debug('Exception during execution of batch', x);\r
2631             this.endBatch();\r
2632             throw x;\r
2633         }\r
2634     };\r
2635 \r
2636     /**\r
2637      * Adds a listener for bayeux messages, performing the given callback in the given scope\r
2638      * when a message for the given channel arrives.\r
2639      * @param channel the channel the listener is interested to\r
2640      * @param scope the scope of the callback, may be omitted\r
2641      * @param callback the callback to call when a message is sent to the channel\r
2642      * @returns the subscription handle to be passed to {@link #removeListener(object)}\r
2643      * @see #removeListener(subscription)\r
2644      */\r
2645     this.addListener = function(channel, scope, callback)\r
2646     {\r
2647         if (arguments.length < 2)\r
2648         {\r
2649             throw 'Illegal arguments number: required 2, got ' + arguments.length;\r
2650         }\r
2651         if (!_isString(channel))\r
2652         {\r
2653             throw 'Illegal argument type: channel must be a string';\r
2654         }\r
2655 \r
2656         return _addListener(channel, scope, callback, true);\r
2657     };\r
2658 \r
2659     /**\r
2660      * Removes the subscription obtained with a call to {@link #addListener(string, object, function)}.\r
2661      * @param subscription the subscription to unsubscribe.\r
2662      * @see #addListener(channel, scope, callback)\r
2663      */\r
2664     this.removeListener = function(subscription)\r
2665     {\r
2666         if (!org.cometd.Utils.isArray(subscription))\r
2667         {\r
2668             throw 'Invalid argument: expected subscription, not ' + subscription;\r
2669         }\r
2670 \r
2671         _removeListener(subscription);\r
2672     };\r
2673 \r
2674     /**\r
2675      * Removes all listeners registered with {@link #addListener(channel, scope, callback)} or\r
2676      * {@link #subscribe(channel, scope, callback)}.\r
2677      */\r
2678     this.clearListeners = function()\r
2679     {\r
2680         _listeners = {};\r
2681     };\r
2682 \r
2683     /**\r
2684      * Subscribes to the given channel, performing the given callback in the given scope\r
2685      * when a message for the channel arrives.\r
2686      * @param channel the channel to subscribe to\r
2687      * @param scope the scope of the callback, may be omitted\r
2688      * @param callback the callback to call when a message is sent to the channel\r
2689      * @param subscribeProps an object to be merged with the subscribe message\r
2690      * @return the subscription handle to be passed to {@link #unsubscribe(object)}\r
2691      */\r
2692     this.subscribe = function(channel, scope, callback, subscribeProps)\r
2693     {\r
2694         if (arguments.length < 2)\r
2695         {\r
2696             throw 'Illegal arguments number: required 2, got ' + arguments.length;\r
2697         }\r
2698         if (!_isString(channel))\r
2699         {\r
2700             throw 'Illegal argument type: channel must be a string';\r
2701         }\r
2702         if (_isDisconnected())\r
2703         {\r
2704             throw 'Illegal state: already disconnected';\r
2705         }\r
2706 \r
2707         // Normalize arguments\r
2708         if (_isFunction(scope))\r
2709         {\r
2710             subscribeProps = callback;\r
2711             callback = scope;\r
2712             scope = undefined;\r
2713         }\r
2714 \r
2715         // Only send the message to the server if this client has not yet subscribed to the channel\r
2716         var send = !_hasSubscriptions(channel);\r
2717 \r
2718         var subscription = _addListener(channel, scope, callback, false);\r
2719 \r
2720         if (send)\r
2721         {\r
2722             // Send the subscription message after the subscription registration to avoid\r
2723             // races where the server would send a message to the subscribers, but here\r
2724             // on the client the subscription has not been added yet to the data structures\r
2725             var bayeuxMessage = {\r
2726                 channel: '/meta/subscribe',\r
2727                 subscription: channel\r
2728             };\r
2729             var message = this._mixin(false, {}, subscribeProps, bayeuxMessage);\r
2730             _queueSend(message);\r
2731         }\r
2732 \r
2733         return subscription;\r
2734     };\r
2735 \r
2736     /**\r
2737      * Unsubscribes the subscription obtained with a call to {@link #subscribe(string, object, function)}.\r
2738      * @param subscription the subscription to unsubscribe.\r
2739      */\r
2740     this.unsubscribe = function(subscription, unsubscribeProps)\r
2741     {\r
2742         if (arguments.length < 1)\r
2743         {\r
2744             throw 'Illegal arguments number: required 1, got ' + arguments.length;\r
2745         }\r
2746         if (_isDisconnected())\r
2747         {\r
2748             throw 'Illegal state: already disconnected';\r
2749         }\r
2750 \r
2751         // Remove the local listener before sending the message\r
2752         // This ensures that if the server fails, this client does not get notifications\r
2753         this.removeListener(subscription);\r
2754 \r
2755         var channel = subscription[0];\r
2756         // Only send the message to the server if this client unsubscribes the last subscription\r
2757         if (!_hasSubscriptions(channel))\r
2758         {\r
2759             var bayeuxMessage = {\r
2760                 channel: '/meta/unsubscribe',\r
2761                 subscription: channel\r
2762             };\r
2763             var message = this._mixin(false, {}, unsubscribeProps, bayeuxMessage);\r
2764             _queueSend(message);\r
2765         }\r
2766     };\r
2767 \r
2768     /**\r
2769      * Removes all subscriptions added via {@link #subscribe(channel, scope, callback, subscribeProps)},\r
2770      * but does not remove the listeners added via {@link addListener(channel, scope, callback)}.\r
2771      */\r
2772     this.clearSubscriptions = function()\r
2773     {\r
2774         _clearSubscriptions();\r
2775     };\r
2776 \r
2777     /**\r
2778      * Publishes a message on the given channel, containing the given content.\r
2779      * @param channel the channel to publish the message to\r
2780      * @param content the content of the message\r
2781      * @param publishProps an object to be merged with the publish message\r
2782      */\r
2783     this.publish = function(channel, content, publishProps, publishCallback)\r
2784     {\r
2785         if (arguments.length < 1)\r
2786         {\r
2787             throw 'Illegal arguments number: required 1, got ' + arguments.length;\r
2788         }\r
2789         if (!_isString(channel))\r
2790         {\r
2791             throw 'Illegal argument type: channel must be a string';\r
2792         }\r
2793         if (_isDisconnected())\r
2794         {\r
2795             throw 'Illegal state: already disconnected';\r
2796         }\r
2797 \r
2798         if (_isFunction(content))\r
2799         {\r
2800             publishCallback = content;\r
2801             content = publishProps = {};\r
2802         }\r
2803         else if (_isFunction(publishProps))\r
2804         {\r
2805             publishCallback = publishProps;\r
2806             publishProps = {};\r
2807         }\r
2808 \r
2809         var bayeuxMessage = {\r
2810             channel: channel,\r
2811             data: content,\r
2812             _callback: publishCallback\r
2813         };\r
2814         var message = this._mixin(false, {}, publishProps, bayeuxMessage);\r
2815         _queueSend(message);\r
2816     };\r
2817 \r
2818     /**\r
2819      * Returns a string representing the status of the bayeux communication with the Bayeux server.\r
2820      */\r
2821     this.getStatus = function()\r
2822     {\r
2823         return _status;\r
2824     };\r
2825 \r
2826     /**\r
2827      * Returns whether this instance has been disconnected.\r
2828      */\r
2829     this.isDisconnected = _isDisconnected;\r
2830 \r
2831     /**\r
2832      * Sets the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.\r
2833      * Default value is 1 second, which means if there is a persistent failure the retries will happen\r
2834      * after 1 second, then after 2 seconds, then after 3 seconds, etc. So for example with 15 seconds of\r
2835      * elapsed time, there will be 5 retries (at 1, 3, 6, 10 and 15 seconds elapsed).\r
2836      * @param period the backoff period to set\r
2837      * @see #getBackoffIncrement()\r
2838      */\r
2839     this.setBackoffIncrement = function(period)\r
2840     {\r
2841         _config.backoffIncrement = period;\r
2842     };\r
2843 \r
2844     /**\r
2845      * Returns the backoff period used to increase the backoff time when retrying an unsuccessful or failed message.\r
2846      * @see #setBackoffIncrement(period)\r
2847      */\r
2848     this.getBackoffIncrement = function()\r
2849     {\r
2850         return _config.backoffIncrement;\r
2851     };\r
2852 \r
2853     /**\r
2854      * Returns the backoff period to wait before retrying an unsuccessful or failed message.\r
2855      */\r
2856     this.getBackoffPeriod = function()\r
2857     {\r
2858         return _backoff;\r
2859     };\r
2860 \r
2861     /**\r
2862      * Sets the log level for console logging.\r
2863      * Valid values are the strings 'error', 'warn', 'info' and 'debug', from\r
2864      * less verbose to more verbose.\r
2865      * @param level the log level string\r
2866      */\r
2867     this.setLogLevel = function(level)\r
2868     {\r
2869         _config.logLevel = level;\r
2870     };\r
2871 \r
2872     /**\r
2873      * Registers an extension whose callbacks are called for every incoming message\r
2874      * (that comes from the server to this client implementation) and for every\r
2875      * outgoing message (that originates from this client implementation for the\r
2876      * server).\r
2877      * The format of the extension object is the following:\r
2878      * <pre>\r
2879      * {\r
2880      *     incoming: function(message) { ... },\r
2881      *     outgoing: function(message) { ... }\r
2882      * }\r
2883      * </pre>\r
2884      * Both properties are optional, but if they are present they will be called\r
2885      * respectively for each incoming message and for each outgoing message.\r
2886      * @param name the name of the extension\r
2887      * @param extension the extension to register\r
2888      * @return true if the extension was registered, false otherwise\r
2889      * @see #unregisterExtension(name)\r
2890      */\r
2891     this.registerExtension = function(name, extension)\r
2892     {\r
2893         if (arguments.length < 2)\r
2894         {\r
2895             throw 'Illegal arguments number: required 2, got ' + arguments.length;\r
2896         }\r
2897         if (!_isString(name))\r
2898         {\r
2899             throw 'Illegal argument type: extension name must be a string';\r
2900         }\r
2901 \r
2902         var existing = false;\r
2903         for (var i = 0; i < _extensions.length; ++i)\r
2904         {\r
2905             var existingExtension = _extensions[i];\r
2906             if (existingExtension.name === name)\r
2907             {\r
2908                 existing = true;\r
2909                 break;\r
2910             }\r
2911         }\r
2912         if (!existing)\r
2913         {\r
2914             _extensions.push({\r
2915                 name: name,\r
2916                 extension: extension\r
2917             });\r
2918             this._debug('Registered extension', name);\r
2919 \r
2920             // Callback for extensions\r
2921             if (_isFunction(extension.registered))\r
2922             {\r
2923                 extension.registered(name, this);\r
2924             }\r
2925 \r
2926             return true;\r
2927         }\r
2928         else\r
2929         {\r
2930             this._info('Could not register extension with name', name, 'since another extension with the same name already exists');\r
2931             return false;\r
2932         }\r
2933     };\r
2934 \r
2935     /**\r
2936      * Unregister an extension previously registered with\r
2937      * {@link #registerExtension(name, extension)}.\r
2938      * @param name the name of the extension to unregister.\r
2939      * @return true if the extension was unregistered, false otherwise\r
2940      */\r
2941     this.unregisterExtension = function(name)\r
2942     {\r
2943         if (!_isString(name))\r
2944         {\r
2945             throw 'Illegal argument type: extension name must be a string';\r
2946         }\r
2947 \r
2948         var unregistered = false;\r
2949         for (var i = 0; i < _extensions.length; ++i)\r
2950         {\r
2951             var extension = _extensions[i];\r
2952             if (extension.name === name)\r
2953             {\r
2954                 _extensions.splice(i, 1);\r
2955                 unregistered = true;\r
2956                 this._debug('Unregistered extension', name);\r
2957 \r
2958                 // Callback for extensions\r
2959                 var ext = extension.extension;\r
2960                 if (_isFunction(ext.unregistered))\r
2961                 {\r
2962                     ext.unregistered();\r
2963                 }\r
2964 \r
2965                 break;\r
2966             }\r
2967         }\r
2968         return unregistered;\r
2969     };\r
2970 \r
2971     /**\r
2972      * Find the extension registered with the given name.\r
2973      * @param name the name of the extension to find\r
2974      * @return the extension found or null if no extension with the given name has been registered\r
2975      */\r
2976     this.getExtension = function(name)\r
2977     {\r
2978         for (var i = 0; i < _extensions.length; ++i)\r
2979         {\r
2980             var extension = _extensions[i];\r
2981             if (extension.name === name)\r
2982             {\r
2983                 return extension.extension;\r
2984             }\r
2985         }\r
2986         return null;\r
2987     };\r
2988 \r
2989     /**\r
2990      * Returns the name assigned to this Cometd object, or the string 'default'\r
2991      * if no name has been explicitly passed as parameter to the constructor.\r
2992      */\r
2993     this.getName = function()\r
2994     {\r
2995         return _name;\r
2996     };\r
2997 \r
2998     /**\r
2999      * Returns the clientId assigned by the Bayeux server during handshake.\r
3000      */\r
3001     this.getClientId = function()\r
3002     {\r
3003         return _clientId;\r
3004     };\r
3005 \r
3006     /**\r
3007      * Returns the URL of the Bayeux server.\r
3008      */\r
3009     this.getURL = function()\r
3010     {\r
3011         return _config.url;\r
3012     };\r
3013 \r
3014     this.getTransport = function()\r
3015     {\r
3016         return _transport;\r
3017     };\r
3018 \r
3019     this.getConfiguration = function()\r
3020     {\r
3021         return this._mixin(true, {}, _config);\r
3022     };\r
3023 \r
3024     this.getAdvice = function()\r
3025     {\r
3026         return this._mixin(true, {}, _advice);\r
3027     };\r
3028 \r
3029     // WebSocket handling for Firefox, which deploys WebSocket\r
3030     // under the name of MozWebSocket in Firefox 6, 7, 8 and 9\r
3031     org.cometd.WebSocket = window.WebSocket;\r
3032     if (!org.cometd.WebSocket)\r
3033     {\r
3034         org.cometd.WebSocket = window.MozWebSocket;\r
3035     }\r
3036 };\r
3037 \r
3038 if (typeof define === 'function' && define.amd)\r
3039 {\r
3040     define(function()\r
3041     {\r
3042         return org.cometd;\r
3043     });\r
3044 }\r
3045 \r