UNPKG

hx-websdk

Version:
1,532 lines (1,407 loc) 193 kB
/** 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: * http://tools.ietf.org/html/rfc7395 */ /* 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. */ /* jshint ignore:start */ (function (callback) { /* jshint ignore:end */ // 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) { if (typeof define === 'function' && define.amd) { 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 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. */ /* jshint undef: true, unused: true:, noarg: true, latedef: true */ /* global define */ /* Some functions and variables have been stripped for use with Strophe */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-sha1', function () { return factory() }) } else { // Browser globals root.SHA1 = factory() } }(this, function () { /* * 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)) } } })); /* * 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) { 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 })); (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-utils', function () { return factory() }) } else { // Browser globals root.stropheUtils = factory() } }(this, function () { var utils = { utf16to8: function (str) { var i, c var out = '' var len = str.length for (i = 0; i < len; i++) { c = str.charCodeAt(i) if ((c >= 0x0000) && (c <= 0x007F)) { out += str.charAt(i) } else if (c > 0x07FF) { out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)) out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)) out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)) } else { out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)) out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)) } } return out }, addCookies: function (cookies) { /* Parameters: * (Object) cookies - either a map of cookie names * to string values or to maps of cookie values. * * For example: * { "myCookie": "1234" } * * or: * { "myCookie": { * "value": "1234", * "domain": ".example.org", * "path": "/", * "expires": expirationDate * } * } * * These values get passed to Strophe.Connection via * options.cookies */ var cookieName, cookieObj, isObj, cookieValue, expires, domain, path for (cookieName in (cookies || {})) { expires = '' domain = '' path = '' cookieObj = cookies[cookieName] isObj = typeof cookieObj == 'object' cookieValue = escape(unescape(isObj ? cookieObj.value : cookieObj)) if (isObj) { expires = cookieObj.expires ? ';expires=' + cookieObj.expires : '' domain = cookieObj.domain ? ';domain=' + cookieObj.domain : '' path = cookieObj.path ? ';path=' + cookieObj.path : '' } document.cookie = cookieName + '=' + cookieValue + expires + domain + path } } } return utils })); /* 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 */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('strophe-polyfill', [], function () { return factory() }) } else { // Browser globals return factory() } }(this, function () { /** 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 * <a href='https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind'>MDC's bind() documentation</a> and * <a href='http://benjamin.smedbergs.us/blog/2007-01-03/bound-functions-and-function-imports-in-javascript/'>Bound Functions and Function Imports in JavaScript</a> * 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 } } })); /* 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) { define('strophe-core', [ 'strophe-sha1', 'strophe-base64', 'strophe-md5', 'strophe-utils', 'strophe-polyfill' ], function () { return factory.apply(this, arguments) }) } else { // Browser globals var o = factory(root.SHA1, root.Base64, root.MD5, root.stropheUtils) 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, utils) { 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 <message/> element as the root. * * Parameters: * (Object) attrs - The <message/> 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 <iq/> element as the root. * * Parameters: * (Object) attrs - The <iq/> 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 <presence/> element as the root. * * Parameters: * (Object) attrs - The <presence/> 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.2.8', /** 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'], /** Function: XHTML.validTag * * Utility method to determine whether a tag is allowed * in the XHTML_IM namespace. * * XHTML tag names are case sensitive and must be lower case. */ validTag: function (tag) { for (var i = 0; i < Strophe.XHTML.tags.length; i++) { if (tag == Strophe.XHTML.tags[i]) { return true } } return false }, /** Function: XHTML.validAttribute * * Utility method to determine whether an attribute is allowed * as recommended per XEP-0071 * * XHTML attribute names are case sensitive and must be lower case. */ 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.CONNTIMEOUT - The connection has timed out */ Status: { ERROR: 0, CONNECTING: 1, CONNFAIL: 2, AUTHENTICATING: 3, AUTHFAIL: 4, CONNECTED: 5, DISCONNECTED: 6, DISCONNECTING: 7, ATTACHED: 8, REDIRECT: 9, CONNTIMEOUT: 10 }, /** 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++) { var arg = arguments[a] if (!arg) { continue } if (typeof (arg) == 'string' || typeof (arg) == 'number') { node.appendChild(Strophe.xmlTextNode(arg)) } else if (typeof (arg) == 'object' && typeof (arg.sort) == 'function') { for (i = 0; i < arg.length; i++) { var attr = arg[i] if (typeof (attr) == 'object' && typeof (attr.sort) == 'function' && attr[1] !== undefined && attr[1] !== null) { node.setAttribute(attr[0], attr[1]) } } } else if (typeof (arg) == 'object') { for (k in arg) { if (arg.hasOwnProperty(k)) { if (arg[k] !== undefined && arg[k] !== null) { node.setAttribute(k, arg[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, '&amp;') text = text.replace(/</g, '&lt;') text = text.replace(/>/g, '&gt;') text = text.replace(/'/g, '&apos;') text = text.replace(/"/g, '&quot;') return text }, /* Function: xmlunescape * Unexcapes invalid xml characters. * * Parameters: * (String) text - text to unescape. * * Returns: * Unescaped text. */ xmlunescape: function (text) { text = text.replace(/\&amp;/g, '&') text = text.replace(/&lt;/g, '<') text = text.replace(/&gt;/g, '>') text = text.replace(/&apos;/g, "'") text = text.replace(/&quot;/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.toLowerCase() // XHTML tags must be lower case. 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, '\\3c') .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(). *