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, "&amp;");
815 text = text.replace(/</g, "&lt;");
816 text = text.replace(/>/g, "&gt;");
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("&", "&amp;")
1138 .replace("'", "&apos;")
1139 .replace("<", "&lt;") + "'";
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 });