/www/js/strophe.js

https://github.com/southwolf/sample-chat-xmpp-phonegap · JavaScript · 3623 lines · 1939 code · 328 blank · 1356 comment · 329 complexity · 3be70f8cbc74c5f032e57fbfadbabc0c MD5 · raw file

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