react-strophe
Version:
Strophe.js is an XMPP library for JavaScript
1,056 lines (978 loc) • 31.8 kB
JavaScript
/*
This program is distributed under the terms of the MIT license.
Please see the LICENSE file for details.
Copyright 2006-2008, OGG, LLC
*/
/* jshint undef: true, unused: true:, noarg: true, latedef: true */
/* global define, window, setTimeout, clearTimeout, XMLHttpRequest, ActiveXObject, Strophe, $build */
/** PrivateClass: Strophe.Request
* _Private_ helper class that provides a cross implementation abstraction
* for a BOSH related XMLHttpRequest.
*
* The Strophe.Request class is used internally to encapsulate BOSH request
* information. It is not meant to be used from user's code.
*/
/** PrivateConstructor: Strophe.Request
* Create and initialize a new Strophe.Request object.
*
* Parameters:
* (XMLElement) elem - The XML data to be sent in the request.
* (Function) func - The function that will be called when the
* XMLHttpRequest readyState changes.
* (Integer) rid - The BOSH rid attribute associated with this request.
* (Integer) sends - The number of times this same request has been sent.
*/
var core = require("./core");
var Strophe = core.Strophe;
var $build = core.$build;
Strophe.Request = function(elem, func, rid, sends) {
this.id = ++Strophe._requestId;
this.xmlData = elem;
this.data = Strophe.serialize(elem);
// save original function in case we need to make a new request
// from this one.
this.origFunc = func;
this.func = func;
this.rid = rid;
this.date = NaN;
this.sends = sends || 0;
this.abort = false;
this.dead = null;
this.age = function() {
if (!this.date) {
return 0;
}
var now = new Date();
return (now - this.date) / 1000;
};
this.timeDead = function() {
if (!this.dead) {
return 0;
}
var now = new Date();
return (now - this.dead) / 1000;
};
this.xhr = this._newXHR();
};
Strophe.Request.prototype = {
/** PrivateFunction: getResponse
* Get a response from the underlying XMLHttpRequest.
*
* This function attempts to get a response from the request and checks
* for errors.
*
* Throws:
* "parsererror" - A parser error occured.
* "badformat" - The entity has sent XML that cannot be processed.
*
* Returns:
* The DOM element tree of the response.
*/
getResponse: function() {
var node = null;
if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {
node = this.xhr.responseXML.documentElement;
if (node.tagName === "parsererror") {
Strophe.error("invalid response received");
Strophe.error("responseText: " + this.xhr.responseText);
Strophe.error(
"responseXML: " + Strophe.serialize(this.xhr.responseXML)
);
throw "parsererror";
}
} else if (this.xhr.responseText) {
// In React Native, we may get responseText but no responseXML. We can try to parse it manually.
Strophe.debug(
"Got responseText but no responseXML; attempting to parse it with DOMParser..."
);
node = new DOMParser().parseFromString(
this.xhr.responseText,
"application/xml"
).documentElement;
if (!node) {
throw new Error("Parsing produced null node");
} else if (node.querySelector("parsererror")) {
Strophe.error(
"invalid response received: " +
node.querySelector("parsererror").textContent
);
Strophe.error("responseText: " + this.xhr.responseText);
throw "badformat";
}
}
return node;
},
/** PrivateFunction: _newXHR
* _Private_ helper function to create XMLHttpRequests.
*
* This function creates XMLHttpRequests across all implementations.
*
* Returns:
* A new XMLHttpRequest.
*/
_newXHR: function() {
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
if (xhr.overrideMimeType) {
xhr.overrideMimeType("text/xml; charset=utf-8");
}
} else if (window.ActiveXObject) {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// use Function.bind() to prepend ourselves as an argument
xhr.onreadystatechange = this.func.bind(null, this);
return xhr;
}
};
/** Class: Strophe.Bosh
* _Private_ helper class that handles BOSH Connections
*
* The Strophe.Bosh class is used internally by Strophe.Connection
* to encapsulate BOSH sessions. It is not meant to be used from user's code.
*/
/** File: bosh.js
* A JavaScript library to enable BOSH in Strophejs.
*
* this library uses Bidirectional-streams Over Synchronous HTTP (BOSH)
* to emulate a persistent, stateful, two-way connection to an XMPP server.
* More information on BOSH can be found in XEP 124.
*/
/** PrivateConstructor: Strophe.Bosh
* Create and initialize a Strophe.Bosh object.
*
* Parameters:
* (Strophe.Connection) connection - The Strophe.Connection that will use BOSH.
*
* Returns:
* A new Strophe.Bosh object.
*/
Strophe.Bosh = function(connection) {
this._conn = connection;
/* request id for body tags */
this.rid = Math.floor(Math.random() * 4294967295);
/* The current session ID. */
this.sid = null;
// default BOSH values
this.hold = 1;
this.wait = 60;
this.window = 5;
this.errors = 0;
this.inactivity = null;
this.lastResponseHeaders = null;
this._requests = [];
};
Strophe.Bosh.prototype = {
/** Variable: strip
*
* BOSH-Connections will have all stanzas wrapped in a <body> tag when
* passed to <Strophe.Connection.xmlInput> or <Strophe.Connection.xmlOutput>.
* To strip this tag, User code can set <Strophe.Bosh.strip> to "body":
*
* > Strophe.Bosh.prototype.strip = "body";
*
* This will enable stripping of the body tag in both
* <Strophe.Connection.xmlInput> and <Strophe.Connection.xmlOutput>.
*/
strip: null,
/** PrivateFunction: _buildBody
* _Private_ helper function to generate the <body/> wrapper for BOSH.
*
* Returns:
* A Strophe.Builder with a <body/> element.
*/
_buildBody: function() {
var bodyWrap = $build("body", {
rid: this.rid++,
xmlns: Strophe.NS.HTTPBIND
});
if (this.sid !== null) {
bodyWrap.attrs({ sid: this.sid });
}
if (this._conn.options.keepalive && this._conn._sessionCachingSupported()) {
this._cacheSession();
}
return bodyWrap;
},
/** PrivateFunction: _reset
* Reset the connection.
*
* This function is called by the reset function of the Strophe Connection
*/
_reset: function() {
this.rid = Math.floor(Math.random() * 4294967295);
this.sid = null;
this.errors = 0;
if (this._conn._sessionCachingSupported()) {
window.sessionStorage.removeItem("strophe-bosh-session");
}
this._conn.nextValidRid(this.rid);
},
/** PrivateFunction: _connect
* _Private_ function that initializes the BOSH connection.
*
* Creates and sends the Request that initializes the BOSH connection.
*/
_connect: function(wait, hold, route) {
this.wait = wait || this.wait;
this.hold = hold || this.hold;
this.errors = 0;
// build the body tag
var body = this._buildBody().attrs({
to: this._conn.domain,
"xml:lang": "en",
wait: this.wait,
hold: this.hold,
content: "text/xml; charset=utf-8",
ver: "1.6",
"xmpp:version": "1.0",
"xmlns:xmpp": Strophe.NS.BOSH
});
if (route) {
body.attrs({
route: route
});
}
var _connect_cb = this._conn._connect_cb;
this._requests.push(
new Strophe.Request(
body.tree(),
this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)),
body.tree().getAttribute("rid")
)
);
this._throttledRequestHandler();
},
/** PrivateFunction: _attach
* Attach to an already created and authenticated BOSH session.
*
* This function is provided to allow Strophe to attach to BOSH
* sessions which have been created externally, perhaps by a Web
* application. This is often used to support auto-login type features
* without putting user credentials into the page.
*
* Parameters:
* (String) jid - The full JID that is bound by the session.
* (String) sid - The SID of the BOSH session.
* (String) rid - The current RID of the BOSH session. This RID
* will be used by the next request.
* (Function) callback The connect callback function.
* (Integer) wait - The optional HTTPBIND wait value. This is the
* time the server will wait before returning an empty result for
* a request. The default setting of 60 seconds is recommended.
* Other settings will require tweaks to the Strophe.TIMEOUT value.
* (Integer) hold - The optional HTTPBIND hold value. This is the
* number of connections the server will hold at one time. This
* should almost always be set to 1 (the default).
* (Integer) wind - The optional HTTBIND window value. This is the
* allowed range of request ids that are valid. The default is 5.
*/
_attach: function(jid, sid, rid, callback, wait, hold, wind) {
this._conn.jid = jid;
this.sid = sid;
this.rid = rid;
this._conn.connect_callback = callback;
this._conn.domain = Strophe.getDomainFromJid(this._conn.jid);
this._conn.authenticated = true;
this._conn.connected = true;
this.wait = wait || this.wait;
this.hold = hold || this.hold;
this.window = wind || this.window;
this._conn._changeConnectStatus(Strophe.Status.ATTACHED, null);
},
/** PrivateFunction: _restore
* Attempt to restore a cached BOSH session
*
* Parameters:
* (String) jid - The full JID that is bound by the session.
* This parameter is optional but recommended, specifically in cases
* where prebinded BOSH sessions are used where it's important to know
* that the right session is being restored.
* (Function) callback The connect callback function.
* (Integer) wait - The optional HTTPBIND wait value. This is the
* time the server will wait before returning an empty result for
* a request. The default setting of 60 seconds is recommended.
* Other settings will require tweaks to the Strophe.TIMEOUT value.
* (Integer) hold - The optional HTTPBIND hold value. This is the
* number of connections the server will hold at one time. This
* should almost always be set to 1 (the default).
* (Integer) wind - The optional HTTBIND window value. This is the
* allowed range of request ids that are valid. The default is 5.
*/
_restore: function(jid, callback, wait, hold, wind) {
var session = JSON.parse(
window.sessionStorage.getItem("strophe-bosh-session")
);
if (
typeof session !== "undefined" &&
session !== null &&
session.rid &&
session.sid &&
session.jid &&
(typeof jid === "undefined" ||
jid === null ||
Strophe.getBareJidFromJid(session.jid) ===
Strophe.getBareJidFromJid(jid) ||
// If authcid is null, then it's an anonymous login, so
// we compare only the domains:
(Strophe.getNodeFromJid(jid) === null &&
Strophe.getDomainFromJid(session.jid) === jid))
) {
this._conn.restored = true;
this._attach(
session.jid,
session.sid,
session.rid,
callback,
wait,
hold,
wind
);
} else {
throw {
name: "StropheSessionError",
message: "_restore: no restoreable session."
};
}
},
/** PrivateFunction: _cacheSession
* _Private_ handler for the beforeunload event.
*
* This handler is used to process the Bosh-part of the initial request.
* Parameters:
* (Strophe.Request) bodyWrap - The received stanza.
*/
_cacheSession: function() {
if (this._conn.authenticated) {
if (this._conn.jid && this.rid && this.sid) {
window.sessionStorage.setItem(
"strophe-bosh-session",
JSON.stringify({
jid: this._conn.jid,
rid: this.rid,
sid: this.sid
})
);
}
} else {
window.sessionStorage.removeItem("strophe-bosh-session");
}
},
/** PrivateFunction: _connect_cb
* _Private_ handler for initial connection request.
*
* This handler is used to process the Bosh-part of the initial request.
* Parameters:
* (Strophe.Request) bodyWrap - The received stanza.
*/
_connect_cb: function(bodyWrap) {
var typ = bodyWrap.getAttribute("type");
var cond, conflict;
if (typ !== null && typ === "terminate") {
// an error occurred
cond = bodyWrap.getAttribute("condition");
Strophe.error("BOSH-Connection failed: " + cond);
conflict = bodyWrap.getElementsByTagName("conflict");
if (cond !== null) {
if (cond === "remote-stream-error" && conflict.length > 0) {
cond = "conflict";
}
this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
} else {
this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
}
this._conn._doDisconnect(cond);
return Strophe.Status.CONNFAIL;
}
// check to make sure we don't overwrite these if _connect_cb is
// called multiple times in the case of missing stream:features
if (!this.sid) {
this.sid = bodyWrap.getAttribute("sid");
}
var wind = bodyWrap.getAttribute("requests");
if (wind) {
this.window = parseInt(wind, 10);
}
var hold = bodyWrap.getAttribute("hold");
if (hold) {
this.hold = parseInt(hold, 10);
}
var wait = bodyWrap.getAttribute("wait");
if (wait) {
this.wait = parseInt(wait, 10);
}
var inactivity = bodyWrap.getAttribute("inactivity");
if (inactivity) {
this.inactivity = parseInt(inactivity, 10);
}
},
/** PrivateFunction: _disconnect
* _Private_ part of Connection.disconnect for Bosh
*
* Parameters:
* (Request) pres - This stanza will be sent before disconnecting.
*/
_disconnect: function(pres) {
this._sendTerminate(pres);
},
/** PrivateFunction: _doDisconnect
* _Private_ function to disconnect.
*
* Resets the SID and RID.
*/
_doDisconnect: function() {
this.sid = null;
this.rid = Math.floor(Math.random() * 4294967295);
if (this._conn._sessionCachingSupported()) {
window.sessionStorage.removeItem("strophe-bosh-session");
}
this._conn.nextValidRid(this.rid);
},
/** PrivateFunction: _emptyQueue
* _Private_ function to check if the Request queue is empty.
*
* Returns:
* True, if there are no Requests queued, False otherwise.
*/
_emptyQueue: function() {
return this._requests.length === 0;
},
/** PrivateFunction: _callProtocolErrorHandlers
* _Private_ function to call error handlers registered for HTTP errors.
*
* Parameters:
* (Strophe.Request) req - The request that is changing readyState.
*/
_callProtocolErrorHandlers: function(req) {
var reqStatus = this._getRequestStatus(req),
err_callback;
err_callback = this._conn.protocolErrorHandlers.HTTP[reqStatus];
if (err_callback) {
err_callback.call(this, reqStatus);
}
},
/** PrivateFunction: _hitError
* _Private_ function to handle the error count.
*
* Requests are resent automatically until their error count reaches
* 5. Each time an error is encountered, this function is called to
* increment the count and disconnect if the count is too high.
*
* Parameters:
* (Integer) reqStatus - The request status.
*/
_hitError: function(reqStatus) {
this.errors++;
Strophe.warn(
"request errored, status: " +
reqStatus +
", number of errors: " +
this.errors
);
if (this.errors > 4) {
this._conn._onDisconnectTimeout();
}
},
/** PrivateFunction: _onDisconnectTimeout
* _Private_ timeout handler for handling non-graceful disconnection.
*
* Cancels all remaining Requests and clears the queue.
*/
_onDisconnectTimeout: function() {
this._abortAllRequests();
},
/** PrivateFunction: _abortAllRequests
* _Private_ helper function that makes sure all pending requests are aborted.
*/
_abortAllRequests: function _abortAllRequests() {
var req;
while (this._requests.length > 0) {
req = this._requests.pop();
req.abort = true;
req.xhr.abort();
// jslint complains, but this is fine. setting to empty func
// is necessary for IE6
req.xhr.onreadystatechange = function() {}; // jshint ignore:line
}
},
/** PrivateFunction: _onIdle
* _Private_ handler called by Strophe.Connection._onIdle
*
* Sends all queued Requests or polls with empty Request if there are none.
*/
_onIdle: function() {
var data = this._conn._data;
// if no requests are in progress, poll
if (
this._conn.authenticated &&
this._requests.length === 0 &&
data.length === 0 &&
!this._conn.disconnecting
) {
Strophe.info("no requests during idle cycle, sending " + "blank request");
data.push(null);
}
if (this._conn.paused) {
return;
}
if (this._requests.length < 2 && data.length > 0) {
var body = this._buildBody();
for (var i = 0; i < data.length; i++) {
if (data[i] !== null) {
if (data[i] === "restart") {
body.attrs({
to: this._conn.domain,
"xml:lang": "en",
"xmpp:restart": "true",
"xmlns:xmpp": Strophe.NS.BOSH
});
} else {
body.cnode(data[i]).up();
}
}
}
delete this._conn._data;
this._conn._data = [];
this._requests.push(
new Strophe.Request(
body.tree(),
this._onRequestStateChange.bind(
this,
this._conn._dataRecv.bind(this._conn)
),
body.tree().getAttribute("rid")
)
);
this._throttledRequestHandler();
}
if (this._requests.length > 0) {
var time_elapsed = this._requests[0].age();
if (this._requests[0].dead !== null) {
if (
this._requests[0].timeDead() >
Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)
) {
this._throttledRequestHandler();
}
}
if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {
Strophe.warn(
"Request " +
this._requests[0].id +
" timed out, over " +
Math.floor(Strophe.TIMEOUT * this.wait) +
" seconds since last activity"
);
this._throttledRequestHandler();
}
}
},
/** PrivateFunction: _getRequestStatus
*
* Returns the HTTP status code from a Strophe.Request
*
* Parameters:
* (Strophe.Request) req - The Strophe.Request instance.
* (Integer) def - The default value that should be returned if no
* status value was found.
*/
_getRequestStatus: function(req, def) {
var reqStatus;
if (req.xhr.readyState === 4) {
try {
reqStatus = req.xhr.status;
} catch (e) {
// ignore errors from undefined status attribute. Works
// around a browser bug
Strophe.error(
"Caught an error while retrieving a request's status, " +
"reqStatus: " +
reqStatus
);
}
}
if (typeof reqStatus === "undefined") {
reqStatus = typeof def === "number" ? def : 0;
}
return reqStatus;
},
/** PrivateFunction: _onRequestStateChange
* _Private_ handler for Strophe.Request state changes.
*
* This function is called when the XMLHttpRequest readyState changes.
* It contains a lot of error handling logic for the many ways that
* requests can fail, and calls the request callback when requests
* succeed.
*
* Parameters:
* (Function) func - The handler for the request.
* (Strophe.Request) req - The request that is changing readyState.
*/
_onRequestStateChange: function(func, req) {
Strophe.debug(
"request id " +
req.id +
"." +
req.sends +
" state changed to " +
req.xhr.readyState
);
if (req.abort) {
req.abort = false;
return;
}
if (req.xhr.readyState !== 4) {
// The request is not yet complete
return;
}
var reqStatus = this._getRequestStatus(req);
this.lastResponseHeaders = req.xhr.getAllResponseHeaders();
if (this.disconnecting && reqStatus >= 400) {
this._hitError(reqStatus);
this._callProtocolErrorHandlers(req);
return;
}
var valid_request = reqStatus > 0 && reqStatus < 500;
var too_many_retries = req.sends > this._conn.maxRetries;
if (valid_request || too_many_retries) {
// remove from internal queue
this._removeRequest(req);
Strophe.debug("request id " + req.id + " should now be removed");
}
if (reqStatus === 200) {
// request succeeded
var reqIs0 = this._requests[0] === req;
var reqIs1 = this._requests[1] === req;
// if request 1 finished, or request 0 finished and request
// 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to
// restart the other - both will be in the first spot, as the
// completed request has been removed from the queue already
if (
reqIs1 ||
(reqIs0 &&
this._requests.length > 0 &&
this._requests[0].age() >
Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait))
) {
this._restartRequest(0);
}
this._conn.nextValidRid(Number(req.rid) + 1);
Strophe.debug("request id " + req.id + "." + req.sends + " got 200");
func(req); // call handler
this.errors = 0;
} else if (
reqStatus === 0 ||
(reqStatus >= 400 && reqStatus < 600) ||
reqStatus >= 12000
) {
// request failed
Strophe.error(
"request id " +
req.id +
"." +
req.sends +
" error " +
reqStatus +
" happened"
);
this._hitError(reqStatus);
this._callProtocolErrorHandlers(req);
if (reqStatus >= 400 && reqStatus < 500) {
this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING, null);
this._conn._doDisconnect();
}
} else {
Strophe.error(
"request id " +
req.id +
"." +
req.sends +
" error " +
reqStatus +
" happened"
);
}
if (!valid_request && !too_many_retries) {
this._throttledRequestHandler();
} else if (too_many_retries && !this._conn.connected) {
this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "giving-up");
}
},
/** PrivateFunction: _processRequest
* _Private_ function to process a request in the queue.
*
* This function takes requests off the queue and sends them and
* restarts dead requests.
*
* Parameters:
* (Integer) i - The index of the request in the queue.
*/
_processRequest: function(i) {
var self = this;
var req = this._requests[i];
var reqStatus = this._getRequestStatus(req, -1);
// make sure we limit the number of retries
if (req.sends > this._conn.maxRetries) {
this._conn._onDisconnectTimeout();
return;
}
var time_elapsed = req.age();
var primaryTimeout =
!isNaN(time_elapsed) &&
time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait);
var secondaryTimeout =
req.dead !== null &&
req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait);
var requestCompletedWithServerError =
req.xhr.readyState === 4 && (reqStatus < 1 || reqStatus >= 500);
if (primaryTimeout || secondaryTimeout || requestCompletedWithServerError) {
if (secondaryTimeout) {
Strophe.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
);
req = this._requests[i];
}
if (req.xhr.readyState === 0) {
Strophe.debug("request id " + req.id + "." + req.sends + " posting");
try {
var contentType =
this._conn.options.contentType || "text/xml; charset=utf-8";
req.xhr.open(
"POST",
this._conn.service,
this._conn.options.sync ? false : true
);
if (typeof req.xhr.setRequestHeader !== "undefined") {
// IE9 doesn't have setRequestHeader
req.xhr.setRequestHeader("Content-Type", contentType);
}
if (this._conn.options.withCredentials) {
req.xhr.withCredentials = true;
}
} catch (e2) {
Strophe.error("XHR open failed: " + e2.toString());
if (!this._conn.connected) {
this._conn._changeConnectStatus(
Strophe.Status.CONNFAIL,
"bad-service"
);
}
this._conn.disconnect();
return;
}
// Fires the XHR request -- may be invoked immediately
// or on a gradually expanding retry window for reconnects
var sendFunc = function() {
req.date = new Date();
if (self._conn.options.customHeaders) {
var headers = self._conn.options.customHeaders;
for (var header in headers) {
if (headers.hasOwnProperty(header)) {
req.xhr.setRequestHeader(header, headers[header]);
}
}
}
req.xhr.send(req.data);
};
// Implement progressive backoff for reconnects --
// First retry (send === 1) should also be instantaneous
if (req.sends > 1) {
// Using a cube of the retry number creates a nicely
// expanding retry window
var backoff =
Math.min(
Math.floor(Strophe.TIMEOUT * this.wait),
Math.pow(req.sends, 3)
) * 1000;
setTimeout(function() {
// XXX: setTimeout should be called only with function expressions (23974bc1)
sendFunc();
}, backoff);
} else {
sendFunc();
}
req.sends++;
if (this._conn.xmlOutput !== Strophe.Connection.prototype.xmlOutput) {
if (
req.xmlData.nodeName === this.strip &&
req.xmlData.childNodes.length
) {
this._conn.xmlOutput(req.xmlData.childNodes[0]);
} else {
this._conn.xmlOutput(req.xmlData);
}
}
if (this._conn.rawOutput !== Strophe.Connection.prototype.rawOutput) {
this._conn.rawOutput(req.data);
}
} else {
Strophe.debug(
"_processRequest: " +
(i === 0 ? "first" : "second") +
" request has readyState of " +
req.xhr.readyState
);
}
},
/** PrivateFunction: _removeRequest
* _Private_ function to remove a request from the queue.
*
* Parameters:
* (Strophe.Request) req - The request to remove.
*/
_removeRequest: function(req) {
Strophe.debug("removing request");
var i;
for (i = this._requests.length - 1; i >= 0; i--) {
if (req === this._requests[i]) {
this._requests.splice(i, 1);
}
}
// IE6 fails on setting to null, so set to empty function
req.xhr.onreadystatechange = function() {};
this._throttledRequestHandler();
},
/** PrivateFunction: _restartRequest
* _Private_ function to restart a request that is presumed dead.
*
* Parameters:
* (Integer) i - The index of the request in the queue.
*/
_restartRequest: function(i) {
var req = this._requests[i];
if (req.dead === null) {
req.dead = new Date();
}
this._processRequest(i);
},
/** PrivateFunction: _reqToData
* _Private_ function to get a stanza out of a request.
*
* Tries to extract a stanza out of a Request Object.
* When this fails the current connection will be disconnected.
*
* Parameters:
* (Object) req - The Request.
*
* Returns:
* The stanza that was passed.
*/
_reqToData: function(req) {
try {
return req.getResponse();
} catch (e) {
if (e !== "parsererror") {
throw e;
}
this._conn.disconnect("strophe-parsererror");
}
},
/** PrivateFunction: _sendTerminate
* _Private_ function to send initial disconnect sequence.
*
* This is the first step in a graceful disconnect. It sends
* the BOSH server a terminate body and includes an unavailable
* presence if authentication has completed.
*/
_sendTerminate: function(pres) {
Strophe.info("_sendTerminate was called");
var body = this._buildBody().attrs({ type: "terminate" });
if (pres) {
body.cnode(pres.tree());
}
var req = new Strophe.Request(
body.tree(),
this._onRequestStateChange.bind(
this,
this._conn._dataRecv.bind(this._conn)
),
body.tree().getAttribute("rid")
);
this._requests.push(req);
this._throttledRequestHandler();
},
/** PrivateFunction: _send
* _Private_ part of the Connection.send function for BOSH
*
* Just triggers the RequestHandler to send the messages that are in the queue
*/
_send: function() {
clearTimeout(this._conn._idleTimeout);
this._throttledRequestHandler();
// XXX: setTimeout should be called only with function expressions (23974bc1)
this._conn._idleTimeout = setTimeout(
function() {
this._onIdle();
}.bind(this._conn),
100
);
},
/** PrivateFunction: _sendRestart
*
* Send an xmpp:restart stanza.
*/
_sendRestart: function() {
this._throttledRequestHandler();
clearTimeout(this._conn._idleTimeout);
},
/** PrivateFunction: _throttledRequestHandler
* _Private_ function to throttle requests to the connection window.
*
* This function makes sure we don't send requests so fast that the
* request ids overflow the connection window in the case that one
* request died.
*/
_throttledRequestHandler: function() {
if (!this._requests) {
Strophe.debug(
"_throttledRequestHandler called with " + "undefined requests"
);
} else {
Strophe.debug(
"_throttledRequestHandler called with " +
this._requests.length +
" requests"
);
}
if (!this._requests || this._requests.length === 0) {
return;
}
if (this._requests.length > 0) {
this._processRequest(0);
}
if (
this._requests.length > 1 &&
Math.abs(this._requests[0].rid - this._requests[1].rid) < this.window
) {
this._processRequest(1);
}
}
};
module.exports = Strophe;
// return Strophe;
// (function(root, factory) {
// if (typeof define === "function" && define.amd) {
// define(["strophe-core"], function(core) {
// return factory(core.Strophe, core.$build);
// });
// } else if (typeof exports === "object") {
// var core = require("./core");
// module.exports = factory(core.Strophe, core.$build);
// } else {
// // Browser globals
// return factory(Strophe, $build);
// }
// })(this, function(Strophe, $build) {});