Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

3257 рядки
104 KiB

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