diff --git a/buildtools/check_license_headers.py b/buildtools/check_license_headers.py
index cf6796c454502669b46753022fa0fb1859459163..dc4a74a64f869cce639911549c7afe59c4c2c155 100755
--- a/buildtools/check_license_headers.py
+++ b/buildtools/check_license_headers.py
@@ -79,6 +79,7 @@ PERMITTED_LICENSES = [
"LGPL-2.1-or-later",
"LGPL-3.0-or-later",
"CC0",
+ "MIT",
]
diff --git a/src/extension/plugins/CMakeLists.txt b/src/extension/plugins/CMakeLists.txt
index dc15b4ae4cdb6089b0b934363d1e837ce425a6f0..42b4594fb1dafc73da2ba331016f8d27726357f2 100644
--- a/src/extension/plugins/CMakeLists.txt
+++ b/src/extension/plugins/CMakeLists.txt
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-or-later
add_subdirectory(grid2)
+add_subdirectory(xmpp)
diff --git a/src/extension/plugins/grid2/CMakeLists.txt b/src/extension/plugins/grid2/CMakeLists.txt
index 1d23d8384e4d8e6b1d51ec1d028be5b2e992c740..f6c40ba14b43c5d6d7216a9e2ad2f3370715c97b 100644
--- a/src/extension/plugins/grid2/CMakeLists.txt
+++ b/src/extension/plugins/grid2/CMakeLists.txt
@@ -4,6 +4,8 @@ set(grid_PART_SRCS grid.cpp)
include_directories( ${CMAKE_BINARY_DIR}/src )
add_library(grid2 SHARED EXCLUDE_FROM_ALL ${grid_PART_SRCS})
+install(FILES libgrid2.inx DESTINATION ${INKSCAPE_SHARE_INSTALL}/extensions/)
+install(TARGETS grid2 DESTINATION ${INKSCAPE_SHARE_INSTALL}/extensions/)
target_link_libraries(grid2 inkscape_base)
diff --git a/src/extension/plugins/xmpp/CMakeLists.txt b/src/extension/plugins/xmpp/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9d8e8eb54ddce5beeca5419dcfeb5ced12a0d0f9
--- /dev/null
+++ b/src/extension/plugins/xmpp/CMakeLists.txt
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+find_package(PkgConfig)
+if(PkgConfig_FOUND)
+ pkg_check_modules(Gloox gloox)
+ if(Gloox_FOUND)
+ set(xmpp_PART_SRCS xmpp.cpp xmpp.h sxe.h sxe.cpp)
+ include_directories(${CMAKE_BINARY_DIR}/src)
+ add_library(xmpp SHARED EXCLUDE_FROM_ALL ${xmpp_PART_SRCS})
+ install(FILES libxmpp.inx DESTINATION ${INKSCAPE_SHARE_INSTALL}/extensions/)
+ install(TARGETS xmpp DESTINATION ${INKSCAPE_SHARE_INSTALL}/extensions/)
+
+
+ target_include_directories(xmpp PRIVATE ${Gloox_INCLUDE_DIRS})
+ target_link_libraries(xmpp inkscape_base ${Gloox_LIBRARIES})
+ endif()
+endif()
diff --git a/src/extension/plugins/xmpp/libxmpp.inx b/src/extension/plugins/xmpp/libxmpp.inx
new file mode 100644
index 0000000000000000000000000000000000000000..9bb931dd53050f6029dc7cc7916c5929851098b7
--- /dev/null
+++ b/src/extension/plugins/xmpp/libxmpp.inx
@@ -0,0 +1,21 @@
+
+
+
+ <_name>XMPP
+ org.inkscape.collaboration.xmpp
+ test@xmpp.r2.enst.fr
+ test
+ isape@conf.xmpp.r2.enst.fr
+
+
+
+
+
+ all
+
+
+
+ <_menu-tip>Start a collaborative edition session
+
+
+
diff --git a/src/extension/plugins/xmpp/sxe.cpp b/src/extension/plugins/xmpp/sxe.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c576d9a3d2f9ccbb0cadfbfbeb09afcfc2acf6be
--- /dev/null
+++ b/src/extension/plugins/xmpp/sxe.cpp
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-3.0
+/*
+ Copyright (c) 2019-2023 by Jakob Schröter
+ This file is part of the gloox library. http://camaya.net/gloox
+
+ This software is distributed under a license. The full license
+ agreement can be found in the file LICENSE in this distribution.
+ This software may not be copied, modified, sold or distributed
+ other than expressed in the named license agreement.
+
+ This software is distributed without any warranty.
+*/
+
+#include "sxe.h"
+#include
+
+#include
+#include
+
+namespace gloox
+{
+
+ Sxe::Sxe( const Tag* tag )
+ : StanzaExtension( ExtUser+1 )
+ {
+ if( !tag || tag->name() != "sxe" || tag->xmlns() != XMLNS_SXE )
+ return;
+
+ m_session = tag->findAttribute( "session" );
+ m_id = tag->findAttribute( "id" );
+ const TagList& l = tag->children();
+ for( TagList::const_iterator it = l.begin(); it != l.end(); ++it )
+ {
+ const Tag* child = *it;
+ if( child->xmlns() != XMLNS_SXE )
+ break;
+
+ if( child->name() == "connect" )
+ m_type = SxeConnect;
+ else if( child->name() == "state-offer" )
+ {
+ const TagList& l = child->children();
+ for( TagList::const_iterator it = l.begin(); it != l.end(); ++it )
+ {
+ const Tag* description = *it;
+ if( description->name() != "description" )
+ return;
+ m_state_offer_xmlns.push_back( description->xmlns() );
+ }
+ m_type = SxeStateOffer;
+ }
+ else if( child->name() == "accept-state" )
+ m_type = SxeAcceptState;
+ else if( child->name() == "refuse-state" )
+ m_type = SxeRefuseState;
+ else if( child->name() == "state" )
+ {
+ const TagList& l = child->children();
+ for( TagList::const_iterator it = l.begin(); it != l.end(); ++it )
+ {
+ const Tag* child2 = *it;
+ if( child2->xmlns() != XMLNS_SXE )
+ return;
+
+ StateChange change;
+ if( child2->name() == "document-begin" )
+ {
+ change.type = StateChangeDocumentBegin;
+ change.document_begin.prolog = child2->findAttribute( "prolog" ).c_str();
+ }
+ else if( child2->name() == "document-end" )
+ {
+ change.type = StateChangeDocumentEnd;
+ change.document_end.last_sender = child2->findAttribute( "last-sender" ).c_str();
+ change.document_end.last_id = child2->findAttribute( "last-id" ).c_str();
+ }
+ else if( child2->name() == "new" )
+ {
+ change.type = StateChangeNew;
+ change.new_.rid = child2->findAttribute( "rid" ).c_str();
+ change.new_.type = child2->findAttribute( "type" ).c_str();
+ change.new_.name = child2->findAttribute( "name" ).c_str();
+ change.new_.ns = child2->findAttribute( "ns" ).c_str();
+ change.new_.chdata = child2->findAttribute( "chdata" ).c_str();
+ }
+ else if( child2->name() == "remove" )
+ {
+ change.type = StateChangeRemove;
+ change.remove.target = child2->findAttribute( "target" ).c_str();
+ }
+ else if( child2->name() == "set" )
+ {
+ change.type = StateChangeSet;
+ change.set.target = child2->findAttribute( "target" ).c_str();
+ change.set.version = child2->findAttribute( "version" ).c_str();
+ change.set.name = child2->findAttribute( "name" ).c_str();
+ change.set.ns = child2->findAttribute( "ns" ).c_str();
+ change.set.chdata = child2->findAttribute( "chdata" ).c_str();
+ }
+ else
+ {
+ return;
+ }
+ m_state_changes.push_back( change );
+ }
+ m_type = SxeState;
+ }
+ }
+ }
+
+ Tag* Sxe::tag() const
+ {
+ if ( m_type == SxeInvalid )
+ return 0;
+
+ Tag* t = new Tag( "sxe" );
+ t->setXmlns( XMLNS_SXE );
+ t->addAttribute( "session", m_session );
+ t->addAttribute( "id", m_id );
+
+ if( m_type == SxeConnect )
+ new Tag( t, "connect" );
+ else if( m_type == SxeAcceptState )
+ new Tag( t, "accept-state" );
+ else if( m_type == SxeRefuseState )
+ new Tag( t, "refuse-state" );
+ else if( m_type == SxeStateOffer )
+ {
+ Tag* child = new Tag( t, "state-offer" );
+ child->setXmlns( XMLNS_SXE );
+ for( std::vector::const_iterator it = m_state_offer_xmlns.begin(); it != m_state_offer_xmlns.end(); ++it )
+ {
+ const std::string& xmlns = *it;
+ Tag* description = new Tag( child, "description" );
+ description->setXmlns( xmlns );
+ }
+ }
+ else if( m_type == SxeState )
+ {
+ Tag* state = new Tag( t, "state" );
+ state->setXmlns( XMLNS_SXE );
+ for( std::vector::const_iterator it = m_state_changes.begin(); it != m_state_changes.end(); ++it )
+ {
+ const StateChange& change = *it;
+ Tag* child;
+ if( change.type == StateChangeDocumentBegin )
+ {
+ child = new Tag( state, "document-begin" );
+ child->addAttribute( "prolog", change.document_begin.prolog );
+ }
+ else if( change.type == StateChangeDocumentEnd )
+ {
+ child = new Tag( state, "document-end" );
+ child->addAttribute( "last-sender", change.document_end.last_sender );
+ child->addAttribute( "last-id", change.document_end.last_id );
+ }
+ else if( change.type == StateChangeNew )
+ {
+ child = new Tag( state, "new" );
+ child->addAttribute( "rid", change.new_.rid );
+ child->addAttribute( "type", change.new_.type );
+ child->addAttribute( "name", change.new_.name );
+ child->addAttribute( "ns", change.new_.ns );
+ child->addAttribute( "chdata", change.new_.chdata );
+ }
+ else if( change.type == StateChangeRemove )
+ {
+ child = new Tag( state, "remove" );
+ child->addAttribute( "target", change.remove.target );
+ }
+ else if( change.type == StateChangeSet )
+ {
+ child = new Tag( state, "set" );
+ child->addAttribute( "target", change.set.target );
+ child->addAttribute( "version", change.set.version );
+ child->addAttribute( "name", change.set.name );
+ child->addAttribute( "ns", change.set.ns );
+ child->addAttribute( "chdata", change.set.chdata );
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ return t;
+ }
+
+ const std::string& Sxe::filterString() const
+ {
+ static const std::string filter = "/message/sxe[@xmlns='" + XMLNS_SXE + "']";
+ return filter;
+ }
+
+}
diff --git a/src/extension/plugins/xmpp/sxe.h b/src/extension/plugins/xmpp/sxe.h
new file mode 100644
index 0000000000000000000000000000000000000000..c78161b9ba9eefcae1361797fcec797ef977b20b
--- /dev/null
+++ b/src/extension/plugins/xmpp/sxe.h
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-3.0
+/*
+ Copyright (c) 2019-2024 by Jakob Schröter
+ Copyright (c) 2019-2024 by Emmanuel Gil Peyrot
+ Copyright (c) 2024 by Marc Jeanmougin
+ This file is part of the gloox library. http://camaya.net/gloox
+
+ This software is distributed under a license. The full license
+ agreement can be found in the file LICENSE in this distribution.
+ This software may not be copied, modified, sold or distributed
+ other than expressed in the named license agreement.
+
+ This software is distributed without any warranty.
+*/
+
+#ifndef SXE_H__
+#define SXE_H__
+
+#include
+
+#include
+#include
+#include
+const std::string XMLNS_SXE = "urn:xmpp:sxe:0";
+
+namespace gloox
+{
+
+ /**
+ * @brief An implementation/abstraction of Shared XML Editing (SXE, @xep{0284})
+ *
+ * XEP Version: 0.1.1
+ *
+ * @author Emmanuel Gil Peyrot
+ * @since 1.0.23
+ */
+ class GLOOX_API Sxe : public StanzaExtension
+ {
+ public:
+
+ enum SxeType
+ {
+ SxeInvalid,
+ SxeConnect,
+ SxeStateOffer,
+ SxeAcceptState,
+ SxeRefuseState,
+ SxeState
+ };
+
+ enum StateChangeType
+ {
+ StateChangeDocumentBegin,
+ StateChangeDocumentEnd,
+ StateChangeNew,
+ StateChangeRemove,
+ StateChangeSet
+ };
+
+ struct DocumentBegin
+ {
+ const char* prolog;
+ };
+
+ struct DocumentEnd
+ {
+ const char* last_sender;
+ const char* last_id;
+ };
+
+ struct New
+ {
+ const char* rid;
+ const char* type;
+ const char* name;
+ const char* ns;
+ const char* parent;
+ const char* chdata;
+ };
+
+ struct Remove
+ {
+ const char* target;
+ };
+
+ struct Set
+ {
+ const char* target;
+ const char* version;
+ const char* parent;
+ const char* name;
+ const char* ns;
+ const char* chdata;
+ };
+
+ struct StateChange
+ {
+ StateChangeType type;
+ union
+ {
+ DocumentBegin document_begin;
+ DocumentEnd document_end;
+ New new_;
+ Remove remove;
+ Set set;
+ };
+ };
+
+ /**
+ * Constructs a new object of the given type.
+ * @param state_changes The changes.
+ */
+ Sxe( std::string session, std::string id, SxeType type, std::vector state_offer_xmlns, std::vector state_changes )
+ : StanzaExtension(ExtUser+1), m_session(session), m_id(id), m_type(type), m_state_offer_xmlns(state_offer_xmlns), m_state_changes(state_changes) {};
+
+ /**
+ * Creates a new SXE object from the given Tag.
+ * @param tag The Tag to parse.
+ */
+ Sxe( const Tag* tag = 0 );
+
+ /**
+ * Virtual destructor.
+ */
+ virtual ~Sxe() {}
+
+ /**
+ * Returns a Tag representing a SXE extension.
+ * @return A Tag representing a SXE extension.
+ */
+ virtual Tag* tag() const;
+
+ /**
+ * Returns a new instance of SXE.
+ * @return The new SXE instance.
+ */
+ virtual StanzaExtension* newInstance( const Tag* tag ) const
+ {
+ return new Sxe( tag );
+ }
+
+ /**
+ * Returns an identical copy of the current SXE.
+ * @return an identical copy of the current SXE.
+ */
+ virtual StanzaExtension* clone() const
+ {
+ return new Sxe( *this );
+ }
+
+ /**
+ * Returns an XPath expression that describes a path to the SXE element.
+ * @return The SXE filter string.
+ */
+ virtual const std::string& filterString() const;
+
+ private:
+ std::string m_session;
+ std::string m_id;
+ SxeType m_type;
+ std::vector m_state_offer_xmlns;
+ std::vector m_state_changes;
+
+ };
+
+}
+
+#endif // SXE_H__
diff --git a/src/extension/plugins/xmpp/web/index.xhtml b/src/extension/plugins/xmpp/web/index.xhtml
new file mode 100644
index 0000000000000000000000000000000000000000..b694abf1bb33c042af4d7a8d54cde85e17af4a7f
--- /dev/null
+++ b/src/extension/plugins/xmpp/web/index.xhtml
@@ -0,0 +1,31 @@
+
+
+
+
+ SVG viewer
+
+
+
+
SVG viewer
+
+
+
+
+
+
+
JID
+
+
+
+
+
+
+
+
+
+
diff --git a/src/extension/plugins/xmpp/web/spinner.svg b/src/extension/plugins/xmpp/web/spinner.svg
new file mode 100644
index 0000000000000000000000000000000000000000..3fbc8b86ea96569b1f98a0eace6aeab8301691f2
--- /dev/null
+++ b/src/extension/plugins/xmpp/web/spinner.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/src/extension/plugins/xmpp/web/strophe.js b/src/extension/plugins/xmpp/web/strophe.js
new file mode 100644
index 0000000000000000000000000000000000000000..2cf74faa4ecc242c5ba82692fa2a856d8503f632
--- /dev/null
+++ b/src/extension/plugins/xmpp/web/strophe.js
@@ -0,0 +1,6102 @@
+// SPDX-License-Identifier: MIT
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory();
+ else if(typeof define === 'function' && define.amd)
+ define([], factory);
+ else if(typeof exports === 'object')
+ exports["strophe"] = factory();
+ else
+ root["strophe"] = factory();
+})(window, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ }
+/******/ };
+/******/
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = function(exports) {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/
+/******/ // create a fake namespace object
+/******/ // mode & 1: value is a module id, require it
+/******/ // mode & 2: merge all properties of value into the ns
+/******/ // mode & 4: return value when already ns object
+/******/ // mode & 8|1: behave like require
+/******/ __webpack_require__.t = function(value, mode) {
+/******/ if(mode & 1) value = __webpack_require__(value);
+/******/ if(mode & 8) return value;
+/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ var ns = Object.create(null);
+/******/ __webpack_require__.r(ns);
+/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ return ns;
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "./src/strophe.js");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "./node_modules/webpack/buildin/global.js":
+/*!***********************************!*\
+ !*** (webpack)/buildin/global.js ***!
+ \***********************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+var g;
+
+// This works in non-strict mode
+g = (function() {
+ return this;
+})();
+
+try {
+ // This works if eval is allowed (see CSP)
+ g = g || Function("return this")() || (1, eval)("this");
+} catch (e) {
+ // This works if the window reference is available
+ if (typeof window === "object") g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
+
+
+/***/ }),
+
+/***/ "./src/bosh.js":
+/*!*********************!*\
+ !*** ./src/bosh.js ***!
+ \*********************/
+/*! no exports provided */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core */ "./src/core.js");
+/*
+ This program is distributed under the terms of the MIT license.
+ Please see the LICENSE file for details.
+
+ Copyright 2006-2008, OGG, LLC
+*/
+
+/* global window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject */
+
+var Strophe = core__WEBPACK_IMPORTED_MODULE_0__["default"].Strophe;
+var $build = core__WEBPACK_IMPORTED_MODULE_0__["default"].$build;
+/** PrivateClass: Strophe.Request
+ * _Private_ helper class that provides a cross implementation abstraction
+ * for a BOSH related XMLHttpRequest.
+ *
+ * The Strophe.Request class is used internally to encapsulate BOSH request
+ * information. It is not meant to be used from user's code.
+ */
+
+/** PrivateConstructor: Strophe.Request
+ * Create and initialize a new Strophe.Request object.
+ *
+ * Parameters:
+ * (XMLElement) elem - The XML data to be sent in the request.
+ * (Function) func - The function that will be called when the
+ * XMLHttpRequest readyState changes.
+ * (Integer) rid - The BOSH rid attribute associated with this request.
+ * (Integer) sends - The number of times this same request has been sent.
+ */
+
+Strophe.Request = function (elem, func, rid, sends) {
+ this.id = ++Strophe._requestId;
+ this.xmlData = elem;
+ this.data = Strophe.serialize(elem); // save original function in case we need to make a new request
+ // from this one.
+
+ this.origFunc = func;
+ this.func = func;
+ this.rid = rid;
+ this.date = NaN;
+ this.sends = sends || 0;
+ this.abort = false;
+ this.dead = null;
+
+ this.age = function () {
+ if (!this.date) {
+ return 0;
+ }
+
+ var now = new Date();
+ return (now - this.date) / 1000;
+ };
+
+ this.timeDead = function () {
+ if (!this.dead) {
+ return 0;
+ }
+
+ var now = new Date();
+ return (now - this.dead) / 1000;
+ };
+
+ this.xhr = this._newXHR();
+};
+
+Strophe.Request.prototype = {
+ /** PrivateFunction: getResponse
+ * Get a response from the underlying XMLHttpRequest.
+ *
+ * This function attempts to get a response from the request and checks
+ * for errors.
+ *
+ * Throws:
+ * "parsererror" - A parser error occured.
+ * "bad-format" - The entity has sent XML that cannot be processed.
+ *
+ * Returns:
+ * The DOM element tree of the response.
+ */
+ getResponse: function getResponse() {
+ var node = null;
+
+ if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
+ node = this.xhr.responseXML.documentElement;
+
+ if (node.tagName === "parsererror") {
+ Strophe.error("invalid response received");
+ Strophe.error("responseText: " + this.xhr.responseText);
+ Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML));
+ throw new Error("parsererror");
+ }
+ } else if (this.xhr.responseText) {
+ // In React Native, we may get responseText but no responseXML. We can try to parse it manually.
+ Strophe.debug("Got responseText but no responseXML; attempting to parse it with DOMParser...");
+ node = new DOMParser().parseFromString(this.xhr.responseText, 'application/xml').documentElement;
+
+ if (!node) {
+ throw new Error('Parsing produced null node');
+ } else if (node.querySelector('parsererror')) {
+ Strophe.error("invalid response received: " + node.querySelector('parsererror').textContent);
+ Strophe.error("responseText: " + this.xhr.responseText);
+ var error = new Error();
+ error.name = Strophe.ErrorCondition.BAD_FORMAT;
+ throw error;
+ }
+ }
+
+ return node;
+ },
+
+ /** PrivateFunction: _newXHR
+ * _Private_ helper function to create XMLHttpRequests.
+ *
+ * This function creates XMLHttpRequests across all implementations.
+ *
+ * Returns:
+ * A new XMLHttpRequest.
+ */
+ _newXHR: function _newXHR() {
+ var xhr = null;
+
+ if (window.XMLHttpRequest) {
+ xhr = new XMLHttpRequest();
+
+ if (xhr.overrideMimeType) {
+ xhr.overrideMimeType("text/xml; charset=utf-8");
+ }
+ } else if (window.ActiveXObject) {
+ xhr = new ActiveXObject("Microsoft.XMLHTTP");
+ } // use Function.bind() to prepend ourselves as an argument
+
+
+ xhr.onreadystatechange = this.func.bind(null, this);
+ return xhr;
+ }
+};
+/** Class: Strophe.Bosh
+ * _Private_ helper class that handles BOSH Connections
+ *
+ * The Strophe.Bosh class is used internally by Strophe.Connection
+ * to encapsulate BOSH sessions. It is not meant to be used from user's code.
+ */
+
+/** File: bosh.js
+ * A JavaScript library to enable BOSH in Strophejs.
+ *
+ * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH)
+ * to emulate a persistent, stateful, two-way connection to an XMPP server.
+ * More information on BOSH can be found in XEP 124.
+ */
+
+/** PrivateConstructor: Strophe.Bosh
+ * Create and initialize a Strophe.Bosh object.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH.
+ *
+ * Returns:
+ * A new Strophe.Bosh object.
+ */
+
+Strophe.Bosh = function (connection) {
+ this._conn = connection;
+ /* request id for body tags */
+
+ this.rid = Math.floor(Math.random() * 4294967295);
+ /* The current session ID. */
+
+ this.sid = null; // default BOSH values
+
+ this.hold = 1;
+ this.wait = 60;
+ this.window = 5;
+ this.errors = 0;
+ this.inactivity = null;
+ this.lastResponseHeaders = null;
+ this._requests = [];
+};
+
+Strophe.Bosh.prototype = {
+ /** Variable: strip
+ *
+ * BOSH-Connections will have all stanzas wrapped in a tag when
+ * passed to or .
+ * To strip this tag, User code can set to "body":
+ *
+ * > Strophe.Bosh.prototype.strip = "body";
+ *
+ * This will enable stripping of the body tag in both
+ * and .
+ */
+ strip: null,
+
+ /** PrivateFunction: _buildBody
+ * _Private_ helper function to generate the wrapper for BOSH.
+ *
+ * Returns:
+ * A Strophe.Builder with a element.
+ */
+ _buildBody: function _buildBody() {
+ var bodyWrap = $build('body', {
+ 'rid': this.rid++,
+ 'xmlns': Strophe.NS.HTTPBIND
+ });
+
+ if (this.sid !== null) {
+ bodyWrap.attrs({
+ 'sid': this.sid
+ });
+ }
+
+ if (this._conn.options.keepalive && this._conn._sessionCachingSupported()) {
+ this._cacheSession();
+ }
+
+ return bodyWrap;
+ },
+
+ /** PrivateFunction: _reset
+ * Reset the connection.
+ *
+ * This function is called by the reset function of the Strophe Connection
+ */
+ _reset: function _reset() {
+ this.rid = Math.floor(Math.random() * 4294967295);
+ this.sid = null;
+ this.errors = 0;
+
+ if (this._conn._sessionCachingSupported()) {
+ window.sessionStorage.removeItem('strophe-bosh-session');
+ }
+
+ this._conn.nextValidRid(this.rid);
+ },
+
+ /** PrivateFunction: _connect
+ * _Private_ function that initializes the BOSH connection.
+ *
+ * Creates and sends the Request that initializes the BOSH connection.
+ */
+ _connect: function _connect(wait, hold, route) {
+ this.wait = wait || this.wait;
+ this.hold = hold || this.hold;
+ this.errors = 0;
+
+ var body = this._buildBody().attrs({
+ "to": this._conn.domain,
+ "xml:lang": "en",
+ "wait": this.wait,
+ "hold": this.hold,
+ "content": "text/xml; charset=utf-8",
+ "ver": "1.6",
+ "xmpp:version": "1.0",
+ "xmlns:xmpp": Strophe.NS.BOSH
+ });
+
+ if (route) {
+ body.attrs({
+ 'route': route
+ });
+ }
+
+ var _connect_cb = this._conn._connect_cb;
+
+ this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)), body.tree().getAttribute("rid")));
+
+ this._throttledRequestHandler();
+ },
+
+ /** PrivateFunction: _attach
+ * Attach to an already created and authenticated BOSH session.
+ *
+ * This function is provided to allow Strophe to attach to BOSH
+ * sessions which have been created externally, perhaps by a Web
+ * application. This is often used to support auto-login type features
+ * without putting user credentials into the page.
+ *
+ * Parameters:
+ * (String) jid - The full JID that is bound by the session.
+ * (String) sid - The SID of the BOSH session.
+ * (String) rid - The current RID of the BOSH session. This RID
+ * will be used by the next request.
+ * (Function) callback The connect callback function.
+ * (Integer) wait - The optional HTTPBIND wait value. This is the
+ * time the server will wait before returning an empty result for
+ * a request. The default setting of 60 seconds is recommended.
+ * Other settings will require tweaks to the Strophe.TIMEOUT value.
+ * (Integer) hold - The optional HTTPBIND hold value. This is the
+ * number of connections the server will hold at one time. This
+ * should almost always be set to 1 (the default).
+ * (Integer) wind - The optional HTTBIND window value. This is the
+ * allowed range of request ids that are valid. The default is 5.
+ */
+ _attach: function _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 = Strophe.getDomainFromJid(this._conn.jid);
+ this._conn.authenticated = true;
+ this._conn.connected = true;
+ this.wait = wait || this.wait;
+ this.hold = hold || this.hold;
+ this.window = wind || this.window;
+
+ this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null);
+ },
+
+ /** PrivateFunction: _restore
+ * Attempt to restore a cached BOSH session
+ *
+ * Parameters:
+ * (String) jid - The full JID that is bound by the session.
+ * This parameter is optional but recommended, specifically in cases
+ * where prebinded BOSH sessions are used where it's important to know
+ * that the right session is being restored.
+ * (Function) callback The connect callback function.
+ * (Integer) wait - The optional HTTPBIND wait value. This is the
+ * time the server will wait before returning an empty result for
+ * a request. The default setting of 60 seconds is recommended.
+ * Other settings will require tweaks to the Strophe.TIMEOUT value.
+ * (Integer) hold - The optional HTTPBIND hold value. This is the
+ * number of connections the server will hold at one time. This
+ * should almost always be set to 1 (the default).
+ * (Integer) wind - The optional HTTBIND window value. This is the
+ * allowed range of request ids that are valid. The default is 5.
+ */
+ _restore: function _restore(jid, callback, wait, hold, wind) {
+ var session = JSON.parse(window.sessionStorage.getItem('strophe-bosh-session'));
+
+ if (typeof session !== "undefined" && session !== null && session.rid && session.sid && session.jid && (typeof jid === "undefined" || jid === null || Strophe.getBareJidFromJid(session.jid) === Strophe.getBareJidFromJid(jid) || // If authcid is null, then it's an anonymous login, so
+ // we compare only the domains:
+ Strophe.getNodeFromJid(jid) === null && Strophe.getDomainFromJid(session.jid) === jid)) {
+ this._conn.restored = true;
+
+ this._attach(session.jid, session.sid, session.rid, callback, wait, hold, wind);
+ } else {
+ var error = new Error("_restore: no restoreable session.");
+ error.name = "StropheSessionError";
+ throw error;
+ }
+ },
+
+ /** PrivateFunction: _cacheSession
+ * _Private_ handler for the beforeunload event.
+ *
+ * This handler is used to process the Bosh-part of the initial request.
+ * Parameters:
+ * (Strophe.Request) bodyWrap - The received stanza.
+ */
+ _cacheSession: function _cacheSession() {
+ if (this._conn.authenticated) {
+ if (this._conn.jid && this.rid && this.sid) {
+ window.sessionStorage.setItem('strophe-bosh-session', JSON.stringify({
+ 'jid': this._conn.jid,
+ 'rid': this.rid,
+ 'sid': this.sid
+ }));
+ }
+ } else {
+ window.sessionStorage.removeItem('strophe-bosh-session');
+ }
+ },
+
+ /** PrivateFunction: _connect_cb
+ * _Private_ handler for initial connection request.
+ *
+ * This handler is used to process the Bosh-part of the initial request.
+ * Parameters:
+ * (Strophe.Request) bodyWrap - The received stanza.
+ */
+ _connect_cb: function _connect_cb(bodyWrap) {
+ var typ = bodyWrap.getAttribute("type");
+
+ if (typ !== null && typ === "terminate") {
+ // an error occurred
+ var cond = bodyWrap.getAttribute("condition");
+ Strophe.error("BOSH-Connection failed: " + cond);
+ var conflict = bodyWrap.getElementsByTagName("conflict");
+
+ if (cond !== null) {
+ if (cond === "remote-stream-error" && conflict.length > 0) {
+ cond = "conflict";
+ }
+
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
+ } else {
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
+ }
+
+ this._conn._doDisconnect(cond);
+
+ return Strophe.Status.CONNFAIL;
+ } // check to make sure we don't overwrite these if _connect_cb is
+ // called multiple times in the case of missing stream:features
+
+
+ if (!this.sid) {
+ this.sid = bodyWrap.getAttribute("sid");
+ }
+
+ var wind = bodyWrap.getAttribute('requests');
+
+ if (wind) {
+ this.window = parseInt(wind, 10);
+ }
+
+ var hold = bodyWrap.getAttribute('hold');
+
+ if (hold) {
+ this.hold = parseInt(hold, 10);
+ }
+
+ var wait = bodyWrap.getAttribute('wait');
+
+ if (wait) {
+ this.wait = parseInt(wait, 10);
+ }
+
+ var inactivity = bodyWrap.getAttribute('inactivity');
+
+ if (inactivity) {
+ this.inactivity = parseInt(inactivity, 10);
+ }
+ },
+
+ /** PrivateFunction: _disconnect
+ * _Private_ part of Connection.disconnect for Bosh
+ *
+ * Parameters:
+ * (Request) pres - This stanza will be sent before disconnecting.
+ */
+ _disconnect: function _disconnect(pres) {
+ this._sendTerminate(pres);
+ },
+
+ /** PrivateFunction: _doDisconnect
+ * _Private_ function to disconnect.
+ *
+ * Resets the SID and RID.
+ */
+ _doDisconnect: function _doDisconnect() {
+ this.sid = null;
+ this.rid = Math.floor(Math.random() * 4294967295);
+
+ if (this._conn._sessionCachingSupported()) {
+ window.sessionStorage.removeItem('strophe-bosh-session');
+ }
+
+ this._conn.nextValidRid(this.rid);
+ },
+
+ /** PrivateFunction: _emptyQueue
+ * _Private_ function to check if the Request queue is empty.
+ *
+ * Returns:
+ * True, if there are no Requests queued, False otherwise.
+ */
+ _emptyQueue: function _emptyQueue() {
+ return this._requests.length === 0;
+ },
+
+ /** PrivateFunction: _callProtocolErrorHandlers
+ * _Private_ function to call error handlers registered for HTTP errors.
+ *
+ * Parameters:
+ * (Strophe.Request) req - The request that is changing readyState.
+ */
+ _callProtocolErrorHandlers: function _callProtocolErrorHandlers(req) {
+ var reqStatus = this._getRequestStatus(req);
+
+ var err_callback = this._conn.protocolErrorHandlers.HTTP[reqStatus];
+
+ if (err_callback) {
+ err_callback.call(this, reqStatus);
+ }
+ },
+
+ /** PrivateFunction: _hitError
+ * _Private_ function to handle the error count.
+ *
+ * Requests are resent automatically until their error count reaches
+ * 5. Each time an error is encountered, this function is called to
+ * increment the count and disconnect if the count is too high.
+ *
+ * Parameters:
+ * (Integer) reqStatus - The request status.
+ */
+ _hitError: function _hitError(reqStatus) {
+ this.errors++;
+ Strophe.warn("request errored, status: " + reqStatus + ", number of errors: " + this.errors);
+
+ if (this.errors > 4) {
+ this._conn._onDisconnectTimeout();
+ }
+ },
+
+ /** PrivateFunction: _no_auth_received
+ *
+ * Called on stream start/restart when no stream:features
+ * has been received and sends a blank poll request.
+ */
+ _no_auth_received: function _no_auth_received(callback) {
+ Strophe.warn("Server did not yet offer a supported authentication " + "mechanism. Sending a blank poll request.");
+
+ if (callback) {
+ callback = callback.bind(this._conn);
+ } else {
+ callback = this._conn._connect_cb.bind(this._conn);
+ }
+
+ var body = this._buildBody();
+
+ this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, callback), body.tree().getAttribute("rid")));
+
+ this._throttledRequestHandler();
+ },
+
+ /** PrivateFunction: _onDisconnectTimeout
+ * _Private_ timeout handler for handling non-graceful disconnection.
+ *
+ * Cancels all remaining Requests and clears the queue.
+ */
+ _onDisconnectTimeout: function _onDisconnectTimeout() {
+ this._abortAllRequests();
+ },
+
+ /** PrivateFunction: _abortAllRequests
+ * _Private_ helper function that makes sure all pending requests are aborted.
+ */
+ _abortAllRequests: function _abortAllRequests() {
+ while (this._requests.length > 0) {
+ var req = this._requests.pop();
+
+ req.abort = true;
+ req.xhr.abort(); // jslint complains, but this is fine. setting to empty func
+ // is necessary for IE6
+
+ req.xhr.onreadystatechange = function () {}; // jshint ignore:line
+
+ }
+ },
+
+ /** PrivateFunction: _onIdle
+ * _Private_ handler called by Strophe.Connection._onIdle
+ *
+ * Sends all queued Requests or polls with empty Request if there are none.
+ */
+ _onIdle: function _onIdle() {
+ var data = this._conn._data; // if no requests are in progress, poll
+
+ if (this._conn.authenticated && this._requests.length === 0 && data.length === 0 && !this._conn.disconnecting) {
+ Strophe.info("no requests during idle cycle, sending " + "blank request");
+ data.push(null);
+ }
+
+ if (this._conn.paused) {
+ return;
+ }
+
+ if (this._requests.length < 2 && data.length > 0) {
+ var body = this._buildBody();
+
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] !== null) {
+ if (data[i] === "restart") {
+ body.attrs({
+ "to": this._conn.domain,
+ "xml:lang": "en",
+ "xmpp:restart": "true",
+ "xmlns:xmpp": Strophe.NS.BOSH
+ });
+ } else {
+ body.cnode(data[i]).up();
+ }
+ }
+ }
+
+ delete this._conn._data;
+ this._conn._data = [];
+
+ this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid")));
+
+ this._throttledRequestHandler();
+ }
+
+ if (this._requests.length > 0) {
+ var time_elapsed = this._requests[0].age();
+
+ if (this._requests[0].dead !== null) {
+ if (this._requests[0].timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
+ this._throttledRequestHandler();
+ }
+ }
+
+ if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
+ Strophe.warn("Request " + this._requests[0].id + " timed out, over " + Math.floor(Strophe.TIMEOUT * this.wait) + " seconds since last activity");
+
+ this._throttledRequestHandler();
+ }
+ }
+ },
+
+ /** PrivateFunction: _getRequestStatus
+ *
+ * Returns the HTTP status code from a Strophe.Request
+ *
+ * Parameters:
+ * (Strophe.Request) req - The Strophe.Request instance.
+ * (Integer) def - The default value that should be returned if no
+ * status value was found.
+ */
+ _getRequestStatus: function _getRequestStatus(req, def) {
+ var reqStatus;
+
+ if (req.xhr.readyState === 4) {
+ try {
+ reqStatus = req.xhr.status;
+ } catch (e) {
+ // ignore errors from undefined status attribute. Works
+ // around a browser bug
+ Strophe.error("Caught an error while retrieving a request's status, " + "reqStatus: " + reqStatus);
+ }
+ }
+
+ if (typeof reqStatus === "undefined") {
+ reqStatus = typeof def === 'number' ? def : 0;
+ }
+
+ return reqStatus;
+ },
+
+ /** PrivateFunction: _onRequestStateChange
+ * _Private_ handler for Strophe.Request state changes.
+ *
+ * This function is called when the XMLHttpRequest readyState changes.
+ * It contains a lot of error handling logic for the many ways that
+ * requests can fail, and calls the request callback when requests
+ * succeed.
+ *
+ * Parameters:
+ * (Function) func - The handler for the request.
+ * (Strophe.Request) req - The request that is changing readyState.
+ */
+ _onRequestStateChange: function _onRequestStateChange(func, req) {
+ Strophe.debug("request id " + req.id + "." + req.sends + " state changed to " + req.xhr.readyState);
+
+ if (req.abort) {
+ req.abort = false;
+ return;
+ }
+
+ if (req.xhr.readyState !== 4) {
+ // The request is not yet complete
+ return;
+ }
+
+ var reqStatus = this._getRequestStatus(req);
+
+ this.lastResponseHeaders = req.xhr.getAllResponseHeaders();
+
+ if (this.disconnecting && reqStatus >= 400) {
+ this._hitError(reqStatus);
+
+ this._callProtocolErrorHandlers(req);
+
+ return;
+ }
+
+ var valid_request = reqStatus > 0 && reqStatus < 500;
+ var too_many_retries = req.sends > this._conn.maxRetries;
+
+ if (valid_request || too_many_retries) {
+ // remove from internal queue
+ this._removeRequest(req);
+
+ Strophe.debug("request id " + req.id + " should now be removed");
+ }
+
+ if (reqStatus === 200) {
+ // request succeeded
+ var reqIs0 = this._requests[0] === req;
+ var reqIs1 = this._requests[1] === req; // if request 1 finished, or request 0 finished and request
+ // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
+ // restart the other - both will be in the first spot, as the
+ // completed request has been removed from the queue already
+
+ if (reqIs1 || reqIs0 && this._requests.length > 0 && this._requests[0].age() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {
+ this._restartRequest(0);
+ }
+
+ this._conn.nextValidRid(Number(req.rid) + 1);
+
+ Strophe.debug("request id " + req.id + "." + req.sends + " got 200");
+ func(req); // call handler
+
+ this.errors = 0;
+ } else if (reqStatus === 0 || reqStatus >= 400 && reqStatus < 600 || reqStatus >= 12000) {
+ // request failed
+ Strophe.error("request id " + req.id + "." + req.sends + " error " + reqStatus + " happened");
+
+ this._hitError(reqStatus);
+
+ this._callProtocolErrorHandlers(req);
+
+ if (reqStatus >= 400 && reqStatus < 500) {
+ this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null);
+
+ this._conn._doDisconnect();
+ }
+ } else {
+ Strophe.error("request id " + req.id + "." + req.sends + " error " + reqStatus + " happened");
+ }
+
+ if (!valid_request && !too_many_retries) {
+ this._throttledRequestHandler();
+ } else if (too_many_retries && !this._conn.connected) {
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "giving-up");
+ }
+ },
+
+ /** PrivateFunction: _processRequest
+ * _Private_ function to process a request in the queue.
+ *
+ * This function takes requests off the queue and sends them and
+ * restarts dead requests.
+ *
+ * Parameters:
+ * (Integer) i - The index of the request in the queue.
+ */
+ _processRequest: function _processRequest(i) {
+ var _this = this;
+
+ var req = this._requests[i];
+
+ var reqStatus = this._getRequestStatus(req, -1); // make sure we limit the number of retries
+
+
+ if (req.sends > this._conn.maxRetries) {
+ this._conn._onDisconnectTimeout();
+
+ return;
+ }
+
+ var time_elapsed = req.age();
+ var primary_timeout = !isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait);
+ var secondary_timeout = req.dead !== null && req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait);
+ var server_error = req.xhr.readyState === 4 && (reqStatus < 1 || reqStatus >= 500);
+
+ if (primary_timeout || secondary_timeout || server_error) {
+ if (secondary_timeout) {
+ Strophe.error("Request ".concat(this._requests[i].id, " timed out (secondary), restarting"));
+ }
+
+ req.abort = true;
+ req.xhr.abort(); // setting to null fails on IE6, so set to empty function
+
+ req.xhr.onreadystatechange = function () {};
+
+ this._requests[i] = new Strophe.Request(req.xmlData, req.origFunc, req.rid, req.sends);
+ req = this._requests[i];
+ }
+
+ if (req.xhr.readyState === 0) {
+ Strophe.debug("request id " + req.id + "." + req.sends + " posting");
+
+ try {
+ var content_type = this._conn.options.contentType || "text/xml; charset=utf-8";
+ req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true);
+
+ if (typeof req.xhr.setRequestHeader !== 'undefined') {
+ // IE9 doesn't have setRequestHeader
+ req.xhr.setRequestHeader("Content-Type", content_type);
+ }
+
+ if (this._conn.options.withCredentials) {
+ req.xhr.withCredentials = true;
+ }
+ } catch (e2) {
+ Strophe.error("XHR open failed: " + e2.toString());
+
+ if (!this._conn.connected) {
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "bad-service");
+ }
+
+ this._conn.disconnect();
+
+ return;
+ } // Fires the XHR request -- may be invoked immediately
+ // or on a gradually expanding retry window for reconnects
+
+
+ var sendFunc = function sendFunc() {
+ req.date = new Date();
+
+ if (_this._conn.options.customHeaders) {
+ var headers = _this._conn.options.customHeaders;
+
+ for (var header in headers) {
+ if (Object.prototype.hasOwnProperty.call(headers, header)) {
+ req.xhr.setRequestHeader(header, headers[header]);
+ }
+ }
+ }
+
+ req.xhr.send(req.data);
+ }; // Implement progressive backoff for reconnects --
+ // First retry (send === 1) should also be instantaneous
+
+
+ if (req.sends > 1) {
+ // Using a cube of the retry number creates a nicely
+ // expanding retry window
+ var backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), Math.pow(req.sends, 3)) * 1000;
+ setTimeout(function () {
+ // XXX: setTimeout should be called only with function expressions (23974bc1)
+ sendFunc();
+ }, backoff);
+ } else {
+ sendFunc();
+ }
+
+ req.sends++;
+
+ if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) {
+ if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) {
+ this._conn.xmlOutput(req.xmlData.childNodes[0]);
+ } else {
+ this._conn.xmlOutput(req.xmlData);
+ }
+ }
+
+ if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) {
+ this._conn.rawOutput(req.data);
+ }
+ } else {
+ Strophe.debug("_processRequest: " + (i === 0 ? "first" : "second") + " request has readyState of " + req.xhr.readyState);
+ }
+ },
+
+ /** PrivateFunction: _removeRequest
+ * _Private_ function to remove a request from the queue.
+ *
+ * Parameters:
+ * (Strophe.Request) req - The request to remove.
+ */
+ _removeRequest: function _removeRequest(req) {
+ Strophe.debug("removing request");
+
+ for (var i = this._requests.length - 1; i >= 0; i--) {
+ if (req === this._requests[i]) {
+ this._requests.splice(i, 1);
+ }
+ } // IE6 fails on setting to null, so set to empty function
+
+
+ req.xhr.onreadystatechange = function () {};
+
+ this._throttledRequestHandler();
+ },
+
+ /** PrivateFunction: _restartRequest
+ * _Private_ function to restart a request that is presumed dead.
+ *
+ * Parameters:
+ * (Integer) i - The index of the request in the queue.
+ */
+ _restartRequest: function _restartRequest(i) {
+ var req = this._requests[i];
+
+ if (req.dead === null) {
+ req.dead = new Date();
+ }
+
+ this._processRequest(i);
+ },
+
+ /** PrivateFunction: _reqToData
+ * _Private_ function to get a stanza out of a request.
+ *
+ * Tries to extract a stanza out of a Request Object.
+ * When this fails the current connection will be disconnected.
+ *
+ * Parameters:
+ * (Object) req - The Request.
+ *
+ * Returns:
+ * The stanza that was passed.
+ */
+ _reqToData: function _reqToData(req) {
+ try {
+ return req.getResponse();
+ } catch (e) {
+ if (e.message !== "parsererror") {
+ throw e;
+ }
+
+ this._conn.disconnect("strophe-parsererror");
+ }
+ },
+
+ /** PrivateFunction: _sendTerminate
+ * _Private_ function to send initial disconnect sequence.
+ *
+ * This is the first step in a graceful disconnect. It sends
+ * the BOSH server a terminate body and includes an unavailable
+ * presence if authentication has completed.
+ */
+ _sendTerminate: function _sendTerminate(pres) {
+ Strophe.info("_sendTerminate was called");
+
+ var body = this._buildBody().attrs({
+ type: "terminate"
+ });
+
+ if (pres) {
+ body.cnode(pres.tree());
+ }
+
+ var req = new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute("rid"));
+
+ this._requests.push(req);
+
+ this._throttledRequestHandler();
+ },
+
+ /** PrivateFunction: _send
+ * _Private_ part of the Connection.send function for BOSH
+ *
+ * Just triggers the RequestHandler to send the messages that are in the queue
+ */
+ _send: function _send() {
+ var _this2 = this;
+
+ clearTimeout(this._conn._idleTimeout);
+
+ this._throttledRequestHandler();
+
+ this._conn._idleTimeout = setTimeout(function () {
+ return _this2._conn._onIdle();
+ }, 100);
+ },
+
+ /** PrivateFunction: _sendRestart
+ *
+ * Send an xmpp:restart stanza.
+ */
+ _sendRestart: function _sendRestart() {
+ this._throttledRequestHandler();
+
+ clearTimeout(this._conn._idleTimeout);
+ },
+
+ /** PrivateFunction: _throttledRequestHandler
+ * _Private_ function to throttle requests to the connection window.
+ *
+ * This function makes sure we don't send requests so fast that the
+ * request ids overflow the connection window in the case that one
+ * request died.
+ */
+ _throttledRequestHandler: function _throttledRequestHandler() {
+ if (!this._requests) {
+ Strophe.debug("_throttledRequestHandler called with " + "undefined requests");
+ } else {
+ Strophe.debug("_throttledRequestHandler called with " + this._requests.length + " requests");
+ }
+
+ if (!this._requests || this._requests.length === 0) {
+ return;
+ }
+
+ if (this._requests.length > 0) {
+ this._processRequest(0);
+ }
+
+ if (this._requests.length > 1 && Math.abs(this._requests[0].rid - this._requests[1].rid) < this.window) {
+ this._processRequest(1);
+ }
+ }
+};
+
+/***/ }),
+
+/***/ "./src/core.js":
+/*!*********************!*\
+ !*** ./src/core.js ***!
+ \*********************/
+/*! exports provided: Strophe, $build, $iq, $msg, $pres, SHA1, MD5, default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Strophe", function() { return Strophe; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$build", function() { return $build; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$iq", function() { return $iq; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$msg", function() { return $msg; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$pres", function() { return $pres; });
+/* harmony import */ var md5__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! md5 */ "./src/md5.js");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MD5", function() { return md5__WEBPACK_IMPORTED_MODULE_0__["default"]; });
+
+/* harmony import */ var sha1__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! sha1 */ "./src/sha1.js");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "SHA1", function() { return sha1__WEBPACK_IMPORTED_MODULE_1__["default"]; });
+
+/* harmony import */ var utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! utils */ "./src/utils.js");
+function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
+
+function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
+
+function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
+
+function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
+
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+
+/*
+ This program is distributed under the terms of the MIT license.
+ Please see the LICENSE file for details.
+
+ Copyright 2006-2018, OGG, LLC
+*/
+
+/*global define, document, sessionStorage, setTimeout, clearTimeout, ActiveXObject, DOMParser, btoa, atob, module */
+
+
+
+/** Function: $build
+ * Create a Strophe.Builder.
+ * This is an alias for 'new Strophe.Builder(name, attrs)'.
+ *
+ * Parameters:
+ * (String) name - The root element name.
+ * (Object) attrs - The attributes for the root element in object notation.
+ *
+ * Returns:
+ * A new Strophe.Builder object.
+ */
+
+function $build(name, attrs) {
+ return new Strophe.Builder(name, attrs);
+}
+/** Function: $msg
+ * Create a Strophe.Builder with a element as the root.
+ *
+ * Parameters:
+ * (Object) attrs - The element attributes in object notation.
+ *
+ * Returns:
+ * A new Strophe.Builder object.
+ */
+
+
+function $msg(attrs) {
+ return new Strophe.Builder("message", attrs);
+}
+/** Function: $iq
+ * Create a Strophe.Builder with an element as the root.
+ *
+ * Parameters:
+ * (Object) attrs - The element attributes in object notation.
+ *
+ * Returns:
+ * A new Strophe.Builder object.
+ */
+
+
+function $iq(attrs) {
+ return new Strophe.Builder("iq", attrs);
+}
+/** Function: $pres
+ * Create a Strophe.Builder with a element as the root.
+ *
+ * Parameters:
+ * (Object) attrs - The element attributes in object notation.
+ *
+ * Returns:
+ * A new Strophe.Builder object.
+ */
+
+
+function $pres(attrs) {
+ return new Strophe.Builder("presence", attrs);
+}
+/** Class: Strophe
+ * An object container for all Strophe library functions.
+ *
+ * This class is just a container for all the objects and constants
+ * used in the library. It is not meant to be instantiated, but to
+ * provide a namespace for library objects, constants, and functions.
+ */
+
+
+var Strophe = {
+ /** Constant: VERSION */
+ VERSION: "1.3.1",
+
+ /** 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 validTag(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 validAttribute(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 validCSS(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.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
+ },
+ 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: function 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: function forEachChild(elem, elemName, func) {
+ for (var i = 0; i < elem.childNodes.length; i++) {
+ var 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 isTagEqual(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 _makeGenerator() {
+ 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 xmlGenerator() {
+ 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 _getIEXmlDom() {
+ 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 xmlElement(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
+
+ for (var 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 (var 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 (var 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: function xmlescape(text) {
+ text = text.replace(/\&/g, "&");
+ text = text.replace(//g, ">");
+ text = text.replace(/'/g, "'");
+ text = text.replace(/"/g, """);
+ return text;
+ },
+
+ /* Function: xmlunescape
+ * Unexcapes invalid xml characters.
+ *
+ * Parameters:
+ * (String) text - text to unescape.
+ *
+ * Returns:
+ * Unescaped text.
+ */
+ xmlunescape: function 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: function 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: function xmlHtmlNode(html) {
+ var node; //ensure text is escaped
+
+ if (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 getText(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 copyElement(elem) {
+ var el;
+
+ if (elem.nodeType === Strophe.ElementType.NORMAL) {
+ el = Strophe.xmlElement(elem.tagName);
+
+ for (var i = 0; i < elem.attributes.length; i++) {
+ el.setAttribute(elem.attributes[i].nodeName, elem.attributes[i].value);
+ }
+
+ for (var _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 createHtml(elem) {
+ var el;
+
+ if (elem.nodeType === Strophe.ElementType.NORMAL) {
+ var tag = elem.nodeName.toLowerCase(); // XHTML tags must be lower case.
+
+ if (Strophe.XHTML.validTag(tag)) {
+ try {
+ el = Strophe.xmlElement(tag);
+
+ for (var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
+ var attribute = Strophe.XHTML.attributes[tag][i];
+ var 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') {
+ var css = [];
+ var cssAttrs = value.split(';');
+
+ for (var j = 0; j < cssAttrs.length; j++) {
+ var attr = cssAttrs[j].split(':');
+ var cssName = attr[0].replace(/^\s*/, "").replace(/\s*$/, "").toLowerCase();
+
+ if (Strophe.XHTML.validCSS(cssName)) {
+ var 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 (var _i2 = 0; _i2 < elem.childNodes.length; _i2++) {
+ el.appendChild(Strophe.createHtml(elem.childNodes[_i2]));
+ }
+ } catch (e) {
+ // invalid elements
+ el = Strophe.xmlTextNode('');
+ }
+ } else {
+ el = Strophe.xmlGenerator().createDocumentFragment();
+
+ for (var _i3 = 0; _i3 < elem.childNodes.length; _i3++) {
+ el.appendChild(Strophe.createHtml(elem.childNodes[_i3]));
+ }
+ }
+ } else if (elem.nodeType === Strophe.ElementType.FRAGMENT) {
+ el = Strophe.xmlGenerator().createDocumentFragment();
+
+ for (var _i4 = 0; _i4 < elem.childNodes.length; _i4++) {
+ el.appendChild(Strophe.createHtml(elem.childNodes[_i4]));
+ }
+ } 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 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, "\\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 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: function 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: function getDomainFromJid(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 getResourceFromJid(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 getBareJidFromJid(jid) {
+ return jid ? jid.split("/")[0] : null;
+ },
+
+ /** PrivateFunction: _handleError
+ * _Private_ function that properly logs an error to the console
+ */
+ _handleError: function _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 logging functions. The default implementation of this
+ * function logs only fatal errors. If client code wishes to handle the logging
+ * messages, it should override this with
+ * > Strophe.log = function (level, msg) {
+ * > (user code here)
+ * > };
+ *
+ * Please note that data sent and received over the wire is logged
+ * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().
+ *
+ * The different levels and their meanings are
+ *
+ * DEBUG - Messages useful for debugging purposes.
+ * INFO - Informational messages. This is mostly information like
+ * 'disconnect was called' or 'SASL auth succeeded'.
+ * WARN - Warnings about potential problems. This is mostly used
+ * to report transient connection errors like request timeouts.
+ * ERROR - Some error occurred.
+ * FATAL - A non-recoverable fatal error occurred.
+ *
+ * Parameters:
+ * (Integer) level - The log level of the log message. This will
+ * be one of the values in Strophe.LogLevel.
+ * (String) msg - The log message.
+ */
+ log: function log(level, msg) {
+ if (level === this.LogLevel.FATAL && _typeof(window.console) === 'object' && typeof window.console.error === 'function') {
+ window.console.error(msg);
+ }
+ },
+
+ /** Function: debug
+ * Log a message at the Strophe.LogLevel.DEBUG level.
+ *
+ * Parameters:
+ * (String) msg - The log message.
+ */
+ debug: function debug(msg) {
+ this.log(this.LogLevel.DEBUG, msg);
+ },
+
+ /** Function: info
+ * Log a message at the Strophe.LogLevel.INFO level.
+ *
+ * Parameters:
+ * (String) msg - The log message.
+ */
+ info: function info(msg) {
+ this.log(this.LogLevel.INFO, msg);
+ },
+
+ /** Function: warn
+ * Log a message at the Strophe.LogLevel.WARN level.
+ *
+ * Parameters:
+ * (String) msg - The log message.
+ */
+ warn: function warn(msg) {
+ this.log(this.LogLevel.WARN, msg);
+ },
+
+ /** Function: error
+ * Log a message at the Strophe.LogLevel.ERROR level.
+ *
+ * Parameters:
+ * (String) msg - The log message.
+ */
+ error: function error(msg) {
+ this.log(this.LogLevel.ERROR, msg);
+ },
+
+ /** Function: fatal
+ * Log a message at the Strophe.LogLevel.FATAL level.
+ *
+ * Parameters:
+ * (String) msg - The log message.
+ */
+ fatal: function fatal(msg) {
+ this.log(this.LogLevel.FATAL, msg);
+ },
+
+ /** Function: serialize
+ * Render a DOM element and all descendants to a String.
+ *
+ * Parameters:
+ * (XMLElement) elem - A DOM element.
+ *
+ * Returns:
+ * The serialized element tree as a String.
+ */
+ serialize: function serialize(elem) {
+ if (!elem) {
+ return null;
+ }
+
+ if (typeof elem.tree === "function") {
+ elem = elem.tree();
+ }
+
+ var names = _toConsumableArray(Array(elem.attributes.length).keys()).map(function (i) {
+ return elem.attributes[i].nodeName;
+ });
+
+ names.sort();
+ var result = names.reduce(function (a, n) {
+ return "".concat(a, " ").concat(n, "=\"").concat(Strophe.xmlescape(elem.attributes.getNamedItem(n).value), "\"");
+ }, "<".concat(elem.nodeName));
+
+ if (elem.childNodes.length > 0) {
+ result += ">";
+
+ for (var i = 0; i < elem.childNodes.length; i++) {
+ var child = elem.childNodes[i];
+
+ switch (child.nodeType) {
+ case Strophe.ElementType.NORMAL:
+ // normal element, so recurse
+ result += Strophe.serialize(child);
+ break;
+
+ case Strophe.ElementType.TEXT:
+ // text element to escape values
+ result += Strophe.xmlescape(child.nodeValue);
+ break;
+
+ case Strophe.ElementType.CDATA:
+ // cdata section so don't escape values
+ result += "";
+ }
+ }
+
+ result += "" + elem.nodeName + ">";
+ } else {
+ result += "/>";
+ }
+
+ return result;
+ },
+
+ /** PrivateVariable: _requestId
+ * _Private_ variable that keeps track of the request ids for
+ * connections.
+ */
+ _requestId: 0,
+
+ /** PrivateVariable: Strophe.connectionPlugins
+ * _Private_ variable Used to store plugin names that need
+ * initialization on Strophe.Connection construction.
+ */
+ _connectionPlugins: {},
+
+ /** Function: addConnectionPlugin
+ * Extends the Strophe.Connection object with the given plugin.
+ *
+ * Parameters:
+ * (String) name - The name of the extension.
+ * (Object) ptype - The plugin's prototype.
+ */
+ addConnectionPlugin: function addConnectionPlugin(name, ptype) {
+ Strophe._connectionPlugins[name] = ptype;
+ }
+};
+/** Class: Strophe.Builder
+ * XML DOM builder.
+ *
+ * This object 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. Here's an
+ * example using the $iq() builder helper.
+ * > $iq({to: 'you', from: 'me', type: 'get', id: '1'})
+ * > .c('query', {xmlns: 'strophe:example'})
+ * > .c('example')
+ * > .toString()
+ *
+ * The above generates this XML fragment
+ * >
+ * >
+ * >
+ * >
+ * >
+ * The corresponding DOM manipulations to get a similar fragment would be
+ * a lot more tedious and probably involve several helper variables.
+ *
+ * Since adding children makes new operations operate on the child, up()
+ * is provided to traverse up the tree. To add two children, do
+ * > builder.c('child1', ...).up().c('child2', ...)
+ * The next operation on the Builder will be relative to the second child.
+ */
+
+/** Constructor: Strophe.Builder
+ * Create a Strophe.Builder object.
+ *
+ * The attributes should be passed in object notation. For example
+ * > let b = new Builder('message', {to: 'you', from: 'me'});
+ * or
+ * > let b = new Builder('messsage', {'xml:lang': 'en'});
+ *
+ * Parameters:
+ * (String) name - The name of the root element.
+ * (Object) attrs - The attributes for the root element in object notation.
+ *
+ * Returns:
+ * A new Strophe.Builder.
+ */
+
+Strophe.Builder = function (name, attrs) {
+ // Set correct namespace for jabber:client elements
+ if (name === "presence" || name === "message" || name === "iq") {
+ if (attrs && !attrs.xmlns) {
+ attrs.xmlns = Strophe.NS.CLIENT;
+ } else if (!attrs) {
+ attrs = {
+ xmlns: Strophe.NS.CLIENT
+ };
+ }
+ } // Holds the tree being built.
+
+
+ this.nodeTree = Strophe.xmlElement(name, attrs); // Points to the current operation node.
+
+ this.node = this.nodeTree;
+};
+
+Strophe.Builder.prototype = {
+ /** Function: tree
+ * Return the DOM tree.
+ *
+ * This function returns the current DOM tree as an element object. This
+ * is suitable for passing to functions like Strophe.Connection.send().
+ *
+ * Returns:
+ * The DOM tree as a element object.
+ */
+ tree: function tree() {
+ return this.nodeTree;
+ },
+
+ /** Function: toString
+ * Serialize the DOM tree to a String.
+ *
+ * This function returns a string serialization of the current DOM
+ * tree. It is often used internally to pass data to a
+ * Strophe.Request object.
+ *
+ * Returns:
+ * The serialized DOM tree in a String.
+ */
+ toString: function toString() {
+ return Strophe.serialize(this.nodeTree);
+ },
+
+ /** Function: up
+ * Make the current parent element the new current element.
+ *
+ * This function is often used after c() to traverse back up the tree.
+ * For example, to add two children to the same element
+ * > builder.c('child1', {}).up().c('child2', {});
+ *
+ * Returns:
+ * The Stophe.Builder object.
+ */
+ up: function up() {
+ this.node = this.node.parentNode;
+ return this;
+ },
+
+ /** Function: root
+ * 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 Stophe.Builder object.
+ */
+ root: function root() {
+ this.node = this.nodeTree;
+ return this;
+ },
+
+ /** Function: attrs
+ * Add or modify attributes of the current element.
+ *
+ * The attributes should be passed in object notation. This function
+ * does not move the current element pointer.
+ *
+ * Parameters:
+ * (Object) moreattrs - The attributes to add/modify in object notation.
+ *
+ * Returns:
+ * The Strophe.Builder object.
+ */
+ attrs: function attrs(moreattrs) {
+ for (var k in moreattrs) {
+ if (Object.prototype.hasOwnProperty.call(moreattrs, k)) {
+ if (moreattrs[k] === undefined) {
+ this.node.removeAttribute(k);
+ } else {
+ this.node.setAttribute(k, moreattrs[k]);
+ }
+ }
+ }
+
+ return this;
+ },
+
+ /** Function: c
+ * Add a child to the current element and make it the new current
+ * element.
+ *
+ * This function moves the current element pointer to the child,
+ * unless text is provided. If you need to add another child, it
+ * is necessary to use up() to go back to the parent in the tree.
+ *
+ * Parameters:
+ * (String) name - The name of the child.
+ * (Object) attrs - The attributes of the child in object notation.
+ * (String) text - The text to add to the child.
+ *
+ * Returns:
+ * The Strophe.Builder object.
+ */
+ c: function c(name, attrs, text) {
+ var child = Strophe.xmlElement(name, attrs, text);
+ this.node.appendChild(child);
+
+ if (typeof text !== "string" && typeof text !== "number") {
+ this.node = child;
+ }
+
+ return this;
+ },
+
+ /** Function: cnode
+ * Add a child to the current element and make it the new current
+ * element.
+ *
+ * This function is the same as c() except that instead of using a
+ * name and an attributes object to create the child it uses an
+ * existing DOM element object.
+ *
+ * Parameters:
+ * (XMLElement) elem - A DOM element.
+ *
+ * Returns:
+ * The Strophe.Builder object.
+ */
+ cnode: function cnode(elem) {
+ var impNode;
+ var xmlGen = Strophe.xmlGenerator();
+
+ try {
+ impNode = xmlGen.importNode !== undefined;
+ } catch (e) {
+ impNode = false;
+ }
+
+ var newElem = impNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem);
+ this.node.appendChild(newElem);
+ this.node = newElem;
+ return this;
+ },
+
+ /** Function: t
+ * Add a child text element.
+ *
+ * This *does not* make the child the new current element since there
+ * are no children of text elements.
+ *
+ * Parameters:
+ * (String) text - The text data to append to the current element.
+ *
+ * Returns:
+ * The Strophe.Builder object.
+ */
+ t: function t(text) {
+ var child = Strophe.xmlTextNode(text);
+ this.node.appendChild(child);
+ return this;
+ },
+
+ /** Function: h
+ * Replace current element contents with the HTML passed in.
+ *
+ * This *does not* make the child the new current element
+ *
+ * Parameters:
+ * (String) html - The html to insert as contents of current element.
+ *
+ * Returns:
+ * The Strophe.Builder object.
+ */
+ h: function h(html) {
+ var fragment = document.createElement('body'); // force the browser to try and fix any invalid HTML tags
+
+ fragment.innerHTML = html; // copy cleaned html into an xml dom
+
+ var xhtml = Strophe.createHtml(fragment);
+
+ while (xhtml.childNodes.length > 0) {
+ this.node.appendChild(xhtml.childNodes[0]);
+ }
+
+ return this;
+ }
+};
+/** PrivateClass: Strophe.Handler
+ * _Private_ helper class for managing stanza handlers.
+ *
+ * A Strophe.Handler encapsulates a user provided callback function to be
+ * executed when matching stanzas are received by the connection.
+ * Handlers can be either one-off or persistant depending on their
+ * return value. Returning true will cause a Handler to remain active, and
+ * returning false will remove the Handler.
+ *
+ * Users will not use Strophe.Handler objects directly, but instead they
+ * will use Strophe.Connection.addHandler() and
+ * Strophe.Connection.deleteHandler().
+ */
+
+/** PrivateConstructor: Strophe.Handler
+ * Create and initialize a new Strophe.Handler.
+ *
+ * Parameters:
+ * (Function) handler - A function to be executed when the handler is run.
+ * (String) ns - The namespace to match.
+ * (String) name - The element name to match.
+ * (String) type - The element type to match.
+ * (String) id - The element id attribute to match.
+ * (String) from - The element from attribute to match.
+ * (Object) options - Handler options
+ *
+ * Returns:
+ * A new Strophe.Handler object.
+ */
+
+Strophe.Handler = function (handler, ns, name, type, id, from, options) {
+ this.handler = handler;
+ this.ns = ns;
+ this.name = name;
+ this.type = type;
+ this.id = id;
+ this.options = options || {
+ 'matchBareFromJid': false,
+ 'ignoreNamespaceFragment': false
+ }; // BBB: Maintain backward compatibility with old `matchBare` option
+
+ if (this.options.matchBare) {
+ Strophe.warn('The "matchBare" option is deprecated, use "matchBareFromJid" instead.');
+ this.options.matchBareFromJid = this.options.matchBare;
+ delete this.options.matchBare;
+ }
+
+ if (this.options.matchBareFromJid) {
+ this.from = from ? Strophe.getBareJidFromJid(from) : null;
+ } else {
+ this.from = from;
+ } // whether the handler is a user handler or a system handler
+
+
+ this.user = true;
+};
+
+Strophe.Handler.prototype = {
+ /** PrivateFunction: getNamespace
+ * Returns the XML namespace attribute on an element.
+ * If `ignoreNamespaceFragment` was passed in for this handler, then the
+ * URL fragment will be stripped.
+ *
+ * Parameters:
+ * (XMLElement) elem - The XML element with the namespace.
+ *
+ * Returns:
+ * The namespace, with optionally the fragment stripped.
+ */
+ getNamespace: function getNamespace(elem) {
+ var elNamespace = elem.getAttribute("xmlns");
+
+ if (elNamespace && this.options.ignoreNamespaceFragment) {
+ elNamespace = elNamespace.split('#')[0];
+ }
+
+ return elNamespace;
+ },
+
+ /** PrivateFunction: namespaceMatch
+ * Tests if a stanza matches the namespace set for this Strophe.Handler.
+ *
+ * Parameters:
+ * (XMLElement) elem - The XML element to test.
+ *
+ * Returns:
+ * true if the stanza matches and false otherwise.
+ */
+ namespaceMatch: function namespaceMatch(elem) {
+ var _this = this;
+
+ var nsMatch = false;
+
+ if (!this.ns) {
+ return true;
+ } else {
+ Strophe.forEachChild(elem, null, function (elem) {
+ if (_this.getNamespace(elem) === _this.ns) {
+ nsMatch = true;
+ }
+ });
+ return nsMatch || this.getNamespace(elem) === this.ns;
+ }
+ },
+
+ /** PrivateFunction: isMatch
+ * Tests if a stanza matches the Strophe.Handler.
+ *
+ * Parameters:
+ * (XMLElement) elem - The XML element to test.
+ *
+ * Returns:
+ * true if the stanza matches and false otherwise.
+ */
+ isMatch: function isMatch(elem) {
+ var from = elem.getAttribute('from');
+
+ if (this.options.matchBareFromJid) {
+ from = Strophe.getBareJidFromJid(from);
+ }
+
+ var elem_type = elem.getAttribute("type");
+
+ if (this.namespaceMatch(elem) && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || (Array.isArray(this.type) ? this.type.indexOf(elem_type) !== -1 : elem_type === this.type)) && (!this.id || elem.getAttribute("id") === this.id) && (!this.from || from === this.from)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /** PrivateFunction: run
+ * Run the callback on a matching stanza.
+ *
+ * Parameters:
+ * (XMLElement) elem - The DOM element that triggered the
+ * Strophe.Handler.
+ *
+ * Returns:
+ * A boolean indicating if the handler should remain active.
+ */
+ run: function run(elem) {
+ var result = null;
+
+ try {
+ result = this.handler(elem);
+ } catch (e) {
+ Strophe._handleError(e);
+
+ throw e;
+ }
+
+ return result;
+ },
+
+ /** PrivateFunction: toString
+ * Get a String representation of the Strophe.Handler object.
+ *
+ * Returns:
+ * A String.
+ */
+ toString: function toString() {
+ return "{Handler: " + this.handler + "(" + this.name + "," + this.id + "," + this.ns + ")}";
+ }
+};
+/** PrivateClass: Strophe.TimedHandler
+ * _Private_ helper class for managing timed handlers.
+ *
+ * A Strophe.TimedHandler encapsulates a user provided callback that
+ * should be called after a certain period of time or at regular
+ * intervals. The return value of the callback determines whether the
+ * Strophe.TimedHandler will continue to fire.
+ *
+ * Users will not use Strophe.TimedHandler objects directly, but instead
+ * they will use Strophe.Connection.addTimedHandler() and
+ * Strophe.Connection.deleteTimedHandler().
+ */
+
+/** PrivateConstructor: Strophe.TimedHandler
+ * Create and initialize a new Strophe.TimedHandler object.
+ *
+ * Parameters:
+ * (Integer) period - The number of milliseconds to wait before the
+ * handler is called.
+ * (Function) handler - The callback to run when the handler fires. This
+ * function should take no arguments.
+ *
+ * Returns:
+ * A new Strophe.TimedHandler object.
+ */
+
+Strophe.TimedHandler = function (period, handler) {
+ this.period = period;
+ this.handler = handler;
+ this.lastCalled = new Date().getTime();
+ this.user = true;
+};
+
+Strophe.TimedHandler.prototype = {
+ /** PrivateFunction: run
+ * Run the callback for the Strophe.TimedHandler.
+ *
+ * Returns:
+ * true if the Strophe.TimedHandler should be called again, and false
+ * otherwise.
+ */
+ run: function run() {
+ this.lastCalled = new Date().getTime();
+ return this.handler();
+ },
+
+ /** PrivateFunction: reset
+ * Reset the last called time for the Strophe.TimedHandler.
+ */
+ reset: function reset() {
+ this.lastCalled = new Date().getTime();
+ },
+
+ /** PrivateFunction: toString
+ * Get a string representation of the Strophe.TimedHandler object.
+ *
+ * Returns:
+ * The string representation.
+ */
+ toString: function toString() {
+ return "{TimedHandler: " + this.handler + "(" + this.period + ")}";
+ }
+};
+/** Class: Strophe.Connection
+ * XMPP Connection manager.
+ *
+ * This class is the main part of Strophe. It manages a BOSH or websocket
+ * connection to an XMPP server and dispatches events to the user callbacks
+ * as data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1
+ * and legacy authentication.
+ *
+ * After creating a Strophe.Connection object, the user will typically
+ * call connect() with a user supplied callback to handle connection level
+ * events like authentication failure, disconnection, or connection
+ * complete.
+ *
+ * The user will also have several event handlers defined by using
+ * addHandler() and addTimedHandler(). These will allow the user code to
+ * respond to interesting stanzas or do something periodically with the
+ * connection. These handlers will be active once authentication is
+ * finished.
+ *
+ * To send data to the connection, use send().
+ */
+
+/** Constructor: Strophe.Connection
+ * Create and initialize a Strophe.Connection object.
+ *
+ * The transport-protocol for this connection will be chosen automatically
+ * based on the given service parameter. URLs starting with "ws://" or
+ * "wss://" will use WebSockets, URLs starting with "http://", "https://"
+ * or without a protocol will use BOSH.
+ *
+ * To make Strophe connect to the current host you can leave out the protocol
+ * and host part and just pass the path, e.g.
+ *
+ * > let conn = new Strophe.Connection("/http-bind/");
+ *
+ * Options common to both Websocket and BOSH:
+ * ------------------------------------------
+ *
+ * cookies:
+ *
+ * The *cookies* option allows you to pass in cookies to be added to the
+ * document. These cookies will then be included in the BOSH XMLHttpRequest
+ * or in the websocket connection.
+ *
+ * The passed in value must be a map of cookie names and string values.
+ *
+ * > { "myCookie": {
+ * > "value": "1234",
+ * > "domain": ".example.org",
+ * > "path": "/",
+ * > "expires": expirationDate
+ * > }
+ * > }
+ *
+ * Note that cookies can't be set in this way for other domains (i.e. cross-domain).
+ * Those cookies need to be set under those domains, for example they can be
+ * set server-side by making a XHR call to that domain to ask it to set any
+ * necessary cookies.
+ *
+ * mechanisms:
+ *
+ * The *mechanisms* option allows you to specify the SASL mechanisms that this
+ * instance of Strophe.Connection (and therefore your XMPP client) will
+ * support.
+ *
+ * The value must be an array of objects with Strophe.SASLMechanism
+ * prototypes.
+ *
+ * If nothing is specified, then the following mechanisms (and their
+ * priorities) are registered:
+ *
+ * SCRAM-SHA1 - 70
+ * DIGEST-MD5 - 60
+ * PLAIN - 50
+ * OAUTH-BEARER - 40
+ * OAUTH-2 - 30
+ * ANONYMOUS - 20
+ * EXTERNAL - 10
+ *
+ * WebSocket options:
+ * ------------------
+ *
+ * If you want to connect to the current host with a WebSocket connection you
+ * can tell Strophe to use WebSockets through a "protocol" attribute in the
+ * optional options parameter. Valid values are "ws" for WebSocket and "wss"
+ * for Secure WebSocket.
+ * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call
+ *
+ * > let conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"});
+ *
+ * Note that relative URLs _NOT_ starting with a "/" will also include the path
+ * of the current site.
+ *
+ * Also because downgrading security is not permitted by browsers, when using
+ * relative URLs both BOSH and WebSocket connections will use their secure
+ * variants if the current connection to the site is also secure (https).
+ *
+ * BOSH options:
+ * -------------
+ *
+ * By adding "sync" to the options, you can control if requests will
+ * be made synchronously or not. The default behaviour is asynchronous.
+ * If you want to make requests synchronous, make "sync" evaluate to true.
+ * > let conn = new Strophe.Connection("/http-bind/", {sync: true});
+ *
+ * You can also toggle this on an already established connection.
+ * > conn.options.sync = true;
+ *
+ * The *customHeaders* option can be used to provide custom HTTP headers to be
+ * included in the XMLHttpRequests made.
+ *
+ * The *keepalive* option can be used to instruct Strophe to maintain the
+ * current BOSH session across interruptions such as webpage reloads.
+ *
+ * It will do this by caching the sessions tokens in sessionStorage, and when
+ * "restore" is called it will check whether there are cached tokens with
+ * which it can resume an existing session.
+ *
+ * The *withCredentials* option should receive a Boolean value and is used to
+ * indicate wether cookies should be included in ajax requests (by default
+ * they're not).
+ * Set this value to true if you are connecting to a BOSH service
+ * and for some reason need to send cookies to it.
+ * In order for this to work cross-domain, the server must also enable
+ * credentials by setting the Access-Control-Allow-Credentials response header
+ * to "true". For most usecases however this setting should be false (which
+ * is the default).
+ * Additionally, when using Access-Control-Allow-Credentials, the
+ * Access-Control-Allow-Origin header can't be set to the wildcard "*", but
+ * instead must be restricted to actual domains.
+ *
+ * The *contentType* option can be set to change the default Content-Type
+ * of "text/xml; charset=utf-8", which can be useful to reduce the amount of
+ * CORS preflight requests that are sent to the server.
+ *
+ * Parameters:
+ * (String) service - The BOSH or WebSocket service URL.
+ * (Object) options - A hash of configuration options
+ *
+ * Returns:
+ * A new Strophe.Connection object.
+ */
+
+Strophe.Connection = function (service, options) {
+ var _this2 = this;
+
+ // The service URL
+ this.service = service; // Configuration options
+
+ this.options = options || {};
+ var proto = this.options.protocol || ""; // Select protocal based on service or options
+
+ if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || proto.indexOf("ws") === 0) {
+ this._proto = new Strophe.Websocket(this);
+ } else {
+ this._proto = new Strophe.Bosh(this);
+ }
+ /* The connected JID. */
+
+
+ this.jid = "";
+ /* the JIDs domain */
+
+ this.domain = null;
+ /* stream:features */
+
+ this.features = null; // SASL
+
+ this._sasl_data = {};
+ this.do_session = false;
+ this.do_bind = false; // handler lists
+
+ this.timedHandlers = [];
+ this.handlers = [];
+ this.removeTimeds = [];
+ this.removeHandlers = [];
+ this.addTimeds = [];
+ this.addHandlers = [];
+ this.protocolErrorHandlers = {
+ 'HTTP': {},
+ 'websocket': {}
+ };
+ this._idleTimeout = null;
+ this._disconnectTimeout = null;
+ this.authenticated = false;
+ this.connected = false;
+ this.disconnecting = false;
+ this.do_authentication = true;
+ this.paused = false;
+ this.restored = false;
+ this._data = [];
+ this._uniqueId = 0;
+ this._sasl_success_handler = null;
+ this._sasl_failure_handler = null;
+ this._sasl_challenge_handler = null; // Max retries before disconnecting
+
+ this.maxRetries = 5; // Call onIdle callback every 1/10th of a second
+
+ this._idleTimeout = setTimeout(function () {
+ return _this2._onIdle();
+ }, 100);
+ utils__WEBPACK_IMPORTED_MODULE_2__["default"].addCookies(this.options.cookies);
+ this.registerSASLMechanisms(this.options.mechanisms); // initialize plugins
+
+ for (var k in Strophe._connectionPlugins) {
+ if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) {
+ var F = function F() {};
+
+ F.prototype = Strophe._connectionPlugins[k];
+ this[k] = new F();
+ this[k].init(this);
+ }
+ }
+};
+
+Strophe.Connection.prototype = {
+ /** Function: reset
+ * Reset the connection.
+ *
+ * This function should be called after a connection is disconnected
+ * before that connection is reused.
+ */
+ reset: function reset() {
+ this._proto._reset(); // SASL
+
+
+ this.do_session = false;
+ this.do_bind = false; // handler lists
+
+ this.timedHandlers = [];
+ this.handlers = [];
+ this.removeTimeds = [];
+ this.removeHandlers = [];
+ this.addTimeds = [];
+ this.addHandlers = [];
+ this.authenticated = false;
+ this.connected = false;
+ this.disconnecting = false;
+ this.restored = false;
+ this._data = [];
+ this._requests = [];
+ this._uniqueId = 0;
+ },
+
+ /** Function: pause
+ * Pause the request manager.
+ *
+ * This will prevent Strophe from sending any more requests to the
+ * server. This is very useful for temporarily pausing
+ * BOSH-Connections while a lot of send() calls are happening quickly.
+ * This causes Strophe to send the data in a single request, saving
+ * many request trips.
+ */
+ pause: function pause() {
+ this.paused = true;
+ },
+
+ /** Function: resume
+ * Resume the request manager.
+ *
+ * This resumes after pause() has been called.
+ */
+ resume: function resume() {
+ this.paused = false;
+ },
+
+ /** Function: getUniqueId
+ * Generate a unique ID for use in elements.
+ *
+ * All stanzas are required to have unique id attributes. This
+ * function makes creating these easy. Each connection instance has
+ * a counter which starts from zero, and the value of this counter
+ * plus a colon followed by the suffix becomes the unique id. If no
+ * suffix is supplied, the counter is used as the unique id.
+ *
+ * Suffixes are used to make debugging easier when reading the stream
+ * data, and their use is recommended. The counter resets to 0 for
+ * every new connection for the same reason. For connections to the
+ * same server that authenticate the same way, all the ids should be
+ * the same, which makes it easy to see changes. This is useful for
+ * automated testing as well.
+ *
+ * Parameters:
+ * (String) suffix - A optional suffix to append to the id.
+ *
+ * Returns:
+ * A unique string to be used for the id attribute.
+ */
+ getUniqueId: function getUniqueId(suffix) {
+ var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ var r = Math.random() * 16 | 0,
+ v = c === 'x' ? r : r & 0x3 | 0x8;
+ return v.toString(16);
+ });
+
+ if (typeof suffix === "string" || typeof suffix === "number") {
+ return uuid + ":" + suffix;
+ } else {
+ return uuid + "";
+ }
+ },
+
+ /** Function: addProtocolErrorHandler
+ * Register a handler function for when a protocol (websocker or HTTP)
+ * error occurs.
+ *
+ * NOTE: Currently only HTTP errors for BOSH requests are handled.
+ * Patches that handle websocket errors would be very welcome.
+ *
+ * Parameters:
+ * (String) protocol - 'HTTP' or 'websocket'
+ * (Integer) status_code - Error status code (e.g 500, 400 or 404)
+ * (Function) callback - Function that will fire on Http error
+ *
+ * Example:
+ * function onError(err_code){
+ * //do stuff
+ * }
+ *
+ * let conn = Strophe.connect('http://example.com/http-bind');
+ * conn.addProtocolErrorHandler('HTTP', 500, onError);
+ * // Triggers HTTP 500 error and onError handler will be called
+ * conn.connect('user_jid@incorrect_jabber_host', 'secret', onConnect);
+ */
+ addProtocolErrorHandler: function addProtocolErrorHandler(protocol, status_code, callback) {
+ this.protocolErrorHandlers[protocol][status_code] = callback;
+ },
+
+ /** Function: connect
+ * Starts the connection process.
+ *
+ * As the connection process proceeds, the user supplied callback will
+ * be triggered multiple times with status updates. The callback
+ * should take two arguments - the status code and the error condition.
+ *
+ * The status code will be one of the values in the Strophe.Status
+ * constants. The error condition will be one of the conditions
+ * defined in RFC 3920 or the condition 'strophe-parsererror'.
+ *
+ * The Parameters _wait_, _hold_ and _route_ are optional and only relevant
+ * for BOSH connections. Please see XEP 124 for a more detailed explanation
+ * of the optional parameters.
+ *
+ * Parameters:
+ * (String) jid - The user's JID. This may be a bare JID,
+ * or a full JID. If a node is not supplied, SASL OAUTHBEARER or
+ * SASL ANONYMOUS authentication will be attempted (OAUTHBEARER will
+ * process the provided password value as an access token).
+ * (String) pass - The user's password.
+ * (Function) callback - The connect callback function.
+ * (Integer) wait - The optional HTTPBIND wait value. This is the
+ * time the server will wait before returning an empty result for
+ * a request. The default setting of 60 seconds is recommended.
+ * (Integer) hold - The optional HTTPBIND hold value. This is the
+ * number of connections the server will hold at one time. This
+ * should almost always be set to 1 (the default).
+ * (String) route - The optional route value.
+ * (String) authcid - The optional alternative authentication identity
+ * (username) if intending to impersonate another user.
+ * When using the SASL-EXTERNAL authentication mechanism, for example
+ * with client certificates, then the authcid value is used to
+ * determine whether an authorization JID (authzid) should be sent to
+ * the server. The authzid should not be sent to the server if the
+ * authzid and authcid are the same. So to prevent it from being sent
+ * (for example when the JID is already contained in the client
+ * certificate), set authcid to that same JID. See XEP-178 for more
+ * details.
+ */
+ connect: function connect(jid, pass, callback, wait, hold, route, authcid) {
+ this.jid = jid;
+ /** Variable: authzid
+ * Authorization identity.
+ */
+
+ this.authzid = Strophe.getBareJidFromJid(this.jid);
+ /** Variable: authcid
+ * Authentication identity (User name).
+ */
+
+ this.authcid = authcid || Strophe.getNodeFromJid(this.jid);
+ /** Variable: pass
+ * Authentication identity (User password).
+ */
+
+ this.pass = pass;
+ /** Variable: servtype
+ * Digest MD5 compatibility.
+ */
+
+ this.servtype = "xmpp";
+ this.connect_callback = callback;
+ this.disconnecting = false;
+ this.connected = false;
+ this.authenticated = false;
+ this.restored = false; // parse jid for domain
+
+ this.domain = Strophe.getDomainFromJid(this.jid);
+
+ this._changeConnectStatus(Strophe.Status.CONNECTING, null);
+
+ this._proto._connect(wait, hold, route);
+ },
+
+ /** Function: attach
+ * Attach to an already created and authenticated BOSH session.
+ *
+ * This function is provided to allow Strophe to attach to BOSH
+ * sessions which have been created externally, perhaps by a Web
+ * application. This is often used to support auto-login type features
+ * without putting user credentials into the page.
+ *
+ * Parameters:
+ * (String) jid - The full JID that is bound by the session.
+ * (String) sid - The SID of the BOSH session.
+ * (String) rid - The current RID of the BOSH session. This RID
+ * will be used by the next request.
+ * (Function) callback The connect callback function.
+ * (Integer) wait - The optional HTTPBIND wait value. This is the
+ * time the server will wait before returning an empty result for
+ * a request. The default setting of 60 seconds is recommended.
+ * Other settings will require tweaks to the Strophe.TIMEOUT value.
+ * (Integer) hold - The optional HTTPBIND hold value. This is the
+ * number of connections the server will hold at one time. This
+ * should almost always be set to 1 (the default).
+ * (Integer) wind - The optional HTTBIND window value. This is the
+ * allowed range of request ids that are valid. The default is 5.
+ */
+ attach: function attach(jid, sid, rid, callback, wait, hold, wind) {
+ if (this._proto instanceof Strophe.Bosh) {
+ this._proto._attach(jid, sid, rid, callback, wait, hold, wind);
+ } else {
+ var error = new Error('The "attach" method can only be used with a BOSH connection.');
+ error.name = 'StropheSessionError';
+ throw error;
+ }
+ },
+
+ /** Function: restore
+ * Attempt to restore a cached BOSH session.
+ *
+ * This function is only useful in conjunction with providing the
+ * "keepalive":true option when instantiating a new Strophe.Connection.
+ *
+ * When "keepalive" is set to true, Strophe will cache the BOSH tokens
+ * RID (Request ID) and SID (Session ID) and then when this function is
+ * called, it will attempt to restore the session from those cached
+ * tokens.
+ *
+ * This function must therefore be called instead of connect or attach.
+ *
+ * For an example on how to use it, please see examples/restore.js
+ *
+ * Parameters:
+ * (String) jid - The user's JID. This may be a bare JID or a full JID.
+ * (Function) callback - The connect callback function.
+ * (Integer) wait - The optional HTTPBIND wait value. This is the
+ * time the server will wait before returning an empty result for
+ * a request. The default setting of 60 seconds is recommended.
+ * (Integer) hold - The optional HTTPBIND hold value. This is the
+ * number of connections the server will hold at one time. This
+ * should almost always be set to 1 (the default).
+ * (Integer) wind - The optional HTTBIND window value. This is the
+ * allowed range of request ids that are valid. The default is 5.
+ */
+ restore: function restore(jid, callback, wait, hold, wind) {
+ if (this._sessionCachingSupported()) {
+ this._proto._restore(jid, callback, wait, hold, wind);
+ } else {
+ var error = new Error('The "restore" method can only be used with a BOSH connection.');
+ error.name = 'StropheSessionError';
+ throw error;
+ }
+ },
+
+ /** PrivateFunction: _sessionCachingSupported
+ * Checks whether sessionStorage and JSON are supported and whether we're
+ * using BOSH.
+ */
+ _sessionCachingSupported: function _sessionCachingSupported() {
+ if (this._proto instanceof Strophe.Bosh) {
+ if (!JSON) {
+ return false;
+ }
+
+ try {
+ sessionStorage.setItem('_strophe_', '_strophe_');
+ sessionStorage.removeItem('_strophe_');
+ } catch (e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ },
+
+ /** Function: xmlInput
+ * User overrideable function that receives XML data coming into the
+ * connection.
+ *
+ * The default function does nothing. User code can override this with
+ * > Strophe.Connection.xmlInput = function (elem) {
+ * > (user code)
+ * > };
+ *
+ * Due to limitations of current Browsers' XML-Parsers the opening and closing
+ * tag for WebSocket-Connoctions will be passed as selfclosing here.
+ *
+ * BOSH-Connections will have all stanzas wrapped in a tag. See
+ * if you want to strip this tag.
+ *
+ * Parameters:
+ * (XMLElement) elem - The XML data received by the connection.
+ */
+
+ /* jshint unused:false */
+ xmlInput: function xmlInput(elem) {
+ return;
+ },
+
+ /* jshint unused:true */
+
+ /** Function: xmlOutput
+ * User overrideable function that receives XML data sent to the
+ * connection.
+ *
+ * The default function does nothing. User code can override this with
+ * > Strophe.Connection.xmlOutput = function (elem) {
+ * > (user code)
+ * > };
+ *
+ * Due to limitations of current Browsers' XML-Parsers the opening and closing
+ * tag for WebSocket-Connoctions will be passed as selfclosing here.
+ *
+ * BOSH-Connections will have all stanzas wrapped in a tag. See
+ * if you want to strip this tag.
+ *
+ * Parameters:
+ * (XMLElement) elem - The XMLdata sent by the connection.
+ */
+
+ /* jshint unused:false */
+ xmlOutput: function xmlOutput(elem) {
+ return;
+ },
+
+ /* jshint unused:true */
+
+ /** Function: rawInput
+ * User overrideable function that receives raw data coming into the
+ * connection.
+ *
+ * The default function does nothing. User code can override this with
+ * > Strophe.Connection.rawInput = function (data) {
+ * > (user code)
+ * > };
+ *
+ * Parameters:
+ * (String) data - The data received by the connection.
+ */
+
+ /* jshint unused:false */
+ rawInput: function rawInput(data) {
+ return;
+ },
+
+ /* jshint unused:true */
+
+ /** Function: rawOutput
+ * User overrideable function that receives raw data sent to the
+ * connection.
+ *
+ * The default function does nothing. User code can override this with
+ * > Strophe.Connection.rawOutput = function (data) {
+ * > (user code)
+ * > };
+ *
+ * Parameters:
+ * (String) data - The data sent by the connection.
+ */
+
+ /* jshint unused:false */
+ rawOutput: function rawOutput(data) {
+ return;
+ },
+
+ /* jshint unused:true */
+
+ /** Function: nextValidRid
+ * User overrideable function that receives the new valid rid.
+ *
+ * The default function does nothing. User code can override this with
+ * > Strophe.Connection.nextValidRid = function (rid) {
+ * > (user code)
+ * > };
+ *
+ * Parameters:
+ * (Number) rid - The next valid rid
+ */
+
+ /* jshint unused:false */
+ nextValidRid: function nextValidRid(rid) {
+ return;
+ },
+
+ /* jshint unused:true */
+
+ /** Function: send
+ * Send a stanza.
+ *
+ * This function is called to push data onto the send queue to
+ * go out over the wire. Whenever a request is sent to the BOSH
+ * server, all pending data is sent and the queue is flushed.
+ *
+ * Parameters:
+ * (XMLElement |
+ * [XMLElement] |
+ * Strophe.Builder) elem - The stanza to send.
+ */
+ send: function send(elem) {
+ if (elem === null) {
+ return;
+ }
+
+ if (typeof elem.sort === "function") {
+ for (var i = 0; i < elem.length; i++) {
+ this._queueData(elem[i]);
+ }
+ } else if (typeof elem.tree === "function") {
+ this._queueData(elem.tree());
+ } else {
+ this._queueData(elem);
+ }
+
+ this._proto._send();
+ },
+
+ /** Function: flush
+ * Immediately send any pending outgoing data.
+ *
+ * Normally send() queues outgoing data until the next idle period
+ * (100ms), which optimizes network use in the common cases when
+ * several send()s are called in succession. flush() can be used to
+ * immediately send all pending data.
+ */
+ flush: function flush() {
+ // cancel the pending idle period and run the idle function
+ // immediately
+ clearTimeout(this._idleTimeout);
+
+ this._onIdle();
+ },
+
+ /** Function: sendPresence
+ * Helper function to send presence stanzas. The main benefit is for
+ * sending presence stanzas for which you expect a responding presence
+ * stanza with the same id (for example when leaving a chat room).
+ *
+ * Parameters:
+ * (XMLElement) elem - The stanza to send.
+ * (Function) callback - The callback function for a successful request.
+ * (Function) errback - The callback function for a failed or timed
+ * out request. On timeout, the stanza will be null.
+ * (Integer) timeout - The time specified in milliseconds for a
+ * timeout to occur.
+ *
+ * Returns:
+ * The id used to send the presence.
+ */
+ sendPresence: function sendPresence(elem, callback, errback, timeout) {
+ var _this3 = this;
+
+ var timeoutHandler = null;
+
+ if (typeof elem.tree === "function") {
+ elem = elem.tree();
+ }
+
+ var id = elem.getAttribute('id');
+
+ if (!id) {
+ // inject id if not found
+ id = this.getUniqueId("sendPresence");
+ elem.setAttribute("id", id);
+ }
+
+ if (typeof callback === "function" || typeof errback === "function") {
+ var handler = this.addHandler(function (stanza) {
+ // remove timeout handler if there is one
+ if (timeoutHandler) {
+ _this3.deleteTimedHandler(timeoutHandler);
+ }
+
+ if (stanza.getAttribute('type') === 'error') {
+ if (errback) {
+ errback(stanza);
+ }
+ } else if (callback) {
+ callback(stanza);
+ }
+ }, null, 'presence', null, id); // if timeout specified, set up a timeout handler.
+
+ if (timeout) {
+ timeoutHandler = this.addTimedHandler(timeout, function () {
+ // get rid of normal handler
+ _this3.deleteHandler(handler); // call errback on timeout with null stanza
+
+
+ if (errback) {
+ errback(null);
+ }
+
+ return false;
+ });
+ }
+ }
+
+ this.send(elem);
+ return id;
+ },
+
+ /** Function: sendIQ
+ * Helper function to send IQ stanzas.
+ *
+ * Parameters:
+ * (XMLElement) elem - The stanza to send.
+ * (Function) callback - The callback function for a successful request.
+ * (Function) errback - The callback function for a failed or timed
+ * out request. On timeout, the stanza will be null.
+ * (Integer) timeout - The time specified in milliseconds for a
+ * timeout to occur.
+ *
+ * Returns:
+ * The id used to send the IQ.
+ */
+ sendIQ: function sendIQ(elem, callback, errback, timeout) {
+ var _this4 = this;
+
+ var timeoutHandler = null;
+
+ if (typeof elem.tree === "function") {
+ elem = elem.tree();
+ }
+
+ var id = elem.getAttribute('id');
+
+ if (!id) {
+ // inject id if not found
+ id = this.getUniqueId("sendIQ");
+ elem.setAttribute("id", id);
+ }
+
+ if (typeof callback === "function" || typeof errback === "function") {
+ var handler = this.addHandler(function (stanza) {
+ // remove timeout handler if there is one
+ if (timeoutHandler) {
+ _this4.deleteTimedHandler(timeoutHandler);
+ }
+
+ var iqtype = stanza.getAttribute('type');
+
+ if (iqtype === 'result') {
+ if (callback) {
+ callback(stanza);
+ }
+ } else if (iqtype === 'error') {
+ if (errback) {
+ errback(stanza);
+ }
+ } else {
+ var error = new Error("Got bad IQ type of ".concat(iqtype));
+ error.name = "StropheError";
+ throw error;
+ }
+ }, null, 'iq', ['error', 'result'], id); // if timeout specified, set up a timeout handler.
+
+ if (timeout) {
+ timeoutHandler = this.addTimedHandler(timeout, function () {
+ // get rid of normal handler
+ _this4.deleteHandler(handler); // call errback on timeout with null stanza
+
+
+ if (errback) {
+ errback(null);
+ }
+
+ return false;
+ });
+ }
+ }
+
+ this.send(elem);
+ return id;
+ },
+
+ /** PrivateFunction: _queueData
+ * Queue outgoing data for later sending. Also ensures that the data
+ * is a DOMElement.
+ */
+ _queueData: function _queueData(element) {
+ if (element === null || !element.tagName || !element.childNodes) {
+ var error = new Error("Cannot queue non-DOMElement.");
+ error.name = "StropheError";
+ throw error;
+ }
+
+ this._data.push(element);
+ },
+
+ /** PrivateFunction: _sendRestart
+ * Send an xmpp:restart stanza.
+ */
+ _sendRestart: function _sendRestart() {
+ var _this5 = this;
+
+ this._data.push("restart");
+
+ this._proto._sendRestart();
+
+ this._idleTimeout = setTimeout(function () {
+ return _this5._onIdle();
+ }, 100);
+ },
+
+ /** Function: addTimedHandler
+ * Add a timed handler to the connection.
+ *
+ * This function adds a timed handler. The provided handler will
+ * be called every period milliseconds until it returns false,
+ * the connection is terminated, or the handler is removed. Handlers
+ * that wish to continue being invoked should return true.
+ *
+ * Because of method binding it is necessary to save the result of
+ * this function if you wish to remove a handler with
+ * deleteTimedHandler().
+ *
+ * Note that user handlers are not active until authentication is
+ * successful.
+ *
+ * Parameters:
+ * (Integer) period - The period of the handler.
+ * (Function) handler - The callback function.
+ *
+ * Returns:
+ * A reference to the handler that can be used to remove it.
+ */
+ addTimedHandler: function addTimedHandler(period, handler) {
+ var thand = new Strophe.TimedHandler(period, handler);
+ this.addTimeds.push(thand);
+ return thand;
+ },
+
+ /** Function: deleteTimedHandler
+ * Delete a timed handler for a connection.
+ *
+ * This function removes a timed handler from the connection. The
+ * handRef parameter is *not* the function passed to addTimedHandler(),
+ * but is the reference returned from addTimedHandler().
+ *
+ * Parameters:
+ * (Strophe.TimedHandler) handRef - The handler reference.
+ */
+ deleteTimedHandler: function deleteTimedHandler(handRef) {
+ // this must be done in the Idle loop so that we don't change
+ // the handlers during iteration
+ this.removeTimeds.push(handRef);
+ },
+
+ /** Function: addHandler
+ * Add a stanza handler for the connection.
+ *
+ * This function adds a stanza handler to the connection. The
+ * handler callback will be called for any stanza that matches
+ * the parameters. Note that if multiple parameters are supplied,
+ * they must all match for the handler to be invoked.
+ *
+ * The handler will receive the stanza that triggered it as its argument.
+ * *The handler should return true if it is to be invoked again;
+ * returning false will remove the handler after it returns.*
+ *
+ * As a convenience, the ns parameters applies to the top level element
+ * and also any of its immediate children. This is primarily to make
+ * matching /iq/query elements easy.
+ *
+ * Options
+ * ~~~~~~~
+ * With the options argument, you can specify boolean flags that affect how
+ * matches are being done.
+ *
+ * Currently two flags exist:
+ *
+ * - matchBareFromJid:
+ * When set to true, the from parameter and the
+ * from attribute on the stanza will be matched as bare JIDs instead
+ * of full JIDs. To use this, pass {matchBareFromJid: true} as the
+ * value of options. The default value for matchBareFromJid is false.
+ *
+ * - ignoreNamespaceFragment:
+ * When set to true, a fragment specified on the stanza's namespace
+ * URL will be ignored when it's matched with the one configured for
+ * the handler.
+ *
+ * This means that if you register like this:
+ * > connection.addHandler(
+ * > handler,
+ * > 'http://jabber.org/protocol/muc',
+ * > null, null, null, null,
+ * > {'ignoreNamespaceFragment': true}
+ * > );
+ *
+ * Then a stanza with XML namespace of
+ * 'http://jabber.org/protocol/muc#user' will also be matched. If
+ * 'ignoreNamespaceFragment' is false, then only stanzas with
+ * 'http://jabber.org/protocol/muc' will be matched.
+ *
+ * Deleting the handler
+ * ~~~~~~~~~~~~~~~~~~~~
+ * The return value should be saved if you wish to remove the handler
+ * with deleteHandler().
+ *
+ * Parameters:
+ * (Function) handler - The user callback.
+ * (String) ns - The namespace to match.
+ * (String) name - The stanza name to match.
+ * (String|Array) type - The stanza type (or types if an array) to match.
+ * (String) id - The stanza id attribute to match.
+ * (String) from - The stanza from attribute to match.
+ * (String) options - The handler options
+ *
+ * Returns:
+ * A reference to the handler that can be used to remove it.
+ */
+ addHandler: function addHandler(handler, ns, name, type, id, from, options) {
+ var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
+ this.addHandlers.push(hand);
+ return hand;
+ },
+
+ /** Function: deleteHandler
+ * Delete a stanza handler for a connection.
+ *
+ * This function removes a stanza handler from the connection. The
+ * handRef parameter is *not* the function passed to addHandler(),
+ * but is the reference returned from addHandler().
+ *
+ * Parameters:
+ * (Strophe.Handler) handRef - The handler reference.
+ */
+ deleteHandler: function deleteHandler(handRef) {
+ // this must be done in the Idle loop so that we don't change
+ // the handlers during iteration
+ this.removeHandlers.push(handRef); // If a handler is being deleted while it is being added,
+ // prevent it from getting added
+
+ var i = this.addHandlers.indexOf(handRef);
+
+ if (i >= 0) {
+ this.addHandlers.splice(i, 1);
+ }
+ },
+
+ /** Function: registerSASLMechanisms
+ *
+ * Register the SASL mechanisms which will be supported by this instance of
+ * Strophe.Connection (i.e. which this XMPP client will support).
+ *
+ * Parameters:
+ * (Array) mechanisms - Array of objects with Strophe.SASLMechanism prototypes
+ *
+ */
+ registerSASLMechanisms: function registerSASLMechanisms(mechanisms) {
+ this.mechanisms = {};
+ mechanisms = mechanisms || [Strophe.SASLAnonymous, Strophe.SASLExternal, Strophe.SASLMD5, Strophe.SASLOAuthBearer, Strophe.SASLXOAuth2, Strophe.SASLPlain, Strophe.SASLSHA1];
+ mechanisms.forEach(this.registerSASLMechanism.bind(this));
+ },
+
+ /** Function: registerSASLMechanism
+ *
+ * Register a single SASL mechanism, to be supported by this client.
+ *
+ * Parameters:
+ * (Object) mechanism - Object with a Strophe.SASLMechanism prototype
+ *
+ */
+ registerSASLMechanism: function registerSASLMechanism(mechanism) {
+ this.mechanisms[mechanism.prototype.name] = mechanism;
+ },
+
+ /** Function: disconnect
+ * Start the graceful disconnection process.
+ *
+ * This function starts the disconnection process. This process starts
+ * by sending unavailable presence and sending BOSH body of type
+ * terminate. A timeout handler makes sure that disconnection happens
+ * even if the BOSH server does not respond.
+ * If the Connection object isn't connected, at least tries to abort all pending requests
+ * so the connection object won't generate successful requests (which were already opened).
+ *
+ * The user supplied connection callback will be notified of the
+ * progress as this process happens.
+ *
+ * Parameters:
+ * (String) reason - The reason the disconnect is occuring.
+ */
+ disconnect: function disconnect(reason) {
+ this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);
+
+ Strophe.info("Disconnect was called because: " + reason);
+
+ if (this.connected) {
+ var pres = false;
+ this.disconnecting = true;
+
+ if (this.authenticated) {
+ pres = $pres({
+ 'xmlns': Strophe.NS.CLIENT,
+ 'type': 'unavailable'
+ });
+ } // setup timeout handler
+
+
+ this._disconnectTimeout = this._addSysTimedHandler(3000, this._onDisconnectTimeout.bind(this));
+
+ this._proto._disconnect(pres);
+ } else {
+ Strophe.info("Disconnect was called before Strophe connected to the server");
+
+ this._proto._abortAllRequests();
+
+ this._doDisconnect();
+ }
+ },
+
+ /** PrivateFunction: _changeConnectStatus
+ * _Private_ helper function that makes sure plugins and the user's
+ * callback are notified of connection status changes.
+ *
+ * Parameters:
+ * (Integer) status - the new connection status, one of the values
+ * in Strophe.Status
+ * (String) condition - the error condition or null
+ * (XMLElement) elem - The triggering stanza.
+ */
+ _changeConnectStatus: function _changeConnectStatus(status, condition, elem) {
+ // notify all plugins listening for status changes
+ for (var k in Strophe._connectionPlugins) {
+ if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) {
+ var plugin = this[k];
+
+ if (plugin.statusChanged) {
+ try {
+ plugin.statusChanged(status, condition);
+ } catch (err) {
+ Strophe.error("".concat(k, " plugin caused an exception changing status: ").concat(err));
+ }
+ }
+ }
+ } // notify the user's callback
+
+
+ if (this.connect_callback) {
+ try {
+ this.connect_callback(status, condition, elem);
+ } catch (e) {
+ Strophe._handleError(e);
+
+ Strophe.error("User connection callback caused an exception: ".concat(e));
+ }
+ }
+ },
+
+ /** PrivateFunction: _doDisconnect
+ * _Private_ function to disconnect.
+ *
+ * This is the last piece of the disconnection logic. This resets the
+ * connection and alerts the user's connection callback.
+ */
+ _doDisconnect: function _doDisconnect(condition) {
+ if (typeof this._idleTimeout === "number") {
+ clearTimeout(this._idleTimeout);
+ } // Cancel Disconnect Timeout
+
+
+ if (this._disconnectTimeout !== null) {
+ this.deleteTimedHandler(this._disconnectTimeout);
+ this._disconnectTimeout = null;
+ }
+
+ Strophe.info("_doDisconnect was called");
+
+ this._proto._doDisconnect();
+
+ this.authenticated = false;
+ this.disconnecting = false;
+ this.restored = false; // delete handlers
+
+ this.handlers = [];
+ this.timedHandlers = [];
+ this.removeTimeds = [];
+ this.removeHandlers = [];
+ this.addTimeds = [];
+ this.addHandlers = []; // tell the parent we disconnected
+
+ this._changeConnectStatus(Strophe.Status.DISCONNECTED, condition);
+
+ this.connected = false;
+ },
+
+ /** PrivateFunction: _dataRecv
+ * _Private_ handler to processes incoming data from the the connection.
+ *
+ * Except for _connect_cb handling the initial connection request,
+ * this function handles the incoming data for all requests. This
+ * function also fires stanza handlers that match each incoming
+ * stanza.
+ *
+ * Parameters:
+ * (Strophe.Request) req - The request that has data ready.
+ * (string) req - The stanza a raw string (optiona).
+ */
+ _dataRecv: function _dataRecv(req, raw) {
+ var _this6 = this;
+
+ Strophe.info("_dataRecv called");
+
+ var elem = this._proto._reqToData(req);
+
+ if (elem === null) {
+ return;
+ }
+
+ if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
+ if (elem.nodeName === this._proto.strip && elem.childNodes.length) {
+ this.xmlInput(elem.childNodes[0]);
+ } else {
+ this.xmlInput(elem);
+ }
+ }
+
+ if (this.rawInput !== Strophe.Connection.prototype.rawInput) {
+ if (raw) {
+ this.rawInput(raw);
+ } else {
+ this.rawInput(Strophe.serialize(elem));
+ }
+ } // remove handlers scheduled for deletion
+
+
+ while (this.removeHandlers.length > 0) {
+ var hand = this.removeHandlers.pop();
+ var i = this.handlers.indexOf(hand);
+
+ if (i >= 0) {
+ this.handlers.splice(i, 1);
+ }
+ } // add handlers scheduled for addition
+
+
+ while (this.addHandlers.length > 0) {
+ this.handlers.push(this.addHandlers.pop());
+ } // handle graceful disconnect
+
+
+ if (this.disconnecting && this._proto._emptyQueue()) {
+ this._doDisconnect();
+
+ return;
+ }
+
+ var type = elem.getAttribute("type");
+
+ if (type !== null && type === "terminate") {
+ // Don't process stanzas that come in after disconnect
+ if (this.disconnecting) {
+ return;
+ } // an error occurred
+
+
+ var cond = elem.getAttribute("condition");
+ var conflict = elem.getElementsByTagName("conflict");
+
+ if (cond !== null) {
+ if (cond === "remote-stream-error" && conflict.length > 0) {
+ cond = "conflict";
+ }
+
+ this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
+ } else {
+ this._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.UNKOWN_REASON);
+ }
+
+ this._doDisconnect(cond);
+
+ return;
+ } // send each incoming stanza through the handler chain
+
+
+ Strophe.forEachChild(elem, null, function (child) {
+ // process handlers
+ var newList = _this6.handlers;
+ _this6.handlers = [];
+
+ for (var _i5 = 0; _i5 < newList.length; _i5++) {
+ var _hand = newList[_i5]; // encapsulate 'handler.run' not to lose the whole handler list if
+ // one of the handlers throws an exception
+
+ try {
+ if (_hand.isMatch(child) && (_this6.authenticated || !_hand.user)) {
+ if (_hand.run(child)) {
+ _this6.handlers.push(_hand);
+ }
+ } else {
+ _this6.handlers.push(_hand);
+ }
+ } catch (e) {
+ // if the handler throws an exception, we consider it as false
+ Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message);
+ }
+ }
+ });
+ },
+
+ /** Attribute: mechanisms
+ * SASL Mechanisms available for Connection.
+ */
+ mechanisms: {},
+
+ /** PrivateFunction: _connect_cb
+ * _Private_ handler for initial connection request.
+ *
+ * This handler is used to process the initial connection request
+ * response from the BOSH server. It is used to set up authentication
+ * handlers and start the authentication process.
+ *
+ * SASL authentication will be attempted if available, otherwise
+ * the code will fall back to legacy authentication.
+ *
+ * Parameters:
+ * (Strophe.Request) req - The current request.
+ * (Function) _callback - low level (xmpp) connect callback function.
+ * Useful for plugins with their own xmpp connect callback (when they
+ * want to do something special).
+ */
+ _connect_cb: function _connect_cb(req, _callback, raw) {
+ Strophe.info("_connect_cb was called");
+ this.connected = true;
+ var bodyWrap;
+
+ try {
+ bodyWrap = this._proto._reqToData(req);
+ } catch (e) {
+ if (e.name !== Strophe.ErrorCondition.BAD_FORMAT) {
+ throw e;
+ }
+
+ this._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.BAD_FORMAT);
+
+ this._doDisconnect(Strophe.ErrorCondition.BAD_FORMAT);
+ }
+
+ if (!bodyWrap) {
+ return;
+ }
+
+ if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
+ if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) {
+ this.xmlInput(bodyWrap.childNodes[0]);
+ } else {
+ this.xmlInput(bodyWrap);
+ }
+ }
+
+ if (this.rawInput !== Strophe.Connection.prototype.rawInput) {
+ if (raw) {
+ this.rawInput(raw);
+ } else {
+ this.rawInput(Strophe.serialize(bodyWrap));
+ }
+ }
+
+ var conncheck = this._proto._connect_cb(bodyWrap);
+
+ if (conncheck === Strophe.Status.CONNFAIL) {
+ return;
+ } // Check for the stream:features tag
+
+
+ var hasFeatures;
+
+ if (bodyWrap.getElementsByTagNameNS) {
+ hasFeatures = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "features").length > 0;
+ } else {
+ hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0 || bodyWrap.getElementsByTagName("features").length > 0;
+ }
+
+ if (!hasFeatures) {
+ this._proto._no_auth_received(_callback);
+
+ return;
+ }
+
+ var matched = [];
+ var mechanisms = bodyWrap.getElementsByTagName("mechanism");
+
+ if (mechanisms.length > 0) {
+ for (var i = 0; i < mechanisms.length; i++) {
+ var mech = Strophe.getText(mechanisms[i]);
+ if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]);
+ }
+ }
+
+ if (matched.length === 0) {
+ if (bodyWrap.getElementsByTagName("auth").length === 0) {
+ // There are no matching SASL mechanisms and also no legacy
+ // auth available.
+ this._proto._no_auth_received(_callback);
+
+ return;
+ }
+ }
+
+ if (this.do_authentication !== false) {
+ this.authenticate(matched);
+ }
+ },
+
+ /** Function: sortMechanismsByPriority
+ *
+ * Sorts an array of objects with prototype SASLMechanism according to
+ * their priorities.
+ *
+ * Parameters:
+ * (Array) mechanisms - Array of SASL mechanisms.
+ *
+ */
+ sortMechanismsByPriority: function sortMechanismsByPriority(mechanisms) {
+ // Sorting mechanisms according to priority.
+ for (var i = 0; i < mechanisms.length - 1; ++i) {
+ var higher = i;
+
+ for (var j = i + 1; j < mechanisms.length; ++j) {
+ if (mechanisms[j].prototype.priority > mechanisms[higher].prototype.priority) {
+ higher = j;
+ }
+ }
+
+ if (higher !== i) {
+ var swap = mechanisms[i];
+ mechanisms[i] = mechanisms[higher];
+ mechanisms[higher] = swap;
+ }
+ }
+
+ return mechanisms;
+ },
+
+ /** PrivateFunction: _attemptSASLAuth
+ *
+ * Iterate through an array of SASL mechanisms and attempt authentication
+ * with the highest priority (enabled) mechanism.
+ *
+ * Parameters:
+ * (Array) mechanisms - Array of SASL mechanisms.
+ *
+ * Returns:
+ * (Boolean) mechanism_found - true or false, depending on whether a
+ * valid SASL mechanism was found with which authentication could be
+ * started.
+ */
+ _attemptSASLAuth: function _attemptSASLAuth(mechanisms) {
+ mechanisms = this.sortMechanismsByPriority(mechanisms || []);
+ var mechanism_found = false;
+
+ for (var i = 0; i < mechanisms.length; ++i) {
+ if (!mechanisms[i].prototype.test(this)) {
+ continue;
+ }
+
+ this._sasl_success_handler = this._addSysHandler(this._sasl_success_cb.bind(this), null, "success", null, null);
+ this._sasl_failure_handler = this._addSysHandler(this._sasl_failure_cb.bind(this), null, "failure", null, null);
+ this._sasl_challenge_handler = this._addSysHandler(this._sasl_challenge_cb.bind(this), null, "challenge", null, null);
+ this._sasl_mechanism = new mechanisms[i]();
+
+ this._sasl_mechanism.onStart(this);
+
+ var request_auth_exchange = $build("auth", {
+ 'xmlns': Strophe.NS.SASL,
+ 'mechanism': this._sasl_mechanism.name
+ });
+
+ if (this._sasl_mechanism.isClientFirst) {
+ var response = this._sasl_mechanism.onChallenge(this, null);
+
+ request_auth_exchange.t(btoa(response));
+ }
+
+ this.send(request_auth_exchange.tree());
+ mechanism_found = true;
+ break;
+ }
+
+ return mechanism_found;
+ },
+
+ /** PrivateFunction: _attemptLegacyAuth
+ *
+ * Attempt legacy (i.e. non-SASL) authentication.
+ *
+ */
+ _attemptLegacyAuth: function _attemptLegacyAuth() {
+ if (Strophe.getNodeFromJid(this.jid) === null) {
+ // we don't have a node, which is required for non-anonymous
+ // client connections
+ this._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.MISSING_JID_NODE);
+
+ this.disconnect(Strophe.ErrorCondition.MISSING_JID_NODE);
+ } else {
+ // Fall back to legacy authentication
+ this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
+
+ this._addSysHandler(this._auth1_cb.bind(this), null, null, null, "_auth_1");
+
+ this.send($iq({
+ 'type': "get",
+ 'to': this.domain,
+ 'id': "_auth_1"
+ }).c("query", {
+ xmlns: Strophe.NS.AUTH
+ }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
+ }
+ },
+
+ /** Function: authenticate
+ * Set up authentication
+ *
+ * Continues the initial connection request by setting up authentication
+ * handlers and starting the authentication process.
+ *
+ * SASL authentication will be attempted if available, otherwise
+ * the code will fall back to legacy authentication.
+ *
+ * Parameters:
+ * (Array) matched - Array of SASL mechanisms supported.
+ *
+ */
+ authenticate: function authenticate(matched) {
+ if (!this._attemptSASLAuth(matched)) {
+ this._attemptLegacyAuth();
+ }
+ },
+
+ /** PrivateFunction: _sasl_challenge_cb
+ * _Private_ handler for the SASL challenge
+ *
+ */
+ _sasl_challenge_cb: function _sasl_challenge_cb(elem) {
+ var challenge = atob(Strophe.getText(elem));
+
+ var response = this._sasl_mechanism.onChallenge(this, challenge);
+
+ var stanza = $build('response', {
+ 'xmlns': Strophe.NS.SASL
+ });
+
+ if (response !== "") {
+ stanza.t(btoa(response));
+ }
+
+ this.send(stanza.tree());
+ return true;
+ },
+
+ /** PrivateFunction: _auth1_cb
+ * _Private_ handler for legacy authentication.
+ *
+ * This handler is called in response to the initial
+ * for legacy authentication. It builds an authentication and
+ * sends it, creating a handler (calling back to _auth2_cb()) to
+ * handle the result
+ *
+ * Parameters:
+ * (XMLElement) elem - The stanza that triggered the callback.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+
+ /* jshint unused:false */
+ _auth1_cb: function _auth1_cb(elem) {
+ // build plaintext auth iq
+ var iq = $iq({
+ type: "set",
+ id: "_auth_2"
+ }).c('query', {
+ xmlns: Strophe.NS.AUTH
+ }).c('username', {}).t(Strophe.getNodeFromJid(this.jid)).up().c('password').t(this.pass);
+
+ if (!Strophe.getResourceFromJid(this.jid)) {
+ // since the user has not supplied a resource, we pick
+ // a default one here. unlike other auth methods, the server
+ // cannot do this for us.
+ this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe';
+ }
+
+ iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid));
+
+ this._addSysHandler(this._auth2_cb.bind(this), null, null, null, "_auth_2");
+
+ this.send(iq.tree());
+ return false;
+ },
+
+ /* jshint unused:true */
+
+ /** PrivateFunction: _sasl_success_cb
+ * _Private_ handler for succesful SASL authentication.
+ *
+ * Parameters:
+ * (XMLElement) elem - The matching stanza.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+ _sasl_success_cb: function _sasl_success_cb(elem) {
+ var _this7 = this;
+
+ if (this._sasl_data["server-signature"]) {
+ var serverSignature;
+ var success = atob(Strophe.getText(elem));
+ var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
+ var matches = success.match(attribMatch);
+
+ if (matches[1] === "v") {
+ serverSignature = matches[2];
+ }
+
+ if (serverSignature !== this._sasl_data["server-signature"]) {
+ // remove old handlers
+ this.deleteHandler(this._sasl_failure_handler);
+ this._sasl_failure_handler = null;
+
+ if (this._sasl_challenge_handler) {
+ this.deleteHandler(this._sasl_challenge_handler);
+ this._sasl_challenge_handler = null;
+ }
+
+ this._sasl_data = {};
+ return this._sasl_failure_cb(null);
+ }
+ }
+
+ Strophe.info("SASL authentication succeeded.");
+
+ if (this._sasl_mechanism) {
+ this._sasl_mechanism.onSuccess();
+ } // remove old handlers
+
+
+ this.deleteHandler(this._sasl_failure_handler);
+ this._sasl_failure_handler = null;
+
+ if (this._sasl_challenge_handler) {
+ this.deleteHandler(this._sasl_challenge_handler);
+ this._sasl_challenge_handler = null;
+ }
+
+ var streamfeature_handlers = [];
+
+ var wrapper = function wrapper(handlers, elem) {
+ while (handlers.length) {
+ _this7.deleteHandler(handlers.pop());
+ }
+
+ _this7._sasl_auth1_cb(elem);
+
+ return false;
+ };
+
+ streamfeature_handlers.push(this._addSysHandler(function (elem) {
+ return wrapper(streamfeature_handlers, elem);
+ }, null, "stream:features", null, null));
+ streamfeature_handlers.push(this._addSysHandler(function (elem) {
+ return wrapper(streamfeature_handlers, elem);
+ }, Strophe.NS.STREAM, "features", null, null)); // we must send an xmpp:restart now
+
+ this._sendRestart();
+
+ return false;
+ },
+
+ /** PrivateFunction: _sasl_auth1_cb
+ * _Private_ handler to start stream binding.
+ *
+ * Parameters:
+ * (XMLElement) elem - The matching stanza.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+ _sasl_auth1_cb: function _sasl_auth1_cb(elem) {
+ // save stream:features for future usage
+ this.features = elem;
+
+ for (var i = 0; i < elem.childNodes.length; i++) {
+ var child = elem.childNodes[i];
+
+ if (child.nodeName === 'bind') {
+ this.do_bind = true;
+ }
+
+ if (child.nodeName === 'session') {
+ this.do_session = true;
+ }
+ }
+
+ if (!this.do_bind) {
+ this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
+
+ return false;
+ } else {
+ this._addSysHandler(this._sasl_bind_cb.bind(this), null, null, null, "_bind_auth_2");
+
+ var resource = Strophe.getResourceFromJid(this.jid);
+
+ if (resource) {
+ this.send($iq({
+ type: "set",
+ id: "_bind_auth_2"
+ }).c('bind', {
+ xmlns: Strophe.NS.BIND
+ }).c('resource', {}).t(resource).tree());
+ } else {
+ this.send($iq({
+ type: "set",
+ id: "_bind_auth_2"
+ }).c('bind', {
+ xmlns: Strophe.NS.BIND
+ }).tree());
+ }
+ }
+
+ return false;
+ },
+
+ /** PrivateFunction: _sasl_bind_cb
+ * _Private_ handler for binding result and session start.
+ *
+ * Parameters:
+ * (XMLElement) elem - The matching stanza.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+ _sasl_bind_cb: function _sasl_bind_cb(elem) {
+ if (elem.getAttribute("type") === "error") {
+ Strophe.info("SASL binding failed.");
+ var conflict = elem.getElementsByTagName("conflict");
+ var condition;
+
+ if (conflict.length > 0) {
+ condition = Strophe.ErrorCondition.CONFLICT;
+ }
+
+ this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition, elem);
+
+ return false;
+ } // TODO - need to grab errors
+
+
+ var bind = elem.getElementsByTagName("bind");
+
+ if (bind.length > 0) {
+ var jidNode = bind[0].getElementsByTagName("jid");
+
+ if (jidNode.length > 0) {
+ this.jid = Strophe.getText(jidNode[0]);
+
+ if (this.do_session) {
+ this._addSysHandler(this._sasl_session_cb.bind(this), null, null, null, "_session_auth_2");
+
+ this.send($iq({
+ type: "set",
+ id: "_session_auth_2"
+ }).c('session', {
+ xmlns: Strophe.NS.SESSION
+ }).tree());
+ } else {
+ this.authenticated = true;
+
+ this._changeConnectStatus(Strophe.Status.CONNECTED, null);
+ }
+ }
+ } else {
+ Strophe.info("SASL binding failed.");
+
+ this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);
+
+ return false;
+ }
+ },
+
+ /** PrivateFunction: _sasl_session_cb
+ * _Private_ handler to finish successful SASL connection.
+ *
+ * This sets Connection.authenticated to true on success, which
+ * starts the processing of user handlers.
+ *
+ * Parameters:
+ * (XMLElement) elem - The matching stanza.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+ _sasl_session_cb: function _sasl_session_cb(elem) {
+ if (elem.getAttribute("type") === "result") {
+ this.authenticated = true;
+
+ this._changeConnectStatus(Strophe.Status.CONNECTED, null);
+ } else if (elem.getAttribute("type") === "error") {
+ Strophe.info("Session creation failed.");
+
+ this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);
+
+ return false;
+ }
+
+ return false;
+ },
+
+ /** PrivateFunction: _sasl_failure_cb
+ * _Private_ handler for SASL authentication failure.
+ *
+ * Parameters:
+ * (XMLElement) elem - The matching stanza.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+
+ /* jshint unused:false */
+ _sasl_failure_cb: function _sasl_failure_cb(elem) {
+ // delete unneeded handlers
+ if (this._sasl_success_handler) {
+ this.deleteHandler(this._sasl_success_handler);
+ this._sasl_success_handler = null;
+ }
+
+ if (this._sasl_challenge_handler) {
+ this.deleteHandler(this._sasl_challenge_handler);
+ this._sasl_challenge_handler = null;
+ }
+
+ if (this._sasl_mechanism) this._sasl_mechanism.onFailure();
+
+ this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);
+
+ return false;
+ },
+
+ /* jshint unused:true */
+
+ /** PrivateFunction: _auth2_cb
+ * _Private_ handler to finish legacy authentication.
+ *
+ * This handler is called when the result from the jabber:iq:auth
+ * stanza is returned.
+ *
+ * Parameters:
+ * (XMLElement) elem - The stanza that triggered the callback.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+ _auth2_cb: function _auth2_cb(elem) {
+ if (elem.getAttribute("type") === "result") {
+ this.authenticated = true;
+
+ this._changeConnectStatus(Strophe.Status.CONNECTED, null);
+ } else if (elem.getAttribute("type") === "error") {
+ this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);
+
+ this.disconnect('authentication failed');
+ }
+
+ return false;
+ },
+
+ /** PrivateFunction: _addSysTimedHandler
+ * _Private_ function to add a system level timed handler.
+ *
+ * This function is used to add a Strophe.TimedHandler for the
+ * library code. System timed handlers are allowed to run before
+ * authentication is complete.
+ *
+ * Parameters:
+ * (Integer) period - The period of the handler.
+ * (Function) handler - The callback function.
+ */
+ _addSysTimedHandler: function _addSysTimedHandler(period, handler) {
+ var thand = new Strophe.TimedHandler(period, handler);
+ thand.user = false;
+ this.addTimeds.push(thand);
+ return thand;
+ },
+
+ /** PrivateFunction: _addSysHandler
+ * _Private_ function to add a system level stanza handler.
+ *
+ * This function is used to add a Strophe.Handler for the
+ * library code. System stanza handlers are allowed to run before
+ * authentication is complete.
+ *
+ * Parameters:
+ * (Function) handler - The callback function.
+ * (String) ns - The namespace to match.
+ * (String) name - The stanza name to match.
+ * (String) type - The stanza type attribute to match.
+ * (String) id - The stanza id attribute to match.
+ */
+ _addSysHandler: function _addSysHandler(handler, ns, name, type, id) {
+ var hand = new Strophe.Handler(handler, ns, name, type, id);
+ hand.user = false;
+ this.addHandlers.push(hand);
+ return hand;
+ },
+
+ /** PrivateFunction: _onDisconnectTimeout
+ * _Private_ timeout handler for handling non-graceful disconnection.
+ *
+ * If the graceful disconnect process does not complete within the
+ * time allotted, this handler finishes the disconnect anyway.
+ *
+ * Returns:
+ * false to remove the handler.
+ */
+ _onDisconnectTimeout: function _onDisconnectTimeout() {
+ Strophe.info("_onDisconnectTimeout was called");
+
+ this._changeConnectStatus(Strophe.Status.CONNTIMEOUT, null);
+
+ this._proto._onDisconnectTimeout(); // actually disconnect
+
+
+ this._doDisconnect();
+
+ return false;
+ },
+
+ /** PrivateFunction: _onIdle
+ * _Private_ handler to process events during idle cycle.
+ *
+ * This handler is called every 100ms to fire timed handlers that
+ * are ready and keep poll requests going.
+ */
+ _onIdle: function _onIdle() {
+ var _this8 = this;
+
+ // add timed handlers scheduled for addition
+ // NOTE: we add before remove in the case a timed handler is
+ // added and then deleted before the next _onIdle() call.
+ while (this.addTimeds.length > 0) {
+ this.timedHandlers.push(this.addTimeds.pop());
+ } // remove timed handlers that have been scheduled for deletion
+
+
+ while (this.removeTimeds.length > 0) {
+ var thand = this.removeTimeds.pop();
+ var i = this.timedHandlers.indexOf(thand);
+
+ if (i >= 0) {
+ this.timedHandlers.splice(i, 1);
+ }
+ } // call ready timed handlers
+
+
+ var now = new Date().getTime();
+ var newList = [];
+
+ for (var _i6 = 0; _i6 < this.timedHandlers.length; _i6++) {
+ var _thand = this.timedHandlers[_i6];
+
+ if (this.authenticated || !_thand.user) {
+ var since = _thand.lastCalled + _thand.period;
+
+ if (since - now <= 0) {
+ if (_thand.run()) {
+ newList.push(_thand);
+ }
+ } else {
+ newList.push(_thand);
+ }
+ }
+ }
+
+ this.timedHandlers = newList;
+ clearTimeout(this._idleTimeout);
+
+ this._proto._onIdle(); // reactivate the timer only if connected
+
+
+ if (this.connected) {
+ this._idleTimeout = setTimeout(function () {
+ return _this8._onIdle();
+ }, 100);
+ }
+ }
+};
+/** Class: Strophe.SASLMechanism
+ *
+ * encapsulates SASL authentication mechanisms.
+ *
+ * User code may override the priority for each mechanism or disable it completely.
+ * See for information about changing priority and for informatian on
+ * how to disable a mechanism.
+ *
+ * By default, all mechanisms are enabled and the priorities are
+ *
+ * OAUTHBEARER - 60
+ * SCRAM-SHA1 - 50
+ * DIGEST-MD5 - 40
+ * PLAIN - 30
+ * ANONYMOUS - 20
+ * EXTERNAL - 10
+ *
+ * See: Strophe.Connection.addSupportedSASLMechanisms
+ */
+
+/**
+ * PrivateConstructor: Strophe.SASLMechanism
+ * SASL auth mechanism abstraction.
+ *
+ * Parameters:
+ * (String) name - SASL Mechanism name.
+ * (Boolean) isClientFirst - If client should send response first without challenge.
+ * (Number) priority - Priority.
+ *
+ * Returns:
+ * A new Strophe.SASLMechanism object.
+ */
+
+Strophe.SASLMechanism = function (name, isClientFirst, priority) {
+ /** PrivateVariable: name
+ * Mechanism name.
+ */
+ this.name = name;
+ /** PrivateVariable: isClientFirst
+ * If client sends response without initial server challenge.
+ */
+
+ this.isClientFirst = isClientFirst;
+ /** Variable: priority
+ * Determines which is chosen for authentication (Higher is better).
+ * Users may override this to prioritize mechanisms differently.
+ *
+ * In the default configuration the priorities are
+ *
+ * SCRAM-SHA1 - 40
+ * DIGEST-MD5 - 30
+ * Plain - 20
+ *
+ * Example: (This will cause Strophe to choose the mechanism that the server sent first)
+ *
+ * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority;
+ *
+ * See for a list of available mechanisms.
+ *
+ */
+
+ this.priority = priority;
+};
+
+Strophe.SASLMechanism.prototype = {
+ /**
+ * Function: test
+ * Checks if mechanism able to run.
+ * To disable a mechanism, make this return false;
+ *
+ * To disable plain authentication run
+ * > Strophe.SASLPlain.test = function() {
+ * > return false;
+ * > }
+ *
+ * See for a list of available mechanisms.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - Target Connection.
+ *
+ * Returns:
+ * (Boolean) If mechanism was able to run.
+ */
+
+ /* jshint unused:false */
+ test: function test(connection) {
+ return true;
+ },
+
+ /* jshint unused:true */
+
+ /** PrivateFunction: onStart
+ * Called before starting mechanism on some connection.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - Target Connection.
+ */
+ onStart: function onStart(connection) {
+ this._connection = connection;
+ },
+
+ /** PrivateFunction: onChallenge
+ * Called by protocol implementation on incoming challenge. If client is
+ * first (isClientFirst === true) challenge will be null on the first call.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - Target Connection.
+ * (String) challenge - current challenge to handle.
+ *
+ * Returns:
+ * (String) Mechanism response.
+ */
+
+ /* jshint unused:false */
+ onChallenge: function onChallenge(connection, challenge) {
+ throw new Error("You should implement challenge handling!");
+ },
+
+ /* jshint unused:true */
+
+ /** PrivateFunction: onFailure
+ * Protocol informs mechanism implementation about SASL failure.
+ */
+ onFailure: function onFailure() {
+ this._connection = null;
+ },
+
+ /** PrivateFunction: onSuccess
+ * Protocol informs mechanism implementation about SASL success.
+ */
+ onSuccess: function onSuccess() {
+ this._connection = null;
+ }
+};
+/** Constants: SASL mechanisms
+ * Available authentication mechanisms
+ *
+ * Strophe.SASLAnonymous - SASL ANONYMOUS authentication.
+ * Strophe.SASLPlain - SASL PLAIN authentication.
+ * Strophe.SASLMD5 - SASL DIGEST-MD5 authentication
+ * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication
+ * Strophe.SASLOAuthBearer - SASL OAuth Bearer authentication
+ * Strophe.SASLExternal - SASL EXTERNAL authentication
+ * Strophe.SASLXOAuth2 - SASL X-OAuth2 authentication
+ */
+// Building SASL callbacks
+
+/** PrivateConstructor: SASLAnonymous
+ * SASL ANONYMOUS authentication.
+ */
+
+Strophe.SASLAnonymous = function () {};
+
+Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 20);
+
+Strophe.SASLAnonymous.prototype.test = function (connection) {
+ return connection.authcid === null;
+};
+/** PrivateConstructor: SASLPlain
+ * SASL PLAIN authentication.
+ */
+
+
+Strophe.SASLPlain = function () {};
+
+Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 50);
+
+Strophe.SASLPlain.prototype.test = function (connection) {
+ return connection.authcid !== null;
+};
+
+Strophe.SASLPlain.prototype.onChallenge = function (connection) {
+ var auth_str = connection.authzid;
+ auth_str = auth_str + "\0";
+ auth_str = auth_str + connection.authcid;
+ auth_str = auth_str + "\0";
+ auth_str = auth_str + connection.pass;
+ return utils__WEBPACK_IMPORTED_MODULE_2__["default"].utf16to8(auth_str);
+};
+/** PrivateConstructor: SASLSHA1
+ * SASL SCRAM SHA 1 authentication.
+ */
+
+
+Strophe.SASLSHA1 = function () {};
+
+Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 70);
+
+Strophe.SASLSHA1.prototype.test = function (connection) {
+ return connection.authcid !== null;
+};
+
+Strophe.SASLSHA1.prototype.onChallenge = function (connection, challenge, test_cnonce) {
+ var cnonce = test_cnonce || md5__WEBPACK_IMPORTED_MODULE_0__["default"].hexdigest(Math.random() * 1234567890);
+ var auth_str = "n=" + utils__WEBPACK_IMPORTED_MODULE_2__["default"].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;
+
+ this.onChallenge = function (connection, challenge) {
+ var nonce, salt, iter, Hi, U, U_old, i, k;
+ var responseText = "c=biws,";
+ var authMessage = "".concat(connection._sasl_data["client-first-message-bare"], ",").concat(challenge, ",");
+ var cnonce = connection._sasl_data.cnonce;
+ var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
+
+ while (challenge.match(attribMatch)) {
+ var matches = challenge.match(attribMatch);
+ challenge = challenge.replace(matches[0], "");
+
+ switch (matches[1]) {
+ case "r":
+ nonce = matches[2];
+ break;
+
+ case "s":
+ salt = matches[2];
+ break;
+
+ case "i":
+ iter = matches[2];
+ break;
+ }
+ }
+
+ if (nonce.substr(0, cnonce.length) !== cnonce) {
+ connection._sasl_data = {};
+ return connection._sasl_failure_cb();
+ }
+
+ responseText += "r=" + nonce;
+ authMessage += responseText;
+ salt = atob(salt);
+ salt += "\x00\x00\x00\x01";
+ var pass = utils__WEBPACK_IMPORTED_MODULE_2__["default"].utf16to8(connection.pass);
+ Hi = U_old = sha1__WEBPACK_IMPORTED_MODULE_1__["default"].core_hmac_sha1(pass, salt);
+
+ for (i = 1; i < iter; i++) {
+ U = sha1__WEBPACK_IMPORTED_MODULE_1__["default"].core_hmac_sha1(pass, sha1__WEBPACK_IMPORTED_MODULE_1__["default"].binb2str(U_old));
+
+ for (k = 0; k < 5; k++) {
+ Hi[k] ^= U[k];
+ }
+
+ U_old = U;
+ }
+
+ Hi = sha1__WEBPACK_IMPORTED_MODULE_1__["default"].binb2str(Hi);
+ var clientKey = sha1__WEBPACK_IMPORTED_MODULE_1__["default"].core_hmac_sha1(Hi, "Client Key");
+ var serverKey = sha1__WEBPACK_IMPORTED_MODULE_1__["default"].str_hmac_sha1(Hi, "Server Key");
+ var clientSignature = sha1__WEBPACK_IMPORTED_MODULE_1__["default"].core_hmac_sha1(sha1__WEBPACK_IMPORTED_MODULE_1__["default"].str_sha1(sha1__WEBPACK_IMPORTED_MODULE_1__["default"].binb2str(clientKey)), authMessage);
+ connection._sasl_data["server-signature"] = sha1__WEBPACK_IMPORTED_MODULE_1__["default"].b64_hmac_sha1(serverKey, authMessage);
+
+ for (k = 0; k < 5; k++) {
+ clientKey[k] ^= clientSignature[k];
+ }
+
+ responseText += ",p=" + btoa(sha1__WEBPACK_IMPORTED_MODULE_1__["default"].binb2str(clientKey));
+ return responseText;
+ };
+
+ return auth_str;
+};
+/** PrivateConstructor: SASLMD5
+ * SASL DIGEST MD5 authentication.
+ */
+
+
+Strophe.SASLMD5 = function () {};
+
+Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 60);
+
+Strophe.SASLMD5.prototype.test = function (connection) {
+ return connection.authcid !== null;
+};
+/** PrivateFunction: _quote
+ * _Private_ utility function to backslash escape and quote strings.
+ *
+ * Parameters:
+ * (String) str - The string to be quoted.
+ *
+ * Returns:
+ * quoted string
+ */
+
+
+Strophe.SASLMD5.prototype._quote = function (str) {
+ return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; //" end string workaround for emacs
+};
+
+Strophe.SASLMD5.prototype.onChallenge = function (connection, challenge, test_cnonce) {
+ var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
+ var cnonce = test_cnonce || md5__WEBPACK_IMPORTED_MODULE_0__["default"].hexdigest("" + Math.random() * 1234567890);
+ var realm = "";
+ var host = null;
+ var nonce = "";
+ var qop = "";
+
+ while (challenge.match(attribMatch)) {
+ var matches = challenge.match(attribMatch);
+ challenge = challenge.replace(matches[0], "");
+ matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
+
+ switch (matches[1]) {
+ case "realm":
+ realm = matches[2];
+ break;
+
+ case "nonce":
+ nonce = matches[2];
+ break;
+
+ case "qop":
+ qop = matches[2];
+ break;
+
+ case "host":
+ host = matches[2];
+ break;
+ }
+ }
+
+ var digest_uri = connection.servtype + "/" + connection.domain;
+
+ if (host !== null) {
+ digest_uri = digest_uri + "/" + host;
+ }
+
+ var cred = utils__WEBPACK_IMPORTED_MODULE_2__["default"].utf16to8(connection.authcid + ":" + realm + ":" + this._connection.pass);
+ var A1 = md5__WEBPACK_IMPORTED_MODULE_0__["default"].hash(cred) + ":" + nonce + ":" + cnonce;
+ var A2 = 'AUTHENTICATE:' + digest_uri;
+ var responseText = "";
+ responseText += 'charset=utf-8,';
+ responseText += 'username=' + this._quote(utils__WEBPACK_IMPORTED_MODULE_2__["default"].utf16to8(connection.authcid)) + ',';
+ responseText += 'realm=' + this._quote(realm) + ',';
+ responseText += 'nonce=' + this._quote(nonce) + ',';
+ responseText += 'nc=00000001,';
+ responseText += 'cnonce=' + this._quote(cnonce) + ',';
+ responseText += 'digest-uri=' + this._quote(digest_uri) + ',';
+ responseText += 'response=' + md5__WEBPACK_IMPORTED_MODULE_0__["default"].hexdigest(md5__WEBPACK_IMPORTED_MODULE_0__["default"].hexdigest(A1) + ":" + nonce + ":00000001:" + cnonce + ":auth:" + md5__WEBPACK_IMPORTED_MODULE_0__["default"].hexdigest(A2)) + ",";
+ responseText += 'qop=auth';
+
+ this.onChallenge = function () {
+ return "";
+ };
+
+ return responseText;
+};
+/** PrivateConstructor: SASLOAuthBearer
+ * SASL OAuth Bearer authentication.
+ */
+
+
+Strophe.SASLOAuthBearer = function () {};
+
+Strophe.SASLOAuthBearer.prototype = new Strophe.SASLMechanism("OAUTHBEARER", true, 40);
+
+Strophe.SASLOAuthBearer.prototype.test = function (connection) {
+ return connection.pass !== null;
+};
+
+Strophe.SASLOAuthBearer.prototype.onChallenge = function (connection) {
+ var auth_str = 'n,';
+
+ if (connection.authcid !== null) {
+ auth_str = auth_str + 'a=' + connection.authzid;
+ }
+
+ auth_str = auth_str + ',';
+ auth_str = auth_str + "\x01";
+ auth_str = auth_str + 'auth=Bearer ';
+ auth_str = auth_str + connection.pass;
+ auth_str = auth_str + "\x01";
+ auth_str = auth_str + "\x01";
+ return utils__WEBPACK_IMPORTED_MODULE_2__["default"].utf16to8(auth_str);
+};
+/** 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.
+ */
+
+
+Strophe.SASLExternal = function () {};
+
+Strophe.SASLExternal.prototype = new Strophe.SASLMechanism("EXTERNAL", true, 10);
+
+Strophe.SASLExternal.prototype.onChallenge = function (connection) {
+ /** 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;
+};
+/** PrivateConstructor: SASLXOAuth2
+ * SASL X-OAuth2 authentication.
+ */
+
+
+Strophe.SASLXOAuth2 = function () {};
+
+Strophe.SASLXOAuth2.prototype = new Strophe.SASLMechanism("X-OAUTH2", true, 30);
+
+Strophe.SASLXOAuth2.prototype.test = function (connection) {
+ return connection.pass !== null;
+};
+
+Strophe.SASLXOAuth2.prototype.onChallenge = function (connection) {
+ var auth_str = "\0";
+
+ if (connection.authcid !== null) {
+ auth_str = auth_str + connection.authzid;
+ }
+
+ auth_str = auth_str + "\0";
+ auth_str = auth_str + connection.pass;
+ return utils__WEBPACK_IMPORTED_MODULE_2__["default"].utf16to8(auth_str);
+};
+
+
+/* harmony default export */ __webpack_exports__["default"] = ({
+ 'Strophe': Strophe,
+ '$build': $build,
+ '$iq': $iq,
+ '$msg': $msg,
+ '$pres': $pres,
+ 'SHA1': sha1__WEBPACK_IMPORTED_MODULE_1__["default"],
+ 'MD5': md5__WEBPACK_IMPORTED_MODULE_0__["default"],
+ 'b64_hmac_sha1': sha1__WEBPACK_IMPORTED_MODULE_1__["default"].b64_hmac_sha1,
+ 'b64_sha1': sha1__WEBPACK_IMPORTED_MODULE_1__["default"].b64_sha1,
+ 'str_hmac_sha1': sha1__WEBPACK_IMPORTED_MODULE_1__["default"].str_hmac_sha1,
+ 'str_sha1': sha1__WEBPACK_IMPORTED_MODULE_1__["default"].str_sha1
+});
+
+/***/ }),
+
+/***/ "./src/md5.js":
+/*!********************!*\
+ !*** ./src/md5.js ***!
+ \********************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return MD5; });
+/*
+ * 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.
+ */
+var safe_add = 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.
+ */
+
+
+var bit_rol = function bit_rol(num, cnt) {
+ return num << cnt | num >>> 32 - cnt;
+};
+/*
+ * Convert a string to an array of little-endian words
+ */
+
+
+var str2binl = function str2binl(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 binl2str(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 binl2hex(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 md5_cmn(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 md5_ff(a, b, c, d, x, s, t) {
+ return md5_cmn(b & c | ~b & d, a, b, x, s, t);
+};
+
+var md5_gg = function md5_gg(a, b, c, d, x, s, t) {
+ return md5_cmn(b & d | c & ~d, a, b, x, s, t);
+};
+
+var md5_hh = function md5_hh(a, b, c, d, x, s, t) {
+ return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+};
+
+var md5_ii = function md5_ii(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 core_md5(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];
+};
+/*
+ * These are the functions you'll usually want to call.
+ * They take string arguments and return either hex or base-64 encoded
+ * strings.
+ */
+
+
+var MD5 = {
+ hexdigest: function hexdigest(s) {
+ return binl2hex(core_md5(str2binl(s), s.length * 8));
+ },
+ hash: function hash(s) {
+ return binl2str(core_md5(str2binl(s), s.length * 8));
+ }
+};
+
+
+/***/ }),
+
+/***/ "./src/sha1.js":
+/*!*********************!*\
+ !*** ./src/sha1.js ***!
+ \*********************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return SHA1; });
+/*
+ * 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: false */
+
+/* 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
+ */
+
+
+var SHA1 = {
+ b64_hmac_sha1: function b64_hmac_sha1(key, data) {
+ return binb2b64(core_hmac_sha1(key, data));
+ },
+ b64_sha1: function b64_sha1(s) {
+ return binb2b64(core_sha1(str2binb(s), s.length * 8));
+ },
+ binb2str: binb2str,
+ core_hmac_sha1: core_hmac_sha1,
+ str_hmac_sha1: function str_hmac_sha1(key, data) {
+ return binb2str(core_hmac_sha1(key, data));
+ },
+ str_sha1: function str_sha1(s) {
+ return binb2str(core_sha1(str2binb(s), s.length * 8));
+ }
+};
+
+
+/***/ }),
+
+/***/ "./src/strophe.js":
+/*!************************!*\
+ !*** ./src/strophe.js ***!
+ \************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core */ "./src/core.js");
+/* harmony import */ var bosh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! bosh */ "./src/bosh.js");
+/* harmony import */ var websocket__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! websocket */ "./src/websocket.js");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "default", function() { return core__WEBPACK_IMPORTED_MODULE_0__["default"]; });
+
+/*global global*/
+
+
+
+global.Strophe = core__WEBPACK_IMPORTED_MODULE_0__["default"].Strophe;
+global.$build = core__WEBPACK_IMPORTED_MODULE_0__["default"].$build;
+global.$iq = core__WEBPACK_IMPORTED_MODULE_0__["default"].$iq;
+global.$msg = core__WEBPACK_IMPORTED_MODULE_0__["default"].$msg;
+global.$pres = core__WEBPACK_IMPORTED_MODULE_0__["default"].$pres;
+
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../node_modules/webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js")))
+
+/***/ }),
+
+/***/ "./src/utils.js":
+/*!**********************!*\
+ !*** ./src/utils.js ***!
+ \**********************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return utils; });
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+
+var utils = {
+ utf16to8: function utf16to8(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 addCookies(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 (var cookieName in cookies) {
+ if (Object.prototype.hasOwnProperty.call(cookies, cookieName)) {
+ var expires = '';
+ var domain = '';
+ var path = '';
+ var cookieObj = cookies[cookieName];
+ var isObj = _typeof(cookieObj) === "object";
+ var 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;
+ }
+ }
+ }
+};
+
+
+/***/ }),
+
+/***/ "./src/websocket.js":
+/*!**************************!*\
+ !*** ./src/websocket.js ***!
+ \**************************/
+/*! no exports provided */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core */ "./src/core.js");
+/*
+ This program is distributed under the terms of the MIT license.
+ Please see the LICENSE file for details.
+
+ Copyright 2006-2008, OGG, LLC
+*/
+
+/* global window, clearTimeout, WebSocket, DOMParser */
+
+var Strophe = core__WEBPACK_IMPORTED_MODULE_0__["default"].Strophe;
+var $build = core__WEBPACK_IMPORTED_MODULE_0__["default"].$build;
+/** Class: Strophe.WebSocket
+ * _Private_ helper class that handles WebSocket Connections
+ *
+ * The Strophe.WebSocket class is used internally by Strophe.Connection
+ * to encapsulate WebSocket sessions. It is not meant to be used from user's code.
+ */
+
+/** File: websocket.js
+ * A JavaScript library to enable XMPP over Websocket in Strophejs.
+ *
+ * This file implements XMPP over WebSockets for Strophejs.
+ * If a Connection is established with a Websocket url (ws://...)
+ * Strophe will use WebSockets.
+ * For more information on XMPP-over-WebSocket see RFC 7395:
+ * http://tools.ietf.org/html/rfc7395
+ *
+ * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de)
+ */
+
+/** PrivateConstructor: Strophe.Websocket
+ * Create and initialize a Strophe.WebSocket object.
+ * Currently only sets the connection Object.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets.
+ *
+ * Returns:
+ * A new Strophe.WebSocket object.
+ */
+
+Strophe.Websocket = function (connection) {
+ this._conn = connection;
+ this.strip = "wrapper";
+ var service = connection.service;
+
+ if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) {
+ // If the service is not an absolute URL, assume it is a path and put the absolute
+ // URL together from options, current URL and the path.
+ var new_service = "";
+
+ if (connection.options.protocol === "ws" && window.location.protocol !== "https:") {
+ new_service += "ws";
+ } else {
+ new_service += "wss";
+ }
+
+ new_service += "://" + window.location.host;
+
+ if (service.indexOf("/") !== 0) {
+ new_service += window.location.pathname + service;
+ } else {
+ new_service += service;
+ }
+
+ connection.service = new_service;
+ }
+};
+
+Strophe.Websocket.prototype = {
+ /** PrivateFunction: _buildStream
+ * _Private_ helper function to generate the start tag for WebSockets
+ *
+ * Returns:
+ * A Strophe.Builder with a element.
+ */
+ _buildStream: function _buildStream() {
+ return $build("open", {
+ "xmlns": Strophe.NS.FRAMING,
+ "to": this._conn.domain,
+ "version": '1.0'
+ });
+ },
+
+ /** PrivateFunction: _check_streamerror
+ * _Private_ checks a message for stream:error
+ *
+ * Parameters:
+ * (Strophe.Request) bodyWrap - The received stanza.
+ * connectstatus - The ConnectStatus that will be set on error.
+ * Returns:
+ * true if there was a streamerror, false otherwise.
+ */
+ _check_streamerror: function _check_streamerror(bodyWrap, connectstatus) {
+ var errors;
+
+ if (bodyWrap.getElementsByTagNameNS) {
+ errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "error");
+ } else {
+ errors = bodyWrap.getElementsByTagName("stream:error");
+ }
+
+ if (errors.length === 0) {
+ return false;
+ }
+
+ var error = errors[0];
+ var condition = "";
+ var text = "";
+ var ns = "urn:ietf:params:xml:ns:xmpp-streams";
+
+ for (var i = 0; i < error.childNodes.length; i++) {
+ var e = error.childNodes[i];
+
+ if (e.getAttribute("xmlns") !== ns) {
+ break;
+ }
+
+ if (e.nodeName === "text") {
+ text = e.textContent;
+ } else {
+ condition = e.nodeName;
+ }
+ }
+
+ var errorString = "WebSocket stream error: ";
+
+ if (condition) {
+ errorString += condition;
+ } else {
+ errorString += "unknown";
+ }
+
+ if (text) {
+ errorString += " - " + text;
+ }
+
+ Strophe.error(errorString); // close the connection on stream_error
+
+ this._conn._changeConnectStatus(connectstatus, condition);
+
+ this._conn._doDisconnect();
+
+ return true;
+ },
+
+ /** PrivateFunction: _reset
+ * Reset the connection.
+ *
+ * This function is called by the reset function of the Strophe Connection.
+ * Is not needed by WebSockets.
+ */
+ _reset: function _reset() {
+ return;
+ },
+
+ /** PrivateFunction: _connect
+ * _Private_ function called by Strophe.Connection.connect
+ *
+ * Creates a WebSocket for a connection and assigns Callbacks to it.
+ * Does nothing if there already is a WebSocket.
+ */
+ _connect: function _connect() {
+ // Ensure that there is no open WebSocket from a previous Connection.
+ this._closeSocket(); // Create the new WobSocket
+
+
+ this.socket = new WebSocket(this._conn.service, "xmpp");
+ this.socket.onopen = this._onOpen.bind(this);
+ this.socket.onerror = this._onError.bind(this);
+ this.socket.onclose = this._onClose.bind(this);
+ this.socket.onmessage = this._connect_cb_wrapper.bind(this);
+ },
+
+ /** PrivateFunction: _connect_cb
+ * _Private_ function called by Strophe.Connection._connect_cb
+ *
+ * checks for stream:error
+ *
+ * Parameters:
+ * (Strophe.Request) bodyWrap - The received stanza.
+ */
+ _connect_cb: function _connect_cb(bodyWrap) {
+ var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL);
+
+ if (error) {
+ return Strophe.Status.CONNFAIL;
+ }
+ },
+
+ /** PrivateFunction: _handleStreamStart
+ * _Private_ function that checks the opening tag for errors.
+ *
+ * Disconnects if there is an error and returns false, true otherwise.
+ *
+ * Parameters:
+ * (Node) message - Stanza containing the tag.
+ */
+ _handleStreamStart: function _handleStreamStart(message) {
+ var error = false; // Check for errors in the tag
+
+ var ns = message.getAttribute("xmlns");
+
+ if (typeof ns !== "string") {
+ error = "Missing xmlns in ";
+ } else if (ns !== Strophe.NS.FRAMING) {
+ error = "Wrong xmlns in : " + ns;
+ }
+
+ var ver = message.getAttribute("version");
+
+ if (typeof ver !== "string") {
+ error = "Missing version in ";
+ } else if (ver !== "1.0") {
+ error = "Wrong version in : " + ver;
+ }
+
+ if (error) {
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error);
+
+ this._conn._doDisconnect();
+
+ return false;
+ }
+
+ return true;
+ },
+
+ /** PrivateFunction: _connect_cb_wrapper
+ * _Private_ function that handles the first connection messages.
+ *
+ * On receiving an opening stream tag this callback replaces itself with the real
+ * message handler. On receiving a stream error the connection is terminated.
+ */
+ _connect_cb_wrapper: function _connect_cb_wrapper(message) {
+ if (message.data.indexOf("\s*)*/, "");
+ if (data === '') return;
+ var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement;
+
+ this._conn.xmlInput(streamStart);
+
+ this._conn.rawInput(message.data); //_handleStreamSteart will check for XML errors and disconnect on error
+
+
+ if (this._handleStreamStart(streamStart)) {
+ //_connect_cb will check for stream:error and disconnect on error
+ this._connect_cb(streamStart);
+ }
+ } else if (message.data.indexOf("WSS, WS->ANY
+
+ var isSecureRedirect = service.indexOf("wss:") >= 0 && see_uri.indexOf("wss:") >= 0 || service.indexOf("ws:") >= 0;
+
+ if (isSecureRedirect) {
+ this._conn._changeConnectStatus(Strophe.Status.REDIRECT, "Received see-other-uri, resetting connection");
+
+ this._conn.reset();
+
+ this._conn.service = see_uri;
+
+ this._connect();
+ }
+ } else {
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream");
+
+ this._conn._doDisconnect();
+ }
+ } else {
+ var string = this._streamWrap(message.data);
+
+ var elem = new DOMParser().parseFromString(string, "text/xml").documentElement;
+ this.socket.onmessage = this._onMessage.bind(this);
+
+ this._conn._connect_cb(elem, null, message.data);
+ }
+ },
+
+ /** PrivateFunction: _disconnect
+ * _Private_ function called by Strophe.Connection.disconnect
+ *
+ * Disconnects and sends a last stanza if one is given
+ *
+ * Parameters:
+ * (Request) pres - This stanza will be sent before disconnecting.
+ */
+ _disconnect: function _disconnect(pres) {
+ if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
+ if (pres) {
+ this._conn.send(pres);
+ }
+
+ var close = $build("close", {
+ "xmlns": Strophe.NS.FRAMING
+ });
+
+ this._conn.xmlOutput(close.tree());
+
+ var closeString = Strophe.serialize(close);
+
+ this._conn.rawOutput(closeString);
+
+ try {
+ this.socket.send(closeString);
+ } catch (e) {
+ Strophe.info("Couldn't send tag.");
+ }
+ }
+
+ this._conn._doDisconnect();
+ },
+
+ /** PrivateFunction: _doDisconnect
+ * _Private_ function to disconnect.
+ *
+ * Just closes the Socket for WebSockets
+ */
+ _doDisconnect: function _doDisconnect() {
+ Strophe.info("WebSockets _doDisconnect was called");
+
+ this._closeSocket();
+ },
+
+ /** PrivateFunction _streamWrap
+ * _Private_ helper function to wrap a stanza in a tag.
+ * This is used so Strophe can process stanzas from WebSockets like BOSH
+ */
+ _streamWrap: function _streamWrap(stanza) {
+ return "" + stanza + '';
+ },
+
+ /** PrivateFunction: _closeSocket
+ * _Private_ function to close the WebSocket.
+ *
+ * Closes the socket if it is still open and deletes it
+ */
+ _closeSocket: function _closeSocket() {
+ if (this.socket) {
+ try {
+ this.socket.onerror = null;
+ this.socket.close();
+ } catch (e) {
+ Strophe.debug(e.message);
+ }
+ }
+
+ this.socket = null;
+ },
+
+ /** PrivateFunction: _emptyQueue
+ * _Private_ function to check if the message queue is empty.
+ *
+ * Returns:
+ * True, because WebSocket messages are send immediately after queueing.
+ */
+ _emptyQueue: function _emptyQueue() {
+ return true;
+ },
+
+ /** PrivateFunction: _onClose
+ * _Private_ function to handle websockets closing.
+ *
+ * Nothing to do here for WebSockets
+ */
+ _onClose: function _onClose(e) {
+ if (this._conn.connected && !this._conn.disconnecting) {
+ Strophe.error("Websocket closed unexpectedly");
+
+ this._conn._doDisconnect();
+ } else if (e && e.code === 1006 && !this._conn.connected && this.socket) {
+ // in case the onError callback was not called (Safari 10 does not
+ // call onerror when the initial connection fails) we need to
+ // dispatch a CONNFAIL status update to be consistent with the
+ // behavior on other browsers.
+ Strophe.error("Websocket closed unexcectedly");
+
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established or was disconnected.");
+
+ this._conn._doDisconnect();
+ } else {
+ Strophe.info("Websocket closed");
+ }
+ },
+
+ /** PrivateFunction: _no_auth_received
+ *
+ * Called on stream start/restart when no stream:features
+ * has been received.
+ */
+ _no_auth_received: function _no_auth_received(callback) {
+ Strophe.error("Server did not offer a supported authentication mechanism");
+
+ this._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.NO_AUTH_MECH);
+
+ if (callback) {
+ callback.call(this._conn);
+ }
+
+ this._conn._doDisconnect();
+ },
+
+ /** PrivateFunction: _onDisconnectTimeout
+ * _Private_ timeout handler for handling non-graceful disconnection.
+ *
+ * This does nothing for WebSockets
+ */
+ _onDisconnectTimeout: function _onDisconnectTimeout() {},
+
+ /** PrivateFunction: _abortAllRequests
+ * _Private_ helper function that makes sure all pending requests are aborted.
+ */
+ _abortAllRequests: function _abortAllRequests() {},
+
+ /** PrivateFunction: _onError
+ * _Private_ function to handle websockets errors.
+ *
+ * Parameters:
+ * (Object) error - The websocket error.
+ */
+ _onError: function _onError(error) {
+ Strophe.error("Websocket error " + error);
+
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established or was disconnected.");
+
+ this._disconnect();
+ },
+
+ /** PrivateFunction: _onIdle
+ * _Private_ function called by Strophe.Connection._onIdle
+ *
+ * sends all queued stanzas
+ */
+ _onIdle: function _onIdle() {
+ var data = this._conn._data;
+
+ if (data.length > 0 && !this._conn.paused) {
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] !== null) {
+ var stanza = void 0;
+
+ if (data[i] === "restart") {
+ stanza = this._buildStream().tree();
+ } else {
+ stanza = data[i];
+ }
+
+ var rawStanza = Strophe.serialize(stanza);
+
+ this._conn.xmlOutput(stanza);
+
+ this._conn.rawOutput(rawStanza);
+
+ this.socket.send(rawStanza);
+ }
+ }
+
+ this._conn._data = [];
+ }
+ },
+
+ /** PrivateFunction: _onMessage
+ * _Private_ function to handle websockets messages.
+ *
+ * This function parses each of the messages as if they are full documents.
+ * [TODO : We may actually want to use a SAX Push parser].
+ *
+ * Since all XMPP traffic starts with
+ *
+ *
+ * The first stanza will always fail to be parsed.
+ *
+ * Additionally, the seconds stanza will always be with
+ * the stream NS defined in the previous stanza, so we need to 'force'
+ * the inclusion of the NS in this stanza.
+ *
+ * Parameters:
+ * (string) message - The websocket message.
+ */
+ _onMessage: function _onMessage(message) {
+ var elem; // check for closing stream
+
+ var close = '';
+
+ if (message.data === close) {
+ this._conn.rawInput(close);
+
+ this._conn.xmlInput(message);
+
+ if (!this._conn.disconnecting) {
+ this._conn._doDisconnect();
+ }
+
+ return;
+ } else if (message.data.search(" tag before we close the connection
+
+
+ return;
+ }
+
+ this._conn._dataRecv(elem, message.data);
+ },
+
+ /** PrivateFunction: _onOpen
+ * _Private_ function to handle websockets connection setup.
+ *
+ * The opening stream tag is sent here.
+ */
+ _onOpen: function _onOpen() {
+ Strophe.info("Websocket open");
+
+ var start = this._buildStream();
+
+ this._conn.xmlOutput(start.tree());
+
+ var startString = Strophe.serialize(start);
+
+ this._conn.rawOutput(startString);
+
+ this.socket.send(startString);
+ },
+
+ /** PrivateFunction: _reqToData
+ * _Private_ function to get a stanza out of a request.
+ *
+ * WebSockets don't use requests, so the passed argument is just returned.
+ *
+ * Parameters:
+ * (Object) stanza - The stanza.
+ *
+ * Returns:
+ * The stanza that was passed.
+ */
+ _reqToData: function _reqToData(stanza) {
+ return stanza;
+ },
+
+ /** PrivateFunction: _send
+ * _Private_ part of the Connection.send function for WebSocket
+ *
+ * Just flushes the messages that are in the queue
+ */
+ _send: function _send() {
+ this._conn.flush();
+ },
+
+ /** PrivateFunction: _sendRestart
+ *
+ * Send an xmpp:restart stanza.
+ */
+ _sendRestart: function _sendRestart() {
+ clearTimeout(this._conn._idleTimeout);
+
+ this._conn._onIdle.bind(this._conn)();
+ }
+};
+
+/***/ })
+
+/******/ })["default"];
+});
+//# sourceMappingURL=strophe.js.map
\ No newline at end of file
diff --git a/src/extension/plugins/xmpp/web/svg.js b/src/extension/plugins/xmpp/web/svg.js
new file mode 100644
index 0000000000000000000000000000000000000000..94371132d2f16438bd7a861d44e78ad49d296179
--- /dev/null
+++ b/src/extension/plugins/xmpp/web/svg.js
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+'use strict';
+
+const NS = {
+ xrd: 'http://docs.oasis-open.org/ns/xri/xrd-1.0',
+ roster: 'jabber:iq:roster',
+ disco_info: 'http://jabber.org/protocol/disco#info',
+ pubsub: 'http://jabber.org/protocol/pubsub',
+ avatar_metadata: 'urn:xmpp:avatar:metadata',
+ avatar_data: 'urn:xmpp:avatar:data',
+ nickname: 'http://jabber.org/protocol/nick',
+ caps: 'http://jabber.org/protocol/caps',
+ ecaps2: 'urn:xmpp:caps',
+ hashes: 'urn:xmpp:hashes:2',
+ xhtml: 'http://www.w3.org/1999/xhtml',
+ svg: 'http://www.w3.org/2000/svg',
+ jingle: 'urn:xmpp:jingle:1',
+ jingle_sxe: 'urn:xmpp:jingle:transports:sxe',
+ sxe: 'urn:xmpp:sxe:0',
+};
+
+function nsResolver(prefix) {
+ return NS[prefix] || null;
+}
+
+function parseXPath(elem, xpath, result)
+{
+ if (result === undefined)
+ result = XPathResult.FIRST_ORDERED_NODE_TYPE;
+ const value = elem.getRootNode().evaluate(xpath, elem, nsResolver, result, null);
+ if (result == XPathResult.FIRST_ORDERED_NODE_TYPE)
+ return value.singleNodeValue;
+ return value;
+}
+
+function parseXPathText(elem, xpath)
+{
+ const value = parseXPath(elem, xpath);
+ if (value === null)
+ return null;
+ return value.textContent;
+}
+
+function displaySpinner(spinner) {
+ if ('timeoutid' in spinner.dataset)
+ clearTimeout(spinner.dataset.timeoutid);
+ spinner.src = 'spinner.svg';
+ spinner.title = '';
+ spinner.hidden = false;
+}
+
+function spinnerOk(spinner) {
+ if ('timeoutid' in spinner.dataset)
+ clearTimeout(spinner.dataset.timeoutid);
+ spinner.src = 'ok.svg';
+ spinner.title = '';
+ spinner.hidden = false;
+ spinner.dataset.timeoutid = setTimeout(function () {
+ spinner.hidden = true;
+ }, 1000);
+}
+
+function spinnerError(spinner, title) {
+ if ('timeoutid' in spinner.dataset)
+ clearTimeout(spinner.dataset.timeoutid);
+ spinner.src = 'error.svg';
+ spinner.title = title ? title : '';
+ spinner.hidden = false;
+}
+
+function hideSpinner(spinner) {
+ if ('timeoutid' in spinner.dataset)
+ clearTimeout(spinner.dataset.timeoutid);
+ spinner.hidden = true;
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+ let connection = null;
+
+ const jid_element = document.getElementById('jid');
+ const pass_element = document.getElementById('pass');
+ const connect_button = document.getElementById('connect');
+ const spinner_img = document.getElementById('connect-spinner');
+
+ const connected_div = document.getElementById('connected');
+ const roster_table = document.getElementById('roster');
+ const canvas_div = document.getElementById('canvas');
+
+ const avatar_img = document.getElementById('avatar');
+
+ function rawInput(data)
+ {
+ console.log('RECV', data);
+ }
+
+ function rawOutput(data)
+ {
+ console.log('SENT', data);
+ }
+
+ connect_button.addEventListener('click', function (evt) {
+ if (!connect_button.classList.contains('disconnect')) {
+ const jid = jid_element.value;
+ getBOSHService(jid).then((bosh_service) => {
+ connection = new Strophe.Connection(bosh_service);
+ connection.rawInput = rawInput;
+ connection.rawOutput = rawOutput;
+ connection.connect(jid + '/coucou',
+ pass_element.value,
+ onConnect);
+ });
+ } else if (connection != null) {
+ connection.disconnect();
+ }
+ evt.preventDefault();
+ });
+
+ function getBOSHService(jid)
+ {
+ return new Promise((resolve, reject) => {
+ const [nodepart, domainpart] = jid.split('@', 2);
+ const url = 'https://' + domainpart + '/.well-known/host-meta';
+ const xhr = new XMLHttpRequest();
+ xhr.onload = function (evt) {
+ const xml = evt.target.responseXML;
+ const links = parseXPath(xml, './xrd:XRD/xrd:Link', XPathResult.ORDERED_NODE_ITERATOR_TYPE);
+ let bosh_service = null;
+ while (true) {
+ const link = links.iterateNext();
+ if (!link)
+ break;
+ if (link.getAttributeNS(null, 'rel') == 'urn:xmpp:alt-connections:xbosh') {
+ bosh_service = link.getAttributeNS(null, 'href');
+ break;
+ }
+ // TODO: also support WebSocket.
+ }
+ console.log('bosh_service', bosh_service);
+ resolve(bosh_service);
+ };
+ xhr.open('GET', url);
+ xhr.send();
+ });
+ }
+
+ function onConnect(status)
+ {
+ if (status == Strophe.Status.CONNECTING) {
+ console.log('Strophe is connecting.');
+ connect_button.value = 'Log out';
+ connect_button.classList.add('disconnect');
+ jid_element.disabled = true;
+ pass_element.disabled = true;
+ displaySpinner(spinner_img);
+ } else if (status == Strophe.Status.CONNFAIL) {
+ console.log('Strophe failed to connect.');
+ onDisconnected();
+ } else if (status == Strophe.Status.DISCONNECTING) {
+ console.log('Strophe is disconnecting.');
+ displaySpinner(spinner_img);
+ } else if (status == Strophe.Status.DISCONNECTED) {
+ console.log('Strophe is disconnected.');
+ onDisconnected();
+ } else if (status == Strophe.Status.CONNECTED) {
+ console.log('Strophe is connected.');
+ onConnected();
+ }
+ }
+
+ function onConnected()
+ {
+ jid_element.hidden = true;
+ pass_element.hidden = true;
+ connected_div.hidden = false;
+ hideSpinner(spinner_img);
+ initRoster(connection);
+ }
+
+ function onDisconnected()
+ {
+ connect_button.value = 'Log in';
+ connect_button.classList.remove('disconnect');
+ jid_element.hidden = false;
+ jid_element.disabled = false;
+ pass_element.hidden = false;
+ pass_element.disabled = false;
+ hideSpinner(spinner_img);
+ connected_div.hidden = true;
+ }
+
+ function initRoster(connection)
+ {
+ const resources_dict = {};
+ const tr_dict = {};
+ const rid_dict = {};
+ const [bare, resource] = connection.jid.split('/', 2);
+ addContact(bare);
+
+ function onJingle(result_iq)
+ {
+ // TODO: don’t assume this is a session-accept.
+ console.log('Assuming session-accept:', result_iq);
+ const jid = result_iq.getAttributeNS(null, 'from');
+ const [bare, resource] = jid.split('/', 2);
+ const message = $msg({to: jid, type: 'chat', id: 'connect'})
+ .c('sxe', {xmlns: 'urn:xmpp:sxe:0', id: 'what-should-this-be?', session: 'foo'})
+ .c('connect');
+ connection.send(message);
+ }
+
+ function onJingleError(string)
+ {
+ console.log('Failed to initiate Jingle session: ' + string);
+ }
+
+ function addContact(jid)
+ {
+ const tr = document.createElementNS(NS.xhtml, 'tr');
+ tr.setAttributeNS(null, 'style', 'opacity: 50%');
+ resources_dict[jid] = {};
+ console.log(resources_dict);
+ tr_dict[jid] = tr;
+ const td = document.createElementNS(NS.xhtml, 'td');
+ const a = document.createElementNS(NS.xhtml, 'a');
+ a.setAttributeNS(null, 'href', 'xmpp:' + jid);
+ a.addEventListener('click', function(evt) {
+ evt.preventDefault();
+ const bare = evt.target.href.substr(5);
+ console.log(bare);
+ const resources = resources_dict[bare];
+ let good_resource = null;
+ for (let resource in resources) {
+ if (resources[resource]) {
+ good_resource = resource;
+ break;
+ }
+ }
+ if (good_resource === null)
+ return;
+ const jid = bare + '/' + good_resource;
+ const iq = $iq({type: 'set', to: jid})
+ .c('jingle', {xmlns: NS.jingle, action: 'session-initiate', initiator: connection.jid, sid: 'foo'})
+ .c('content', {creator: 'initiator', name: 'collaborative edition'})
+ // XXX: Standardise the application namespaces!
+ .c('description', {xmlns: 'urn:xmpp:jingle:apps:svg'}).up()
+ .c('transport', {xmlns: NS.jingle_sxe})
+ .c('host')
+ .t(jid)
+ connection.sendIQ(iq, onJingle, onJingleError.bind(null, 'Jingle session-initiate failed.'));
+ });
+ const text = document.createTextNode(jid);
+ a.appendChild(text);
+ td.appendChild(a);
+ tr.appendChild(td);
+ roster_table.lastChild.appendChild(tr);
+ }
+
+ function onRoster(result_iq)
+ {
+ const items = parseXPath(result_iq, './roster:query/roster:item', XPathResult.ORDERED_NODE_ITERATOR_TYPE);
+ while (true) {
+ const item = items.iterateNext();
+ if (!item)
+ break;
+ const jid = item.getAttributeNS(null, 'jid');
+ const subscription = item.getAttributeNS(null, 'subscription');
+ const name = item.getAttributeNS(null, 'name');
+ const groups = item.children;
+ console.log("got contact:", jid, subscription, name, groups);
+ addContact(jid);
+ }
+ }
+
+ function onRosterError(string)
+ {
+ console.log('Failed to retrieve your contact list: ' + string);
+ }
+
+ function onPresence(presence)
+ {
+ function onDiscoInfo(result_iq)
+ {
+ const features = parseXPath(result_iq, './disco_info:query/disco_info:feature', XPathResult.ORDERED_NODE_ITERATOR_TYPE);
+ let has_jingle_transport = false;
+ let has_sxe = false;
+ while (true) {
+ const feature = features.iterateNext();
+ if (!feature)
+ break;
+ const var_ = feature.getAttributeNS(null, 'var');
+ if (var_ === 'urn:xmpp:jingle:transports:sxe')
+ has_jingle_transport = true;
+ else if (var_ === 'urn:xmpp:sxe:0')
+ has_sxe = true;
+ }
+ if (has_jingle_transport && has_sxe) {
+ console.log('Hello Inkscape!', jid);
+ const tr = tr_dict[bare];
+ tr.setAttributeNS(null, 'style', 'opacity: 100%');
+ resources_dict[bare][resource] = true;
+ console.log(resources_dict);
+ }
+ }
+
+ function onDiscoInfoError(string)
+ {
+ console.log('Failed to retrieve contact disco#info: ' + string);
+ }
+
+ const jid = presence.getAttributeNS(null, 'from');
+ const [bare, resource] = jid.split('/', 2);
+ if (resource !== undefined) {
+ resources_dict[bare][resource] = false;
+ console.log(resources_dict);
+ }
+ const type = presence.getAttributeNS(null, 'type');
+ console.log("got presence:", bare, resource, type);
+
+ // TODO: handle our own presence differently.
+ // TODO: handle more than one resource per contact.
+ const tr = tr_dict[bare];
+ if (type === null) {
+ const caps = parseXPath(presence, './caps:c', XPathResult.ORDERED_NODE_ITERATOR_TYPE).iterateNext();
+ if (caps !== null) {
+ const node = caps.getAttributeNS(null, 'node');
+ const ver = caps.getAttributeNS(null, 'ver');
+ if (node !== null && ver !== null) {
+ const iq = $iq({type: 'get', to: jid})
+ .c('query', {xmlns: NS.disco_info, node: node + '#' + ver});
+ connection.sendIQ(iq, onDiscoInfo, onDiscoInfoError.bind(null, 'disco#info query failed.'));
+ }
+ }
+ // TODO: move to only ecaps2.
+ /*
+ const hashes = parseXPath(presence, './ecaps2:c/hashes:hash', XPathResult.ORDERED_NODE_ITERATOR_TYPE);
+ while (true) {
+ const hash = hashes.iterateNext();
+ if (hash === null)
+ break;
+ console.log(hash);
+ }
+ */
+ } else if (type === 'unavailable') {
+ const resources = resources_dict[bare];
+ delete resources[resource];
+ let disconnect = true;
+ for (let resource in resources) {
+ if (resources[resource]) {
+ disconnect = true;
+ break;
+ }
+ }
+ console.log('disconnect?', disconnect);
+ if (tr !== undefined && disconnect) {
+ tr.setAttributeNS(null, 'style', 'opacity: 50%');
+ }
+ }
+
+ return true;
+ }
+
+ function onMessage(message)
+ {
+ const jid = message.getAttributeNS(null, 'from');
+ const [bare, resource] = jid.split('/', 2);
+ if (resource !== undefined) {
+ resources_dict[bare][resource] = false;
+ console.log(resources_dict);
+ }
+ const type = message.getAttributeNS(null, 'type');
+ console.log("got message:", bare, resource, type);
+
+ const sxe = parseXPath(message, './sxe:sxe', XPathResult.ORDERED_NODE_ITERATOR_TYPE).iterateNext();
+ console.log(sxe);
+ if (sxe !== null) {
+ const child = sxe.firstChild;
+ console.log(child);
+ if (child.localName == 'state-offer') {
+ const message = $msg({to: jid, type: 'chat', id: 'state-accept'})
+ .c('sxe', {xmlns: 'urn:xmpp:sxe:0', id: 'coucou', session: 'foo'})
+ .c('accept-state');
+ connection.send(message);
+ } else if (child.localName == 'state') {
+ for (let change of child.children) {
+ console.log(change);
+ if (change.localName == 'new') {
+ const rid = change.getAttributeNS(null, 'rid');
+ const type = change.getAttributeNS(null, 'type');
+ const version = change.getAttributeNS(null, 'version');
+ let parent = change.getAttributeNS(null, 'parent');
+ const primary_weight = change.getAttributeNS(null, 'primary-weight');
+ const ns = change.getAttributeNS(null, 'ns');
+ const name = change.getAttributeNS(null, 'name');
+ const chdata = change.getAttributeNS(null, 'chdata');
+ const pitarget = change.getAttributeNS(null, 'pitarget');
+ const pidata = change.getAttributeNS(null, 'pidata');
+ const creator = change.getAttributeNS(null, 'creator');
+ const last_modified_by = change.getAttributeNS(null, 'last-modified-by');
+
+ console.log('parent', parent);
+ if (parent !== null) {
+ parent = rid_dict[parent];
+ if (parent === null) {
+ console.warn('Invalid parent!');
+ return;
+ }
+ } else
+ parent = canvas_div;
+ console.log('parent2', parent);
+
+ if (type == 'element') {
+ const elem = document.createElementNS(ns, name);
+ parent.appendChild(elem);
+ rid_dict[rid] = elem;
+ } else if (type == 'attr') {
+ parent.setAttributeNS(ns, name, chdata);
+ // TODO: store rid.
+ } else if (type == 'text') {
+ const text = document.createTextNode(chdata);
+ parent.appendChild(text);
+ rid_dict[rid] = text;
+ } else if (type == 'comment') {
+ } else if (type == 'processinginstruction') {
+ } else {
+ // TODO: Invalid!
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ const iq = $iq({type: 'get'})
+ .c('query', {xmlns: NS.roster});
+ connection.sendIQ(iq, onRoster, onRosterError.bind(null, 'roster query failed.'));
+
+ const presence = $pres();
+ connection.addHandler(onPresence, null, 'presence');
+ connection.send(presence);
+
+ connection.addHandler(onMessage, null, 'message');
+ }
+});
diff --git a/src/extension/plugins/xmpp/xmpp.cpp b/src/extension/plugins/xmpp/xmpp.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..104f2805953885207d30bbcacf5270c59245a5b7
--- /dev/null
+++ b/src/extension/plugins/xmpp/xmpp.cpp
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ \file xmpp.cpp
+
+ A collaborative edition plugin.
+*/
+/*
+ * Copyright (C) 2019 Marc Jeanmougin
+ * Copyright (C) 2020 Emmanuel Gil Peyrot
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#undef lookup
+#include "sxe.h"
+
+#include
+#include
+#include
+
+#include "desktop.h"
+
+#include <2geom/geom.h>
+#include "event.h"
+#include "document.h"
+#include "object/sp-object.h"
+#include "selection.h"
+#include "xml/attribute-record.h"
+#include "inkscape-version.h"
+
+#include "svg/path-string.h"
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "util/units.h"
+
+#include "xmpp.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+using namespace gloox;
+
+std::string get_uuid()
+{
+ // TODO: use a real UUID.
+ char attr_rid[11];
+ snprintf(attr_rid, 11, "%d", rand());
+ return std::string(attr_rid);
+}
+
+
+// TODO: this is a hack, this namespace isn’t reserved so shouldn’t be used, we
+// probably want to change the XEP to use an application based on the MIME type
+// of the document or something like that.
+class SvgApplication : public Jingle::Plugin
+{
+public:
+ SvgApplication(const Tag* tag = 0)
+ : Jingle::Plugin(Jingle::PluginUser)
+ {}
+
+ // reimplemented from Plugin
+ const StringList features() const override
+ {
+ StringList sl;
+ sl.push_back("urn:xmpp:jingle:apps:svg");
+ return sl;
+ }
+
+ // reimplemented from Plugin
+ const std::string& filterString() const override
+ {
+ static const std::string filter = "content[@xmlns='" + XMLNS_JINGLE + "']/transport[@xmlns='urn:xmpp:jingle:apps:svg']";
+ return filter;
+ }
+
+ // reimplemented from Plugin
+ Tag* tag() const override
+ {
+ return new Tag("description", XMLNS, "urn:xmpp:jingle:apps:svg");
+ }
+
+ // reimplemented from Plugin
+ Jingle::Plugin* newInstance(const Tag* tag) const override
+ {
+ return new SvgApplication(tag);
+ }
+
+ // reimplemented from Plugin
+ Jingle::Plugin* clone() const override
+ {
+ return new SvgApplication(*this);
+ }
+};
+
+InkscapeClient::InkscapeClient(JID jid, const std::string& password)
+{
+ client = std::make_unique(jid, password);
+ // TODO: figure out why SCRAM-SHA-1 isn’t working.
+ client->setSASLMechanisms(SaslMechPlain);
+ // TODO: fetch the OS properly, instead of hardcoding it to Linux.
+ client->disco()->setVersion("Inkscape", version_string_without_revision, "Linux");
+ client->disco()->setIdentity("collaboration", "whiteboard", "Inkscape");
+ session_manager = std::unique_ptr(new Jingle::SessionManager(client.get(), this));
+ session_manager->registerPlugin(new Jingle::Content());
+ client->registerConnectionListener(this);
+ //client->logInstance().registerLogHandler(LogLevelDebug, LogAreaXmlOutgoing | LogAreaXmlIncoming, this);
+ client->logInstance().registerLogHandler(LogLevelDebug, ~0, this);
+}
+
+bool InkscapeClient::connect()
+{
+ return client->connect(false);
+}
+
+void InkscapeClient::disconnect()
+{
+ client->disconnect();
+ connected = false;
+}
+
+bool InkscapeClient::isConnected()
+{
+ return connected;
+}
+
+ConnectionError InkscapeClient::recv()
+{
+ // Return immediately if no data was available on the socket.
+ return client->recv(0);
+}
+
+void InkscapeClient::send(Tag *tag)
+{
+ client->send(tag);
+}
+
+void InkscapeClient::sendChanges(JID recipient, std::string& sid, std::vector state_changes)
+{
+ //TODO
+ // client->send(something)
+ //TODO sxe_manager->sendChanges(recipient, sid, state_changes);
+}
+
+int InkscapeClient::runLoop(void *data)
+{
+ InkscapeClient *client = static_cast(data);
+ ConnectionError err = client->recv();
+ if (err != ConnNoError) {
+ printf("Error while receiving on gloox socket: %d\n", err);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+// From ConnectionListener
+void InkscapeClient::onConnect()
+{
+ printf("connected!\n");
+ connected = true;
+}
+
+void InkscapeClient::onDisconnect(ConnectionError e)
+{
+ printf("disconnected\n");
+ connected = false;
+}
+
+bool InkscapeClient::onTLSConnect(const CertInfo& info)
+{
+ printf("accept cert? yes of course\n");
+ return true;
+}
+
+// From LogHandler
+void InkscapeClient::handleLog(LogLevel level, LogArea area, const std::string& message)
+{
+ switch (area) {
+ case LogAreaXmlIncoming:
+ printf("RECV %s\n", message.c_str());
+ break;
+ case LogAreaXmlOutgoing:
+ printf("SEND %s\n", message.c_str());
+ break;
+ default:
+ printf("gloox: %s\n", message.c_str());
+ break;
+ }
+ fflush(stdout);
+}
+
+void InkscapeClient::handleSessionAction(Jingle::Action action, Jingle::Session* session, const Jingle::Session::Jingle* jingle)
+{
+ printf("handleSessionAction(action=%d, session=%p, jingle=%p)\n", action, session, jingle);
+
+ switch (action) {
+ case Jingle::SessionInitiate: {
+ std::string name;
+ printf("plugins: %zd\n", jingle->plugins().size());
+ for (const Jingle::Plugin* p : jingle->plugins()) {
+ printf("- %p\n", p);
+ // XXX: Don’t assume this is a Jingle::Content…
+ const Jingle::Content* content = reinterpret_cast(p);
+ name = content->name();
+ break;
+ }
+ printf("that’s it!\n");
+ fflush(stdout);
+ if (true/*accept*/) {
+ std::list plugins_list;
+ SvgApplication* description = new SvgApplication();
+ plugins_list.push_front(description);
+ bool ret = session->sessionAccept(new Jingle::Content(name, plugins_list));
+ printf("accepted? %d\n", ret);
+ } else {
+ bool ret = session->sessionTerminate(new Jingle::Session::Reason(Jingle::Session::Reason::UnsupportedApplications));
+ printf("terminated? %d\n", ret);
+ }
+ fflush(stdout);
+ break;
+ }
+ default:
+ printf("Unhandled…\n");
+ break;
+ }
+}
+
+void InkscapeClient::handleSessionActionError(Jingle::Action action, Jingle::Session* session, const Error* error )
+{
+ printf("handleSessionActionError(action=%d, session=%p, error=%p)\n", action, session, error);
+}
+
+void InkscapeClient::handleIncomingSession(Jingle::Session* session)
+{
+ printf("handleIncomingSession(session=%p)\n", session);
+}
+/*
+std::vector InkscapeClient::getCurrentState(const std::string& session, const std::string& id)
+{
+ printf("getCurrentState(session=%s, id=%s)\n", session.c_str(), id.c_str());
+ std::vector state;
+ Sxe::DocumentBegin document_begin = {
+ .prolog = "data:image/svg+xml,",
+ };
+ StateChangeType begin = {
+ .type = StateChangeTypeDocumentBegin,
+ .document_begin = document_begin,
+ };
+ state.push_back(begin);
+ m_rid = get_uuid();
+ gloox::New new_ = gloox::New::Element(
+ /*rid* / m_rid.c_str(),
+ /*ns* / "http://www.w3.org/2000/svg",
+ /*name* / "svg"
+ );
+ StateChangeType change = {
+ .type = StateChangeTypeNew,
+ .new_ = new_,
+ };
+ state.push_back(change);
+ Sxe::DocumentEnd document_end = {
+ .last_sender = "foo@bar/baz",
+ .last_id = "unknown",
+ };
+ StateChangeType end = {
+ .type = StateChangeTypeDocumentEnd,
+ .document_end = document_end,
+ };
+ state.push_back(end);
+ return state;
+}*/
+
+void XMPPObserver::notifyUndoCommitEvent(Event *ee)
+{
+ XML::Event *e = ee->event;
+ std::cout << "UndoCommitEvent" << std::endl;
+
+ while (e) {
+ // printf("DOCUMENT %p\n", e->repr->document());
+ if (e->repr)
+ printf("AFFECTED %s\n", e->repr->attribute("id"));
+ XML::EventAdd *eadd;
+ XML::EventDel *edel;
+ XML::EventChgAttr *echga;
+ XML::EventChgContent *echgc;
+ XML::EventChgOrder *echgo;
+ XML::EventChgElementName *echgn;
+
+ if ((eadd = dynamic_cast(e))) {
+ std::cout << "EventAdd" << std::endl;
+ sp_repr_write_stream(eadd->child, *writer, 0, false, GQuark(0), 0, 0);
+ printf("\n");
+ XML::Node *node = eadd->child;
+
+ std::string rid = get_uuid();
+ std::string name = node->name();
+ if (name.substr(0, 4) != "svg:") {
+ printf("Wrong prefix \"%s\"!\n", name.substr(0, 4).c_str());
+ abort();
+ }
+ name = name.substr(4);
+
+ Sxe::New new_ = {
+ /*rid*/ rid.c_str(),
+ /*parent*/ client->m_rid.c_str(),
+ /*ns*/ "http://www.w3.org/2000/svg",
+ /*name*/ name.c_str(),
+ "",
+ ""
+ };
+ Sxe::StateChange change = {
+ .type = Sxe::StateChangeNew,
+ .new_ = new_
+ };
+ std::vector state_changes = {};
+ state_changes.push_back(change);
+
+ // XXX: huge hack to keep all rids on the stack…
+ size_t num_attrs = 0;
+ for (auto it : node->attributeList()) {
+ ++num_attrs;
+ }
+ std::vector rids;
+ rids.reserve(num_attrs);
+
+ size_t cur_attr = 0;
+ for (auto it : node->attributeList()) {
+ rids.push_back(get_uuid());
+
+ Sxe::New new_ = {
+ /*rid*/ rids[cur_attr].c_str(),
+ /*parent*/ rid.c_str(),
+ /*name*/ g_quark_to_string(it.key),
+ /*chdata*/ it.value,
+ "",
+ ""
+ };
+ Sxe::StateChange change = {
+ .type = Sxe::StateChangeNew,
+ .new_ = new_,
+ };
+ state_changes.push_back(change);
+ ++cur_attr;
+ }
+
+ std::vector copy(state_changes);
+ Sxe* coucou = new Sxe("session", "id", Sxe::SxeStateOffer, {XMLNS_SXE}, copy);
+ fprintf(stderr, "coucou: %s\n", coucou->tag()->xml().c_str());
+
+ //Message msg(Message::Normal, JID("linkmauve@linkmauve.fr"));
+ //msg.addExtension(new Sxe("session", "id", Sxe::TypeState, {}, state_changes));
+ std::string sid = "foo";
+ client->sendChanges(JID(client->document_jid), sid, state_changes);
+ printf("coucou\n");
+ } else if ((edel = dynamic_cast(e))) {
+ std::cout << "EventDel" << std::endl;
+ sp_repr_write_stream(edel->child, *writer, 0, false, GQuark(0), 0, 0);
+ printf("\n");
+
+ Sxe::StateChange change = {
+ .type = Sxe::StateChangeRemove,
+ .remove = Sxe::Remove {
+ .target = "coucou",
+ },
+ };
+ std::vector state_changes = {};
+ state_changes.push_back(change);
+
+ Message msg(Message::Normal, client->jid());
+ msg.addExtension(new Sxe("session", "id", Sxe::SxeState, {}, state_changes));
+ client->send(msg.tag());
+ } else if ((echga = dynamic_cast(e))) {
+ std::cout << "EventChgAttr" << std::endl;
+ printf("%s from %s to %s\n", g_quark_to_string(echga->key), &*(echga->oldval), &*(echga->newval));
+ } else if ((echgc = dynamic_cast(e))) {
+ std::cout << "EventChgContent" << std::endl;
+ printf("%s to %s\n", &*(echgc->oldval), &*(echgc->newval));
+ } else if ((echgo = dynamic_cast(e))) {
+ std::cout << "EventChgOrder" << std::endl;
+ } else if ((echgn = dynamic_cast(e))) {
+ std::cout << "EventChgElementName" << std::endl;
+ } else {
+ std::cout << "Unknown event" << std::endl;
+ }
+
+ e = e->next;
+ }
+}
+void XMPPObserver::notifyUndoEvent(Event *e)
+{
+ std::cout << "UndoEvent" << std::endl;
+ this->notifyUndoCommitEvent(e);
+}
+void XMPPObserver::notifyRedoEvent(Event *e)
+{
+ std::cout << "RedoEvent" << std::endl;
+ this->notifyUndoCommitEvent(e);
+}
+void XMPPObserver::notifyClearUndoEvent() { std::cout << "ClearUndoEvent" << std::endl; }
+void XMPPObserver::notifyClearRedoEvent() { std::cout << "ClearRedoEvent" << std::endl; }
+
+XMPPObserver::XMPPObserver(std::shared_ptr client)
+ : client(client)
+{}
+
+/**
+ \brief A function to allocated anything -- just an example here
+ \return Whether the load was successful
+*/
+bool XMPP::load(Inkscape::Extension::Extension * module)
+{
+ enabled = false;
+ _event_source = 0;
+
+ return TRUE;
+}
+
+/**
+ \brief This actually draws the grid.
+ \param module The effect that was called (unused)
+ \param document What should be edited.
+*/
+void XMPP::effect(Inkscape::Extension::Effect *module, SPDesktop *desktop,
+ Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ std::cout << (enabled ? "disabling" : "enabling") << std::endl;
+ if (!enabled) {
+ JID jid(module->get_param_string("JID"));
+ const char *password = module->get_param_string("pw");
+
+ client = std::make_shared(jid, password);
+ client->document_jid = module->get_param_string("docJID");
+ client->connect();
+
+ // TODO: find a better way to integrate gloox’s fd into the main loop.
+ _event_source = g_timeout_add(16, &InkscapeClient::runLoop, client.get());
+
+ obs = std::unique_ptr(new XMPPObserver(client));
+ obs->writer = std::unique_ptr(new IO::StdWriter());
+ desktop->doc()->addUndoObserver(*obs);
+ } else {
+ g_source_remove(_event_source);
+ client->disconnect();
+ desktop->doc()->removeUndoObserver(*obs);
+ obs.reset();
+ client.reset();
+ }
+ enabled = !enabled;
+}
+
+
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/plugins/xmpp/xmpp.h b/src/extension/plugins/xmpp/xmpp.h
new file mode 100644
index 0000000000000000000000000000000000000000..65b57db0c8dd2e34c1856b50fe12adeac5447842
--- /dev/null
+++ b/src/extension/plugins/xmpp/xmpp.h
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2019, 2024 Marc Jeanmougin
+ * Copyright (C) 2020 Emmanuel Gil Peyrot
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __TRUC_H
+
+#include "extension/implementation/implementation.h"
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "inkscape-version.cpp"
+#include "undo-stack-observer.h"
+#include "io/stream/inkscapestream.h"
+#include "xml/event.h"
+
+namespace gloox {
+class Client;
+}
+
+namespace Inkscape {
+namespace Extension {
+
+class Effect;
+class Extension;
+
+namespace Internal {
+
+class InkscapeClient : gloox::ConnectionListener, gloox::LogHandler, gloox::Jingle::SessionHandler {
+public:
+ InkscapeClient(gloox::JID jid, const std::string& password);
+ bool connect();
+ void disconnect();
+ bool isConnected();
+ gloox::ConnectionError recv();
+ void send(gloox::Tag *tag);
+ void sendChanges(gloox::JID recipient, std::string& sid, std::vector state_changes);
+ static int runLoop(void *data);
+ gloox::JID jid() {return client->jid();}
+
+private:
+ // From ConnectionListener
+ void onConnect() override;
+ void onDisconnect(gloox::ConnectionError e) override;
+ bool onTLSConnect(const gloox::CertInfo& info) override;
+
+ // From LogHandler
+ void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message) override;
+
+ // From Jingle::SessionHandler
+ void handleSessionAction(gloox::Jingle::Action action, gloox::Jingle::Session* session, const gloox::Jingle::Session::Jingle* jingle) override;
+ void handleSessionActionError(gloox::Jingle::Action action, gloox::Jingle::Session* session, const gloox::Error* error) override;
+ void handleIncomingSession(gloox::Jingle::Session* session) override;
+
+ // std::vector getCurrentState(const std::string& session, const std::string& id);
+
+private:
+ std::unique_ptr client;
+ std::unique_ptr session_manager;
+ bool connected;
+
+// XXX: hack
+public:
+ std::string m_rid;
+ std::string document_jid;
+};
+
+
+class XMPPObserver : public UndoStackObserver {
+ void notifyUndoCommitEvent(Event* log) override;
+ void notifyUndoEvent(Event* log) override;
+ void notifyRedoEvent(Event* log) override;
+ void notifyClearUndoEvent() override;
+ void notifyClearRedoEvent() override;
+
+public:
+ XMPPObserver(std::shared_ptr client);
+
+ std::unique_ptr writer;
+ std::shared_ptr client;
+};
+
+
+class XMPP : public Inkscape::Extension::Implementation::Implementation {
+
+
+public:
+ bool load(Inkscape::Extension::Extension *module) override;
+ void effect(Inkscape::Extension::Effect *module, SPDesktop *desktop, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+
+private:
+ std::unique_ptr obs;
+ std::shared_ptr client;
+ unsigned int _event_source;
+ bool enabled;
+};
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+extern "C" G_MODULE_EXPORT Inkscape::Extension::Implementation::Implementation* GetImplementation() { return new Inkscape::Extension::Internal::XMPP(); }
+extern "C" G_MODULE_EXPORT const gchar* GetInkscapeVersion() { return Inkscape::version_string; }
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :