support-chat/js/strophe.js

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
 
1188
/** Class: Strophe.Builder
1189
 *  XML DOM builder.
1190
 *
1191
 *  This object provides an interface similar to JQuery but for building
1192
 *  DOM element easily and rapidly.  All the functions except for toString()
1193
 *  and tree() return the object, so calls can be chained.  Here's an
1194
 *  example using the $iq() builder helper.
1195
 *  > $iq({to: 'you': from: 'me': type: 'get', id: '1'})
1196
 *  >     .c('query', {xmlns: 'strophe:example'})
1197
 *  >     .c('example')
1198
 *  >     .toString()
1199
 *  The above generates this XML fragment
1200
 *  > <iq to='you' from='me' type='get' id='1'>
1201
 *  >   <query xmlns='strophe:example'>
1202
 *  >     <example/>
1203
 *  >   </query>
1204
 *  > </iq>
1205
 *  The corresponding DOM manipulations to get a similar fragment would be
1206
 *  a lot more tedious and probably involve several helper variables.
1207
 *
1208
 *  Since adding children makes new operations operate on the child, up()
1209
 *  is provided to traverse up the tree.  To add two children, do
1210
 *  > builder.c('child1', ...).up().c('child2', ...)
1211
 *  The next operation on the Builder will be relative to the second child.
1212
 */
1213
 
1214
/** Constructor: Strophe.Builder
1215
 *  Create a Strophe.Builder object.
1216
 *
1217
 *  The attributes should be passed in object notation.  For example
1218
 *  > var b = new Builder('message', {to: 'you', from: 'me'});
1219
 *  or
1220
 *  > var b = new Builder('messsage', {'xml:lang': 'en'});
1221
 *
1222
 *  Parameters:
1223
 *    (String) name - The name of the root element.
1224
 *    (Object) attrs - The attributes for the root element in object notation.
1225
 *
1226
 *  Returns:
1227
 *    A new Strophe.Builder.
1228
 */
1229
Strophe.Builder = function (name, attrs)
1230
{
1231
    // Set correct namespace for jabber:client elements
1232
    if (name == "presence" || name == "message" || name == "iq") {
1233
        if (attrs && !attrs.xmlns) {
1234
            attrs.xmlns = Strophe.NS.CLIENT;
1235
        } else if (!attrs) {
1236
            attrs = {xmlns: Strophe.NS.CLIENT};
1237
        }
1238
    }
1239
 
1240
    // Holds the tree being built.
1241
    this.nodeTree = Strophe.xmlElement(name, attrs);
1242
 
1243
    // Points to the current operation node.
1244
    this.node = this.nodeTree;
1245
};
1246
 
1247
Strophe.Builder.prototype = {
1248
    /** Function: tree
1249
     *  Return the DOM tree.
1250
     *
1251
     *  This function returns the current DOM tree as an element object.  This
1252
     *  is suitable for passing to functions like Strophe.Connection.send().
1253
     *
1254
     *  Returns:
1255
     *    The DOM tree as a element object.
1256
     */
1257
    tree: function ()
1258
    {
1259
        return this.nodeTree;
1260
    },
1261
 
1262
    /** Function: toString
1263
     *  Serialize the DOM tree to a String.
1264
     *
1265
     *  This function returns a string serialization of the current DOM
1266
     *  tree.  It is often used internally to pass data to a
1267
     *  Strophe.Request object.
1268
     *
1269
     *  Returns:
1270
     *    The serialized DOM tree in a String.
1271
     */
1272
    toString: function ()
1273
    {
1274
        return Strophe.serialize(this.nodeTree);
1275
    },
1276
 
1277
    /** Function: up
1278
     *  Make the current parent element the new current element.
1279
     *
1280
     *  This function is often used after c() to traverse back up the tree.
1281
     *  For example, to add two children to the same element
1282
     *  > builder.c('child1', {}).up().c('child2', {});
1283
     *
1284
     *  Returns:
1285
     *    The Stophe.Builder object.
1286
     */
1287
    up: function ()
1288
    {
1289
        this.node = this.node.parentNode;
1290
        return this;
1291
    },
1292
 
1293
    /** Function: attrs
1294
     *  Add or modify attributes of the current element.
1295
     *
1296
     *  The attributes should be passed in object notation.  This function
1297
     *  does not move the current element pointer.
1298
     *
1299
     *  Parameters:
1300
     *    (Object) moreattrs - The attributes to add/modify in object notation.
1301
     *
1302
     *  Returns:
1303
     *    The Strophe.Builder object.
1304
     */
1305
    attrs: function (moreattrs)
1306
    {
1307
        for (var k in moreattrs) {
1308
            if (moreattrs.hasOwnProperty(k)) {
1309
                this.node.setAttribute(k, moreattrs[k]);
1310
            }
1311
        }
1312
        return this;
1313
    },
1314
 
1315
    /** Function: c
1316
     *  Add a child to the current element and make it the new current
1317
     *  element.
1318
     *
1319
     *  This function moves the current element pointer to the child.  If you
1320
     *  need to add another child, it is necessary to use up() to go back
1321
     *  to the parent in the tree.
1322
     *
1323
     *  Parameters:
1324
     *    (String) name - The name of the child.
1325
     *    (Object) attrs - The attributes of the child in object notation.
1326
     *
1327
     *  Returns:
1328
     *    The Strophe.Builder object.
1329
     */
1330
    c: function (name, attrs)
1331
    {
1332
        var child = Strophe.xmlElement(name, attrs);
1333
        this.node.appendChild(child);
1334
        this.node = child;
1335
        return this;
1336
    },
1337
 
1338
    /** Function: cnode
1339
     *  Add a child to the current element and make it the new current
1340
     *  element.
1341
     *
1342
     *  This function is the same as c() except that instead of using a
1343
     *  name and an attributes object to create the child it uses an
1344
     *  existing DOM element object.
1345
     *
1346
     *  Parameters:
1347
     *    (XMLElement) elem - A DOM element.
1348
     *
1349
     *  Returns:
1350
     *    The Strophe.Builder object.
1351
     */
1352
    cnode: function (elem)
1353
    {
1354
        this.node.appendChild(elem);
1355
        this.node = elem;
1356
        return this;
1357
    },
1358
 
1359
    /** Function: t
1360
     *  Add a child text element.
1361
     *
1362
     *  This *does not* make the child the new current element since there
1363
     *  are no children of text elements.
1364
     *
1365
     *  Parameters:
1366
     *    (String) text - The text data to append to the current element.
1367
     *
1368
     *  Returns:
1369
     *    The Strophe.Builder object.
1370
     */
1371
    t: function (text)
1372
    {
1373
        var child = Strophe.xmlTextNode(text);
1374
        this.node.appendChild(child);
1375
        return this;
1376
    }
1377
};
1378
 
1379
 
1380
/** PrivateClass: Strophe.Handler
1381
 *  _Private_ helper class for managing stanza handlers.
1382
 *
1383
 *  A Strophe.Handler encapsulates a user provided callback function to be
1384
 *  executed when matching stanzas are received by the connection.
1385
 *  Handlers can be either one-off or persistant depending on their
1386
 *  return value. Returning true will cause a Handler to remain active, and
1387
 *  returning false will remove the Handler.
1388
 *
1389
 *  Users will not use Strophe.Handler objects directly, but instead they
1390
 *  will use Strophe.Connection.addHandler() and
1391
 *  Strophe.Connection.deleteHandler().
1392
 */
1393
 
1394
/** PrivateConstructor: Strophe.Handler
1395
 *  Create and initialize a new Strophe.Handler.
1396
 *
1397
 *  Parameters:
1398
 *    (Function) handler - A function to be executed when the handler is run.
1399
 *    (String) ns - The namespace to match.
1400
 *    (String) name - The element name to match.
1401
 *    (String) type - The element type to match.
1402
 *    (String) id - The element id attribute to match.
1403
 *    (String) from - The element from attribute to match.
1404
 *    (Object) options - Handler options
1405
 *
1406
 *  Returns:
1407
 *    A new Strophe.Handler object.
1408
 */
1409
Strophe.Handler = function (handler, ns, name, type, id, from, options)
1410
{
1411
    this.handler = handler;
1412
    this.ns = ns;
1413
    this.name = name;
1414
    this.type = type;
1415
    this.id = id;
1416
    this.options = options || {matchbare: false};
1417
    
1418
    // default matchBare to false if undefined
1419
    if (!this.options.matchBare) {
1420
        this.options.matchBare = false;
1421
    }
1422
 
1423
    if (this.options.matchBare) {
1424
        this.from = Strophe.getBareJidFromJid(from);
1425
    } else {
1426
        this.from = from;
1427
    }
1428
 
1429
    // whether the handler is a user handler or a system handler
1430
    this.user = true;
1431
};
1432
 
1433
Strophe.Handler.prototype = {
1434
    /** PrivateFunction: isMatch
1435
     *  Tests if a stanza matches the Strophe.Handler.
1436
     *
1437
     *  Parameters:
1438
     *    (XMLElement) elem - The XML element to test.
1439
     *
1440
     *  Returns:
1441
     *    true if the stanza matches and false otherwise.
1442
     */
1443
    isMatch: function (elem)
1444
    {
1445
        var nsMatch;
1446
        var from = null;
1447
        
1448
        if (this.options.matchBare) {
1449
            from = Strophe.getBareJidFromJid(elem.getAttribute('from'));
1450
        } else {
1451
            from = elem.getAttribute('from');
1452
        }
1453
 
1454
        nsMatch = false;
1455
        if (!this.ns) {
1456
            nsMatch = true;
1457
        } else {
1458
            var self = this;
1459
            Strophe.forEachChild(elem, null, function (elem) {
1460
                if (elem.getAttribute("xmlns") == self.ns) {
1461
                    nsMatch = true;
1462
                }
1463
            });
1464
 
1465
            nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns;
1466
        }
1467
 
1468
        if (nsMatch &&
1469
            (!this.name || Strophe.isTagEqual(elem, this.name)) &&
1470
            (!this.type || elem.getAttribute("type") === this.type) &&
1471
            (!this.id || elem.getAttribute("id") === this.id) &&
1472
            (!this.from || from === this.from)) {
1473
                return true;
1474
        }
1475
 
1476
        return false;
1477
    },
1478
 
1479
    /** PrivateFunction: run
1480
     *  Run the callback on a matching stanza.
1481
     *
1482
     *  Parameters:
1483
     *    (XMLElement) elem - The DOM element that triggered the
1484
     *      Strophe.Handler.
1485
     *
1486
     *  Returns:
1487
     *    A boolean indicating if the handler should remain active.
1488
     */
1489
    run: function (elem)
1490
    {
1491
        var result = null;
1492
        try {
1493
            result = this.handler(elem);
1494
        } catch (e) {
1495
            if (e.sourceURL) {
1496
                Strophe.fatal("error: " + this.handler +
1497
                              " " + e.sourceURL + ":" +
1498
                              e.line + " - " + e.name + ": " + e.message);
1499
            } else if (e.fileName) {
1500
                if (typeof(console) != "undefined") {
1501
                    console.trace();
1502
                    console.error(this.handler, " - error - ", e, e.message);
1503
                }
1504
                Strophe.fatal("error: " + this.handler + " " +
1505
                              e.fileName + ":" + e.lineNumber + " - " +
1506
                              e.name + ": " + e.message);
1507
            } else {
1508
                Strophe.fatal("error: " + this.handler);
1509
            }
1510
 
1511
            throw e;
1512
        }
1513
 
1514
        return result;
1515
    },
1516
 
1517
    /** PrivateFunction: toString
1518
     *  Get a String representation of the Strophe.Handler object.
1519
     *
1520
     *  Returns:
1521
     *    A String.
1522
     */
1523
    toString: function ()
1524
    {
1525
        return "{Handler: " + this.handler + "(" + this.name + "," +
1526
            this.id + "," + this.ns + ")}";
1527
    }
1528
};
1529
 
1530
/** PrivateClass: Strophe.TimedHandler
1531
 *  _Private_ helper class for managing timed handlers.
1532
 *
1533
 *  A Strophe.TimedHandler encapsulates a user provided callback that
1534
 *  should be called after a certain period of time or at regular
1535
 *  intervals.  The return value of the callback determines whether the
1536
 *  Strophe.TimedHandler will continue to fire.
1537
 *
1538
 *  Users will not use Strophe.TimedHandler objects directly, but instead
1539
 *  they will use Strophe.Connection.addTimedHandler() and
1540
 *  Strophe.Connection.deleteTimedHandler().
1541
 */
1542
 
1543
/** PrivateConstructor: Strophe.TimedHandler
1544
 *  Create and initialize a new Strophe.TimedHandler object.
1545
 *
1546
 *  Parameters:
1547
 *    (Integer) period - The number of milliseconds to wait before the
1548
 *      handler is called.
1549
 *    (Function) handler - The callback to run when the handler fires.  This
1550
 *      function should take no arguments.
1551
 *
1552
 *  Returns:
1553
 *    A new Strophe.TimedHandler object.
1554
 */
1555
Strophe.TimedHandler = function (period, handler)
1556
{
1557
    this.period = period;
1558
    this.handler = handler;
1559
 
1560
    this.lastCalled = new Date().getTime();
1561
    this.user = true;
1562
};
1563
 
1564
Strophe.TimedHandler.prototype = {
1565
    /** PrivateFunction: run
1566
     *  Run the callback for the Strophe.TimedHandler.
1567
     *
1568
     *  Returns:
1569
     *    true if the Strophe.TimedHandler should be called again, and false
1570
     *      otherwise.
1571
     */
1572
    run: function ()
1573
    {
1574
        this.lastCalled = new Date().getTime();
1575
        return this.handler();
1576
    },
1577
 
1578
    /** PrivateFunction: reset
1579
     *  Reset the last called time for the Strophe.TimedHandler.
1580
     */
1581
    reset: function ()
1582
    {
1583
        this.lastCalled = new Date().getTime();
1584
    },
1585
 
1586
    /** PrivateFunction: toString
1587
     *  Get a string representation of the Strophe.TimedHandler object.
1588
     *
1589
     *  Returns:
1590
     *    The string representation.
1591
     */
1592
    toString: function ()
1593
    {
1594
        return "{TimedHandler: " + this.handler + "(" + this.period +")}";
1595
    }
1596
};
1597
 
1598
/** PrivateClass: Strophe.Request
1599
 *  _Private_ helper class that provides a cross implementation abstraction
1600
 *  for a BOSH related XMLHttpRequest.
1601
 *
1602
 *  The Strophe.Request class is used internally to encapsulate BOSH request
1603
 *  information.  It is not meant to be used from user's code.
1604
 */
1605
 
1606
/** PrivateConstructor: Strophe.Request
1607
 *  Create and initialize a new Strophe.Request object.
1608
 *
1609
 *  Parameters:
1610
 *    (XMLElement) elem - The XML data to be sent in the request.
1611
 *    (Function) func - The function that will be called when the
1612
 *      XMLHttpRequest readyState changes.
1613
 *    (Integer) rid - The BOSH rid attribute associated with this request.
1614
 *    (Integer) sends - The number of times this same request has been
1615
 *      sent.
1616
 */
1617
Strophe.Request = function (elem, func, rid, sends)
1618
{
1619
    this.id = ++Strophe._requestId;
1620
    this.xmlData = elem;
1621
    this.data = Strophe.serialize(elem);
1622
    // save original function in case we need to make a new request
1623
    // from this one.
1624
    this.origFunc = func;
1625
    this.func = func;
1626
    this.rid = rid;
1627
    this.date = NaN;
1628
    this.sends = sends || 0;
1629
    this.abort = false;
1630
    this.dead = null;
1631
    this.age = function () {
1632
        if (!this.date) { return 0; }
1633
        var now = new Date();
1634
        return (now - this.date) / 1000;
1635
    };
1636
    this.timeDead = function () {
1637
        if (!this.dead) { return 0; }
1638
        var now = new Date();
1639
        return (now - this.dead) / 1000;
1640
    };
1641
    this.xhr = this._newXHR();
1642
};
1643
 
1644
Strophe.Request.prototype = {
1645
    /** PrivateFunction: getResponse
1646
     *  Get a response from the underlying XMLHttpRequest.
1647
     *
1648
     *  This function attempts to get a response from the request and checks
1649
     *  for errors.
1650
     *
1651
     *  Throws:
1652
     *    "parsererror" - A parser error occured.
1653
     *
1654
     *  Returns:
1655
     *    The DOM element tree of the response.
1656
     */
1657
    getResponse: function ()
1658
    {
1659
        var node = null;
1660
        if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
1661
            node = this.xhr.responseXML.documentElement;
1662
            if (node.tagName == "parsererror") {
1663
                Strophe.error("invalid response received");
1664
                Strophe.error("responseText: " + this.xhr.responseText);
1665
                Strophe.error("responseXML: " +
1666
                              Strophe.serialize(this.xhr.responseXML));
1667
                throw "parsererror";
1668
            }
1669
        } else if (this.xhr.responseText) {
1670
            Strophe.error("invalid response received");
1671
            Strophe.error("responseText: " + this.xhr.responseText);
1672
            Strophe.error("responseXML: " +
1673
                          Strophe.serialize(this.xhr.responseXML));
1674
        }
1675
 
1676
        return node;
1677
    },
1678
 
1679
    /** PrivateFunction: _newXHR
1680
     *  _Private_ helper function to create XMLHttpRequests.
1681
     *
1682
     *  This function creates XMLHttpRequests across all implementations.
1683
     *
1684
     *  Returns:
1685
     *    A new XMLHttpRequest.
1686
     */
1687
    _newXHR: function ()
1688
    {
1689
        var xhr = null;
1690
        if (window.XMLHttpRequest) {
1691
            xhr = new XMLHttpRequest();
1692
            if (xhr.overrideMimeType) {
1693
                xhr.overrideMimeType("text/xml");
1694
            }
1695
        } else if (window.ActiveXObject) {
1696
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
1697
        }
1698
 
1699
        xhr.onreadystatechange = this.func.prependArg(this);
1700
 
1701
        return xhr;
1702
    }
1703
};
1704
 
1705
/** Class: Strophe.Connection
1706
 *  XMPP Connection manager.
1707
 *
1708
 *  Thie class is the main part of Strophe.  It manages a BOSH connection
1709
 *  to an XMPP server and dispatches events to the user callbacks as
1710
 *  data arrives.  It supports SASL PLAIN, SASL DIGEST-MD5, and legacy
1711
 *  authentication.
1712
 *
1713
 *  After creating a Strophe.Connection object, the user will typically
1714
 *  call connect() with a user supplied callback to handle connection level
1715
 *  events like authentication failure, disconnection, or connection
1716
 *  complete.
1717
 *
1718
 *  The user will also have several event handlers defined by using
1719
 *  addHandler() and addTimedHandler().  These will allow the user code to
1720
 *  respond to interesting stanzas or do something periodically with the
1721
 *  connection.  These handlers will be active once authentication is
1722
 *  finished.
1723
 *
1724
 *  To send data to the connection, use send().
1725
 */
1726
 
1727
/** Constructor: Strophe.Connection
1728
 *  Create and initialize a Strophe.Connection object.
1729
 *
1730
 *  Parameters:
1731
 *    (String) service - The BOSH service URL.
1732
 *
1733
 *  Returns:
1734
 *    A new Strophe.Connection object.
1735
 */
1736
Strophe.Connection = function (service)
1737
{
1738
    /* The path to the httpbind service. */
1739
    this.service = service;
1740
    /* The connected JID. */
1741
    this.jid = "";
1742
    /* request id for body tags */
1743
    this.rid = Math.floor(Math.random() * 4294967295);
1744
    /* The current session ID. */
1745
    this.sid = null;
1746
    this.streamId = null;
1747
 
1748
    // SASL
1749
    this.do_session = false;
1750
    this.do_bind = false;
1751
 
1752
    // handler lists
1753
    this.timedHandlers = [];
1754
    this.handlers = [];
1755
    this.removeTimeds = [];
1756
    this.removeHandlers = [];
1757
    this.addTimeds = [];
1758
    this.addHandlers = [];
1759
 
1760
    this._idleTimeout = null;
1761
    this._disconnectTimeout = null;
1762
 
1763
    this.authenticated = false;
1764
    this.disconnecting = false;
1765
    this.connected = false;
1766
 
1767
    this.errors = 0;
1768
 
1769
    this.paused = false;
1770
 
1771
    // default BOSH values
1772
    this.hold = 1;
1773
    this.wait = 60;
1774
    this.window = 5;
1775
 
1776
    this._data = [];
1777
    this._requests = [];
1778
    this._uniqueId = Math.round(Math.random() * 10000);
1779
 
1780
    this._sasl_success_handler = null;
1781
    this._sasl_failure_handler = null;
1782
    this._sasl_challenge_handler = null;
1783
 
1784
    // setup onIdle callback every 1/10th of a second
1785
    this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
1786
 
1787
    // initialize plugins
1788
    for (var k in Strophe._connectionPlugins) {
1789
        if (Strophe._connectionPlugins.hasOwnProperty(k)) {
1790
	    var ptype = Strophe._connectionPlugins[k];
1791
            // jslint complaints about the below line, but this is fine
1792
            var F = function () {};
1793
            F.prototype = ptype;
1794
            this[k] = new F();
1795
	    this[k].init(this);
1796
        }
1797
    }
1798
};
1799
 
1800
Strophe.Connection.prototype = {
1801
    /** Function: reset
1802
     *  Reset the connection.
1803
     *
1804
     *  This function should be called after a connection is disconnected
1805
     *  before that connection is reused.
1806
     */
1807
    reset: function ()
1808
    {
1809
        this.rid = Math.floor(Math.random() * 4294967295);
1810
 
1811
        this.sid = null;
1812
        this.streamId = null;
1813
 
1814
        // SASL
1815
        this.do_session = false;
1816
        this.do_bind = false;
1817
 
1818
        // handler lists
1819
        this.timedHandlers = [];
1820
        this.handlers = [];
1821
        this.removeTimeds = [];
1822
        this.removeHandlers = [];
1823
        this.addTimeds = [];
1824
        this.addHandlers = [];
1825
 
1826
        this.authenticated = false;
1827
        this.disconnecting = false;
1828
        this.connected = false;
1829
 
1830
        this.errors = 0;
1831
 
1832
        this._requests = [];
1833
        this._uniqueId = Math.round(Math.random()*10000);
1834
    },
1835
 
1836
    /** Function: pause
1837
     *  Pause the request manager.
1838
     *
1839
     *  This will prevent Strophe from sending any more requests to the
1840
     *  server.  This is very useful for temporarily pausing while a lot
1841
     *  of send() calls are happening quickly.  This causes Strophe to
1842
     *  send the data in a single request, saving many request trips.
1843
     */
1844
    pause: function ()
1845
    {
1846
        this.paused = true;
1847
    },
1848
 
1849
    /** Function: resume
1850
     *  Resume the request manager.
1851
     *
1852
     *  This resumes after pause() has been called.
1853
     */
1854
    resume: function ()
1855
    {
1856
        this.paused = false;
1857
    },
1858
 
1859
    /** Function: getUniqueId
1860
     *  Generate a unique ID for use in <iq/> elements.
1861
     *
1862
     *  All <iq/> stanzas are required to have unique id attributes.  This
1863
     *  function makes creating these easy.  Each connection instance has
1864
     *  a counter which starts from zero, and the value of this counter
1865
     *  plus a colon followed by the suffix becomes the unique id. If no
1866
     *  suffix is supplied, the counter is used as the unique id.
1867
     *
1868
     *  Suffixes are used to make debugging easier when reading the stream
1869
     *  data, and their use is recommended.  The counter resets to 0 for
1870
     *  every new connection for the same reason.  For connections to the
1871
     *  same server that authenticate the same way, all the ids should be
1872
     *  the same, which makes it easy to see changes.  This is useful for
1873
     *  automated testing as well.
1874
     *
1875
     *  Parameters:
1876
     *    (String) suffix - A optional suffix to append to the id.
1877
     *
1878
     *  Returns:
1879
     *    A unique string to be used for the id attribute.
1880
     */
1881
    getUniqueId: function (suffix)
1882
    {
1883
        if (typeof(suffix) == "string" || typeof(suffix) == "number") {
1884
            return ++this._uniqueId + ":" + suffix;
1885
        } else {
1886
            return ++this._uniqueId + "";
1887
        }
1888
    },
1889
 
1890
    /** Function: connect
1891
     *  Starts the connection process.
1892
     *
1893
     *  As the connection process proceeds, the user supplied callback will
1894
     *  be triggered multiple times with status updates.  The callback
1895
     *  should take two arguments - the status code and the error condition.
1896
     *
1897
     *  The status code will be one of the values in the Strophe.Status
1898
     *  constants.  The error condition will be one of the conditions
1899
     *  defined in RFC 3920 or the condition 'strophe-parsererror'.
1900
     *
1901
     *  Please see XEP 124 for a more detailed explanation of the optional
1902
     *  parameters below.
1903
     *
1904
     *  Parameters:
1905
     *    (String) jid - The user's JID.  This may be a bare JID,
1906
     *      or a full JID.  If a node is not supplied, SASL ANONYMOUS
1907
     *      authentication will be attempted.
1908
     *    (String) pass - The user's password.
1909
     *    (Function) callback The connect callback function.
1910
     *    (Integer) wait - The optional HTTPBIND wait value.  This is the
1911
     *      time the server will wait before returning an empty result for
1912
     *      a request.  The default setting of 60 seconds is recommended.
1913
     *      Other settings will require tweaks to the Strophe.TIMEOUT value.
1914
     *    (Integer) hold - The optional HTTPBIND hold value.  This is the
1915
     *      number of connections the server will hold at one time.  This
1916
     *      should almost always be set to 1 (the default).
1917
     */
1918
    connect: function (jid, pass, callback, wait, hold)
1919
    {
1920
        this.jid = jid;
1921
        this.pass = pass;
1922
        this.connect_callback = callback;
1923
        this.disconnecting = false;
1924
        this.connected = false;
1925
        this.authenticated = false;
1926
        this.errors = 0;
1927
 
1928
        this.wait = wait || this.wait;
1929
        this.hold = hold || this.hold;
1930
 
1931
        // parse jid for domain and resource
1932
        this.domain = Strophe.getDomainFromJid(this.jid);
1933
 
1934
        // build the body tag
1935
        var body = this._buildBody().attrs({
1936
            to: this.domain,
1937
            "xml:lang": "en",
1938
            wait: this.wait,
1939
            hold: this.hold,
1940
            content: "text/xml; charset=utf-8",
1941
            ver: "1.6",
1942
            "xmpp:version": "1.0",
1943
            "xmlns:xmpp": Strophe.NS.BOSH
1944
        });
1945
 
1946
        this._changeConnectStatus(Strophe.Status.CONNECTING, null);
1947
 
1948
        this._requests.push(
1949
            new Strophe.Request(body.tree(),
1950
                                this._onRequestStateChange.bind(this)
1951
                                    .prependArg(this._connect_cb.bind(this)),
1952
                                body.tree().getAttribute("rid")));
1953
        this._throttledRequestHandler();
1954
    },
1955
 
1956
    /** Function: attach
1957
     *  Attach to an already created and authenticated BOSH session.
1958
     *
1959
     *  This function is provided to allow Strophe to attach to BOSH
1960
     *  sessions which have been created externally, perhaps by a Web
1961
     *  application.  This is often used to support auto-login type features
1962
     *  without putting user credentials into the page.
1963
     *
1964
     *  Parameters:
1965
     *    (String) jid - The full JID that is bound by the session.
1966
     *    (String) sid - The SID of the BOSH session.
1967
     *    (String) rid - The current RID of the BOSH session.  This RID
1968
     *      will be used by the next request.
1969
     *    (Function) callback The connect callback function.
1970
     *    (Integer) wait - The optional HTTPBIND wait value.  This is the
1971
     *      time the server will wait before returning an empty result for
1972
     *      a request.  The default setting of 60 seconds is recommended.
1973
     *      Other settings will require tweaks to the Strophe.TIMEOUT value.
1974
     *    (Integer) hold - The optional HTTPBIND hold value.  This is the
1975
     *      number of connections the server will hold at one time.  This
1976
     *      should almost always be set to 1 (the default).
1977
     *    (Integer) wind - The optional HTTBIND window value.  This is the
1978
     *      allowed range of request ids that are valid.  The default is 5.
1979
     */
1980
    attach: function (jid, sid, rid, callback, wait, hold, wind)
1981
    {
1982
        this.jid = jid;
1983
        this.sid = sid;
1984
        this.rid = rid;
1985
        this.connect_callback = callback;
1986
 
1987
        this.domain = Strophe.getDomainFromJid(this.jid);
1988
 
1989
        this.authenticated = true;
1990
        this.connected = true;
1991
 
1992
        this.wait = wait || this.wait;
1993
        this.hold = hold || this.hold;
1994
        this.window = wind || this.window;
1995
 
1996
        this._changeConnectStatus(Strophe.Status.ATTACHED, null);
1997
    },
1998
 
1999
    /** Function: xmlInput
2000
     *  User overrideable function that receives XML data coming into the
2001
     *  connection.
2002
     *
2003
     *  The default function does nothing.  User code can override this with
2004
     *  > Strophe.Connection.xmlInput = function (elem) {
2005
     *  >   (user code)
2006
     *  > };
2007
     *
2008
     *  Parameters:
2009
     *    (XMLElement) elem - The XML data received by the connection.
2010
     */
2011
    xmlInput: function (elem)
2012
    {
2013
        return;
2014
    },
2015
 
2016
    /** Function: xmlOutput
2017
     *  User overrideable function that receives XML data sent to the
2018
     *  connection.
2019
     *
2020
     *  The default function does nothing.  User code can override this with
2021
     *  > Strophe.Connection.xmlOutput = function (elem) {
2022
     *  >   (user code)
2023
     *  > };
2024
     *
2025
     *  Parameters:
2026
     *    (XMLElement) elem - The XMLdata sent by the connection.
2027
     */
2028
    xmlOutput: function (elem)
2029
    {
2030
        return;
2031
    },
2032
 
2033
    /** Function: rawInput
2034
     *  User overrideable function that receives raw data coming into the
2035
     *  connection.
2036
     *
2037
     *  The default function does nothing.  User code can override this with
2038
     *  > Strophe.Connection.rawInput = function (data) {
2039
     *  >   (user code)
2040
     *  > };
2041
     *
2042
     *  Parameters:
2043
     *    (String) data - The data received by the connection.
2044
     */
2045
    rawInput: function (data)
2046
    {
2047
        return;
2048
    },
2049
 
2050
    /** Function: rawOutput
2051
     *  User overrideable function that receives raw data sent to the
2052
     *  connection.
2053
     *
2054
     *  The default function does nothing.  User code can override this with
2055
     *  > Strophe.Connection.rawOutput = function (data) {
2056
     *  >   (user code)
2057
     *  > };
2058
     *
2059
     *  Parameters:
2060
     *    (String) data - The data sent by the connection.
2061
     */
2062
    rawOutput: function (data)
2063
    {
2064
        return;
2065
    },
2066
 
2067
    /** Function: send
2068
     *  Send a stanza.
2069
     *
2070
     *  This function is called to push data onto the send queue to
2071
     *  go out over the wire.  Whenever a request is sent to the BOSH
2072
     *  server, all pending data is sent and the queue is flushed.
2073
     *
2074
     *  Parameters:
2075
     *    (XMLElement |
2076
     *     [XMLElement] |
2077
     *     Strophe.Builder) elem - The stanza to send.
2078
     */
2079
    send: function (elem)
2080
    {
2081
        if (elem === null) { return ; }
2082
        if (typeof(elem.sort) === "function") {
2083
            for (var i = 0; i < elem.length; i++) {
2084
                this._queueData(elem[i]);
2085
            }
2086
        } else if (typeof(elem.tree) === "function") {
2087
            this._queueData(elem.tree());
2088
        } else {
2089
            this._queueData(elem);
2090
        }
2091
 
2092
        this._throttledRequestHandler();
2093
        clearTimeout(this._idleTimeout);
2094
        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
2095
    },
2096
 
2097
    /** Function: flush
2098
     *  Immediately send any pending outgoing data.
2099
     *  
2100
     *  Normally send() queues outgoing data until the next idle period
2101
     *  (100ms), which optimizes network use in the common cases when
2102
     *  several send()s are called in succession. flush() can be used to 
2103
     *  immediately send all pending data.
2104
     */
2105
    flush: function ()
2106
    {
2107
        // cancel the pending idle period and run the idle function
2108
        // immediately
2109
        clearTimeout(this._idleTimeout);
2110
        this._onIdle();
2111
    },
2112
 
2113
    /** Function: sendIQ
2114
     *  Helper function to send IQ stanzas.
2115
     *
2116
     *  Parameters:
2117
     *    (XMLElement) elem - The stanza to send.
2118
     *    (Function) callback - The callback function for a successful request.
2119
     *    (Function) errback - The callback function for a failed or timed 
2120
     *      out request.  On timeout, the stanza will be null.
2121
     *    (Integer) timeout - The time specified in milliseconds for a 
2122
     *      timeout to occur.
2123
     *
2124
     *  Returns:
2125
     *    The id used to send the IQ.
2126
    */
2127
    sendIQ: function(elem, callback, errback, timeout) {
2128
        var timeoutHandler = null;
2129
        var that = this;
2130
 
2131
        if (typeof(elem.tree) === "function") {
2132
            elem = elem.tree();
2133
        }
2134
	var id = elem.getAttribute('id');
2135
 
2136
	// inject id if not found
2137
	if (!id) {
2138
	    id = this.getUniqueId("sendIQ");
2139
	    elem.setAttribute("id", id);
2140
	}
2141
 
2142
	var handler = this.addHandler(function (stanza) {
2143
	    // remove timeout handler if there is one
2144
            if (timeoutHandler) {
2145
                that.deleteTimedHandler(timeoutHandler);
2146
            }
2147
 
2148
            var iqtype = stanza.getAttribute('type');
2149
	    if (iqtype === 'result') {
2150
		if (callback) {
2151
                    callback(stanza);
2152
                }
2153
	    } else if (iqtype === 'error') {
2154
		if (errback) {
2155
                    errback(stanza);
2156
                }
2157
	    } else {
2158
                throw {
2159
                    name: "StropheError",
2160
                    message: "Got bad IQ type of " + iqtype
2161
                };
2162
            }
2163
	}, null, 'iq', null, id);
2164
 
2165
	// if timeout specified, setup timeout handler.
2166
	if (timeout) {
2167
	    timeoutHandler = this.addTimedHandler(timeout, function () {
2168
                // get rid of normal handler
2169
                that.deleteHandler(handler);
2170
 
2171
	        // call errback on timeout with null stanza
2172
                if (errback) {
2173
		    errback(null);
2174
                }
2175
		return false;
2176
	    });
2177
	}
2178
 
2179
	this.send(elem);
2180
 
2181
	return id;
2182
    },
2183
 
2184
    /** PrivateFunction: _queueData
2185
     *  Queue outgoing data for later sending.  Also ensures that the data
2186
     *  is a DOMElement.
2187
     */
2188
    _queueData: function (element) {
2189
        if (element === null ||
2190
            !element.tagName ||
2191
            !element.childNodes) {
2192
            throw {
2193
                name: "StropheError",
2194
                message: "Cannot queue non-DOMElement."
2195
            };
2196
        }
2197
        
2198
        this._data.push(element);
2199
    },
2200
 
2201
    /** PrivateFunction: _sendRestart
2202
     *  Send an xmpp:restart stanza.
2203
     */
2204
    _sendRestart: function ()
2205
    {
2206
        this._data.push("restart");
2207
 
2208
        this._throttledRequestHandler();
2209
        clearTimeout(this._idleTimeout);
2210
        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
2211
    },
2212
 
2213
    /** Function: addTimedHandler
2214
     *  Add a timed handler to the connection.
2215
     *
2216
     *  This function adds a timed handler.  The provided handler will
2217
     *  be called every period milliseconds until it returns false,
2218
     *  the connection is terminated, or the handler is removed.  Handlers
2219
     *  that wish to continue being invoked should return true.
2220
     *
2221
     *  Because of method binding it is necessary to save the result of
2222
     *  this function if you wish to remove a handler with
2223
     *  deleteTimedHandler().
2224
     *
2225
     *  Note that user handlers are not active until authentication is
2226
     *  successful.
2227
     *
2228
     *  Parameters:
2229
     *    (Integer) period - The period of the handler.
2230
     *    (Function) handler - The callback function.
2231
     *
2232
     *  Returns:
2233
     *    A reference to the handler that can be used to remove it.
2234
     */
2235
    addTimedHandler: function (period, handler)
2236
    {
2237
        var thand = new Strophe.TimedHandler(period, handler);
2238
        this.addTimeds.push(thand);
2239
        return thand;
2240
    },
2241
 
2242
    /** Function: deleteTimedHandler
2243
     *  Delete a timed handler for a connection.
2244
     *
2245
     *  This function removes a timed handler from the connection.  The
2246
     *  handRef parameter is *not* the function passed to addTimedHandler(),
2247
     *  but is the reference returned from addTimedHandler().
2248
     *
2249
     *  Parameters:
2250
     *    (Strophe.TimedHandler) handRef - The handler reference.
2251
     */
2252
    deleteTimedHandler: function (handRef)
2253
    {
2254
        // this must be done in the Idle loop so that we don't change
2255
        // the handlers during iteration
2256
        this.removeTimeds.push(handRef);
2257
    },
2258
 
2259
    /** Function: addHandler
2260
     *  Add a stanza handler for the connection.
2261
     *
2262
     *  This function adds a stanza handler to the connection.  The
2263
     *  handler callback will be called for any stanza that matches
2264
     *  the parameters.  Note that if multiple parameters are supplied,
2265
     *  they must all match for the handler to be invoked.
2266
     *
2267
     *  The handler will receive the stanza that triggered it as its argument.
2268
     *  The handler should return true if it is to be invoked again;
2269
     *  returning false will remove the handler after it returns.
2270
     *
2271
     *  As a convenience, the ns parameters applies to the top level element
2272
     *  and also any of its immediate children.  This is primarily to make
2273
     *  matching /iq/query elements easy.
2274
     *
2275
     *  The options argument contains handler matching flags that affect how
2276
     *  matches are determined. Currently the only flag is matchBare (a
2277
     *  boolean). When matchBare is true, the from parameter and the from
2278
     *  attribute on the stanza will be matched as bare JIDs instead of
2279
     *  full JIDs. To use this, pass {matchBare: true} as the value of
2280
     *  options. The default value for matchBare is false. 
2281
     *
2282
     *  The return value should be saved if you wish to remove the handler
2283
     *  with deleteHandler().
2284
     *
2285
     *  Parameters:
2286
     *    (Function) handler - The user callback.
2287
     *    (String) ns - The namespace to match.
2288
     *    (String) name - The stanza name to match.
2289
     *    (String) type - The stanza type attribute to match.
2290
     *    (String) id - The stanza id attribute to match.
2291
     *    (String) from - The stanza from attribute to match.
2292
     *    (String) options - The handler options
2293
     *
2294
     *  Returns:
2295
     *    A reference to the handler that can be used to remove it.
2296
     */
2297
    addHandler: function (handler, ns, name, type, id, from, options)
2298
    {
2299
        var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
2300
        this.addHandlers.push(hand);
2301
        return hand;
2302
    },
2303
 
2304
    /** Function: deleteHandler
2305
     *  Delete a stanza handler for a connection.
2306
     *
2307
     *  This function removes a stanza handler from the connection.  The
2308
     *  handRef parameter is *not* the function passed to addHandler(),
2309
     *  but is the reference returned from addHandler().
2310
     *
2311
     *  Parameters:
2312
     *    (Strophe.Handler) handRef - The handler reference.
2313
     */
2314
    deleteHandler: function (handRef)
2315
    {
2316
        // this must be done in the Idle loop so that we don't change
2317
        // the handlers during iteration
2318
        this.removeHandlers.push(handRef);
2319
    },
2320
 
2321
    /** Function: disconnect
2322
     *  Start the graceful disconnection process.
2323
     *
2324
     *  This function starts the disconnection process.  This process starts
2325
     *  by sending unavailable presence and sending BOSH body of type
2326
     *  terminate.  A timeout handler makes sure that disconnection happens
2327
     *  even if the BOSH server does not respond.
2328
     *
2329
     *  The user supplied connection callback will be notified of the
2330
     *  progress as this process happens.
2331
     *
2332
     *  Parameters:
2333
     *    (String) reason - The reason the disconnect is occuring.
2334
     */
2335
    disconnect: function (reason)
2336
    {
2337
        this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
2338
 
2339
        Strophe.info("Disconnect was called because: " + reason);
2340
        if (this.connected) {
2341
            // setup timeout handler
2342
            this._disconnectTimeout = this._addSysTimedHandler(
2343
                3000, this._onDisconnectTimeout.bind(this));
2344
            this._sendTerminate();
2345
        }
2346
    },
2347
 
2348
    /** PrivateFunction: _changeConnectStatus
2349
     *  _Private_ helper function that makes sure plugins and the user's
2350
     *  callback are notified of connection status changes.
2351
     *
2352
     *  Parameters:
2353
     *    (Integer) status - the new connection status, one of the values
2354
     *      in Strophe.Status
2355
     *    (String) condition - the error condition or null
2356
     */
2357
    _changeConnectStatus: function (status, condition)
2358
    {
2359
        // notify all plugins listening for status changes
2360
        for (var k in Strophe._connectionPlugins) {
2361
            if (Strophe._connectionPlugins.hasOwnProperty(k)) {
2362
                var plugin = this[k];
2363
                if (plugin.statusChanged) {
2364
                    try {
2365
                        plugin.statusChanged(status, condition);
2366
                    } catch (err) {
2367
                        Strophe.error("" + k + " plugin caused an exception " +
2368
                                      "changing status: " + err);
2369
                    }
2370
                }
2371
            }
2372
        }
2373
 
2374
        // notify the user's callback
2375
        if (this.connect_callback) {
2376
            try {
2377
                this.connect_callback(status, condition);
2378
            } catch (e) {
2379
                Strophe.error("User connection callback caused an " +
2380
                              "exception: " + e);
2381
            }
2382
        }
2383
    },
2384
 
2385
    /** PrivateFunction: _buildBody
2386
     *  _Private_ helper function to generate the <body/> wrapper for BOSH.
2387
     *
2388
     *  Returns:
2389
     *    A Strophe.Builder with a <body/> element.
2390
     */
2391
    _buildBody: function ()
2392
    {
2393
        var bodyWrap = $build('body', {
2394
            rid: this.rid++,
2395
            xmlns: Strophe.NS.HTTPBIND
2396
        });
2397
 
2398
        if (this.sid !== null) {
2399
            bodyWrap.attrs({sid: this.sid});
2400
        }
2401
 
2402
        return bodyWrap;
2403
    },
2404
 
2405
    /** PrivateFunction: _removeRequest
2406
     *  _Private_ function to remove a request from the queue.
2407
     *
2408
     *  Parameters:
2409
     *    (Strophe.Request) req - The request to remove.
2410
     */
2411
    _removeRequest: function (req)
2412
    {
2413
        Strophe.debug("removing request");
2414
 
2415
        var i;
2416
        for (i = this._requests.length - 1; i >= 0; i--) {
2417
            if (req == this._requests[i]) {
2418
                this._requests.splice(i, 1);
2419
            }
2420
        }
2421
 
2422
        // IE6 fails on setting to null, so set to empty function
2423
        req.xhr.onreadystatechange = function () {};
2424
 
2425
        this._throttledRequestHandler();
2426
    },
2427
 
2428
    /** PrivateFunction: _restartRequest
2429
     *  _Private_ function to restart a request that is presumed dead.
2430
     *
2431
     *  Parameters:
2432
     *    (Integer) i - The index of the request in the queue.
2433
     */
2434
    _restartRequest: function (i)
2435
    {
2436
        var req = this._requests[i];
2437
        if (req.dead === null) {
2438
            req.dead = new Date();
2439
        }
2440
 
2441
        this._processRequest(i);
2442
    },
2443
 
2444
    /** PrivateFunction: _processRequest
2445
     *  _Private_ function to process a request in the queue.
2446
     *
2447
     *  This function takes requests off the queue and sends them and
2448
     *  restarts dead requests.
2449
     *
2450
     *  Parameters:
2451
     *    (Integer) i - The index of the request in the queue.
2452
     */
2453
    _processRequest: function (i)
2454
    {
2455
        var req = this._requests[i];
2456
        var reqStatus = -1;
2457
 
2458
        try {
2459
            if (req.xhr.readyState == 4) {
2460
                reqStatus = req.xhr.status;
2461
            }
2462
        } catch (e) {
2463
            Strophe.error("caught an error in _requests[" + i +
2464
                          "], reqStatus: " + reqStatus);
2465
        }
2466
 
2467
        if (typeof(reqStatus) == "undefined") {
2468
            reqStatus = -1;
2469
        }
2470
 
2471
        var time_elapsed = req.age();
2472
        var primaryTimeout = (!isNaN(time_elapsed) &&
2473
                              time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait));
2474
        var secondaryTimeout = (req.dead !== null &&
2475
                                req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait));
2476
        var requestCompletedWithServerError = (req.xhr.readyState == 4 &&
2477
                                               (reqStatus < 1 ||
2478
                                                reqStatus >= 500));
2479
        if (primaryTimeout || secondaryTimeout ||
2480
            requestCompletedWithServerError) {
2481
            if (secondaryTimeout) {
2482
                Strophe.error("Request " +
2483
                              this._requests[i].id +
2484
                              " timed out (secondary), restarting");
2485
            }
2486
            req.abort = true;
2487
            req.xhr.abort();
2488
            // setting to null fails on IE6, so set to empty function
2489
            req.xhr.onreadystatechange = function () {};
2490
            this._requests[i] = new Strophe.Request(req.xmlData,
2491
                                                    req.origFunc,
2492
                                                    req.rid,
2493
                                                    req.sends);
2494
            req = this._requests[i];
2495
        }
2496
 
2497
        if (req.xhr.readyState === 0) {
2498
            Strophe.debug("request id " + req.id +
2499
                          "." + req.sends + " posting");
2500
 
2501
            req.date = new Date();
2502
            try {
2503
                req.xhr.open("POST", this.service, true);
2504
            } catch (e2) {
2505
                Strophe.error("XHR open failed.");
2506
                if (!this.connected) {
2507
                    this._changeConnectStatus(Strophe.Status.CONNFAIL,
2508
                                              "bad-service");
2509
                }
2510
                this.disconnect();
2511
                return;
2512
            }
2513
 
2514
            // Fires the XHR request -- may be invoked immediately
2515
            // or on a gradually expanding retry window for reconnects
2516
            var sendFunc = function () {
2517
                req.xhr.send(req.data);
2518
            };
2519
 
2520
            // Implement progressive backoff for reconnects --
2521
            // First retry (send == 1) should also be instantaneous
2522
            if (req.sends > 1) {
2523
                // Using a cube of the retry number creats a nicely
2524
                // expanding retry window
2525
                var backoff = Math.pow(req.sends, 3) * 1000;
2526
                setTimeout(sendFunc, backoff);
2527
            } else {
2528
                sendFunc();
2529
            }
2530
 
2531
            req.sends++;
2532
 
2533
            this.xmlOutput(req.xmlData);
2534
            this.rawOutput(req.data);
2535
        } else {
2536
            Strophe.debug("_processRequest: " +
2537
                          (i === 0 ? "first" : "second") +
2538
                          " request has readyState of " +
2539
                          req.xhr.readyState);
2540
        }
2541
    },
2542
 
2543
    /** PrivateFunction: _throttledRequestHandler
2544
     *  _Private_ function to throttle requests to the connection window.
2545
     *
2546
     *  This function makes sure we don't send requests so fast that the
2547
     *  request ids overflow the connection window in the case that one
2548
     *  request died.
2549
     */
2550
    _throttledRequestHandler: function ()
2551
    {
2552
        if (!this._requests) {
2553
            Strophe.debug("_throttledRequestHandler called with " +
2554
                          "undefined requests");
2555
        } else {
2556
            Strophe.debug("_throttledRequestHandler called with " +
2557
                          this._requests.length + " requests");
2558
        }
2559
 
2560
        if (!this._requests || this._requests.length === 0) {
2561
            return;
2562
        }
2563
 
2564
        if (this._requests.length > 0) {
2565
            this._processRequest(0);
2566
        }
2567
 
2568
        if (this._requests.length > 1 &&
2569
            Math.abs(this._requests[0].rid -
2570
                     this._requests[1].rid) < this.window - 1) {
2571
            this._processRequest(1);
2572
        }
2573
    },
2574
 
2575
    /** PrivateFunction: _onRequestStateChange
2576
     *  _Private_ handler for Strophe.Request state changes.
2577
     *
2578
     *  This function is called when the XMLHttpRequest readyState changes.
2579
     *  It contains a lot of error handling logic for the many ways that
2580
     *  requests can fail, and calls the request callback when requests
2581
     *  succeed.
2582
     *
2583
     *  Parameters:
2584
     *    (Function) func - The handler for the request.
2585
     *    (Strophe.Request) req - The request that is changing readyState.
2586
     */
2587
    _onRequestStateChange: function (func, req)
2588
    {
2589
        Strophe.debug("request id " + req.id +
2590
                      "." + req.sends + " state changed to " +
2591
                      req.xhr.readyState);
2592
 
2593
        if (req.abort) {
2594
            req.abort = false;
2595
            return;
2596
        }
2597
 
2598
        // request complete
2599
        var reqStatus;
2600
        if (req.xhr.readyState == 4) {
2601
            reqStatus = 0;
2602
            try {
2603
                reqStatus = req.xhr.status;
2604
            } catch (e) {
2605
                // ignore errors from undefined status attribute.  works
2606
                // around a browser bug
2607
            }
2608
 
2609
            if (typeof(reqStatus) == "undefined") {
2610
                reqStatus = 0;
2611
            }
2612
 
2613
            if (this.disconnecting) {
2614
                if (reqStatus >= 400) {
2615
                    this._hitError(reqStatus);
2616
                    return;
2617
                }
2618
            }
2619
 
2620
            var reqIs0 = (this._requests[0] == req);
2621
            var reqIs1 = (this._requests[1] == req);
2622
 
2623
            if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
2624
                // remove from internal queue
2625
                this._removeRequest(req);
2626
                Strophe.debug("request id " +
2627
                              req.id +
2628
                              " should now be removed");
2629
            }
2630
 
2631
            // request succeeded
2632
            if (reqStatus == 200) {
2633
                // if request 1 finished, or request 0 finished and request
2634
                // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
2635
                // restart the other - both will be in the first spot, as the
2636
                // completed request has been removed from the queue already
2637
                if (reqIs1 ||
2638
                    (reqIs0 && this._requests.length > 0 &&
2639
                     this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) {
2640
                    this._restartRequest(0);
2641
                }
2642
                // call handler
2643
                Strophe.debug("request id " +
2644
                              req.id + "." +
2645
                              req.sends + " got 200");
2646
                func(req);
2647
                this.errors = 0;
2648
            } else {
2649
                Strophe.error("request id " +
2650
                              req.id + "." +
2651
                              req.sends + " error " + reqStatus +
2652
                              " happened");
2653
                if (reqStatus === 0 ||
2654
                    (reqStatus >= 400 && reqStatus < 600) ||
2655
                    reqStatus >= 12000) {
2656
                    this._hitError(reqStatus);
2657
                    if (reqStatus >= 400 && reqStatus < 500) {
2658
                        this._changeConnectStatus(Strophe.Status.DISCONNECTING,
2659
                                                  null);
2660
                        this._doDisconnect();
2661
                    }
2662
                }
2663
            }
2664
 
2665
            if (!((reqStatus > 0 && reqStatus < 10000) ||
2666
                  req.sends > 5)) {
2667
                this._throttledRequestHandler();
2668
            }
2669
        }
2670
    },
2671
 
2672
    /** PrivateFunction: _hitError
2673
     *  _Private_ function to handle the error count.
2674
     *
2675
     *  Requests are resent automatically until their error count reaches
2676
     *  5.  Each time an error is encountered, this function is called to
2677
     *  increment the count and disconnect if the count is too high.
2678
     *
2679
     *  Parameters:
2680
     *    (Integer) reqStatus - The request status.
2681
     */
2682
    _hitError: function (reqStatus)
2683
    {
2684
        this.errors++;
2685
        Strophe.warn("request errored, status: " + reqStatus +
2686
                     ", number of errors: " + this.errors);
2687
        if (this.errors > 4) {
2688
            this._onDisconnectTimeout();
2689
        }
2690
    },
2691
 
2692
    /** PrivateFunction: _doDisconnect
2693
     *  _Private_ function to disconnect.
2694
     *
2695
     *  This is the last piece of the disconnection logic.  This resets the
2696
     *  connection and alerts the user's connection callback.
2697
     */
2698
    _doDisconnect: function ()
2699
    {
2700
        Strophe.info("_doDisconnect was called");
2701
        this.authenticated = false;
2702
        this.disconnecting = false;
2703
        this.sid = null;
2704
        this.streamId = null;
2705
        this.rid = Math.floor(Math.random() * 4294967295);
2706
 
2707
        // tell the parent we disconnected
2708
        if (this.connected) {
2709
            this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
2710
            this.connected = false;
2711
        }
2712
 
2713
        // delete handlers
2714
        this.handlers = [];
2715
        this.timedHandlers = [];
2716
        this.removeTimeds = [];
2717
        this.removeHandlers = [];
2718
        this.addTimeds = [];
2719
        this.addHandlers = [];
2720
    },
2721
 
2722
    /** PrivateFunction: _dataRecv
2723
     *  _Private_ handler to processes incoming data from the the connection.
2724
     *
2725
     *  Except for _connect_cb handling the initial connection request,
2726
     *  this function handles the incoming data for all requests.  This
2727
     *  function also fires stanza handlers that match each incoming
2728
     *  stanza.
2729
     *
2730
     *  Parameters:
2731
     *    (Strophe.Request) req - The request that has data ready.
2732
     */
2733
    _dataRecv: function (req)
2734
    {
2735
        try {
2736
            var elem = req.getResponse();
2737
        } catch (e) {
2738
            if (e != "parsererror") { throw e; }
2739
            this.disconnect("strophe-parsererror");
2740
        }
2741
        if (elem === null) { return; }
2742
 
2743
        this.xmlInput(elem);
2744
        this.rawInput(Strophe.serialize(elem));
2745
 
2746
        // remove handlers scheduled for deletion
2747
        var i, hand;
2748
        while (this.removeHandlers.length > 0) {
2749
            hand = this.removeHandlers.pop();
2750
            i = this.handlers.indexOf(hand);
2751
            if (i >= 0) {
2752
                this.handlers.splice(i, 1);
2753
            }
2754
        }
2755
 
2756
        // add handlers scheduled for addition
2757
        while (this.addHandlers.length > 0) {
2758
            this.handlers.push(this.addHandlers.pop());
2759
        }
2760
 
2761
        // handle graceful disconnect
2762
        if (this.disconnecting && this._requests.length === 0) {
2763
            this.deleteTimedHandler(this._disconnectTimeout);
2764
            this._disconnectTimeout = null;
2765
            this._doDisconnect();
2766
            return;
2767
        }
2768
 
2769
        var typ = elem.getAttribute("type");
2770
        var cond, conflict;
2771
        if (typ !== null && typ == "terminate") {
2772
            // an error occurred
2773
            cond = elem.getAttribute("condition");
2774
            conflict = elem.getElementsByTagName("conflict");
2775
            if (cond !== null) {
2776
                if (cond == "remote-stream-error" && conflict.length > 0) {
2777
                    cond = "conflict";
2778
                }
2779
                this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
2780
            } else {
2781
                this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
2782
            }
2783
            this.disconnect();
2784
            return;
2785
        }
2786
 
2787
        // send each incoming stanza through the handler chain
2788
        var self = this;
2789
        Strophe.forEachChild(elem, null, function (child) {
2790
            var i, newList;
2791
            // process handlers
2792
            newList = self.handlers;
2793
            self.handlers = [];
2794
            for (i = 0; i < newList.length; i++) {
2795
                var hand = newList[i];
2796
                if (hand.isMatch(child) &&
2797
                    (self.authenticated || !hand.user)) {
2798
                    if (hand.run(child)) {
2799
                        self.handlers.push(hand);
2800
                    }
2801
                } else {
2802
                    self.handlers.push(hand);
2803
                }
2804
            }
2805
        });
2806
    },
2807
 
2808
    /** PrivateFunction: _sendTerminate
2809
     *  _Private_ function to send initial disconnect sequence.
2810
     *
2811
     *  This is the first step in a graceful disconnect.  It sends
2812
     *  the BOSH server a terminate body and includes an unavailable
2813
     *  presence if authentication has completed.
2814
     */
2815
    _sendTerminate: function ()
2816
    {
2817
        Strophe.info("_sendTerminate was called");
2818
        var body = this._buildBody().attrs({type: "terminate"});
2819
 
2820
        if (this.authenticated) {
2821
            body.c('presence', {
2822
                xmlns: Strophe.NS.CLIENT,
2823
                type: 'unavailable'
2824
            });
2825
        }
2826
 
2827
        this.disconnecting = true;
2828
 
2829
        var req = new Strophe.Request(body.tree(),
2830
                                      this._onRequestStateChange.bind(this)
2831
                                          .prependArg(this._dataRecv.bind(this)),
2832
                                      body.tree().getAttribute("rid"));
2833
 
2834
        this._requests.push(req);
2835
        this._throttledRequestHandler();
2836
    },
2837
 
2838
    /** PrivateFunction: _connect_cb
2839
     *  _Private_ handler for initial connection request.
2840
     *
2841
     *  This handler is used to process the initial connection request
2842
     *  response from the BOSH server. It is used to set up authentication
2843
     *  handlers and start the authentication process.
2844
     *
2845
     *  SASL authentication will be attempted if available, otherwise
2846
     *  the code will fall back to legacy authentication.
2847
     *
2848
     *  Parameters:
2849
     *    (Strophe.Request) req - The current request.
2850
     */
2851
    _connect_cb: function (req)
2852
    {
2853
        Strophe.info("_connect_cb was called");
2854
 
2855
        this.connected = true;
2856
        var bodyWrap = req.getResponse();
2857
        if (!bodyWrap) { return; }
2858
 
2859
        this.xmlInput(bodyWrap);
2860
        this.rawInput(Strophe.serialize(bodyWrap));
2861
 
2862
        var typ = bodyWrap.getAttribute("type");
2863
        var cond, conflict;
2864
        if (typ !== null && typ == "terminate") {
2865
            // an error occurred
2866
            cond = bodyWrap.getAttribute("condition");
2867
            conflict = bodyWrap.getElementsByTagName("conflict");
2868
            if (cond !== null) {
2869
                if (cond == "remote-stream-error" && conflict.length > 0) {
2870
                    cond = "conflict";
2871
                }
2872
                this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
2873
            } else {
2874
                this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
2875
            }
2876
            return;
2877
        }
2878
 
2879
        // check to make sure we don't overwrite these if _connect_cb is
2880
        // called multiple times in the case of missing stream:features
2881
        if (!this.sid) {
2882
            this.sid = bodyWrap.getAttribute("sid");
2883
        }
2884
        if (!this.stream_id) {
2885
            this.stream_id = bodyWrap.getAttribute("authid");
2886
        }
2887
        var wind = bodyWrap.getAttribute('requests');
2888
        if (wind) { this.window = parseInt(wind, 10); }
2889
        var hold = bodyWrap.getAttribute('hold');
2890
        if (hold) { this.hold = parseInt(hold, 10); }
2891
        var wait = bodyWrap.getAttribute('wait');
2892
        if (wait) { this.wait = parseInt(wait, 10); }
2893
        
2894
 
2895
        var do_sasl_plain = false;
2896
        var do_sasl_digest_md5 = false;
2897
        var do_sasl_anonymous = false;
2898
 
2899
        var mechanisms = bodyWrap.getElementsByTagName("mechanism");
2900
        var i, mech, auth_str, hashed_auth_str;
2901
        if (mechanisms.length > 0) {
2902
            for (i = 0; i < mechanisms.length; i++) {
2903
                mech = Strophe.getText(mechanisms[i]);
2904
                if (mech == 'DIGEST-MD5') {
2905
                    do_sasl_digest_md5 = true;
2906
                } else if (mech == 'PLAIN') {
2907
                    do_sasl_plain = true;
2908
                } else if (mech == 'ANONYMOUS') {
2909
                    do_sasl_anonymous = true;
2910
                }
2911
            }
2912
        } else {
2913
            // we didn't get stream:features yet, so we need wait for it
2914
            // by sending a blank poll request
2915
            var body = this._buildBody();
2916
            this._requests.push(
2917
                new Strophe.Request(body.tree(),
2918
                                    this._onRequestStateChange.bind(this)
2919
                                      .prependArg(this._connect_cb.bind(this)),
2920
                                    body.tree().getAttribute("rid")));
2921
            this._throttledRequestHandler();
2922
            return;
2923
        }
2924
 
2925
        if (Strophe.getNodeFromJid(this.jid) === null &&
2926
            do_sasl_anonymous) {
2927
            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2928
            this._sasl_success_handler = this._addSysHandler(
2929
                this._sasl_success_cb.bind(this), null,
2930
                "success", null, null);
2931
            this._sasl_failure_handler = this._addSysHandler(
2932
                this._sasl_failure_cb.bind(this), null,
2933
                "failure", null, null);
2934
 
2935
            this.send($build("auth", {
2936
                xmlns: Strophe.NS.SASL,
2937
                mechanism: "ANONYMOUS"
2938
            }).tree());
2939
        } else if (Strophe.getNodeFromJid(this.jid) === null) {
2940
            // we don't have a node, which is required for non-anonymous
2941
            // client connections
2942
            this._changeConnectStatus(Strophe.Status.CONNFAIL,
2943
                                      'x-strophe-bad-non-anon-jid');
2944
            this.disconnect();
2945
        } else if (do_sasl_digest_md5) {
2946
            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2947
            this._sasl_challenge_handler = this._addSysHandler(
2948
                this._sasl_challenge1_cb.bind(this), null,
2949
                "challenge", null, null);
2950
            this._sasl_failure_handler = this._addSysHandler(
2951
                this._sasl_failure_cb.bind(this), null,
2952
                "failure", null, null);
2953
 
2954
            this.send($build("auth", {
2955
                xmlns: Strophe.NS.SASL,
2956
                mechanism: "DIGEST-MD5"
2957
            }).tree());
2958
        } else if (do_sasl_plain) {
2959
            // Build the plain auth string (barejid null
2960
            // username null password) and base 64 encoded.
2961
            auth_str = Strophe.getBareJidFromJid(this.jid);
2962
            auth_str = auth_str + "\u0000";
2963
            auth_str = auth_str + Strophe.getNodeFromJid(this.jid);
2964
            auth_str = auth_str + "\u0000";
2965
            auth_str = auth_str + this.pass;
2966
 
2967
            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2968
            this._sasl_success_handler = this._addSysHandler(
2969
                this._sasl_success_cb.bind(this), null,
2970
                "success", null, null);
2971
            this._sasl_failure_handler = this._addSysHandler(
2972
                this._sasl_failure_cb.bind(this), null,
2973
                "failure", null, null);
2974
 
2975
            hashed_auth_str = Base64.encode(auth_str);
2976
            this.send($build("auth", {
2977
                xmlns: Strophe.NS.SASL,
2978
                mechanism: "PLAIN"
2979
            }).t(hashed_auth_str).tree());
2980
        } else {
2981
            this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
2982
            this._addSysHandler(this._auth1_cb.bind(this), null, null,
2983
                                null, "_auth_1");
2984
 
2985
            this.send($iq({
2986
                type: "get",
2987
                to: this.domain,
2988
                id: "_auth_1"
2989
            }).c("query", {
2990
                xmlns: Strophe.NS.AUTH
2991
            }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
2992
        }
2993
    },
2994
 
2995
    /** PrivateFunction: _sasl_challenge1_cb
2996
     *  _Private_ handler for DIGEST-MD5 SASL authentication.
2997
     *
2998
     *  Parameters:
2999
     *    (XMLElement) elem - The challenge stanza.
3000
     *
3001
     *  Returns:
3002
     *    false to remove the handler.
3003
     */
3004
    _sasl_challenge1_cb: function (elem)
3005
    {
3006
        var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
3007
 
3008
        var challenge = Base64.decode(Strophe.getText(elem));
3009
        var cnonce = MD5.hexdigest(Math.random() * 1234567890);
3010
        var realm = "";
3011
        var host = null;
3012
        var nonce = "";
3013
        var qop = "";
3014
        var matches;
3015
 
3016
        // remove unneeded handlers
3017
        this.deleteHandler(this._sasl_failure_handler);
3018
 
3019
        while (challenge.match(attribMatch)) {
3020
            matches = challenge.match(attribMatch);
3021
            challenge = challenge.replace(matches[0], "");
3022
            matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
3023
            switch (matches[1]) {
3024
            case "realm":
3025
                realm = matches[2];
3026
                break;
3027
            case "nonce":
3028
                nonce = matches[2];
3029
                break;
3030
            case "qop":
3031
                qop = matches[2];
3032
                break;
3033
            case "host":
3034
                host = matches[2];
3035
                break;
3036
            }
3037
        }
3038
 
3039
        var digest_uri = "xmpp/" + this.domain;
3040
        if (host !== null) {
3041
            digest_uri = digest_uri + "/" + host;
3042
        }
3043
 
3044
        var A1 = MD5.hash(Strophe.getNodeFromJid(this.jid) +
3045
                          ":" + realm + ":" + this.pass) +
3046
            ":" + nonce + ":" + cnonce;
3047
        var A2 = 'AUTHENTICATE:' + digest_uri;
3048
 
3049
        var responseText = "";
3050
        responseText += 'username=' +
3051
            this._quote(Strophe.getNodeFromJid(this.jid)) + ',';
3052
        responseText += 'realm=' + this._quote(realm) + ',';
3053
        responseText += 'nonce=' + this._quote(nonce) + ',';
3054
        responseText += 'cnonce=' + this._quote(cnonce) + ',';
3055
        responseText += 'nc="00000001",';
3056
        responseText += 'qop="auth",';
3057
        responseText += 'digest-uri=' + this._quote(digest_uri) + ',';
3058
        responseText += 'response=' + this._quote(
3059
            MD5.hexdigest(MD5.hexdigest(A1) + ":" +
3060
                          nonce + ":00000001:" +
3061
                          cnonce + ":auth:" +
3062
                          MD5.hexdigest(A2))) + ',';
3063
        responseText += 'charset="utf-8"';
3064
 
3065
        this._sasl_challenge_handler = this._addSysHandler(
3066
            this._sasl_challenge2_cb.bind(this), null,
3067
            "challenge", null, null);
3068
        this._sasl_success_handler = this._addSysHandler(
3069
            this._sasl_success_cb.bind(this), null,
3070
            "success", null, null);
3071
        this._sasl_failure_handler = this._addSysHandler(
3072
            this._sasl_failure_cb.bind(this), null,
3073
            "failure", null, null);
3074
 
3075
        this.send($build('response', {
3076
            xmlns: Strophe.NS.SASL
3077
        }).t(Base64.encode(responseText)).tree());
3078
 
3079
        return false;
3080
    },
3081
 
3082
    /** PrivateFunction: _quote
3083
     *  _Private_ utility function to backslash escape and quote strings.
3084
     *
3085
     *  Parameters:
3086
     *    (String) str - The string to be quoted.
3087
     *
3088
     *  Returns:
3089
     *    quoted string
3090
     */
3091
    _quote: function (str)
3092
    {
3093
        return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; 
3094
        //" end string workaround for emacs
3095
    },
3096
 
3097
 
3098
    /** PrivateFunction: _sasl_challenge2_cb
3099
     *  _Private_ handler for second step of DIGEST-MD5 SASL authentication.
3100
     *
3101
     *  Parameters:
3102
     *    (XMLElement) elem - The challenge stanza.
3103
     *
3104
     *  Returns:
3105
     *    false to remove the handler.
3106
     */
3107
    _sasl_challenge2_cb: function (elem)
3108
    {
3109
        // remove unneeded handlers
3110
        this.deleteHandler(this._sasl_success_handler);
3111
        this.deleteHandler(this._sasl_failure_handler);
3112
 
3113
        this._sasl_success_handler = this._addSysHandler(
3114
            this._sasl_success_cb.bind(this), null,
3115
            "success", null, null);
3116
        this._sasl_failure_handler = this._addSysHandler(
3117
            this._sasl_failure_cb.bind(this), null,
3118
            "failure", null, null);
3119
        this.send($build('response', {xmlns: Strophe.NS.SASL}).tree());
3120
        return false;
3121
    },
3122
 
3123
    /** PrivateFunction: _auth1_cb
3124
     *  _Private_ handler for legacy authentication.
3125
     *
3126
     *  This handler is called in response to the initial <iq type='get'/>
3127
     *  for legacy authentication.  It builds an authentication <iq/> and
3128
     *  sends it, creating a handler (calling back to _auth2_cb()) to
3129
     *  handle the result
3130
     *
3131
     *  Parameters:
3132
     *    (XMLElement) elem - The stanza that triggered the callback.
3133
     *
3134
     *  Returns:
3135
     *    false to remove the handler.
3136
     */
3137
    _auth1_cb: function (elem)
3138
    {
3139
        // build plaintext auth iq
3140
        var iq = $iq({type: "set", id: "_auth_2"})
3141
            .c('query', {xmlns: Strophe.NS.AUTH})
3142
            .c('username', {}).t(Strophe.getNodeFromJid(this.jid))
3143
            .up()
3144
            .c('password').t(this.pass);
3145
 
3146
        if (!Strophe.getResourceFromJid(this.jid)) {
3147
            // since the user has not supplied a resource, we pick
3148
            // a default one here.  unlike other auth methods, the server
3149
            // cannot do this for us.
3150
            this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe';
3151
        }
3152
        iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid));
3153
 
3154
        this._addSysHandler(this._auth2_cb.bind(this), null,
3155
                            null, null, "_auth_2");
3156
 
3157
        this.send(iq.tree());
3158
 
3159
        return false;
3160
    },
3161
 
3162
    /** PrivateFunction: _sasl_success_cb
3163
     *  _Private_ handler for succesful SASL authentication.
3164
     *
3165
     *  Parameters:
3166
     *    (XMLElement) elem - The matching stanza.
3167
     *
3168
     *  Returns:
3169
     *    false to remove the handler.
3170
     */
3171
    _sasl_success_cb: function (elem)
3172
    {
3173
        Strophe.info("SASL authentication succeeded.");
3174
 
3175
        // remove old handlers
3176
        this.deleteHandler(this._sasl_failure_handler);
3177
        this._sasl_failure_handler = null;
3178
        if (this._sasl_challenge_handler) {
3179
            this.deleteHandler(this._sasl_challenge_handler);
3180
            this._sasl_challenge_handler = null;
3181
        }
3182
 
3183
        this._addSysHandler(this._sasl_auth1_cb.bind(this), null,
3184
                            "stream:features", null, null);
3185
 
3186
        // we must send an xmpp:restart now
3187
        this._sendRestart();
3188
 
3189
        return false;
3190
    },
3191
 
3192
    /** PrivateFunction: _sasl_auth1_cb
3193
     *  _Private_ handler to start stream binding.
3194
     *
3195
     *  Parameters:
3196
     *    (XMLElement) elem - The matching stanza.
3197
     *
3198
     *  Returns:
3199
     *    false to remove the handler.
3200
     */
3201
    _sasl_auth1_cb: function (elem)
3202
    {
3203
        var i, child;
3204
 
3205
        for (i = 0; i < elem.childNodes.length; i++) {
3206
            child = elem.childNodes[i];
3207
            if (child.nodeName == 'bind') {
3208
                this.do_bind = true;
3209
            }
3210
 
3211
            if (child.nodeName == 'session') {
3212
                this.do_session = true;
3213
            }
3214
        }
3215
 
3216
        if (!this.do_bind) {
3217
            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3218
            return false;
3219
        } else {
3220
            this._addSysHandler(this._sasl_bind_cb.bind(this), null, null,
3221
                                null, "_bind_auth_2");
3222
 
3223
            var resource = Strophe.getResourceFromJid(this.jid);
3224
            if (resource) {
3225
                this.send($iq({type: "set", id: "_bind_auth_2"})
3226
                          .c('bind', {xmlns: Strophe.NS.BIND})
3227
                          .c('resource', {}).t(resource).tree());
3228
            } else {
3229
                this.send($iq({type: "set", id: "_bind_auth_2"})
3230
                          .c('bind', {xmlns: Strophe.NS.BIND})
3231
                          .tree());
3232
            }
3233
        }
3234
 
3235
        return false;
3236
    },
3237
 
3238
    /** PrivateFunction: _sasl_bind_cb
3239
     *  _Private_ handler for binding result and session start.
3240
     *
3241
     *  Parameters:
3242
     *    (XMLElement) elem - The matching stanza.
3243
     *
3244
     *  Returns:
3245
     *    false to remove the handler.
3246
     */
3247
    _sasl_bind_cb: function (elem)
3248
    {
3249
        if (elem.getAttribute("type") == "error") {
3250
            Strophe.info("SASL binding failed.");
3251
            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3252
            return false;
3253
        }
3254
 
3255
        // TODO - need to grab errors
3256
        var bind = elem.getElementsByTagName("bind");
3257
        var jidNode;
3258
        if (bind.length > 0) {
3259
            // Grab jid
3260
            jidNode = bind[0].getElementsByTagName("jid");
3261
            if (jidNode.length > 0) {
3262
                this.jid = Strophe.getText(jidNode[0]);
3263
 
3264
                if (this.do_session) {
3265
                    this._addSysHandler(this._sasl_session_cb.bind(this),
3266
                                        null, null, null, "_session_auth_2");
3267
 
3268
                    this.send($iq({type: "set", id: "_session_auth_2"})
3269
                                  .c('session', {xmlns: Strophe.NS.SESSION})
3270
                                  .tree());
3271
                } else {
3272
                    this.authenticated = true;
3273
                    this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3274
                }
3275
            }
3276
        } else {
3277
            Strophe.info("SASL binding failed.");
3278
            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3279
            return false;
3280
        }
3281
    },
3282
 
3283
    /** PrivateFunction: _sasl_session_cb
3284
     *  _Private_ handler to finish successful SASL connection.
3285
     *
3286
     *  This sets Connection.authenticated to true on success, which
3287
     *  starts the processing of user handlers.
3288
     *
3289
     *  Parameters:
3290
     *    (XMLElement) elem - The matching stanza.
3291
     *
3292
     *  Returns:
3293
     *    false to remove the handler.
3294
     */
3295
    _sasl_session_cb: function (elem)
3296
    {
3297
        if (elem.getAttribute("type") == "result") {
3298
            this.authenticated = true;
3299
            this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3300
        } else if (elem.getAttribute("type") == "error") {
3301
            Strophe.info("Session creation failed.");
3302
            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3303
            return false;
3304
        }
3305
 
3306
        return false;
3307
    },
3308
 
3309
    /** PrivateFunction: _sasl_failure_cb
3310
     *  _Private_ handler for SASL authentication failure.
3311
     *
3312
     *  Parameters:
3313
     *    (XMLElement) elem - The matching stanza.
3314
     *
3315
     *  Returns:
3316
     *    false to remove the handler.
3317
     */
3318
    _sasl_failure_cb: function (elem)
3319
    {
3320
        // delete unneeded handlers
3321
        if (this._sasl_success_handler) {
3322
            this.deleteHandler(this._sasl_success_handler);
3323
            this._sasl_success_handler = null;
3324
        }
3325
        if (this._sasl_challenge_handler) {
3326
            this.deleteHandler(this._sasl_challenge_handler);
3327
            this._sasl_challenge_handler = null;
3328
        }
3329
 
3330
        this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3331
        return false;
3332
    },
3333
 
3334
    /** PrivateFunction: _auth2_cb
3335
     *  _Private_ handler to finish legacy authentication.
3336
     *
3337
     *  This handler is called when the result from the jabber:iq:auth
3338
     *  <iq/> stanza is returned.
3339
     *
3340
     *  Parameters:
3341
     *    (XMLElement) elem - The stanza that triggered the callback.
3342
     *
3343
     *  Returns:
3344
     *    false to remove the handler.
3345
     */
3346
    _auth2_cb: function (elem)
3347
    {
3348
        if (elem.getAttribute("type") == "result") {
3349
            this.authenticated = true;
3350
            this._changeConnectStatus(Strophe.Status.CONNECTED, null);
3351
        } else if (elem.getAttribute("type") == "error") {
3352
            this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
3353
            this.disconnect();
3354
        }
3355
 
3356
        return false;
3357
    },
3358
 
3359
    /** PrivateFunction: _addSysTimedHandler
3360
     *  _Private_ function to add a system level timed handler.
3361
     *
3362
     *  This function is used to add a Strophe.TimedHandler for the
3363
     *  library code.  System timed handlers are allowed to run before
3364
     *  authentication is complete.
3365
     *
3366
     *  Parameters:
3367
     *    (Integer) period - The period of the handler.
3368
     *    (Function) handler - The callback function.
3369
     */
3370
    _addSysTimedHandler: function (period, handler)
3371
    {
3372
        var thand = new Strophe.TimedHandler(period, handler);
3373
        thand.user = false;
3374
        this.addTimeds.push(thand);
3375
        return thand;
3376
    },
3377
 
3378
    /** PrivateFunction: _addSysHandler
3379
     *  _Private_ function to add a system level stanza handler.
3380
     *
3381
     *  This function is used to add a Strophe.Handler for the
3382
     *  library code.  System stanza handlers are allowed to run before
3383
     *  authentication is complete.
3384
     *
3385
     *  Parameters:
3386
     *    (Function) handler - The callback function.
3387
     *    (String) ns - The namespace to match.
3388
     *    (String) name - The stanza name to match.
3389
     *    (String) type - The stanza type attribute to match.
3390
     *    (String) id - The stanza id attribute to match.
3391
     */
3392
    _addSysHandler: function (handler, ns, name, type, id)
3393
    {
3394
        var hand = new Strophe.Handler(handler, ns, name, type, id);
3395
        hand.user = false;
3396
        this.addHandlers.push(hand);
3397
        return hand;
3398
    },
3399
 
3400
    /** PrivateFunction: _onDisconnectTimeout
3401
     *  _Private_ timeout handler for handling non-graceful disconnection.
3402
     *
3403
     *  If the graceful disconnect process does not complete within the
3404
     *  time allotted, this handler finishes the disconnect anyway.
3405
     *
3406
     *  Returns:
3407
     *    false to remove the handler.
3408
     */
3409
    _onDisconnectTimeout: function ()
3410
    {
3411
        Strophe.info("_onDisconnectTimeout was called");
3412
 
3413
        // cancel all remaining requests and clear the queue
3414
        var req;
3415
        while (this._requests.length > 0) {
3416
            req = this._requests.pop();
3417
            req.abort = true;
3418
            req.xhr.abort();
3419
            // jslint complains, but this is fine. setting to empty func
3420
            // is necessary for IE6
3421
            req.xhr.onreadystatechange = function () {};
3422
        }
3423
 
3424
        // actually disconnect
3425
        this._doDisconnect();
3426
 
3427
        return false;
3428
    },
3429
 
3430
    /** PrivateFunction: _onIdle
3431
     *  _Private_ handler to process events during idle cycle.
3432
     *
3433
     *  This handler is called every 100ms to fire timed handlers that
3434
     *  are ready and keep poll requests going.
3435
     */
3436
    _onIdle: function ()
3437
    {
3438
        var i, thand, since, newList;
3439
 
3440
        // remove timed handlers that have been scheduled for deletion
3441
        while (this.removeTimeds.length > 0) {
3442
            thand = this.removeTimeds.pop();
3443
            i = this.timedHandlers.indexOf(thand);
3444
            if (i >= 0) {
3445
                this.timedHandlers.splice(i, 1);
3446
            }
3447
        }
3448
 
3449
        // add timed handlers scheduled for addition
3450
        while (this.addTimeds.length > 0) {
3451
            this.timedHandlers.push(this.addTimeds.pop());
3452
        }
3453
 
3454
        // call ready timed handlers
3455
        var now = new Date().getTime();
3456
        newList = [];
3457
        for (i = 0; i < this.timedHandlers.length; i++) {
3458
            thand = this.timedHandlers[i];
3459
            if (this.authenticated || !thand.user) {
3460
                since = thand.lastCalled + thand.period;
3461
                if (since - now <= 0) {
3462
                    if (thand.run()) {
3463
                        newList.push(thand);
3464
                    }
3465
                } else {
3466
                    newList.push(thand);
3467
                }
3468
            }
3469
        }
3470
        this.timedHandlers = newList;
3471
 
3472
        var body, time_elapsed;
3473
 
3474
        // if no requests are in progress, poll
3475
        if (this.authenticated && this._requests.length === 0 &&
3476
            this._data.length === 0 && !this.disconnecting) {
3477
            Strophe.info("no requests during idle cycle, sending " +
3478
                         "blank request");
3479
            this._data.push(null);
3480
        }
3481
 
3482
        if (this._requests.length < 2 && this._data.length > 0 &&
3483
            !this.paused) {
3484
            body = this._buildBody();
3485
            for (i = 0; i < this._data.length; i++) {
3486
                if (this._data[i] !== null) {
3487
                    if (this._data[i] === "restart") {
3488
                        body.attrs({
3489
                            to: this.domain,
3490
                            "xml:lang": "en",
3491
                            "xmpp:restart": "true",
3492
                            "xmlns:xmpp": Strophe.NS.BOSH
3493
                        });
3494
                    } else {
3495
                        body.cnode(this._data[i]).up();
3496
                    }
3497
                }
3498
            }
3499
            delete this._data;
3500
            this._data = [];
3501
            this._requests.push(
3502
                new Strophe.Request(body.tree(),
3503
                                    this._onRequestStateChange.bind(this)
3504
                                    .prependArg(this._dataRecv.bind(this)),
3505
                                    body.tree().getAttribute("rid")));
3506
            this._processRequest(this._requests.length - 1);
3507
        }
3508
 
3509
        if (this._requests.length > 0) {
3510
            time_elapsed = this._requests[0].age();
3511
            if (this._requests[0].dead !== null) {
3512
                if (this._requests[0].timeDead() >
3513
                    Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
3514
                    this._throttledRequestHandler();
3515
                }
3516
            }
3517
 
3518
            if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
3519
                Strophe.warn("Request " +
3520
                             this._requests[0].id +
3521
                             " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) +
3522
                             " seconds since last activity");
3523
                this._throttledRequestHandler();
3524
            }
3525
        }
3526
 
3527
        // reactivate the timer
3528
        clearTimeout(this._idleTimeout);
3529
        this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
3530
    }
3531
};
3532
 
3533
if (callback) {
3534
    callback(Strophe, $build, $msg, $iq, $pres);
3535
}
3536
 
3537
})(function () {
3538
    window.Strophe = arguments[0];
3539
    window.$build = arguments[1];
3540
    window.$msg = arguments[2];
3541
    window.$iq = arguments[3];
3542
    window.$pres = arguments[4];
3543
});