strophe.js
Version:
Strophe.js is an XMPP library for JavaScript
1,750 lines (1,508 loc) • 183 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var abab = require('abab');
var global$1 = typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
/*
* This module provides uniform
* Shims APIs and globals that are not present in all JS environments,
* the most common example for Strophe being browser APIs like WebSocket
* and DOM that don't exist under nodejs.
*
* Usually these will be supplied in nodejs by conditionally requiring a
* NPM module that provides a compatible implementation.
*/
/* global global */
/**
* WHATWG WebSockets API
* https://www.w3.org/TR/websockets/
*
* Interface to use the web socket protocol
*
* Used implementations:
* - supported browsers: built-in in WebSocket global
* https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Browser_compatibility
* - nodejs: use standard-compliant 'ws' module
* https://www.npmjs.com/package/ws
*/
function getWebSocketImplementation() {
let WebSocketImplementation = global$1.WebSocket;
if (typeof WebSocketImplementation === 'undefined') {
try {
WebSocketImplementation = require('ws');
} catch (err) {
throw new Error('You must install the "ws" package to use Strophe in nodejs.');
}
}
return WebSocketImplementation;
}
const WebSocket = getWebSocketImplementation();
/**
* DOMParser
* https://w3c.github.io/DOM-Parsing/#the-domparser-interface
*
* Interface to parse XML strings into Document objects
*
* Used implementations:
* - supported browsers: built-in in DOMParser global
* https://developer.mozilla.org/en-US/docs/Web/API/DOMParser#Browser_compatibility
* - nodejs: use '@xmldom/xmldom' module
* https://www.npmjs.com/package/@xmldom/xmldom
*/
function getDOMParserImplementation() {
let DOMParserImplementation = global$1.DOMParser;
if (typeof DOMParserImplementation === 'undefined') {
try {
DOMParserImplementation = require('@xmldom/xmldom').DOMParser;
} catch (err) {
throw new Error('You must install the "@xmldom/xmldom" package to use Strophe in nodejs.');
}
}
return DOMParserImplementation;
}
const DOMParser = getDOMParserImplementation();
/**
* Gets IE xml doc object. Used by getDummyXMLDocument shim.
*
* Returns:
* A Microsoft XML DOM Object
* See Also:
* http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx
*/
function _getIEXmlDom() {
const docStrings = ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"];
for (let d = 0; d < docStrings.length; d++) {
try {
// eslint-disable-next-line no-undef
const doc = new ActiveXObject(docStrings[d]);
return doc;
} catch (e) {// Try next one
}
}
}
/**
* Creates a dummy XML DOM document to serve as an element and text node generator.
*
* Used implementations:
* - IE < 10: avoid using createDocument() due to a memory leak, use ie-specific
* workaround
* - other supported browsers: use document's createDocument
* - nodejs: use '@xmldom/xmldom'
*/
function getDummyXMLDOMDocument() {
// nodejs
if (typeof document === 'undefined') {
try {
const DOMImplementation = require('@xmldom/xmldom').DOMImplementation;
return new DOMImplementation().createDocument('jabber:client', 'strophe', null);
} catch (err) {
throw new Error('You must install the "@xmldom/xmldom" package to use Strophe in nodejs.');
}
} // IE < 10
if (document.implementation.createDocument === undefined || document.implementation.createDocument && document.documentMode && document.documentMode < 10) {
const doc = _getIEXmlDom();
doc.appendChild(doc.createElement('strophe'));
return doc;
} // All other supported browsers
return document.implementation.createDocument('jabber:client', 'strophe', null);
}
/*
* 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!
*/
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
const safe_add$1 = function (x, y) {
const lsw = (x & 0xFFFF) + (y & 0xFFFF);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return msw << 16 | lsw & 0xFFFF;
};
/*
* Bitwise rotate a 32-bit number to the left.
*/
const bit_rol = function (num, cnt) {
return num << cnt | num >>> 32 - cnt;
};
/*
* Convert a string to an array of little-endian words
*/
const str2binl = function (str) {
if (typeof str !== "string") {
throw new Error("str2binl was passed a non-string");
}
const bin = [];
for (let 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
*/
const binl2str = function (bin) {
let str = "";
for (let 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.
*/
const binl2hex = function (binarray) {
const hex_tab = "0123456789abcdef";
let str = "";
for (let 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.
*/
const md5_cmn = function (q, a, b, x, s, t) {
return safe_add$1(bit_rol(safe_add$1(safe_add$1(a, q), safe_add$1(x, t)), s), b);
};
const md5_ff = function (a, b, c, d, x, s, t) {
return md5_cmn(b & c | ~b & d, a, b, x, s, t);
};
const md5_gg = function (a, b, c, d, x, s, t) {
return md5_cmn(b & d | c & ~d, a, b, x, s, t);
};
const md5_hh = function (a, b, c, d, x, s, t) {
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
};
const 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
*/
const core_md5 = function (x, len) {
/* append padding */
x[len >> 5] |= 0x80 << len % 32;
x[(len + 64 >>> 9 << 4) + 14] = len;
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
let olda, oldb, oldc, oldd;
for (let 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$1(a, olda);
b = safe_add$1(b, oldb);
c = safe_add$1(c, oldc);
d = safe_add$1(d, oldd);
}
return [a, b, c, d];
};
/*
* These are the functions you'll usually want to call.
* They take string arguments and return either hex or base-64 encoded
* strings.
*/
const MD5 = {
hexdigest: function (s) {
return binl2hex(core_md5(str2binl(s), s.length * 8));
},
hash: function (s) {
return binl2str(core_md5(str2binl(s), s.length * 8));
}
};
/** Class: Strophe.SASLMechanism
*
* Encapsulates an SASL authentication mechanism.
*
* User code may override the priority for each mechanism or disable it completely.
* See <priority> for information about changing priority and <test> for informatian on
* how to disable a mechanism.
*
* By default, all mechanisms are enabled and the priorities are
*
* SCRAM-SHA-1 - 60
* PLAIN - 50
* OAUTHBEARER - 40
* X-OAUTH2 - 30
* ANONYMOUS - 20
* EXTERNAL - 10
*
* See: Strophe.Connection.addSupportedSASLMechanisms
*/
class SASLMechanism {
/**
* 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.
*/
constructor(name, isClientFirst, priority) {
/** PrivateVariable: mechname
* Mechanism name.
*/
this.mechname = name;
/** PrivateVariable: isClientFirst
* If client sends response without initial server challenge.
*/
this.isClientFirst = isClientFirst;
/** Variable: priority
* Determines which <SASLMechanism> is chosen for authentication (Higher is better).
* Users may override this to prioritize mechanisms differently.
*
* Example: (This will cause Strophe to choose the mechanism that the server sent first)
*
* > Strophe.SASLPlain.priority = Strophe.SASLSHA1.priority;
*
* See <SASL mechanisms> for a list of available mechanisms.
*
*/
this.priority = priority;
}
/**
* 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 <SASL mechanisms> for a list of available mechanisms.
*
* Parameters:
* (Strophe.Connection) connection - Target Connection.
*
* Returns:
* (Boolean) If mechanism was able to run.
*/
test() {
// eslint-disable-line class-methods-use-this
return true;
}
/** PrivateFunction: onStart
* Called before starting mechanism on some connection.
*
* Parameters:
* (Strophe.Connection) connection - Target Connection.
*/
onStart(connection) {
this._connection = connection;
}
/** PrivateFunction: onChallenge
* Called by protocol implementation on incoming challenge.
*
* By deafult, if the client is expected to send data first (isClientFirst === true),
* this method is called with `challenge` as null on the first call,
* unless `clientChallenge` is overridden in the relevant subclass.
*
* Parameters:
* (Strophe.Connection) connection - Target Connection.
* (String) challenge - current challenge to handle.
*
* Returns:
* (String) Mechanism response.
*/
onChallenge(connection, challenge) {
// eslint-disable-line
throw new Error("You should implement challenge handling!");
}
/** PrivateFunction: clientChallenge
* Called by the protocol implementation if the client is expected to send
* data first in the authentication exchange (i.e. isClientFirst === true).
*
* Parameters:
* (Strophe.Connection) connection - Target Connection.
*
* Returns:
* (String) Mechanism response.
*/
clientChallenge(connection) {
if (!this.isClientFirst) {
throw new Error("clientChallenge should not be called if isClientFirst is false!");
}
return this.onChallenge(connection);
}
/** PrivateFunction: onFailure
* Protocol informs mechanism implementation about SASL failure.
*/
onFailure() {
this._connection = null;
}
/** PrivateFunction: onSuccess
* Protocol informs mechanism implementation about SASL success.
*/
onSuccess() {
this._connection = null;
}
}
class SASLAnonymous extends SASLMechanism {
/** PrivateConstructor: SASLAnonymous
* SASL ANONYMOUS authentication.
*/
constructor(mechname = 'ANONYMOUS', isClientFirst = false, priority = 20) {
super(mechname, isClientFirst, priority);
}
test(connection) {
// eslint-disable-line class-methods-use-this
return connection.authcid === null;
}
}
class SASLExternal extends SASLMechanism {
/** PrivateConstructor: SASLExternal
* SASL EXTERNAL authentication.
*
* The EXTERNAL mechanism allows a client to request the server to use
* credentials established by means external to the mechanism to
* authenticate the client. The external means may be, for instance,
* TLS services.
*/
constructor(mechname = 'EXTERNAL', isClientFirst = true, priority = 10) {
super(mechname, isClientFirst, priority);
}
onChallenge(connection) {
// eslint-disable-line class-methods-use-this
/** According to XEP-178, an authzid SHOULD NOT be presented when the
* authcid contained or implied in the client certificate is the JID (i.e.
* authzid) with which the user wants to log in as.
*
* To NOT send the authzid, the user should therefore set the authcid equal
* to the JID when instantiating a new Strophe.Connection object.
*/
return connection.authcid === connection.authzid ? '' : connection.authzid;
}
}
const 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
*/
cookies = cookies || {};
for (const cookieName in cookies) {
if (Object.prototype.hasOwnProperty.call(cookies, cookieName)) {
let expires = '';
let domain = '';
let path = '';
const cookieObj = cookies[cookieName];
const isObj = typeof cookieObj === "object";
const 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;
}
}
}
};
class SASLOAuthBearer extends SASLMechanism {
/** PrivateConstructor: SASLOAuthBearer
* SASL OAuth Bearer authentication.
*/
constructor(mechname = 'OAUTHBEARER', isClientFirst = true, priority = 40) {
super(mechname, isClientFirst, priority);
}
test(connection) {
// eslint-disable-line class-methods-use-this
return connection.pass !== null;
}
onChallenge(connection) {
// eslint-disable-line class-methods-use-this
let auth_str = 'n,';
if (connection.authcid !== null) {
auth_str = auth_str + 'a=' + connection.authzid;
}
auth_str = auth_str + ',';
auth_str = auth_str + "\u0001";
auth_str = auth_str + 'auth=Bearer ';
auth_str = auth_str + connection.pass;
auth_str = auth_str + "\u0001";
auth_str = auth_str + "\u0001";
return utils.utf16to8(auth_str);
}
}
class SASLPlain extends SASLMechanism {
/** PrivateConstructor: SASLPlain
* SASL PLAIN authentication.
*/
constructor(mechname = 'PLAIN', isClientFirst = true, priority = 50) {
super(mechname, isClientFirst, priority);
}
test(connection) {
// eslint-disable-line class-methods-use-this
return connection.authcid !== null;
}
onChallenge(connection) {
// eslint-disable-line class-methods-use-this
const {
authcid,
authzid,
domain,
pass
} = connection;
if (!domain) {
throw new Error("SASLPlain onChallenge: domain is not defined!");
} // Only include authzid if it differs from authcid.
// See: https://tools.ietf.org/html/rfc6120#section-6.3.8
let auth_str = authzid !== `${authcid}@${domain}` ? authzid : '';
auth_str = auth_str + "\u0000";
auth_str = auth_str + authcid;
auth_str = auth_str + "\u0000";
auth_str = auth_str + pass;
return utils.utf16to8(auth_str);
}
}
/*
* 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.
*/
/* global define */
/* Some functions and variables have been stripped for use with Strophe */
/*
* 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 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;
}
/*
* 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;
}
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
const SHA1 = {
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));
}
};
class SASLSHA1 extends SASLMechanism {
/** PrivateConstructor: SASLSHA1
* SASL SCRAM SHA 1 authentication.
*/
constructor(mechname = 'SCRAM-SHA-1', isClientFirst = true, priority = 60) {
super(mechname, isClientFirst, priority);
}
test(connection) {
// eslint-disable-line class-methods-use-this
return connection.authcid !== null;
}
onChallenge(connection, challenge) {
// eslint-disable-line class-methods-use-this
let nonce, salt, iter, Hi, U, U_old, i, k;
let responseText = "c=biws,";
let authMessage = `${connection._sasl_data["client-first-message-bare"]},${challenge},`;
const cnonce = connection._sasl_data.cnonce;
const attribMatch = /([a-z]+)=([^,]+)(,|$)/;
while (challenge.match(attribMatch)) {
const 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 = atob(salt);
salt += "\x00\x00\x00\x01";
const pass = utils.utf16to8(connection.pass);
Hi = U_old = SHA1.core_hmac_sha1(pass, salt);
for (i = 1; i < iter; i++) {
U = SHA1.core_hmac_sha1(pass, SHA1.binb2str(U_old));
for (k = 0; k < 5; k++) {
Hi[k] ^= U[k];
}
U_old = U;
}
Hi = SHA1.binb2str(Hi);
const clientKey = SHA1.core_hmac_sha1(Hi, "Client Key");
const serverKey = SHA1.str_hmac_sha1(Hi, "Server Key");
const 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=" + btoa(SHA1.binb2str(clientKey));
return responseText;
}
clientChallenge(connection, test_cnonce) {
// eslint-disable-line class-methods-use-this
const cnonce = test_cnonce || MD5.hexdigest("" + Math.random() * 1234567890);
let auth_str = "n=" + utils.utf16to8(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;
return auth_str;
}
}
class SASLXOAuth2 extends SASLMechanism {
/** PrivateConstructor: SASLXOAuth2
* SASL X-OAuth2 authentication.
*/
constructor(mechname = 'X-OAUTH2', isClientFirst = true, priority = 30) {
super(mechname, isClientFirst, priority);
}
test(connection) {
// eslint-disable-line class-methods-use-this
return connection.pass !== null;
}
onChallenge(connection) {
// eslint-disable-line class-methods-use-this
let auth_str = '\u0000';
if (connection.authcid !== null) {
auth_str = auth_str + connection.authzid;
}
auth_str = auth_str + "\u0000";
auth_str = auth_str + connection.pass;
return utils.utf16to8(auth_str);
}
}
/*
This program is distributed under the terms of the MIT license.
Please see the LICENSE file for details.
Copyright 2006-2018, OGG, LLC
*/
/** 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.
*/
const Strophe = {
/** Constant: VERSION */
VERSION: "1.4.4",
/** 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 and RFC 6120.
* 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(tag) {
for (let 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(tag, attribute) {
if (typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) {
for (let i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
if (attribute === Strophe.XHTML.attributes[tag][i]) {
return true;
}
}
}
return false;
},
validCSS(style) {
for (let 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.REDIRECT - The connection has been redirected
* 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,
BINDREQUIRED: 11,
ATTACHFAIL: 12
},
ErrorCondition: {
BAD_FORMAT: "bad-format",
CONFLICT: "conflict",
MISSING_JID_NODE: "x-strophe-bad-non-anon-jid",
NO_AUTH_MECH: "no-auth-mech",
UNKNOWN_REASON: "unknown"
},
/** 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(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(elem, elemName, func) {
for (let i = 0; i < elem.childNodes.length; i++) {
const 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(el, name) {
return el.tagName === name;
},
/** PrivateVariable: _xmlGenerator
* _Private_ variable that caches a DOM document to
* generate elements.
*/
_xmlGenerator: null,
/** Function: xmlGenerator
* Get the DOM document to generate elements.
*
* Returns:
* The currently used DOM document.
*/
xmlGenerator() {
if (!Strophe._xmlGenerator) {
Strophe._xmlGenerator = getDummyXMLDOMDocument();
}
return Strophe._xmlGenerator;
},
/** 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(name) {
if (!name) {
return null;
}
const node = Strophe.xmlGenerator().createElement(name); // FIXME: this should throw errors if args are the wrong type or
// there are more than two optional args
for (let a = 1; a < arguments.length; a++) {
const 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 (let i = 0; i < arg.length; i++) {
const 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 (const k in arg) {
if (Object.prototype.hasOwnProperty.call(arg, k) && 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(text) {
text = text.replace(/\&/g, "&");
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(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(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(html) {
let node; //ensure text is escaped
if (DOMParser) {
const 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(elem) {
if (!elem) {
return null;
}
let str = "";
if (elem.childNodes.length === 0 && elem.nodeType === Strophe.ElementType.TEXT) {
str += elem.nodeValue;
}
for (let 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(elem) {
let el;
if (elem.nodeType === Strophe.ElementType.NORMAL) {
el = Strophe.xmlElement(elem.tagName);
for (let i = 0; i < elem.attributes.length; i++) {
el.setAttribute(elem.attributes[i].nodeName, elem.attributes[i].value);
}
for (let 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(elem) {
let el;
if (elem.nodeType === Strophe.ElementType.NORMAL) {
const tag = elem.nodeName.toLowerCase(); // XHTML tags must be lower case.
if (Strophe.XHTML.validTag(tag)) {
try {
el = Strophe.xmlElement(tag);
for (let i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
const attribute = Strophe.XHTML.attributes[tag][i];
let value = elem.getAttribute(attribute);
if (typeof value === 'undefined' || value === null || value === '' || value === false || value === 0) {
continue;
}
if (attribute === 'style' && typeof value === 'object' && 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') {
const css = [];
const cssAttrs = value.split(';');
for (let j = 0; j < cssAttrs.length; j++) {
const attr = cssAttrs[j].split(':');
const cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase();
if (Strophe.XHTML.validCSS(cssName)) {
const 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 (let 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 (let 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 (let 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(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(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(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(jid) {
const bare = Strophe.getBareJidFromJid(jid);
if (bare.indexOf("@") < 0) {
return bare;
} else {
const 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(jid) {
if (!jid) {
return null;
}
const 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(jid) {
return jid ? jid.split("/")[0] : null;
},
/** PrivateFunction: _handleError
* _Private_ function that properly logs an error to the console
*/
_handleError(e) {
if (typeof e.stack !== "undefined") {
Strophe.fatal(e.stack);
}
if (e.sourceURL) {
Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message);
} else if (e.fileName) {
Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message);
} else {
Strophe.fatal("error: " + e.message);
}
},
/** Function: log
* User overrideable logging function.
*
* This function is called whenever the Strophe library calls any
* of the logg