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