2 This program is distributed under the terms of the MIT license.
3 Please see the LICENSE file for details.
5 Copyright 2006-2008, OGG, LLC
9 * A JavaScript library for XMPP BOSH.
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.
18 /** PrivateFunction: Function.prototype.bind
19 * Bind a function to an instance.
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.
27 * This extension already exists in some browsers (namely, Firefox 3), but
28 * we provide it to support those that don't.
31 * (Object) obj - The object that will become 'this' in the bound function.
36 if (!Function.prototype.bind) {
37 Function.prototype.bind = function (obj)
40 return function () { return func.apply(obj, arguments); };
44 /** PrivateFunction: Function.prototype.prependArg
45 * Prepend an argument to a function.
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>
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.
58 * This is used inside Strophe for passing the Strophe.Request object to
59 * the onreadystatechange handler of XMLHttpRequests.
62 * arg - The argument to pass as the first parameter to the function.
65 * A new Function which calls the original with the prepended argument.
67 if (!Function.prototype.prependArg) {
68 Function.prototype.prependArg = function (arg)
74 for (var i = 0; i < arguments.length; i++)
75 newargs.push(arguments[i]);
76 return func.apply(this, newargs);
81 /** PrivateFunction: Array.prototype.indexOf
82 * Return the index of an object in an array.
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
89 * (Object) elt - The object to look for.
90 * (Integer) from - The index from which to start looking. (optional).
93 * The index of elt in the array or -1 if not found.
95 if (!Array.prototype.indexOf)
97 Array.prototype.indexOf = function(elt /*, from*/)
99 var len = this.length;
101 var from = Number(arguments[1]) || 0;
102 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
106 for (; from < len; from++) {
107 if (from in this && this[from] === elt)
117 * Create a Strophe.Builder.
118 * This is an alias for 'new Strophe.Builder(name, attrs)'.
121 * (String) name - The root element name.
122 * (Object) attrs - The attributes for the root element in object notation.
125 * A new Strophe.Builder object.
127 function $build(name, attrs) { return new Strophe.Builder(name, attrs); }
129 * Create a Strophe.Builder with a <message/> element as the root.
132 * (Object) attrs - The <message/> element attributes in object notation.
135 * A new Strophe.Builder object.
137 function $msg(attrs) { return new Strophe.Builder("message", attrs); }
139 * Create a Strophe.Builder with an <iq/> element as the root.
142 * (Object) attrs - The <iq/> element attributes in object notation.
145 * A new Strophe.Builder object.
147 function $iq(attrs) { return new Strophe.Builder("iq", attrs); }
149 * Create a Strophe.Builder with a <presence/> element as the root.
152 * (Object) attrs - The <presence/> element attributes in object notation.
155 * A new Strophe.Builder object.
157 function $pres(attrs) { return new Strophe.Builder("presence", attrs); }
160 * An object container for all Strophe library functions.
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.
167 /** Constants: XMPP Namespace Constants
168 * Common namespace constants from the XMPP RFCs and XEPs.
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.
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"
201 /** Constants: Connection Status Constants
202 * Connection status constants for use by the connection handler
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
225 /** Constants: Log Level Constants
226 * Logging level indicators.
228 * LogLevel.DEBUG - Debug output
229 * LogLevel.INFO - Informational output
230 * LogLevel.WARN - Warnings
231 * LogLevel.ERROR - Errors
232 * LogLevel.FATAL - Fatal errors
242 /** PrivateConstants: DOM Element Type Constants
245 * ElementType.NORMAL - Normal element.
246 * ElementType.TEXT - Text data element.
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
258 * TIMEOUT - Time to wait for a request to return. This defaults to
260 * SECONDARY_TIMEOUT - Time to wait for immediate request return. This
261 * defaults to 7 seconds.
264 SECONDARY_TIMEOUT: 7,
266 /** Function: forEachChild
267 * Map a function over some or all child elements of a given element.
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.
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.
280 forEachChild: function (elem, elemName, func)
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))) {
293 /** Function: isTagEqual
294 * Compare an element's tag name with a string.
296 * This function is case insensitive.
299 * (XMLElement) el - A DOM element.
300 * (String) name - The element name.
303 * true if the element's tag name matches _el_, and false
306 isTagEqual: function (el, name)
308 return el.tagName.toLowerCase() == name.toLowerCase();
311 /** Function: xmlElement
312 * Create an XML DOM element.
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.
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.
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.
334 * A new XML DOM element.
336 xmlElement: function (name)
338 // FIXME: this should also support attrs argument in object notation
339 if (!name) { return null; }
342 if (window.ActiveXObject) {
343 node = new ActiveXObject("Microsoft.XMLDOM").createElement(name);
345 node = document.createElement(name);
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);
353 // FIXME: this should throw errors if args are the wrong type or
354 // there are more than two optional args
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],
376 /** Function: xmlTextNode
377 * Creates an XML DOM text node.
379 * Provides a cross implementation version of document.createTextNode.
382 * (String) text - The content of the text node.
385 * A new XML DOM text node.
387 xmlTextNode: function (text)
389 if (window.ActiveXObject) {
390 return new ActiveXObject("Microsoft.XMLDOM").createTextNode(text);
392 return document.createTextNode(text);
396 /** Function: getText
397 * Get the concatenation of all text children of an element.
400 * (XMLElement) elem - A DOM element.
403 * A String with the concatenated text of all text element children.
405 getText: function (elem)
407 if (!elem) return null;
410 if (elem.childNodes.length === 0 && elem.nodeType ==
411 Strophe.ElementType.TEXT) {
412 str += elem.nodeValue;
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;
424 /** Function: copyElement
425 * Copy an XML DOM element.
427 * This function copies a DOM element and all its descendants and returns
431 * (XMLElement) elem - A DOM element.
434 * A new, copied DOM element tree.
436 copyElement: function (elem)
439 if (elem.nodeType == Strophe.ElementType.NORMAL) {
440 el = Strophe.xmlElement(elem.tagName);
442 for (i = 0; i < elem.attributes.length; i++) {
443 el.setAttribute(elem.attributes[i].nodeName.toLowerCase(),
444 elem.attributes[i].value);
447 for (i = 0; i < elem.childNodes.length; i++) {
448 el.appendChild(Strophe.copyElement(elem.childNodes[i]));
450 } else if (elem.nodeType == Strophe.ElementType.TEXT) {
451 el = Strophe.xmlTextNode(elem.nodeValue);
457 /** Function: escapeJid
461 * (String) jid - A JID.
464 * An escaped JID String.
466 escapeJid: function (jid)
468 var user = jid.split("@");
469 if (user.length == 1)
470 // no user so nothing to escape
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");
487 return [user, host].join("@");
490 /** Function: unescapeJid
494 * (String) jid - A JID.
497 * An unescaped JID String.
499 unescapeJid: function (jid)
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, "\\");
513 /** Function: getNodeFromJid
514 * Get the node portion of a JID String.
517 * (String) jid - A JID.
520 * A String containing the node.
522 getNodeFromJid: function (jid)
524 if (jid.indexOf("@") < 0)
526 return Strophe.escapeJid(jid).split("@")[0];
529 /** Function: getDomainFromJid
530 * Get the domain portion of a JID String.
533 * (String) jid - A JID.
536 * A String containing the domain.
538 getDomainFromJid: function (jid)
540 var bare = Strophe.escapeJid(Strophe.getBareJidFromJid(jid));
541 if (bare.indexOf("@") < 0)
544 return bare.split("@")[1];
547 /** Function: getResourceFromJid
548 * Get the resource portion of a JID String.
551 * (String) jid - A JID.
554 * A String containing the resource.
556 getResourceFromJid: function (jid)
558 var s = Strophe.escapeJid(jid).split("/");
559 if (s.length < 2) return null;
563 /** Function: getBareJidFromJid
564 * Get the bare JID from a JID String.
567 * (String) jid - A JID.
570 * A String containing the bare JID.
572 getBareJidFromJid: function (jid)
574 return this.escapeJid(jid).split("/")[0];
578 * User overrideable logging function.
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) {
588 * Please note that data sent and received over the wire is logged
589 * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().
591 * The different levels and their meanings are
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.
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.
606 log: function (level, msg)
612 * Log a message at the Strophe.LogLevel.DEBUG level.
615 * (String) msg - The log message.
619 this.log(this.LogLevel.DEBUG, msg);
623 * Log a message at the Strophe.LogLevel.INFO level.
626 * (String) msg - The log message.
630 this.log(this.LogLevel.INFO, msg);
634 * Log a message at the Strophe.LogLevel.WARN level.
637 * (String) msg - The log message.
641 this.log(this.LogLevel.WARN, msg);
645 * Log a message at the Strophe.LogLevel.ERROR level.
648 * (String) msg - The log message.
650 error: function (msg)
652 this.log(this.LogLevel.ERROR, msg);
656 * Log a message at the Strophe.LogLevel.FATAL level.
659 * (String) msg - The log message.
661 fatal: function (msg)
663 this.log(this.LogLevel.FATAL, msg);
666 /** Function: serialize
667 * Render a DOM element and all descendants to a String.
670 * (XMLElement) elem - A DOM element.
673 * The serialized element tree as a String.
675 serialize: function (elem)
679 if (!elem) return null;
681 var nodeName = elem.nodeName;
684 if (elem.getAttribute("_realname")) {
685 nodeName = elem.getAttribute("_realname");
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("'", "'").replace("&", "&") + "'";
697 if (elem.childNodes.length > 0) {
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) {
706 result += child.nodeValue;
709 result += "</" + nodeName + ">";
717 /** PrivateVariable: _requestId
718 * _Private_ variable that keeps track of the request ids for
724 /** Class: Strophe.Builder
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'})
735 * The above generates this XML fragment
736 * > <iq to='you' from='me' type='get' id='1'>
737 * > <query xmlns='strophe:example'>
741 * The corresponding DOM manipulations to get a similar fragment would be
742 * a lot more tedious and probably involve several helper variables.
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.
750 /** Constructor: Strophe.Builder
751 * Create a Strophe.Builder object.
753 * The attributes should be passed in object notation. For example
754 * > var b = new Builder('message', {to: 'you', from: 'me'});
756 * > var b = new Builder('messsage', {'xml:lang': 'en'});
759 * (String) name - The name of the root element.
760 * (Object) attrs - The attributes for the root element in object notation.
763 * A new Strophe.Builder.
765 Strophe.Builder = function (name, attrs)
767 // Holds the tree being built.
768 this.nodeTree = this._makeNode(name, attrs);
770 // Points to the current operation node.
771 this.node = this.nodeTree;
774 Strophe.Builder.prototype = {
776 * Return the DOM tree.
778 * This function returns the current DOM tree as an element object. This
779 * is suitable for passing to functions like Strophe.Connection.send().
782 * The DOM tree as a element object.
786 return this.nodeTree;
789 /** Function: toString
790 * Serialize the DOM tree to a String.
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.
797 * The serialized DOM tree in a String.
799 toString: function ()
801 return Strophe.serialize(this.nodeTree);
805 * Make the current parent element the new current element.
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', {});
812 * The Stophe.Builder object.
816 this.node = this.node.parentNode;
821 * Add or modify attributes of the current element.
823 * The attributes should be passed in object notation. This function
824 * does not move the current element pointer.
827 * (Object) moreattrs - The attributes to add/modify in object notation.
830 * The Strophe.Builder object.
832 attrs: function (moreattrs)
834 for (var k in moreattrs)
835 this.node.setAttribute(k, moreattrs[k]);
840 * Add a child to the current element and make it the new current
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.
848 * (String) name - The name of the child.
849 * (Object) attrs - The attributes of the child in object notation.
852 * The Strophe.Builder object.
854 c: function (name, attrs)
856 var child = this._makeNode(name, attrs);
857 this.node.appendChild(child);
863 * Add a child to the current element and make it the new current
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.
871 * (XMLElement) elem - A DOM element.
874 * The Strophe.Builder object.
876 cnode: function (elem)
878 this.node.appendChild(elem);
884 * Add a child text element.
886 * This *does not* make the child the new current element since there
887 * are no children of text elements.
890 * (String) text - The text data to append to the current element.
893 * The Strophe.Builder object.
897 var child = Strophe.xmlTextNode(text);
898 this.node.appendChild(child);
902 /** PrivateFunction: _makeNode
903 * _Private_ helper function to create a DOM element.
906 * (String) name - The name of the new element.
907 * (Object) attrs - The attributes for the new element in object
913 _makeNode: function (name, attrs)
915 var node = Strophe.xmlElement(name);
917 node.setAttribute(k, attrs[k]);
923 /** PrivateClass: Strophe.Handler
924 * _Private_ helper class for managing stanza handlers.
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.
932 * Users will not use Strophe.Handler objects directly, but instead they
933 * will use Strophe.Connection.addHandler() and
934 * Strophe.Connection.deleteHandler().
937 /** PrivateConstructor: Strophe.Handler
938 * Create and initialize a new Strophe.Handler.
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.
949 * A new Strophe.Handler object.
951 Strophe.Handler = function (handler, ns, name, type, id, from)
953 this.handler = handler;
960 // whether the handler is a user handler or a system handler
964 Strophe.Handler.prototype = {
965 /** PrivateFunction: isMatch
966 * Tests if a stanza matches the Strophe.Handler.
969 * (XMLElement) elem - The XML element to test.
972 * true if the stanza matches and false otherwise.
974 isMatch: function (elem)
983 Strophe.forEachChild(elem, null, function (elem) {
984 if (elem.getAttribute("xmlns") == self.ns)
988 nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns;
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)) {
1002 /** PrivateFunction: run
1003 * Run the callback on a matching stanza.
1006 * (XMLElement) elem - The DOM element that triggered the
1010 * A boolean indicating if the handler should remain active.
1012 run: function (elem)
1016 result = this.handler(elem);
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") {
1025 console.error(this.handler, " - error - ", e, e.message);
1027 Strophe.fatal("error: " + this.handler + " " +
1028 e.fileName + ":" + e.lineNumber + " - " +
1029 e.name + ": " + e.message);
1031 Strophe.fatal("error: " + this.handler);
1040 /** PrivateFunction: toString
1041 * Get a String representation of the Strophe.Handler object.
1046 toString: function ()
1048 return "{Handler: " + this.handler + "(" + this.name + "," +
1049 this.id + "," + this.ns + ")}";
1053 /** PrivateClass: Strophe.TimedHandler
1054 * _Private_ helper class for managing timed handlers.
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.
1061 * Users will not use Strophe.TimedHandler objects directly, but instead
1062 * they will use Strophe.Connection.addTimedHandler() and
1063 * Strophe.Connection.deleteTimedHandler().
1066 /** PrivateConstructor: Strophe.TimedHandler
1067 * Create and initialize a new Strophe.TimedHandler object.
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.
1076 * A new Strophe.TimedHandler object.
1078 Strophe.TimedHandler = function (period, handler)
1080 this.period = period;
1081 this.handler = handler;
1083 this.lastCalled = new Date().getTime();
1087 Strophe.TimedHandler.prototype = {
1088 /** PrivateFunction: run
1089 * Run the callback for the Strophe.TimedHandler.
1092 * true if the Strophe.TimedHandler should be called again, and false
1097 this.lastCalled = new Date().getTime();
1098 return this.handler();
1101 /** PrivateFunction: reset
1102 * Reset the last called time for the Strophe.TimedHandler.
1106 this.lastCalled = new Date().getTime();
1109 /** PrivateFunction: toString
1110 * Get a string representation of the Strophe.TimedHandler object.
1113 * The string representation.
1115 toString: function ()
1117 return "{TimedHandler: " + this.handler + "(" + this.period +")}";
1121 /** PrivateClass: Strophe.Request
1122 * _Private_ helper class that provides a cross implementation abstraction
1123 * for a BOSH related XMLHttpRequest.
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.
1129 /** PrivateConstructor: Strophe.Request
1130 * Create and initialize a new Strophe.Request object.
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
1140 Strophe.Request = function (data, func, rid, sends)
1142 this.id = ++Strophe._requestId;
1144 // save original function in case we need to make a new request
1146 this.origFunc = func;
1150 this.sends = sends || 0;
1153 this.age = function () {
1154 if (!this.date) return 0;
1155 var now = new Date();
1156 return (now - this.date) / 1000;
1158 this.timeDead = function () {
1159 if (!this.dead) return 0;
1160 var now = new Date();
1161 return (now - this.dead) / 1000;
1163 this.xhr = this._newXHR();
1166 Strophe.Request.prototype = {
1167 /** PrivateFunction: getResponse
1168 * Get a response from the underlying XMLHttpRequest.
1170 * This function attempts to get a response from the request and checks
1174 * "parsererror" - A parser error occured.
1177 * The DOM element tree of the response.
1179 getResponse: function ()
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";
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));
1201 /** PrivateFunction: _newXHR
1202 * _Private_ helper function to create XMLHttpRequests.
1204 * This function creates XMLHttpRequests across all implementations.
1207 * A new XMLHttpRequest.
1209 _newXHR: function ()
1212 if (window.XMLHttpRequest) {
1213 xhr = new XMLHttpRequest();
1214 if (xhr.overrideMimeType) {
1215 xhr.overrideMimeType("text/xml");
1217 } else if (window.ActiveXObject) {
1218 xhr = new ActiveXObject("Microsoft.XMLHTTP");
1221 xhr.onreadystatechange = this.func.prependArg(this);
1227 /** Class: Strophe.Connection
1228 * XMPP Connection manager.
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
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
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
1246 * To send data to the connection, use send().
1249 /** Constructor: Strophe.Connection
1250 * Create and initialize a Strophe.Connection object.
1253 * (String) service - The BOSH service URL.
1256 * A new Strophe.Connection object.
1258 Strophe.Connection = function (service)
1260 /* The path to the httpbind service. */
1261 this.service = service;
1262 /* The connected JID. */
1264 /* request id for body tags */
1265 this.rid = Math.floor(Math.random() * 4294967295);
1266 /* The current session ID. */
1268 this.streamId = null;
1271 this.do_session = false;
1272 this.do_bind = false;
1275 this.timedHandlers = [];
1277 this.removeTimeds = [];
1278 this.removeHandlers = [];
1279 this.addTimeds = [];
1280 this.addHandlers = [];
1282 this._idleTimeout = null;
1283 this._disconnectTimeout = null;
1285 this.authenticated = false;
1286 this.disconnecting = false;
1287 this.connected = false;
1291 this.paused = false;
1293 // default BOSH window
1297 this._requests = [];
1298 this._uniqueId = Math.round(Math.random() * 10000);
1300 this._sasl_success_handler = null;
1301 this._sasl_failure_handler = null;
1302 this._sasl_challenge_handler = null;
1304 // setup onIdle callback every 1/10th of a second
1305 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1308 Strophe.Connection.prototype = {
1310 * Reset the connection.
1312 * This function should be called after a connection is disconnected
1313 * before that connection is reused.
1317 this.rid = Math.floor(Math.random() * 4294967295);
1320 this.streamId = null;
1323 this.do_session = false;
1324 this.do_bind = false;
1327 this.timedHandlers = [];
1329 this.removeTimeds = [];
1330 this.removeHandlers = [];
1331 this.addTimeds = [];
1332 this.addHandlers = [];
1334 this.authenticated = false;
1335 this.disconnecting = false;
1336 this.connected = false;
1340 this._requests = [];
1341 this._uniqueId = Math.round(Math.random()*10000);
1345 * Pause the request manager.
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.
1357 /** Function: resume
1358 * Resume the request manager.
1360 * This resumes after pause() has been called.
1364 this.paused = false;
1367 /** Function: getUniqueId
1368 * Generate a unique ID for use in <iq/> elements.
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.
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.
1384 * (String) suffix - A optional suffix to append to the id.
1387 * A unique string to be used for the id attribute.
1389 getUniqueId: function (suffix)
1391 if (typeof(suffix) == "string" || typeof(suffix) == "number") {
1392 return ++this._uniqueId + ":" + suffix;
1394 return ++this._uniqueId + "";
1398 /** Function: connect
1399 * Starts the connection process.
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.
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'.
1409 * Please see XEP 124 for a more detailed explanation of the optional
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.
1428 connect: function (jid, pass, callback, wait, hold, wind)
1432 this.connect_callback = callback;
1433 this.disconnecting = false;
1434 this.connected = false;
1435 this.authenticated = false;
1438 if (!wait) wait = 60;
1439 if (!hold) hold = 1;
1440 if (wind) this.window = wind;
1442 // parse jid for domain and resource
1443 this.domain = Strophe.getDomainFromJid(this.jid);
1445 // build the body tag
1446 var body = this._buildBody().attrs({
1451 window: this.window,
1452 content: "text/xml; charset=utf-8",
1454 "xmpp:version": "1.0",
1455 "xmlns:xmpp": Strophe.NS.BOSH
1458 this.connect_callback(Strophe.Status.CONNECTING, null);
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();
1468 /** Function: attach
1469 * Attach to an already created and authenticated BOSH session.
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.
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.
1483 attach: function (jid, sid, rid, callback)
1488 this.connect_callback = callback;
1490 this.domain = Strophe.getDomainFromJid(this.jid);
1492 this.authenticated = true;
1493 this.connected = true;
1496 /** Function: rawInput
1497 * User overrideable function that receives raw data coming into the
1500 * The default function does nothing. User code can override this with
1501 * > Strophe.Connection.rawInput = function (data) {
1506 * (String) data - The data received by the connection.
1508 rawInput: function (data)
1513 /** Function: rawOutput
1514 * User overrideable function that receives raw data sent to the
1517 * The default function does nothing. User code can override this with
1518 * > Strophe.Connection.rawOutput = function (data) {
1523 * (String) data - The data sent by the connection.
1525 rawOutput: function (data)
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.
1538 * (XMLElement) elem - The stanza to send.
1540 send: function (elem)
1542 if (elem !== null && typeof(elem["sort"]) == "function") {
1543 for (var i = 0; i < elem.length; i++) {
1544 this._data.push(elem[i]);
1547 this._data.push(elem);
1550 this._throttledRequestHandler();
1551 clearTimeout(this._idleTimeout);
1552 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1555 /** PrivateFunction: _sendRestart
1556 * Send an xmpp:restart stanza.
1558 _sendRestart: function ()
1560 this._data.push("restart");
1562 this._throttledRequestHandler();
1563 clearTimeout(this._idleTimeout);
1564 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1567 /** Function: addTimedHandler
1568 * Add a timed handler to the connection.
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.
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().
1579 * Note that user handlers are not active until authentication is
1583 * (Integer) period - The period of the handler.
1584 * (Function) handler - The callback function.
1587 * A reference to the handler that can be used to remove it.
1589 addTimedHandler: function (period, handler)
1591 var thand = new Strophe.TimedHandler(period, handler);
1592 this.addTimeds.push(thand);
1596 /** Function: deleteTimedHandler
1597 * Delete a timed handler for a connection.
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().
1604 * (Strophe.TimedHandler) handRef - The handler reference.
1606 deleteTimedHandler: function (handRef)
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);
1613 /** Function: addHandler
1614 * Add a stanza handler for the connection.
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.
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.
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.
1629 * The return value should be saved if you wish to remove the handler
1630 * with deleteHandler().
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.
1641 * A reference to the handler that can be used to remove it.
1643 addHandler: function (handler, ns, name, type, id, from)
1645 var hand = new Strophe.Handler(handler, ns, name, type, id, from);
1646 this.addHandlers.push(hand);
1650 /** Function: deleteHandler
1651 * Delete a stanza handler for a connection.
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().
1658 * (Strophe.Handler) handRef - The handler reference.
1660 deleteHandler: function (handRef)
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);
1667 /** Function: disconnect
1668 * Start the graceful disconnection process.
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.
1675 * The user supplied connection callback will be notified of the
1676 * progress as this process happens.
1678 disconnect: function ()
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();
1689 /** PrivateFunction: _buildBody
1690 * _Private_ helper function to generate the <body/> wrapper for BOSH.
1693 * A Strophe.Builder with a <body/> element.
1695 _buildBody: function ()
1697 var bodyWrap = $build('body', {
1699 xmlns: Strophe.NS.HTTPBIND
1702 if (this.sid !== null) {
1703 bodyWrap.attrs({sid: this.sid});
1709 /** PrivateFunction: _removeRequest
1710 * _Private_ function to remove a request from the queue.
1713 * (Strophe.Request) req - The request to remove.
1715 _removeRequest: function (req)
1717 Strophe.debug("removing request");
1720 for (i = this._requests.length - 1; i >= 0; i--) {
1721 if (req == this._requests[i]) {
1722 this._requests.splice(i, 1);
1726 // set the onreadystatechange handler to a null function so
1727 // that we don't get any misfires
1728 req.xhr.onreadystatechange = function () {};
1730 this._throttledRequestHandler();
1733 /** PrivateFunction: _restartRequest
1734 * _Private_ function to restart a request that is presumed dead.
1737 * (Integer) i - The index of the request in the queue.
1739 _restartRequest: function (i)
1741 var req = this._requests[i];
1742 if (req.dead === null) {
1743 req.dead = new Date();
1746 this._processRequest(i);
1749 /** PrivateFunction: _processRequest
1750 * _Private_ function to process a request in the queue.
1752 * This function takes requests off the queue and sends them and
1753 * restarts dead requests.
1756 * (Integer) i - The index of the request in the queue.
1758 _processRequest: function (i)
1760 var req = this._requests[i];
1764 if (req.xhr.readyState == 4) {
1765 reqStatus = req.xhr.status;
1768 Strophe.error("caught an error in _requests[" + i +
1769 "], reqStatus: " + reqStatus);
1772 if (typeof(reqStatus) == "undefined") {
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 &&
1787 if (primaryTimeout || secondaryTimeout ||
1788 requestCompletedWithServerError) {
1789 if (secondaryTimeout) {
1790 Strophe.error("Request " +
1791 this._requests[i].id +
1792 " timed out (secondary), restarting");
1797 this._requests[i] = new Strophe.Request(req.data,
1801 req = this._requests[i];
1804 if (req.xhr.readyState === 0) {
1805 Strophe.debug("request id " + req.id +
1806 "." + req.sends + " posting");
1808 req.date = new Date();
1810 req.xhr.open("POST", this.service, true);
1812 Strophe.error("XHR open failed.");
1813 if (!this.connected)
1814 this.connect_callback(Strophe.Status.CONNFAIL,
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);
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);
1839 this.rawOutput(req.data);
1841 Strophe.debug("_throttledRequestHandler: " +
1842 (i === 0 ? "first" : "second") +
1843 " request has readyState of " +
1844 req.xhr.readyState);
1848 /** PrivateFunction: _throttledRequestHandler
1849 * _Private_ function to throttle requests to the connection window.
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
1855 _throttledRequestHandler: function ()
1857 if (!this._requests) {
1858 Strophe.debug("_throttledRequestHandler called with " +
1859 "undefined requests");
1861 Strophe.debug("_throttledRequestHandler called with " +
1862 this._requests.length + " requests");
1865 if (!this._requests || this._requests.length === 0) {
1869 if (this._requests.length > 0) {
1870 this._processRequest(0);
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);
1880 /** PrivateFunction: _onRequestStateChange
1881 * _Private_ handler for Strophe.Request state changes.
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
1889 * (Function) func - The handler for the request.
1890 * (Strophe.Request) req - The request that is changing readyState.
1892 _onRequestStateChange: function (func, req)
1894 Strophe.debug("request id " + req.id +
1895 "." + req.sends + " state changed to " +
1896 req.xhr.readyState);
1905 if (req.xhr.readyState == 4) {
1908 reqStatus = req.xhr.status;
1910 // ignore errors from undefined status attribute. works
1911 // around a browser bug
1914 if (typeof(reqStatus) == "undefined") {
1918 if (this.disconnecting) {
1919 if (reqStatus >= 400) {
1920 this._hitError(reqStatus);
1925 var reqIs0 = (this._requests[0] == req);
1926 var reqIs1 = (this._requests[1] == req);
1928 if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
1929 // remove from internal queue
1930 this._removeRequest(req);
1931 Strophe.debug("request id " +
1933 " should now be removed");
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
1943 (reqIs0 && this._requests.length > 0 &&
1944 this._requests[0].age() > Strophe.SECONDARY_TIMEOUT)) {
1945 this._restartRequest(0);
1948 Strophe.debug("request id " +
1950 req.sends + " got 200");
1954 Strophe.error("request id " +
1956 req.sends + " error " + reqStatus +
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,
1965 this._doDisconnect();
1970 if (!((reqStatus > 0 && reqStatus < 10000) ||
1972 this._throttledRequestHandler();
1977 /** PrivateFunction: _hitError
1978 * _Private_ function to handle the error count.
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.
1985 * (Integer) reqStatus - The request status.
1987 _hitError: function (reqStatus)
1990 Strophe.warn("request errored, status: " + reqStatus +
1991 ", number of errors: " + this.errors);
1992 if (this.errors > 4) {
1993 this._onDisconnectTimeout();
1997 /** PrivateFunction: _doDisconnect
1998 * _Private_ function to disconnect.
2000 * This is the last piece of the disconnection logic. This resets the
2001 * connection and alerts the user's connection callback.
2003 _doDisconnect: function ()
2005 Strophe.info("_doDisconnect was called");
2006 this.authenticated = false;
2007 this.disconnecting = false;
2009 this.streamId = null;
2010 this.rid = Math.floor(Math.random() * 4294967295);
2012 // tell the parent we disconnected
2013 if (this.connected) {
2014 this.connect_callback(Strophe.Status.DISCONNECTED, null);
2015 this.connected = false;
2020 this.timedHandlers = [];
2021 this.removeTimeds = [];
2022 this.removeHandlers = [];
2023 this.addTimeds = [];
2024 this.addHandlers = [];
2027 /** PrivateFunction: _dataRecv
2028 * _Private_ handler to processes incoming data from the the connection.
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
2036 * (Strophe.Request) req - The request that has data ready.
2038 _dataRecv: function (req)
2041 var elem = req.getResponse();
2043 if (e != "parsererror") throw e;
2045 this.connect_callback(Strophe.Status.DISCONNECTING,
2046 "strophe-parsererror");
2049 if (elem === null) return;
2051 // handle graceful disconnect
2052 if (this.disconnecting && this._requests.length == 0) {
2053 this.deleteTimedHandler(this._disconnectTimeout);
2054 this._disconnectTimeout = null;
2055 this._doDisconnect();
2058 this.rawInput(Strophe.serialize(elem));
2060 var typ = elem.getAttribute("type");
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) {
2070 this.connect_callback(Strophe.Status.CONNFAIL, cond);
2072 this.connect_callback(Strophe.Status.CONNFAIL, "unknown");
2074 this.connect_callback(Strophe.Status.DISCONNECTING, null);
2079 // remove handlers scheduled for deletion
2081 while (this.removeHandlers.length > 0) {
2082 hand = this.removeHandlers.pop();
2083 i = this.handlers.indexOf(hand);
2085 this.handlers.splice(i, 1);
2088 // add handlers scheduled for addition
2089 while (this.addHandlers.length > 0) {
2090 this.handlers.push(this.addHandlers.pop());
2093 // send each incoming stanza through the handler chain
2095 Strophe.forEachChild(elem, null, function (child) {
2098 newList = 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);
2108 self.handlers.push(hand);
2114 /** PrivateFunction: _sendTerminate
2115 * _Private_ function to send initial disconnect sequence.
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.
2121 _sendTerminate: function ()
2123 Strophe.info("_sendTerminate was called");
2124 var body = this._buildBody().attrs({type: "terminate"});
2127 if (this.authenticated) {
2128 body.c('presence', {
2129 xmlns: Strophe.NS.CLIENT,
2134 this.disconnecting = true;
2136 var req = new Strophe.Request(body.toString(),
2137 this._onRequestStateChange.bind(this)
2138 .prependArg(this._dataRecv.bind(this)),
2139 body.tree().getAttribute("rid"));
2141 // abort and clear all waiting requests
2143 while (this._requests.length > 0) {
2144 r = this._requests.pop();
2149 this._requests.push(req);
2150 this._throttledRequestHandler();
2153 /** PrivateFunction: _connect_cb
2154 * _Private_ handler for initial connection request.
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.
2160 * SASL authentication will be attempted if available, otherwise
2161 * the code will fall back to legacy authentication.
2164 * (Strophe.Request) req - The current request.
2166 _connect_cb: function (req)
2168 Strophe.info("_connect_cb was called");
2170 this.connected = true;
2171 var bodyWrap = req.getResponse();
2172 if (!bodyWrap) return;
2174 this.rawInput(Strophe.serialize(bodyWrap));
2176 var typ = bodyWrap.getAttribute("type");
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) {
2186 this.connect_callback(Strophe.Status.CONNFAIL, cond);
2188 this.connect_callback(Strophe.Status.CONNFAIL, "unknown");
2193 this.sid = bodyWrap.getAttribute("sid");
2194 this.stream_id = bodyWrap.getAttribute("authid");
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;
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;
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);
2226 this.send($build("auth", {
2227 xmlns: Strophe.NS.SASL,
2228 mechanism: "ANONYMOUS"
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);
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);
2244 this.send($build("auth", {
2245 xmlns: Strophe.NS.SASL,
2246 mechanism: "DIGEST-MD5"
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;
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);
2266 hashed_auth_str = encode64(auth_str);
2267 this.send($build("auth", {
2268 xmlns: Strophe.NS.SASL,
2270 }).t(hashed_auth_str).tree());
2272 this.connect_callback(Strophe.Status.AUTHENTICATING, null);
2273 this._addSysHandler(this._auth1_cb.bind(this), null, null,
2281 xmlns: Strophe.NS.AUTH
2282 }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
2286 /** PrivateFunction: _sasl_challenge1_cb
2287 * _Private_ handler for DIGEST-MD5 SASL authentication.
2290 * (XMLElement) elem - The challenge stanza.
2293 * false to remove the handler.
2295 _sasl_challenge1_cb: function (elem)
2297 var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
2299 var challenge = decode64(Strophe.getText(elem));
2300 var cnonce = hex_md5(Math.random() * 1234567890);
2307 // remove unneeded handlers
2308 this.deleteHandler(this._sasl_failure_handler);
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]) {
2330 var digest_uri = "xmpp/" + realm;
2331 if (host !== null) {
2332 digest_uri = digest_uri + "/" + host;
2335 var A1 = str_md5(Strophe.getNodeFromJid(this.jid) +
2336 ":" + realm + ":" + this.pass) +
2337 ":" + nonce + ":" + cnonce;
2338 var A2 = 'AUTHENTICATE:' + digest_uri;
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:" +
2352 hex_md5(A2)) + '",';
2353 responseText += 'charset="utf-8"';
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);
2365 this.send($build('response', {
2366 xmlns: Strophe.NS.SASL
2367 }).t(encode64(responseText)).tree());
2372 /** PrivateFunction: _sasl_challenge2_cb
2373 * _Private_ handler for second step of DIGEST-MD5 SASL authentication.
2376 * (XMLElement) elem - The challenge stanza.
2379 * false to remove the handler.
2381 _sasl_challenge2_cb: function (elem)
2383 // remove unneeded handlers
2384 this.deleteHandler(this._sasl_success_handler);
2385 this.deleteHandler(this._sasl_failure_handler);
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());
2397 /** PrivateFunction: _auth1_cb
2398 * _Private_ handler for legacy authentication.
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
2406 * (XMLElement) elem - The stanza that triggered the callback.
2409 * false to remove the handler.
2411 _auth1_cb: function (elem)
2413 var use_digest = false;
2414 var check_query, check_digest;
2416 if (elem.getAttribute("type") == "result") {
2418 check_query = elem.childNodes[0];
2420 check_digest = check_query.getElementsByTagName("digest")[0];
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));
2432 iq.up().c("digest", {})
2433 .t(hex_sha1(this.stream_id + this.pass));
2435 iq.up().c('password', {}).t(this.pass);
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';
2443 iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid));
2445 this._addSysHandler(this._auth2_cb.bind(this), null,
2446 null, null, "_auth_2");
2448 this.send(iq.tree());
2453 /** PrivateFunction: _sasl_success_cb
2454 * _Private_ handler for succesful SASL authentication.
2457 * (XMLElement) elem - The matching stanza.
2460 * false to remove the handler.
2462 _sasl_success_cb: function (elem)
2464 Strophe.info("SASL authentication succeeded.");
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;
2474 this._addSysHandler(this._sasl_auth1_cb.bind(this), null,
2475 "stream:features", null, null);
2477 // we must send an xmpp:restart now
2478 this._sendRestart();
2483 /** PrivateFunction: _sasl_auth1_cb
2484 * _Private_ handler to start stream binding.
2487 * (XMLElement) elem - The matching stanza.
2490 * false to remove the handler.
2492 _sasl_auth1_cb: function (elem)
2496 for (i = 0; i < elem.childNodes.length; i++) {
2497 child = elem.childNodes[i];
2498 if (child.nodeName == 'bind') {
2499 this.do_bind = true;
2502 if (child.nodeName == 'session') {
2503 this.do_session = true;
2507 if (!this.do_bind) {
2508 this.connect_callback(Strophe.Status.AUTHFAIL, null);
2511 this._addSysHandler(this._sasl_bind_cb.bind(this), null, null,
2512 null, "_bind_auth_2");
2514 var resource = Strophe.getResourceFromJid(this.jid);
2516 this.send($iq({type: "set", id: "_bind_auth_2"})
2517 .c('bind', {xmlns: Strophe.NS.BIND})
2518 .c('resource', {}).t(resource).tree());
2520 this.send($iq({type: "set", id: "_bind_auth_2"})
2521 .c('bind', {xmlns: Strophe.NS.BIND})
2528 /** PrivateFunction: _sasl_bind_cb
2529 * _Private_ handler for binding result and session start.
2532 * (XMLElement) elem - The matching stanza.
2535 * false to remove the handler.
2537 _sasl_bind_cb: function (elem)
2539 if (elem.getAttribute("type") == "error") {
2540 Strophe.info("SASL binding failed.");
2541 this.connect_callback(Strophe.Status.AUTHFAIL, null);
2545 // TODO - need to grab errors
2546 var bind = elem.getElementsByTagName("bind");
2548 if (bind.length > 0) {
2550 jidNode = bind[0].getElementsByTagName("jid");
2551 if (jidNode.length > 0) {
2552 this.jid = Strophe.getText(jidNode[0]);
2554 if (this.do_session) {
2555 this._addSysHandler(this._sasl_session_cb.bind(this),
2556 null, null, null, "_session_auth_2");
2558 this.send($iq({type: "set", id: "_session_auth_2"})
2559 .c('session', {xmlns: Strophe.NS.SESSION})
2564 Strophe.info("SASL binding failed.");
2565 this.connect_callback(Strophe.Status.AUTHFAIL, null);
2570 /** PrivateFunction: _sasl_session_cb
2571 * _Private_ handler to finish successful SASL connection.
2573 * This sets Connection.authenticated to true on success, which
2574 * starts the processing of user handlers.
2577 * (XMLElement) elem - The matching stanza.
2580 * false to remove the handler.
2582 _sasl_session_cb: function (elem)
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);
2596 /** PrivateFunction: _sasl_failure_cb
2597 * _Private_ handler for SASL authentication failure.
2600 * (XMLElement) elem - The matching stanza.
2603 * false to remove the handler.
2605 _sasl_failure_cb: function (elem)
2607 // delete unneeded handlers
2608 if (this._sasl_success_handler) {
2609 this.deleteHandler(this._sasl_success_handler);
2610 this._sasl_success_handler = null;
2612 if (this._sasl_challenge_handler) {
2613 this.deleteHandler(this._sasl_challenge_handler);
2614 this._sasl_challenge_handler = null;
2617 this.connect_callback(Strophe.Status.AUTHFAIL, null);
2621 /** PrivateFunction: _auth2_cb
2622 * _Private_ handler to finish legacy authentication.
2624 * This handler is called when the result from the jabber:iq:auth
2625 * <iq/> stanza is returned.
2628 * (XMLElement) elem - The stanza that triggered the callback.
2631 * false to remove the handler.
2633 _auth2_cb: function (elem)
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);
2646 /** PrivateFunction: _addSysTimedHandler
2647 * _Private_ function to add a system level timed handler.
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.
2654 * (Integer) period - The period of the handler.
2655 * (Function) handler - The callback function.
2657 _addSysTimedHandler: function (period, handler)
2659 var thand = new Strophe.TimedHandler(period, handler);
2661 this.addTimeds.push(thand);
2665 /** PrivateFunction: _addSysHandler
2666 * _Private_ function to add a system level stanza handler.
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.
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.
2679 _addSysHandler: function (handler, ns, name, type, id)
2681 var hand = new Strophe.Handler(handler, ns, name, type, id);
2683 this.addHandlers.push(hand);
2687 /** PrivateFunction: _onDisconnectTimeout
2688 * _Private_ timeout handler for handling non-graceful disconnection.
2690 * If the graceful disconnect process does not complete within the
2691 * time allotted, this handler finishes the disconnect anyway.
2694 * false to remove the handler.
2696 _onDisconnectTimeout: function ()
2698 Strophe.info("_onDisconnectTimeout was called");
2700 // cancel all remaining requests and clear the queue
2702 while (this._requests.length > 0) {
2703 req = this._requests.pop();
2708 // actually disconnect
2709 this._doDisconnect();
2714 /** PrivateFunction: _onIdle
2715 * _Private_ handler to process events during idle cycle.
2717 * This handler is called every 100ms to fire timed handlers that
2718 * are ready and keep poll requests going.
2720 _onIdle: function ()
2722 var i, thand, since, newList;
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);
2729 this.timedHandlers.splice(i, 1);
2732 // add timed handlers scheduled for addition
2733 while (this.addTimeds.length > 0) {
2734 this.timedHandlers.push(this.addTimeds.pop());
2737 // call ready timed handlers
2738 var now = new Date().getTime();
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) {
2746 newList.push(thand);
2749 newList.push(thand);
2753 this.timedHandlers = newList;
2755 var body, time_elapsed;
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 " +
2764 if (this._requests.length < 2 && this._data.length > 0 &&
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") {
2773 "xmpp:restart": "true",
2774 "xmlns:xmpp": Strophe.NS.BOSH
2777 body.cnode(this._data[i]).up();
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);
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();
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();
2810 // reactivate the timer
2811 clearTimeout(this._idleTimeout);
2812 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);