Openfire Strophe开发中文乱码问题
网站上有很多Openfire Web方案,之前想用Smack 但是jar包支持客户端版本的,还有JDK版本问题 一直没调试成功 估计成功的方法只能拜读源码进行修改了。
SparkWeb 官网代码很久没维护 CSDN上下来个版本但jar包路径不对 花了不少时间总算能跑起来,不过版本是flex3版本,太老了 自己花精力升级有点费时间呀
最后采用存脚本开发Strophejs,下面网站写的很详细
学习的网站:http://www.dotblogs.com.tw/sungnoone/archive/2014/06/20/145642.aspx
Strophejs中文发送到服务器端老会出现乱码问题,这个问题网上也没好的解决方案,在这里我分享下我的方法。
1、修改Strophe.js
添加chencode中文编码,cht IQ请求节点中文内容,添加utf16to8方法
1 /** File: strophe.js 2 * A JavaScript library for XMPP BOSH/XMPP over Websocket. 3 * 4 * This is the JavaScript version of the Strophe library. Since JavaScript 5 * had no facilities for persistent TCP connections, this library uses 6 * Bidirectional-streams Over Synchronous HTTP (BOSH) to emulate 7 * a persistent, stateful, two-way connection to an XMPP server. More 8 * information on BOSH can be found in XEP 124. 9 * 10 * This version of Strophe also works with WebSockets. 11 * For more information on XMPP-over WebSocket see this RFC: 12 * http://tools.ietf.org/html/rfc7395 13 */ 14 15 /* All of the Strophe globals are defined in this special function below so 16 * that references to the globals become closures. This will ensure that 17 * on page reload, these references will still be available to callbacks 18 * that are still executing. 19 */ 20 21 /* jshint ignore:start */ 22 (function (callback) { 23 /* jshint ignore:end */ 24 25 // This code was written by Tyler Akins and has been placed in the 26 // public domain. It would be nice if you left this header intact. 27 // Base64 code from Tyler Akins -- http://rumkin.com 28 29 (function (root, factory) { 30 if (typeof define === 'function' && define.amd) { 31 define('strophe-base64', function () { 32 return factory(); 33 }); 34 } else { 35 // Browser globals 36 root.Base64 = factory(); 37 } 38 }(this, function () { 39 var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 40 41 var obj = { 42 /** 43 * Encodes a string in base64 44 * @param {String} input The string to encode in base64. 45 */ 46 encode: function (input) { 47 var output = ""; 48 var chr1, chr2, chr3; 49 var enc1, enc2, enc3, enc4; 50 var i = 0; 51 52 do { 53 chr1 = input.charCodeAt(i++); 54 chr2 = input.charCodeAt(i++); 55 chr3 = input.charCodeAt(i++); 56 57 enc1 = chr1 >> 2; 58 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 59 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 60 enc4 = chr3 & 63; 61 62 if (isNaN(chr2)) { 63 enc2 = ((chr1 & 3) << 4); 64 enc3 = enc4 = 64; 65 } else if (isNaN(chr3)) { 66 enc4 = 64; 67 } 68 69 output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + 70 keyStr.charAt(enc3) + keyStr.charAt(enc4); 71 } while (i < input.length); 72 73 return output; 74 }, 75 chencode:function(input) { 76 input = utf16to8(input); 77 var output = ""; 78 var chr1, chr2, chr3; 79 var enc1, enc2, enc3, enc4; 80 var i = 0; 81 do { 82 chr1 = input.charCodeAt(i++); 83 chr2 = input.charCodeAt(i++); 84 chr3 = input.charCodeAt(i++); 85 86 enc1 = chr1 >> 2; 87 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 88 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 89 enc4 = chr3 & 63; 90 91 if (isNaN(chr2)) { 92 enc2 = ((chr1 & 3) << 4); 93 enc3 = enc4 = 64; 94 } else if (isNaN(chr3)) { 95 enc4 = 64; 96 } 97 98 output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + 99 keyStr.charAt(enc3) + keyStr.charAt(enc4); 100 } while (i < input.length); 101 return output; 102 }, 103 /** 104 * Decodes a base64 string. 105 * @param {String} input The string to decode. 106 */ 107 decode: function (input) { 108 var output = ""; 109 var chr1, chr2, chr3; 110 var enc1, enc2, enc3, enc4; 111 var i = 0; 112 113 // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 114 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 115 116 do { 117 enc1 = keyStr.indexOf(input.charAt(i++)); 118 enc2 = keyStr.indexOf(input.charAt(i++)); 119 enc3 = keyStr.indexOf(input.charAt(i++)); 120 enc4 = keyStr.indexOf(input.charAt(i++)); 121 122 chr1 = (enc1 << 2) | (enc2 >> 4); 123 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 124 chr3 = ((enc3 & 3) << 6) | enc4; 125 126 output = output + String.fromCharCode(chr1); 127 128 if (enc3 != 64) { 129 output = output + String.fromCharCode(chr2); 130 } 131 if (enc4 != 64) { 132 output = output + String.fromCharCode(chr3); 133 } 134 } while (i < input.length); 135 136 return output; 137 } 138 }; 139 return obj; 140 })); 141 142 /* 143 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined 144 * in FIPS PUB 180-1 145 * Version 2.1a Copyright Paul Johnston 2000 - 2002. 146 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 147 * Distributed under the BSD License 148 * See http://pajhome.org.uk/crypt/md5 for details. 149 */ 150 151 /* jshint undef: true, unused: true:, noarg: true, latedef: true */ 152 /* global define */ 153 154 /* Some functions and variables have been stripped for use with Strophe */ 155 156 (function (root, factory) { 157 if (typeof define === 'function' && define.amd) { 158 define('strophe-sha1', function () { 159 return factory(); 160 }); 161 } else { 162 // Browser globals 163 root.SHA1 = factory(); 164 } 165 }(this, function () { 166 167 /* 168 * Calculate the SHA-1 of an array of big-endian words, and a bit length 169 */ 170 function core_sha1(x, len) 171 { 172 /* append padding */ 173 x[len >> 5] |= 0x80 << (24 - len % 32); 174 x[((len + 64 >> 9) << 4) + 15] = len; 175 176 var w = new Array(80); 177 var a = 1732584193; 178 var b = -271733879; 179 var c = -1732584194; 180 var d = 271733878; 181 var e = -1009589776; 182 183 var i, j, t, olda, oldb, oldc, oldd, olde; 184 for (i = 0; i < x.length; i += 16) 185 { 186 olda = a; 187 oldb = b; 188 oldc = c; 189 oldd = d; 190 olde = e; 191 192 for (j = 0; j < 80; j++) 193 { 194 if (j < 16) { w[j] = x[i + j]; } 195 else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); } 196 t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), 197 safe_add(safe_add(e, w[j]), sha1_kt(j))); 198 e = d; 199 d = c; 200 c = rol(b, 30); 201 b = a; 202 a = t; 203 } 204 205 a = safe_add(a, olda); 206 b = safe_add(b, oldb); 207 c = safe_add(c, oldc); 208 d = safe_add(d, oldd); 209 e = safe_add(e, olde); 210 } 211 return [a, b, c, d, e]; 212 } 213 214 /* 215 * Perform the appropriate triplet combination function for the current 216 * iteration 217 */ 218 function sha1_ft(t, b, c, d) 219 { 220 if (t < 20) { return (b & c) | ((~b) & d); } 221 if (t < 40) { return b ^ c ^ d; } 222 if (t < 60) { return (b & c) | (b & d) | (c & d); } 223 return b ^ c ^ d; 224 } 225 226 /* 227 * Determine the appropriate additive constant for the current iteration 228 */ 229 function sha1_kt(t) 230 { 231 return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : 232 (t < 60) ? -1894007588 : -899497514; 233 } 234 235 /* 236 * Calculate the HMAC-SHA1 of a key and some data 237 */ 238 function core_hmac_sha1(key, data) 239 { 240 var bkey = str2binb(key); 241 if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * 8); } 242 243 var ipad = new Array(16), opad = new Array(16); 244 for (var i = 0; i < 16; i++) 245 { 246 ipad[i] = bkey[i] ^ 0x36363636; 247 opad[i] = bkey[i] ^ 0x5C5C5C5C; 248 } 249 250 var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8); 251 return core_sha1(opad.concat(hash), 512 + 160); 252 } 253 254 /* 255 * Add integers, wrapping at 2^32. This uses 16-bit operations internally 256 * to work around bugs in some JS interpreters. 257 */ 258 function safe_add(x, y) 259 { 260 var lsw = (x & 0xFFFF) + (y & 0xFFFF); 261 var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 262 return (msw << 16) | (lsw & 0xFFFF); 263 } 264 265 /* 266 * Bitwise rotate a 32-bit number to the left. 267 */ 268 function rol(num, cnt) 269 { 270 return (num << cnt) | (num >>> (32 - cnt)); 271 } 272 273 /* 274 * Convert an 8-bit or 16-bit string to an array of big-endian words 275 * In 8-bit function, characters >255 have their hi-byte silently ignored. 276 */ 277 function str2binb(str) 278 { 279 var bin = []; 280 var mask = 255; 281 for (var i = 0; i < str.length * 8; i += 8) 282 { 283 bin[i>>5] |= (str.charCodeAt(i / 8) & mask) << (24 - i%32); 284 } 285 return bin; 286 } 287 288 /* 289 * Convert an array of big-endian words to a string 290 */ 291 function binb2str(bin) 292 { 293 var str = ""; 294 var mask = 255; 295 for (var i = 0; i < bin.length * 32; i += 8) 296 { 297 str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask); 298 } 299 return str; 300 } 301 302 /* 303 * Convert an array of big-endian words to a base-64 string 304 */ 305 function binb2b64(binarray) 306 { 307 var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 308 var str = ""; 309 var triplet, j; 310 for (var i = 0; i < binarray.length * 4; i += 3) 311 { 312 triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) | 313 (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) | 314 ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); 315 for (j = 0; j < 4; j++) 316 { 317 if (i * 8 + j * 6 > binarray.length * 32) { str += "="; } 318 else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } 319 } 320 } 321 return str; 322 } 323 324 /* 325 * These are the functions you'll usually want to call 326 * They take string arguments and return either hex or base-64 encoded strings 327 */ 328 return { 329 b64_hmac_sha1: function (key, data){ return binb2b64(core_hmac_sha1(key, data)); }, 330 b64_sha1: function (s) { return binb2b64(core_sha1(str2binb(s),s.length * 8)); }, 331 binb2str: binb2str, 332 core_hmac_sha1: core_hmac_sha1, 333 str_hmac_sha1: function (key, data){ return binb2str(core_hmac_sha1(key, data)); }, 334 str_sha1: function (s) { return binb2str(core_sha1(str2binb(s),s.length * 8)); }, 335 }; 336 })); 337 338 /* 339 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 340 * Digest Algorithm, as defined in RFC 1321. 341 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. 342 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 343 * Distributed under the BSD License 344 * See http://pajhome.org.uk/crypt/md5 for more info. 345 */ 346 347 /* 348 * Everything that isn't used by Strophe has been stripped here! 349 */ 350 351 (function (root, factory) { 352 if (typeof define === 'function' && define.amd) { 353 define('strophe-md5', function () { 354 return factory(); 355 }); 356 } else { 357 // Browser globals 358 root.MD5 = factory(); 359 } 360 }(this, function (b) { 361 /* 362 * Add integers, wrapping at 2^32. This uses 16-bit operations internally 363 * to work around bugs in some JS interpreters. 364 */ 365 var safe_add = function (x, y) { 366 var lsw = (x & 0xFFFF) + (y & 0xFFFF); 367 var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 368 return (msw << 16) | (lsw & 0xFFFF); 369 }; 370 371 /* 372 * Bitwise rotate a 32-bit number to the left. 373 */ 374 var bit_rol = function (num, cnt) { 375 return (num << cnt) | (num >>> (32 - cnt)); 376 }; 377 378 /* 379 * Convert a string to an array of little-endian words 380 */ 381 var str2binl = function (str) { 382 var bin = []; 383 for(var i = 0; i < str.length * 8; i += 8) 384 { 385 bin[i>>5] |= (str.charCodeAt(i / 8) & 255) << (i%32); 386 } 387 return bin; 388 }; 389 390 /* 391 * Convert an array of little-endian words to a string 392 */ 393 var binl2str = function (bin) { 394 var str = ""; 395 for(var i = 0; i < bin.length * 32; i += 8) 396 { 397 str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & 255); 398 } 399 return str; 400 }; 401 402 /* 403 * Convert an array of little-endian words to a hex string. 404 */ 405 var binl2hex = function (binarray) { 406 var hex_tab = "0123456789abcdef"; 407 var str = ""; 408 for(var i = 0; i < binarray.length * 4; i++) 409 { 410 str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + 411 hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); 412 } 413 return str; 414 }; 415 416 /* 417 * These functions implement the four basic operations the algorithm uses. 418 */ 419 var md5_cmn = function (q, a, b, x, s, t) { 420 return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b); 421 }; 422 423 var md5_ff = function (a, b, c, d, x, s, t) { 424 return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 425 }; 426 427 var md5_gg = function (a, b, c, d, x, s, t) { 428 return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 429 }; 430 431 var md5_hh = function (a, b, c, d, x, s, t) { 432 return md5_cmn(b ^ c ^ d, a, b, x, s, t); 433 }; 434 435 var md5_ii = function (a, b, c, d, x, s, t) { 436 return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 437 }; 438 439 /* 440 * Calculate the MD5 of an array of little-endian words, and a bit length 441 */ 442 var core_md5 = function (x, len) { 443 /* append padding */ 444 x[len >> 5] |= 0x80 << ((len) % 32); 445 x[(((len + 64) >>> 9) << 4) + 14] = len; 446 447 var a = 1732584193; 448 var b = -271733879; 449 var c = -1732584194; 450 var d = 271733878; 451 452 var olda, oldb, oldc, oldd; 453 for (var i = 0; i < x.length; i += 16) 454 { 455 olda = a; 456 oldb = b; 457 oldc = c; 458 oldd = d; 459 460 a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); 461 d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); 462 c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); 463 b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); 464 a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); 465 d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); 466 c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); 467 b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); 468 a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); 469 d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); 470 c = md5_ff(c, d, a, b, x[i+10], 17, -42063); 471 b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); 472 a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); 473 d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); 474 c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); 475 b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); 476 477 a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); 478 d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); 479 c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); 480 b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); 481 a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); 482 d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); 483 c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); 484 b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); 485 a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); 486 d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); 487 c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); 488 b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); 489 a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); 490 d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); 491 c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); 492 b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); 493 494 a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); 495 d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); 496 c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); 497 b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); 498 a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); 499 d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); 500 c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); 501 b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); 502 a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); 503 d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); 504 c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); 505 b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); 506 a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); 507 d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); 508 c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); 509 b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); 510 511 a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); 512 d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); 513 c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); 514 b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); 515 a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); 516 d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); 517 c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); 518 b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); 519 a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); 520 d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); 521 c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); 522 b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); 523 a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); 524 d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); 525 c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); 526 b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); 527 528 a = safe_add(a, olda); 529 b = safe_add(b, oldb); 530 c = safe_add(c, oldc); 531 d = safe_add(d, oldd); 532 } 533 return [a, b, c, d]; 534 }; 535 536 var obj = { 537 /* 538 * These are the functions you'll usually want to call. 539 * They take string arguments and return either hex or base-64 encoded 540 * strings. 541 */ 542 hexdigest: function (s) { 543 return binl2hex(core_md5(str2binl(s), s.length * 8)); 544 }, 545 546 hash: function (s) { 547 return binl2str(core_md5(str2binl(s), s.length * 8)); 548 } 549 }; 550 return obj; 551 })); 552 553 /* 554 This program is distributed under the terms of the MIT license. 555 Please see the LICENSE file for details. 556 557 Copyright 2006-2008, OGG, LLC 558 */ 559 560 /* jshint undef: true, unused: true:, noarg: true, latedef: true */ 561 562 /** PrivateFunction: Function.prototype.bind 563 * Bind a function to an instance. 564 * 565 * This Function object extension method creates a bound method similar 566 * to those in Python. This means that the 'this' object will point 567 * to the instance you want. See 568 * <a href='https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind'>MDC's bind() documentation</a> and 569 * <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> 570 * for a complete explanation. 571 * 572 * This extension already exists in some browsers (namely, Firefox 3), but 573 * we provide it to support those that don't. 574 * 575 * Parameters: 576 * (Object) obj - The object that will become 'this' in the bound function. 577 * (Object) argN - An option argument that will be prepended to the 578 * arguments given for the function call 579 * 580 * Returns: 581 * The bound function. 582 */ 583 if (!Function.prototype.bind) { 584 Function.prototype.bind = function (obj /*, arg1, arg2, ... */) 585 { 586 var func = this; 587 var _slice = Array.prototype.slice; 588 var _concat = Array.prototype.concat; 589 var _args = _slice.call(arguments, 1); 590 591 return function () { 592 return func.apply(obj ? obj : this, 593 _concat.call(_args, 594 _slice.call(arguments, 0))); 595 }; 596 }; 597 } 598 599 /** PrivateFunction: Array.isArray 600 * This is a polyfill for the ES5 Array.isArray method. 601 */ 602 if (!Array.isArray) { 603 Array.isArray = function(arg) { 604 return Object.prototype.toString.call(arg) === '[object Array]'; 605 }; 606 } 607 608 /** PrivateFunction: Array.prototype.indexOf 609 * Return the index of an object in an array. 610 * 611 * This function is not supplied by some JavaScript implementations, so 612 * we provide it if it is missing. This code is from: 613 * http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf 614 * 615 * Parameters: 616 * (Object) elt - The object to look for. 617 * (Integer) from - The index from which to start looking. (optional). 618 * 619 * Returns: 620 * The index of elt in the array or -1 if not found. 621 */ 622 if (!Array.prototype.indexOf) 623 { 624 Array.prototype.indexOf = function(elt /*, from*/) 625 { 626 var len = this.length; 627 628 var from = Number(arguments[1]) || 0; 629 from = (from < 0) ? Math.ceil(from) : Math.floor(from); 630 if (from < 0) { 631 from += len; 632 } 633 634 for (; from < len; from++) { 635 if (from in this && this[from] === elt) { 636 return from; 637 } 638 } 639 640 return -1; 641 }; 642 } 643 644 /* 645 This program is distributed under the terms of the MIT license. 646 Please see the LICENSE file for details. 647 648 Copyright 2006-2008, OGG, LLC 649 */ 650 651 /* jshint undef: true, unused: true:, noarg: true, latedef: true */ 652 /*global define, document, window, setTimeout, clearTimeout, console, ActiveXObject, DOMParser */ 653 654 (function (root, factory) { 655 if (typeof define === 'function' && define.amd) { 656 define('strophe-core', [ 657 'strophe-sha1', 658 'strophe-base64', 659 'strophe-md5', 660 "strophe-polyfill" 661 ], function () { 662 return factory.apply(this, arguments); 663 }); 664 } else { 665 // Browser globals 666 var o = factory(root.SHA1, root.Base64, root.MD5); 667 window.Strophe = o.Strophe; 668 window.$build = o.$build; 669 window.$iq = o.$iq; 670 window.$msg = o.$msg; 671 window.$pres = o.$pres; 672 window.SHA1 = o.SHA1; 673 window.Base64 = o.Base64; 674 window.MD5 = o.MD5; 675 window.b64_hmac_sha1 = o.SHA1.b64_hmac_sha1; 676 window.b64_sha1 = o.SHA1.b64_sha1; 677 window.str_hmac_sha1 = o.SHA1.str_hmac_sha1; 678 window.str_sha1 = o.SHA1.str_sha1; 679 } 680 }(this, function (SHA1, Base64, MD5) { 681 682 var Strophe; 683 684 /** Function: $build 685 * Create a Strophe.Builder. 686 * This is an alias for 'new Strophe.Builder(name, attrs)'. 687 * 688 * Parameters: 689 * (String) name - The root element name. 690 * (Object) attrs - The attributes for the root element in object notation. 691 * 692 * Returns: 693 * A new Strophe.Builder object. 694 */ 695 function $build(name, attrs) { return new Strophe.Builder(name, attrs); } 696 697 /** Function: $msg 698 * Create a Strophe.Builder with a <message/> element as the root. 699 * 700 * Parmaeters: 701 * (Object) attrs - The <message/> element attributes in object notation. 702 * 703 * Returns: 704 * A new Strophe.Builder object. 705 */ 706 function $msg(attrs) { return new Strophe.Builder("message", attrs); } 707 708 /** Function: $iq 709 * Create a Strophe.Builder with an <iq/> element as the root. 710 * 711 * Parameters: 712 * (Object) attrs - The <iq/> element attributes in object notation. 713 * 714 * Returns: 715 * A new Strophe.Builder object. 716 */ 717 function $iq(attrs) { return new Strophe.Builder("iq", attrs); } 718 719 /** Function: $pres 720 * Create a Strophe.Builder with a <presence/> element as the root. 721 * 722 * Parameters: 723 * (Object) attrs - The <presence/> element attributes in object notation. 724 * 725 * Returns: 726 * A new Strophe.Builder object. 727 */ 728 function $pres(attrs) { return new Strophe.Builder("presence", attrs); } 729 730 /** Class: Strophe 731 * An object container for all Strophe library functions. 732 * 733 * This class is just a container for all the objects and constants 734 * used in the library. It is not meant to be instantiated, but to 735 * provide a namespace for library objects, constants, and functions. 736 */ 737 Strophe = { 738 /** Constant: VERSION 739 * The version of the Strophe library. Unreleased builds will have 740 * a version of head-HASH where HASH is a partial revision. 741 */ 742 VERSION: "1.2.2", 743 744 /** Constants: XMPP Namespace Constants 745 * Common namespace constants from the XMPP RFCs and XEPs. 746 * 747 * NS.HTTPBIND - HTTP BIND namespace from XEP 124. 748 * NS.BOSH - BOSH namespace from XEP 206. 749 * NS.CLIENT - Main XMPP client namespace. 750 * NS.AUTH - Legacy authentication namespace. 751 * NS.ROSTER - Roster operations namespace. 752 * NS.PROFILE - Profile namespace. 753 * NS.DISCO_INFO - Service discovery info namespace from XEP 30. 754 * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30. 755 * NS.MUC - Multi-User Chat namespace from XEP 45. 756 * NS.SASL - XMPP SASL namespace from RFC 3920. 757 * NS.STREAM - XMPP Streams namespace from RFC 3920. 758 * NS.BIND - XMPP Binding namespace from RFC 3920. 759 * NS.SESSION - XMPP Session namespace from RFC 3920. 760 * NS.XHTML_IM - XHTML-IM namespace from XEP 71. 761 * NS.XHTML - XHTML body namespace from XEP 71. 762 */ 763 NS: { 764 HTTPBIND: "http://jabber.org/protocol/httpbind", 765 BOSH: "urn:xmpp:xbosh", 766 CLIENT: "jabber:client", 767 AUTH: "jabber:iq:auth", 768 ROSTER: "jabber:iq:roster", 769 PROFILE: "jabber:iq:profile", 770 DISCO_INFO: "http://jabber.org/protocol/disco#info", 771 DISCO_ITEMS: "http://jabber.org/protocol/disco#items", 772 MUC: "http://jabber.org/protocol/muc", 773 SASL: "urn:ietf:params:xml:ns:xmpp-sasl", 774 STREAM: "http://etherx.jabber.org/streams", 775 FRAMING: "urn:ietf:params:xml:ns:xmpp-framing", 776 BIND: "urn:ietf:params:xml:ns:xmpp-bind", 777 SESSION: "urn:ietf:params:xml:ns:xmpp-session", 778 VERSION: "jabber:iq:version", 779 STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas", 780 XHTML_IM: "http://jabber.org/protocol/xhtml-im", 781 XHTML: "http://www.w3.org/1999/xhtml" 782 }, 783 784 785 /** Constants: XHTML_IM Namespace 786 * contains allowed tags, tag attributes, and css properties. 787 * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset. 788 * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended 789 * allowed tags and their attributes. 790 */ 791 XHTML: { 792 tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'], 793 attributes: { 794 'a': ['href'], 795 'blockquote': ['style'], 796 'br': [], 797 'cite': ['style'], 798 'em': [], 799 'img': ['src', 'alt', 'style', 'height', 'width'], 800 'li': ['style'], 801 'ol': ['style'], 802 'p': ['style'], 803 'span': ['style'], 804 'strong': [], 805 'ul': ['style'], 806 'body': [] 807 }, 808 css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'], 809 /** Function: XHTML.validTag 810 * 811 * Utility method to determine whether a tag is allowed 812 * in the XHTML_IM namespace. 813 * 814 * XHTML tag names are case sensitive and must be lower case. 815 */ 816 validTag: function(tag) { 817 for (var i = 0; i < Strophe.XHTML.tags.length; i++) { 818 if (tag == Strophe.XHTML.tags[i]) { 819 return true; 820 } 821 } 822 return false; 823 }, 824 /** Function: XHTML.validAttribute 825 * 826 * Utility method to determine whether an attribute is allowed 827 * as recommended per XEP-0071 828 * 829 * XHTML attribute names are case sensitive and must be lower case. 830 */ 831 validAttribute: function(tag, attribute) { 832 if(typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) { 833 for(var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { 834 if(attribute == Strophe.XHTML.attributes[tag][i]) { 835 return true; 836 } 837 } 838 } 839 return false; 840 }, 841 validCSS: function(style) 842 { 843 for(var i = 0; i < Strophe.XHTML.css.length; i++) { 844 if(style == Strophe.XHTML.css[i]) { 845 return true; 846 } 847 } 848 return false; 849 } 850 }, 851 852 /** Constants: Connection Status Constants 853 * Connection status constants for use by the connection handler 854 * callback. 855 * 856 * Status.ERROR - An error has occurred 857 * Status.CONNECTING - The connection is currently being made 858 * Status.CONNFAIL - The connection attempt failed 859 * Status.AUTHENTICATING - The connection is authenticating 860 * Status.AUTHFAIL - The authentication attempt failed 861 * Status.CONNECTED - The connection has succeeded 862 * Status.DISCONNECTED - The connection has been terminated 863 * Status.DISCONNECTING - The connection is currently being terminated 864 * Status.ATTACHED - The connection has been attached 865 */ 866 Status: { 867 ERROR: 0, 868 CONNECTING: 1, 869 CONNFAIL: 2, 870 AUTHENTICATING: 3, 871 AUTHFAIL: 4, 872 CONNECTED: 5, 873 DISCONNECTED: 6, 874 DISCONNECTING: 7, 875 ATTACHED: 8, 876 REDIRECT: 9 877 }, 878 879 /** Constants: Log Level Constants 880 * Logging level indicators. 881 * 882 * LogLevel.DEBUG - Debug output 883 * LogLevel.INFO - Informational output 884 * LogLevel.WARN - Warnings 885 * LogLevel.ERROR - Errors 886 * LogLevel.FATAL - Fatal errors 887 */ 888 LogLevel: { 889 DEBUG: 0, 890 INFO: 1, 891 WARN: 2, 892 ERROR: 3, 893 FATAL: 4 894 }, 895 896 /** PrivateConstants: DOM Element Type Constants 897 * DOM element types. 898 * 899 * ElementType.NORMAL - Normal element. 900 * ElementType.TEXT - Text data element. 901 * ElementType.FRAGMENT - XHTML fragment element. 902 */ 903 ElementType: { 904 NORMAL: 1, 905 TEXT: 3, 906 CDATA: 4, 907 FRAGMENT: 11 908 }, 909 910 /** PrivateConstants: Timeout Values 911 * Timeout values for error states. These values are in seconds. 912 * These should not be changed unless you know exactly what you are 913 * doing. 914 * 915 * TIMEOUT - Timeout multiplier. A waiting request will be considered 916 * failed after Math.floor(TIMEOUT * wait) seconds have elapsed. 917 * This defaults to 1.1, and with default wait, 66 seconds. 918 * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where 919 * Strophe can detect early failure, it will consider the request 920 * failed if it doesn't return after 921 * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed. 922 * This defaults to 0.1, and with default wait, 6 seconds. 923 */ 924 TIMEOUT: 1.1, 925 SECONDARY_TIMEOUT: 0.1, 926 927 /** Function: addNamespace 928 * This function is used to extend the current namespaces in 929 * Strophe.NS. It takes a key and a value with the key being the 930 * name of the new namespace, with its actual value. 931 * For example: 932 * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); 933 * 934 * Parameters: 935 * (String) name - The name under which the namespace will be 936 * referenced under Strophe.NS 937 * (String) value - The actual namespace. 938 */ 939 addNamespace: function (name, value) 940 { 941 Strophe.NS[name] = value; 942 }, 943 944 /** Function: forEachChild 945 * Map a function over some or all child elements of a given element. 946 * 947 * This is a small convenience function for mapping a function over 948 * some or all of the children of an element. If elemName is null, all 949 * children will be passed to the function, otherwise only children 950 * whose tag names match elemName will be passed. 951 * 952 * Parameters: 953 * (XMLElement) elem - The element to operate on. 954 * (String) elemName - The child element tag name filter. 955 * (Function) func - The function to apply to each child. This 956 * function should take a single argument, a DOM element. 957 */ 958 forEachChild: function (elem, elemName, func) 959 { 960 var i, childNode; 961 962 for (i = 0; i < elem.childNodes.length; i++) { 963 childNode = elem.childNodes[i]; 964 if (childNode.nodeType == Strophe.ElementType.NORMAL && 965 (!elemName || this.isTagEqual(childNode, elemName))) { 966 func(childNode); 967 } 968 } 969 }, 970 971 /** Function: isTagEqual 972 * Compare an element's tag name with a string. 973 * 974 * This function is case sensitive. 975 * 976 * Parameters: 977 * (XMLElement) el - A DOM element. 978 * (String) name - The element name. 979 * 980 * Returns: 981 * true if the element's tag name matches _el_, and false 982 * otherwise. 983 */ 984 isTagEqual: function (el, name) 985 { 986 return el.tagName == name; 987 }, 988 989 /** PrivateVariable: _xmlGenerator 990 * _Private_ variable that caches a DOM document to 991 * generate elements. 992 */ 993 _xmlGenerator: null, 994 995 /** PrivateFunction: _makeGenerator 996 * _Private_ function that creates a dummy XML DOM document to serve as 997 * an element and text node generator. 998 */ 999 _makeGenerator: function () { 1000 var doc; 1001 1002 // IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload. 1003 // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be 1004 // less than 10 in the case of IE9 and below. 1005 if (document.implementation.createDocument === undefined || 1006 document.implementation.createDocument && document.documentMode && document.documentMode < 10) { 1007 doc = this._getIEXmlDom(); 1008 doc.appendChild(doc.createElement('strophe')); 1009 } else { 1010 doc = document.implementation 1011 .createDocument('jabber:client', 'strophe', null); 1012 } 1013 1014 return doc; 1015 }, 1016 1017 /** Function: xmlGenerator 1018 * Get the DOM document to generate elements. 1019 * 1020 * Returns: 1021 * The currently used DOM document. 1022 */ 1023 xmlGenerator: function () { 1024 if (!Strophe._xmlGenerator) { 1025 Strophe._xmlGenerator = Strophe._makeGenerator(); 1026 } 1027 return Strophe._xmlGenerator; 1028 }, 1029 1030 /** PrivateFunction: _getIEXmlDom 1031 * Gets IE xml doc object 1032 * 1033 * Returns: 1034 * A Microsoft XML DOM Object 1035 * See Also: 1036 * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx 1037 */ 1038 _getIEXmlDom : function() { 1039 var doc = null; 1040 var docStrings = [ 1041 "Msxml2.DOMDocument.6.0", 1042 "Msxml2.DOMDocument.5.0", 1043 "Msxml2.DOMDocument.4.0", 1044 "MSXML2.DOMDocument.3.0", 1045 "MSXML2.DOMDocument", 1046 "MSXML.DOMDocument", 1047 "Microsoft.XMLDOM" 1048 ]; 1049 1050 for (var d = 0; d < docStrings.length; d++) { 1051 if (doc === null) { 1052 try { 1053 doc = new ActiveXObject(docStrings[d]); 1054 } catch (e) { 1055 doc = null; 1056 } 1057 } else { 1058 break; 1059 } 1060 } 1061 1062 return doc; 1063 }, 1064 1065 /** Function: xmlElement 1066 * Create an XML DOM element. 1067 * 1068 * This function creates an XML DOM element correctly across all 1069 * implementations. Note that these are not HTML DOM elements, which 1070 * aren't appropriate for XMPP stanzas. 1071 * 1072 * Parameters: 1073 * (String) name - The name for the element. 1074 * (Array|Object) attrs - An optional array or object containing 1075 * key/value pairs to use as element attributes. The object should 1076 * be in the format {'key': 'value'} or {key: 'value'}. The array 1077 * should have the format [['key1', 'value1'], ['key2', 'value2']]. 1078 * (String) text - The text child data for the element. 1079 * 1080 * Returns: 1081 * A new XML DOM element. 1082 */ 1083 xmlElement: function (name) 1084 { 1085 if (!name) { return null; } 1086 1087 var node = Strophe.xmlGenerator().createElement(name); 1088 1089 // FIXME: this should throw errors if args are the wrong type or 1090 // there are more than two optional args 1091 var a, i, k; 1092 for (a = 1; a < arguments.length; a++) { 1093 var arg = arguments[a]; 1094 if (!arg) { continue; } 1095 if (typeof(arg) == "string" || 1096 typeof(arg) == "number") { 1097 node.appendChild(Strophe.xmlTextNode(arg)); 1098 } else if (typeof(arg) == "object" && 1099 typeof(arg.sort) == "function") { 1100 for (i = 0; i < arg.length; i++) { 1101 var attr = arg[i]; 1102 if (typeof(attr) == "object" && 1103 typeof(attr.sort) == "function" && 1104 attr[1] !== undefined) { 1105 node.setAttribute(attr[0], attr[1]); 1106 } 1107 } 1108 } else if (typeof(arg) == "object") { 1109 for (k in arg) { 1110 if (arg.hasOwnProperty(k)) { 1111 if (arg[k] !== undefined) { 1112 node.setAttribute(k, arg[k]); 1113 } 1114 } 1115 } 1116 } 1117 } 1118 1119 return node; 1120 }, 1121 1122 /* Function: xmlescape 1123 * Excapes invalid xml characters. 1124 * 1125 * Parameters: 1126 * (String) text - text to escape. 1127 * 1128 * Returns: 1129 * Escaped text. 1130 */ 1131 xmlescape: function(text) 1132 { 1133 text = text.replace(/\&/g, "&"); 1134 text = text.replace(/</g, "<"); 1135 text = text.replace(/>/g, ">"); 1136 text = text.replace(/'/g, "'"); 1137 text = text.replace(/"/g, """); 1138 return text; 1139 }, 1140 1141 /* Function: xmlunescape 1142 * Unexcapes invalid xml characters. 1143 * 1144 * Parameters: 1145 * (String) text - text to unescape. 1146 * 1147 * Returns: 1148 * Unescaped text. 1149 */ 1150 xmlunescape: function(text) 1151 { 1152 text = text.replace(/\&/g, "&"); 1153 text = text.replace(/</g, "<"); 1154 text = text.replace(/>/g, ">"); 1155 text = text.replace(/'/g, "'"); 1156 text = text.replace(/"/g, "\""); 1157 return text; 1158 }, 1159 1160 /** Function: xmlTextNode 1161 * Creates an XML DOM text node. 1162 * 1163 * Provides a cross implementation version of document.createTextNode. 1164 * 1165 * Parameters: 1166 * (String) text - The content of the text node. 1167 * 1168 * Returns: 1169 * A new XML DOM text node. 1170 */ 1171 xmlTextNode: function (text) 1172 { 1173 return Strophe.xmlGenerator().createTextNode(text); 1174 }, 1175 1176 /** Function: xmlHtmlNode 1177 * Creates an XML DOM html node. 1178 * 1179 * Parameters: 1180 * (String) html - The content of the html node. 1181 * 1182 * Returns: 1183 * A new XML DOM text node. 1184 */ 1185 xmlHtmlNode: function (html) 1186 { 1187 var node; 1188 //ensure text is escaped 1189 if (window.DOMParser) { 1190 var parser = new DOMParser(); 1191 node = parser.parseFromString(html, "text/xml"); 1192 } else { 1193 node = new ActiveXObject("Microsoft.XMLDOM"); 1194 node.async="false"; 1195 node.loadXML(html); 1196 } 1197 return node; 1198 }, 1199 1200 /** Function: getText 1201 * Get the concatenation of all text children of an element. 1202 * 1203 * Parameters: 1204 * (XMLElement) elem - A DOM element. 1205 * 1206 * Returns: 1207 * A String with the concatenated text of all text element children. 1208 */ 1209 getText: function (elem) 1210 { 1211 if (!elem) { return null; } 1212 1213 var str = ""; 1214 if (elem.childNodes.length === 0 && elem.nodeType == 1215 Strophe.ElementType.TEXT) { 1216 str += elem.nodeValue; 1217 } 1218 1219 for (var i = 0; i < elem.childNodes.length; i++) { 1220 if (elem.childNodes[i].nodeType == Strophe.ElementType.TEXT) { 1221 str += elem.childNodes[i].nodeValue; 1222 } 1223 } 1224 1225 return Strophe.xmlescape(str); 1226 }, 1227 1228 /** Function: copyElement 1229 * Copy an XML DOM element. 1230 * 1231 * This function copies a DOM element and all its descendants and returns 1232 * the new copy. 1233 * 1234 * Parameters: 1235 * (XMLElement) elem - A DOM element. 1236 * 1237 * Returns: 1238 * A new, copied DOM element tree. 1239 */ 1240 copyElement: function (elem) 1241 { 1242 var i, el; 1243 if (elem.nodeType == Strophe.ElementType.NORMAL) { 1244 el = Strophe.xmlElement(elem.tagName); 1245 1246 for (i = 0; i < elem.attributes.length; i++) { 1247 el.setAttribute(elem.attributes[i].nodeName, 1248 elem.attributes[i].value); 1249 } 1250 1251 for (i = 0; i < elem.childNodes.length; i++) { 1252 el.appendChild(Strophe.copyElement(elem.childNodes[i])); 1253 } 1254 } else if (elem.nodeType == Strophe.ElementType.TEXT) { 1255 el = Strophe.xmlGenerator().createTextNode(elem.nodeValue); 1256 } 1257 1258 return el; 1259 }, 1260 1261 1262 /** Function: createHtml 1263 * Copy an HTML DOM element into an XML DOM. 1264 * 1265 * This function copies a DOM element and all its descendants and returns 1266 * the new copy. 1267 * 1268 * Parameters: 1269 * (HTMLElement) elem - A DOM element. 1270 * 1271 * Returns: 1272 * A new, copied DOM element tree. 1273 */ 1274 createHtml: function (elem) 1275 { 1276 var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue; 1277 if (elem.nodeType == Strophe.ElementType.NORMAL) { 1278 tag = elem.nodeName.toLowerCase(); // XHTML tags must be lower case. 1279 if(Strophe.XHTML.validTag(tag)) { 1280 try { 1281 el = Strophe.xmlElement(tag); 1282 for(i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { 1283 attribute = Strophe.XHTML.attributes[tag][i]; 1284 value = elem.getAttribute(attribute); 1285 if(typeof value == 'undefined' || value === null || value === '' || value === false || value === 0) { 1286 continue; 1287 } 1288 if(attribute == 'style' && typeof value == 'object') { 1289 if(typeof value.cssText != 'undefined') { 1290 value = value.cssText; // we're dealing with IE, need to get CSS out 1291 } 1292 } 1293 // filter out invalid css styles 1294 if(attribute == 'style') { 1295 css = []; 1296 cssAttrs = value.split(';'); 1297 for(j = 0; j < cssAttrs.length; j++) { 1298 attr = cssAttrs[j].split(':'); 1299 cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase(); 1300 if(Strophe.XHTML.validCSS(cssName)) { 1301 cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, ""); 1302 css.push(cssName + ': ' + cssValue); 1303 } 1304 } 1305 if(css.length > 0) { 1306 value = css.join('; '); 1307 el.setAttribute(attribute, value); 1308 } 1309 } else { 1310 el.setAttribute(attribute, value); 1311 } 1312 } 1313 1314 for (i = 0; i < elem.childNodes.length; i++) { 1315 el.appendChild(Strophe.createHtml(elem.childNodes[i])); 1316 } 1317 } catch(e) { // invalid elements 1318 el = Strophe.xmlTextNode(''); 1319 } 1320 } else { 1321 el = Strophe.xmlGenerator().createDocumentFragment(); 1322 for (i = 0; i < elem.childNodes.length; i++) { 1323 el.appendChild(Strophe.createHtml(elem.childNodes[i])); 1324 } 1325 } 1326 } else if (elem.nodeType == Strophe.ElementType.FRAGMENT) { 1327 el = Strophe.xmlGenerator().createDocumentFragment(); 1328 for (i = 0; i < elem.childNodes.length; i++) { 1329 el.appendChild(Strophe.createHtml(elem.childNodes[i])); 1330 } 1331 } else if (elem.nodeType == Strophe.ElementType.TEXT) { 1332 el = Strophe.xmlTextNode(elem.nodeValue); 1333 } 1334 1335 return el; 1336 }, 1337 1338 /** Function: escapeNode 1339 * Escape the node part (also called local part) of a JID. 1340 * 1341 * Parameters: 1342 * (String) node - A node (or local part). 1343 * 1344 * Returns: 1345 * An escaped node (or local part). 1346 */ 1347 escapeNode: function (node) 1348 { 1349 if (typeof node !== "string") { return node; } 1350 return node.replace(/^\s+|\s+$/g, '') 1351 .replace(/\\/g, "\\5c") 1352 .replace(/ /g, "\\20") 1353 .replace(/\"/g, "\\22") 1354 .replace(/\&/g, "\\26") 1355 .replace(/\'/g, "\\27") 1356 .replace(/\//g, "\\2f") 1357 .replace(/:/g, "\\3a") 1358 .replace(/</g, "\\3c") 1359 .replace(/>/g, "\\3e") 1360 .replace(/@/g, "\\40"); 1361 }, 1362 1363 /** Function: unescapeNode 1364 * Unescape a node part (also called local part) of a JID. 1365 * 1366 * Parameters: 1367 * (String) node - A node (or local part). 1368 * 1369 * Returns: 1370 * An unescaped node (or local part). 1371 */ 1372 unescapeNode: function (node) 1373 { 1374 if (typeof node !== "string") { return node; } 1375 return node.replace(/\\20/g, " ") 1376 .replace(/\\22/g, '"') 1377 .replace(/\\26/g, "&") 1378 .replace(/\\27/g, "'") 1379 .replace(/\\2f/g, "/") 1380 .replace(/\\3a/g, ":") 1381 .replace(/\\3c/g, "<") 1382 .replace(/\\3e/g, ">") 1383 .replace(/\\40/g, "@") 1384 .replace(/\\5c/g, "\\"); 1385 }, 1386 1387 /** Function: getNodeFromJid 1388 * Get the node portion of a JID String. 1389 * 1390 * Parameters: 1391 * (String) jid - A JID. 1392 * 1393 * Returns: 1394 * A String containing the node. 1395 */ 1396 getNodeFromJid: function (jid) 1397 { 1398 if (jid.indexOf("@") < 0) { return null; } 1399 return jid.split("@")[0]; 1400 }, 1401 1402 /** Function: getDomainFromJid 1403 * Get the domain portion of a JID String. 1404 * 1405 * Parameters: 1406 * (String) jid - A JID. 1407 * 1408 * Returns: 1409 * A String containing the domain. 1410 */ 1411 getDomainFromJid: function (jid) 1412 { 1413 var bare = Strophe.getBareJidFromJid(jid); 1414 if (bare.indexOf("@") < 0) { 1415 return bare; 1416 } else { 1417 var parts = bare.split("@"); 1418 parts.splice(0, 1); 1419 return parts.join('@'); 1420 } 1421 }, 1422 1423 /** Function: getResourceFromJid 1424 * Get the resource portion of a JID String. 1425 * 1426 * Parameters: 1427 * (String) jid - A JID. 1428 * 1429 * Returns: 1430 * A String containing the resource. 1431 */ 1432 getResourceFromJid: function (jid) 1433 { 1434 var s = jid.split("/"); 1435 if (s.length < 2) { return null; } 1436 s.splice(0, 1); 1437 return s.join('/'); 1438 }, 1439 1440 /** Function: getBareJidFromJid 1441 * Get the bare JID from a JID String. 1442 * 1443 * Parameters: 1444 * (String) jid - A JID. 1445 * 1446 * Returns: 1447 * A String containing the bare JID. 1448 */ 1449 getBareJidFromJid: function (jid) 1450 { 1451 return jid ? jid.split("/")[0] : null; 1452 }, 1453 1454 /** Function: log 1455 * User overrideable logging function. 1456 * 1457 * This function is called whenever the Strophe library calls any 1458 * of the logging functions. The default implementation of this 1459 * function does nothing. If client code wishes to handle the logging 1460 * messages, it should override this with 1461 * > Strophe.log = function (level, msg) { 1462 * > (user code here) 1463 * > }; 1464 * 1465 * Please note that data sent and received over the wire is logged 1466 * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput(). 1467 * 1468 * The different levels and their meanings are 1469 * 1470 * DEBUG - Messages useful for debugging purposes. 1471 * INFO - Informational messages. This is mostly information like 1472 * 'disconnect was called' or 'SASL auth succeeded'. 1473 * WARN - Warnings about potential problems. This is mostly used 1474 * to report transient connection errors like request timeouts. 1475 * ERROR - Some error occurred. 1476 * FATAL - A non-recoverable fatal error occurred. 1477 * 1478 * Parameters: 1479 * (Integer) level - The log level of the log message. This will 1480 * be one of the values in Strophe.LogLevel. 1481 * (String) msg - The log message. 1482 */ 1483 /* jshint ignore:start */ 1484 log: function (level, msg) 1485 { 1486 return; 1487 }, 1488 /* jshint ignore:end */ 1489 1490 /** Function: debug 1491 * Log a message at the Strophe.LogLevel.DEBUG level. 1492 * 1493 * Parameters: 1494 * (String) msg - The log message. 1495 */ 1496 debug: function(msg) 1497 { 1498 this.log(this.LogLevel.DEBUG, msg); 1499 }, 1500 1501 /** Function: info 1502 * Log a message at the Strophe.LogLevel.INFO level. 1503 * 1504 * Parameters: 1505 * (String) msg - The log message. 1506 */ 1507 info: function (msg) 1508 { 1509 this.log(this.LogLevel.INFO, msg); 1510 }, 1511 1512 /** Function: warn 1513 * Log a message at the Strophe.LogLevel.WARN level. 1514 * 1515 * Parameters: 1516 * (String) msg - The log message. 1517 */ 1518 warn: function (msg) 1519 { 1520 this.log(this.LogLevel.WARN, msg); 1521 }, 1522 1523 /** Function: error 1524 * Log a message at the Strophe.LogLevel.ERROR level. 1525 * 1526 * Parameters: 1527 * (String) msg - The log message. 1528 */ 1529 error: function (msg) 1530 { 1531 this.log(this.LogLevel.ERROR, msg); 1532 }, 1533 1534 /** Function: fatal 1535 * Log a message at the Strophe.LogLevel.FATAL level. 1536 * 1537 * Parameters: 1538 * (String) msg - The log message. 1539 */ 1540 fatal: function (msg) 1541 { 1542 this.log(this.LogLevel.FATAL, msg); 1543 }, 1544 1545 /** Function: serialize 1546 * Render a DOM element and all descendants to a String. 1547 * 1548 * Parameters: 1549 * (XMLElement) elem - A DOM element. 1550 * 1551 * Returns: 1552 * The serialized element tree as a String. 1553 */ 1554 serialize: function (elem) 1555 { 1556 var result; 1557 1558 if (!elem) { return null; } 1559 1560 if (typeof(elem.tree) === "function") { 1561 elem = elem.tree(); 1562 } 1563 1564 var nodeName = elem.nodeName; 1565 var i, child; 1566 1567 if (elem.getAttribute("_realname")) { 1568 nodeName = elem.getAttribute("_realname"); 1569 } 1570 1571 result = "<" + nodeName; 1572 for (i = 0; i < elem.attributes.length; i++) { 1573 if(elem.attributes[i].nodeName != "_realname") { 1574 result += " " + elem.attributes[i].nodeName + 1575 "='" + elem.attributes[i].value 1576 .replace(/&/g, "&") 1577 .replace(/\'/g, "'") 1578 .replace(/>/g, ">") 1579 .replace(/</g, "<") + "'"; 1580 } 1581 } 1582 1583 if (elem.childNodes.length > 0) { 1584 result += ">"; 1585 for (i = 0; i < elem.childNodes.length; i++) { 1586 child = elem.childNodes[i]; 1587 switch( child.nodeType ){ 1588 case Strophe.ElementType.NORMAL: 1589 // normal element, so recurse 1590 result += Strophe.serialize(child); 1591 break; 1592 case Strophe.ElementType.TEXT: 1593 // text element to escape values 1594 result += Strophe.xmlescape(child.nodeValue); 1595 break; 1596 case Strophe.ElementType.CDATA: 1597 // cdata section so don't escape values 1598 result += "<![CDATA["+child.nodeValue+"]]>"; 1599 } 1600 } 1601 result += "</" + nodeName + ">"; 1602 } else { 1603 result += "/>"; 1604 } 1605 1606 return result; 1607 }, 1608 1609 /** PrivateVariable: _requestId 1610 * _Private_ variable that keeps track of the request ids for 1611 * connections. 1612 */ 1613 _requestId: 0, 1614 1615 /** PrivateVariable: Strophe.connectionPlugins 1616 * _Private_ variable Used to store plugin names that need 1617 * initialization on Strophe.Connection construction. 1618 */ 1619 _connectionPlugins: {}, 1620 1621 /** Function: addConnectionPlugin 1622 * Extends the Strophe.Connection object with the given plugin. 1623 * 1624 * Parameters: 1625 * (String) name - The name of the extension. 1626 * (Object) ptype - The plugin's prototype. 1627 */ 1628 addConnectionPlugin: function (name, ptype) 1629 { 1630 Strophe._connectionPlugins[name] = ptype; 1631 } 1632 }; 1633 1634 /** Class: Strophe.Builder 1635 * XML DOM builder. 1636 * 1637 * This object provides an interface similar to JQuery but for building 1638 * DOM element easily and rapidly. All the functions except for toString() 1639 * and tree() return the object, so calls can be chained. Here's an 1640 * example using the $iq() builder helper. 1641 * > $iq({to: 'you', from: 'me', type: 'get', id: '1'}) 1642 * > .c('query', {xmlns: 'strophe:example'}) 1643 * > .c('example') 1644 * > .toString() 1645 * The above generates this XML fragment 1646 * > <iq to='you' from='me' type='get' id='1'> 1647 * > <query xmlns='strophe:example'> 1648 * > <example/> 1649 * > </query> 1650 * > </iq> 1651 * The corresponding DOM manipulations to get a similar fragment would be 1652 * a lot more tedious and probably involve several helper variables. 1653 * 1654 * Since adding children makes new operations operate on the child, up() 1655 * is provided to traverse up the tree. To add two children, do 1656 * > builder.c('child1', ...).up().c('child2', ...) 1657 * The next operation on the Builder will be relative to the second child. 1658 */ 1659 1660 /** Constructor: Strophe.Builder 1661 * Create a Strophe.Builder object. 1662 * 1663 * The attributes should be passed in object notation. For example 1664 * > var b = new Builder('message', {to: 'you', from: 'me'}); 1665 * or 1666 * > var b = new Builder('messsage', {'xml:lang': 'en'}); 1667 * 1668 * Parameters: 1669 * (String) name - The name of the root element. 1670 * (Object) attrs - The attributes for the root element in object notation. 1671 * 1672 * Returns: 1673 * A new Strophe.Builder. 1674 */ 1675 Strophe.Builder = function (name, attrs) 1676 { 1677 // Set correct namespace for jabber:client elements 1678 if (name == "presence" || name == "message" || name == "iq") { 1679 if (attrs && !attrs.xmlns) { 1680 attrs.xmlns = Strophe.NS.CLIENT; 1681 } else if (!attrs) { 1682 attrs = {xmlns: Strophe.NS.CLIENT}; 1683 } 1684 } 1685 1686 // Holds the tree being built. 1687 this.nodeTree = Strophe.xmlElement(name, attrs); 1688 1689 // Points to the current operation node. 1690 this.node = this.nodeTree; 1691 }; 1692 1693 Strophe.Builder.prototype = { 1694 /** Function: tree 1695 * Return the DOM tree. 1696 * 1697 * This function returns the current DOM tree as an element object. This 1698 * is suitable for passing to functions like Strophe.Connection.send(). 1699 * 1700 * Returns: 1701 * The DOM tree as a element object. 1702 */ 1703 tree: function () 1704 { 1705 return this.nodeTree; 1706 }, 1707 1708 /** Function: toString 1709 * Serialize the DOM tree to a String. 1710 * 1711 * This function returns a string serialization of the current DOM 1712 * tree. It is often used internally to pass data to a 1713 * Strophe.Request object. 1714 * 1715 * Returns: 1716 * The serialized DOM tree in a String. 1717 */ 1718 toString: function () 1719 { 1720 return Strophe.serialize(this.nodeTree); 1721 }, 1722 1723 /** Function: up 1724 * Make the current parent element the new current element. 1725 * 1726 * This function is often used after c() to traverse back up the tree. 1727 * For example, to add two children to the same element 1728 * > builder.c('child1', {}).up().c('child2', {}); 1729 * 1730 * Returns: 1731 * The Stophe.Builder object. 1732 */ 1733 up: function () 1734 { 1735 this.node = this.node.parentNode; 1736 return this; 1737 }, 1738 1739 /** Function: attrs 1740 * Add or modify attributes of the current element. 1741 * 1742 * The attributes should be passed in object notation. This function 1743 * does not move the current element pointer. 1744 * 1745 * Parameters: 1746 * (Object) moreattrs - The attributes to add/modify in object notation. 1747 * 1748 * Returns: 1749 * The Strophe.Builder object. 1750 */ 1751 attrs: function (moreattrs) 1752 { 1753 for (var k in moreattrs) { 1754 if (moreattrs.hasOwnProperty(k)) { 1755 if (moreattrs[k] === undefined) { 1756 this.node.removeAttribute(k); 1757 } else { 1758 this.node.setAttribute(k, moreattrs[k]); 1759 } 1760 } 1761 } 1762 return this; 1763 }, 1764 1765 /** Function: c 1766 * Add a child to the current element and make it the new current 1767 * element. 1768 * 1769 * This function moves the current element pointer to the child, 1770 * unless text is provided. If you need to add another child, it 1771 * is necessary to use up() to go back to the parent in the tree. 1772 * 1773 * Parameters: 1774 * (String) name - The name of the child. 1775 * (Object) attrs - The attributes of the child in object notation. 1776 * (String) text - The text to add to the child. 1777 * 1778 * Returns: 1779 * The Strophe.Builder object. 1780 */ 1781 c: function (name, attrs, text) 1782 { 1783 var child = Strophe.xmlElement(name, attrs, text); 1784 this.node.appendChild(child); 1785 if (typeof text !== "string") { 1786 this.node = child; 1787 } 1788 return this; 1789 }, 1790 1791 /** Function: cnode 1792 * Add a child to the current element and make it the new current 1793 * element. 1794 * 1795 * This function is the same as c() except that instead of using a 1796 * name and an attributes object to create the child it uses an 1797 * existing DOM element object. 1798 * 1799 * Parameters: 1800 * (XMLElement) elem - A DOM element. 1801 * 1802 * Returns: 1803 * The Strophe.Builder object. 1804 */ 1805 cnode: function (elem) 1806 { 1807 var impNode; 1808 var xmlGen = Strophe.xmlGenerator(); 1809 try { 1810 impNode = (xmlGen.importNode !== undefined); 1811 } 1812 catch (e) { 1813 impNode = false; 1814 } 1815 var newElem = impNode ? 1816 xmlGen.importNode(elem, true) : 1817 Strophe.copyElement(elem); 1818 this.node.appendChild(newElem); 1819 this.node = newElem; 1820 return this; 1821 }, 1822 1823 /** Function: t 1824 * Add a child text element. 1825 * 1826 * This *does not* make the child the new current element since there 1827 * are no children of text elements. 1828 * 1829 * Parameters: 1830 * (String) text - The text data to append to the current element. 1831 * 1832 * Returns: 1833 * The Strophe.Builder object. 1834 */ 1835 t: function (text) 1836 { 1837 //text=CHEncode(text); 1838 var child = Strophe.xmlTextNode(text); 1839 this.node.appendChild(child); 1840 return this; 1841 }, 1842 1843 cht: function (text) 1844 { 1845 text=Base64.chencode(text); 1846 var child = Strophe.xmlTextNode(text); 1847 this.node.appendChild(child); 1848 return this; 1849 }, 1850 1851 /** Function: h 1852 * Replace current element contents with the HTML passed in. 1853 * 1854 * This *does not* make the child the new current element 1855 * 1856 * Parameters: 1857 * (String) html - The html to insert as contents of current element. 1858 * 1859 * Returns: 1860 * The Strophe.Builder object. 1861 */ 1862 h: function (html) 1863 { 1864 var fragment = document.createElement('body'); 1865 1866 // force the browser to try and fix any invalid HTML tags 1867 fragment.innerHTML = html; 1868 1869 // copy cleaned html into an xml dom 1870 var xhtml = Strophe.createHtml(fragment); 1871 1872 while(xhtml.childNodes.length > 0) { 1873 this.node.appendChild(xhtml.childNodes[0]); 1874 } 1875 return this; 1876 } 1877 }; 1878 1879 /** PrivateClass: Strophe.Handler 1880 * _Private_ helper class for managing stanza handlers. 1881 * 1882 * A Strophe.Handler encapsulates a user provided callback function to be 1883 * executed when matching stanzas are received by the connection. 1884 * Handlers can be either one-off or persistant depending on their 1885 * return value. Returning true will cause a Handler to remain active, and 1886 * returning false will remove the Handler. 1887 * 1888 * Users will not use Strophe.Handler objects directly, but instead they 1889 * will use Strophe.Connection.addHandler() and 1890 * Strophe.Connection.deleteHandler(). 1891 */ 1892 1893 /** PrivateConstructor: Strophe.Handler 1894 * Create and initialize a new Strophe.Handler. 1895 * 1896 * Parameters: 1897 * (Function) handler - A function to be executed when the handler is run. 1898 * (String) ns - The namespace to match. 1899 * (String) name - The element name to match. 1900 * (String) type - The element type to match. 1901 * (String) id - The element id attribute to match. 1902 * (String) from - The element from attribute to match. 1903 * (Object) options - Handler options 1904 * 1905 * Returns: 1906 * A new Strophe.Handler object. 1907 */ 1908 Strophe.Handler = function (handler, ns, name, type, id, from, options) 1909 { 1910 this.handler = handler; 1911 this.ns = ns; 1912 this.name = name; 1913 this.type = type; 1914 this.id = id; 1915 this.options = options || {matchBare: false}; 1916 1917 // default matchBare to false if undefined 1918 if (!this.options.matchBare) { 1919 this.options.matchBare = false; 1920 } 1921 1922 if (this.options.matchBare) { 1923 this.from = from ? Strophe.getBareJidFromJid(from) : null; 1924 } else { 1925 this.from = from; 1926 } 1927 1928 // whether the handler is a user handler or a system handler 1929 this.user = true; 1930 }; 1931 1932 Strophe.Handler.prototype = { 1933 /** PrivateFunction: isMatch 1934 * Tests if a stanza matches the Strophe.Handler. 1935 * 1936 * Parameters: 1937 * (XMLElement) elem - The XML element to test. 1938 * 1939 * Returns: 1940 * true if the stanza matches and false otherwise. 1941 */ 1942 isMatch: function (elem) 1943 { 1944 var nsMatch; 1945 var from = null; 1946 1947 if (this.options.matchBare) { 1948 from = Strophe.getBareJidFromJid(elem.getAttribute('from')); 1949 } else { 1950 from = elem.getAttribute('from'); 1951 } 1952 1953 nsMatch = false; 1954 if (!this.ns) { 1955 nsMatch = true; 1956 } else { 1957 var that = this; 1958 Strophe.forEachChild(elem, null, function (elem) { 1959 if (elem.getAttribute("xmlns") == that.ns) { 1960 nsMatch = true; 1961 } 1962 }); 1963 1964 nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns; 1965 } 1966 1967 var elem_type = elem.getAttribute("type"); 1968 if (nsMatch && 1969 (!this.name || Strophe.isTagEqual(elem, this.name)) && 1970 (!this.type || (Array.isArray(this.type) ? this.type.indexOf(elem_type) != -1 : elem_type == this.type)) && 1971 (!this.id || elem.getAttribute("id") == this.id) && 1972 (!this.from || from == this.from)) { 1973 return true; 1974 } 1975 1976 return false; 1977 }, 1978 1979 /** PrivateFunction: run 1980 * Run the callback on a matching stanza. 1981 * 1982 * Parameters: 1983 * (XMLElement) elem - The DOM element that triggered the 1984 * Strophe.Handler. 1985 * 1986 * Returns: 1987 * A boolean indicating if the handler should remain active. 1988 */ 1989 run: function (elem) 1990 { 1991 var result = null; 1992 try { 1993 result = this.handler(elem); 1994 } catch (e) { 1995 if (e.sourceURL) { 1996 Strophe.fatal("error: " + this.handler + 1997 " " + e.sourceURL + ":" + 1998 e.line + " - " + e.name + ": " + e.message); 1999 } else if (e.fileName) { 2000 if (typeof(console) != "undefined") { 2001 console.trace(); 2002 console.error(this.handler, " - error - ", e, e.message); 2003 } 2004 Strophe.fatal("error: " + this.handler + " " + 2005 e.fileName + ":" + e.lineNumber + " - " + 2006 e.name + ": " + e.message); 2007 } else { 2008 Strophe.fatal("error: " + e.message + "\n" + e.stack); 2009 } 2010 2011 throw e; 2012 } 2013 2014 return result; 2015 }, 2016 2017 /** PrivateFunction: toString 2018 * Get a String representation of the Strophe.Handler object. 2019 * 2020 * Returns: 2021 * A String. 2022 */ 2023 toString: function () 2024 { 2025 return "{Handler: " + this.handler + "(" + this.name + "," + 2026 this.id + "," + this.ns + ")}"; 2027 } 2028 }; 2029 2030 /** PrivateClass: Strophe.TimedHandler 2031 * _Private_ helper class for managing timed handlers. 2032 * 2033 * A Strophe.TimedHandler encapsulates a user provided callback that 2034 * should be called after a certain period of time or at regular 2035 * intervals. The return value of the callback determines whether the 2036 * Strophe.TimedHandler will continue to fire. 2037 * 2038 * Users will not use Strophe.TimedHandler objects directly, but instead 2039 * they will use Strophe.Connection.addTimedHandler() and 2040 * Strophe.Connection.deleteTimedHandler(). 2041 */ 2042 2043 /** PrivateConstructor: Strophe.TimedHandler 2044 * Create and initialize a new Strophe.TimedHandler object. 2045 * 2046 * Parameters: 2047 * (Integer) period - The number of milliseconds to wait before the 2048 * handler is called. 2049 * (Function) handler - The callback to run when the handler fires. This 2050 * function should take no arguments. 2051 * 2052 * Returns: 2053 * A new Strophe.TimedHandler object. 2054 */ 2055 Strophe.TimedHandler = function (period, handler) 2056 { 2057 this.period = period; 2058 this.handler = handler; 2059 2060 this.lastCalled = new Date().getTime(); 2061 this.user = true; 2062 }; 2063 2064 Strophe.TimedHandler.prototype = { 2065 /** PrivateFunction: run 2066 * Run the callback for the Strophe.TimedHandler. 2067 * 2068 * Returns: 2069 * true if the Strophe.TimedHandler should be called again, and false 2070 * otherwise. 2071 */ 2072 run: function () 2073 { 2074 this.lastCalled = new Date().getTime(); 2075 return this.handler(); 2076 }, 2077 2078 /** PrivateFunction: reset 2079 * Reset the last called time for the Strophe.TimedHandler. 2080 */ 2081 reset: function () 2082 { 2083 this.lastCalled = new Date().getTime(); 2084 }, 2085 2086 /** PrivateFunction: toString 2087 * Get a string representation of the Strophe.TimedHandler object. 2088 * 2089 * Returns: 2090 * The string representation. 2091 */ 2092 toString: function () 2093 { 2094 return "{TimedHandler: " + this.handler + "(" + this.period +")}"; 2095 } 2096 }; 2097 2098 /** Class: Strophe.Connection 2099 * XMPP Connection manager. 2100 * 2101 * This class is the main part of Strophe. It manages a BOSH or websocket 2102 * connection to an XMPP server and dispatches events to the user callbacks 2103 * as data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1 2104 * and legacy authentication. 2105 * 2106 * After creating a Strophe.Connection object, the user will typically 2107 * call connect() with a user supplied callback to handle connection level 2108 * events like authentication failure, disconnection, or connection 2109 * complete. 2110 * 2111 * The user will also have several event handlers defined by using 2112 * addHandler() and addTimedHandler(). These will allow the user code to 2113 * respond to interesting stanzas or do something periodically with the 2114 * connection. These handlers will be active once authentication is 2115 * finished. 2116 * 2117 * To send data to the connection, use send(). 2118 */ 2119 2120 /** Constructor: Strophe.Connection 2121 * Create and initialize a Strophe.Connection object. 2122 * 2123 * The transport-protocol for this connection will be chosen automatically 2124 * based on the given service parameter. URLs starting with "ws://" or 2125 * "wss://" will use WebSockets, URLs starting with "http://", "https://" 2126 * or without a protocol will use BOSH. 2127 * 2128 * To make Strophe connect to the current host you can leave out the protocol 2129 * and host part and just pass the path, e.g. 2130 * 2131 * > var conn = new Strophe.Connection("/http-bind/"); 2132 * 2133 * WebSocket options: 2134 * 2135 * If you want to connect to the current host with a WebSocket connection you 2136 * can tell Strophe to use WebSockets through a "protocol" attribute in the 2137 * optional options parameter. Valid values are "ws" for WebSocket and "wss" 2138 * for Secure WebSocket. 2139 * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call 2140 * 2141 * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"}); 2142 * 2143 * Note that relative URLs _NOT_ starting with a "/" will also include the path 2144 * of the current site. 2145 * 2146 * Also because downgrading security is not permitted by browsers, when using 2147 * relative URLs both BOSH and WebSocket connections will use their secure 2148 * variants if the current connection to the site is also secure (https). 2149 * 2150 * BOSH options: 2151 * 2152 * By adding "sync" to the options, you can control if requests will 2153 * be made synchronously or not. The default behaviour is asynchronous. 2154 * If you want to make requests synchronous, make "sync" evaluate to true: 2155 * > var conn = new Strophe.Connection("/http-bind/", {sync: true}); 2156 * 2157 * You can also toggle this on an already established connection: 2158 * > conn.options.sync = true; 2159 * 2160 * The "customHeaders" option can be used to provide custom HTTP headers to be 2161 * included in the XMLHttpRequests made. 2162 * 2163 * The "keepalive" option can be used to instruct Strophe to maintain the 2164 * current BOSH session across interruptions such as webpage reloads. 2165 * 2166 * It will do this by caching the sessions tokens in sessionStorage, and when 2167 * "restore" is called it will check whether there are cached tokens with 2168 * which it can resume an existing session. 2169 * 2170 * Parameters: 2171 * (String) service - The BOSH or WebSocket service URL. 2172 * (Object) options - A hash of configuration options 2173 * 2174 * Returns: 2175 * A new Strophe.Connection object. 2176 */ 2177 Strophe.Connection = function (service, options) 2178 { 2179 //alert(service); 2180 // The service URL 2181 this.service = service; 2182 2183 // Configuration options 2184 this.options = options || {}; 2185 var proto = this.options.protocol || ""; 2186 2187 // Select protocal based on service or options 2188 if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || 2189 proto.indexOf("ws") === 0) { 2190 this._proto = new Strophe.Websocket(this); 2191 } else { 2192 this._proto = new Strophe.Bosh(this); 2193 } 2194 2195 /* The connected JID. */ 2196 this.jid = ""; 2197 /* the JIDs domain */ 2198 this.domain = null; 2199 /* stream:features */ 2200 this.features = null; 2201 2202 // SASL 2203 this._sasl_data = {}; 2204 this.do_session = false; 2205 this.do_bind = false; 2206 2207 // handler lists 2208 this.timedHandlers = []; 2209 this.handlers = []; 2210 this.removeTimeds = []; 2211 this.removeHandlers = []; 2212 this.addTimeds = []; 2213 this.addHandlers = []; 2214 2215 this._authentication = {}; 2216 this._idleTimeout = null; 2217 this._disconnectTimeout = null; 2218 2219 this.authenticated = false; 2220 this.connected = false; 2221 this.disconnecting = false; 2222 this.do_authentication = true; 2223 this.paused = false; 2224 this.restored = false; 2225 2226 this._data = []; 2227 this._uniqueId = 0; 2228 2229 this._sasl_success_handler = null; 2230 this._sasl_failure_handler = null; 2231 this._sasl_challenge_handler = null; 2232 2233 // Max retries before disconnecting 2234 this.maxRetries = 5; 2235 2236 // setup onIdle callback every 1/10th of a second 2237 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); 2238 2239 // initialize plugins 2240 for (var k in Strophe._connectionPlugins) { 2241 if (Strophe._connectionPlugins.hasOwnProperty(k)) { 2242 var ptype = Strophe._connectionPlugins[k]; 2243 // jslint complaints about the below line, but this is fine 2244 var F = function () {}; // jshint ignore:line 2245 F.prototype = ptype; 2246 this[k] = new F(); 2247 this[k].init(this); 2248 } 2249 } 2250 }; 2251 2252 Strophe.Connection.prototype = { 2253 /** Function: reset 2254 * Reset the connection. 2255 * 2256 * This function should be called after a connection is disconnected 2257 * before that connection is reused. 2258 */ 2259 reset: function () 2260 { 2261 this._proto._reset(); 2262 2263 // SASL 2264 this.do_session = false; 2265 this.do_bind = false; 2266 2267 // handler lists 2268 this.timedHandlers = []; 2269 this.handlers = []; 2270 this.removeTimeds = []; 2271 this.removeHandlers = []; 2272 this.addTimeds = []; 2273 this.addHandlers = []; 2274 this._authentication = {}; 2275 2276 this.authenticated = false; 2277 this.connected = false; 2278 this.disconnecting = false; 2279 this.restored = false; 2280 2281 this._data = []; 2282 this._requests = []; 2283 this._uniqueId = 0; 2284 }, 2285 2286 /** Function: pause 2287 * Pause the request manager. 2288 * 2289 * This will prevent Strophe from sending any more requests to the 2290 * server. This is very useful for temporarily pausing 2291 * BOSH-Connections while a lot of send() calls are happening quickly. 2292 * This causes Strophe to send the data in a single request, saving 2293 * many request trips. 2294 */ 2295 pause: function () 2296 { 2297 this.paused = true; 2298 }, 2299 2300 /** Function: resume 2301 * Resume the request manager. 2302 * 2303 * This resumes after pause() has been called. 2304 */ 2305 resume: function () 2306 { 2307 this.paused = false; 2308 }, 2309 2310 /** Function: getUniqueId 2311 * Generate a unique ID for use in <iq/> elements. 2312 * 2313 * All <iq/> stanzas are required to have unique id attributes. This 2314 * function makes creating these easy. Each connection instance has 2315 * a counter which starts from zero, and the value of this counter 2316 * plus a colon followed by the suffix becomes the unique id. If no 2317 * suffix is supplied, the counter is used as the unique id. 2318 * 2319 * Suffixes are used to make debugging easier when reading the stream 2320 * data, and their use is recommended. The counter resets to 0 for 2321 * every new connection for the same reason. For connections to the 2322 * same server that authenticate the same way, all the ids should be 2323 * the same, which makes it easy to see changes. This is useful for 2324 * automated testing as well. 2325 * 2326 * Parameters: 2327 * (String) suffix - A optional suffix to append to the id. 2328 * 2329 * Returns: 2330 * A unique string to be used for the id attribute. 2331 */ 2332 getUniqueId: function (suffix) 2333 { 2334 if (typeof(suffix) == "string" || typeof(suffix) == "number") { 2335 return ++this._uniqueId + ":" + suffix; 2336 } else { 2337 return ++this._uniqueId + ""; 2338 } 2339 }, 2340 2341 /** Function: connect 2342 * Starts the connection process. 2343 * 2344 * As the connection process proceeds, the user supplied callback will 2345 * be triggered multiple times with status updates. The callback 2346 * should take two arguments - the status code and the error condition. 2347 * 2348 * The status code will be one of the values in the Strophe.Status 2349 * constants. The error condition will be one of the conditions 2350 * defined in RFC 3920 or the condition 'strophe-parsererror'. 2351 * 2352 * The Parameters _wait_, _hold_ and _route_ are optional and only relevant 2353 * for BOSH connections. Please see XEP 124 for a more detailed explanation 2354 * of the optional parameters. 2355 * 2356 * Parameters: 2357 * (String) jid - The user's JID. This may be a bare JID, 2358 * or a full JID. If a node is not supplied, SASL ANONYMOUS 2359 * authentication will be attempted. 2360 * (String) pass - The user's password. 2361 * (Function) callback - The connect callback function. 2362 * (Integer) wait - The optional HTTPBIND wait value. This is the 2363 * time the server will wait before returning an empty result for 2364 * a request. The default setting of 60 seconds is recommended. 2365 * (Integer) hold - The optional HTTPBIND hold value. This is the 2366 * number of connections the server will hold at one time. This 2367 * should almost always be set to 1 (the default). 2368 * (String) route - The optional route value. 2369 * (String) authcid - The optional alternative authentication identity 2370 * (username) if intending to impersonate another user. 2371 */ 2372 connect: function (jid, pass, callback, wait, hold, route, authcid) 2373 { 2374 this.jid = jid; 2375 /** Variable: authzid 2376 * Authorization identity. 2377 */ 2378 this.authzid = Strophe.getBareJidFromJid(this.jid); 2379 /** Variable: authcid 2380 * Authentication identity (User name). 2381 */ 2382 this.authcid = authcid || Strophe.getNodeFromJid(this.jid); 2383 /** Variable: pass 2384 * Authentication identity (User password). 2385 */ 2386 this.pass = pass; 2387 /** Variable: servtype 2388 * Digest MD5 compatibility. 2389 */ 2390 this.servtype = "xmpp"; 2391 this.connect_callback = callback; 2392 this.disconnecting = false; 2393 this.connected = false; 2394 this.authenticated = false; 2395 this.restored = false; 2396 2397 // parse jid for domain 2398 this.domain = Strophe.getDomainFromJid(this.jid); 2399 2400 this._changeConnectStatus(Strophe.Status.CONNECTING, null); 2401 2402 this._proto._connect(wait, hold, route); 2403 }, 2404 2405 /** Function: attach 2406 * Attach to an already created and authenticated BOSH session. 2407 * 2408 * This function is provided to allow Strophe to attach to BOSH 2409 * sessions which have been created externally, perhaps by a Web 2410 * application. This is often used to support auto-login type features 2411 * without putting user credentials into the page. 2412 * 2413 * Parameters: 2414 * (String) jid - The full JID that is bound by the session. 2415 * (String) sid - The SID of the BOSH session. 2416 * (String) rid - The current RID of the BOSH session. This RID 2417 * will be used by the next request. 2418 * (Function) callback The connect callback function. 2419 * (Integer) wait - The optional HTTPBIND wait value. This is the 2420 * time the server will wait before returning an empty result for 2421 * a request. The default setting of 60 seconds is recommended. 2422 * Other settings will require tweaks to the Strophe.TIMEOUT value. 2423 * (Integer) hold - The optional HTTPBIND hold value. This is the 2424 * number of connections the server will hold at one time. This 2425 * should almost always be set to 1 (the default). 2426 * (Integer) wind - The optional HTTBIND window value. This is the 2427 * allowed range of request ids that are valid. The default is 5. 2428 */ 2429 attach: function (jid, sid, rid, callback, wait, hold, wind) 2430 { 2431 if (this._proto instanceof Strophe.Bosh) { 2432 this._proto._attach(jid, sid, rid, callback, wait, hold, wind); 2433 } else { 2434 throw { 2435 name: 'StropheSessionError', 2436 message: 'The "attach" method can only be used with a BOSH connection.' 2437 }; 2438 } 2439 }, 2440 2441 /** Function: restore 2442 * Attempt to restore a cached BOSH session. 2443 * 2444 * This function is only useful in conjunction with providing the 2445 * "keepalive":true option when instantiating a new Strophe.Connection. 2446 * 2447 * When "keepalive" is set to true, Strophe will cache the BOSH tokens 2448 * RID (Request ID) and SID (Session ID) and then when this function is 2449 * called, it will attempt to restore the session from those cached 2450 * tokens. 2451 * 2452 * This function must therefore be called instead of connect or attach. 2453 * 2454 * For an example on how to use it, please see examples/restore.js 2455 * 2456 * Parameters: 2457 * (String) jid - The user's JID. This may be a bare JID or a full JID. 2458 * (Function) callback - The connect callback function. 2459 * (Integer) wait - The optional HTTPBIND wait value. This is the 2460 * time the server will wait before returning an empty result for 2461 * a request. The default setting of 60 seconds is recommended. 2462 * (Integer) hold - The optional HTTPBIND hold value. This is the 2463 * number of connections the server will hold at one time. This 2464 * should almost always be set to 1 (the default). 2465 * (Integer) wind - The optional HTTBIND window value. This is the 2466 * allowed range of request ids that are valid. The default is 5. 2467 */ 2468 restore: function (jid, callback, wait, hold, wind) 2469 { 2470 if (this._sessionCachingSupported()) { 2471 this._proto._restore(jid, callback, wait, hold, wind); 2472 } else { 2473 throw { 2474 name: 'StropheSessionError', 2475 message: 'The "restore" method can only be used with a BOSH connection.' 2476 }; 2477 } 2478 }, 2479 2480 /** PrivateFunction: _sessionCachingSupported 2481 * Checks whether sessionStorage and JSON are supported and whether we're 2482 * using BOSH. 2483 */ 2484 _sessionCachingSupported: function () 2485 { 2486 if (this._proto instanceof Strophe.Bosh) { 2487 if (!JSON) { return false; } 2488 try { 2489 window.sessionStorage.setItem('_strophe_', '_strophe_'); 2490 window.sessionStorage.removeItem('_strophe_'); 2491 } catch (e) { 2492 return false; 2493 } 2494 return true; 2495 } 2496 return false; 2497 }, 2498 2499 /** Function: xmlInput 2500 * User overrideable function that receives XML data coming into the 2501 * connection. 2502 * 2503 * The default function does nothing. User code can override this with 2504 * > Strophe.Connection.xmlInput = function (elem) { 2505 * > (user code) 2506 * > }; 2507 * 2508 * Due to limitations of current Browsers' XML-Parsers the opening and closing 2509 * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here. 2510 * 2511 * BOSH-Connections will have all stanzas wrapped in a <body> tag. See 2512 * <Strophe.Bosh.strip> if you want to strip this tag. 2513 * 2514 * Parameters: 2515 * (XMLElement) elem - The XML data received by the connection. 2516 */ 2517 /* jshint unused:false */ 2518 xmlInput: function (elem) 2519 { 2520 return; 2521 }, 2522 /* jshint unused:true */ 2523 2524 /** Function: xmlOutput 2525 * User overrideable function that receives XML data sent to the 2526 * connection. 2527 * 2528 * The default function does nothing. User code can override this with 2529 * > Strophe.Connection.xmlOutput = function (elem) { 2530 * > (user code) 2531 * > }; 2532 * 2533 * Due to limitations of current Browsers' XML-Parsers the opening and closing 2534 * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here. 2535 * 2536 * BOSH-Connections will have all stanzas wrapped in a <body> tag. See 2537 * <Strophe.Bosh.strip> if you want to strip this tag. 2538 * 2539 * Parameters: 2540 * (XMLElement) elem - The XMLdata sent by the connection. 2541 */ 2542 /* jshint unused:false */ 2543 xmlOutput: function (elem) 2544 { 2545 return; 2546 }, 2547 /* jshint unused:true */ 2548 2549 /** Function: rawInput 2550 * User overrideable function that receives raw data coming into the 2551 * connection. 2552 * 2553 * The default function does nothing. User code can override this with 2554 * > Strophe.Connection.rawInput = function (data) { 2555 * > (user code) 2556 * > }; 2557 * 2558 * Parameters: 2559 * (String) data - The data received by the connection. 2560 */ 2561 /* jshint unused:false */ 2562 rawInput: function (data) 2563 { 2564 return; 2565 }, 2566 /* jshint unused:true */ 2567 2568 /** Function: rawOutput 2569 * User overrideable function that receives raw data sent to the 2570 * connection. 2571 * 2572 * The default function does nothing. User code can override this with 2573 * > Strophe.Connection.rawOutput = function (data) { 2574 * > (user code) 2575 * > }; 2576 * 2577 * Parameters: 2578 * (String) data - The data sent by the connection. 2579 */ 2580 /* jshint unused:false */ 2581 rawOutput: function (data) 2582 { 2583 return; 2584 }, 2585 /* jshint unused:true */ 2586 2587 /** Function: send 2588 * Send a stanza. 2589 * 2590 * This function is called to push data onto the send queue to 2591 * go out over the wire. Whenever a request is sent to the BOSH 2592 * server, all pending data is sent and the queue is flushed. 2593 * 2594 * Parameters: 2595 * (XMLElement | 2596 * [XMLElement] | 2597 * Strophe.Builder) elem - The stanza to send. 2598 */ 2599 send: function (elem) 2600 { 2601 if (elem === null) { return ; } 2602 if (typeof(elem.sort) === "function") { 2603 for (var i = 0; i < elem.length; i++) { 2604 this._queueData(elem[i]); 2605 } 2606 } else if (typeof(elem.tree) === "function") { 2607 this._queueData(elem.tree()); 2608 } else { 2609 this._queueData(elem); 2610 } 2611 2612 this._proto._send(); 2613 }, 2614 2615 /** Function: flush 2616 * Immediately send any pending outgoing data. 2617 * 2618 * Normally send() queues outgoing data until the next idle period 2619 * (100ms), which optimizes network use in the common cases when 2620 * several send()s are called in succession. flush() can be used to 2621 * immediately send all pending data. 2622 */ 2623 flush: function () 2624 { 2625 // cancel the pending idle period and run the idle function 2626 // immediately 2627 clearTimeout(this._idleTimeout); 2628 this._onIdle(); 2629 }, 2630 2631 /** Function: sendIQ 2632 * Helper function to send IQ stanzas. 2633 * 2634 * Parameters: 2635 * (XMLElement) elem - The stanza to send. 2636 * (Function) callback - The callback function for a successful request. 2637 * (Function) errback - The callback function for a failed or timed 2638 * out request. On timeout, the stanza will be null. 2639 * (Integer) timeout - The time specified in milliseconds for a 2640 * timeout to occur. 2641 * 2642 * Returns: 2643 * The id used to send the IQ. 2644 */ 2645 sendIQ: function(elem, callback, errback, timeout) { 2646 var timeoutHandler = null; 2647 var that = this; 2648 2649 if (typeof(elem.tree) === "function") { 2650 elem = elem.tree(); 2651 } 2652 var id = elem.getAttribute('id'); 2653 2654 // inject id if not found 2655 if (!id) { 2656 id = this.getUniqueId("sendIQ"); 2657 elem.setAttribute("id", id); 2658 } 2659 2660 var expectedFrom = elem.getAttribute("to"); 2661 var fulljid = this.jid; 2662 2663 var handler = this.addHandler(function (stanza) { 2664 // remove timeout handler if there is one 2665 if (timeoutHandler) { 2666 that.deleteTimedHandler(timeoutHandler); 2667 } 2668 2669 var acceptable = false; 2670 var from = stanza.getAttribute("from"); 2671 if (from === expectedFrom || 2672 (expectedFrom === null && 2673 (from === Strophe.getBareJidFromJid(fulljid) || 2674 from === Strophe.getDomainFromJid(fulljid) || 2675 from === fulljid))) { 2676 acceptable = true; 2677 } 2678 2679 if (!acceptable) { 2680 throw { 2681 name: "StropheError", 2682 message: "Got answer to IQ from wrong jid:" + from + 2683 "\nExpected jid: " + expectedFrom 2684 }; 2685 } 2686 2687 var iqtype = stanza.getAttribute('type'); 2688 if (iqtype == 'result') { 2689 if (callback) { 2690 callback(stanza); 2691 } 2692 } else if (iqtype == 'error') { 2693 if (errback) { 2694 errback(stanza); 2695 } 2696 } else { 2697 throw { 2698 name: "StropheError", 2699 message: "Got bad IQ type of " + iqtype 2700 }; 2701 } 2702 }, null, 'iq', ['error', 'result'], id); 2703 2704 // if timeout specified, setup timeout handler. 2705 if (timeout) { 2706 timeoutHandler = this.addTimedHandler(timeout, function () { 2707 // get rid of normal handler 2708 that.deleteHandler(handler); 2709 // call errback on timeout with null stanza 2710 if (errback) { 2711 errback(null); 2712 } 2713 return false; 2714 }); 2715 } 2716 this.send(elem); 2717 return id; 2718 }, 2719 2720 /** PrivateFunction: _queueData 2721 * Queue outgoing data for later sending. Also ensures that the data 2722 * is a DOMElement. 2723 */ 2724 _queueData: function (element) { 2725 if (element === null || 2726 !element.tagName || 2727 !element.childNodes) { 2728 throw { 2729 name: "StropheError", 2730 message: "Cannot queue non-DOMElement." 2731 }; 2732 } 2733 2734 this._data.push(element); 2735 }, 2736 2737 /** PrivateFunction: _sendRestart 2738 * Send an xmpp:restart stanza. 2739 */ 2740 _sendRestart: function () 2741 { 2742 this._data.push("restart"); 2743 2744 this._proto._sendRestart(); 2745 2746 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); 2747 }, 2748 2749 /** Function: addTimedHandler 2750 * Add a timed handler to the connection. 2751 * 2752 * This function adds a timed handler. The provided handler will 2753 * be called every period milliseconds until it returns false, 2754 * the connection is terminated, or the handler is removed. Handlers 2755 * that wish to continue being invoked should return true. 2756 * 2757 * Because of method binding it is necessary to save the result of 2758 * this function if you wish to remove a handler with 2759 * deleteTimedHandler(). 2760 * 2761 * Note that user handlers are not active until authentication is 2762 * successful. 2763 * 2764 * Parameters: 2765 * (Integer) period - The period of the handler. 2766 * (Function) handler - The callback function. 2767 * 2768 * Returns: 2769 * A reference to the handler that can be used to remove it. 2770 */ 2771 addTimedHandler: function (period, handler) 2772 { 2773 var thand = new Strophe.TimedHandler(period, handler); 2774 this.addTimeds.push(thand); 2775 return thand; 2776 }, 2777 2778 /** Function: deleteTimedHandler 2779 * Delete a timed handler for a connection. 2780 * 2781 * This function removes a timed handler from the connection. The 2782 * handRef parameter is *not* the function passed to addTimedHandler(), 2783 * but is the reference returned from addTimedHandler(). 2784 * 2785 * Parameters: 2786 * (Strophe.TimedHandler) handRef - The handler reference. 2787 */ 2788 deleteTimedHandler: function (handRef) 2789 { 2790 // this must be done in the Idle loop so that we don't change 2791 // the handlers during iteration 2792 this.removeTimeds.push(handRef); 2793 }, 2794 2795 /** Function: addHandler 2796 * Add a stanza handler for the connection. 2797 * 2798 * This function adds a stanza handler to the connection. The 2799 * handler callback will be called for any stanza that matches 2800 * the parameters. Note that if multiple parameters are supplied, 2801 * they must all match for the handler to be invoked. 2802 * 2803 * The handler will receive the stanza that triggered it as its argument. 2804 * *The handler should return true if it is to be invoked again; 2805 * returning false will remove the handler after it returns.* 2806 * 2807 * As a convenience, the ns parameters applies to the top level element 2808 * and also any of its immediate children. This is primarily to make 2809 * matching /iq/query elements easy. 2810 * 2811 * The options argument contains handler matching flags that affect how 2812 * matches are determined. Currently the only flag is matchBare (a 2813 * boolean). When matchBare is true, the from parameter and the from 2814 * attribute on the stanza will be matched as bare JIDs instead of 2815 * full JIDs. To use this, pass {matchBare: true} as the value of 2816 * options. The default value for matchBare is false. 2817 * 2818 * The return value should be saved if you wish to remove the handler 2819 * with deleteHandler(). 2820 * 2821 * Parameters: 2822 * (Function) handler - The user callback. 2823 * (String) ns - The namespace to match. 2824 * (String) name - The stanza name to match. 2825 * (String) type - The stanza type attribute to match. 2826 * (String) id - The stanza id attribute to match. 2827 * (String) from - The stanza from attribute to match. 2828 * (String) options - The handler options 2829 * 2830 * Returns: 2831 * A reference to the handler that can be used to remove it. 2832 */ 2833 addHandler: function (handler, ns, name, type, id, from, options) 2834 { 2835 var hand = new Strophe.Handler(handler, ns, name, type, id, from, options); 2836 this.addHandlers.push(hand); 2837 return hand; 2838 }, 2839 2840 /** Function: deleteHandler 2841 * Delete a stanza handler for a connection. 2842 * 2843 * This function removes a stanza handler from the connection. The 2844 * handRef parameter is *not* the function passed to addHandler(), 2845 * but is the reference returned from addHandler(). 2846 * 2847 * Parameters: 2848 * (Strophe.Handler) handRef - The handler reference. 2849 */ 2850 deleteHandler: function (handRef) 2851 { 2852 // this must be done in the Idle loop so that we don't change 2853 // the handlers during iteration 2854 this.removeHandlers.push(handRef); 2855 // If a handler is being deleted while it is being added, 2856 // prevent it from getting added 2857 var i = this.addHandlers.indexOf(handRef); 2858 if (i >= 0) { 2859 this.addHandlers.splice(i, 1); 2860 } 2861 }, 2862 2863 /** Function: disconnect 2864 * Start the graceful disconnection process. 2865 * 2866 * This function starts the disconnection process. This process starts 2867 * by sending unavailable presence and sending BOSH body of type 2868 * terminate. A timeout handler makes sure that disconnection happens 2869 * even if the BOSH server does not respond. 2870 * If the Connection object isn't connected, at least tries to abort all pending requests 2871 * so the connection object won't generate successful requests (which were already opened). 2872 * 2873 * The user supplied connection callback will be notified of the 2874 * progress as this process happens. 2875 * 2876 * Parameters: 2877 * (String) reason - The reason the disconnect is occuring. 2878 */ 2879 disconnect: function (reason) 2880 { 2881 this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason); 2882 2883 Strophe.info("Disconnect was called because: " + reason); 2884 if (this.connected) { 2885 var pres = false; 2886 this.disconnecting = true; 2887 if (this.authenticated) { 2888 pres = $pres({ 2889 xmlns: Strophe.NS.CLIENT, 2890 type: 'unavailable' 2891 }); 2892 } 2893 // setup timeout handler 2894 this._disconnectTimeout = this._addSysTimedHandler( 2895 3000, this._onDisconnectTimeout.bind(this)); 2896 this._proto._disconnect(pres); 2897 } else { 2898 Strophe.info("Disconnect was called before Strophe connected to the server"); 2899 this._proto._abortAllRequests(); 2900 } 2901 }, 2902 2903 /** PrivateFunction: _changeConnectStatus 2904 * _Private_ helper function that makes sure plugins and the user's 2905 * callback are notified of connection status changes. 2906 * 2907 * Parameters: 2908 * (Integer) status - the new connection status, one of the values 2909 * in Strophe.Status 2910 * (String) condition - the error condition or null 2911 */ 2912 _changeConnectStatus: function (status, condition) 2913 { 2914 // notify all plugins listening for status changes 2915 for (var k in Strophe._connectionPlugins) { 2916 if (Strophe._connectionPlugins.hasOwnProperty(k)) { 2917 var plugin = this[k]; 2918 if (plugin.statusChanged) { 2919 try { 2920 plugin.statusChanged(status, condition); 2921 } catch (err) { 2922 Strophe.error("" + k + " plugin caused an exception " + 2923 "changing status: " + err); 2924 } 2925 } 2926 } 2927 } 2928 2929 // notify the user's callback 2930 if (this.connect_callback) { 2931 try { 2932 this.connect_callback(status, condition); 2933 } catch (e) { 2934 Strophe.error("User connection callback caused an " + 2935 "exception: " + e); 2936 } 2937 } 2938 }, 2939 2940 /** PrivateFunction: _doDisconnect 2941 * _Private_ function to disconnect. 2942 * 2943 * This is the last piece of the disconnection logic. This resets the 2944 * connection and alerts the user's connection callback. 2945 */ 2946 _doDisconnect: function (condition) 2947 { 2948 if (typeof this._idleTimeout == "number") { 2949 clearTimeout(this._idleTimeout); 2950 } 2951 2952 // Cancel Disconnect Timeout 2953 if (this._disconnectTimeout !== null) { 2954 this.deleteTimedHandler(this._disconnectTimeout); 2955 this._disconnectTimeout = null; 2956 } 2957 2958 Strophe.info("_doDisconnect was called"); 2959 this._proto._doDisconnect(); 2960 2961 this.authenticated = false; 2962 this.disconnecting = false; 2963 this.restored = false; 2964 2965 // delete handlers 2966 this.handlers = []; 2967 this.timedHandlers = []; 2968 this.removeTimeds = []; 2969 this.removeHandlers = []; 2970 this.addTimeds = []; 2971 this.addHandlers = []; 2972 2973 // tell the parent we disconnected 2974 this._changeConnectStatus(Strophe.Status.DISCONNECTED, condition); 2975 this.connected = false; 2976 }, 2977 2978 /** PrivateFunction: _dataRecv 2979 * _Private_ handler to processes incoming data from the the connection. 2980 * 2981 * Except for _connect_cb handling the initial connection request, 2982 * this function handles the incoming data for all requests. This 2983 * function also fires stanza handlers that match each incoming 2984 * stanza. 2985 * 2986 * Parameters: 2987 * (Strophe.Request) req - The request that has data ready. 2988 * (string) req - The stanza a raw string (optiona). 2989 */ 2990 _dataRecv: function (req, raw) 2991 { 2992 Strophe.info("_dataRecv called"); 2993 var elem = this._proto._reqToData(req); 2994 if (elem === null) { return; } 2995 2996 if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { 2997 if (elem.nodeName === this._proto.strip && elem.childNodes.length) { 2998 this.xmlInput(elem.childNodes[0]); 2999 } else { 3000 this.xmlInput(elem); 3001 } 3002 } 3003 if (this.rawInput !== Strophe.Connection.prototype.rawInput) { 3004 if (raw) { 3005 this.rawInput(raw); 3006 } else { 3007 this.rawInput(Strophe.serialize(elem)); 3008 } 3009 } 3010 3011 // remove handlers scheduled for deletion 3012 var i, hand; 3013 while (this.removeHandlers.length > 0) { 3014 hand = this.removeHandlers.pop(); 3015 i = this.handlers.indexOf(hand); 3016 if (i >= 0) { 3017 this.handlers.splice(i, 1); 3018 } 3019 } 3020 3021 // add handlers scheduled for addition 3022 while (this.addHandlers.length > 0) { 3023 this.handlers.push(this.addHandlers.pop()); 3024 } 3025 3026 // handle graceful disconnect 3027 if (this.disconnecting && this._proto._emptyQueue()) { 3028 this._doDisconnect(); 3029 return; 3030 } 3031 3032 var type = elem.getAttribute("type"); 3033 var cond, conflict; 3034 if (type !== null && type == "terminate") { 3035 // Don't process stanzas that come in after disconnect 3036 if (this.disconnecting) { 3037 return; 3038 } 3039 3040 // an error occurred 3041 cond = elem.getAttribute("condition"); 3042 conflict = elem.getElementsByTagName("conflict"); 3043 if (cond !== null) { 3044 if (cond == "remote-stream-error" && conflict.length > 0) { 3045 cond = "conflict"; 3046 } 3047 this._changeConnectStatus(Strophe.Status.CONNFAIL, cond); 3048 } else { 3049 this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); 3050 } 3051 this._doDisconnect(cond); 3052 return; 3053 } 3054 3055 // send each incoming stanza through the handler chain 3056 var that = this; 3057 Strophe.forEachChild(elem, null, function (child) { 3058 var i, newList; 3059 // process handlers 3060 newList = that.handlers; 3061 that.handlers = []; 3062 for (i = 0; i < newList.length; i++) { 3063 var hand = newList[i]; 3064 // encapsulate 'handler.run' not to lose the whole handler list if 3065 // one of the handlers throws an exception 3066 try { 3067 if (hand.isMatch(child) && 3068 (that.authenticated || !hand.user)) { 3069 if (hand.run(child)) { 3070 that.handlers.push(hand); 3071 } 3072 } else { 3073 that.handlers.push(hand); 3074 } 3075 } catch(e) { 3076 // if the handler throws an exception, we consider it as false 3077 Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message); 3078 } 3079 } 3080 }); 3081 }, 3082 3083 3084 /** Attribute: mechanisms 3085 * SASL Mechanisms available for Conncection. 3086 */ 3087 mechanisms: {}, 3088 3089 /** PrivateFunction: _connect_cb 3090 * _Private_ handler for initial connection request. 3091 * 3092 * This handler is used to process the initial connection request 3093 * response from the BOSH server. It is used to set up authentication 3094 * handlers and start the authentication process. 3095 * 3096 * SASL authentication will be attempted if available, otherwise 3097 * the code will fall back to legacy authentication. 3098 * 3099 * Parameters: 3100 * (Strophe.Request) req - The current request. 3101 * (Function) _callback - low level (xmpp) connect callback function. 3102 * Useful for plugins with their own xmpp connect callback (when their) 3103 * want to do something special). 3104 */ 3105 _connect_cb: function (req, _callback, raw) 3106 { 3107 Strophe.info("_connect_cb was called"); 3108 3109 this.connected = true; 3110 3111 var bodyWrap = this._proto._reqToData(req); 3112 if (!bodyWrap) { return; } 3113 3114 if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { 3115 if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) { 3116 this.xmlInput(bodyWrap.childNodes[0]); 3117 } else { 3118 this.xmlInput(bodyWrap); 3119 } 3120 } 3121 if (this.rawInput !== Strophe.Connection.prototype.rawInput) { 3122 if (raw) { 3123 this.rawInput(raw); 3124 } else { 3125 this.rawInput(Strophe.serialize(bodyWrap)); 3126 } 3127 } 3128 3129 var conncheck = this._proto._connect_cb(bodyWrap); 3130 if (conncheck === Strophe.Status.CONNFAIL) { 3131 return; 3132 } 3133 3134 this._authentication.sasl_scram_sha1 = false; 3135 this._authentication.sasl_plain = false; 3136 this._authentication.sasl_digest_md5 = false; 3137 this._authentication.sasl_anonymous = false; 3138 3139 this._authentication.legacy_auth = false; 3140 3141 // Check for the stream:features tag 3142 var hasFeatures; 3143 if (bodyWrap.getElementsByTagNameNS) { 3144 hasFeatures = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "features").length > 0; 3145 } else { 3146 hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0 || bodyWrap.getElementsByTagName("features").length > 0; 3147 } 3148 var mechanisms = bodyWrap.getElementsByTagName("mechanism"); 3149 var matched = []; 3150 var i, mech, found_authentication = false; 3151 if (!hasFeatures) { 3152 this._proto._no_auth_received(_callback); 3153 return; 3154 } 3155 if (mechanisms.length > 0) { 3156 for (i = 0; i < mechanisms.length; i++) { 3157 mech = Strophe.getText(mechanisms[i]); 3158 if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]); 3159 } 3160 } 3161 this._authentication.legacy_auth = 3162 bodyWrap.getElementsByTagName("auth").length > 0; 3163 found_authentication = this._authentication.legacy_auth || 3164 matched.length > 0; 3165 if (!found_authentication) { 3166 this._proto._no_auth_received(_callback); 3167 return; 3168 } 3169 if (this.do_authentication !== false) 3170 this.authenticate(matched); 3171 }, 3172 3173 /** Function: authenticate 3174 * Set up authentication 3175 * 3176 * Contiunues the initial connection request by setting up authentication 3177 * handlers and start the authentication process. 3178 * 3179 * SASL authentication will be attempted if available, otherwise 3180 * the code will fall back to legacy authentication. 3181 * 3182 */ 3183 authenticate: function (matched) 3184 { 3185 var i; 3186 // Sorting matched mechanisms according to priority. 3187 for (i = 0; i < matched.length - 1; ++i) { 3188 var higher = i; 3189 for (var j = i + 1; j < matched.length; ++j) { 3190 if (matched[j].prototype.priority > matched[higher].prototype.priority) { 3191 higher = j; 3192 } 3193 } 3194 if (higher != i) { 3195 var swap = matched[i]; 3196 matched[i] = matched[higher]; 3197 matched[higher] = swap; 3198 } 3199 } 3200 3201 // run each mechanism 3202 var mechanism_found = false; 3203 for (i = 0; i < matched.length; ++i) { 3204 if (!matched[i].test(this)) continue; 3205 3206 this._sasl_success_handler = this._addSysHandler( 3207 this._sasl_success_cb.bind(this), null, 3208 "success", null, null); 3209 this._sasl_failure_handler = this._addSysHandler( 3210 this._sasl_failure_cb.bind(this), null, 3211 "failure", null, null); 3212 this._sasl_challenge_handler = this._addSysHandler( 3213 this._sasl_challenge_cb.bind(this), null, 3214 "challenge", null, null); 3215 3216 this._sasl_mechanism = new matched[i](); 3217 this._sasl_mechanism.onStart(this); 3218 3219 var request_auth_exchange = $build("auth", { 3220 xmlns: Strophe.NS.SASL, 3221 mechanism: this._sasl_mechanism.name 3222 }); 3223 3224 if (this._sasl_mechanism.isClientFirst) { 3225 var response = this._sasl_mechanism.onChallenge(this, null); 3226 request_auth_exchange.t(Base64.encode(response)); 3227 } 3228 3229 this.send(request_auth_exchange.tree()); 3230 3231 mechanism_found = true; 3232 break; 3233 } 3234 3235 if (!mechanism_found) { 3236 // if none of the mechanism worked 3237 if (Strophe.getNodeFromJid(this.jid) === null) { 3238 // we don't have a node, which is required for non-anonymous 3239 // client connections 3240 this._changeConnectStatus(Strophe.Status.CONNFAIL, 3241 'x-strophe-bad-non-anon-jid'); 3242 this.disconnect('x-strophe-bad-non-anon-jid'); 3243 } else { 3244 // fall back to legacy authentication 3245 this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null); 3246 this._addSysHandler(this._auth1_cb.bind(this), null, null, 3247 null, "_auth_1"); 3248 3249 this.send($iq({ 3250 type: "get", 3251 to: this.domain, 3252 id: "_auth_1" 3253 }).c("query", { 3254 xmlns: Strophe.NS.AUTH 3255 }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree()); 3256 } 3257 } 3258 3259 }, 3260 3261 _sasl_challenge_cb: function(elem) { 3262 var challenge = Base64.decode(Strophe.getText(elem)); 3263 var response = this._sasl_mechanism.onChallenge(this, challenge); 3264 3265 var stanza = $build('response', { 3266 xmlns: Strophe.NS.SASL 3267 }); 3268 if (response !== "") { 3269 stanza.t(Base64.encode(response)); 3270 } 3271 this.send(stanza.tree()); 3272 3273 return true; 3274 }, 3275 3276 /** PrivateFunction: _auth1_cb 3277 * _Private_ handler for legacy authentication. 3278 * 3279 * This handler is called in response to the initial <iq type='get'/> 3280 * for legacy authentication. It builds an authentication <iq/> and 3281 * sends it, creating a handler (calling back to _auth2_cb()) to 3282 * handle the result 3283 * 3284 * Parameters: 3285 * (XMLElement) elem - The stanza that triggered the callback. 3286 * 3287 * Returns: 3288 * false to remove the handler. 3289 */ 3290 /* jshint unused:false */ 3291 _auth1_cb: function (elem) 3292 { 3293 // build plaintext auth iq 3294 var iq = $iq({type: "set", id: "_auth_2"}) 3295 .c('query', {xmlns: Strophe.NS.AUTH}) 3296 .c('username', {}).t(Strophe.getNodeFromJid(this.jid)) 3297 .up() 3298 .c('password').t(this.pass); 3299 3300 if (!Strophe.getResourceFromJid(this.jid)) { 3301 // since the user has not supplied a resource, we pick 3302 // a default one here. unlike other auth methods, the server 3303 // cannot do this for us. 3304 this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe'; 3305 } 3306 iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid)); 3307 3308 this._addSysHandler(this._auth2_cb.bind(this), null, 3309 null, null, "_auth_2"); 3310 3311 this.send(iq.tree()); 3312 3313 return false; 3314 }, 3315 /* jshint unused:true */ 3316 3317 /** PrivateFunction: _sasl_success_cb 3318 * _Private_ handler for succesful SASL authentication. 3319 * 3320 * Parameters: 3321 * (XMLElement) elem - The matching stanza. 3322 * 3323 * Returns: 3324 * false to remove the handler. 3325 */ 3326 _sasl_success_cb: function (elem) 3327 { 3328 if (this._sasl_data["server-signature"]) { 3329 var serverSignature; 3330 var success = Base64.decode(Strophe.getText(elem)); 3331 var attribMatch = /([a-z]+)=([^,]+)(,|$)/; 3332 var matches = success.match(attribMatch); 3333 if (matches[1] == "v") { 3334 serverSignature = matches[2]; 3335 } 3336 3337 if (serverSignature != this._sasl_data["server-signature"]) { 3338 // remove old handlers 3339 this.deleteHandler(this._sasl_failure_handler); 3340 this._sasl_failure_handler = null; 3341 if (this._sasl_challenge_handler) { 3342 this.deleteHandler(this._sasl_challenge_handler); 3343 this._sasl_challenge_handler = null; 3344 } 3345 3346 this._sasl_data = {}; 3347 return this._sasl_failure_cb(null); 3348 } 3349 } 3350 3351 Strophe.info("SASL authentication succeeded."); 3352 3353 if(this._sasl_mechanism) 3354 this._sasl_mechanism.onSuccess(); 3355 3356 // remove old handlers 3357 this.deleteHandler(this._sasl_failure_handler); 3358 this._sasl_failure_handler = null; 3359 if (this._sasl_challenge_handler) { 3360 this.deleteHandler(this._sasl_challenge_handler); 3361 this._sasl_challenge_handler = null; 3362 } 3363 3364 var streamfeature_handlers = []; 3365 var wrapper = function(handlers, elem) { 3366 while (handlers.length) { 3367 this.deleteHandler(handlers.pop()); 3368 } 3369 this._sasl_auth1_cb.bind(this)(elem); 3370 return false; 3371 }; 3372 streamfeature_handlers.push(this._addSysHandler(function(elem) { 3373 wrapper.bind(this)(streamfeature_handlers, elem); 3374 }.bind(this), null, "stream:features", null, null)); 3375 streamfeature_handlers.push(this._addSysHandler(function(elem) { 3376 wrapper.bind(this)(streamfeature_handlers, elem); 3377 }.bind(this), Strophe.NS.STREAM, "features", null, null)); 3378 3379 // we must send an xmpp:restart now 3380 this._sendRestart(); 3381 3382 return false; 3383 }, 3384 3385 /** PrivateFunction: _sasl_auth1_cb 3386 * _Private_ handler to start stream binding. 3387 * 3388 * Parameters: 3389 * (XMLElement) elem - The matching stanza. 3390 * 3391 * Returns: 3392 * false to remove the handler. 3393 */ 3394 _sasl_auth1_cb: function (elem) 3395 { 3396 // save stream:features for future usage 3397 this.features = elem; 3398 3399 var i, child; 3400 3401 for (i = 0; i < elem.childNodes.length; i++) { 3402 child = elem.childNodes[i]; 3403 if (child.nodeName == 'bind') { 3404 this.do_bind = true; 3405 } 3406 3407 if (child.nodeName == 'session') { 3408 this.do_session = true; 3409 } 3410 } 3411 3412 if (!this.do_bind) { 3413 this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); 3414 return false; 3415 } else { 3416 this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, 3417 null, "_bind_auth_2"); 3418 3419 var resource = Strophe.getResourceFromJid(this.jid); 3420 if (resource) { 3421 this.send($iq({type: "set", id: "_bind_auth_2"}) 3422 .c('bind', {xmlns: Strophe.NS.BIND}) 3423 .c('resource', {}).t(resource).tree()); 3424 } else { 3425 this.send($iq({type: "set", id: "_bind_auth_2"}) 3426 .c('bind', {xmlns: Strophe.NS.BIND}) 3427 .tree()); 3428 } 3429 } 3430 3431 return false; 3432 }, 3433 3434 /** PrivateFunction: _sasl_bind_cb 3435 * _Private_ handler for binding result and session start. 3436 * 3437 * Parameters: 3438 * (XMLElement) elem - The matching stanza. 3439 * 3440 * Returns: 3441 * false to remove the handler. 3442 */ 3443 _sasl_bind_cb: function (elem) 3444 { 3445 if (elem.getAttribute("type") == "error") { 3446 Strophe.info("SASL binding failed."); 3447 var conflict = elem.getElementsByTagName("conflict"), condition; 3448 if (conflict.length > 0) { 3449 condition = 'conflict'; 3450 } 3451 this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition); 3452 return false; 3453 } 3454 3455 // TODO - need to grab errors 3456 var bind = elem.getElementsByTagName("bind"); 3457 var jidNode; 3458 if (bind.length > 0) { 3459 // Grab jid 3460 jidNode = bind[0].getElementsByTagName("jid"); 3461 if (jidNode.length > 0) { 3462 this.jid = Strophe.getText(jidNode[0]); 3463 3464 if (this.do_session) { 3465 this._addSysHandler(this._sasl_session_cb.bind(this), 3466 null, null, null, "_session_auth_2"); 3467 3468 this.send($iq({type: "set", id: "_session_auth_2"}) 3469 .c('session', {xmlns: Strophe.NS.SESSION}) 3470 .tree()); 3471 } else { 3472 this.authenticated = true; 3473 this._changeConnectStatus(Strophe.Status.CONNECTED, null); 3474 } 3475 } 3476 } else { 3477 Strophe.info("SASL binding failed."); 3478 this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); 3479 return false; 3480 } 3481 }, 3482 3483 /** PrivateFunction: _sasl_session_cb 3484 * _Private_ handler to finish successful SASL connection. 3485 * 3486 * This sets Connection.authenticated to true on success, which 3487 * starts the processing of user handlers. 3488 * 3489 * Parameters: 3490 * (XMLElement) elem - The matching stanza. 3491 * 3492 * Returns: 3493 * false to remove the handler. 3494 */ 3495 _sasl_session_cb: function (elem) 3496 { 3497 if (elem.getAttribute("type") == "result") { 3498 this.authenticated = true; 3499 this._changeConnectStatus(Strophe.Status.CONNECTED, null); 3500 } else if (elem.getAttribute("type") == "error") { 3501 Strophe.info("Session creation failed."); 3502 this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); 3503 return false; 3504 } 3505 3506 return false; 3507 }, 3508 3509 /** PrivateFunction: _sasl_failure_cb 3510 * _Private_ handler for SASL authentication failure. 3511 * 3512 * Parameters: 3513 * (XMLElement) elem - The matching stanza. 3514 * 3515 * Returns: 3516 * false to remove the handler. 3517 */ 3518 /* jshint unused:false */ 3519 _sasl_failure_cb: function (elem) 3520 { 3521 // delete unneeded handlers 3522 if (this._sasl_success_handler) { 3523 this.deleteHandler(this._sasl_success_handler); 3524 this._sasl_success_handler = null; 3525 } 3526 if (this._sasl_challenge_handler) { 3527 this.deleteHandler(this._sasl_challenge_handler); 3528 this._sasl_challenge_handler = null; 3529 } 3530 3531 if(this._sasl_mechanism) 3532 this._sasl_mechanism.onFailure(); 3533 this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); 3534 return false; 3535 }, 3536 /* jshint unused:true */ 3537 3538 /** PrivateFunction: _auth2_cb 3539 * _Private_ handler to finish legacy authentication. 3540 * 3541 * This handler is called when the result from the jabber:iq:auth 3542 * <iq/> stanza is returned. 3543 * 3544 * Parameters: 3545 * (XMLElement) elem - The stanza that triggered the callback. 3546 * 3547 * Returns: 3548 * false to remove the handler. 3549 */ 3550 _auth2_cb: function (elem) 3551 { 3552 if (elem.getAttribute("type") == "result") { 3553 this.authenticated = true; 3554 this._changeConnectStatus(Strophe.Status.CONNECTED, null); 3555 } else if (elem.getAttribute("type") == "error") { 3556 this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); 3557 this.disconnect('authentication failed'); 3558 } 3559 3560 return false; 3561 }, 3562 3563 /** PrivateFunction: _addSysTimedHandler 3564 * _Private_ function to add a system level timed handler. 3565 * 3566 * This function is used to add a Strophe.TimedHandler for the 3567 * library code. System timed handlers are allowed to run before 3568 * authentication is complete. 3569 * 3570 * Parameters: 3571 * (Integer) period - The period of the handler. 3572 * (Function) handler - The callback function. 3573 */ 3574 _addSysTimedHandler: function (period, handler) 3575 { 3576 var thand = new Strophe.TimedHandler(period, handler); 3577 thand.user = false; 3578 this.addTimeds.push(thand); 3579 return thand; 3580 }, 3581 3582 /** PrivateFunction: _addSysHandler 3583 * _Private_ function to add a system level stanza handler. 3584 * 3585 * This function is used to add a Strophe.Handler for the 3586 * library code. System stanza handlers are allowed to run before 3587 * authentication is complete. 3588 * 3589 * Parameters: 3590 * (Function) handler - The callback function. 3591 * (String) ns - The namespace to match. 3592 * (String) name - The stanza name to match. 3593 * (String) type - The stanza type attribute to match. 3594 * (String) id - The stanza id attribute to match. 3595 */ 3596 _addSysHandler: function (handler, ns, name, type, id) 3597 { 3598 var hand = new Strophe.Handler(handler, ns, name, type, id); 3599 hand.user = false; 3600 this.addHandlers.push(hand); 3601 return hand; 3602 }, 3603 3604 /** PrivateFunction: _onDisconnectTimeout 3605 * _Private_ timeout handler for handling non-graceful disconnection. 3606 * 3607 * If the graceful disconnect process does not complete within the 3608 * time allotted, this handler finishes the disconnect anyway. 3609 * 3610 * Returns: 3611 * false to remove the handler. 3612 */ 3613 _onDisconnectTimeout: function () 3614 { 3615 Strophe.info("_onDisconnectTimeout was called"); 3616 3617 this._proto._onDisconnectTimeout(); 3618 3619 // actually disconnect 3620 this._doDisconnect(); 3621 3622 return false; 3623 }, 3624 3625 /** PrivateFunction: _onIdle 3626 * _Private_ handler to process events during idle cycle. 3627 * 3628 * This handler is called every 100ms to fire timed handlers that 3629 * are ready and keep poll requests going. 3630 */ 3631 _onIdle: function () 3632 { 3633 var i, thand, since, newList; 3634 3635 // add timed handlers scheduled for addition 3636 // NOTE: we add before remove in the case a timed handler is 3637 // added and then deleted before the next _onIdle() call. 3638 while (this.addTimeds.length > 0) { 3639 this.timedHandlers.push(this.addTimeds.pop()); 3640 } 3641 3642 // remove timed handlers that have been scheduled for deletion 3643 while (this.removeTimeds.length > 0) { 3644 thand = this.removeTimeds.pop(); 3645 i = this.timedHandlers.indexOf(thand); 3646 if (i >= 0) { 3647 this.timedHandlers.splice(i, 1); 3648 } 3649 } 3650 3651 // call ready timed handlers 3652 var now = new Date().getTime(); 3653 newList = []; 3654 for (i = 0; i < this.timedHandlers.length; i++) { 3655 thand = this.timedHandlers[i]; 3656 if (this.authenticated || !thand.user) { 3657 since = thand.lastCalled + thand.period; 3658 if (since - now <= 0) { 3659 if (thand.run()) { 3660 newList.push(thand); 3661 } 3662 } else { 3663 newList.push(thand); 3664 } 3665 } 3666 } 3667 this.timedHandlers = newList; 3668 3669 clearTimeout(this._idleTimeout); 3670 3671 this._proto._onIdle(); 3672 3673 // reactivate the timer only if connected 3674 if (this.connected) { 3675 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); 3676 } 3677 } 3678 }; 3679 3680 /** Class: Strophe.SASLMechanism 3681 * 3682 * encapsulates SASL authentication mechanisms. 3683 * 3684 * User code may override the priority for each mechanism or disable it completely. 3685 * See <priority> for information about changing priority and <test> for informatian on 3686 * how to disable a mechanism. 3687 * 3688 * By default, all mechanisms are enabled and the priorities are 3689 * 3690 * SCRAM-SHA1 - 40 3691 * DIGEST-MD5 - 30 3692 * Plain - 20 3693 */ 3694 3695 /** 3696 * PrivateConstructor: Strophe.SASLMechanism 3697 * SASL auth mechanism abstraction. 3698 * 3699 * Parameters: 3700 * (String) name - SASL Mechanism name. 3701 * (Boolean) isClientFirst - If client should send response first without challenge. 3702 * (Number) priority - Priority. 3703 * 3704 * Returns: 3705 * A new Strophe.SASLMechanism object. 3706 */ 3707 Strophe.SASLMechanism = function(name, isClientFirst, priority) { 3708 /** PrivateVariable: name 3709 * Mechanism name. 3710 */ 3711 this.name = name; 3712 /** PrivateVariable: isClientFirst 3713 * If client sends response without initial server challenge. 3714 */ 3715 this.isClientFirst = isClientFirst; 3716 /** Variable: priority 3717 * Determines which <SASLMechanism> is chosen for authentication (Higher is better). 3718 * Users may override this to prioritize mechanisms differently. 3719 * 3720 * In the default configuration the priorities are 3721 * 3722 * SCRAM-SHA1 - 40 3723 * DIGEST-MD5 - 30 3724 * Plain - 20 3725 * 3726 * Example: (This will cause Strophe to choose the mechanism that the server sent first) 3727 * 3728 * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority; 3729 * 3730 * See <SASL mechanisms> for a list of available mechanisms. 3731 * 3732 */ 3733 this.priority = priority; 3734 }; 3735 3736 Strophe.SASLMechanism.prototype = { 3737 /** 3738 * Function: test 3739 * Checks if mechanism able to run. 3740 * To disable a mechanism, make this return false; 3741 * 3742 * To disable plain authentication run 3743 * > Strophe.SASLPlain.test = function() { 3744 * > return false; 3745 * > } 3746 * 3747 * See <SASL mechanisms> for a list of available mechanisms. 3748 * 3749 * Parameters: 3750 * (Strophe.Connection) connection - Target Connection. 3751 * 3752 * Returns: 3753 * (Boolean) If mechanism was able to run. 3754 */ 3755 /* jshint unused:false */ 3756 test: function(connection) { 3757 return true; 3758 }, 3759 /* jshint unused:true */ 3760 3761 /** PrivateFunction: onStart 3762 * Called before starting mechanism on some connection. 3763 * 3764 * Parameters: 3765 * (Strophe.Connection) connection - Target Connection. 3766 */ 3767 onStart: function(connection) 3768 { 3769 this._connection = connection; 3770 }, 3771 3772 /** PrivateFunction: onChallenge 3773 * Called by protocol implementation on incoming challenge. If client is 3774 * first (isClientFirst == true) challenge will be null on the first call. 3775 * 3776 * Parameters: 3777 * (Strophe.Connection) connection - Target Connection. 3778 * (String) challenge - current challenge to handle. 3779 * 3780 * Returns: 3781 * (String) Mechanism response. 3782 */ 3783 /* jshint unused:false */ 3784 onChallenge: function(connection, challenge) { 3785 throw new Error("You should implement challenge handling!"); 3786 }, 3787 /* jshint unused:true */ 3788 3789 /** PrivateFunction: onFailure 3790 * Protocol informs mechanism implementation about SASL failure. 3791 */ 3792 onFailure: function() { 3793 this._connection = null; 3794 }, 3795 3796 /** PrivateFunction: onSuccess 3797 * Protocol informs mechanism implementation about SASL success. 3798 */ 3799 onSuccess: function() { 3800 this._connection = null; 3801 } 3802 }; 3803 3804 /** Constants: SASL mechanisms 3805 * Available authentication mechanisms 3806 * 3807 * Strophe.SASLAnonymous - SASL Anonymous authentication. 3808 * Strophe.SASLPlain - SASL Plain authentication. 3809 * Strophe.SASLMD5 - SASL Digest-MD5 authentication 3810 * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication 3811 */ 3812 3813 // Building SASL callbacks 3814 3815 /** PrivateConstructor: SASLAnonymous 3816 * SASL Anonymous authentication. 3817 */ 3818 Strophe.SASLAnonymous = function() {}; 3819 3820 Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 10); 3821 3822 Strophe.SASLAnonymous.test = function(connection) { 3823 return connection.authcid === null; 3824 }; 3825 3826 Strophe.Connection.prototype.mechanisms[Strophe.SASLAnonymous.prototype.name] = Strophe.SASLAnonymous; 3827 3828 /** PrivateConstructor: SASLPlain 3829 * SASL Plain authentication. 3830 */ 3831 Strophe.SASLPlain = function() {}; 3832 3833 Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 20); 3834 3835 Strophe.SASLPlain.test = function(connection) { 3836 return connection.authcid !== null; 3837 }; 3838 3839 Strophe.SASLPlain.prototype.onChallenge = function(connection) { 3840 var auth_str = connection.authzid; 3841 auth_str = auth_str + "\u0000"; 3842 auth_str = auth_str + connection.authcid; 3843 auth_str = auth_str + "\u0000"; 3844 auth_str = auth_str + connection.pass; 3845 return auth_str; 3846 }; 3847 3848 Strophe.Connection.prototype.mechanisms[Strophe.SASLPlain.prototype.name] = Strophe.SASLPlain; 3849 3850 /** PrivateConstructor: SASLSHA1 3851 * SASL SCRAM SHA 1 authentication. 3852 */ 3853 Strophe.SASLSHA1 = function() {}; 3854 3855 /* TEST: 3856 * This is a simple example of a SCRAM-SHA-1 authentication exchange 3857 * when the client doesn't support channel bindings (username 'user' and 3858 * password 'pencil' are used): 3859 * 3860 * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL 3861 * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92, 3862 * i=4096 3863 * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j, 3864 * p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts= 3865 * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ= 3866 * 3867 */ 3868 3869 Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 40); 3870 3871 Strophe.SASLSHA1.test = function(connection) { 3872 return connection.authcid !== null; 3873 }; 3874 3875 Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) { 3876 var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890); 3877 3878 var auth_str = "n=" + connection.authcid; 3879 auth_str += ",r="; 3880 auth_str += cnonce; 3881 3882 connection._sasl_data.cnonce = cnonce; 3883 connection._sasl_data["client-first-message-bare"] = auth_str; 3884 3885 auth_str = "n,," + auth_str; 3886 3887 this.onChallenge = function (connection, challenge) 3888 { 3889 var nonce, salt, iter, Hi, U, U_old, i, k; 3890 var clientKey, serverKey, clientSignature; 3891 var responseText = "c=biws,"; 3892 var authMessage = connection._sasl_data["client-first-message-bare"] + "," + 3893 challenge + ","; 3894 var cnonce = connection._sasl_data.cnonce; 3895 var attribMatch = /([a-z]+)=([^,]+)(,|$)/; 3896 3897 while (challenge.match(attribMatch)) { 3898 var matches = challenge.match(attribMatch); 3899 challenge = challenge.replace(matches[0], ""); 3900 switch (matches[1]) { 3901 case "r": 3902 nonce = matches[2]; 3903 break; 3904 case "s": 3905 salt = matches[2]; 3906 break; 3907 case "i": 3908 iter = matches[2]; 3909 break; 3910 } 3911 } 3912 3913 if (nonce.substr(0, cnonce.length) !== cnonce) { 3914 connection._sasl_data = {}; 3915 return connection._sasl_failure_cb(); 3916 } 3917 3918 responseText += "r=" + nonce; 3919 authMessage += responseText; 3920 3921 salt = Base64.decode(salt); 3922 salt += "\x00\x00\x00\x01"; 3923 3924 Hi = U_old = SHA1.core_hmac_sha1(connection.pass, salt); 3925 for (i = 1; i < iter; i++) { 3926 U = SHA1.core_hmac_sha1(connection.pass, SHA1.binb2str(U_old)); 3927 for (k = 0; k < 5; k++) { 3928 Hi[k] ^= U[k]; 3929 } 3930 U_old = U; 3931 } 3932 Hi = SHA1.binb2str(Hi); 3933 3934 clientKey = SHA1.core_hmac_sha1(Hi, "Client Key"); 3935 serverKey = SHA1.str_hmac_sha1(Hi, "Server Key"); 3936 clientSignature = SHA1.core_hmac_sha1(SHA1.str_sha1(SHA1.binb2str(clientKey)), authMessage); 3937 connection._sasl_data["server-signature"] = SHA1.b64_hmac_sha1(serverKey, authMessage); 3938 3939 for (k = 0; k < 5; k++) { 3940 clientKey[k] ^= clientSignature[k]; 3941 } 3942 3943 responseText += ",p=" + Base64.encode(SHA1.binb2str(clientKey)); 3944 3945 return responseText; 3946 }.bind(this); 3947 3948 return auth_str; 3949 }; 3950 3951 Strophe.Connection.prototype.mechanisms[Strophe.SASLSHA1.prototype.name] = Strophe.SASLSHA1; 3952 3953 /** PrivateConstructor: SASLMD5 3954 * SASL DIGEST MD5 authentication. 3955 */ 3956 Strophe.SASLMD5 = function() {}; 3957 3958 Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 30); 3959 3960 Strophe.SASLMD5.test = function(connection) { 3961 return connection.authcid !== null; 3962 }; 3963 3964 /** PrivateFunction: _quote 3965 * _Private_ utility function to backslash escape and quote strings. 3966 * 3967 * Parameters: 3968 * (String) str - The string to be quoted. 3969 * 3970 * Returns: 3971 * quoted string 3972 */ 3973 Strophe.SASLMD5.prototype._quote = function (str) 3974 { 3975 return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; 3976 //" end string workaround for emacs 3977 }; 3978 3979 3980 Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) { 3981 var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; 3982 var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890)); 3983 var realm = ""; 3984 var host = null; 3985 var nonce = ""; 3986 var qop = ""; 3987 var matches; 3988 3989 while (challenge.match(attribMatch)) { 3990 matches = challenge.match(attribMatch); 3991 challenge = challenge.replace(matches[0], ""); 3992 matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); 3993 switch (matches[1]) { 3994 case "realm": 3995 realm = matches[2]; 3996 break; 3997 case "nonce": 3998 nonce = matches[2]; 3999 break; 4000 case "qop": 4001 qop = matches[2]; 4002 break; 4003 case "host": 4004 host = matches[2]; 4005 break; 4006 } 4007 } 4008 4009 var digest_uri = connection.servtype + "/" + connection.domain; 4010 if (host !== null) { 4011 digest_uri = digest_uri + "/" + host; 4012 } 4013 4014 var A1 = MD5.hash(connection.authcid + 4015 ":" + realm + ":" + this._connection.pass) + 4016 ":" + nonce + ":" + cnonce; 4017 var A2 = 'AUTHENTICATE:' + digest_uri; 4018 4019 var responseText = ""; 4020 responseText += 'charset=utf-8,'; 4021 responseText += 'username=' + 4022 this._quote(connection.authcid) + ','; 4023 responseText += 'realm=' + this._quote(realm) + ','; 4024 responseText += 'nonce=' + this._quote(nonce) + ','; 4025 responseText += 'nc=00000001,'; 4026 responseText += 'cnonce=' + this._quote(cnonce) + ','; 4027 responseText += 'digest-uri=' + this._quote(digest_uri) + ','; 4028 responseText += 'response=' + MD5.hexdigest(MD5.hexdigest(A1) + ":" + 4029 nonce + ":00000001:" + 4030 cnonce + ":auth:" + 4031 MD5.hexdigest(A2)) + ","; 4032 responseText += 'qop=auth'; 4033 4034 this.onChallenge = function () 4035 { 4036 return ""; 4037 }.bind(this); 4038 4039 return responseText; 4040 }; 4041 4042 Strophe.Connection.prototype.mechanisms[Strophe.SASLMD5.prototype.name] = Strophe.SASLMD5; 4043 4044 return { 4045 Strophe: Strophe, 4046 $build: $build, 4047 $msg: $msg, 4048 $iq: $iq, 4049 $pres: $pres, 4050 SHA1: SHA1, 4051 Base64: Base64, 4052 MD5: MD5, 4053 }; 4054 })); 4055 4056 /* 4057 This program is distributed under the terms of the MIT license. 4058 Please see the LICENSE file for details. 4059 4060 Copyright 2006-2008, OGG, LLC 4061 */ 4062 4063 /* jshint undef: true, unused: true:, noarg: true, latedef: true */ 4064 /* global define, window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject, Strophe, $build */ 4065 4066 (function (root, factory) { 4067 if (typeof define === 'function' && define.amd) { 4068 define('strophe-bosh', ['strophe-core'], function (core) { 4069 return factory( 4070 core.Strophe, 4071 core.$build 4072 ); 4073 }); 4074 } else { 4075 // Browser globals 4076 return factory(Strophe, $build); 4077 } 4078 }(this, function (Strophe, $build) { 4079 4080 /** PrivateClass: Strophe.Request 4081 * _Private_ helper class that provides a cross implementation abstraction 4082 * for a BOSH related XMLHttpRequest. 4083 * 4084 * The Strophe.Request class is used internally to encapsulate BOSH request 4085 * information. It is not meant to be used from user's code. 4086 */ 4087 4088 /** PrivateConstructor: Strophe.Request 4089 * Create and initialize a new Strophe.Request object. 4090 * 4091 * Parameters: 4092 * (XMLElement) elem - The XML data to be sent in the request. 4093 * (Function) func - The function that will be called when the 4094 * XMLHttpRequest readyState changes. 4095 * (Integer) rid - The BOSH rid attribute associated with this request. 4096 * (Integer) sends - The number of times this same request has been 4097 * sent. 4098 */ 4099 Strophe.Request = function (elem, func, rid, sends) 4100 { 4101 this.id = ++Strophe._requestId; 4102 this.xmlData = elem; 4103 this.data = Strophe.serialize(elem); 4104 // save original function in case we need to make a new request 4105 // from this one. 4106 this.origFunc = func; 4107 this.func = func; 4108 this.rid = rid; 4109 this.date = NaN; 4110 this.sends = sends || 0; 4111 this.abort = false; 4112 this.dead = null; 4113 4114 this.age = function () { 4115 if (!this.date) { return 0; } 4116 var now = new Date(); 4117 return (now - this.date) / 1000; 4118 }; 4119 this.timeDead = function () { 4120 if (!this.dead) { return 0; } 4121 var now = new Date(); 4122 return (now - this.dead) / 1000; 4123 }; 4124 this.xhr = this._newXHR(); 4125 }; 4126 4127 Strophe.Request.prototype = { 4128 /** PrivateFunction: getResponse 4129 * Get a response from the underlying XMLHttpRequest. 4130 * 4131 * This function attempts to get a response from the request and checks 4132 * for errors. 4133 * 4134 * Throws: 4135 * "parsererror" - A parser error occured. 4136 * 4137 * Returns: 4138 * The DOM element tree of the response. 4139 */ 4140 getResponse: function () 4141 { 4142 var node = null; 4143 if (this.xhr.responseXML && this.xhr.responseXML.documentElement) { 4144 node = this.xhr.responseXML.documentElement; 4145 if (node.tagName == "parsererror") { 4146 Strophe.error("invalid response received"); 4147 Strophe.error("responseText: " + this.xhr.responseText); 4148 Strophe.error("responseXML: " + 4149 Strophe.serialize(this.xhr.responseXML)); 4150 throw "parsererror"; 4151 } 4152 } else if (this.xhr.responseText) { 4153 Strophe.error("invalid response received"); 4154 Strophe.error("responseText: " + this.xhr.responseText); 4155 Strophe.error("responseXML: " + 4156 Strophe.serialize(this.xhr.responseXML)); 4157 } 4158 4159 return node; 4160 }, 4161 4162 /** PrivateFunction: _newXHR 4163 * _Private_ helper function to create XMLHttpRequests. 4164 * 4165 * This function creates XMLHttpRequests across all implementations. 4166 * 4167 * Returns: 4168 * A new XMLHttpRequest. 4169 */ 4170 _newXHR: function () 4171 { 4172 var xhr = null; 4173 if (window.XMLHttpRequest) { 4174 xhr = new XMLHttpRequest(); 4175 if (xhr.overrideMimeType) { 4176 xhr.overrideMimeType("text/xml; charset=utf-8"); 4177 } 4178 } else if (window.ActiveXObject) { 4179 xhr = new ActiveXObject("Microsoft.XMLHTTP"); 4180 } 4181 4182 // use Function.bind() to prepend ourselves as an argument 4183 xhr.onreadystatechange = this.func.bind(null, this); 4184 4185 return xhr; 4186 } 4187 }; 4188 4189 /** Class: Strophe.Bosh 4190 * _Private_ helper class that handles BOSH Connections 4191 * 4192 * The Strophe.Bosh class is used internally by Strophe.Connection 4193 * to encapsulate BOSH sessions. It is not meant to be used from user's code. 4194 */ 4195 4196 /** File: bosh.js 4197 * A JavaScript library to enable BOSH in Strophejs. 4198 * 4199 * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH) 4200 * to emulate a persistent, stateful, two-way connection to an XMPP server. 4201 * More information on BOSH can be found in XEP 124. 4202 */ 4203 4204 /** PrivateConstructor: Strophe.Bosh 4205 * Create and initialize a Strophe.Bosh object. 4206 * 4207 * Parameters: 4208 * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH. 4209 * 4210 * Returns: 4211 * A new Strophe.Bosh object. 4212 */ 4213 Strophe.Bosh = function(connection) { 4214 this._conn = connection; 4215 /* request id for body tags */ 4216 this.rid = Math.floor(Math.random() * 4294967295); 4217 /* The current session ID. */ 4218 this.sid = null; 4219 4220 // default BOSH values 4221 this.hold = 1; 4222 this.wait = 60; 4223 this.window = 5; 4224 this.errors = 0; 4225 4226 this._requests = []; 4227 }; 4228 4229 Strophe.Bosh.prototype = { 4230 /** Variable: strip 4231 * 4232 * BOSH-Connections will have all stanzas wrapped in a <body> tag when 4233 * passed to <Strophe.Connection.xmlInput> or <Strophe.Connection.xmlOutput>. 4234 * To strip this tag, User code can set <Strophe.Bosh.strip> to "body": 4235 * 4236 * > Strophe.Bosh.prototype.strip = "body"; 4237 * 4238 * This will enable stripping of the body tag in both 4239 * <Strophe.Connection.xmlInput> and <Strophe.Connection.xmlOutput>. 4240 */ 4241 strip: null, 4242 4243 /** PrivateFunction: _buildBody 4244 * _Private_ helper function to generate the <body/> wrapper for BOSH. 4245 * 4246 * Returns: 4247 * A Strophe.Builder with a <body/> element. 4248 */ 4249 _buildBody: function () 4250 { 4251 var bodyWrap = $build('body', { 4252 rid: this.rid++, 4253 xmlns: Strophe.NS.HTTPBIND 4254 }); 4255 if (this.sid !== null) { 4256 bodyWrap.attrs({sid: this.sid}); 4257 } 4258 if (this._conn.options.keepalive) { 4259 this._cacheSession(); 4260 } 4261 return bodyWrap; 4262 }, 4263 4264 /** PrivateFunction: _reset 4265 * Reset the connection. 4266 * 4267 * This function is called by the reset function of the Strophe Connection 4268 */ 4269 _reset: function () 4270 { 4271 this.rid = Math.floor(Math.random() * 4294967295); 4272 this.sid = null; 4273 this.errors = 0; 4274 window.sessionStorage.removeItem('strophe-bosh-session'); 4275 }, 4276 4277 /** PrivateFunction: _connect 4278 * _Private_ function that initializes the BOSH connection. 4279 * 4280 * Creates and sends the Request that initializes the BOSH connection. 4281 */ 4282 _connect: function (wait, hold, route) 4283 { 4284 this.wait = wait || this.wait; 4285 this.hold = hold || this.hold; 4286 this.errors = 0; 4287 4288 // build the body tag 4289 var body = this._buildBody().attrs({ 4290 to: this._conn.domain, 4291 "xml:lang": "en", 4292 wait: this.wait, 4293 hold: this.hold, 4294 content: "text/xml; charset=utf-8", 4295 ver: "1.6", 4296 "xmpp:version": "1.0", 4297 "xmlns:xmpp": Strophe.NS.BOSH 4298 }); 4299 4300 if(route){ 4301 body.attrs({ 4302 route: route 4303 }); 4304 } 4305 4306 var _connect_cb = this._conn._connect_cb; 4307 4308 this._requests.push( 4309 new Strophe.Request(body.tree(), 4310 this._onRequestStateChange.bind( 4311 this, _connect_cb.bind(this._conn)), 4312 body.tree().getAttribute("rid"))); 4313 this._throttledRequestHandler(); 4314 }, 4315 4316 /** PrivateFunction: _attach 4317 * Attach to an already created and authenticated BOSH session. 4318 * 4319 * This function is provided to allow Strophe to attach to BOSH 4320 * sessions which have been created externally, perhaps by a Web 4321 * application. This is often used to support auto-login type features 4322 * without putting user credentials into the page. 4323 * 4324 * Parameters: 4325 * (String) jid - The full JID that is bound by the session. 4326 * (String) sid - The SID of the BOSH session. 4327 * (String) rid - The current RID of the BOSH session. This RID 4328 * will be used by the next request. 4329 * (Function) callback The connect callback function. 4330 * (Integer) wait - The optional HTTPBIND wait value. This is the 4331 * time the server will wait before returning an empty result for 4332 * a request. The default setting of 60 seconds is recommended. 4333 * Other settings will require tweaks to the Strophe.TIMEOUT value. 4334 * (Integer) hold - The optional HTTPBIND hold value. This is the 4335 * number of connections the server will hold at one time. This 4336 * should almost always be set to 1 (the default). 4337 * (Integer) wind - The optional HTTBIND window value. This is the 4338 * allowed range of request ids that are valid. The default is 5. 4339 */ 4340 _attach: function (jid, sid, rid, callback, wait, hold, wind) 4341 { 4342 this._conn.jid = jid; 4343 this.sid = sid; 4344 this.rid = rid; 4345 4346 this._conn.connect_callback = callback; 4347 4348 this._conn.domain = Strophe.getDomainFromJid(this._conn.jid); 4349 4350 this._conn.authenticated = true; 4351 this._conn.connected = true; 4352 4353 this.wait = wait || this.wait; 4354 this.hold = hold || this.hold; 4355 this.window = wind || this.window; 4356 4357 this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null); 4358 }, 4359 4360 /** PrivateFunction: _restore 4361 * Attempt to restore a cached BOSH session 4362 * 4363 * Parameters: 4364 * (String) jid - The full JID that is bound by the session. 4365 * This parameter is optional but recommended, specifically in cases 4366 * where prebinded BOSH sessions are used where it's important to know 4367 * that the right session is being restored. 4368 * (Function) callback The connect callback function. 4369 * (Integer) wait - The optional HTTPBIND wait value. This is the 4370 * time the server will wait before returning an empty result for 4371 * a request. The default setting of 60 seconds is recommended. 4372 * Other settings will require tweaks to the Strophe.TIMEOUT value. 4373 * (Integer) hold - The optional HTTPBIND hold value. This is the 4374 * number of connections the server will hold at one time. This 4375 * should almost always be set to 1 (the default). 4376 * (Integer) wind - The optional HTTBIND window value. This is the 4377 * allowed range of request ids that are valid. The default is 5. 4378 */ 4379 _restore: function (jid, callback, wait, hold, wind) 4380 { 4381 var session = JSON.parse(window.sessionStorage.getItem('strophe-bosh-session')); 4382 if (typeof session !== "undefined" && 4383 session !== null && 4384 session.rid && 4385 session.sid && 4386 session.jid && 4387 (typeof jid === "undefined" || Strophe.getBareJidFromJid(session.jid) == Strophe.getBareJidFromJid(jid))) 4388 { 4389 this._conn.restored = true; 4390 this._attach(session.jid, session.sid, session.rid, callback, wait, hold, wind); 4391 } else { 4392 throw { name: "StropheSessionError", message: "_restore: no restoreable session." }; 4393 } 4394 }, 4395 4396 /** PrivateFunction: _cacheSession 4397 * _Private_ handler for the beforeunload event. 4398 * 4399 * This handler is used to process the Bosh-part of the initial request. 4400 * Parameters: 4401 * (Strophe.Request) bodyWrap - The received stanza. 4402 */ 4403 _cacheSession: function () 4404 { 4405 if (this._conn.authenticated) { 4406 if (this._conn.jid && this.rid && this.sid) { 4407 window.sessionStorage.setItem('strophe-bosh-session', JSON.stringify({ 4408 'jid': this._conn.jid, 4409 'rid': this.rid, 4410 'sid': this.sid 4411 })); 4412 } 4413 } else { 4414 window.sessionStorage.removeItem('strophe-bosh-session'); 4415 } 4416 }, 4417 4418 /** PrivateFunction: _connect_cb 4419 * _Private_ handler for initial connection request. 4420 * 4421 * This handler is used to process the Bosh-part of the initial request. 4422 * Parameters: 4423 * (Strophe.Request) bodyWrap - The received stanza. 4424 */ 4425 _connect_cb: function (bodyWrap) 4426 { 4427 var typ = bodyWrap.getAttribute("type"); 4428 var cond, conflict; 4429 if (typ !== null && typ == "terminate") { 4430 // an error occurred 4431 cond = bodyWrap.getAttribute("condition"); 4432 Strophe.error("BOSH-Connection failed: " + cond); 4433 conflict = bodyWrap.getElementsByTagName("conflict"); 4434 if (cond !== null) { 4435 if (cond == "remote-stream-error" && conflict.length > 0) { 4436 cond = "conflict"; 4437 } 4438 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond); 4439 } else { 4440 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); 4441 } 4442 this._conn._doDisconnect(cond); 4443 return Strophe.Status.CONNFAIL; 4444 } 4445 4446 // check to make sure we don't overwrite these if _connect_cb is 4447 // called multiple times in the case of missing stream:features 4448 if (!this.sid) { 4449 this.sid = bodyWrap.getAttribute("sid"); 4450 } 4451 var wind = bodyWrap.getAttribute('requests'); 4452 if (wind) { this.window = parseInt(wind, 10); } 4453 var hold = bodyWrap.getAttribute('hold'); 4454 if (hold) { this.hold = parseInt(hold, 10); } 4455 var wait = bodyWrap.getAttribute('wait'); 4456 if (wait) { this.wait = parseInt(wait, 10); } 4457 }, 4458 4459 /** PrivateFunction: _disconnect 4460 * _Private_ part of Connection.disconnect for Bosh 4461 * 4462 * Parameters: 4463 * (Request) pres - This stanza will be sent before disconnecting. 4464 */ 4465 _disconnect: function (pres) 4466 { 4467 this._sendTerminate(pres); 4468 }, 4469 4470 /** PrivateFunction: _doDisconnect 4471 * _Private_ function to disconnect. 4472 * 4473 * Resets the SID and RID. 4474 */ 4475 _doDisconnect: function () 4476 { 4477 this.sid = null; 4478 this.rid = Math.floor(Math.random() * 4294967295); 4479 window.sessionStorage.removeItem('strophe-bosh-session'); 4480 }, 4481 4482 /** PrivateFunction: _emptyQueue 4483 * _Private_ function to check if the Request queue is empty. 4484 * 4485 * Returns: 4486 * True, if there are no Requests queued, False otherwise. 4487 */ 4488 _emptyQueue: function () 4489 { 4490 return this._requests.length === 0; 4491 }, 4492 4493 /** PrivateFunction: _hitError 4494 * _Private_ function to handle the error count. 4495 * 4496 * Requests are resent automatically until their error count reaches 4497 * 5. Each time an error is encountered, this function is called to 4498 * increment the count and disconnect if the count is too high. 4499 * 4500 * Parameters: 4501 * (Integer) reqStatus - The request status. 4502 */ 4503 _hitError: function (reqStatus) 4504 { 4505 this.errors++; 4506 Strophe.warn("request errored, status: " + reqStatus + 4507 ", number of errors: " + this.errors); 4508 if (this.errors > 4) { 4509 this._conn._onDisconnectTimeout(); 4510 } 4511 }, 4512 4513 /** PrivateFunction: _no_auth_received 4514 * 4515 * Called on stream start/restart when no stream:features 4516 * has been received and sends a blank poll request. 4517 */ 4518 _no_auth_received: function (_callback) 4519 { 4520 if (_callback) { 4521 _callback = _callback.bind(this._conn); 4522 } else { 4523 _callback = this._conn._connect_cb.bind(this._conn); 4524 } 4525 var body = this._buildBody(); 4526 this._requests.push( 4527 new Strophe.Request(body.tree(), 4528 this._onRequestStateChange.bind( 4529 this, _callback.bind(this._conn)), 4530 body.tree().getAttribute("rid"))); 4531 this._throttledRequestHandler(); 4532 }, 4533 4534 /** PrivateFunction: _onDisconnectTimeout 4535 * _Private_ timeout handler for handling non-graceful disconnection. 4536 * 4537 * Cancels all remaining Requests and clears the queue. 4538 */ 4539 _onDisconnectTimeout: function () { 4540 this._abortAllRequests(); 4541 }, 4542 4543 /** PrivateFunction: _abortAllRequests 4544 * _Private_ helper function that makes sure all pending requests are aborted. 4545 */ 4546 _abortAllRequests: function _abortAllRequests() { 4547 var req; 4548 while (this._requests.length > 0) { 4549 req = this._requests.pop(); 4550 req.abort = true; 4551 req.xhr.abort(); 4552 // jslint complains, but this is fine. setting to empty func 4553 // is necessary for IE6 4554 req.xhr.onreadystatechange = function () {}; // jshint ignore:line 4555 } 4556 }, 4557 4558 /** PrivateFunction: _onIdle 4559 * _Private_ handler called by Strophe.Connection._onIdle 4560 * 4561 * Sends all queued Requests or polls with empty Request if there are none. 4562 */ 4563 _onIdle: function () { 4564 var data = this._conn._data; 4565 4566 // if no requests are in progress, poll 4567 if (this._conn.authenticated && this._requests.length === 0 && 4568 data.length === 0 && !this._conn.disconnecting) { 4569 Strophe.info("no requests during idle cycle, sending " + 4570 "blank request"); 4571 data.push(null); 4572 } 4573 4574 if (this._conn.paused) { 4575 return; 4576 } 4577 4578 if (this._requests.length < 2 && data.length > 0) { 4579 var body = this._buildBody(); 4580 for (var i = 0; i < data.length; i++) { 4581 if (data[i] !== null) { 4582 if (data[i] === "restart") { 4583 body.attrs({ 4584 to: this._conn.domain, 4585 "xml:lang": "en", 4586 "xmpp:restart": "true", 4587 "xmlns:xmpp": Strophe.NS.BOSH 4588 }); 4589 } else { 4590 body.cnode(data[i]).up(); 4591 } 4592 } 4593 } 4594 delete this._conn._data; 4595 this._conn._data = []; 4596 this._requests.push( 4597 new Strophe.Request(body.tree(), 4598 this._onRequestStateChange.bind( 4599 this, this._conn._dataRecv.bind(this._conn)), 4600 body.tree().getAttribute("rid"))); 4601 this._throttledRequestHandler(); 4602 } 4603 4604 if (this._requests.length > 0) { 4605 var time_elapsed = this._requests[0].age(); 4606 if (this._requests[0].dead !== null) { 4607 if (this._requests[0].timeDead() > 4608 Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) { 4609 this._throttledRequestHandler(); 4610 } 4611 } 4612 4613 if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) { 4614 Strophe.warn("Request " + 4615 this._requests[0].id + 4616 " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + 4617 " seconds since last activity"); 4618 this._throttledRequestHandler(); 4619 } 4620 } 4621 }, 4622 4623 /** PrivateFunction: _onRequestStateChange 4624 * _Private_ handler for Strophe.Request state changes. 4625 * 4626 * This function is called when the XMLHttpRequest readyState changes. 4627 * It contains a lot of error handling logic for the many ways that 4628 * requests can fail, and calls the request callback when requests 4629 * succeed. 4630 * 4631 * Parameters: 4632 * (Function) func - The handler for the request. 4633 * (Strophe.Request) req - The request that is changing readyState. 4634 */ 4635 _onRequestStateChange: function (func, req) 4636 { 4637 Strophe.debug("request id " + req.id + 4638 "." + req.sends + " state changed to " + 4639 req.xhr.readyState); 4640 4641 if (req.abort) { 4642 req.abort = false; 4643 return; 4644 } 4645 4646 // request complete 4647 var reqStatus; 4648 if (req.xhr.readyState == 4) { 4649 reqStatus = 0; 4650 try { 4651 reqStatus = req.xhr.status; 4652 } catch (e) { 4653 // ignore errors from undefined status attribute. works 4654 // around a browser bug 4655 } 4656 4657 if (typeof(reqStatus) == "undefined") { 4658 reqStatus = 0; 4659 } 4660 4661 if (this.disconnecting) { 4662 if (reqStatus >= 400) { 4663 this._hitError(reqStatus); 4664 return; 4665 } 4666 } 4667 4668 var reqIs0 = (this._requests[0] == req); 4669 var reqIs1 = (this._requests[1] == req); 4670 4671 if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) { 4672 // remove from internal queue 4673 this._removeRequest(req); 4674 Strophe.debug("request id " + 4675 req.id + 4676 " should now be removed"); 4677 } 4678 4679 // request succeeded 4680 if (reqStatus == 200) { 4681 // if request 1 finished, or request 0 finished and request 4682 // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to 4683 // restart the other - both will be in the first spot, as the 4684 // completed request has been removed from the queue already 4685 if (reqIs1 || 4686 (reqIs0 && this._requests.length > 0 && 4687 this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) { 4688 this._restartRequest(0); 4689 } 4690 // call handler 4691 Strophe.debug("request id " + 4692 req.id + "." + 4693 req.sends + " got 200"); 4694 func(req); 4695 this.errors = 0; 4696 } else { 4697 Strophe.error("request id " + 4698 req.id + "." + 4699 req.sends + " error " + reqStatus + 4700 " happened"); 4701 if (reqStatus === 0 || 4702 (reqStatus >= 400 && reqStatus < 600) || 4703 reqStatus >= 12000) { 4704 this._hitError(reqStatus); 4705 if (reqStatus >= 400 && reqStatus < 500) { 4706 this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null); 4707 this._conn._doDisconnect(); 4708 } 4709 } 4710 } 4711 4712 if (!((reqStatus > 0 && reqStatus < 500) || 4713 req.sends > 5)) { 4714 this._throttledRequestHandler(); 4715 } 4716 } 4717 }, 4718 4719 /** PrivateFunction: _processRequest 4720 * _Private_ function to process a request in the queue. 4721 * 4722 * This function takes requests off the queue and sends them and 4723 * restarts dead requests. 4724 * 4725 * Parameters: 4726 * (Integer) i - The index of the request in the queue. 4727 */ 4728 _processRequest: function (i) 4729 { 4730 var self = this; 4731 var req = this._requests[i]; 4732 var reqStatus = -1; 4733 4734 try { 4735 if (req.xhr.readyState == 4) { 4736 reqStatus = req.xhr.status; 4737 } 4738 } catch (e) { 4739 Strophe.error("caught an error in _requests[" + i + 4740 "], reqStatus: " + reqStatus); 4741 } 4742 4743 if (typeof(reqStatus) == "undefined") { 4744 reqStatus = -1; 4745 } 4746 4747 // make sure we limit the number of retries 4748 if (req.sends > this._conn.maxRetries) { 4749 this._conn._onDisconnectTimeout(); 4750 return; 4751 } 4752 4753 var time_elapsed = req.age(); 4754 var primaryTimeout = (!isNaN(time_elapsed) && 4755 time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)); 4756 var secondaryTimeout = (req.dead !== null && 4757 req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)); 4758 var requestCompletedWithServerError = (req.xhr.readyState == 4 && 4759 (reqStatus < 1 || 4760 reqStatus >= 500)); 4761 if (primaryTimeout || secondaryTimeout || 4762 requestCompletedWithServerError) { 4763 if (secondaryTimeout) { 4764 Strophe.error("Request " + 4765 this._requests[i].id + 4766 " timed out (secondary), restarting"); 4767 } 4768 req.abort = true; 4769 req.xhr.abort(); 4770 // setting to null fails on IE6, so set to empty function 4771 req.xhr.onreadystatechange = function () {}; 4772 this._requests[i] = new Strophe.Request(req.xmlData, 4773 req.origFunc, 4774 req.rid, 4775 req.sends); 4776 req = this._requests[i]; 4777 } 4778 4779 if (req.xhr.readyState === 0) { 4780 Strophe.debug("request id " + req.id + 4781 "." + req.sends + " posting"); 4782 4783 try { 4784 req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true); 4785 req.xhr.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); 4786 } catch (e2) { 4787 Strophe.error("XHR open failed."); 4788 if (!this._conn.connected) { 4789 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, 4790 "bad-service"); 4791 } 4792 this._conn.disconnect(); 4793 return; 4794 } 4795 4796 // Fires the XHR request -- may be invoked immediately 4797 // or on a gradually expanding retry window for reconnects 4798 var sendFunc = function () { 4799 req.date = new Date(); 4800 if (self._conn.options.customHeaders){ 4801 var headers = self._conn.options.customHeaders; 4802 for (var header in headers) { 4803 if (headers.hasOwnProperty(header)) { 4804 req.xhr.setRequestHeader(header, headers[header]); 4805 } 4806 } 4807 } 4808 req.xhr.send(req.data); 4809 }; 4810 4811 // Implement progressive backoff for reconnects -- 4812 // First retry (send == 1) should also be instantaneous 4813 if (req.sends > 1) { 4814 // Using a cube of the retry number creates a nicely 4815 // expanding retry window 4816 var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), 4817 Math.pow(req.sends, 3)) * 1000; 4818 setTimeout(sendFunc, backoff); 4819 } else { 4820 sendFunc(); 4821 } 4822 4823 req.sends++; 4824 4825 if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) { 4826 if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) { 4827 this._conn.xmlOutput(req.xmlData.childNodes[0]); 4828 } else { 4829 this._conn.xmlOutput(req.xmlData); 4830 } 4831 } 4832 if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) { 4833 this._conn.rawOutput(req.data); 4834 } 4835 } else { 4836 Strophe.debug("_processRequest: " + 4837 (i === 0 ? "first" : "second") + 4838 " request has readyState of " + 4839 req.xhr.readyState); 4840 } 4841 }, 4842 4843 /** PrivateFunction: _removeRequest 4844 * _Private_ function to remove a request from the queue. 4845 * 4846 * Parameters: 4847 * (Strophe.Request) req - The request to remove. 4848 */ 4849 _removeRequest: function (req) 4850 { 4851 Strophe.debug("removing request"); 4852 4853 var i; 4854 for (i = this._requests.length - 1; i >= 0; i--) { 4855 if (req == this._requests[i]) { 4856 this._requests.splice(i, 1); 4857 } 4858 } 4859 4860 // IE6 fails on setting to null, so set to empty function 4861 req.xhr.onreadystatechange = function () {}; 4862 4863 this._throttledRequestHandler(); 4864 }, 4865 4866 /** PrivateFunction: _restartRequest 4867 * _Private_ function to restart a request that is presumed dead. 4868 * 4869 * Parameters: 4870 * (Integer) i - The index of the request in the queue. 4871 */ 4872 _restartRequest: function (i) 4873 { 4874 var req = this._requests[i]; 4875 if (req.dead === null) { 4876 req.dead = new Date(); 4877 } 4878 4879 this._processRequest(i); 4880 }, 4881 4882 /** PrivateFunction: _reqToData 4883 * _Private_ function to get a stanza out of a request. 4884 * 4885 * Tries to extract a stanza out of a Request Object. 4886 * When this fails the current connection will be disconnected. 4887 * 4888 * Parameters: 4889 * (Object) req - The Request. 4890 * 4891 * Returns: 4892 * The stanza that was passed. 4893 */ 4894 _reqToData: function (req) 4895 { 4896 try { 4897 return req.getResponse(); 4898 } catch (e) { 4899 if (e != "parsererror") { throw e; } 4900 this._conn.disconnect("strophe-parsererror"); 4901 } 4902 }, 4903 4904 /** PrivateFunction: _sendTerminate 4905 * _Private_ function to send initial disconnect sequence. 4906 * 4907 * This is the first step in a graceful disconnect. It sends 4908 * the BOSH server a terminate body and includes an unavailable 4909 * presence if authentication has completed. 4910 */ 4911 _sendTerminate: function (pres) 4912 { 4913 Strophe.info("_sendTerminate was called"); 4914 var body = this._buildBody().attrs({type: "terminate"}); 4915 4916 if (pres) { 4917 body.cnode(pres.tree()); 4918 } 4919 4920 var req = new Strophe.Request(body.tree(), 4921 this._onRequestStateChange.bind( 4922 this, this._conn._dataRecv.bind(this._conn)), 4923 body.tree().getAttribute("rid")); 4924 4925 this._requests.push(req); 4926 this._throttledRequestHandler(); 4927 }, 4928 4929 /** PrivateFunction: _send 4930 * _Private_ part of the Connection.send function for BOSH 4931 * 4932 * Just triggers the RequestHandler to send the messages that are in the queue 4933 */ 4934 _send: function () { 4935 clearTimeout(this._conn._idleTimeout); 4936 this._throttledRequestHandler(); 4937 this._conn._idleTimeout = setTimeout(this._conn._onIdle.bind(this._conn), 100); 4938 }, 4939 4940 /** PrivateFunction: _sendRestart 4941 * 4942 * Send an xmpp:restart stanza. 4943 */ 4944 _sendRestart: function () 4945 { 4946 this._throttledRequestHandler(); 4947 clearTimeout(this._conn._idleTimeout); 4948 }, 4949 4950 /** PrivateFunction: _throttledRequestHandler 4951 * _Private_ function to throttle requests to the connection window. 4952 * 4953 * This function makes sure we don't send requests so fast that the 4954 * request ids overflow the connection window in the case that one 4955 * request died. 4956 */ 4957 _throttledRequestHandler: function () 4958 { 4959 if (!this._requests) { 4960 Strophe.debug("_throttledRequestHandler called with " + 4961 "undefined requests"); 4962 } else { 4963 Strophe.debug("_throttledRequestHandler called with " + 4964 this._requests.length + " requests"); 4965 } 4966 4967 if (!this._requests || this._requests.length === 0) { 4968 return; 4969 } 4970 4971 if (this._requests.length > 0) { 4972 this._processRequest(0); 4973 } 4974 4975 if (this._requests.length > 1 && 4976 Math.abs(this._requests[0].rid - 4977 this._requests[1].rid) < this.window) { 4978 this._processRequest(1); 4979 } 4980 } 4981 }; 4982 return Strophe; 4983 })); 4984 4985 /* 4986 This program is distributed under the terms of the MIT license. 4987 Please see the LICENSE file for details. 4988 4989 Copyright 2006-2008, OGG, LLC 4990 */ 4991 4992 /* jshint undef: true, unused: true:, noarg: true, latedef: true */ 4993 /* global define, window, clearTimeout, WebSocket, DOMParser, Strophe, $build */ 4994 4995 (function (root, factory) { 4996 if (typeof define === 'function' && define.amd) { 4997 define('strophe-websocket', ['strophe-core'], function (core) { 4998 return factory( 4999 core.Strophe, 5000 core.$build 5001 ); 5002 }); 5003 } else { 5004 // Browser globals 5005 return factory(Strophe, $build); 5006 } 5007 }(this, function (Strophe, $build) { 5008 5009 /** Class: Strophe.WebSocket 5010 * _Private_ helper class that handles WebSocket Connections 5011 * 5012 * The Strophe.WebSocket class is used internally by Strophe.Connection 5013 * to encapsulate WebSocket sessions. It is not meant to be used from user's code. 5014 */ 5015 5016 /** File: websocket.js 5017 * A JavaScript library to enable XMPP over Websocket in Strophejs. 5018 * 5019 * This file implements XMPP over WebSockets for Strophejs. 5020 * If a Connection is established with a Websocket url (ws://...) 5021 * Strophe will use WebSockets. 5022 * For more information on XMPP-over-WebSocket see RFC 7395: 5023 * http://tools.ietf.org/html/rfc7395 5024 * 5025 * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de) 5026 */ 5027 5028 /** PrivateConstructor: Strophe.Websocket 5029 * Create and initialize a Strophe.WebSocket object. 5030 * Currently only sets the connection Object. 5031 * 5032 * Parameters: 5033 * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets. 5034 * 5035 * Returns: 5036 * A new Strophe.WebSocket object. 5037 */ 5038 Strophe.Websocket = function(connection) { 5039 alert(window.location.host); 5040 this._conn = connection; 5041 this.strip = "wrapper"; 5042 5043 var service = connection.service; 5044 if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) { 5045 // If the service is not an absolute URL, assume it is a path and put the absolute 5046 // URL together from options, current URL and the path. 5047 var new_service = ""; 5048 5049 if (connection.options.protocol === "ws" && window.location.protocol !== "https:") { 5050 new_service += "ws"; 5051 } else { 5052 new_service += "wss"; 5053 } 5054 5055 new_service += "://" + window.location.host; 5056 5057 if (service.indexOf("/") !== 0) { 5058 new_service += window.location.pathname + service; 5059 } else { 5060 new_service += service; 5061 } 5062 5063 connection.service = new_service; 5064 } 5065 }; 5066 5067 Strophe.Websocket.prototype = { 5068 /** PrivateFunction: _buildStream 5069 * _Private_ helper function to generate the <stream> start tag for WebSockets 5070 * 5071 * Returns: 5072 * A Strophe.Builder with a <stream> element. 5073 */ 5074 _buildStream: function () 5075 { 5076 return $build("open", { 5077 "xmlns": Strophe.NS.FRAMING, 5078 "to": this._conn.domain, 5079 "version": '1.0' 5080 }); 5081 }, 5082 5083 /** PrivateFunction: _check_streamerror 5084 * _Private_ checks a message for stream:error 5085 * 5086 * Parameters: 5087 * (Strophe.Request) bodyWrap - The received stanza. 5088 * connectstatus - The ConnectStatus that will be set on error. 5089 * Returns: 5090 * true if there was a streamerror, false otherwise. 5091 */ 5092 _check_streamerror: function (bodyWrap, connectstatus) { 5093 var errors; 5094 if (bodyWrap.getElementsByTagNameNS) { 5095 errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "error"); 5096 } else { 5097 errors = bodyWrap.getElementsByTagName("stream:error"); 5098 } 5099 if (errors.length === 0) { 5100 return false; 5101 } 5102 var error = errors[0]; 5103 5104 var condition = ""; 5105 var text = ""; 5106 5107 var ns = "urn:ietf:params:xml:ns:xmpp-streams"; 5108 for (var i = 0; i < error.childNodes.length; i++) { 5109 var e = error.childNodes[i]; 5110 if (e.getAttribute("xmlns") !== ns) { 5111 break; 5112 } if (e.nodeName === "text") { 5113 text = e.textContent; 5114 } else { 5115 condition = e.nodeName; 5116 } 5117 } 5118 5119 var errorString = "WebSocket stream error: "; 5120 5121 if (condition) { 5122 errorString += condition; 5123 } else { 5124 errorString += "unknown"; 5125 } 5126 5127 if (text) { 5128 errorString += " - " + condition; 5129 } 5130 5131 Strophe.error(errorString); 5132 5133 // close the connection on stream_error 5134 this._conn._changeConnectStatus(connectstatus, condition); 5135 this._conn._doDisconnect(); 5136 return true; 5137 }, 5138 5139 /** PrivateFunction: _reset 5140 * Reset the connection. 5141 * 5142 * This function is called by the reset function of the Strophe Connection. 5143 * Is not needed by WebSockets. 5144 */ 5145 _reset: function () 5146 { 5147 return; 5148 }, 5149 5150 /** PrivateFunction: _connect 5151 * _Private_ function called by Strophe.Connection.connect 5152 * 5153 * Creates a WebSocket for a connection and assigns Callbacks to it. 5154 * Does nothing if there already is a WebSocket. 5155 */ 5156 _connect: function () { 5157 // Ensure that there is no open WebSocket from a previous Connection. 5158 this._closeSocket(); 5159 5160 // Create the new WobSocket 5161 this.socket = new WebSocket(this._conn.service, "xmpp"); 5162 this.socket.onopen = this._onOpen.bind(this); 5163 this.socket.onerror = this._onError.bind(this); 5164 this.socket.onclose = this._onClose.bind(this); 5165 this.socket.onmessage = this._connect_cb_wrapper.bind(this); 5166 }, 5167 5168 /** PrivateFunction: _connect_cb 5169 * _Private_ function called by Strophe.Connection._connect_cb 5170 * 5171 * checks for stream:error 5172 * 5173 * Parameters: 5174 * (Strophe.Request) bodyWrap - The received stanza. 5175 */ 5176 _connect_cb: function(bodyWrap) { 5177 var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL); 5178 if (error) { 5179 return Strophe.Status.CONNFAIL; 5180 } 5181 }, 5182 5183 /** PrivateFunction: _handleStreamStart 5184 * _Private_ function that checks the opening <open /> tag for errors. 5185 * 5186 * Disconnects if there is an error and returns false, true otherwise. 5187 * 5188 * Parameters: 5189 * (Node) message - Stanza containing the <open /> tag. 5190 */ 5191 _handleStreamStart: function(message) { 5192 var error = false; 5193 5194 // Check for errors in the <open /> tag 5195 var ns = message.getAttribute("xmlns"); 5196 if (typeof ns !== "string") { 5197 error = "Missing xmlns in <open />"; 5198 } else if (ns !== Strophe.NS.FRAMING) { 5199 error = "Wrong xmlns in <open />: " + ns; 5200 } 5201 5202 var ver = message.getAttribute("version"); 5203 if (typeof ver !== "string") { 5204 error = "Missing version in <open />"; 5205 } else if (ver !== "1.0") { 5206 error = "Wrong version in <open />: " + ver; 5207 } 5208 5209 if (error) { 5210 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error); 5211 this._conn._doDisconnect(); 5212 return false; 5213 } 5214 5215 return true; 5216 }, 5217 5218 /** PrivateFunction: _connect_cb_wrapper 5219 * _Private_ function that handles the first connection messages. 5220 * 5221 * On receiving an opening stream tag this callback replaces itself with the real 5222 * message handler. On receiving a stream error the connection is terminated. 5223 */ 5224 _connect_cb_wrapper: function(message) { 5225 if (message.data.indexOf("<open ") === 0 || message.data.indexOf("<?xml") === 0) { 5226 // Strip the XML Declaration, if there is one 5227 var data = message.data.replace(/^(<\?.*?\?>\s*)*/, ""); 5228 if (data === '') return; 5229 5230 var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement; 5231 this._conn.xmlInput(streamStart); 5232 this._conn.rawInput(message.data); 5233 5234 //_handleStreamSteart will check for XML errors and disconnect on error 5235 if (this._handleStreamStart(streamStart)) { 5236 //_connect_cb will check for stream:error and disconnect on error 5237 this._connect_cb(streamStart); 5238 } 5239 } else if (message.data.indexOf("<close ") === 0) { //'<close xmlns="urn:ietf:params:xml:ns:xmpp-framing />') { 5240 this._conn.rawInput(message.data); 5241 this._conn.xmlInput(message); 5242 var see_uri = message.getAttribute("see-other-uri"); 5243 if (see_uri) { 5244 this._conn._changeConnectStatus(Strophe.Status.REDIRECT, "Received see-other-uri, resetting connection"); 5245 this._conn.reset(); 5246 this._conn.service = see_uri; 5247 this._connect(); 5248 } else { 5249 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream"); 5250 this._conn._doDisconnect(); 5251 } 5252 } else { 5253 var string = this._streamWrap(message.data); 5254 var elem = new DOMParser().parseFromString(string, "text/xml").documentElement; 5255 this.socket.onmessage = this._onMessage.bind(this); 5256 this._conn._connect_cb(elem, null, message.data); 5257 } 5258 }, 5259 5260 /** PrivateFunction: _disconnect 5261 * _Private_ function called by Strophe.Connection.disconnect 5262 * 5263 * Disconnects and sends a last stanza if one is given 5264 * 5265 * Parameters: 5266 * (Request) pres - This stanza will be sent before disconnecting. 5267 */ 5268 _disconnect: function (pres) 5269 { 5270 if (this.socket && this.socket.readyState !== WebSocket.CLOSED) { 5271 if (pres) { 5272 this._conn.send(pres); 5273 } 5274 var close = $build("close", { "xmlns": Strophe.NS.FRAMING, }); 5275 this._conn.xmlOutput(close); 5276 var closeString = Strophe.serialize(close); 5277 this._conn.rawOutput(closeString); 5278 try { 5279 this.socket.send(closeString); 5280 } catch (e) { 5281 Strophe.info("Couldn't send <close /> tag."); 5282 } 5283 } 5284 this._conn._doDisconnect(); 5285 }, 5286 5287 /** PrivateFunction: _doDisconnect 5288 * _Private_ function to disconnect. 5289 * 5290 * Just closes the Socket for WebSockets 5291 */ 5292 _doDisconnect: function () 5293 { 5294 Strophe.info("WebSockets _doDisconnect was called"); 5295 this._closeSocket(); 5296 }, 5297 5298 /** PrivateFunction _streamWrap 5299 * _Private_ helper function to wrap a stanza in a <stream> tag. 5300 * This is used so Strophe can process stanzas from WebSockets like BOSH 5301 */ 5302 _streamWrap: function (stanza) 5303 { 5304 return "<wrapper>" + stanza + '</wrapper>'; 5305 }, 5306 5307 5308 /** PrivateFunction: _closeSocket 5309 * _Private_ function to close the WebSocket. 5310 * 5311 * Closes the socket if it is still open and deletes it 5312 */ 5313 _closeSocket: function () 5314 { 5315 if (this.socket) { try { 5316 this.socket.close(); 5317 } catch (e) {} } 5318 this.socket = null; 5319 }, 5320 5321 /** PrivateFunction: _emptyQueue 5322 * _Private_ function to check if the message queue is empty. 5323 * 5324 * Returns: 5325 * True, because WebSocket messages are send immediately after queueing. 5326 */ 5327 _emptyQueue: function () 5328 { 5329 return true; 5330 }, 5331 5332 /** PrivateFunction: _onClose 5333 * _Private_ function to handle websockets closing. 5334 * 5335 * Nothing to do here for WebSockets 5336 */ 5337 _onClose: function() { 5338 if(this._conn.connected && !this._conn.disconnecting) { 5339 Strophe.error("Websocket closed unexcectedly"); 5340 this._conn._doDisconnect(); 5341 } else { 5342 Strophe.info("Websocket closed"); 5343 } 5344 }, 5345 5346 /** PrivateFunction: _no_auth_received 5347 * 5348 * Called on stream start/restart when no stream:features 5349 * has been received. 5350 */ 5351 _no_auth_received: function (_callback) 5352 { 5353 Strophe.error("Server did not send any auth methods"); 5354 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods"); 5355 if (_callback) { 5356 _callback = _callback.bind(this._conn); 5357 _callback(); 5358 } 5359 this._conn._doDisconnect(); 5360 }, 5361 5362 /** PrivateFunction: _onDisconnectTimeout 5363 * _Private_ timeout handler for handling non-graceful disconnection. 5364 * 5365 * This does nothing for WebSockets 5366 */ 5367 _onDisconnectTimeout: function () {}, 5368 5369 /** PrivateFunction: _abortAllRequests 5370 * _Private_ helper function that makes sure all pending requests are aborted. 5371 */ 5372 _abortAllRequests: function () {}, 5373 5374 /** PrivateFunction: _onError 5375 * _Private_ function to handle websockets errors. 5376 * 5377 * Parameters: 5378 * (Object) error - The websocket error. 5379 */ 5380 _onError: function(error) { 5381 Strophe.error("Websocket error " + error); 5382 this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established was disconnected."); 5383 this._disconnect(); 5384 }, 5385 5386 /** PrivateFunction: _onIdle 5387 * _Private_ function called by Strophe.Connection._onIdle 5388 * 5389 * sends all queued stanzas 5390 */ 5391 _onIdle: function () { 5392 var data = this._conn._data; 5393 if (data.length > 0 && !this._conn.paused) { 5394 for (var i = 0; i < data.length; i++) { 5395 if (data[i] !== null) { 5396 var stanza, rawStanza; 5397 if (data[i] === "restart") { 5398 stanza = this._buildStream().tree(); 5399 } else { 5400 stanza = data[i]; 5401 } 5402 rawStanza = Strophe.serialize(stanza); 5403 this._conn.xmlOutput(stanza); 5404 this._conn.rawOutput(rawStanza); 5405 this.socket.send(rawStanza); 5406 } 5407 } 5408 this._conn._data = []; 5409 } 5410 }, 5411 5412 /** PrivateFunction: _onMessage 5413 * _Private_ function to handle websockets messages. 5414 * 5415 * This function parses each of the messages as if they are full documents. [TODO : We may actually want to use a SAX Push parser]. 5416 * 5417 * Since all XMPP traffic starts with "<stream:stream version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='3697395463' from='SERVER'>" 5418 * The first stanza will always fail to be parsed... 5419 * Addtionnaly, the seconds stanza will always be a <stream:features> with the stream NS defined in the previous stanza... so we need to 'force' the inclusion of the NS in this stanza! 5420 * 5421 * Parameters: 5422 * (string) message - The websocket message. 5423 */ 5424 _onMessage: function(message) { 5425 var elem, data; 5426 // check for closing stream 5427 var close = '<close xmlns="urn:ietf:params:xml:ns:xmpp-framing" />'; 5428 if (message.data === close) { 5429 this._conn.rawInput(close); 5430 this._conn.xmlInput(message); 5431 if (!this._conn.disconnecting) { 5432 this._conn._doDisconnect(); 5433 } 5434 return; 5435 } else if (message.data.search("<open ") === 0) { 5436 // This handles stream restarts 5437 elem = new DOMParser().parseFromString(message.data, "text/xml").documentElement; 5438 5439 if (!this._handleStreamStart(elem)) { 5440 return; 5441 } 5442 } else { 5443 data = this._streamWrap(message.data); 5444 elem = new DOMParser().parseFromString(data, "text/xml").documentElement; 5445 } 5446 5447 if (this._check_streamerror(elem, Strophe.Status.ERROR)) { 5448 return; 5449 } 5450 5451 //handle unavailable presence stanza before disconnecting 5452 if (this._conn.disconnecting && 5453 elem.firstChild.nodeName === "presence" && 5454 elem.firstChild.getAttribute("type") === "unavailable") { 5455 this._conn.xmlInput(elem); 5456 this._conn.rawInput(Strophe.serialize(elem)); 5457 // if we are already disconnecting we will ignore the unavailable stanza and 5458 // wait for the </stream:stream> tag before we close the connection 5459 return; 5460 } 5461 this._conn._dataRecv(elem, message.data); 5462 }, 5463 5464 /** PrivateFunction: _onOpen 5465 * _Private_ function to handle websockets connection setup. 5466 * 5467 * The opening stream tag is sent here. 5468 */ 5469 _onOpen: function() { 5470 Strophe.info("Websocket open"); 5471 var start = this._buildStream(); 5472 this._conn.xmlOutput(start.tree()); 5473 5474 var startString = Strophe.serialize(start); 5475 this._conn.rawOutput(startString); 5476 this.socket.send(startString); 5477 }, 5478 5479 /** PrivateFunction: _reqToData 5480 * _Private_ function to get a stanza out of a request. 5481 * 5482 * WebSockets don't use requests, so the passed argument is just returned. 5483 * 5484 * Parameters: 5485 * (Object) stanza - The stanza. 5486 * 5487 * Returns: 5488 * The stanza that was passed. 5489 */ 5490 _reqToData: function (stanza) 5491 { 5492 return stanza; 5493 }, 5494 5495 /** PrivateFunction: _send 5496 * _Private_ part of the Connection.send function for WebSocket 5497 * 5498 * Just flushes the messages that are in the queue 5499 */ 5500 _send: function () { 5501 this._conn.flush(); 5502 }, 5503 5504 /** PrivateFunction: _sendRestart 5505 * 5506 * Send an xmpp:restart stanza. 5507 */ 5508 _sendRestart: function () 5509 { 5510 clearTimeout(this._conn._idleTimeout); 5511 this._conn._onIdle.bind(this._conn)(); 5512 } 5513 }; 5514 return Strophe; 5515 })); 5516 5517 /* jshint ignore:start */ 5518 if (callback) { 5519 return callback(Strophe, $build, $msg, $iq, $pres); 5520 } 5521 5522 5523 })(function (Strophe, build, msg, iq, pres) { 5524 window.Strophe = Strophe; 5525 window.$build = build; 5526 window.$msg = msg; 5527 window.$iq = iq; 5528 window.$pres = pres; 5529 }); 5530 /* jshint ignore:end */ 5531 5532 5533 5534 /*utf*/ 5535 function utf16to8(str) { 5536 var out, i, len, c; 5537 out = ""; 5538 len = str.length; 5539 for(i = 0; i < len; i++) { 5540 c = str.charCodeAt(i); 5541 if ((c >= 0x0001) && (c <= 0x007F)) { 5542 out += str.charAt(i); 5543 } else if (c > 0x07FF) { 5544 out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); 5545 out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); 5546 out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); 5547 } else { 5548 out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); 5549 out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); 5550 } 5551 } 5552 return out; 5553 } 5554 //utf-16转utf-8 5555 function utf8to16(str) { 5556 var out, i, len, c; 5557 var char2, char3; 5558 out = ""; 5559 len = str.length; 5560 i = 0; 5561 while(i < len) { 5562 c = str.charCodeAt(i++); 5563 switch(c >> 4) 5564 { 5565 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: 5566 // 0xxxxxxx 5567 out += str.charAt(i-1); 5568 break; 5569 case 12: case 13: 5570 // 110x xxxx 10xx xxxx 5571 char2 = str.charCodeAt(i++); 5572 out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); 5573 break; 5574 case 14: 5575 // 1110 xxxx 10xx xxxx 10xx xxxx 5576 char2 = str.charCodeAt(i++); 5577 char3 = str.charCodeAt(i++); 5578 out += String.fromCharCode(((c & 0x0F) << 12) | 5579 ((char2 & 0x3F) << 6) | 5580 ((char3 & 0x3F) << 0)); 5581 break; 5582 } 5583 } 5584 return out; 5585 }
2、后台插件进行编码解析
Web界面
var iq=$iq({id:"iqwd_factorylogin",type:"get"}).
c("query",{xmlns:"query-factorylogin"}).
c("item").
c("username").cht("张三");
Openfire插件
String username= Base64.getFromBase64(item.elementText("username"));
1 package com.plugin.common; 2 3 import java.io.UnsupportedEncodingException; 4 5 import sun.misc.BASE64Decoder; 6 import sun.misc.BASE64Encoder; 7 8 public class Base64 { 9 // 加密 10 public static String getBase64(String str) { 11 byte[] b = null; 12 String s = null; 13 try { 14 b = str.getBytes("utf-8"); 15 } catch (UnsupportedEncodingException e) { 16 e.printStackTrace(); 17 } 18 if (b != null) { 19 s = new BASE64Encoder().encode(b); 20 } 21 return s; 22 } 23 24 // 解密 25 public static String getFromBase64(String s) { 26 byte[] b = null; 27 String result = null; 28 if (s != null) { 29 BASE64Decoder decoder = new BASE64Decoder(); 30 try { 31 b = decoder.decodeBuffer(s); 32 result = new String(b, "utf-8"); 33 } catch (Exception e) { 34 e.printStackTrace(); 35 } 36 } 37 return result; 38 } 39 40 }