diff --git a/bower.json b/bower.json index 1c99d0b16..4f2d91c6a 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "converse.js", "description": "Web-based XMPP/Jabber chat client written in javascript", - "version": "0.8.6", + "version": "0.9.0", "license": "MPL", "devDependencies": { "jasmine": "https://github.com/jcbrand/jasmine.git#1_3_x", diff --git a/builds/converse-no-locales-no-otr.js b/builds/converse-no-locales-no-otr.js index 8fe2473e6..21a431c4e 100644 --- a/builds/converse-no-locales-no-otr.js +++ b/builds/converse-no-locales-no-otr.js @@ -1,5 +1,5 @@ /** - * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. + * @license almond 0.3.0 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/almond for details */ @@ -150,7 +150,15 @@ var requirejs, require, define; //A version of a require function that passes a moduleName //value for items that may need to //look up paths relative to the moduleName - return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); + var args = aps.call(arguments, 0); + + //If first arg is not require('string'), and there is only + //one arg, it is the array form without a callback. Insert + //a null so that the following concat is correct. + if (typeof args[0] !== 'string' && args.length === 1) { + args.push(null); + } + return req.apply(undef, args.concat([relName, forceSync])); }; } @@ -10765,7 +10773,7 @@ define('jquery-private',['jquery'], function (jq) { }); /** - * @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. + * @license RequireJS text 2.0.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/requirejs/text for details */ @@ -10789,7 +10797,7 @@ define('text',['module'], function (module) { masterConfig = (module.config && module.config()) || {}; text = { - version: '2.0.12', + version: '2.0.14', strip: function (content) { //Strips declarations so that external SVG and XML @@ -10851,13 +10859,13 @@ define('text',['module'], function (module) { parseName: function (name) { var modName, ext, temp, strip = false, - index = name.indexOf("."), + index = name.lastIndexOf("."), isRelative = name.indexOf('./') === 0 || name.indexOf('../') === 0; if (index !== -1 && (!isRelative || index > 1)) { modName = name.substring(0, index); - ext = name.substring(index + 1, name.length); + ext = name.substring(index + 1); } else { modName = name; } @@ -11010,7 +11018,8 @@ define('text',['module'], function (module) { typeof process !== "undefined" && process.versions && !!process.versions.node && - !process.versions['node-webkit'])) { + !process.versions['node-webkit'] && + !process.versions['atom-shell'])) { //Using special require.nodeRequire, something added by r.js. fs = require.nodeRequire('fs'); @@ -11018,7 +11027,7 @@ define('text',['module'], function (module) { try { var file = fs.readFileSync(url, 'utf8'); //Remove BOM (Byte Mark Order) from utf8 files if it is there. - if (file.indexOf('\uFEFF') === 0) { + if (file[0] === '\uFEFF') { file = file.substring(1); } callback(file); @@ -12623,13 +12632,13 @@ return __p; define('tpl!change_status_message', [],function () { return function(obj){ var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');}; with(obj||{}){ -__p+='
\n \n \n
\n'; +'\n \n\n'; } return __p; }; }); @@ -12724,7 +12733,7 @@ __p+='
\n
\n ((__t=(heading))==null?'':__t)+ '\n \n \n \n
\n
\n'; } @@ -13109,7 +13118,11 @@ return __p; define('tpl!pending_contact', [],function () { return function(obj){ var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');}; with(obj||{}){ -__p+=''+ +__p+=''+ ((__t=(fullname))==null?'':__t)+ ' '+ +__p+=''+ ((__t=(fullname))==null?'':__t)+ '\n\n \n \n \n'; } return __p; }; }); @@ -13371,7 +13394,11 @@ return __p; define('tpl!roster_item', [],function () { return function(obj){ var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');}; with(obj||{}){ -__p+='= val_list.length ) { + if (this.options.missing_key_callback) { + this.options.missing_key_callback(key); + } + res = [ null, singular_key, plural_key ]; + return res[ getPluralFormFunc(pluralForms)( val ) + 1 ]; + } + + res = val_list[ val_idx ]; + + // This includes empty strings on purpose + if ( ! res ) { + res = [ null, singular_key, plural_key ]; + return res[ getPluralFormFunc(pluralForms)( val ) + 1 ]; + } + return res; + } + }); + + + // We add in sprintf capabilities for post translation value interolation + // This is not internally used, so you can remove it if you have this + // available somewhere else, or want to use a different system. + + // We _slightly_ modify the normal sprintf behavior to more gracefully handle + // undefined values. + + /** + sprintf() for JavaScript 0.7-beta1 + http://www.diveintojavascript.com/projects/javascript-sprintf + + Copyright (c) Alexandru Marasteanu + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of sprintf() for JavaScript nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + var sprintf = (function() { + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); + } + function str_repeat(input, multiplier) { + for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} + return output.join(''); + } + + var str_format = function() { + if (!str_format.cache.hasOwnProperty(arguments[0])) { + str_format.cache[arguments[0]] = str_format.parse(arguments[0]); + } + return str_format.format.call(null, str_format.cache[arguments[0]], arguments); + }; + + str_format.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); + } + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } + + if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); + } + + // Jed EDIT + if ( typeof arg == 'undefined' || arg === null ) { + arg = ''; + } + // Jed EDIT + + switch (match[8]) { + case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'd': arg = parseInt(arg, 10); break; + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; + case 'o': arg = arg.toString(8); break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = Math.abs(arg); break; + case 'x': arg = arg.toString(16); break; + case 'X': arg = arg.toString(16).toUpperCase(); break; + } + arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; + + str_format.cache = {}; + + str_format.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else { + throw('[sprintf] huh?'); + } + } + } + else { + throw('[sprintf] huh?'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); + } + else { + throw('[sprintf] huh?'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; + + return str_format; + })(); + + var vsprintf = function(fmt, argv) { + argv.unshift(fmt); + return sprintf.apply(null, argv); + }; + + Jed.parse_plural = function ( plural_forms, n ) { + plural_forms = plural_forms.replace(/n/g, n); + return Jed.parse_expression(plural_forms); + }; + + Jed.sprintf = function ( fmt, args ) { + if ( {}.toString.call( args ) == '[object Array]' ) { + return vsprintf( fmt, [].slice.call(args) ); + } + return sprintf.apply(this, [].slice.call(arguments) ); + }; + + Jed.prototype.sprintf = function () { + return Jed.sprintf.apply(this, arguments); + }; + // END sprintf Implementation + + // Start the Plural forms section + // This is a full plural form expression parser. It is used to avoid + // running 'eval' or 'new Function' directly against the plural + // forms. + // + // This can be important if you get translations done through a 3rd + // party vendor. I encourage you to use this instead, however, I + // also will provide a 'precompiler' that you can use at build time + // to output valid/safe function representations of the plural form + // expressions. This means you can build this code out for the most + // part. + Jed.PF = {}; + + Jed.PF.parse = function ( p ) { + var plural_str = Jed.PF.extractPluralExpr( p ); + return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str); + }; + + Jed.PF.compile = function ( p ) { + // Handle trues and falses as 0 and 1 + function imply( val ) { + return (val === true ? 1 : val ? val : 0); + } + + var ast = Jed.PF.parse( p ); + return function ( n ) { + return imply( Jed.PF.interpreter( ast )( n ) ); + }; + }; + + Jed.PF.interpreter = function ( ast ) { + return function ( n ) { + var res; + switch ( ast.type ) { + case 'GROUP': + return Jed.PF.interpreter( ast.expr )( n ); + case 'TERNARY': + if ( Jed.PF.interpreter( ast.expr )( n ) ) { + return Jed.PF.interpreter( ast.truthy )( n ); + } + return Jed.PF.interpreter( ast.falsey )( n ); + case 'OR': + return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n ); + case 'AND': + return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n ); + case 'LT': + return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n ); + case 'GT': + return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n ); + case 'LTE': + return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n ); + case 'GTE': + return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n ); + case 'EQ': + return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n ); + case 'NEQ': + return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n ); + case 'MOD': + return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n ); + case 'VAR': + return n; + case 'NUM': + return ast.val; + default: + throw new Error("Invalid Token found."); + } + }; + }; + + Jed.PF.extractPluralExpr = function ( p ) { + // trim first + p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + + if (! /;\s*$/.test(p)) { + p = p.concat(';'); + } + + var nplurals_re = /nplurals\=(\d+);/, + plural_re = /plural\=(.*);/, + nplurals_matches = p.match( nplurals_re ), + res = {}, + plural_matches; + + // Find the nplurals number + if ( nplurals_matches.length > 1 ) { + res.nplurals = nplurals_matches[1]; + } + else { + throw new Error('nplurals not found in plural_forms string: ' + p ); + } + + // remove that data to get to the formula + p = p.replace( nplurals_re, "" ); + plural_matches = p.match( plural_re ); + + if (!( plural_matches && plural_matches.length > 1 ) ) { + throw new Error('`plural` expression not found: ' + p); + } + return plural_matches[ 1 ]; + }; + + /* Jison generated parser */ + Jed.PF.parser = (function(){ + +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"}, +productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]], +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + +var $0 = $$.length - 1; +switch (yystate) { +case 1: return { type : 'GROUP', expr: $$[$0-1] }; +break; +case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] }; +break; +case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] }; +break; +case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] }; +break; +case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] }; +break; +case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] }; +break; +case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] }; +break; +case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] }; +break; +case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] }; +break; +case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] }; +break; +case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] }; +break; +case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] }; +break; +case 13:this.$ = { type: 'VAR' }; +break; +case 14:this.$ = { type: 'NUM', val: Number(yytext) }; +break; +} +}, +table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}], +defaultActions: {6:[2,1]}, +parseError: function parseError(str, hash) { + throw new Error(str); +}, +parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], // semantic value stack + lstack = [], // location stack + table = this.table, + yytext = '', + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + + //this.reductionCount = this.shiftCount = 0; + + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + if (typeof this.lexer.yylloc == 'undefined') + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + + if (typeof this.yy.parseError === 'function') + this.parseError = this.yy.parseError; + + function popStack (n) { + stack.length = stack.length - 2*n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + + function lex() { + var token; + token = self.lexer.lex() || 1; // $end = 1 + // if token isn't its numeric value, convert + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + } + + var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; + while (true) { + // retreive state number from top of stack + state = stack[stack.length-1]; + + // use default actions if available + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol == null) + symbol = lex(); + // read action for current state and first input + action = table[state] && table[state][symbol]; + } + + // handle parse error + _handle_error: + if (typeof action === 'undefined' || !action.length || !action[0]) { + + if (!recovering) { + // Report error + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'"+this.terminals_[p]+"'"); + } + var errStr = ''; + if (this.lexer.showPosition) { + errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; + } else { + errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + + (symbol == 1 /*EOF*/ ? "end of input" : + ("'"+(this.terminals_[symbol] || symbol)+"'")); + } + this.parseError(errStr, + {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + + // just recovered from another error + if (recovering == 3) { + if (symbol == EOF) { + throw new Error(errStr || 'Parsing halted.'); + } + + // discard current lookahead and grab another + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + symbol = lex(); + } + + // try to recover from error + while (1) { + // check for error recovery rule in this state + if ((TERROR.toString()) in table[state]) { + break; + } + if (state == 0) { + throw new Error(errStr || 'Parsing halted.'); + } + popStack(1); + state = stack[stack.length-1]; + } + + preErrorSymbol = symbol; // save the lookahead token + symbol = TERROR; // insert generic error symbol as new lookahead + state = stack[stack.length-1]; + action = table[state] && table[state][TERROR]; + recovering = 3; // allow 3 real symbols to be shifted before reporting a new error + } + + // this shouldn't happen, unless resolve defaults are off + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); + } + + switch (action[0]) { + + case 1: // shift + //this.shiftCount++; + + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); // push state + symbol = null; + if (!preErrorSymbol) { // normal execution/no error + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { // error just occurred, resume old lookahead f/ before error + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + + case 2: // reduce + //this.reductionCount++; + + len = this.productions_[action[1]][1]; + + // perform semantic action + yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 + // default location, uses first token for firsts, last for lasts + yyval._$ = { + first_line: lstack[lstack.length-(len||1)].first_line, + last_line: lstack[lstack.length-1].last_line, + first_column: lstack[lstack.length-(len||1)].first_column, + last_column: lstack[lstack.length-1].last_column + }; + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + + if (typeof r !== 'undefined') { + return r; + } + + // pop off stack + if (len) { + stack = stack.slice(0,-1*len*2); + vstack = vstack.slice(0, -1*len); + lstack = lstack.slice(0, -1*len); + } + + stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) + vstack.push(yyval.$); + lstack.push(yyval._$); + // goto new state = table[STATE][NONTERMINAL] + newState = table[stack[stack.length-2]][stack[stack.length-1]]; + stack.push(newState); + break; + + case 3: // accept + return true; + } + + } + + return true; +}};/* Jison generated lexer */ +var lexer = (function(){ + +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parseError) { + this.yy.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext+=ch; + this.yyleng++; + this.match+=ch; + this.matched+=ch; + var lines = ch.match(/\n/); + if (lines) this.yylineno++; + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + this._input = ch + this._input; + return this; + }, +more:function () { + this._more = true; + return this; + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + match = this._input.match(this.rules[rules[i]]); + if (match) { + lines = match[0].match(/\n.*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); + if (token) return token; + else return; + } + } + if (this._input === "") { + return this.EOF; + } else { + this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, +topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, +pushState:function begin(condition) { + this.begin(condition); + }}); +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:/* skip whitespace */ +break; +case 1:return 20 +break; +case 2:return 19 +break; +case 3:return 8 +break; +case 4:return 9 +break; +case 5:return 6 +break; +case 6:return 7 +break; +case 7:return 11 +break; +case 8:return 13 +break; +case 9:return 10 +break; +case 10:return 12 +break; +case 11:return 14 +break; +case 12:return 15 +break; +case 13:return 16 +break; +case 14:return 17 +break; +case 15:return 18 +break; +case 16:return 5 +break; +case 17:return 'INVALID' +break; +} +}; +lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./]; +lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})() +parser.lexer = lexer; +return parser; +})(); +// End parser + + // Handle node, amd, and global systems + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = Jed; + } + exports.Jed = Jed; + } + else { + if (typeof define === 'function' && define.amd) { + define('jed', [],function() { + return Jed; + }); + } + // Leak a global regardless of module system + root['Jed'] = Jed; + } + +})(this); + +/* + * This file can be used if no locale support is required. + */ +(function (root, factory) { + define("locales", ['jed'], function (Jed) { + var translations = { + "domain": "converse", + "locale_data": { + "converse": { + "": { + "domain": "converse", + "lang": "en", + "plural_forms": "nplurals=2; plural=(n != 1);" + } + } + } + }; + root.locales = { 'en': new Jed(translations) }; + }); +})(this); + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('utils',["jquery", "converse-templates", "locales"], factory); + } else { + root.utils = factory(jQuery, templates); + } +}(this, function ($, templates, locales) { var XFORM_TYPE_MAP = { @@ -13709,10 +14772,16 @@ define('utils',["jquery", "converse-templates"], function ($, templates) { // --------------------- __: function (str) { // Translation factory - if (this.i18n === undefined) { + if (typeof this.i18n === "undefined") { this.i18n = locales.en; } - var t = this.i18n.translate(str); + if (typeof this.i18n === "string") { + this.i18n = $.parseJSON(this.i18n); + } + if (typeof this.jed === "undefined") { + this.jed = new Jed(this.i18n); + } + var t = this.jed.translate(str); if (arguments.length>1) { return t.fetch.apply(t, [].slice.call(arguments,1)); } else { @@ -13733,10 +14802,10 @@ define('utils',["jquery", "converse-templates"], function ($, templates) { webForm2xForm: function (field) { /* Takes an HTML DOM and turns it into an XForm field. - * - * Parameters: - * (DOMElement) field - the field to convert - */ + * + * Parameters: + * (DOMElement) field - the field to convert + */ var $input = $(field), value; if ($input.is('[type=checkbox]')) { value = $input.is(':checked') && 1 || 0; @@ -13760,11 +14829,11 @@ define('utils',["jquery", "converse-templates"], function ($, templates) { xForm2webForm: function ($field, $stanza) { /* Takes a field in XMPP XForm (XEP-004: Data Forms) format - * and turns it into a HTML DOM field. - * - * Parameters: - * (XMLElement) field - the field to convert - */ + * and turns it into a HTML DOM field. + * + * Parameters: + * (XMLElement) field - the field to convert + */ // FIXME: take into consideration var options = [], j, $options, $values, value, values; @@ -13846,7 +14915,7 @@ define('utils',["jquery", "converse-templates"], function ($, templates) { } }; return utils; -}); +})); //! moment.js //! version : 2.6.0 @@ -16339,1034 +17408,6084 @@ define('utils',["jquery", "converse-templates"], function ($, templates) { }).call(this); /* -jed.js -v0.5.0beta + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1a Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ -https://github.com/SlexAxton/Jed ------------ -A gettext compatible i18n library for modern JavaScript Applications +/* jshint undef: true, unused: true:, noarg: true, latedef: true */ +/* global define */ -by Alex Sexton - AlexSexton [at] gmail - @SlexAxton -WTFPL license for use -Dojo CLA for contributions +/* Some functions and variables have been stripped for use with Strophe */ -Jed offers the entire applicable GNU gettext spec'd set of -functions, but also offers some nicer wrappers around them. -The api for gettext was written for a language with no function -overloading, so Jed allows a little more of that. - -Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote -gettext.js back in 2008. I was able to vet a lot of my ideas -against his. I also made sure Jed passed against his tests -in order to offer easy upgrades -- jsgettext.berlios.de -*/ -(function (root, undef) { - - // Set up some underscore-style functions, if you already have - // underscore, feel free to delete this section, and use it - // directly, however, the amount of functions used doesn't - // warrant having underscore as a full dependency. - // Underscore 1.3.0 was used to port and is licensed - // under the MIT License by Jeremy Ashkenas. - var ArrayProto = Array.prototype, - ObjProto = Object.prototype, - slice = ArrayProto.slice, - hasOwnProp = ObjProto.hasOwnProperty, - nativeForEach = ArrayProto.forEach, - breaker = {}; - - // We're not using the OOP style _ so we don't need the - // extra level of indirection. This still means that you - // sub out for real `_` though. - var _ = { - forEach : function( obj, iterator, context ) { - var i, l, key; - if ( obj === null ) { - return; - } - - if ( nativeForEach && obj.forEach === nativeForEach ) { - obj.forEach( iterator, context ); - } - else if ( obj.length === +obj.length ) { - for ( i = 0, l = obj.length; i < l; i++ ) { - if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) { - return; - } - } - } - else { - for ( key in obj) { - if ( hasOwnProp.call( obj, key ) ) { - if ( iterator.call (context, obj[key], key, obj ) === breaker ) { - return; - } - } - } - } - }, - extend : function( obj ) { - this.forEach( slice.call( arguments, 1 ), function ( source ) { - for ( var prop in source ) { - obj[prop] = source[prop]; - } - }); - return obj; - } - }; - // END Miniature underscore impl - - // Jed is a constructor function - var Jed = function ( options ) { - // Some minimal defaults - this.defaults = { - "locale_data" : { - "messages" : { - "" : { - "domain" : "messages", - "lang" : "en", - "plural_forms" : "nplurals=2; plural=(n != 1);" - } - // There are no default keys, though - } - }, - // The default domain if one is missing - "domain" : "messages" - }; - - // Mix in the sent options with the default options - this.options = _.extend( {}, this.defaults, options ); - this.textdomain( this.options.domain ); - - if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) { - throw new Error('Text domain set to non-existent domain: `' + options.domain + '`'); - } - }; - - // The gettext spec sets this character as the default - // delimiter for context lookups. - // e.g.: context\u0004key - // If your translation company uses something different, - // just change this at any time and it will use that instead. - Jed.context_delimiter = String.fromCharCode( 4 ); - - function getPluralFormFunc ( plural_form_string ) { - return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);"); - } - - function Chain( key, i18n ){ - this._key = key; - this._i18n = i18n; - } - - // Create a chainable api for adding args prettily - _.extend( Chain.prototype, { - onDomain : function ( domain ) { - this._domain = domain; - return this; - }, - withContext : function ( context ) { - this._context = context; - return this; - }, - ifPlural : function ( num, pkey ) { - this._val = num; - this._pkey = pkey; - return this; - }, - fetch : function ( sArr ) { - if ( {}.toString.call( sArr ) != '[object Array]' ) { - sArr = [].slice.call(arguments); - } - return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )( - this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val), - sArr - ); - } - }); - - // Add functions to the Jed prototype. - // These will be the functions on the object that's returned - // from creating a `new Jed()` - // These seem redundant, but they gzip pretty well. - _.extend( Jed.prototype, { - // The sexier api start point - translate : function ( key ) { - return new Chain( key, this ); - }, - - textdomain : function ( domain ) { - if ( ! domain ) { - return this._textdomain; - } - this._textdomain = domain; - }, - - gettext : function ( key ) { - return this.dcnpgettext.call( this, undef, undef, key ); - }, - - dgettext : function ( domain, key ) { - return this.dcnpgettext.call( this, domain, undef, key ); - }, - - dcgettext : function ( domain , key /*, category */ ) { - // Ignores the category anyways - return this.dcnpgettext.call( this, domain, undef, key ); - }, - - ngettext : function ( skey, pkey, val ) { - return this.dcnpgettext.call( this, undef, undef, skey, pkey, val ); - }, - - dngettext : function ( domain, skey, pkey, val ) { - return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); - }, - - dcngettext : function ( domain, skey, pkey, val/*, category */) { - return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); - }, - - pgettext : function ( context, key ) { - return this.dcnpgettext.call( this, undef, context, key ); - }, - - dpgettext : function ( domain, context, key ) { - return this.dcnpgettext.call( this, domain, context, key ); - }, - - dcpgettext : function ( domain, context, key/*, category */) { - return this.dcnpgettext.call( this, domain, context, key ); - }, - - npgettext : function ( context, skey, pkey, val ) { - return this.dcnpgettext.call( this, undef, context, skey, pkey, val ); - }, - - dnpgettext : function ( domain, context, skey, pkey, val ) { - return this.dcnpgettext.call( this, domain, context, skey, pkey, val ); - }, - - // The most fully qualified gettext function. It has every option. - // Since it has every option, we can use it from every other method. - // This is the bread and butter. - // Technically there should be one more argument in this function for 'Category', - // but since we never use it, we might as well not waste the bytes to define it. - dcnpgettext : function ( domain, context, singular_key, plural_key, val ) { - // Set some defaults - - plural_key = plural_key || singular_key; - - // Use the global domain default if one - // isn't explicitly passed in - domain = domain || this._textdomain; - - // Default the value to the singular case - val = typeof val == 'undefined' ? 1 : val; - - var fallback; - - // Handle special cases - - // No options found - if ( ! this.options ) { - // There's likely something wrong, but we'll return the correct key for english - // We do this by instantiating a brand new Jed instance with the default set - // for everything that could be broken. - fallback = new Jed(); - return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val ); - } - - // No translation data provided - if ( ! this.options.locale_data ) { - throw new Error('No locale data provided.'); - } - - if ( ! this.options.locale_data[ domain ] ) { - throw new Error('Domain `' + domain + '` was not found.'); - } - - if ( ! this.options.locale_data[ domain ][ "" ] ) { - throw new Error('No locale meta information provided.'); - } - - // Make sure we have a truthy key. Otherwise we might start looking - // into the empty string key, which is the options for the locale - // data. - if ( ! singular_key ) { - throw new Error('No translation key found.'); - } - - // Handle invalid numbers, but try casting strings for good measure - if ( typeof val != 'number' ) { - val = parseInt( val, 10 ); - - if ( isNaN( val ) ) { - throw new Error('The number that was passed in is not a number.'); - } - } - - var key = context ? context + Jed.context_delimiter + singular_key : singular_key, - locale_data = this.options.locale_data, - dict = locale_data[ domain ], - pluralForms = dict[""].plural_forms || (locale_data.messages || this.defaults.locale_data.messages)[""].plural_forms, - val_idx = getPluralFormFunc(pluralForms)(val) + 1, - val_list, - res; - - // Throw an error if a domain isn't found - if ( ! dict ) { - throw new Error('No domain named `' + domain + '` could be found.'); - } - - val_list = dict[ key ]; - - // If there is no match, then revert back to - // english style singular/plural with the keys passed in. - if ( ! val_list || val_idx >= val_list.length ) { - if (this.options.missing_key_callback) { - this.options.missing_key_callback(key); - } - res = [ null, singular_key, plural_key ]; - return res[ getPluralFormFunc(pluralForms)( val ) + 1 ]; - } - - res = val_list[ val_idx ]; - - // This includes empty strings on purpose - if ( ! res ) { - res = [ null, singular_key, plural_key ]; - return res[ getPluralFormFunc(pluralForms)( val ) + 1 ]; - } - return res; - } - }); - - - // We add in sprintf capabilities for post translation value interolation - // This is not internally used, so you can remove it if you have this - // available somewhere else, or want to use a different system. - - // We _slightly_ modify the normal sprintf behavior to more gracefully handle - // undefined values. - - /** - sprintf() for JavaScript 0.7-beta1 - http://www.diveintojavascript.com/projects/javascript-sprintf - - Copyright (c) Alexandru Marasteanu - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of sprintf() for JavaScript nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - var sprintf = (function() { - function get_type(variable) { - return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); - } - function str_repeat(input, multiplier) { - for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} - return output.join(''); - } - - var str_format = function() { - if (!str_format.cache.hasOwnProperty(arguments[0])) { - str_format.cache[arguments[0]] = str_format.parse(arguments[0]); - } - return str_format.format.call(null, str_format.cache[arguments[0]], arguments); - }; - - str_format.format = function(parse_tree, argv) { - var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; - for (i = 0; i < tree_length; i++) { - node_type = get_type(parse_tree[i]); - if (node_type === 'string') { - output.push(parse_tree[i]); - } - else if (node_type === 'array') { - match = parse_tree[i]; // convenience purposes only - if (match[2]) { // keyword argument - arg = argv[cursor]; - for (k = 0; k < match[2].length; k++) { - if (!arg.hasOwnProperty(match[2][k])) { - throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); - } - arg = arg[match[2][k]]; - } - } - else if (match[1]) { // positional argument (explicit) - arg = argv[match[1]]; - } - else { // positional argument (implicit) - arg = argv[cursor++]; - } - - if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { - throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); - } - - // Jed EDIT - if ( typeof arg == 'undefined' || arg === null ) { - arg = ''; - } - // Jed EDIT - - switch (match[8]) { - case 'b': arg = arg.toString(2); break; - case 'c': arg = String.fromCharCode(arg); break; - case 'd': arg = parseInt(arg, 10); break; - case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; - case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; - case 'o': arg = arg.toString(8); break; - case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; - case 'u': arg = Math.abs(arg); break; - case 'x': arg = arg.toString(16); break; - case 'X': arg = arg.toString(16).toUpperCase(); break; - } - arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); - pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; - pad_length = match[6] - String(arg).length; - pad = match[6] ? str_repeat(pad_character, pad_length) : ''; - output.push(match[5] ? arg + pad : pad + arg); - } - } - return output.join(''); - }; - - str_format.cache = {}; - - str_format.parse = function(fmt) { - var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; - while (_fmt) { - if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { - parse_tree.push(match[0]); - } - else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { - parse_tree.push('%'); - } - else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { - if (match[2]) { - arg_names |= 1; - var field_list = [], replacement_field = match[2], field_match = []; - if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { - if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - } - else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - } - else { - throw('[sprintf] huh?'); - } - } - } - else { - throw('[sprintf] huh?'); - } - match[2] = field_list; - } - else { - arg_names |= 2; - } - if (arg_names === 3) { - throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); - } - parse_tree.push(match); - } - else { - throw('[sprintf] huh?'); - } - _fmt = _fmt.substring(match[0].length); - } - return parse_tree; - }; - - return str_format; - })(); - - var vsprintf = function(fmt, argv) { - argv.unshift(fmt); - return sprintf.apply(null, argv); - }; - - Jed.parse_plural = function ( plural_forms, n ) { - plural_forms = plural_forms.replace(/n/g, n); - return Jed.parse_expression(plural_forms); - }; - - Jed.sprintf = function ( fmt, args ) { - if ( {}.toString.call( args ) == '[object Array]' ) { - return vsprintf( fmt, [].slice.call(args) ); - } - return sprintf.apply(this, [].slice.call(arguments) ); - }; - - Jed.prototype.sprintf = function () { - return Jed.sprintf.apply(this, arguments); - }; - // END sprintf Implementation - - // Start the Plural forms section - // This is a full plural form expression parser. It is used to avoid - // running 'eval' or 'new Function' directly against the plural - // forms. - // - // This can be important if you get translations done through a 3rd - // party vendor. I encourage you to use this instead, however, I - // also will provide a 'precompiler' that you can use at build time - // to output valid/safe function representations of the plural form - // expressions. This means you can build this code out for the most - // part. - Jed.PF = {}; - - Jed.PF.parse = function ( p ) { - var plural_str = Jed.PF.extractPluralExpr( p ); - return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str); - }; - - Jed.PF.compile = function ( p ) { - // Handle trues and falses as 0 and 1 - function imply( val ) { - return (val === true ? 1 : val ? val : 0); - } - - var ast = Jed.PF.parse( p ); - return function ( n ) { - return imply( Jed.PF.interpreter( ast )( n ) ); - }; - }; - - Jed.PF.interpreter = function ( ast ) { - return function ( n ) { - var res; - switch ( ast.type ) { - case 'GROUP': - return Jed.PF.interpreter( ast.expr )( n ); - case 'TERNARY': - if ( Jed.PF.interpreter( ast.expr )( n ) ) { - return Jed.PF.interpreter( ast.truthy )( n ); - } - return Jed.PF.interpreter( ast.falsey )( n ); - case 'OR': - return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n ); - case 'AND': - return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n ); - case 'LT': - return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n ); - case 'GT': - return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n ); - case 'LTE': - return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n ); - case 'GTE': - return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n ); - case 'EQ': - return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n ); - case 'NEQ': - return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n ); - case 'MOD': - return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n ); - case 'VAR': - return n; - case 'NUM': - return ast.val; - default: - throw new Error("Invalid Token found."); - } - }; - }; - - Jed.PF.extractPluralExpr = function ( p ) { - // trim first - p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - - if (! /;\s*$/.test(p)) { - p = p.concat(';'); - } - - var nplurals_re = /nplurals\=(\d+);/, - plural_re = /plural\=(.*);/, - nplurals_matches = p.match( nplurals_re ), - res = {}, - plural_matches; - - // Find the nplurals number - if ( nplurals_matches.length > 1 ) { - res.nplurals = nplurals_matches[1]; - } - else { - throw new Error('nplurals not found in plural_forms string: ' + p ); - } - - // remove that data to get to the formula - p = p.replace( nplurals_re, "" ); - plural_matches = p.match( plural_re ); - - if (!( plural_matches && plural_matches.length > 1 ) ) { - throw new Error('`plural` expression not found: ' + p); - } - return plural_matches[ 1 ]; - }; - - /* Jison generated parser */ - Jed.PF.parser = (function(){ - -var parser = {trace: function trace() { }, -yy: {}, -symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1}, -terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"}, -productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]], -performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { - -var $0 = $$.length - 1; -switch (yystate) { -case 1: return { type : 'GROUP', expr: $$[$0-1] }; -break; -case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] }; -break; -case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] }; -break; -case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] }; -break; -case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] }; -break; -case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] }; -break; -case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] }; -break; -case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] }; -break; -case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] }; -break; -case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] }; -break; -case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] }; -break; -case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] }; -break; -case 13:this.$ = { type: 'VAR' }; -break; -case 14:this.$ = { type: 'NUM', val: Number(yytext) }; -break; -} -}, -table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}], -defaultActions: {6:[2,1]}, -parseError: function parseError(str, hash) { - throw new Error(str); -}, -parse: function parse(input) { - var self = this, - stack = [0], - vstack = [null], // semantic value stack - lstack = [], // location stack - table = this.table, - yytext = '', - yylineno = 0, - yyleng = 0, - recovering = 0, - TERROR = 2, - EOF = 1; - - //this.reductionCount = this.shiftCount = 0; - - this.lexer.setInput(input); - this.lexer.yy = this.yy; - this.yy.lexer = this.lexer; - if (typeof this.lexer.yylloc == 'undefined') - this.lexer.yylloc = {}; - var yyloc = this.lexer.yylloc; - lstack.push(yyloc); - - if (typeof this.yy.parseError === 'function') - this.parseError = this.yy.parseError; - - function popStack (n) { - stack.length = stack.length - 2*n; - vstack.length = vstack.length - n; - lstack.length = lstack.length - n; - } - - function lex() { - var token; - token = self.lexer.lex() || 1; // $end = 1 - // if token isn't its numeric value, convert - if (typeof token !== 'number') { - token = self.symbols_[token] || token; - } - return token; - } - - var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; - while (true) { - // retreive state number from top of stack - state = stack[stack.length-1]; - - // use default actions if available - if (this.defaultActions[state]) { - action = this.defaultActions[state]; - } else { - if (symbol == null) - symbol = lex(); - // read action for current state and first input - action = table[state] && table[state][symbol]; - } - - // handle parse error - _handle_error: - if (typeof action === 'undefined' || !action.length || !action[0]) { - - if (!recovering) { - // Report error - expected = []; - for (p in table[state]) if (this.terminals_[p] && p > 2) { - expected.push("'"+this.terminals_[p]+"'"); - } - var errStr = ''; - if (this.lexer.showPosition) { - errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; - } else { - errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + - (symbol == 1 /*EOF*/ ? "end of input" : - ("'"+(this.terminals_[symbol] || symbol)+"'")); - } - this.parseError(errStr, - {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - - // just recovered from another error - if (recovering == 3) { - if (symbol == EOF) { - throw new Error(errStr || 'Parsing halted.'); - } - - // discard current lookahead and grab another - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - symbol = lex(); - } - - // try to recover from error - while (1) { - // check for error recovery rule in this state - if ((TERROR.toString()) in table[state]) { - break; - } - if (state == 0) { - throw new Error(errStr || 'Parsing halted.'); - } - popStack(1); - state = stack[stack.length-1]; - } - - preErrorSymbol = symbol; // save the lookahead token - symbol = TERROR; // insert generic error symbol as new lookahead - state = stack[stack.length-1]; - action = table[state] && table[state][TERROR]; - recovering = 3; // allow 3 real symbols to be shifted before reporting a new error - } - - // this shouldn't happen, unless resolve defaults are off - if (action[0] instanceof Array && action.length > 1) { - throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); - } - - switch (action[0]) { - - case 1: // shift - //this.shiftCount++; - - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); // push state - symbol = null; - if (!preErrorSymbol) { // normal execution/no error - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { // error just occurred, resume old lookahead f/ before error - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - - case 2: // reduce - //this.reductionCount++; - - len = this.productions_[action[1]][1]; - - // perform semantic action - yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 - // default location, uses first token for firsts, last for lasts - yyval._$ = { - first_line: lstack[lstack.length-(len||1)].first_line, - last_line: lstack[lstack.length-1].last_line, - first_column: lstack[lstack.length-(len||1)].first_column, - last_column: lstack[lstack.length-1].last_column - }; - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - - if (typeof r !== 'undefined') { - return r; - } - - // pop off stack - if (len) { - stack = stack.slice(0,-1*len*2); - vstack = vstack.slice(0, -1*len); - lstack = lstack.slice(0, -1*len); - } - - stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) - vstack.push(yyval.$); - lstack.push(yyval._$); - // goto new state = table[STATE][NONTERMINAL] - newState = table[stack[stack.length-2]][stack[stack.length-1]]; - stack.push(newState); - break; - - case 3: // accept - return true; - } - - } - - return true; -}};/* Jison generated lexer */ -var lexer = (function(){ - -var lexer = ({EOF:1, -parseError:function parseError(str, hash) { - if (this.yy.parseError) { - this.yy.parseError(str, hash); - } else { - throw new Error(str); - } - }, -setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - return this; - }, -input:function () { - var ch = this._input[0]; - this.yytext+=ch; - this.yyleng++; - this.match+=ch; - this.matched+=ch; - var lines = ch.match(/\n/); - if (lines) this.yylineno++; - this._input = this._input.slice(1); - return ch; - }, -unput:function (ch) { - this._input = ch + this._input; - return this; - }, -more:function () { - this._more = true; - return this; - }, -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - match = this._input.match(this.rules[rules[i]]); - if (match) { - lines = match[0].match(/\n.*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); - if (token) return token; - else return; - } - } - if (this._input === "") { - return this.EOF; - } else { - this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, -lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, -begin:function begin(condition) { - this.conditionStack.push(condition); - }, -popState:function popState() { - return this.conditionStack.pop(); - }, -_currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, -topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, -pushState:function begin(condition) { - this.begin(condition); - }}); -lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - -var YYSTATE=YY_START; -switch($avoiding_name_collisions) { -case 0:/* skip whitespace */ -break; -case 1:return 20 -break; -case 2:return 19 -break; -case 3:return 8 -break; -case 4:return 9 -break; -case 5:return 6 -break; -case 6:return 7 -break; -case 7:return 11 -break; -case 8:return 13 -break; -case 9:return 10 -break; -case 10:return 12 -break; -case 11:return 14 -break; -case 12:return 15 -break; -case 13:return 16 -break; -case 14:return 17 -break; -case 15:return 18 -break; -case 16:return 5 -break; -case 17:return 'INVALID' -break; -} -}; -lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./]; -lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})() -parser.lexer = lexer; -return parser; -})(); -// End parser - - // Handle node, amd, and global systems - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = Jed; - } - exports.Jed = Jed; - } - else { +(function (root, factory) { if (typeof define === 'function' && define.amd) { - define('jed', [],function() { - return Jed; - }); + // AMD. Register as an anonymous module. + define('strophe-sha1',[],function () { + return factory(); + }); + } else { + // Browser globals + root.SHA1 = factory(); } - // Leak a global regardless of module system - root['Jed'] = Jed; - } - -})(this); +}(this, function () { /* - * This file can be used if no locale support is required. + * Calculate the SHA-1 of an array of big-endian words, and a bit length */ +function core_sha1(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << (24 - len % 32); + x[((len + 64 >> 9) << 4) + 15] = len; + + var w = new Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + var i, j, t, olda, oldb, oldc, oldd, olde; + for (i = 0; i < x.length; i += 16) + { + olda = a; + oldb = b; + oldc = c; + oldd = d; + olde = e; + + for (j = 0; j < 80; j++) + { + if (j < 16) { w[j] = x[i + j]; } + else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); } + t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), + safe_add(safe_add(e, w[j]), sha1_kt(j))); + e = d; + d = c; + c = rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return [a, b, c, d, e]; +} + +/* + * Perform the appropriate triplet combination function for the current + * iteration + */ +function sha1_ft(t, b, c, d) +{ + if (t < 20) { return (b & c) | ((~b) & d); } + if (t < 40) { return b ^ c ^ d; } + if (t < 60) { return (b & c) | (b & d) | (c & d); } + return b ^ c ^ d; +} + +/* + * Determine the appropriate additive constant for the current iteration + */ +function sha1_kt(t) +{ + return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : + (t < 60) ? -1894007588 : -899497514; +} + +/* + * Calculate the HMAC-SHA1 of a key and some data + */ +function core_hmac_sha1(key, data) +{ + var bkey = str2binb(key); + if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * 8); } + + var ipad = new Array(16), opad = new Array(16); + for (var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8); + return core_sha1(opad.concat(hash), 512 + 160); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert an 8-bit or 16-bit string to an array of big-endian words + * In 8-bit function, characters >255 have their hi-byte silently ignored. + */ +function str2binb(str) +{ + var bin = []; + var mask = 255; + for (var i = 0; i < str.length * 8; i += 8) + { + bin[i>>5] |= (str.charCodeAt(i / 8) & mask) << (24 - i%32); + } + return bin; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2str(bin) +{ + var str = ""; + var mask = 255; + for (var i = 0; i < bin.length * 32; i += 8) + { + str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask); + } + return str; +} + +/* + * Convert an array of big-endian words to a base-64 string + */ +function binb2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + var triplet, j; + for (var i = 0; i < binarray.length * 4; i += 3) + { + triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) | + (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) | + ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); + for (j = 0; j < 4; j++) + { + if (i * 8 + j * 6 > binarray.length * 32) { str += "="; } + else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } + } + } + return str; +} + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +return { + b64_hmac_sha1: function (key, data){ return binb2b64(core_hmac_sha1(key, data)); }, + b64_sha1: function (s) { return binb2b64(core_sha1(str2binb(s),s.length * 8)); }, + binb2str: binb2str, + core_hmac_sha1: core_hmac_sha1, + str_hmac_sha1: function (key, data){ return binb2str(core_hmac_sha1(key, data)); }, + str_sha1: function (s) { return binb2str(core_sha1(str2binb(s),s.length * 8)); }, +}; +})); + +// This code was written by Tyler Akins and has been placed in the +// public domain. It would be nice if you left this header intact. +// Base64 code from Tyler Akins -- http://rumkin.com + (function (root, factory) { - define("locales", ['jed'], function (Jed) { - var translations = { - "domain": "converse", - "locale_data": { - "converse": { - "": { - "domain": "converse", - "lang": "en", - "plural_forms": "nplurals=2; plural=(n != 1);" + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe-base64',[],function () { + return factory(); + }); + } else { + // Browser globals + root.Base64 = factory(); + } +}(this, function () { + var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + var obj = { + /** + * Encodes a string in base64 + * @param {String} input The string to encode in base64. + */ + encode: function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + do { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc2 = ((chr1 & 3) << 4); + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + keyStr.charAt(enc4); + } while (i < input.length); + + return output; + }, + + /** + * Decodes a base64 string. + * @param {String} input The string to decode. + */ + decode: function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } while (i < input.length); + + return output; + } + }; + return obj; +})); + +/* + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + +/* + * Everything that isn't used by Strophe has been stripped here! + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe-md5',[],function () { + return factory(); + }); + } else { + // Browser globals + root.MD5 = factory(); + } +}(this, function (b) { + /* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ + var safe_add = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + + /* + * Bitwise rotate a 32-bit number to the left. + */ + var bit_rol = function (num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); + }; + + /* + * Convert a string to an array of little-endian words + */ + var str2binl = function (str) { + var bin = []; + for(var i = 0; i < str.length * 8; i += 8) + { + bin[i>>5] |= (str.charCodeAt(i / 8) & 255) << (i%32); + } + return bin; + }; + + /* + * Convert an array of little-endian words to a string + */ + var binl2str = function (bin) { + var str = ""; + for(var i = 0; i < bin.length * 32; i += 8) + { + str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & 255); + } + return str; + }; + + /* + * Convert an array of little-endian words to a hex string. + */ + var binl2hex = function (binarray) { + var hex_tab = "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); + } + return str; + }; + + /* + * These functions implement the four basic operations the algorithm uses. + */ + var md5_cmn = function (q, a, b, x, s, t) { + return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b); + }; + + var md5_ff = function (a, b, c, d, x, s, t) { + return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); + }; + + var md5_gg = function (a, b, c, d, x, s, t) { + return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); + }; + + var md5_hh = function (a, b, c, d, x, s, t) { + return md5_cmn(b ^ c ^ d, a, b, x, s, t); + }; + + var md5_ii = function (a, b, c, d, x, s, t) { + return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); + }; + + /* + * Calculate the MD5 of an array of little-endian words, and a bit length + */ + var core_md5 = function (x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + var olda, oldb, oldc, oldd; + for (var i = 0; i < x.length; i += 16) + { + olda = a; + oldb = b; + oldc = c; + oldd = d; + + a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + } + return [a, b, c, d]; + }; + + var obj = { + /* + * These are the functions you'll usually want to call. + * They take string arguments and return either hex or base-64 encoded + * strings. + */ + hexdigest: function (s) { + return binl2hex(core_md5(str2binl(s), s.length * 8)); + }, + + hash: function (s) { + return binl2str(core_md5(str2binl(s), s.length * 8)); + } + }; + return obj; +})); + +/* + This program is distributed under the terms of the MIT license. + Please see the LICENSE file for details. + + Copyright 2006-2008, OGG, LLC +*/ + +/* jshint undef: true, unused: true:, noarg: true, latedef: true */ + +/** PrivateFunction: Function.prototype.bind + * Bind a function to an instance. + * + * This Function object extension method creates a bound method similar + * to those in Python. This means that the 'this' object will point + * to the instance you want. See + * MDC's bind() documentation and + * Bound Functions and Function Imports in JavaScript + * for a complete explanation. + * + * This extension already exists in some browsers (namely, Firefox 3), but + * we provide it to support those that don't. + * + * Parameters: + * (Object) obj - The object that will become 'this' in the bound function. + * (Object) argN - An option argument that will be prepended to the + * arguments given for the function call + * + * Returns: + * The bound function. + */ +if (!Function.prototype.bind) { + Function.prototype.bind = function (obj /*, arg1, arg2, ... */) + { + var func = this; + var _slice = Array.prototype.slice; + var _concat = Array.prototype.concat; + var _args = _slice.call(arguments, 1); + + return function () { + return func.apply(obj ? obj : this, + _concat.call(_args, + _slice.call(arguments, 0))); + }; + }; +} + +/** PrivateFunction: Array.isArray + * This is a polyfill for the ES5 Array.isArray method. + */ +if (!Array.isArray) { + Array.isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; +} + +/** PrivateFunction: Array.prototype.indexOf + * Return the index of an object in an array. + * + * This function is not supplied by some JavaScript implementations, so + * we provide it if it is missing. This code is from: + * http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf + * + * Parameters: + * (Object) elt - The object to look for. + * (Integer) from - The index from which to start looking. (optional). + * + * Returns: + * The index of elt in the array or -1 if not found. + */ +if (!Array.prototype.indexOf) + { + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length; + + var from = Number(arguments[1]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + if (from < 0) { + from += len; + } + + for (; from < len; from++) { + if (from in this && this[from] === elt) { + return from; + } + } + + return -1; + }; + } +; +define("strophe-polyfill", function(){}); + +/* + This program is distributed under the terms of the MIT license. + Please see the LICENSE file for details. + + Copyright 2006-2008, OGG, LLC +*/ + +/* jshint undef: true, unused: true:, noarg: true, latedef: true */ +/*global define, document, window, setTimeout, clearTimeout, console, ActiveXObject, DOMParser */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe-core',[ + 'strophe-sha1', + 'strophe-base64', + 'strophe-md5', + "strophe-polyfill" + ], function () { + return factory.apply(this, arguments); + }); + } else { + // Browser globals + var o = factory(root.SHA1, root.Base64, root.MD5); + window.Strophe = o.Strophe; + window.$build = o.$build; + window.$iq = o.$iq; + window.$msg = o.$msg; + window.$pres = o.$pres; + window.SHA1 = o.SHA1; + window.Base64 = o.Base64; + window.MD5 = o.MD5; + window.b64_hmac_sha1 = o.SHA1.b64_hmac_sha1; + window.b64_sha1 = o.SHA1.b64_sha1; + window.str_hmac_sha1 = o.SHA1.str_hmac_sha1; + window.str_sha1 = o.SHA1.str_sha1; + } +}(this, function (SHA1, Base64, MD5) { + +var Strophe; + +/** Function: $build + * Create a Strophe.Builder. + * This is an alias for 'new Strophe.Builder(name, attrs)'. + * + * Parameters: + * (String) name - The root element name. + * (Object) attrs - The attributes for the root element in object notation. + * + * Returns: + * A new Strophe.Builder object. + */ +function $build(name, attrs) { return new Strophe.Builder(name, attrs); } + +/** Function: $msg + * Create a Strophe.Builder with a element as the root. + * + * Parmaeters: + * (Object) attrs - The element attributes in object notation. + * + * Returns: + * A new Strophe.Builder object. + */ +function $msg(attrs) { return new Strophe.Builder("message", attrs); } + +/** Function: $iq + * Create a Strophe.Builder with an element as the root. + * + * Parameters: + * (Object) attrs - The element attributes in object notation. + * + * Returns: + * A new Strophe.Builder object. + */ +function $iq(attrs) { return new Strophe.Builder("iq", attrs); } + +/** Function: $pres + * Create a Strophe.Builder with a element as the root. + * + * Parameters: + * (Object) attrs - The element attributes in object notation. + * + * Returns: + * A new Strophe.Builder object. + */ +function $pres(attrs) { return new Strophe.Builder("presence", attrs); } + +/** Class: Strophe + * An object container for all Strophe library functions. + * + * This class is just a container for all the objects and constants + * used in the library. It is not meant to be instantiated, but to + * provide a namespace for library objects, constants, and functions. + */ +Strophe = { + /** Constant: VERSION + * The version of the Strophe library. Unreleased builds will have + * a version of head-HASH where HASH is a partial revision. + */ + VERSION: "@VERSION@", + + /** Constants: XMPP Namespace Constants + * Common namespace constants from the XMPP RFCs and XEPs. + * + * NS.HTTPBIND - HTTP BIND namespace from XEP 124. + * NS.BOSH - BOSH namespace from XEP 206. + * NS.CLIENT - Main XMPP client namespace. + * NS.AUTH - Legacy authentication namespace. + * NS.ROSTER - Roster operations namespace. + * NS.PROFILE - Profile namespace. + * NS.DISCO_INFO - Service discovery info namespace from XEP 30. + * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30. + * NS.MUC - Multi-User Chat namespace from XEP 45. + * NS.SASL - XMPP SASL namespace from RFC 3920. + * NS.STREAM - XMPP Streams namespace from RFC 3920. + * NS.BIND - XMPP Binding namespace from RFC 3920. + * NS.SESSION - XMPP Session namespace from RFC 3920. + * NS.XHTML_IM - XHTML-IM namespace from XEP 71. + * NS.XHTML - XHTML body namespace from XEP 71. + */ + NS: { + HTTPBIND: "http://jabber.org/protocol/httpbind", + BOSH: "urn:xmpp:xbosh", + CLIENT: "jabber:client", + AUTH: "jabber:iq:auth", + ROSTER: "jabber:iq:roster", + PROFILE: "jabber:iq:profile", + DISCO_INFO: "http://jabber.org/protocol/disco#info", + DISCO_ITEMS: "http://jabber.org/protocol/disco#items", + MUC: "http://jabber.org/protocol/muc", + SASL: "urn:ietf:params:xml:ns:xmpp-sasl", + STREAM: "http://etherx.jabber.org/streams", + FRAMING: "urn:ietf:params:xml:ns:xmpp-framing", + BIND: "urn:ietf:params:xml:ns:xmpp-bind", + SESSION: "urn:ietf:params:xml:ns:xmpp-session", + VERSION: "jabber:iq:version", + STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas", + XHTML_IM: "http://jabber.org/protocol/xhtml-im", + XHTML: "http://www.w3.org/1999/xhtml" + }, + + + /** Constants: XHTML_IM Namespace + * contains allowed tags, tag attributes, and css properties. + * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset. + * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended + * allowed tags and their attributes. + */ + XHTML: { + tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'], + attributes: { + 'a': ['href'], + 'blockquote': ['style'], + 'br': [], + 'cite': ['style'], + 'em': [], + 'img': ['src', 'alt', 'style', 'height', 'width'], + 'li': ['style'], + 'ol': ['style'], + 'p': ['style'], + 'span': ['style'], + 'strong': [], + 'ul': ['style'], + 'body': [] + }, + css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'], + validTag: function(tag) + { + for(var i = 0; i < Strophe.XHTML.tags.length; i++) { + if(tag == Strophe.XHTML.tags[i]) { + return true; + } + } + return false; + }, + validAttribute: function(tag, attribute) + { + if(typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) { + for(var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { + if(attribute == Strophe.XHTML.attributes[tag][i]) { + return true; + } + } + } + return false; + }, + validCSS: function(style) + { + for(var i = 0; i < Strophe.XHTML.css.length; i++) { + if(style == Strophe.XHTML.css[i]) { + return true; + } + } + return false; + } + }, + + /** Constants: Connection Status Constants + * Connection status constants for use by the connection handler + * callback. + * + * Status.ERROR - An error has occurred + * Status.CONNECTING - The connection is currently being made + * Status.CONNFAIL - The connection attempt failed + * Status.AUTHENTICATING - The connection is authenticating + * Status.AUTHFAIL - The authentication attempt failed + * Status.CONNECTED - The connection has succeeded + * Status.DISCONNECTED - The connection has been terminated + * Status.DISCONNECTING - The connection is currently being terminated + * Status.ATTACHED - The connection has been attached + */ + Status: { + ERROR: 0, + CONNECTING: 1, + CONNFAIL: 2, + AUTHENTICATING: 3, + AUTHFAIL: 4, + CONNECTED: 5, + DISCONNECTED: 6, + DISCONNECTING: 7, + ATTACHED: 8, + REDIRECT: 9 + }, + + /** Constants: Log Level Constants + * Logging level indicators. + * + * LogLevel.DEBUG - Debug output + * LogLevel.INFO - Informational output + * LogLevel.WARN - Warnings + * LogLevel.ERROR - Errors + * LogLevel.FATAL - Fatal errors + */ + LogLevel: { + DEBUG: 0, + INFO: 1, + WARN: 2, + ERROR: 3, + FATAL: 4 + }, + + /** PrivateConstants: DOM Element Type Constants + * DOM element types. + * + * ElementType.NORMAL - Normal element. + * ElementType.TEXT - Text data element. + * ElementType.FRAGMENT - XHTML fragment element. + */ + ElementType: { + NORMAL: 1, + TEXT: 3, + CDATA: 4, + FRAGMENT: 11 + }, + + /** PrivateConstants: Timeout Values + * Timeout values for error states. These values are in seconds. + * These should not be changed unless you know exactly what you are + * doing. + * + * TIMEOUT - Timeout multiplier. A waiting request will be considered + * failed after Math.floor(TIMEOUT * wait) seconds have elapsed. + * This defaults to 1.1, and with default wait, 66 seconds. + * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where + * Strophe can detect early failure, it will consider the request + * failed if it doesn't return after + * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed. + * This defaults to 0.1, and with default wait, 6 seconds. + */ + TIMEOUT: 1.1, + SECONDARY_TIMEOUT: 0.1, + + /** Function: addNamespace + * This function is used to extend the current namespaces in + * Strophe.NS. It takes a key and a value with the key being the + * name of the new namespace, with its actual value. + * For example: + * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); + * + * Parameters: + * (String) name - The name under which the namespace will be + * referenced under Strophe.NS + * (String) value - The actual namespace. + */ + addNamespace: function (name, value) + { + Strophe.NS[name] = value; + }, + + /** Function: forEachChild + * Map a function over some or all child elements of a given element. + * + * This is a small convenience function for mapping a function over + * some or all of the children of an element. If elemName is null, all + * children will be passed to the function, otherwise only children + * whose tag names match elemName will be passed. + * + * Parameters: + * (XMLElement) elem - The element to operate on. + * (String) elemName - The child element tag name filter. + * (Function) func - The function to apply to each child. This + * function should take a single argument, a DOM element. + */ + forEachChild: function (elem, elemName, func) + { + var i, childNode; + + for (i = 0; i < elem.childNodes.length; i++) { + childNode = elem.childNodes[i]; + if (childNode.nodeType == Strophe.ElementType.NORMAL && + (!elemName || this.isTagEqual(childNode, elemName))) { + func(childNode); + } + } + }, + + /** Function: isTagEqual + * Compare an element's tag name with a string. + * + * This function is case sensitive. + * + * Parameters: + * (XMLElement) el - A DOM element. + * (String) name - The element name. + * + * Returns: + * true if the element's tag name matches _el_, and false + * otherwise. + */ + isTagEqual: function (el, name) + { + return el.tagName == name; + }, + + /** PrivateVariable: _xmlGenerator + * _Private_ variable that caches a DOM document to + * generate elements. + */ + _xmlGenerator: null, + + /** PrivateFunction: _makeGenerator + * _Private_ function that creates a dummy XML DOM document to serve as + * an element and text node generator. + */ + _makeGenerator: function () { + var doc; + + // IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload. + // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be + // less than 10 in the case of IE9 and below. + if (document.implementation.createDocument === undefined || + document.implementation.createDocument && document.documentMode && document.documentMode < 10) { + doc = this._getIEXmlDom(); + doc.appendChild(doc.createElement('strophe')); + } else { + doc = document.implementation + .createDocument('jabber:client', 'strophe', null); + } + + return doc; + }, + + /** Function: xmlGenerator + * Get the DOM document to generate elements. + * + * Returns: + * The currently used DOM document. + */ + xmlGenerator: function () { + if (!Strophe._xmlGenerator) { + Strophe._xmlGenerator = Strophe._makeGenerator(); + } + return Strophe._xmlGenerator; + }, + + /** PrivateFunction: _getIEXmlDom + * Gets IE xml doc object + * + * Returns: + * A Microsoft XML DOM Object + * See Also: + * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx + */ + _getIEXmlDom : function() { + var doc = null; + var docStrings = [ + "Msxml2.DOMDocument.6.0", + "Msxml2.DOMDocument.5.0", + "Msxml2.DOMDocument.4.0", + "MSXML2.DOMDocument.3.0", + "MSXML2.DOMDocument", + "MSXML.DOMDocument", + "Microsoft.XMLDOM" + ]; + + for (var d = 0; d < docStrings.length; d++) { + if (doc === null) { + try { + doc = new ActiveXObject(docStrings[d]); + } catch (e) { + doc = null; + } + } else { + break; + } + } + + return doc; + }, + + /** Function: xmlElement + * Create an XML DOM element. + * + * This function creates an XML DOM element correctly across all + * implementations. Note that these are not HTML DOM elements, which + * aren't appropriate for XMPP stanzas. + * + * Parameters: + * (String) name - The name for the element. + * (Array|Object) attrs - An optional array or object containing + * key/value pairs to use as element attributes. The object should + * be in the format {'key': 'value'} or {key: 'value'}. The array + * should have the format [['key1', 'value1'], ['key2', 'value2']]. + * (String) text - The text child data for the element. + * + * Returns: + * A new XML DOM element. + */ + xmlElement: function (name) + { + if (!name) { return null; } + + var node = Strophe.xmlGenerator().createElement(name); + + // FIXME: this should throw errors if args are the wrong type or + // there are more than two optional args + var a, i, k; + for (a = 1; a < arguments.length; a++) { + if (!arguments[a]) { continue; } + if (typeof(arguments[a]) == "string" || + typeof(arguments[a]) == "number") { + node.appendChild(Strophe.xmlTextNode(arguments[a])); + } else if (typeof(arguments[a]) == "object" && + typeof(arguments[a].sort) == "function") { + for (i = 0; i < arguments[a].length; i++) { + if (typeof(arguments[a][i]) == "object" && + typeof(arguments[a][i].sort) == "function") { + node.setAttribute(arguments[a][i][0], + arguments[a][i][1]); + } + } + } else if (typeof(arguments[a]) == "object") { + for (k in arguments[a]) { + if (arguments[a].hasOwnProperty(k)) { + node.setAttribute(k, arguments[a][k]); } } } + } + + return node; + }, + + /* Function: xmlescape + * Excapes invalid xml characters. + * + * Parameters: + * (String) text - text to escape. + * + * Returns: + * Escaped text. + */ + xmlescape: function(text) + { + text = text.replace(/\&/g, "&"); + text = text.replace(//g, ">"); + text = text.replace(/'/g, "'"); + text = text.replace(/"/g, """); + return text; + }, + + /* Function: xmlunescape + * Unexcapes invalid xml characters. + * + * Parameters: + * (String) text - text to unescape. + * + * Returns: + * Unescaped text. + */ + xmlunescape: function(text) + { + text = text.replace(/\&/g, "&"); + text = text.replace(/</g, "<"); + text = text.replace(/>/g, ">"); + text = text.replace(/'/g, "'"); + text = text.replace(/"/g, "\""); + return text; + }, + + /** Function: xmlTextNode + * Creates an XML DOM text node. + * + * Provides a cross implementation version of document.createTextNode. + * + * Parameters: + * (String) text - The content of the text node. + * + * Returns: + * A new XML DOM text node. + */ + xmlTextNode: function (text) + { + return Strophe.xmlGenerator().createTextNode(text); + }, + + /** Function: xmlHtmlNode + * Creates an XML DOM html node. + * + * Parameters: + * (String) html - The content of the html node. + * + * Returns: + * A new XML DOM text node. + */ + xmlHtmlNode: function (html) + { + var node; + //ensure text is escaped + if (window.DOMParser) { + var parser = new DOMParser(); + node = parser.parseFromString(html, "text/xml"); + } else { + node = new ActiveXObject("Microsoft.XMLDOM"); + node.async="false"; + node.loadXML(html); + } + return node; + }, + + /** Function: getText + * Get the concatenation of all text children of an element. + * + * Parameters: + * (XMLElement) elem - A DOM element. + * + * Returns: + * A String with the concatenated text of all text element children. + */ + getText: function (elem) + { + if (!elem) { return null; } + + var str = ""; + if (elem.childNodes.length === 0 && elem.nodeType == + Strophe.ElementType.TEXT) { + str += elem.nodeValue; + } + + for (var i = 0; i < elem.childNodes.length; i++) { + if (elem.childNodes[i].nodeType == Strophe.ElementType.TEXT) { + str += elem.childNodes[i].nodeValue; + } + } + + return Strophe.xmlescape(str); + }, + + /** Function: copyElement + * Copy an XML DOM element. + * + * This function copies a DOM element and all its descendants and returns + * the new copy. + * + * Parameters: + * (XMLElement) elem - A DOM element. + * + * Returns: + * A new, copied DOM element tree. + */ + copyElement: function (elem) + { + var i, el; + if (elem.nodeType == Strophe.ElementType.NORMAL) { + el = Strophe.xmlElement(elem.tagName); + + for (i = 0; i < elem.attributes.length; i++) { + el.setAttribute(elem.attributes[i].nodeName, + elem.attributes[i].value); + } + + for (i = 0; i < elem.childNodes.length; i++) { + el.appendChild(Strophe.copyElement(elem.childNodes[i])); + } + } else if (elem.nodeType == Strophe.ElementType.TEXT) { + el = Strophe.xmlGenerator().createTextNode(elem.nodeValue); + } + + return el; + }, + + + /** Function: createHtml + * Copy an HTML DOM element into an XML DOM. + * + * This function copies a DOM element and all its descendants and returns + * the new copy. + * + * Parameters: + * (HTMLElement) elem - A DOM element. + * + * Returns: + * A new, copied DOM element tree. + */ + createHtml: function (elem) + { + var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue; + if (elem.nodeType == Strophe.ElementType.NORMAL) { + tag = elem.nodeName; + if(Strophe.XHTML.validTag(tag)) { + try { + el = Strophe.xmlElement(tag); + for(i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { + attribute = Strophe.XHTML.attributes[tag][i]; + value = elem.getAttribute(attribute); + if(typeof value == 'undefined' || value === null || value === '' || value === false || value === 0) { + continue; + } + if(attribute == 'style' && typeof value == 'object') { + if(typeof value.cssText != 'undefined') { + value = value.cssText; // we're dealing with IE, need to get CSS out + } + } + // filter out invalid css styles + if(attribute == 'style') { + css = []; + cssAttrs = value.split(';'); + for(j = 0; j < cssAttrs.length; j++) { + attr = cssAttrs[j].split(':'); + cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase(); + if(Strophe.XHTML.validCSS(cssName)) { + cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, ""); + css.push(cssName + ': ' + cssValue); + } + } + if(css.length > 0) { + value = css.join('; '); + el.setAttribute(attribute, value); + } + } else { + el.setAttribute(attribute, value); + } + } + + for (i = 0; i < elem.childNodes.length; i++) { + el.appendChild(Strophe.createHtml(elem.childNodes[i])); + } + } catch(e) { // invalid elements + el = Strophe.xmlTextNode(''); + } + } else { + el = Strophe.xmlGenerator().createDocumentFragment(); + for (i = 0; i < elem.childNodes.length; i++) { + el.appendChild(Strophe.createHtml(elem.childNodes[i])); + } + } + } else if (elem.nodeType == Strophe.ElementType.FRAGMENT) { + el = Strophe.xmlGenerator().createDocumentFragment(); + for (i = 0; i < elem.childNodes.length; i++) { + el.appendChild(Strophe.createHtml(elem.childNodes[i])); + } + } else if (elem.nodeType == Strophe.ElementType.TEXT) { + el = Strophe.xmlTextNode(elem.nodeValue); + } + + return el; + }, + + /** Function: escapeNode + * Escape the node part (also called local part) of a JID. + * + * Parameters: + * (String) node - A node (or local part). + * + * Returns: + * An escaped node (or local part). + */ + escapeNode: function (node) + { + if (typeof node !== "string") { return node; } + return node.replace(/^\s+|\s+$/g, '') + .replace(/\\/g, "\\5c") + .replace(/ /g, "\\20") + .replace(/\"/g, "\\22") + .replace(/\&/g, "\\26") + .replace(/\'/g, "\\27") + .replace(/\//g, "\\2f") + .replace(/:/g, "\\3a") + .replace(//g, "\\3e") + .replace(/@/g, "\\40"); + }, + + /** Function: unescapeNode + * Unescape a node part (also called local part) of a JID. + * + * Parameters: + * (String) node - A node (or local part). + * + * Returns: + * An unescaped node (or local part). + */ + unescapeNode: function (node) + { + if (typeof node !== "string") { return node; } + return node.replace(/\\20/g, " ") + .replace(/\\22/g, '"') + .replace(/\\26/g, "&") + .replace(/\\27/g, "'") + .replace(/\\2f/g, "/") + .replace(/\\3a/g, ":") + .replace(/\\3c/g, "<") + .replace(/\\3e/g, ">") + .replace(/\\40/g, "@") + .replace(/\\5c/g, "\\"); + }, + + /** Function: getNodeFromJid + * Get the node portion of a JID String. + * + * Parameters: + * (String) jid - A JID. + * + * Returns: + * A String containing the node. + */ + getNodeFromJid: function (jid) + { + if (jid.indexOf("@") < 0) { return null; } + return jid.split("@")[0]; + }, + + /** Function: getDomainFromJid + * Get the domain portion of a JID String. + * + * Parameters: + * (String) jid - A JID. + * + * Returns: + * A String containing the domain. + */ + getDomainFromJid: function (jid) + { + var bare = Strophe.getBareJidFromJid(jid); + if (bare.indexOf("@") < 0) { + return bare; + } else { + var parts = bare.split("@"); + parts.splice(0, 1); + return parts.join('@'); + } + }, + + /** Function: getResourceFromJid + * Get the resource portion of a JID String. + * + * Parameters: + * (String) jid - A JID. + * + * Returns: + * A String containing the resource. + */ + getResourceFromJid: function (jid) + { + var s = jid.split("/"); + if (s.length < 2) { return null; } + s.splice(0, 1); + return s.join('/'); + }, + + /** Function: getBareJidFromJid + * Get the bare JID from a JID String. + * + * Parameters: + * (String) jid - A JID. + * + * Returns: + * A String containing the bare JID. + */ + getBareJidFromJid: function (jid) + { + return jid ? jid.split("/")[0] : null; + }, + + /** Function: log + * User overrideable logging function. + * + * This function is called whenever the Strophe library calls any + * of the logging functions. The default implementation of this + * function does nothing. If client code wishes to handle the logging + * messages, it should override this with + * > Strophe.log = function (level, msg) { + * > (user code here) + * > }; + * + * Please note that data sent and received over the wire is logged + * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput(). + * + * The different levels and their meanings are + * + * DEBUG - Messages useful for debugging purposes. + * INFO - Informational messages. This is mostly information like + * 'disconnect was called' or 'SASL auth succeeded'. + * WARN - Warnings about potential problems. This is mostly used + * to report transient connection errors like request timeouts. + * ERROR - Some error occurred. + * FATAL - A non-recoverable fatal error occurred. + * + * Parameters: + * (Integer) level - The log level of the log message. This will + * be one of the values in Strophe.LogLevel. + * (String) msg - The log message. + */ + /* jshint ignore:start */ + log: function (level, msg) + { + return; + }, + /* jshint ignore:end */ + + /** Function: debug + * Log a message at the Strophe.LogLevel.DEBUG level. + * + * Parameters: + * (String) msg - The log message. + */ + debug: function(msg) + { + this.log(this.LogLevel.DEBUG, msg); + }, + + /** Function: info + * Log a message at the Strophe.LogLevel.INFO level. + * + * Parameters: + * (String) msg - The log message. + */ + info: function (msg) + { + this.log(this.LogLevel.INFO, msg); + }, + + /** Function: warn + * Log a message at the Strophe.LogLevel.WARN level. + * + * Parameters: + * (String) msg - The log message. + */ + warn: function (msg) + { + this.log(this.LogLevel.WARN, msg); + }, + + /** Function: error + * Log a message at the Strophe.LogLevel.ERROR level. + * + * Parameters: + * (String) msg - The log message. + */ + error: function (msg) + { + this.log(this.LogLevel.ERROR, msg); + }, + + /** Function: fatal + * Log a message at the Strophe.LogLevel.FATAL level. + * + * Parameters: + * (String) msg - The log message. + */ + fatal: function (msg) + { + this.log(this.LogLevel.FATAL, msg); + }, + + /** Function: serialize + * Render a DOM element and all descendants to a String. + * + * Parameters: + * (XMLElement) elem - A DOM element. + * + * Returns: + * The serialized element tree as a String. + */ + serialize: function (elem) + { + var result; + + if (!elem) { return null; } + + if (typeof(elem.tree) === "function") { + elem = elem.tree(); + } + + var nodeName = elem.nodeName; + var i, child; + + if (elem.getAttribute("_realname")) { + nodeName = elem.getAttribute("_realname"); + } + + result = "<" + nodeName; + for (i = 0; i < elem.attributes.length; i++) { + if(elem.attributes[i].nodeName != "_realname") { + result += " " + elem.attributes[i].nodeName + + "='" + elem.attributes[i].value + .replace(/&/g, "&") + .replace(/\'/g, "'") + .replace(/>/g, ">") + .replace(/ 0) { + result += ">"; + for (i = 0; i < elem.childNodes.length; i++) { + child = elem.childNodes[i]; + switch( child.nodeType ){ + case Strophe.ElementType.NORMAL: + // normal element, so recurse + result += Strophe.serialize(child); + break; + case Strophe.ElementType.TEXT: + // text element to escape values + result += Strophe.xmlescape(child.nodeValue); + break; + case Strophe.ElementType.CDATA: + // cdata section so don't escape values + result += ""; + } + } + result += ""; + } else { + result += "/>"; + } + + return result; + }, + + /** PrivateVariable: _requestId + * _Private_ variable that keeps track of the request ids for + * connections. + */ + _requestId: 0, + + /** PrivateVariable: Strophe.connectionPlugins + * _Private_ variable Used to store plugin names that need + * initialization on Strophe.Connection construction. + */ + _connectionPlugins: {}, + + /** Function: addConnectionPlugin + * Extends the Strophe.Connection object with the given plugin. + * + * Parameters: + * (String) name - The name of the extension. + * (Object) ptype - The plugin's prototype. + */ + addConnectionPlugin: function (name, ptype) + { + Strophe._connectionPlugins[name] = ptype; + } +}; + +/** Class: Strophe.Builder + * XML DOM builder. + * + * This object provides an interface similar to JQuery but for building + * DOM element easily and rapidly. All the functions except for toString() + * and tree() return the object, so calls can be chained. Here's an + * example using the $iq() builder helper. + * > $iq({to: 'you', from: 'me', type: 'get', id: '1'}) + * > .c('query', {xmlns: 'strophe:example'}) + * > .c('example') + * > .toString() + * The above generates this XML fragment + * > + * > + * > + * > + * > + * The corresponding DOM manipulations to get a similar fragment would be + * a lot more tedious and probably involve several helper variables. + * + * Since adding children makes new operations operate on the child, up() + * is provided to traverse up the tree. To add two children, do + * > builder.c('child1', ...).up().c('child2', ...) + * The next operation on the Builder will be relative to the second child. + */ + +/** Constructor: Strophe.Builder + * Create a Strophe.Builder object. + * + * The attributes should be passed in object notation. For example + * > var b = new Builder('message', {to: 'you', from: 'me'}); + * or + * > var b = new Builder('messsage', {'xml:lang': 'en'}); + * + * Parameters: + * (String) name - The name of the root element. + * (Object) attrs - The attributes for the root element in object notation. + * + * Returns: + * A new Strophe.Builder. + */ +Strophe.Builder = function (name, attrs) +{ + // Set correct namespace for jabber:client elements + if (name == "presence" || name == "message" || name == "iq") { + if (attrs && !attrs.xmlns) { + attrs.xmlns = Strophe.NS.CLIENT; + } else if (!attrs) { + attrs = {xmlns: Strophe.NS.CLIENT}; + } + } + + // Holds the tree being built. + this.nodeTree = Strophe.xmlElement(name, attrs); + + // Points to the current operation node. + this.node = this.nodeTree; +}; + +Strophe.Builder.prototype = { + /** Function: tree + * Return the DOM tree. + * + * This function returns the current DOM tree as an element object. This + * is suitable for passing to functions like Strophe.Connection.send(). + * + * Returns: + * The DOM tree as a element object. + */ + tree: function () + { + return this.nodeTree; + }, + + /** Function: toString + * Serialize the DOM tree to a String. + * + * This function returns a string serialization of the current DOM + * tree. It is often used internally to pass data to a + * Strophe.Request object. + * + * Returns: + * The serialized DOM tree in a String. + */ + toString: function () + { + return Strophe.serialize(this.nodeTree); + }, + + /** Function: up + * Make the current parent element the new current element. + * + * This function is often used after c() to traverse back up the tree. + * For example, to add two children to the same element + * > builder.c('child1', {}).up().c('child2', {}); + * + * Returns: + * The Stophe.Builder object. + */ + up: function () + { + this.node = this.node.parentNode; + return this; + }, + + /** Function: attrs + * Add or modify attributes of the current element. + * + * The attributes should be passed in object notation. This function + * does not move the current element pointer. + * + * Parameters: + * (Object) moreattrs - The attributes to add/modify in object notation. + * + * Returns: + * The Strophe.Builder object. + */ + attrs: function (moreattrs) + { + for (var k in moreattrs) { + if (moreattrs.hasOwnProperty(k)) { + this.node.setAttribute(k, moreattrs[k]); + } + } + return this; + }, + + /** Function: c + * Add a child to the current element and make it the new current + * element. + * + * This function moves the current element pointer to the child, + * unless text is provided. If you need to add another child, it + * is necessary to use up() to go back to the parent in the tree. + * + * Parameters: + * (String) name - The name of the child. + * (Object) attrs - The attributes of the child in object notation. + * (String) text - The text to add to the child. + * + * Returns: + * The Strophe.Builder object. + */ + c: function (name, attrs, text) + { + var child = Strophe.xmlElement(name, attrs, text); + this.node.appendChild(child); + if (!text) { + this.node = child; + } + return this; + }, + + /** Function: cnode + * Add a child to the current element and make it the new current + * element. + * + * This function is the same as c() except that instead of using a + * name and an attributes object to create the child it uses an + * existing DOM element object. + * + * Parameters: + * (XMLElement) elem - A DOM element. + * + * Returns: + * The Strophe.Builder object. + */ + cnode: function (elem) + { + var impNode; + var xmlGen = Strophe.xmlGenerator(); + try { + impNode = (xmlGen.importNode !== undefined); + } + catch (e) { + impNode = false; + } + var newElem = impNode ? + xmlGen.importNode(elem, true) : + Strophe.copyElement(elem); + this.node.appendChild(newElem); + this.node = newElem; + return this; + }, + + /** Function: t + * Add a child text element. + * + * This *does not* make the child the new current element since there + * are no children of text elements. + * + * Parameters: + * (String) text - The text data to append to the current element. + * + * Returns: + * The Strophe.Builder object. + */ + t: function (text) + { + var child = Strophe.xmlTextNode(text); + this.node.appendChild(child); + return this; + }, + + /** Function: h + * Replace current element contents with the HTML passed in. + * + * This *does not* make the child the new current element + * + * Parameters: + * (String) html - The html to insert as contents of current element. + * + * Returns: + * The Strophe.Builder object. + */ + h: function (html) + { + var fragment = document.createElement('body'); + + // force the browser to try and fix any invalid HTML tags + fragment.innerHTML = html; + + // copy cleaned html into an xml dom + var xhtml = Strophe.createHtml(fragment); + + while(xhtml.childNodes.length > 0) { + this.node.appendChild(xhtml.childNodes[0]); + } + return this; + } +}; + +/** PrivateClass: Strophe.Handler + * _Private_ helper class for managing stanza handlers. + * + * A Strophe.Handler encapsulates a user provided callback function to be + * executed when matching stanzas are received by the connection. + * Handlers can be either one-off or persistant depending on their + * return value. Returning true will cause a Handler to remain active, and + * returning false will remove the Handler. + * + * Users will not use Strophe.Handler objects directly, but instead they + * will use Strophe.Connection.addHandler() and + * Strophe.Connection.deleteHandler(). + */ + +/** PrivateConstructor: Strophe.Handler + * Create and initialize a new Strophe.Handler. + * + * Parameters: + * (Function) handler - A function to be executed when the handler is run. + * (String) ns - The namespace to match. + * (String) name - The element name to match. + * (String) type - The element type to match. + * (String) id - The element id attribute to match. + * (String) from - The element from attribute to match. + * (Object) options - Handler options + * + * Returns: + * A new Strophe.Handler object. + */ +Strophe.Handler = function (handler, ns, name, type, id, from, options) +{ + this.handler = handler; + this.ns = ns; + this.name = name; + this.type = type; + this.id = id; + this.options = options || {matchBare: false}; + + // default matchBare to false if undefined + if (!this.options.matchBare) { + this.options.matchBare = false; + } + + if (this.options.matchBare) { + this.from = from ? Strophe.getBareJidFromJid(from) : null; + } else { + this.from = from; + } + + // whether the handler is a user handler or a system handler + this.user = true; +}; + +Strophe.Handler.prototype = { + /** PrivateFunction: isMatch + * Tests if a stanza matches the Strophe.Handler. + * + * Parameters: + * (XMLElement) elem - The XML element to test. + * + * Returns: + * true if the stanza matches and false otherwise. + */ + isMatch: function (elem) + { + var nsMatch; + var from = null; + + if (this.options.matchBare) { + from = Strophe.getBareJidFromJid(elem.getAttribute('from')); + } else { + from = elem.getAttribute('from'); + } + + nsMatch = false; + if (!this.ns) { + nsMatch = true; + } else { + var that = this; + Strophe.forEachChild(elem, null, function (elem) { + if (elem.getAttribute("xmlns") == that.ns) { + nsMatch = true; + } + }); + + nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns; + } + + var elem_type = elem.getAttribute("type"); + if (nsMatch && + (!this.name || Strophe.isTagEqual(elem, this.name)) && + (!this.type || (Array.isArray(this.type) ? this.type.indexOf(elem_type) != -1 : elem_type == this.type)) && + (!this.id || elem.getAttribute("id") == this.id) && + (!this.from || from == this.from)) { + return true; + } + + return false; + }, + + /** PrivateFunction: run + * Run the callback on a matching stanza. + * + * Parameters: + * (XMLElement) elem - The DOM element that triggered the + * Strophe.Handler. + * + * Returns: + * A boolean indicating if the handler should remain active. + */ + run: function (elem) + { + var result = null; + try { + result = this.handler(elem); + } catch (e) { + if (e.sourceURL) { + Strophe.fatal("error: " + this.handler + + " " + e.sourceURL + ":" + + e.line + " - " + e.name + ": " + e.message); + } else if (e.fileName) { + if (typeof(console) != "undefined") { + console.trace(); + console.error(this.handler, " - error - ", e, e.message); + } + Strophe.fatal("error: " + this.handler + " " + + e.fileName + ":" + e.lineNumber + " - " + + e.name + ": " + e.message); + } else { + Strophe.fatal("error: " + e.message + "\n" + e.stack); + } + + throw e; + } + + return result; + }, + + /** PrivateFunction: toString + * Get a String representation of the Strophe.Handler object. + * + * Returns: + * A String. + */ + toString: function () + { + return "{Handler: " + this.handler + "(" + this.name + "," + + this.id + "," + this.ns + ")}"; + } +}; + +/** PrivateClass: Strophe.TimedHandler + * _Private_ helper class for managing timed handlers. + * + * A Strophe.TimedHandler encapsulates a user provided callback that + * should be called after a certain period of time or at regular + * intervals. The return value of the callback determines whether the + * Strophe.TimedHandler will continue to fire. + * + * Users will not use Strophe.TimedHandler objects directly, but instead + * they will use Strophe.Connection.addTimedHandler() and + * Strophe.Connection.deleteTimedHandler(). + */ + +/** PrivateConstructor: Strophe.TimedHandler + * Create and initialize a new Strophe.TimedHandler object. + * + * Parameters: + * (Integer) period - The number of milliseconds to wait before the + * handler is called. + * (Function) handler - The callback to run when the handler fires. This + * function should take no arguments. + * + * Returns: + * A new Strophe.TimedHandler object. + */ +Strophe.TimedHandler = function (period, handler) +{ + this.period = period; + this.handler = handler; + + this.lastCalled = new Date().getTime(); + this.user = true; +}; + +Strophe.TimedHandler.prototype = { + /** PrivateFunction: run + * Run the callback for the Strophe.TimedHandler. + * + * Returns: + * true if the Strophe.TimedHandler should be called again, and false + * otherwise. + */ + run: function () + { + this.lastCalled = new Date().getTime(); + return this.handler(); + }, + + /** PrivateFunction: reset + * Reset the last called time for the Strophe.TimedHandler. + */ + reset: function () + { + this.lastCalled = new Date().getTime(); + }, + + /** PrivateFunction: toString + * Get a string representation of the Strophe.TimedHandler object. + * + * Returns: + * The string representation. + */ + toString: function () + { + return "{TimedHandler: " + this.handler + "(" + this.period +")}"; + } +}; + +/** Class: Strophe.Connection + * XMPP Connection manager. + * + * This class is the main part of Strophe. It manages a BOSH connection + * to an XMPP server and dispatches events to the user callbacks as + * data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1 + * and legacy authentication. + * + * After creating a Strophe.Connection object, the user will typically + * call connect() with a user supplied callback to handle connection level + * events like authentication failure, disconnection, or connection + * complete. + * + * The user will also have several event handlers defined by using + * addHandler() and addTimedHandler(). These will allow the user code to + * respond to interesting stanzas or do something periodically with the + * connection. These handlers will be active once authentication is + * finished. + * + * To send data to the connection, use send(). + */ + +/** Constructor: Strophe.Connection + * Create and initialize a Strophe.Connection object. + * + * The transport-protocol for this connection will be chosen automatically + * based on the given service parameter. URLs starting with "ws://" or + * "wss://" will use WebSockets, URLs starting with "http://", "https://" + * or without a protocol will use BOSH. + * + * To make Strophe connect to the current host you can leave out the protocol + * and host part and just pass the path, e.g. + * + * > var conn = new Strophe.Connection("/http-bind/"); + * + * WebSocket options: + * + * If you want to connect to the current host with a WebSocket connection you + * can tell Strophe to use WebSockets through a "protocol" attribute in the + * optional options parameter. Valid values are "ws" for WebSocket and "wss" + * for Secure WebSocket. + * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call + * + * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"}); + * + * Note that relative URLs _NOT_ starting with a "/" will also include the path + * of the current site. + * + * Also because downgrading security is not permitted by browsers, when using + * relative URLs both BOSH and WebSocket connections will use their secure + * variants if the current connection to the site is also secure (https). + * + * BOSH options: + * + * by adding "sync" to the options, you can control if requests will + * be made synchronously or not. The default behaviour is asynchronous. + * If you want to make requests synchronous, make "sync" evaluate to true: + * > var conn = new Strophe.Connection("/http-bind/", {sync: true}); + * You can also toggle this on an already established connection: + * > conn.options.sync = true; + * + * + * Parameters: + * (String) service - The BOSH or WebSocket service URL. + * (Object) options - A hash of configuration options + * + * Returns: + * A new Strophe.Connection object. + */ +Strophe.Connection = function (service, options) +{ + // The service URL + this.service = service; + + // Configuration options + this.options = options || {}; + var proto = this.options.protocol || ""; + + // Select protocal based on service or options + if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || + proto.indexOf("ws") === 0) { + this._proto = new Strophe.Websocket(this); + } else { + this._proto = new Strophe.Bosh(this); + } + /* The connected JID. */ + this.jid = ""; + /* the JIDs domain */ + this.domain = null; + /* stream:features */ + this.features = null; + + // SASL + this._sasl_data = {}; + this.do_session = false; + this.do_bind = false; + + // handler lists + this.timedHandlers = []; + this.handlers = []; + this.removeTimeds = []; + this.removeHandlers = []; + this.addTimeds = []; + this.addHandlers = []; + + this._authentication = {}; + this._idleTimeout = null; + this._disconnectTimeout = null; + + this.do_authentication = true; + this.authenticated = false; + this.disconnecting = false; + this.connected = false; + + this.paused = false; + + this._data = []; + this._uniqueId = 0; + + this._sasl_success_handler = null; + this._sasl_failure_handler = null; + this._sasl_challenge_handler = null; + + // Max retries before disconnecting + this.maxRetries = 5; + + // setup onIdle callback every 1/10th of a second + this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); + + // initialize plugins + for (var k in Strophe._connectionPlugins) { + if (Strophe._connectionPlugins.hasOwnProperty(k)) { + var ptype = Strophe._connectionPlugins[k]; + // jslint complaints about the below line, but this is fine + var F = function () {}; // jshint ignore:line + F.prototype = ptype; + this[k] = new F(); + this[k].init(this); + } + } +}; + +Strophe.Connection.prototype = { + /** Function: reset + * Reset the connection. + * + * This function should be called after a connection is disconnected + * before that connection is reused. + */ + reset: function () + { + this._proto._reset(); + + // SASL + this.do_session = false; + this.do_bind = false; + + // handler lists + this.timedHandlers = []; + this.handlers = []; + this.removeTimeds = []; + this.removeHandlers = []; + this.addTimeds = []; + this.addHandlers = []; + this._authentication = {}; + + this.authenticated = false; + this.disconnecting = false; + this.connected = false; + + this._data = []; + this._requests = []; + this._uniqueId = 0; + }, + + /** Function: pause + * Pause the request manager. + * + * This will prevent Strophe from sending any more requests to the + * server. This is very useful for temporarily pausing + * BOSH-Connections while a lot of send() calls are happening quickly. + * This causes Strophe to send the data in a single request, saving + * many request trips. + */ + pause: function () + { + this.paused = true; + }, + + /** Function: resume + * Resume the request manager. + * + * This resumes after pause() has been called. + */ + resume: function () + { + this.paused = false; + }, + + /** Function: getUniqueId + * Generate a unique ID for use in elements. + * + * All stanzas are required to have unique id attributes. This + * function makes creating these easy. Each connection instance has + * a counter which starts from zero, and the value of this counter + * plus a colon followed by the suffix becomes the unique id. If no + * suffix is supplied, the counter is used as the unique id. + * + * Suffixes are used to make debugging easier when reading the stream + * data, and their use is recommended. The counter resets to 0 for + * every new connection for the same reason. For connections to the + * same server that authenticate the same way, all the ids should be + * the same, which makes it easy to see changes. This is useful for + * automated testing as well. + * + * Parameters: + * (String) suffix - A optional suffix to append to the id. + * + * Returns: + * A unique string to be used for the id attribute. + */ + getUniqueId: function (suffix) + { + if (typeof(suffix) == "string" || typeof(suffix) == "number") { + return ++this._uniqueId + ":" + suffix; + } else { + return ++this._uniqueId + ""; + } + }, + + /** Function: connect + * Starts the connection process. + * + * As the connection process proceeds, the user supplied callback will + * be triggered multiple times with status updates. The callback + * should take two arguments - the status code and the error condition. + * + * The status code will be one of the values in the Strophe.Status + * constants. The error condition will be one of the conditions + * defined in RFC 3920 or the condition 'strophe-parsererror'. + * + * The Parameters _wait_, _hold_ and _route_ are optional and only relevant + * for BOSH connections. Please see XEP 124 for a more detailed explanation + * of the optional parameters. + * + * Parameters: + * (String) jid - The user's JID. This may be a bare JID, + * or a full JID. If a node is not supplied, SASL ANONYMOUS + * authentication will be attempted. + * (String) pass - The user's password. + * (Function) callback - The connect callback function. + * (Integer) wait - The optional HTTPBIND wait value. This is the + * time the server will wait before returning an empty result for + * a request. The default setting of 60 seconds is recommended. + * (Integer) hold - The optional HTTPBIND hold value. This is the + * number of connections the server will hold at one time. This + * should almost always be set to 1 (the default). + * (String) route - The optional route value. + */ + connect: function (jid, pass, callback, wait, hold, route) + { + this.jid = jid; + /** Variable: authzid + * Authorization identity. + */ + this.authzid = Strophe.getBareJidFromJid(this.jid); + /** Variable: authcid + * Authentication identity (User name). + */ + this.authcid = Strophe.getNodeFromJid(this.jid); + /** Variable: pass + * Authentication identity (User password). + */ + this.pass = pass; + /** Variable: servtype + * Digest MD5 compatibility. + */ + this.servtype = "xmpp"; + this.connect_callback = callback; + this.disconnecting = false; + this.connected = false; + this.authenticated = false; + + // parse jid for domain + this.domain = Strophe.getDomainFromJid(this.jid); + + this._changeConnectStatus(Strophe.Status.CONNECTING, null); + + this._proto._connect(wait, hold, route); + }, + + /** Function: attach + * Attach to an already created and authenticated BOSH session. + * + * This function is provided to allow Strophe to attach to BOSH + * sessions which have been created externally, perhaps by a Web + * application. This is often used to support auto-login type features + * without putting user credentials into the page. + * + * Parameters: + * (String) jid - The full JID that is bound by the session. + * (String) sid - The SID of the BOSH session. + * (String) rid - The current RID of the BOSH session. This RID + * will be used by the next request. + * (Function) callback The connect callback function. + * (Integer) wait - The optional HTTPBIND wait value. This is the + * time the server will wait before returning an empty result for + * a request. The default setting of 60 seconds is recommended. + * Other settings will require tweaks to the Strophe.TIMEOUT value. + * (Integer) hold - The optional HTTPBIND hold value. This is the + * number of connections the server will hold at one time. This + * should almost always be set to 1 (the default). + * (Integer) wind - The optional HTTBIND window value. This is the + * allowed range of request ids that are valid. The default is 5. + */ + attach: function (jid, sid, rid, callback, wait, hold, wind) + { + this._proto._attach(jid, sid, rid, callback, wait, hold, wind); + }, + + /** Function: xmlInput + * User overrideable function that receives XML data coming into the + * connection. + * + * The default function does nothing. User code can override this with + * > Strophe.Connection.xmlInput = function (elem) { + * > (user code) + * > }; + * + * Due to limitations of current Browsers' XML-Parsers the opening and closing + * tag for WebSocket-Connoctions will be passed as selfclosing here. + * + * BOSH-Connections will have all stanzas wrapped in a tag. See + * if you want to strip this tag. + * + * Parameters: + * (XMLElement) elem - The XML data received by the connection. + */ + /* jshint unused:false */ + xmlInput: function (elem) + { + return; + }, + /* jshint unused:true */ + + /** Function: xmlOutput + * User overrideable function that receives XML data sent to the + * connection. + * + * The default function does nothing. User code can override this with + * > Strophe.Connection.xmlOutput = function (elem) { + * > (user code) + * > }; + * + * Due to limitations of current Browsers' XML-Parsers the opening and closing + * tag for WebSocket-Connoctions will be passed as selfclosing here. + * + * BOSH-Connections will have all stanzas wrapped in a tag. See + * if you want to strip this tag. + * + * Parameters: + * (XMLElement) elem - The XMLdata sent by the connection. + */ + /* jshint unused:false */ + xmlOutput: function (elem) + { + return; + }, + /* jshint unused:true */ + + /** Function: rawInput + * User overrideable function that receives raw data coming into the + * connection. + * + * The default function does nothing. User code can override this with + * > Strophe.Connection.rawInput = function (data) { + * > (user code) + * > }; + * + * Parameters: + * (String) data - The data received by the connection. + */ + /* jshint unused:false */ + rawInput: function (data) + { + return; + }, + /* jshint unused:true */ + + /** Function: rawOutput + * User overrideable function that receives raw data sent to the + * connection. + * + * The default function does nothing. User code can override this with + * > Strophe.Connection.rawOutput = function (data) { + * > (user code) + * > }; + * + * Parameters: + * (String) data - The data sent by the connection. + */ + /* jshint unused:false */ + rawOutput: function (data) + { + return; + }, + /* jshint unused:true */ + + /** Function: send + * Send a stanza. + * + * This function is called to push data onto the send queue to + * go out over the wire. Whenever a request is sent to the BOSH + * server, all pending data is sent and the queue is flushed. + * + * Parameters: + * (XMLElement | + * [XMLElement] | + * Strophe.Builder) elem - The stanza to send. + */ + send: function (elem) + { + if (elem === null) { return ; } + if (typeof(elem.sort) === "function") { + for (var i = 0; i < elem.length; i++) { + this._queueData(elem[i]); + } + } else if (typeof(elem.tree) === "function") { + this._queueData(elem.tree()); + } else { + this._queueData(elem); + } + + this._proto._send(); + }, + + /** Function: flush + * Immediately send any pending outgoing data. + * + * Normally send() queues outgoing data until the next idle period + * (100ms), which optimizes network use in the common cases when + * several send()s are called in succession. flush() can be used to + * immediately send all pending data. + */ + flush: function () + { + // cancel the pending idle period and run the idle function + // immediately + clearTimeout(this._idleTimeout); + this._onIdle(); + }, + + /** Function: sendIQ + * Helper function to send IQ stanzas. + * + * Parameters: + * (XMLElement) elem - The stanza to send. + * (Function) callback - The callback function for a successful request. + * (Function) errback - The callback function for a failed or timed + * out request. On timeout, the stanza will be null. + * (Integer) timeout - The time specified in milliseconds for a + * timeout to occur. + * + * Returns: + * The id used to send the IQ. + */ + sendIQ: function(elem, callback, errback, timeout) { + var timeoutHandler = null; + var that = this; + + if (typeof(elem.tree) === "function") { + elem = elem.tree(); + } + var id = elem.getAttribute('id'); + + // inject id if not found + if (!id) { + id = this.getUniqueId("sendIQ"); + elem.setAttribute("id", id); + } + + var expectedFrom = elem.getAttribute("to"); + var fulljid = this.jid; + + var handler = this.addHandler(function (stanza) { + // remove timeout handler if there is one + if (timeoutHandler) { + that.deleteTimedHandler(timeoutHandler); + } + + var acceptable = false; + var from = stanza.getAttribute("from"); + if (from === expectedFrom || + (expectedFrom === null && + (from === Strophe.getBareJidFromJid(fulljid) || + from === Strophe.getDomainFromJid(fulljid) || + from === fulljid))) { + acceptable = true; + } + + if (!acceptable) { + throw { + name: "StropheError", + message: "Got answer to IQ from wrong jid:" + from + + "\nExpected jid: " + expectedFrom + }; + } + + var iqtype = stanza.getAttribute('type'); + if (iqtype == 'result') { + if (callback) { + callback(stanza); + } + } else if (iqtype == 'error') { + if (errback) { + errback(stanza); + } + } else { + throw { + name: "StropheError", + message: "Got bad IQ type of " + iqtype + }; + } + }, null, 'iq', ['error', 'result'], id); + + // if timeout specified, setup timeout handler. + if (timeout) { + timeoutHandler = this.addTimedHandler(timeout, function () { + // get rid of normal handler + that.deleteHandler(handler); + // call errback on timeout with null stanza + if (errback) { + errback(null); + } + return false; + }); + } + this.send(elem); + return id; + }, + + /** PrivateFunction: _queueData + * Queue outgoing data for later sending. Also ensures that the data + * is a DOMElement. + */ + _queueData: function (element) { + if (element === null || + !element.tagName || + !element.childNodes) { + throw { + name: "StropheError", + message: "Cannot queue non-DOMElement." + }; + } + + this._data.push(element); + }, + + /** PrivateFunction: _sendRestart + * Send an xmpp:restart stanza. + */ + _sendRestart: function () + { + this._data.push("restart"); + + this._proto._sendRestart(); + + this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); + }, + + /** Function: addTimedHandler + * Add a timed handler to the connection. + * + * This function adds a timed handler. The provided handler will + * be called every period milliseconds until it returns false, + * the connection is terminated, or the handler is removed. Handlers + * that wish to continue being invoked should return true. + * + * Because of method binding it is necessary to save the result of + * this function if you wish to remove a handler with + * deleteTimedHandler(). + * + * Note that user handlers are not active until authentication is + * successful. + * + * Parameters: + * (Integer) period - The period of the handler. + * (Function) handler - The callback function. + * + * Returns: + * A reference to the handler that can be used to remove it. + */ + addTimedHandler: function (period, handler) + { + var thand = new Strophe.TimedHandler(period, handler); + this.addTimeds.push(thand); + return thand; + }, + + /** Function: deleteTimedHandler + * Delete a timed handler for a connection. + * + * This function removes a timed handler from the connection. The + * handRef parameter is *not* the function passed to addTimedHandler(), + * but is the reference returned from addTimedHandler(). + * + * Parameters: + * (Strophe.TimedHandler) handRef - The handler reference. + */ + deleteTimedHandler: function (handRef) + { + // this must be done in the Idle loop so that we don't change + // the handlers during iteration + this.removeTimeds.push(handRef); + }, + + /** Function: addHandler + * Add a stanza handler for the connection. + * + * This function adds a stanza handler to the connection. The + * handler callback will be called for any stanza that matches + * the parameters. Note that if multiple parameters are supplied, + * they must all match for the handler to be invoked. + * + * The handler will receive the stanza that triggered it as its argument. + * *The handler should return true if it is to be invoked again; + * returning false will remove the handler after it returns.* + * + * As a convenience, the ns parameters applies to the top level element + * and also any of its immediate children. This is primarily to make + * matching /iq/query elements easy. + * + * The options argument contains handler matching flags that affect how + * matches are determined. Currently the only flag is matchBare (a + * boolean). When matchBare is true, the from parameter and the from + * attribute on the stanza will be matched as bare JIDs instead of + * full JIDs. To use this, pass {matchBare: true} as the value of + * options. The default value for matchBare is false. + * + * The return value should be saved if you wish to remove the handler + * with deleteHandler(). + * + * Parameters: + * (Function) handler - The user callback. + * (String) ns - The namespace to match. + * (String) name - The stanza name to match. + * (String) type - The stanza type attribute to match. + * (String) id - The stanza id attribute to match. + * (String) from - The stanza from attribute to match. + * (String) options - The handler options + * + * Returns: + * A reference to the handler that can be used to remove it. + */ + addHandler: function (handler, ns, name, type, id, from, options) + { + var hand = new Strophe.Handler(handler, ns, name, type, id, from, options); + this.addHandlers.push(hand); + return hand; + }, + + /** Function: deleteHandler + * Delete a stanza handler for a connection. + * + * This function removes a stanza handler from the connection. The + * handRef parameter is *not* the function passed to addHandler(), + * but is the reference returned from addHandler(). + * + * Parameters: + * (Strophe.Handler) handRef - The handler reference. + */ + deleteHandler: function (handRef) + { + // this must be done in the Idle loop so that we don't change + // the handlers during iteration + this.removeHandlers.push(handRef); + // If a handler is being deleted while it is being added, + // prevent it from getting added + var i = this.addHandlers.indexOf(handRef); + if (i >= 0) { + this.addHandlers.splice(i, 1); + } + }, + + /** Function: disconnect + * Start the graceful disconnection process. + * + * This function starts the disconnection process. This process starts + * by sending unavailable presence and sending BOSH body of type + * terminate. A timeout handler makes sure that disconnection happens + * even if the BOSH server does not respond. + * If the Connection object isn't connected, at least tries to abort all pending requests + * so the connection object won't generate successful requests (which were already opened). + * + * The user supplied connection callback will be notified of the + * progress as this process happens. + * + * Parameters: + * (String) reason - The reason the disconnect is occuring. + */ + disconnect: function (reason) + { + this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason); + + Strophe.info("Disconnect was called because: " + reason); + if (this.connected) { + var pres = false; + this.disconnecting = true; + if (this.authenticated) { + pres = $pres({ + xmlns: Strophe.NS.CLIENT, + type: 'unavailable' + }); + } + // setup timeout handler + this._disconnectTimeout = this._addSysTimedHandler( + 3000, this._onDisconnectTimeout.bind(this)); + this._proto._disconnect(pres); + } else { + Strophe.info("Disconnect was called before Strophe connected to the server"); + this._proto._abortAllRequests(); + } + }, + + /** PrivateFunction: _changeConnectStatus + * _Private_ helper function that makes sure plugins and the user's + * callback are notified of connection status changes. + * + * Parameters: + * (Integer) status - the new connection status, one of the values + * in Strophe.Status + * (String) condition - the error condition or null + */ + _changeConnectStatus: function (status, condition) + { + // notify all plugins listening for status changes + for (var k in Strophe._connectionPlugins) { + if (Strophe._connectionPlugins.hasOwnProperty(k)) { + var plugin = this[k]; + if (plugin.statusChanged) { + try { + plugin.statusChanged(status, condition); + } catch (err) { + Strophe.error("" + k + " plugin caused an exception " + + "changing status: " + err); + } + } + } + } + + // notify the user's callback + if (this.connect_callback) { + try { + this.connect_callback(status, condition); + } catch (e) { + Strophe.error("User connection callback caused an " + + "exception: " + e); + } + } + }, + + /** PrivateFunction: _doDisconnect + * _Private_ function to disconnect. + * + * This is the last piece of the disconnection logic. This resets the + * connection and alerts the user's connection callback. + */ + _doDisconnect: function () + { + if (typeof this._idleTimeout == "number") { + clearTimeout(this._idleTimeout); + } + + // Cancel Disconnect Timeout + if (this._disconnectTimeout !== null) { + this.deleteTimedHandler(this._disconnectTimeout); + this._disconnectTimeout = null; + } + + Strophe.info("_doDisconnect was called"); + this._proto._doDisconnect(); + + this.authenticated = false; + this.disconnecting = false; + + // delete handlers + this.handlers = []; + this.timedHandlers = []; + this.removeTimeds = []; + this.removeHandlers = []; + this.addTimeds = []; + this.addHandlers = []; + + // tell the parent we disconnected + this._changeConnectStatus(Strophe.Status.DISCONNECTED, null); + this.connected = false; + }, + + /** PrivateFunction: _dataRecv + * _Private_ handler to processes incoming data from the the connection. + * + * Except for _connect_cb handling the initial connection request, + * this function handles the incoming data for all requests. This + * function also fires stanza handlers that match each incoming + * stanza. + * + * Parameters: + * (Strophe.Request) req - The request that has data ready. + * (string) req - The stanza a raw string (optiona). + */ + _dataRecv: function (req, raw) + { + Strophe.info("_dataRecv called"); + var elem = this._proto._reqToData(req); + if (elem === null) { return; } + + if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { + if (elem.nodeName === this._proto.strip && elem.childNodes.length) { + this.xmlInput(elem.childNodes[0]); + } else { + this.xmlInput(elem); + } + } + if (this.rawInput !== Strophe.Connection.prototype.rawInput) { + if (raw) { + this.rawInput(raw); + } else { + this.rawInput(Strophe.serialize(elem)); + } + } + + // remove handlers scheduled for deletion + var i, hand; + while (this.removeHandlers.length > 0) { + hand = this.removeHandlers.pop(); + i = this.handlers.indexOf(hand); + if (i >= 0) { + this.handlers.splice(i, 1); + } + } + + // add handlers scheduled for addition + while (this.addHandlers.length > 0) { + this.handlers.push(this.addHandlers.pop()); + } + + // handle graceful disconnect + if (this.disconnecting && this._proto._emptyQueue()) { + this._doDisconnect(); + return; + } + + var type = elem.getAttribute("type"); + var cond, conflict; + if (type !== null && type == "terminate") { + // Don't process stanzas that come in after disconnect + if (this.disconnecting) { + return; + } + + // an error occurred + cond = elem.getAttribute("condition"); + conflict = elem.getElementsByTagName("conflict"); + if (cond !== null) { + if (cond == "remote-stream-error" && conflict.length > 0) { + cond = "conflict"; + } + this._changeConnectStatus(Strophe.Status.CONNFAIL, cond); + } else { + this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); + } + this._doDisconnect(); + return; + } + + // send each incoming stanza through the handler chain + var that = this; + Strophe.forEachChild(elem, null, function (child) { + var i, newList; + // process handlers + newList = that.handlers; + that.handlers = []; + for (i = 0; i < newList.length; i++) { + var hand = newList[i]; + // encapsulate 'handler.run' not to lose the whole handler list if + // one of the handlers throws an exception + try { + if (hand.isMatch(child) && + (that.authenticated || !hand.user)) { + if (hand.run(child)) { + that.handlers.push(hand); + } + } else { + that.handlers.push(hand); + } + } catch(e) { + // if the handler throws an exception, we consider it as false + Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message); + } + } + }); + }, + + + /** Attribute: mechanisms + * SASL Mechanisms available for Conncection. + */ + mechanisms: {}, + + /** PrivateFunction: _connect_cb + * _Private_ handler for initial connection request. + * + * This handler is used to process the initial connection request + * response from the BOSH server. It is used to set up authentication + * handlers and start the authentication process. + * + * SASL authentication will be attempted if available, otherwise + * the code will fall back to legacy authentication. + * + * Parameters: + * (Strophe.Request) req - The current request. + * (Function) _callback - low level (xmpp) connect callback function. + * Useful for plugins with their own xmpp connect callback (when their) + * want to do something special). + */ + _connect_cb: function (req, _callback, raw) + { + Strophe.info("_connect_cb was called"); + + this.connected = true; + + var bodyWrap = this._proto._reqToData(req); + if (!bodyWrap) { return; } + + if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { + if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) { + this.xmlInput(bodyWrap.childNodes[0]); + } else { + this.xmlInput(bodyWrap); + } + } + if (this.rawInput !== Strophe.Connection.prototype.rawInput) { + if (raw) { + this.rawInput(raw); + } else { + this.rawInput(Strophe.serialize(bodyWrap)); + } + } + + var conncheck = this._proto._connect_cb(bodyWrap); + if (conncheck === Strophe.Status.CONNFAIL) { + return; + } + + this._authentication.sasl_scram_sha1 = false; + this._authentication.sasl_plain = false; + this._authentication.sasl_digest_md5 = false; + this._authentication.sasl_anonymous = false; + + this._authentication.legacy_auth = false; + + // Check for the stream:features tag + var hasFeatures = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "features").length > 0; + var mechanisms = bodyWrap.getElementsByTagName("mechanism"); + var matched = []; + var i, mech, found_authentication = false; + if (!hasFeatures) { + this._proto._no_auth_received(_callback); + return; + } + if (mechanisms.length > 0) { + for (i = 0; i < mechanisms.length; i++) { + mech = Strophe.getText(mechanisms[i]); + if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]); + } + } + this._authentication.legacy_auth = + bodyWrap.getElementsByTagName("auth").length > 0; + found_authentication = this._authentication.legacy_auth || + matched.length > 0; + if (!found_authentication) { + this._proto._no_auth_received(_callback); + return; + } + if (this.do_authentication !== false) + this.authenticate(matched); + }, + + /** Function: authenticate + * Set up authentication + * + * Contiunues the initial connection request by setting up authentication + * handlers and start the authentication process. + * + * SASL authentication will be attempted if available, otherwise + * the code will fall back to legacy authentication. + * + */ + authenticate: function (matched) + { + var i; + // Sorting matched mechanisms according to priority. + for (i = 0; i < matched.length - 1; ++i) { + var higher = i; + for (var j = i + 1; j < matched.length; ++j) { + if (matched[j].prototype.priority > matched[higher].prototype.priority) { + higher = j; + } + } + if (higher != i) { + var swap = matched[i]; + matched[i] = matched[higher]; + matched[higher] = swap; + } + } + + // run each mechanism + var mechanism_found = false; + for (i = 0; i < matched.length; ++i) { + if (!matched[i].test(this)) continue; + + this._sasl_success_handler = this._addSysHandler( + this._sasl_success_cb.bind(this), null, + "success", null, null); + this._sasl_failure_handler = this._addSysHandler( + this._sasl_failure_cb.bind(this), null, + "failure", null, null); + this._sasl_challenge_handler = this._addSysHandler( + this._sasl_challenge_cb.bind(this), null, + "challenge", null, null); + + this._sasl_mechanism = new matched[i](); + this._sasl_mechanism.onStart(this); + + var request_auth_exchange = $build("auth", { + xmlns: Strophe.NS.SASL, + mechanism: this._sasl_mechanism.name + }); + + if (this._sasl_mechanism.isClientFirst) { + var response = this._sasl_mechanism.onChallenge(this, null); + request_auth_exchange.t(Base64.encode(response)); + } + + this.send(request_auth_exchange.tree()); + + mechanism_found = true; + break; + } + + if (!mechanism_found) { + // if none of the mechanism worked + if (Strophe.getNodeFromJid(this.jid) === null) { + // we don't have a node, which is required for non-anonymous + // client connections + this._changeConnectStatus(Strophe.Status.CONNFAIL, + 'x-strophe-bad-non-anon-jid'); + this.disconnect('x-strophe-bad-non-anon-jid'); + } else { + // fall back to legacy authentication + this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null); + this._addSysHandler(this._auth1_cb.bind(this), null, null, + null, "_auth_1"); + + this.send($iq({ + type: "get", + to: this.domain, + id: "_auth_1" + }).c("query", { + xmlns: Strophe.NS.AUTH + }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree()); + } + } + + }, + + _sasl_challenge_cb: function(elem) { + var challenge = Base64.decode(Strophe.getText(elem)); + var response = this._sasl_mechanism.onChallenge(this, challenge); + + var stanza = $build('response', { + xmlns: Strophe.NS.SASL + }); + if (response !== "") { + stanza.t(Base64.encode(response)); + } + this.send(stanza.tree()); + + return true; + }, + + /** PrivateFunction: _auth1_cb + * _Private_ handler for legacy authentication. + * + * This handler is called in response to the initial + * for legacy authentication. It builds an authentication and + * sends it, creating a handler (calling back to _auth2_cb()) to + * handle the result + * + * Parameters: + * (XMLElement) elem - The stanza that triggered the callback. + * + * Returns: + * false to remove the handler. + */ + /* jshint unused:false */ + _auth1_cb: function (elem) + { + // build plaintext auth iq + var iq = $iq({type: "set", id: "_auth_2"}) + .c('query', {xmlns: Strophe.NS.AUTH}) + .c('username', {}).t(Strophe.getNodeFromJid(this.jid)) + .up() + .c('password').t(this.pass); + + if (!Strophe.getResourceFromJid(this.jid)) { + // since the user has not supplied a resource, we pick + // a default one here. unlike other auth methods, the server + // cannot do this for us. + this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe'; + } + iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid)); + + this._addSysHandler(this._auth2_cb.bind(this), null, + null, null, "_auth_2"); + + this.send(iq.tree()); + + return false; + }, + /* jshint unused:true */ + + /** PrivateFunction: _sasl_success_cb + * _Private_ handler for succesful SASL authentication. + * + * Parameters: + * (XMLElement) elem - The matching stanza. + * + * Returns: + * false to remove the handler. + */ + _sasl_success_cb: function (elem) + { + if (this._sasl_data["server-signature"]) { + var serverSignature; + var success = Base64.decode(Strophe.getText(elem)); + var attribMatch = /([a-z]+)=([^,]+)(,|$)/; + var matches = success.match(attribMatch); + if (matches[1] == "v") { + serverSignature = matches[2]; + } + + if (serverSignature != this._sasl_data["server-signature"]) { + // remove old handlers + this.deleteHandler(this._sasl_failure_handler); + this._sasl_failure_handler = null; + if (this._sasl_challenge_handler) { + this.deleteHandler(this._sasl_challenge_handler); + this._sasl_challenge_handler = null; + } + + this._sasl_data = {}; + return this._sasl_failure_cb(null); + } + } + + Strophe.info("SASL authentication succeeded."); + + if(this._sasl_mechanism) + this._sasl_mechanism.onSuccess(); + + // remove old handlers + this.deleteHandler(this._sasl_failure_handler); + this._sasl_failure_handler = null; + if (this._sasl_challenge_handler) { + this.deleteHandler(this._sasl_challenge_handler); + this._sasl_challenge_handler = null; + } + + var streamfeature_handlers = []; + var wrapper = function(handlers, elem) { + while (handlers.length) { + this.deleteHandler(handlers.pop()); + } + this._sasl_auth1_cb.bind(this)(elem); + return false; }; - root.locales = { 'en': new Jed(translations) }; + streamfeature_handlers.push(this._addSysHandler(function(elem) { + wrapper.bind(this)(streamfeature_handlers, elem); + }.bind(this), null, "stream:features", null, null)); + streamfeature_handlers.push(this._addSysHandler(function(elem) { + wrapper.bind(this)(streamfeature_handlers, elem); + }.bind(this), Strophe.NS.STREAM, "features", null, null)); + + // we must send an xmpp:restart now + this._sendRestart(); + + return false; + }, + + /** PrivateFunction: _sasl_auth1_cb + * _Private_ handler to start stream binding. + * + * Parameters: + * (XMLElement) elem - The matching stanza. + * + * Returns: + * false to remove the handler. + */ + _sasl_auth1_cb: function (elem) + { + // save stream:features for future usage + this.features = elem; + + var i, child; + + for (i = 0; i < elem.childNodes.length; i++) { + child = elem.childNodes[i]; + if (child.nodeName == 'bind') { + this.do_bind = true; + } + + if (child.nodeName == 'session') { + this.do_session = true; + } + } + + if (!this.do_bind) { + this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); + return false; + } else { + this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, + null, "_bind_auth_2"); + + var resource = Strophe.getResourceFromJid(this.jid); + if (resource) { + this.send($iq({type: "set", id: "_bind_auth_2"}) + .c('bind', {xmlns: Strophe.NS.BIND}) + .c('resource', {}).t(resource).tree()); + } else { + this.send($iq({type: "set", id: "_bind_auth_2"}) + .c('bind', {xmlns: Strophe.NS.BIND}) + .tree()); + } + } + + return false; + }, + + /** PrivateFunction: _sasl_bind_cb + * _Private_ handler for binding result and session start. + * + * Parameters: + * (XMLElement) elem - The matching stanza. + * + * Returns: + * false to remove the handler. + */ + _sasl_bind_cb: function (elem) + { + if (elem.getAttribute("type") == "error") { + Strophe.info("SASL binding failed."); + var conflict = elem.getElementsByTagName("conflict"), condition; + if (conflict.length > 0) { + condition = 'conflict'; + } + this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition); + return false; + } + + // TODO - need to grab errors + var bind = elem.getElementsByTagName("bind"); + var jidNode; + if (bind.length > 0) { + // Grab jid + jidNode = bind[0].getElementsByTagName("jid"); + if (jidNode.length > 0) { + this.jid = Strophe.getText(jidNode[0]); + + if (this.do_session) { + this._addSysHandler(this._sasl_session_cb.bind(this), + null, null, null, "_session_auth_2"); + + this.send($iq({type: "set", id: "_session_auth_2"}) + .c('session', {xmlns: Strophe.NS.SESSION}) + .tree()); + } else { + this.authenticated = true; + this._changeConnectStatus(Strophe.Status.CONNECTED, null); + } + } + } else { + Strophe.info("SASL binding failed."); + this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); + return false; + } + }, + + /** PrivateFunction: _sasl_session_cb + * _Private_ handler to finish successful SASL connection. + * + * This sets Connection.authenticated to true on success, which + * starts the processing of user handlers. + * + * Parameters: + * (XMLElement) elem - The matching stanza. + * + * Returns: + * false to remove the handler. + */ + _sasl_session_cb: function (elem) + { + if (elem.getAttribute("type") == "result") { + this.authenticated = true; + this._changeConnectStatus(Strophe.Status.CONNECTED, null); + } else if (elem.getAttribute("type") == "error") { + Strophe.info("Session creation failed."); + this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); + return false; + } + + return false; + }, + + /** PrivateFunction: _sasl_failure_cb + * _Private_ handler for SASL authentication failure. + * + * Parameters: + * (XMLElement) elem - The matching stanza. + * + * Returns: + * false to remove the handler. + */ + /* jshint unused:false */ + _sasl_failure_cb: function (elem) + { + // delete unneeded handlers + if (this._sasl_success_handler) { + this.deleteHandler(this._sasl_success_handler); + this._sasl_success_handler = null; + } + if (this._sasl_challenge_handler) { + this.deleteHandler(this._sasl_challenge_handler); + this._sasl_challenge_handler = null; + } + + if(this._sasl_mechanism) + this._sasl_mechanism.onFailure(); + this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); + return false; + }, + /* jshint unused:true */ + + /** PrivateFunction: _auth2_cb + * _Private_ handler to finish legacy authentication. + * + * This handler is called when the result from the jabber:iq:auth + * stanza is returned. + * + * Parameters: + * (XMLElement) elem - The stanza that triggered the callback. + * + * Returns: + * false to remove the handler. + */ + _auth2_cb: function (elem) + { + if (elem.getAttribute("type") == "result") { + this.authenticated = true; + this._changeConnectStatus(Strophe.Status.CONNECTED, null); + } else if (elem.getAttribute("type") == "error") { + this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); + this.disconnect('authentication failed'); + } + + return false; + }, + + /** PrivateFunction: _addSysTimedHandler + * _Private_ function to add a system level timed handler. + * + * This function is used to add a Strophe.TimedHandler for the + * library code. System timed handlers are allowed to run before + * authentication is complete. + * + * Parameters: + * (Integer) period - The period of the handler. + * (Function) handler - The callback function. + */ + _addSysTimedHandler: function (period, handler) + { + var thand = new Strophe.TimedHandler(period, handler); + thand.user = false; + this.addTimeds.push(thand); + return thand; + }, + + /** PrivateFunction: _addSysHandler + * _Private_ function to add a system level stanza handler. + * + * This function is used to add a Strophe.Handler for the + * library code. System stanza handlers are allowed to run before + * authentication is complete. + * + * Parameters: + * (Function) handler - The callback function. + * (String) ns - The namespace to match. + * (String) name - The stanza name to match. + * (String) type - The stanza type attribute to match. + * (String) id - The stanza id attribute to match. + */ + _addSysHandler: function (handler, ns, name, type, id) + { + var hand = new Strophe.Handler(handler, ns, name, type, id); + hand.user = false; + this.addHandlers.push(hand); + return hand; + }, + + /** PrivateFunction: _onDisconnectTimeout + * _Private_ timeout handler for handling non-graceful disconnection. + * + * If the graceful disconnect process does not complete within the + * time allotted, this handler finishes the disconnect anyway. + * + * Returns: + * false to remove the handler. + */ + _onDisconnectTimeout: function () + { + Strophe.info("_onDisconnectTimeout was called"); + + this._proto._onDisconnectTimeout(); + + // actually disconnect + this._doDisconnect(); + + return false; + }, + + /** PrivateFunction: _onIdle + * _Private_ handler to process events during idle cycle. + * + * This handler is called every 100ms to fire timed handlers that + * are ready and keep poll requests going. + */ + _onIdle: function () + { + var i, thand, since, newList; + + // add timed handlers scheduled for addition + // NOTE: we add before remove in the case a timed handler is + // added and then deleted before the next _onIdle() call. + while (this.addTimeds.length > 0) { + this.timedHandlers.push(this.addTimeds.pop()); + } + + // remove timed handlers that have been scheduled for deletion + while (this.removeTimeds.length > 0) { + thand = this.removeTimeds.pop(); + i = this.timedHandlers.indexOf(thand); + if (i >= 0) { + this.timedHandlers.splice(i, 1); + } + } + + // call ready timed handlers + var now = new Date().getTime(); + newList = []; + for (i = 0; i < this.timedHandlers.length; i++) { + thand = this.timedHandlers[i]; + if (this.authenticated || !thand.user) { + since = thand.lastCalled + thand.period; + if (since - now <= 0) { + if (thand.run()) { + newList.push(thand); + } + } else { + newList.push(thand); + } + } + } + this.timedHandlers = newList; + + clearTimeout(this._idleTimeout); + + this._proto._onIdle(); + + // reactivate the timer only if connected + if (this.connected) { + this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); + } + } +}; + +/** Class: Strophe.SASLMechanism + * + * encapsulates SASL authentication mechanisms. + * + * User code may override the priority for each mechanism or disable it completely. + * See for information about changing priority and for informatian on + * how to disable a mechanism. + * + * By default, all mechanisms are enabled and the priorities are + * + * SCRAM-SHA1 - 40 + * DIGEST-MD5 - 30 + * Plain - 20 + */ + +/** + * PrivateConstructor: Strophe.SASLMechanism + * SASL auth mechanism abstraction. + * + * Parameters: + * (String) name - SASL Mechanism name. + * (Boolean) isClientFirst - If client should send response first without challenge. + * (Number) priority - Priority. + * + * Returns: + * A new Strophe.SASLMechanism object. + */ +Strophe.SASLMechanism = function(name, isClientFirst, priority) { + /** PrivateVariable: name + * Mechanism name. + */ + this.name = name; + /** PrivateVariable: isClientFirst + * If client sends response without initial server challenge. + */ + this.isClientFirst = isClientFirst; + /** Variable: priority + * Determines which is chosen for authentication (Higher is better). + * Users may override this to prioritize mechanisms differently. + * + * In the default configuration the priorities are + * + * SCRAM-SHA1 - 40 + * DIGEST-MD5 - 30 + * Plain - 20 + * + * Example: (This will cause Strophe to choose the mechanism that the server sent first) + * + * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority; + * + * See for a list of available mechanisms. + * + */ + this.priority = priority; +}; + +Strophe.SASLMechanism.prototype = { + /** + * Function: test + * Checks if mechanism able to run. + * To disable a mechanism, make this return false; + * + * To disable plain authentication run + * > Strophe.SASLPlain.test = function() { + * > return false; + * > } + * + * See for a list of available mechanisms. + * + * Parameters: + * (Strophe.Connection) connection - Target Connection. + * + * Returns: + * (Boolean) If mechanism was able to run. + */ + /* jshint unused:false */ + test: function(connection) { + return true; + }, + /* jshint unused:true */ + + /** PrivateFunction: onStart + * Called before starting mechanism on some connection. + * + * Parameters: + * (Strophe.Connection) connection - Target Connection. + */ + onStart: function(connection) + { + this._connection = connection; + }, + + /** PrivateFunction: onChallenge + * Called by protocol implementation on incoming challenge. If client is + * first (isClientFirst == true) challenge will be null on the first call. + * + * Parameters: + * (Strophe.Connection) connection - Target Connection. + * (String) challenge - current challenge to handle. + * + * Returns: + * (String) Mechanism response. + */ + /* jshint unused:false */ + onChallenge: function(connection, challenge) { + throw new Error("You should implement challenge handling!"); + }, + /* jshint unused:true */ + + /** PrivateFunction: onFailure + * Protocol informs mechanism implementation about SASL failure. + */ + onFailure: function() { + this._connection = null; + }, + + /** PrivateFunction: onSuccess + * Protocol informs mechanism implementation about SASL success. + */ + onSuccess: function() { + this._connection = null; + } +}; + + /** Constants: SASL mechanisms + * Available authentication mechanisms + * + * Strophe.SASLAnonymous - SASL Anonymous authentication. + * Strophe.SASLPlain - SASL Plain authentication. + * Strophe.SASLMD5 - SASL Digest-MD5 authentication + * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication + */ + +// Building SASL callbacks + +/** PrivateConstructor: SASLAnonymous + * SASL Anonymous authentication. + */ +Strophe.SASLAnonymous = function() {}; + +Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 10); + +Strophe.SASLAnonymous.test = function(connection) { + return connection.authcid === null; +}; + +Strophe.Connection.prototype.mechanisms[Strophe.SASLAnonymous.prototype.name] = Strophe.SASLAnonymous; + +/** PrivateConstructor: SASLPlain + * SASL Plain authentication. + */ +Strophe.SASLPlain = function() {}; + +Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 20); + +Strophe.SASLPlain.test = function(connection) { + return connection.authcid !== null; +}; + +Strophe.SASLPlain.prototype.onChallenge = function(connection) { + var auth_str = connection.authzid; + auth_str = auth_str + "\u0000"; + auth_str = auth_str + connection.authcid; + auth_str = auth_str + "\u0000"; + auth_str = auth_str + connection.pass; + return auth_str; +}; + +Strophe.Connection.prototype.mechanisms[Strophe.SASLPlain.prototype.name] = Strophe.SASLPlain; + +/** PrivateConstructor: SASLSHA1 + * SASL SCRAM SHA 1 authentication. + */ +Strophe.SASLSHA1 = function() {}; + +/* TEST: + * This is a simple example of a SCRAM-SHA-1 authentication exchange + * when the client doesn't support channel bindings (username 'user' and + * password 'pencil' are used): + * + * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL + * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92, + * i=4096 + * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j, + * p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts= + * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ= + * + */ + +Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 40); + +Strophe.SASLSHA1.test = function(connection) { + return connection.authcid !== null; +}; + +Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) { + var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890); + + var auth_str = "n=" + connection.authcid; + auth_str += ",r="; + auth_str += cnonce; + + connection._sasl_data.cnonce = cnonce; + connection._sasl_data["client-first-message-bare"] = auth_str; + + auth_str = "n,," + auth_str; + + this.onChallenge = function (connection, challenge) + { + var nonce, salt, iter, Hi, U, U_old, i, k; + var clientKey, serverKey, clientSignature; + var responseText = "c=biws,"; + var authMessage = connection._sasl_data["client-first-message-bare"] + "," + + challenge + ","; + var cnonce = connection._sasl_data.cnonce; + var attribMatch = /([a-z]+)=([^,]+)(,|$)/; + + while (challenge.match(attribMatch)) { + var matches = challenge.match(attribMatch); + challenge = challenge.replace(matches[0], ""); + switch (matches[1]) { + case "r": + nonce = matches[2]; + break; + case "s": + salt = matches[2]; + break; + case "i": + iter = matches[2]; + break; + } + } + + if (nonce.substr(0, cnonce.length) !== cnonce) { + connection._sasl_data = {}; + return connection._sasl_failure_cb(); + } + + responseText += "r=" + nonce; + authMessage += responseText; + + salt = Base64.decode(salt); + salt += "\x00\x00\x00\x01"; + + Hi = U_old = SHA1.core_hmac_sha1(connection.pass, salt); + for (i = 1; i < iter; i++) { + U = SHA1.core_hmac_sha1(connection.pass, SHA1.binb2str(U_old)); + for (k = 0; k < 5; k++) { + Hi[k] ^= U[k]; + } + U_old = U; + } + Hi = SHA1.binb2str(Hi); + + clientKey = SHA1.core_hmac_sha1(Hi, "Client Key"); + serverKey = SHA1.str_hmac_sha1(Hi, "Server Key"); + clientSignature = SHA1.core_hmac_sha1(SHA1.str_sha1(SHA1.binb2str(clientKey)), authMessage); + connection._sasl_data["server-signature"] = SHA1.b64_hmac_sha1(serverKey, authMessage); + + for (k = 0; k < 5; k++) { + clientKey[k] ^= clientSignature[k]; + } + + responseText += ",p=" + Base64.encode(SHA1.binb2str(clientKey)); + + return responseText; + }.bind(this); + + return auth_str; +}; + +Strophe.Connection.prototype.mechanisms[Strophe.SASLSHA1.prototype.name] = Strophe.SASLSHA1; + +/** PrivateConstructor: SASLMD5 + * SASL DIGEST MD5 authentication. + */ +Strophe.SASLMD5 = function() {}; + +Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 30); + +Strophe.SASLMD5.test = function(connection) { + return connection.authcid !== null; +}; + +/** PrivateFunction: _quote + * _Private_ utility function to backslash escape and quote strings. + * + * Parameters: + * (String) str - The string to be quoted. + * + * Returns: + * quoted string + */ +Strophe.SASLMD5.prototype._quote = function (str) + { + return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; + //" end string workaround for emacs + }; + + +Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) { + var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; + var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890)); + var realm = ""; + var host = null; + var nonce = ""; + var qop = ""; + var matches; + + while (challenge.match(attribMatch)) { + matches = challenge.match(attribMatch); + challenge = challenge.replace(matches[0], ""); + matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); + switch (matches[1]) { + case "realm": + realm = matches[2]; + break; + case "nonce": + nonce = matches[2]; + break; + case "qop": + qop = matches[2]; + break; + case "host": + host = matches[2]; + break; + } + } + + var digest_uri = connection.servtype + "/" + connection.domain; + if (host !== null) { + digest_uri = digest_uri + "/" + host; + } + + var A1 = MD5.hash(connection.authcid + + ":" + realm + ":" + this._connection.pass) + + ":" + nonce + ":" + cnonce; + var A2 = 'AUTHENTICATE:' + digest_uri; + + var responseText = ""; + responseText += 'charset=utf-8,'; + responseText += 'username=' + + this._quote(connection.authcid) + ','; + responseText += 'realm=' + this._quote(realm) + ','; + responseText += 'nonce=' + this._quote(nonce) + ','; + responseText += 'nc=00000001,'; + responseText += 'cnonce=' + this._quote(cnonce) + ','; + responseText += 'digest-uri=' + this._quote(digest_uri) + ','; + responseText += 'response=' + MD5.hexdigest(MD5.hexdigest(A1) + ":" + + nonce + ":00000001:" + + cnonce + ":auth:" + + MD5.hexdigest(A2)) + ","; + responseText += 'qop=auth'; + + this.onChallenge = function () + { + return ""; + }.bind(this); + + return responseText; +}; + +Strophe.Connection.prototype.mechanisms[Strophe.SASLMD5.prototype.name] = Strophe.SASLMD5; + +return { + Strophe: Strophe, + $build: $build, + $msg: $msg, + $iq: $iq, + $pres: $pres, + SHA1: SHA1, + Base64: Base64, + MD5: MD5, +}; +})); + +/* + This program is distributed under the terms of the MIT license. + Please see the LICENSE file for details. + + Copyright 2006-2008, OGG, LLC +*/ + +/* jshint undef: true, unused: true:, noarg: true, latedef: true */ +/* global define, window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject, Strophe, $build */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe-bosh',['strophe-core'], function (core) { + return factory( + core.Strophe, + core.$build + ); + }); + } else { + // Browser globals + return factory(Strophe, $build); + } +}(this, function (Strophe, $build) { + +/** PrivateClass: Strophe.Request + * _Private_ helper class that provides a cross implementation abstraction + * for a BOSH related XMLHttpRequest. + * + * The Strophe.Request class is used internally to encapsulate BOSH request + * information. It is not meant to be used from user's code. + */ + +/** PrivateConstructor: Strophe.Request + * Create and initialize a new Strophe.Request object. + * + * Parameters: + * (XMLElement) elem - The XML data to be sent in the request. + * (Function) func - The function that will be called when the + * XMLHttpRequest readyState changes. + * (Integer) rid - The BOSH rid attribute associated with this request. + * (Integer) sends - The number of times this same request has been + * sent. + */ +Strophe.Request = function (elem, func, rid, sends) +{ + this.id = ++Strophe._requestId; + this.xmlData = elem; + this.data = Strophe.serialize(elem); + // save original function in case we need to make a new request + // from this one. + this.origFunc = func; + this.func = func; + this.rid = rid; + this.date = NaN; + this.sends = sends || 0; + this.abort = false; + this.dead = null; + + this.age = function () { + if (!this.date) { return 0; } + var now = new Date(); + return (now - this.date) / 1000; + }; + this.timeDead = function () { + if (!this.dead) { return 0; } + var now = new Date(); + return (now - this.dead) / 1000; + }; + this.xhr = this._newXHR(); +}; + +Strophe.Request.prototype = { + /** PrivateFunction: getResponse + * Get a response from the underlying XMLHttpRequest. + * + * This function attempts to get a response from the request and checks + * for errors. + * + * Throws: + * "parsererror" - A parser error occured. + * + * Returns: + * The DOM element tree of the response. + */ + getResponse: function () + { + var node = null; + if (this.xhr.responseXML && this.xhr.responseXML.documentElement) { + node = this.xhr.responseXML.documentElement; + if (node.tagName == "parsererror") { + Strophe.error("invalid response received"); + Strophe.error("responseText: " + this.xhr.responseText); + Strophe.error("responseXML: " + + Strophe.serialize(this.xhr.responseXML)); + throw "parsererror"; + } + } else if (this.xhr.responseText) { + Strophe.error("invalid response received"); + Strophe.error("responseText: " + this.xhr.responseText); + Strophe.error("responseXML: " + + Strophe.serialize(this.xhr.responseXML)); + } + + return node; + }, + + /** PrivateFunction: _newXHR + * _Private_ helper function to create XMLHttpRequests. + * + * This function creates XMLHttpRequests across all implementations. + * + * Returns: + * A new XMLHttpRequest. + */ + _newXHR: function () + { + var xhr = null; + if (window.XMLHttpRequest) { + xhr = new XMLHttpRequest(); + if (xhr.overrideMimeType) { + xhr.overrideMimeType("text/xml; charset=utf-8"); + } + } else if (window.ActiveXObject) { + xhr = new ActiveXObject("Microsoft.XMLHTTP"); + } + + // use Function.bind() to prepend ourselves as an argument + xhr.onreadystatechange = this.func.bind(null, this); + + return xhr; + } +}; + +/** Class: Strophe.Bosh + * _Private_ helper class that handles BOSH Connections + * + * The Strophe.Bosh class is used internally by Strophe.Connection + * to encapsulate BOSH sessions. It is not meant to be used from user's code. + */ + +/** File: bosh.js + * A JavaScript library to enable BOSH in Strophejs. + * + * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH) + * to emulate a persistent, stateful, two-way connection to an XMPP server. + * More information on BOSH can be found in XEP 124. + */ + +/** PrivateConstructor: Strophe.Bosh + * Create and initialize a Strophe.Bosh object. + * + * Parameters: + * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH. + * + * Returns: + * A new Strophe.Bosh object. + */ +Strophe.Bosh = function(connection) { + this._conn = connection; + /* request id for body tags */ + this.rid = Math.floor(Math.random() * 4294967295); + /* The current session ID. */ + this.sid = null; + + // default BOSH values + this.hold = 1; + this.wait = 60; + this.window = 5; + this.errors = 0; + + this._requests = []; +}; + +Strophe.Bosh.prototype = { + /** Variable: strip + * + * BOSH-Connections will have all stanzas wrapped in a tag when + * passed to or . + * To strip this tag, User code can set to "body": + * + * > Strophe.Bosh.prototype.strip = "body"; + * + * This will enable stripping of the body tag in both + * and . + */ + strip: null, + + /** PrivateFunction: _buildBody + * _Private_ helper function to generate the wrapper for BOSH. + * + * Returns: + * A Strophe.Builder with a element. + */ + _buildBody: function () + { + var bodyWrap = $build('body', { + rid: this.rid++, + xmlns: Strophe.NS.HTTPBIND + }); + + if (this.sid !== null) { + bodyWrap.attrs({sid: this.sid}); + } + + return bodyWrap; + }, + + /** PrivateFunction: _reset + * Reset the connection. + * + * This function is called by the reset function of the Strophe Connection + */ + _reset: function () + { + this.rid = Math.floor(Math.random() * 4294967295); + this.sid = null; + this.errors = 0; + }, + + /** PrivateFunction: _connect + * _Private_ function that initializes the BOSH connection. + * + * Creates and sends the Request that initializes the BOSH connection. + */ + _connect: function (wait, hold, route) + { + this.wait = wait || this.wait; + this.hold = hold || this.hold; + this.errors = 0; + + // build the body tag + var body = this._buildBody().attrs({ + to: this._conn.domain, + "xml:lang": "en", + wait: this.wait, + hold: this.hold, + content: "text/xml; charset=utf-8", + ver: "1.6", + "xmpp:version": "1.0", + "xmlns:xmpp": Strophe.NS.BOSH + }); + + if(route){ + body.attrs({ + route: route + }); + } + + var _connect_cb = this._conn._connect_cb; + + this._requests.push( + new Strophe.Request(body.tree(), + this._onRequestStateChange.bind( + this, _connect_cb.bind(this._conn)), + body.tree().getAttribute("rid"))); + this._throttledRequestHandler(); + }, + + /** PrivateFunction: _attach + * Attach to an already created and authenticated BOSH session. + * + * This function is provided to allow Strophe to attach to BOSH + * sessions which have been created externally, perhaps by a Web + * application. This is often used to support auto-login type features + * without putting user credentials into the page. + * + * Parameters: + * (String) jid - The full JID that is bound by the session. + * (String) sid - The SID of the BOSH session. + * (String) rid - The current RID of the BOSH session. This RID + * will be used by the next request. + * (Function) callback The connect callback function. + * (Integer) wait - The optional HTTPBIND wait value. This is the + * time the server will wait before returning an empty result for + * a request. The default setting of 60 seconds is recommended. + * Other settings will require tweaks to the Strophe.TIMEOUT value. + * (Integer) hold - The optional HTTPBIND hold value. This is the + * number of connections the server will hold at one time. This + * should almost always be set to 1 (the default). + * (Integer) wind - The optional HTTBIND window value. This is the + * allowed range of request ids that are valid. The default is 5. + */ + _attach: function (jid, sid, rid, callback, wait, hold, wind) + { + this._conn.jid = jid; + this.sid = sid; + this.rid = rid; + + this._conn.connect_callback = callback; + + this._conn.domain = Strophe.getDomainFromJid(this._conn.jid); + + this._conn.authenticated = true; + this._conn.connected = true; + + this.wait = wait || this.wait; + this.hold = hold || this.hold; + this.window = wind || this.window; + + this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null); + }, + + /** PrivateFunction: _connect_cb + * _Private_ handler for initial connection request. + * + * This handler is used to process the Bosh-part of the initial request. + * Parameters: + * (Strophe.Request) bodyWrap - The received stanza. + */ + _connect_cb: function (bodyWrap) + { + var typ = bodyWrap.getAttribute("type"); + var cond, conflict; + if (typ !== null && typ == "terminate") { + // an error occurred + Strophe.error("BOSH-Connection failed: " + cond); + cond = bodyWrap.getAttribute("condition"); + conflict = bodyWrap.getElementsByTagName("conflict"); + if (cond !== null) { + if (cond == "remote-stream-error" && conflict.length > 0) { + cond = "conflict"; + } + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond); + } else { + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); + } + this._conn._doDisconnect(); + return Strophe.Status.CONNFAIL; + } + + // check to make sure we don't overwrite these if _connect_cb is + // called multiple times in the case of missing stream:features + if (!this.sid) { + this.sid = bodyWrap.getAttribute("sid"); + } + var wind = bodyWrap.getAttribute('requests'); + if (wind) { this.window = parseInt(wind, 10); } + var hold = bodyWrap.getAttribute('hold'); + if (hold) { this.hold = parseInt(hold, 10); } + var wait = bodyWrap.getAttribute('wait'); + if (wait) { this.wait = parseInt(wait, 10); } + }, + + /** PrivateFunction: _disconnect + * _Private_ part of Connection.disconnect for Bosh + * + * Parameters: + * (Request) pres - This stanza will be sent before disconnecting. + */ + _disconnect: function (pres) + { + this._sendTerminate(pres); + }, + + /** PrivateFunction: _doDisconnect + * _Private_ function to disconnect. + * + * Resets the SID and RID. + */ + _doDisconnect: function () + { + this.sid = null; + this.rid = Math.floor(Math.random() * 4294967295); + }, + + /** PrivateFunction: _emptyQueue + * _Private_ function to check if the Request queue is empty. + * + * Returns: + * True, if there are no Requests queued, False otherwise. + */ + _emptyQueue: function () + { + return this._requests.length === 0; + }, + + /** PrivateFunction: _hitError + * _Private_ function to handle the error count. + * + * Requests are resent automatically until their error count reaches + * 5. Each time an error is encountered, this function is called to + * increment the count and disconnect if the count is too high. + * + * Parameters: + * (Integer) reqStatus - The request status. + */ + _hitError: function (reqStatus) + { + this.errors++; + Strophe.warn("request errored, status: " + reqStatus + + ", number of errors: " + this.errors); + if (this.errors > 4) { + this._conn._onDisconnectTimeout(); + } + }, + + /** PrivateFunction: _no_auth_received + * + * Called on stream start/restart when no stream:features + * has been received and sends a blank poll request. + */ + _no_auth_received: function (_callback) + { + if (_callback) { + _callback = _callback.bind(this._conn); + } else { + _callback = this._conn._connect_cb.bind(this._conn); + } + var body = this._buildBody(); + this._requests.push( + new Strophe.Request(body.tree(), + this._onRequestStateChange.bind( + this, _callback.bind(this._conn)), + body.tree().getAttribute("rid"))); + this._throttledRequestHandler(); + }, + + /** PrivateFunction: _onDisconnectTimeout + * _Private_ timeout handler for handling non-graceful disconnection. + * + * Cancels all remaining Requests and clears the queue. + */ + _onDisconnectTimeout: function () { + this._abortAllRequests(); + }, + + /** PrivateFunction: _abortAllRequests + * _Private_ helper function that makes sure all pending requests are aborted. + */ + _abortAllRequests: function _abortAllRequests() { + var req; + while (this._requests.length > 0) { + req = this._requests.pop(); + req.abort = true; + req.xhr.abort(); + // jslint complains, but this is fine. setting to empty func + // is necessary for IE6 + req.xhr.onreadystatechange = function () {}; // jshint ignore:line + } + }, + + /** PrivateFunction: _onIdle + * _Private_ handler called by Strophe.Connection._onIdle + * + * Sends all queued Requests or polls with empty Request if there are none. + */ + _onIdle: function () { + var data = this._conn._data; + + // if no requests are in progress, poll + if (this._conn.authenticated && this._requests.length === 0 && + data.length === 0 && !this._conn.disconnecting) { + Strophe.info("no requests during idle cycle, sending " + + "blank request"); + data.push(null); + } + + if (this._conn.paused) { + return; + } + + if (this._requests.length < 2 && data.length > 0) { + var body = this._buildBody(); + for (var i = 0; i < data.length; i++) { + if (data[i] !== null) { + if (data[i] === "restart") { + body.attrs({ + to: this._conn.domain, + "xml:lang": "en", + "xmpp:restart": "true", + "xmlns:xmpp": Strophe.NS.BOSH + }); + } else { + body.cnode(data[i]).up(); + } + } + } + delete this._conn._data; + this._conn._data = []; + this._requests.push( + new Strophe.Request(body.tree(), + this._onRequestStateChange.bind( + this, this._conn._dataRecv.bind(this._conn)), + body.tree().getAttribute("rid"))); + this._throttledRequestHandler(); + } + + if (this._requests.length > 0) { + var time_elapsed = this._requests[0].age(); + if (this._requests[0].dead !== null) { + if (this._requests[0].timeDead() > + Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) { + this._throttledRequestHandler(); + } + } + + if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) { + Strophe.warn("Request " + + this._requests[0].id + + " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + + " seconds since last activity"); + this._throttledRequestHandler(); + } + } + }, + + /** PrivateFunction: _onRequestStateChange + * _Private_ handler for Strophe.Request state changes. + * + * This function is called when the XMLHttpRequest readyState changes. + * It contains a lot of error handling logic for the many ways that + * requests can fail, and calls the request callback when requests + * succeed. + * + * Parameters: + * (Function) func - The handler for the request. + * (Strophe.Request) req - The request that is changing readyState. + */ + _onRequestStateChange: function (func, req) + { + Strophe.debug("request id " + req.id + + "." + req.sends + " state changed to " + + req.xhr.readyState); + + if (req.abort) { + req.abort = false; + return; + } + + // request complete + var reqStatus; + if (req.xhr.readyState == 4) { + reqStatus = 0; + try { + reqStatus = req.xhr.status; + } catch (e) { + // ignore errors from undefined status attribute. works + // around a browser bug + } + + if (typeof(reqStatus) == "undefined") { + reqStatus = 0; + } + + if (this.disconnecting) { + if (reqStatus >= 400) { + this._hitError(reqStatus); + return; + } + } + + var reqIs0 = (this._requests[0] == req); + var reqIs1 = (this._requests[1] == req); + + if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) { + // remove from internal queue + this._removeRequest(req); + Strophe.debug("request id " + + req.id + + " should now be removed"); + } + + // request succeeded + if (reqStatus == 200) { + // if request 1 finished, or request 0 finished and request + // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to + // restart the other - both will be in the first spot, as the + // completed request has been removed from the queue already + if (reqIs1 || + (reqIs0 && this._requests.length > 0 && + this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) { + this._restartRequest(0); + } + // call handler + Strophe.debug("request id " + + req.id + "." + + req.sends + " got 200"); + func(req); + this.errors = 0; + } else { + Strophe.error("request id " + + req.id + "." + + req.sends + " error " + reqStatus + + " happened"); + if (reqStatus === 0 || + (reqStatus >= 400 && reqStatus < 600) || + reqStatus >= 12000) { + this._hitError(reqStatus); + if (reqStatus >= 400 && reqStatus < 500) { + this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, + null); + this._conn._doDisconnect(); + } + } + } + + if (!((reqStatus > 0 && reqStatus < 500) || + req.sends > 5)) { + this._throttledRequestHandler(); + } + } + }, + + /** PrivateFunction: _processRequest + * _Private_ function to process a request in the queue. + * + * This function takes requests off the queue and sends them and + * restarts dead requests. + * + * Parameters: + * (Integer) i - The index of the request in the queue. + */ + _processRequest: function (i) + { + var self = this; + var req = this._requests[i]; + var reqStatus = -1; + + try { + if (req.xhr.readyState == 4) { + reqStatus = req.xhr.status; + } + } catch (e) { + Strophe.error("caught an error in _requests[" + i + + "], reqStatus: " + reqStatus); + } + + if (typeof(reqStatus) == "undefined") { + reqStatus = -1; + } + + // make sure we limit the number of retries + if (req.sends > this._conn.maxRetries) { + this._conn._onDisconnectTimeout(); + return; + } + + var time_elapsed = req.age(); + var primaryTimeout = (!isNaN(time_elapsed) && + time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)); + var secondaryTimeout = (req.dead !== null && + req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)); + var requestCompletedWithServerError = (req.xhr.readyState == 4 && + (reqStatus < 1 || + reqStatus >= 500)); + if (primaryTimeout || secondaryTimeout || + requestCompletedWithServerError) { + if (secondaryTimeout) { + Strophe.error("Request " + + this._requests[i].id + + " timed out (secondary), restarting"); + } + req.abort = true; + req.xhr.abort(); + // setting to null fails on IE6, so set to empty function + req.xhr.onreadystatechange = function () {}; + this._requests[i] = new Strophe.Request(req.xmlData, + req.origFunc, + req.rid, + req.sends); + req = this._requests[i]; + } + + if (req.xhr.readyState === 0) { + Strophe.debug("request id " + req.id + + "." + req.sends + " posting"); + + try { + req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true); + req.xhr.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); + } catch (e2) { + Strophe.error("XHR open failed."); + if (!this._conn.connected) { + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, + "bad-service"); + } + this._conn.disconnect(); + return; + } + + // Fires the XHR request -- may be invoked immediately + // or on a gradually expanding retry window for reconnects + var sendFunc = function () { + req.date = new Date(); + if (self._conn.options.customHeaders){ + var headers = self._conn.options.customHeaders; + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + req.xhr.setRequestHeader(header, headers[header]); + } + } + } + req.xhr.send(req.data); + }; + + // Implement progressive backoff for reconnects -- + // First retry (send == 1) should also be instantaneous + if (req.sends > 1) { + // Using a cube of the retry number creates a nicely + // expanding retry window + var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), + Math.pow(req.sends, 3)) * 1000; + setTimeout(sendFunc, backoff); + } else { + sendFunc(); + } + + req.sends++; + + if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) { + if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) { + this._conn.xmlOutput(req.xmlData.childNodes[0]); + } else { + this._conn.xmlOutput(req.xmlData); + } + } + if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) { + this._conn.rawOutput(req.data); + } + } else { + Strophe.debug("_processRequest: " + + (i === 0 ? "first" : "second") + + " request has readyState of " + + req.xhr.readyState); + } + }, + + /** PrivateFunction: _removeRequest + * _Private_ function to remove a request from the queue. + * + * Parameters: + * (Strophe.Request) req - The request to remove. + */ + _removeRequest: function (req) + { + Strophe.debug("removing request"); + + var i; + for (i = this._requests.length - 1; i >= 0; i--) { + if (req == this._requests[i]) { + this._requests.splice(i, 1); + } + } + + // IE6 fails on setting to null, so set to empty function + req.xhr.onreadystatechange = function () {}; + + this._throttledRequestHandler(); + }, + + /** PrivateFunction: _restartRequest + * _Private_ function to restart a request that is presumed dead. + * + * Parameters: + * (Integer) i - The index of the request in the queue. + */ + _restartRequest: function (i) + { + var req = this._requests[i]; + if (req.dead === null) { + req.dead = new Date(); + } + + this._processRequest(i); + }, + + /** PrivateFunction: _reqToData + * _Private_ function to get a stanza out of a request. + * + * Tries to extract a stanza out of a Request Object. + * When this fails the current connection will be disconnected. + * + * Parameters: + * (Object) req - The Request. + * + * Returns: + * The stanza that was passed. + */ + _reqToData: function (req) + { + try { + return req.getResponse(); + } catch (e) { + if (e != "parsererror") { throw e; } + this._conn.disconnect("strophe-parsererror"); + } + }, + + /** PrivateFunction: _sendTerminate + * _Private_ function to send initial disconnect sequence. + * + * This is the first step in a graceful disconnect. It sends + * the BOSH server a terminate body and includes an unavailable + * presence if authentication has completed. + */ + _sendTerminate: function (pres) + { + Strophe.info("_sendTerminate was called"); + var body = this._buildBody().attrs({type: "terminate"}); + + if (pres) { + body.cnode(pres.tree()); + } + + var req = new Strophe.Request(body.tree(), + this._onRequestStateChange.bind( + this, this._conn._dataRecv.bind(this._conn)), + body.tree().getAttribute("rid")); + + this._requests.push(req); + this._throttledRequestHandler(); + }, + + /** PrivateFunction: _send + * _Private_ part of the Connection.send function for BOSH + * + * Just triggers the RequestHandler to send the messages that are in the queue + */ + _send: function () { + clearTimeout(this._conn._idleTimeout); + this._throttledRequestHandler(); + this._conn._idleTimeout = setTimeout(this._conn._onIdle.bind(this._conn), 100); + }, + + /** PrivateFunction: _sendRestart + * + * Send an xmpp:restart stanza. + */ + _sendRestart: function () + { + this._throttledRequestHandler(); + clearTimeout(this._conn._idleTimeout); + }, + + /** PrivateFunction: _throttledRequestHandler + * _Private_ function to throttle requests to the connection window. + * + * This function makes sure we don't send requests so fast that the + * request ids overflow the connection window in the case that one + * request died. + */ + _throttledRequestHandler: function () + { + if (!this._requests) { + Strophe.debug("_throttledRequestHandler called with " + + "undefined requests"); + } else { + Strophe.debug("_throttledRequestHandler called with " + + this._requests.length + " requests"); + } + + if (!this._requests || this._requests.length === 0) { + return; + } + + if (this._requests.length > 0) { + this._processRequest(0); + } + + if (this._requests.length > 1 && + Math.abs(this._requests[0].rid - + this._requests[1].rid) < this.window) { + this._processRequest(1); + } + } +}; +return Strophe; +})); + +/* + This program is distributed under the terms of the MIT license. + Please see the LICENSE file for details. + + Copyright 2006-2008, OGG, LLC +*/ + +/* jshint undef: true, unused: true:, noarg: true, latedef: true */ +/* global define, window, clearTimeout, WebSocket, DOMParser, Strophe, $build */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe-websocket',['strophe-core'], function (core) { + return factory( + core.Strophe, + core.$build + ); + }); + } else { + // Browser globals + return factory(Strophe, $build); + } +}(this, function (Strophe, $build) { + +/** Class: Strophe.WebSocket + * _Private_ helper class that handles WebSocket Connections + * + * The Strophe.WebSocket class is used internally by Strophe.Connection + * to encapsulate WebSocket sessions. It is not meant to be used from user's code. + */ + +/** File: websocket.js + * A JavaScript library to enable XMPP over Websocket in Strophejs. + * + * This file implements XMPP over WebSockets for Strophejs. + * If a Connection is established with a Websocket url (ws://...) + * Strophe will use WebSockets. + * For more information on XMPP-over-WebSocket see RFC 7395: + * http://tools.ietf.org/html/rfc7395 + * + * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de) + */ + +/** PrivateConstructor: Strophe.Websocket + * Create and initialize a Strophe.WebSocket object. + * Currently only sets the connection Object. + * + * Parameters: + * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets. + * + * Returns: + * A new Strophe.WebSocket object. + */ +Strophe.Websocket = function(connection) { + this._conn = connection; + this.strip = "wrapper"; + + var service = connection.service; + if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) { + // If the service is not an absolute URL, assume it is a path and put the absolute + // URL together from options, current URL and the path. + var new_service = ""; + + if (connection.options.protocol === "ws" && window.location.protocol !== "https:") { + new_service += "ws"; + } else { + new_service += "wss"; + } + + new_service += "://" + window.location.host; + + if (service.indexOf("/") !== 0) { + new_service += window.location.pathname + service; + } else { + new_service += service; + } + + connection.service = new_service; + } +}; + +Strophe.Websocket.prototype = { + /** PrivateFunction: _buildStream + * _Private_ helper function to generate the start tag for WebSockets + * + * Returns: + * A Strophe.Builder with a element. + */ + _buildStream: function () + { + return $build("open", { + "xmlns": Strophe.NS.FRAMING, + "to": this._conn.domain, + "version": '1.0' + }); + }, + + /** PrivateFunction: _check_streamerror + * _Private_ checks a message for stream:error + * + * Parameters: + * (Strophe.Request) bodyWrap - The received stanza. + * connectstatus - The ConnectStatus that will be set on error. + * Returns: + * true if there was a streamerror, false otherwise. + */ + _check_streamerror: function (bodyWrap, connectstatus) { + var errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "error"); + if (errors.length === 0) { + return false; + } + var error = errors[0]; + + var condition = ""; + var text = ""; + + var ns = "urn:ietf:params:xml:ns:xmpp-streams"; + for (var i = 0; i < error.childNodes.length; i++) { + var e = error.childNodes[i]; + if (e.getAttribute("xmlns") !== ns) { + break; + } if (e.nodeName === "text") { + text = e.textContent; + } else { + condition = e.nodeName; + } + } + + var errorString = "WebSocket stream error: "; + + if (condition) { + errorString += condition; + } else { + errorString += "unknown"; + } + + if (text) { + errorString += " - " + condition; + } + + Strophe.error(errorString); + + // close the connection on stream_error + this._conn._changeConnectStatus(connectstatus, condition); + this._conn._doDisconnect(); + return true; + }, + + /** PrivateFunction: _reset + * Reset the connection. + * + * This function is called by the reset function of the Strophe Connection. + * Is not needed by WebSockets. + */ + _reset: function () + { + return; + }, + + /** PrivateFunction: _connect + * _Private_ function called by Strophe.Connection.connect + * + * Creates a WebSocket for a connection and assigns Callbacks to it. + * Does nothing if there already is a WebSocket. + */ + _connect: function () { + // Ensure that there is no open WebSocket from a previous Connection. + this._closeSocket(); + + // Create the new WobSocket + this.socket = new WebSocket(this._conn.service, "xmpp"); + this.socket.onopen = this._onOpen.bind(this); + this.socket.onerror = this._onError.bind(this); + this.socket.onclose = this._onClose.bind(this); + this.socket.onmessage = this._connect_cb_wrapper.bind(this); + }, + + /** PrivateFunction: _connect_cb + * _Private_ function called by Strophe.Connection._connect_cb + * + * checks for stream:error + * + * Parameters: + * (Strophe.Request) bodyWrap - The received stanza. + */ + _connect_cb: function(bodyWrap) { + var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL); + if (error) { + return Strophe.Status.CONNFAIL; + } + }, + + /** PrivateFunction: _handleStreamStart + * _Private_ function that checks the opening tag for errors. + * + * Disconnects if there is an error and returns false, true otherwise. + * + * Parameters: + * (Node) message - Stanza containing the tag. + */ + _handleStreamStart: function(message) { + var error = false; + + // Check for errors in the tag + var ns = message.getAttribute("xmlns"); + if (typeof ns !== "string") { + error = "Missing xmlns in "; + } else if (ns !== Strophe.NS.FRAMING) { + error = "Wrong xmlns in : " + ns; + } + + var ver = message.getAttribute("version"); + if (typeof ver !== "string") { + error = "Missing version in "; + } else if (ver !== "1.0") { + error = "Wrong version in : " + ver; + } + + if (error) { + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error); + this._conn._doDisconnect(); + return false; + } + + return true; + }, + + /** PrivateFunction: _connect_cb_wrapper + * _Private_ function that handles the first connection messages. + * + * On receiving an opening stream tag this callback replaces itself with the real + * message handler. On receiving a stream error the connection is terminated. + */ + _connect_cb_wrapper: function(message) { + if (message.data.indexOf("\s*)*/, ""); + if (data === '') return; + + var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement; + this._conn.xmlInput(streamStart); + this._conn.rawInput(message.data); + + //_handleStreamSteart will check for XML errors and disconnect on error + if (this._handleStreamStart(streamStart)) { + //_connect_cb will check for stream:error and disconnect on error + this._connect_cb(streamStart); + } + } else if (message.data.indexOf(" tag."); + } + } + this._conn._doDisconnect(); + }, + + /** PrivateFunction: _doDisconnect + * _Private_ function to disconnect. + * + * Just closes the Socket for WebSockets + */ + _doDisconnect: function () + { + Strophe.info("WebSockets _doDisconnect was called"); + this._closeSocket(); + }, + + /** PrivateFunction _streamWrap + * _Private_ helper function to wrap a stanza in a tag. + * This is used so Strophe can process stanzas from WebSockets like BOSH + */ + _streamWrap: function (stanza) + { + return "" + stanza + ''; + }, + + + /** PrivateFunction: _closeSocket + * _Private_ function to close the WebSocket. + * + * Closes the socket if it is still open and deletes it + */ + _closeSocket: function () + { + if (this.socket) { try { + this.socket.close(); + } catch (e) {} } + this.socket = null; + }, + + /** PrivateFunction: _emptyQueue + * _Private_ function to check if the message queue is empty. + * + * Returns: + * True, because WebSocket messages are send immediately after queueing. + */ + _emptyQueue: function () + { + return true; + }, + + /** PrivateFunction: _onClose + * _Private_ function to handle websockets closing. + * + * Nothing to do here for WebSockets + */ + _onClose: function() { + if(this._conn.connected && !this._conn.disconnecting) { + Strophe.error("Websocket closed unexcectedly"); + this._conn._doDisconnect(); + } else { + Strophe.info("Websocket closed"); + } + }, + + /** PrivateFunction: _no_auth_received + * + * Called on stream start/restart when no stream:features + * has been received. + */ + _no_auth_received: function (_callback) + { + Strophe.error("Server did not send any auth methods"); + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods"); + if (_callback) { + _callback = _callback.bind(this._conn); + _callback(); + } + this._conn._doDisconnect(); + }, + + /** PrivateFunction: _onDisconnectTimeout + * _Private_ timeout handler for handling non-graceful disconnection. + * + * This does nothing for WebSockets + */ + _onDisconnectTimeout: function () {}, + + /** PrivateFunction: _abortAllRequests + * _Private_ helper function that makes sure all pending requests are aborted. + */ + _abortAllRequests: function () {}, + + /** PrivateFunction: _onError + * _Private_ function to handle websockets errors. + * + * Parameters: + * (Object) error - The websocket error. + */ + _onError: function(error) { + Strophe.error("Websocket error " + error); + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established was disconnected."); + this._disconnect(); + }, + + /** PrivateFunction: _onIdle + * _Private_ function called by Strophe.Connection._onIdle + * + * sends all queued stanzas + */ + _onIdle: function () { + var data = this._conn._data; + if (data.length > 0 && !this._conn.paused) { + for (var i = 0; i < data.length; i++) { + if (data[i] !== null) { + var stanza, rawStanza; + if (data[i] === "restart") { + stanza = this._buildStream().tree(); + } else { + stanza = data[i]; + } + rawStanza = Strophe.serialize(stanza); + this._conn.xmlOutput(stanza); + this._conn.rawOutput(rawStanza); + this.socket.send(rawStanza); + } + } + this._conn._data = []; + } + }, + + /** PrivateFunction: _onMessage + * _Private_ function to handle websockets messages. + * + * This function parses each of the messages as if they are full documents. [TODO : We may actually want to use a SAX Push parser]. + * + * Since all XMPP traffic starts with "" + * The first stanza will always fail to be parsed... + * Addtionnaly, the seconds stanza will always be a with the stream NS defined in the previous stanza... so we need to 'force' the inclusion of the NS in this stanza! + * + * Parameters: + * (string) message - The websocket message. + */ + _onMessage: function(message) { + var elem, data; + // check for closing stream + var close = ''; + if (message.data === close) { + this._conn.rawInput(close); + this._conn.xmlInput(message); + if (!this._conn.disconnecting) { + this._conn._doDisconnect(); + } + return; + } else if (message.data.search(" tag before we close the connection + return; + } + this._conn._dataRecv(elem, message.data); + }, + + /** PrivateFunction: _onOpen + * _Private_ function to handle websockets connection setup. + * + * The opening stream tag is sent here. + */ + _onOpen: function() { + Strophe.info("Websocket open"); + var start = this._buildStream(); + this._conn.xmlOutput(start.tree()); + + var startString = Strophe.serialize(start); + this._conn.rawOutput(startString); + this.socket.send(startString); + }, + + /** PrivateFunction: _reqToData + * _Private_ function to get a stanza out of a request. + * + * WebSockets don't use requests, so the passed argument is just returned. + * + * Parameters: + * (Object) stanza - The stanza. + * + * Returns: + * The stanza that was passed. + */ + _reqToData: function (stanza) + { + return stanza; + }, + + /** PrivateFunction: _send + * _Private_ part of the Connection.send function for WebSocket + * + * Just flushes the messages that are in the queue + */ + _send: function () { + this._conn.flush(); + }, + + /** PrivateFunction: _sendRestart + * + * Send an xmpp:restart stanza. + */ + _sendRestart: function () + { + clearTimeout(this._conn._idleTimeout); + this._conn._onIdle.bind(this._conn)(); + } +}; +return Strophe; +})); + +define("strophe", [ + "strophe-core", + "strophe-bosh", + "strophe-websocket" +], function (wrapper) { + return wrapper; +}); + +/* + Copyright 2010, François de Metz +*/ +/** + * Roster Plugin + * Allow easily roster management + * + * Features + * * Get roster from server + * * handle presence + * * handle roster iq + * * subscribe/unsubscribe + * * authorize/unauthorize + * * roster versioning (xep 237) + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe.roster',[ + "strophe" + ], function (Strophe) { + factory( + Strophe.Strophe, + Strophe.$build, + Strophe.$iq , + Strophe.$msg, + Strophe.$pres + ); + return Strophe; + }); + } else { + // Browser globals + factory( + root.Strophe, + root.$build, + root.$iq , + root.$msg, + root.$pres + ); + } +}(this, function (Strophe, $build, $iq, $msg, $pres) { + + Strophe.addConnectionPlugin('roster', { + /** Function: init + * Plugin init + * + * Parameters: + * (Strophe.Connection) conn - Strophe connection + */ + init: function(conn) { + this._connection = conn; + this._callbacks = []; + /** Property: items + * Roster items + * [ + * { + * name : "", + * jid : "", + * subscription : "", + * ask : "", + * groups : ["", ""], + * resources : { + * myresource : { + * show : "", + * status : "", + * priority : "" + * } + * } + * } + * ] + */ + this.items = []; + /** Property: ver + * current roster revision + * always null if server doesn't support xep 237 + */ + this.ver = null; + // Override the connect and attach methods to always add presence and roster handlers. + // They are removed when the connection disconnects, so must be added on connection. + var oldCallback, roster = this, _connect = conn.connect, _attach = conn.attach; + var newCallback = function(status) { + if (status == Strophe.Status.ATTACHED || status == Strophe.Status.CONNECTED) { + try { + // Presence subscription + conn.addHandler(roster._onReceivePresence.bind(roster), null, 'presence', null, null, null); + conn.addHandler(roster._onReceiveIQ.bind(roster), Strophe.NS.ROSTER, 'iq', "set", null, null); + } + catch (e) { + Strophe.error(e); + } + } + if (typeof oldCallback === "function") { + oldCallback.apply(this, arguments); + } + }; + + conn.connect = function(jid, pass, callback, wait, hold, route) { + oldCallback = callback; + if (typeof jid == "undefined") + jid = null; + if (typeof pass == "undefined") + pass = null; + callback = newCallback; + _connect.apply(conn, [jid, pass, callback, wait, hold, route]); + }; + + conn.attach = function(jid, sid, rid, callback, wait, hold, wind) { + oldCallback = callback; + if (typeof jid == "undefined") + jid = null; + if (typeof sid == "undefined") + sid = null; + if (typeof rid == "undefined") + rid = null; + callback = newCallback; + _attach.apply(conn, [jid, sid, rid, callback, wait, hold, wind]); + }; + + Strophe.addNamespace('ROSTER_VER', 'urn:xmpp:features:rosterver'); + Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick'); + }, + + /** Function: supportVersioning + * return true if roster versioning is enabled on server + */ + supportVersioning: function() { + return (this._connection.features && this._connection.features.getElementsByTagName('ver').length > 0); + }, + + /** Function: get + * Get Roster on server + * + * Parameters: + * (Function) userCallback - callback on roster result + * (String) ver - current rev of roster + * (only used if roster versioning is enabled) + * (Array) items - initial items of ver + * (only used if roster versioning is enabled) + * In browser context you can use sessionStorage + * to store your roster in json (JSON.stringify()) + */ + get: function(userCallback, ver, items) { + var attrs = {xmlns: Strophe.NS.ROSTER}; + if (this.supportVersioning()) { + // empty rev because i want an rev attribute in the result + attrs.ver = ver || ''; + this.items = items || []; + } + var iq = $iq({type: 'get', 'id' : this._connection.getUniqueId('roster')}).c('query', attrs); + return this._connection.sendIQ(iq, + this._onReceiveRosterSuccess.bind(this, userCallback), + this._onReceiveRosterError.bind(this, userCallback)); + }, + + /** Function: registerCallback + * register callback on roster (presence and iq) + * + * Parameters: + * (Function) call_back + */ + registerCallback: function(call_back) { + this._callbacks.push(call_back); + }, + + /** Function: findItem + * Find item by JID + * + * Parameters: + * (String) jid + */ + findItem : function(jid) { + try { + for (var i = 0; i < this.items.length; i++) { + if (this.items[i] && this.items[i].jid == jid) { + return this.items[i]; + } + } + } catch (e) { + Strophe.error(e); + } + return false; + }, + + /** Function: removeItem + * Remove item by JID + * + * Parameters: + * (String) jid + */ + removeItem : function(jid) { + for (var i = 0; i < this.items.length; i++) { + if (this.items[i] && this.items[i].jid == jid) { + this.items.splice(i, 1); + return true; + } + } + return false; + }, + + /** Function: subscribe + * Subscribe presence + * + * Parameters: + * (String) jid + * (String) message (optional) + * (String) nick (optional) + */ + subscribe: function(jid, message, nick) { + var pres = $pres({to: jid, type: "subscribe"}); + if (message && message !== "") { + pres.c("status").t(message).up(); + } + if (nick && nick !== "") { + pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up(); + } + this._connection.send(pres); + }, + + /** Function: unsubscribe + * Unsubscribe presence + * + * Parameters: + * (String) jid + * (String) message + */ + unsubscribe: function(jid, message) { + var pres = $pres({to: jid, type: "unsubscribe"}); + if (message && message !== "") + pres.c("status").t(message); + this._connection.send(pres); + }, + + /** Function: authorize + * Authorize presence subscription + * + * Parameters: + * (String) jid + * (String) message + */ + authorize: function(jid, message) { + var pres = $pres({to: jid, type: "subscribed"}); + if (message && message !== "") + pres.c("status").t(message); + this._connection.send(pres); + }, + + /** Function: unauthorize + * Unauthorize presence subscription + * + * Parameters: + * (String) jid + * (String) message + */ + unauthorize: function(jid, message) { + var pres = $pres({to: jid, type: "unsubscribed"}); + if (message && message !== "") + pres.c("status").t(message); + this._connection.send(pres); + }, + + /** Function: add + * Add roster item + * + * Parameters: + * (String) jid - item jid + * (String) name - name + * (Array) groups + * (Function) call_back + */ + add: function(jid, name, groups, call_back) { + var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: jid, + name: name}); + for (var i = 0; i < groups.length; i++) { + iq.c('group').t(groups[i]).up(); + } + this._connection.sendIQ(iq, call_back, call_back); + }, + + /** Function: update + * Update roster item + * + * Parameters: + * (String) jid - item jid + * (String) name - name + * (Array) groups + * (Function) call_back + */ + update: function(jid, name, groups, call_back) { + var item = this.findItem(jid); + if (!item) { + throw "item not found"; + } + var newName = name || item.name; + var newGroups = groups || item.groups; + var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid, + name: newName}); + for (var i = 0; i < newGroups.length; i++) { + iq.c('group').t(newGroups[i]).up(); + } + return this._connection.sendIQ(iq, call_back, call_back); + }, + + /** Function: remove + * Remove roster item + * + * Parameters: + * (String) jid - item jid + * (Function) call_back + */ + remove: function(jid, call_back) { + var item = this.findItem(jid); + if (!item) { + throw "item not found"; + } + var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid, + subscription: "remove"}); + this._connection.sendIQ(iq, call_back, call_back); + }, + + /** PrivateFunction: _onReceiveRosterSuccess + * + */ + _onReceiveRosterSuccess: function(userCallback, stanza) { + this._updateItems(stanza); + if (typeof userCallback === "function") { + userCallback(this.items); + } + }, + + /** PrivateFunction: _onReceiveRosterError + * + */ + _onReceiveRosterError: function(userCallback, stanza) { + userCallback(this.items); + }, + + /** PrivateFunction: _onReceivePresence + * Handle presence + */ + _onReceivePresence : function(presence) { + // TODO: from is optional + var jid = presence.getAttribute('from'); + var from = Strophe.getBareJidFromJid(jid); + var item = this.findItem(from); + // not in roster + if (!item) { + return true; + } + var type = presence.getAttribute('type'); + if (type == 'unavailable') { + delete item.resources[Strophe.getResourceFromJid(jid)]; + } else if (!type) { + // TODO: add timestamp + item.resources[Strophe.getResourceFromJid(jid)] = { + show : (presence.getElementsByTagName('show').length !== 0) ? Strophe.getText(presence.getElementsByTagName('show')[0]) : "", + status : (presence.getElementsByTagName('status').length !== 0) ? Strophe.getText(presence.getElementsByTagName('status')[0]) : "", + priority : (presence.getElementsByTagName('priority').length !== 0) ? Strophe.getText(presence.getElementsByTagName('priority')[0]) : "" + }; + } else { + // Stanza is not a presence notification. (It's probably a subscription type stanza.) + return true; + } + this._call_backs(this.items, item); + return true; + }, + + /** PrivateFunction: _call_backs + * + */ + _call_backs : function(items, item) { + for (var i = 0; i < this._callbacks.length; i++) { + this._callbacks[i](items, item); + } + }, + + /** PrivateFunction: _onReceiveIQ + * Handle roster push. + */ + _onReceiveIQ : function(iq) { + var id = iq.getAttribute('id'); + var from = iq.getAttribute('from'); + // Receiving client MUST ignore stanza unless it has no from or from = user's JID. + if (from && from !== "" && from != this._connection.jid && from != Strophe.getBareJidFromJid(this._connection.jid)) + return true; + var iqresult = $iq({type: 'result', id: id, from: this._connection.jid}); + this._connection.send(iqresult); + this._updateItems(iq); + return true; + }, + /** PrivateFunction: _updateItems + * Update items from iq + */ + _updateItems : function(iq) { + var query = iq.getElementsByTagName('query'); + if (query.length !== 0) { + this.ver = query.item(0).getAttribute('ver'); + var self = this; + Strophe.forEachChild(query.item(0), 'item', + function (item) { + self._updateItem(item); + } + ); + } + this._call_backs(this.items); + }, + + /** PrivateFunction: _updateItem + * Update internal representation of roster item + */ + _updateItem : function(item) { + var jid = item.getAttribute("jid"); + var name = item.getAttribute("name"); + var subscription = item.getAttribute("subscription"); + var ask = item.getAttribute("ask"); + var groups = []; + Strophe.forEachChild(item, 'group', + function(group) { + groups.push(Strophe.getText(group)); + } + ); + + if (subscription == "remove") { + this.removeItem(jid); + return; + } + + item = this.findItem(jid); + if (!item) { this.items.push({ + name : name, + jid : jid, + subscription : subscription, + ask : ask, + groups : groups, + resources : {} + }); + } else { + item.name = name; + item.subscription = subscription; + item.ask = ask; + item.groups = groups; + } + } }); -})(this); +})); + +/* Plugin to implement the vCard extension. + * http://xmpp.org/extensions/xep-0054.html + * + * Author: Nathan Zorn (nathan.zorn@gmail.com) + * AMD support by JC Brand + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe.vcard',[ + "strophe" + ], function (Strophe) { + factory( + Strophe.Strophe, + Strophe.$build, + Strophe.$iq , + Strophe.$msg, + Strophe.$pres + ); + return Strophe; + }); + } else { + // Browser globals + factory( + root.Strophe, + root.$build, + root.$iq , + root.$msg, + root.$pres + ); + } +}(this, function (Strophe, $build, $iq, $msg, $pres) { + + var buildIq = function(type, jid, vCardEl) { + var iq = $iq(jid ? {type: type, to: jid} : {type: type}); + iq.c("vCard", {xmlns: Strophe.NS.VCARD}); + if (vCardEl) { + iq.cnode(vCardEl); + } + return iq; + }; + + Strophe.addConnectionPlugin('vcard', { + _connection: null, + init: function(conn) { + this._connection = conn; + return Strophe.addNamespace('VCARD', 'vcard-temp'); + }, + + /*Function + Retrieve a vCard for a JID/Entity + Parameters: + (Function) handler_cb - The callback function used to handle the request. + (String) jid - optional - The name of the entity to request the vCard + If no jid is given, this function retrieves the current user's vcard. + */ + get: function(handler_cb, jid, error_cb) { + var iq = buildIq("get", jid); + return this._connection.sendIQ(iq, handler_cb, error_cb); + }, + + /* Function + Set an entity's vCard. + */ + set: function(handler_cb, vCardEl, jid, error_cb) { + var iq = buildIq("set", jid, vCardEl); + return this._connection.sendIQ(iq, handler_cb, error_cb); + } + }); +})); + +/* + Copyright 2010, François de Metz +*/ + +/** + * Disco Strophe Plugin + * Implement http://xmpp.org/extensions/xep-0030.html + * TODO: manage node hierarchies, and node on info request + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define('strophe.disco',[ + "strophe" + ], function (Strophe) { + factory( + Strophe.Strophe, + Strophe.$build, + Strophe.$iq , + Strophe.$msg, + Strophe.$pres + ); + return Strophe; + }); + } else { + // Browser globals + factory( + root.Strophe, + root.$build, + root.$iq , + root.$msg, + root.$pres + ); + } +}(this, function (Strophe, $build, $iq, $msg, $pres) { + +Strophe.addConnectionPlugin('disco', +{ + _connection: null, + _identities : [], + _features : [], + _items : [], + /** Function: init + * Plugin init + * + * Parameters: + * (Strophe.Connection) conn - Strophe connection + */ + init: function(conn) + { + this._connection = conn; + this._identities = []; + this._features = []; + this._items = []; + // disco info + conn.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null); + // disco items + conn.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null); + }, + /** Function: addIdentity + * See http://xmpp.org/registrar/disco-categories.html + * Parameters: + * (String) category - category of identity (like client, automation, etc ...) + * (String) type - type of identity (like pc, web, bot , etc ...) + * (String) name - name of identity in natural language + * (String) lang - lang of name parameter + * + * Returns: + * Boolean + */ + addIdentity: function(category, type, name, lang) + { + for (var i=0; i= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || - []; + var match = /(edge)\/([\w.]+)/.exec( ua ) || + /(opr)[\/]([\w.]+)/.exec( ua ) || + /(chrome)[ \/]([\w.]+)/.exec( ua ) || + /(version)(applewebkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec( ua ) || + /(webkit)[ \/]([\w.]+).*(version)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec( ua ) || + /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || + []; - var platform_match = /(ipad)/.exec( ua ) || - /(iphone)/.exec( ua ) || - /(android)/.exec( ua ) || - /(windows phone)/.exec( ua ) || - /(win)/.exec( ua ) || - /(mac)/.exec( ua ) || - /(linux)/.exec( ua ) || - /(cros)/i.exec( ua ) || - []; + var platform_match = /(ipad)/.exec( ua ) || + /(ipod)/.exec( ua ) || + /(iphone)/.exec( ua ) || + /(kindle)/.exec( ua ) || + /(silk)/.exec( ua ) || + /(android)/.exec( ua ) || + /(windows phone)/.exec( ua ) || + /(win)/.exec( ua ) || + /(mac)/.exec( ua ) || + /(linux)/.exec( ua ) || + /(cros)/.exec( ua ) || + /(playbook)/.exec( ua ) || + /(bb)/.exec( ua ) || + /(blackberry)/.exec( ua ) || + []; - return { - browser: match[ 3 ] || match[ 1 ] || "", - version: match[ 2 ] || "0", - platform: platform_match[ 0 ] || "" - }; + return { + browser: match[ 5 ] || match[ 3 ] || match[ 1 ] || "", + version: match[ 2 ] || match[ 4 ] || "0", + versionNumber: match[ 4 ] || match[ 2 ] || "0", + platform: platform_match[ 0 ] || "" + }; }; matched = jQuery.uaMatch( window.navigator.userAgent ); browser = {}; if ( matched.browser ) { - browser[ matched.browser ] = true; - browser.version = matched.version; - browser.versionNumber = parseInt(matched.version); + browser[ matched.browser ] = true; + browser.version = matched.version; + browser.versionNumber = parseInt(matched.versionNumber, 10); } if ( matched.platform ) { - browser[ matched.platform ] = true; + browser[ matched.platform ] = true; } // These are all considered mobile platforms, meaning they run a mobile browser - if ( browser.android || browser.ipad || browser.iphone || browser[ "windows phone" ] ) { - browser.mobile = true; + if ( browser.android || browser.bb || browser.blackberry || browser.ipad || browser.iphone || + browser.ipod || browser.kindle || browser.playbook || browser.silk || browser[ "windows phone" ]) { + browser.mobile = true; } // These are all considered desktop platforms, meaning they run a desktop browser if ( browser.cros || browser.mac || browser.linux || browser.win ) { - browser.desktop = true; + browser.desktop = true; } // Chrome, Opera 15+ and Safari are webkit based browsers if ( browser.chrome || browser.opr || browser.safari ) { - browser.webkit = true; + browser.webkit = true; } // IE11 has a new token so we will assign it msie to avoid breaking changes - if ( browser.rv ) - { - var ie = "msie"; + // IE12 disguises itself as Chrome, but adds a new Edge token. + if ( browser.rv || browser.edge ) { + var ie = "msie"; - matched.browser = ie; - browser[ie] = true; + matched.browser = ie; + browser[ie] = true; + } + + // Blackberry browsers are marked as Safari on BlackBerry + if ( browser.safari && browser.blackberry ) { + var blackberry = "blackberry"; + + matched.browser = blackberry; + browser[blackberry] = true; + } + + // Playbook browsers are marked as Safari on Playbook + if ( browser.safari && browser.playbook ) { + var playbook = "playbook"; + + matched.browser = playbook; + browser[playbook] = true; + } + + // BB10 is a newer OS version of BlackBerry + if ( browser.bb ) { + var bb = "blackberry"; + + matched.browser = bb; + browser[bb] = true; } // Opera 15+ are identified as opr - if ( browser.opr ) - { - var opera = "opera"; + if ( browser.opr ) { + var opera = "opera"; - matched.browser = opera; - browser[opera] = true; + matched.browser = opera; + browser[opera] = true; } // Stock Android browsers are marked as Safari on Android. - if ( browser.safari && browser.android ) - { - var android = "android"; + if ( browser.safari && browser.android ) { + var android = "android"; - matched.browser = android; - browser[android] = true; + matched.browser = android; + browser[android] = true; + } + + // Kindle browsers are marked as Safari on Kindle + if ( browser.safari && browser.kindle ) { + var kindle = "kindle"; + + matched.browser = kindle; + browser[kindle] = true; + } + + // Kindle Silk browsers are marked as Safari on Kindle + if ( browser.safari && browser.silk ) { + var silk = "silk"; + + matched.browser = silk; + browser[silk] = true; } // Assign the name and platform variable @@ -20623,7081 +26790,26 @@ return Backbone.BrowserStorage; return {}; })); -// This code was written by Tyler Akins and has been placed in the -// public domain. It would be nice if you left this header intact. -// Base64 code from Tyler Akins -- http://rumkin.com - -var Base64 = (function () { - var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - var obj = { - /** - * Encodes a string in base64 - * @param {String} input The string to encode in base64. - */ - encode: function (input) { - var output = ""; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - - do { - chr1 = input.charCodeAt(i++); - chr2 = input.charCodeAt(i++); - chr3 = input.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + - keyStr.charAt(enc3) + keyStr.charAt(enc4); - } while (i < input.length); - - return output; - }, - - /** - * Decodes a base64 string. - * @param {String} input The string to decode. - */ - decode: function (input) { - var output = ""; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - - // remove all characters that are not A-Z, a-z, 0-9, +, /, or = - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - - do { - enc1 = keyStr.indexOf(input.charAt(i++)); - enc2 = keyStr.indexOf(input.charAt(i++)); - enc3 = keyStr.indexOf(input.charAt(i++)); - enc4 = keyStr.indexOf(input.charAt(i++)); - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { - output = output + String.fromCharCode(chr2); - } - if (enc4 != 64) { - output = output + String.fromCharCode(chr3); - } - } while (i < input.length); - - return output; - } - }; - - return obj; -})(); - -/* - * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined - * in FIPS PUB 180-1 - * Version 2.1a Copyright Paul Johnston 2000 - 2002. - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for details. - */ - -/* Some functions and variables have been stripped for use with Strophe */ - -/* - * These are the functions you'll usually want to call - * They take string arguments and return either hex or base-64 encoded strings - */ -function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * 8));} -function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * 8));} -function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} -function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} - -/* - * Calculate the SHA-1 of an array of big-endian words, and a bit length - */ -function core_sha1(x, len) -{ - /* append padding */ - x[len >> 5] |= 0x80 << (24 - len % 32); - x[((len + 64 >> 9) << 4) + 15] = len; - - var w = new Array(80); - var a = 1732584193; - var b = -271733879; - var c = -1732584194; - var d = 271733878; - var e = -1009589776; - - var i, j, t, olda, oldb, oldc, oldd, olde; - for (i = 0; i < x.length; i += 16) - { - olda = a; - oldb = b; - oldc = c; - oldd = d; - olde = e; - - for (j = 0; j < 80; j++) - { - if (j < 16) { w[j] = x[i + j]; } - else { w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); } - t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), - safe_add(safe_add(e, w[j]), sha1_kt(j))); - e = d; - d = c; - c = rol(b, 30); - b = a; - a = t; - } - - a = safe_add(a, olda); - b = safe_add(b, oldb); - c = safe_add(c, oldc); - d = safe_add(d, oldd); - e = safe_add(e, olde); - } - return [a, b, c, d, e]; -} - -/* - * Perform the appropriate triplet combination function for the current - * iteration - */ -function sha1_ft(t, b, c, d) -{ - if (t < 20) { return (b & c) | ((~b) & d); } - if (t < 40) { return b ^ c ^ d; } - if (t < 60) { return (b & c) | (b & d) | (c & d); } - return b ^ c ^ d; -} - -/* - * Determine the appropriate additive constant for the current iteration - */ -function sha1_kt(t) -{ - return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : - (t < 60) ? -1894007588 : -899497514; -} - -/* - * Calculate the HMAC-SHA1 of a key and some data - */ -function core_hmac_sha1(key, data) -{ - var bkey = str2binb(key); - if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * 8); } - - var ipad = new Array(16), opad = new Array(16); - for (var i = 0; i < 16; i++) - { - ipad[i] = bkey[i] ^ 0x36363636; - opad[i] = bkey[i] ^ 0x5C5C5C5C; - } - - var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8); - return core_sha1(opad.concat(hash), 512 + 160); -} - -/* - * Add integers, wrapping at 2^32. This uses 16-bit operations internally - * to work around bugs in some JS interpreters. - */ -function safe_add(x, y) -{ - var lsw = (x & 0xFFFF) + (y & 0xFFFF); - var msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); -} - -/* - * Bitwise rotate a 32-bit number to the left. - */ -function rol(num, cnt) -{ - return (num << cnt) | (num >>> (32 - cnt)); -} - -/* - * Convert an 8-bit or 16-bit string to an array of big-endian words - * In 8-bit function, characters >255 have their hi-byte silently ignored. - */ -function str2binb(str) -{ - var bin = []; - var mask = 255; - for (var i = 0; i < str.length * 8; i += 8) - { - bin[i>>5] |= (str.charCodeAt(i / 8) & mask) << (24 - i%32); - } - return bin; -} - -/* - * Convert an array of big-endian words to a string - */ -function binb2str(bin) -{ - var str = ""; - var mask = 255; - for (var i = 0; i < bin.length * 32; i += 8) - { - str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask); - } - return str; -} - -/* - * Convert an array of big-endian words to a base-64 string - */ -function binb2b64(binarray) -{ - var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - var str = ""; - var triplet, j; - for (var i = 0; i < binarray.length * 4; i += 3) - { - triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) | - (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) | - ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); - for (j = 0; j < 4; j++) - { - if (i * 8 + j * 6 > binarray.length * 32) { str += "="; } - else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } - } - } - return str; -} - -/* - * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message - * Digest Algorithm, as defined in RFC 1321. - * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for more info. - */ - -/* - * Everything that isn't used by Strophe has been stripped here! - */ - -var MD5 = (function () { - /* - * Add integers, wrapping at 2^32. This uses 16-bit operations internally - * to work around bugs in some JS interpreters. - */ - var safe_add = function (x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF); - var msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - }; - - /* - * Bitwise rotate a 32-bit number to the left. - */ - var bit_rol = function (num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)); - }; - - /* - * Convert a string to an array of little-endian words - */ - var str2binl = function (str) { - var bin = []; - for(var i = 0; i < str.length * 8; i += 8) - { - bin[i>>5] |= (str.charCodeAt(i / 8) & 255) << (i%32); - } - return bin; - }; - - /* - * Convert an array of little-endian words to a string - */ - var binl2str = function (bin) { - var str = ""; - for(var i = 0; i < bin.length * 32; i += 8) - { - str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & 255); - } - return str; - }; - - /* - * Convert an array of little-endian words to a hex string. - */ - var binl2hex = function (binarray) { - var hex_tab = "0123456789abcdef"; - var str = ""; - for(var i = 0; i < binarray.length * 4; i++) - { - str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + - hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); - } - return str; - }; - - /* - * These functions implement the four basic operations the algorithm uses. - */ - var md5_cmn = function (q, a, b, x, s, t) { - return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b); - }; - - var md5_ff = function (a, b, c, d, x, s, t) { - return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); - }; - - var md5_gg = function (a, b, c, d, x, s, t) { - return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); - }; - - var md5_hh = function (a, b, c, d, x, s, t) { - return md5_cmn(b ^ c ^ d, a, b, x, s, t); - }; - - var md5_ii = function (a, b, c, d, x, s, t) { - return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); - }; - - /* - * Calculate the MD5 of an array of little-endian words, and a bit length - */ - var core_md5 = function (x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << ((len) % 32); - x[(((len + 64) >>> 9) << 4) + 14] = len; - - var a = 1732584193; - var b = -271733879; - var c = -1732584194; - var d = 271733878; - - var olda, oldb, oldc, oldd; - for (var i = 0; i < x.length; i += 16) - { - olda = a; - oldb = b; - oldc = c; - oldd = d; - - a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); - d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); - c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); - b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); - a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); - d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); - c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); - b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); - a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); - d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); - c = md5_ff(c, d, a, b, x[i+10], 17, -42063); - b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); - a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); - d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); - c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); - b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); - - a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); - d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); - c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); - b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); - a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); - d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); - c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); - b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); - a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); - d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); - c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); - b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); - a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); - d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); - c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); - b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); - - a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); - d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); - c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); - b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); - a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); - d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); - c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); - b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); - a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); - d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); - c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); - b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); - a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); - d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); - c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); - b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); - - a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); - d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); - c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); - b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); - a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); - d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); - c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); - b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); - a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); - d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); - c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); - b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); - a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); - d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); - c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); - b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); - - a = safe_add(a, olda); - b = safe_add(b, oldb); - c = safe_add(c, oldc); - d = safe_add(d, oldd); - } - return [a, b, c, d]; - }; - - - var obj = { - /* - * These are the functions you'll usually want to call. - * They take string arguments and return either hex or base-64 encoded - * strings. - */ - hexdigest: function (s) { - return binl2hex(core_md5(str2binl(s), s.length * 8)); - }, - - hash: function (s) { - return binl2str(core_md5(str2binl(s), s.length * 8)); - } - }; - - return obj; -})(); - -/* - This program is distributed under the terms of the MIT license. - Please see the LICENSE file for details. - - Copyright 2006-2008, OGG, LLC -*/ - -/* jshint undef: true, unused: true:, noarg: true, latedef: true */ -/*global document, window, setTimeout, clearTimeout, console, - ActiveXObject, Base64, MD5, DOMParser */ -// from sha1.js -/*global core_hmac_sha1, binb2str, str_hmac_sha1, str_sha1, b64_hmac_sha1*/ - -/** File: strophe.js - * A JavaScript library for XMPP BOSH/XMPP over Websocket. - * - * This is the JavaScript version of the Strophe library. Since JavaScript - * had no facilities for persistent TCP connections, this library uses - * Bidirectional-streams Over Synchronous HTTP (BOSH) to emulate - * a persistent, stateful, two-way connection to an XMPP server. More - * information on BOSH can be found in XEP 124. - * - * This version of Strophe also works with WebSockets. - * For more information on XMPP-over WebSocket see this RFC draft: - * http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00 - */ - -/** PrivateFunction: Function.prototype.bind - * Bind a function to an instance. - * - * This Function object extension method creates a bound method similar - * to those in Python. This means that the 'this' object will point - * to the instance you want. See - * MDC's bind() documentation and - * Bound Functions and Function Imports in JavaScript - * for a complete explanation. - * - * This extension already exists in some browsers (namely, Firefox 3), but - * we provide it to support those that don't. - * - * Parameters: - * (Object) obj - The object that will become 'this' in the bound function. - * (Object) argN - An option argument that will be prepended to the - * arguments given for the function call - * - * Returns: - * The bound function. - */ -if (!Function.prototype.bind) { - Function.prototype.bind = function (obj /*, arg1, arg2, ... */) - { - var func = this; - var _slice = Array.prototype.slice; - var _concat = Array.prototype.concat; - var _args = _slice.call(arguments, 1); - - return function () { - return func.apply(obj ? obj : this, - _concat.call(_args, - _slice.call(arguments, 0))); - }; - }; -} - -/** PrivateFunction: Array.prototype.indexOf - * Return the index of an object in an array. - * - * This function is not supplied by some JavaScript implementations, so - * we provide it if it is missing. This code is from: - * http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Objects:Array:indexOf - * - * Parameters: - * (Object) elt - The object to look for. - * (Integer) from - The index from which to start looking. (optional). - * - * Returns: - * The index of elt in the array or -1 if not found. - */ -if (!Array.prototype.indexOf) -{ - Array.prototype.indexOf = function(elt /*, from*/) - { - var len = this.length; - - var from = Number(arguments[1]) || 0; - from = (from < 0) ? Math.ceil(from) : Math.floor(from); - if (from < 0) { - from += len; - } - - for (; from < len; from++) { - if (from in this && this[from] === elt) { - return from; - } - } - - return -1; - }; -} - -/* All of the Strophe globals are defined in this special function below so - * that references to the globals become closures. This will ensure that - * on page reload, these references will still be available to callbacks - * that are still executing. - */ - -(function (callback) { -var Strophe; - -/** Function: $build - * Create a Strophe.Builder. - * This is an alias for 'new Strophe.Builder(name, attrs)'. - * - * Parameters: - * (String) name - The root element name. - * (Object) attrs - The attributes for the root element in object notation. - * - * Returns: - * A new Strophe.Builder object. - */ -function $build(name, attrs) { return new Strophe.Builder(name, attrs); } -/** Function: $msg - * Create a Strophe.Builder with a element as the root. - * - * Parmaeters: - * (Object) attrs - The element attributes in object notation. - * - * Returns: - * A new Strophe.Builder object. - */ -function $msg(attrs) { return new Strophe.Builder("message", attrs); } -/** Function: $iq - * Create a Strophe.Builder with an element as the root. - * - * Parameters: - * (Object) attrs - The element attributes in object notation. - * - * Returns: - * A new Strophe.Builder object. - */ -function $iq(attrs) { return new Strophe.Builder("iq", attrs); } -/** Function: $pres - * Create a Strophe.Builder with a element as the root. - * - * Parameters: - * (Object) attrs - The element attributes in object notation. - * - * Returns: - * A new Strophe.Builder object. - */ -function $pres(attrs) { return new Strophe.Builder("presence", attrs); } - -/** Class: Strophe - * An object container for all Strophe library functions. - * - * This class is just a container for all the objects and constants - * used in the library. It is not meant to be instantiated, but to - * provide a namespace for library objects, constants, and functions. - */ -Strophe = { - /** Constant: VERSION - * The version of the Strophe library. Unreleased builds will have - * a version of head-HASH where HASH is a partial revision. - */ - VERSION: "1.1.3", - - /** Constants: XMPP Namespace Constants - * Common namespace constants from the XMPP RFCs and XEPs. - * - * NS.HTTPBIND - HTTP BIND namespace from XEP 124. - * NS.BOSH - BOSH namespace from XEP 206. - * NS.CLIENT - Main XMPP client namespace. - * NS.AUTH - Legacy authentication namespace. - * NS.ROSTER - Roster operations namespace. - * NS.PROFILE - Profile namespace. - * NS.DISCO_INFO - Service discovery info namespace from XEP 30. - * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30. - * NS.MUC - Multi-User Chat namespace from XEP 45. - * NS.SASL - XMPP SASL namespace from RFC 3920. - * NS.STREAM - XMPP Streams namespace from RFC 3920. - * NS.BIND - XMPP Binding namespace from RFC 3920. - * NS.SESSION - XMPP Session namespace from RFC 3920. - * NS.XHTML_IM - XHTML-IM namespace from XEP 71. - * NS.XHTML - XHTML body namespace from XEP 71. - */ - NS: { - HTTPBIND: "http://jabber.org/protocol/httpbind", - BOSH: "urn:xmpp:xbosh", - CLIENT: "jabber:client", - AUTH: "jabber:iq:auth", - ROSTER: "jabber:iq:roster", - PROFILE: "jabber:iq:profile", - DISCO_INFO: "http://jabber.org/protocol/disco#info", - DISCO_ITEMS: "http://jabber.org/protocol/disco#items", - MUC: "http://jabber.org/protocol/muc", - SASL: "urn:ietf:params:xml:ns:xmpp-sasl", - STREAM: "http://etherx.jabber.org/streams", - BIND: "urn:ietf:params:xml:ns:xmpp-bind", - SESSION: "urn:ietf:params:xml:ns:xmpp-session", - VERSION: "jabber:iq:version", - STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas", - XHTML_IM: "http://jabber.org/protocol/xhtml-im", - XHTML: "http://www.w3.org/1999/xhtml" - }, - - - /** Constants: XHTML_IM Namespace - * contains allowed tags, tag attributes, and css properties. - * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset. - * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended - * allowed tags and their attributes. - */ - XHTML: { - tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'], - attributes: { - 'a': ['href'], - 'blockquote': ['style'], - 'br': [], - 'cite': ['style'], - 'em': [], - 'img': ['src', 'alt', 'style', 'height', 'width'], - 'li': ['style'], - 'ol': ['style'], - 'p': ['style'], - 'span': ['style'], - 'strong': [], - 'ul': ['style'], - 'body': [] - }, - css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'], - validTag: function(tag) - { - for(var i = 0; i < Strophe.XHTML.tags.length; i++) { - if(tag == Strophe.XHTML.tags[i]) { - return true; - } - } - return false; - }, - validAttribute: function(tag, attribute) - { - if(typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) { - for(var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { - if(attribute == Strophe.XHTML.attributes[tag][i]) { - return true; - } - } - } - return false; - }, - validCSS: function(style) - { - for(var i = 0; i < Strophe.XHTML.css.length; i++) { - if(style == Strophe.XHTML.css[i]) { - return true; - } - } - return false; - } - }, - - /** Constants: Connection Status Constants - * Connection status constants for use by the connection handler - * callback. - * - * Status.ERROR - An error has occurred - * Status.CONNECTING - The connection is currently being made - * Status.CONNFAIL - The connection attempt failed - * Status.AUTHENTICATING - The connection is authenticating - * Status.AUTHFAIL - The authentication attempt failed - * Status.CONNECTED - The connection has succeeded - * Status.DISCONNECTED - The connection has been terminated - * Status.DISCONNECTING - The connection is currently being terminated - * Status.ATTACHED - The connection has been attached - */ - Status: { - ERROR: 0, - CONNECTING: 1, - CONNFAIL: 2, - AUTHENTICATING: 3, - AUTHFAIL: 4, - CONNECTED: 5, - DISCONNECTED: 6, - DISCONNECTING: 7, - ATTACHED: 8 - }, - - /** Constants: Log Level Constants - * Logging level indicators. - * - * LogLevel.DEBUG - Debug output - * LogLevel.INFO - Informational output - * LogLevel.WARN - Warnings - * LogLevel.ERROR - Errors - * LogLevel.FATAL - Fatal errors - */ - LogLevel: { - DEBUG: 0, - INFO: 1, - WARN: 2, - ERROR: 3, - FATAL: 4 - }, - - /** PrivateConstants: DOM Element Type Constants - * DOM element types. - * - * ElementType.NORMAL - Normal element. - * ElementType.TEXT - Text data element. - * ElementType.FRAGMENT - XHTML fragment element. - */ - ElementType: { - NORMAL: 1, - TEXT: 3, - CDATA: 4, - FRAGMENT: 11 - }, - - /** PrivateConstants: Timeout Values - * Timeout values for error states. These values are in seconds. - * These should not be changed unless you know exactly what you are - * doing. - * - * TIMEOUT - Timeout multiplier. A waiting request will be considered - * failed after Math.floor(TIMEOUT * wait) seconds have elapsed. - * This defaults to 1.1, and with default wait, 66 seconds. - * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where - * Strophe can detect early failure, it will consider the request - * failed if it doesn't return after - * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed. - * This defaults to 0.1, and with default wait, 6 seconds. - */ - TIMEOUT: 1.1, - SECONDARY_TIMEOUT: 0.1, - - /** Function: addNamespace - * This function is used to extend the current namespaces in - * Strophe.NS. It takes a key and a value with the key being the - * name of the new namespace, with its actual value. - * For example: - * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); - * - * Parameters: - * (String) name - The name under which the namespace will be - * referenced under Strophe.NS - * (String) value - The actual namespace. - */ - addNamespace: function (name, value) - { - Strophe.NS[name] = value; - }, - - /** Function: forEachChild - * Map a function over some or all child elements of a given element. - * - * This is a small convenience function for mapping a function over - * some or all of the children of an element. If elemName is null, all - * children will be passed to the function, otherwise only children - * whose tag names match elemName will be passed. - * - * Parameters: - * (XMLElement) elem - The element to operate on. - * (String) elemName - The child element tag name filter. - * (Function) func - The function to apply to each child. This - * function should take a single argument, a DOM element. - */ - forEachChild: function (elem, elemName, func) - { - var i, childNode; - - for (i = 0; i < elem.childNodes.length; i++) { - childNode = elem.childNodes[i]; - if (childNode.nodeType == Strophe.ElementType.NORMAL && - (!elemName || this.isTagEqual(childNode, elemName))) { - func(childNode); - } - } - }, - - /** Function: isTagEqual - * Compare an element's tag name with a string. - * - * This function is case insensitive. - * - * Parameters: - * (XMLElement) el - A DOM element. - * (String) name - The element name. - * - * Returns: - * true if the element's tag name matches _el_, and false - * otherwise. - */ - isTagEqual: function (el, name) - { - return el.tagName.toLowerCase() == name.toLowerCase(); - }, - - /** PrivateVariable: _xmlGenerator - * _Private_ variable that caches a DOM document to - * generate elements. - */ - _xmlGenerator: null, - - /** PrivateFunction: _makeGenerator - * _Private_ function that creates a dummy XML DOM document to serve as - * an element and text node generator. - */ - _makeGenerator: function () { - var doc; - - // IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload. - // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be - // less than 10 in the case of IE9 and below. - if (document.implementation.createDocument === undefined || - document.implementation.createDocument && document.documentMode && document.documentMode < 10) { - doc = this._getIEXmlDom(); - doc.appendChild(doc.createElement('strophe')); - } else { - doc = document.implementation - .createDocument('jabber:client', 'strophe', null); - } - - return doc; - }, - - /** Function: xmlGenerator - * Get the DOM document to generate elements. - * - * Returns: - * The currently used DOM document. - */ - xmlGenerator: function () { - if (!Strophe._xmlGenerator) { - Strophe._xmlGenerator = Strophe._makeGenerator(); - } - return Strophe._xmlGenerator; - }, - - /** PrivateFunction: _getIEXmlDom - * Gets IE xml doc object - * - * Returns: - * A Microsoft XML DOM Object - * See Also: - * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx - */ - _getIEXmlDom : function() { - var doc = null; - var docStrings = [ - "Msxml2.DOMDocument.6.0", - "Msxml2.DOMDocument.5.0", - "Msxml2.DOMDocument.4.0", - "MSXML2.DOMDocument.3.0", - "MSXML2.DOMDocument", - "MSXML.DOMDocument", - "Microsoft.XMLDOM" - ]; - - for (var d = 0; d < docStrings.length; d++) { - if (doc === null) { - try { - doc = new ActiveXObject(docStrings[d]); - } catch (e) { - doc = null; - } - } else { - break; - } - } - - return doc; - }, - - /** Function: xmlElement - * Create an XML DOM element. - * - * This function creates an XML DOM element correctly across all - * implementations. Note that these are not HTML DOM elements, which - * aren't appropriate for XMPP stanzas. - * - * Parameters: - * (String) name - The name for the element. - * (Array|Object) attrs - An optional array or object containing - * key/value pairs to use as element attributes. The object should - * be in the format {'key': 'value'} or {key: 'value'}. The array - * should have the format [['key1', 'value1'], ['key2', 'value2']]. - * (String) text - The text child data for the element. - * - * Returns: - * A new XML DOM element. - */ - xmlElement: function (name) - { - if (!name) { return null; } - - var node = Strophe.xmlGenerator().createElement(name); - - // FIXME: this should throw errors if args are the wrong type or - // there are more than two optional args - var a, i, k; - for (a = 1; a < arguments.length; a++) { - if (!arguments[a]) { continue; } - if (typeof(arguments[a]) == "string" || - typeof(arguments[a]) == "number") { - node.appendChild(Strophe.xmlTextNode(arguments[a])); - } else if (typeof(arguments[a]) == "object" && - typeof(arguments[a].sort) == "function") { - for (i = 0; i < arguments[a].length; i++) { - if (typeof(arguments[a][i]) == "object" && - typeof(arguments[a][i].sort) == "function") { - node.setAttribute(arguments[a][i][0], - arguments[a][i][1]); - } - } - } else if (typeof(arguments[a]) == "object") { - for (k in arguments[a]) { - if (arguments[a].hasOwnProperty(k)) { - node.setAttribute(k, arguments[a][k]); - } - } - } - } - - return node; - }, - - /* Function: xmlescape - * Excapes invalid xml characters. - * - * Parameters: - * (String) text - text to escape. - * - * Returns: - * Escaped text. - */ - xmlescape: function(text) - { - text = text.replace(/\&/g, "&"); - text = text.replace(//g, ">"); - text = text.replace(/'/g, "'"); - text = text.replace(/"/g, """); - return text; - }, - - /** Function: xmlTextNode - * Creates an XML DOM text node. - * - * Provides a cross implementation version of document.createTextNode. - * - * Parameters: - * (String) text - The content of the text node. - * - * Returns: - * A new XML DOM text node. - */ - xmlTextNode: function (text) - { - return Strophe.xmlGenerator().createTextNode(text); - }, - - /** Function: xmlHtmlNode - * Creates an XML DOM html node. - * - * Parameters: - * (String) html - The content of the html node. - * - * Returns: - * A new XML DOM text node. - */ - xmlHtmlNode: function (html) - { - var node; - //ensure text is escaped - if (window.DOMParser) { - var parser = new DOMParser(); - node = parser.parseFromString(html, "text/xml"); - } else { - node = new ActiveXObject("Microsoft.XMLDOM"); - node.async="false"; - node.loadXML(html); - } - return node; - }, - - /** Function: getText - * Get the concatenation of all text children of an element. - * - * Parameters: - * (XMLElement) elem - A DOM element. - * - * Returns: - * A String with the concatenated text of all text element children. - */ - getText: function (elem) - { - if (!elem) { return null; } - - var str = ""; - if (elem.childNodes.length === 0 && elem.nodeType == - Strophe.ElementType.TEXT) { - str += elem.nodeValue; - } - - for (var i = 0; i < elem.childNodes.length; i++) { - if (elem.childNodes[i].nodeType == Strophe.ElementType.TEXT) { - str += elem.childNodes[i].nodeValue; - } - } - - return Strophe.xmlescape(str); - }, - - /** Function: copyElement - * Copy an XML DOM element. - * - * This function copies a DOM element and all its descendants and returns - * the new copy. - * - * Parameters: - * (XMLElement) elem - A DOM element. - * - * Returns: - * A new, copied DOM element tree. - */ - copyElement: function (elem) - { - var i, el; - if (elem.nodeType == Strophe.ElementType.NORMAL) { - el = Strophe.xmlElement(elem.tagName); - - for (i = 0; i < elem.attributes.length; i++) { - el.setAttribute(elem.attributes[i].nodeName.toLowerCase(), - elem.attributes[i].value); - } - - for (i = 0; i < elem.childNodes.length; i++) { - el.appendChild(Strophe.copyElement(elem.childNodes[i])); - } - } else if (elem.nodeType == Strophe.ElementType.TEXT) { - el = Strophe.xmlGenerator().createTextNode(elem.nodeValue); - } - - return el; - }, - - - /** Function: createHtml - * Copy an HTML DOM element into an XML DOM. - * - * This function copies a DOM element and all its descendants and returns - * the new copy. - * - * Parameters: - * (HTMLElement) elem - A DOM element. - * - * Returns: - * A new, copied DOM element tree. - */ - createHtml: function (elem) - { - var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue; - if (elem.nodeType == Strophe.ElementType.NORMAL) { - tag = elem.nodeName.toLowerCase(); - if(Strophe.XHTML.validTag(tag)) { - try { - el = Strophe.xmlElement(tag); - for(i = 0; i < Strophe.XHTML.attributes[tag].length; i++) { - attribute = Strophe.XHTML.attributes[tag][i]; - value = elem.getAttribute(attribute); - if(typeof value == 'undefined' || value === null || value === '' || value === false || value === 0) { - continue; - } - if(attribute == 'style' && typeof value == 'object') { - if(typeof value.cssText != 'undefined') { - value = value.cssText; // we're dealing with IE, need to get CSS out - } - } - // filter out invalid css styles - if(attribute == 'style') { - css = []; - cssAttrs = value.split(';'); - for(j = 0; j < cssAttrs.length; j++) { - attr = cssAttrs[j].split(':'); - cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase(); - if(Strophe.XHTML.validCSS(cssName)) { - cssValue = attr[1].replace(/^\s*/, "").replace(/\s*$/, ""); - css.push(cssName + ': ' + cssValue); - } - } - if(css.length > 0) { - value = css.join('; '); - el.setAttribute(attribute, value); - } - } else { - el.setAttribute(attribute, value); - } - } - - for (i = 0; i < elem.childNodes.length; i++) { - el.appendChild(Strophe.createHtml(elem.childNodes[i])); - } - } catch(e) { // invalid elements - el = Strophe.xmlTextNode(''); - } - } else { - el = Strophe.xmlGenerator().createDocumentFragment(); - for (i = 0; i < elem.childNodes.length; i++) { - el.appendChild(Strophe.createHtml(elem.childNodes[i])); - } - } - } else if (elem.nodeType == Strophe.ElementType.FRAGMENT) { - el = Strophe.xmlGenerator().createDocumentFragment(); - for (i = 0; i < elem.childNodes.length; i++) { - el.appendChild(Strophe.createHtml(elem.childNodes[i])); - } - } else if (elem.nodeType == Strophe.ElementType.TEXT) { - el = Strophe.xmlTextNode(elem.nodeValue); - } - - return el; - }, - - /** Function: escapeNode - * Escape the node part (also called local part) of a JID. - * - * Parameters: - * (String) node - A node (or local part). - * - * Returns: - * An escaped node (or local part). - */ - escapeNode: function (node) - { - return node.replace(/^\s+|\s+$/g, '') - .replace(/\\/g, "\\5c") - .replace(/ /g, "\\20") - .replace(/\"/g, "\\22") - .replace(/\&/g, "\\26") - .replace(/\'/g, "\\27") - .replace(/\//g, "\\2f") - .replace(/:/g, "\\3a") - .replace(//g, "\\3e") - .replace(/@/g, "\\40"); - }, - - /** Function: unescapeNode - * Unescape a node part (also called local part) of a JID. - * - * Parameters: - * (String) node - A node (or local part). - * - * Returns: - * An unescaped node (or local part). - */ - unescapeNode: function (node) - { - return node.replace(/\\20/g, " ") - .replace(/\\22/g, '"') - .replace(/\\26/g, "&") - .replace(/\\27/g, "'") - .replace(/\\2f/g, "/") - .replace(/\\3a/g, ":") - .replace(/\\3c/g, "<") - .replace(/\\3e/g, ">") - .replace(/\\40/g, "@") - .replace(/\\5c/g, "\\"); - }, - - /** Function: getNodeFromJid - * Get the node portion of a JID String. - * - * Parameters: - * (String) jid - A JID. - * - * Returns: - * A String containing the node. - */ - getNodeFromJid: function (jid) - { - if (jid.indexOf("@") < 0) { return null; } - return jid.split("@")[0]; - }, - - /** Function: getDomainFromJid - * Get the domain portion of a JID String. - * - * Parameters: - * (String) jid - A JID. - * - * Returns: - * A String containing the domain. - */ - getDomainFromJid: function (jid) - { - var bare = Strophe.getBareJidFromJid(jid); - if (bare.indexOf("@") < 0) { - return bare; - } else { - var parts = bare.split("@"); - parts.splice(0, 1); - return parts.join('@'); - } - }, - - /** Function: getResourceFromJid - * Get the resource portion of a JID String. - * - * Parameters: - * (String) jid - A JID. - * - * Returns: - * A String containing the resource. - */ - getResourceFromJid: function (jid) - { - var s = jid.split("/"); - if (s.length < 2) { return null; } - s.splice(0, 1); - return s.join('/'); - }, - - /** Function: getBareJidFromJid - * Get the bare JID from a JID String. - * - * Parameters: - * (String) jid - A JID. - * - * Returns: - * A String containing the bare JID. - */ - getBareJidFromJid: function (jid) - { - return jid ? jid.split("/")[0] : null; - }, - - /** Function: log - * User overrideable logging function. - * - * This function is called whenever the Strophe library calls any - * of the logging functions. The default implementation of this - * function does nothing. If client code wishes to handle the logging - * messages, it should override this with - * > Strophe.log = function (level, msg) { - * > (user code here) - * > }; - * - * Please note that data sent and received over the wire is logged - * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput(). - * - * The different levels and their meanings are - * - * DEBUG - Messages useful for debugging purposes. - * INFO - Informational messages. This is mostly information like - * 'disconnect was called' or 'SASL auth succeeded'. - * WARN - Warnings about potential problems. This is mostly used - * to report transient connection errors like request timeouts. - * ERROR - Some error occurred. - * FATAL - A non-recoverable fatal error occurred. - * - * Parameters: - * (Integer) level - The log level of the log message. This will - * be one of the values in Strophe.LogLevel. - * (String) msg - The log message. - */ - /* jshint ignore:start */ - log: function (level, msg) - { - return; - }, - /* jshint ignore:end */ - - /** Function: debug - * Log a message at the Strophe.LogLevel.DEBUG level. - * - * Parameters: - * (String) msg - The log message. - */ - debug: function(msg) - { - this.log(this.LogLevel.DEBUG, msg); - }, - - /** Function: info - * Log a message at the Strophe.LogLevel.INFO level. - * - * Parameters: - * (String) msg - The log message. - */ - info: function (msg) - { - this.log(this.LogLevel.INFO, msg); - }, - - /** Function: warn - * Log a message at the Strophe.LogLevel.WARN level. - * - * Parameters: - * (String) msg - The log message. - */ - warn: function (msg) - { - this.log(this.LogLevel.WARN, msg); - }, - - /** Function: error - * Log a message at the Strophe.LogLevel.ERROR level. - * - * Parameters: - * (String) msg - The log message. - */ - error: function (msg) - { - this.log(this.LogLevel.ERROR, msg); - }, - - /** Function: fatal - * Log a message at the Strophe.LogLevel.FATAL level. - * - * Parameters: - * (String) msg - The log message. - */ - fatal: function (msg) - { - this.log(this.LogLevel.FATAL, msg); - }, - - /** Function: serialize - * Render a DOM element and all descendants to a String. - * - * Parameters: - * (XMLElement) elem - A DOM element. - * - * Returns: - * The serialized element tree as a String. - */ - serialize: function (elem) - { - var result; - - if (!elem) { return null; } - - if (typeof(elem.tree) === "function") { - elem = elem.tree(); - } - - var nodeName = elem.nodeName; - var i, child; - - if (elem.getAttribute("_realname")) { - nodeName = elem.getAttribute("_realname"); - } - - result = "<" + nodeName; - for (i = 0; i < elem.attributes.length; i++) { - if(elem.attributes[i].nodeName != "_realname") { - result += " " + elem.attributes[i].nodeName.toLowerCase() + - "='" + elem.attributes[i].value - .replace(/&/g, "&") - .replace(/\'/g, "'") - .replace(/>/g, ">") - .replace(/ 0) { - result += ">"; - for (i = 0; i < elem.childNodes.length; i++) { - child = elem.childNodes[i]; - switch( child.nodeType ){ - case Strophe.ElementType.NORMAL: - // normal element, so recurse - result += Strophe.serialize(child); - break; - case Strophe.ElementType.TEXT: - // text element to escape values - result += Strophe.xmlescape(child.nodeValue); - break; - case Strophe.ElementType.CDATA: - // cdata section so don't escape values - result += ""; - } - } - result += ""; - } else { - result += "/>"; - } - - return result; - }, - - /** PrivateVariable: _requestId - * _Private_ variable that keeps track of the request ids for - * connections. - */ - _requestId: 0, - - /** PrivateVariable: Strophe.connectionPlugins - * _Private_ variable Used to store plugin names that need - * initialization on Strophe.Connection construction. - */ - _connectionPlugins: {}, - - /** Function: addConnectionPlugin - * Extends the Strophe.Connection object with the given plugin. - * - * Parameters: - * (String) name - The name of the extension. - * (Object) ptype - The plugin's prototype. - */ - addConnectionPlugin: function (name, ptype) - { - Strophe._connectionPlugins[name] = ptype; - } -}; - -/** Class: Strophe.Builder - * XML DOM builder. - * - * This object provides an interface similar to JQuery but for building - * DOM element easily and rapidly. All the functions except for toString() - * and tree() return the object, so calls can be chained. Here's an - * example using the $iq() builder helper. - * > $iq({to: 'you', from: 'me', type: 'get', id: '1'}) - * > .c('query', {xmlns: 'strophe:example'}) - * > .c('example') - * > .toString() - * The above generates this XML fragment - * > - * > - * > - * > - * > - * The corresponding DOM manipulations to get a similar fragment would be - * a lot more tedious and probably involve several helper variables. - * - * Since adding children makes new operations operate on the child, up() - * is provided to traverse up the tree. To add two children, do - * > builder.c('child1', ...).up().c('child2', ...) - * The next operation on the Builder will be relative to the second child. - */ - -/** Constructor: Strophe.Builder - * Create a Strophe.Builder object. - * - * The attributes should be passed in object notation. For example - * > var b = new Builder('message', {to: 'you', from: 'me'}); - * or - * > var b = new Builder('messsage', {'xml:lang': 'en'}); - * - * Parameters: - * (String) name - The name of the root element. - * (Object) attrs - The attributes for the root element in object notation. - * - * Returns: - * A new Strophe.Builder. - */ -Strophe.Builder = function (name, attrs) -{ - // Set correct namespace for jabber:client elements - if (name == "presence" || name == "message" || name == "iq") { - if (attrs && !attrs.xmlns) { - attrs.xmlns = Strophe.NS.CLIENT; - } else if (!attrs) { - attrs = {xmlns: Strophe.NS.CLIENT}; - } - } - - // Holds the tree being built. - this.nodeTree = Strophe.xmlElement(name, attrs); - - // Points to the current operation node. - this.node = this.nodeTree; -}; - -Strophe.Builder.prototype = { - /** Function: tree - * Return the DOM tree. - * - * This function returns the current DOM tree as an element object. This - * is suitable for passing to functions like Strophe.Connection.send(). - * - * Returns: - * The DOM tree as a element object. - */ - tree: function () - { - return this.nodeTree; - }, - - /** Function: toString - * Serialize the DOM tree to a String. - * - * This function returns a string serialization of the current DOM - * tree. It is often used internally to pass data to a - * Strophe.Request object. - * - * Returns: - * The serialized DOM tree in a String. - */ - toString: function () - { - return Strophe.serialize(this.nodeTree); - }, - - /** Function: up - * Make the current parent element the new current element. - * - * This function is often used after c() to traverse back up the tree. - * For example, to add two children to the same element - * > builder.c('child1', {}).up().c('child2', {}); - * - * Returns: - * The Stophe.Builder object. - */ - up: function () - { - this.node = this.node.parentNode; - return this; - }, - - /** Function: attrs - * Add or modify attributes of the current element. - * - * The attributes should be passed in object notation. This function - * does not move the current element pointer. - * - * Parameters: - * (Object) moreattrs - The attributes to add/modify in object notation. - * - * Returns: - * The Strophe.Builder object. - */ - attrs: function (moreattrs) - { - for (var k in moreattrs) { - if (moreattrs.hasOwnProperty(k)) { - this.node.setAttribute(k, moreattrs[k]); - } - } - return this; - }, - - /** Function: c - * Add a child to the current element and make it the new current - * element. - * - * This function moves the current element pointer to the child, - * unless text is provided. If you need to add another child, it - * is necessary to use up() to go back to the parent in the tree. - * - * Parameters: - * (String) name - The name of the child. - * (Object) attrs - The attributes of the child in object notation. - * (String) text - The text to add to the child. - * - * Returns: - * The Strophe.Builder object. - */ - c: function (name, attrs, text) - { - var child = Strophe.xmlElement(name, attrs, text); - this.node.appendChild(child); - if (!text) { - this.node = child; - } - return this; - }, - - /** Function: cnode - * Add a child to the current element and make it the new current - * element. - * - * This function is the same as c() except that instead of using a - * name and an attributes object to create the child it uses an - * existing DOM element object. - * - * Parameters: - * (XMLElement) elem - A DOM element. - * - * Returns: - * The Strophe.Builder object. - */ - cnode: function (elem) - { - var impNode; - var xmlGen = Strophe.xmlGenerator(); - try { - impNode = (xmlGen.importNode !== undefined); - } - catch (e) { - impNode = false; - } - var newElem = impNode ? - xmlGen.importNode(elem, true) : - Strophe.copyElement(elem); - this.node.appendChild(newElem); - this.node = newElem; - return this; - }, - - /** Function: t - * Add a child text element. - * - * This *does not* make the child the new current element since there - * are no children of text elements. - * - * Parameters: - * (String) text - The text data to append to the current element. - * - * Returns: - * The Strophe.Builder object. - */ - t: function (text) - { - var child = Strophe.xmlTextNode(text); - this.node.appendChild(child); - return this; - }, - - /** Function: h - * Replace current element contents with the HTML passed in. - * - * This *does not* make the child the new current element - * - * Parameters: - * (String) html - The html to insert as contents of current element. - * - * Returns: - * The Strophe.Builder object. - */ - h: function (html) - { - var fragment = document.createElement('body'); - - // force the browser to try and fix any invalid HTML tags - fragment.innerHTML = html; - - // copy cleaned html into an xml dom - var xhtml = Strophe.createHtml(fragment); - - while(xhtml.childNodes.length > 0) { - this.node.appendChild(xhtml.childNodes[0]); - } - return this; - } -}; - -/** PrivateClass: Strophe.Handler - * _Private_ helper class for managing stanza handlers. - * - * A Strophe.Handler encapsulates a user provided callback function to be - * executed when matching stanzas are received by the connection. - * Handlers can be either one-off or persistant depending on their - * return value. Returning true will cause a Handler to remain active, and - * returning false will remove the Handler. - * - * Users will not use Strophe.Handler objects directly, but instead they - * will use Strophe.Connection.addHandler() and - * Strophe.Connection.deleteHandler(). - */ - -/** PrivateConstructor: Strophe.Handler - * Create and initialize a new Strophe.Handler. - * - * Parameters: - * (Function) handler - A function to be executed when the handler is run. - * (String) ns - The namespace to match. - * (String) name - The element name to match. - * (String) type - The element type to match. - * (String) id - The element id attribute to match. - * (String) from - The element from attribute to match. - * (Object) options - Handler options - * - * Returns: - * A new Strophe.Handler object. - */ -Strophe.Handler = function (handler, ns, name, type, id, from, options) -{ - this.handler = handler; - this.ns = ns; - this.name = name; - this.type = type; - this.id = id; - this.options = options || {matchBare: false}; - - // default matchBare to false if undefined - if (!this.options.matchBare) { - this.options.matchBare = false; - } - - if (this.options.matchBare) { - this.from = from ? Strophe.getBareJidFromJid(from) : null; - } else { - this.from = from; - } - - // whether the handler is a user handler or a system handler - this.user = true; -}; - -Strophe.Handler.prototype = { - /** PrivateFunction: isMatch - * Tests if a stanza matches the Strophe.Handler. - * - * Parameters: - * (XMLElement) elem - The XML element to test. - * - * Returns: - * true if the stanza matches and false otherwise. - */ - isMatch: function (elem) - { - var nsMatch; - var from = null; - - if (this.options.matchBare) { - from = Strophe.getBareJidFromJid(elem.getAttribute('from')); - } else { - from = elem.getAttribute('from'); - } - - nsMatch = false; - if (!this.ns) { - nsMatch = true; - } else { - var that = this; - Strophe.forEachChild(elem, null, function (elem) { - if (elem.getAttribute("xmlns") == that.ns) { - nsMatch = true; - } - }); - - nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns; - } - - if (nsMatch && - (!this.name || Strophe.isTagEqual(elem, this.name)) && - (!this.type || elem.getAttribute("type") == this.type) && - (!this.id || elem.getAttribute("id") == this.id) && - (!this.from || from == this.from)) { - return true; - } - - return false; - }, - - /** PrivateFunction: run - * Run the callback on a matching stanza. - * - * Parameters: - * (XMLElement) elem - The DOM element that triggered the - * Strophe.Handler. - * - * Returns: - * A boolean indicating if the handler should remain active. - */ - run: function (elem) - { - var result = null; - try { - result = this.handler(elem); - } catch (e) { - if (e.sourceURL) { - Strophe.fatal("error: " + this.handler + - " " + e.sourceURL + ":" + - e.line + " - " + e.name + ": " + e.message); - } else if (e.fileName) { - if (typeof(console) != "undefined") { - console.trace(); - console.error(this.handler, " - error - ", e, e.message); - } - Strophe.fatal("error: " + this.handler + " " + - e.fileName + ":" + e.lineNumber + " - " + - e.name + ": " + e.message); - } else { - Strophe.fatal("error: " + e.message + "\n" + e.stack); - } - - throw e; - } - - return result; - }, - - /** PrivateFunction: toString - * Get a String representation of the Strophe.Handler object. - * - * Returns: - * A String. - */ - toString: function () - { - return "{Handler: " + this.handler + "(" + this.name + "," + - this.id + "," + this.ns + ")}"; - } -}; - -/** PrivateClass: Strophe.TimedHandler - * _Private_ helper class for managing timed handlers. - * - * A Strophe.TimedHandler encapsulates a user provided callback that - * should be called after a certain period of time or at regular - * intervals. The return value of the callback determines whether the - * Strophe.TimedHandler will continue to fire. - * - * Users will not use Strophe.TimedHandler objects directly, but instead - * they will use Strophe.Connection.addTimedHandler() and - * Strophe.Connection.deleteTimedHandler(). - */ - -/** PrivateConstructor: Strophe.TimedHandler - * Create and initialize a new Strophe.TimedHandler object. - * - * Parameters: - * (Integer) period - The number of milliseconds to wait before the - * handler is called. - * (Function) handler - The callback to run when the handler fires. This - * function should take no arguments. - * - * Returns: - * A new Strophe.TimedHandler object. - */ -Strophe.TimedHandler = function (period, handler) -{ - this.period = period; - this.handler = handler; - - this.lastCalled = new Date().getTime(); - this.user = true; -}; - -Strophe.TimedHandler.prototype = { - /** PrivateFunction: run - * Run the callback for the Strophe.TimedHandler. - * - * Returns: - * true if the Strophe.TimedHandler should be called again, and false - * otherwise. - */ - run: function () - { - this.lastCalled = new Date().getTime(); - return this.handler(); - }, - - /** PrivateFunction: reset - * Reset the last called time for the Strophe.TimedHandler. - */ - reset: function () - { - this.lastCalled = new Date().getTime(); - }, - - /** PrivateFunction: toString - * Get a string representation of the Strophe.TimedHandler object. - * - * Returns: - * The string representation. - */ - toString: function () - { - return "{TimedHandler: " + this.handler + "(" + this.period +")}"; - } -}; - -/** Class: Strophe.Connection - * XMPP Connection manager. - * - * This class is the main part of Strophe. It manages a BOSH connection - * to an XMPP server and dispatches events to the user callbacks as - * data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1 - * and legacy authentication. - * - * After creating a Strophe.Connection object, the user will typically - * call connect() with a user supplied callback to handle connection level - * events like authentication failure, disconnection, or connection - * complete. - * - * The user will also have several event handlers defined by using - * addHandler() and addTimedHandler(). These will allow the user code to - * respond to interesting stanzas or do something periodically with the - * connection. These handlers will be active once authentication is - * finished. - * - * To send data to the connection, use send(). - */ - -/** Constructor: Strophe.Connection - * Create and initialize a Strophe.Connection object. - * - * The transport-protocol for this connection will be chosen automatically - * based on the given service parameter. URLs starting with "ws://" or - * "wss://" will use WebSockets, URLs starting with "http://", "https://" - * or without a protocol will use BOSH. - * - * To make Strophe connect to the current host you can leave out the protocol - * and host part and just pass the path, e.g. - * - * > var conn = new Strophe.Connection("/http-bind/"); - * - * WebSocket options: - * - * If you want to connect to the current host with a WebSocket connection you - * can tell Strophe to use WebSockets through a "protocol" attribute in the - * optional options parameter. Valid values are "ws" for WebSocket and "wss" - * for Secure WebSocket. - * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call - * - * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"}); - * - * Note that relative URLs _NOT_ starting with a "/" will also include the path - * of the current site. - * - * Also because downgrading security is not permitted by browsers, when using - * relative URLs both BOSH and WebSocket connections will use their secure - * variants if the current connection to the site is also secure (https). - * - * BOSH options: - * - * by adding "sync" to the options, you can control if requests will - * be made synchronously or not. The default behaviour is asynchronous. - * If you want to make requests synchronous, make "sync" evaluate to true: - * > var conn = new Strophe.Connection("/http-bind/", {sync: true}); - * You can also toggle this on an already established connection: - * > conn.options.sync = true; - * - * - * Parameters: - * (String) service - The BOSH or WebSocket service URL. - * (Object) options - A hash of configuration options - * - * Returns: - * A new Strophe.Connection object. - */ -Strophe.Connection = function (service, options) -{ - // The service URL - this.service = service; - - // Configuration options - this.options = options || {}; - var proto = this.options.protocol || ""; - - // Select protocal based on service or options - if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || - proto.indexOf("ws") === 0) { - this._proto = new Strophe.Websocket(this); - } else { - this._proto = new Strophe.Bosh(this); - } - /* The connected JID. */ - this.jid = ""; - /* the JIDs domain */ - this.domain = null; - /* stream:features */ - this.features = null; - - // SASL - this._sasl_data = {}; - this.do_session = false; - this.do_bind = false; - - // handler lists - this.timedHandlers = []; - this.handlers = []; - this.removeTimeds = []; - this.removeHandlers = []; - this.addTimeds = []; - this.addHandlers = []; - - this._authentication = {}; - this._idleTimeout = null; - this._disconnectTimeout = null; - - this.do_authentication = true; - this.authenticated = false; - this.disconnecting = false; - this.connected = false; - - this.errors = 0; - - this.paused = false; - - this._data = []; - this._uniqueId = 0; - - this._sasl_success_handler = null; - this._sasl_failure_handler = null; - this._sasl_challenge_handler = null; - - // Max retries before disconnecting - this.maxRetries = 5; - - // setup onIdle callback every 1/10th of a second - this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); - - // initialize plugins - for (var k in Strophe._connectionPlugins) { - if (Strophe._connectionPlugins.hasOwnProperty(k)) { - var ptype = Strophe._connectionPlugins[k]; - // jslint complaints about the below line, but this is fine - var F = function () {}; // jshint ignore:line - F.prototype = ptype; - this[k] = new F(); - this[k].init(this); - } - } -}; - -Strophe.Connection.prototype = { - /** Function: reset - * Reset the connection. - * - * This function should be called after a connection is disconnected - * before that connection is reused. - */ - reset: function () - { - this._proto._reset(); - - // SASL - this.do_session = false; - this.do_bind = false; - - // handler lists - this.timedHandlers = []; - this.handlers = []; - this.removeTimeds = []; - this.removeHandlers = []; - this.addTimeds = []; - this.addHandlers = []; - this._authentication = {}; - - this.authenticated = false; - this.disconnecting = false; - this.connected = false; - - this.errors = 0; - - this._requests = []; - this._uniqueId = 0; - }, - - /** Function: pause - * Pause the request manager. - * - * This will prevent Strophe from sending any more requests to the - * server. This is very useful for temporarily pausing - * BOSH-Connections while a lot of send() calls are happening quickly. - * This causes Strophe to send the data in a single request, saving - * many request trips. - */ - pause: function () - { - this.paused = true; - }, - - /** Function: resume - * Resume the request manager. - * - * This resumes after pause() has been called. - */ - resume: function () - { - this.paused = false; - }, - - /** Function: getUniqueId - * Generate a unique ID for use in elements. - * - * All stanzas are required to have unique id attributes. This - * function makes creating these easy. Each connection instance has - * a counter which starts from zero, and the value of this counter - * plus a colon followed by the suffix becomes the unique id. If no - * suffix is supplied, the counter is used as the unique id. - * - * Suffixes are used to make debugging easier when reading the stream - * data, and their use is recommended. The counter resets to 0 for - * every new connection for the same reason. For connections to the - * same server that authenticate the same way, all the ids should be - * the same, which makes it easy to see changes. This is useful for - * automated testing as well. - * - * Parameters: - * (String) suffix - A optional suffix to append to the id. - * - * Returns: - * A unique string to be used for the id attribute. - */ - getUniqueId: function (suffix) - { - if (typeof(suffix) == "string" || typeof(suffix) == "number") { - return ++this._uniqueId + ":" + suffix; - } else { - return ++this._uniqueId + ""; - } - }, - - /** Function: connect - * Starts the connection process. - * - * As the connection process proceeds, the user supplied callback will - * be triggered multiple times with status updates. The callback - * should take two arguments - the status code and the error condition. - * - * The status code will be one of the values in the Strophe.Status - * constants. The error condition will be one of the conditions - * defined in RFC 3920 or the condition 'strophe-parsererror'. - * - * The Parameters _wait_, _hold_ and _route_ are optional and only relevant - * for BOSH connections. Please see XEP 124 for a more detailed explanation - * of the optional parameters. - * - * Parameters: - * (String) jid - The user's JID. This may be a bare JID, - * or a full JID. If a node is not supplied, SASL ANONYMOUS - * authentication will be attempted. - * (String) pass - The user's password. - * (Function) callback - The connect callback function. - * (Integer) wait - The optional HTTPBIND wait value. This is the - * time the server will wait before returning an empty result for - * a request. The default setting of 60 seconds is recommended. - * (Integer) hold - The optional HTTPBIND hold value. This is the - * number of connections the server will hold at one time. This - * should almost always be set to 1 (the default). - * (String) route - The optional route value. - */ - connect: function (jid, pass, callback, wait, hold, route) - { - this.jid = jid; - /** Variable: authzid - * Authorization identity. - */ - this.authzid = Strophe.getBareJidFromJid(this.jid); - /** Variable: authcid - * Authentication identity (User name). - */ - this.authcid = Strophe.getNodeFromJid(this.jid); - /** Variable: pass - * Authentication identity (User password). - */ - this.pass = pass; - /** Variable: servtype - * Digest MD5 compatibility. - */ - this.servtype = "xmpp"; - this.connect_callback = callback; - this.disconnecting = false; - this.connected = false; - this.authenticated = false; - this.errors = 0; - - // parse jid for domain - this.domain = Strophe.getDomainFromJid(this.jid); - - this._changeConnectStatus(Strophe.Status.CONNECTING, null); - - this._proto._connect(wait, hold, route); - }, - - /** Function: attach - * Attach to an already created and authenticated BOSH session. - * - * This function is provided to allow Strophe to attach to BOSH - * sessions which have been created externally, perhaps by a Web - * application. This is often used to support auto-login type features - * without putting user credentials into the page. - * - * Parameters: - * (String) jid - The full JID that is bound by the session. - * (String) sid - The SID of the BOSH session. - * (String) rid - The current RID of the BOSH session. This RID - * will be used by the next request. - * (Function) callback The connect callback function. - * (Integer) wait - The optional HTTPBIND wait value. This is the - * time the server will wait before returning an empty result for - * a request. The default setting of 60 seconds is recommended. - * Other settings will require tweaks to the Strophe.TIMEOUT value. - * (Integer) hold - The optional HTTPBIND hold value. This is the - * number of connections the server will hold at one time. This - * should almost always be set to 1 (the default). - * (Integer) wind - The optional HTTBIND window value. This is the - * allowed range of request ids that are valid. The default is 5. - */ - attach: function (jid, sid, rid, callback, wait, hold, wind) - { - this._proto._attach(jid, sid, rid, callback, wait, hold, wind); - }, - - /** Function: xmlInput - * User overrideable function that receives XML data coming into the - * connection. - * - * The default function does nothing. User code can override this with - * > Strophe.Connection.xmlInput = function (elem) { - * > (user code) - * > }; - * - * Due to limitations of current Browsers' XML-Parsers the opening and closing - * tag for WebSocket-Connoctions will be passed as selfclosing here. - * - * BOSH-Connections will have all stanzas wrapped in a tag. See - * if you want to strip this tag. - * - * Parameters: - * (XMLElement) elem - The XML data received by the connection. - */ - /* jshint unused:false */ - xmlInput: function (elem) - { - return; - }, - /* jshint unused:true */ - - /** Function: xmlOutput - * User overrideable function that receives XML data sent to the - * connection. - * - * The default function does nothing. User code can override this with - * > Strophe.Connection.xmlOutput = function (elem) { - * > (user code) - * > }; - * - * Due to limitations of current Browsers' XML-Parsers the opening and closing - * tag for WebSocket-Connoctions will be passed as selfclosing here. - * - * BOSH-Connections will have all stanzas wrapped in a tag. See - * if you want to strip this tag. - * - * Parameters: - * (XMLElement) elem - The XMLdata sent by the connection. - */ - /* jshint unused:false */ - xmlOutput: function (elem) - { - return; - }, - /* jshint unused:true */ - - /** Function: rawInput - * User overrideable function that receives raw data coming into the - * connection. - * - * The default function does nothing. User code can override this with - * > Strophe.Connection.rawInput = function (data) { - * > (user code) - * > }; - * - * Parameters: - * (String) data - The data received by the connection. - */ - /* jshint unused:false */ - rawInput: function (data) - { - return; - }, - /* jshint unused:true */ - - /** Function: rawOutput - * User overrideable function that receives raw data sent to the - * connection. - * - * The default function does nothing. User code can override this with - * > Strophe.Connection.rawOutput = function (data) { - * > (user code) - * > }; - * - * Parameters: - * (String) data - The data sent by the connection. - */ - /* jshint unused:false */ - rawOutput: function (data) - { - return; - }, - /* jshint unused:true */ - - /** Function: send - * Send a stanza. - * - * This function is called to push data onto the send queue to - * go out over the wire. Whenever a request is sent to the BOSH - * server, all pending data is sent and the queue is flushed. - * - * Parameters: - * (XMLElement | - * [XMLElement] | - * Strophe.Builder) elem - The stanza to send. - */ - send: function (elem) - { - if (elem === null) { return ; } - if (typeof(elem.sort) === "function") { - for (var i = 0; i < elem.length; i++) { - this._queueData(elem[i]); - } - } else if (typeof(elem.tree) === "function") { - this._queueData(elem.tree()); - } else { - this._queueData(elem); - } - - this._proto._send(); - }, - - /** Function: flush - * Immediately send any pending outgoing data. - * - * Normally send() queues outgoing data until the next idle period - * (100ms), which optimizes network use in the common cases when - * several send()s are called in succession. flush() can be used to - * immediately send all pending data. - */ - flush: function () - { - // cancel the pending idle period and run the idle function - // immediately - clearTimeout(this._idleTimeout); - this._onIdle(); - }, - - /** Function: sendIQ - * Helper function to send IQ stanzas. - * - * Parameters: - * (XMLElement) elem - The stanza to send. - * (Function) callback - The callback function for a successful request. - * (Function) errback - The callback function for a failed or timed - * out request. On timeout, the stanza will be null. - * (Integer) timeout - The time specified in milliseconds for a - * timeout to occur. - * - * Returns: - * The id used to send the IQ. - */ - sendIQ: function(elem, callback, errback, timeout) { - var timeoutHandler = null; - var that = this; - - if (typeof(elem.tree) === "function") { - elem = elem.tree(); - } - var id = elem.getAttribute('id'); - - // inject id if not found - if (!id) { - id = this.getUniqueId("sendIQ"); - elem.setAttribute("id", id); - } - - var handler = this.addHandler(function (stanza) { - // remove timeout handler if there is one - if (timeoutHandler) { - that.deleteTimedHandler(timeoutHandler); - } - - var iqtype = stanza.getAttribute('type'); - if (iqtype == 'result') { - if (callback) { - callback(stanza); - } - } else if (iqtype == 'error') { - if (errback) { - errback(stanza); - } - } else { - throw { - name: "StropheError", - message: "Got bad IQ type of " + iqtype - }; - } - }, null, 'iq', null, id); - - // if timeout specified, setup timeout handler. - if (timeout) { - timeoutHandler = this.addTimedHandler(timeout, function () { - // get rid of normal handler - that.deleteHandler(handler); - - // call errback on timeout with null stanza - if (errback) { - errback(null); - } - return false; - }); - } - - this.send(elem); - - return id; - }, - - /** PrivateFunction: _queueData - * Queue outgoing data for later sending. Also ensures that the data - * is a DOMElement. - */ - _queueData: function (element) { - if (element === null || - !element.tagName || - !element.childNodes) { - throw { - name: "StropheError", - message: "Cannot queue non-DOMElement." - }; - } - - this._data.push(element); - }, - - /** PrivateFunction: _sendRestart - * Send an xmpp:restart stanza. - */ - _sendRestart: function () - { - this._data.push("restart"); - - this._proto._sendRestart(); - - this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); - }, - - /** Function: addTimedHandler - * Add a timed handler to the connection. - * - * This function adds a timed handler. The provided handler will - * be called every period milliseconds until it returns false, - * the connection is terminated, or the handler is removed. Handlers - * that wish to continue being invoked should return true. - * - * Because of method binding it is necessary to save the result of - * this function if you wish to remove a handler with - * deleteTimedHandler(). - * - * Note that user handlers are not active until authentication is - * successful. - * - * Parameters: - * (Integer) period - The period of the handler. - * (Function) handler - The callback function. - * - * Returns: - * A reference to the handler that can be used to remove it. - */ - addTimedHandler: function (period, handler) - { - var thand = new Strophe.TimedHandler(period, handler); - this.addTimeds.push(thand); - return thand; - }, - - /** Function: deleteTimedHandler - * Delete a timed handler for a connection. - * - * This function removes a timed handler from the connection. The - * handRef parameter is *not* the function passed to addTimedHandler(), - * but is the reference returned from addTimedHandler(). - * - * Parameters: - * (Strophe.TimedHandler) handRef - The handler reference. - */ - deleteTimedHandler: function (handRef) - { - // this must be done in the Idle loop so that we don't change - // the handlers during iteration - this.removeTimeds.push(handRef); - }, - - /** Function: addHandler - * Add a stanza handler for the connection. - * - * This function adds a stanza handler to the connection. The - * handler callback will be called for any stanza that matches - * the parameters. Note that if multiple parameters are supplied, - * they must all match for the handler to be invoked. - * - * The handler will receive the stanza that triggered it as its argument. - * The handler should return true if it is to be invoked again; - * returning false will remove the handler after it returns. - * - * As a convenience, the ns parameters applies to the top level element - * and also any of its immediate children. This is primarily to make - * matching /iq/query elements easy. - * - * The options argument contains handler matching flags that affect how - * matches are determined. Currently the only flag is matchBare (a - * boolean). When matchBare is true, the from parameter and the from - * attribute on the stanza will be matched as bare JIDs instead of - * full JIDs. To use this, pass {matchBare: true} as the value of - * options. The default value for matchBare is false. - * - * The return value should be saved if you wish to remove the handler - * with deleteHandler(). - * - * Parameters: - * (Function) handler - The user callback. - * (String) ns - The namespace to match. - * (String) name - The stanza name to match. - * (String) type - The stanza type attribute to match. - * (String) id - The stanza id attribute to match. - * (String) from - The stanza from attribute to match. - * (String) options - The handler options - * - * Returns: - * A reference to the handler that can be used to remove it. - */ - addHandler: function (handler, ns, name, type, id, from, options) - { - var hand = new Strophe.Handler(handler, ns, name, type, id, from, options); - this.addHandlers.push(hand); - return hand; - }, - - /** Function: deleteHandler - * Delete a stanza handler for a connection. - * - * This function removes a stanza handler from the connection. The - * handRef parameter is *not* the function passed to addHandler(), - * but is the reference returned from addHandler(). - * - * Parameters: - * (Strophe.Handler) handRef - The handler reference. - */ - deleteHandler: function (handRef) - { - // this must be done in the Idle loop so that we don't change - // the handlers during iteration - this.removeHandlers.push(handRef); - }, - - /** Function: disconnect - * Start the graceful disconnection process. - * - * This function starts the disconnection process. This process starts - * by sending unavailable presence and sending BOSH body of type - * terminate. A timeout handler makes sure that disconnection happens - * even if the BOSH server does not respond. - * - * The user supplied connection callback will be notified of the - * progress as this process happens. - * - * Parameters: - * (String) reason - The reason the disconnect is occuring. - */ - disconnect: function (reason) - { - this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason); - - Strophe.info("Disconnect was called because: " + reason); - if (this.connected) { - var pres = false; - this.disconnecting = true; - if (this.authenticated) { - pres = $pres({ - xmlns: Strophe.NS.CLIENT, - type: 'unavailable' - }); - } - // setup timeout handler - this._disconnectTimeout = this._addSysTimedHandler( - 3000, this._onDisconnectTimeout.bind(this)); - this._proto._disconnect(pres); - } - }, - - /** PrivateFunction: _changeConnectStatus - * _Private_ helper function that makes sure plugins and the user's - * callback are notified of connection status changes. - * - * Parameters: - * (Integer) status - the new connection status, one of the values - * in Strophe.Status - * (String) condition - the error condition or null - */ - _changeConnectStatus: function (status, condition) - { - // notify all plugins listening for status changes - for (var k in Strophe._connectionPlugins) { - if (Strophe._connectionPlugins.hasOwnProperty(k)) { - var plugin = this[k]; - if (plugin.statusChanged) { - try { - plugin.statusChanged(status, condition); - } catch (err) { - Strophe.error("" + k + " plugin caused an exception " + - "changing status: " + err); - } - } - } - } - - // notify the user's callback - if (this.connect_callback) { - try { - this.connect_callback(status, condition); - } catch (e) { - Strophe.error("User connection callback caused an " + - "exception: " + e); - } - } - }, - - /** PrivateFunction: _doDisconnect - * _Private_ function to disconnect. - * - * This is the last piece of the disconnection logic. This resets the - * connection and alerts the user's connection callback. - */ - _doDisconnect: function () - { - // Cancel Disconnect Timeout - if (this._disconnectTimeout !== null) { - this.deleteTimedHandler(this._disconnectTimeout); - this._disconnectTimeout = null; - } - - Strophe.info("_doDisconnect was called"); - this._proto._doDisconnect(); - - this.authenticated = false; - this.disconnecting = false; - - // delete handlers - this.handlers = []; - this.timedHandlers = []; - this.removeTimeds = []; - this.removeHandlers = []; - this.addTimeds = []; - this.addHandlers = []; - - // tell the parent we disconnected - this._changeConnectStatus(Strophe.Status.DISCONNECTED, null); - this.connected = false; - }, - - /** PrivateFunction: _dataRecv - * _Private_ handler to processes incoming data from the the connection. - * - * Except for _connect_cb handling the initial connection request, - * this function handles the incoming data for all requests. This - * function also fires stanza handlers that match each incoming - * stanza. - * - * Parameters: - * (Strophe.Request) req - The request that has data ready. - * (string) req - The stanza a raw string (optiona). - */ - _dataRecv: function (req, raw) - { - Strophe.info("_dataRecv called"); - var elem = this._proto._reqToData(req); - if (elem === null) { return; } - - if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { - if (elem.nodeName === this._proto.strip && elem.childNodes.length) { - this.xmlInput(elem.childNodes[0]); - } else { - this.xmlInput(elem); - } - } - if (this.rawInput !== Strophe.Connection.prototype.rawInput) { - if (raw) { - this.rawInput(raw); - } else { - this.rawInput(Strophe.serialize(elem)); - } - } - - // remove handlers scheduled for deletion - var i, hand; - while (this.removeHandlers.length > 0) { - hand = this.removeHandlers.pop(); - i = this.handlers.indexOf(hand); - if (i >= 0) { - this.handlers.splice(i, 1); - } - } - - // add handlers scheduled for addition - while (this.addHandlers.length > 0) { - this.handlers.push(this.addHandlers.pop()); - } - - // handle graceful disconnect - if (this.disconnecting && this._proto._emptyQueue()) { - this._doDisconnect(); - return; - } - - var typ = elem.getAttribute("type"); - var cond, conflict; - if (typ !== null && typ == "terminate") { - // Don't process stanzas that come in after disconnect - if (this.disconnecting || !this.connected) { - return; - } - - // an error occurred - cond = elem.getAttribute("condition"); - conflict = elem.getElementsByTagName("conflict"); - if (cond !== null) { - if (cond == "remote-stream-error" && conflict.length > 0) { - cond = "conflict"; - } - this._changeConnectStatus(Strophe.Status.CONNFAIL, cond); - } else { - this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); - } - this.disconnect('unknown stream-error'); - return; - } - - // send each incoming stanza through the handler chain - var that = this; - Strophe.forEachChild(elem, null, function (child) { - var i, newList; - // process handlers - newList = that.handlers; - that.handlers = []; - for (i = 0; i < newList.length; i++) { - var hand = newList[i]; - // encapsulate 'handler.run' not to lose the whole handler list if - // one of the handlers throws an exception - try { - if (hand.isMatch(child) && - (that.authenticated || !hand.user)) { - if (hand.run(child)) { - that.handlers.push(hand); - } - } else { - that.handlers.push(hand); - } - } catch(e) { - // if the handler throws an exception, we consider it as false - Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message); - } - } - }); - }, - - - /** Attribute: mechanisms - * SASL Mechanisms available for Conncection. - */ - mechanisms: {}, - - /** PrivateFunction: _connect_cb - * _Private_ handler for initial connection request. - * - * This handler is used to process the initial connection request - * response from the BOSH server. It is used to set up authentication - * handlers and start the authentication process. - * - * SASL authentication will be attempted if available, otherwise - * the code will fall back to legacy authentication. - * - * Parameters: - * (Strophe.Request) req - The current request. - * (Function) _callback - low level (xmpp) connect callback function. - * Useful for plugins with their own xmpp connect callback (when their) - * want to do something special). - */ - _connect_cb: function (req, _callback, raw) - { - Strophe.info("_connect_cb was called"); - - this.connected = true; - - var bodyWrap = this._proto._reqToData(req); - if (!bodyWrap) { return; } - - if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { - if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) { - this.xmlInput(bodyWrap.childNodes[0]); - } else { - this.xmlInput(bodyWrap); - } - } - if (this.rawInput !== Strophe.Connection.prototype.rawInput) { - if (raw) { - this.rawInput(raw); - } else { - this.rawInput(Strophe.serialize(bodyWrap)); - } - } - - var conncheck = this._proto._connect_cb(bodyWrap); - if (conncheck === Strophe.Status.CONNFAIL) { - return; - } - - this._authentication.sasl_scram_sha1 = false; - this._authentication.sasl_plain = false; - this._authentication.sasl_digest_md5 = false; - this._authentication.sasl_anonymous = false; - - this._authentication.legacy_auth = false; - - // Check for the stream:features tag - var hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0; - if (!hasFeatures) { - hasFeatures = bodyWrap.getElementsByTagName("features").length > 0; - } - var mechanisms = bodyWrap.getElementsByTagName("mechanism"); - var matched = []; - var i, mech, found_authentication = false; - if (!hasFeatures) { - this._proto._no_auth_received(_callback); - return; - } - if (mechanisms.length > 0) { - for (i = 0; i < mechanisms.length; i++) { - mech = Strophe.getText(mechanisms[i]); - if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]); - } - } - this._authentication.legacy_auth = - bodyWrap.getElementsByTagName("auth").length > 0; - found_authentication = this._authentication.legacy_auth || - matched.length > 0; - if (!found_authentication) { - this._proto._no_auth_received(_callback); - return; - } - if (this.do_authentication !== false) - this.authenticate(matched); - }, - - /** Function: authenticate - * Set up authentication - * - * Contiunues the initial connection request by setting up authentication - * handlers and start the authentication process. - * - * SASL authentication will be attempted if available, otherwise - * the code will fall back to legacy authentication. - * - */ - authenticate: function (matched) - { - var i; - // Sorting matched mechanisms according to priority. - for (i = 0; i < matched.length - 1; ++i) { - var higher = i; - for (var j = i + 1; j < matched.length; ++j) { - if (matched[j].prototype.priority > matched[higher].prototype.priority) { - higher = j; - } - } - if (higher != i) { - var swap = matched[i]; - matched[i] = matched[higher]; - matched[higher] = swap; - } - } - - // run each mechanism - var mechanism_found = false; - for (i = 0; i < matched.length; ++i) { - if (!matched[i].test(this)) continue; - - this._sasl_success_handler = this._addSysHandler( - this._sasl_success_cb.bind(this), null, - "success", null, null); - this._sasl_failure_handler = this._addSysHandler( - this._sasl_failure_cb.bind(this), null, - "failure", null, null); - this._sasl_challenge_handler = this._addSysHandler( - this._sasl_challenge_cb.bind(this), null, - "challenge", null, null); - - this._sasl_mechanism = new matched[i](); - this._sasl_mechanism.onStart(this); - - var request_auth_exchange = $build("auth", { - xmlns: Strophe.NS.SASL, - mechanism: this._sasl_mechanism.name - }); - - if (this._sasl_mechanism.isClientFirst) { - var response = this._sasl_mechanism.onChallenge(this, null); - request_auth_exchange.t(Base64.encode(response)); - } - - this.send(request_auth_exchange.tree()); - - mechanism_found = true; - break; - } - - if (!mechanism_found) { - // if none of the mechanism worked - if (Strophe.getNodeFromJid(this.jid) === null) { - // we don't have a node, which is required for non-anonymous - // client connections - this._changeConnectStatus(Strophe.Status.CONNFAIL, - 'x-strophe-bad-non-anon-jid'); - this.disconnect('x-strophe-bad-non-anon-jid'); - } else { - // fall back to legacy authentication - this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null); - this._addSysHandler(this._auth1_cb.bind(this), null, null, - null, "_auth_1"); - - this.send($iq({ - type: "get", - to: this.domain, - id: "_auth_1" - }).c("query", { - xmlns: Strophe.NS.AUTH - }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree()); - } - } - - }, - - _sasl_challenge_cb: function(elem) { - var challenge = Base64.decode(Strophe.getText(elem)); - var response = this._sasl_mechanism.onChallenge(this, challenge); - - var stanza = $build('response', { - xmlns: Strophe.NS.SASL - }); - if (response !== "") { - stanza.t(Base64.encode(response)); - } - this.send(stanza.tree()); - - return true; - }, - - /** PrivateFunction: _auth1_cb - * _Private_ handler for legacy authentication. - * - * This handler is called in response to the initial - * for legacy authentication. It builds an authentication and - * sends it, creating a handler (calling back to _auth2_cb()) to - * handle the result - * - * Parameters: - * (XMLElement) elem - The stanza that triggered the callback. - * - * Returns: - * false to remove the handler. - */ - /* jshint unused:false */ - _auth1_cb: function (elem) - { - // build plaintext auth iq - var iq = $iq({type: "set", id: "_auth_2"}) - .c('query', {xmlns: Strophe.NS.AUTH}) - .c('username', {}).t(Strophe.getNodeFromJid(this.jid)) - .up() - .c('password').t(this.pass); - - if (!Strophe.getResourceFromJid(this.jid)) { - // since the user has not supplied a resource, we pick - // a default one here. unlike other auth methods, the server - // cannot do this for us. - this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe'; - } - iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid)); - - this._addSysHandler(this._auth2_cb.bind(this), null, - null, null, "_auth_2"); - - this.send(iq.tree()); - - return false; - }, - /* jshint unused:true */ - - /** PrivateFunction: _sasl_success_cb - * _Private_ handler for succesful SASL authentication. - * - * Parameters: - * (XMLElement) elem - The matching stanza. - * - * Returns: - * false to remove the handler. - */ - _sasl_success_cb: function (elem) - { - if (this._sasl_data["server-signature"]) { - var serverSignature; - var success = Base64.decode(Strophe.getText(elem)); - var attribMatch = /([a-z]+)=([^,]+)(,|$)/; - var matches = success.match(attribMatch); - if (matches[1] == "v") { - serverSignature = matches[2]; - } - - if (serverSignature != this._sasl_data["server-signature"]) { - // remove old handlers - this.deleteHandler(this._sasl_failure_handler); - this._sasl_failure_handler = null; - if (this._sasl_challenge_handler) { - this.deleteHandler(this._sasl_challenge_handler); - this._sasl_challenge_handler = null; - } - - this._sasl_data = {}; - return this._sasl_failure_cb(null); - } - } - - Strophe.info("SASL authentication succeeded."); - - if(this._sasl_mechanism) - this._sasl_mechanism.onSuccess(); - - // remove old handlers - this.deleteHandler(this._sasl_failure_handler); - this._sasl_failure_handler = null; - if (this._sasl_challenge_handler) { - this.deleteHandler(this._sasl_challenge_handler); - this._sasl_challenge_handler = null; - } - - this._addSysHandler(this._sasl_auth1_cb.bind(this), null, - "stream:features", null, null); - - // we must send an xmpp:restart now - this._sendRestart(); - - return false; - }, - - /** PrivateFunction: _sasl_auth1_cb - * _Private_ handler to start stream binding. - * - * Parameters: - * (XMLElement) elem - The matching stanza. - * - * Returns: - * false to remove the handler. - */ - _sasl_auth1_cb: function (elem) - { - // save stream:features for future usage - this.features = elem; - - var i, child; - - for (i = 0; i < elem.childNodes.length; i++) { - child = elem.childNodes[i]; - if (child.nodeName == 'bind') { - this.do_bind = true; - } - - if (child.nodeName == 'session') { - this.do_session = true; - } - } - - if (!this.do_bind) { - this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); - return false; - } else { - this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, - null, "_bind_auth_2"); - - var resource = Strophe.getResourceFromJid(this.jid); - if (resource) { - this.send($iq({type: "set", id: "_bind_auth_2"}) - .c('bind', {xmlns: Strophe.NS.BIND}) - .c('resource', {}).t(resource).tree()); - } else { - this.send($iq({type: "set", id: "_bind_auth_2"}) - .c('bind', {xmlns: Strophe.NS.BIND}) - .tree()); - } - } - - return false; - }, - - /** PrivateFunction: _sasl_bind_cb - * _Private_ handler for binding result and session start. - * - * Parameters: - * (XMLElement) elem - The matching stanza. - * - * Returns: - * false to remove the handler. - */ - _sasl_bind_cb: function (elem) - { - if (elem.getAttribute("type") == "error") { - Strophe.info("SASL binding failed."); - var conflict = elem.getElementsByTagName("conflict"), condition; - if (conflict.length > 0) { - condition = 'conflict'; - } - this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition); - return false; - } - - // TODO - need to grab errors - var bind = elem.getElementsByTagName("bind"); - var jidNode; - if (bind.length > 0) { - // Grab jid - jidNode = bind[0].getElementsByTagName("jid"); - if (jidNode.length > 0) { - this.jid = Strophe.getText(jidNode[0]); - - if (this.do_session) { - this._addSysHandler(this._sasl_session_cb.bind(this), - null, null, null, "_session_auth_2"); - - this.send($iq({type: "set", id: "_session_auth_2"}) - .c('session', {xmlns: Strophe.NS.SESSION}) - .tree()); - } else { - this.authenticated = true; - this._changeConnectStatus(Strophe.Status.CONNECTED, null); - } - } - } else { - Strophe.info("SASL binding failed."); - this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); - return false; - } - }, - - /** PrivateFunction: _sasl_session_cb - * _Private_ handler to finish successful SASL connection. - * - * This sets Connection.authenticated to true on success, which - * starts the processing of user handlers. - * - * Parameters: - * (XMLElement) elem - The matching stanza. - * - * Returns: - * false to remove the handler. - */ - _sasl_session_cb: function (elem) - { - if (elem.getAttribute("type") == "result") { - this.authenticated = true; - this._changeConnectStatus(Strophe.Status.CONNECTED, null); - } else if (elem.getAttribute("type") == "error") { - Strophe.info("Session creation failed."); - this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); - return false; - } - - return false; - }, - - /** PrivateFunction: _sasl_failure_cb - * _Private_ handler for SASL authentication failure. - * - * Parameters: - * (XMLElement) elem - The matching stanza. - * - * Returns: - * false to remove the handler. - */ - /* jshint unused:false */ - _sasl_failure_cb: function (elem) - { - // delete unneeded handlers - if (this._sasl_success_handler) { - this.deleteHandler(this._sasl_success_handler); - this._sasl_success_handler = null; - } - if (this._sasl_challenge_handler) { - this.deleteHandler(this._sasl_challenge_handler); - this._sasl_challenge_handler = null; - } - - if(this._sasl_mechanism) - this._sasl_mechanism.onFailure(); - this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); - return false; - }, - /* jshint unused:true */ - - /** PrivateFunction: _auth2_cb - * _Private_ handler to finish legacy authentication. - * - * This handler is called when the result from the jabber:iq:auth - * stanza is returned. - * - * Parameters: - * (XMLElement) elem - The stanza that triggered the callback. - * - * Returns: - * false to remove the handler. - */ - _auth2_cb: function (elem) - { - if (elem.getAttribute("type") == "result") { - this.authenticated = true; - this._changeConnectStatus(Strophe.Status.CONNECTED, null); - } else if (elem.getAttribute("type") == "error") { - this._changeConnectStatus(Strophe.Status.AUTHFAIL, null); - this.disconnect('authentication failed'); - } - - return false; - }, - - /** PrivateFunction: _addSysTimedHandler - * _Private_ function to add a system level timed handler. - * - * This function is used to add a Strophe.TimedHandler for the - * library code. System timed handlers are allowed to run before - * authentication is complete. - * - * Parameters: - * (Integer) period - The period of the handler. - * (Function) handler - The callback function. - */ - _addSysTimedHandler: function (period, handler) - { - var thand = new Strophe.TimedHandler(period, handler); - thand.user = false; - this.addTimeds.push(thand); - return thand; - }, - - /** PrivateFunction: _addSysHandler - * _Private_ function to add a system level stanza handler. - * - * This function is used to add a Strophe.Handler for the - * library code. System stanza handlers are allowed to run before - * authentication is complete. - * - * Parameters: - * (Function) handler - The callback function. - * (String) ns - The namespace to match. - * (String) name - The stanza name to match. - * (String) type - The stanza type attribute to match. - * (String) id - The stanza id attribute to match. - */ - _addSysHandler: function (handler, ns, name, type, id) - { - var hand = new Strophe.Handler(handler, ns, name, type, id); - hand.user = false; - this.addHandlers.push(hand); - return hand; - }, - - /** PrivateFunction: _onDisconnectTimeout - * _Private_ timeout handler for handling non-graceful disconnection. - * - * If the graceful disconnect process does not complete within the - * time allotted, this handler finishes the disconnect anyway. - * - * Returns: - * false to remove the handler. - */ - _onDisconnectTimeout: function () - { - Strophe.info("_onDisconnectTimeout was called"); - - this._proto._onDisconnectTimeout(); - - // actually disconnect - this._doDisconnect(); - - return false; - }, - - /** PrivateFunction: _onIdle - * _Private_ handler to process events during idle cycle. - * - * This handler is called every 100ms to fire timed handlers that - * are ready and keep poll requests going. - */ - _onIdle: function () - { - var i, thand, since, newList; - - // add timed handlers scheduled for addition - // NOTE: we add before remove in the case a timed handler is - // added and then deleted before the next _onIdle() call. - while (this.addTimeds.length > 0) { - this.timedHandlers.push(this.addTimeds.pop()); - } - - // remove timed handlers that have been scheduled for deletion - while (this.removeTimeds.length > 0) { - thand = this.removeTimeds.pop(); - i = this.timedHandlers.indexOf(thand); - if (i >= 0) { - this.timedHandlers.splice(i, 1); - } - } - - // call ready timed handlers - var now = new Date().getTime(); - newList = []; - for (i = 0; i < this.timedHandlers.length; i++) { - thand = this.timedHandlers[i]; - if (this.authenticated || !thand.user) { - since = thand.lastCalled + thand.period; - if (since - now <= 0) { - if (thand.run()) { - newList.push(thand); - } - } else { - newList.push(thand); - } - } - } - this.timedHandlers = newList; - - clearTimeout(this._idleTimeout); - - this._proto._onIdle(); - - // reactivate the timer only if connected - if (this.connected) { - this._idleTimeout = setTimeout(this._onIdle.bind(this), 100); - } - } -}; - -if (callback) { - callback(Strophe, $build, $msg, $iq, $pres); -} - -/** Class: Strophe.SASLMechanism - * - * encapsulates SASL authentication mechanisms. - * - * User code may override the priority for each mechanism or disable it completely. - * See for information about changing priority and for informatian on - * how to disable a mechanism. - * - * By default, all mechanisms are enabled and the priorities are - * - * SCRAM-SHA1 - 40 - * DIGEST-MD5 - 30 - * Plain - 20 - */ - -/** - * PrivateConstructor: Strophe.SASLMechanism - * SASL auth mechanism abstraction. - * - * Parameters: - * (String) name - SASL Mechanism name. - * (Boolean) isClientFirst - If client should send response first without challenge. - * (Number) priority - Priority. - * - * Returns: - * A new Strophe.SASLMechanism object. - */ -Strophe.SASLMechanism = function(name, isClientFirst, priority) { - /** PrivateVariable: name - * Mechanism name. - */ - this.name = name; - /** PrivateVariable: isClientFirst - * If client sends response without initial server challenge. - */ - this.isClientFirst = isClientFirst; - /** Variable: priority - * Determines which is chosen for authentication (Higher is better). - * Users may override this to prioritize mechanisms differently. - * - * In the default configuration the priorities are - * - * SCRAM-SHA1 - 40 - * DIGEST-MD5 - 30 - * Plain - 20 - * - * Example: (This will cause Strophe to choose the mechanism that the server sent first) - * - * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority; - * - * See for a list of available mechanisms. - * - */ - this.priority = priority; -}; - -Strophe.SASLMechanism.prototype = { - /** - * Function: test - * Checks if mechanism able to run. - * To disable a mechanism, make this return false; - * - * To disable plain authentication run - * > Strophe.SASLPlain.test = function() { - * > return false; - * > } - * - * See for a list of available mechanisms. - * - * Parameters: - * (Strophe.Connection) connection - Target Connection. - * - * Returns: - * (Boolean) If mechanism was able to run. - */ - /* jshint unused:false */ - test: function(connection) { - return true; - }, - /* jshint unused:true */ - - /** PrivateFunction: onStart - * Called before starting mechanism on some connection. - * - * Parameters: - * (Strophe.Connection) connection - Target Connection. - */ - onStart: function(connection) - { - this._connection = connection; - }, - - /** PrivateFunction: onChallenge - * Called by protocol implementation on incoming challenge. If client is - * first (isClientFirst == true) challenge will be null on the first call. - * - * Parameters: - * (Strophe.Connection) connection - Target Connection. - * (String) challenge - current challenge to handle. - * - * Returns: - * (String) Mechanism response. - */ - /* jshint unused:false */ - onChallenge: function(connection, challenge) { - throw new Error("You should implement challenge handling!"); - }, - /* jshint unused:true */ - - /** PrivateFunction: onFailure - * Protocol informs mechanism implementation about SASL failure. - */ - onFailure: function() { - this._connection = null; - }, - - /** PrivateFunction: onSuccess - * Protocol informs mechanism implementation about SASL success. - */ - onSuccess: function() { - this._connection = null; - } -}; - - /** Constants: SASL mechanisms - * Available authentication mechanisms - * - * Strophe.SASLAnonymous - SASL Anonymous authentication. - * Strophe.SASLPlain - SASL Plain authentication. - * Strophe.SASLMD5 - SASL Digest-MD5 authentication - * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication - */ - -// Building SASL callbacks - -/** PrivateConstructor: SASLAnonymous - * SASL Anonymous authentication. - */ -Strophe.SASLAnonymous = function() {}; - -Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 10); - -Strophe.SASLAnonymous.test = function(connection) { - return connection.authcid === null; -}; - -Strophe.Connection.prototype.mechanisms[Strophe.SASLAnonymous.prototype.name] = Strophe.SASLAnonymous; - -/** PrivateConstructor: SASLPlain - * SASL Plain authentication. - */ -Strophe.SASLPlain = function() {}; - -Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 20); - -Strophe.SASLPlain.test = function(connection) { - return connection.authcid !== null; -}; - -Strophe.SASLPlain.prototype.onChallenge = function(connection) { - var auth_str = connection.authzid; - auth_str = auth_str + "\u0000"; - auth_str = auth_str + connection.authcid; - auth_str = auth_str + "\u0000"; - auth_str = auth_str + connection.pass; - return auth_str; -}; - -Strophe.Connection.prototype.mechanisms[Strophe.SASLPlain.prototype.name] = Strophe.SASLPlain; - -/** PrivateConstructor: SASLSHA1 - * SASL SCRAM SHA 1 authentication. - */ -Strophe.SASLSHA1 = function() {}; - -/* TEST: - * This is a simple example of a SCRAM-SHA-1 authentication exchange - * when the client doesn't support channel bindings (username 'user' and - * password 'pencil' are used): - * - * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL - * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92, - * i=4096 - * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j, - * p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts= - * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ= - * - */ - -Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 40); - -Strophe.SASLSHA1.test = function(connection) { - return connection.authcid !== null; -}; - -Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) { - var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890); - - var auth_str = "n=" + connection.authcid; - auth_str += ",r="; - auth_str += cnonce; - - connection._sasl_data.cnonce = cnonce; - connection._sasl_data["client-first-message-bare"] = auth_str; - - auth_str = "n,," + auth_str; - - this.onChallenge = function (connection, challenge) - { - var nonce, salt, iter, Hi, U, U_old, i, k; - var clientKey, serverKey, clientSignature; - var responseText = "c=biws,"; - var authMessage = connection._sasl_data["client-first-message-bare"] + "," + - challenge + ","; - var cnonce = connection._sasl_data.cnonce; - var attribMatch = /([a-z]+)=([^,]+)(,|$)/; - - while (challenge.match(attribMatch)) { - var matches = challenge.match(attribMatch); - challenge = challenge.replace(matches[0], ""); - switch (matches[1]) { - case "r": - nonce = matches[2]; - break; - case "s": - salt = matches[2]; - break; - case "i": - iter = matches[2]; - break; - } - } - - if (nonce.substr(0, cnonce.length) !== cnonce) { - connection._sasl_data = {}; - return connection._sasl_failure_cb(); - } - - responseText += "r=" + nonce; - authMessage += responseText; - - salt = Base64.decode(salt); - salt += "\x00\x00\x00\x01"; - - Hi = U_old = core_hmac_sha1(connection.pass, salt); - for (i = 1; i < iter; i++) { - U = core_hmac_sha1(connection.pass, binb2str(U_old)); - for (k = 0; k < 5; k++) { - Hi[k] ^= U[k]; - } - U_old = U; - } - Hi = binb2str(Hi); - - clientKey = core_hmac_sha1(Hi, "Client Key"); - serverKey = str_hmac_sha1(Hi, "Server Key"); - clientSignature = core_hmac_sha1(str_sha1(binb2str(clientKey)), authMessage); - connection._sasl_data["server-signature"] = b64_hmac_sha1(serverKey, authMessage); - - for (k = 0; k < 5; k++) { - clientKey[k] ^= clientSignature[k]; - } - - responseText += ",p=" + Base64.encode(binb2str(clientKey)); - - return responseText; - }.bind(this); - - return auth_str; -}; - -Strophe.Connection.prototype.mechanisms[Strophe.SASLSHA1.prototype.name] = Strophe.SASLSHA1; - -/** PrivateConstructor: SASLMD5 - * SASL DIGEST MD5 authentication. - */ -Strophe.SASLMD5 = function() {}; - -Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 30); - -Strophe.SASLMD5.test = function(connection) { - return connection.authcid !== null; -}; - -/** PrivateFunction: _quote - * _Private_ utility function to backslash escape and quote strings. - * - * Parameters: - * (String) str - The string to be quoted. - * - * Returns: - * quoted string - */ -Strophe.SASLMD5.prototype._quote = function (str) - { - return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; - //" end string workaround for emacs - }; - - -Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) { - var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; - var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890)); - var realm = ""; - var host = null; - var nonce = ""; - var qop = ""; - var matches; - - while (challenge.match(attribMatch)) { - matches = challenge.match(attribMatch); - challenge = challenge.replace(matches[0], ""); - matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); - switch (matches[1]) { - case "realm": - realm = matches[2]; - break; - case "nonce": - nonce = matches[2]; - break; - case "qop": - qop = matches[2]; - break; - case "host": - host = matches[2]; - break; - } - } - - var digest_uri = connection.servtype + "/" + connection.domain; - if (host !== null) { - digest_uri = digest_uri + "/" + host; - } - - var A1 = MD5.hash(connection.authcid + - ":" + realm + ":" + this._connection.pass) + - ":" + nonce + ":" + cnonce; - var A2 = 'AUTHENTICATE:' + digest_uri; - - var responseText = ""; - responseText += 'charset=utf-8,'; - responseText += 'username=' + - this._quote(connection.authcid) + ','; - responseText += 'realm=' + this._quote(realm) + ','; - responseText += 'nonce=' + this._quote(nonce) + ','; - responseText += 'nc=00000001,'; - responseText += 'cnonce=' + this._quote(cnonce) + ','; - responseText += 'digest-uri=' + this._quote(digest_uri) + ','; - responseText += 'response=' + MD5.hexdigest(MD5.hexdigest(A1) + ":" + - nonce + ":00000001:" + - cnonce + ":auth:" + - MD5.hexdigest(A2)) + ","; - responseText += 'qop=auth'; - - this.onChallenge = function () - { - return ""; - }.bind(this); - - return responseText; -}; - -Strophe.Connection.prototype.mechanisms[Strophe.SASLMD5.prototype.name] = Strophe.SASLMD5; - -})(function () { - window.Strophe = arguments[0]; - window.$build = arguments[1]; - window.$msg = arguments[2]; - window.$iq = arguments[3]; - window.$pres = arguments[4]; -}); - -/* - This program is distributed under the terms of the MIT license. - Please see the LICENSE file for details. - - Copyright 2006-2008, OGG, LLC -*/ - -/* jshint undef: true, unused: true:, noarg: true, latedef: true */ -/*global window, setTimeout, clearTimeout, - XMLHttpRequest, ActiveXObject, - Strophe, $build */ - - -/** PrivateClass: Strophe.Request - * _Private_ helper class that provides a cross implementation abstraction - * for a BOSH related XMLHttpRequest. - * - * The Strophe.Request class is used internally to encapsulate BOSH request - * information. It is not meant to be used from user's code. - */ - -/** PrivateConstructor: Strophe.Request - * Create and initialize a new Strophe.Request object. - * - * Parameters: - * (XMLElement) elem - The XML data to be sent in the request. - * (Function) func - The function that will be called when the - * XMLHttpRequest readyState changes. - * (Integer) rid - The BOSH rid attribute associated with this request. - * (Integer) sends - The number of times this same request has been - * sent. - */ -Strophe.Request = function (elem, func, rid, sends) -{ - this.id = ++Strophe._requestId; - this.xmlData = elem; - this.data = Strophe.serialize(elem); - // save original function in case we need to make a new request - // from this one. - this.origFunc = func; - this.func = func; - this.rid = rid; - this.date = NaN; - this.sends = sends || 0; - this.abort = false; - this.dead = null; - - this.age = function () { - if (!this.date) { return 0; } - var now = new Date(); - return (now - this.date) / 1000; - }; - this.timeDead = function () { - if (!this.dead) { return 0; } - var now = new Date(); - return (now - this.dead) / 1000; - }; - this.xhr = this._newXHR(); -}; - -Strophe.Request.prototype = { - /** PrivateFunction: getResponse - * Get a response from the underlying XMLHttpRequest. - * - * This function attempts to get a response from the request and checks - * for errors. - * - * Throws: - * "parsererror" - A parser error occured. - * - * Returns: - * The DOM element tree of the response. - */ - getResponse: function () - { - var node = null; - if (this.xhr.responseXML && this.xhr.responseXML.documentElement) { - node = this.xhr.responseXML.documentElement; - if (node.tagName == "parsererror") { - Strophe.error("invalid response received"); - Strophe.error("responseText: " + this.xhr.responseText); - Strophe.error("responseXML: " + - Strophe.serialize(this.xhr.responseXML)); - throw "parsererror"; - } - } else if (this.xhr.responseText) { - Strophe.error("invalid response received"); - Strophe.error("responseText: " + this.xhr.responseText); - Strophe.error("responseXML: " + - Strophe.serialize(this.xhr.responseXML)); - } - - return node; - }, - - /** PrivateFunction: _newXHR - * _Private_ helper function to create XMLHttpRequests. - * - * This function creates XMLHttpRequests across all implementations. - * - * Returns: - * A new XMLHttpRequest. - */ - _newXHR: function () - { - var xhr = null; - if (window.XMLHttpRequest) { - xhr = new XMLHttpRequest(); - if (xhr.overrideMimeType) { - xhr.overrideMimeType("text/xml"); - } - } else if (window.ActiveXObject) { - xhr = new ActiveXObject("Microsoft.XMLHTTP"); - } - - // use Function.bind() to prepend ourselves as an argument - xhr.onreadystatechange = this.func.bind(null, this); - - return xhr; - } -}; - -/** Class: Strophe.Bosh - * _Private_ helper class that handles BOSH Connections - * - * The Strophe.Bosh class is used internally by Strophe.Connection - * to encapsulate BOSH sessions. It is not meant to be used from user's code. - */ - -/** File: bosh.js - * A JavaScript library to enable BOSH in Strophejs. - * - * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH) - * to emulate a persistent, stateful, two-way connection to an XMPP server. - * More information on BOSH can be found in XEP 124. - */ - -/** PrivateConstructor: Strophe.Bosh - * Create and initialize a Strophe.Bosh object. - * - * Parameters: - * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH. - * - * Returns: - * A new Strophe.Bosh object. - */ -Strophe.Bosh = function(connection) { - this._conn = connection; - /* request id for body tags */ - this.rid = Math.floor(Math.random() * 4294967295); - /* The current session ID. */ - this.sid = null; - - // default BOSH values - this.hold = 1; - this.wait = 60; - this.window = 5; - - this._requests = []; -}; - -Strophe.Bosh.prototype = { - /** Variable: strip - * - * BOSH-Connections will have all stanzas wrapped in a tag when - * passed to or . - * To strip this tag, User code can set to "body": - * - * > Strophe.Bosh.prototype.strip = "body"; - * - * This will enable stripping of the body tag in both - * and . - */ - strip: null, - - /** PrivateFunction: _buildBody - * _Private_ helper function to generate the wrapper for BOSH. - * - * Returns: - * A Strophe.Builder with a element. - */ - _buildBody: function () - { - var bodyWrap = $build('body', { - rid: this.rid++, - xmlns: Strophe.NS.HTTPBIND - }); - - if (this.sid !== null) { - bodyWrap.attrs({sid: this.sid}); - } - - return bodyWrap; - }, - - /** PrivateFunction: _reset - * Reset the connection. - * - * This function is called by the reset function of the Strophe Connection - */ - _reset: function () - { - this.rid = Math.floor(Math.random() * 4294967295); - this.sid = null; - }, - - /** PrivateFunction: _connect - * _Private_ function that initializes the BOSH connection. - * - * Creates and sends the Request that initializes the BOSH connection. - */ - _connect: function (wait, hold, route) - { - this.wait = wait || this.wait; - this.hold = hold || this.hold; - - // build the body tag - var body = this._buildBody().attrs({ - to: this._conn.domain, - "xml:lang": "en", - wait: this.wait, - hold: this.hold, - content: "text/xml; charset=utf-8", - ver: "1.6", - "xmpp:version": "1.0", - "xmlns:xmpp": Strophe.NS.BOSH - }); - - if(route){ - body.attrs({ - route: route - }); - } - - var _connect_cb = this._conn._connect_cb; - - this._requests.push( - new Strophe.Request(body.tree(), - this._onRequestStateChange.bind( - this, _connect_cb.bind(this._conn)), - body.tree().getAttribute("rid"))); - this._throttledRequestHandler(); - }, - - /** PrivateFunction: _attach - * Attach to an already created and authenticated BOSH session. - * - * This function is provided to allow Strophe to attach to BOSH - * sessions which have been created externally, perhaps by a Web - * application. This is often used to support auto-login type features - * without putting user credentials into the page. - * - * Parameters: - * (String) jid - The full JID that is bound by the session. - * (String) sid - The SID of the BOSH session. - * (String) rid - The current RID of the BOSH session. This RID - * will be used by the next request. - * (Function) callback The connect callback function. - * (Integer) wait - The optional HTTPBIND wait value. This is the - * time the server will wait before returning an empty result for - * a request. The default setting of 60 seconds is recommended. - * Other settings will require tweaks to the Strophe.TIMEOUT value. - * (Integer) hold - The optional HTTPBIND hold value. This is the - * number of connections the server will hold at one time. This - * should almost always be set to 1 (the default). - * (Integer) wind - The optional HTTBIND window value. This is the - * allowed range of request ids that are valid. The default is 5. - */ - _attach: function (jid, sid, rid, callback, wait, hold, wind) - { - this._conn.jid = jid; - this.sid = sid; - this.rid = rid; - - this._conn.connect_callback = callback; - - this._conn.domain = Strophe.getDomainFromJid(this._conn.jid); - - this._conn.authenticated = true; - this._conn.connected = true; - - this.wait = wait || this.wait; - this.hold = hold || this.hold; - this.window = wind || this.window; - - this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null); - }, - - /** PrivateFunction: _connect_cb - * _Private_ handler for initial connection request. - * - * This handler is used to process the Bosh-part of the initial request. - * Parameters: - * (Strophe.Request) bodyWrap - The received stanza. - */ - _connect_cb: function (bodyWrap) - { - var typ = bodyWrap.getAttribute("type"); - var cond, conflict; - if (typ !== null && typ == "terminate") { - // an error occurred - Strophe.error("BOSH-Connection failed: " + cond); - cond = bodyWrap.getAttribute("condition"); - conflict = bodyWrap.getElementsByTagName("conflict"); - if (cond !== null) { - if (cond == "remote-stream-error" && conflict.length > 0) { - cond = "conflict"; - } - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond); - } else { - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown"); - } - this._conn._doDisconnect(); - return Strophe.Status.CONNFAIL; - } - - // check to make sure we don't overwrite these if _connect_cb is - // called multiple times in the case of missing stream:features - if (!this.sid) { - this.sid = bodyWrap.getAttribute("sid"); - } - var wind = bodyWrap.getAttribute('requests'); - if (wind) { this.window = parseInt(wind, 10); } - var hold = bodyWrap.getAttribute('hold'); - if (hold) { this.hold = parseInt(hold, 10); } - var wait = bodyWrap.getAttribute('wait'); - if (wait) { this.wait = parseInt(wait, 10); } - }, - - /** PrivateFunction: _disconnect - * _Private_ part of Connection.disconnect for Bosh - * - * Parameters: - * (Request) pres - This stanza will be sent before disconnecting. - */ - _disconnect: function (pres) - { - this._sendTerminate(pres); - }, - - /** PrivateFunction: _doDisconnect - * _Private_ function to disconnect. - * - * Resets the SID and RID. - */ - _doDisconnect: function () - { - this.sid = null; - this.rid = Math.floor(Math.random() * 4294967295); - }, - - /** PrivateFunction: _emptyQueue - * _Private_ function to check if the Request queue is empty. - * - * Returns: - * True, if there are no Requests queued, False otherwise. - */ - _emptyQueue: function () - { - return this._requests.length === 0; - }, - - /** PrivateFunction: _hitError - * _Private_ function to handle the error count. - * - * Requests are resent automatically until their error count reaches - * 5. Each time an error is encountered, this function is called to - * increment the count and disconnect if the count is too high. - * - * Parameters: - * (Integer) reqStatus - The request status. - */ - _hitError: function (reqStatus) - { - this.errors++; - Strophe.warn("request errored, status: " + reqStatus + - ", number of errors: " + this.errors); - if (this.errors > 4) { - this._onDisconnectTimeout(); - } - }, - - /** PrivateFunction: _no_auth_received - * - * Called on stream start/restart when no stream:features - * has been received and sends a blank poll request. - */ - _no_auth_received: function (_callback) - { - if (_callback) { - _callback = _callback.bind(this._conn); - } else { - _callback = this._conn._connect_cb.bind(this._conn); - } - var body = this._buildBody(); - this._requests.push( - new Strophe.Request(body.tree(), - this._onRequestStateChange.bind( - this, _callback.bind(this._conn)), - body.tree().getAttribute("rid"))); - this._throttledRequestHandler(); - }, - - /** PrivateFunction: _onDisconnectTimeout - * _Private_ timeout handler for handling non-graceful disconnection. - * - * Cancels all remaining Requests and clears the queue. - */ - _onDisconnectTimeout: function () - { - var req; - while (this._requests.length > 0) { - req = this._requests.pop(); - req.abort = true; - req.xhr.abort(); - // jslint complains, but this is fine. setting to empty func - // is necessary for IE6 - req.xhr.onreadystatechange = function () {}; // jshint ignore:line - } - }, - - /** PrivateFunction: _onIdle - * _Private_ handler called by Strophe.Connection._onIdle - * - * Sends all queued Requests or polls with empty Request if there are none. - */ - _onIdle: function () { - var data = this._conn._data; - - // if no requests are in progress, poll - if (this._conn.authenticated && this._requests.length === 0 && - data.length === 0 && !this._conn.disconnecting) { - Strophe.info("no requests during idle cycle, sending " + - "blank request"); - data.push(null); - } - - if (this._requests.length < 2 && data.length > 0 && - !this._conn.paused) { - var body = this._buildBody(); - for (var i = 0; i < data.length; i++) { - if (data[i] !== null) { - if (data[i] === "restart") { - body.attrs({ - to: this._conn.domain, - "xml:lang": "en", - "xmpp:restart": "true", - "xmlns:xmpp": Strophe.NS.BOSH - }); - } else { - body.cnode(data[i]).up(); - } - } - } - delete this._conn._data; - this._conn._data = []; - this._requests.push( - new Strophe.Request(body.tree(), - this._onRequestStateChange.bind( - this, this._conn._dataRecv.bind(this._conn)), - body.tree().getAttribute("rid"))); - this._processRequest(this._requests.length - 1); - } - - if (this._requests.length > 0) { - var time_elapsed = this._requests[0].age(); - if (this._requests[0].dead !== null) { - if (this._requests[0].timeDead() > - Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) { - this._throttledRequestHandler(); - } - } - - if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) { - Strophe.warn("Request " + - this._requests[0].id + - " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + - " seconds since last activity"); - this._throttledRequestHandler(); - } - } - }, - - /** PrivateFunction: _onRequestStateChange - * _Private_ handler for Strophe.Request state changes. - * - * This function is called when the XMLHttpRequest readyState changes. - * It contains a lot of error handling logic for the many ways that - * requests can fail, and calls the request callback when requests - * succeed. - * - * Parameters: - * (Function) func - The handler for the request. - * (Strophe.Request) req - The request that is changing readyState. - */ - _onRequestStateChange: function (func, req) - { - Strophe.debug("request id " + req.id + - "." + req.sends + " state changed to " + - req.xhr.readyState); - - if (req.abort) { - req.abort = false; - return; - } - - // request complete - var reqStatus; - if (req.xhr.readyState == 4) { - reqStatus = 0; - try { - reqStatus = req.xhr.status; - } catch (e) { - // ignore errors from undefined status attribute. works - // around a browser bug - } - - if (typeof(reqStatus) == "undefined") { - reqStatus = 0; - } - - if (this.disconnecting) { - if (reqStatus >= 400) { - this._hitError(reqStatus); - return; - } - } - - var reqIs0 = (this._requests[0] == req); - var reqIs1 = (this._requests[1] == req); - - if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) { - // remove from internal queue - this._removeRequest(req); - Strophe.debug("request id " + - req.id + - " should now be removed"); - } - - // request succeeded - if (reqStatus == 200) { - // if request 1 finished, or request 0 finished and request - // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to - // restart the other - both will be in the first spot, as the - // completed request has been removed from the queue already - if (reqIs1 || - (reqIs0 && this._requests.length > 0 && - this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))) { - this._restartRequest(0); - } - // call handler - Strophe.debug("request id " + - req.id + "." + - req.sends + " got 200"); - func(req); - this.errors = 0; - } else { - Strophe.error("request id " + - req.id + "." + - req.sends + " error " + reqStatus + - " happened"); - if (reqStatus === 0 || - (reqStatus >= 400 && reqStatus < 600) || - reqStatus >= 12000) { - this._hitError(reqStatus); - if (reqStatus >= 400 && reqStatus < 500) { - this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, - null); - this._conn._doDisconnect(); - } - } - } - - if (!((reqStatus > 0 && reqStatus < 500) || - req.sends > 5)) { - this._throttledRequestHandler(); - } - } - }, - - /** PrivateFunction: _processRequest - * _Private_ function to process a request in the queue. - * - * This function takes requests off the queue and sends them and - * restarts dead requests. - * - * Parameters: - * (Integer) i - The index of the request in the queue. - */ - _processRequest: function (i) - { - var self = this; - var req = this._requests[i]; - var reqStatus = -1; - - try { - if (req.xhr.readyState == 4) { - reqStatus = req.xhr.status; - } - } catch (e) { - Strophe.error("caught an error in _requests[" + i + - "], reqStatus: " + reqStatus); - } - - if (typeof(reqStatus) == "undefined") { - reqStatus = -1; - } - - // make sure we limit the number of retries - if (req.sends > this.maxRetries) { - this._onDisconnectTimeout(); - return; - } - - var time_elapsed = req.age(); - var primaryTimeout = (!isNaN(time_elapsed) && - time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)); - var secondaryTimeout = (req.dead !== null && - req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)); - var requestCompletedWithServerError = (req.xhr.readyState == 4 && - (reqStatus < 1 || - reqStatus >= 500)); - if (primaryTimeout || secondaryTimeout || - requestCompletedWithServerError) { - if (secondaryTimeout) { - Strophe.error("Request " + - this._requests[i].id + - " timed out (secondary), restarting"); - } - req.abort = true; - req.xhr.abort(); - // setting to null fails on IE6, so set to empty function - req.xhr.onreadystatechange = function () {}; - this._requests[i] = new Strophe.Request(req.xmlData, - req.origFunc, - req.rid, - req.sends); - req = this._requests[i]; - } - - if (req.xhr.readyState === 0) { - Strophe.debug("request id " + req.id + - "." + req.sends + " posting"); - - try { - req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true); - } catch (e2) { - Strophe.error("XHR open failed."); - if (!this._conn.connected) { - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, - "bad-service"); - } - this._conn.disconnect(); - return; - } - - // Fires the XHR request -- may be invoked immediately - // or on a gradually expanding retry window for reconnects - var sendFunc = function () { - req.date = new Date(); - if (self._conn.options.customHeaders){ - var headers = self._conn.options.customHeaders; - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - req.xhr.setRequestHeader(header, headers[header]); - } - } - } - req.xhr.send(req.data); - }; - - // Implement progressive backoff for reconnects -- - // First retry (send == 1) should also be instantaneous - if (req.sends > 1) { - // Using a cube of the retry number creates a nicely - // expanding retry window - var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), - Math.pow(req.sends, 3)) * 1000; - setTimeout(sendFunc, backoff); - } else { - sendFunc(); - } - - req.sends++; - - if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) { - if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) { - this._conn.xmlOutput(req.xmlData.childNodes[0]); - } else { - this._conn.xmlOutput(req.xmlData); - } - } - if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) { - this._conn.rawOutput(req.data); - } - } else { - Strophe.debug("_processRequest: " + - (i === 0 ? "first" : "second") + - " request has readyState of " + - req.xhr.readyState); - } - }, - - /** PrivateFunction: _removeRequest - * _Private_ function to remove a request from the queue. - * - * Parameters: - * (Strophe.Request) req - The request to remove. - */ - _removeRequest: function (req) - { - Strophe.debug("removing request"); - - var i; - for (i = this._requests.length - 1; i >= 0; i--) { - if (req == this._requests[i]) { - this._requests.splice(i, 1); - } - } - - // IE6 fails on setting to null, so set to empty function - req.xhr.onreadystatechange = function () {}; - - this._throttledRequestHandler(); - }, - - /** PrivateFunction: _restartRequest - * _Private_ function to restart a request that is presumed dead. - * - * Parameters: - * (Integer) i - The index of the request in the queue. - */ - _restartRequest: function (i) - { - var req = this._requests[i]; - if (req.dead === null) { - req.dead = new Date(); - } - - this._processRequest(i); - }, - - /** PrivateFunction: _reqToData - * _Private_ function to get a stanza out of a request. - * - * Tries to extract a stanza out of a Request Object. - * When this fails the current connection will be disconnected. - * - * Parameters: - * (Object) req - The Request. - * - * Returns: - * The stanza that was passed. - */ - _reqToData: function (req) - { - try { - return req.getResponse(); - } catch (e) { - if (e != "parsererror") { throw e; } - this._conn.disconnect("strophe-parsererror"); - } - }, - - /** PrivateFunction: _sendTerminate - * _Private_ function to send initial disconnect sequence. - * - * This is the first step in a graceful disconnect. It sends - * the BOSH server a terminate body and includes an unavailable - * presence if authentication has completed. - */ - _sendTerminate: function (pres) - { - Strophe.info("_sendTerminate was called"); - var body = this._buildBody().attrs({type: "terminate"}); - - if (pres) { - body.cnode(pres.tree()); - } - - var req = new Strophe.Request(body.tree(), - this._onRequestStateChange.bind( - this, this._conn._dataRecv.bind(this._conn)), - body.tree().getAttribute("rid")); - - this._requests.push(req); - this._throttledRequestHandler(); - }, - - /** PrivateFunction: _send - * _Private_ part of the Connection.send function for BOSH - * - * Just triggers the RequestHandler to send the messages that are in the queue - */ - _send: function () { - clearTimeout(this._conn._idleTimeout); - this._throttledRequestHandler(); - this._conn._idleTimeout = setTimeout(this._conn._onIdle.bind(this._conn), 100); - }, - - /** PrivateFunction: _sendRestart - * - * Send an xmpp:restart stanza. - */ - _sendRestart: function () - { - this._throttledRequestHandler(); - clearTimeout(this._conn._idleTimeout); - }, - - /** PrivateFunction: _throttledRequestHandler - * _Private_ function to throttle requests to the connection window. - * - * This function makes sure we don't send requests so fast that the - * request ids overflow the connection window in the case that one - * request died. - */ - _throttledRequestHandler: function () - { - if (!this._requests) { - Strophe.debug("_throttledRequestHandler called with " + - "undefined requests"); - } else { - Strophe.debug("_throttledRequestHandler called with " + - this._requests.length + " requests"); - } - - if (!this._requests || this._requests.length === 0) { - return; - } - - if (this._requests.length > 0) { - this._processRequest(0); - } - - if (this._requests.length > 1 && - Math.abs(this._requests[0].rid - - this._requests[1].rid) < this.window) { - this._processRequest(1); - } - } -}; - -/* - This program is distributed under the terms of the MIT license. - Please see the LICENSE file for details. - - Copyright 2006-2008, OGG, LLC -*/ - -/* jshint undef: true, unused: true:, noarg: true, latedef: true */ -/*global document, window, clearTimeout, WebSocket, - DOMParser, Strophe, $build */ - -/** Class: Strophe.WebSocket - * _Private_ helper class that handles WebSocket Connections - * - * The Strophe.WebSocket class is used internally by Strophe.Connection - * to encapsulate WebSocket sessions. It is not meant to be used from user's code. - */ - -/** File: websocket.js - * A JavaScript library to enable XMPP over Websocket in Strophejs. - * - * This file implements XMPP over WebSockets for Strophejs. - * If a Connection is established with a Websocket url (ws://...) - * Strophe will use WebSockets. - * For more information on XMPP-over WebSocket see this RFC draft: - * http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00 - * - * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de) - */ - -/** PrivateConstructor: Strophe.Websocket - * Create and initialize a Strophe.WebSocket object. - * Currently only sets the connection Object. - * - * Parameters: - * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets. - * - * Returns: - * A new Strophe.WebSocket object. - */ -Strophe.Websocket = function(connection) { - this._conn = connection; - this.strip = "stream:stream"; - - var service = connection.service; - if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) { - // If the service is not an absolute URL, assume it is a path and put the absolute - // URL together from options, current URL and the path. - var new_service = ""; - - if (connection.options.protocol === "ws" && window.location.protocol !== "https:") { - new_service += "ws"; - } else { - new_service += "wss"; - } - - new_service += "://" + window.location.host; - - if (service.indexOf("/") !== 0) { - new_service += window.location.pathname + service; - } else { - new_service += service; - } - - connection.service = new_service; - } -}; - -Strophe.Websocket.prototype = { - /** PrivateFunction: _buildStream - * _Private_ helper function to generate the start tag for WebSockets - * - * Returns: - * A Strophe.Builder with a element. - */ - _buildStream: function () - { - return $build("stream:stream", { - "to": this._conn.domain, - "xmlns": Strophe.NS.CLIENT, - "xmlns:stream": Strophe.NS.STREAM, - "version": '1.0' - }); - }, - - /** PrivateFunction: _check_streamerror - * _Private_ checks a message for stream:error - * - * Parameters: - * (Strophe.Request) bodyWrap - The received stanza. - * connectstatus - The ConnectStatus that will be set on error. - * Returns: - * true if there was a streamerror, false otherwise. - */ - _check_streamerror: function (bodyWrap, connectstatus) { - var errors = bodyWrap.getElementsByTagName("stream:error"); - if (errors.length === 0) { - return false; - } - var error = errors[0]; - - var condition = ""; - var text = ""; - - var ns = "urn:ietf:params:xml:ns:xmpp-streams"; - for (var i = 0; i < error.childNodes.length; i++) { - var e = error.childNodes[i]; - if (e.getAttribute("xmlns") !== ns) { - break; - } if (e.nodeName === "text") { - text = e.textContent; - } else { - condition = e.nodeName; - } - } - - var errorString = "WebSocket stream error: "; - - if (condition) { - errorString += condition; - } else { - errorString += "unknown"; - } - - if (text) { - errorString += " - " + condition; - } - - Strophe.error(errorString); - - // close the connection on stream_error - this._conn._changeConnectStatus(connectstatus, condition); - this._conn._doDisconnect(); - return true; - }, - - /** PrivateFunction: _reset - * Reset the connection. - * - * This function is called by the reset function of the Strophe Connection. - * Is not needed by WebSockets. - */ - _reset: function () - { - return; - }, - - /** PrivateFunction: _connect - * _Private_ function called by Strophe.Connection.connect - * - * Creates a WebSocket for a connection and assigns Callbacks to it. - * Does nothing if there already is a WebSocket. - */ - _connect: function () { - // Ensure that there is no open WebSocket from a previous Connection. - this._closeSocket(); - - // Create the new WobSocket - this.socket = new WebSocket(this._conn.service, "xmpp"); - this.socket.onopen = this._onOpen.bind(this); - this.socket.onerror = this._onError.bind(this); - this.socket.onclose = this._onClose.bind(this); - this.socket.onmessage = this._connect_cb_wrapper.bind(this); - }, - - /** PrivateFunction: _connect_cb - * _Private_ function called by Strophe.Connection._connect_cb - * - * checks for stream:error - * - * Parameters: - * (Strophe.Request) bodyWrap - The received stanza. - */ - _connect_cb: function(bodyWrap) { - var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL); - if (error) { - return Strophe.Status.CONNFAIL; - } - }, - - /** PrivateFunction: _handleStreamStart - * _Private_ function that checks the opening stream:stream tag for errors. - * - * Disconnects if there is an error and returns false, true otherwise. - * - * Parameters: - * (Node) message - Stanza containing the stream:stream. - */ - _handleStreamStart: function(message) { - var error = false; - // Check for errors in the stream:stream tag - var ns = message.getAttribute("xmlns"); - if (typeof ns !== "string") { - error = "Missing xmlns in stream:stream"; - } else if (ns !== Strophe.NS.CLIENT) { - error = "Wrong xmlns in stream:stream: " + ns; - } - - var ns_stream = message.namespaceURI; - if (typeof ns_stream !== "string") { - error = "Missing xmlns:stream in stream:stream"; - } else if (ns_stream !== Strophe.NS.STREAM) { - error = "Wrong xmlns:stream in stream:stream: " + ns_stream; - } - - var ver = message.getAttribute("version"); - if (typeof ver !== "string") { - error = "Missing version in stream:stream"; - } else if (ver !== "1.0") { - error = "Wrong version in stream:stream: " + ver; - } - - if (error) { - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error); - this._conn._doDisconnect(); - return false; - } - - return true; - }, - - /** PrivateFunction: _connect_cb_wrapper - * _Private_ function that handles the first connection messages. - * - * On receiving an opening stream tag this callback replaces itself with the real - * message handler. On receiving a stream error the connection is terminated. - */ - _connect_cb_wrapper: function(message) { - if (message.data.indexOf("\s*)*/, ""); - if (data === '') return; - - //Make the initial stream:stream selfclosing to parse it without a SAX parser. - data = message.data.replace(//, ""); - - var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement; - this._conn.xmlInput(streamStart); - this._conn.rawInput(message.data); - - //_handleStreamSteart will check for XML errors and disconnect on error - if (this._handleStreamStart(streamStart)) { - - //_connect_cb will check for stream:error and disconnect on error - this._connect_cb(streamStart); - - // ensure received stream:stream is NOT selfclosing and save it for following messages - this.streamStart = message.data.replace(/^$/, ""); - } - } else if (message.data === "") { - this._conn.rawInput(message.data); - this._conn.xmlInput(document.createElement("stream:stream")); - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream"); - this._conn._doDisconnect(); - return; - } else { - this.streamStart = ""; - var string = this._streamWrap(message.data); - var elem = new DOMParser().parseFromString(string, "text/xml").documentElement; - this.socket.onmessage = this._onMessage.bind(this); - this._conn._connect_cb(elem, null, message.data); - } - }, - - /** PrivateFunction: _disconnect - * _Private_ function called by Strophe.Connection.disconnect - * - * Disconnects and sends a last stanza if one is given - * - * Parameters: - * (Request) pres - This stanza will be sent before disconnecting. - */ - _disconnect: function (pres) - { - if (this.socket.readyState !== WebSocket.CLOSED) { - if (pres) { - this._conn.send(pres); - } - var close = ''; - this._conn.xmlOutput(document.createElement("stream:stream")); - this._conn.rawOutput(close); - try { - this.socket.send(close); - } catch (e) { - Strophe.info("Couldn't send closing stream tag."); - } - } - - this._conn._doDisconnect(); - }, - - /** PrivateFunction: _doDisconnect - * _Private_ function to disconnect. - * - * Just closes the Socket for WebSockets - */ - _doDisconnect: function () - { - Strophe.info("WebSockets _doDisconnect was called"); - this._closeSocket(); - }, - - /** PrivateFunction _streamWrap - * _Private_ helper function to wrap a stanza in a tag. - * This is used so Strophe can process stanzas from WebSockets like BOSH - */ - _streamWrap: function (stanza) - { - return this.streamStart + stanza + ''; - }, - - - /** PrivateFunction: _closeSocket - * _Private_ function to close the WebSocket. - * - * Closes the socket if it is still open and deletes it - */ - _closeSocket: function () - { - if (this.socket) { try { - this.socket.close(); - } catch (e) {} } - this.socket = null; - }, - - /** PrivateFunction: _emptyQueue - * _Private_ function to check if the message queue is empty. - * - * Returns: - * True, because WebSocket messages are send immediately after queueing. - */ - _emptyQueue: function () - { - return true; - }, - - /** PrivateFunction: _onClose - * _Private_ function to handle websockets closing. - * - * Nothing to do here for WebSockets - */ - _onClose: function() { - if(this._conn.connected && !this._conn.disconnecting) { - Strophe.error("Websocket closed unexcectedly"); - this._conn._doDisconnect(); - } else { - Strophe.info("Websocket closed"); - } - }, - - /** PrivateFunction: _no_auth_received - * - * Called on stream start/restart when no stream:features - * has been received. - */ - _no_auth_received: function (_callback) - { - Strophe.error("Server did not send any auth methods"); - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods"); - if (_callback) { - _callback = _callback.bind(this._conn); - _callback(); - } - this._conn._doDisconnect(); - }, - - /** PrivateFunction: _onDisconnectTimeout - * _Private_ timeout handler for handling non-graceful disconnection. - * - * This does nothing for WebSockets - */ - _onDisconnectTimeout: function () {}, - - /** PrivateFunction: _onError - * _Private_ function to handle websockets errors. - * - * Parameters: - * (Object) error - The websocket error. - */ - _onError: function(error) { - Strophe.error("Websocket error " + error); - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established was disconnected."); - this._disconnect(); - }, - - /** PrivateFunction: _onIdle - * _Private_ function called by Strophe.Connection._onIdle - * - * sends all queued stanzas - */ - _onIdle: function () { - var data = this._conn._data; - if (data.length > 0 && !this._conn.paused) { - for (var i = 0; i < data.length; i++) { - if (data[i] !== null) { - var stanza, rawStanza; - if (data[i] === "restart") { - stanza = this._buildStream(); - rawStanza = this._removeClosingTag(stanza); - stanza = stanza.tree(); - } else { - stanza = data[i]; - rawStanza = Strophe.serialize(stanza); - } - this._conn.xmlOutput(stanza); - this._conn.rawOutput(rawStanza); - this.socket.send(rawStanza); - } - } - this._conn._data = []; - } - }, - - /** PrivateFunction: _onMessage - * _Private_ function to handle websockets messages. - * - * This function parses each of the messages as if they are full documents. [TODO : We may actually want to use a SAX Push parser]. - * - * Since all XMPP traffic starts with "" - * The first stanza will always fail to be parsed... - * Addtionnaly, the seconds stanza will always be a with the stream NS defined in the previous stanza... so we need to 'force' the inclusion of the NS in this stanza! - * - * Parameters: - * (string) message - The websocket message. - */ - _onMessage: function(message) { - var elem, data; - // check for closing stream - if (message.data === "") { - var close = ""; - this._conn.rawInput(close); - this._conn.xmlInput(document.createElement("stream:stream")); - if (!this._conn.disconnecting) { - this._conn._doDisconnect(); - } - return; - } else if (message.data.search("/, ""); - elem = new DOMParser().parseFromString(data, "text/xml").documentElement; - - if (!this._handleStreamStart(elem)) { - return; - } - } else { - data = this._streamWrap(message.data); - elem = new DOMParser().parseFromString(data, "text/xml").documentElement; - } - - if (this._check_streamerror(elem, Strophe.Status.ERROR)) { - return; - } - - //handle unavailable presence stanza before disconnecting - if (this._conn.disconnecting && - elem.firstChild.nodeName === "presence" && - elem.firstChild.getAttribute("type") === "unavailable") { - this._conn.xmlInput(elem); - this._conn.rawInput(Strophe.serialize(elem)); - // if we are already disconnecting we will ignore the unavailable stanza and - // wait for the tag before we close the connection - return; - } - this._conn._dataRecv(elem, message.data); - }, - - /** PrivateFunction: _onOpen - * _Private_ function to handle websockets connection setup. - * - * The opening stream tag is sent here. - */ - _onOpen: function() { - Strophe.info("Websocket open"); - var start = this._buildStream(); - this._conn.xmlOutput(start.tree()); - - var startString = this._removeClosingTag(start); - this._conn.rawOutput(startString); - this.socket.send(startString); - }, - - /** PrivateFunction: _removeClosingTag - * _Private_ function to Make the first non-selfclosing - * - * Parameters: - * (Object) elem - The tag. - * - * Returns: - * The stream:stream tag as String - */ - _removeClosingTag: function(elem) { - var string = Strophe.serialize(elem); - string = string.replace(/<(stream:stream .*[^\/])\/>$/, "<$1>"); - return string; - }, - - /** PrivateFunction: _reqToData - * _Private_ function to get a stanza out of a request. - * - * WebSockets don't use requests, so the passed argument is just returned. - * - * Parameters: - * (Object) stanza - The stanza. - * - * Returns: - * The stanza that was passed. - */ - _reqToData: function (stanza) - { - return stanza; - }, - - /** PrivateFunction: _send - * _Private_ part of the Connection.send function for WebSocket - * - * Just flushes the messages that are in the queue - */ - _send: function () { - this._conn.flush(); - }, - - /** PrivateFunction: _sendRestart - * - * Send an xmpp:restart stanza. - */ - _sendRestart: function () - { - clearTimeout(this._conn._idleTimeout); - this._conn._onIdle.bind(this._conn)(); - } -}; - -define("strophe", (function (global) { - return function () { - var ret, fn; - return ret || global.Strophe; - }; -}(this))); - -// Generated by CoffeeScript 1.8.0 - -/* - *Plugin to implement the MUC extension. - http://xmpp.org/extensions/xep-0045.html - *Previous Author: - Nathan Zorn - *Complete CoffeeScript rewrite: - Andreas Guth - */ - -(function() { - var Occupant, RoomConfig, XmppRoom, - __hasProp = {}.hasOwnProperty, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - Strophe.addConnectionPlugin('muc', { - _connection: null, - rooms: {}, - roomNames: [], - - /*Function - Initialize the MUC plugin. Sets the correct connection object and - extends the namesace. - */ - init: function(conn) { - this._connection = conn; - this._muc_handler = null; - Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner"); - Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin"); - Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user"); - Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig"); - return Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register"); - }, - - /*Function - Join a multi-user chat room - Parameters: - (String) room - The multi-user chat room to join. - (String) nick - The nickname to use in the chat room. Optional - (Function) msg_handler_cb - The function call to handle messages from the - specified chat room. - (Function) pres_handler_cb - The function call back to handle presence - in the chat room. - (Function) roster_cb - The function call to handle roster info in the chat room - (String) password - The optional password to use. (password protected - rooms only) - (Object) history_attrs - Optional attributes for retrieving history - (XML DOM Element) extended_presence - Optional XML for extending presence - */ - join: function(room, nick, msg_handler_cb, pres_handler_cb, roster_cb, password, history_attrs) { - var msg, room_nick; - room_nick = this.test_append_nick(room, nick); - msg = $pres({ - from: this._connection.jid, - to: room_nick - }).c("x", { - xmlns: Strophe.NS.MUC - }); - if (history_attrs != null) { - msg = msg.c("history", history_attrs).up(); - } - if (password != null) { - msg.cnode(Strophe.xmlElement("password", [], password)); - } - if (typeof extended_presence !== "undefined" && extended_presence !== null) { - msg.up.cnode(extended_presence); - } - if (this._muc_handler == null) { - this._muc_handler = this._connection.addHandler((function(_this) { - return function(stanza) { - var from, handler, handlers, id, roomname, x, xmlns, xquery, _i, _len; - from = stanza.getAttribute('from'); - if (!from) { - return true; - } - roomname = from.split("/")[0]; - if (!_this.rooms[roomname]) { - return true; - } - room = _this.rooms[roomname]; - handlers = {}; - if (stanza.nodeName === "message") { - handlers = room._message_handlers; - } else if (stanza.nodeName === "presence") { - xquery = stanza.getElementsByTagName("x"); - if (xquery.length > 0) { - for (_i = 0, _len = xquery.length; _i < _len; _i++) { - x = xquery[_i]; - xmlns = x.getAttribute("xmlns"); - if (xmlns && xmlns.match(Strophe.NS.MUC)) { - handlers = room._presence_handlers; - break; - } - } - } - } - for (id in handlers) { - handler = handlers[id]; - if (!handler(stanza, room)) { - delete handlers[id]; - } - } - return true; - }; - })(this)); - } - if (!this.rooms.hasOwnProperty(room)) { - this.rooms[room] = new XmppRoom(this, room, nick, password); - this.roomNames.push(room); - } - if (pres_handler_cb) { - this.rooms[room].addHandler('presence', pres_handler_cb); - } - if (msg_handler_cb) { - this.rooms[room].addHandler('message', msg_handler_cb); - } - if (roster_cb) { - this.rooms[room].addHandler('roster', roster_cb); - } - return this._connection.send(msg); - }, - - /*Function - Leave a multi-user chat room - Parameters: - (String) room - The multi-user chat room to leave. - (String) nick - The nick name used in the room. - (Function) handler_cb - Optional function to handle the successful leave. - (String) exit_msg - optional exit message. - Returns: - iqid - The unique id for the room leave. - */ - leave: function(room, nick, handler_cb, exit_msg) { - var id, presence, presenceid, room_nick; - id = this.roomNames.indexOf(room); - delete this.rooms[room]; - if (id >= 0) { - this.roomNames.splice(id, 1); - if (this.roomNames.length === 0) { - this._connection.deleteHandler(this._muc_handler); - this._muc_handler = null; - } - } - room_nick = this.test_append_nick(room, nick); - presenceid = this._connection.getUniqueId(); - presence = $pres({ - type: "unavailable", - id: presenceid, - from: this._connection.jid, - to: room_nick - }); - if (exit_msg != null) { - presence.c("status", exit_msg); - } - if (handler_cb != null) { - this._connection.addHandler(handler_cb, null, "presence", null, presenceid); - } - this._connection.send(presence); - return presenceid; - }, - - /*Function - Parameters: - (String) room - The multi-user chat room name. - (String) nick - The nick name used in the chat room. - (String) message - The plaintext message to send to the room. - (String) html_message - The message to send to the room with html markup. - (String) type - "groupchat" for group chat messages o - "chat" for private chat messages - Returns: - msgiq - the unique id used to send the message - */ - message: function(room, nick, message, html_message, type, msgid) { - var msg, parent, room_nick; - room_nick = this.test_append_nick(room, nick); - type = type || (nick != null ? "chat" : "groupchat"); - msgid = msgid || this._connection.getUniqueId(); - msg = $msg({ - to: room_nick, - from: this._connection.jid, - type: type, - id: msgid - }).c("body", { - xmlns: Strophe.NS.CLIENT - }).t(message); - msg.up(); - if (html_message != null) { - msg.c("html", { - xmlns: Strophe.NS.XHTML_IM - }).c("body", { - xmlns: Strophe.NS.XHTML - }).h(html_message); - if (msg.node.childNodes.length === 0) { - parent = msg.node.parentNode; - msg.up().up(); - msg.node.removeChild(parent); - } else { - msg.up().up(); - } - } - msg.c("x", { - xmlns: "jabber:x:event" - }).c("composing"); - this._connection.send(msg); - return msgid; - }, - - /*Function - Convenience Function to send a Message to all Occupants - Parameters: - (String) room - The multi-user chat room name. - (String) message - The plaintext message to send to the room. - (String) html_message - The message to send to the room with html markup. - (String) msgid - Optional unique ID which will be set as the 'id' attribute of the stanza - Returns: - msgiq - the unique id used to send the message - */ - groupchat: function(room, message, html_message, msgid) { - return this.message(room, null, message, html_message, void 0, msgid); - }, - - /*Function - Send a mediated invitation. - Parameters: - (String) room - The multi-user chat room name. - (String) receiver - The invitation's receiver. - (String) reason - Optional reason for joining the room. - Returns: - msgiq - the unique id used to send the invitation - */ - invite: function(room, receiver, reason) { - var invitation, msgid; - msgid = this._connection.getUniqueId(); - invitation = $msg({ - from: this._connection.jid, - to: room, - id: msgid - }).c('x', { - xmlns: Strophe.NS.MUC_USER - }).c('invite', { - to: receiver - }); - if (reason != null) { - invitation.c('reason', reason); - } - this._connection.send(invitation); - return msgid; - }, - - /*Function - Send a mediated multiple invitation. - Parameters: - (String) room - The multi-user chat room name. - (Array) receivers - The invitation's receivers. - (String) reason - Optional reason for joining the room. - Returns: - msgiq - the unique id used to send the invitation - */ - multipleInvites: function(room, receivers, reason) { - var invitation, msgid, receiver, _i, _len; - msgid = this._connection.getUniqueId(); - invitation = $msg({ - from: this._connection.jid, - to: room, - id: msgid - }).c('x', { - xmlns: Strophe.NS.MUC_USER - }); - for (_i = 0, _len = receivers.length; _i < _len; _i++) { - receiver = receivers[_i]; - invitation.c('invite', { - to: receiver - }); - if (reason != null) { - invitation.c('reason', reason); - invitation.up(); - } - invitation.up(); - } - this._connection.send(invitation); - return msgid; - }, - - /*Function - Send a direct invitation. - Parameters: - (String) room - The multi-user chat room name. - (String) receiver - The invitation's receiver. - (String) reason - Optional reason for joining the room. - (String) password - Optional password for the room. - Returns: - msgiq - the unique id used to send the invitation - */ - directInvite: function(room, receiver, reason, password) { - var attrs, invitation, msgid; - msgid = this._connection.getUniqueId(); - attrs = { - xmlns: 'jabber:x:conference', - jid: room - }; - if (reason != null) { - attrs.reason = reason; - } - if (password != null) { - attrs.password = password; - } - invitation = $msg({ - from: this._connection.jid, - to: receiver, - id: msgid - }).c('x', attrs); - this._connection.send(invitation); - return msgid; - }, - - /*Function - Queries a room for a list of occupants - (String) room - The multi-user chat room name. - (Function) success_cb - Optional function to handle the info. - (Function) error_cb - Optional function to handle an error. - Returns: - id - the unique id used to send the info request - */ - queryOccupants: function(room, success_cb, error_cb) { - var attrs, info; - attrs = { - xmlns: Strophe.NS.DISCO_ITEMS - }; - info = $iq({ - from: this._connection.jid, - to: room, - type: 'get' - }).c('query', attrs); - return this._connection.sendIQ(info, success_cb, error_cb); - }, - - /*Function - Start a room configuration. - Parameters: - (String) room - The multi-user chat room name. - (Function) handler_cb - Optional function to handle the config form. - Returns: - id - the unique id used to send the configuration request - */ - configure: function(room, handler_cb, error_cb) { - var config, stanza; - config = $iq({ - to: room, - type: "get" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }); - stanza = config.tree(); - return this._connection.sendIQ(stanza, handler_cb, error_cb); - }, - - /*Function - Cancel the room configuration - Parameters: - (String) room - The multi-user chat room name. - Returns: - id - the unique id used to cancel the configuration. - */ - cancelConfigure: function(room) { - var config, stanza; - config = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }).c("x", { - xmlns: "jabber:x:data", - type: "cancel" - }); - stanza = config.tree(); - return this._connection.sendIQ(stanza); - }, - - /*Function - Save a room configuration. - Parameters: - (String) room - The multi-user chat room name. - (Array) config- Form Object or an array of form elements used to configure the room. - Returns: - id - the unique id used to save the configuration. - */ - saveConfiguration: function(room, config, success_cb, error_cb) { - var conf, iq, stanza, _i, _len; - iq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }); - if (typeof Form !== "undefined" && config instanceof Form) { - config.type = "submit"; - iq.cnode(config.toXML()); - } else { - iq.c("x", { - xmlns: "jabber:x:data", - type: "submit" - }); - for (_i = 0, _len = config.length; _i < _len; _i++) { - conf = config[_i]; - iq.cnode(conf).up(); - } - } - stanza = iq.tree(); - return this._connection.sendIQ(stanza, success_cb, error_cb); - }, - - /*Function - Parameters: - (String) room - The multi-user chat room name. - Returns: - id - the unique id used to create the chat room. - */ - createInstantRoom: function(room, success_cb, error_cb) { - var roomiq; - roomiq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }).c("x", { - xmlns: "jabber:x:data", - type: "submit" - }); - return this._connection.sendIQ(roomiq.tree(), success_cb, error_cb); - }, - - /*Function - Parameters: - (String) room - The multi-user chat room name. - (Object) config - the configuration. ex: {"muc#roomconfig_publicroom": "0", "muc#roomconfig_persistentroom": "1"} - Returns: - id - the unique id used to create the chat room. - */ - createConfiguredRoom: function(room, config, success_cb, error_cb) { - var k, roomiq, v; - roomiq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }).c("x", { - xmlns: "jabber:x:data", - type: "submit" - }); - roomiq.c('field', { - 'var': 'FORM_TYPE' - }).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up(); - for (k in config) { - if (!__hasProp.call(config, k)) continue; - v = config[k]; - roomiq.c('field', { - 'var': k - }).c('value').t(v).up().up(); - } - return this._connection.sendIQ(roomiq.tree(), success_cb, error_cb); - }, - - /*Function - Set the topic of the chat room. - Parameters: - (String) room - The multi-user chat room name. - (String) topic - Topic message. - */ - setTopic: function(room, topic) { - var msg; - msg = $msg({ - to: room, - from: this._connection.jid, - type: "groupchat" - }).c("subject", { - xmlns: "jabber:client" - }).t(topic); - return this._connection.send(msg.tree()); - }, - - /*Function - Internal Function that Changes the role or affiliation of a member - of a MUC room. This function is used by modifyRole and modifyAffiliation. - The modification can only be done by a room moderator. An error will be - returned if the user doesn't have permission. - Parameters: - (String) room - The multi-user chat room name. - (Object) item - Object with nick and role or jid and affiliation attribute - (String) reason - Optional reason for the change. - (Function) handler_cb - Optional callback for success - (Function) error_cb - Optional callback for error - Returns: - iq - the id of the mode change request. - */ - _modifyPrivilege: function(room, item, reason, handler_cb, error_cb) { - var iq; - iq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_ADMIN - }).cnode(item.node); - if (reason != null) { - iq.c("reason", reason); - } - return this._connection.sendIQ(iq.tree(), handler_cb, error_cb); - }, - - /*Function - Changes the role of a member of a MUC room. - The modification can only be done by a room moderator. An error will be - returned if the user doesn't have permission. - Parameters: - (String) room - The multi-user chat room name. - (String) nick - The nick name of the user to modify. - (String) role - The new role of the user. - (String) affiliation - The new affiliation of the user. - (String) reason - Optional reason for the change. - (Function) handler_cb - Optional callback for success - (Function) error_cb - Optional callback for error - Returns: - iq - the id of the mode change request. - */ - modifyRole: function(room, nick, role, reason, handler_cb, error_cb) { - var item; - item = $build("item", { - nick: nick, - role: role - }); - return this._modifyPrivilege(room, item, reason, handler_cb, error_cb); - }, - kick: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'none', reason, handler_cb, error_cb); - }, - voice: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb); - }, - mute: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'visitor', reason, handler_cb, error_cb); - }, - op: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'moderator', reason, handler_cb, error_cb); - }, - deop: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb); - }, - - /*Function - Changes the affiliation of a member of a MUC room. - The modification can only be done by a room moderator. An error will be - returned if the user doesn't have permission. - Parameters: - (String) room - The multi-user chat room name. - (String) jid - The jid of the user to modify. - (String) affiliation - The new affiliation of the user. - (String) reason - Optional reason for the change. - (Function) handler_cb - Optional callback for success - (Function) error_cb - Optional callback for error - Returns: - iq - the id of the mode change request. - */ - modifyAffiliation: function(room, jid, affiliation, reason, handler_cb, error_cb) { - var item; - item = $build("item", { - jid: jid, - affiliation: affiliation - }); - return this._modifyPrivilege(room, item, reason, handler_cb, error_cb); - }, - ban: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'outcast', reason, handler_cb, error_cb); - }, - member: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'member', reason, handler_cb, error_cb); - }, - revoke: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'none', reason, handler_cb, error_cb); - }, - owner: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'owner', reason, handler_cb, error_cb); - }, - admin: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'admin', reason, handler_cb, error_cb); - }, - - /*Function - Change the current users nick name. - Parameters: - (String) room - The multi-user chat room name. - (String) user - The new nick name. - */ - changeNick: function(room, user) { - var presence, room_nick; - room_nick = this.test_append_nick(room, user); - presence = $pres({ - from: this._connection.jid, - to: room_nick, - id: this._connection.getUniqueId() - }); - return this._connection.send(presence.tree()); - }, - - /*Function - Change the current users status. - Parameters: - (String) room - The multi-user chat room name. - (String) user - The current nick. - (String) show - The new show-text. - (String) status - The new status-text. - */ - setStatus: function(room, user, show, status) { - var presence, room_nick; - room_nick = this.test_append_nick(room, user); - presence = $pres({ - from: this._connection.jid, - to: room_nick - }); - if (show != null) { - presence.c('show', show).up(); - } - if (status != null) { - presence.c('status', status); - } - return this._connection.send(presence.tree()); - }, - - /*Function - Registering with a room. - @see http://xmpp.org/extensions/xep-0045.html#register - Parameters: - (String) room - The multi-user chat room name. - (Function) handle_cb - Function to call for room list return. - (Function) error_cb - Function to call on error. - */ - registrationRequest: function(room, handle_cb, error_cb) { - var iq; - iq = $iq({ - to: room, - from: this._connection.jid, - type: "get" - }).c("query", { - xmlns: Strophe.NS.MUC_REGISTER - }); - return this._connection.sendIQ(iq, function(stanza) { - var $field, $fields, field, fields, length, _i, _len; - $fields = stanza.getElementsByTagName('field'); - length = $fields.length; - fields = { - required: [], - optional: [] - }; - for (_i = 0, _len = $fields.length; _i < _len; _i++) { - $field = $fields[_i]; - field = { - "var": $field.getAttribute('var'), - label: $field.getAttribute('label'), - type: $field.getAttribute('type') - }; - if ($field.getElementsByTagName('required').length > 0) { - fields.required.push(field); - } else { - fields.optional.push(field); - } - } - return handle_cb(fields); - }, error_cb); - }, - - /*Function - Submits registration form. - Parameters: - (String) room - The multi-user chat room name. - (Function) handle_cb - Function to call for room list return. - (Function) error_cb - Function to call on error. - */ - submitRegistrationForm: function(room, fields, handle_cb, error_cb) { - var iq, key, val; - iq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_REGISTER - }); - iq.c("x", { - xmlns: "jabber:x:data", - type: "submit" - }); - iq.c('field', { - 'var': 'FORM_TYPE' - }).c('value').t('http://jabber.org/protocol/muc#register').up().up(); - for (key in fields) { - val = fields[key]; - iq.c('field', { - 'var': key - }).c('value').t(val).up().up(); - } - return this._connection.sendIQ(iq, handle_cb, error_cb); - }, - - /*Function - List all chat room available on a server. - Parameters: - (String) server - name of chat server. - (String) handle_cb - Function to call for room list return. - (String) error_cb - Function to call on error. - */ - listRooms: function(server, handle_cb, error_cb) { - var iq; - iq = $iq({ - to: server, - from: this._connection.jid, - type: "get" - }).c("query", { - xmlns: Strophe.NS.DISCO_ITEMS - }); - return this._connection.sendIQ(iq, handle_cb, error_cb); - }, - test_append_nick: function(room, nick) { - var domain, node; - node = Strophe.escapeNode(Strophe.getNodeFromJid(room)); - domain = Strophe.getDomainFromJid(room); - return node + "@" + domain + (nick != null ? "/" + nick : ""); - } - }); - - XmppRoom = (function() { - function XmppRoom(client, name, nick, password) { - this.client = client; - this.name = name; - this.nick = nick; - this.password = password; - this._roomRosterHandler = __bind(this._roomRosterHandler, this); - this._addOccupant = __bind(this._addOccupant, this); - this.roster = {}; - this._message_handlers = {}; - this._presence_handlers = {}; - this._roster_handlers = {}; - this._handler_ids = 0; - if (client.muc) { - this.client = client.muc; - } - this.name = Strophe.getBareJidFromJid(name); - this.addHandler('presence', this._roomRosterHandler); - } - - XmppRoom.prototype.join = function(msg_handler_cb, pres_handler_cb, roster_cb) { - return this.client.join(this.name, this.nick, msg_handler_cb, pres_handler_cb, roster_cb, this.password); - }; - - XmppRoom.prototype.leave = function(handler_cb, message) { - this.client.leave(this.name, this.nick, handler_cb, message); - return delete this.client.rooms[this.name]; - }; - - XmppRoom.prototype.message = function(nick, message, html_message, type) { - return this.client.message(this.name, nick, message, html_message, type); - }; - - XmppRoom.prototype.groupchat = function(message, html_message) { - return this.client.groupchat(this.name, message, html_message); - }; - - XmppRoom.prototype.invite = function(receiver, reason) { - return this.client.invite(this.name, receiver, reason); - }; - - XmppRoom.prototype.multipleInvites = function(receivers, reason) { - return this.client.invite(this.name, receivers, reason); - }; - - XmppRoom.prototype.directInvite = function(receiver, reason) { - return this.client.directInvite(this.name, receiver, reason, this.password); - }; - - XmppRoom.prototype.configure = function(handler_cb) { - return this.client.configure(this.name, handler_cb); - }; - - XmppRoom.prototype.cancelConfigure = function() { - return this.client.cancelConfigure(this.name); - }; - - XmppRoom.prototype.saveConfiguration = function(config) { - return this.client.saveConfiguration(this.name, config); - }; - - XmppRoom.prototype.queryOccupants = function(success_cb, error_cb) { - return this.client.queryOccupants(this.name, success_cb, error_cb); - }; - - XmppRoom.prototype.setTopic = function(topic) { - return this.client.setTopic(this.name, topic); - }; - - XmppRoom.prototype.modifyRole = function(nick, role, reason, success_cb, error_cb) { - return this.client.modifyRole(this.name, nick, role, reason, success_cb, error_cb); - }; - - XmppRoom.prototype.kick = function(nick, reason, handler_cb, error_cb) { - return this.client.kick(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.voice = function(nick, reason, handler_cb, error_cb) { - return this.client.voice(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.mute = function(nick, reason, handler_cb, error_cb) { - return this.client.mute(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.op = function(nick, reason, handler_cb, error_cb) { - return this.client.op(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.deop = function(nick, reason, handler_cb, error_cb) { - return this.client.deop(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.modifyAffiliation = function(jid, affiliation, reason, success_cb, error_cb) { - return this.client.modifyAffiliation(this.name, jid, affiliation, reason, success_cb, error_cb); - }; - - XmppRoom.prototype.ban = function(jid, reason, handler_cb, error_cb) { - return this.client.ban(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.member = function(jid, reason, handler_cb, error_cb) { - return this.client.member(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.revoke = function(jid, reason, handler_cb, error_cb) { - return this.client.revoke(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.owner = function(jid, reason, handler_cb, error_cb) { - return this.client.owner(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.admin = function(jid, reason, handler_cb, error_cb) { - return this.client.admin(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.changeNick = function(nick) { - this.nick = nick; - return this.client.changeNick(this.name, nick); - }; - - XmppRoom.prototype.setStatus = function(show, status) { - return this.client.setStatus(this.name, this.nick, show, status); - }; - - - /*Function - Adds a handler to the MUC room. - Parameters: - (String) handler_type - 'message', 'presence' or 'roster'. - (Function) handler - The handler function. - Returns: - id - the id of handler. - */ - - XmppRoom.prototype.addHandler = function(handler_type, handler) { - var id; - id = this._handler_ids++; - switch (handler_type) { - case 'presence': - this._presence_handlers[id] = handler; - break; - case 'message': - this._message_handlers[id] = handler; - break; - case 'roster': - this._roster_handlers[id] = handler; - break; - default: - this._handler_ids--; - return null; - } - return id; - }; - - - /*Function - Removes a handler from the MUC room. - This function takes ONLY ids returned by the addHandler function - of this room. passing handler ids returned by connection.addHandler - may brake things! - Parameters: - (number) id - the id of the handler - */ - - XmppRoom.prototype.removeHandler = function(id) { - delete this._presence_handlers[id]; - delete this._message_handlers[id]; - return delete this._roster_handlers[id]; - }; - - - /*Function - Creates and adds an Occupant to the Room Roster. - Parameters: - (Object) data - the data the Occupant is filled with - Returns: - occ - the created Occupant. - */ - - XmppRoom.prototype._addOccupant = function(data) { - var occ; - occ = new Occupant(data, this); - this.roster[occ.nick] = occ; - return occ; - }; - - - /*Function - The standard handler that managed the Room Roster. - Parameters: - (Object) pres - the presence stanza containing user information - */ - - XmppRoom.prototype._roomRosterHandler = function(pres) { - var data, handler, id, newnick, nick, _ref; - data = XmppRoom._parsePresence(pres); - nick = data.nick; - newnick = data.newnick || null; - switch (data.type) { - case 'error': - return true; - case 'unavailable': - if (newnick) { - data.nick = newnick; - if (this.roster[nick] && this.roster[newnick]) { - this.roster[nick].update(this.roster[newnick]); - this.roster[newnick] = this.roster[nick]; - } - if (this.roster[nick] && !this.roster[newnick]) { - this.roster[newnick] = this.roster[nick].update(data); - } - } - delete this.roster[nick]; - break; - default: - if (this.roster[nick]) { - this.roster[nick].update(data); - } else { - this._addOccupant(data); - } - } - _ref = this._roster_handlers; - for (id in _ref) { - handler = _ref[id]; - if (!handler(this.roster, this)) { - delete this._roster_handlers[id]; - } - } - return true; - }; - - - /*Function - Parses a presence stanza - Parameters: - (Object) data - the data extracted from the presence stanza - */ - - XmppRoom._parsePresence = function(pres) { - var c, c2, data, _i, _j, _len, _len1, _ref, _ref1; - data = {}; - data.nick = Strophe.getResourceFromJid(pres.getAttribute("from")); - data.type = pres.getAttribute("type"); - data.states = []; - _ref = pres.childNodes; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - c = _ref[_i]; - switch (c.nodeName) { - case "status": - data.status = c.textContent || null; - break; - case "show": - data.show = c.textContent || null; - break; - case "x": - if (c.getAttribute("xmlns") === Strophe.NS.MUC_USER) { - _ref1 = c.childNodes; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - c2 = _ref1[_j]; - switch (c2.nodeName) { - case "item": - data.affiliation = c2.getAttribute("affiliation"); - data.role = c2.getAttribute("role"); - data.jid = c2.getAttribute("jid"); - data.newnick = c2.getAttribute("nick"); - break; - case "status": - if (c2.getAttribute("code")) { - data.states.push(c2.getAttribute("code")); - } - } - } - } - } - } - return data; - }; - - return XmppRoom; - - })(); - - RoomConfig = (function() { - function RoomConfig(info) { - this.parse = __bind(this.parse, this); - if (info != null) { - this.parse(info); - } - } - - RoomConfig.prototype.parse = function(result) { - var attr, attrs, child, field, identity, query, _i, _j, _k, _len, _len1, _len2, _ref; - query = result.getElementsByTagName("query")[0].childNodes; - this.identities = []; - this.features = []; - this.x = []; - for (_i = 0, _len = query.length; _i < _len; _i++) { - child = query[_i]; - attrs = child.attributes; - switch (child.nodeName) { - case "identity": - identity = {}; - for (_j = 0, _len1 = attrs.length; _j < _len1; _j++) { - attr = attrs[_j]; - identity[attr.name] = attr.textContent; - } - this.identities.push(identity); - break; - case "feature": - this.features.push(child.getAttribute("var")); - break; - case "x": - if ((!child.childNodes[0].getAttribute("var") === 'FORM_TYPE') || (!child.childNodes[0].getAttribute("type") === 'hidden')) { - break; - } - _ref = child.childNodes; - for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) { - field = _ref[_k]; - if (!field.attributes.type) { - this.x.push({ - "var": field.getAttribute("var"), - label: field.getAttribute("label") || "", - value: field.firstChild.textContent || "" - }); - } - } - } - } - return { - "identities": this.identities, - "features": this.features, - "x": this.x - }; - }; - - return RoomConfig; - - })(); - - Occupant = (function() { - function Occupant(data, room) { - this.room = room; - this.update = __bind(this.update, this); - this.admin = __bind(this.admin, this); - this.owner = __bind(this.owner, this); - this.revoke = __bind(this.revoke, this); - this.member = __bind(this.member, this); - this.ban = __bind(this.ban, this); - this.modifyAffiliation = __bind(this.modifyAffiliation, this); - this.deop = __bind(this.deop, this); - this.op = __bind(this.op, this); - this.mute = __bind(this.mute, this); - this.voice = __bind(this.voice, this); - this.kick = __bind(this.kick, this); - this.modifyRole = __bind(this.modifyRole, this); - this.update(data); - } - - Occupant.prototype.modifyRole = function(role, reason, success_cb, error_cb) { - return this.room.modifyRole(this.nick, role, reason, success_cb, error_cb); - }; - - Occupant.prototype.kick = function(reason, handler_cb, error_cb) { - return this.room.kick(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.voice = function(reason, handler_cb, error_cb) { - return this.room.voice(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.mute = function(reason, handler_cb, error_cb) { - return this.room.mute(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.op = function(reason, handler_cb, error_cb) { - return this.room.op(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.deop = function(reason, handler_cb, error_cb) { - return this.room.deop(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.modifyAffiliation = function(affiliation, reason, success_cb, error_cb) { - return this.room.modifyAffiliation(this.jid, affiliation, reason, success_cb, error_cb); - }; - - Occupant.prototype.ban = function(reason, handler_cb, error_cb) { - return this.room.ban(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.member = function(reason, handler_cb, error_cb) { - return this.room.member(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.revoke = function(reason, handler_cb, error_cb) { - return this.room.revoke(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.owner = function(reason, handler_cb, error_cb) { - return this.room.owner(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.admin = function(reason, handler_cb, error_cb) { - return this.room.admin(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.update = function(data) { - this.nick = data.nick || null; - this.affiliation = data.affiliation || null; - this.role = data.role || null; - this.jid = data.jid || null; - this.status = data.status || null; - this.show = data.show || null; - return this; - }; - - return Occupant; - - })(); - -}).call(this); - -define("strophe.muc", ["strophe"], function(){}); - -/* - Copyright 2010, François de Metz -*/ -/** - * Roster Plugin - * Allow easily roster management - * - * Features - * * Get roster from server - * * handle presence - * * handle roster iq - * * subscribe/unsubscribe - * * authorize/unauthorize - * * roster versioning (xep 237) - */ -Strophe.addConnectionPlugin('roster', -{ - /** Function: init - * Plugin init - * - * Parameters: - * (Strophe.Connection) conn - Strophe connection - */ - init: function(conn) - { - this._connection = conn; - this._callbacks = []; - /** Property: items - * Roster items - * [ - * { - * name : "", - * jid : "", - * subscription : "", - * ask : "", - * groups : ["", ""], - * resources : { - * myresource : { - * show : "", - * status : "", - * priority : "" - * } - * } - * } - * ] - */ - this.items = []; - /** Property: ver - * current roster revision - * always null if server doesn't support xep 237 - */ - this.ver = null; - // Override the connect and attach methods to always add presence and roster handlers. - // They are removed when the connection disconnects, so must be added on connection. - var oldCallback, roster = this, _connect = conn.connect, _attach = conn.attach; - var newCallback = function(status) - { - if (status == Strophe.Status.ATTACHED || status == Strophe.Status.CONNECTED) - { - try - { - // Presence subscription - conn.addHandler(roster._onReceivePresence.bind(roster), null, 'presence', null, null, null); - conn.addHandler(roster._onReceiveIQ.bind(roster), Strophe.NS.ROSTER, 'iq', "set", null, null); - } - catch (e) - { - Strophe.error(e); - } - } - if (typeof oldCallback === "function") { - oldCallback.apply(this, arguments); - } - }; - conn.connect = function(jid, pass, callback, wait, hold, route) - { - oldCallback = callback; - if (typeof jid == "undefined") - jid = null; - if (typeof pass == "undefined") - pass = null; - callback = newCallback; - _connect.apply(conn, [jid, pass, callback, wait, hold, route]); - }; - conn.attach = function(jid, sid, rid, callback, wait, hold, wind) - { - oldCallback = callback; - if (typeof jid == "undefined") - jid = null; - if (typeof sid == "undefined") - sid = null; - if (typeof rid == "undefined") - rid = null; - callback = newCallback; - _attach.apply(conn, [jid, sid, rid, callback, wait, hold, wind]); - }; - - Strophe.addNamespace('ROSTER_VER', 'urn:xmpp:features:rosterver'); - Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick'); - }, - /** Function: supportVersioning - * return true if roster versioning is enabled on server - */ - supportVersioning: function() - { - return (this._connection.features && this._connection.features.getElementsByTagName('ver').length > 0); - }, - /** Function: get - * Get Roster on server - * - * Parameters: - * (Function) userCallback - callback on roster result - * (String) ver - current rev of roster - * (only used if roster versioning is enabled) - * (Array) items - initial items of ver - * (only used if roster versioning is enabled) - * In browser context you can use sessionStorage - * to store your roster in json (JSON.stringify()) - */ - get: function(userCallback, ver, items) - { - var attrs = {xmlns: Strophe.NS.ROSTER}; - if (this.supportVersioning()) - { - // empty rev because i want an rev attribute in the result - attrs.ver = ver || ''; - this.items = items || []; - } - var iq = $iq({type: 'get', 'id' : this._connection.getUniqueId('roster')}).c('query', attrs); - return this._connection.sendIQ(iq, - this._onReceiveRosterSuccess.bind(this, userCallback), - this._onReceiveRosterError.bind(this, userCallback)); - }, - /** Function: registerCallback - * register callback on roster (presence and iq) - * - * Parameters: - * (Function) call_back - */ - registerCallback: function(call_back) - { - this._callbacks.push(call_back); - }, - /** Function: findItem - * Find item by JID - * - * Parameters: - * (String) jid - */ - findItem : function(jid) - { - try { - for (var i = 0; i < this.items.length; i++) - { - if (this.items[i] && this.items[i].jid == jid) - { - return this.items[i]; - } - } - } catch (e) - { - Strophe.error(e); - } - return false; - }, - /** Function: removeItem - * Remove item by JID - * - * Parameters: - * (String) jid - */ - removeItem : function(jid) - { - for (var i = 0; i < this.items.length; i++) - { - if (this.items[i] && this.items[i].jid == jid) - { - this.items.splice(i, 1); - return true; - } - } - return false; - }, - /** Function: subscribe - * Subscribe presence - * - * Parameters: - * (String) jid - * (String) message (optional) - * (String) nick (optional) - */ - subscribe: function(jid, message, nick) { - var pres = $pres({to: jid, type: "subscribe"}); - if (message && message !== "") { - pres.c("status").t(message).up(); - } - if (nick && nick !== "") { - pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up(); - } - this._connection.send(pres); - }, - /** Function: unsubscribe - * Unsubscribe presence - * - * Parameters: - * (String) jid - * (String) message - */ - unsubscribe: function(jid, message) - { - var pres = $pres({to: jid, type: "unsubscribe"}); - if (message && message !== "") - pres.c("status").t(message); - this._connection.send(pres); - }, - /** Function: authorize - * Authorize presence subscription - * - * Parameters: - * (String) jid - * (String) message - */ - authorize: function(jid, message) - { - var pres = $pres({to: jid, type: "subscribed"}); - if (message && message !== "") - pres.c("status").t(message); - this._connection.send(pres); - }, - /** Function: unauthorize - * Unauthorize presence subscription - * - * Parameters: - * (String) jid - * (String) message - */ - unauthorize: function(jid, message) - { - var pres = $pres({to: jid, type: "unsubscribed"}); - if (message && message !== "") - pres.c("status").t(message); - this._connection.send(pres); - }, - /** Function: add - * Add roster item - * - * Parameters: - * (String) jid - item jid - * (String) name - name - * (Array) groups - * (Function) call_back - */ - add: function(jid, name, groups, call_back) - { - var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: jid, - name: name}); - for (var i = 0; i < groups.length; i++) - { - iq.c('group').t(groups[i]).up(); - } - this._connection.sendIQ(iq, call_back, call_back); - }, - /** Function: update - * Update roster item - * - * Parameters: - * (String) jid - item jid - * (String) name - name - * (Array) groups - * (Function) call_back - */ - update: function(jid, name, groups, call_back) - { - var item = this.findItem(jid); - if (!item) - { - throw "item not found"; - } - var newName = name || item.name; - var newGroups = groups || item.groups; - var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid, - name: newName}); - for (var i = 0; i < newGroups.length; i++) - { - iq.c('group').t(newGroups[i]).up(); - } - return this._connection.sendIQ(iq, call_back, call_back); - }, - /** Function: remove - * Remove roster item - * - * Parameters: - * (String) jid - item jid - * (Function) call_back - */ - remove: function(jid, call_back) - { - var item = this.findItem(jid); - if (!item) - { - throw "item not found"; - } - var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid, - subscription: "remove"}); - this._connection.sendIQ(iq, call_back, call_back); - }, - /** PrivateFunction: _onReceiveRosterSuccess - * - */ - _onReceiveRosterSuccess: function(userCallback, stanza) - { - this._updateItems(stanza); - if (typeof userCallback === "function") { - userCallback(this.items); - } - }, - /** PrivateFunction: _onReceiveRosterError - * - */ - _onReceiveRosterError: function(userCallback, stanza) - { - userCallback(this.items); - }, - /** PrivateFunction: _onReceivePresence - * Handle presence - */ - _onReceivePresence : function(presence) - { - // TODO: from is optional - var jid = presence.getAttribute('from'); - var from = Strophe.getBareJidFromJid(jid); - var item = this.findItem(from); - // not in roster - if (!item) - { - return true; - } - var type = presence.getAttribute('type'); - if (type == 'unavailable') - { - delete item.resources[Strophe.getResourceFromJid(jid)]; - } - else if (!type) - { - // TODO: add timestamp - item.resources[Strophe.getResourceFromJid(jid)] = { - show : (presence.getElementsByTagName('show').length !== 0) ? Strophe.getText(presence.getElementsByTagName('show')[0]) : "", - status : (presence.getElementsByTagName('status').length !== 0) ? Strophe.getText(presence.getElementsByTagName('status')[0]) : "", - priority : (presence.getElementsByTagName('priority').length !== 0) ? Strophe.getText(presence.getElementsByTagName('priority')[0]) : "" - }; - } - else - { - // Stanza is not a presence notification. (It's probably a subscription type stanza.) - return true; - } - this._call_backs(this.items, item); - return true; - }, - /** PrivateFunction: _call_backs - * - */ - _call_backs : function(items, item) - { - for (var i = 0; i < this._callbacks.length; i++) // [].forEach my love ... - { - this._callbacks[i](items, item); - } - }, - /** PrivateFunction: _onReceiveIQ - * Handle roster push. - */ - _onReceiveIQ : function(iq) - { - var id = iq.getAttribute('id'); - var from = iq.getAttribute('from'); - // Receiving client MUST ignore stanza unless it has no from or from = user's JID. - if (from && from !== "" && from != this._connection.jid && from != Strophe.getBareJidFromJid(this._connection.jid)) - return true; - var iqresult = $iq({type: 'result', id: id, from: this._connection.jid}); - this._connection.send(iqresult); - this._updateItems(iq); - return true; - }, - /** PrivateFunction: _updateItems - * Update items from iq - */ - _updateItems : function(iq) - { - var query = iq.getElementsByTagName('query'); - if (query.length !== 0) - { - this.ver = query.item(0).getAttribute('ver'); - var self = this; - Strophe.forEachChild(query.item(0), 'item', - function (item) - { - self._updateItem(item); - } - ); - } - this._call_backs(this.items); - }, - /** PrivateFunction: _updateItem - * Update internal representation of roster item - */ - _updateItem : function(item) - { - var jid = item.getAttribute("jid"); - var name = item.getAttribute("name"); - var subscription = item.getAttribute("subscription"); - var ask = item.getAttribute("ask"); - var groups = []; - Strophe.forEachChild(item, 'group', - function(group) - { - groups.push(Strophe.getText(group)); - } - ); - - if (subscription == "remove") - { - this.removeItem(jid); - return; - } - - item = this.findItem(jid); - if (!item) - { - this.items.push({ - name : name, - jid : jid, - subscription : subscription, - ask : ask, - groups : groups, - resources : {} - }); - } - else - { - item.name = name; - item.subscription = subscription; - item.ask = ask; - item.groups = groups; - } - } -}); - -define("strophe.roster", ["strophe"], function(){}); - -// Generated by CoffeeScript 1.3.3 -/* -Plugin to implement the vCard extension. -http://xmpp.org/extensions/xep-0054.html - -Author: Nathan Zorn (nathan.zorn@gmail.com) -CoffeeScript port: Andreas Guth (guth@dbis.rwth-aachen.de) -*/ - -/* jslint configuration: -*/ - -/* global document, window, setTimeout, clearTimeout, console, - XMLHttpRequest, ActiveXObject, - Base64, MD5, - Strophe, $build, $msg, $iq, $pres -*/ - -var buildIq; - -buildIq = function(type, jid, vCardEl) { - var iq; - iq = $iq(jid ? { - type: type, - to: jid - } : { - type: type - }); - iq.c("vCard", { - xmlns: Strophe.NS.VCARD - }); - if (vCardEl) { - iq.cnode(vCardEl); - } - return iq; -}; - -Strophe.addConnectionPlugin('vcard', { - _connection: null, - init: function(conn) { - this._connection = conn; - return Strophe.addNamespace('VCARD', 'vcard-temp'); - }, - /*Function - Retrieve a vCard for a JID/Entity - Parameters: - (Function) handler_cb - The callback function used to handle the request. - (String) jid - optional - The name of the entity to request the vCard - If no jid is given, this function retrieves the current user's vcard. - */ - - get: function(handler_cb, jid, error_cb) { - var iq; - iq = buildIq("get", jid); - return this._connection.sendIQ(iq, handler_cb, error_cb); - }, - /* Function - Set an entity's vCard. - */ - - set: function(handler_cb, vCardEl, jid, error_cb) { - var iq; - iq = buildIq("set", jid, vCardEl); - return this._connection.sendIQ(iq, handler_cb, error_rb); - } -}); - -define("strophe.vcard", ["strophe"], function(){}); - -/* - Copyright 2010, François de Metz -*/ - -/** - * Disco Strophe Plugin - * Implement http://xmpp.org/extensions/xep-0030.html - * TODO: manage node hierarchies, and node on info request - */ -Strophe.addConnectionPlugin('disco', -{ - _connection: null, - _identities : [], - _features : [], - _items : [], - /** Function: init - * Plugin init - * - * Parameters: - * (Strophe.Connection) conn - Strophe connection - */ - init: function(conn) - { - this._connection = conn; - this._identities = []; - this._features = []; - this._items = []; - // disco info - conn.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null); - // disco items - conn.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null); - }, - /** Function: addIdentity - * See http://xmpp.org/registrar/disco-categories.html - * Parameters: - * (String) category - category of identity (like client, automation, etc ...) - * (String) type - type of identity (like pc, web, bot , etc ...) - * (String) name - name of identity in natural language - * (String) lang - lang of name parameter - * - * Returns: - * Boolean - */ - addIdentity: function(category, type, name, lang) - { - for (var i=0; i 0) { converse.log('ERROR: An error occured while trying to enable message carbons.'); } else { - converse.log('Message carbons appear to have been enabled.'); + this.session.save({carbons_enabled: true}); + converse.log('Message carbons have been enabled.'); } - }, null, "iq", null, "enablecarbons"); + }, this), null, "iq", null, "enablecarbons"); + this.connection.send(carbons_iq); }; this.onConnected = function () { @@ -28343,18 +27478,13 @@ define("converse-dependencies", [ this.initStatus($.proxy(function () { this.chatboxes.onConnected(); - this.giveFeedback(__('Online Contacts')); + this.giveFeedback(__('Contacts')); if (this.callback) { if (this.connection.service === 'jasmine tests') { // XXX: Call back with the internal converse object. This // object should never be exposed to production systems. // 'jasmine tests' is an invalid http bind service value, // so we're sure that this is just for tests. - // - // TODO: We might need to consider websockets, which - // probably won't use the 'service' attr. Current - // strophe.js version used by converse.js doesn't support - // websockets. this.callback(this); } else { this.callback(); @@ -28414,15 +27544,18 @@ define("converse-dependencies", [ this.messages.browserStorage = new Backbone.BrowserStorage[converse.storage]( b64_sha1('converse.messages'+this.get('jid')+converse.bare_jid)); this.save({ + // The chat_state will be set to ACTIVE once the chat box is opened + // and we listen for change:chat_state, so shouldn't set it to ACTIVE here. + 'chat_state': undefined, 'box_id' : b64_sha1(this.get('jid')), 'height': height, 'minimized': this.get('minimized') || false, + 'num_unread': this.get('num_unread') || 0, 'otr_status': this.get('otr_status') || UNENCRYPTED, 'time_minimized': this.get('time_minimized') || moment(), 'time_opened': this.get('time_opened') || moment().valueOf(), - 'user_id' : Strophe.getNodeFromJid(this.get('jid')), - 'num_unread': this.get('num_unread') || 0, - 'url': '' + 'url': '', + 'user_id' : Strophe.getNodeFromJid(this.get('jid')) }); } else { this.set({ @@ -28575,56 +27708,48 @@ define("converse-dependencies", [ createMessage: function ($message) { var body = $message.children('body').text(), - composing = $message.find('composing'), - paused = $message.find('paused'), delayed = $message.find('delay').length > 0, fullname = this.get('fullname'), is_groupchat = $message.attr('type') === 'groupchat', msgid = $message.attr('id'), - stamp, time, sender, from; + chat_state = $message.find(COMPOSING).length && COMPOSING || + $message.find(PAUSED).length && PAUSED || + $message.find(INACTIVE).length && INACTIVE || + $message.find(ACTIVE).length && ACTIVE || + $message.find(GONE).length && GONE, + stamp, time, sender, from, createMessage; if (is_groupchat) { from = Strophe.unescapeNode(Strophe.getResourceFromJid($message.attr('from'))); } else { from = Strophe.getBareJidFromJid($message.attr('from')); } - fullname = (_.isEmpty(fullname)? from: fullname).split(' ')[0]; - - if (!body) { - if (composing.length || paused.length) { - // FIXME: use one attribute for chat states (e.g. - // chatstate) instead of saving 'paused' and - // 'composing' separately. - this.messages.add({ - fullname: fullname, - sender: 'them', - delayed: delayed, - time: moment().format(), - composing: composing.length, - paused: paused.length - }); - } + fullname = (_.isEmpty(fullname) ? from: fullname).split(' ')[0]; + if (delayed) { + stamp = $message.find('delay').attr('stamp'); + time = stamp; } else { - if (delayed) { - stamp = $message.find('delay').attr('stamp'); - time = stamp; - } else { - time = moment().format(); - } - if ((is_groupchat && from === this.get('nick')) || (!is_groupchat && from == converse.bare_jid)) { - sender = 'me'; - } else { - sender = 'them'; - } - this.messages.create({ - fullname: fullname, - sender: sender, - delayed: delayed, - time: time, - message: body, - msgid: msgid - }); + time = moment().format(); } + if ((is_groupchat && from === this.get('nick')) || (!is_groupchat && from == converse.bare_jid)) { + sender = 'me'; + } else { + sender = 'them'; + } + if (!body) { + createMessage = this.messages.add; + } else { + createMessage = this.messages.create; + } + this.messages.create({ + chat_state: chat_state, + delayed: delayed, + fullname: fullname, + message: body || undefined, + msgid: msgid, + sender: sender, + time: time + }); }, receiveMessage: function ($message) { @@ -28664,6 +27789,8 @@ define("converse-dependencies", [ 'click .close-chatbox-button': 'close', 'click .toggle-chatbox-button': 'minimize', 'keypress textarea.chat-textarea': 'keyPressed', + 'focus textarea.chat-textarea': 'chatBoxFocused', + 'blur textarea.chat-textarea': 'chatBoxBlurred', 'click .toggle-smiley': 'toggleEmoticonMenu', 'click .toggle-smiley ul li': 'insertEmoticon', 'click .toggle-clear': 'clearMessages', @@ -28679,10 +27806,14 @@ define("converse-dependencies", [ this.model.messages.on('add', this.onMessageAdded, this); this.model.on('show', this.show, this); this.model.on('destroy', this.hide, this); - this.model.on('change', this.onChange, this); + // TODO check for changed fullname as well + this.model.on('change:chat_state', this.sendChatState, this); + this.model.on('change:chat_status', this.onChatStatusChanged, this); + this.model.on('change:image', this.renderAvatar, this); + this.model.on('change:otr_status', this.onOTRStatusChanged, this); + this.model.on('change:minimized', this.onMinimizedChanged, this); + this.model.on('change:status', this.onStatusChanged, this); this.model.on('showOTRError', this.showOTRError, this); - // XXX: doesn't look like this event is being used? - this.model.on('buddyStartsOTR', this.buddyStartsOTR, this); this.model.on('showHelpMessages', this.showHelpMessages, this); this.model.on('sendMessageStanza', this.sendMessageStanza, this); this.model.on('showSentOTRMessage', function (text) { @@ -28817,12 +27948,20 @@ define("converse-dependencies", [ })); } } - if (message.get(COMPOSING)) { - this.showStatusNotification(message.get('fullname')+' '+__('is typing')); - return; - } else if (message.get(PAUSED)) { - this.showStatusNotification(message.get('fullname')+' '+__('has stopped typing')); - return; + if (!message.get('message')) { + if (message.get('chat_state') === COMPOSING) { + this.showStatusNotification(message.get('fullname')+' '+__('is typing')); + return; + } else if (message.get('chat_state') === PAUSED) { + this.showStatusNotification(message.get('fullname')+' '+__('has stopped typing')); + return; + } else if (_.contains([INACTIVE, ACTIVE], message.get('chat_state'))) { + this.$el.find('.chat-content div.chat-event').remove(); + return; + } else if (message.get('chat_state') === GONE) { + this.showStatusNotification(message.get('fullname')+' '+__('has gone away')); + return; + } } else { this.showMessage(_.clone(message.attributes)); } @@ -28833,8 +27972,7 @@ define("converse-dependencies", [ }, sendMessageStanza: function (text) { - /* - * Sends the actual XML stanza to the XMPP server. + /* Sends the actual XML stanza to the XMPP server. */ // TODO: Look in ChatPartners to see what resources we have for the recipient. // if we have one resource, we sent to only that resources, if we have multiple @@ -28843,7 +27981,7 @@ define("converse-dependencies", [ var bare_jid = this.model.get('jid'); var message = $msg({from: converse.connection.jid, to: bare_jid, type: 'chat', id: timestamp}) .c('body').t(text).up() - .c('active', {'xmlns': 'http://jabber.org/protocol/chatstates'}); + .c(ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}); converse.connection.send(message); if (converse.forward_messages) { // Forward the message, so that other connected resources are also aware of it. @@ -28893,10 +28031,52 @@ define("converse-dependencies", [ } }, + sendChatState: function () { + /* Sends a message with the status of the user in this chat session + * as taken from the 'chat_state' attribute of the chat box. + * See XEP-0085 Chat State Notifications. + */ + converse.connection.send( + $msg({'to':this.model.get('jid'), 'type': 'chat'}) + .c(this.model.get('chat_state'), {'xmlns': Strophe.NS.CHATSTATES}) + ); + }, + + setChatState: function (state, no_save) { + /* Mutator for setting the chat state of this chat session. + * Handles clearing of any chat state notification timeouts and + * setting new ones if necessary. + * Timeouts are set when the state being set is COMPOSING or PAUSED. + * After the timeout, COMPOSING will become PAUSED and PAUSED will become INACTIVE. + * See XEP-0085 Chat State Notifications. + * + * Parameters: + * (string) state - The chat state (consts ACTIVE, COMPOSING, PAUSED, INACTIVE, GONE) + * (no_save) no_save - Just do the cleanup or setup but don't actually save the state. + */ + if (_.contains([ACTIVE, INACTIVE, GONE], state)) { + if (typeof this.chat_state_timeout !== 'undefined') { + clearTimeout(this.chat_state_timeout); + delete this.chat_state_timeout; + } + } else if (state === COMPOSING) { + this.chat_state_timeout = setTimeout( + $.proxy(this.setChatState, this), converse.TIMEOUTS.PAUSED, PAUSED); + } else if (state === PAUSED) { + this.chat_state_timeout = setTimeout( + $.proxy(this.setChatState, this), converse.TIMEOUTS.INACTIVE, INACTIVE); + } + if (!no_save && this.model.get('chat_state') != state) { + this.model.set('chat_state', state); + } + return this; + }, + keyPressed: function (ev) { - var $textarea = $(ev.target), - message, notify, composing; - if(ev.keyCode == KEY.ENTER) { + /* Event handler for when a key is pressed in a chat box textarea. + */ + var $textarea = $(ev.target), message; + if (ev.keyCode == KEY.ENTER) { ev.preventDefault(); message = $textarea.val(); $textarea.val('').focus(); @@ -28908,23 +28088,24 @@ define("converse-dependencies", [ } converse.emit('messageSend', message); } - this.$el.data('composing', false); - } else if (!this.model.get('chatroom')) { - // composing data is only for single user chat - composing = this.$el.data('composing'); - if (!composing) { - if (ev.keyCode != 47) { - // We don't send composing messages if the message - // starts with forward-slash. - notify = $msg({'to':this.model.get('jid'), 'type': 'chat'}) - .c('composing', {'xmlns':'http://jabber.org/protocol/chatstates'}); - converse.connection.send(notify); - } - this.$el.data('composing', true); - } + this.setChatState(ACTIVE); + } else if (!this.model.get('chatroom')) { // chat state data is currently only for single user chat + // Set chat state to composing if keyCode is not a forward-slash + // (which would imply an internal command and not a message). + this.setChatState(COMPOSING, ev.keyCode==KEY.FORWARD_SLASH); } }, + chatBoxFocused: function (ev) { + ev.preventDefault(); + this.setChatState(ACTIVE); + }, + + chatBoxBlurred: function (ev) { + ev.preventDefault(); + this.setChatState(INACTIVE); + }, + onDragResizeStart: function (ev) { if (!converse.allow_dragresize) { return true; } // Record element attributes for mouseMove(). @@ -28998,11 +28179,6 @@ define("converse-dependencies", [ console.log("OTR ERROR:"+msg); }, - buddyStartsOTR: function (ev) { - this.showHelpMessages([__('This user has requested an encrypted session.')]); - this.model.initiateOTR(); - }, - startOTRFromToolbar: function (ev) { $(ev.target).parent().parent().slideUp(); ev.stopPropagation(); @@ -29053,46 +28229,39 @@ define("converse-dependencies", [ }); }, - onChange: function (item, changed) { - if (_.has(item.changed, 'chat_status')) { - var chat_status = item.get('chat_status'), - fullname = item.get('fullname'); - fullname = _.isEmpty(fullname)? item.get('jid'): fullname; - if (this.$el.is(':visible')) { - if (chat_status === 'offline') { - this.showStatusNotification(fullname+' '+'has gone offline'); - } else if (chat_status === 'away') { - this.showStatusNotification(fullname+' '+'has gone away'); - } else if ((chat_status === 'dnd')) { - this.showStatusNotification(fullname+' '+'is busy'); - } else if (chat_status === 'online') { - this.$el.find('div.chat-event').remove(); - } - } - converse.emit('contactStatusChanged', item.attributes, item.get('chat_status')); - // TODO: DEPRECATED AND SHOULD BE REMOVED IN 0.9.0 - converse.emit('buddyStatusChanged', item.attributes, item.get('chat_status')); - } - if (_.has(item.changed, 'status')) { - this.showStatusMessage(); - converse.emit('contactStatusMessageChanged', item.attributes, item.get('status')); - // TODO: DEPRECATED AND SHOULD BE REMOVED IN 0.9.0 - converse.emit('buddyStatusMessageChanged', item.attributes, item.get('status')); - } - if (_.has(item.changed, 'image')) { - this.renderAvatar(); - } - if (_.has(item.changed, 'otr_status')) { - this.renderToolbar().informOTRChange(); - } - if (_.has(item.changed, 'minimized')) { - if (item.get('minimized')) { - this.hide(); - } else { - this.maximize(); + onChatStatusChanged: function (item) { + var chat_status = item.get('chat_status'), + fullname = item.get('fullname'); + fullname = _.isEmpty(fullname)? item.get('jid'): fullname; + if (this.$el.is(':visible')) { + if (chat_status === 'offline') { + this.showStatusNotification(fullname+' '+__('has gone offline')); + } else if (chat_status === 'away') { + this.showStatusNotification(fullname+' '+__('has gone away')); + } else if ((chat_status === 'dnd')) { + this.showStatusNotification(fullname+' '+__('is busy')); + } else if (chat_status === 'online') { + this.$el.find('div.chat-event').remove(); } } - // TODO check for changed fullname as well + converse.emit('contactStatusChanged', item.attributes, item.get('chat_status')); + }, + + onStatusChanged: function (item) { + this.showStatusMessage(); + converse.emit('contactStatusMessageChanged', item.attributes, item.get('status')); + }, + + onOTRStatusChanged: function (item) { + this.renderToolbar().informOTRChange(); + }, + + onMinimizedChanged: function (item) { + if (item.get('minimized')) { + this.hide(); + } else { + this.maximize(); + } }, showStatusMessage: function (msg) { @@ -29107,8 +28276,9 @@ define("converse-dependencies", [ if (ev && ev.preventDefault) { ev.preventDefault(); } if (converse.connection.connected) { this.model.destroy(); + this.setChatState(INACTIVE); } else { - this.model.trigger('hide'); + this.hide(); } converse.emit('chatBoxClosed', this); return this; @@ -29118,7 +28288,7 @@ define("converse-dependencies", [ // Restores a minimized chat box this.$el.insertAfter(converse.chatboxviews.get("controlbox").$el).show('fast', $.proxy(function () { converse.refreshWebkit(); - this.focus(); + this.setChatState(ACTIVE).focus(); converse.emit('chatBoxMaximized', this); }, this)); }, @@ -29126,7 +28296,7 @@ define("converse-dependencies", [ minimize: function (ev) { if (ev && ev.preventDefault) { ev.preventDefault(); } // Minimizes a chat box - this.model.minimize(); + this.setChatState(INACTIVE).model.minimize(); this.$el.hide('fast', converse.refreshwebkit); converse.emit('chatBoxMinimized', this); }, @@ -29214,7 +28384,7 @@ define("converse-dependencies", [ return; } var img_src = 'data:'+this.model.get('image_type')+';base64,'+this.model.get('image'), - canvas = $('').get(0); + canvas = $('').get(0); if (!(canvas.getContext && canvas.getContext('2d'))) { return this; @@ -29255,6 +28425,7 @@ define("converse-dependencies", [ this.model.save(); this.initDragResize(); } + this.setChatState(ACTIVE); return this; }, @@ -29405,7 +28576,7 @@ define("converse-dependencies", [ 'label_room_name': __('Room name'), 'label_nickname': __('Nickname'), 'label_server': __('Server'), - 'label_join': __('Join'), + 'label_join': __('Join Room'), 'label_show_rooms': __('Show rooms') }) ).hide()); @@ -29434,41 +28605,51 @@ define("converse-dependencies", [ $('input#show-rooms').show().siblings('span.spinner').remove(); }, + onRoomsFound: function (iq) { + /* Handle the IQ stanza returned from the server, containing + * all its public rooms. + */ + var name, jid, i, fragment, + that = this, + $available_chatrooms = this.$el.find('#available-chatrooms'); + this.rooms = $(iq).find('query').find('item'); + if (this.rooms.length) { + // # For translators: %1$s is a variable and will be + // # replaced with the XMPP server name + $available_chatrooms.html('
'+__('Rooms on %1$s',this.model.get('muc_domain'))+'
'); + fragment = document.createDocumentFragment(); + for (i=0; i'+__('Rooms on %1$s',this.model.get('muc_domain'))+''); - fragment = document.createDocumentFragment(); - for (i=0; i/admin: ' +__("Change user's affiliation to admin"), '/ban: ' +__('Ban user from room'), '/clear: ' +__('Remove messages'), + '/deop: ' +__('Change user role to participant'), '/help: ' +__('Show this menu'), '/kick: ' +__('Kick user from room'), '/me: ' +__('Write in 3rd person'), + '/member: '+__('Grant membership to a user'), '/mute: ' +__("Remove user's ability to post messages"), '/nick: ' +__('Change your nickname'), + '/op: ' +__('Grant moderator role to user'), + '/owner: ' +__('Grant ownership of this room'), + '/revoke: '+__("Revoke user's membership"), '/topic: ' +__('Set room topic'), '/voice: ' +__('Allow muted user to post messages') ]); break; case 'kick': - args = match[2].splitOnce(' '); - converse.connection.muc.kick(this.model.get('jid'), args[0], args[1], undefined, $.proxy(this.onCommandError, this)); + this.modifyRole( + this.model.get('jid'), args[0], 'none', args[1], + undefined, $.proxy(this.onCommandError, this)); break; case 'mute': - args = match[2].splitOnce(' '); - converse.connection.muc.mute(this.model.get('jid'), args[0], args[1], undefined, $.proxy(this.onCommandError, this)); + this.modifyRole( + this.model.get('jid'), args[0], 'visitor', args[1], + undefined, $.proxy(this.onCommandError, this)); + break; + case 'member': + this.setAffiliation( + this.model.get('jid'), args[0], 'member', args[1], + undefined, $.proxy(this.onCommandError, this)); break; case 'nick': - converse.connection.muc.changeNick(this.model.get('jid'), match[2]); + converse.connection.send($pres({ + from: converse.connection.jid, + to: this.getRoomJIDAndNick(match[2]), + id: converse.connection.getUniqueId() + }).tree()); + break; + case 'owner': + this.setAffiliation( + this.model.get('jid'), args[0], 'owner', args[1], + undefined, $.proxy(this.onCommandError, this)); break; case 'op': - args = match[2].splitOnce(' '); - converse.connection.muc.op(this.model.get('jid'), args[0], args[1], undefined, $.proxy(this.onCommandError, this)); + this.modifyRole( + this.model.get('jid'), args[0], 'moderator', args[1], + undefined, $.proxy(this.onCommandError, this)); + break; + case 'revoke': + this.setAffiliation( + this.model.get('jid'), args[0], 'none', args[1], + undefined, $.proxy(this.onCommandError, this)); break; case 'topic': - converse.connection.muc.setTopic(this.model.get('jid'), match[2]); + converse.connection.send( + $msg({ + to: this.model.get('jid'), + from: converse.connection.jid, + type: "groupchat" + }).c("subject", {xmlns: "jabber:client"}).t(match[2]).tree() + ); break; case 'voice': - args = match[2].splitOnce(' '); - converse.connection.muc.voice(this.model.get('jid'), args[0], args[1], undefined, $.proxy(this.onCommandError, this)); + this.modifyRole( + this.model.get('jid'), args[0], 'participant', args[1], + undefined, $.proxy(this.onCommandError, this)); break; default: this.createChatRoomMessage(text); @@ -30087,25 +29390,75 @@ define("converse-dependencies", [ } }, - connect: function (password) { - if (_.has(converse.connection.muc.rooms, this.model.get('jid'))) { - // If the room exists, it already has event listeners, so we - // don't add them again. - converse.connection.muc.join( - this.model.get('jid'), this.model.get('nick'), null, null, null, password); - } else { - converse.connection.muc.join( - this.model.get('jid'), - this.model.get('nick'), - $.proxy(this.onChatRoomMessage, this), - $.proxy(this.onChatRoomPresence, this), - $.proxy(this.onChatRoomRoster, this), - password); + handleMUCStanza: function (stanza) { + var xmlns, xquery, i; + var from = stanza.getAttribute('from'); + if (!from || (this.model.get('id') !== from.split("/")[0])) { + return true; } + if (stanza.nodeName === "message") { + this.onChatRoomMessage(stanza); + } else if (stanza.nodeName === "presence") { + xquery = stanza.getElementsByTagName("x"); + if (xquery.length > 0) { + for (i = 0; i < xquery.length; i++) { + xmlns = xquery[i].getAttribute("xmlns"); + if (xmlns && xmlns.match(Strophe.NS.MUC)) { + this.onChatRoomPresence(stanza); + break; + } + } + } + } + return true; }, - onLeave: function () { - this.model.set('connected', false); + getRoomJIDAndNick: function (nick) { + nick = nick || this.model.get('nick'); + var room = this.model.get('jid'); + var node = Strophe.escapeNode(Strophe.getNodeFromJid(room)); + var domain = Strophe.getDomainFromJid(room); + return node + "@" + domain + (nick !== null ? "/" + nick : ""); + }, + + join: function (password, history_attrs, extended_presence) { + var msg = $pres({ + from: converse.connection.jid, + to: this.getRoomJIDAndNick() + }).c("x", { + xmlns: Strophe.NS.MUC + }); + if (typeof history_attrs === "object" && history_attrs.length) { + msg = msg.c("history", history_attrs).up(); + } + if (password) { + msg.cnode(Strophe.xmlElement("password", [], password)); + } + if (typeof extended_presence !== "undefined" && extended_presence !== null) { + msg.up.cnode(extended_presence); + } + if (!this.handler) { + this.handler = converse.connection.addHandler($.proxy(this.handleMUCStanza, this)); + } + this.model.set('connection_status', Strophe.Status.CONNECTING); + return converse.connection.send(msg); + }, + + leave: function(exit_msg) { + var presenceid = converse.connection.getUniqueId(); + var presence = $pres({ + type: "unavailable", + id: presenceid, + from: converse.connection.jid, + to: this.getRoomJIDAndNick() + }); + if (exit_msg !== null) { + presence.c("status", exit_msg); + } + converse.connection.addHandler( + $.proxy(function () { this.model.set('connection_status', Strophe.Status.DISCONNECTED); }, this), + null, "presence", null, presenceid); + converse.connection.send(presence); }, renderConfigurationForm: function (stanza) { @@ -30124,10 +29477,19 @@ define("converse-dependencies", [ }); $form.append(''); $form.append(''); - $form.on('submit', $.proxy(this.saveConfiguration, this)); + $form.on('submit', this.saveConfiguration.bind(this)); $form.find('input[type=button]').on('click', $.proxy(this.cancelConfiguration, this)); }, + sendConfiguration: function(config, onSuccess, onError) { + // Send an IQ stanza with the room configuration. + var iq = $iq({to: this.model.get('jid'), type: "set"}) + .c("query", {xmlns: Strophe.NS.MUC_OWNER}) + .c("x", {xmlns: "jabber:x:data", type: "submit"}); + _.each(config, function (node) { iq.cnode(node).up(); }); + return converse.connection.sendIQ(iq.tree(), onSuccess, onError); + }, + saveConfiguration: function (ev) { ev.preventDefault(); var that = this; @@ -30137,8 +29499,7 @@ define("converse-dependencies", [ $inputs.each(function () { configArray.push(utils.webForm2xForm(this)); if (!--count) { - converse.connection.muc.saveConfiguration( - that.model.get('jid'), + that.sendConfiguration( configArray, $.proxy(that.onConfigSaved, that), $.proxy(that.onErrorConfigSaved, that) @@ -30184,9 +29545,12 @@ define("converse-dependencies", [ ''+ ''+ '')); - converse.connection.muc.configure( - this.model.get('jid'), - $.proxy(this.renderConfigurationForm, this) + converse.connection.sendIQ( + $iq({ + to: this.model.get('jid'), + type: "get" + }).c("query", {xmlns: Strophe.NS.MUC_OWNER}).tree(), + this.renderConfigurationForm.bind(this) ); }, @@ -30194,7 +29558,7 @@ define("converse-dependencies", [ ev.preventDefault(); var password = this.$el.find('.chatroom-form').find('input[type=password]').val(); this.$el.find('.chatroom-form-container').replaceWith(''); - this.connect(password); + this.join(password); }, renderPasswordForm: function () { @@ -30303,15 +29667,15 @@ define("converse-dependencies", [ }); $(x).find('status').each($.proxy(function (idx, stat) { var code = stat.getAttribute('code'); - if (is_self && _.contains(_.keys(this.newNicknameMessages), code)) { - this.model.save({'nick': Strophe.getResourceFromJid($el.attr('from'))}); + var from_nick = Strophe.unescapeNode(Strophe.getResourceFromJid($el.attr('from'))); + if (is_self && code === "210") { + msgs.push(__(this.newNicknameMessages[code], from_nick)); + } else if (is_self && code === "303") { msgs.push(__(this.newNicknameMessages[code], $item.attr('nick'))); } else if (is_self && _.contains(_.keys(this.disconnectMessages), code)) { disconnect_msgs.push(this.disconnectMessages[code]); } else if (!is_self && _.contains(_.keys(this.actionInfoMessages), code)) { - msgs.push( - __(this.actionInfoMessages[code], Strophe.unescapeNode(Strophe.getResourceFromJid($el.attr('from')))) - ); + msgs.push(__(this.actionInfoMessages[code], from_nick)); } else if (_.contains(_.keys(this.infoMessages), code)) { msgs.push(this.infoMessages[code]); } else if (code !== '110') { @@ -30329,7 +29693,7 @@ define("converse-dependencies", [ for (i=0; i this.$el.outerWidth(true)) { oldest_chat = this.getOldestMaximizedChat(); - if (oldest_chat) { + if (oldest_chat && oldest_chat.get('id') !== new_id) { oldest_chat.minimize(); } } @@ -30713,9 +30083,9 @@ define("converse-dependencies", [ }, showChat: function (attrs) { - /* Find the chat box and show it. - * If it doesn't exist, create it. + /* Find the chat box and show it. If it doesn't exist, create it. */ + // TODO: Send the chat state ACTIVE to the contact once the chat box is opened. var chatbox = this.model.get(attrs.jid); if (!chatbox) { chatbox = this.model.create(attrs, { @@ -30736,7 +30106,6 @@ define("converse-dependencies", [ this.MinimizedChatBoxView = Backbone.View.extend({ tagName: 'div', className: 'chat-head', - events: { 'click .close-chatbox-button': 'close', 'click .restore-chat': 'restore' @@ -30744,7 +30113,7 @@ define("converse-dependencies", [ initialize: function () { this.model.messages.on('add', function (m) { - if (!(m.get('composing') || m.get('paused'))) { + if (m.get('message')) { this.updateUnreadMessagesCounter(); } }, this); @@ -30787,18 +30156,15 @@ define("converse-dependencies", [ }, restore: _.debounce(function (ev) { - if (ev && ev.preventDefault) { - ev.preventDefault(); - } + if (ev && ev.preventDefault) { ev.preventDefault(); } this.model.messages.off('add',null,this); this.remove(); this.model.maximize(); - }, 200) + }, 200, true) }); this.MinimizedChats = Backbone.Overview.extend({ el: "#minimized-chats", - events: { "click #toggle-minimized-chats": "toggle" }, @@ -30923,6 +30289,8 @@ define("converse-dependencies", [ 'user_id': Strophe.getNodeFromJid(jid), 'resources': [], 'groups': [], + 'image_type': 'image/png', + 'image': "iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAIAAABt+uBvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwHCy455JBsggAABkJJREFUeNrtnM1PE1sUwHvvTD8otWLHST/Gimi1CEgr6M6FEWuIBo2pujDVsNDEP8GN/4MbN7oxrlipG2OCgZgYlxAbkRYw1KqkIDRCSkM7nXvvW8x7vjyNeQ9m7p1p3z1LQk/v/Dhz7vkEXL161cHl9wI5Ag6IA+KAOCAOiAPigDggLhwQB2S+iNZ+PcYY/SWEEP2HAAAIoSAIoihCCP+ngDDGtVotGAz29/cfOXJEUZSOjg6n06lp2sbGRqlUWlhYyGazS0tLbrdbEASrzgksyeYJId3d3el0uqenRxRFAAAA4KdfIIRgjD9+/Pj8+fOpqSndslofEIQwHA6Pjo4mEon//qmFhYXHjx8vLi4ihBgDEnp7e9l8E0Jo165dQ0NDd+/eDYVC2/qsJElDQ0OEkKWlpa2tLZamxAhQo9EIBoOjo6MXL17csZLe3l5FUT59+lQul5l5JRaAVFWNRqN37tw5ceKEQVWRSOTw4cOFQuHbt2+iKLYCIISQLMu3b99OJpOmKAwEAgcPHszn8+vr6wzsiG6UQQhxuVyXLl0aGBgwUW0sFstkMl6v90fo1KyAMMYDAwPnzp0zXfPg4GAqlWo0Gk0MiBAiy/L58+edTqf5Aa4onj59OhaLYYybFRCEMBaL0fNxBw4cSCQStN0QRUBut3t4eJjq6U+dOiVJElVPRBFQIBDo6+ujCqirqyscDlONGykC2lYyYSR6pBoQQapHZwAoHo/TuARYAOrs7GQASFEUqn6aIiBJkhgA6ujooFpUo6iaTa7koFwnaoWadLNe81tbWwzoaJrWrICWl5cZAFpbW6OabVAEtLi4yABQsVjUNK0pAWWzWQaAcrlcswKanZ1VVZUqHYRQEwOq1Wpv3ryhCmh6erpcLjdrNl+v1ycnJ+l5UELI27dvv3//3qxxEADgy5cvExMT9Mznw4cPtFtAdAPFarU6Pj5eKpVM17yxsfHy5cvV1VXazXu62gVBKBQKT58+rdVqJqrFGL948eLdu3dU8/g/H4FBUaJYLAqC0NPTY9brMD4+PjY25mDSracOCABACJmZmXE6nUePHjWu8NWrV48ePSKEsGlAs7Agfd5nenq6Wq0mk0kjDzY2NvbkyRMIIbP2PLvhBUEQ8vl8NpuNx+M+n29bzhVjvLKycv/+/YmJCcazQuwA6YzW1tYmJyf1SY+2trZ/rRk1Go1SqfT69esHDx4UCgVmNaa/zZ/9ABUhRFXVYDB48uTJeDweiUQkSfL7/T9MA2NcqVTK5fLy8vL8/PzU1FSxWHS5XJaM4wGr9sUwxqqqer3eUCgkSZJuUBBCfTRvc3OzXC6vrKxUKhWn02nhCJ5lM4oQQo/HgxD6+vXr58+fHf8sDOp+HQDg8XgclorFU676dKLlo6yWRdItIBwQB8QBcUCtfosRQjRNQwhhjPUC4w46WXryBSHU1zgEQWBz99EFhDGu1+t+v//48ePxeFxRlD179ng8nh0Efgiher2+vr6ur3HMzMysrq7uTJVdACGEurq6Ll++nEgkPB7Pj9jPoDHqOxyqqubz+WfPnuVyuV9XPeyeagAAAoHArVu3BgcHab8CuVzu4cOHpVKJUnfA5GweY+xyuc6cOXPv3r1IJMLAR8iyPDw8XK/Xi8Wiqqqmm5KZgBBC7e3tN27cuHbtGuPVpf7+/lAoNDs7W61WzfVKpgHSSzw3b95MpVKW3MfRaDQSiczNzVUqFRMZmQOIEOL1eq9fv3727FlL1t50URRFluX5+flqtWpWEGAOIFEUU6nUlStXLKSjy759+xwOx9zcnKZpphzGHMzhcDiTydgk9r1w4YIp7RPTAAmCkMlk2FeLf/tIEKbTab/fbwtAhJBoNGrutpNx6e7uPnTokC1eMU3T0um0DZPMkZER6wERQnw+n/FFSxpy7Nix3bt3WwwIIcRgIWnHkkwmjecfRgGx7DtuV/r6+iwGhDHev3+/bQF1dnYaH6E2CkiWZdsC2rt3r8WAHA5HW1ubbQGZcjajgOwTH/4qNko1Wlg4IA6IA+KAOKBWBUQIsfNojyliKIoRRfH9+/dut9umf3wzpoUNNQ4BAJubmwz+ic+OxefzWWlBhJD29nbug7iT5sIBcUAcEAfEAXFAHBAHxOVn+QMrmWpuPZx12gAAAABJRU5ErkJggg==", 'status': '' }, attributes); this.set(attrs); @@ -30930,10 +30298,13 @@ define("converse-dependencies", [ showInRoster: function () { var chatStatus = this.get('chat_status'); - if (converse.show_only_online_users && chatStatus !== 'online') - return false; - if (converse.hide_offline_users && chatStatus === 'offline') + if ((converse.show_only_online_users && chatStatus !== 'online') || (converse.hide_offline_users && chatStatus === 'offline')) { + // If pending or requesting, show + if ((this.get('ask') === 'subscribe') || (this.get('subscription') === 'from') || (this.get('requesting') === true)) { + return true; + } return false; + } return true; } }); @@ -31024,17 +30395,7 @@ define("converse-dependencies", [ openChat: function (ev) { if (ev && ev.preventDefault) { ev.preventDefault(); } - // XXX: Can this.model.attributes be used here, instead of - // manually specifying all attributes? - return converse.chatboxviews.showChat({ - 'id': this.model.get('jid'), - 'jid': this.model.get('jid'), - 'fullname': this.model.get('fullname'), - 'image_type': this.model.get('image_type'), - 'image': this.model.get('image'), - 'url': this.model.get('url'), - 'status': this.model.get('status') - }); + return converse.chatboxviews.showChat(this.model.attributes); }, removeContact: function (ev) { @@ -31184,20 +30545,20 @@ define("converse-dependencies", [ /* The localstorage cache containing roster contacts might contain * some contacts that aren't actually in our roster anymore. We * therefore need to remove them now. + * + * TODO: The method is a performance bottleneck. + * Basically we need to chuck out strophe.roster and + * rewrite it with backbone.js and well integrated into + * converse.js. Then we won't need to have this method at all. */ - var id, i, contact; - for (i=0; i < this.models.length; ++i) { - id = this.models[i].get('id'); - if (_.indexOf(_.pluck(items, 'jid'), id) === -1) { - contact = this.get(id); - if (contact && !contact.get('requesting')) { - contact.destroy(); - } + _.each(_.difference(this.pluck('jid'), _.pluck(items, 'jid')), $.proxy(function (jid) { + var contact = this.get(jid); + if (contact && !contact.get('requesting')) { + contact.destroy(); } - } + }, this)); }, - // TODO: see if we can only use 2nd item par rosterHandler: function (items, item) { converse.emit('roster', items); this.clearCache(items); @@ -31907,7 +31268,7 @@ define("converse-dependencies", [ this.XMPPStatus = Backbone.Model.extend({ initialize: function () { this.set({ - 'status' : this.get('status') || 'online' + 'status' : this.getStatus() }); this.on('change', $.proxy(function (item) { if (this.get('fullname') === undefined) { @@ -31927,12 +31288,14 @@ define("converse-dependencies", [ }, this)); }, - sendPresence: function (type) { - if (type === undefined) { + sendPresence: function (type, status_message) { + if (typeof type === 'undefined') { type = this.get('status') || 'online'; } - var status_message = this.get('status_message'), - presence; + if (typeof status_message === 'undefined') { + status_message = this.get('status_message'); + } + var presence; // Most of these presence types are actually not explicitly sent, // but I add all of them here fore reference and future proofing. if ((type === 'unavailable') || @@ -31966,8 +31329,13 @@ define("converse-dependencies", [ this.save({'status': value}); }, + getStatus: function() { + return this.get('status') || 'online'; + }, + setStatusMessage: function (status_message) { - converse.connection.send($pres().c('show').t(this.get('status')).up().c('status').t(status_message)); + this.sendPresence(this.getStatus(), status_message); + var prev_status = this.get('status_message'); this.save({'status_message': status_message}); if (this.xhr_custom_status) { $.ajax({ @@ -31976,6 +31344,9 @@ define("converse-dependencies", [ data: {'msg': status_message} }); } + if (prev_status === status_message) { + this.trigger("update-status-ui", this); + } } }); @@ -31990,7 +31361,9 @@ define("converse-dependencies", [ }, initialize: function () { - this.model.on("change", this.updateStatusUI, this); + this.model.on("change:status", this.updateStatusUI, this); + this.model.on("change:status_message", this.updateStatusUI, this); + this.model.on("update-status-ui", this.updateStatusUI, this); }, render: function () { @@ -32041,8 +31414,7 @@ define("converse-dependencies", [ setStatusMessage: function (ev) { ev.preventDefault(); - var status_message = $(ev.target).find('input').val(); - this.model.setStatusMessage(status_message); + this.model.setStatusMessage($(ev.target).find('input').val()); }, setStatus: function (ev) { @@ -32075,9 +31447,6 @@ define("converse-dependencies", [ }, updateStatusUI: function (model) { - if (!(_.has(model.changed, 'status')) && !(_.has(model.changed, 'status_message'))) { - return; - } var stat = model.get('status'); // # For translators: the %1$s part gets replaced with the status // # Example, I am online @@ -32133,7 +31502,7 @@ define("converse-dependencies", [ * TODO: these features need to be added in the relevant * feature-providing Models, not here */ - converse.connection.disco.addFeature('http://jabber.org/protocol/chatstates'); // Limited support + converse.connection.disco.addFeature(Strophe.NS.CHATSTATES); converse.connection.disco.addFeature('http://jabber.org/protocol/rosterx'); // Limited support converse.connection.disco.addFeature('jabber:x:conference'); converse.connection.disco.addFeature('urn:xmpp:carbons:2'); @@ -32745,10 +32114,16 @@ define("converse-dependencies", [ // // Also, what do we do when the keepalive session values are // expired? Do we try to fall back? - if (!this.bosh_service_url) { - throw("Error: you must supply a value for the bosh_service_url"); + if (!this.bosh_service_url && ! this.websocket_url) { + throw("Error: you must supply a value for the bosh_service_url or websocket_url"); + } + if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) { + this.connection = new Strophe.Connection(this.websocket_url); + } else if (this.bosh_service_url) { + this.connection = new Strophe.Connection(this.bosh_service_url); + } else { + throw("Error: this browser does not support websockets and no bosh_service_url specified."); } - this.connection = new Strophe.Connection(this.bosh_service_url); this.setUpXMLLogging(); if (this.prebind) { @@ -32765,11 +32140,32 @@ define("converse-dependencies", [ sid = this.session.get('sid'); jid = this.session.get('jid'); if (rid && jid && sid) { - this.session.save({rid: rid}); // The RID needs to be increased with each request. + // The RID needs to be increased with each request. + this.session.save({rid: rid}); this.connection.attach(jid, sid, rid, this.onConnect); } else if (this.prebind) { - delete this.connection; - this.emit('noResumeableSession'); + if (this.prebind_url) { + $.ajax({ + url: this.prebind_url, + type: 'GET', + success: function (response) { + this.session.save({rid: rid}); + this.connection.attach( + response.jid, + response.sid, + response.rid, + this.onConnect + ); + }.bind(this), + error: function (response) { + delete this.connection; + this.emit('noResumeableSession'); + }.bind(this) + }); + } else { + delete this.connection; + this.emit('noResumeableSession'); + } } } } @@ -32833,20 +32229,38 @@ define("converse-dependencies", [ }; var wrappedChatBox = function (chatbox) { + var view = converse.chatboxviews.get(chatbox.get('jid')); return { + 'close': $.proxy(view.close, view), 'endOTR': $.proxy(chatbox.endOTR, chatbox), + 'focus': $.proxy(view.focus, view), 'get': $.proxy(chatbox.get, chatbox), 'initiateOTR': $.proxy(chatbox.initiateOTR, chatbox), 'maximize': $.proxy(chatbox.maximize, chatbox), 'minimize': $.proxy(chatbox.minimize, chatbox), - 'set': $.proxy(chatbox.set, chatbox), - 'open': chatbox.trigger.bind(chatbox, 'show') + 'set': $.proxy(chatbox.set, chatbox) }; }; return { 'initialize': function (settings, callback) { converse.initialize(settings, callback); }, + 'settings': { + 'get': function (key) { + if (_.contains(Object.keys(converse.default_settings), key)) { + return converse[key]; + } + }, + 'set': function (key, val) { + var o = {}; + if (typeof key === "object") { + _.extend(converse, _.pick(key, Object.keys(converse.default_settings))); + } else if (typeof key === "string") { + o[key] = val; + _.extend(converse, _.pick(o, Object.keys(converse.default_settings))); + } + } + }, 'contacts': { 'get': function (jids) { var _transform = function (jid) { @@ -32856,14 +32270,16 @@ define("converse-dependencies", [ } return null; }; - if (typeof jids === "string") { + if (typeof jids === "undefined") { + jids = converse.roster.pluck('jid'); + } else if (typeof jids === "string") { return _transform(jids); } return _.map(jids, _transform); } }, 'chats': { - 'get': function (jids) { + 'open': function (jids) { var _transform = function (jid) { var chatbox = converse.chatboxes.get(jid); if (!chatbox) { @@ -32883,10 +32299,28 @@ define("converse-dependencies", [ } return wrappedChatBox(chatbox); }; - if (typeof jids === "string") { + if (typeof jids === "undefined") { + converse.log("chats.open: You need to provide at least one JID", "error"); + return null; + } else if (typeof jids === "string") { return _transform(jids); } return _.map(jids, _transform); + }, + 'get': function (jids) { + var _transform = function (jid) { + var chatbox = converse.chatboxes.get(jid); + if (!chatbox) { + return null; + } + return wrappedChatBox(chatbox); + }; + if (typeof jids === "undefined") { + jids = converse.roster.pluck('jid'); + } else if (typeof jids === "string") { + return _transform(jids); + } + return _.filter(_.map(jids, _transform), function (i) {return i !== null;}); } }, 'tokens': { @@ -32912,6 +32346,9 @@ define("converse-dependencies", [ converse.off(evt, handler); }, }, + 'send': function (stanza) { + converse.connection.send(stanza); + }, 'plugins': { 'add': function (name, callback) { converse.plugins[name] = callback; @@ -32946,43 +32383,12 @@ define("converse-dependencies", [ 'env': { 'jQuery': $, 'Strophe': Strophe, - '_': _ - }, - - // Deprecated API methods - 'getBuddy': function (jid) { - converse.log('WARNING: the "getBuddy" API method has been deprecated. Please use "contacts.get" instead'); - return this.contacts.get(jid); - }, - 'getChatBox': function (jid) { - converse.log('WARNING: the "getChatBox" API method has been deprecated. Please use "chats.get" instead'); - return this.chats.get(jid); - }, - 'openChatBox': function (jid) { - converse.log('WARNING: the "openChatBox" API method has been deprecated. Please use "chats.get(jid).open()" instead'); - var chat = this.chats.get(jid); - if (chat) { chat.open(); } - return chat; - }, - 'getRID': function () { - converse.log('WARNING: the "getRID" API method has been deprecated. Please use "tokens.get(\'rid\')" instead'); - return this.tokens.get('rid'); - }, - 'getSID': function () { - converse.log('WARNING: the "getSID" API method has been deprecated. Please use "tokens.get(\'sid\')" instead'); - return this.tokens.get('sid'); - }, - 'once': function (evt, handler) { - converse.log('WARNING: the "one" API method has been deprecated. Please use "listen.once" instead'); - return this.listen.once(evt, handler); - }, - 'on': function (evt, handler) { - converse.log('WARNING: the "on" API method has been deprecated. Please use "listen.on" instead'); - return this.listen.on(evt, handler); - }, - 'off': function (evt, handler) { - converse.log('WARNING: the "off" API method has been deprecated. Please use "listen.not" instead'); - return this.listen.not(evt, handler); + '$build': $build, + '$iq': $iq, + '$pres': $pres, + '$msg': $msg, + '_': _, + 'b64_sha1': b64_sha1 } }; })); @@ -32990,9 +32396,9 @@ define("converse-dependencies", [ var config; if (typeof(require) === 'undefined') { /* XXX: Hack to work around r.js's stupid parsing. - * We want to save the configuration in a variable so that we can reuse it in - * tests/main.js. - */ + * We want to save the configuration in a variable so that we can reuse it in + * tests/main.js. + */ require = { config: function (c) { config = c; @@ -33013,14 +32419,20 @@ require.config({ "eventemitter": "components/otr/build/dep/eventemitter", "jquery": "components/jquery/dist/jquery", "jquery-private": "src/jquery-private", - "jquery.browser": "components/jquery.browser/index", + "jquery.browser": "components/jquery.browser/dist/jquery.browser", "jquery.easing": "components/jquery-easing-original/index", // XXX: Only required for https://conversejs.org website "moment": "components/momentjs/moment", - "strophe": "components/strophe/strophe", + "strophe-base64": "components/strophejs/src/base64", + "strophe-bosh": "components/strophejs/src/bosh", + "strophe-core": "components/strophejs/src/core", + "strophe": "components/strophejs/src/wrapper", + "strophe-md5": "components/strophejs/src/md5", + "strophe-sha1": "components/strophejs/src/sha1", + "strophe-websocket": "components/strophejs/src/websocket", + "strophe-polyfill": "components/strophejs/src/polyfills", "strophe.disco": "components/strophejs-plugins/disco/strophe.disco", - "strophe.muc": "components/strophe.muc/index", "strophe.roster": "src/strophe.roster", - "strophe.vcard": "components/strophejs-plugins/vcard/strophe.vcard", + "strophe.vcard": "src/strophe.vcard", "text": 'components/requirejs-text/text', "tpl": 'components/requirejs-tpl-jcbrand/tpl', "typeahead": "components/typeahead.js/index", @@ -33045,22 +32457,24 @@ require.config({ "otr": "src/otr", // Locales paths - "locales": "locale/locales", + "locales": "src/locales", "jed": "components/jed/jed", - "af": "locale/af/LC_MESSAGES/af", - "de": "locale/de/LC_MESSAGES/de", - "en": "locale/en/LC_MESSAGES/en", - "es": "locale/es/LC_MESSAGES/es", - "fr": "locale/fr/LC_MESSAGES/fr", - "he": "locale/he/LC_MESSAGES/he", - "hu": "locale/hu/LC_MESSAGES/hu", - "id": "locale/id/LC_MESSAGES/id", - "it": "locale/it/LC_MESSAGES/it", - "ja": "locale/ja/LC_MESSAGES/ja", - "nl": "locale/nl/LC_MESSAGES/nl", - "pt_BR": "locale/pt_BR/LC_MESSAGES/pt_BR", - "ru": "locale/ru/LC_MESSAGES/ru", - "zh": "locale/zh/LC_MESSAGES/zh", + "af": "locale/af/LC_MESSAGES/converse.json", + "de": "locale/de/LC_MESSAGES/converse.json", + "en": "locale/en/LC_MESSAGES/converse.json", + "es": "locale/es/LC_MESSAGES/converse.json", + "fr": "locale/fr/LC_MESSAGES/converse.json", + "he": "locale/he/LC_MESSAGES/converse.json", + "hu": "locale/hu/LC_MESSAGES/converse.json", + "id": "locale/id/LC_MESSAGES/converse.json", + "it": "locale/it/LC_MESSAGES/converse.json", + "ja": "locale/ja/LC_MESSAGES/converse.json", + "nb": "locale/nb/LC_MESSAGES/converse.json", + "nl": "locale/nl/LC_MESSAGES/converse.json", + "pl": "locale/pl/LC_MESSAGES/converse.json", + "pt_BR": "locale/pt_BR/LC_MESSAGES/converse.json", + "ru": "locale/ru/LC_MESSAGES/converse.json", + "zh": "locale/zh/LC_MESSAGES/converse.json", // Templates "action": "src/templates/action", @@ -33148,9 +32562,7 @@ require.config({ 'crypto.sha1': { deps: ['crypto.core'] }, 'crypto.sha256': { deps: ['crypto.core'] }, 'bigint': { deps: ['crypto'] }, - 'strophe': { exports: 'Strophe' }, 'strophe.disco': { deps: ['strophe'] }, - 'strophe.muc': { deps: ['strophe'] }, 'strophe.register': { deps: ['strophe'] }, 'strophe.roster': { deps: ['strophe'] }, 'strophe.vcard': { deps: ['strophe'] } diff --git a/builds/converse-no-locales-no-otr.min.js b/builds/converse-no-locales-no-otr.min.js index 0cda95fce..bfd0fc9e4 100644 --- a/builds/converse-no-locales-no-otr.min.js +++ b/builds/converse-no-locales-no-otr.min.js @@ -1,5 +1,5 @@ /** - * @license almond 0.2.9 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. + * @license almond 0.3.0 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/jrburke/almond for details */ @@ -30,7 +30,7 @@ */ /** - * @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. + * @license RequireJS text 2.0.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. * Available via the MIT or new BSD license. * see: http://github.com/requirejs/text for details */ @@ -70,12 +70,6 @@ // } // }); -//! moment.js -//! version : 2.6.0 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com - /* jed.js v0.5.0beta @@ -131,38 +125,11 @@ in order to offer easy upgrades -- jsgettext.berlios.de SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -// (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Backbone may be freely distributed under the MIT license. -// For all details and documentation: -// http://backbonejs.org - -/*! - * Backbone.Overview - * - * Copyright (c) 2014, JC Brand - * Licensed under the Mozilla Public License (MPL) - */ - -/*! - * jQuery Browser Plugin v0.0.6 - * https://github.com/gabceb/jquery-browser-plugin - * - * Original jquery-browser code Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors - * http://jquery.org/license - * - * Modifications Copyright 2013 Gabriel Cebrian - * https://github.com/gabceb - * - * Released under the MIT license - * - * Date: 2013-07-29T17:23:27-07:00 - */ - -/*! - * typeahead.js 0.10.5 - * https://github.com/twitter/typeahead.js - * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT - */ +//! moment.js +//! version : 2.6.0 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com /* * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined @@ -193,6 +160,39 @@ in order to offer easy upgrades -- jsgettext.berlios.de Copyright 2010, François de Metz */ +// (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +/*! + * Backbone.Overview + * + * Copyright (c) 2014, JC Brand + * Licensed under the Mozilla Public License (MPL) + */ + +/*! + * jQuery Browser Plugin 0.0.7 + * https://github.com/gabceb/jquery-browser-plugin + * + * Original jquery-browser code Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors + * http://jquery.org/license + * + * Modifications Copyright 2014 Gabriel Cebrian + * https://github.com/gabceb + * + * Released under the MIT license + * + * Date: 12-12-2014 + */ + +/*! + * typeahead.js 0.10.5 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT + */ + /*! * Converse.js (Web-based XMPP instant messaging client) * http://conversejs.org @@ -201,4 +201,4 @@ in order to offer easy upgrades -- jsgettext.berlios.de * Licensed under the Mozilla Public License (MPL) */ -function b64_sha1(e){return binb2b64(core_sha1(str2binb(e),e.length*8))}function str_sha1(e){return binb2str(core_sha1(str2binb(e),e.length*8))}function b64_hmac_sha1(e,t){return binb2b64(core_hmac_sha1(e,t))}function str_hmac_sha1(e,t){return binb2str(core_hmac_sha1(e,t))}function core_sha1(e,t){e[t>>5]|=128<<24-t%32,e[(t+64>>9<<4)+15]=t;var n=new Array(80),r=1732584193,i=-271733879,s=-1732584194,o=271733878,u=-1009589776,a,f,l,c,h,p,d,v;for(a=0;a16&&(n=core_sha1(n,e.length*8));var r=new Array(16),i=new Array(16);for(var s=0;s<16;s++)r[s]=n[s]^909522486,i[s]=n[s]^1549556828;var o=core_sha1(r.concat(str2binb(t)),512+t.length*8);return core_sha1(i.concat(o),672)}function safe_add(e,t){var n=(e&65535)+(t&65535),r=(e>>16)+(t>>16)+(n>>16);return r<<16|n&65535}function rol(e,t){return e<>>32-t}function str2binb(e){var t=[],n=255;for(var r=0;r>5]|=(e.charCodeAt(r/8)&n)<<24-r%32;return t}function binb2str(e){var t="",n=255;for(var r=0;r>5]>>>24-r%32&n);return t}function binb2b64(e){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n="",r,i;for(var s=0;s>2]>>8*(3-s%4)&255)<<16|(e[s+1>>2]>>8*(3-(s+1)%4)&255)<<8|e[s+2>>2]>>8*(3-(s+2)%4)&255;for(i=0;i<4;i++)s*8+i*6>e.length*32?n+="=":n+=t.charAt(r>>6*(3-i)&63)}return n}var requirejs,require,define;(function(e){function h(e,t){return f.call(e,t)}function p(e,t){var n,r,i,s,o,a,f,l,h,p,d,v=t&&t.split("/"),m=u.map,g=m&&m["*"]||{};if(e&&e.charAt(0)===".")if(t){v=v.slice(0,v.length-1),e=e.split("/"),o=e.length-1,u.nodeIdCompat&&c.test(e[o])&&(e[o]=e[o].replace(c,"")),e=v.concat(e);for(h=0;h0&&(e.splice(h-1,2),h-=2)}}e=e.join("/")}else e.indexOf("./")===0&&(e=e.substring(2));if((v||g)&&m){n=e.split("/");for(h=n.length;h>0;h-=1){r=n.slice(0,h).join("/");if(v)for(p=v.length;p>0;p-=1){i=m[v.slice(0,p).join("/")];if(i){i=i[r];if(i){s=i,a=h;break}}}if(s)break;!f&&g&&g[r]&&(f=g[r],l=h)}!s&&f&&(s=f,a=l),s&&(n.splice(0,a,s),e=n.join("/"))}return e}function d(t,r){return function(){return n.apply(e,l.call(arguments,0).concat([t,r]))}}function v(e){return function(t){return p(t,e)}}function m(e){return function(t){s[e]=t}}function g(n){if(h(o,n)){var r=o[n];delete o[n],a[n]=!0,t.apply(e,r)}if(!h(s,n)&&!h(a,n))throw new Error("No "+n);return s[n]}function y(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function b(e){return function(){return u&&u.config&&u.config[e]||{}}}var t,n,r,i,s={},o={},u={},a={},f=Object.prototype.hasOwnProperty,l=[].slice,c=/\.js$/;r=function(e,t){var n,r=y(e),i=r[0];return e=r[1],i&&(i=p(i,t),n=g(i)),i?n&&n.normalize?e=n.normalize(e,v(t)):e=p(e,t):(e=p(e,t),r=y(e),i=r[0],e=r[1],i&&(n=g(i))),{f:i?i+"!"+e:e,n:e,pr:i,p:n}},i={require:function(e){return d(e)},exports:function(e){var t=s[e];return typeof t!="undefined"?t:s[e]={}},module:function(e){return{id:e,uri:"",exports:s[e],config:b(e)}}},t=function(t,n,u,f){var l,c,p,v,y,b=[],w=typeof u,E;f=f||t;if(w==="undefined"||w==="function"){n=!n.length&&u.length?["require","exports","module"]:n;for(y=0;y0&&t-1 in e}function x(e,t,n){if(p.isFunction(t))return p.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return p.grep(e,function(e){return e===t!==n});if(typeof t=="string"){if(S.test(t))return p.filter(t,e,n);t=p.filter(t,e)}return p.grep(e,function(e){return p.inArray(e,t)>=0!==n})}function O(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function D(e){var t=_[e]={};return p.each(e.match(M)||[],function(e,n){t[n]=!0}),t}function H(){N.addEventListener?(N.removeEventListener("DOMContentLoaded",B,!1),e.removeEventListener("load",B,!1)):(N.detachEvent("onreadystatechange",B),e.detachEvent("onload",B))}function B(){if(N.addEventListener||event.type==="load"||N.readyState==="complete")H(),p.ready()}function R(e,t,n){if(n===undefined&&e.nodeType===1){var r="data-"+t.replace(q,"-$1").toLowerCase();n=e.getAttribute(r);if(typeof n=="string"){try{n=n==="true"?!0:n==="false"?!1:n==="null"?null:+n+""===n?+n:I.test(n)?p.parseJSON(n):n}catch(i){}p.data(e,t,n)}else n=undefined}return n}function U(e){var t;for(t in e){if(t==="data"&&p.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function z(e,t,r,i){if(!p.acceptData(e))return;var s,o,u=p.expando,a=e.nodeType,f=a?p.cache:e,l=a?e[u]:e[u]&&u;if((!l||!f[l]||!i&&!f[l].data)&&r===undefined&&typeof t=="string")return;l||(a?l=e[u]=n.pop()||p.guid++:l=u),f[l]||(f[l]=a?{}:{toJSON:p.noop});if(typeof t=="object"||typeof t=="function")i?f[l]=p.extend(f[l],t):f[l].data=p.extend(f[l].data,t);return o=f[l],i||(o.data||(o.data={}),o=o.data),r!==undefined&&(o[p.camelCase(t)]=r),typeof t=="string"?(s=o[t],s==null&&(s=o[p.camelCase(t)])):s=o,s}function W(e,t,n){if(!p.acceptData(e))return;var r,i,s=e.nodeType,o=s?p.cache:e,u=s?e[p.expando]:p.expando;if(!o[u])return;if(t){r=n?o[u]:o[u].data;if(r){p.isArray(t)?t=t.concat(p.map(t,p.camelCase)):t in r?t=[t]:(t=p.camelCase(t),t in r?t=[t]:t=t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!U(r):!p.isEmptyObject(r))return}}if(!n){delete o[u].data;if(!U(o[u]))return}s?p.cleanData([e],!0):c.deleteExpando||o!=o.window?delete o[u]:o[u]=null}function tt(){return!0}function nt(){return!1}function rt(){try{return N.activeElement}catch(e){}}function it(e){var t=st.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Et(e,t){var n,r,i=0,s=typeof e.getElementsByTagName!==j?e.getElementsByTagName(t||"*"):typeof e.querySelectorAll!==j?e.querySelectorAll(t||"*"):undefined;if(!s)for(s=[],n=e.childNodes||e;(r=n[i])!=null;i++)!t||p.nodeName(r,t)?s.push(r):p.merge(s,Et(r,t));return t===undefined||t&&p.nodeName(e,t)?p.merge([e],s):s}function St(e){K.test(e.type)&&(e.defaultChecked=e.checked)}function xt(e,t){return p.nodeName(e,"table")&&p.nodeName(t.nodeType!==11?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Tt(e){return e.type=(p.find.attr(e,"type")!==null)+"/"+e.type,e}function Nt(e){var t=mt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Ct(e,t){var n,r=0;for(;(n=e[r])!=null;r++)p._data(n,"globalEval",!t||p._data(t[r],"globalEval"))}function kt(e,t){if(t.nodeType!==1||!p.hasData(e))return;var n,r,i,s=p._data(e),o=p._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r")).appendTo(t.documentElement),t=(At[0].contentWindow||At[0].contentDocument).document,t.write(),t.close(),n=Mt(e,t),At.detach();Ot[e]=n}return n}function Ft(e,t){return{get:function(){var n=e();if(n==null)return;if(n){delete this.get;return}return(this.get=t).apply(this,arguments)}}}function $t(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Vt.length;while(i--){t=Vt[i]+n;if(t in e)return t}return r}function Jt(e,t){var n,r,i,s=[],o=0,u=e.length;for(;o=0&&n=0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},isPlainObject:function(e){var t;if(!e||p.type(e)!=="object"||e.nodeType||p.isWindow(e))return!1;try{if(e.constructor&&!f.call(e,"constructor")&&!f.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}if(c.ownLast)for(t in e)return f.call(e,t);for(t in e);return t===undefined||f.call(e,t)},type:function(e){return e==null?e+"":typeof e=="object"||typeof e=="function"?u[a.call(e)]||"object":typeof e},globalEval:function(t){t&&p.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(v,"ms-").replace(m,g)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,s=e.length,o=y(e);if(n)if(o)for(;ir.cacheLength&&delete t[e.shift()],t[n+" "]=i}var e=[];return t}function st(e){return e[y]=!0,e}function ot(e){var t=c.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ut(e,t){var n=e.split("|"),i=e.length;while(i--)r.attrHandle[n[i]]=t}function at(e,t){var n=t&&e,r=n&&e.nodeType===1&&t.nodeType===1&&(~t.sourceIndex||k)-(~e.sourceIndex||k);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function ct(e){return st(function(t){return t=+t,st(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ht(e){return e&&typeof e.getElementsByTagName!==C&&e}function pt(){}function dt(e,t){var n,i,s,o,u,a,f,l=x[e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=r.preFilter;while(u){if(!n||(i=U.exec(u)))i&&(u=u.slice(i[0].length)||u),a.push(s=[]);n=!1;if(i=z.exec(u))n=i.shift(),s.push({value:n,type:i[0].replace(R," ")}),u=u.slice(n.length);for(o in r.filter)(i=$[o].exec(u))&&(!f[o]||(i=f[o](i)))&&(n=i.shift(),s.push({value:n,type:o,matches:i}),u=u.slice(n.length));if(!n)break}return t?u.length:u?rt.error(e):x(e,a).slice(0)}function vt(e){var t=0,n=e.length,r="";for(;t1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function yt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=yt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):_.apply(o,g)})}function wt(e){var t,n,i,s=e.length,o=r.relative[e[0].type],a=o||r.relative[" "],f=o?1:0,l=mt(function(e){return e===t},a,!0),c=mt(function(e){return P.call(t,e)>-1},a,!0),h=[function(e,n,r){return!o&&(r||n!==u)||((t=n).nodeType?l(e,n,r):c(e,n,r))}];for(;f1&>(h),f>1&&vt(e.slice(0,f-1).concat({value:e[f-2].type===" "?"*":""})).replace(R,"$1"),n,f0,i=e.length>0,s=function(s,o,a,f,l){var h,p,d,v=0,m="0",g=s&&[],y=[],b=u,E=s||i&&r.find.TAG("*",l),S=w+=b==null?1:Math.random()||.1,x=E.length;l&&(u=o!==c&&o);for(;m!==x&&(h=E[m])!=null;m++){if(i&&h){p=0;while(d=e[p++])if(d(h,o,a)){f.push(h);break}l&&(w=S)}n&&((h=!d&&h)&&v--,s&&g.push(h))}v+=m;if(n&&m!==v){p=0;while(d=t[p++])d(g,y,o,a);if(s){if(v>0)while(m--)!g[m]&&!y[m]&&(y[m]=O.call(f));y=yt(y)}_.apply(f,y),l&&!s&&y.length>0&&v+t.length>1&&rt.uniqueSort(f)}return l&&(w=S,u=b),g};return n?st(s):s}function St(e,t,n){var r=0,i=t.length;for(;r2&&(f=a[0]).type==="ID"&&n.getById&&t.nodeType===9&&p&&r.relative[a[1].type]){t=(r.find.ID(f.matches[0].replace(et,tt),t)||[])[0];if(!t)return i;e=e.slice(a.shift().value.length)}u=$.needsContext.test(e)?0:a.length;while(u--){f=a[u];if(r.relative[l=f.type])break;if(c=r.find[l])if(s=c(f.matches[0].replace(et,tt),Y.test(a[0].type)&&ht(t.parentNode)||t)){a.splice(u,1),e=s.length&&vt(a);if(!e)return _.apply(i,s),i;break}}}return o(e,h)(s,t,!p,i,Y.test(e)&&ht(t.parentNode)||t),i}var t,n,r,i,s,o,u,a,f,l,c,h,p,d,v,m,g,y="sizzle"+ -(new Date),b=e.document,w=0,E=0,S=it(),x=it(),T=it(),N=function(e,t){return e===t&&(f=!0),0},C=typeof undefined,k=1<<31,L={}.hasOwnProperty,A=[],O=A.pop,M=A.push,_=A.push,D=A.slice,P=A.indexOf||function(e){var t=0,n=this.length;for(;t+~]|"+B+")"+B+"*"),W=new RegExp("="+B+"*([^\\]'\"]*?)"+B+"*\\]","g"),X=new RegExp(q),V=new RegExp("^"+F+"$"),$={ID:new RegExp("^#("+j+")"),CLASS:new RegExp("^\\.("+j+")"),TAG:new RegExp("^("+j.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+B+"*(even|odd|(([+-]|)(\\d*)n|)"+B+"*(?:([+-]|)"+B+"*(\\d+)|))"+B+"*\\)|)","i"),bool:new RegExp("^(?:"+H+")$","i"),needsContext:new RegExp("^"+B+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+B+"*((?:-\\d)?\\d*)"+B+"*\\)|)(?=[^-]|$)","i")},J=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,G=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Y=/[+~]/,Z=/'|\\/g,et=new RegExp("\\\\([\\da-f]{1,6}"+B+"?|("+B+")|.)","ig"),tt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,r&1023|56320)};try{_.apply(A=D.call(b.childNodes),b.childNodes),A[b.childNodes.length].nodeType}catch(nt){_={apply:A.length?function(e,t){M.apply(e,D.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}n=rt.support={},s=rt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},l=rt.setDocument=function(e){var t,i=e?e.ownerDocument||e:b,o=i.defaultView;if(i===c||i.nodeType!==9||!i.documentElement)return c;c=i,h=i.documentElement,p=!s(i),o&&o!==o.top&&(o.addEventListener?o.addEventListener("unload",function(){l()},!1):o.attachEvent&&o.attachEvent("onunload",function(){l()})),n.attributes=ot(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ot(function(e){return e.appendChild(i.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(i.getElementsByClassName)&&ot(function(e){return e.innerHTML="
",e.firstChild.className="i",e.getElementsByClassName("i").length===2}),n.getById=ot(function(e){return h.appendChild(e).id=y,!i.getElementsByName||!i.getElementsByName(y).length}),n.getById?(r.find.ID=function(e,t){if(typeof t.getElementById!==C&&p){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},r.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(delete r.find.ID,r.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==C&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=n.getElementsByTagName?function(e,t){if(typeof t.getElementsByTagName!==C)return t.getElementsByTagName(e)}:function(e,t){var n,r=[],i=0,s=t.getElementsByTagName(e);if(e==="*"){while(n=s[i++])n.nodeType===1&&r.push(n);return r}return s},r.find.CLASS=n.getElementsByClassName&&function(e,t){if(typeof t.getElementsByClassName!==C&&p)return t.getElementsByClassName(e)},v=[],d=[];if(n.qsa=Q.test(i.querySelectorAll))ot(function(e){e.innerHTML="",e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+B+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||d.push("\\["+B+"*(?:value|"+H+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ot(function(e){var t=i.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&d.push("name"+B+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")});return(n.matchesSelector=Q.test(m=h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ot(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),v.push("!=",q)}),d=d.length&&new RegExp(d.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),g=t||Q.test(h.contains)?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!r&&r.nodeType===1&&!!(n.contains?n.contains(r):e.compareDocumentPosition&&e.compareDocumentPosition(r)&16)}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},N=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r?r:(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,r&1||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===i||e.ownerDocument===b&&g(b,e)?-1:t===i||t.ownerDocument===b&&g(b,t)?1:a?P.call(a,e)-P.call(a,t):0:r&4?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,s=e.parentNode,o=t.parentNode,u=[e],l=[t];if(!s||!o)return e===i?-1:t===i?1:s?-1:o?1:a?P.call(a,e)-P.call(a,t):0;if(s===o)return at(e,t);n=e;while(n=n.parentNode)u.unshift(n);n=t;while(n=n.parentNode)l.unshift(n);while(u[r]===l[r])r++;return r?at(u[r],l[r]):u[r]===b?-1:l[r]===b?1:0},i},rt.matches=function(e,t){return rt(e,null,null,t)},rt.matchesSelector=function(e,t){(e.ownerDocument||e)!==c&&l(e),t=t.replace(W,"='$1']");if(n.matchesSelector&&p&&(!v||!v.test(t))&&(!d||!d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&e.document.nodeType!==11)return r}catch(i){}return rt(t,c,null,[e]).length>0},rt.contains=function(e,t){return(e.ownerDocument||e)!==c&&l(e),g(e,t)},rt.attr=function(e,t){(e.ownerDocument||e)!==c&&l(e);var i=r.attrHandle[t.toLowerCase()],s=i&&L.call(r.attrHandle,t.toLowerCase())?i(e,t,!p):undefined;return s!==undefined?s:n.attributes||!p?e.getAttribute(t):(s=e.getAttributeNode(t))&&s.specified?s.value:null},rt.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},rt.uniqueSort=function(e){var t,r=[],i=0,s=0;f=!n.detectDuplicates,a=!n.sortStable&&e.slice(0),e.sort(N);if(f){while(t=e[s++])t===e[s]&&(i=r.push(s));while(i--)e.splice(r[i],1)}return a=null,e},i=rt.getText=function(e){var t,n="",r=0,s=e.nodeType;if(!s)while(t=e[r++])n+=i(t);else if(s===1||s===9||s===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(s===3||s===4)return e.nodeValue;return n},r=rt.selectors={cacheLength:50,createPseudo:st,match:$,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1].slice(0,3)==="nth"?(e[3]||rt.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*(e[3]==="even"||e[3]==="odd")),e[5]=+(e[7]+e[8]||e[3]==="odd")):e[3]&&rt.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return $.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&X.test(n)&&(t=dt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(et,tt).toLowerCase();return e==="*"?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=S[e+" "];return t||(t=new RegExp("(^|"+B+")"+e+"("+B+"|$)"))&&S(e,function(e){return t.test(typeof e.className=="string"&&e.className||typeof e.getAttribute!==C&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=rt.attr(r,e);return i==null?t==="!=":t?(i+="",t==="="?i===n:t==="!="?i!==n:t==="^="?n&&i.indexOf(n)===0:t==="*="?n&&i.indexOf(n)>-1:t==="$="?n&&i.slice(-n.length)===n:t==="~="?(" "+i+" ").indexOf(n)>-1:t==="|="?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var s=e.slice(0,3)!=="nth",o=e.slice(-4)!=="last",u=t==="of-type";return r===1&&i===0?function(e){return!!e.parentNode}:function(t,n,a){var f,l,c,h,p,d,v=s!==o?"nextSibling":"previousSibling",m=t.parentNode,g=u&&t.nodeName.toLowerCase(),b=!a&&!u;if(m){if(s){while(v){c=t;while(c=c[v])if(u?c.nodeName.toLowerCase()===g:c.nodeType===1)return!1;d=v=e==="only"&&!d&&"nextSibling"}return!0}d=[o?m.firstChild:m.lastChild];if(o&&b){l=m[y]||(m[y]={}),f=l[e]||[],p=f[0]===w&&f[1],h=f[0]===w&&f[2],c=p&&m.childNodes[p];while(c=++p&&c&&c[v]||(h=p=0)||d.pop())if(c.nodeType===1&&++h&&c===t){l[e]=[w,p,h];break}}else if(b&&(f=(t[y]||(t[y]={}))[e])&&f[0]===w)h=f[1];else while(c=++p&&c&&c[v]||(h=p=0)||d.pop())if((u?c.nodeName.toLowerCase()===g:c.nodeType===1)&&++h){b&&((c[y]||(c[y]={}))[e]=[w,h]);if(c===t)break}return h-=i,h===r||h%r===0&&h/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||rt.error("unsupported pseudo: "+e);return i[y]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?st(function(e,n){var r,s=i(e,t),o=s.length;while(o--)r=P.call(e,s[o]),e[r]=!(n[r]=s[o])}):function(e){return i(e,0,n)}):i}},pseudos:{not:st(function(e){var t=[],n=[],r=o(e.replace(R,"$1"));return r[y]?st(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:st(function(e){return function(t){return rt(e,t).length>0}}),contains:st(function(e){return function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:st(function(e){return V.test(e||"")||rt.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=p?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||n.indexOf(e+"-")===0;while((t=t.parentNode)&&t.nodeType===1);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===c.activeElement&&(!c.hasFocus||c.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return K.test(e.nodeName)},input:function(e){return J.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},text:function(e){var t;return e.nodeName.toLowerCase()==="input"&&e.type==="text"&&((t=e.getAttribute("type"))==null||t.toLowerCase()==="text")},first:ct(function(){return[0]}),last:ct(function(e,t){return[t-1]}),eq:ct(function(e,t,n){return[n<0?n+t:n]}),even:ct(function(e,t){var n=0;for(;n=0;)e.push(r);return e}),gt:ct(function(e,t,n){var r=n<0?n+t:n;for(;++r(?:<\/\1>|)$/,S=/^.[^:#\[\.,]*$/;p.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),t.length===1&&r.nodeType===1?p.find.matchesSelector(r,e)?[r]:[]:p.find.matches(e,p.grep(t,function(e){return e.nodeType===1}))},p.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if(typeof e!="string")return this.pushStack(p(e).filter(function(){for(t=0;t1?p.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},filter:function(e){return this.pushStack(x(this,e||[],!1))},not:function(e){return this.pushStack(x(this,e||[],!0))},is:function(e){return!!x(this,typeof e=="string"&&w.test(e)?p(e):e||[],!1).length}});var T,N=e.document,C=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=p.fn.init=function(e,t){var n,r;if(!e)return this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?n=[null,e,null]:n=C.exec(e);if(n&&(n[1]||!t)){if(n[1]){t=t instanceof p?t[0]:t,p.merge(this,p.parseHTML(n[1],t&&t.nodeType?t.ownerDocument||t:N,!0));if(E.test(n[1])&&p.isPlainObject(t))for(n in t)p.isFunction(this[n])?this[n](t[n]):this.attr(n,t[n]);return this}r=N.getElementById(n[2]);if(r&&r.parentNode){if(r.id!==n[2])return T.find(e);this.length=1,this[0]=r}return this.context=N,this.selector=e,this}return!t||t.jquery?(t||T).find(e):this.constructor(t).find(e)}return e.nodeType?(this.context=this[0]=e,this.length=1,this):p.isFunction(e)?typeof T.ready!="undefined"?T.ready(e):e(p):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),p.makeArray(e,this))};k.prototype=p.fn,T=p(N);var L=/^(?:parents|prev(?:Until|All))/,A={children:!0,contents:!0,next:!0,prev:!0};p.extend({dir:function(e,t,n){var r=[],i=e[t];while(i&&i.nodeType!==9&&(n===undefined||i.nodeType!==1||!p(i).is(n)))i.nodeType===1&&r.push(i),i=i[t];return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}}),p.fn.extend({has:function(e){var t,n=p(e,this),r=n.length;return this.filter(function(){for(t=0;t-1:n.nodeType===1&&p.find.matchesSelector(n,e))){s.push(n);break}return this.pushStack(s.length>1?p.unique(s):s)},index:function(e){return e?typeof e=="string"?p.inArray(this[0],p(e)):p.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(p.unique(p.merge(this.get(),p(e,t))))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),p.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return p.dir(e,"parentNode")},parentsUntil:function(e,t,n){return p.dir(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return p.dir(e,"nextSibling")},prevAll:function(e){return p.dir(e,"previousSibling")},nextUntil:function(e,t,n){return p.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return p.dir(e,"previousSibling",n)},siblings:function(e){return p.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return p.sibling(e.firstChild)},contents:function(e){return p.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:p.merge([],e.childNodes)}},function(e,t){p.fn[e]=function(n,r){var i=p.map(this,t,n);return e.slice(-5)!=="Until"&&(r=n),r&&typeof r=="string"&&(i=p.filter(r,i)),this.length>1&&(A[e]||(i=p.unique(i)),L.test(e)&&(i=i.reverse())),this.pushStack(i)}});var M=/\S+/g,_={};p.Callbacks=function(e){e=typeof e=="string"?_[e]||D(e):p.extend({},e);var t,n,r,i,s,o,u=[],a=!e.once&&[],f=function(c){n=e.memory&&c,r=!0,s=o||0,o=0,i=u.length,t=!0;for(;u&&s-1)u.splice(r,1),t&&(r<=i&&i--,r<=s&&s--)}),this},has:function(e){return e?p.inArray(e,u)>-1:!!u&&!!u.length},empty:function(){return u=[],i=0,this},disable:function(){return u=a=n=undefined,this},disabled:function(){return!u},lock:function(){return a=undefined,n||l.disable(),this},locked:function(){return!a},fireWith:function(e,n){return u&&(!r||a)&&(n=n||[],n=[e,n.slice?n.slice():n],t?a.push(n):f(n)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l},p.extend({Deferred:function(e){var t=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return p.Deferred(function(n){p.each(t,function(t,s){var o=p.isFunction(e[t])&&e[t];i[s[1]](function(){var e=o&&o.apply(this,arguments);e&&p.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s[0]+"With"](this===r?n.promise():this,o?[e]:arguments)})}),e=null}).promise()},promise:function(e){return e!=null?p.extend(e,r):r}},i={};return r.pipe=r.then,p.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=function(){return i[s[0]+"With"](this===i?r:this,arguments),this},i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=r.call(arguments),i=n.length,s=i!==1||e&&p.isFunction(e.promise)?i:0,o=s===1?e:p.Deferred(),u=function(e,t,n){return function(i){t[e]=this,n[e]=arguments.length>1?r.call(arguments):i,n===a?o.notifyWith(t,n):--s||o.resolveWith(t,n)}},a,f,l;if(i>1){a=new Array(i),f=new Array(i),l=new Array(i);for(;t0)return;P.resolveWith(N,[p]),p.fn.trigger&&p(N).trigger("ready").off("ready")}}),p.ready.promise=function(t){if(!P){P=p.Deferred();if(N.readyState==="complete")setTimeout(p.ready);else if(N.addEventListener)N.addEventListener("DOMContentLoaded",B,!1),e.addEventListener("load",B,!1);else{N.attachEvent("onreadystatechange",B),e.attachEvent("onload",B);var n=!1;try{n=e.frameElement==null&&N.documentElement}catch(r){}n&&n.doScroll&&function i(){if(!p.isReady){try{n.doScroll("left")}catch(e){return setTimeout(i,50)}H(),p.ready()}}()}}return P.promise(t)};var j=typeof undefined,F;for(F in p(c))break;c.ownLast=F!=="0",c.inlineBlockNeedsLayout=!1,p(function(){var e,t,n=N.getElementsByTagName("body")[0];if(!n)return;e=N.createElement("div"),e.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",t=N.createElement("div"),n.appendChild(e).appendChild(t);if(typeof t.style.zoom!==j){t.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1";if(c.inlineBlockNeedsLayout=t.offsetWidth===3)n.style.zoom=1}n.removeChild(e),e=t=null}),function(){var e=N.createElement("div");if(c.deleteExpando==null){c.deleteExpando=!0;try{delete e.test}catch(t){c.deleteExpando=!1}}e=null}(),p.acceptData=function(e){var t=p.noData[(e.nodeName+" ").toLowerCase()],n=+e.nodeType||1;return n!==1&&n!==9?!1:!t||t!==!0&&e.getAttribute("classid")===t};var I=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,q=/([A-Z])/g;p.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?p.cache[e[p.expando]]:e[p.expando],!!e&&!U(e)},data:function(e,t,n){return z(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return z(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)}}),p.fn.extend({data:function(e,t){var n,r,i,s=this[0],o=s&&s.attributes;if(e===undefined){if(this.length){i=p.data(s);if(s.nodeType===1&&!p._data(s,"parsedAttrs")){n=o.length;while(n--)r=o[n].name,r.indexOf("data-")===0&&(r=p.camelCase(r.slice(5)),R(s,r,i[r]));p._data(s,"parsedAttrs",!0)}}return i}return typeof e=="object"?this.each(function(){p.data(this,e)}):arguments.length>1?this.each(function(){p.data(this,e,t)}):s?R(s,e,p.data(s,e)):undefined},removeData:function(e){return this.each(function(){p.removeData(this,e)})}}),p.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=p._data(e,t),n&&(!r||p.isArray(n)?r=p._data(e,t,p.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=p.queue(e,t),r=n.length,i=n.shift(),s=p._queueHooks(e,t),o=function(){p.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return p._data(e,n)||p._data(e,n,{empty:p.Callbacks("once memory").add(function(){p._removeData(e,t+"queue"),p._removeData(e,n)})})}}),p.fn.extend({queue:function(e,t){var n=2;return typeof e!="string"&&(t=e,e="fx",n--),arguments.length
a",c.leadingWhitespace=t.firstChild.nodeType===3,c.tbody=!t.getElementsByTagName("tbody").length,c.htmlSerialize=!!t.getElementsByTagName("link").length,c.html5Clone=N.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",n.type="checkbox",n.checked=!0,e.appendChild(n),c.appendChecked=n.checked,t.innerHTML="",c.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue,e.appendChild(t),t.innerHTML="",c.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,c.noCloneEvent=!0,t.attachEvent&&(t.attachEvent("onclick",function(){c.noCloneEvent=!1}),t.cloneNode(!0).click());if(c.deleteExpando==null){c.deleteExpando=!0;try{delete t.test}catch(r){c.deleteExpando=!1}}e=t=n=null})(),function(){var t,n,r=N.createElement("div");for(t in{submit:!0,change:!0,focusin:!0})n="on"+t,(c[t+"Bubbles"]=n in e)||(r.setAttribute(n,"t"),c[t+"Bubbles"]=r.attributes[n].expando===!1);r=null}();var Q=/^(?:input|select|textarea)$/i,G=/^key/,Y=/^(?:mouse|contextmenu)|click/,Z=/^(?:focusinfocus|focusoutblur)$/,et=/^([^.]*)(?:\.(.+)|)$/;p.event={global:{},add:function(e,t,n,r,i){var s,o,u,a,f,l,c,h,d,v,m,g=p._data(e);if(!g)return;n.handler&&(a=n,n=a.handler,i=a.selector),n.guid||(n.guid=p.guid++),(o=g.events)||(o=g.events={}),(l=g.handle)||(l=g.handle=function(e){return typeof p===j||!!e&&p.event.triggered===e.type?undefined:p.event.dispatch.apply(l.elem,arguments)},l.elem=e),t=(t||"").match(M)||[""],u=t.length;while(u--){s=et.exec(t[u])||[],d=m=s[1],v=(s[2]||"").split(".").sort();if(!d)continue;f=p.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=p.event.special[d]||{},c=p.extend({type:d,origType:m,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&p.expr.match.needsContext.test(i),namespace:v.join(".")},a);if(!(h=o[d])){h=o[d]=[],h.delegateCount=0;if(!f.setup||f.setup.call(e,r,v,l)===!1)e.addEventListener?e.addEventListener(d,l,!1):e.attachEvent&&e.attachEvent("on"+d,l)}f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?h.splice(h.delegateCount++,0,c):h.push(c),p.event.global[d]=!0}e=null},remove:function(e,t,n,r,i){var s,o,u,a,f,l,c,h,d,v,m,g=p.hasData(e)&&p._data(e);if(!g||!(l=g.events))return;t=(t||"").match(M)||[""],f=t.length;while(f--){u=et.exec(t[f])||[],d=m=u[1],v=(u[2]||"").split(".").sort();if(!d){for(d in l)p.event.remove(e,d+t[f],n,r,!0);continue}c=p.event.special[d]||{},d=(r?c.delegateType:c.bindType)||d,h=l[d]||[],u=u[2]&&new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=s=h.length;while(s--)o=h[s],(i||m===o.origType)&&(!n||n.guid===o.guid)&&(!u||u.test(o.namespace))&&(!r||r===o.selector||r==="**"&&o.selector)&&(h.splice(s,1),o.selector&&h.delegateCount--,c.remove&&c.remove.call(e,o));a&&!h.length&&((!c.teardown||c.teardown.call(e,v,g.handle)===!1)&&p.removeEvent(e,d,g.handle),delete l[d])}p.isEmptyObject(l)&&(delete g.handle,p._removeData(e,"events"))},trigger:function(t,n,r,i){var s,o,u,a,l,c,h,d=[r||N],v=f.call(t,"type")?t.type:t,m=f.call(t,"namespace")?t.namespace.split("."):[];u=c=r=r||N;if(r.nodeType===3||r.nodeType===8)return;if(Z.test(v+p.event.triggered))return;v.indexOf(".")>=0&&(m=v.split("."),v=m.shift(),m.sort()),o=v.indexOf(":")<0&&"on"+v,t=t[p.expando]?t:new p.Event(v,typeof t=="object"&&t),t.isTrigger=i?2:3,t.namespace=m.join("."),t.namespace_re=t.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=n==null?[t]:p.makeArray(n,[t]),l=p.event.special[v]||{};if(!i&&l.trigger&&l.trigger.apply(r,n)===!1)return;if(!i&&!l.noBubble&&!p.isWindow(r)){a=l.delegateType||v,Z.test(a+v)||(u=u.parentNode);for(;u;u=u.parentNode)d.push(u),c=u;c===(r.ownerDocument||N)&&d.push(c.defaultView||c.parentWindow||e)}h=0;while((u=d[h++])&&!t.isPropagationStopped())t.type=h>1?a:l.bindType||v,s=(p._data(u,"events")||{})[t.type]&&p._data(u,"handle"),s&&s.apply(u,n),s=o&&u[o],s&&s.apply&&p.acceptData(u)&&(t.result=s.apply(u,n),t.result===!1&&t.preventDefault());t.type=v;if(!i&&!t.isDefaultPrevented()&&(!l._default||l._default.apply(d.pop(),n)===!1)&&p.acceptData(r)&&o&&r[v]&&!p.isWindow(r)){c=r[o],c&&(r[o]=null),p.event.triggered=v;try{r[v]()}catch(g){}p.event.triggered=undefined,c&&(r[o]=c)}return t.result},dispatch:function(e){e=p.event.fix(e);var t,n,i,s,o,u=[],a=r.call(arguments),f=(p._data(this,"events")||{})[e.type]||[],l=p.event.special[e.type]||{};a[0]=e,e.delegateTarget=this;if(l.preDispatch&&l.preDispatch.call(this,e)===!1)return;u=p.event.handlers.call(this,e,f),t=0;while((s=u[t++])&&!e.isPropagationStopped()){e.currentTarget=s.elem,o=0;while((i=s.handlers[o++])&&!e.isImmediatePropagationStopped())if(!e.namespace_re||e.namespace_re.test(i.namespace))e.handleObj=i,e.data=i.data,n=((p.event.special[i.origType]||{}).handle||i.handler).apply(s.elem,a),n!==undefined&&(e.result=n)===!1&&(e.preventDefault(),e.stopPropagation())}return l.postDispatch&&l.postDispatch.call(this,e),e.result},handlers:function(e,t){var n,r,i,s,o=[],u=t.delegateCount,a=e.target;if(u&&a.nodeType&&(!e.button||e.type!=="click"))for(;a!=this;a=a.parentNode||this)if(a.nodeType===1&&(a.disabled!==!0||e.type!=="click")){i=[];for(s=0;s=0:p.find(n,this,null,[a]).length),i[n]&&i.push(r);i.length&&o.push({elem:a,handlers:i})}return u]","i"),at=/^\s+/,ft=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,lt=/<([\w:]+)/,ct=/\s*$/g,yt={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:c.htmlSerialize?[0,"",""]:[1,"X
","
"]},bt=it(N),wt=bt.appendChild(N.createElement("div"));yt.optgroup=yt.option,yt.tbody=yt.tfoot=yt.colgroup=yt.caption=yt.thead,yt.th=yt.td,p.extend({clone:function(e,t,n){var r,i,s,o,u,a=p.contains(e.ownerDocument,e);c.html5Clone||p.isXMLDoc(e)||!ut.test("<"+e.nodeName+">")?s=e.cloneNode(!0):(wt.innerHTML=e.outerHTML,wt.removeChild(s=wt.firstChild));if((!c.noCloneEvent||!c.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!p.isXMLDoc(e)){r=Et(s),u=Et(e);for(o=0;(i=u[o])!=null;++o)r[o]&&Lt(i,r[o])}if(t)if(n){u=u||Et(e),r=r||Et(s);for(o=0;(i=u[o])!=null;o++)kt(i,r[o])}else kt(e,s);return r=Et(s,"script"),r.length>0&&Ct(r,!a&&Et(e,"script")),r=u=i=null,s},buildFragment:function(e,t,n,r){var i,s,o,u,a,f,l,h=e.length,d=it(t),v=[],m=0;for(;m")+l[2],i=l[0];while(i--)u=u.lastChild;!c.leadingWhitespace&&at.test(s)&&v.push(t.createTextNode(at.exec(s)[0]));if(!c.tbody){s=a==="table"&&!ct.test(s)?u.firstChild:l[1]===""&&!ct.test(s)?u:0,i=s&&s.childNodes.length;while(i--)p.nodeName(f=s.childNodes[i],"tbody")&&!f.childNodes.length&&s.removeChild(f)}p.merge(v,u.childNodes),u.textContent="";while(u.firstChild)u.removeChild(u.firstChild);u=d.lastChild}}u&&d.removeChild(u),c.appendChecked||p.grep(Et(v,"input"),St),m=0;while(s=v[m++]){if(r&&p.inArray(s,r)!==-1)continue;o=p.contains(s.ownerDocument,s),u=Et(d.appendChild(s),"script"),o&&Ct(u);if(n){i=0;while(s=u[i++])vt.test(s.type||"")&&n.push(s)}}return u=null,d},cleanData:function(e,t){var r,i,s,o,u=0,a=p.expando,f=p.cache,l=c.deleteExpando,h=p.event.special;for(;(r=e[u])!=null;u++)if(t||p.acceptData(r)){s=r[a],o=s&&f[s];if(o){if(o.events)for(i in o.events)h[i]?p.event.remove(r,i):p.removeEvent(r,i,o.handle);f[s]&&(delete f[s],l?delete r[a]:typeof r.removeAttribute!==j?r.removeAttribute(a):r[a]=null,n.push(s))}}}}),p.fn.extend({text:function(e){return J(this,function(e){return e===undefined?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||N).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var t=xt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(this.nodeType===1||this.nodeType===11||this.nodeType===9){var t=xt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?p.filter(e,this):this,i=0;for(;(n=r[i])!=null;i++)!t&&n.nodeType===1&&p.cleanData(Et(n)),n.parentNode&&(t&&p.contains(n.ownerDocument,n)&&Ct(Et(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&p.cleanData(Et(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&p.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return p.clone(this,e,t)})},html:function(e){return J(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined)return t.nodeType===1?t.innerHTML.replace(ot,""):undefined;if(typeof e=="string"&&!pt.test(e)&&(c.htmlSerialize||!ut.test(e))&&(c.leadingWhitespace||!at.test(e))&&!yt[(lt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(ft,"<$1>");try{for(;n1&&typeof v=="string"&&!c.checkClone&&dt.test(v))return this.each(function(n){var r=h.eq(n);m&&(e[0]=v.call(this,n,r.html())),r.domManip(e,t)});if(l){a=p.buildFragment(e,this[0].ownerDocument,!1,this),n=a.firstChild,a.childNodes.length===1&&(a=n);if(n){o=p.map(Et(a,"script"),Tt),s=o.length;for(;f
a",e=n.getElementsByTagName("a")[0],e.style.cssText="float:left;opacity:.5",c.opacity=/^0.5/.test(e.style.opacity),c.cssFloat=!!e.style.cssFloat,n.style.backgroundClip="content-box",n.cloneNode(!0).style.backgroundClip="",c.clearCloneStyle=n.style.backgroundClip==="content-box",e=n=null,c.shrinkWrapBlocks=function(){var e,n,i,s;if(t==null){e=N.getElementsByTagName("body")[0];if(!e)return;s="border:0;width:0;height:0;position:absolute;top:0;left:-9999px",n=N.createElement("div"),i=N.createElement("div"),e.appendChild(n).appendChild(i),t=!1,typeof i.style.zoom!==j&&(i.style.cssText=r+";width:1px;padding:1px;zoom:1",i.innerHTML="
",i.firstChild.style.width="5px",t=i.offsetWidth!==3),e.removeChild(n),e=n=i=null}return t}})();var Dt=/^margin/,Pt=new RegExp("^("+X+")(?!px)[a-z%]+$","i"),Ht,Bt,jt=/^(top|right|bottom|left)$/;e.getComputedStyle?(Ht=function(e){return e.ownerDocument.defaultView.getComputedStyle(e,null)},Bt=function(e,t,n){var r,i,s,o,u=e.style;return n=n||Ht(e),o=n?n.getPropertyValue(t)||n[t]:undefined,n&&(o===""&&!p.contains(e.ownerDocument,e)&&(o=p.style(e,t)),Pt.test(o)&&Dt.test(t)&&(r=u.width,i=u.minWidth,s=u.maxWidth,u.minWidth=u.maxWidth=u.width=o,o=n.width,u.width=r,u.minWidth=i,u.maxWidth=s)),o===undefined?o:o+""}):N.documentElement.currentStyle&&(Ht=function(e){return e.currentStyle},Bt=function(e,t,n){var r,i,s,o,u=e.style;return n=n||Ht(e),o=n?n[t]:undefined,o==null&&u&&u[t]&&(o=u[t]),Pt.test(o)&&!jt.test(t)&&(r=u.left,i=e.runtimeStyle,s=i&&i.left,s&&(i.left=e.currentStyle.left),u.left=t==="fontSize"?"1em":o,o=u.pixelLeft+"px",u.left=r,s&&(i.left=s)),o===undefined?o:o+""||"auto"}),function(){function l(){var t,n,u=N.getElementsByTagName("body")[0];if(!u)return;t=N.createElement("div"),n=N.createElement("div"),t.style.cssText=a,u.appendChild(t).appendChild(n),n.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;display:block;padding:1px;border:1px;width:4px;margin-top:1%;top:1%",p.swap(u,u.style.zoom!=null?{zoom:1}:{},function(){r=n.offsetWidth===4}),i=!0,s=!1,o=!0,e.getComputedStyle&&(s=(e.getComputedStyle(n,null)||{}).top!=="1%",i=(e.getComputedStyle(n,null)||{width:"4px"}).width==="4px"),u.removeChild(t),n=u=null}var t,n,r,i,s,o,u=N.createElement("div"),a="border:0;width:0;height:0;position:absolute;top:0;left:-9999px",f="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;padding:0;margin:0;border:0";u.innerHTML="
a",t=u.getElementsByTagName("a")[0],t.style.cssText="float:left;opacity:.5",c.opacity=/^0.5/.test(t.style.opacity),c.cssFloat=!!t.style.cssFloat,u.style.backgroundClip="content-box",u.cloneNode(!0).style.backgroundClip="",c.clearCloneStyle=u.style.backgroundClip==="content-box",t=u=null,p.extend(c,{reliableHiddenOffsets:function(){if(n!=null)return n;var e,t,r,i=N.createElement("div"),s=N.getElementsByTagName("body")[0];if(!s)return;return i.setAttribute("className","t"),i.innerHTML="
a",e=N.createElement("div"),e.style.cssText=a,s.appendChild(e).appendChild(i),i.innerHTML="
t
",t=i.getElementsByTagName("td"),t[0].style.cssText="padding:0;margin:0;border:0;display:none",r=t[0].offsetHeight===0,t[0].style.display="",t[1].style.display="none",n=r&&t[0].offsetHeight===0,s.removeChild(e),i=s=null,n},boxSizing:function(){return r==null&&l(),r},boxSizingReliable:function(){return i==null&&l(),i},pixelPosition:function(){return s==null&&l(),s},reliableMarginRight:function(){var t,n,r,i;if(o==null&&e.getComputedStyle){t=N.getElementsByTagName("body")[0];if(!t)return;n=N.createElement("div"),r=N.createElement("div"),n.style.cssText=a,t.appendChild(n).appendChild(r),i=r.appendChild(N.createElement("div")),i.style.cssText=r.style.cssText=f,i.style.marginRight=i.style.width="0",r.style.width="1px",o=!parseFloat((e.getComputedStyle(i,null)||{}).marginRight),t.removeChild(n)}return o}})}(),p.swap=function(e,t,n,r){var i,s,o={};for(s in t)o[s]=e.style[s],e.style[s]=t[s];i=n.apply(e,r||[]);for(s in t)e.style[s]=o[s];return i};var It=/alpha\([^)]*\)/i,qt=/opacity\s*=\s*([^)]*)/,Rt=/^(none|table(?!-c[ea]).+)/,Ut=new RegExp("^("+X+")(.*)$","i"),zt=new RegExp("^([+-])=("+X+")","i"),Wt={position:"absolute",visibility:"hidden",display:"block"},Xt={letterSpacing:0,fontWeight:400},Vt=["Webkit","O","Moz","ms"];p.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Bt(e,"opacity");return n===""?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":c.cssFloat?"cssFloat":"styleFloat"},style:function(e,t,n,r){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var i,s,o,u=p.camelCase(t),a=e.style;t=p.cssProps[u]||(p.cssProps[u]=$t(a,u)),o=p.cssHooks[t]||p.cssHooks[u];if(n===undefined)return o&&"get"in o&&(i=o.get(e,!1,r))!==undefined?i:a[t];s=typeof n,s==="string"&&(i=zt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(p.css(e,t)),s="number");if(n==null||n!==n)return;s==="number"&&!p.cssNumber[u]&&(n+="px"),!c.clearCloneStyle&&n===""&&t.indexOf("background")===0&&(a[t]="inherit");if(!o||!("set"in o)||(n=o.set(e,n,r))!==undefined)try{a[t]="",a[t]=n}catch(f){}},css:function(e,t,n,r){var i,s,o,u=p.camelCase(t);return t=p.cssProps[u]||(p.cssProps[u]=$t(e.style,u)),o=p.cssHooks[t]||p.cssHooks[u],o&&"get"in o&&(s=o.get(e,!0,n)),s===undefined&&(s=Bt(e,t,r)),s==="normal"&&t in Xt&&(s=Xt[t]),n===""||n?(i=parseFloat(s),n===!0||p.isNumeric(i)?i||0:s):s}}),p.each(["height","width"],function(e,t){p.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&Rt.test(p.css(e,"display"))?p.swap(e,Wt,function(){return Gt(e,t,r)}):Gt(e,t,r)},set:function(e,n,r){var i=r&&Ht(e);return Kt(e,n,r?Qt(e,t,r,c.boxSizing()&&p.css(e,"boxSizing",!1,i)==="border-box",i):0)}}}),c.opacity||(p.cssHooks.opacity={get:function(e,t){return qt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=p.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if((t>=1||t==="")&&p.trim(s.replace(It,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(t===""||r&&!r.filter)return}n.filter=It.test(s)?s.replace(It,i):s+" "+i}}),p.cssHooks.marginRight=Ft(c.reliableMarginRight,function(e,t){if(t)return p.swap(e,{display:"inline-block"},Bt,[e,"marginRight"])}),p.each({margin:"",padding:"",border:"Width"},function(e,t){p.cssHooks[e+t]={expand:function(n){var r=0,i={},s=typeof n=="string"?n.split(" "):[n];for(;r<4;r++)i[e+V[r]+t]=s[r]||s[r-2]||s[0];return i}},Dt.test(e)||(p.cssHooks[e+t].set=Kt)}),p.fn.extend({css:function(e,t){return J(this,function(e,t,n){var r,i,s={},o=0;if(p.isArray(t)){r=Ht(e),i=t.length;for(;o1)},show:function(){return Jt(this,!0)},hide:function(){return Jt(this)},toggle:function(e){return typeof e=="boolean"?e?this.show():this.hide():this.each(function(){$(this)?p(this).show():p(this).hide()})}}),p.Tween=Yt,Yt.prototype={constructor:Yt,init:function(e,t,n,r,i,s){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=s||(p.cssNumber[n]?"":"px")},cur:function(){var e=Yt.propHooks[this.prop];return e&&e.get?e.get(this):Yt.propHooks._default.get(this)},run:function(e){var t,n=Yt.propHooks[this.prop];return this.options.duration?this.pos=t=p.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Yt.propHooks._default.set(this),this}},Yt.prototype.init.prototype=Yt.prototype,Yt.propHooks={_default:{get:function(e){var t;return e.elem[e.prop]==null||!!e.elem.style&&e.elem.style[e.prop]!=null?(t=p.css(e.elem,e.prop,""),!t||t==="auto"?0:t):e.elem[e.prop]},set:function(e){p.fx.step[e.prop]?p.fx.step[e.prop](e):e.elem.style&&(e.elem.style[p.cssProps[e.prop]]!=null||p.cssHooks[e.prop])?p.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},Yt.propHooks.scrollTop=Yt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},p.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},p.fx=Yt.prototype.init,p.fx.step={};var Zt,en,tn=/^(?:toggle|show|hide)$/,nn=new RegExp("^(?:([+-])=|)("+X+")([a-z%]*)$","i"),rn=/queueHooks$/,sn=[ln],on={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=nn.exec(t),s=i&&i[3]||(p.cssNumber[e]?"":"px"),o=(p.cssNumber[e]||s!=="px"&&+r)&&nn.exec(p.css(n.elem,e)),u=1,a=20;if(o&&o[3]!==s){s=s||o[3],i=i||[],o=+r||1;do u=u||".5",o/=u,p.style(n.elem,e,o+s);while(u!==(u=n.cur()/r)&&u!==1&&--a)}return i&&(o=n.start=+o||+r||0,n.unit=s,n.end=i[1]?o+(i[1]+1)*i[2]:+i[2]),n}]};p.Animation=p.extend(hn,{tweener:function(e,t){p.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r
a",e=i.getElementsByTagName("a")[0],n=N.createElement("select"),r=n.appendChild(N.createElement("option")),t=i.getElementsByTagName("input")[0],e.style.cssText="top:1px",c.getSetAttribute=i.className!=="t",c.style=/top/.test(e.getAttribute("style")),c.hrefNormalized=e.getAttribute("href")==="/a",c.checkOn=!!t.value,c.optSelected=r.selected,c.enctype=!!N.createElement("form").enctype,n.disabled=!0,c.optDisabled=!r.disabled,t=N.createElement("input"),t.setAttribute("value",""),c.input=t.getAttribute("value")==="",t.value="t",t.setAttribute("type","radio"),c.radioValue=t.value==="t",e=t=n=r=i=null}();var pn=/\r/g;p.fn.extend({val:function(e){var t,n,r,i=this[0];if(!arguments.length){if(i)return t=p.valHooks[i.type]||p.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,typeof n=="string"?n.replace(pn,""):n==null?"":n);return}return r=p.isFunction(e),this.each(function(n){var i;if(this.nodeType!==1)return;r?i=e.call(this,n,p(this).val()):i=e,i==null?i="":typeof i=="number"?i+="":p.isArray(i)&&(i=p.map(i,function(e){return e==null?"":e+""})),t=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!t||!("set"in t)||t.set(this,i,"value")===undefined)this.value=i})}}),p.extend({valHooks:{option:{get:function(e){var t=p.find.attr(e,"value");return t!=null?t:p.text(e)}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0)try{r.selected=n=!0}catch(u){r.scrollHeight}else r.selected=!1}return n||(e.selectedIndex=-1),i}}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]={set:function(e,t){if(p.isArray(t))return e.checked=p.inArray(p(e).val(),t)>=0}},c.checkOn||(p.valHooks[this].get=function(e){return e.getAttribute("value")===null?"on":e.value})});var dn,vn,mn=p.expr.attrHandle,gn=/^(?:checked|selected)$/i,yn=c.getSetAttribute,bn=c.input;p.fn.extend({attr:function(e,t){return J(this,p.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){p.removeAttr(this,e)})}}),p.extend({attr:function(e,t,n){var r,i,s=e.nodeType;if(!e||s===3||s===8||s===2)return;if(typeof e.getAttribute===j)return p.prop(e,t,n);if(s!==1||!p.isXMLDoc(e))t=t.toLowerCase(),r=p.attrHooks[t]||(p.expr.match.bool.test(t)?vn:dn);if(n===undefined)return r&&"get"in r&&(i=r.get(e,t))!==null?i:(i=p.find.attr(e,t),i==null?undefined:i);if(n!==null)return r&&"set"in r&&(i=r.set(e,n,t))!==undefined?i:(e.setAttribute(t,n+""),n);p.removeAttr(e,t)},removeAttr:function(e,t){var n,r,i=0,s=t&&t.match(M);if(s&&e.nodeType===1)while(n=s[i++])r=p.propFix[n]||n,p.expr.match.bool.test(n)?bn&&yn||!gn.test(n)?e[r]=!1:e[p.camelCase("default-"+n)]=e[r]=!1:p.attr(e,n,""),e.removeAttribute(yn?n:r)},attrHooks:{type:{set:function(e,t){if(!c.radioValue&&t==="radio"&&p.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}}}),vn={set:function(e,t,n){return t===!1?p.removeAttr(e,n):bn&&yn||!gn.test(n)?e.setAttribute(!yn&&p.propFix[n]||n,n):e[p.camelCase("default-"+n)]=e[n]=!0,n}},p.each(p.expr.match.bool.source.match(/\w+/g),function(e,t){var n=mn[t]||p.find.attr;mn[t]=bn&&yn||!gn.test(t)?function(e,t,r){var i,s;return r||(s=mn[t],mn[t]=i,i=n(e,t,r)!=null?t.toLowerCase():null,mn[t]=s),i}:function(e,t,n){if(!n)return e[p.camelCase("default-"+t)]?t.toLowerCase():null}});if(!bn||!yn)p.attrHooks.value={set:function(e,t,n){if(!p.nodeName(e,"input"))return dn&&dn.set(e,t,n);e.defaultValue=t}};yn||(dn={set:function(e,t,n){var r=e.getAttributeNode(n);r||e.setAttributeNode(r=e.ownerDocument.createAttribute(n)),r.value=t+="";if(n==="value"||t===e.getAttribute(n))return t}},mn.id=mn.name=mn.coords=function(e,t,n){var r;if(!n)return(r=e.getAttributeNode(t))&&r.value!==""?r.value:null},p.valHooks.button={get:function(e,t){var n=e.getAttributeNode(t);if(n&&n.specified)return n.value},set:dn.set},p.attrHooks.contenteditable={set:function(e,t,n){dn.set(e,t===""?!1:t,n)}},p.each(["width","height"],function(e,t){p.attrHooks[t]={set:function(e,n){if(n==="")return e.setAttribute(t,"auto"),n}}})),c.style||(p.attrHooks.style={get:function(e){return e.style.cssText||undefined},set:function(e,t){return e.style.cssText=t+""}});var wn=/^(?:input|select|textarea|button|object)$/i,En=/^(?:a|area)$/i;p.fn.extend({prop:function(e,t){return J(this,p.prop,e,t,arguments.length>1)},removeProp:function(e){return e=p.propFix[e]||e,this.each(function(){try{this[e]=undefined,delete this[e]}catch(t){}})}}),p.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,s,o=e.nodeType;if(!e||o===3||o===8||o===2)return;return s=o!==1||!p.isXMLDoc(e),s&&(t=p.propFix[t]||t,i=p.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&(r=i.get(e,t))!==null?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=p.find.attr(e,"tabindex");return t?parseInt(t,10):wn.test(e.nodeName)||En.test(e.nodeName)&&e.href?0:-1}}}}),c.hrefNormalized||p.each(["href","src"],function(e,t){p.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),c.optSelected||(p.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),p.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){p.propFix[this.toLowerCase()]=this}),c.enctype||(p.propFix.enctype="encoding");var Sn=/[\t\r\n\f]/g;p.fn.extend({addClass:function(e){var t,n,r,i,s,o,u=0,a=this.length,f=typeof e=="string"&&e;if(p.isFunction(e))return this.each(function(t){p(this).addClass(e.call(this,t,this.className))});if(f){t=(e||"").match(M)||[];for(;u=0)r=r.replace(" "+i+" "," ");o=e?p.trim(r):"",n.className!==o&&(n.className=o)}}}return this},toggleClass:function(e,t){var n=typeof e;return typeof t=="boolean"&&n==="string"?t?this.addClass(e):this.removeClass(e):p.isFunction(e)?this.each(function(n){p(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var t,r=0,i=p(this),s=e.match(M)||[];while(t=s[r++])i.hasClass(t)?i.removeClass(t):i.addClass(t)}else if(n===j||n==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||e===!1?"":p._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){p.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),p.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)}});var xn=p.now(),Tn=/\?/,Nn=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;p.parseJSON=function(t){if(e.JSON&&e.JSON.parse)return e.JSON.parse(t+"");var n,r=null,i=p.trim(t+"");return i&&!p.trim(i.replace(Nn,function(e,t,i,s){return n&&t&&(r=0),r===0?e:(n=i||t,r+=!s-!i,"")}))?Function("return "+i)():p.error("Invalid JSON: "+t)},p.parseXML=function(t){var n,r;if(!t||typeof t!="string")return null;try{e.DOMParser?(r=new DOMParser,n=r.parseFromString(t,"text/xml")):(n=new ActiveXObject("Microsoft.XMLDOM"),n.async="false",n.loadXML(t))}catch(i){n=undefined}return(!n||!n.documentElement||n.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+t),n};var Cn,kn,Ln=/#.*$/,An=/([?&])_=[^&]*/,On=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,Mn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,_n=/^(?:GET|HEAD)$/,Dn=/^\/\//,Pn=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hn={},Bn={},jn="*/".concat("*");try{kn=location.href}catch(Fn){kn=N.createElement("a"),kn.href="",kn=kn.href}Cn=Pn.exec(kn.toLowerCase())||[],p.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:kn,type:"GET",isLocal:Mn.test(Cn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":jn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Rn(Rn(e,p.ajaxSettings),t):Rn(p.ajaxSettings,e)},ajaxPrefilter:In(Hn),ajaxTransport:In(Bn),ajax:function(e,t){function x(e,t,n,r){var f,g,y,w,S,x=t;if(b===2)return;b=2,o&&clearTimeout(o),a=undefined,s=r||"",E.readyState=e>0?4:0,f=e>=200&&e<300||e===304,n&&(w=Un(l,E,n)),w=zn(l,w,E,f);if(f)l.ifModified&&(S=E.getResponseHeader("Last-Modified"),S&&(p.lastModified[i]=S),S=E.getResponseHeader("etag"),S&&(p.etag[i]=S)),e===204||l.type==="HEAD"?x="nocontent":e===304?x="notmodified":(x=w.state,g=w.data,y=w.error,f=!y);else{y=x;if(e||!x)x="error",e<0&&(e=0)}E.status=e,E.statusText=(t||x)+"",f?d.resolveWith(c,[g,x,E]):d.rejectWith(c,[E,x,y]),E.statusCode(m),m=undefined,u&&h.trigger(f?"ajaxSuccess":"ajaxError",[E,l,f?g:y]),v.fireWith(c,[E,x]),u&&(h.trigger("ajaxComplete",[E,l]),--p.active||p.event.trigger("ajaxStop"))}typeof e=="object"&&(t=e,e=undefined),t=t||{};var n,r,i,s,o,u,a,f,l=p.ajaxSetup({},t),c=l.context||l,h=l.context&&(c.nodeType||c.jquery)?p(c):p.event,d=p.Deferred(),v=p.Callbacks("once memory"),m=l.statusCode||{},g={},y={},b=0,w="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(b===2){if(!f){f={};while(t=On.exec(s))f[t[1].toLowerCase()]=t[2]}t=f[e.toLowerCase()]}return t==null?null:t},getAllResponseHeaders:function(){return b===2?s:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=y[n]=y[n]||e,g[e]=t),this},overrideMimeType:function(e){return b||(l.mimeType=e),this},statusCode:function(e){var t;if(e)if(b<2)for(t in e)m[t]=[m[t],e[t]];else E.always(e[E.status]);return this},abort:function(e){var t=e||w;return a&&a.abort(t),x(0,t),this}};d.promise(E).complete=v.add,E.success=E.done,E.error=E.fail,l.url=((e||l.url||kn)+"").replace(Ln,"").replace(Dn,Cn[1]+"//"),l.type=t.method||t.type||l.method||l.type,l.dataTypes=p.trim(l.dataType||"*").toLowerCase().match(M)||[""],l.crossDomain==null&&(n=Pn.exec(l.url.toLowerCase()),l.crossDomain=!(!n||n[1]===Cn[1]&&n[2]===Cn[2]&&(n[3]||(n[1]==="http:"?"80":"443"))===(Cn[3]||(Cn[1]==="http:"?"80":"443")))),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),qn(Hn,l,t,E);if(b===2)return E;u=l.global,u&&p.active++===0&&p.event.trigger("ajaxStart"),l.type=l.type.toUpperCase(),l.hasContent=!_n.test(l.type),i=l.url,l.hasContent||(l.data&&(i=l.url+=(Tn.test(i)?"&":"?")+l.data,delete l.data),l.cache===!1&&(l.url=An.test(i)?i.replace(An,"$1_="+xn++):i+(Tn.test(i)?"&":"?")+"_="+xn++)),l.ifModified&&(p.lastModified[i]&&E.setRequestHeader("If-Modified-Since",p.lastModified[i]),p.etag[i]&&E.setRequestHeader("If-None-Match",p.etag[i])),(l.data&&l.hasContent&&l.contentType!==!1||t.contentType)&&E.setRequestHeader("Content-Type",l.contentType),E.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+jn+"; q=0.01":""):l.accepts["*"]);for(r in l.headers)E.setRequestHeader(r,l.headers[r]);if(!l.beforeSend||l.beforeSend.call(c,E,l)!==!1&&b!==2){w="abort";for(r in{success:1,error:1,complete:1})E[r](l[r]);a=qn(Bn,l,t,E);if(!a)x(-1,"No Transport");else{E.readyState=1,u&&h.trigger("ajaxSend",[E,l]),l.async&&l.timeout>0&&(o=setTimeout(function(){E.abort("timeout")},l.timeout));try{b=1,a.send(g,x)}catch(S){if(!(b<2))throw S;x(-1,S)}}return E}return E.abort()},getJSON:function(e,t,n){return p.get(e,t,n,"json")},getScript:function(e,t){return p.get(e,undefined,t,"script")}}),p.each(["get","post"],function(e,t){p[t]=function(e,n,r,i){return p.isFunction(n)&&(i=i||r,r=n,n=undefined),p.ajax({url:e,type:t,dataType:i,data:n,success:r})}}),p.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){p.fn[t]=function(e){return this.on(t,e)}}),p._evalUrl=function(e){return p.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},p.fn.extend({wrapAll:function(e){if(p.isFunction(e))return this.each(function(t){p(this).wrapAll(e.call(this,t))});if(this[0]){var t=p(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return p.isFunction(e)?this.each(function(t){p(this).wrapInner(e.call(this,t))}):this.each(function(){var t=p(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=p.isFunction(e);return this.each(function(n){p(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()}}),p.expr.filters.hidden=function(e){return e.offsetWidth<=0&&e.offsetHeight<=0||!c.reliableHiddenOffsets()&&(e.style&&e.style.display||p.css(e,"display"))==="none"},p.expr.filters.visible=function(e){return!p.expr.filters.hidden(e)};var Wn=/%20/g,Xn=/\[\]$/,Vn=/\r?\n/g,$n=/^(?:submit|button|image|reset|file)$/i,Jn=/^(?:input|select|textarea|keygen)/i;p.param=function(e,t){var n,r=[],i=function(e,t){t=p.isFunction(t)?t():t==null?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};t===undefined&&(t=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(e)||e.jquery&&!p.isPlainObject(e))p.each(e,function(){i(this.name,this.value)});else for(n in e)Kn(n,e[n],t,i);return r.join("&").replace(Wn,"+")},p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=p.prop(this,"elements");return e?p.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!p(this).is(":disabled")&&Jn.test(this.nodeName)&&!$n.test(e)&&(this.checked||!K.test(e))}).map(function(e,t){var n=p(this).val();return n==null?null:p.isArray(n)?p.map(n,function(e){return{name:t.name,value:e.replace(Vn,"\r\n")}}):{name:t.name,value:n.replace(Vn,"\r\n")}}).get()}}),p.ajaxSettings.xhr=e.ActiveXObject!==undefined?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zn()||er()}:Zn;var Qn=0,Gn={},Yn=p.ajaxSettings.xhr();e.ActiveXObject&&p(e).on("unload",function(){for(var e in Gn)Gn[e](undefined,!0)}),c.cors=!!Yn&&"withCredentials"in Yn,Yn=c.ajax=!!Yn,Yn&&p.ajaxTransport(function(e){if(!e.crossDomain||c.cors){var t;return{send:function(n,r){var i,s=e.xhr(),o=++Qn;s.open(e.type,e.url,e.async,e.username,e.password);if(e.xhrFields)for(i in e.xhrFields)s[i]=e.xhrFields[i];e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),!e.crossDomain&&!n["X-Requested-With"]&&(n["X-Requested-With"]="XMLHttpRequest");for(i in n)n[i]!==undefined&&s.setRequestHeader(i,n[i]+"");s.send(e.hasContent&&e.data||null),t=function(n,i){var u,a,f;if(t&&(i||s.readyState===4)){delete Gn[o],t=undefined,s.onreadystatechange=p.noop;if(i)s.readyState!==4&&s.abort();else{f={},u=s.status,typeof s.responseText=="string"&&(f.text=s.responseText);try{a=s.statusText}catch(l){a=""}!u&&e.isLocal&&!e.crossDomain?u=f.text?200:404:u===1223&&(u=204)}}f&&r(u,a,f,s.getAllResponseHeaders())},e.async?s.readyState===4?setTimeout(t):s.onreadystatechange=Gn[o]=t:t()},abort:function(){t&&t(undefined,!0)}}}}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return p.globalEval(e),e}}}),p.ajaxPrefilter("script",function(e){e.cache===undefined&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),p.ajaxTransport("script",function(e){if(e.crossDomain){var t,n=N.head||p("head")[0]||N.documentElement;return{send:function(r,i){t=N.createElement("script"),t.async=!0,e.scriptCharset&&(t.charset=e.scriptCharset),t.src=e.url,t.onload=t.onreadystatechange=function(e,n){if(n||!t.readyState||/loaded|complete/.test(t.readyState))t.onload=t.onreadystatechange=null,t.parentNode&&t.parentNode.removeChild(t),t=null,n||i(200,"success")},n.insertBefore(t,n.firstChild)},abort:function(){t&&t.onload(undefined,!0)}}}});var tr=[],nr=/(=)\?(?=&|$)|\?\?/;p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=tr.pop()||p.expando+"_"+xn++;return this[e]=!0,e}}),p.ajaxPrefilter("json jsonp",function(t,n,r){var i,s,o,u=t.jsonp!==!1&&(nr.test(t.url)?"url":typeof t.data=="string"&&!(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&nr.test(t.data)&&"data");if(u||t.dataTypes[0]==="jsonp")return i=t.jsonpCallback=p.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,u?t[u]=t[u].replace(nr,"$1"+i):t.jsonp!==!1&&(t.url+=(Tn.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return o||p.error(i+" was not called"),o[0]},t.dataTypes[0]="json",s=e[i],e[i]=function(){o=arguments},r.always(function(){e[i]=s,t[i]&&(t.jsonpCallback=n.jsonpCallback,tr.push(i)),o&&p.isFunction(s)&&s(o[0]),o=s=undefined}),"script"}),p.parseHTML=function(e,t,n){if(!e||typeof e!="string")return null;typeof t=="boolean"&&(n=t,t=!1),t=t||N;var r=E.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=p.buildFragment([e],t,i),i&&i.length&&p(i).remove(),p.merge([],r.childNodes))};var rr=p.fn.load;p.fn.load=function(e,t,n){if(typeof e!="string"&&rr)return rr.apply(this,arguments);var r,i,s,o=this,u=e.indexOf(" ");return u>=0&&(r=e.slice(u,e.length),e=e.slice(0,u)),p.isFunction(t)?(n=t,t=undefined):t&&typeof t=="object"&&(s="POST"),o.length>0&&p.ajax({url:e,type:s,dataType:"html",data:t}).done(function(e){i=arguments,o.html(r?p("
").append(p.parseHTML(e)).find(r):e)}).complete(n&&function(e,t){o.each(n,i||[e.responseText,t,e])}),this},p.expr.filters.animated=function(e){return p.grep(p.timers,function(t){return e===t.elem}).length};var ir=e.document.documentElement;p.offset={setOffset:function(e,t,n){var r,i,s,o,u,a,f,l=p.css(e,"position"),c=p(e),h={};l==="static"&&(e.style.position="relative"),u=c.offset(),s=p.css(e,"top"),a=p.css(e,"left"),f=(l==="absolute"||l==="fixed")&&p.inArray("auto",[s,a])>-1,f?(r=c.position(),o=r.top,i=r.left):(o=parseFloat(s)||0,i=parseFloat(a)||0),p.isFunction(t)&&(t=t.call(e,n,u)),t.top!=null&&(h.top=t.top-u.top+o),t.left!=null&&(h.left=t.left-u.left+i),"using"in t?t.using.call(e,h):c.css(h)}},p.fn.extend({offset:function(e){if(arguments.length)return e===undefined?this:this.each(function(t){p.offset.setOffset(this,e,t)});var t,n,r={top:0,left:0},i=this[0],s=i&&i.ownerDocument;if(!s)return;return t=s.documentElement,p.contains(t,i)?(typeof i.getBoundingClientRect!==j&&(r=i.getBoundingClientRect()),n=sr(s),{top:r.top+(n.pageYOffset||t.scrollTop)-(t.clientTop||0),left:r.left+(n.pageXOffset||t.scrollLeft)-(t.clientLeft||0)}):r},position:function(){if(!this[0])return;var e,t,n={top:0,left:0},r=this[0];return p.css(r,"position")==="fixed"?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),p.nodeName(e[0],"html")||(n=e.offset()),n.top+=p.css(e[0],"borderTopWidth",!0),n.left+=p.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-p.css(r,"marginTop",!0),left:t.left-n.left-p.css(r,"marginLeft",!0)}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||ir;while(e&&!p.nodeName(e,"html")&&p.css(e,"position")==="static")e=e.offsetParent;return e||ir})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n=/Y/.test(t);p.fn[e]=function(r){return J(this,function(e,r,i){var s=sr(e);if(i===undefined)return s?t in s?s[t]:s.document.documentElement[r]:e[r];s?s.scrollTo(n?p(s).scrollLeft():i,n?i:p(s).scrollTop()):e[r]=i},e,r,arguments.length,null)}}),p.each(["top","left"],function(e,t){p.cssHooks[t]=Ft(c.pixelPosition,function(e,n){if(n)return n=Bt(e,t),Pt.test(n)?p(e).position()[t]+"px":n})}),p.each({Height:"height",Width:"width"},function(e,t){p.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){p.fn[r]=function(r,i){var s=arguments.length&&(n||typeof r!="boolean"),o=n||(r===!0||i===!0?"margin":"border");return J(this,function(t,n,r){var i;return p.isWindow(t)?t.document.documentElement["client"+e]:t.nodeType===9?(i=t.documentElement,Math.max(t.body["scroll"+e],i["scroll"+e],t.body["offset"+e],i["offset"+e],i["client"+e])):r===undefined?p.css(t,n,o):p.style(t,n,r,o)},t,s?r:undefined,s,null)}})}),p.fn.size=function(){return this.length},p.fn.andSelf=p.fn.addBack,typeof define=="function"&&define.amd&&define("jquery",[],function(){return p});var or=e.jQuery,ur=e.$;return p.noConflict=function(t){return e.$===p&&(e.$=ur),t&&e.jQuery===p&&(e.jQuery=or),p},typeof t===j&&(e.jQuery=e.$=p),p}),define("jquery-private",["jquery"],function(e){return e.noConflict(!0)}),define("text",["module"],function(e){var t,n,r,i,s,o=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"],u=/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,a=/]*>\s*([\s\S]+)\s*<\/body>/im,f=typeof location!="undefined"&&location.href,l=f&&location.protocol&&location.protocol.replace(/\:/,""),c=f&&location.hostname,h=f&&(location.port||undefined),p={},d=e.config&&e.config()||{};t={version:"2.0.12",strip:function(e){if(e){e=e.replace(u,"");var t=e.match(a);t&&(e=t[1])}else e="";return e},jsEscape:function(e){return e.replace(/(['\\])/g,"\\$1").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r").replace(/[\u2028]/g,"\\u2028").replace(/[\u2029]/g,"\\u2029")},createXhr:d.createXhr||function(){var e,t,n;if(typeof XMLHttpRequest!="undefined")return new XMLHttpRequest;if(typeof ActiveXObject!="undefined")for(t=0;t<3;t+=1){n=o[t];try{e=new ActiveXObject(n)}catch(r){}if(e){o=[n];break}}return e},parseName:function(e){var t,n,r,i=!1,s=e.indexOf("."),o=e.indexOf("./")===0||e.indexOf("../")===0;return s!==-1&&(!o||s>1)?(t=e.substring(0,s),n=e.substring(s+1,e.length)):t=e,r=n||t,s=r.indexOf("!"),s!==-1&&(i=r.substring(s+1)==="strip",r=r.substring(0,s),n?n=r:t=r),{moduleName:t,ext:n,strip:i}},xdRegExp:/^((\w+)\:)?\/\/([^\/\\]+)/,useXhr:function(e,n,r,i){var s,o,u,a=t.xdRegExp.exec(e);return a?(s=a[2],o=a[3],o=o.split(":"),u=o[1],o=o[0],(!s||s===n)&&(!o||o.toLowerCase()===r.toLowerCase())&&(!u&&!o||u===i)):!0},finishLoad:function(e,n,r,i){r=n?t.strip(r):r,d.isBuild&&(p[e]=r),i(r)},load:function(e,n,r,i){if(i&&i.isBuild&&!i.inlineText){r();return}d.isBuild=i&&i.isBuild;var s=t.parseName(e),o=s.moduleName+(s.ext?"."+s.ext:""),u=n.toUrl(o),a=d.useXhr||t.useXhr;if(u.indexOf("empty:")===0){r();return}!f||a(u,l,c,h)?t.get(u,function(n){t.finishLoad(e,s.strip,n,r)},function(e){r.error&&r.error(e)}):n([o],function(e){t.finishLoad(s.moduleName+"."+s.ext,s.strip,e,r)})},write:function(e,n,r,i){if(p.hasOwnProperty(n)){var s=t.jsEscape(p[n]);r.asModule(e+"!"+n,"define(function () { return '"+s+"';});\n")}},writeFile:function(e,n,r,i,s){var o=t.parseName(n),u=o.ext?"."+o.ext:"",a=o.moduleName+u,f=r.toUrl(o.moduleName+u)+".js";t.load(a,r,function(n){var r=function(e){return i(f,e)};r.asModule=function(e,t){return i.asModule(e,f,t)},t.write(e,a,r,s)},s)}};if(d.env==="node"||!d.env&&typeof process!="undefined"&&process.versions&&!!process.versions.node&&!process.versions["node-webkit"])n=require.nodeRequire("fs"),t.get=function(e,t,r){try{var i=n.readFileSync(e,"utf8");i.indexOf("")===0&&(i=i.substring(1)),t(i)}catch(s){r&&r(s)}};else if(d.env==="xhr"||!d.env&&t.createXhr())t.get=function(e,n,r,i){var s=t.createXhr(),o;s.open("GET",e,!0);if(i)for(o in i)i.hasOwnProperty(o)&&s.setRequestHeader(o.toLowerCase(),i[o]);d.onXhr&&d.onXhr(s,e),s.onreadystatechange=function(t){var i,o;s.readyState===4&&(i=s.status||0,i>399&&i<600?(o=new Error(e+" HTTP status: "+i),o.xhr=s,r&&r(o)):n(s.responseText),d.onXhrComplete&&d.onXhrComplete(s,e))},s.send(null)};else if(d.env==="rhino"||!d.env&&typeof Packages!="undefined"&&typeof java!="undefined")t.get=function(e,t){var n,r,i="utf-8",s=new java.io.File(e),o=java.lang.System.getProperty("line.separator"),u=new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(s),i)),a="";try{n=new java.lang.StringBuffer,r=u.readLine(),r&&r.length()&&r.charAt(0)===65279&&(r=r.substring(1)),r!==null&&n.append(r);while((r=u.readLine())!==null)n.append(o),n.append(r);a=String(n.toString())}finally{u.close()}t(a)};else if(d.env==="xpconnect"||!d.env&&typeof Components!="undefined"&&Components.classes&&Components.interfaces)r=Components.classes,i=Components.interfaces,Components.utils["import"]("resource://gre/modules/FileUtils.jsm"),s="@mozilla.org/windows-registry-key;1"in r,t.get=function(e,t){var n,o,u,a={};s&&(e=e.replace(/\//g,"\\")),u=new FileUtils.File(e);try{n=r["@mozilla.org/network/file-input-stream;1"].createInstance(i.nsIFileInputStream),n.init(u,1,0,!1),o=r["@mozilla.org/intl/converter-input-stream;1"].createInstance(i.nsIConverterInputStream),o.init(n,"utf-8",n.available(),i.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER),o.readString(n.available(),a),o.close(),n.close(),t(a.value)}catch(f){throw new Error((u&&u.path||"")+": "+f)}};return t}),function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=i.toString,l=i.hasOwnProperty,c=r.forEach,h=r.map,p=r.reduce,d=r.reduceRight,v=r.filter,m=r.every,g=r.some,y=r.indexOf,b=r.lastIndexOf,w=Array.isArray,E=Object.keys,S=s.bind,x=function(e){if(e instanceof x)return e;if(!(this instanceof x))return new x(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=x),exports._=x):e._=x,x.VERSION="1.6.0";var T=x.each=x.forEach=function(e,t,r){if(e==null)return e;if(c&&e.forEach===c)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i2;e==null&&(e=[]);if(p&&e.reduce===p)return r&&(t=x.bind(t,r)),i?e.reduce(t,n):e.reduce(t);T(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError(N);return n},x.reduceRight=x.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduceRight===d)return r&&(t=x.bind(t,r)),i?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=x.keys(e);s=o.length}T(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError(N);return n},x.find=x.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},x.filter=x.select=function(e,t,n){var r=[];return e==null?r:v&&e.filter===v?e.filter(t,n):(T(e,function(e,i,s){t.call(n,e,i,s)&&r.push(e)}),r)},x.reject=function(e,t,n){return x.filter(e,function(e,r,i){return!t.call(n,e,r,i)},n)},x.every=x.all=function(e,t,r){t||(t=x.identity);var i=!0;return e==null?i:m&&e.every===m?e.every(t,r):(T(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=x.some=x.any=function(e,t,r){t||(t=x.identity);var i=!1;return e==null?i:g&&e.some===g?e.some(t,r):(T(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};x.contains=x.include=function(e,t){return e==null?!1:y&&e.indexOf===y?e.indexOf(t)!=-1:C(e,function(e){return e===t})},x.invoke=function(e,t){var n=u.call(arguments,2),r=x.isFunction(t);return x.map(e,function(e){return(r?t:e[t]).apply(e,n)})},x.pluck=function(e,t){return x.map(e,x.property(t))},x.where=function(e,t){return x.filter(e,x.matches(t))},x.findWhere=function(e,t){return x.find(e,x.matches(t))},x.max=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);var r=-Infinity,i=-Infinity;return T(e,function(e,s,o){var u=t?t.call(n,e,s,o):e;u>i&&(r=e,i=u)}),r},x.min=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);var r=Infinity,i=Infinity;return T(e,function(e,s,o){var u=t?t.call(n,e,s,o):e;ur||n===void 0)return 1;if(n>>1;n.call(r,e[u])=0;n--)t=[e[n].apply(this,t)];return t[0]}},x.after=function(e,t){return function(){if(--e<1)return t.apply(this,arguments)}},x.keys=function(e){if(!x.isObject(e))return[];if(E)return E(e);var t=[];for(var n in e)x.has(e,n)&&t.push(n);return t},x.values=function(e){var t=x.keys(e),n=t.length,r=new Array(n);for(var i=0;i":">",'"':""","'":"'"}};_.unescape=x.invert(_.escape);var D={escape:new RegExp("["+x.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+x.keys(_.unescape).join("|")+")","g")};x.each(["escape","unescape"],function(e){x[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),x.result=function(e,t){if(e==null)return void 0;var n=e[t];return x.isFunction(n)?n.call(e):n},x.mixin=function(e){T(x.functions(e),function(t){var n=x[t]=e[t];x.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(x,e))}})};var P=0;x.uniqueId=function(e){var t=++P+"";return e?e+t:t},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;x.template=function(e,t,n){var r;n=x.defaults({},n,x.templateSettings);var i=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),s=0,o="__p+='";e.replace(i,function(t,n,r,i,u){return o+=e.slice(s,u).replace(j,function(e){return"\\"+B[e]}),n&&(o+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'"),r&&(o+="'+\n((__t=("+r+"))==null?'':__t)+\n'"),i&&(o+="';\n"+i+"\n__p+='"),s=u+t.length,t}),o+="';\n",n.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{r=new Function(n.variable||"obj","_",o)}catch(u){throw u.source=o,u}if(t)return r(t,x);var a=function(e){return r.call(this,e,x)};return a.source="function("+(n.variable||"obj")+"){\n"+o+"}",a},x.chain=function(e){return x(e).chain()};var F=function(e){return this._chain?x(e).chain():e};x.mixin(x),T(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];x.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),T(["concat","join","slice"],function(e){var t=r[e];x.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),x.extend(x.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),typeof define=="function"&&define.amd&&define("underscore",[],function(){return x})}.call(this),define("tpl",["text","underscore"],function(e,t){var n={},r="define('{pluginName}!{moduleName}', function () { return {source}; });\n";return{version:"0.0.2",load:function(r,i,s,o){o.tpl&&o.tpl.templateSettings&&(t.templateSettings=o.tpl.templateSettings);if(n[r])s(n[r]);else{var u=o.tpl&&o.tpl.extension||".html",a=o.tpl&&o.tpl.path||"";e.load(a+r+u,i,function(e){n[r]=t.template(e),s(n[r])},o)}},write:function(e,t,i){var s=n[t],o=s&&s.source;o&&i.asModule(e+"!"+t,r.replace("{pluginName}",e).replace("{moduleName}",t).replace("{source}",o))}}}),define("tpl!action",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
\n '+((__t=time)==null?"":__t)+" **"+((__t=username)==null?"":__t)+' \n '+((__t=message)==null?"":__t)+"\n
\n";return __p}}),define("tpl!add_contact_dropdown",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='\n';return __p}}),define("tpl!add_contact_form",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
  • \n
    \n \n \n
    \n
  • \n";return __p}}),define("tpl!change_status_message",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
    \n \n \n
    \n";return __p}}),define("tpl!chat_status",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='\n';return __p}}),define("tpl!chatarea",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
    \n
    \n
    \n ',show_toolbar&&(__p+='\n
      \n '),__p+='\n \n";return __p}}),define("tpl!form_username",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+="",label&&(__p+="\n\n"),__p+='\n
      \n \n
      \n";return __p}}),define("tpl!group_header",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+=''+((__t=label_group)==null?"":__t)+"\n";return __p}}),define("tpl!info",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
      '+((__t=message)==null?"":__t)+"
      \n";return __p}}),define("tpl!login_panel",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='\n \n \n \n \n \n \n
      \n';return __p}}),define("tpl!login_tab",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
    • '+((__t=label_sign_in)==null?"":__t)+"
    • \n";return __p}}),define("tpl!message",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
      \n '+((__t=time)==null?"":__t)+" "+((__t=username)==null?"":__t)+': \n '+((__t=message)==null?"":__t)+"\n
      \n";return __p}}),define("tpl!new_day",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='\n";return __p}}),define("tpl!occupant",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
    • \n";return __p}}),define("tpl!pending_contact",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+=''+((__t=fullname)==null?"":__t)+' \n';return __p}}),define("tpl!pending_contacts",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
      '+((__t=label_pending_contacts)==null?"":__t)+"
      \n";return __p}}),define("tpl!register_panel",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
      \n \n \n \n

      '+((__t=help_providers)==null?"":__t)+' '+((__t=help_providers_link)==null?"":__t)+'.

      \n \n
      \n';return __p}}),define("tpl!register_tab",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='
    • '+((__t=label_register)==null?"":__t)+"
    • \n";return __p}}),define("tpl!registration_form",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='

      '+((__t=domain)==null?"":__t)+"

      \n\n xmpp.net score\n\n

      "+((__t=title)==null?"":__t)+'

      \n

      '+((__t=instructions)==null?"":__t)+"

      \n";return __p}}),define("tpl!registration_request",[],function(){return function(obj){var __t,__p="",__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,"")};with(obj||{})__p+='