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