httpbase
the http base address of the service to be used for
* connecting to jabberoDbg
(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 {
DataStore.removeDB(MINI_HASH, '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
*
<error code='404' type='cancel'>
* <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
* </error>
* The WebSocket protocol is a bit of a mess. Various, incompatible, * protocol drafts were implemented in browsers. Fortunately, recently * a finished protocol was released in RFC6455. Further description * assumes RFC6455 WebSocket protocol version. * * WebSocket browser support. Current (November 2012) browser status: * - Chrome 16+ - works properly and supports RFC6455 * - Firefox 16+ - works properly and support RFC6455 (ealier versions * have problems with proxies) * - Opera 12.10 - supports RFC6455, but does not work at all if a * proxy is configured (earlier versions do not support RFC6455) * - Internet Explorer 10+ - works properly and supports RFC6455 * * Due to the above status, this code is currently recommended on * Chrome 16+, Firefox 16+ and Internet Explorer 10+. Using it on * other browsers is discouraged. * * Please also note that some users are only able to connect to ports * 80 and 443. Port 80 is sometimes intercepted by transparent HTTP * proxies, which mostly does not support WebSocket, so port 443 is * the best choice currently (it does not have to be * encrypted). WebSocket also usually does not work well with reverse * proxies, be sure to make extensive tests if you use one. * * There is no standard for XMPP over WebSocket. However, there is a * draft (http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00) and * this implementation follows it. * * Tested servers: * * - node-xmpp-bosh (https://github.com/dhruvbird/node-xmpp-bosh) - * supports RFC6455 and works with no problems since 0.6.1, it also * transparently uses STARTTLS if necessary * - wxg (https://github.com/Gordin/wxg) - supports RFC6455 and works * with no problems, but cannot connect to servers requiring * STARTTLS (original wxg at https://github.com/hocken/wxg has some * issues, that were fixed by Gordin). * - ejabberd-websockets * (https://github.com/superfeedr/ejabberd-websockets) - does not * support RFC6455 hence it does not work, adapting it to support * RFC6455 should be quite easy for anyone knowing Erlang (some work * in progress can be found on github) * - Openfire (http://www.igniterealtime.org/projects/openfire/) - * unofficial plugin is available, but it lacks support * for RFC6455 hence it does not work * - Apache Vysper (http://mina.apache.org/vysper/) - does * not support RFC6455 hence does not work * - Tigase (http://www.tigase.org/) - works fine since 5.2.0. * - MongooseIM (https://github.com/esl/ejabberd) - a fork of ejabberd * with support for XMPP over Websockets. **/ /*exported JSJaCWebSocketConnection */ /** * Instantiates a WebSocket session. * @class Implementation of {@link http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00 | An XMPP Sub-protocol for WebSocket}. * @extends JSJaCConnection * @constructor * @param {Object} oArg connection properties. * @param {string} oArg.httpbase WebSocket connection endpoint (i.e. ws://localhost:5280) * @param {JSJaCDebugger} [oArg.oDbg] A reference to a debugger implementing the JSJaCDebugger interface. */ function JSJaCWebSocketConnection(oArg) { this.base = JSJaCConnection; this.base(oArg); this._ws = null; this.registerHandler('onerror', JSJaC.bind(this._cleanupWebSocket, this)); } JSJaCWebSocketConnection.prototype = new JSJaCConnection(); JSJaCWebSocketConnection.prototype._cleanupWebSocket = function() { if (this._ws !== null) { this._ws.onclose = null; this._ws.onerror = null; this._ws.onopen = null; this._ws.onmessage = null; this._ws.close(); this._ws = null; } }; /** * Connect to a jabber/XMPP server. * @param {Object} oArg The configuration to be used for connecting. * @param {string} oArg.domain The domain name of the XMPP service. * @param {string} oArg.username The username (nodename) to be logged in with. * @param {string} oArg.resource The resource to identify the login with. * @param {string} oArg.password The user's password. * @param {string} [oArg.authzid] Authorization identity. Used to act as another user, in most cases not needed and rarely supported by servers. If present should be a bare JID (user@example.net). * @param {boolean} [oArg.allow_plain] Whether to allow plain text logins. * @param {boolean} [oArg.allow_scram] Whether to allow SCRAM-SHA-1 authentication. Please note that it is quite slow, do some testing on all required browsers before enabling. * @param {boolean} [oArg.register] Whether to register a new account. * @param {string} [oArg.authhost] The host that handles the actualy authorization. There are cases where this is different from the settings above, e.g. if there's a service that provides anonymous logins at 'anon.example.org'. * @param {string} [oArg.authtype] Must be one of 'sasl' (default), 'nonsasl', 'saslanon', or 'anonymous'. * @param {string} [oArg.xmllang] The requested language for this login. Typically XMPP server try to respond with error messages and the like in this language if available. */ JSJaCWebSocketConnection.prototype.connect = function(oArg) { this._setStatus('connecting'); this.domain = oArg.domain || 'localhost'; this.username = oArg.username; this.resource = oArg.resource; this.pass = oArg.password || oArg.pass; this.authzid = oArg.authzid || ''; this.register = oArg.register; this.authhost = oArg.authhost || this.domain; this.authtype = oArg.authtype || 'sasl'; this.jid = this.username + '@' + this.domain; this.fulljid = this.jid + '/' + this.resource; if (oArg.allow_plain) { this._allow_plain = oArg.allow_plain; } else { this._allow_plain = JSJAC_ALLOW_PLAIN; } if (oArg.allow_scram) { this._allow_scram = oArg.allow_scram; } else { this._allow_scram = JSJAC_ALLOW_SCRAM; } if (oArg.xmllang && oArg.xmllang !== '') { this._xmllang = oArg.xmllang; } else { this._xmllang = 'en'; } if (typeof WebSocket === 'undefined') { this._handleEvent('onerror', JSJaCError('503', 'cancel', 'service-unavailable')); return; } this._ws = new WebSocket(this._httpbase, 'xmpp'); this._ws.onclose = JSJaC.bind(this._onclose, this); this._ws.onerror = JSJaC.bind(this._onerror, this); this._ws.onopen = JSJaC.bind(this._onopen, this); }; /** * @private */ JSJaCWebSocketConnection.prototype._onopen = function() { var reqstr = this._getInitialRequestString(); this.oDbg.log(reqstr, 4); this._ws.onmessage = JSJaC.bind(this._handleOpenStream, this); this._ws.send(reqstr); }; /** * @private */ JSJaCWebSocketConnection.prototype._handleOpenStream = function(event) { var open, stream; this.oDbg.log(event.data, 4); open = event.data; // skip XML prolog if any open = open.substr(open.indexOf('