UNPKG

strophe.js

Version:

Strophe.js is an XMPP library for JavaScript

1,481 lines (1,467 loc) 224 kB
const _NS = { AUTH: 'jabber:iq:auth', BIND: 'urn:ietf:params:xml:ns:xmpp-bind', BOSH: 'urn:xmpp:xbosh', CLIENT: 'jabber:client', COMPONENT: 'jabber:component:accept' /** XEP-0114 */, DISCO_INFO: 'http://jabber.org/protocol/disco#info', DISCO_ITEMS: 'http://jabber.org/protocol/disco#items', DELAY: 'urn:xmpp:delay' /** XEP-0203 */, FRAMING: 'urn:ietf:params:xml:ns:xmpp-framing', HTTPBIND: 'http://jabber.org/protocol/httpbind', MUC: 'http://jabber.org/protocol/muc', PROFILE: 'jabber:iq:profile', ROSTER: 'jabber:iq:roster', SASL: 'urn:ietf:params:xml:ns:xmpp-sasl', SERVER: 'jabber:server', SESSION: 'urn:ietf:params:xml:ns:xmpp-session', SM: 'urn:xmpp:sm:3', STANZAS: 'urn:ietf:params:xml:ns:xmpp-stanzas', STREAM: 'http://etherx.jabber.org/streams', VERSION: 'jabber:iq:version', XHTML: 'http://www.w3.org/1999/xhtml', XHTML_IM: 'http://jabber.org/protocol/xhtml-im', }; /** * Common namespace constants from the XMPP RFCs and XEPs. * Extensible at runtime via {@link Strophe.addNamespace}, hence the string * index signature. */ const NS = _NS; const PARSE_ERROR_NS = 'http://www.w3.org/1999/xhtml'; /** * The version of the page↔worker message protocol spoken between * WorkerWebsocket and dist/shared-connection-worker.js. A SharedWorker can * outlive the pages that spawned it, so after a deploy a freshly loaded page * may attach to a worker from an older build (or vice versa). The version is * exchanged on _connect/_attach so that a mismatch fails loudly instead of * silently misbehaving. */ const SHARED_WORKER_PROTOCOL_VERSION = 3; /** * Contains allowed tags, tag attributes, and css properties. * Used in the {@link Strophe.createHtml} function to filter incoming html into the allowed XHTML-IM subset. * See [XEP-0071](http://xmpp.org/extensions/xep-0071.html#profile-summary) for the list of recommended * allowed tags and their attributes. */ const 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', ], }; /** * Connection status constants for use by the connection handler * callback. */ const 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, RECONNECTING: 13, }; const 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', }; const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, FATAL: 4, }; /** * DOM element types. * * - ElementType.NORMAL - Normal element. * - ElementType.TEXT - Text data element. * - ElementType.FRAGMENT - XHTML fragment element. */ const ElementType = { NORMAL: 1, TEXT: 3, CDATA: 4, FRAGMENT: 11, }; let logLevel = LOG_LEVELS.DEBUG; const log = { /** * Library consumers can use this function to set the log level of Strophe. * The default log level is Strophe.LogLevel.INFO. * @param level * @example Strophe.setLogLevel(Strophe.LogLevel.DEBUG); */ setLogLevel(level) { if (level < LOG_LEVELS.DEBUG || level > LOG_LEVELS.FATAL) { throw new Error("Invalid log level supplied to setLogLevel"); } logLevel = level; }, /** * * Please note that data sent and received over the wire is logged * via {@link Strophe.Connection#rawInput|Strophe.Connection.rawInput()} * and {@link Strophe.Connection#rawOutput|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. * * @param level - The log level of the log message. * This will be one of the values in Strophe.LOG_LEVELS. * @param msg - The log message. */ log(level, msg) { if (level < logLevel) { return; } if (level >= LOG_LEVELS.ERROR) { console === null || console === void 0 ? void 0 : console.error(msg); } else if (level === LOG_LEVELS.INFO) { console === null || console === void 0 ? void 0 : console.info(msg); } else if (level === LOG_LEVELS.WARN) { console === null || console === void 0 ? void 0 : console.warn(msg); } else if (level === LOG_LEVELS.DEBUG) { console === null || console === void 0 ? void 0 : console.debug(msg); } }, /** * Log a message at the Strophe.LOG_LEVELS.DEBUG level. * @param msg - The log message. */ debug(msg) { this.log(LOG_LEVELS.DEBUG, msg); }, /** * Log a message at the Strophe.LOG_LEVELS.INFO level. * @param msg - The log message. */ info(msg) { this.log(LOG_LEVELS.INFO, msg); }, /** * Log a message at the Strophe.LOG_LEVELS.WARN level. * @param msg - The log message. */ warn(msg) { this.log(LOG_LEVELS.WARN, msg); }, /** * Log a message at the Strophe.LOG_LEVELS.ERROR level. * @param msg - The log message. */ error(msg) { this.log(LOG_LEVELS.ERROR, msg); }, /** * Log a message at the Strophe.LOG_LEVELS.FATAL level. * @param msg - The log message. */ fatal(msg) { this.log(LOG_LEVELS.FATAL, msg); }, }; /** * Takes a string and turns it into an XML Element. * @param string * @param throwErrorIfInvalidNS * @returns */ function toElement(string, throwErrorIfInvalidNS) { const doc = xmlHtmlNode(string); const parserError = getParserError(doc); if (parserError) { throw new Error(`Parser Error: ${parserError}`); } const node = getFirstElementChild(doc); if (['message', 'iq', 'presence'].includes(node.nodeName.toLowerCase()) && node.namespaceURI !== 'jabber:client' && node.namespaceURI !== 'jabber:server') { const err_msg = `Invalid namespaceURI ${node.namespaceURI}`; if (throwErrorIfInvalidNS) { throw new Error(err_msg); } else { log.error(err_msg); } } return node; } /** * Properly logs an error to the console * @param e */ function handleError(e) { if (typeof e.stack !== 'undefined') { log.fatal(e.stack); } log.fatal('error: ' + e.message); } /** * @param str * @returns */ function utf16to8(str) { let out = ''; const len = str.length; for (let i = 0; i < len; i++) { const 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; } /** * @param x * @param y * @returns */ function xorArrayBuffers(x, y) { const xIntArray = new Uint8Array(x); const yIntArray = new Uint8Array(y); const zIntArray = new Uint8Array(x.byteLength); for (let i = 0; i < x.byteLength; i++) { zIntArray[i] = xIntArray[i] ^ yIntArray[i]; } return zIntArray.buffer; } /** * @param buffer * @returns */ function arrayBufToBase64(buffer) { let binary = ''; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } /** * @param str * @returns */ function base64ToArrayBuf(str) { var _a; return (_a = Uint8Array.from(atob(str), (c) => c.charCodeAt(0))) === null || _a === void 0 ? void 0 : _a.buffer; } /** * @param str * @returns */ function stringToArrayBuf(str) { const bytes = new TextEncoder().encode(str); return bytes.buffer; } /** * @param cookies */ function addCookies(cookies) { if (typeof document === 'undefined') { log.error(`addCookies: not adding any cookies, since there's no document object`); return; } const cookieMap = cookies || {}; for (const cookieName in cookieMap) { if (Object.prototype.hasOwnProperty.call(cookieMap, cookieName)) { let expires = ''; let domain = ''; let path = ''; const cookieObj = cookieMap[cookieName]; const isObj = typeof cookieObj === 'object'; const cookieValue = escape(unescape(isObj ? cookieObj.value : cookieObj)); if (isObj) { const cv = cookieObj; expires = cv.expires ? ';expires=' + cv.expires : ''; domain = cv.domain ? ';domain=' + cv.domain : ''; path = cv.path ? ';path=' + cv.path : ''; } document.cookie = cookieName + '=' + cookieValue + expires + domain + path; } } } let _xmlGenerator = null; /** * Get the DOM document to generate elements. * @returns The currently used DOM document. */ function xmlGenerator() { if (!_xmlGenerator) { _xmlGenerator = document.implementation.createDocument('jabber:client', 'strophe', null); } return _xmlGenerator; } /** * Creates an XML DOM text node. * Provides a cross implementation version of document.createTextNode. * @param text - The content of the text node. * @returns A new XML DOM text node. */ function xmlTextNode(text) { return xmlGenerator().createTextNode(text); } /** * @param stanza * @returns */ function stripWhitespace(stanza) { const childNodes = Array.from(stanza.childNodes); if (childNodes.length === 1 && childNodes[0].nodeType === ElementType.TEXT) { return stanza; } childNodes.forEach((node) => { if (node.nodeName.toLowerCase() === 'body') { return; } if (node.nodeType === ElementType.TEXT && !/\S/.test(node.nodeValue)) { stanza.removeChild(node); } else if (node.nodeType === ElementType.NORMAL) { stripWhitespace(node); } }); return stanza; } /** * Creates an XML DOM node. * @param text - The contents of the XML element. * @returns */ function xmlHtmlNode(text) { const parser = new DOMParser(); return parser.parseFromString(text, 'text/xml'); } /** * @param doc * @returns */ function getParserError(doc) { var _a; const el = ((_a = doc.firstElementChild) === null || _a === void 0 ? void 0 : _a.nodeName) === 'parsererror' ? doc.firstElementChild : doc.getElementsByTagNameNS(PARSE_ERROR_NS, 'parsererror')[0]; return (el === null || el === void 0 ? void 0 : el.nodeName) === 'parsererror' ? el === null || el === void 0 ? void 0 : el.textContent : null; } /** * @param el * @returns */ function getFirstElementChild(el) { if (el.firstElementChild) return el.firstElementChild; let node; let i = 0; const nodes = el.childNodes; while ((node = nodes[i++])) { if (node.nodeType === 1) return node; } return null; } /** * 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. * * @param name - The name for the element. * @param attrs * An optional array or object containing * key/value pairs to use as element attributes. * The object should be in the format `{'key': 'value'}`. * The array should have the format `[['key1', 'value1'], ['key2', 'value2']]`. * @param text - The text child data for the element. * * @returns A new XML DOM element. */ function xmlElement(name, attrs, text) { if (!name) return null; const node = xmlGenerator().createElement(name); if (text && (typeof text === 'string' || typeof text === 'number')) { node.appendChild(xmlTextNode(text.toString())); } else if (typeof attrs === 'string' || typeof attrs === 'number') { node.appendChild(xmlTextNode(attrs.toString())); return node; } if (!attrs) { return node; } else if (Array.isArray(attrs)) { for (const attr of attrs) { if (Array.isArray(attr)) { if (attr[0] != null && attr[1] != null) { node.setAttribute(attr[0], attr[1]); } } } } else if (typeof attrs === 'object') { for (const k of Object.keys(attrs)) { if (k && attrs[k] != null) { node.setAttribute(k, attrs[k].toString()); } } } return node; } /** * 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. * @method Strophe.XHTML.validTag * @param tag */ function validTag(tag) { for (let i = 0; i < XHTML.tags.length; i++) { if (tag === XHTML.tags[i]) { return true; } } return false; } /** * 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. * @method Strophe.XHTML.validAttribute * @param tag * @param attribute */ function validAttribute(tag, attribute) { const attrs = XHTML.attributes[tag]; if ((attrs === null || attrs === void 0 ? void 0 : attrs.length) > 0) { for (let i = 0; i < attrs.length; i++) { if (attribute === attrs[i]) { return true; } } } return false; } /** * @method Strophe.XHTML.validCSS * @param style */ function validCSS(style) { for (let i = 0; i < XHTML.css.length; i++) { if (style === XHTML.css[i]) { return true; } } return false; } /** * Copy an HTML DOM Element into an XML DOM. * This function copies a DOM element and all its descendants and returns * the new copy. * @param elem - A DOM element. * @returns A new, copied DOM element tree. */ function createFromHtmlElement(elem) { var _a; let el; const tag = elem.nodeName.toLowerCase(); if (validTag(tag)) { try { el = xmlElement(tag); if (tag in XHTML.attributes) { const attrs = XHTML.attributes[tag]; for (let i = 0; i < attrs.length; i++) { const attribute = attrs[i]; let value = elem.getAttribute(attribute); if (typeof value === 'undefined' || value === null || value === '') { continue; } if (attribute === 'style' && typeof value === 'object') { value = (_a = value.cssText) !== null && _a !== void 0 ? _a : value; } 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 (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(createHtml(elem.childNodes[i])); } } } catch (_e) { el = xmlTextNode(''); } } else { el = xmlGenerator().createDocumentFragment(); for (let i = 0; i < elem.childNodes.length; i++) { el.appendChild(createHtml(elem.childNodes[i])); } } return el; } /** * Copy an HTML DOM Node into an XML DOM. * This function copies a DOM element and all its descendants and returns * the new copy. * @method Strophe.createHtml * @param node - A DOM element. * @returns A new, copied DOM element tree. */ function createHtml(node) { if (node.nodeType === ElementType.NORMAL) { return createFromHtmlElement(node); } else if (node.nodeType === ElementType.FRAGMENT) { const el = xmlGenerator().createDocumentFragment(); for (let i = 0; i < node.childNodes.length; i++) { el.appendChild(createHtml(node.childNodes[i])); } return el; } else if (node.nodeType === ElementType.TEXT) { return xmlTextNode(node.nodeValue); } } /** * Copy an XML DOM element. * * This function copies a DOM element and all its descendants and returns * the new copy. * @method Strophe.copyElement * @param node - A DOM element. * @returns A new, copied DOM element tree. */ function copyElement(node) { let out; if (node.nodeType === ElementType.NORMAL) { const el = node; out = xmlElement(el.tagName); for (let i = 0; i < el.attributes.length; i++) { out.setAttribute(el.attributes[i].nodeName, el.attributes[i].value); } for (let i = 0; i < el.childNodes.length; i++) { out.appendChild(copyElement(el.childNodes[i])); } } else if (node.nodeType === ElementType.TEXT) { out = xmlGenerator().createTextNode(node.nodeValue); } return out; } /** * Excapes invalid xml characters. * @method Strophe.xmlescape * @param text - text to escape. * @returns Escaped text. */ function xmlescape(text) { return text .replace(/\&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/'/g, '&apos;') .replace(/"/g, '&quot;'); } /** * Unexcapes invalid xml characters. * @method Strophe.xmlunescape * @param text - text to unescape. * @returns Unescaped text. */ function xmlunescape(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; } /** * 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. * * @method Strophe.forEachChild * @param elem - The element to operate on. * @param elemName - The child element tag name filter. * @param func - The function to apply to each child. This * function should take a single argument, a DOM element. */ function forEachChild(elem, elemName, func) { for (let i = 0; i < elem.childNodes.length; i++) { const childNode = elem.childNodes[i]; if (childNode.nodeType === ElementType.NORMAL && (!elemName || isTagEqual(childNode, elemName))) { func(childNode); } } } /** * Compare an element's tag name with a string. * This function is case sensitive. * @method Strophe.isTagEqual * @param el - A DOM element. * @param name - The element name. * @returns * true if the element's tag name matches _el_, and false * otherwise. */ function isTagEqual(el, name) { return el.tagName === name; } /** * Return the XML namespace of an element. * * Prefers the serialized `xmlns` attribute and falls back to the DOM * `namespaceURI`, because the two diverge depending on how the element was * built and neither is reliable on its own: * * - Locally-built stanzas (`$iq`, `stx`, {@link Builder}) are created with * `createElement` and carry their namespace only in the `xmlns` attribute; * their `namespaceURI` is null. * - Stanzas received over the XEP-0114 component transport are built with * `createElementNS` and carry their namespace only on `namespaceURI`; the * redundant `xmlns` attribute is omitted. * - WebSocket / BOSH stanzas parsed by `DOMParser` carry both, except on * child elements that inherit the default namespace without redeclaring it * (those have only `namespaceURI`). * * Checking both is the transport-agnostic way to read an element's namespace. * * @method Strophe.getNamespace * @param elem - The element whose namespace is wanted. * @returns The namespace URI, or null if the element has none. */ function getNamespace(elem) { return elem.getAttribute('xmlns') || elem.namespaceURI; } /** * Get the concatenation of all text children of an element. * @method Strophe.getText * @param elem - A DOM element. * @returns A String with the concatenated text of all text element children. */ function getText(elem) { if (!elem) return null; let str = ''; if (!elem.childNodes.length && elem.nodeType === ElementType.TEXT) { str += elem.nodeValue; } for (const child of elem.childNodes) { if (child.nodeType === ElementType.TEXT) { str += child.nodeValue; } } return xmlescape(str); } /** * Escape the node part (also called local part) of a JID. * @method Strophe.escapeNode * @param node - A node (or local part). * @returns An escaped node (or local part). */ function 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'); } /** * Unescape a node part (also called local part) of a JID. * @method Strophe.unescapeNode * @param node - A node (or local part). * @returns An unescaped node (or local part). */ function 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, '\\'); } /** * Get the node portion of a JID String. * @method Strophe.getNodeFromJid * @param jid - A JID. * @returns A String containing the node. */ function getNodeFromJid(jid) { if (jid.indexOf('@') < 0) { return null; } return jid.split('@')[0]; } /** * Get the domain portion of a JID String. * @method Strophe.getDomainFromJid * @param jid - A JID. * @returns A String containing the domain. */ function getDomainFromJid(jid) { const bare = getBareJidFromJid(jid); if (bare.indexOf('@') < 0) { return bare; } else { const parts = bare.split('@'); parts.splice(0, 1); return parts.join('@'); } } /** * Get the resource portion of a JID String. * @method Strophe.getResourceFromJid * @param jid - A JID. * @returns A String containing the resource. */ function getResourceFromJid(jid) { if (!jid) { return null; } const s = jid.split('/'); if (s.length < 2) { return null; } s.splice(0, 1); return s.join('/'); } /** * Get the bare JID from a JID String. * @method Strophe.getBareJidFromJid * @param jid - A JID. * @returns A String containing the bare JID. */ function getBareJidFromJid(jid) { return jid ? jid.split('/')[0] : null; } const utils = { utf16to8, xorArrayBuffers, arrayBufToBase64, base64ToArrayBuf, stringToArrayBuf, addCookies, }; var utils$1 = /*#__PURE__*/Object.freeze({ __proto__: null, addCookies: addCookies, arrayBufToBase64: arrayBufToBase64, base64ToArrayBuf: base64ToArrayBuf, copyElement: copyElement, createHtml: createHtml, default: utils, escapeNode: escapeNode, forEachChild: forEachChild, getBareJidFromJid: getBareJidFromJid, getDomainFromJid: getDomainFromJid, getFirstElementChild: getFirstElementChild, getNamespace: getNamespace, getNodeFromJid: getNodeFromJid, getParserError: getParserError, getResourceFromJid: getResourceFromJid, getText: getText, handleError: handleError, isTagEqual: isTagEqual, stringToArrayBuf: stringToArrayBuf, stripWhitespace: stripWhitespace, toElement: toElement, unescapeNode: unescapeNode, utf16to8: utf16to8, validAttribute: validAttribute, validCSS: validCSS, validTag: validTag, xmlElement: xmlElement, xmlGenerator: xmlGenerator, xmlHtmlNode: xmlHtmlNode, xmlTextNode: xmlTextNode, xmlescape: xmlescape, xmlunescape: xmlunescape, xorArrayBuffers: xorArrayBuffers }); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __classPrivateFieldGet(receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); } function __classPrivateFieldSet(receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var _Builder_nodeTree, _Builder_node, _Builder_name, _Builder_attrs; /** * Create a {@link Strophe.Builder} * This is an alias for `new Strophe.Builder(name, attrs)`. * @param name - The root element name. * @param attrs - The attributes for the root element in object notation. * @returns A new Strophe.Builder object. */ function $build(name, attrs) { return new Builder(name, attrs); } /** * Create a {@link Strophe.Builder} with a `<message/>` element as the root. * @param attrs - The <message/> element attributes in object notation. * @returns A new Strophe.Builder object. */ function $msg(attrs) { return new Builder('message', attrs); } /** * Create a {@link Strophe.Builder} with an `<iq/>` element as the root. * @param attrs - The <iq/> element attributes in object notation. * @returns A new Strophe.Builder object. */ function $iq(attrs) { return new Builder('iq', attrs); } /** * Create a {@link Strophe.Builder} with a `<presence/>` element as the root. * @param attrs - The <presence/> element attributes in object notation. * @returns A new Strophe.Builder object. */ function $pres(attrs) { return new Builder('presence', attrs); } /** * This class provides an interface similar to JQuery but for building * DOM elements easily and rapidly. All the functions except for `toString()` * and tree() return the object, so calls can be chained. * * 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. * * @example * // 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 * // <iq to='you' from='me' type='get' id='1'> * // <query xmlns='strophe:example'> * // <example/> * // </query> * // </iq> */ class Builder { /** * The attributes should be passed in object notation. * @param name - The name of the root element. * @param attrs - The attributes for the root element in object notation. * @example const b = new Builder('message', {to: 'you', from: 'me'}); * @example const b = new Builder('messsage', {'xml:lang': 'en'}); */ constructor(name, attrs) { _Builder_nodeTree.set(this, void 0); _Builder_node.set(this, void 0); _Builder_name.set(this, void 0); _Builder_attrs.set(this, void 0); // Set correct namespace for jabber:client elements if (name === 'presence' || name === 'message' || name === 'iq') { if (attrs && !attrs.xmlns) { attrs.xmlns = NS.CLIENT; } else if (!attrs) { attrs = { xmlns: NS.CLIENT }; } } __classPrivateFieldSet(this, _Builder_name, name, "f"); __classPrivateFieldSet(this, _Builder_attrs, attrs, "f"); } /** * Creates a new Builder object from an XML string. * @param str * @returns * @example const stanza = Builder.fromString('<presence from="juliet@example.com/chamber"></presence>'); */ static fromString(str) { const el = toElement(str, true); const b = new Builder(''); __classPrivateFieldSet(b, _Builder_nodeTree, el, "f"); return b; } buildTree() { return xmlElement(__classPrivateFieldGet(this, _Builder_name, "f"), __classPrivateFieldGet(this, _Builder_attrs, "f")); } get nodeTree() { if (!__classPrivateFieldGet(this, _Builder_nodeTree, "f")) { // Holds the tree being built. __classPrivateFieldSet(this, _Builder_nodeTree, this.buildTree(), "f"); } return __classPrivateFieldGet(this, _Builder_nodeTree, "f"); } get node() { if (!__classPrivateFieldGet(this, _Builder_node, "f")) { __classPrivateFieldSet(this, _Builder_node, this.tree(), "f"); } return __classPrivateFieldGet(this, _Builder_node, "f"); } set node(el) { __classPrivateFieldSet(this, _Builder_node, el, "f"); } /** * Render a DOM element and all descendants to a String. * @param elem - A DOM element. * @returns The serialized element tree as a String. */ static serialize(elem) { if (!elem) return null; const el = elem instanceof Builder ? elem.tree() : elem; const names = [...Array(el.attributes.length).keys()].map((i) => el.attributes[i].nodeName); names.sort(); let result = names.reduce((a, n) => `${a} ${n}="${xmlescape(el.attributes.getNamedItem(n).value)}"`, `<${el.nodeName}`); if (el.childNodes.length > 0) { result += '>'; for (let i = 0; i < el.childNodes.length; i++) { const child = el.childNodes[i]; switch (child.nodeType) { case ElementType.NORMAL: result += Builder.serialize(child); break; case ElementType.TEXT: result += xmlescape(child.nodeValue); break; case ElementType.CDATA: result += '<![CDATA[' + child.nodeValue + ']]>'; } } result += '</' + el.nodeName + '>'; } else { result += '/>'; } return result; } /** * 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() { return this.nodeTree; } /** * 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() { return Builder.serialize(this.tree()); } /** * Make the current parent element the new current element. * This function is often used after c() to traverse back up the tree. * * @example * // For example, to add two children to the same element * builder.c('child1', {}).up().c('child2', {}); * * @returns The Strophe.Builder object. */ up() { this.node = this.node.parentElement ? this.node.parentElement : this.node.parentNode; return this; } /** * Make the root element the new current element. * * When at a deeply nested element in the tree, this function can be used * to jump back to the root of the tree, instead of having to repeatedly * call up(). * * @returns The Strophe.Builder object. */ root() { this.node = this.tree(); return this; } /** * 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. * @param moreattrs - The attributes to add/modify in object notation. * If an attribute is set to `null` or `undefined`, it will be removed. * @returns The Strophe.Builder object. */ attrs(moreattrs) { for (const k in moreattrs) { if (Object.prototype.hasOwnProperty.call(moreattrs, k)) { if (moreattrs[k] != null) { this.node.setAttribute(k, moreattrs[k].toString()); } else { this.node.removeAttribute(k); } } } return this; } /** * 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. * * @param name - The name of the child. * @param attrs - The attributes of the child in object notation. * @param text - The text to add to the child. * * @returns The Strophe.Builder object. */ c(name, attrs, text) { const child = xmlElement(name, attrs, text); this.node.appendChild(child); if (typeof text !== 'string' && typeof text !== 'number') { this.node = child; } return this; } /** * 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. * * @param elem - A DOM element. * @returns The Strophe.Builder object. */ cnode(elem) { if (elem instanceof Builder) { elem = elem.tree(); } let impNode; const xmlGen = xmlGenerator(); try { impNode = xmlGen.importNode !== undefined; } catch (_e) { impNode = false; } const newElem = impNode ? xmlGen.importNode(elem, true) : copyElement(elem); this.node.appendChild(newElem); this.node = newElem; return this; } /** * Add a child text element. * * This *does not* make the child the new current element since there * are no children of text elements. * * @param text - The text data to append to the current element. * @returns The Strophe.Builder object. */ t(text) { const child = xmlTextNode(text); this.node.appendChild(child); return this; } /** * Replace current element contents with the HTML passed in. * * This *does not* make the child the new current element * * @param html - The html to insert as contents of current element. * @returns The Strophe.Builder object. */ h(html) { const fragment = xmlGenerator().createElement('body'); fragment.innerHTML = html; const xhtml = createHtml(fragment); while (xhtml.childNodes.length > 0) { this.node.appendChild(xhtml.childNodes[0]); } return this; } } _Builder_nodeTree = new WeakMap(), _Builder_node = new WeakMap(), _Builder_name = new WeakMap(), _Builder_attrs = new WeakMap(); /** * _Private_ variable that keeps track of the request ids for connections. */ let _requestId = 0; /** * Helper class that provides a cross implementation abstraction * for a BOSH related XMLHttpRequest. * * The Request class is used internally to encapsulate BOSH request * information. It is not meant to be used from user's code. */ class Request { /** * Create and initialize a new Request object. * * @param elem - The XML data to be sent in the request. * @param func - The function that will be called when the * XMLHttpRequest readyState changes. * @param rid - The BOSH rid attribute associated with this request. * @param sends - The number of times this same request has been sent. */ constructor(elem, func, rid, sends = 0) { this.id = ++_requestId; this.xmlData = elem; this.data = Builder.serialize(elem); this.origFunc = func; this.func = func; this.rid = rid; this.date = NaN; this.sends = sends; this.abort = false; this.dead = null; this.age = () => (this.date ? (new Date().valueOf() - this.date.valueOf()) / 1000 : 0); this.timeDead = () => (this.dead ? (new Date().valueOf() - this.dead.valueOf()) / 1000 : 0); this.xhr = this._newXHR(); } /** * 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. * @throws "bad-format" - The entity has sent XML that cannot be processed. * @returns The DOM element tree of the response. */ getResponse() { var _a; const node = (_a = this.xhr.responseXML) === null || _a === void 0 ? void 0 : _a.documentElement; if (node) { if (node.tagName === 'parsererror') { log.error('invalid response received'); log.error('responseText: ' + this.xhr.responseText); log.error('responseXML: ' + Builder.serialize(node)); throw new Error('parsererror'); } } else if (this.xhr.responseText) { log.debug('Got responseText but no responseXML; attempting to parse it with DOMParser...'); const doc = xmlHtmlNode(this.xhr.responseText); const parserError = getParserError(doc); if (!doc || parserError) { if (parserError) { log.error('invalid response received: ' + parserError); log.error('responseText: ' + this.xhr.responseText); } const error = new Error(); error.name = ErrorCondition.BAD_FORMAT; throw error; } } return node !== null && node !== void 0 ? node : null; } /** * _Private_ helper function to create XMLHttpRequests. * This function creates XMLHttpRequests across all implementations. * @private */ _newXHR() { const xhr = new XMLHttpRequest(); if (xhr.overrideMimeType) { xhr.overrideMimeType('text/xml; charset=utf-8'); } xhr.onreadystatechange = this.func.bind(null, this); return xhr; } } /** * 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. */ let timeoutMultiplier = 1.1; let secondaryTimeoutMultiplier = 0.1; /** * _Private_ helper class that handles BOSH Connections * The Bosh class is used internally by Connection * to encapsulate BOSH sessions. It is not meant to be used from user's code. */ class Bosh { /** * @param connection - The Connection that will use BOSH. */ constructor(connection) { var _a; 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.inactivity = null; /** * BOSH-Connections will have all stanzas wrapped in a <body> tag when * passed to {@link Connection#xmlInput|xmlInput()} or {@link Connection#xmlOutput|xmlOutput()}. * To strip this tag, User code can set {@link Bosh#strip|strip} to `true`: * * > // You can set `strip` on the prototype * > Bosh.prototype.strip = true; * * > // Or you can set it on the Bosh instance (which is `._proto` on the connection instance. * > const conn = new Connection(); * > conn._proto.strip = true; * * This will enable stripping of the body tag in both * {@link Connection#xmlInput|xmlInput} and {@link Connection#xmlOutput|xmlOutput}. */ this.strip = (_a = Bosh.prototype.strip) !== null && _a !== void 0 ? _a : false; this.lastResponseHeaders = null; this._requests = []; } static setTimeoutMultiplier(m) { timeoutMultiplier = m; } static getTimeoutMultplier() { return timeoutMultiplier; } static setSecondaryTimeoutMultiplier(m) { secondaryTimeoutMultiplier = m; } static getSecondaryTimeoutMultplier() { return secondaryTimeoutMultiplier; } /** * _Private_ helper function to generate the <body/> wrapper for BOSH. * @private * @returns A Builder with a <body/> element. */ _buildBody() { const bodyWrap = $build('body', { 'rid': this.rid++, 'xmlns': NS.HTTPBIND, }); if (this.sid !== null) { bodyWrap.attrs({ 'sid': this.sid }); } if (this._conn.options.keepalive && this._conn._sessionCachingSupported()) { this._cacheSession(); } return bodyWrap; } /** * Reset the connection. * This function is called by the reset function of the Connection */ _reset() { this.rid = Math.floor(Math.random() * 4294967295); this.sid = null; this.errors = 0; if (this._conn._sessionCachingSupported()) { sessionStorage.removeItem('strophe-bosh-session'); } this._conn.nextValidRid(this.rid); } /** * _Private_ function that initializes the BOSH connection. * Creates and sends the Request that initializes the BOSH connection. * @param 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. * @param 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). * @param route */ _connect(wait, hold, route) { this.wait = wait || this.wait; this.hold = hold || this.hold; this.errors = 0; const 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': NS.BOSH, }); if (route) { body.attrs({ route }); } const _connect_cb = this._conn._connect_cb; this._requests.push(new Request(body.tree(), this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)), Number(body.tree().getAttribute('rid')))); this._throttledRequestHandler(); } /** * 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. * * @param jid - The full JID that is bound by the session. * @param sid - The SID of the BOSH session. * @param rid - The current RID of the BOSH session. This RID * will be used by the next request. * @param callback The connect callback function. * @param 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. * @param 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). * @param wind - The optional HTTBIND window value. This is the * allowed range of request ids that are valid. The default is 5. */ _attach(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 = getDomainFromJid(this._conn.jid); this._conn.authenticated = true; this._conn.connected = true; this.wait = wait || this.wait;