]> git.babelmonkeys.de Git - xmppchat.git/blob - js/strophejs/strophe.js
Fade out Bubbles and handle IQs
[xmppchat.git] / js / strophejs / strophe.js
1 /*
2     This program is distributed under the terms of the MIT license.
3     Please see the LICENSE file for details.
4
5     Copyright 2006-2008, OGG, LLC
6 */
7
8 /** File: strophe.js
9  *  A JavaScript library for XMPP BOSH.
10  * 
11  *  This is the JavaScript version of the Strophe library.  Since JavaScript
12  *  has no facilities for persistent TCP connections, this library uses
13  *  Bidirectional-streams Over Synchronous HTTP (BOSH) to emulate
14  *  a persistent, stateful, two-way connection to an XMPP server.  More
15  *  information on BOSH can be found in XEP 124.
16  */
17
18 /** PrivateFunction: Function.prototype.bind
19  *  Bind a function to an instance.
20  * 
21  *  This Function object extension method creates a bound method similar
22  *  to those in Python.  This means that the 'this' object will point
23  *  to the instance you want.  See 
24  *  <a href='http://benjamin.smedbergs.us/blog/2007-01-03/bound-functions-and-function-imports-in-javascript/'>Bound Functions and Function Imports in JavaScript</a>
25  *  for a complete explanation.
26  * 
27  *  This extension already exists in some browsers (namely, Firefox 3), but
28  *  we provide it to support those that don't.
29  * 
30  *  Parameters:
31  *    (Object) obj - The object that will become 'this' in the bound function.
32  *  
33  *  Returns:
34  *    The bound function.
35  */
36 if (!Function.prototype.bind) {
37     Function.prototype.bind = function (obj)
38     {
39         var func = this;
40         return function () { return func.apply(obj, arguments); };
41     };
42 }
43
44 /** PrivateFunction: Function.prototype.prependArg
45  *  Prepend an argument to a function.
46  *
47  *  This Function object extension method returns a Function that will
48  *  invoke the original function with an argument prepended.  This is useful 
49  *  when some object has a callback that needs to get that same object as 
50  *  an argument.  The following fragment illustrates a simple case of this
51  *  > var obj = new Foo(this.someMethod);</code></blockquote>
52  *  
53  *  Foo's constructor can now use func.prependArg(this) to ensure the 
54  *  passed in callback function gets the instance of Foo as an argument.  
55  *  Doing this without prependArg would mean not setting the callback
56  *  from the constructor.
57  * 
58  *  This is used inside Strophe for passing the Strophe.Request object to
59  *  the onreadystatechange handler of XMLHttpRequests.
60  *
61  *  Parameters:
62  *    arg - The argument to pass as the first parameter to the function.
63  *
64  *  Returns:
65  *    A new Function which calls the original with the prepended argument.
66  */
67 if (!Function.prototype.prependArg) {
68     Function.prototype.prependArg = function (arg)
69     {
70         var func = this;
71         
72         return function () { 
73             var newargs = [arg];
74             for (var i = 0; i < arguments.length; i++)
75                 newargs.push(arguments[i]);
76             return func.apply(this, newargs); 
77         };
78     };
79 }
80
81 /** PrivateFunction: Array.prototype.indexOf
82  *  Return the index of an object in an array.
83  *
84  *  This function is not supplied by some JavaScript implementations, so
85  *  we provide it if it is missing.  This code is from:
86  *  http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf
87  *
88  *  Parameters:
89  *    (Object) elt - The object to look for.
90  *    (Integer) from - The index from which to start looking. (optional).
91  * 
92  *  Returns:
93  *    The index of elt in the array or -1 if not found.
94  */
95 if (!Array.prototype.indexOf)
96 {
97     Array.prototype.indexOf = function(elt /*, from*/)
98     {
99         var len = this.length;
100         
101         var from = Number(arguments[1]) || 0;
102         from = (from < 0) ? Math.ceil(from) : Math.floor(from);
103         if (from < 0)
104             from += len;
105         
106         for (; from < len; from++) {
107             if (from in this && this[from] === elt)
108                 return from;
109         }
110
111         return -1;
112     };
113 }
114
115
116 /** Function: $build
117  *  Create a Strophe.Builder.
118  *  This is an alias for 'new Strophe.Builder(name, attrs)'.
119  *
120  *  Parameters:
121  *    (String) name - The root element name.
122  *    (Object) attrs - The attributes for the root element in object notation.
123  * 
124  *  Returns:
125  *    A new Strophe.Builder object.
126  */
127 function $build(name, attrs) { return new Strophe.Builder(name, attrs); }
128 /** Function: $msg
129  *  Create a Strophe.Builder with a <message/> element as the root.
130  *
131  *  Parmaeters:
132  *    (Object) attrs - The <message/> element attributes in object notation.
133  *
134  *  Returns:
135  *    A new Strophe.Builder object.
136  */
137 function $msg(attrs) { return new Strophe.Builder("message", attrs); }
138 /** Function: $iq
139  *  Create a Strophe.Builder with an <iq/> element as the root.
140  *
141  *  Parameters:
142  *    (Object) attrs - The <iq/> element attributes in object notation.
143  *
144  *  Returns:
145  *    A new Strophe.Builder object.
146  */
147 function $iq(attrs) { return new Strophe.Builder("iq", attrs); }
148 /** Function: $pres
149  *  Create a Strophe.Builder with a <presence/> element as the root.
150  *
151  *  Parameters:
152  *    (Object) attrs - The <presence/> element attributes in object notation.
153  *
154  *  Returns:
155  *    A new Strophe.Builder object.
156  */
157 function $pres(attrs) { return new Strophe.Builder("presence", attrs); }
158
159 /** Class: Strophe
160  *  An object container for all Strophe library functions.
161  *
162  *  This class is just a container for all the objects and constants
163  *  used in the library.  It is not meant to be instantiated, but to 
164  *  provide a namespace for library objects, constants, and functions.
165  */
166 Strophe = {
167     /** Constants: XMPP Namespace Constants
168      *  Common namespace constants from the XMPP RFCs and XEPs.
169      *
170      *  NS.HTTPBIND - HTTP BIND namespace from XEP 124.
171      *  NS.BOSH - BOSH namespace from XEP 206.
172      *  NS.CLIENT - Main XMPP client namespace.
173      *  NS.AUTH - Legacy authentication namespace.
174      *  NS.ROSTER - Roster operations namespace.
175      *  NS.PROFILE - Profile namespace.
176      *  NS.DISCO_INFO - Service discovery info namespace from XEP 30.
177      *  NS.DISCO_ITEMS - Service discovery items namespace from XEP 30.
178      *  NS.MUC - Multi-User Chat namespace from XEP 45.
179      *  NS.SASL - XMPP SASL namespace from RFC 3920.
180      *  NS.STREAM - XMPP Streams namespace from RFC 3920.
181      *  NS.BIND - XMPP Binding namespace from RFC 3920.
182      *  NS.SESSION - XMPP Session namespace from RFC 3920.
183      */
184     NS: {
185         HTTPBIND: "http://jabber.org/protocol/httpbind",
186         BOSH: "urn:xmpp:xbosh",
187         CLIENT: "jabber:client",
188         AUTH: "jabber:iq:auth",
189         ROSTER: "jabber:iq:roster",
190         PROFILE: "jabber:iq:profile",
191         DISCO_INFO: "http://jabber.org/protocol/disco#info",
192         DISCO_ITEMS: "http://jabber.org/protocol/disco#items",
193         MUC: "http://jabber.org/protocol/muc",
194         SASL: "urn:ietf:params:xml:ns:xmpp-sasl",
195         STREAM: "http://etherx.jabber.org/streams",
196         BIND: "urn:ietf:params:xml:ns:xmpp-bind",
197         SESSION: "urn:ietf:params:xml:ns:xmpp-session",
198         VERSION: "jabber:iq:version"
199     },
200     
201     /** Constants: Connection Status Constants
202      *  Connection status constants for use by the connection handler
203      *  callback.
204      *  
205      *  Status.ERROR - An error has occurred
206      *  Status.CONNECTING - The connection is currently being made
207      *  Status.CONNFAIL - The connection attempt failed
208      *  Status.AUTHENTICATING - The connection is authenticating
209      *  Status.AUTHFAIL - The authentication attempt failed
210      *  Status.CONNECTED - The connection has succeeded
211      *  Status.DISCONNECTED - The connection has been terminated
212      *  Status.DISCONNECTING - The connection is currently being terminated
213      */
214     Status: {
215         ERROR: 0,
216         CONNECTING: 1,
217         CONNFAIL: 2,
218         AUTHENTICATING: 3,
219         AUTHFAIL: 4,
220         CONNECTED: 5,
221         DISCONNECTED: 6,
222         DISCONNECTING: 7
223     },
224
225     /** Constants: Log Level Constants
226      *  Logging level indicators.
227      *
228      *  LogLevel.DEBUG - Debug output
229      *  LogLevel.INFO - Informational output
230      *  LogLevel.WARN - Warnings
231      *  LogLevel.ERROR - Errors
232      *  LogLevel.FATAL - Fatal errors
233      */
234     LogLevel: {
235         DEBUG: 0,
236         INFO: 1,
237         WARN: 2,
238         ERROR: 3,
239         FATAL: 4
240     },
241
242     /** PrivateConstants: DOM Element Type Constants
243      *  DOM element types.
244      *
245      *  ElementType.NORMAL - Normal element.
246      *  ElementType.TEXT - Text data element.
247      */
248     ElementType: {
249         NORMAL: 1,
250         TEXT: 3
251     },
252
253     /** PrivateConstants: Timeout Values
254      *  Timeout values for error states.  These values are in seconds.  
255      *  These should not be changed unless you know exactly what you are 
256      *  doing.
257      *
258      *  TIMEOUT - Time to wait for a request to return.  This defaults to
259      *      70 seconds.
260      *  SECONDARY_TIMEOUT - Time to wait for immediate request return. This
261      *      defaults to 7 seconds.
262      */
263     TIMEOUT: 70,
264     SECONDARY_TIMEOUT: 7,
265
266     /** Function: forEachChild
267      *  Map a function over some or all child elements of a given element.
268      *
269      *  This is a small convenience function for mapping a function over
270      *  some or all of the children of an element.  If elemName is null, all
271      *  children will be passed to the function, otherwise only children
272      *  whose tag names match elemName will be passed.
273      *
274      *  Parameters:
275      *    (XMLElement) elem - The element to operate on.
276      *    (String) elemName - The child element tag name filter.
277      *    (Function) func - The function to apply to each child.  This 
278      *      function should take a single argument, a DOM element.
279      */
280     forEachChild: function (elem, elemName, func) 
281     {
282         var i, childNode;
283         
284         for (i = 0; i < elem.childNodes.length; i++) {
285             childNode = elem.childNodes[i];
286             if (childNode.nodeType == Strophe.ElementType.NORMAL &&
287                 (!elemName || this.isTagEqual(childNode, elemName))) {
288                 func(childNode);
289             }
290         }
291     },
292
293     /** Function: isTagEqual
294      *  Compare an element's tag name with a string.
295      *
296      *  This function is case insensitive.
297      *
298      *  Parameters:
299      *    (XMLElement) el - A DOM element.
300      *    (String) name - The element name.
301      * 
302      *  Returns:
303      *    true if the element's tag name matches _el_, and false
304      *    otherwise.
305      */
306     isTagEqual: function (el, name)
307     {
308         return el.tagName.toLowerCase() == name.toLowerCase();
309     },
310
311     /** Function: xmlElement
312      *  Create an XML DOM element.
313      *
314      *  This function creates an XML DOM element correctly across all 
315      *  implementations. Specifically the Microsoft implementation of
316      *  document.createElement makes DOM elements with 43+ default attributes
317      *  unless elements are created with the ActiveX object Microsoft.XMLDOM.
318      *
319      *  Most DOMs force element names to lowercase, so we use the
320      *  _realname attribute on the created element to store the case
321      *  sensitive name.  This is required to generate proper XML for
322      *  things like vCard avatars (XEP 153).  This attribute is stripped
323      *  out before being sent over the wire or serialized, but you may
324      *  notice it during debugging.
325      *
326      *  Parameters:
327      *    (String) name - The name for the element.
328      *    (Array) attrs - An optional array of key/value pairs to use as 
329      *      element attributes in the following format [['key1', 'value1'], 
330      *      ['key2', 'value2']]
331      *    (String) text - The text child data for the element.
332      *
333      *  Returns:
334      *    A new XML DOM element.
335      */
336     xmlElement: function (name)
337     {
338         // FIXME: this should also support attrs argument in object notation
339         if (!name) { return null; }
340
341         var node = null;
342         if (window.ActiveXObject) {
343             node = new ActiveXObject("Microsoft.XMLDOM").createElement(name);
344         } else {
345             node = document.createElement(name);
346         }
347         // use node._realname to store the case-sensitive version of the tag
348         // name, since some browsers will force tagnames to all lowercase.
349         // this is needed for the <vCard/> tag in XMPP specifically.
350         if (node.tagName != name)
351             node.setAttribute("_realname", name);
352         
353         // FIXME: this should throw errors if args are the wrong type or
354         // there are more than two optional args 
355         var a, i;
356         for (a = 1; a < arguments.length; a++) {
357             if (!arguments[a]) { continue; }
358             if (typeof(arguments[a]) == "string" ||
359                 typeof(arguments[a]) == "number") {
360                 node.appendChild(Strophe.xmlTextNode(arguments[a]));
361             } else if (typeof(arguments[a]) == "object" && 
362                        typeof(arguments[a]['sort']) == "function") {
363                 for (i = 0; i < arguments[a].length; i++) {
364                     if (typeof(arguments[a][i]) == "object" && 
365                         typeof(arguments[a][i]['sort']) == "function") {
366                         node.setAttribute(arguments[a][i][0], 
367                                           arguments[a][i][1]);
368                     }
369                 }
370             }
371         }
372
373         return node;
374     },
375
376     /** Function: xmlTextNode
377      *  Creates an XML DOM text node.
378      *
379      *  Provides a cross implementation version of document.createTextNode.
380      *
381      *  Parameters:
382      *    (String) text - The content of the text node.
383      *
384      *  Returns:
385      *    A new XML DOM text node.
386      */
387     xmlTextNode: function (text)
388     {
389         if (window.ActiveXObject) {
390             return new ActiveXObject("Microsoft.XMLDOM").createTextNode(text);
391         } else {
392             return document.createTextNode(text);
393         }
394     },
395
396     /** Function: getText
397      *  Get the concatenation of all text children of an element.
398      *
399      *  Parameters:
400      *    (XMLElement) elem - A DOM element.
401      *
402      *  Returns:
403      *    A String with the concatenated text of all text element children.
404      */
405     getText: function (elem)
406     {
407         if (!elem) return null;
408
409         var str = "";
410         if (elem.childNodes.length === 0 && elem.nodeType == 
411             Strophe.ElementType.TEXT) {
412             str += elem.nodeValue;
413         }
414
415         for (var i = 0; i < elem.childNodes.length; i++) {
416             if (elem.childNodes[i].nodeType == Strophe.ElementType.TEXT) {
417                 str += elem.childNodes[i].nodeValue;
418             }
419         }
420
421         return str;
422     },
423
424     /** Function: copyElement
425      *  Copy an XML DOM element.
426      *
427      *  This function copies a DOM element and all its descendants and returns
428      *  the new copy.
429      *
430      *  Parameters:
431      *    (XMLElement) elem - A DOM element.
432      *
433      *  Returns:
434      *    A new, copied DOM element tree.
435      */
436     copyElement: function (elem)
437     {
438         var i, el;
439         if (elem.nodeType == Strophe.ElementType.NORMAL) {
440             el = Strophe.xmlElement(elem.tagName);
441             
442             for (i = 0; i < elem.attributes.length; i++) {
443                 el.setAttribute(elem.attributes[i].nodeName.toLowerCase(),
444                                 elem.attributes[i].value);
445             }
446             
447             for (i = 0; i < elem.childNodes.length; i++) {
448                 el.appendChild(Strophe.copyElement(elem.childNodes[i]));
449             }
450         } else if (elem.nodeType == Strophe.ElementType.TEXT) {
451             el = Strophe.xmlTextNode(elem.nodeValue);
452         }
453
454         return el;
455     },
456
457     /** Function: escapeJid
458      *  Escape a JID.
459      *
460      *  Parameters:
461      *    (String) jid - A JID.
462      *
463      *  Returns:
464      *    An escaped JID String.
465      */
466     escapeJid: function (jid)
467     {
468         var user = jid.split("@");
469         if (user.length == 1) 
470             // no user so nothing to escape
471             return jid;
472
473         var host = user.splice(user.length - 1, 1)[0];
474         user = user.join("@")
475             .replace(/^\s+|\s+$/g, '')
476             .replace(/\\/g,  "\\5c")
477             .replace(/ /g,   "\\20")
478             .replace(/\"/g,  "\\22")
479             .replace(/\&/g,  "\\26")
480             .replace(/\'/g,  "\\27")
481             .replace(/\//g,  "\\2f")
482             .replace(/:/g,   "\\3a")
483             .replace(/</g,   "\\3c")
484             .replace(/>/g,   "\\3e")
485             .replace(/@/g,   "\\40");
486         
487         return [user, host].join("@");
488     },
489
490     /** Function: unescapeJid
491      *  Unescape a JID.
492      *
493      *  Parameters:
494      *    (String) jid - A JID.
495      *
496      *  Returns:
497      *    An unescaped JID String.
498      */
499     unescapeJid: function (jid)
500     {
501         return jid.replace(/\\20/g, " ")
502             .replace(/\\22/g, '"')
503             .replace(/\\26/g, "&")
504             .replace(/\\27/g, "'")
505             .replace(/\\2f/g, "/")
506             .replace(/\\3a/g, ":")
507             .replace(/\\3c/g, "<")
508             .replace(/\\3e/g, ">")
509             .replace(/\\40/g, "@")
510             .replace(/\\5c/g, "\\");
511     },
512
513     /** Function: getNodeFromJid
514      *  Get the node portion of a JID String.
515      *
516      *  Parameters:
517      *    (String) jid - A JID.
518      *
519      *  Returns:
520      *    A String containing the node.
521      */
522     getNodeFromJid: function (jid)
523     {
524         if (jid.indexOf("@") < 0)
525             return null;
526         return Strophe.escapeJid(jid).split("@")[0];
527     },
528
529     /** Function: getDomainFromJid
530      *  Get the domain portion of a JID String.
531      *
532      *  Parameters:
533      *    (String) jid - A JID.
534      *
535      *  Returns:
536      *    A String containing the domain.
537      */
538     getDomainFromJid: function (jid)
539     {
540         var bare = Strophe.escapeJid(Strophe.getBareJidFromJid(jid));
541         if (bare.indexOf("@") < 0)
542             return bare;
543         else
544             return bare.split("@")[1];
545     },
546
547     /** Function: getResourceFromJid
548      *  Get the resource portion of a JID String.
549      *
550      *  Parameters:
551      *    (String) jid - A JID.
552      *
553      *  Returns:
554      *    A String containing the resource.
555      */
556     getResourceFromJid: function (jid)
557     {
558         var s = Strophe.escapeJid(jid).split("/");
559         if (s.length < 2) return null;
560         return s[1];
561     },
562
563     /** Function: getBareJidFromJid
564      *  Get the bare JID from a JID String.
565      *
566      *  Parameters:
567      *    (String) jid - A JID.
568      *
569      *  Returns:
570      *    A String containing the bare JID.
571      */
572     getBareJidFromJid: function (jid)
573     {
574         return this.escapeJid(jid).split("/")[0];
575     },
576
577     /** Function: log
578      *  User overrideable logging function.
579      *
580      *  This function is called whenever the Strophe library calls any
581      *  of the logging functions.  The default implementation of this 
582      *  function does nothing.  If client code wishes to handle the logging
583      *  messages, it should override this with
584      *  > Strophe.log = function (level, msg) {
585      *  >   (user code here)
586      *  > };
587      *
588      *  Please note that data sent and received over the wire is logged
589      *  via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().
590      *
591      *  The different levels and their meanings are
592      *
593      *    DEBUG - Messages useful for debugging purposes.
594      *    INFO - Informational messages.  This is mostly information like
595      *      'disconnect was called' or 'SASL auth succeeded'.
596      *    WARN - Warnings about potential problems.  This is mostly used
597      *      to report transient connection errors like request timeouts.
598      *    ERROR - Some error occurred.
599      *    FATAL - A non-recoverable fatal error occurred.
600      *
601      *  Parameters:
602      *    (Integer) level - The log level of the log message.  This will 
603      *      be one of the values in Strophe.LogLevel.
604      *    (String) msg - The log message.
605      */
606     log: function (level, msg)
607     {
608         return;
609     },
610
611     /** Function: debug
612      *  Log a message at the Strophe.LogLevel.DEBUG level.
613      *
614      *  Parameters:
615      *    (String) msg - The log message.
616      */
617     debug: function(msg)
618     {
619         this.log(this.LogLevel.DEBUG, msg);
620     },
621
622     /** Function: info
623      *  Log a message at the Strophe.LogLevel.INFO level.
624      *
625      *  Parameters:
626      *    (String) msg - The log message.
627      */
628     info: function (msg)
629     {
630         this.log(this.LogLevel.INFO, msg);
631     },
632
633     /** Function: warn
634      *  Log a message at the Strophe.LogLevel.WARN level.
635      *
636      *  Parameters:
637      *    (String) msg - The log message.
638      */
639     warn: function (msg)
640     {
641         this.log(this.LogLevel.WARN, msg);
642     },
643
644     /** Function: error
645      *  Log a message at the Strophe.LogLevel.ERROR level.
646      *
647      *  Parameters:
648      *    (String) msg - The log message.
649      */
650     error: function (msg)
651     {
652         this.log(this.LogLevel.ERROR, msg);
653     },
654
655     /** Function: fatal
656      *  Log a message at the Strophe.LogLevel.FATAL level.
657      *
658      *  Parameters:
659      *    (String) msg - The log message.
660      */
661     fatal: function (msg)
662     {
663         this.log(this.LogLevel.FATAL, msg);
664     },
665
666     /** Function: serialize
667      *  Render a DOM element and all descendants to a String.
668      *
669      *  Parameters:
670      *    (XMLElement) elem - A DOM element.
671      *
672      *  Returns:
673      *    The serialized element tree as a String.
674      */
675     serialize: function (elem)
676     {
677         var result;
678
679         if (!elem) return null;
680
681         var nodeName = elem.nodeName;
682         var i, child;
683
684         if (elem.getAttribute("_realname")) {
685             nodeName = elem.getAttribute("_realname");
686         }
687
688         result = "<" + nodeName;
689         for (i = 0; i < elem.attributes.length; i++) {
690                if(elem.attributes[i].nodeName != "_realname") {
691                  result += " " + elem.attributes[i].nodeName.toLowerCase() + 
692                 "='" + elem.attributes[i].value
693                     .replace("'", "&#39;").replace("&", "&#x26;") + "'";
694                }
695         }
696
697         if (elem.childNodes.length > 0) {
698             result += ">";
699             for (i = 0; i < elem.childNodes.length; i++) {
700                 child = elem.childNodes[i];
701                 if (child.nodeType == Strophe.ElementType.NORMAL) {
702                     // normal element, so recurse
703                     result += Strophe.serialize(child);
704                 } else if (child.nodeType == Strophe.ElementType.TEXT) {
705                     // text element
706                     result += child.nodeValue;
707                 }
708             }
709             result += "</" + nodeName + ">";
710         } else {
711             result += "/>";
712         }
713
714         return result;
715     },
716
717     /** PrivateVariable: _requestId
718      *  _Private_ variable that keeps track of the request ids for 
719      *  connections.
720      */
721     _requestId: 0
722 };
723
724 /** Class: Strophe.Builder
725  *  XML DOM builder.
726  *
727  *  This object provides an interface similar to JQuery but for building
728  *  DOM element easily and rapidly.  All the functions except for toString()
729  *  and tree() return the object, so calls can be chained.  Here's an
730  *  example using the $iq() builder helper.
731  *  > $iq({to: 'you': from: 'me': type: 'get', id: '1'})
732  *  >     .c('query', {xmlns: 'strophe:example'})
733  *  >     .c('example')
734  *  >     .toString()
735  *  The above generates this XML fragment
736  *  > <iq to='you' from='me' type='get' id='1'>
737  *  >   <query xmlns='strophe:example'>
738  *  >     <example/>
739  *  >   </query>
740  *  > </iq>
741  *  The corresponding DOM manipulations to get a similar fragment would be
742  *  a lot more tedious and probably involve several helper variables.
743  *
744  *  Since adding children makes new operations operate on the child, up()
745  *  is provided to traverse up the tree.  To add two children, do
746  *  > builder.c('child1', ...).up().c('child2', ...)
747  *  The next operation on the Builder will be relative to the second child.
748  */
749
750 /** Constructor: Strophe.Builder
751  *  Create a Strophe.Builder object.
752  *
753  *  The attributes should be passed in object notation.  For example
754  *  > var b = new Builder('message', {to: 'you', from: 'me'});
755  *  or
756  *  > var b = new Builder('messsage', {'xml:lang': 'en'});
757  *
758  *  Parameters:
759  *    (String) name - The name of the root element.
760  *    (Object) attrs - The attributes for the root element in object notation.
761  *
762  *  Returns:
763  *    A new Strophe.Builder.
764  */
765 Strophe.Builder = function (name, attrs)
766 {
767     // Holds the tree being built.
768     this.nodeTree = this._makeNode(name, attrs);
769
770     // Points to the current operation node.
771     this.node = this.nodeTree;
772 };
773
774 Strophe.Builder.prototype = {
775     /** Function: tree
776      *  Return the DOM tree.
777      *
778      *  This function returns the current DOM tree as an element object.  This
779      *  is suitable for passing to functions like Strophe.Connection.send().
780      *
781      *  Returns:
782      *    The DOM tree as a element object.
783      */
784     tree: function ()
785     {
786         return this.nodeTree;
787     },
788
789     /** Function: toString
790      *  Serialize the DOM tree to a String.
791      *
792      *  This function returns a string serialization of the current DOM
793      *  tree.  It is often used internally to pass data to a 
794      *  Strophe.Request object.
795      *
796      *  Returns:
797      *    The serialized DOM tree in a String.
798      */
799     toString: function ()
800     {
801         return Strophe.serialize(this.nodeTree);
802     },
803
804     /** Function: up
805      *  Make the current parent element the new current element.
806      *
807      *  This function is often used after c() to traverse back up the tree.
808      *  For example, to add two children to the same element
809      *  > builder.c('child1', {}).up().c('child2', {});
810      *
811      *  Returns:
812      *    The Stophe.Builder object.
813      */
814     up: function ()
815     {
816         this.node = this.node.parentNode;
817         return this;
818     },
819
820     /** Function: attrs
821      *  Add or modify attributes of the current element.
822      *  
823      *  The attributes should be passed in object notation.  This function
824      *  does not move the current element pointer.
825      *
826      *  Parameters:
827      *    (Object) moreattrs - The attributes to add/modify in object notation.
828      *
829      *  Returns:
830      *    The Strophe.Builder object.
831      */
832     attrs: function (moreattrs)
833     {
834         for (var k in moreattrs)
835             this.node.setAttribute(k, moreattrs[k]);
836         return this;
837     },
838
839     /** Function: c
840      *  Add a child to the current element and make it the new current 
841      *  element.
842      *
843      *  This function moves the current element pointer to the child.  If you
844      *  need to add another child, it is necessary to use up() to go back
845      *  to the parent in the tree.
846      *
847      *  Parameters:
848      *    (String) name - The name of the child.
849      *    (Object) attrs - The attributes of the child in object notation.
850      *
851      *  Returns:
852      *    The Strophe.Builder object.
853      */
854     c: function (name, attrs)
855     {
856         var child = this._makeNode(name, attrs);
857         this.node.appendChild(child);
858         this.node = child;
859         return this;
860     },
861
862     /** Function: cnode
863      *  Add a child to the current element and make it the new current
864      *  element.
865      *
866      *  This function is the same as c() except that instead of using a
867      *  name and an attributes object to create the child it uses an
868      *  existing DOM element object.
869      *
870      *  Parameters:
871      *    (XMLElement) elem - A DOM element.
872      *
873      *  Returns:
874      *    The Strophe.Builder object.
875      */
876     cnode: function (elem)
877     {
878         this.node.appendChild(elem);
879         this.node = elem;
880         return this;
881     },
882
883     /** Function: t
884      *  Add a child text element.
885      *
886      *  This *does not* make the child the new current element since there
887      *  are no children of text elements.
888      *
889      *  Parameters:
890      *    (String) text - The text data to append to the current element.
891      *
892      *  Returns:
893      *    The Strophe.Builder object.
894      */
895     t: function (text)
896     {
897         var child = Strophe.xmlTextNode(text);
898         this.node.appendChild(child);
899         return this;
900     },
901
902     /** PrivateFunction: _makeNode
903      *  _Private_ helper function to create a DOM element.
904      *
905      *  Parameters:
906      *    (String) name - The name of the new element.
907      *    (Object) attrs - The attributes for the new element in object 
908      *      notation.
909      *
910      *  Returns:
911      *    A new DOM element.
912      */
913     _makeNode: function (name, attrs) 
914     {
915         var node = Strophe.xmlElement(name);
916         for (var k in attrs)
917             node.setAttribute(k, attrs[k]);
918         return node;
919     }
920 };
921
922
923 /** PrivateClass: Strophe.Handler
924  *  _Private_ helper class for managing stanza handlers.
925  *
926  *  A Strophe.Handler encapsulates a user provided callback function to be
927  *  executed when matching stanzas are received by the connection.
928  *  Handlers can be either one-off or persistant depending on their 
929  *  return value. Returning true will cause a Handler to remain active, and
930  *  returning false will remove the Handler.
931  *
932  *  Users will not use Strophe.Handler objects directly, but instead they
933  *  will use Strophe.Connection.addHandler() and
934  *  Strophe.Connection.deleteHandler().
935  */
936
937 /** PrivateConstructor: Strophe.Handler
938  *  Create and initialize a new Strophe.Handler.
939  *
940  *  Parameters:
941  *    (Function) handler - A function to be executed when the handler is run.
942  *    (String) ns - The namespace to match.
943  *    (String) name - The element name to match.
944  *    (String) type - The element type to match.
945  *    (String) id - The element id attribute to match.
946  *    (String) from - The element from attribute to match.
947  *
948  *  Returns:
949  *    A new Strophe.Handler object.
950  */
951 Strophe.Handler = function (handler, ns, name, type, id, from)
952 {
953     this.handler = handler;
954     this.ns = ns;
955     this.name = name;
956     this.type = type;
957     this.id = id;
958     this.from = from;
959     
960     // whether the handler is a user handler or a system handler
961     this.user = true;
962 };
963
964 Strophe.Handler.prototype = {
965     /** PrivateFunction: isMatch
966      *  Tests if a stanza matches the Strophe.Handler.
967      *
968      *  Parameters:
969      *    (XMLElement) elem - The XML element to test.
970      *
971      *  Returns:
972      *    true if the stanza matches and false otherwise.
973      */
974     isMatch: function (elem)
975     {
976         var nsMatch, i;
977
978         nsMatch = false;
979         if (!this.ns) {
980             nsMatch = true;
981         } else {
982             var self = this;
983             Strophe.forEachChild(elem, null, function (elem) {
984                 if (elem.getAttribute("xmlns") == self.ns)
985                     nsMatch = true;
986             });
987
988             nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns;
989         }
990
991         if (nsMatch &&
992             (!this.name || Strophe.isTagEqual(elem, this.name)) &&
993             (!this.type || elem.getAttribute("type") == this.type) &&
994             (!this.id || elem.getAttribute("id") == this.id) &&
995             (!this.from || elem.getAttribute("from") == this.from)) {
996                 return true;
997         }
998
999         return false;
1000     },
1001
1002     /** PrivateFunction: run
1003      *  Run the callback on a matching stanza.
1004      *
1005      *  Parameters:
1006      *    (XMLElement) elem - The DOM element that triggered the 
1007      *      Strophe.Handler.
1008      *
1009      *  Returns:
1010      *    A boolean indicating if the handler should remain active.
1011      */
1012     run: function (elem)
1013     {
1014         var result = null;
1015         try {
1016             result = this.handler(elem);
1017         } catch (e) {
1018             if (e.sourceURL) {
1019                 Strophe.fatal("error: " + this.handler + 
1020                               " " + e.sourceURL + ":" + 
1021                               e.line + " - " + e.name + ": " + e.message);
1022             } else if (e.fileName) {
1023                 if (typeof(console) != "undefined") {
1024                     console.trace();
1025                     console.error(this.handler, " - error - ", e, e.message);
1026                 }
1027                 Strophe.fatal("error: " + this.handler + " " + 
1028                               e.fileName + ":" + e.lineNumber + " - " + 
1029                               e.name + ": " + e.message);
1030             } else {
1031                 Strophe.fatal("error: " + this.handler);
1032             }
1033             
1034             throw e;
1035         }
1036
1037         return result;
1038     },
1039
1040     /** PrivateFunction: toString
1041      *  Get a String representation of the Strophe.Handler object.
1042      *
1043      *  Returns:
1044      *    A String.
1045      */
1046     toString: function ()
1047     {
1048         return "{Handler: " + this.handler + "(" + this.name + "," +
1049             this.id + "," + this.ns + ")}";
1050     }
1051 };
1052
1053 /** PrivateClass: Strophe.TimedHandler
1054  *  _Private_ helper class for managing timed handlers.
1055  * 
1056  *  A Strophe.TimedHandler encapsulates a user provided callback that
1057  *  should be called after a certain period of time or at regular
1058  *  intervals.  The return value of the callback determines whether the
1059  *  Strophe.TimedHandler will continue to fire.
1060  *
1061  *  Users will not use Strophe.TimedHandler objects directly, but instead 
1062  *  they will use Strophe.Connection.addTimedHandler() and
1063  *  Strophe.Connection.deleteTimedHandler().
1064  */
1065
1066 /** PrivateConstructor: Strophe.TimedHandler
1067  *  Create and initialize a new Strophe.TimedHandler object.
1068  *
1069  *  Parameters:
1070  *    (Integer) period - The number of milliseconds to wait before the
1071  *      handler is called.
1072  *    (Function) handler - The callback to run when the handler fires.  This
1073  *      function should take no arguments.
1074  *
1075  *  Returns:
1076  *    A new Strophe.TimedHandler object.
1077  */
1078 Strophe.TimedHandler = function (period, handler)
1079 {
1080     this.period = period;
1081     this.handler = handler;
1082     
1083     this.lastCalled = new Date().getTime();
1084     this.user = true;
1085 };
1086
1087 Strophe.TimedHandler.prototype = {
1088     /** PrivateFunction: run
1089      *  Run the callback for the Strophe.TimedHandler.
1090      *
1091      *  Returns:
1092      *    true if the Strophe.TimedHandler should be called again, and false
1093      *      otherwise.
1094      */
1095     run: function ()
1096     {
1097         this.lastCalled = new Date().getTime();
1098         return this.handler();
1099     },
1100
1101     /** PrivateFunction: reset
1102      *  Reset the last called time for the Strophe.TimedHandler.
1103      */
1104     reset: function ()
1105     {
1106         this.lastCalled = new Date().getTime();
1107     },
1108
1109     /** PrivateFunction: toString
1110      *  Get a string representation of the Strophe.TimedHandler object.
1111      *
1112      *  Returns:
1113      *    The string representation.
1114      */
1115     toString: function ()
1116     {
1117         return "{TimedHandler: " + this.handler + "(" + this.period +")}";
1118     }
1119 };
1120
1121 /** PrivateClass: Strophe.Request
1122  *  _Private_ helper class that provides a cross implementation abstraction 
1123  *  for a BOSH related XMLHttpRequest.
1124  *
1125  *  The Strophe.Request class is used internally to encapsulate BOSH request
1126  *  information.  It is not meant to be used from user's code.
1127  */
1128
1129 /** PrivateConstructor: Strophe.Request
1130  *  Create and initialize a new Strophe.Request object.
1131  *
1132  *  Parameters:
1133  *    (String) data - The data to be sent in the request.
1134  *    (Function) func - The function that will be called when the
1135  *      XMLHttpRequest readyState changes.
1136  *    (Integer) rid - The BOSH rid attribute associated with this request.
1137  *    (Integer) sends - The number of times this same request has been
1138  *      sent.
1139  */
1140 Strophe.Request = function (data, func, rid, sends)
1141 {
1142     this.id = ++Strophe._requestId;
1143     this.data = data;
1144     // save original function in case we need to make a new request
1145     // from this one.
1146     this.origFunc = func;
1147     this.func = func;
1148     this.rid = rid;
1149     this.date = NaN;
1150     this.sends = sends || 0;
1151     this.abort = false;
1152     this.dead = null;
1153     this.age = function () {
1154         if (!this.date) return 0;
1155         var now = new Date();
1156         return (now - this.date) / 1000;
1157     };
1158     this.timeDead = function () {
1159         if (!this.dead) return 0;
1160         var now = new Date();
1161         return (now - this.dead) / 1000;
1162     };
1163     this.xhr = this._newXHR();
1164 };
1165
1166 Strophe.Request.prototype = {
1167     /** PrivateFunction: getResponse
1168      *  Get a response from the underlying XMLHttpRequest.
1169      *
1170      *  This function attempts to get a response from the request and checks
1171      *  for errors.
1172      *
1173      *  Throws:
1174      *    "parsererror" - A parser error occured.
1175      *
1176      *  Returns:
1177      *    The DOM element tree of the response.
1178      */
1179     getResponse: function ()
1180     {
1181         var node = null;
1182         if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
1183             node = this.xhr.responseXML.documentElement;
1184             if (node.tagName == "parsererror") {
1185                 Strophe.error("invalid response received");
1186                 Strophe.error("responseText: " + this.xhr.responseText);
1187                 Strophe.error("responseXML: " + 
1188                               Strophe.serialize(this.xhr.responseXML));
1189                 throw "parsererror";
1190             }
1191         } else if (this.xhr.responseText) {
1192             Strophe.error("invalid response received");
1193             Strophe.error("responseText: " + this.xhr.responseText);
1194             Strophe.error("responseXML: " + 
1195                           Strophe.serialize(this.xhr.responseXML));
1196         }
1197         
1198         return node;
1199     },
1200
1201     /** PrivateFunction: _newXHR
1202      *  _Private_ helper function to create XMLHttpRequests.
1203      *
1204      *  This function creates XMLHttpRequests across all implementations.
1205      * 
1206      *  Returns:
1207      *    A new XMLHttpRequest.
1208      */
1209     _newXHR: function ()
1210     {
1211         var xhr = null;
1212         if (window.XMLHttpRequest) {
1213             xhr = new XMLHttpRequest();
1214             if (xhr.overrideMimeType) {
1215                 xhr.overrideMimeType("text/xml");
1216             }
1217         } else if (window.ActiveXObject) {
1218             xhr = new ActiveXObject("Microsoft.XMLHTTP");
1219         }
1220                 
1221         xhr.onreadystatechange = this.func.prependArg(this);
1222
1223         return xhr;
1224     }
1225 };
1226
1227 /** Class: Strophe.Connection
1228  *  XMPP Connection manager.
1229  *
1230  *  Thie class is the main part of Strophe.  It manages a BOSH connection
1231  *  to an XMPP server and dispatches events to the user callbacks as
1232  *  data arrives.  It supports SASL PLAIN, SASL DIGEST-MD5, and legacy
1233  *  authentication.
1234  *
1235  *  After creating a Strophe.Connection object, the user will typically
1236  *  call connect() with a user supplied callback to handle connection level
1237  *  events like authentication failure, disconnection, or connection 
1238  *  complete.
1239  * 
1240  *  The user will also have several event handlers defined by using 
1241  *  addHandler() and addTimedHandler().  These will allow the user code to
1242  *  respond to interesting stanzas or do something periodically with the
1243  *  connection.  These handlers will be active once authentication is 
1244  *  finished.
1245  *
1246  *  To send data to the connection, use send().
1247  */
1248
1249 /** Constructor: Strophe.Connection
1250  *  Create and initialize a Strophe.Connection object.
1251  *
1252  *  Parameters:
1253  *    (String) service - The BOSH service URL.
1254  *
1255  *  Returns:
1256  *    A new Strophe.Connection object.
1257  */
1258 Strophe.Connection = function (service)
1259 {
1260     /* The path to the httpbind service. */
1261     this.service = service;
1262     /* The connected JID. */
1263     this.jid = "";
1264     /* request id for body tags */
1265     this.rid = Math.floor(Math.random() * 4294967295);
1266     /* The current session ID. */
1267     this.sid = null;
1268     this.streamId = null;
1269     
1270     // SASL
1271     this.do_session = false;
1272     this.do_bind = false;
1273     
1274     // handler lists
1275     this.timedHandlers = [];
1276     this.handlers = [];
1277     this.removeTimeds = [];
1278     this.removeHandlers = [];
1279     this.addTimeds = [];
1280     this.addHandlers = [];
1281     
1282     this._idleTimeout = null;
1283     this._disconnectTimeout = null;
1284     
1285     this.authenticated = false;
1286     this.disconnecting = false;
1287     this.connected = false;
1288     
1289     this.errors = 0;
1290
1291     this.paused = false;
1292
1293     // default BOSH window
1294     this.window = 5;
1295     
1296     this._data = [];
1297     this._requests = [];
1298     this._uniqueId = Math.round(Math.random() * 10000);
1299
1300     this._sasl_success_handler = null;
1301     this._sasl_failure_handler = null;
1302     this._sasl_challenge_handler = null;
1303     
1304     // setup onIdle callback every 1/10th of a second
1305     this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1306 };
1307
1308 Strophe.Connection.prototype = {
1309     /** Function: reset
1310      *  Reset the connection.
1311      *
1312      *  This function should be called after a connection is disconnected
1313      *  before that connection is reused.
1314      */
1315     reset: function ()
1316     {
1317         this.rid = Math.floor(Math.random() * 4294967295);
1318         
1319         this.sid = null;
1320         this.streamId = null;
1321         
1322         // SASL
1323         this.do_session = false;
1324         this.do_bind = false;
1325         
1326         // handler lists
1327         this.timedHandlers = [];
1328         this.handlers = [];
1329         this.removeTimeds = [];
1330         this.removeHandlers = [];
1331         this.addTimeds = [];
1332         this.addHandlers = [];
1333         
1334         this.authenticated = false;
1335         this.disconnecting = false;
1336         this.connected = false;
1337         
1338         this.errors = 0;
1339         
1340         this._requests = [];
1341         this._uniqueId = Math.round(Math.random()*10000);
1342     },
1343
1344     /** Function: pause
1345      *  Pause the request manager.
1346      *
1347      *  This will prevent Strophe from sending any more requests to the
1348      *  server.  This is very useful for temporarily pausing while a lot
1349      *  of send() calls are happening quickly.  This causes Strophe to
1350      *  send the data in a single request, saving many request trips.
1351      */
1352     pause: function ()
1353     {
1354         this.paused = true;
1355     },
1356     
1357     /** Function: resume
1358      *  Resume the request manager.
1359      *
1360      *  This resumes after pause() has been called.
1361      */
1362     resume: function ()
1363     {
1364         this.paused = false;
1365     },
1366     
1367     /** Function: getUniqueId
1368      *  Generate a unique ID for use in <iq/> elements.
1369      *
1370      *  All <iq/> stanzas are required to have unique id attributes.  This
1371      *  function makes creating these easy.  Each connection instance has
1372      *  a counter which starts from zero, and the value of this counter 
1373      *  plus a colon followed by the suffix becomes the unique id. If no 
1374      *  suffix is supplied, the counter is used as the unique id.
1375      *
1376      *  Suffixes are used to make debugging easier when reading the stream
1377      *  data, and their use is recommended.  The counter resets to 0 for
1378      *  every new connection for the same reason.  For connections to the
1379      *  same server that authenticate the same way, all the ids should be
1380      *  the same, which makes it easy to see changes.  This is useful for
1381      *  automated testing as well.
1382      *
1383      *  Parameters:
1384      *    (String) suffix - A optional suffix to append to the id.
1385      *
1386      *  Returns:
1387      *    A unique string to be used for the id attribute.
1388      */
1389     getUniqueId: function (suffix)
1390     {
1391         if (typeof(suffix) == "string" || typeof(suffix) == "number") {
1392             return ++this._uniqueId + ":" + suffix;
1393         } else {
1394             return ++this._uniqueId + "";
1395         }
1396     },
1397     
1398     /** Function: connect
1399      *  Starts the connection process.
1400      *
1401      *  As the connection process proceeds, the user supplied callback will
1402      *  be triggered multiple times with status updates.  The callback
1403      *  should take two arguments - the status code and the error condition.
1404      *
1405      *  The status code will be one of the values in the Strophe.Status
1406      *  constants.  The error condition will be one of the conditions
1407      *  defined in RFC 3920 or the condition 'strophe-parsererror'.
1408      *
1409      *  Please see XEP 124 for a more detailed explanation of the optional
1410      *  parameters below.
1411      *
1412      *  Parameters:
1413      *    (String) jid - The user's JID.  This may be a bare JID,
1414      *      or a full JID.  If a node is not supplied, SASL ANONYMOUS
1415      *      authentication will be attempted.
1416      *    (String) pass - The user's password.
1417      *    (Function) callback The connect callback function.
1418      *    (Integer) wait - The optional HTTPBIND wait value.  This is the 
1419      *      time the server will wait before returning an empty result for 
1420      *      a request.  The default setting of 60 seconds is recommended.  
1421      *      Other settings will require tweaks to the Strophe.TIMEOUT value.
1422      *    (Integer) hold - The optional HTTPBIND hold value.  This is the 
1423      *      number of connections the server will hold at one time.  This 
1424      *      should almost always be set to 1 (the default).
1425      *    (Integer) wind - The optional HTTBIND window value.  This is the
1426      *      allowed range of request ids that are valid.  The default is 5.
1427      */
1428     connect: function (jid, pass, callback, wait, hold, wind)
1429     {
1430         this.jid = jid;
1431         this.pass = pass;
1432         this.connect_callback = callback;
1433         this.disconnecting = false;
1434         this.connected = false;
1435         this.authenticated = false;
1436         this.errors = 0;
1437
1438         if (!wait) wait = 60;
1439         if (!hold) hold = 1;
1440         if (wind) this.window = wind;
1441
1442         // parse jid for domain and resource
1443         this.domain = Strophe.getDomainFromJid(this.jid);
1444
1445         // build the body tag
1446         var body = this._buildBody().attrs({
1447             to: this.domain,
1448             "xml:lang": "en",
1449             wait: wait,
1450             hold: hold,
1451             window: this.window,
1452             content: "text/xml; charset=utf-8",
1453             ver: "1.6",
1454             "xmpp:version": "1.0",
1455             "xmlns:xmpp": Strophe.NS.BOSH
1456         });
1457
1458         this.connect_callback(Strophe.Status.CONNECTING, null);
1459
1460         this._requests.push(
1461             new Strophe.Request(body.toString(),
1462                                 this._onRequestStateChange.bind(this)
1463                                     .prependArg(this._connect_cb.bind(this)),
1464                                 body.tree().getAttribute("rid")));
1465         this._throttledRequestHandler();
1466     },
1467
1468     /** Function: attach
1469      *  Attach to an already created and authenticated BOSH session.
1470      *
1471      *  This function is provided to allow Strophe to attach to BOSH
1472      *  sessions which have been created externally, perhaps by a Web
1473      *  application.  This is often used to support auto-login type features
1474      *  without putting user credentials into the page.
1475      *
1476      *  Parameters:
1477      *    (String) jid - The full JID that is bound by the session.
1478      *    (String) sid - The SID of the BOSH session.
1479      *    (String) rid - The current RID of the BOSH session.  This RID
1480      *      will be used by the next request.
1481      *    (Function) callback The connect callback function.
1482      */
1483     attach: function (jid, sid, rid, callback)
1484     {
1485         this.jid = jid;
1486         this.sid = sid;
1487         this.rid = rid;
1488         this.connect_callback = callback;
1489
1490         this.domain = Strophe.getDomainFromJid(this.jid);
1491         
1492         this.authenticated = true;
1493         this.connected = true;
1494     },
1495
1496     /** Function: rawInput
1497      *  User overrideable function that receives raw data coming into the 
1498      *  connection.
1499      *
1500      *  The default function does nothing.  User code can override this with
1501      *  > Strophe.Connection.rawInput = function (data) {
1502      *  >   (user code)
1503      *  > };
1504      *
1505      *  Parameters:
1506      *    (String) data - The data received by the connection.
1507      */
1508     rawInput: function (data)
1509     {
1510         return;
1511     },
1512
1513     /** Function: rawOutput
1514      *  User overrideable function that receives raw data sent to the
1515      *  connection.
1516      *
1517      *  The default function does nothing.  User code can override this with
1518      *  > Strophe.Connection.rawOutput = function (data) {
1519      *  >   (user code)
1520      *  > };
1521      *
1522      *  Parameters:
1523      *    (String) data - The data sent by the connection.
1524      */
1525     rawOutput: function (data)
1526     {
1527         return;
1528     },
1529
1530     /** Function: send
1531      *  Send a stanza.
1532      *
1533      *  This function is called to push data onto the send queue to
1534      *  go out over the wire.  Whenever a request is sent to the BOSH
1535      *  server, all pending data is sent and the queue is flushed.
1536      *
1537      *  Parameters:
1538      *    (XMLElement) elem - The stanza to send.
1539      */
1540     send: function (elem)
1541     {
1542         if (elem !== null && typeof(elem["sort"]) == "function") {
1543             for (var i = 0; i < elem.length; i++) {
1544                 this._data.push(elem[i]);
1545             }
1546         } else {
1547             this._data.push(elem);
1548         }
1549
1550         this._throttledRequestHandler();
1551         clearTimeout(this._idleTimeout);
1552         this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1553     },
1554
1555     /** PrivateFunction: _sendRestart
1556      *  Send an xmpp:restart stanza.
1557      */
1558     _sendRestart: function ()
1559     {
1560         this._data.push("restart");
1561
1562         this._throttledRequestHandler();
1563         clearTimeout(this._idleTimeout);
1564         this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1565     },
1566
1567     /** Function: addTimedHandler
1568      *  Add a timed handler to the connection.
1569      *
1570      *  This function adds a timed handler.  The provided handler will
1571      *  be called every period milliseconds until it returns false,
1572      *  the connection is terminated, or the handler is removed.  Handlers
1573      *  that wish to continue being invoked should return true.
1574      *
1575      *  Because of method binding it is necessary to save the result of 
1576      *  this function if you wish to remove a handler with 
1577      *  deleteTimedHandler().
1578      *
1579      *  Note that user handlers are not active until authentication is
1580      *  successful.
1581      *
1582      *  Parameters:
1583      *    (Integer) period - The period of the handler.
1584      *    (Function) handler - The callback function.
1585      *
1586      *  Returns:
1587      *    A reference to the handler that can be used to remove it.
1588      */
1589     addTimedHandler: function (period, handler)
1590     {
1591         var thand = new Strophe.TimedHandler(period, handler);
1592         this.addTimeds.push(thand);
1593         return thand;
1594     },
1595
1596     /** Function: deleteTimedHandler
1597      *  Delete a timed handler for a connection.
1598      *  
1599      *  This function removes a timed handler from the connection.  The 
1600      *  handRef parameter is *not* the function passed to addTimedHandler(),
1601      *  but is the reference returned from addTimedHandler().
1602      *
1603      *  Parameters:
1604      *    (Strophe.TimedHandler) handRef - The handler reference.
1605      */
1606     deleteTimedHandler: function (handRef)
1607     {
1608         // this must be done in the Idle loop so that we don't change
1609         // the handlers during iteration
1610         this.removeTimeds.push(handRef);
1611     },
1612     
1613     /** Function: addHandler
1614      *  Add a stanza handler for the connection.
1615      *
1616      *  This function adds a stanza handler to the connection.  The 
1617      *  handler callback will be called for any stanza that matches
1618      *  the parameters.  Note that if multiple parameters are supplied,
1619      *  they must all match for the handler to be invoked.
1620      *
1621      *  The handler will receive the stanza that triggered it as its argument.
1622      *  The handler should return true if it is to be invoked again;
1623      *  returning false will remove the handler after it returns.
1624      *
1625      *  As a convenience, the ns parameters applies to the top level element
1626      *  and also any of its immediate children.  This is primarily to make
1627      *  matching /iq/query elements easy.
1628      *
1629      *  The return value should be saved if you wish to remove the handler
1630      *  with deleteHandler().
1631      *
1632      *  Parameters:
1633      *    (Function) handler - The user callback.
1634      *    (String) ns - The namespace to match.
1635      *    (String) name - The stanza name to match.
1636      *    (String) type - The stanza type attribute to match.
1637      *    (String) id - The stanza id attribute to match.
1638      *    (String) from - The stanza from attribute to match.
1639      *
1640      *  Returns:
1641      *    A reference to the handler that can be used to remove it.
1642      */
1643     addHandler: function (handler, ns, name, type, id, from)
1644     {
1645         var hand = new Strophe.Handler(handler, ns, name, type, id, from);
1646         this.addHandlers.push(hand);
1647         return hand;
1648     },
1649
1650     /** Function: deleteHandler
1651      *  Delete a stanza handler for a connection.
1652      *  
1653      *  This function removes a stanza handler from the connection.  The 
1654      *  handRef parameter is *not* the function passed to addHandler(),
1655      *  but is the reference returned from addHandler().
1656      *
1657      *  Parameters:
1658      *    (Strophe.Handler) handRef - The handler reference.
1659      */
1660     deleteHandler: function (handRef)
1661     {
1662         // this must be done in the Idle loop so that we don't change
1663         // the handlers during iteration
1664         this.removeHandlers.push(handRef);
1665     },
1666
1667     /** Function: disconnect
1668      *  Start the graceful disconnection process.
1669      *
1670      *  This function starts the disconnection process.  This process starts
1671      *  by sending unavailable presence and sending BOSH body of type
1672      *  terminate.  A timeout handler makes sure that disconnection happens
1673      *  even if the BOSH server does not respond.
1674      *
1675      *  The user supplied connection callback will be notified of the
1676      *  progress as this process happens.
1677      */
1678     disconnect: function ()
1679     {
1680         Strophe.info("disconnect was called");
1681         if (this.connected) {
1682             // setup timeout handler
1683             this._disconnectTimeout = this._addSysTimedHandler(
1684                 3000, this._onDisconnectTimeout.bind(this));
1685             this._sendTerminate();
1686         }
1687     },
1688     
1689     /** PrivateFunction: _buildBody
1690      *  _Private_ helper function to generate the <body/> wrapper for BOSH.
1691      *
1692      *  Returns:
1693      *    A Strophe.Builder with a <body/> element.
1694      */
1695     _buildBody: function ()
1696     {
1697         var bodyWrap = $build('body', {
1698             rid: this.rid++,
1699             xmlns: Strophe.NS.HTTPBIND
1700         });
1701
1702         if (this.sid !== null) {
1703             bodyWrap.attrs({sid: this.sid});
1704         }
1705
1706         return bodyWrap;
1707     },
1708
1709     /** PrivateFunction: _removeRequest
1710      *  _Private_ function to remove a request from the queue.
1711      *
1712      *  Parameters:
1713      *    (Strophe.Request) req - The request to remove.
1714      */
1715     _removeRequest: function (req)
1716     {
1717         Strophe.debug("removing request");
1718
1719         var i;
1720         for (i = this._requests.length - 1; i >= 0; i--) {
1721             if (req == this._requests[i]) {
1722                 this._requests.splice(i, 1);
1723             }
1724         }
1725
1726         // set the onreadystatechange handler to a null function so
1727         // that we don't get any misfires
1728         req.xhr.onreadystatechange = function () {};
1729
1730         this._throttledRequestHandler();
1731     },
1732
1733     /** PrivateFunction: _restartRequest
1734      *  _Private_ function to restart a request that is presumed dead.
1735      *
1736      *  Parameters:
1737      *    (Integer) i - The index of the request in the queue.
1738      */
1739     _restartRequest: function (i)
1740     {
1741         var req = this._requests[i];
1742         if (req.dead === null) {
1743             req.dead = new Date();
1744         }
1745
1746         this._processRequest(i);
1747     },
1748
1749     /** PrivateFunction: _processRequest
1750      *  _Private_ function to process a request in the queue.
1751      *
1752      *  This function takes requests off the queue and sends them and
1753      *  restarts dead requests.
1754      *
1755      *  Parameters:
1756      *    (Integer) i - The index of the request in the queue.
1757      */
1758     _processRequest: function (i)
1759     {
1760         var req = this._requests[i];
1761         var reqStatus = -1;
1762         
1763         try {
1764             if (req.xhr.readyState == 4) {
1765                 reqStatus = req.xhr.status;
1766             }
1767         } catch (e) {
1768             Strophe.error("caught an error in _requests[" + i + 
1769                           "], reqStatus: " + reqStatus);
1770         }
1771                 
1772         if (typeof(reqStatus) == "undefined") { 
1773             reqStatus = -1; 
1774         }
1775
1776         var now = new Date();
1777         var time_elapsed = req.age();
1778         var primaryTimeout = (!isNaN(time_elapsed) &&
1779                               time_elapsed > Strophe.TIMEOUT);
1780         var secondaryTimeout = (req.dead !== null &&
1781                                 req.timeDead() > Strophe.SECONDARY_TIMEOUT);
1782         var requestCompletedWithServerError = (req.xhr.readyState == 4 &&
1783                                                (reqStatus < 1 || 
1784                                                 reqStatus >= 500));
1785         var oldreq;
1786
1787         if (primaryTimeout || secondaryTimeout ||
1788             requestCompletedWithServerError) {
1789             if (secondaryTimeout) {
1790                 Strophe.error("Request " + 
1791                               this._requests[i].id + 
1792                               " timed out (secondary), restarting");
1793             }
1794             req.abort = true;
1795             req.xhr.abort();
1796             oldreq = req;
1797             this._requests[i] = new Strophe.Request(req.data, 
1798                                                     req.origFunc, 
1799                                                     req.rid, 
1800                                                     req.sends);
1801             req = this._requests[i];
1802         }
1803
1804         if (req.xhr.readyState === 0) {
1805             Strophe.debug("request id " + req.id + 
1806                           "." + req.sends + " posting");
1807
1808             req.date = new Date();
1809             try {
1810                 req.xhr.open("POST", this.service, true);
1811             } catch (e) {
1812                 Strophe.error("XHR open failed.");
1813                 if (!this.connected)
1814                     this.connect_callback(Strophe.Status.CONNFAIL, 
1815                                           "bad-service");
1816                 this.disconnect();
1817                 return;
1818             }
1819
1820       // Fires the XHR request -- may be invoked immediately
1821       // or on a gradually expanding retry window for reconnects
1822       var sendFunc = function () {
1823           req.xhr.send(req.data);
1824       };
1825
1826       // Implement progressive backoff for reconnects --
1827       // First retry (send == 1) should also be instantaneous
1828       if (req.sends > 1) {
1829           // Using a cube of the retry number creats a nicely
1830           // expanding retry window
1831           var backoff = Math.pow(req.sends, 3) * 1000;
1832           setTimeout(sendFunc, backoff);
1833       } else {
1834           sendFunc();
1835       }
1836
1837       req.sends++;
1838
1839             this.rawOutput(req.data);
1840         } else {
1841             Strophe.debug("_throttledRequestHandler: " + 
1842                           (i === 0 ? "first" : "second") + 
1843                           " request has readyState of " + 
1844                           req.xhr.readyState);
1845         }
1846     },
1847
1848     /** PrivateFunction: _throttledRequestHandler
1849      *  _Private_ function to throttle requests to the connection window.
1850      *
1851      *  This function makes sure we don't send requests so fast that the 
1852      *  request ids overflow the connection window in the case that one
1853      *  request died.
1854      */
1855     _throttledRequestHandler: function ()
1856     {
1857         if (!this._requests) {
1858             Strophe.debug("_throttledRequestHandler called with " + 
1859                           "undefined requests");
1860         } else {
1861             Strophe.debug("_throttledRequestHandler called with " + 
1862                           this._requests.length + " requests");
1863         }
1864         
1865         if (!this._requests || this._requests.length === 0) {
1866             return; 
1867         }
1868         
1869         if (this._requests.length > 0) {
1870             this._processRequest(0);
1871         }
1872                 
1873         if (this._requests.length > 1 && 
1874             Math.abs(this._requests[0].rid - 
1875                      this._requests[1].rid) < this.window - 1) {
1876             this._processRequest(1);
1877         }
1878     },
1879     
1880     /** PrivateFunction: _onRequestStateChange
1881      *  _Private_ handler for Strophe.Request state changes.
1882      *
1883      *  This function is called when the XMLHttpRequest readyState changes.
1884      *  It contains a lot of error handling logic for the many ways that
1885      *  requests can fail, and calls the request callback when requests
1886      *  succeed.
1887      *
1888      *  Parameters:
1889      *    (Function) func - The handler for the request.
1890      *    (Strophe.Request) req - The request that is changing readyState.
1891      */
1892     _onRequestStateChange: function (func, req)
1893     {
1894         Strophe.debug("request id " + req.id + 
1895                       "." + req.sends + " state changed to " + 
1896                       req.xhr.readyState);
1897
1898         if (req.abort) {
1899             req.abort = false;
1900             return;
1901         }
1902         
1903         // request complete
1904         var reqStatus;
1905         if (req.xhr.readyState == 4) {
1906             reqStatus = 0;
1907             try {
1908                 reqStatus = req.xhr.status;
1909             } catch (e) {
1910                 // ignore errors from undefined status attribute.  works
1911                 // around a browser bug
1912             }
1913             
1914             if (typeof(reqStatus) == "undefined") {
1915                 reqStatus = 0;
1916             }
1917
1918             if (this.disconnecting) {
1919                 if (reqStatus >= 400) {
1920                     this._hitError(reqStatus);
1921                     return;
1922                 } 
1923             }
1924
1925             var reqIs0 = (this._requests[0] == req);
1926             var reqIs1 = (this._requests[1] == req);
1927             
1928             if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
1929                 // remove from internal queue
1930                 this._removeRequest(req);
1931                 Strophe.debug("request id " + 
1932                               req.id + 
1933                               " should now be removed"); 
1934             }
1935             
1936             // request succeeded
1937             if (reqStatus == 200) {
1938                 // if request 1 finished, or request 0 finished and request
1939                 // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
1940                 // restart the other - both will be in the first spot, as the
1941                 // completed request has been removed from the queue already
1942                 if (reqIs1 || 
1943                     (reqIs0 && this._requests.length > 0 && 
1944                      this._requests[0].age() > Strophe.SECONDARY_TIMEOUT)) {
1945                     this._restartRequest(0);
1946                 }
1947                 // call handler
1948                 Strophe.debug("request id " + 
1949                               req.id + "." + 
1950                               req.sends + " got 200");
1951                 func(req);
1952                 this.errors = 0;
1953             } else {
1954                 Strophe.error("request id " + 
1955                               req.id + "." + 
1956                               req.sends + " error " + reqStatus + 
1957                               " happened");
1958                 if (reqStatus === 0 || 
1959                     (reqStatus >= 400 && reqStatus < 600) || 
1960                     reqStatus >= 12000) {
1961                     this._hitError(reqStatus);
1962                     if (reqStatus >= 400 && reqStatus < 500) {
1963                         this.connect_callback(Strophe.Status.DISCONNECTING, 
1964                                               null);
1965                         this._doDisconnect();
1966                     }
1967                 }
1968             }
1969
1970             if (!((reqStatus > 0 && reqStatus < 10000) || 
1971                   req.sends > 5)) {
1972                 this._throttledRequestHandler();
1973             }
1974         }
1975     },
1976
1977     /** PrivateFunction: _hitError
1978      *  _Private_ function to handle the error count.
1979      *
1980      *  Requests are resent automatically until their error count reaches
1981      *  5.  Each time an error is encountered, this function is called to
1982      *  increment the count and disconnect if the count is too high.
1983      *
1984      *  Parameters:
1985      *    (Integer) reqStatus - The request status.
1986      */
1987     _hitError: function (reqStatus)
1988     {
1989         this.errors++;
1990         Strophe.warn("request errored, status: " + reqStatus + 
1991                      ", number of errors: " + this.errors);
1992         if (this.errors > 4) {
1993             this._onDisconnectTimeout();
1994         }
1995     },
1996
1997     /** PrivateFunction: _doDisconnect
1998      *  _Private_ function to disconnect.
1999      *
2000      *  This is the last piece of the disconnection logic.  This resets the
2001      *  connection and alerts the user's connection callback.
2002      */
2003     _doDisconnect: function ()
2004     {
2005         Strophe.info("_doDisconnect was called");
2006         this.authenticated = false;
2007         this.disconnecting = false;
2008         this.sid = null;
2009         this.streamId = null;
2010         this.rid = Math.floor(Math.random() * 4294967295);
2011
2012         // tell the parent we disconnected
2013         if (this.connected) {
2014             this.connect_callback(Strophe.Status.DISCONNECTED, null);
2015             this.connected = false;
2016         }
2017
2018         // delete handlers
2019         this.handlers = [];
2020         this.timedHandlers = [];
2021         this.removeTimeds = [];
2022         this.removeHandlers = [];
2023         this.addTimeds = [];
2024         this.addHandlers = [];
2025     },
2026     
2027     /** PrivateFunction: _dataRecv
2028      *  _Private_ handler to processes incoming data from the the connection.
2029      *
2030      *  Except for _connect_cb handling the initial connection request, 
2031      *  this function handles the incoming data for all requests.  This
2032      *  function also fires stanza handlers that match each incoming 
2033      *  stanza.
2034      *
2035      *  Parameters:
2036      *    (Strophe.Request) req - The request that has data ready.
2037      */
2038     _dataRecv: function (req)
2039     {
2040         try {
2041             var elem = req.getResponse();
2042         } catch (e) {
2043             if (e != "parsererror") throw e;
2044
2045             this.connect_callback(Strophe.Status.DISCONNECTING, 
2046                                   "strophe-parsererror");
2047             this.disconnect();
2048         }
2049         if (elem === null) return;
2050
2051         // handle graceful disconnect
2052         if (this.disconnecting && this._requests.length == 0) {
2053             this.deleteTimedHandler(this._disconnectTimeout);
2054             this._disconnectTimeout = null;
2055             this._doDisconnect();
2056         }
2057
2058         this.rawInput(Strophe.serialize(elem));
2059
2060         var typ = elem.getAttribute("type");
2061         var cond, conflict;
2062         if (typ !== null && typ == "terminate") {
2063             // an error occurred
2064             cond = elem.getAttribute("condition");
2065             conflict = elem.getElementsByTagName("conflict");
2066             if (cond !== null) {
2067                 if (cond == "remote-stream-error" && conflict.length > 0) {
2068                     cond = "conflict";
2069                 }
2070                 this.connect_callback(Strophe.Status.CONNFAIL, cond);
2071             } else {
2072                 this.connect_callback(Strophe.Status.CONNFAIL, "unknown");
2073             }
2074             this.connect_callback(Strophe.Status.DISCONNECTING, null);
2075             this.disconnect();
2076             return;
2077         }
2078
2079         // remove handlers scheduled for deletion
2080         var i, hand;
2081         while (this.removeHandlers.length > 0) {
2082             hand = this.removeHandlers.pop();
2083             i = this.handlers.indexOf(hand);
2084             if (i >= 0) 
2085                 this.handlers.splice(i, 1);
2086         }
2087
2088         // add handlers scheduled for addition
2089         while (this.addHandlers.length > 0) {
2090             this.handlers.push(this.addHandlers.pop());
2091         }
2092         
2093         // send each incoming stanza through the handler chain
2094         var self = this;
2095         Strophe.forEachChild(elem, null, function (child) {
2096             var i, newList;
2097             // process handlers
2098             newList = self.handlers;
2099             self.handlers = [];
2100             for (i = 0; i < newList.length; i++) {
2101                 var hand = newList[i];
2102                 if (hand.isMatch(child) && 
2103                     (self.authenticated || !hand.user)) {
2104                     if (hand.run(child)) {
2105                         self.handlers.push(hand);
2106                     }
2107                 } else {
2108                     self.handlers.push(hand);
2109                 }
2110             }
2111         });
2112     },
2113
2114     /** PrivateFunction: _sendTerminate
2115      *  _Private_ function to send initial disconnect sequence.
2116      *
2117      *  This is the first step in a graceful disconnect.  It sends
2118      *  the BOSH server a terminate body and includes an unavailable
2119      *  presence if authentication has completed.
2120      */
2121     _sendTerminate: function ()
2122     {
2123         Strophe.info("_sendTerminate was called");
2124         var body = this._buildBody().attrs({type: "terminate"});
2125
2126         var presence, i;
2127         if (this.authenticated) {
2128             body.c('presence', {
2129                 xmlns: Strophe.NS.CLIENT,
2130                 type: 'unavailable'
2131             });
2132         }
2133
2134         this.disconnecting = true;
2135
2136         var req = new Strophe.Request(body.toString(),
2137                                       this._onRequestStateChange.bind(this)
2138                                           .prependArg(this._dataRecv.bind(this)),
2139                                       body.tree().getAttribute("rid"));
2140         
2141         // abort and clear all waiting requests
2142         var r;
2143         while (this._requests.length > 0) {
2144             r = this._requests.pop();
2145             r.xhr.abort();
2146             r.abort = true;
2147         }
2148
2149         this._requests.push(req);
2150         this._throttledRequestHandler();
2151     },
2152
2153     /** PrivateFunction: _connect_cb
2154      *  _Private_ handler for initial connection request.
2155      *
2156      *  This handler is used to process the initial connection request
2157      *  response from the BOSH server. It is used to set up authentication
2158      *  handlers and start the authentication process.
2159      *
2160      *  SASL authentication will be attempted if available, otherwise
2161      *  the code will fall back to legacy authentication.
2162      *
2163      *  Parameters:
2164      *    (Strophe.Request) req - The current request.
2165      */
2166     _connect_cb: function (req)
2167     {
2168         Strophe.info("_connect_cb was called");
2169
2170         this.connected = true;
2171         var bodyWrap = req.getResponse();
2172         if (!bodyWrap) return;
2173
2174         this.rawInput(Strophe.serialize(bodyWrap));
2175
2176         var typ = bodyWrap.getAttribute("type");
2177         var cond, conflict;
2178         if (typ !== null && typ == "terminate") {
2179             // an error occurred
2180             cond = bodyWrap.getAttribute("condition");
2181             conflict = bodyWrap.getElementsByTagName("conflict");
2182             if (cond !== null) {
2183                 if (cond == "remote-stream-error" && conflict.length > 0) {
2184                     cond = "conflict";
2185                 }
2186                 this.connect_callback(Strophe.Status.CONNFAIL, cond);
2187             } else {
2188                 this.connect_callback(Strophe.Status.CONNFAIL, "unknown");
2189             }
2190             return;
2191         }
2192
2193         this.sid = bodyWrap.getAttribute("sid");
2194         this.stream_id = bodyWrap.getAttribute("authid");
2195
2196         // TODO - add SASL anonymous for guest accounts
2197         var do_sasl_plain = false;
2198         var do_sasl_digest_md5 = false;
2199         var do_sasl_anonymous = false;
2200
2201         var mechanisms = bodyWrap.getElementsByTagName("mechanism");
2202         var i, mech, auth_str, hashed_auth_str;
2203         if (mechanisms.length > 0) {
2204             for (i = 0; i < mechanisms.length; i++) {
2205                 mech = Strophe.getText(mechanisms[i]);
2206                 if (mech == 'DIGEST-MD5') {
2207                     do_sasl_digest_md5 = true;
2208                 } else if (mech == 'PLAIN') {
2209                     do_sasl_plain = true;
2210                 } else if (mech == 'ANONYMOUS') {
2211                     do_sasl_anonymous = true;
2212                 }
2213             }
2214         }
2215         
2216         if (Strophe.getNodeFromJid(this.jid) === null && 
2217             do_sasl_anonymous) {
2218             this.connect_callback(Strophe.Status.AUTHENTICATING, null);
2219             this._sasl_success_handler = this._addSysHandler(
2220                 this._sasl_success_cb.bind(this), null,
2221                 "success", null, null);
2222             this._sasl_failure_handler = this._addSysHandler(
2223                 this._sasl_failure_cb.bind(this), null,
2224                 "failure", null, null);
2225
2226             this.send($build("auth", {
2227                 xmlns: Strophe.NS.SASL,
2228                 mechanism: "ANONYMOUS"
2229             }).tree());
2230         } else if (Strophe.getNodeFromJid(this.jid) === null) {
2231             // we don't have a node, which is required for non-anonymous
2232             // client connections
2233             this.connect_callback(Strophe.Status.CONNFAIL, null);
2234             this.disconnect();
2235         } else if (do_sasl_digest_md5) {
2236             this.connect_callback(Strophe.Status.AUTHENTICATING, null);
2237             this._sasl_challenge_handler = this._addSysHandler(
2238                 this._sasl_challenge1_cb.bind(this), null, 
2239                 "challenge", null, null);
2240             this._sasl_failure_handler = this._addSysHandler(
2241                 this._sasl_failure_cb.bind(this), null, 
2242                 "failure", null, null);
2243
2244             this.send($build("auth", {
2245                 xmlns: Strophe.NS.SASL,
2246                 mechanism: "DIGEST-MD5"
2247             }).tree());
2248         } else if (do_sasl_plain) {
2249             // Build the plain auth string (barejid null
2250             // username null password) and base 64 encoded.
2251             auth_str = Strophe.escapeJid(
2252                 Strophe.getBareJidFromJid(this.jid));
2253             auth_str = auth_str + "\u0000";
2254             auth_str = auth_str + Strophe.getNodeFromJid(this.jid);
2255             auth_str = auth_str + "\u0000";
2256             auth_str = auth_str + this.pass;
2257             
2258             this.connect_callback(Strophe.Status.AUTHENTICATING, null);
2259             this._sasl_success_handler = this._addSysHandler(
2260                 this._sasl_success_cb.bind(this), null, 
2261                 "success", null, null);
2262             this._sasl_failure_handler = this._addSysHandler(
2263                 this._sasl_failure_cb.bind(this), null,
2264                 "failure", null, null);
2265
2266             hashed_auth_str = encode64(auth_str);
2267             this.send($build("auth", {
2268                 xmlns: Strophe.NS.SASL,
2269                 mechanism: "PLAIN"
2270             }).t(hashed_auth_str).tree());
2271         } else {
2272             this.connect_callback(Strophe.Status.AUTHENTICATING, null);
2273             this._addSysHandler(this._auth1_cb.bind(this), null, null, 
2274                                 null, "_auth_1");
2275             
2276             this.send($iq({
2277                 type: "get",
2278                 to: this.domain,
2279                 id: "_auth_1"
2280             }).c("query", {
2281                 xmlns: Strophe.NS.AUTH
2282             }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
2283         }
2284     },
2285
2286     /** PrivateFunction: _sasl_challenge1_cb
2287      *  _Private_ handler for DIGEST-MD5 SASL authentication.
2288      *
2289      *  Parameters:
2290      *    (XMLElement) elem - The challenge stanza.
2291      *
2292      *  Returns:
2293      *    false to remove the handler.
2294      */
2295     _sasl_challenge1_cb: function (elem)
2296     {
2297         var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
2298
2299         var challenge = decode64(Strophe.getText(elem));
2300         var cnonce = hex_md5(Math.random() * 1234567890);
2301         var realm = "";
2302         var host = null;
2303         var nonce = "";
2304         var qop = "";
2305         var matches;
2306
2307         // remove unneeded handlers
2308         this.deleteHandler(this._sasl_failure_handler);
2309
2310         while (challenge.match(attribMatch)) {
2311             matches = challenge.match(attribMatch);
2312             challenge = challenge.replace(matches[0], "");
2313             matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
2314             switch (matches[1]) {
2315             case "realm": 
2316                 realm = matches[2]; 
2317                 break;
2318             case "nonce": 
2319                 nonce = matches[2]; 
2320                 break;
2321             case "qop":
2322                 qop = matches[2]; 
2323                 break;
2324             case "host":
2325                 host = matches[2];
2326                 break;
2327             }
2328         }
2329
2330         var digest_uri = "xmpp/" + realm;
2331         if (host !== null) {
2332             digest_uri = digest_uri + "/" + host;
2333         }
2334                         
2335         var A1 = str_md5(Strophe.getNodeFromJid(this.jid) + 
2336                          ":" + realm + ":" + this.pass) + 
2337             ":" + nonce + ":" + cnonce;
2338         var A2 = 'AUTHENTICATE:' + digest_uri;
2339
2340         var responseText = "";
2341         responseText += 'username="' + 
2342             Strophe.getNodeFromJid(this.jid) + '",';
2343         responseText += 'realm="' + realm + '",';
2344         responseText += 'nonce="' + nonce + '",';
2345         responseText += 'cnonce="' + cnonce + '",';
2346         responseText += 'nc="00000001",';
2347         responseText += 'qop="auth",';
2348         responseText += 'digest-uri="' + digest_uri + '",';
2349         responseText += 'response="' + hex_md5(hex_md5(A1) + ":" + 
2350                                                nonce + ":00000001:" + 
2351                                                cnonce + ":auth:" + 
2352                                                hex_md5(A2)) + '",';
2353         responseText += 'charset="utf-8"';
2354
2355         this._sasl_challenge_handler = this._addSysHandler(
2356             this._sasl_challenge2_cb.bind(this), null, 
2357             "challenge", null, null);
2358         this._sasl_success_handler = this._addSysHandler(
2359             this._sasl_success_cb.bind(this), null, 
2360             "success", null, null);
2361         this._sasl_failure_handler = this._addSysHandler(
2362             this._sasl_failure_cb.bind(this), null, 
2363             "failure", null, null);
2364
2365         this.send($build('response', {
2366             xmlns: Strophe.NS.SASL
2367         }).t(encode64(responseText)).tree());
2368
2369         return false;
2370     },
2371
2372     /** PrivateFunction: _sasl_challenge2_cb
2373      *  _Private_ handler for second step of DIGEST-MD5 SASL authentication.
2374      *
2375      *  Parameters:
2376      *    (XMLElement) elem - The challenge stanza.
2377      *
2378      *  Returns:
2379      *    false to remove the handler.
2380      */
2381     _sasl_challenge2_cb: function (elem)
2382     {
2383         // remove unneeded handlers
2384         this.deleteHandler(this._sasl_success_handler);
2385         this.deleteHandler(this._sasl_failure_handler);
2386
2387         this._sasl_success_handler = this._addSysHandler(
2388             this._sasl_success_cb.bind(this), null, 
2389             "success", null, null);
2390         this._sasl_failure_handler = this._addSysHandler(
2391             this._sasl_failure_cb.bind(this), null, 
2392             "failure", null, null);
2393         this.send($build('response', {xmlns: Strophe.NS.SASL}).tree());
2394         return false;
2395     },
2396
2397     /** PrivateFunction: _auth1_cb
2398      *  _Private_ handler for legacy authentication.
2399      *
2400      *  This handler is called in response to the initial <iq type='get'/>
2401      *  for legacy authentication.  It builds an authentication <iq/> and
2402      *  sends it, creating a handler (calling back to _auth2_cb()) to 
2403      *  handle the result
2404      *
2405      *  Parameters:
2406      *    (XMLElement) elem - The stanza that triggered the callback.
2407      *
2408      *  Returns:
2409      *    false to remove the handler.
2410      */
2411     _auth1_cb: function (elem)
2412     {
2413         var use_digest = false;
2414         var check_query, check_digest;
2415
2416         if (elem.getAttribute("type") == "result") {
2417             // Find digest
2418             check_query = elem.childNodes[0];
2419             if (check_query) {
2420                 check_digest = check_query.getElementsByTagName("digest")[0];
2421                 if (check_digest) {
2422                     use_digest = true;
2423                 }
2424             }
2425         }
2426
2427         // Use digest or plaintext depending on the server features
2428         var iq = $iq({type: "set", id: "_auth_2"})
2429             .c('query', {xmlns: Strophe.NS.AUTH})
2430             .c('username', {}).t(Strophe.getNodeFromJid(this.jid));
2431         if (use_digest) {
2432             iq.up().c("digest", {})
2433                 .t(hex_sha1(this.stream_id + this.pass));
2434         } else {
2435             iq.up().c('password', {}).t(this.pass);
2436         }
2437         if (!Strophe.getResourceFromJid(this.jid)) {
2438             // since the user has not supplied a resource, we pick
2439             // a default one here.  unlike other auth methods, the server
2440             // cannot do this for us.
2441             this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe';
2442         }
2443         iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid));
2444
2445         this._addSysHandler(this._auth2_cb.bind(this), null, 
2446                             null, null, "_auth_2");
2447
2448         this.send(iq.tree());
2449
2450         return false;
2451     },
2452
2453     /** PrivateFunction: _sasl_success_cb
2454      *  _Private_ handler for succesful SASL authentication.
2455      *
2456      *  Parameters:
2457      *    (XMLElement) elem - The matching stanza.
2458      *
2459      *  Returns:
2460      *    false to remove the handler.
2461      */
2462     _sasl_success_cb: function (elem)
2463     {
2464         Strophe.info("SASL authentication succeeded.");
2465
2466         // remove old handlers
2467         this.deleteHandler(this._sasl_failure_handler);
2468         this._sasl_failure_handler = null;
2469         if (this._sasl_challenge_handler) {
2470             this.deleteHandler(this._sasl_challenge_handler);
2471             this._sasl_challenge_handler = null;
2472         }
2473
2474         this._addSysHandler(this._sasl_auth1_cb.bind(this), null, 
2475                             "stream:features", null, null);
2476
2477         // we must send an xmpp:restart now
2478         this._sendRestart();
2479
2480         return false;
2481     },
2482
2483     /** PrivateFunction: _sasl_auth1_cb
2484      *  _Private_ handler to start stream binding.
2485      *
2486      *  Parameters:
2487      *    (XMLElement) elem - The matching stanza.
2488      *
2489      *  Returns:
2490      *    false to remove the handler.
2491      */
2492     _sasl_auth1_cb: function (elem)
2493     {
2494         var i, child;
2495         
2496         for (i = 0; i < elem.childNodes.length; i++) {
2497             child = elem.childNodes[i];
2498             if (child.nodeName == 'bind') {
2499                 this.do_bind = true;
2500             }
2501
2502             if (child.nodeName == 'session') {
2503                 this.do_session = true;
2504             }
2505         }
2506
2507         if (!this.do_bind) {
2508             this.connect_callback(Strophe.Status.AUTHFAIL, null);
2509             return false;
2510         } else {
2511             this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, 
2512                                 null, "_bind_auth_2");
2513             
2514             var resource = Strophe.getResourceFromJid(this.jid);
2515             if (resource)
2516                 this.send($iq({type: "set", id: "_bind_auth_2"})
2517                           .c('bind', {xmlns: Strophe.NS.BIND})
2518                           .c('resource', {}).t(resource).tree());
2519             else
2520                 this.send($iq({type: "set", id: "_bind_auth_2"})
2521                           .c('bind', {xmlns: Strophe.NS.BIND})
2522                           .tree());
2523         }
2524
2525         return false;
2526     },
2527
2528     /** PrivateFunction: _sasl_bind_cb
2529      *  _Private_ handler for binding result and session start.
2530      *
2531      *  Parameters:
2532      *    (XMLElement) elem - The matching stanza.
2533      *
2534      *  Returns:
2535      *    false to remove the handler.
2536      */
2537     _sasl_bind_cb: function (elem)
2538     {
2539         if (elem.getAttribute("type") == "error") {
2540             Strophe.info("SASL binding failed.");
2541             this.connect_callback(Strophe.Status.AUTHFAIL, null);
2542             return false;
2543         }
2544
2545         // TODO - need to grab errors
2546         var bind = elem.getElementsByTagName("bind");
2547         var jidNode;
2548         if (bind.length > 0) {
2549             // Grab jid
2550             jidNode = bind[0].getElementsByTagName("jid");
2551             if (jidNode.length > 0) {
2552                 this.jid = Strophe.getText(jidNode[0]);
2553                 
2554                 if (this.do_session) {
2555                     this._addSysHandler(this._sasl_session_cb.bind(this), 
2556                                         null, null, null, "_session_auth_2");
2557                     
2558                     this.send($iq({type: "set", id: "_session_auth_2"})
2559                                   .c('session', {xmlns: Strophe.NS.SESSION})
2560                                   .tree());
2561                 }
2562             }
2563         } else {
2564             Strophe.info("SASL binding failed.");
2565             this.connect_callback(Strophe.Status.AUTHFAIL, null);
2566             return false;
2567         }
2568     },
2569
2570     /** PrivateFunction: _sasl_session_cb
2571      *  _Private_ handler to finish successful SASL connection.
2572      *
2573      *  This sets Connection.authenticated to true on success, which
2574      *  starts the processing of user handlers.
2575      *
2576      *  Parameters:
2577      *    (XMLElement) elem - The matching stanza.
2578      *
2579      *  Returns:
2580      *    false to remove the handler.
2581      */
2582     _sasl_session_cb: function (elem)
2583     {
2584         if (elem.getAttribute("type") == "result") {
2585             this.authenticated = true;
2586             this.connect_callback(Strophe.Status.CONNECTED, null);
2587         } else if (elem.getAttribute("type") == "error") {
2588             Strophe.info("Session creation failed.");
2589             this.connect_callback(Strophe.Status.AUTHFAIL, null);
2590             return false;
2591         }
2592
2593         return false;
2594     },
2595
2596     /** PrivateFunction: _sasl_failure_cb
2597      *  _Private_ handler for SASL authentication failure.
2598      *
2599      *  Parameters:
2600      *    (XMLElement) elem - The matching stanza.
2601      *
2602      *  Returns:
2603      *    false to remove the handler.
2604      */
2605     _sasl_failure_cb: function (elem)
2606     {
2607         // delete unneeded handlers
2608         if (this._sasl_success_handler) {
2609             this.deleteHandler(this._sasl_success_handler);
2610             this._sasl_success_handler = null;
2611         }
2612         if (this._sasl_challenge_handler) {
2613             this.deleteHandler(this._sasl_challenge_handler);
2614             this._sasl_challenge_handler = null;
2615         }
2616         
2617         this.connect_callback(Strophe.Status.AUTHFAIL, null);
2618         return false;
2619     },
2620
2621     /** PrivateFunction: _auth2_cb
2622      *  _Private_ handler to finish legacy authentication.
2623      *
2624      *  This handler is called when the result from the jabber:iq:auth
2625      *  <iq/> stanza is returned.
2626      *
2627      *  Parameters:
2628      *    (XMLElement) elem - The stanza that triggered the callback.
2629      *
2630      *  Returns:
2631      *    false to remove the handler.
2632      */
2633     _auth2_cb: function (elem)
2634     {
2635         if (elem.getAttribute("type") == "result") {
2636             this.authenticated = true;
2637             this.connect_callback(Strophe.Status.CONNECTED, null);
2638         } else if (elem.getAttribute("type") == "error") {
2639             this.connect_callback(Strophe.Status.AUTHFAIL, null);
2640             this.disconnect();
2641         }
2642
2643         return false;
2644     },
2645
2646     /** PrivateFunction: _addSysTimedHandler
2647      *  _Private_ function to add a system level timed handler.
2648      *
2649      *  This function is used to add a Strophe.TimedHandler for the
2650      *  library code.  System timed handlers are allowed to run before
2651      *  authentication is complete.
2652      *
2653      *  Parameters:
2654      *    (Integer) period - The period of the handler.
2655      *    (Function) handler - The callback function.
2656      */
2657     _addSysTimedHandler: function (period, handler)
2658     {
2659         var thand = new Strophe.TimedHandler(period, handler);
2660         thand.user = false;
2661         this.addTimeds.push(thand);
2662         return thand;
2663     },
2664
2665     /** PrivateFunction: _addSysHandler
2666      *  _Private_ function to add a system level stanza handler.
2667      *
2668      *  This function is used to add a Strophe.Handler for the
2669      *  library code.  System stanza handlers are allowed to run before
2670      *  authentication is complete.
2671      *
2672      *  Parameters:
2673      *    (Function) handler - The callback function.
2674      *    (String) ns - The namespace to match.
2675      *    (String) name - The stanza name to match.
2676      *    (String) type - The stanza type attribute to match.
2677      *    (String) id - The stanza id attribute to match.
2678      */
2679     _addSysHandler: function (handler, ns, name, type, id)
2680     {
2681         var hand = new Strophe.Handler(handler, ns, name, type, id);
2682         hand.user = false;
2683         this.addHandlers.push(hand);
2684         return hand;
2685     },
2686
2687     /** PrivateFunction: _onDisconnectTimeout
2688      *  _Private_ timeout handler for handling non-graceful disconnection.
2689      *
2690      *  If the graceful disconnect process does not complete within the 
2691      *  time allotted, this handler finishes the disconnect anyway.
2692      *
2693      *  Returns:
2694      *    false to remove the handler.
2695      */
2696     _onDisconnectTimeout: function ()
2697     {
2698         Strophe.info("_onDisconnectTimeout was called");
2699
2700         // cancel all remaining requests and clear the queue
2701         var req;
2702         while (this._requests.length > 0) {
2703             req = this._requests.pop();
2704             req.xhr.abort();
2705             req.abort = true;
2706         }
2707         
2708         // actually disconnect
2709         this._doDisconnect();
2710         
2711         return false;
2712     },
2713
2714     /** PrivateFunction: _onIdle
2715      *  _Private_ handler to process events during idle cycle.
2716      *
2717      *  This handler is called every 100ms to fire timed handlers that 
2718      *  are ready and keep poll requests going.
2719      */
2720     _onIdle: function ()
2721     {
2722         var i, thand, since, newList;
2723
2724         // remove timed handlers that have been scheduled for deletion
2725         while (this.removeTimeds.length > 0) {
2726             thand = this.removeTimeds.pop();
2727             i = this.timedHandlers.indexOf(thand);
2728             if (i >= 0)
2729                 this.timedHandlers.splice(i, 1);
2730         }
2731
2732         // add timed handlers scheduled for addition
2733         while (this.addTimeds.length > 0) {
2734             this.timedHandlers.push(this.addTimeds.pop());
2735         }
2736
2737         // call ready timed handlers
2738         var now = new Date().getTime();
2739         newList = [];
2740         for (i = 0; i < this.timedHandlers.length; i++) {
2741             thand = this.timedHandlers[i];
2742             if (this.authenticated || !thand.user) {
2743                 since = thand.lastCalled + thand.period;
2744                 if (since - now <= 0) {
2745                     if (thand.run()) {
2746                         newList.push(thand);
2747                     }
2748                 } else {
2749                     newList.push(thand);
2750                 }
2751             }
2752         }
2753         this.timedHandlers = newList;
2754         
2755         var body, time_elapsed;
2756
2757         // if no requests are in progress, poll
2758         if (this.authenticated && this._requests.length === 0 && 
2759             this._data.length === 0 && !this.disconnecting) {
2760             Strophe.info("no requests during idle cycle, sending " + 
2761                          "blank request");
2762             this.send(null);
2763         } else {
2764             if (this._requests.length < 2 && this._data.length > 0 && 
2765                !this.paused) {
2766                 body = this._buildBody();
2767                 for (i = 0; i < this._data.length; i++) {
2768                     if (this._data[i] !== null) {
2769                         if (this._data[i] === "restart") {
2770                             body.attrs({
2771                                 to: this.domain,
2772                                 "xml:lang": "en",
2773                                 "xmpp:restart": "true",
2774                                 "xmlns:xmpp": Strophe.NS.BOSH
2775                             })
2776                         } else {
2777                             body.cnode(this._data[i]).up();
2778                         }
2779                     }
2780                 }
2781                 delete this._data;
2782                 this._data = [];
2783                 this._requests.push(
2784                     new Strophe.Request(body.toString(),
2785                                         this._onRequestStateChange.bind(this)
2786                                             .prependArg(this._dataRecv.bind(this)),
2787                                         body.tree().getAttribute("rid")));
2788                 this._processRequest(this._requests.length - 1);
2789             }
2790
2791             if (this._requests.length > 0) {
2792                 time_elapsed = this._requests[0].age();
2793                 if (this._requests[0].dead !== null) {
2794                     if (this._requests[0].timeDead() > 
2795                         Strophe.SECONDARY_TIMEOUT) {
2796                         this._throttledRequestHandler();
2797                     }
2798                 }
2799                 
2800                 if (time_elapsed > Strophe.TIMEOUT) {
2801                     Strophe.warn("Request " + 
2802                                  this._requests[0].id + 
2803                                  " timed out, over " + Strophe.TIMEOUT + 
2804                                  " seconds since last activity");
2805                     this._throttledRequestHandler();
2806                 }
2807             }
2808         }
2809         
2810         // reactivate the timer
2811         clearTimeout(this._idleTimeout);
2812         this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
2813     }
2814 };