Openfire Strophe开发中文乱码问题

来源:互联网 时间:1970-01-01

网站上有很多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 be1004 // 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.implementation1011 .createDocument('jabber:client', 'strophe', null);1012 }1013 1014 return doc;1015 },1016 1017 /** Function: xmlGenerator1018 * 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: _getIEXmlDom1031 * Gets IE xml doc object1032 *1033 * Returns:1034 * A Microsoft XML DOM Object1035 * See Also:1036 * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx1037 */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: xmlElement1066 * Create an XML DOM element.1067 *1068 * This function creates an XML DOM element correctly across all1069 * implementations. Note that these are not HTML DOM elements, which1070 * 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 containing1075 * key/value pairs to use as element attributes. The object should1076 * be in the format {'key': 'value'} or {key: 'value'}. The array1077 * 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 or1090 // there are more than two optional args1091 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: xmlescape1123 * 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, "&amp;");1134 text = text.replace(/</g, "&lt;");1135 text = text.replace(/>/g, "&gt;");1136 text = text.replace(/'/g, "&apos;");1137 text = text.replace(/"/g, "&quot;");1138 return text;1139 },1140 1141 /* Function: xmlunescape1142 * 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(//&amp;/g, "&");1153 text = text.replace(/&lt;/g, "<");1154 text = text.replace(/&gt;/g, ">");1155 text = text.replace(/&apos;/g, "'");1156 text = text.replace(/&quot;/g, "/"");1157 return text;1158 },1159 1160 /** Function: xmlTextNode1161 * 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: xmlHtmlNode1177 * 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 escaped1189 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: getText1201 * 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: copyElement1229 * Copy an XML DOM element.1230 *1231 * This function copies a DOM element and all its descendants and returns1232 * 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: createHtml1263 * Copy an HTML DOM element into an XML DOM.1264 *1265 * This function copies a DOM element and all its descendants and returns1266 * 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 out1291 }1292 }1293 // filter out invalid css styles1294 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 elements1318 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: escapeNode1339 * 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: unescapeNode1364 * 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: getNodeFromJid1388 * 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: getDomainFromJid1403 * 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: getResourceFromJid1424 * 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: getBareJidFromJid1441 * 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: log1455 * User overrideable logging function.1456 *1457 * This function is called whenever the Strophe library calls any1458 * of the logging functions. The default implementation of this1459 * function does nothing. If client code wishes to handle the logging1460 * messages, it should override this with1461 * > Strophe.log = function (level, msg) {1462 * > (user code here)1463 * > };1464 *1465 * Please note that data sent and received over the wire is logged1466 * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().1467 *1468 * The different levels and their meanings are1469 *1470 * DEBUG - Messages useful for debugging purposes.1471 * INFO - Informational messages. This is mostly information like1472 * 'disconnect was called' or 'SASL auth succeeded'.1473 * WARN - Warnings about potential problems. This is mostly used1474 * 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 will1480 * 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: debug1491 * 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: info1502 * 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: warn1513 * 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: error1524 * 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: fatal1535 * 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: serialize1546 * 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].value1576 .replace(/&/g, "&amp;")1577 .replace(//'/g, "&apos;")1578 .replace(/>/g, "&gt;")1579 .replace(/</g, "&lt;") + "'";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 recurse1590 result += Strophe.serialize(child);1591 break;1592 case Strophe.ElementType.TEXT:1593 // text element to escape values1594 result += Strophe.xmlescape(child.nodeValue);1595 break;1596 case Strophe.ElementType.CDATA:1597 // cdata section so don't escape values1598 result += "<![CDATA["+child.nodeValue+"]]>";1599 }1600 }1601 result += "</" + nodeName + ">";1602 } else {1603 result += "/>";1604 }1605 1606 return result;1607 },1608 1609 /** PrivateVariable: _requestId1610 * _Private_ variable that keeps track of the request ids for1611 * connections.1612 */1613 _requestId: 0,1614 1615 /** PrivateVariable: Strophe.connectionPlugins1616 * _Private_ variable Used to store plugin names that need1617 * initialization on Strophe.Connection construction.1618 */1619 _connectionPlugins: {},1620 1621 /** Function: addConnectionPlugin1622 * 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.Builder1635 * XML DOM builder.1636 *1637 * This object provides an interface similar to JQuery but for building1638 * DOM element easily and rapidly. All the functions except for toString()1639 * and tree() return the object, so calls can be chained. Here's an1640 * 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 fragment1646 * > <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 be1652 * 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, do1656 * > 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.Builder1661 * Create a Strophe.Builder object.1662 *1663 * The attributes should be passed in object notation. For example1664 * > var b = new Builder('message', {to: 'you', from: 'me'});1665 * or1666 * > 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 elements1678 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: tree1695 * Return the DOM tree.1696 *1697 * This function returns the current DOM tree as an element object. This1698 * 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: toString1709 * Serialize the DOM tree to a String.1710 *1711 * This function returns a string serialization of the current DOM1712 * tree. It is often used internally to pass data to a1713 * 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: up1724 * 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 element1728 * > 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: attrs1740 * Add or modify attributes of the current element.1741 *1742 * The attributes should be passed in object notation. This function1743 * 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: c1766 * Add a child to the current element and make it the new current1767 * 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, it1771 * 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: cnode1792 * Add a child to the current element and make it the new current1793 * element.1794 *1795 * This function is the same as c() except that instead of using a1796 * name and an attributes object to create the child it uses an1797 * 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: t1824 * Add a child text element.1825 *1826 * This *does not* make the child the new current element since there1827 * 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: h1852 * Replace current element contents with the HTML passed in.1853 *1854 * This *does not* make the child the new current element1855 *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 tags1867 fragment.innerHTML = html;1868 1869 // copy cleaned html into an xml dom1870 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.Handler1880 * _Private_ helper class for managing stanza handlers.1881 *1882 * A Strophe.Handler encapsulates a user provided callback function to be1883 * executed when matching stanzas are received by the connection.1884 * Handlers can be either one-off or persistant depending on their1885 * return value. Returning true will cause a Handler to remain active, and1886 * returning false will remove the Handler.1887 *1888 * Users will not use Strophe.Handler objects directly, but instead they1889 * will use Strophe.Connection.addHandler() and1890 * Strophe.Connection.deleteHandler().1891 */1892 1893 /** PrivateConstructor: Strophe.Handler1894 * 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 options1904 *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 undefined1918 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 handler1929 this.user = true;1930 };1931 1932 Strophe.Handler.prototype = {1933 /** PrivateFunction: isMatch1934 * 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: run1980 * Run the callback on a matching stanza.1981 *1982 * Parameters:1983 * (XMLElement) elem - The DOM element that triggered the1984 * 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: toString2018 * 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.TimedHandler2031 * _Private_ helper class for managing timed handlers.2032 *2033 * A Strophe.TimedHandler encapsulates a user provided callback that2034 * should be called after a certain period of time or at regular2035 * intervals. The return value of the callback determines whether the2036 * Strophe.TimedHandler will continue to fire.2037 *2038 * Users will not use Strophe.TimedHandler objects directly, but instead2039 * they will use Strophe.Connection.addTimedHandler() and2040 * Strophe.Connection.deleteTimedHandler().2041 */2042 2043 /** PrivateConstructor: Strophe.TimedHandler2044 * Create and initialize a new Strophe.TimedHandler object.2045 *2046 * Parameters:2047 * (Integer) period - The number of milliseconds to wait before the2048 * handler is called.2049 * (Function) handler - The callback to run when the handler fires. This2050 * 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: run2066 * Run the callback for the Strophe.TimedHandler.2067 *2068 * Returns:2069 * true if the Strophe.TimedHandler should be called again, and false2070 * otherwise.2071 */2072 run: function ()2073 {2074 this.lastCalled = new Date().getTime();2075 return this.handler();2076 },2077 2078 /** PrivateFunction: reset2079 * Reset the last called time for the Strophe.TimedHandler.2080 */2081 reset: function ()2082 {2083 this.lastCalled = new Date().getTime();2084 },2085 2086 /** PrivateFunction: toString2087 * 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.Connection2099 * XMPP Connection manager.2100 *2101 * This class is the main part of Strophe. It manages a BOSH or websocket2102 * connection to an XMPP server and dispatches events to the user callbacks2103 * as data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA12104 * and legacy authentication.2105 *2106 * After creating a Strophe.Connection object, the user will typically2107 * call connect() with a user supplied callback to handle connection level2108 * events like authentication failure, disconnection, or connection2109 * complete.2110 *2111 * The user will also have several event handlers defined by using2112 * addHandler() and addTimedHandler(). These will allow the user code to2113 * respond to interesting stanzas or do something periodically with the2114 * connection. These handlers will be active once authentication is2115 * finished.2116 *2117 * To send data to the connection, use send().2118 */2119 2120 /** Constructor: Strophe.Connection2121 * Create and initialize a Strophe.Connection object.2122 *2123 * The transport-protocol for this connection will be chosen automatically2124 * based on the given service parameter. URLs starting with "ws://" or2125 * "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 protocol2129 * 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 you2136 * can tell Strophe to use WebSockets through a "protocol" attribute in the2137 * 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 call2140 *2141 * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"});2142 *2143 * Note that relative URLs _NOT_ starting with a "/" will also include the path2144 * of the current site.2145 *2146 * Also because downgrading security is not permitted by browsers, when using2147 * relative URLs both BOSH and WebSocket connections will use their secure2148 * 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 will2153 * 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 be2161 * included in the XMLHttpRequests made.2162 *2163 * The "keepalive" option can be used to instruct Strophe to maintain the2164 * current BOSH session across interruptions such as webpage reloads.2165 *2166 * It will do this by caching the sessions tokens in sessionStorage, and when2167 * "restore" is called it will check whether there are cached tokens with2168 * 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 options2173 *2174 * Returns:2175 * A new Strophe.Connection object.2176 */2177 Strophe.Connection = function (service, options)2178 {2179 //alert(service);2180 // The service URL2181 this.service = service;2182 2183 // Configuration options2184 this.options = options || {};2185 var proto = this.options.protocol || "";2186 2187 // Select protocal based on service or options2188 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 // SASL2203 this._sasl_data = {};2204 this.do_session = false;2205 this.do_bind = false;2206 2207 // handler lists2208 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 disconnecting2234 this.maxRetries = 5;2235 2236 // setup onIdle callback every 1/10th of a second2237 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);2238 2239 // initialize plugins2240 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 fine2244 var F = function () {}; // jshint ignore:line2245 F.prototype = ptype;2246 this[k] = new F();2247 this[k].init(this);2248 }2249 }2250 };2251 2252 Strophe.Connection.prototype = {2253 /** Function: reset2254 * Reset the connection.2255 *2256 * This function should be called after a connection is disconnected2257 * before that connection is reused.2258 */2259 reset: function ()2260 {2261 this._proto._reset();2262 2263 // SASL2264 this.do_session = false;2265 this.do_bind = false;2266 2267 // handler lists2268 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: pause2287 * Pause the request manager.2288 *2289 * This will prevent Strophe from sending any more requests to the2290 * server. This is very useful for temporarily pausing2291 * BOSH-Connections while a lot of send() calls are happening quickly.2292 * This causes Strophe to send the data in a single request, saving2293 * many request trips.2294 */2295 pause: function ()2296 {2297 this.paused = true;2298 },2299 2300 /** Function: resume2301 * 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: getUniqueId2311 * Generate a unique ID for use in <iq/> elements.2312 *2313 * All <iq/> stanzas are required to have unique id attributes. This2314 * function makes creating these easy. Each connection instance has2315 * a counter which starts from zero, and the value of this counter2316 * plus a colon followed by the suffix becomes the unique id. If no2317 * suffix is supplied, the counter is used as the unique id.2318 *2319 * Suffixes are used to make debugging easier when reading the stream2320 * data, and their use is recommended. The counter resets to 0 for2321 * every new connection for the same reason. For connections to the2322 * same server that authenticate the same way, all the ids should be2323 * the same, which makes it easy to see changes. This is useful for2324 * 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: connect2342 * Starts the connection process.2343 *2344 * As the connection process proceeds, the user supplied callback will2345 * be triggered multiple times with status updates. The callback2346 * 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.Status2349 * constants. The error condition will be one of the conditions2350 * defined in RFC 3920 or the condition 'strophe-parsererror'.2351 *2352 * The Parameters _wait_, _hold_ and _route_ are optional and only relevant2353 * for BOSH connections. Please see XEP 124 for a more detailed explanation2354 * 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 ANONYMOUS2359 * 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 the2363 * time the server will wait before returning an empty result for2364 * a request. The default setting of 60 seconds is recommended.2365 * (Integer) hold - The optional HTTPBIND hold value. This is the2366 * number of connections the server will hold at one time. This2367 * should almost always be set to 1 (the default).2368 * (String) route - The optional route value.2369 * (String) authcid - The optional alternative authentication identity2370 * (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: authzid2376 * Authorization identity.2377 */2378 this.authzid = Strophe.getBareJidFromJid(this.jid);2379 /** Variable: authcid2380 * Authentication identity (User name).2381 */2382 this.authcid = authcid || Strophe.getNodeFromJid(this.jid);2383 /** Variable: pass2384 * Authentication identity (User password).2385 */2386 this.pass = pass;2387 /** Variable: servtype2388 * 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 domain2398 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: attach2406 * Attach to an already created and authenticated BOSH session.2407 *2408 * This function is provided to allow Strophe to attach to BOSH2409 * sessions which have been created externally, perhaps by a Web2410 * application. This is often used to support auto-login type features2411 * 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 RID2417 * will be used by the next request.2418 * (Function) callback The connect callback function.2419 * (Integer) wait - The optional HTTPBIND wait value. This is the2420 * time the server will wait before returning an empty result for2421 * 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 the2424 * number of connections the server will hold at one time. This2425 * should almost always be set to 1 (the default).2426 * (Integer) wind - The optional HTTBIND window value. This is the2427 * 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: restore2442 * Attempt to restore a cached BOSH session.2443 *2444 * This function is only useful in conjunction with providing the2445 * "keepalive":true option when instantiating a new Strophe.Connection.2446 *2447 * When "keepalive" is set to true, Strophe will cache the BOSH tokens2448 * RID (Request ID) and SID (Session ID) and then when this function is2449 * called, it will attempt to restore the session from those cached2450 * 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.js2455 *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 the2460 * time the server will wait before returning an empty result for2461 * a request. The default setting of 60 seconds is recommended.2462 * (Integer) hold - The optional HTTPBIND hold value. This is the2463 * number of connections the server will hold at one time. This2464 * should almost always be set to 1 (the default).2465 * (Integer) wind - The optional HTTBIND window value. This is the2466 * 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: _sessionCachingSupported2481 * Checks whether sessionStorage and JSON are supported and whether we're2482 * 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: xmlInput2500 * User overrideable function that receives XML data coming into the2501 * connection.2502 *2503 * The default function does nothing. User code can override this with2504 * > Strophe.Connection.xmlInput = function (elem) {2505 * > (user code)2506 * > };2507 *2508 * Due to limitations of current Browsers' XML-Parsers the opening and closing2509 * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here.2510 *2511 * BOSH-Connections will have all stanzas wrapped in a <body> tag. See2512 * <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: xmlOutput2525 * User overrideable function that receives XML data sent to the2526 * connection.2527 *2528 * The default function does nothing. User code can override this with2529 * > Strophe.Connection.xmlOutput = function (elem) {2530 * > (user code)2531 * > };2532 *2533 * Due to limitations of current Browsers' XML-Parsers the opening and closing2534 * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here.2535 *2536 * BOSH-Connections will have all stanzas wrapped in a <body> tag. See2537 * <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: rawInput2550 * User overrideable function that receives raw data coming into the2551 * connection.2552 *2553 * The default function does nothing. User code can override this with2554 * > 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: rawOutput2569 * User overrideable function that receives raw data sent to the2570 * connection.2571 *2572 * The default function does nothing. User code can override this with2573 * > 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: send2588 * Send a stanza.2589 *2590 * This function is called to push data onto the send queue to2591 * go out over the wire. Whenever a request is sent to the BOSH2592 * 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: flush2616 * Immediately send any pending outgoing data.2617 *2618 * Normally send() queues outgoing data until the next idle period2619 * (100ms), which optimizes network use in the common cases when2620 * several send()s are called in succession. flush() can be used to2621 * immediately send all pending data.2622 */2623 flush: function ()2624 {2625 // cancel the pending idle period and run the idle function2626 // immediately2627 clearTimeout(this._idleTimeout);2628 this._onIdle();2629 },2630 2631 /** Function: sendIQ2632 * 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 timed2638 * out request. On timeout, the stanza will be null.2639 * (Integer) timeout - The time specified in milliseconds for a2640 * 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 found2655 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 one2665 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: " + expectedFrom2684 };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 " + iqtype2700 };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 handler2708 that.deleteHandler(handler);2709 // call errback on timeout with null stanza2710 if (errback) {2711 errback(null);2712 }2713 return false;2714 });2715 }2716 this.send(elem);2717 return id;2718 },2719 2720 /** PrivateFunction: _queueData2721 * Queue outgoing data for later sending. Also ensures that the data2722 * 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: _sendRestart2738 * 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: addTimedHandler2750 * Add a timed handler to the connection.2751 *2752 * This function adds a timed handler. The provided handler will2753 * be called every period milliseconds until it returns false,2754 * the connection is terminated, or the handler is removed. Handlers2755 * that wish to continue being invoked should return true.2756 *2757 * Because of method binding it is necessary to save the result of2758 * this function if you wish to remove a handler with2759 * deleteTimedHandler().2760 *2761 * Note that user handlers are not active until authentication is2762 * 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: deleteTimedHandler2779 * Delete a timed handler for a connection.2780 *2781 * This function removes a timed handler from the connection. The2782 * 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 change2791 // the handlers during iteration2792 this.removeTimeds.push(handRef);2793 },2794 2795 /** Function: addHandler2796 * Add a stanza handler for the connection.2797 *2798 * This function adds a stanza handler to the connection. The2799 * handler callback will be called for any stanza that matches2800 * 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 element2808 * and also any of its immediate children. This is primarily to make2809 * matching /iq/query elements easy.2810 *2811 * The options argument contains handler matching flags that affect how2812 * matches are determined. Currently the only flag is matchBare (a2813 * boolean). When matchBare is true, the from parameter and the from2814 * attribute on the stanza will be matched as bare JIDs instead of2815 * full JIDs. To use this, pass {matchBare: true} as the value of2816 * options. The default value for matchBare is false.2817 *2818 * The return value should be saved if you wish to remove the handler2819 * 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 options2829 *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: deleteHandler2841 * Delete a stanza handler for a connection.2842 *2843 * This function removes a stanza handler from the connection. The2844 * 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 change2853 // the handlers during iteration2854 this.removeHandlers.push(handRef);2855 // If a handler is being deleted while it is being added,2856 // prevent it from getting added2857 var i = this.addHandlers.indexOf(handRef);2858 if (i >= 0) {2859 this.addHandlers.splice(i, 1);2860 }2861 },2862 2863 /** Function: disconnect2864 * Start the graceful disconnection process.2865 *2866 * This function starts the disconnection process. This process starts2867 * by sending unavailable presence and sending BOSH body of type2868 * terminate. A timeout handler makes sure that disconnection happens2869 * even if the BOSH server does not respond.2870 * If the Connection object isn't connected, at least tries to abort all pending requests2871 * so the connection object won't generate successful requests (which were already opened).2872 *2873 * The user supplied connection callback will be notified of the2874 * 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 handler2894 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: _changeConnectStatus2904 * _Private_ helper function that makes sure plugins and the user's2905 * callback are notified of connection status changes.2906 *2907 * Parameters:2908 * (Integer) status - the new connection status, one of the values2909 * in Strophe.Status2910 * (String) condition - the error condition or null2911 */2912 _changeConnectStatus: function (status, condition)2913 {2914 // notify all plugins listening for status changes2915 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 callback2930 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: _doDisconnect2941 * _Private_ function to disconnect.2942 *2943 * This is the last piece of the disconnection logic. This resets the2944 * 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 Timeout2953 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 handlers2966 this.handlers = [];2967 this.timedHandlers = [];2968 this.removeTimeds = [];2969 this.removeHandlers = [];2970 this.addTimeds = [];2971 this.addHandlers = [];2972 2973 // tell the parent we disconnected2974 this._changeConnectStatus(Strophe.Status.DISCONNECTED, condition);2975 this.connected = false;2976 },2977 2978 /** PrivateFunction: _dataRecv2979 * _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. This2983 * function also fires stanza handlers that match each incoming2984 * 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 deletion3012 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 addition3022 while (this.addHandlers.length > 0) {3023 this.handlers.push(this.addHandlers.pop());3024 }3025 3026 // handle graceful disconnect3027 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 disconnect3036 if (this.disconnecting) {3037 return;3038 }3039 3040 // an error occurred3041 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 chain3056 var that = this;3057 Strophe.forEachChild(elem, null, function (child) {3058 var i, newList;3059 // process handlers3060 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 if3065 // one of the handlers throws an exception3066 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 false3077 Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message);3078 }3079 }3080 });3081 },3082 3083 3084 /** Attribute: mechanisms3085 * SASL Mechanisms available for Conncection.3086 */3087 mechanisms: {},3088 3089 /** PrivateFunction: _connect_cb3090 * _Private_ handler for initial connection request.3091 *3092 * This handler is used to process the initial connection request3093 * response from the BOSH server. It is used to set up authentication3094 * handlers and start the authentication process.3095 *3096 * SASL authentication will be attempted if available, otherwise3097 * 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 tag3142 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: authenticate3174 * Set up authentication3175 *3176 * Contiunues the initial connection request by setting up authentication3177 * handlers and start the authentication process.3178 *3179 * SASL authentication will be attempted if available, otherwise3180 * 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 mechanism3202 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.name3222 });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 worked3237 if (Strophe.getNodeFromJid(this.jid) === null) {3238 // we don't have a node, which is required for non-anonymous3239 // client connections3240 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 authentication3245 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.AUTH3255 }).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.SASL3267 });3268 if (response !== "") {3269 stanza.t(Base64.encode(response));3270 }3271 this.send(stanza.tree());3272 3273 return true;3274 },3275 3276 /** PrivateFunction: _auth1_cb3277 * _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/> and3281 * sends it, creating a handler (calling back to _auth2_cb()) to3282 * handle the result3283 *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 iq3294 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 pick3302 // a default one here. unlike other auth methods, the server3303 // 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_cb3318 * _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 handlers3339 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 handlers3357 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 now3380 this._sendRestart();3381 3382 return false;3383 },3384 3385 /** PrivateFunction: _sasl_auth1_cb3386 * _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 usage3397 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_cb3435 * _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 errors3456 var bind = elem.getElementsByTagName("bind");3457 var jidNode;3458 if (bind.length > 0) {3459 // Grab jid3460 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_cb3484 * _Private_ handler to finish successful SASL connection.3485 *3486 * This sets Connection.authenticated to true on success, which3487 * 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_cb3510 * _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 handlers3522 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_cb3539 * _Private_ handler to finish legacy authentication.3540 *3541 * This handler is called when the result from the jabber:iq:auth3542 * <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: _addSysTimedHandler3564 * _Private_ function to add a system level timed handler.3565 *3566 * This function is used to add a Strophe.TimedHandler for the3567 * library code. System timed handlers are allowed to run before3568 * 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: _addSysHandler3583 * _Private_ function to add a system level stanza handler.3584 *3585 * This function is used to add a Strophe.Handler for the3586 * library code. System stanza handlers are allowed to run before3587 * 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: _onDisconnectTimeout3605 * _Private_ timeout handler for handling non-graceful disconnection.3606 *3607 * If the graceful disconnect process does not complete within the3608 * 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 disconnect3620 this._doDisconnect();3621 3622 return false;3623 },3624 3625 /** PrivateFunction: _onIdle3626 * _Private_ handler to process events during idle cycle.3627 *3628 * This handler is called every 100ms to fire timed handlers that3629 * 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 addition3636 // NOTE: we add before remove in the case a timed handler is3637 // 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 deletion3643 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 handlers3652 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 connected3674 if (this.connected) {3675 this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);3676 }3677 }3678 };3679 3680 /** Class: Strophe.SASLMechanism3681 *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 on3686 * how to disable a mechanism.3687 *3688 * By default, all mechanisms are enabled and the priorities are3689 *3690 * SCRAM-SHA1 - 403691 * DIGEST-MD5 - 303692 * Plain - 203693 */3694 3695 /**3696 * PrivateConstructor: Strophe.SASLMechanism3697 * 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: name3709 * Mechanism name.3710 */3711 this.name = name;3712 /** PrivateVariable: isClientFirst3713 * If client sends response without initial server challenge.3714 */3715 this.isClientFirst = isClientFirst;3716 /** Variable: priority3717 * 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 are3721 *3722 * SCRAM-SHA1 - 403723 * DIGEST-MD5 - 303724 * Plain - 203725 *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: test3739 * Checks if mechanism able to run.3740 * To disable a mechanism, make this return false;3741 *3742 * To disable plain authentication run3743 * > 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: onStart3762 * 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: onChallenge3773 * Called by protocol implementation on incoming challenge. If client is3774 * 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: onFailure3790 * Protocol informs mechanism implementation about SASL failure.3791 */3792 onFailure: function() {3793 this._connection = null;3794 },3795 3796 /** PrivateFunction: onSuccess3797 * Protocol informs mechanism implementation about SASL success.3798 */3799 onSuccess: function() {3800 this._connection = null;3801 }3802 };3803 3804 /** Constants: SASL mechanisms3805 * Available authentication mechanisms3806 *3807 * Strophe.SASLAnonymous - SASL Anonymous authentication.3808 * Strophe.SASLPlain - SASL Plain authentication.3809 * Strophe.SASLMD5 - SASL Digest-MD5 authentication3810 * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication3811 */3812 3813 // Building SASL callbacks3814 3815 /** PrivateConstructor: SASLAnonymous3816 * 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: SASLPlain3829 * 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: SASLSHA13851 * 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 exchange3857 * when the client doesn't support channel bindings (username 'user' and3858 * password 'pencil' are used):3859 *3860 * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL3861 * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,3862 * i=40963863 * 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: SASLMD53954 * 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: _quote3965 * _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 string3972 */3973 Strophe.SASLMD5.prototype._quote = function (str)3974 {3975 return '"' + str.replace(////g, "////").replace(/"/g, '//"') + '"';3976 //" end string workaround for emacs3977 };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, LLC4061 */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.$build4072 );4073 });4074 } else {4075 // Browser globals4076 return factory(Strophe, $build);4077 }4078 }(this, function (Strophe, $build) {4079 4080 /** PrivateClass: Strophe.Request4081 * _Private_ helper class that provides a cross implementation abstraction4082 * for a BOSH related XMLHttpRequest.4083 *4084 * The Strophe.Request class is used internally to encapsulate BOSH request4085 * information. It is not meant to be used from user's code.4086 */4087 4088 /** PrivateConstructor: Strophe.Request4089 * 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 the4094 * 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 been4097 * 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 request4105 // 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: getResponse4129 * Get a response from the underlying XMLHttpRequest.4130 *4131 * This function attempts to get a response from the request and checks4132 * 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: _newXHR4163 * _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 argument4183 xhr.onreadystatechange = this.func.bind(null, this);4184 4185 return xhr;4186 }4187 };4188 4189 /** Class: Strophe.Bosh4190 * _Private_ helper class that handles BOSH Connections4191 *4192 * The Strophe.Bosh class is used internally by Strophe.Connection4193 * to encapsulate BOSH sessions. It is not meant to be used from user's code.4194 */4195 4196 /** File: bosh.js4197 * 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.Bosh4205 * 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 values4221 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: strip4231 *4232 * BOSH-Connections will have all stanzas wrapped in a <body> tag when4233 * 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 both4239 * <Strophe.Connection.xmlInput> and <Strophe.Connection.xmlOutput>.4240 */4241 strip: null,4242 4243 /** PrivateFunction: _buildBody4244 * _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.HTTPBIND4254 });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: _reset4265 * Reset the connection.4266 *4267 * This function is called by the reset function of the Strophe Connection4268 */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: _connect4278 * _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 tag4289 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.BOSH4298 });4299 4300 if(route){4301 body.attrs({4302 route: route4303 });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: _attach4317 * Attach to an already created and authenticated BOSH session.4318 *4319 * This function is provided to allow Strophe to attach to BOSH4320 * sessions which have been created externally, perhaps by a Web4321 * application. This is often used to support auto-login type features4322 * 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 RID4328 * will be used by the next request.4329 * (Function) callback The connect callback function.4330 * (Integer) wait - The optional HTTPBIND wait value. This is the4331 * time the server will wait before returning an empty result for4332 * 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 the4335 * number of connections the server will hold at one time. This4336 * should almost always be set to 1 (the default).4337 * (Integer) wind - The optional HTTBIND window value. This is the4338 * 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: _restore4361 * Attempt to restore a cached BOSH session4362 *4363 * Parameters:4364 * (String) jid - The full JID that is bound by the session.4365 * This parameter is optional but recommended, specifically in cases4366 * where prebinded BOSH sessions are used where it's important to know4367 * that the right session is being restored.4368 * (Function) callback The connect callback function.4369 * (Integer) wait - The optional HTTPBIND wait value. This is the4370 * time the server will wait before returning an empty result for4371 * 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 the4374 * number of connections the server will hold at one time. This4375 * should almost always be set to 1 (the default).4376 * (Integer) wind - The optional HTTBIND window value. This is the4377 * 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: _cacheSession4397 * _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.sid4411 }));4412 }4413 } else {4414 window.sessionStorage.removeItem('strophe-bosh-session');4415 }4416 },4417 4418 /** PrivateFunction: _connect_cb4419 * _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 occurred4431 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 is4447 // called multiple times in the case of missing stream:features4448 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: _disconnect4460 * _Private_ part of Connection.disconnect for Bosh4461 *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: _doDisconnect4471 * _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: _emptyQueue4483 * _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: _hitError4494 * _Private_ function to handle the error count.4495 *4496 * Requests are resent automatically until their error count reaches4497 * 5. Each time an error is encountered, this function is called to4498 * 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_received4514 *4515 * Called on stream start/restart when no stream:features4516 * 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: _onDisconnectTimeout4535 * _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: _abortAllRequests4544 * _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 func4553 // is necessary for IE64554 req.xhr.onreadystatechange = function () {}; // jshint ignore:line4555 }4556 },4557 4558 /** PrivateFunction: _onIdle4559 * _Private_ handler called by Strophe.Connection._onIdle4560 *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, poll4567 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.BOSH4588 });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: _onRequestStateChange4624 * _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 that4628 * requests can fail, and calls the request callback when requests4629 * 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 complete4647 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. works4654 // around a browser bug4655 }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 queue4673 this._removeRequest(req);4674 Strophe.debug("request id " +4675 req.id +4676 " should now be removed");4677 }4678 4679 // request succeeded4680 if (reqStatus == 200) {4681 // if request 1 finished, or request 0 finished and request4682 // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to4683 // restart the other - both will be in the first spot, as the4684 // completed request has been removed from the queue already4685 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 handler4691 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: _processRequest4720 * _Private_ function to process a request in the queue.4721 *4722 * This function takes requests off the queue and sends them and4723 * 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 retries4748 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 function4771 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 immediately4797 // or on a gradually expanding retry window for reconnects4798 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 instantaneous4813 if (req.sends > 1) {4814 // Using a cube of the retry number creates a nicely4815 // expanding retry window4816 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: _removeRequest4844 * _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 function4861 req.xhr.onreadystatechange = function () {};4862 4863 this._throttledRequestHandler();4864 },4865 4866 /** PrivateFunction: _restartRequest4867 * _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: _reqToData4883 * _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: _sendTerminate4905 * _Private_ function to send initial disconnect sequence.4906 *4907 * This is the first step in a graceful disconnect. It sends4908 * the BOSH server a terminate body and includes an unavailable4909 * 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: _send4930 * _Private_ part of the Connection.send function for BOSH4931 *4932 * Just triggers the RequestHandler to send the messages that are in the queue4933 */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: _sendRestart4941 *4942 * Send an xmpp:restart stanza.4943 */4944 _sendRestart: function ()4945 {4946 this._throttledRequestHandler();4947 clearTimeout(this._conn._idleTimeout);4948 },4949 4950 /** PrivateFunction: _throttledRequestHandler4951 * _Private_ function to throttle requests to the connection window.4952 *4953 * This function makes sure we don't send requests so fast that the4954 * request ids overflow the connection window in the case that one4955 * 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, LLC4990 */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.$build5001 );5002 });5003 } else {5004 // Browser globals5005 return factory(Strophe, $build);5006 }5007 }(this, function (Strophe, $build) {5008 5009 /** Class: Strophe.WebSocket5010 * _Private_ helper class that handles WebSocket Connections5011 *5012 * The Strophe.WebSocket class is used internally by Strophe.Connection5013 * to encapsulate WebSocket sessions. It is not meant to be used from user's code.5014 */5015 5016 /** File: websocket.js5017 * 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/rfc73955024 *5025 * WebSocket support implemented by Andreas Guth ([email protected])5026 */5027 5028 /** PrivateConstructor: Strophe.Websocket5029 * 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 absolute5046 // 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: _buildStream5069 * _Private_ helper function to generate the <stream> start tag for WebSockets5070 *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_streamerror5084 * _Private_ checks a message for stream:error5085 *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_error5134 this._conn._changeConnectStatus(connectstatus, condition);5135 this._conn._doDisconnect();5136 return true;5137 },5138 5139 /** PrivateFunction: _reset5140 * 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: _connect5151 * _Private_ function called by Strophe.Connection.connect5152 *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 WobSocket5161 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_cb5169 * _Private_ function called by Strophe.Connection._connect_cb5170 *5171 * checks for stream:error5172 *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: _handleStreamStart5184 * _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 /> tag5195 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_wrapper5219 * _Private_ function that handles the first connection messages.5220 *5221 * On receiving an opening stream tag this callback replaces itself with the real5222 * 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 one5227 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 error5235 if (this._handleStreamStart(streamStart)) {5236 //_connect_cb will check for stream:error and disconnect on error5237 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: _disconnect5261 * _Private_ function called by Strophe.Connection.disconnect5262 *5263 * Disconnects and sends a last stanza if one is given5264 *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: _doDisconnect5288 * _Private_ function to disconnect.5289 *5290 * Just closes the Socket for WebSockets5291 */5292 _doDisconnect: function ()5293 {5294 Strophe.info("WebSockets _doDisconnect was called");5295 this._closeSocket();5296 },5297 5298 /** PrivateFunction _streamWrap5299 * _Private_ helper function to wrap a stanza in a <stream> tag.5300 * This is used so Strophe can process stanzas from WebSockets like BOSH5301 */5302 _streamWrap: function (stanza)5303 {5304 return "<wrapper>" + stanza + '</wrapper>';5305 },5306 5307 5308 /** PrivateFunction: _closeSocket5309 * _Private_ function to close the WebSocket.5310 *5311 * Closes the socket if it is still open and deletes it5312 */5313 _closeSocket: function ()5314 {5315 if (this.socket) { try {5316 this.socket.close();5317 } catch (e) {} }5318 this.socket = null;5319 },5320 5321 /** PrivateFunction: _emptyQueue5322 * _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: _onClose5333 * _Private_ function to handle websockets closing.5334 *5335 * Nothing to do here for WebSockets5336 */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_received5347 *5348 * Called on stream start/restart when no stream:features5349 * 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: _onDisconnectTimeout5363 * _Private_ timeout handler for handling non-graceful disconnection.5364 *5365 * This does nothing for WebSockets5366 */5367 _onDisconnectTimeout: function () {},5368 5369 /** PrivateFunction: _abortAllRequests5370 * _Private_ helper function that makes sure all pending requests are aborted.5371 */5372 _abortAllRequests: function () {},5373 5374 /** PrivateFunction: _onError5375 * _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: _onIdle5387 * _Private_ function called by Strophe.Connection._onIdle5388 *5389 * sends all queued stanzas5390 */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: _onMessage5413 * _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 stream5427 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 restarts5437 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 disconnecting5452 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 and5458 // wait for the </stream:stream> tag before we close the connection5459 return;5460 }5461 this._conn._dataRecv(elem, message.data);5462 },5463 5464 /** PrivateFunction: _onOpen5465 * _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: _reqToData5480 * _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: _send5496 * _Private_ part of the Connection.send function for WebSocket5497 *5498 * Just flushes the messages that are in the queue5499 */5500 _send: function () {5501 this._conn.flush();5502 },5503 5504 /** PrivateFunction: _sendRestart5505 *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 }

 



相关阅读:
Top