diff --git a/src/bosh.js b/src/bosh.js index c2ba53f2..2fbc03d4 100644 --- a/src/bosh.js +++ b/src/bosh.js @@ -8,20 +8,24 @@ /** * @typedef {import("./connection.js").default} Connection - * @typedef {import("./request.js").default} Request */ - -import Strophe from './core.js'; +import log from './log.js'; import Builder, { $build } from './builder.js'; +import Request from './request.js'; +import {getBareJidFromJid, getDomainFromJid, getNodeFromJid} from './utils.js'; +import { Status, NS } from './constants.js'; + +let timeoutMultiplier = 1.1; +let secondaryTimeoutMultiplier = 0.1; /** * _Private_ helper class that handles BOSH Connections - * The Strophe.Bosh class is used internally by Strophe.Connection + * The Bosh class is used internally by Connection * to encapsulate BOSH sessions. It is not meant to be used from user's code. */ class Bosh { /** - * @param {Connection} connection - The Strophe.Connection that will use BOSH. + * @param {Connection} connection - The Connection that will use BOSH. */ constructor(connection) { this._conn = connection; @@ -39,18 +43,18 @@ class Bosh { /** * BOSH-Connections will have all stanzas wrapped in a tag when - * passed to {@link Strophe.Connection#xmlInput|xmlInput()} or {@link Strophe.Connection#xmlOutput|xmlOutput()}. - * To strip this tag, User code can set {@link Strophe.Bosh#strip|strip} to `true`: + * passed to {@link Connection#xmlInput|xmlInput()} or {@link Connection#xmlOutput|xmlOutput()}. + * To strip this tag, User code can set {@link Bosh#strip|strip} to `true`: * * > // You can set `strip` on the prototype - * > Strophe.Bosh.prototype.strip = true; + * > Bosh.prototype.strip = true; * * > // Or you can set it on the Bosh instance (which is `._proto` on the connection instance. - * > const conn = new Strophe.Connection(); + * > const conn = new Connection(); * > conn._proto.strip = true; * * This will enable stripping of the body tag in both - * {@link Strophe.Connection#xmlInput|xmlInput} and {@link Strophe.Connection#xmlOutput|xmlOutput}. + * {@link Connection#xmlInput|xmlInput} and {@link Connection#xmlOutput|xmlOutput}. * * @property {boolean} [strip=false] */ @@ -61,15 +65,43 @@ class Bosh { this._requests = []; } + /** + * @param {number} m + */ + static setTimeoutMultiplier(m) { + timeoutMultiplier = m; + } + + /** + * @returns {number} + */ + static getTimeoutMultplier() { + return timeoutMultiplier; + } + + /** + * @param {number} m + */ + static setSecondaryTimeoutMultiplier(m) { + secondaryTimeoutMultiplier = m; + } + + /** + * @returns {number} + */ + static getSecondaryTimeoutMultplier() { + return secondaryTimeoutMultiplier; + } + /** * _Private_ helper function to generate the wrapper for BOSH. * @private - * @return {Builder} - A Strophe.Builder with a element. + * @return {Builder} - A Builder with a element. */ _buildBody() { const bodyWrap = $build('body', { 'rid': this.rid++, - 'xmlns': Strophe.NS.HTTPBIND, + 'xmlns': NS.HTTPBIND, }); if (this.sid !== null) { bodyWrap.attrs({ 'sid': this.sid }); @@ -82,7 +114,7 @@ class Bosh { /** * Reset the connection. - * This function is called by the reset function of the Strophe Connection + * This function is called by the reset function of the Connection */ _reset() { this.rid = Math.floor(Math.random() * 4294967295); @@ -120,7 +152,7 @@ class Bosh { 'content': 'text/xml; charset=utf-8', 'ver': '1.6', 'xmpp:version': '1.0', - 'xmlns:xmpp': Strophe.NS.BOSH, + 'xmlns:xmpp': NS.BOSH, }); if (route) { body.attrs({ route }); @@ -128,7 +160,7 @@ class Bosh { const _connect_cb = this._conn._connect_cb; this._requests.push( - new Strophe.Request( + new Request( body.tree(), this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)), Number(body.tree().getAttribute('rid')) @@ -166,7 +198,7 @@ class Bosh { this.rid = rid; this._conn.connect_callback = callback; - this._conn.domain = Strophe.getDomainFromJid(this._conn.jid); + this._conn.domain = getDomainFromJid(this._conn.jid); this._conn.authenticated = true; this._conn.connected = true; @@ -174,7 +206,7 @@ class Bosh { this.hold = hold || this.hold; this.window = wind || this.window; - this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null); + this._conn._changeConnectStatus(Status.ATTACHED, null); } /** @@ -205,10 +237,10 @@ class Bosh { session.jid && (typeof jid === 'undefined' || jid === null || - Strophe.getBareJidFromJid(session.jid) === Strophe.getBareJidFromJid(jid) || + getBareJidFromJid(session.jid) === 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)) + (getNodeFromJid(jid) === null && getDomainFromJid(session.jid) === jid)) ) { this._conn.restored = true; this._attach(session.jid, session.sid, session.rid, callback, wait, hold, wind); @@ -251,18 +283,18 @@ class Bosh { if (typ !== null && typ === 'terminate') { // an error occurred let cond = bodyWrap.getAttribute('condition'); - Strophe.error('BOSH-Connection failed: ' + cond); + log.error('BOSH-Connection failed: ' + cond); const conflict = bodyWrap.getElementsByTagName('conflict'); if (cond !== null) { if (cond === 'remote-stream-error' && conflict.length > 0) { cond = 'conflict'; } - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond); + this._conn._changeConnectStatus(Status.CONNFAIL, cond); } else { - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, 'unknown'); + this._conn._changeConnectStatus(Status.CONNFAIL, 'unknown'); } this._conn._doDisconnect(cond); - return Strophe.Status.CONNFAIL; + return Status.CONNFAIL; } // check to make sure we don't overwrite these if _connect_cb is @@ -342,7 +374,7 @@ class Bosh { */ _hitError(reqStatus) { this.errors++; - Strophe.warn('request errored, status: ' + reqStatus + ', number of errors: ' + this.errors); + log.warn('request errored, status: ' + reqStatus + ', number of errors: ' + this.errors); if (this.errors > 4) { this._conn._onDisconnectTimeout(); } @@ -359,7 +391,7 @@ class Bosh { * @param {connectionCallback} callback */ _no_auth_received(callback) { - Strophe.warn( + log.warn( 'Server did not yet offer a supported authentication ' + 'mechanism. Sending a blank poll request.' ); if (callback) { @@ -369,7 +401,7 @@ class Bosh { } const body = this._buildBody(); this._requests.push( - new Strophe.Request( + new Request( body.tree(), this._onRequestStateChange.bind(this, callback), Number(body.tree().getAttribute('rid')) @@ -399,14 +431,14 @@ class Bosh { } /** - * _Private_ handler called by {@link Strophe.Connection#_onIdle|Strophe.Connection._onIdle()}. + * _Private_ handler called by {@link Connection#_onIdle|Connection._onIdle()}. * Sends all queued Requests or polls with empty Request if there are none. */ _onIdle() { const 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.debug('no requests during idle cycle, sending blank request'); + log.debug('no requests during idle cycle, sending blank request'); data.push(null); } @@ -423,7 +455,7 @@ class Bosh { 'to': this._conn.domain, 'xml:lang': 'en', 'xmpp:restart': 'true', - 'xmlns:xmpp': Strophe.NS.BOSH, + 'xmlns:xmpp': NS.BOSH, }); } else { body.cnode(/** @type {Element} */ (data[i])).up(); @@ -433,7 +465,7 @@ class Bosh { delete this._conn._data; this._conn._data = []; this._requests.push( - new Strophe.Request( + new Request( body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), Number(body.tree().getAttribute('rid')) @@ -445,16 +477,16 @@ class Bosh { if (this._requests.length > 0) { const time_elapsed = this._requests[0].age(); if (this._requests[0].dead !== null) { - if (this._requests[0].timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) { + if (this._requests[0].timeDead() > Math.floor(timeoutMultiplier * this.wait)) { this._throttledRequestHandler(); } } - if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) { - Strophe.warn( + if (time_elapsed > Math.floor(timeoutMultiplier * this.wait)) { + log.warn( 'Request ' + this._requests[0].id + ' timed out, over ' + - Math.floor(Strophe.TIMEOUT * this.wait) + + Math.floor(timeoutMultiplier * this.wait) + ' seconds since last activity' ); this._throttledRequestHandler(); @@ -463,9 +495,9 @@ class Bosh { } /** - * Returns the HTTP status code from a {@link Strophe.Request} + * Returns the HTTP status code from a {@link Request} * @private - * @param {Request} req - The {@link Strophe.Request} instance. + * @param {Request} req - The {@link Request} instance. * @param {number} [def] - The default value that should be returned if no status value was found. */ static _getRequestStatus(req, def) { @@ -476,7 +508,7 @@ class Bosh { } 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); + log.error("Caught an error while retrieving a request's status, " + 'reqStatus: ' + reqStatus); } } if (typeof reqStatus === 'undefined') { @@ -486,7 +518,7 @@ class Bosh { } /** - * _Private_ handler for {@link Strophe.Request} state changes. + * _Private_ handler for {@link 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 @@ -498,7 +530,7 @@ class Bosh { * @param {Request} req - The request that is changing readyState. */ _onRequestStateChange(func, req) { - Strophe.debug('request id ' + req.id + '.' + req.sends + ' state changed to ' + req.xhr.readyState); + log.debug('request id ' + req.id + '.' + req.sends + ' state changed to ' + req.xhr.readyState); if (req.abort) { req.abort = false; return; @@ -523,44 +555,44 @@ class Bosh { if (valid_request || too_many_retries) { // remove from internal queue this._removeRequest(req); - Strophe.debug('request id ' + req.id + ' should now be removed'); + log.debug('request id ' + req.id + ' should now be removed'); } if (reqStatus === 200) { // request succeeded // if request 1 finished, or request 0 finished and request - // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to + // 1 is over _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._requests[0].age() > Math.floor(timeoutMultiplier * this.wait)) ) { this._restartRequest(0); } this._conn.nextValidRid(req.rid + 1); - Strophe.debug('request id ' + req.id + '.' + req.sends + ' got 200'); + log.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'); + log.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._changeConnectStatus(Status.DISCONNECTING, null); this._conn._doDisconnect(); } } else { - Strophe.error('request id ' + req.id + '.' + req.sends + ' error ' + reqStatus + ' happened'); + log.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'); + this._conn._changeConnectStatus(Status.CONNFAIL, 'giving-up'); } } @@ -583,25 +615,25 @@ class Bosh { return; } const time_elapsed = req.age(); - const primary_timeout = !isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait); + const primary_timeout = !isNaN(time_elapsed) && time_elapsed > Math.floor(timeoutMultiplier * this.wait); const secondary_timeout = - req.dead !== null && req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait); + req.dead !== null && req.timeDead() > Math.floor(secondaryTimeoutMultiplier * this.wait); const server_error = req.xhr.readyState === 4 && (reqStatus < 1 || reqStatus >= 500); if (primary_timeout || secondary_timeout || server_error) { if (secondary_timeout) { - Strophe.error(`Request ${this._requests[i].id} timed out (secondary), restarting`); + log.error(`Request ${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); + this._requests[i] = new 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'); + log.debug('request id ' + req.id + '.' + req.sends + ' posting'); try { const content_type = this._conn.options.contentType || 'text/xml; charset=utf-8'; @@ -614,9 +646,9 @@ class Bosh { req.xhr.withCredentials = true; } } catch (e2) { - Strophe.error('XHR open failed: ' + e2.toString()); + log.error('XHR open failed: ' + e2.toString()); if (!this._conn.connected) { - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, 'bad-service'); + this._conn._changeConnectStatus(Status.CONNFAIL, 'bad-service'); } this._conn.disconnect(); return; @@ -642,7 +674,7 @@ class Bosh { if (req.sends > 1) { // Using a cube of the retry number creates a nicely // expanding retry window - const backoff = Math.min(Math.floor(Strophe.TIMEOUT * this.wait), Math.pow(req.sends, 3)) * 1000; + const backoff = Math.min(Math.floor(timeoutMultiplier * this.wait), Math.pow(req.sends, 3)) * 1000; setTimeout(function () { // XXX: setTimeout should be called only with function expressions (23974bc1) sendFunc(); @@ -660,7 +692,7 @@ class Bosh { } this._conn.rawOutput?.(req.data); } else { - Strophe.debug( + log.debug( '_processRequest: ' + (i === 0 ? 'first' : 'second') + ' request has readyState of ' + @@ -675,7 +707,7 @@ class Bosh { * @param {Request} req - The request to remove. */ _removeRequest(req) { - Strophe.debug('removing request'); + log.debug('removing request'); for (let i = this._requests.length - 1; i >= 0; i--) { if (req === this._requests[i]) { this._requests.splice(i, 1); @@ -729,7 +761,7 @@ class Bosh { * @param {Element|Builder} [pres] */ _sendTerminate(pres) { - Strophe.debug('_sendTerminate was called'); + log.debug('_sendTerminate was called'); const body = this._buildBody().attrs({ type: 'terminate' }); const el = pres instanceof Builder ? pres.tree() : pres; @@ -737,7 +769,7 @@ class Bosh { if (pres) { body.cnode(el); } - const req = new Strophe.Request( + const req = new Request( body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), Number(body.tree().getAttribute('rid')) @@ -774,9 +806,9 @@ class Bosh { */ _throttledRequestHandler() { if (!this._requests) { - Strophe.debug('_throttledRequestHandler called with ' + 'undefined requests'); + log.debug('_throttledRequestHandler called with ' + 'undefined requests'); } else { - Strophe.debug('_throttledRequestHandler called with ' + this._requests.length + ' requests'); + log.debug('_throttledRequestHandler called with ' + this._requests.length + ' requests'); } if (!this._requests || this._requests.length === 0) { diff --git a/src/builder.js b/src/builder.js index 97c7fd94..ccf8e1f9 100644 --- a/src/builder.js +++ b/src/builder.js @@ -1,5 +1,5 @@ -import { NS } from './constants.js'; -import { copyElement, createHtml, serialize, xmlElement, xmlGenerator, xmlTextNode } from './utils.js'; +import { ElementType, NS } from './constants.js'; +import { copyElement, createHtml, xmlElement, xmlGenerator, xmlTextNode, xmlescape } from './utils.js'; /** * Create a {@link Strophe.Builder} @@ -95,6 +95,48 @@ class Builder { this.node = this.nodeTree; } + /** + * Render a DOM element and all descendants to a String. + * @param {Element|Builder} elem - A DOM element. + * @return {string} - The serialized element tree as a String. + */ + static serialize(elem) { + if (!elem) return null; + + const el = elem instanceof Builder ? elem.tree() : elem; + + const names = [...Array(el.attributes.length).keys()].map((i) => el.attributes[i].nodeName); + names.sort(); + let result = names.reduce( + (a, n) => `${a} ${n}="${xmlescape(el.attributes.getNamedItem(n).value)}"`, + `<${el.nodeName}` + ); + + if (el.childNodes.length > 0) { + result += '>'; + for (let i = 0; i < el.childNodes.length; i++) { + const child = el.childNodes[i]; + switch (child.nodeType) { + case ElementType.NORMAL: + // normal element, so recurse + result += Builder.serialize(/** @type {Element} */ (child)); + break; + case ElementType.TEXT: + // text element to escape values + result += xmlescape(child.nodeValue); + break; + case ElementType.CDATA: + // cdata section so don't escape values + result += ''; + } + } + result += ''; + } else { + result += '/>'; + } + return result; + } + /** * Return the DOM tree. * @@ -117,7 +159,7 @@ class Builder { * @return {string} The serialized DOM tree in a String. */ toString() { - return serialize(this.nodeTree); + return Builder.serialize(this.nodeTree); } /** diff --git a/src/connection.js b/src/connection.js index 9adfaf96..5130aed8 100644 --- a/src/connection.js +++ b/src/connection.js @@ -1,11 +1,39 @@ +import { atob, btoa } from 'abab'; import Handler from './handler.js'; import TimedHandler from './timed-handler.js'; import Builder, { $build, $iq, $pres } from './builder.js'; -import { Status } from './constants.js'; -import Strophe from './core.js'; -import { addCookies, getText } from './utils.js'; -import { atob, btoa } from 'abab'; +import log from './log.js'; +import { ErrorCondition, NS, Status } from './constants.js'; +import SASLAnonymous from './sasl-anon.js'; +import SASLExternal from './sasl-external.js'; +import SASLOAuthBearer from './sasl-oauthbearer.js'; +import SASLPlain from './sasl-plain.js'; +import SASLSHA1 from './sasl-sha1.js'; +import SASLSHA256 from './sasl-sha256.js'; +import SASLSHA384 from './sasl-sha384.js'; +import SASLSHA512 from './sasl-sha512.js'; +import SASLXOAuth2 from './sasl-xoauth2.js'; +import { + addCookies, + forEachChild, + getBareJidFromJid, + getDomainFromJid, + getNodeFromJid, + getResourceFromJid, + getText, + handleError, +} from './utils.js'; import { SessionError } from './errors.js'; +import Bosh from './bosh.js'; +import WorkerWebsocket from './worker-websocket.js'; +import Websocket from './websocket.js'; + +/** + * _Private_ variable Used to store plugin names that need + * initialization during Connection construction. + * @type {Object.} + */ +const connectionPlugins = {}; /** * @typedef {import("./sasl.js").default} SASLMechanism @@ -21,21 +49,21 @@ import { SessionError } from './errors.js'; * * It supports various authentication mechanisms (e.g. SASL PLAIN, SASL SCRAM), * and more can be added via - * {@link Strophe.Connection#registerSASLMechanisms|registerSASLMechanisms()}. + * {@link Connection#registerSASLMechanisms|registerSASLMechanisms()}. * - * After creating a Strophe.Connection object, the user will typically - * call {@link Strophe.Connection#connect|connect()} with a user supplied callback + * After creating a Connection object, the user will typically + * call {@link Connection#connect|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 - * {@link Strophe.Connection#addHandler|addHandler()} and - * {@link Strophe.Connection#addTimedHandler|addTimedHandler()}. + * {@link Connection#addHandler|addHandler()} and + * {@link Connection#addTimedHandler|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 {@link Strophe.Connection#send|send()}. + * To send data to the connection, use {@link Connection#send|send()}. * * @memberof Strophe */ @@ -68,9 +96,9 @@ class Connection { * necessary cookies. * @property {SASLMechanism[]} [mechanisms] * Allows you to specify the SASL authentication mechanisms that this - * instance of Strophe.Connection (and therefore your XMPP client) will support. + * instance of Connection (and therefore your XMPP client) will support. * - * The value must be an array of objects with {@link Strophe.SASLMechanism} + * The value must be an array of objects with {@link SASLMechanism} * prototypes. * * If nothing is specified, then the following mechanisms (and their @@ -90,7 +118,7 @@ class Connection { * * @property {boolean} [explicitResourceBinding] * If `explicitResourceBinding` is set to `true`, then the XMPP client - * needs to explicitly call {@link Strophe.Connection.bind} once the XMPP + * needs to explicitly call {@link Connection.bind} once the XMPP * server has advertised the `urn:ietf:propertys:xml:ns:xmpp-bind` feature. * * Making this step explicit allows client authors to first finish other @@ -124,7 +152,7 @@ class Connection { * * To run the websocket connection inside a shared worker. * This allows you to share a single websocket-based connection between - * multiple Strophe.Connection instances, for example one per browser tab. + * multiple Connection instances, for example one per browser tab. * * The script to use is the one in `src/shared-connection-worker.js`. * @@ -170,7 +198,7 @@ class Connection { */ /** - * Create and initialize a {@link Strophe.Connection} object. + * Create and initialize a {@link Connection} object. * * The transport-protocol for this connection will be chosen automatically * based on the given service parameter. URLs starting with "ws://" or @@ -279,7 +307,7 @@ class Connection { this.send( $iq({ type: 'error', id: iq.getAttribute('id') }) .c('error', { 'type': 'cancel' }) - .c('service-unavailable', { 'xmlns': Strophe.NS.STANZAS }) + .c('service-unavailable', { 'xmlns': NS.STANZAS }) ), null, 'iq', @@ -287,10 +315,10 @@ class Connection { ); // initialize plugins - for (const k in Strophe._connectionPlugins) { - if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) { + for (const k in connectionPlugins) { + if (Object.prototype.hasOwnProperty.call(connectionPlugins, k)) { const F = function () {}; - F.prototype = Strophe._connectionPlugins[k]; + F.prototype = connectionPlugins[k]; // @ts-ignore this[k] = new F(); // @ts-ignore @@ -299,21 +327,30 @@ class Connection { } } + /** + * Extends the Connection object with the given plugin. + * @param {string} name - The name of the extension. + * @param {Object} ptype - The plugin's prototype. + */ + static addConnectionPlugin(name, ptype) { + connectionPlugins[name] = ptype; + } + /** * Select protocal based on this.options or this.service */ setProtocol() { const proto = this.options.protocol || ''; if (this.options.worker) { - this._proto = new Strophe.WorkerWebsocket(this); + this._proto = new WorkerWebsocket(this); } else if ( this.service.indexOf('ws:') === 0 || this.service.indexOf('wss:') === 0 || proto.indexOf('ws') === 0 ) { - this._proto = new Strophe.Websocket(this); + this._proto = new Websocket(this); } else { - this._proto = new Strophe.Bosh(this); + this._proto = new Bosh(this); } } @@ -490,9 +527,9 @@ class Connection { connect(jid, pass, callback, wait, hold, route, authcid, disconnection_timeout = 3000) { this.jid = jid; /** Authorization identity */ - this.authzid = Strophe.getBareJidFromJid(this.jid); + this.authzid = getBareJidFromJid(this.jid); /** Authentication identity (User name) */ - this.authcid = authcid || Strophe.getNodeFromJid(this.jid); + this.authcid = authcid || getNodeFromJid(this.jid); /** Authentication identity (User password) */ this.pass = pass; @@ -510,7 +547,7 @@ class Connection { this.disconnection_timeout = disconnection_timeout; // parse jid for domain - this.domain = Strophe.getDomainFromJid(this.jid); + this.domain = getDomainFromJid(this.jid); this._changeConnectStatus(Status.CONNECTING, null); @@ -541,9 +578,9 @@ class Connection { * allowed range of request ids that are valid. The default is 5. */ attach(jid, sid, rid, callback, wait, hold, wind) { - if (this._proto instanceof Strophe.Bosh && typeof jid === 'string') { + if (this._proto instanceof Bosh && typeof jid === 'string') { return this._proto._attach(jid, sid, rid, callback, wait, hold, wind); - } else if (this._proto instanceof Strophe.WorkerWebsocket && typeof jid === 'function') { + } else if (this._proto instanceof WorkerWebsocket && typeof jid === 'function') { const callback = jid; return this._proto._attach(callback); } else { @@ -555,7 +592,7 @@ class Connection { * Attempt to restore a cached BOSH session. * * This function is only useful in conjunction with providing the - * "keepalive":true option when instantiating a new {@link Strophe.Connection}. + * "keepalive":true option when instantiating a new {@link 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 @@ -578,7 +615,7 @@ class Connection { * allowed range of request ids that are valid. The default is 5. */ restore(jid, callback, wait, hold, wind) { - if (!(this._proto instanceof Strophe.Bosh) || !this._sessionCachingSupported()) { + if (!(this._proto instanceof Bosh) || !this._sessionCachingSupported()) { throw new SessionError('The "restore" method can only be used with a BOSH connection.'); } @@ -592,7 +629,7 @@ class Connection { * using BOSH. */ _sessionCachingSupported() { - if (this._proto instanceof Strophe.Bosh) { + if (this._proto instanceof Bosh) { if (!JSON) { return false; } @@ -612,7 +649,7 @@ class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.xmlInput = function (elem) { + * > Connection.xmlInput = function (elem) { * > (user code) * > }; * @@ -620,7 +657,7 @@ class Connection { * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See - * if you want to strip this tag. + * if you want to strip this tag. * * @param {Node|MessageEvent} elem - The XML data received by the connection. */ @@ -634,7 +671,7 @@ class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.xmlOutput = function (elem) { + * > Connection.xmlOutput = function (elem) { * > (user code) * > }; * @@ -642,7 +679,7 @@ class Connection { * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See - * if you want to strip this tag. + * if you want to strip this tag. * * @param {Element} elem - The XMLdata sent by the connection. */ @@ -656,7 +693,7 @@ class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.rawInput = function (data) { + * > Connection.rawInput = function (data) { * > (user code) * > }; * @@ -672,7 +709,7 @@ class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.rawOutput = function (data) { + * > Connection.rawOutput = function (data) { * > (user code) * > }; * @@ -687,7 +724,7 @@ class Connection { * 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) { + * > Connection.nextValidRid = function (rid) { * > (user code) * > }; * @@ -902,7 +939,7 @@ class Connection { * @return {TimedHandler} A reference to the handler that can be used to remove it. */ addTimedHandler(period, handler) { - const thand = new Strophe.TimedHandler(period, handler); + const thand = new TimedHandler(period, handler); this.addTimeds.push(thand); return thand; } @@ -1018,22 +1055,22 @@ class Connection { /** * Register the SASL mechanisms which will be supported by this instance of - * Strophe.Connection (i.e. which this XMPP client will support). - * @param {SASLMechanism[]} mechanisms - Array of objects with Strophe.SASLMechanism prototypes + * Connection (i.e. which this XMPP client will support). + * @param {SASLMechanism[]} mechanisms - Array of objects with SASLMechanism prototypes */ registerSASLMechanisms(mechanisms) { this.mechanisms = {}; ( mechanisms || [ - Strophe.SASLAnonymous, - Strophe.SASLExternal, - Strophe.SASLOAuthBearer, - Strophe.SASLXOAuth2, - Strophe.SASLPlain, - Strophe.SASLSHA1, - Strophe.SASLSHA256, - Strophe.SASLSHA384, - Strophe.SASLSHA512, + SASLAnonymous, + SASLExternal, + SASLOAuthBearer, + SASLXOAuth2, + SASLPlain, + SASLSHA1, + SASLSHA256, + SASLSHA384, + SASLSHA512, ] ).forEach((m) => this.registerSASLMechanism(m)); } @@ -1065,16 +1102,16 @@ class Connection { disconnect(reason) { this._changeConnectStatus(Status.DISCONNECTING, reason); if (reason) { - Strophe.warn('Disconnect was called because: ' + reason); + log.warn('Disconnect was called because: ' + reason); } else { - Strophe.info('Disconnect was called'); + log.info('Disconnect was called'); } if (this.connected) { let pres = null; this.disconnecting = true; if (this.authenticated) { pres = $pres({ - 'xmlns': Strophe.NS.CLIENT, + 'xmlns': NS.CLIENT, 'type': 'unavailable', }); } @@ -1085,7 +1122,7 @@ class Connection { ); this._proto._disconnect(pres); } else { - Strophe.warn('Disconnect was called before Strophe connected to the server'); + log.warn('Disconnect was called before Strophe connected to the server'); this._proto._abortAllRequests(); this._doDisconnect(); } @@ -1101,15 +1138,15 @@ class Connection { */ _changeConnectStatus(status, condition, elem) { // notify all plugins listening for status changes - for (const k in Strophe._connectionPlugins) { - if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) { + for (const k in connectionPlugins) { + if (Object.prototype.hasOwnProperty.call(connectionPlugins, k)) { // @ts-ignore const plugin = this[k]; if (plugin.statusChanged) { try { plugin.statusChanged(status, condition); } catch (err) { - Strophe.error(`${k} plugin caused an exception changing status: ${err}`); + log.error(`${k} plugin caused an exception changing status: ${err}`); } } } @@ -1119,8 +1156,8 @@ class Connection { try { this.connect_callback(status, condition, elem); } catch (e) { - Strophe._handleError(e); - Strophe.error(`User connection callback caused an exception: ${e}`); + handleError(e); + log.error(`User connection callback caused an exception: ${e}`); } } } @@ -1143,7 +1180,7 @@ class Connection { this._disconnectTimeout = null; } - Strophe.debug('_doDisconnect was called'); + log.debug('_doDisconnect was called'); this._proto._doDisconnect(); this.authenticated = false; @@ -1181,18 +1218,18 @@ class Connection { return; } - if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { + if (this.xmlInput !== 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 (this.rawInput !== Connection.prototype.rawInput) { if (raw) { this.rawInput(raw); } else { - this.rawInput(Strophe.serialize(elem)); + this.rawInput(Builder.serialize(elem)); } } @@ -1231,14 +1268,14 @@ class Connection { } this._changeConnectStatus(Status.CONNFAIL, cond); } else { - this._changeConnectStatus(Status.CONNFAIL, Strophe.ErrorCondition.UNKNOWN_REASON); + this._changeConnectStatus(Status.CONNFAIL, ErrorCondition.UNKNOWN_REASON); } this._doDisconnect(cond); return; } // send each incoming stanza through the handler chain - Strophe.forEachChild( + forEachChild( elem, null, /** @param {Element} child */ @@ -1256,7 +1293,7 @@ class Connection { } } catch (e) { // if the handler throws an exception, we consider it as false - Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message); + log.warn('Removing Strophe handlers due to uncaught exception: ' + e.message); } return handlers; @@ -1293,7 +1330,7 @@ class Connection { * @param {string} [raw] - The stanza as raw string. */ _connect_cb(req, _callback, raw) { - Strophe.debug('_connect_cb was called'); + log.debug('_connect_cb was called'); this.connected = true; let bodyWrap; @@ -1302,28 +1339,28 @@ class Connection { '_reqToData' in this._proto ? this._proto._reqToData(/** @type {Request} */ (req)) : req ); } catch (e) { - if (e.name !== Strophe.ErrorCondition.BAD_FORMAT) { + if (e.name !== ErrorCondition.BAD_FORMAT) { throw e; } - this._changeConnectStatus(Status.CONNFAIL, Strophe.ErrorCondition.BAD_FORMAT); - this._doDisconnect(Strophe.ErrorCondition.BAD_FORMAT); + this._changeConnectStatus(Status.CONNFAIL, ErrorCondition.BAD_FORMAT); + this._doDisconnect(ErrorCondition.BAD_FORMAT); } if (!bodyWrap) { return; } - if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) { + if (this.xmlInput !== 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 (this.rawInput !== Connection.prototype.rawInput) { if (raw) { this.rawInput(raw); } else { - this.rawInput(Strophe.serialize(bodyWrap)); + this.rawInput(Builder.serialize(bodyWrap)); } } @@ -1335,7 +1372,7 @@ class Connection { // Check for the stream:features tag let hasFeatures; if (bodyWrap.getElementsByTagNameNS) { - hasFeatures = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, 'features').length > 0; + hasFeatures = bodyWrap.getElementsByTagNameNS(NS.STREAM, 'features').length > 0; } else { hasFeatures = bodyWrap.getElementsByTagName('stream:features').length > 0 || @@ -1446,7 +1483,7 @@ class Connection { this._sasl_mechanism.onStart(this); const request_auth_exchange = $build('auth', { - 'xmlns': Strophe.NS.SASL, + 'xmlns': NS.SASL, 'mechanism': this._sasl_mechanism.mechname, }); if (this._sasl_mechanism.isClientFirst) { @@ -1468,7 +1505,7 @@ class Connection { async _sasl_challenge_cb(elem) { const challenge = atob(getText(elem)); const response = await this._sasl_mechanism.onChallenge(this, challenge); - const stanza = $build('response', { 'xmlns': Strophe.NS.SASL }); + const stanza = $build('response', { 'xmlns': NS.SASL }); if (response) stanza.t(btoa(response)); this.send(stanza.tree()); return true; @@ -1479,11 +1516,11 @@ class Connection { * @private */ _attemptLegacyAuth() { - if (Strophe.getNodeFromJid(this.jid) === null) { + if (getNodeFromJid(this.jid) === null) { // we don't have a node, which is required for non-anonymous // client connections - this._changeConnectStatus(Status.CONNFAIL, Strophe.ErrorCondition.MISSING_JID_NODE); - this.disconnect(Strophe.ErrorCondition.MISSING_JID_NODE); + this._changeConnectStatus(Status.CONNFAIL, ErrorCondition.MISSING_JID_NODE); + this.disconnect(ErrorCondition.MISSING_JID_NODE); } else { // Fall back to legacy authentication this._changeConnectStatus(Status.AUTHENTICATING, null); @@ -1494,9 +1531,9 @@ class Connection { 'to': this.domain, 'id': '_auth_1', }) - .c('query', { xmlns: Strophe.NS.AUTH }) + .c('query', { xmlns: NS.AUTH }) .c('username', {}) - .t(Strophe.getNodeFromJid(this.jid)) + .t(getNodeFromJid(this.jid)) .tree() ); } @@ -1519,20 +1556,20 @@ class Connection { // build plaintext auth iq const iq = $iq({ type: 'set', id: '_auth_2' }) - .c('query', { xmlns: Strophe.NS.AUTH }) + .c('query', { xmlns: NS.AUTH }) .c('username', {}) - .t(Strophe.getNodeFromJid(this.jid)) + .t(getNodeFromJid(this.jid)) .up() .c('password') .t(pass); - if (!Strophe.getResourceFromJid(this.jid)) { + if (!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'; + this.jid = getBareJidFromJid(this.jid) + '/strophe'; } - iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid)); + iq.up().c('resource', {}).t(getResourceFromJid(this.jid)); this._addSysHandler(this._auth2_cb.bind(this), null, null, null, '_auth_2'); this.send(iq.tree()); @@ -1566,7 +1603,7 @@ class Connection { return this._sasl_failure_cb(null); } } - Strophe.info('SASL authentication succeeded.'); + log.info('SASL authentication succeeded.'); if (this._sasl_data.keys) { this.scram_keys = this._sasl_data.keys; @@ -1612,7 +1649,7 @@ class Connection { this._addSysHandler( /** @param {Element} elem */ (elem) => wrapper(streamfeature_handlers, elem), - Strophe.NS.STREAM, + NS.STREAM, 'features', null, null @@ -1659,7 +1696,7 @@ class Connection { * https://tools.ietf.org/html/rfc6120#section-7.5 * * If `explicitResourceBinding` was set to a truthy value in the options - * passed to the Strophe.Connection constructor, then this function needs + * passed to the Connection constructor, then this function needs * to be called explicitly by the client author. * * Otherwise it'll be called automatically as soon as the XMPP server @@ -1667,22 +1704,22 @@ class Connection { */ bind() { if (!this.do_bind) { - Strophe.log(Strophe.LogLevel.INFO, `Strophe.Connection.prototype.bind called but "do_bind" is false`); + log.info(`Connection.prototype.bind called but "do_bind" is false`); return; } this._addSysHandler(this._onResourceBindResultIQ.bind(this), null, null, null, '_bind_auth_2'); - const resource = Strophe.getResourceFromJid(this.jid); + const resource = getResourceFromJid(this.jid); if (resource) { this.send( $iq({ type: 'set', id: '_bind_auth_2' }) - .c('bind', { xmlns: Strophe.NS.BIND }) + .c('bind', { xmlns: NS.BIND }) .c('resource', {}) .t(resource) .tree() ); } else { - this.send($iq({ type: 'set', id: '_bind_auth_2' }).c('bind', { xmlns: Strophe.NS.BIND }).tree()); + this.send($iq({ type: 'set', id: '_bind_auth_2' }).c('bind', { xmlns: NS.BIND }).tree()); } } @@ -1694,11 +1731,11 @@ class Connection { */ _onResourceBindResultIQ(elem) { if (elem.getAttribute('type') === 'error') { - Strophe.warn('Resource binding failed.'); + log.warn('Resource binding failed.'); const conflict = elem.getElementsByTagName('conflict'); let condition; if (conflict.length > 0) { - condition = Strophe.ErrorCondition.CONFLICT; + condition = ErrorCondition.CONFLICT; } this._changeConnectStatus(Status.AUTHFAIL, condition, elem); return false; @@ -1717,7 +1754,7 @@ class Connection { } } } else { - Strophe.warn('Resource binding failed.'); + log.warn('Resource binding failed.'); this._changeConnectStatus(Status.AUTHFAIL, null, elem); return false; } @@ -1735,13 +1772,13 @@ class Connection { _establishSession() { if (!this.do_session) { throw new Error( - `Strophe.Connection.prototype._establishSession ` + - `called but apparently ${Strophe.NS.SESSION} wasn't advertised by the server` + `Connection.prototype._establishSession ` + + `called but apparently ${NS.SESSION} wasn't advertised by the server` ); } this._addSysHandler(this._onSessionResultIQ.bind(this), null, null, null, '_session_auth_2'); - this.send($iq({ type: 'set', id: '_session_auth_2' }).c('session', { xmlns: Strophe.NS.SESSION }).tree()); + this.send($iq({ type: 'set', id: '_session_auth_2' }).c('session', { xmlns: NS.SESSION }).tree()); } /** @@ -1765,7 +1802,7 @@ class Connection { this._changeConnectStatus(Status.CONNECTED, null); } else if (elem.getAttribute('type') === 'error') { this.authenticated = false; - Strophe.warn('Session creation failed.'); + log.warn('Session creation failed.'); this._changeConnectStatus(Status.AUTHFAIL, null, elem); return false; } @@ -1816,7 +1853,7 @@ class Connection { /** * _Private_ function to add a system level timed handler. * - * This function is used to add a Strophe.TimedHandler for the + * This function is used to add a TimedHandler for the * library code. System timed handlers are allowed to run before * authentication is complete. * @param {number} period - The period of the handler. @@ -1856,7 +1893,7 @@ class Connection { * @return {false} `false` to remove the handler. */ _onDisconnectTimeout() { - Strophe.debug('_onDisconnectTimeout was called'); + log.debug('_onDisconnectTimeout was called'); this._changeConnectStatus(Status.CONNTIMEOUT, null); this._proto._onDisconnectTimeout(); // actually disconnect diff --git a/src/constants.js b/src/constants.js index 3ea4f228..6836edbb 100644 --- a/src/constants.js +++ b/src/constants.js @@ -129,25 +129,13 @@ export const ErrorCondition = { UNKNOWN_REASON: 'unknown', }; -/** - * @typedef {Object} LogLevel - * @property {string} DEBUG - * @property {string} INFO - * @property {string} WARN - * @property {string} ERROR - * @property {string} FATAL - */ - /** * Logging level indicators. - * - * - Strophe.LogLevel.DEBUG - Debug output - * - Strophe.LogLevel.INFO - Informational output - * - Strophe.LogLevel.WARN - Warnings - * - Strophe.LogLevel.ERROR - Errors - * - Strophe.LogLevel.FATAL - Fatal errors + * @typedef {0|1|2|3|4} LogLevel + * @typedef {'DEBUG'|'INFO'|'WARN'|'ERROR'|'FATAL'} LogLevelName + * @typedef {Record} LogLevels */ -export const LogLevel = { +export const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, diff --git a/src/core.js b/src/core.js deleted file mode 100644 index 8483b419..00000000 --- a/src/core.js +++ /dev/null @@ -1,230 +0,0 @@ -import * as utils from './utils.js'; -import Builder from './builder.js'; -import * as shims from './shims.js'; -import Connection from './connection.js'; -import Handler from './handler.js'; -import SASLAnonymous from './sasl-anon.js'; -import SASLExternal from './sasl-external.js'; -import SASLMechanism from './sasl.js'; -import SASLOAuthBearer from './sasl-oauthbearer.js'; -import SASLPlain from './sasl-plain.js'; -import SASLSHA1 from './sasl-sha1.js'; -import SASLSHA256 from './sasl-sha256.js'; -import SASLSHA384 from './sasl-sha384.js'; -import SASLSHA512 from './sasl-sha512.js'; -import SASLXOAuth2 from './sasl-xoauth2.js'; -import TimedHandler from './timed-handler.js'; -import { ElementType, ErrorCondition, LogLevel, NS, Status, XHTML } from './constants.js'; -import Request from './request.js'; -import Bosh from './bosh.js'; -import Websocket from './websocket.js'; -import WorkerWebsocket from './worker-websocket.js'; - -/** - * A container for all Strophe library functions. - * - * This object is 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. - * - * @namespace Strophe - * @property {Handler} Handler - * @property {Builder} Builder - * @property {Request} Request Represents HTTP Requests made for a BOSH connection - * @property {Bosh} Bosh Support for XMPP-over-HTTP via XEP-0124 (BOSH) - * @property {Websocket} Websocket Support for XMPP over websocket - * @property {WorkerWebsocket} WorkerWebsocket Support for XMPP over websocket in a shared worker - * @property {number} TIMEOUT=1.1 Timeout multiplier. A waiting BOSH HTTP request - * will be considered failed after Math.floor(TIMEOUT * wait) seconds have elapsed. - * This defaults to 1.1, and with default wait, 66 seconds. - * @property {number} SECONDARY_TIMEOUT=0.1 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. - * @property {SASLAnonymous} SASLAnonymous SASL ANONYMOUS authentication. - * @property {SASLPlain} SASLPlain SASL PLAIN authentication - * @property {SASLSHA1} SASLSHA1 SASL SCRAM-SHA-1 authentication - * @property {SASLSHA256} SASLSHA256 SASL SCRAM-SHA-256 authentication - * @property {SASLSHA384} SASLSHA384 SASL SCRAM-SHA-384 authentication - * @property {SASLSHA512} SASLSHA512 SASL SCRAM-SHA-512 authentication - * @property {SASLOAuthBearer} SASLOAuthBearer SASL OAuth Bearer authentication - * @property {SASLExternal} SASLExternal SASL EXTERNAL authentication - * @property {SASLXOAuth2} SASLXOAuth2 SASL X-OAuth2 authentication - * @property {Status} Status - * @property {Object.} NS - * @property {XHTML} XHTML - */ -const Strophe = { - /** @constant: VERSION */ - VERSION: '1.6.1', - - TIMEOUT: 1.1, - SECONDARY_TIMEOUT: 0.1, - - shims, - - Request, - - // Transports - Bosh, - Websocket, - WorkerWebsocket, - - // Available authentication mechanisms - SASLAnonymous, - SASLPlain, - SASLSHA1, - SASLSHA256, - SASLSHA384, - SASLSHA512, - SASLOAuthBearer, - SASLExternal, - SASLXOAuth2, - - Builder, - Connection, - ElementType, - ErrorCondition, - Handler, - LogLevel, - /** @type {Object.} */ - NS, - SASLMechanism, - /** @type {Status} */ - Status, - TimedHandler, - ...utils, - - XHTML: { - ...XHTML, - validTag: utils.validTag, - validCSS: utils.validCSS, - validAttribute: utils.validAttribute, - }, - - /** - * 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. - * @example: Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); - * - * @param {string} name - The name under which the namespace will be - * referenced under Strophe.NS - * @param {string} value - The actual namespace. - */ - addNamespace(name, value) { - Strophe.NS[name] = value; - }, - - /** - * _Private_ function that properly logs an error to the console - * @private - * @param {Error} e - */ - _handleError(e) { - if (typeof e.stack !== 'undefined') { - Strophe.fatal(e.stack); - } - Strophe.fatal('error: ' + e.message); - }, - - /** - * 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 {@link Strophe.Connection#rawInput|Strophe.Connection.rawInput()} - * and {@link Strophe.Connection#rawOutput|Strophe.Connection.rawOutput()}. - * - * The different levels and their meanings are - * - * DEBUG - Messages useful for debugging purposes. - * INFO - Informational messages. This is mostly information like - * 'disconnect was called' or 'SASL auth succeeded'. - * WARN - Warnings about potential problems. This is mostly used - * to report transient connection errors like request timeouts. - * ERROR - Some error occurred. - * FATAL - A non-recoverable fatal error occurred. - * - * @param {number} level - The log level of the log message. - * This will be one of the values in Strophe.LogLevel. - * @param {string} msg - The log message. - */ - log(level, msg) { - if (level === this.LogLevel.FATAL) { - console?.error(msg); - } - }, - - /** - * Log a message at the Strophe.LogLevel.DEBUG level. - * @param {string} msg - The log message. - */ - debug(msg) { - this.log(this.LogLevel.DEBUG, msg); - }, - - /** - * Log a message at the Strophe.LogLevel.INFO level. - * @param {string} msg - The log message. - */ - info(msg) { - this.log(this.LogLevel.INFO, msg); - }, - - /** - * Log a message at the Strophe.LogLevel.WARN level. - * @param {string} msg - The log message. - */ - warn(msg) { - this.log(this.LogLevel.WARN, msg); - }, - - /** - * Log a message at the Strophe.LogLevel.ERROR level. - * @param {string} msg - The log message. - */ - error(msg) { - this.log(this.LogLevel.ERROR, msg); - }, - - /** - * Log a message at the Strophe.LogLevel.FATAL level. - * @param {string} msg - The log message. - */ - fatal(msg) { - this.log(this.LogLevel.FATAL, msg); - }, - - /** - * _Private_ variable that keeps track of the request ids for connections. - * @private - */ - _requestId: 0, - - /** - * _Private_ variable Used to store plugin names that need - * initialization on Strophe.Connection construction. - * @private - * @type {Object.} - */ - _connectionPlugins: {}, - - /** - * Extends the Strophe.Connection object with the given plugin. - * @param {string} name - The name of the extension. - * @param {Object} ptype - The plugin's prototype. - */ - addConnectionPlugin(name, ptype) { - Strophe._connectionPlugins[name] = ptype; - }, -}; - -export default Strophe; diff --git a/src/handler.js b/src/handler.js index d83d40a6..894b52ca 100644 --- a/src/handler.js +++ b/src/handler.js @@ -1,18 +1,17 @@ -import Strophe from './core.js'; -import { forEachChild, getBareJidFromJid } from './utils.js'; +import { forEachChild, getBareJidFromJid, handleError, isTagEqual } from './utils.js'; /** * _Private_ helper class for managing stanza handlers. * - * A Strophe.Handler encapsulates a user provided callback function to be + * A 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 {@link Strophe.Connection.addHandler} and - * {@link Strophe.Connection.deleteHandler}. + * Users will not use Handler objects directly, but instead they + * will use {@link Connection.addHandler} and + * {@link Connection.deleteHandler}. */ class Handler { /** @@ -22,7 +21,7 @@ class Handler { */ /** - * Create and initialize a new Strophe.Handler. + * Create and initialize a new Handler. * * @param {Function} handler - A function to be executed when the handler is run. * @param {string} ns - The namespace to match. @@ -64,7 +63,7 @@ class Handler { } /** - * Tests if a stanza matches the namespace set for this Strophe.Handler. + * Tests if a stanza matches the namespace set for this Handler. * @param {Element} elem - The XML element to test. * @return {boolean} - true if the stanza matches and false otherwise. */ @@ -88,7 +87,7 @@ class Handler { } /** - * Tests if a stanza matches the Strophe.Handler. + * Tests if a stanza matches the Handler. * @param {Element} elem - The XML element to test. * @return {boolean} - true if the stanza matches and false otherwise. */ @@ -100,7 +99,7 @@ class Handler { const elem_type = elem.getAttribute('type'); if ( this.namespaceMatch(elem) && - (!this.name || Strophe.isTagEqual(elem, this.name)) && + (!this.name || 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) && @@ -113,7 +112,7 @@ class Handler { /** * Run the callback on a matching stanza. - * @param {Element} elem - The DOM element that triggered the Strophe.Handler. + * @param {Element} elem - The DOM element that triggered the Handler. * @return {boolean} - A boolean indicating if the handler should remain active. */ run(elem) { @@ -121,14 +120,14 @@ class Handler { try { result = this.handler(elem); } catch (e) { - Strophe._handleError(e); + handleError(e); throw e; } return result; } /** - * Get a String representation of the Strophe.Handler object. + * Get a String representation of the Handler object. * @return {string} */ toString() { diff --git a/src/index.js b/src/index.js index 2b124a08..e8f3259e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,183 @@ /*global globalThis*/ -import Strophe from './core.js'; +import * as shims from './shims.js'; +import * as utils from './utils.js'; +import Bosh from './bosh.js'; import Builder, { $build, $msg, $pres, $iq } from './builder.js'; +import Connection from './connection.js'; +import Handler from './handler.js'; +import Request from './request.js'; +import SASLAnonymous from './sasl-anon.js'; +import SASLExternal from './sasl-external.js'; +import SASLMechanism from './sasl.js'; +import SASLOAuthBearer from './sasl-oauthbearer.js'; +import SASLPlain from './sasl-plain.js'; +import SASLSHA1 from './sasl-sha1.js'; +import SASLSHA256 from './sasl-sha256.js'; +import SASLSHA384 from './sasl-sha384.js'; +import SASLSHA512 from './sasl-sha512.js'; +import SASLXOAuth2 from './sasl-xoauth2.js'; +import TimedHandler from './timed-handler.js'; +import Websocket from './websocket.js'; +import WorkerWebsocket from './worker-websocket.js'; +import log from './log.js'; +import { ElementType, ErrorCondition, LOG_LEVELS, NS, Status, XHTML } from './constants.js'; import { stx, toStanza } from './stanza.js'; +/** + * A container for all Strophe library functions. + * + * This object is 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. + * + * @namespace Strophe + * @property {Handler} Handler + * @property {Builder} Builder + * @property {Request} Request Represents HTTP Requests made for a BOSH connection + * @property {Bosh} Bosh Support for XMPP-over-HTTP via XEP-0124 (BOSH) + * @property {Websocket} Websocket Support for XMPP over websocket + * @property {WorkerWebsocket} WorkerWebsocket Support for XMPP over websocket in a shared worker + * @property {number} TIMEOUT=1.1 Timeout multiplier. A waiting BOSH HTTP request + * will be considered failed after Math.floor(TIMEOUT * wait) seconds have elapsed. + * This defaults to 1.1, and with default wait, 66 seconds. + * @property {number} SECONDARY_TIMEOUT=0.1 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. + * @property {SASLAnonymous} SASLAnonymous SASL ANONYMOUS authentication. + * @property {SASLPlain} SASLPlain SASL PLAIN authentication + * @property {SASLSHA1} SASLSHA1 SASL SCRAM-SHA-1 authentication + * @property {SASLSHA256} SASLSHA256 SASL SCRAM-SHA-256 authentication + * @property {SASLSHA384} SASLSHA384 SASL SCRAM-SHA-384 authentication + * @property {SASLSHA512} SASLSHA512 SASL SCRAM-SHA-512 authentication + * @property {SASLOAuthBearer} SASLOAuthBearer SASL OAuth Bearer authentication + * @property {SASLExternal} SASLExternal SASL EXTERNAL authentication + * @property {SASLXOAuth2} SASLXOAuth2 SASL X-OAuth2 authentication + * @property {Status} Status + * @property {Object.} NS + * @property {XHTML} XHTML + */ +const Strophe = { + /** @constant: VERSION */ + VERSION: '3.0.0', + + /** + * @returns {number} + */ + get TIMEOUT() { + return Bosh.getTimeoutMultplier(); + }, + + /** + * @param {number} n + */ + set TIMEOUT(n) { + Bosh.setTimeoutMultiplier(n); + }, + + /** + * @returns {number} + */ + get SECONDARY_TIMEOUT() { + return Bosh.getSecondaryTimeoutMultplier(); + }, + + /** + * @param {number} n + */ + set SECONDARY_TIMEOUT(n) { + Bosh.setSecondaryTimeoutMultiplier(n); + }, + + ...utils, + + shims, + + Request, + + // Transports + Bosh, + Websocket, + WorkerWebsocket, + Connection, + Handler, + + // Available authentication mechanisms + SASLAnonymous, + SASLPlain, + SASLSHA1, + SASLSHA256, + SASLSHA384, + SASLSHA512, + SASLOAuthBearer, + SASLExternal, + SASLXOAuth2, + + Builder, + ElementType, + ErrorCondition, + LogLevel: LOG_LEVELS, + /** @type {Object.} */ + NS, + SASLMechanism, + /** @type {Status} */ + Status, + TimedHandler, + + XHTML: { + ...XHTML, + validTag: utils.validTag, + validCSS: utils.validCSS, + validAttribute: utils.validAttribute, + }, + + /** + * Render a DOM element and all descendants to a String. + * @method Strophe.serialize + * @param {Element|Builder} elem - A DOM element. + * @return {string} - The serialized element tree as a String. + */ + serialize(elem) { + return Builder.serialize(elem) + }, + + /** + * @typedef {import('./constants').LogLevel} LogLevel + * + * Library consumers can use this function to set the log level of Strophe. + * The default log level is Strophe.LogLevel.INFO. + * @param {LogLevel} level + * @example Strophe.setLogLevel(Strophe.LogLevel.DEBUG); + */ + setLogLevel(level) { + log.setLogLevel(level); + }, + + /** + * 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. + * @example: Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); + * + * @param {string} name - The name under which the namespace will be + * referenced under Strophe.NS + * @param {string} value - The actual namespace. + */ + addNamespace(name, value) { + Strophe.NS[name] = value; + }, + + /** + * Extends the Strophe.Connection object with the given plugin. + * @param {string} name - The name of the extension. + * @param {Object} ptype - The plugin's prototype. + */ + addConnectionPlugin(name, ptype) { + Connection.addConnectionPlugin(name, ptype); + }, +}; + globalThis.$build = $build; globalThis.$iq = $iq; globalThis.$msg = $msg; diff --git a/src/log.js b/src/log.js new file mode 100644 index 00000000..9d335b03 --- /dev/null +++ b/src/log.js @@ -0,0 +1,99 @@ +/** + * @typedef {import('./constants').LogLevel} LogLevel + */ +import { LOG_LEVELS } from './constants.js'; + +let logLevel = LOG_LEVELS.DEBUG; + +const log = { + /** + * Library consumers can use this function to set the log level of Strophe. + * The default log level is Strophe.LogLevel.INFO. + * @param {LogLevel} level + * @example Strophe.setLogLevel(Strophe.LogLevel.DEBUG); + */ + setLogLevel(level) { + if (level < LOG_LEVELS.DEBUG || level > LOG_LEVELS.FATAL) { + throw new Error("Invalid log level supplied to setLogLevel"); + } + logLevel = level; + }, + + /** + * + * Please note that data sent and received over the wire is logged + * via {@link Strophe.Connection#rawInput|Strophe.Connection.rawInput()} + * and {@link Strophe.Connection#rawOutput|Strophe.Connection.rawOutput()}. + * + * The different levels and their meanings are + * + * DEBUG - Messages useful for debugging purposes. + * INFO - Informational messages. This is mostly information like + * 'disconnect was called' or 'SASL auth succeeded'. + * WARN - Warnings about potential problems. This is mostly used + * to report transient connection errors like request timeouts. + * ERROR - Some error occurred. + * FATAL - A non-recoverable fatal error occurred. + * + * @param {number} level - The log level of the log message. + * This will be one of the values in Strophe.LOG_LEVELS. + * @param {string} msg - The log message. + */ + log(level, msg) { + if (level < logLevel) { + return; + } + + if (level >= LOG_LEVELS.ERROR) { + console?.error(msg); + } else if (level === LOG_LEVELS.INFO) { + console?.info(msg); + } else if (level === LOG_LEVELS.WARN) { + console?.warn(msg); + } else if (level === LOG_LEVELS.DEBUG) { + console?.debug(msg); + } + }, + + /** + * Log a message at the Strophe.LOG_LEVELS.DEBUG level. + * @param {string} msg - The log message. + */ + debug(msg) { + this.log(LOG_LEVELS.DEBUG, msg); + }, + + /** + * Log a message at the Strophe.LOG_LEVELS.INFO level. + * @param {string} msg - The log message. + */ + info(msg) { + this.log(LOG_LEVELS.INFO, msg); + }, + + /** + * Log a message at the Strophe.LOG_LEVELS.WARN level. + * @param {string} msg - The log message. + */ + warn(msg) { + this.log(LOG_LEVELS.WARN, msg); + }, + + /** + * Log a message at the Strophe.LOG_LEVELS.ERROR level. + * @param {string} msg - The log message. + */ + error(msg) { + this.log(LOG_LEVELS.ERROR, msg); + }, + + /** + * Log a message at the Strophe.LOG_LEVELS.FATAL level. + * @param {string} msg - The log message. + */ + fatal(msg) { + this.log(LOG_LEVELS.FATAL, msg); + }, +}; + +export default log; diff --git a/src/request.js b/src/request.js index f6d9071e..f39c969d 100644 --- a/src/request.js +++ b/src/request.js @@ -1,11 +1,18 @@ import { DOMParser } from './shims'; -import Strophe from './core'; +import log from './log.js'; +import Builder from './builder.js'; +import { ErrorCondition } from './constants.js'; + +/** + * _Private_ variable that keeps track of the request ids for connections. + */ +let _requestId = 0; /** * Helper class that provides a cross implementation abstraction * for a BOSH related XMLHttpRequest. * - * The Strophe.Request class is used internally to encapsulate BOSH request + * The Request class is used internally to encapsulate BOSH request * information. It is not meant to be used from user's code. * * @property {number} id @@ -14,7 +21,7 @@ import Strophe from './core'; */ class Request { /** - * Create and initialize a new Strophe.Request object. + * Create and initialize a new Request object. * * @param {Element} elem - The XML data to be sent in the request. * @param {Function} func - The function that will be called when the @@ -23,9 +30,9 @@ class Request { * @param {number} [sends=0] - The number of times this same request has been sent. */ constructor(elem, func, rid, sends = 0) { - this.id = ++Strophe._requestId; + this.id = ++_requestId; this.xmlData = elem; - this.data = Strophe.serialize(elem); + this.data = Builder.serialize(elem); // save original function in case we need to make a new request // from this one. this.origFunc = func; @@ -53,25 +60,25 @@ class Request { let node = this.xhr.responseXML?.documentElement; if (node) { if (node.tagName === 'parsererror') { - Strophe.error('invalid response received'); - Strophe.error('responseText: ' + this.xhr.responseText); - Strophe.error('responseXML: ' + Strophe.serialize(node)); + log.error('invalid response received'); + log.error('responseText: ' + this.xhr.responseText); + log.error('responseXML: ' + Builder.serialize(node)); throw new Error('parsererror'); } } else if (this.xhr.responseText) { // In Node (with xhr2) or 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...'); + log.debug('Got responseText but no responseXML; attempting to parse it with DOMParser...'); node = new DOMParser().parseFromString(this.xhr.responseText, 'application/xml').documentElement; const parserError = node?.querySelector('parsererror'); if (!node || parserError) { if (parserError) { - Strophe.error('invalid response received: ' + parserError.textContent); - Strophe.error('responseText: ' + this.xhr.responseText); + log.error('invalid response received: ' + parserError.textContent); + log.error('responseText: ' + this.xhr.responseText); } const error = new Error(); - error.name = Strophe.ErrorCondition.BAD_FORMAT; + error.name = ErrorCondition.BAD_FORMAT; throw error; } } diff --git a/src/scram.js b/src/scram.js index 8eb522ef..a5edd306 100644 --- a/src/scram.js +++ b/src/scram.js @@ -2,7 +2,7 @@ * @typedef {import("./connection.js").default} Connection */ import utils from './utils'; -import Strophe from './core.js'; +import log from './log.js'; /** * @param {string} authMessage @@ -60,12 +60,12 @@ function scramParseChallenge(challenge) { // Consider iteration counts less than 4096 insecure, as reccommended by // RFC 5802 if (isNaN(iter) || iter < 4096) { - Strophe.warn('Failing SCRAM authentication because server supplied iteration count < 4096.'); + log.warn('Failing SCRAM authentication because server supplied iteration count < 4096.'); return undefined; } if (!salt) { - Strophe.warn('Failing SCRAM authentication because server supplied incorrect salt.'); + log.warn('Failing SCRAM authentication because server supplied incorrect salt.'); return undefined; } @@ -157,7 +157,7 @@ const scram = { // The RFC requires that we verify the (server) nonce has the client // nonce as an initial substring. if (!challengeData && challengeData?.nonce.slice(0, cnonce.length) !== cnonce) { - Strophe.warn('Failing SCRAM authentication because server supplied incorrect nonce.'); + log.warn('Failing SCRAM authentication because server supplied incorrect nonce.'); connection._sasl_data = {}; return connection._sasl_failure_cb(); } diff --git a/src/stanza.js b/src/stanza.js index 16a5e359..150fd847 100644 --- a/src/stanza.js +++ b/src/stanza.js @@ -1,14 +1,15 @@ -import Strophe from './core.js'; +import log from './log.js'; +import { xmlHtmlNode } from './utils.js'; const PARSE_ERROR_NS = 'http://www.w3.org/1999/xhtml'; /** - * @param { string } string - * @param { boolean } [throwErrorIfInvalidNS] - * @return { Element } + * @param {string} string + * @param {boolean} [throwErrorIfInvalidNS] + * @return {Element} */ -export function toStanza (string, throwErrorIfInvalidNS) { - const doc = Strophe.xmlHtmlNode(string); +export function toStanza(string, throwErrorIfInvalidNS) { + const doc = xmlHtmlNode(string); if (doc.getElementsByTagNameNS(PARSE_ERROR_NS, 'parsererror').length) { throw new Error(`Parser Error: ${string}`); @@ -25,7 +26,7 @@ export function toStanza (string, throwErrorIfInvalidNS) { if (throwErrorIfInvalidNS) { throw new Error(err_msg); } else { - Strophe.log(Strophe.LogLevel.ERROR, err_msg); + log.error(err_msg); } } return node; @@ -36,12 +37,11 @@ export function toStanza (string, throwErrorIfInvalidNS) { * stanzas). */ class Stanza { - - /** - * @param { string[] } strings - * @param { any[] } values - */ - constructor (strings, values) { + /** + * @param { string[] } strings + * @param { any[] } values + */ + constructor(strings, values) { this.strings = strings; this.values = values; } @@ -49,9 +49,10 @@ class Stanza { /** * @return { string } */ - toString () { - this.string = this.string || - this.strings.reduce((acc, str) => { + toString() { + this.string = + this.string || + this.strings.reduce((acc, str) => { const idx = this.strings.indexOf(str); const value = this.values.length > idx ? this.values[idx].toString() : ''; return acc + str + value; @@ -62,7 +63,7 @@ class Stanza { /** * @return { Element } */ - tree () { + tree() { this.node = this.node ?? toStanza(this.toString(), true); return this.node; } @@ -75,6 +76,6 @@ class Stanza { * @param { string[] } strings * @param { ...any } values */ -export function stx (strings, ...values) { +export function stx(strings, ...values) { return new Stanza(strings, values); } diff --git a/src/types/bosh.d.ts b/src/types/bosh.d.ts index 3f0d718d..60b2d6ef 100644 --- a/src/types/bosh.d.ts +++ b/src/types/bosh.d.ts @@ -1,21 +1,36 @@ export default Bosh; export type Connection = import("./connection.js").default; -export type Request = import("./request.js").default; /** * _Private_ helper class that handles BOSH Connections - * The Strophe.Bosh class is used internally by Strophe.Connection + * The Bosh class is used internally by Connection * to encapsulate BOSH sessions. It is not meant to be used from user's code. */ declare class Bosh { /** - * Returns the HTTP status code from a {@link Strophe.Request} + * @param {number} m + */ + static setTimeoutMultiplier(m: number): void; + /** + * @returns {number} + */ + static getTimeoutMultplier(): number; + /** + * @param {number} m + */ + static setSecondaryTimeoutMultiplier(m: number): void; + /** + * @returns {number} + */ + static getSecondaryTimeoutMultplier(): number; + /** + * Returns the HTTP status code from a {@link Request} * @private - * @param {Request} req - The {@link Strophe.Request} instance. + * @param {Request} req - The {@link Request} instance. * @param {number} [def] - The default value that should be returned if no status value was found. */ private static _getRequestStatus; /** - * @param {Connection} connection - The Strophe.Connection that will use BOSH. + * @param {Connection} connection - The Connection that will use BOSH. */ constructor(connection: Connection); _conn: import("./connection.js").default; @@ -28,18 +43,18 @@ declare class Bosh { inactivity: number; /** * BOSH-Connections will have all stanzas wrapped in a tag when - * passed to {@link Strophe.Connection#xmlInput|xmlInput()} or {@link Strophe.Connection#xmlOutput|xmlOutput()}. - * To strip this tag, User code can set {@link Strophe.Bosh#strip|strip} to `true`: + * passed to {@link Connection#xmlInput|xmlInput()} or {@link Connection#xmlOutput|xmlOutput()}. + * To strip this tag, User code can set {@link Bosh#strip|strip} to `true`: * * > // You can set `strip` on the prototype - * > Strophe.Bosh.prototype.strip = true; + * > Bosh.prototype.strip = true; * * > // Or you can set it on the Bosh instance (which is `._proto` on the connection instance. - * > const conn = new Strophe.Connection(); + * > const conn = new Connection(); * > conn._proto.strip = true; * * This will enable stripping of the body tag in both - * {@link Strophe.Connection#xmlInput|xmlInput} and {@link Strophe.Connection#xmlOutput|xmlOutput}. + * {@link Connection#xmlInput|xmlInput} and {@link Connection#xmlOutput|xmlOutput}. * * @property {boolean} [strip=false] */ @@ -50,12 +65,12 @@ declare class Bosh { /** * _Private_ helper function to generate the wrapper for BOSH. * @private - * @return {Builder} - A Strophe.Builder with a element. + * @return {Builder} - A Builder with a element. */ private _buildBody; /** * Reset the connection. - * This function is called by the reset function of the Strophe Connection + * This function is called by the reset function of the Connection */ _reset(): void; /** @@ -177,12 +192,12 @@ declare class Bosh { */ _abortAllRequests(): void; /** - * _Private_ handler called by {@link Strophe.Connection#_onIdle|Strophe.Connection._onIdle()}. + * _Private_ handler called by {@link Connection#_onIdle|Connection._onIdle()}. * Sends all queued Requests or polls with empty Request if there are none. */ _onIdle(): void; /** - * _Private_ handler for {@link Strophe.Request} state changes. + * _Private_ handler for {@link 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 @@ -255,5 +270,6 @@ declare class Bosh { */ private _throttledRequestHandler; } +import Request from './request.js'; import Builder from './builder.js'; //# sourceMappingURL=bosh.d.ts.map \ No newline at end of file diff --git a/src/types/builder.d.ts b/src/types/builder.d.ts index 66dc5be4..c07796b8 100644 --- a/src/types/builder.d.ts +++ b/src/types/builder.d.ts @@ -62,6 +62,12 @@ export default Builder; * // */ declare class Builder { + /** + * Render a DOM element and all descendants to a String. + * @param {Element|Builder} elem - A DOM element. + * @return {string} - The serialized element tree as a String. + */ + static serialize(elem: Element | Builder): string; /** * @typedef {Object.} StanzaAttrs * @property {string} [StanzaAttrs.xmlns] diff --git a/src/types/connection.d.ts b/src/types/connection.d.ts index 59c911f4..24497f18 100644 --- a/src/types/connection.d.ts +++ b/src/types/connection.d.ts @@ -14,25 +14,31 @@ export type Request = import("./request.js").default; * * It supports various authentication mechanisms (e.g. SASL PLAIN, SASL SCRAM), * and more can be added via - * {@link Strophe.Connection#registerSASLMechanisms|registerSASLMechanisms()}. + * {@link Connection#registerSASLMechanisms|registerSASLMechanisms()}. * - * After creating a Strophe.Connection object, the user will typically - * call {@link Strophe.Connection#connect|connect()} with a user supplied callback + * After creating a Connection object, the user will typically + * call {@link Connection#connect|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 - * {@link Strophe.Connection#addHandler|addHandler()} and - * {@link Strophe.Connection#addTimedHandler|addTimedHandler()}. + * {@link Connection#addHandler|addHandler()} and + * {@link Connection#addTimedHandler|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 {@link Strophe.Connection#send|send()}. + * To send data to the connection, use {@link Connection#send|send()}. * * @memberof Strophe */ declare class Connection { + /** + * Extends the Connection object with the given plugin. + * @param {string} name - The name of the extension. + * @param {Object} ptype - The plugin's prototype. + */ + static addConnectionPlugin(name: string, ptype: Object): void; /** * @typedef {Object.} Cookie * @typedef {Cookie|Object.} Cookies @@ -60,9 +66,9 @@ declare class Connection { * necessary cookies. * @property {SASLMechanism[]} [mechanisms] * Allows you to specify the SASL authentication mechanisms that this - * instance of Strophe.Connection (and therefore your XMPP client) will support. + * instance of Connection (and therefore your XMPP client) will support. * - * The value must be an array of objects with {@link Strophe.SASLMechanism} + * The value must be an array of objects with {@link SASLMechanism} * prototypes. * * If nothing is specified, then the following mechanisms (and their @@ -82,7 +88,7 @@ declare class Connection { * * @property {boolean} [explicitResourceBinding] * If `explicitResourceBinding` is set to `true`, then the XMPP client - * needs to explicitly call {@link Strophe.Connection.bind} once the XMPP + * needs to explicitly call {@link Connection.bind} once the XMPP * server has advertised the `urn:ietf:propertys:xml:ns:xmpp-bind` feature. * * Making this step explicit allows client authors to first finish other @@ -116,7 +122,7 @@ declare class Connection { * * To run the websocket connection inside a shared worker. * This allows you to share a single websocket-based connection between - * multiple Strophe.Connection instances, for example one per browser tab. + * multiple Connection instances, for example one per browser tab. * * The script to use is the one in `src/shared-connection-worker.js`. * @@ -161,7 +167,7 @@ declare class Connection { * to the server. */ /** - * Create and initialize a {@link Strophe.Connection} object. + * Create and initialize a {@link Connection} object. * * The transport-protocol for this connection will be chosen automatically * based on the given service parameter. URLs starting with "ws://" or @@ -206,9 +212,9 @@ declare class Connection { }; /** * Allows you to specify the SASL authentication mechanisms that this - * instance of Strophe.Connection (and therefore your XMPP client) will support. + * instance of Connection (and therefore your XMPP client) will support. * - * The value must be an array of objects with {@link Strophe.SASLMechanism }prototypes. + * The value must be an array of objects with {@link SASLMechanism }prototypes. * * If nothing is specified, then the following mechanisms (and their * priorities) are registered: @@ -228,7 +234,7 @@ declare class Connection { mechanisms?: SASLMechanism[]; /** * If `explicitResourceBinding` is set to `true`, then the XMPP client - * needs to explicitly call {@link Strophe.Connection.bind } once the XMPP + * needs to explicitly call {@link Connection.bind } once the XMPP * server has advertised the `urn:ietf:propertys:xml:ns:xmpp-bind` feature. * * Making this step explicit allows client authors to first finish other @@ -264,7 +270,7 @@ declare class Connection { * * To run the websocket connection inside a shared worker. * This allows you to share a single websocket-based connection between - * multiple Strophe.Connection instances, for example one per browser tab. + * multiple Connection instances, for example one per browser tab. * * The script to use is the one in `src/shared-connection-worker.js`. */ @@ -346,9 +352,9 @@ declare class Connection { }; /** * Allows you to specify the SASL authentication mechanisms that this - * instance of Strophe.Connection (and therefore your XMPP client) will support. + * instance of Connection (and therefore your XMPP client) will support. * - * The value must be an array of objects with {@link Strophe.SASLMechanism }prototypes. + * The value must be an array of objects with {@link SASLMechanism }prototypes. * * If nothing is specified, then the following mechanisms (and their * priorities) are registered: @@ -368,7 +374,7 @@ declare class Connection { mechanisms?: SASLMechanism[]; /** * If `explicitResourceBinding` is set to `true`, then the XMPP client - * needs to explicitly call {@link Strophe.Connection.bind } once the XMPP + * needs to explicitly call {@link Connection.bind } once the XMPP * server has advertised the `urn:ietf:propertys:xml:ns:xmpp-bind` feature. * * Making this step explicit allows client authors to first finish other @@ -404,7 +410,7 @@ declare class Connection { * * To run the websocket connection inside a shared worker. * This allows you to share a single websocket-based connection between - * multiple Strophe.Connection instances, for example one per browser tab. + * multiple Connection instances, for example one per browser tab. * * The script to use is the one in `src/shared-connection-worker.js`. */ @@ -514,7 +520,7 @@ declare class Connection { * Select protocal based on this.options or this.service */ setProtocol(): void; - _proto: import("./bosh.js").default | import("./websocket.js").default | import("./worker-websocket.js").default; + _proto: Bosh | Websocket | WorkerWebsocket; /** * Reset the connection. * @@ -694,7 +700,7 @@ declare class Connection { * Attempt to restore a cached BOSH session. * * This function is only useful in conjunction with providing the - * "keepalive":true option when instantiating a new {@link Strophe.Connection}. + * "keepalive":true option when instantiating a new {@link 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 @@ -727,7 +733,7 @@ declare class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.xmlInput = function (elem) { + * > Connection.xmlInput = function (elem) { * > (user code) * > }; * @@ -735,7 +741,7 @@ declare class Connection { * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See - * if you want to strip this tag. + * if you want to strip this tag. * * @param {Node|MessageEvent} elem - The XML data received by the connection. */ @@ -745,7 +751,7 @@ declare class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.xmlOutput = function (elem) { + * > Connection.xmlOutput = function (elem) { * > (user code) * > }; * @@ -753,7 +759,7 @@ declare class Connection { * tag for WebSocket-Connoctions will be passed as selfclosing here. * * BOSH-Connections will have all stanzas wrapped in a tag. See - * if you want to strip this tag. + * if you want to strip this tag. * * @param {Element} elem - The XMLdata sent by the connection. */ @@ -763,7 +769,7 @@ declare class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.rawInput = function (data) { + * > Connection.rawInput = function (data) { * > (user code) * > }; * @@ -775,7 +781,7 @@ declare class Connection { * connection. * * The default function does nothing. User code can override this with - * > Strophe.Connection.rawOutput = function (data) { + * > Connection.rawOutput = function (data) { * > (user code) * > }; * @@ -786,7 +792,7 @@ declare class Connection { * 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) { + * > Connection.nextValidRid = function (rid) { * > (user code) * > }; * @@ -962,8 +968,8 @@ declare class Connection { deleteHandler(handRef: Handler): void; /** * Register the SASL mechanisms which will be supported by this instance of - * Strophe.Connection (i.e. which this XMPP client will support). - * @param {SASLMechanism[]} mechanisms - Array of objects with Strophe.SASLMechanism prototypes + * Connection (i.e. which this XMPP client will support). + * @param {SASLMechanism[]} mechanisms - Array of objects with SASLMechanism prototypes */ registerSASLMechanisms(mechanisms: SASLMechanism[]): void; /** @@ -1106,7 +1112,7 @@ declare class Connection { * https://tools.ietf.org/html/rfc6120#section-7.5 * * If `explicitResourceBinding` was set to a truthy value in the options - * passed to the Strophe.Connection constructor, then this function needs + * passed to the Connection constructor, then this function needs * to be called explicitly by the client author. * * Otherwise it'll be called automatically as soon as the XMPP server @@ -1165,7 +1171,7 @@ declare class Connection { /** * _Private_ function to add a system level timed handler. * - * This function is used to add a Strophe.TimedHandler for the + * This function is used to add a TimedHandler for the * library code. System timed handlers are allowed to run before * authentication is complete. * @param {number} period - The period of the handler. @@ -1203,5 +1209,8 @@ declare class Connection { } import TimedHandler from './timed-handler.js'; import Handler from './handler.js'; +import Bosh from './bosh.js'; +import Websocket from './websocket.js'; +import WorkerWebsocket from './worker-websocket.js'; import Builder from './builder.js'; //# sourceMappingURL=connection.d.ts.map \ No newline at end of file diff --git a/src/types/constants.d.ts b/src/types/constants.d.ts index bd4ba4b2..2e36d6c8 100644 --- a/src/types/constants.d.ts +++ b/src/types/constants.d.ts @@ -189,14 +189,7 @@ export namespace ErrorCondition { let NO_AUTH_MECH: string; let UNKNOWN_REASON: string; } -export type LogLevel = { - DEBUG: string; - INFO: string; - WARN: string; - ERROR: string; - FATAL: string; -}; -export namespace LogLevel { +export namespace LOG_LEVELS { export let DEBUG: number; export let INFO: number; export let WARN: number; @@ -211,4 +204,16 @@ export namespace ElementType { let FRAGMENT: number; } export type connstatus = number; +/** + * Logging level indicators. + */ +export type LogLevel = 0 | 1 | 2 | 3 | 4; +/** + * Logging level indicators. + */ +export type LogLevelName = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL'; +/** + * Logging level indicators. + */ +export type LogLevels = Record; //# sourceMappingURL=constants.d.ts.map \ No newline at end of file diff --git a/src/types/core.d.ts b/src/types/core.d.ts index 086433a9..ddf35689 100644 --- a/src/types/core.d.ts +++ b/src/types/core.d.ts @@ -1,62 +1,27 @@ export default Strophe; -/** - * A container for all Strophe library functions. - * - * This object is 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. - * - * @namespace Strophe - * @property {Handler} Handler - * @property {Builder} Builder - * @property {Request} Request Represents HTTP Requests made for a BOSH connection - * @property {Bosh} Bosh Support for XMPP-over-HTTP via XEP-0124 (BOSH) - * @property {Websocket} Websocket Support for XMPP over websocket - * @property {WorkerWebsocket} WorkerWebsocket Support for XMPP over websocket in a shared worker - * @property {number} TIMEOUT=1.1 Timeout multiplier. A waiting BOSH HTTP request - * will be considered failed after Math.floor(TIMEOUT * wait) seconds have elapsed. - * This defaults to 1.1, and with default wait, 66 seconds. - * @property {number} SECONDARY_TIMEOUT=0.1 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. - * @property {SASLAnonymous} SASLAnonymous SASL ANONYMOUS authentication. - * @property {SASLPlain} SASLPlain SASL PLAIN authentication - * @property {SASLSHA1} SASLSHA1 SASL SCRAM-SHA-1 authentication - * @property {SASLSHA256} SASLSHA256 SASL SCRAM-SHA-256 authentication - * @property {SASLSHA384} SASLSHA384 SASL SCRAM-SHA-384 authentication - * @property {SASLSHA512} SASLSHA512 SASL SCRAM-SHA-512 authentication - * @property {SASLOAuthBearer} SASLOAuthBearer SASL OAuth Bearer authentication - * @property {SASLExternal} SASLExternal SASL EXTERNAL authentication - * @property {SASLXOAuth2} SASLXOAuth2 SASL X-OAuth2 authentication - * @property {Status} Status - * @property {Object.} NS - * @property {XHTML} XHTML - */ -declare const Strophe: { - XHTML: { - validTag: typeof utils.validTag; - validCSS: typeof utils.validCSS; - validAttribute: typeof utils.validAttribute; - tags: string[]; - attributes: { - a: string[]; - blockquote: string[]; - br: never[]; - cite: string[]; - em: never[]; - img: string[]; - li: string[]; - ol: string[]; - p: string[]; - span: string[]; - strong: never[]; - /** @constant: VERSION */ - ul: string[]; - body: never[]; - }; - css: string[]; - }; +declare namespace Strophe { + export let VERSION: string; + export let TIMEOUT: number; + export let SECONDARY_TIMEOUT: number; + export { shims }; + export { SASLAnonymous }; + export { SASLPlain }; + export { SASLSHA1 }; + export { SASLSHA256 }; + export { SASLSHA384 }; + export { SASLSHA512 }; + export { SASLOAuthBearer }; + export { SASLExternal }; + export { SASLXOAuth2 }; + export { Builder }; + export { ElementType }; + export { ErrorCondition }; + export { LOG_LEVELS as LogLevel }; + export let setLogLevel: (level: import("./constants.js").LogLevel) => void; + export { NS }; + export { SASLMechanism }; + export { Status }; + export { TimedHandler }; /** * This function is used to extend the current namespaces in * Strophe.NS. It takes a key and a value with the key being the @@ -67,80 +32,8 @@ declare const Strophe: { * referenced under Strophe.NS * @param {string} value - The actual namespace. */ - addNamespace(name: string, value: string): void; - /** - * _Private_ function that properly logs an error to the console - * @private - * @param {Error} e - */ - _handleError(e: Error): void; - /** - * 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 {@link Strophe.Connection#rawInput|Strophe.Connection.rawInput()} - * and {@link Strophe.Connection#rawOutput|Strophe.Connection.rawOutput()}. - * - * The different levels and their meanings are - * - * DEBUG - Messages useful for debugging purposes. - * INFO - Informational messages. This is mostly information like - * 'disconnect was called' or 'SASL auth succeeded'. - * WARN - Warnings about potential problems. This is mostly used - * to report transient connection errors like request timeouts. - * ERROR - Some error occurred. - * FATAL - A non-recoverable fatal error occurred. - * - * @param {number} level - The log level of the log message. - * This will be one of the values in Strophe.LogLevel. - * @param {string} msg - The log message. - */ - log(level: number, msg: string): void; - /** - * Log a message at the Strophe.LogLevel.DEBUG level. - * @param {string} msg - The log message. - */ - debug(msg: string): void; - /** - * Log a message at the Strophe.LogLevel.INFO level. - * @param {string} msg - The log message. - */ - info(msg: string): void; - /** - * Log a message at the Strophe.LogLevel.WARN level. - * @param {string} msg - The log message. - */ - warn(msg: string): void; - /** - * Log a message at the Strophe.LogLevel.ERROR level. - * @param {string} msg - The log message. - */ - error(msg: string): void; - /** - * Log a message at the Strophe.LogLevel.FATAL level. - * @param {string} msg - The log message. - */ - fatal(msg: string): void; - /** - * _Private_ variable that keeps track of the request ids for connections. - * @private - */ - _requestId: number; - /** - * _Private_ variable Used to store plugin names that need - * initialization on Strophe.Connection construction. - * @private - * @type {Object.} - */ - _connectionPlugins: { + export function addNamespace(name: string, value: string): void; + export let _connectionPlugins: { [x: string]: Object; }; /** @@ -148,107 +41,9 @@ declare const Strophe: { * @param {string} name - The name of the extension. * @param {Object} ptype - The plugin's prototype. */ - addConnectionPlugin(name: string, ptype: Object): void; - utf16to8(str: string): string; - xorArrayBuffers(x: ArrayBufferLike, y: ArrayBufferLike): ArrayBufferLike; - arrayBufToBase64(buffer: ArrayBufferLike): string; - base64ToArrayBuf(str: string): ArrayBufferLike; - stringToArrayBuf(str: string): ArrayBufferLike; - addCookies(cookies: { - [x: string]: string; - } | { - [x: string]: { - [x: string]: string; - }; - }): void; - xmlGenerator(): Document; - xmlTextNode(text: string): Text; - xmlHtmlNode(html: string): XMLDocument; - xmlElement(name: string, attrs?: string | number | string[][] | { - [x: string]: string | number; - }, text?: string | number): Element; - validTag(tag: string): boolean; - validAttribute(tag: string, attribute: string): boolean; - validCSS(style: string): boolean; - createHtml(node: Node): Node; - copyElement(node: Node): Element | Text; - xmlescape(text: string): string; - xmlunescape(text: string): string; - serialize(elem: Builder | Element): string; - forEachChild(elem: Element, elemName: string, func: Function): void; - isTagEqual(el: Element, name: string): boolean; - getText(elem: Element): string; - escapeNode(node: string): string; - unescapeNode(node: string): string; - getNodeFromJid(jid: string): string; - getDomainFromJid(jid: string): string; - getResourceFromJid(jid: string): string; - getBareJidFromJid(jid: string): string; - default: { - utf16to8: typeof utils.utf16to8; - xorArrayBuffers: typeof utils.xorArrayBuffers; - arrayBufToBase64: typeof utils.arrayBufToBase64; - base64ToArrayBuf: typeof utils.base64ToArrayBuf; - stringToArrayBuf: typeof utils.stringToArrayBuf; - addCookies: typeof utils.addCookies; - }; - /** @constant: VERSION */ - VERSION: string; - TIMEOUT: number; - SECONDARY_TIMEOUT: number; - shims: typeof shims; - Request: typeof Request; - Bosh: typeof Bosh; - Websocket: typeof Websocket; - WorkerWebsocket: typeof WorkerWebsocket; - SASLAnonymous: typeof SASLAnonymous; - SASLPlain: typeof SASLPlain; - SASLSHA1: typeof SASLSHA1; - SASLSHA256: typeof SASLSHA256; - SASLSHA384: typeof SASLSHA384; - SASLSHA512: typeof SASLSHA512; - SASLOAuthBearer: typeof SASLOAuthBearer; - SASLExternal: typeof SASLExternal; - SASLXOAuth2: typeof SASLXOAuth2; - Builder: typeof Builder; - Connection: typeof Connection; - ElementType: { - NORMAL: number; - TEXT: number; - CDATA: number; - FRAGMENT: number; - }; - ErrorCondition: { - BAD_FORMAT: string; - CONFLICT: string; - MISSING_JID_NODE: string; - NO_AUTH_MECH: string; - UNKNOWN_REASON: string; - }; - Handler: typeof Handler; - LogLevel: { - DEBUG: number; - INFO: number; - WARN: number; - ERROR: number; - FATAL: number; - }; - /** @type {Object.} */ - NS: { - [x: string]: string; - }; - SASLMechanism: typeof SASLMechanism; - /** @type {Status} */ - Status: Status; - TimedHandler: typeof TimedHandler; -}; -import * as utils from './utils.js'; -import Builder from './builder.js'; + export function addConnectionPlugin(name: string, ptype: Object): void; +} import * as shims from './shims.js'; -import Request from './request.js'; -import Bosh from './bosh.js'; -import Websocket from './websocket.js'; -import WorkerWebsocket from './worker-websocket.js'; import SASLAnonymous from './sasl-anon.js'; import SASLPlain from './sasl-plain.js'; import SASLSHA1 from './sasl-sha1.js'; @@ -258,8 +53,11 @@ import SASLSHA512 from './sasl-sha512.js'; import SASLOAuthBearer from './sasl-oauthbearer.js'; import SASLExternal from './sasl-external.js'; import SASLXOAuth2 from './sasl-xoauth2.js'; -import Connection from './connection.js'; -import Handler from './handler.js'; +import Builder from './builder.js'; +import { ElementType } from './constants.js'; +import { ErrorCondition } from './constants.js'; +import { LOG_LEVELS } from './constants.js'; +import { NS } from './constants.js'; import SASLMechanism from './sasl.js'; import { Status } from './constants.js'; import TimedHandler from './timed-handler.js'; diff --git a/src/types/handler.d.ts b/src/types/handler.d.ts index d48975a6..c796e0ff 100644 --- a/src/types/handler.d.ts +++ b/src/types/handler.d.ts @@ -2,15 +2,15 @@ export default Handler; /** * _Private_ helper class for managing stanza handlers. * - * A Strophe.Handler encapsulates a user provided callback function to be + * A 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 {@link Strophe.Connection.addHandler} and - * {@link Strophe.Connection.deleteHandler}. + * Users will not use Handler objects directly, but instead they + * will use {@link Connection.addHandler} and + * {@link Connection.deleteHandler}. */ declare class Handler { /** @@ -19,7 +19,7 @@ declare class Handler { * @property {boolean} [HandlerOptions.ignoreNamespaceFragment] */ /** - * Create and initialize a new Strophe.Handler. + * Create and initialize a new Handler. * * @param {Function} handler - A function to be executed when the handler is run. * @param {string} ns - The namespace to match. @@ -53,25 +53,25 @@ declare class Handler { */ getNamespace(elem: Element): string; /** - * Tests if a stanza matches the namespace set for this Strophe.Handler. + * Tests if a stanza matches the namespace set for this Handler. * @param {Element} elem - The XML element to test. * @return {boolean} - true if the stanza matches and false otherwise. */ namespaceMatch(elem: Element): boolean; /** - * Tests if a stanza matches the Strophe.Handler. + * Tests if a stanza matches the Handler. * @param {Element} elem - The XML element to test. * @return {boolean} - true if the stanza matches and false otherwise. */ isMatch(elem: Element): boolean; /** * Run the callback on a matching stanza. - * @param {Element} elem - The DOM element that triggered the Strophe.Handler. + * @param {Element} elem - The DOM element that triggered the Handler. * @return {boolean} - A boolean indicating if the handler should remain active. */ run(elem: Element): boolean; /** - * Get a String representation of the Strophe.Handler object. + * Get a String representation of the Handler object. * @return {string} */ toString(): string; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index ab8c60cb..0ddecc66 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -3,8 +3,225 @@ import { $build } from './builder.js'; import { $iq } from './builder.js'; import { $msg } from './builder.js'; import { $pres } from './builder.js'; -import Strophe from './core.js'; +/** + * A container for all Strophe library functions. + * + * This object is 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. + * + * @namespace Strophe + * @property {Handler} Handler + * @property {Builder} Builder + * @property {Request} Request Represents HTTP Requests made for a BOSH connection + * @property {Bosh} Bosh Support for XMPP-over-HTTP via XEP-0124 (BOSH) + * @property {Websocket} Websocket Support for XMPP over websocket + * @property {WorkerWebsocket} WorkerWebsocket Support for XMPP over websocket in a shared worker + * @property {number} TIMEOUT=1.1 Timeout multiplier. A waiting BOSH HTTP request + * will be considered failed after Math.floor(TIMEOUT * wait) seconds have elapsed. + * This defaults to 1.1, and with default wait, 66 seconds. + * @property {number} SECONDARY_TIMEOUT=0.1 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. + * @property {SASLAnonymous} SASLAnonymous SASL ANONYMOUS authentication. + * @property {SASLPlain} SASLPlain SASL PLAIN authentication + * @property {SASLSHA1} SASLSHA1 SASL SCRAM-SHA-1 authentication + * @property {SASLSHA256} SASLSHA256 SASL SCRAM-SHA-256 authentication + * @property {SASLSHA384} SASLSHA384 SASL SCRAM-SHA-384 authentication + * @property {SASLSHA512} SASLSHA512 SASL SCRAM-SHA-512 authentication + * @property {SASLOAuthBearer} SASLOAuthBearer SASL OAuth Bearer authentication + * @property {SASLExternal} SASLExternal SASL EXTERNAL authentication + * @property {SASLXOAuth2} SASLXOAuth2 SASL X-OAuth2 authentication + * @property {Status} Status + * @property {Object.} NS + * @property {XHTML} XHTML + */ +export const Strophe: { + shims: typeof shims; + Request: typeof Request; + Bosh: typeof Bosh; + Websocket: typeof Websocket; + WorkerWebsocket: typeof WorkerWebsocket; + Connection: typeof Connection; + Handler: typeof Handler; + SASLAnonymous: typeof SASLAnonymous; + SASLPlain: typeof SASLPlain; + SASLSHA1: typeof SASLSHA1; + SASLSHA256: typeof SASLSHA256; + SASLSHA384: typeof SASLSHA384; + SASLSHA512: typeof SASLSHA512; + SASLOAuthBearer: typeof SASLOAuthBearer; + SASLExternal: typeof SASLExternal; + SASLXOAuth2: typeof SASLXOAuth2; + Builder: typeof Builder; + ElementType: { + NORMAL: number; + TEXT: number; + CDATA: number; + FRAGMENT: number; + }; + ErrorCondition: { + BAD_FORMAT: string; + CONFLICT: string; /** + * 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. + * @example: Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); + * + * @param {string} name - The name under which the namespace will be + * referenced under Strophe.NS + * @param {string} value - The actual namespace. + */ + MISSING_JID_NODE: string; + NO_AUTH_MECH: string; + UNKNOWN_REASON: string; + }; + LogLevel: { + DEBUG: number; + INFO: number; + WARN: number; + ERROR: number; + FATAL: number; + }; + /** @type {Object.} */ + NS: { + [x: string]: string; + }; + SASLMechanism: typeof SASLMechanism; + /** @type {Status} */ + Status: Status; + TimedHandler: typeof TimedHandler; + XHTML: { + validTag: typeof utils.validTag; + validCSS: typeof utils.validCSS; + validAttribute: typeof utils.validAttribute; + tags: string[]; + attributes: { + a: string[]; + blockquote: string[]; + br: never[]; + cite: string[]; + em: never[]; + img: string[]; + li: string[]; + ol: string[]; + p: string[]; + span: string[]; + strong: never[]; + ul: string[]; + body: never[]; + }; + css: string[]; + }; + /** + * Render a DOM element and all descendants to a String. + * @method Strophe.serialize + * @param {Element|Builder} elem - A DOM element. + * @return {string} - The serialized element tree as a String. + */ + serialize(elem: Element | Builder): string; + /** + * @typedef {import('./constants').LogLevel} LogLevel + * + * Library consumers can use this function to set the log level of Strophe. + * The default log level is Strophe.LogLevel.INFO. + * @param {LogLevel} level + * @example Strophe.setLogLevel(Strophe.LogLevel.DEBUG); + */ + setLogLevel(level: import("./constants.js").LogLevel): void; + /** + * 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. + * @example: Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub"); + * + * @param {string} name - The name under which the namespace will be + * referenced under Strophe.NS + * @param {string} value - The actual namespace. + */ + addNamespace(name: string, value: string): void; + /** + * Extends the Strophe.Connection object with the given plugin. + * @param {string} name - The name of the extension. + * @param {Object} ptype - The plugin's prototype. + */ + addConnectionPlugin(name: string, ptype: Object): void; + handleError(e: Error): void; + utf16to8(str: string): string; + xorArrayBuffers(x: ArrayBufferLike, y: ArrayBufferLike): ArrayBufferLike; + arrayBufToBase64(buffer: ArrayBufferLike): string; + base64ToArrayBuf(str: string): ArrayBufferLike; + stringToArrayBuf(str: string): ArrayBufferLike; + addCookies(cookies: { + [x: string]: string; + } | { + [x: string]: { + [x: string]: string; + }; + }): void; + xmlGenerator(): Document; + xmlTextNode(text: string): Text; + xmlHtmlNode(html: string): XMLDocument; + xmlElement(name: string, attrs?: string | number | string[][] | { + [x: string]: string | number; + }, text?: string | number): Element; + validTag(tag: string): boolean; + validAttribute(tag: string, attribute: string): boolean; + validCSS(style: string): boolean; + createHtml(node: Node): Node; + copyElement(node: Node): Element | Text; + xmlescape(text: string): string; + xmlunescape(text: string): string; + forEachChild(elem: Element, elemName: string, func: Function): void; + isTagEqual(el: Element, name: string): boolean; + getText(elem: Element): string; + escapeNode(node: string): string; + unescapeNode(node: string): string; + getNodeFromJid(jid: string): string; + getDomainFromJid(jid: string): string; + getResourceFromJid(jid: string): string; + getBareJidFromJid(jid: string): string; + default: { + utf16to8: typeof utils.utf16to8; + xorArrayBuffers: typeof utils.xorArrayBuffers; + arrayBufToBase64: typeof utils.arrayBufToBase64; + base64ToArrayBuf: typeof utils.base64ToArrayBuf; + stringToArrayBuf: typeof utils.stringToArrayBuf; + addCookies: typeof utils.addCookies; + }; + /** @constant: VERSION */ + VERSION: string; + /** + * @returns {number} + */ + TIMEOUT: number; + /** + * @returns {number} + */ + SECONDARY_TIMEOUT: number; +}; import { stx } from './stanza.js'; import { toStanza } from './stanza.js'; -export { Builder, $build, $iq, $msg, $pres, Strophe, stx, toStanza }; +import * as shims from './shims.js'; +import Request from './request.js'; +import Bosh from './bosh.js'; +import Websocket from './websocket.js'; +import WorkerWebsocket from './worker-websocket.js'; +import Connection from './connection.js'; +import Handler from './handler.js'; +import SASLAnonymous from './sasl-anon.js'; +import SASLPlain from './sasl-plain.js'; +import SASLSHA1 from './sasl-sha1.js'; +import SASLSHA256 from './sasl-sha256.js'; +import SASLSHA384 from './sasl-sha384.js'; +import SASLSHA512 from './sasl-sha512.js'; +import SASLOAuthBearer from './sasl-oauthbearer.js'; +import SASLExternal from './sasl-external.js'; +import SASLXOAuth2 from './sasl-xoauth2.js'; +import SASLMechanism from './sasl.js'; +import { Status } from './constants.js'; +import TimedHandler from './timed-handler.js'; +import * as utils from './utils.js'; +export { Builder, $build, $iq, $msg, $pres, stx, toStanza }; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/src/types/log.d.ts b/src/types/log.d.ts new file mode 100644 index 00000000..06ef5c57 --- /dev/null +++ b/src/types/log.d.ts @@ -0,0 +1,58 @@ +export default log; +export type LogLevel = import('./constants').LogLevel; +declare namespace log { + /** + * Library consumers can use this function to set the log level of Strophe. + * The default log level is Strophe.LogLevel.INFO. + * @param {LogLevel} level + * @example Strophe.setLogLevel(Strophe.LogLevel.DEBUG); + */ + function setLogLevel(level: import("./constants.js").LogLevel): void; + /** + * + * Please note that data sent and received over the wire is logged + * via {@link Strophe.Connection#rawInput|Strophe.Connection.rawInput()} + * and {@link Strophe.Connection#rawOutput|Strophe.Connection.rawOutput()}. + * + * The different levels and their meanings are + * + * DEBUG - Messages useful for debugging purposes. + * INFO - Informational messages. This is mostly information like + * 'disconnect was called' or 'SASL auth succeeded'. + * WARN - Warnings about potential problems. This is mostly used + * to report transient connection errors like request timeouts. + * ERROR - Some error occurred. + * FATAL - A non-recoverable fatal error occurred. + * + * @param {number} level - The log level of the log message. + * This will be one of the values in Strophe.LOG_LEVELS. + * @param {string} msg - The log message. + */ + function log(level: number, msg: string): void; + /** + * Log a message at the Strophe.LOG_LEVELS.DEBUG level. + * @param {string} msg - The log message. + */ + function debug(msg: string): void; + /** + * Log a message at the Strophe.LOG_LEVELS.INFO level. + * @param {string} msg - The log message. + */ + function info(msg: string): void; + /** + * Log a message at the Strophe.LOG_LEVELS.WARN level. + * @param {string} msg - The log message. + */ + function warn(msg: string): void; + /** + * Log a message at the Strophe.LOG_LEVELS.ERROR level. + * @param {string} msg - The log message. + */ + function error(msg: string): void; + /** + * Log a message at the Strophe.LOG_LEVELS.FATAL level. + * @param {string} msg - The log message. + */ + function fatal(msg: string): void; +} +//# sourceMappingURL=log.d.ts.map \ No newline at end of file diff --git a/src/types/request.d.ts b/src/types/request.d.ts index bb62389e..aae43625 100644 --- a/src/types/request.d.ts +++ b/src/types/request.d.ts @@ -3,7 +3,7 @@ export default Request; * Helper class that provides a cross implementation abstraction * for a BOSH related XMLHttpRequest. * - * The Strophe.Request class is used internally to encapsulate BOSH request + * The Request class is used internally to encapsulate BOSH request * information. It is not meant to be used from user's code. * * @property {number} id @@ -12,7 +12,7 @@ export default Request; */ declare class Request { /** - * Create and initialize a new Strophe.Request object. + * Create and initialize a new Request object. * * @param {Element} elem - The XML data to be sent in the request. * @param {Function} func - The function that will be called when the diff --git a/src/types/stanza.d.ts b/src/types/stanza.d.ts index c50f6369..b3f5d198 100644 --- a/src/types/stanza.d.ts +++ b/src/types/stanza.d.ts @@ -1,7 +1,7 @@ /** - * @param { string } string - * @param { boolean } [throwErrorIfInvalidNS] - * @return { Element } + * @param {string} string + * @param {boolean} [throwErrorIfInvalidNS] + * @return {Element} */ export function toStanza(string: string, throwErrorIfInvalidNS?: boolean): Element; /** diff --git a/src/types/utils.d.ts b/src/types/utils.d.ts index 72d12e43..0013aa56 100644 --- a/src/types/utils.d.ts +++ b/src/types/utils.d.ts @@ -1,3 +1,8 @@ +/** + * Properly logs an error to the console + * @param {Error} e + */ +export function handleError(e: Error): void; /** * @param {string} str * @return {string} @@ -131,13 +136,6 @@ export function xmlescape(text: string): string; * @return {string} - Unescaped text. */ export function xmlunescape(text: string): string; -/** - * Render a DOM element and all descendants to a String. - * @method Strophe.serialize - * @param {Element|Builder} elem - A DOM element. - * @return {string} - The serialized element tree as a String. - */ -export function serialize(elem: Element | Builder): string; /** * Map a function over some or all child elements of a given element. * @@ -215,7 +213,6 @@ export function getResourceFromJid(jid: string): string; export function getBareJidFromJid(jid: string): string; export { utils as default }; export type XHTMLAttrs = 'a' | 'blockquote' | 'br' | 'cite' | 'em' | 'img' | 'li' | 'ol' | 'p' | 'span' | 'strong' | 'ul' | 'body'; -import Builder from './builder.js'; declare namespace utils { export { utf16to8 }; export { xorArrayBuffers }; diff --git a/src/types/websocket.d.ts b/src/types/websocket.d.ts index a2dd9d4e..57102377 100644 --- a/src/types/websocket.d.ts +++ b/src/types/websocket.d.ts @@ -1,17 +1,16 @@ export default Websocket; -export type Builder = import("./builder.js").default; export type Connection = import("./connection.js").default; /** * Helper class that handles WebSocket Connections * - * The Strophe.WebSocket class is used internally by Strophe.Connection + * The WebSocket class is used internally by Connection * to encapsulate WebSocket sessions. It is not meant to be used from user's code. */ declare class Websocket { /** - * Create and initialize a Strophe.WebSocket object. + * Create and initialize a WebSocket object. * Currently only sets the connection Object. - * @param {Connection} connection - The Strophe.Connection that will use WebSockets. + * @param {Connection} connection - The Connection that will use WebSockets. */ constructor(connection: Connection); _conn: import("./connection.js").default; @@ -19,7 +18,7 @@ declare class Websocket { /** * _Private_ helper function to generate the start tag for WebSockets * @private - * @return {Builder} - A Strophe.Builder with a element. + * @return {Builder} - A Builder with a element. */ private _buildStream; /** @@ -38,7 +37,7 @@ declare class Websocket { */ _reset(): void; /** - * _Private_ function called by Strophe.Connection.connect + * _Private_ function called by Connection.connect * * Creates a WebSocket for a connection and assigns Callbacks to it. * Does nothing if there already is a WebSocket. @@ -65,7 +64,7 @@ declare class Websocket { readyState: string; }; /** - * _Private_ function called by Strophe.Connection._connect_cb + * _Private_ function called by Connection._connect_cb * checks for stream:error * @param {Element} bodyWrap - The received stanza. */ @@ -88,13 +87,13 @@ declare class Websocket { _onInitialMessage(message: MessageEvent): void; /** * Called by _onInitialMessage in order to replace itself with the general message handler. - * This method is overridden by Strophe.WorkerWebsocket, which manages a + * This method is overridden by WorkerWebsocket, which manages a * websocket connection via a service worker and doesn't have direct access * to the socket. */ _replaceMessageHandler(): void; /** - * _Private_ function called by Strophe.Connection.disconnect + * _Private_ function called by Connection.disconnect * Disconnects and sends a last stanza if one is given * @param {Element|Builder} [pres] - This stanza will be sent before disconnecting. */ @@ -153,7 +152,7 @@ declare class Websocket { */ _onError(error: Object): void; /** - * _Private_ function called by Strophe.Connection._onIdle + * _Private_ function called by Connection._onIdle * sends all queued stanzas */ _onIdle(): void; @@ -197,4 +196,5 @@ declare class Websocket { _sendRestart(): void; } import { WebSocket } from './shims'; +import Builder from './builder.js'; //# sourceMappingURL=websocket.d.ts.map \ No newline at end of file diff --git a/src/types/worker-websocket.d.ts b/src/types/worker-websocket.d.ts index 289f5aab..b65b5fbb 100644 --- a/src/types/worker-websocket.d.ts +++ b/src/types/worker-websocket.d.ts @@ -1,6 +1,4 @@ export default WorkerWebsocket; -export type Connection = import("./connection.js").default; -export type Builder = import("./builder.js").default; /** * Helper class that handles a websocket connection inside a shared worker. */ @@ -33,4 +31,5 @@ declare class WorkerWebsocket extends Websocket { private _onWorkerMessage; } import Websocket from './websocket.js'; +import Builder from './builder.js'; //# sourceMappingURL=worker-websocket.d.ts.map \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index b809ac32..0ac4e96b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,8 +1,18 @@ /* global btoa */ -import Strophe from './core.js'; +import log from './log.js'; import * as shims from './shims.js'; import { ElementType, XHTML } from './constants.js'; -import Builder from './builder.js'; + +/** + * Properly logs an error to the console + * @param {Error} e + */ +export function handleError(e) { + if (typeof e.stack !== 'undefined') { + log.fatal(e.stack); + } + log.fatal('error: ' + e.message); +} /** * @param {string} str @@ -79,7 +89,7 @@ export function stringToArrayBuf(str) { */ export function addCookies(cookies) { if (typeof document === 'undefined') { - Strophe.log(Strophe.LogLevel.ERROR, `addCookies: not adding any cookies, since there's no document object`); + log.error(`addCookies: not adding any cookies, since there's no document object`); } /** @@ -410,49 +420,6 @@ export function xmlunescape(text) { return text; } -/** - * Render a DOM element and all descendants to a String. - * @method Strophe.serialize - * @param {Element|Builder} elem - A DOM element. - * @return {string} - The serialized element tree as a String. - */ -export function serialize(elem) { - if (!elem) return null; - - const el = elem instanceof Builder ? elem.tree() : elem; - - const names = [...Array(el.attributes.length).keys()].map((i) => el.attributes[i].nodeName); - names.sort(); - let result = names.reduce( - (a, n) => `${a} ${n}="${xmlescape(el.attributes.getNamedItem(n).value)}"`, - `<${el.nodeName}` - ); - - if (el.childNodes.length > 0) { - result += '>'; - for (let i = 0; i < el.childNodes.length; i++) { - const child = el.childNodes[i]; - switch (child.nodeType) { - case ElementType.NORMAL: - // normal element, so recurse - result += serialize(/** @type {Element} */ (child)); - break; - case ElementType.TEXT: - // text element to escape values - result += xmlescape(child.nodeValue); - break; - case ElementType.CDATA: - // cdata section so don't escape values - result += ''; - } - } - result += ''; - } else { - result += '/>'; - } - return result; -} - /** * Map a function over some or all child elements of a given element. * diff --git a/src/websocket.js b/src/websocket.js index 80b1fc83..b83acbe8 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -13,25 +13,25 @@ /* global clearTimeout, location */ /** - * @typedef {import("./builder.js").default} Builder * @typedef {import("./connection.js").default} Connection */ import { DOMParser, WebSocket } from './shims'; -import { $build } from './builder.js'; -import Strophe from './core'; +import Builder, { $build } from './builder.js'; +import log from './log.js'; +import { NS, ErrorCondition, Status } from './constants.js'; /** * Helper class that handles WebSocket Connections * - * The Strophe.WebSocket class is used internally by Strophe.Connection + * The WebSocket class is used internally by Connection * to encapsulate WebSocket sessions. It is not meant to be used from user's code. */ class Websocket { /** - * Create and initialize a Strophe.WebSocket object. + * Create and initialize a WebSocket object. * Currently only sets the connection Object. - * @param {Connection} connection - The Strophe.Connection that will use WebSockets. + * @param {Connection} connection - The Connection that will use WebSockets. */ constructor(connection) { this._conn = connection; @@ -61,11 +61,11 @@ class Websocket { /** * _Private_ helper function to generate the start tag for WebSockets * @private - * @return {Builder} - A Strophe.Builder with a element. + * @return {Builder} - A Builder with a element. */ _buildStream() { return $build('open', { - 'xmlns': Strophe.NS.FRAMING, + 'xmlns': NS.FRAMING, 'to': this._conn.domain, 'version': '1.0', }); @@ -81,7 +81,7 @@ class Websocket { _checkStreamError(bodyWrap, connectstatus) { let errors; if (bodyWrap.getElementsByTagNameNS) { - errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, 'error'); + errors = bodyWrap.getElementsByTagNameNS(NS.STREAM, 'error'); } else { errors = bodyWrap.getElementsByTagName('stream:error'); } @@ -116,7 +116,7 @@ class Websocket { if (text) { errorString += ' - ' + text; } - Strophe.error(errorString); + log.error(errorString); // close the connection on stream_error this._conn._changeConnectStatus(connectstatus, condition); @@ -136,7 +136,7 @@ class Websocket { } /** - * _Private_ function called by Strophe.Connection.connect + * _Private_ function called by Connection.connect * * Creates a WebSocket for a connection and assigns Callbacks to it. * Does nothing if there already is a WebSocket. @@ -171,14 +171,14 @@ class Websocket { } /** - * _Private_ function called by Strophe.Connection._connect_cb + * _Private_ function called by Connection._connect_cb * checks for stream:error * @param {Element} bodyWrap - The received stanza. */ _connect_cb(bodyWrap) { - const error = this._checkStreamError(bodyWrap, Strophe.Status.CONNFAIL); + const error = this._checkStreamError(bodyWrap, Status.CONNFAIL); if (error) { - return Strophe.Status.CONNFAIL; + return Status.CONNFAIL; } } @@ -196,7 +196,7 @@ class Websocket { const ns = message.getAttribute('xmlns'); if (typeof ns !== 'string') { error = 'Missing xmlns in '; - } else if (ns !== Strophe.NS.FRAMING) { + } else if (ns !== NS.FRAMING) { error = 'Wrong xmlns in : ' + ns; } @@ -208,7 +208,7 @@ class Websocket { } if (error) { - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error); + this._conn._changeConnectStatus(Status.CONNFAIL, error); this._conn._doDisconnect(); return false; } @@ -252,7 +252,7 @@ class Websocket { (service.indexOf('wss:') >= 0 && see_uri.indexOf('wss:') >= 0) || service.indexOf('ws:') >= 0; if (isSecureRedirect) { this._conn._changeConnectStatus( - Strophe.Status.REDIRECT, + Status.REDIRECT, 'Received see-other-uri, resetting connection' ); this._conn.reset(); @@ -260,7 +260,7 @@ class Websocket { this._connect(); } } else { - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, 'Received closing stream'); + this._conn._changeConnectStatus(Status.CONNFAIL, 'Received closing stream'); this._conn._doDisconnect(); } } else { @@ -273,7 +273,7 @@ class Websocket { /** * Called by _onInitialMessage in order to replace itself with the general message handler. - * This method is overridden by Strophe.WorkerWebsocket, which manages a + * This method is overridden by WorkerWebsocket, which manages a * websocket connection via a service worker and doesn't have direct access * to the socket. */ @@ -283,7 +283,7 @@ class Websocket { } /** - * _Private_ function called by Strophe.Connection.disconnect + * _Private_ function called by Connection.disconnect * Disconnects and sends a last stanza if one is given * @param {Element|Builder} [pres] - This stanza will be sent before disconnecting. */ @@ -292,14 +292,14 @@ class Websocket { if (pres) { this._conn.send(pres); } - const close = $build('close', { 'xmlns': Strophe.NS.FRAMING }); + const close = $build('close', { 'xmlns': NS.FRAMING }); this._conn.xmlOutput(close.tree()); - const closeString = Strophe.serialize(close); + const closeString = Builder.serialize(close); this._conn.rawOutput(closeString); try { this.socket.send(closeString); } catch (e) { - Strophe.warn("Couldn't send tag."); + log.warn("Couldn't send tag."); } } setTimeout(() => this._conn._doDisconnect(), 0); @@ -310,7 +310,7 @@ class Websocket { * Just closes the Socket for WebSockets */ _doDisconnect() { - Strophe.debug('WebSockets _doDisconnect was called'); + log.debug('WebSockets _doDisconnect was called'); this._closeSocket(); } @@ -338,7 +338,7 @@ class Websocket { this.socket.onmessage = null; this.socket.close(); } catch (e) { - Strophe.debug(e.message); + log.debug(e.message); } } this.socket = null; @@ -359,21 +359,21 @@ class Websocket { */ _onClose(e) { if (this._conn.connected && !this._conn.disconnecting) { - Strophe.error('Websocket closed unexpectedly'); + log.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'); + log.error('Websocket closed unexcectedly'); this._conn._changeConnectStatus( - Strophe.Status.CONNFAIL, + Status.CONNFAIL, 'The WebSocket connection could not be established or was disconnected.' ); this._conn._doDisconnect(); } else { - Strophe.debug('Websocket closed'); + log.debug('Websocket closed'); } } @@ -388,8 +388,8 @@ class Websocket { * @param {connectionCallback} callback */ _no_auth_received(callback) { - Strophe.error('Server did not offer a supported authentication mechanism'); - this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.NO_AUTH_MECH); + log.error('Server did not offer a supported authentication mechanism'); + this._conn._changeConnectStatus(Status.CONNFAIL, ErrorCondition.NO_AUTH_MECH); callback?.call(this._conn); this._conn._doDisconnect(); } @@ -411,16 +411,16 @@ class Websocket { * @param {Object} error - The websocket error. */ _onError(error) { - Strophe.error('Websocket error ' + JSON.stringify(error)); + log.error('Websocket error ' + JSON.stringify(error)); this._conn._changeConnectStatus( - Strophe.Status.CONNFAIL, + Status.CONNFAIL, 'The WebSocket connection could not be established or was disconnected.' ); this._disconnect(); } /** - * _Private_ function called by Strophe.Connection._onIdle + * _Private_ function called by Connection._onIdle * sends all queued stanzas */ _onIdle() { @@ -430,7 +430,7 @@ class Websocket { if (data[i] !== null) { const stanza = data[i] === 'restart' ? this._buildStream().tree() : data[i]; if (stanza === 'restart') throw new Error('Wrong type for stanza'); // Shut up tsc - const rawStanza = Strophe.serialize(stanza); + const rawStanza = Builder.serialize(stanza); this._conn.xmlOutput(stanza); this._conn.rawOutput(rawStanza); this.socket.send(rawStanza); @@ -484,7 +484,7 @@ class Websocket { elem = new DOMParser().parseFromString(data, 'text/xml').documentElement; } - if (this._checkStreamError(elem, Strophe.Status.ERROR)) { + if (this._checkStreamError(elem, Status.ERROR)) { return; } @@ -495,7 +495,7 @@ class Websocket { elem.firstElementChild.getAttribute('type') === 'unavailable' ) { this._conn.xmlInput(elem); - this._conn.rawInput(Strophe.serialize(elem)); + this._conn.rawInput(Builder.serialize(elem)); // if we are already disconnecting we will ignore the unavailable stanza and // wait for the tag before we close the connection return; @@ -509,11 +509,11 @@ class Websocket { * @private */ _onOpen() { - Strophe.debug('Websocket open'); + log.debug('Websocket open'); const start = this._buildStream(); this._conn.xmlOutput(start.tree()); - const startString = Strophe.serialize(start); + const startString = Builder.serialize(start); this._conn.rawOutput(startString); this.socket.send(startString); } diff --git a/src/worker-websocket.js b/src/worker-websocket.js index 7aaaa3a9..e8aaf4c5 100644 --- a/src/worker-websocket.js +++ b/src/worker-websocket.js @@ -1,22 +1,23 @@ /** * @license MIT * @copyright JC Brand - * - * @typedef {import("./connection.js").default} Connection - * @typedef {import("./builder.js").default} Builder */ - import Websocket from './websocket.js'; -import { $build } from './builder.js'; -import Strophe from './core.js'; +import log from './log.js'; +import Builder, { $build } from './builder.js'; +import { LOG_LEVELS, NS, Status } from './constants.js'; /** * Helper class that handles a websocket connection inside a shared worker. */ class WorkerWebsocket extends Websocket { /** - * Create and initialize a Strophe.WorkerWebsocket object. - * @param {Connection} connection - The Strophe.Connection + * @typedef {import("./connection.js").default} Connection + */ + + /** + * Create and initialize a WorkerWebsocket object. + * @param {Connection} connection - The Connection */ constructor(connection) { super(connection); @@ -24,7 +25,7 @@ class WorkerWebsocket extends Websocket { this.worker = new SharedWorker(this._conn.options.worker, 'Strophe XMPP Connection'); this.worker.onerror = (e) => { console?.error(e); - Strophe.log(Strophe.LogLevel.ERROR, `Shared Worker Error: ${e}`); + log.error(`Shared Worker Error: ${e}`); }; } @@ -73,17 +74,17 @@ class WorkerWebsocket extends Websocket { * @param {string} jid */ _attachCallback(status, jid) { - if (status === Strophe.Status.ATTACHED) { + if (status === Status.ATTACHED) { this._conn.jid = jid; this._conn.authenticated = true; this._conn.connected = true; this._conn.restored = true; - this._conn._changeConnectStatus(Strophe.Status.ATTACHED); - } else if (status === Strophe.Status.ATTACHFAIL) { + this._conn._changeConnectStatus(Status.ATTACHED); + } else if (status === Status.ATTACHFAIL) { this._conn.authenticated = false; this._conn.connected = false; this._conn.restored = false; - this._conn._changeConnectStatus(Strophe.Status.ATTACHFAIL); + this._conn._changeConnectStatus(Status.ATTACHFAIL); } } @@ -92,9 +93,9 @@ class WorkerWebsocket extends Websocket { */ _disconnect(pres) { pres && this._conn.send(pres); - const close = $build('close', { 'xmlns': Strophe.NS.FRAMING }); + const close = $build('close', { 'xmlns': NS.FRAMING }); this._conn.xmlOutput(close.tree()); - const closeString = Strophe.serialize(close); + const closeString = Builder.serialize(close); this._conn.rawOutput(closeString); this.worker.port.postMessage(['send', closeString]); this._conn._doDisconnect(); @@ -121,14 +122,6 @@ class WorkerWebsocket extends Websocket { * @param {MessageEvent} ev */ _onWorkerMessage(ev) { - /** @type {Object.} */ - const lmap = {}; - lmap['debug'] = Strophe.LogLevel.DEBUG; - lmap['info'] = Strophe.LogLevel.INFO; - lmap['warn'] = Strophe.LogLevel.WARN; - lmap['error'] = Strophe.LogLevel.ERROR; - lmap['fatal'] = Strophe.LogLevel.FATAL; - const { data } = ev; const method_name = data[0]; if (method_name === '_onMessage') { @@ -140,14 +133,22 @@ class WorkerWebsocket extends Websocket { (method_name) ].apply(this, ev.data.slice(1)); } catch (e) { - Strophe.log(Strophe.LogLevel.ERROR, e); + log.error(e); } } else if (method_name === 'log') { + /** @type {Object.} */ + const lmap = { + debug: LOG_LEVELS.DEBUG, + info: LOG_LEVELS.INFO, + warn: LOG_LEVELS.WARN, + error: LOG_LEVELS.ERROR, + fatal: LOG_LEVELS.FATAL, + }; const level = data[1]; const msg = data[2]; - Strophe.log(lmap[level], msg); + log.log(lmap[level], msg); } else { - Strophe.log(Strophe.LogLevel.ERROR, `Found unhandled service worker message: ${data}`); + log.error(`Found unhandled service worker message: ${data}`); } } }