true if there's only one key left, false otherwise
* @type boolean
*/
this.lastKey = function() { return (this._indexAt == 0); };
/**
* Returns number of overall/initial stack size
* @return Number of keys created
* @type int
*/
this.size = function() { return this._k.length; };
/**
* @private
*/
this._getSuspendVars = function() {
return ('_k,_indexAt').split(',');
}
}
/**
* @fileoverview Contains all things in common for all subtypes of connections
* supported.
* @author Stefan Strigler steve@zeank.in-berlin.de
* @version $Revision$
*/
/**
* Creates a new Jabber connection (a connection to a jabber server)
* @class Somewhat abstract base class for jabber connections. Contains all
* of the code in common for all jabber connections
* @constructor
* @param {JSON http://www.json.org/index} oArg JSON with properties:
* * httpbase
the http base address of the service to be used for
* connecting to jabber
* * oDbg
(optional) a reference to a debugger interface
*/
function JSJaCConnection(oArg) {
if (oArg && oArg.oDbg && oArg.oDbg.log) {
/**
* Reference to debugger interface
* (needs to implement method log
)
* @type Debugger
*/
this.oDbg = oArg.oDbg;
} else {
this.oDbg = new Object(); // always initialise a debugger
this.oDbg.log = function() { };
}
if (oArg && oArg.timerval)
this.setPollInterval(oArg.timerval);
else
this.setPollInterval(JSJAC_TIMERVAL);
if (oArg && oArg.httpbase)
/**
* @private
*/
this._httpbase = oArg.httpbase;
if (oArg &&oArg.allow_plain)
/**
* @private
*/
this.allow_plain = oArg.allow_plain;
else
this.allow_plain = JSJAC_ALLOW_PLAIN;
if (oArg && oArg.cookie_prefix)
/**
* @private
*/
this._cookie_prefix = oArg.cookie_prefix;
else
this._cookie_prefix = "";
/**
* @private
*/
this._connected = false;
/**
* @private
*/
this._events = new Array();
/**
* @private
*/
this._keys = null;
/**
* @private
*/
this._ID = 0;
/**
* @private
*/
this._inQ = new Array();
/**
* @private
*/
this._pQueue = new Array();
/**
* @private
*/
this._regIDs = new Array();
/**
* @private
*/
this._req = new Array();
/**
* @private
*/
this._status = 'intialized';
/**
* @private
*/
this._errcnt = 0;
/**
* @private
*/
this._inactivity = JSJAC_INACTIVITY;
/**
* @private
*/
this._sendRawCallbacks = new Array();
}
// Generates an ID
var STANZA_ID = 1;
function genID() {
return STANZA_ID++;
}
JSJaCConnection.prototype.connect = function(oArg) {
this._setStatus('connecting');
this.domain = oArg.domain || 'localhost';
this.username = oArg.username;
this.resource = oArg.resource;
this.pass = oArg.pass;
this.register = oArg.register;
this.authhost = oArg.authhost || this.domain;
this.authtype = oArg.authtype || 'sasl';
if (oArg.xmllang && oArg.xmllang != '')
this._xmllang = oArg.xmllang;
else
this._xmllang = 'en';
this.host = oArg.host || this.domain;
this.port = oArg.port || 5222;
if (oArg.secure)
this.secure = 'true';
else
this.secure = 'false';
if (oArg.wait)
this._wait = oArg.wait;
this.jid = this.username + '@' + this.domain;
this.fulljid = this.jid + '/' + this.resource;
this._rid = Math.round( 100000.5 + ( ( (900000.49999) - (100000.5) ) * Math.random() ) );
// setupRequest must be done after rid is created but before first use in reqstr
var slot = this._getFreeSlot();
this._req[slot] = this._setupRequest(true);
var reqstr = this._getInitialRequestString();
this.oDbg.log(reqstr,4);
this._req[slot].r.onreadystatechange =
JSJaC.bind(function() {
var r = this._req[slot].r;
if (r.readyState == 4) {
this.oDbg.log("async recv: "+r.responseText,4);
this._handleInitialResponse(r); // handle response
}
}, this);
if (typeof(this._req[slot].r.onerror) != 'undefined') {
this._req[slot].r.onerror =
JSJaC.bind(function(e) {
this.oDbg.log('XmlHttpRequest error',1);
return false;
}, this);
}
this._req[slot].r.send(reqstr);
};
/**
* Tells whether this connection is connected
* @return true
if this connections is connected,
* false
otherwise
* @type boolean
*/
JSJaCConnection.prototype.connected = function() { return this._connected; };
/**
* Disconnects from jabber server and terminates session (if applicable)
*/
JSJaCConnection.prototype.disconnect = function() {
this._setStatus('disconnecting');
if (!this.connected())
return;
this._connected = false;
clearInterval(this._interval);
clearInterval(this._inQto);
if (this._timeout)
clearTimeout(this._timeout); // remove timer
var slot = this._getFreeSlot();
// Intentionally synchronous
this._req[slot] = this._setupRequest(false);
request = this._getRequestString(false, true);
this.oDbg.log("Disconnecting: " + request,4);
this._req[slot].r.send(request);
try {
removeDB('jsjac', 'state');
} catch (e) {}
this.oDbg.log("Disconnected: "+this._req[slot].r.responseText,2);
this._handleEvent('ondisconnect');
};
/**
* Gets current value of polling interval
* @return Polling interval in milliseconds
* @type int
*/
JSJaCConnection.prototype.getPollInterval = function() {
return this._timerval;
};
/**
* Registers an event handler (callback) for this connection.
* Note: All of the packet handlers for specific packets (like
* message_in, presence_in and iq_in) fire only if there's no
* callback associated with the id.
*
Example:
* con.registerHandler('iq', 'query', 'jabber:iq:version', handleIqVersion);
* @param {String} event One of
*
* - onConnect - connection has been established and authenticated
* - onDisconnect - connection has been disconnected
* - onResume - connection has been resumed
* - onStatusChanged - connection status has changed, current
* status as being passed argument to handler. See {@link #status}.
* - onError - an error has occured, error node is supplied as
* argument, like this:
<error code='404' type='cancel'>
* <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
* </error>
* - packet_in - a packet has been received (argument: the
* packet)
* - packet_out - a packet is to be sent(argument: the
* packet)
* - message_in | message - a message has been received (argument:
* the packet)
* - message_out - a message packet is to be sent (argument: the
* packet)
* - presence_in | presence - a presence has been received
* (argument: the packet)
* - presence_out - a presence packet is to be sent (argument: the
* packet)
* - iq_in | iq - an iq has been received (argument: the packet)
* - iq_out - an iq is to be sent (argument: the packet)
*
* @param {String} childName A childnode's name that must occur within a
* retrieved packet [optional]
* @param {String} childNS A childnode's namespace that must occure within
* a retrieved packet (works only if childName is given) [optional]
* @param {String} type The type of the packet to handle (works only if childName and chidNS are given (both may be set to '*' in order to get skipped) [optional]
* @param {Function} handler The handler to be called when event occurs. If your handler returns 'true' it cancels bubbling of the event. No other registered handlers for this event will be fired.
*/
JSJaCConnection.prototype.registerHandler = function(event) {
event = event.toLowerCase(); // don't be case-sensitive here
var eArg = {handler: arguments[arguments.length-1],
childName: '*',
childNS: '*',
type: '*'};
if (arguments.length > 2)
eArg.childName = arguments[1];
if (arguments.length > 3)
eArg.childNS = arguments[2];
if (arguments.length > 4)
eArg.type = arguments[3];
if (!this._events[event])
this._events[event] = new Array(eArg);
else
this._events[event] = this._events[event].concat(eArg);
// sort events in order how specific they match criterias thus using
// wildcard patterns puts them back in queue when it comes to
// bubbling the event
this._events[event] =
this._events[event].sort(function(a,b) {
var aRank = 0;
var bRank = 0;
with (a) {
if (type == '*')
aRank++;
if (childNS == '*')
aRank++;
if (childName == '*')
aRank++;
}
with (b) {
if (type == '*')
bRank++;
if (childNS == '*')
bRank++;
if (childName == '*')
bRank++;
}
if (aRank > bRank)
return 1;
if (aRank < bRank)
return -1;
return 0;
});
this.oDbg.log("registered handler for event '"+event+"'",2);
};
JSJaCConnection.prototype.unregisterHandler = function(event,handler) {
event = event.toLowerCase(); // don't be case-sensitive here
if (!this._events[event])
return;
var arr = this._events[event], res = new Array();
for (var i=0; i
* 'initializing' ... well
* 'connecting' if connect() was called
* 'resuming' if resume() was called
* 'processing' if it's about to operate as normal
* 'onerror_fallback' if there was an error with the request object
* 'protoerror_fallback' if there was an error at the http binding protocol flow (most likely that's where you interested in)
* 'internal_server_error' in case of an internal server error
* 'suspending' if suspend() is being called
* 'aborted' if abort() was called
* 'disconnecting' if disconnect() has been called
*
* @type String
*/
JSJaCConnection.prototype.status = function() { return this._status; };
/**
* Suspends this connection (saving state for later resume)
* Saves state to cookie
* @return Whether suspend (saving to cookie) was successful
* @type boolean
*/
JSJaCConnection.prototype.suspend = function(has_pause) {
var data = this.suspendToData(has_pause);
try {
var c = setDB('jsjac', 'state', JSJaCJSON.toString(data));
return c;
} catch (e) {
this.oDbg.log("Failed creating cookie '"+this._cookie_prefix+
"JSJaC_State': "+e.message,1);
}
return false;
};
/**
* Suspend connection and return serialized JSJaC connection state
* @return JSJaC connection state object
* @type Object
*/
JSJaCConnection.prototype.suspendToData = function(has_pause) {
// remove timers
if(has_pause) {
clearTimeout(this._timeout);
clearInterval(this._interval);
clearInterval(this._inQto);
this._suspend();
}
var u = ('_connected,_keys,_ID,_inQ,_pQueue,_regIDs,_errcnt,_inactivity,domain,username,resource,jid,fulljid,_sid,_httpbase,_timerval,_is_polling').split(',');
u = u.concat(this._getSuspendVars());
var s = new Object();
for (var i=0; i",
this._doSASLAuthDone);
}
this.oDbg.log("SASL ANONYMOUS requested but not supported",1);
} else {
if (this.mechs['DIGEST-MD5']) {
this.oDbg.log("SASL using mechanism 'DIGEST-MD5'",2);
return this._sendRaw("",
this._doSASLAuthDigestMd5S1);
} else if (this.allow_plain && this.mechs['PLAIN']) {
this.oDbg.log("SASL using mechanism 'PLAIN'",2);
var authStr = this.username+'@'+
this.domain+String.fromCharCode(0)+
this.username+String.fromCharCode(0)+
this.pass;
this.oDbg.log("authenticating with '"+authStr+"'",2);
authStr = b64encode(authStr);
return this._sendRaw(""+authStr+"",
this._doSASLAuthDone);
}
this.oDbg.log("No SASL mechanism applied",1);
this.authtype = 'nonsasl'; // fallback
}
return false;
};
/**
* @private
*/
JSJaCConnection.prototype._doSASLAuthDigestMd5S1 = function(el) {
if (el.nodeName != "challenge") {
this.oDbg.log("challenge missing",1);
this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
this.disconnect();
} else {
var challenge = b64decode(el.firstChild.nodeValue);
this.oDbg.log("got challenge: "+challenge,2);
this._nonce = challenge.substring(challenge.indexOf("nonce=")+7);
this._nonce = this._nonce.substring(0,this._nonce.indexOf("\""));
this.oDbg.log("nonce: "+this._nonce,2);
if (this._nonce == '' || this._nonce.indexOf('\"') != -1) {
this.oDbg.log("nonce not valid, aborting",1);
this.disconnect();
return;
}
this._digest_uri = "xmpp/";
// if (typeof(this.host) != 'undefined' && this.host != '') {
// this._digest-uri += this.host;
// if (typeof(this.port) != 'undefined' && this.port)
// this._digest-uri += ":" + this.port;
// this._digest-uri += '/';
// }
this._digest_uri += this.domain;
this._cnonce = cnonce(14);
this._nc = '00000001';
var X = this.username+':'+this.domain+':'+this.pass;
var Y = rstr_md5(str2rstr_utf8(X));
var A1 = Y+':'+this._nonce+':'+this._cnonce;
var HA1 = rstr2hex(rstr_md5(A1));
var A2 = 'AUTHENTICATE:'+this._digest_uri;
var HA2 = hex_md5(A2);
var response = hex_md5(HA1+':'+this._nonce+':'+this._nc+':'+
this._cnonce+':auth:'+HA2);
var rPlain = 'username="'+this.username+'",realm="'+this.domain+
'",nonce="'+this._nonce+'",cnonce="'+this._cnonce+'",nc="'+this._nc+
'",qop=auth,digest-uri="'+this._digest_uri+'",response="'+response+
'",charset="utf-8"';
this.oDbg.log("response: "+rPlain,2);
this._sendRaw(""+
b64encode(rPlain)+"",
this._doSASLAuthDigestMd5S2);
}
};
/**
* @private
*/
JSJaCConnection.prototype._doSASLAuthDigestMd5S2 = function(el) {
if (el.nodeName == 'failure') {
if (el.xml)
this.oDbg.log("auth error: "+el.xml,1);
else
this.oDbg.log("auth error",1);
this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
this.disconnect();
return;
}
var response = b64decode(el.firstChild.nodeValue);
this.oDbg.log("response: "+response,2);
var rspauth = response.substring(response.indexOf("rspauth=")+8);
this.oDbg.log("rspauth: "+rspauth,2);
var X = this.username+':'+this.domain+':'+this.pass;
var Y = rstr_md5(str2rstr_utf8(X));
var A1 = Y+':'+this._nonce+':'+this._cnonce;
var HA1 = rstr2hex(rstr_md5(A1));
var A2 = ':'+this._digest_uri;
var HA2 = hex_md5(A2);
var rsptest = hex_md5(HA1+':'+this._nonce+':'+this._nc+':'+
this._cnonce+':auth:'+HA2);
this.oDbg.log("rsptest: "+rsptest,2);
if (rsptest != rspauth) {
this.oDbg.log("SASL Digest-MD5: server repsonse with wrong rspauth",1);
this.disconnect();
return;
}
if (el.nodeName == 'success') {
this._reInitStream(JSJaC.bind(this._doStreamBind, this));
} else { // some extra turn
this._sendRaw("",
this._doSASLAuthDone);
}
};
/**
* @private
*/
JSJaCConnection.prototype._doSASLAuthDone = function (el) {
if (el.nodeName != 'success') {
this.oDbg.log("auth failed",1);
this._handleEvent('onerror',JSJaCError('401','auth','not-authorized'));
this.disconnect();
} else {
this._reInitStream(JSJaC.bind(this._doStreamBind, this));
}
};
/**
* @private
*/
JSJaCConnection.prototype._doStreamBind = function() {
var iq = new JSJaCIQ();
iq.setIQ(null,'set','bind_1');
iq.appendNode("bind", {xmlns: "urn:ietf:params:xml:ns:xmpp-bind"},
[["resource", this.resource]]);
this.oDbg.log(iq.xml());
this.send(iq,this._doXMPPSess);
};
/**
* @private
*/
JSJaCConnection.prototype._doXMPPSess = function(iq) {
if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
this.disconnect();
if (iq.getType() == 'error')
this._handleEvent('onerror',iq.getChild('error'));
return;
}
this.fulljid = iq.getChildVal("jid");
this.jid = this.fulljid.substring(0,this.fulljid.lastIndexOf('/'));
iq = new JSJaCIQ();
iq.setIQ(null,'set','sess_1');
iq.appendNode("session", {xmlns: "urn:ietf:params:xml:ns:xmpp-session"},
[]);
this.oDbg.log(iq.xml());
this.send(iq,this._doXMPPSessDone);
};
/**
* @private
*/
JSJaCConnection.prototype._doXMPPSessDone = function(iq) {
if (iq.getType() != 'result' || iq.getType() == 'error') { // failed
this.disconnect();
if (iq.getType() == 'error')
this._handleEvent('onerror',iq.getChild('error'));
return;
} else
this._handleEvent('onconnect');
};
/**
* @private
*/
JSJaCConnection.prototype._handleEvent = function(event,arg) {
event = event.toLowerCase(); // don't be case-sensitive here
this.oDbg.log("incoming event '"+event+"'",3);
if (!this._events[event])
return;
this.oDbg.log("handling event '"+event+"'",2);
for (var i=0;i match for handler "+aEvent.handler,3);
}
if (aEvent.handler(arg)) {
// handled!
break;
}
}
else
if (aEvent.handler()) {
// handled!
break;
}
} catch (e) {
if (e.fileName&&e.lineNumber) {
this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message+' in '+e.fileName+' line '+e.lineNumber,1);
} else {
this.oDbg.log(aEvent.handler+"\n>>>"+e.name+": "+ e.message,1);
}
}
}
}
};
/**
* @private
*/
JSJaCConnection.prototype._handlePID = function(aJSJaCPacket) {
if (!aJSJaCPacket.getID())
return false;
for (var i in this._regIDs) {
if (this._regIDs.hasOwnProperty(i) &&
this._regIDs[i] && i == aJSJaCPacket.getID()) {
var pID = aJSJaCPacket.getID();
this.oDbg.log("handling "+pID,3);
try {
if (this._regIDs[i].cb.call(this, aJSJaCPacket, this._regIDs[i].arg) === false) {
// don't unregister
return false;
} else {
this._unregisterPID(pID);
return true;
}
} catch (e) {
// broken handler?
this.oDbg.log(e.name+": "+ e.message, 1);
this._unregisterPID(pID);
return true;
}
}
}
return false;
};
/**
* @private
*/
JSJaCConnection.prototype._handleResponse = function(req) {
var rootEl = this._parseResponse(req);
if (!rootEl)
return;
for (var i=0; i JSJAC_ERR_COUNT) {
// abort
this._abort();
return false;
}
this._setStatus('onerror_fallback');
// schedule next tick
setTimeout(JSJaC.bind(this._resume, this),this.getPollInterval());
return false;
}, this);
} catch(e) { } // well ... no onerror property available, maybe we
// can catch the error somewhere else ...
var reqstr = this._getRequestString();
if (typeof(this._rid) != 'undefined') // remember request id if any
this._req[slot].rid = this._rid;
this.oDbg.log("sending: " + reqstr,4);
this._req[slot].r.send(reqstr);
};
/**
* @private
*/
JSJaCConnection.prototype._registerPID = function(pID,cb,arg) {
if (!pID || !cb)
return false;
this._regIDs[pID] = new Object();
this._regIDs[pID].cb = cb;
if (arg)
this._regIDs[pID].arg = arg;
this.oDbg.log("registered "+pID,3);
return true;
};
/**
* partial function binding sendEmpty to callback
* @private
*/
JSJaCConnection.prototype._prepSendEmpty = function(cb, ctx) {
return function() {
ctx._sendEmpty(JSJaC.bind(cb, ctx));
};
};
/**
* send empty request
* waiting for stream id to be able to proceed with authentication
* @private
*/
JSJaCConnection.prototype._sendEmpty = function(cb) {
var slot = this._getFreeSlot();
this._req[slot] = this._setupRequest(true);
this._req[slot].r.onreadystatechange =
JSJaC.bind(function() {
if (this._req[slot].r.readyState == 4) {
this.oDbg.log("async recv: "+this._req[slot].r.responseText,4);
cb(this._req[slot].r); // handle response
}
},this);
if (typeof(this._req[slot].r.onerror) != 'undefined') {
this._req[slot].r.onerror =
JSJaC.bind(function(e) {
this.oDbg.log('XmlHttpRequest error',1);
return false;
}, this);
}
var reqstr = this._getRequestString();
this.oDbg.log("sending: " + reqstr,4);
this._req[slot].r.send(reqstr);
};
/**
* @private
*/
JSJaCConnection.prototype._sendRaw = function(xml,cb,arg) {
if (cb)
this._sendRawCallbacks.push({fn: cb, arg: arg});
this._pQueue.push(xml);
this._process();
return true;
};
/**
* @private
*/
JSJaCConnection.prototype._setStatus = function(status) {
if (!status || status == '')
return;
if (status != this._status) { // status changed!
this._status = status;
this._handleEvent('onstatuschanged', status);
this._handleEvent('status_changed', status);
}
};
/**
* @private
*/
JSJaCConnection.prototype._unregisterPID = function(pID) {
if (!this._regIDs[pID])
return false;
this._regIDs[pID] = null;
this.oDbg.log("unregistered "+pID,3);
return true;
};
/**
* @fileoverview All stuff related to HTTP Binding
* @author Stefan Strigler steve@zeank.in-berlin.de
* @version $Revision$
*/
/**
* Instantiates an HTTP Binding session
* @class Implementation of {@link
* http://www.xmpp.org/extensions/xep-0206.html XMPP Over BOSH}
* formerly known as HTTP Binding.
* @extends JSJaCConnection
* @constructor
*/
function JSJaCHttpBindingConnection(oArg) {
/**
* @ignore
*/
this.base = JSJaCConnection;
this.base(oArg);
// member vars
/**
* @private
*/
this._hold = JSJACHBC_MAX_HOLD;
/**
* @private
*/
this._inactivity = 0;
/**
* @private
*/
this._last_requests = new Object(); // 'hash' storing hold+1 last requests
/**
* @private
*/
this._last_rid = 0; // I know what you did last summer
/**
* @private
*/
this._min_polling = 0;
/**
* @private
*/
this._pause = 0;
/**
* @private
*/
this._wait = JSJACHBC_MAX_WAIT;
}
JSJaCHttpBindingConnection.prototype = new JSJaCConnection();
/**
* Inherit an instantiated HTTP Binding session
*/
JSJaCHttpBindingConnection.prototype.inherit = function(oArg) {
if (oArg.jid) {
var oJid = new JSJaCJID(oArg.jid);
this.domain = oJid.getDomain();
this.username = oJid.getNode();
this.resource = oJid.getResource();
} else {
this.domain = oArg.domain || 'localhost';
this.username = oArg.username;
this.resource = oArg.resource;
}
this._sid = oArg.sid;
this._rid = oArg.rid;
this._min_polling = oArg.polling;
this._inactivity = oArg.inactivity;
this._setHold(oArg.requests-1);
this.setPollInterval(this._timerval);
if (oArg.wait)
this._wait = oArg.wait; // for whatever reason
this._connected = true;
this._handleEvent('onconnect');
this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
JSJAC_CHECKQUEUEINTERVAL);
this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
JSJAC_CHECKINQUEUEINTERVAL);
this._timeout = setTimeout(JSJaC.bind(this._process, this),
this.getPollInterval());
};
/**
* Sets poll interval
* @param {int} timerval the interval in seconds
*/
JSJaCHttpBindingConnection.prototype.setPollInterval = function(timerval) {
if (timerval && !isNaN(timerval)) {
if (!this.isPolling())
this._timerval = 100;
else if (this._min_polling && timerval < this._min_polling*1000)
this._timerval = this._min_polling*1000;
else if (this._inactivity && timerval > this._inactivity*1000)
this._timerval = this._inactivity*1000;
else
this._timerval = timerval;
}
return this._timerval;
};
/**
* whether this session is in polling mode
* @type boolean
*/
JSJaCHttpBindingConnection.prototype.isPolling = function() { return (this._hold == 0) };
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getFreeSlot = function() {
for (var i=0; i" + raw + xml + "";
} else {
reqstr += "/>";
}
this._last_requests[this._rid] = new Object();
this._last_requests[this._rid].xml = reqstr;
this._last_rid = this._rid;
for (var i in this._last_requests)
if (this._last_requests.hasOwnProperty(i) &&
i < this._rid-this._hold)
delete(this._last_requests[i]); // truncate
}
return reqstr;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getInitialRequestString = function() {
var reqstr = "";
return reqstr;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getStreamID = function(req) {
this.oDbg.log(req.responseText,4);
if (!req.responseXML || !req.responseXML.documentElement) {
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
var body = req.responseXML.documentElement;
// any session error?
if(body.getAttribute('type') == 'terminate') {
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
// extract stream id used for non-SASL authentication
if (body.getAttribute('authid')) {
this.streamid = body.getAttribute('authid');
this.oDbg.log("got streamid: "+this.streamid,2);
}
if (!this._parseStreamFeatures(body)) {
this._sendEmpty(JSJaC.bind(this._getStreamID, this));
return;
}
this._timeout = setTimeout(JSJaC.bind(this._process, this),
this.getPollInterval());
if (this.register)
this._doInBandReg();
else
this._doAuth();
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._getSuspendVars = function() {
return ('host,port,secure,_rid,_last_rid,_wait,_min_polling,_inactivity,_hold,_last_requests,_pause').split(',');
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._handleInitialResponse = function(req) {
try {
// This will throw an error on Mozilla when the connection was refused
this.oDbg.log(req.getAllResponseHeaders(),4);
this.oDbg.log(req.responseText,4);
} catch(ex) {
this.oDbg.log("No response",4);
}
if (req.status != 200 || !req.responseXML) {
this.oDbg.log("initial response broken (status: "+req.status+")",1);
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
var body = req.responseXML.documentElement;
if (!body || body.tagName != 'body' || body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
this.oDbg.log("no body element or incorrect body in initial response",1);
this._handleEvent("onerror",JSJaCError("500","wait","internal-service-error"));
return;
}
// Check for errors from the server
if (body.getAttribute("type") == "terminate") {
this.oDbg.log("invalid response:\n" + req.responseText,1);
clearTimeout(this._timeout); // remove timer
this._connected = false;
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
this._handleEvent('onerror',JSJaCError('503','cancel','service-unavailable'));
return;
}
// get session ID
this._sid = body.getAttribute('sid');
this.oDbg.log("got sid: "+this._sid,2);
// get attributes from response body
if (body.getAttribute('polling'))
this._min_polling = body.getAttribute('polling');
if (body.getAttribute('inactivity'))
this._inactivity = body.getAttribute('inactivity');
if (body.getAttribute('requests'))
this._setHold(body.getAttribute('requests')-1);
this.oDbg.log("set hold to " + this._getHold(),2);
if (body.getAttribute('ver'))
this._bosh_version = body.getAttribute('ver');
if (body.getAttribute('maxpause'))
this._pause = Number.min(body.getAttribute('maxpause'), JSJACHBC_MAXPAUSE);
// must be done after response attributes have been collected
this.setPollInterval(this._timerval);
/* start sending from queue for not polling connections */
this._connected = true;
this._inQto = setInterval(JSJaC.bind(this._checkInQ, this),
JSJAC_CHECKINQUEUEINTERVAL);
this._interval= setInterval(JSJaC.bind(this._checkQueue, this),
JSJAC_CHECKQUEUEINTERVAL);
/* wait for initial stream response to extract streamid needed
* for digest auth
*/
this._getStreamID(req);
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._parseResponse = function(req) {
if (!this.connected() || !req)
return null;
var r = req.r; // the XmlHttpRequest
try {
if (r.status == 404 || r.status == 403) {
// connection manager killed session
this._abort();
return null;
}
if (r.status != 200 || !r.responseXML) {
this._errcnt++;
var errmsg = "invalid response ("+r.status+"):\n" + r.getAllResponseHeaders()+"\n"+r.responseText;
if (!r.responseXML)
errmsg += "\nResponse failed to parse!";
this.oDbg.log(errmsg,1);
if (this._errcnt > JSJAC_ERR_COUNT) {
// abort
this._abort();
return null;
}
if (this.connected()) {
this.oDbg.log("repeating ("+this._errcnt+")",1);
this._setStatus('proto_error_fallback');
// schedule next tick
setTimeout(JSJaC.bind(this._resume, this),
this.getPollInterval());
}
return null;
}
} catch (e) {
this.oDbg.log("XMLHttpRequest error: status not available", 1);
this._errcnt++;
if (this._errcnt > JSJAC_ERR_COUNT) {
// abort
this._abort();
} else {
if (this.connected()) {
this.oDbg.log("repeating ("+this._errcnt+")",1);
this._setStatus('proto_error_fallback');
// schedule next tick
setTimeout(JSJaC.bind(this._resume, this),
this.getPollInterval());
}
}
return null;
}
var body = r.responseXML.documentElement;
if (!body || body.tagName != 'body' ||
body.namespaceURI != 'http://jabber.org/protocol/httpbind') {
this.oDbg.log("invalid response:\n" + r.responseText,1);
clearTimeout(this._timeout); // remove timer
clearInterval(this._interval);
clearInterval(this._inQto);
this._connected = false;
this.oDbg.log("Disconnected.",1);
this._handleEvent('ondisconnect');
this._setStatus('internal_server_error');
this._handleEvent('onerror',
JSJaCError('500','wait','internal-server-error'));
return null;
}
if (typeof(req.rid) != 'undefined' && this._last_requests[req.rid]) {
if (this._last_requests[req.rid].handled) {
this.oDbg.log("already handled "+req.rid,2);
return null;
} else
this._last_requests[req.rid].handled = true;
}
// Check for errors from the server
if (body.getAttribute("type") == "terminate") {
// read condition
var condition = body.getAttribute('condition');
if (condition != "item-not-found") {
this.oDbg.log("session terminated:\n" + r.responseText,1);
clearTimeout(this._timeout); // remove timer
clearInterval(this._interval);
clearInterval(this._inQto);
try {
removeDB('jsjac', 'state');
} catch (e) {}
this._connected = false;
if (condition == "remote-stream-error")
if (body.getElementsByTagName("conflict").length > 0)
this._setStatus("session-terminate-conflict");
if (condition == null)
condition = 'session-terminate';
this._handleEvent('onerror',JSJaCError('503','cancel',condition));
this.oDbg.log("Aborting remaining connections",4);
for (var i=0; i JSJAC_ERR_COUNT)
this._abort();
}
return null;
}
// no error
this._errcnt = 0;
return r.responseXML.documentElement;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._reInitStream = function(cb) {
// tell http binding to reinit stream with/before next request
this._reinit = true;
this._sendEmpty(this._prepReInitStreamWait(cb));
};
JSJaCHttpBindingConnection.prototype._prepReInitStreamWait = function(cb) {
return JSJaC.bind(function(req) {
this._reInitStreamWait(req, cb);
}, this);
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._reInitStreamWait = function(req, cb) {
this.oDbg.log("checking for stream features");
var doc = req.responseXML.documentElement;
this.oDbg.log(doc);
if (doc.getElementsByTagNameNS) {
this.oDbg.log("checking with namespace");
var features = doc.getElementsByTagNameNS('http://etherx.jabber.org/streams',
'features').item(0);
if (features) {
var bind = features.getElementsByTagNameNS('urn:ietf:params:xml:ns:xmpp-bind',
'bind').item(0);
}
} else {
var featuresNL = doc.getElementsByTagName('stream:features');
for (var i=0, l=featuresNL.length; i= this._last_rid)
this._rid = this._last_rid-1;
this._process();
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._setHold = function(hold) {
if (!hold || isNaN(hold) || hold < 0)
hold = 0;
else if (hold > JSJACHBC_MAX_HOLD)
hold = JSJACHBC_MAX_HOLD;
this._hold = hold;
return this._hold;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._setupRequest = function(async) {
var req = new Object();
var r = XmlHttp.create();
try {
r.open("POST",this._httpbase,async);
r.setRequestHeader('Content-Type','text/xml; charset=utf-8');
} catch(e) { this.oDbg.log(e,1); }
req.r = r;
this._rid++;
req.rid = this._rid;
return req;
};
/**
* @private
*/
JSJaCHttpBindingConnection.prototype._suspend = function() {
if (this._pause == 0)
return; // got nothing to do
var slot = this._getFreeSlot();
// Intentionally synchronous
this._req[slot] = this._setupRequest(false);
var reqstr = "";
while (this._pQueue.length) {
var curNode = this._pQueue[0];
reqstr += curNode;
this._pQueue = this._pQueue.slice(1,this._pQueue.length);
}
//reqstr += "";
reqstr += "";
this.oDbg.log("Disconnecting: " + reqstr,4);
this._req[slot].r.send(reqstr);
};