Mercurial > xmpp-account-manager
diff strophe.js @ 0:2a8d4e8600d0
Initial commit.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Fri, 21 Dec 2018 21:34:17 +0100 |
parents | |
children |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/strophe.js @@ -0,0 +1,6101 @@ +(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 <body> tag when + * passed to <Strophe.Connection.xmlInput> or <Strophe.Connection.xmlOutput>. + * To strip this tag, User code can set <Strophe.Bosh.strip> to "body": + * + * > Strophe.Bosh.prototype.strip = "body"; + * + * This will enable stripping of the body tag in both + * <Strophe.Connection.xmlInput> and <Strophe.Connection.xmlOutput>. + */ + strip: null, + + /** PrivateFunction: _buildBody + * _Private_ helper function to generate the <body/> wrapper for BOSH. + * + * Returns: + * A Strophe.Builder with a <body/> 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 <message/> element as the root. + * + * Parameters: + * (Object) attrs - The <message/> element attributes in object notation. + * + * Returns: + * A new Strophe.Builder object. + */ + + +function $msg(attrs) { + return new Strophe.Builder("message", attrs); +} +/** Function: $iq + * Create a Strophe.Builder with an <iq/> element as the root. + * + * Parameters: + * (Object) attrs - The <iq/> element attributes in object notation. + * + * Returns: + * A new Strophe.Builder object. + */ + + +function $iq(attrs) { + return new Strophe.Builder("iq", attrs); +} +/** Function: $pres + * Create a Strophe.Builder with a <presence/> element as the root. + * + * Parameters: + * (Object) attrs - The <presence/> element attributes in object notation. + * + * Returns: + * A new Strophe.Builder object. + */ + + +function $pres(attrs) { + return new Strophe.Builder("presence", attrs); +} +/** Class: Strophe + * An object container for all Strophe library functions. + * + * This class is just a container for all the objects and constants + * used in the library. It is not meant to be instantiated, but to + * provide a namespace for library objects, constants, and functions. + */ + + +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, "'"); + 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, "\\3c").replace(/>/g, "\\3e").replace(/@/g, "\\40"); + }, + + /** Function: unescapeNode + * Unescape a node part (also called local part) of a JID. + * + * Parameters: + * (String) node - A node (or local part). + * + * Returns: + * An unescaped node (or local part). + */ + unescapeNode: function 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 += "<![CDATA[" + child.nodeValue + "]]>"; + } + } + + 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 + * > <iq to='you' from='me' type='get' id='1'> + * > <query xmlns='strophe:example'> + * > <example/> + * > </query> + * > </iq> + * 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 <iq/> elements. + * + * All <iq/> 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 + * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here. + * + * BOSH-Connections will have all stanzas wrapped in a <body> tag. See + * <Strophe.Bosh.strip> 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 + * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here. + * + * BOSH-Connections will have all stanzas wrapped in a <body> tag. See + * <Strophe.Bosh.strip> 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 <iq type='get'/> + * for legacy authentication. It builds an authentication <iq/> 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 + * <iq/> 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 <priority> for information about changing priority and <test> for informatian on + * how to disable a mechanism. + * + * By default, all mechanisms are enabled and the priorities are + * + * 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 <SASLMechanism> 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 <SASL mechanisms> 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 <SASL mechanisms> 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 <stream> start tag for WebSockets + * + * Returns: + * A Strophe.Builder with a <stream> 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 <open /> tag for errors. + * + * Disconnects if there is an error and returns false, true otherwise. + * + * Parameters: + * (Node) message - Stanza containing the <open /> tag. + */ + _handleStreamStart: function _handleStreamStart(message) { + var error = false; // Check for errors in the <open /> tag + + var ns = message.getAttribute("xmlns"); + + if (typeof ns !== "string") { + error = "Missing xmlns in <open />"; + } else if (ns !== Strophe.NS.FRAMING) { + error = "Wrong xmlns in <open />: " + ns; + } + + var ver = message.getAttribute("version"); + + if (typeof ver !== "string") { + error = "Missing version in <open />"; + } else if (ver !== "1.0") { + error = "Wrong version in <open />: " + 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("<open ") === 0 || message.data.indexOf("<?xml") === 0) { + // Strip the XML Declaration, if there is one + var data = message.data.replace(/^(<\?.*?\?>\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("<close ") === 0) { + // <close xmlns="urn:ietf:params:xml:ns:xmpp-framing /> + // Parse the raw string to an XML element + var parsedMessage = new DOMParser().parseFromString(message.data, "text/xml").documentElement; // Report this input to the raw and xml handlers + + this._conn.xmlInput(parsedMessage); + + this._conn.rawInput(message.data); + + var see_uri = parsedMessage.getAttribute("see-other-uri"); + + if (see_uri) { + var service = this._conn.service; // Valid scenarios: WSS->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 <close /> 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 <stream> tag. + * This is used so Strophe can process stanzas from WebSockets like BOSH + */ + _streamWrap: function _streamWrap(stanza) { + return "<wrapper>" + stanza + '</wrapper>'; + }, + + /** 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 + * <stream:stream version='1.0' + * xml:lang='en' + * xmlns='jabber:client' + * xmlns:stream='http://etherx.jabber.org/streams' + * id='3697395463' + * from='SERVER'> + * + * The first stanza will always fail to be parsed. + * + * Additionally, the seconds stanza will always be <stream:features> 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 = '<close xmlns="urn:ietf:params:xml:ns:xmpp-framing" />'; + + 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("<open ") === 0) { + // This handles stream restarts + elem = new DOMParser().parseFromString(message.data, "text/xml").documentElement; + + if (!this._handleStreamStart(elem)) { + return; + } + } else { + var data = this._streamWrap(message.data); + + elem = new DOMParser().parseFromString(data, "text/xml").documentElement; + } + + if (this._check_streamerror(elem, Strophe.Status.ERROR)) { + return; + } //handle unavailable presence stanza before disconnecting + + + if (this._conn.disconnecting && elem.firstChild.nodeName === "presence" && elem.firstChild.getAttribute("type") === "unavailable") { + this._conn.xmlInput(elem); + + this._conn.rawInput(Strophe.serialize(elem)); // if we are already disconnecting we will ignore the unavailable stanza and + // wait for the </stream:stream> 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