Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/jsxc.nextcloud.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsualko <github@spam.herberth.eu>2014-01-28 17:57:12 +0400
committersualko <github@spam.herberth.eu>2014-01-28 17:57:12 +0400
commit7735202a0a1e4fb321e3fe01de9db16b5af6ce7e (patch)
tree1998a7949151832dbaa10202005ef6db9e12b13f
parentf071f149675253ce6a1286cd01a781a7108a097b (diff)
bump version
-rw-r--r--CHANGELOG.md4
-rw-r--r--app.json2
-rwxr-xr-xappinfo/info.xml2
-rwxr-xr-xappinfo/version2
-rw-r--r--build/appinfo/info.xml2
-rw-r--r--build/appinfo/version2
-rw-r--r--build/css/jsxc.oc.css8
-rw-r--r--build/js/admin.js4
-rw-r--r--build/js/eof.js4
-rw-r--r--build/js/jsxc/jsxc.lib.js162
-rw-r--r--build/js/jsxc/jsxc.lib.webrtc.js4
-rw-r--r--build/js/jsxc/lib/strophe.jingle/strophe.jingle.adapter.js12
-rw-r--r--build/js/jsxc/lib/strophe.jingle/strophe.jingle.js2
-rw-r--r--build/js/jsxc/lib/strophe.jingle/strophe.jingle.sdp.js6
-rw-r--r--build/js/jsxc/lib/strophe.jingle/strophe.jingle.session.js6
-rw-r--r--build/js/jsxc/lib/strophe.js3377
-rw-r--r--build/js/ojsxc.js13
-rw-r--r--css/jsxc.oc.css4
m---------js/jsxc0
-rw-r--r--js/ojsxc.js9
20 files changed, 2324 insertions, 1301 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6fadaf2..2dcbc5e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+v0.5.2 / 2014-01-28
+===
+- upgrade jsxc to v0.5.2
+
v0.5.1 / 2014-01-27
===
- downgrade required oc version
diff --git a/app.json b/app.json
index 506d885..9e9e9d3 100644
--- a/app.json
+++ b/app.json
@@ -1,6 +1,6 @@
{
"name": "ojsxc",
- "version": "0.5.1",
+ "version": "0.5.2",
"description": "Real-time chat app for owncloud",
"homepage": "http://jsxc.org/",
"license": "MIT",
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 8cd672e..5d88b87 100755
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -3,7 +3,7 @@
<id>ojsxc</id>
<name>JavaScript XMPP Chat</name>
<description>XMPP Chat with OTR</description>
- <version>0.5.1</version>
+ <version>0.5.2</version>
<licence>MIT</licence>
<author>Klaus Herberth</author>
<require>5</require>
diff --git a/appinfo/version b/appinfo/version
index 5d4294b..2411653 100755
--- a/appinfo/version
+++ b/appinfo/version
@@ -1 +1 @@
-0.5.1 \ No newline at end of file
+0.5.2 \ No newline at end of file
diff --git a/build/appinfo/info.xml b/build/appinfo/info.xml
index 8cd672e..5d88b87 100644
--- a/build/appinfo/info.xml
+++ b/build/appinfo/info.xml
@@ -3,7 +3,7 @@
<id>ojsxc</id>
<name>JavaScript XMPP Chat</name>
<description>XMPP Chat with OTR</description>
- <version>0.5.1</version>
+ <version>0.5.2</version>
<licence>MIT</licence>
<author>Klaus Herberth</author>
<require>5</require>
diff --git a/build/appinfo/version b/build/appinfo/version
index 5d4294b..2411653 100644
--- a/build/appinfo/version
+++ b/build/appinfo/version
@@ -1 +1 @@
-0.5.1 \ No newline at end of file
+0.5.2 \ No newline at end of file
diff --git a/build/css/jsxc.oc.css b/build/css/jsxc.oc.css
index 9913917..609a5ab 100644
--- a/build/css/jsxc.oc.css
+++ b/build/css/jsxc.oc.css
@@ -1,5 +1,5 @@
/**
- * ojsxc v0.5.1 - 2014-01-27
+ * ojsxc v0.5.2 - 2014-01-28
*
* Copyright (c) 2014 Klaus Herberth <klaus@jsxc.org> <br>
* Released under the MIT license
@@ -7,7 +7,7 @@
* Please see http://jsxc.org/
*
* @author Klaus Herberth <klaus@jsxc.org>
- * @version 0.5.1
+ * @version 0.5.2
*/
.jsxc_online {
@@ -595,4 +595,8 @@ div.jsxc_transfer.jsxc_enc.jsxc_trust {
background-image: url('%appswebroot%/ojsxc/img/fail-icon.png');
color: #800000;
border-color: #800000;
+}
+
+.jsxc_log{
+ width: 500px;
} \ No newline at end of file
diff --git a/build/js/admin.js b/build/js/admin.js
index b159a20..6ec423d 100644
--- a/build/js/admin.js
+++ b/build/js/admin.js
@@ -1,5 +1,5 @@
/**
- * ojsxc v0.5.1 - 2014-01-27
+ * ojsxc v0.5.2 - 2014-01-28
*
* Copyright (c) 2014 Klaus Herberth <klaus@jsxc.org> <br>
* Released under the MIT license
@@ -7,7 +7,7 @@
* Please see http://jsxc.org/
*
* @author Klaus Herberth <klaus@jsxc.org>
- * @version 0.5.1
+ * @version 0.5.2
*/
$(document).ready(function() {
diff --git a/build/js/eof.js b/build/js/eof.js
index d833831..9a6cee6 100644
--- a/build/js/eof.js
+++ b/build/js/eof.js
@@ -1,5 +1,5 @@
/**
- * ojsxc v0.5.1 - 2014-01-27
+ * ojsxc v0.5.2 - 2014-01-28
*
* Copyright (c) 2014 Klaus Herberth <klaus@jsxc.org> <br>
* Released under the MIT license
@@ -7,7 +7,7 @@
* Please see http://jsxc.org/
*
* @author Klaus Herberth <klaus@jsxc.org>
- * @version 0.5.1
+ * @version 0.5.2
*/
/**
diff --git a/build/js/jsxc/jsxc.lib.js b/build/js/jsxc/jsxc.lib.js
index 0b8185a..ad9b6af 100644
--- a/build/js/jsxc/jsxc.lib.js
+++ b/build/js/jsxc/jsxc.lib.js
@@ -1,5 +1,5 @@
/**
- * jsxc v0.5.1 - 2014-01-27
+ * jsxc v0.5.2 - 2014-01-28
*
* Copyright (c) 2014 Klaus Herberth <klaus@jsxc.org> <br>
* Released under the MIT license
@@ -7,7 +7,7 @@
* Please see http://jsxc.org/
*
* @author Klaus Herberth <klaus@jsxc.org>
- * @version 0.5.1
+ * @version 0.5.2
*/
var jsxc;
@@ -22,7 +22,7 @@ var jsxc;
*/
jsxc = {
/** Version of jsxc */
- version: '0.5.1',
+ version: '0.5.2',
/** True if i'm the chief */
chief: false,
@@ -81,9 +81,6 @@ var jsxc;
/** My css id */
cid: null,
- /** Shortcut for jsxc.options.debug */
- debug: null,
-
/** Some constants */
CONST: {
NOTIFICATION_DEFAULT: 'default',
@@ -93,6 +90,53 @@ var jsxc;
},
/**
+ * Write debug message to console and to log.
+ *
+ * @memberOf jsxc
+ * @param {String} msg Debug message
+ * @param {Object} data
+ * @param {String} Could be warn|error|null
+ */
+ debug: function(msg, data, level) {
+ if (level) {
+ msg = '[' + level + '] ' + msg;
+ }
+
+ if (data) {
+ console.log(msg, data);
+ jsxc.log = jsxc.log + msg + ': ' + $("<span>").prepend($(data).clone()).html() + '\n';
+ } else {
+ console.log(msg);
+ jsxc.log = jsxc.log + msg + '\n';
+ }
+ },
+
+ /**
+ * Write warn message.
+ *
+ * @memberOf jsxc
+ * @param {String} msg Warn message
+ * @param {Object} data
+ */
+ warn: function(msg, data) {
+ jsxc.debug(msg, data, 'WARN');
+ },
+
+ /**
+ * Write error message.
+ *
+ * @memberOf jsxc
+ * @param {String} msg Error message
+ * @param {Object} data
+ */
+ error: function(msg, data) {
+ jsxc.debug(msg, data, 'ERROR');
+ },
+
+ /** debug log */
+ log: '',
+
+ /**
* Starts the action
*
* @memberOf jsxc
@@ -114,9 +158,6 @@ var jsxc;
jsxc.storage.updateUserItem('options', key, value);
};
- // Shortcut
- jsxc.debug = jsxc.options.debug;
-
jsxc.storageNotConform = jsxc.storage.getItem('storageNotConform') || 2;
// detect language
@@ -174,7 +215,9 @@ var jsxc;
// create jquery object
var form = jsxc.options.loginForm.form = $(jsxc.options.loginForm.form);
- var events = form.data('events') || {submit: []};
+ var events = form.data('events') || {
+ submit: []
+ };
var submits = [];
// save attached submit events and remove them. Will be reattached
@@ -182,7 +225,7 @@ var jsxc;
$.each(events.submit, function(index, val) {
submits.push(val.handler);
});
-
+
form.data('submits', submits);
form.off('submit');
@@ -455,12 +498,12 @@ var jsxc;
submitLoginForm: function() {
var form = jsxc.options.loginForm.form.off('submit');
- //Attach original events
+ // Attach original events
var submits = form.data('submits') || [];
$.each(submits, function(index, val) {
form.submit(val);
- });
-
+ });
+
if (form.find('#submit')) {
form.find('#submit').click();
} else {
@@ -526,7 +569,7 @@ var jsxc;
var k = key.replace(/ /gi, '_');
if (!jsxc.l[k]) {
- jsxc.debug('[WARN] No translation for: ' + k);
+ jsxc.warn('No translation for: ' + k);
}
return jsxc.l[k] || key.replace(/_/gi, ' ');
@@ -672,6 +715,7 @@ var jsxc;
var ri = $('#' + cid); // roster item from user
var we = jsxc.gui.getWindow(cid); // window element from user
var ue = $('#' + cid + ', #jsxc_window_' + cid + ', .jsxc_buddy_' + cid); // both
+ var bullet = $('.jsxc_buddy_' + cid);
// Attach data to corresponding roster item
ri.data(data);
@@ -681,6 +725,7 @@ var jsxc;
// Change name and add title
ue.find('.jsxc_name').text(data.name).attr('title', 'is ' + jsxc.CONST.STATUS[data.status]);
+ bullet.attr('title', 'is ' + jsxc.CONST.STATUS[data.status]);
// Update gui according to encryption state
switch (data.msgstate) {
@@ -738,7 +783,7 @@ var jsxc;
jsxc.storage.setUserItem('avatar_' + data.avatar, src);
setAvatar(src);
}, Strophe.getBareJidFromJid(data.jid), function(msg) {
- jsxc.debug('Error', msg);
+ jsxc.error('Could not load vcard.', msg);
});
}
}
@@ -1083,6 +1128,37 @@ var jsxc;
*/
showAboutDialog: function() {
jsxc.gui.dialog.open(jsxc.gui.template.get('aboutDialog'));
+
+ $('#jsxc_dialog .jsxc_debuglog').click(function() {
+ jsxc.gui.showDebugLog();
+ });
+ },
+
+ /**
+ * Show debug log.
+ *
+ * @memberOf jsxc.gui
+ */
+ showDebugLog: function() {
+ var userInfo = '<h3>User information</h3>';
+
+ if (navigator) {
+ var key;
+ for (key in navigator) {
+ if (navigator.hasOwnProperty(key) && typeof navigator[key] === 'string') {
+ userInfo += '<b>' + key + ':</b> ' + navigator[key] + '<br />';
+ }
+ }
+ }
+
+ if (window.screen) {
+ userInfo += '<b>Height:</b> ' + window.screen.height + '<br />';
+ userInfo += '<b>Width:</b> ' + window.screen.width + '<br />';
+ }
+
+ userInfo += '<b>jsxc version:</b> ' + jsxc.version + '<br />';
+
+ jsxc.gui.dialog.open('<div class="jsxc_log">'+userInfo+'<h3>Log</h3><pre>' + jsxc.escapeHTML(jsxc.log) + '</pre></div>');
}
};
@@ -1156,7 +1232,7 @@ var jsxc;
*
* @param {String} cid CSS compatible jid
*/
- add: function(cid) {
+ add: function(cid) {
var data = jsxc.storage.getUserItem('buddy_' + cid);
var bud = jsxc.gui.buddyTemplate.clone().attr('id', cid).attr('data-type', data.type || 'chat');
@@ -1577,9 +1653,9 @@ var jsxc;
* @param {String} cid CSS compatible jid
*/
close: function(cid) {
-
+
if (!jsxc.el_exists('#jsxc_window_' + cid)) {
- jsxc.debug('[Warning] Want to close a window, that is not open.');
+ jsxc.warn('Want to close a window, that is not open.');
return;
}
@@ -1653,7 +1729,7 @@ var jsxc;
*
* @param {String} cid
*/
- hide: function(cid) {
+ hide: function(cid) {
jsxc.storage.updateUserItem('window_' + cid, 'minimize', true);
jsxc.gui.window._hide(cid);
@@ -1664,7 +1740,7 @@ var jsxc;
*
* @param {String} cid
*/
- _hide: function(cid) {
+ _hide: function(cid) {
$('#jsxc_window_' + cid + ' .jsxc_window').slideUp();
jsxc.gui.getWindow(cid).trigger('hidden.window.jsxc');
},
@@ -2019,7 +2095,8 @@ var jsxc;
<br />\
Real-time chat app for OwnCloud. This app requires external<br /> XMPP server (openfire, ejabberd etc.).<br />\
<br />\
- <i>Released under the MIT license</i></p>'
+ <i>Released under the MIT license</i></p>\
+ <p class="jsxc_right"><a class="button jsxc_debuglog" href="#">Show debug log</a></p>'
};
/**
@@ -2051,16 +2128,16 @@ var jsxc;
// Create new connection (no login)
jsxc.xmpp.conn = new Strophe.Connection(url);
- // jsxc.xmpp.conn.xmlInput = function(data) {
- // jsxc.debug('<', data);
- // };
- // jsxc.xmpp.conn.xmlOutput = function(data) {
- // jsxc.debug('>', data);
- // };
- //
- // Strophe.log = function (level, msg) {
- // jsxc.debug(level + " " + msg);
- // };
+// jsxc.xmpp.conn.xmlInput = function(data) {
+// console.log('<', data);
+// };
+// jsxc.xmpp.conn.xmlOutput = function(data) {
+// console.log('>', data);
+// };
+
+// Strophe.log = function (level, msg) {
+// console.log(level + " " + msg);
+// };
var callback = function(status, condition) {
@@ -2071,7 +2148,7 @@ var jsxc;
jsxc.cid = jsxc.jidToCid(jsxc.xmpp.conn.jid.toLowerCase());
$(document).trigger('connected.jsxc');
break;
- case Strophe.Status.ATTACHED:
+ case Strophe.Status.ATTACHED:
$(document).trigger('attached.jsxc');
break;
case Strophe.Status.DISCONNECTED:
@@ -2142,7 +2219,7 @@ var jsxc;
jsxc.xmpp.conn.pause();
// Save sid and jid
- jsxc.storage.setItem('sid', jsxc.xmpp.conn.sid);
+ jsxc.storage.setItem('sid', jsxc.xmpp.conn._proto.sid);
jsxc.storage.setItem('jid', jsxc.xmpp.conn.jid.toLowerCase());
jsxc.storage.setItem('lastActivity', (new Date()).getTime());
@@ -2191,7 +2268,7 @@ var jsxc;
}).c('query', {
xmlns: 'jabber:iq:roster'
});
-
+
jsxc.xmpp.conn.sendIQ(iq, jsxc.xmpp.onRoster);
} else {
jsxc.xmpp.sendPres();
@@ -2226,6 +2303,7 @@ var jsxc;
pres.c('c', jsxc.xmpp.conn.caps.generateCapsAttrs());
}
+ jsxc.debug('Send presence', pres.toString());
jsxc.xmpp.conn.send(pres);
},
@@ -2392,7 +2470,7 @@ var jsxc;
* @param {dom} presence
* @private
*/
- onPresence: function(presence) {
+ onPresence: function(presence) {
/*
* <presence xmlns='jabber:client' type='unavailable' from='' to=''/>
*
@@ -2407,6 +2485,8 @@ var jsxc;
* node='http://psi-im.org/caps' ver='caps-b75d8d2b25' ext='ca cs
* ep-notify-2 html'/> </presence>
*/
+ jsxc.debug('onPresence', presence);
+
var ptype = $(presence).attr('type');
var from = $(presence).attr('from');
var jid = Strophe.getBareJidFromJid(from).toLowerCase();
@@ -2418,14 +2498,12 @@ var jsxc;
var status = null;
var xVCard = $(presence).find('x[xmlns="vcard-temp:x:update"]');
- jsxc.debug('onPresence', presence);
-
if (jid === to) {
return true;
}
if (ptype === 'error') {
- jsxc.debug('[XMPP ERROR] ' + $(presence).attr('code'));
+ jsxc.error('[XMPP] ' + $(presence).attr('code'));
return true;
}
@@ -2456,7 +2534,7 @@ var jsxc;
}
var maxVal = [];
- var max = 0, prop;
+ var max = 0, prop = null;
for (prop in res) {
if (res.hasOwnProperty(prop)) {
if (max <= res[prop]) {
@@ -2515,7 +2593,7 @@ var jsxc;
* <body>...</body> <active
* xmlns='http://jabber.org/protocol/chatstates'/> </message>
*/
-
+
jsxc.debug('Incoming message', message);
var type = $(message).attr('type');
@@ -3205,7 +3283,7 @@ var jsxc;
});
jsxc.buddyList[cid].on('error', function(err) {
- jsxc.debug('[OTR] ' + err);
+ jsxc.error('[OTR] ', err);
jsxc.gui.window.postMessage(cid, 'sys', '[OTR] ' + err);
});
diff --git a/build/js/jsxc/jsxc.lib.webrtc.js b/build/js/jsxc/jsxc.lib.webrtc.js
index d0bbe28..97bb2ef 100644
--- a/build/js/jsxc/jsxc.lib.webrtc.js
+++ b/build/js/jsxc/jsxc.lib.webrtc.js
@@ -1,5 +1,5 @@
/**
- * jsxc v0.5.1 - 2014-01-27
+ * jsxc v0.5.2 - 2014-01-28
*
* Copyright (c) 2014 Klaus Herberth <klaus@jsxc.org> <br>
* Released under the MIT license
@@ -7,7 +7,7 @@
* Please see http://jsxc.org/
*
* @author Klaus Herberth <klaus@jsxc.org>
- * @version 0.5.1
+ * @version 0.5.2
*/
/* jsxc, Strophe, SDPUtil, getUserMediaWithConstraints, setupRTC, jQuery */
diff --git a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.adapter.js b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.adapter.js
index 23984de..f10fcdf 100644
--- a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.adapter.js
+++ b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.adapter.js
@@ -1,5 +1,8 @@
/* jshint -W117 */
-function TraceablePeerConnection(ice_config, constraints) {
+var setupRTC, getUserMediaWithConstraints, TraceablePeerConnection;
+
+(function($){
+TraceablePeerConnection = function(ice_config, constraints) {
var self = this;
var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
this.peerconnection = new RTCPeerconnection(ice_config, constraints);
@@ -176,7 +179,7 @@ TraceablePeerConnection.prototype.getStats = function(callback) {
// mozilla chrome compat layer -- very similar to adapter.js
-function setupRTC() {
+setupRTC = function (){
var RTC = null;
if (navigator.mozGetUserMedia) {
console.log('This appears to be Firefox');
@@ -229,9 +232,9 @@ function setupRTC() {
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
}
return RTC;
-}
+};
-function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
+getUserMediaWithConstraints = function(um, resolution, bandwidth, fps) {
var constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
@@ -324,3 +327,4 @@ function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
$(document).trigger('mediafailure.jingle');
}
}
+}(jQuery)); \ No newline at end of file
diff --git a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.js b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.js
index 4417f10..f1fff5a 100644
--- a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.js
+++ b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.js
@@ -1,4 +1,5 @@
/* jshint -W117 */
+(function($){
Strophe.addConnectionPlugin('jingle', {
connection: null,
sessions: {},
@@ -257,3 +258,4 @@ Strophe.addConnectionPlugin('jingle', {
// implement push?
}
});
+}(jQuery));
diff --git a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.sdp.js b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.sdp.js
index 3ec230c..7cdcd97 100644
--- a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.sdp.js
+++ b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.sdp.js
@@ -1,6 +1,9 @@
/* jshint -W117 */
+var SDP;
+
+(function($){
// SDP STUFF
-function SDP(sdp) {
+SDP = function(sdp) {
this.media = sdp.split('\r\nm=');
for (var i = 1; i < this.media.length; i++) {
this.media[i] = 'm=' + this.media[i];
@@ -807,3 +810,4 @@ SDPUtil = {
return line + '\r\n';
}
};
+}(jQuery)); \ No newline at end of file
diff --git a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.session.js b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.session.js
index d5be0b0..e1cfa0b 100644
--- a/build/js/jsxc/lib/strophe.jingle/strophe.jingle.session.js
+++ b/build/js/jsxc/lib/strophe.jingle/strophe.jingle.session.js
@@ -1,6 +1,9 @@
/* jshint -W117 */
// Jingle stuff
-function JingleSession(me, sid, connection) {
+var JingleSession;
+
+(function($){
+JingleSession = function(me, sid, connection) {
this.me = me;
this.sid = sid;
this.connection = connection;
@@ -853,3 +856,4 @@ JingleSession.prototype.getStats = function (interval) {
return this.statsinterval;
};
+}(jQuery));
diff --git a/build/js/jsxc/lib/strophe.js b/build/js/jsxc/lib/strophe.js
index 2cdfb91..564465b 100644
--- a/build/js/jsxc/lib/strophe.js
+++ b/build/js/jsxc/lib/strophe.js
@@ -1,8 +1,6 @@
-
/**
* Modified by
- * Klaus Herberth, 2013
- * https://github.com/sualko/strophejs/tree/master/
+ * Klaus Herberth, 2014
*/
// This code was written by Tyler Akins and has been placed in the
@@ -85,6 +83,7 @@ var Base64 = (function () {
return obj;
})();
+
/*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
* in FIPS PUB 180-1
@@ -94,34 +93,18 @@ var Base64 = (function () {
* See http://pajhome.org.uk/crypt/md5 for details.
*/
-/*
- * Configurable variables. You may need to tweak these to be compatible with
- * the server-side, but the defaults work in most cases.
- */
-var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
-var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
-var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+/* Some functions and variables have been stripped for use with Strophe */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
-function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
-function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
-function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
-function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
+function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * 8));}
+function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * 8));}
function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
/*
- * Perform a simple self-test to see if the VM is working
- */
-function sha1_vm_test()
-{
- return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
-}
-
-/*
* Calculate the SHA-1 of an array of big-endian words, and a bit length
*/
function core_sha1(x, len)
@@ -195,7 +178,7 @@ function sha1_kt(t)
function core_hmac_sha1(key, data)
{
var bkey = str2binb(key);
- if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * chrsz); }
+ if (bkey.length > 16) { bkey = core_sha1(bkey, key.length * 8); }
var ipad = new Array(16), opad = new Array(16);
for (var i = 0; i < 16; i++)
@@ -204,7 +187,7 @@ function core_hmac_sha1(key, data)
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
- var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
+ var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8);
return core_sha1(opad.concat(hash), 512 + 160);
}
@@ -234,10 +217,10 @@ function rol(num, cnt)
function str2binb(str)
{
var bin = [];
- var mask = (1 << chrsz) - 1;
- for (var i = 0; i < str.length * chrsz; i += chrsz)
+ var mask = 255;
+ for (var i = 0; i < str.length * 8; i += 8)
{
- bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
+ bin[i>>5] |= (str.charCodeAt(i / 8) & mask) << (24 - i%32);
}
return bin;
}
@@ -248,25 +231,10 @@ function str2binb(str)
function binb2str(bin)
{
var str = "";
- var mask = (1 << chrsz) - 1;
- for (var i = 0; i < bin.length * 32; i += chrsz)
- {
- str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
- }
- return str;
-}
-
-/*
- * Convert an array of big-endian words to a hex string.
- */
-function binb2hex(binarray)
-{
- var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
- var str = "";
- for (var i = 0; i < binarray.length * 4; i++)
+ var mask = 255;
+ for (var i = 0; i < bin.length * 32; i += 8)
{
- str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
- hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask);
}
return str;
}
@@ -286,12 +254,13 @@ function binb2b64(binarray)
((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
for (j = 0; j < 4; j++)
{
- if (i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
+ if (i * 8 + j * 6 > binarray.length * 32) { str += "="; }
else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); }
}
}
return str;
}
+
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
@@ -301,15 +270,11 @@ function binb2b64(binarray)
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
-var MD5 = (function () {
- /*
- * Configurable variables. You may need to tweak these to be compatible with
- * the server-side, but the defaults work in most cases.
- */
- var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
- var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
- var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+/*
+ * Everything that isn't used by Strophe has been stripped here!
+ */
+var MD5 = (function () {
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
@@ -329,14 +294,12 @@ var MD5 = (function () {
/*
* Convert a string to an array of little-endian words
- * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
var str2binl = function (str) {
var bin = [];
- var mask = (1 << chrsz) - 1;
- for(var i = 0; i < str.length * chrsz; i += chrsz)
+ for(var i = 0; i < str.length * 8; i += 8)
{
- bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+ bin[i>>5] |= (str.charCodeAt(i / 8) & 255) << (i%32);
}
return bin;
};
@@ -346,10 +309,9 @@ var MD5 = (function () {
*/
var binl2str = function (bin) {
var str = "";
- var mask = (1 << chrsz) - 1;
- for(var i = 0; i < bin.length * 32; i += chrsz)
+ for(var i = 0; i < bin.length * 32; i += 8)
{
- str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+ str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & 255);
}
return str;
};
@@ -358,7 +320,7 @@ var MD5 = (function () {
* Convert an array of little-endian words to a hex string.
*/
var binl2hex = function (binarray) {
- var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var hex_tab = "0123456789abcdef";
var str = "";
for(var i = 0; i < binarray.length * 4; i++)
{
@@ -369,27 +331,6 @@ var MD5 = (function () {
};
/*
- * Convert an array of little-endian words to a base-64 string
- */
- var binl2b64 = function (binarray) {
- var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- var str = "";
- var triplet, j;
- for(var i = 0; i < binarray.length * 4; i += 3)
- {
- triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) |
- (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) |
- ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
- for(j = 0; j < 4; j++)
- {
- if(i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
- else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); }
- }
- }
- return str;
- };
-
- /*
* These functions implement the four basic operations the algorithm uses.
*/
var md5_cmn = function (q, a, b, x, s, t) {
@@ -510,24 +451,6 @@ var MD5 = (function () {
};
- /*
- * Calculate the HMAC-MD5, of a key and some data
- */
- var core_hmac_md5 = function (key, data) {
- var bkey = str2binl(key);
- if(bkey.length > 16) { bkey = core_md5(bkey, key.length * chrsz); }
-
- var ipad = new Array(16), opad = new Array(16);
- for(var i = 0; i < 16; i++)
- {
- ipad[i] = bkey[i] ^ 0x36363636;
- opad[i] = bkey[i] ^ 0x5C5C5C5C;
- }
-
- var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
- return core_md5(opad.concat(hash), 512 + 128);
- };
-
var obj = {
/*
* These are the functions you'll usually want to call.
@@ -535,39 +458,17 @@ var MD5 = (function () {
* strings.
*/
hexdigest: function (s) {
- return binl2hex(core_md5(str2binl(s), s.length * chrsz));
- },
-
- b64digest: function (s) {
- return binl2b64(core_md5(str2binl(s), s.length * chrsz));
+ return binl2hex(core_md5(str2binl(s), s.length * 8));
},
hash: function (s) {
- return binl2str(core_md5(str2binl(s), s.length * chrsz));
- },
-
- hmac_hexdigest: function (key, data) {
- return binl2hex(core_hmac_md5(key, data));
- },
-
- hmac_b64digest: function (key, data) {
- return binl2b64(core_hmac_md5(key, data));
- },
-
- hmac_hash: function (key, data) {
- return binl2str(core_hmac_md5(key, data));
- },
-
- /*
- * Perform a simple self-test to see if the VM is working
- */
- test: function () {
- return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72";
+ return binl2str(core_md5(str2binl(s), s.length * 8));
}
};
return obj;
})();
+
/*
This program is distributed under the terms of the MIT license.
Please see the LICENSE file for details.
@@ -575,20 +476,24 @@ var MD5 = (function () {
Copyright 2006-2008, OGG, LLC
*/
-/* jslint configuration: */
+/* jshint undef: true, unused: true:, noarg: true, latedef: true */
/*global document, window, setTimeout, clearTimeout, console,
- XMLHttpRequest, ActiveXObject,
- Base64, MD5,
- Strophe, $build, $msg, $iq, $pres */
+ ActiveXObject, Base64, MD5, DOMParser */
+// from sha1.js
+/*global core_hmac_sha1, binb2str, str_hmac_sha1, str_sha1, b64_hmac_sha1*/
/** File: strophe.js
- * A JavaScript library for XMPP BOSH.
+ * A JavaScript library for XMPP BOSH/XMPP over Websocket.
*
* This is the JavaScript version of the Strophe library. Since JavaScript
- * has no facilities for persistent TCP connections, this library uses
+ * had no facilities for persistent TCP connections, 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.
+ *
+ * This version of Strophe also works with WebSockets.
+ * For more information on XMPP-over WebSocket see this RFC draft:
+ * http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00
*/
/** PrivateFunction: Function.prototype.bind
@@ -597,7 +502,7 @@ var MD5 = (function () {
* This Function object extension method creates a bound method similar
* to those in Python. This means that the 'this' object will point
* to the instance you want. See
- * <a href='https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind'>MDC's bind() documentation</a> and
+ * <a href='https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind'>MDC's bind() documentation</a> and
* <a href='http://benjamin.smedbergs.us/blog/2007-01-03/bound-functions-and-function-imports-in-javascript/'>Bound Functions and Function Imports in JavaScript</a>
* for a complete explanation.
*
@@ -606,7 +511,7 @@ var MD5 = (function () {
*
* Parameters:
* (Object) obj - The object that will become 'this' in the bound function.
- * (Object) argN - An option argument that will be prepended to the
+ * (Object) argN - An option argument that will be prepended to the
* arguments given for the function call
*
* Returns:
@@ -724,12 +629,11 @@ function $pres(attrs) { return new Strophe.Builder("presence", attrs); }
* provide a namespace for library objects, constants, and functions.
*/
Strophe = {
- ASYNC: true,
/** Constant: VERSION
* The version of the Strophe library. Unreleased builds will have
* a version of head-HASH where HASH is a partial revision.
*/
- VERSION: "8e14efd",
+ VERSION: "1.1.3",
/** Constants: XMPP Namespace Constants
* Common namespace constants from the XMPP RFCs and XEPs.
@@ -771,76 +675,59 @@ Strophe = {
},
- /** Constants: XHTML_IM Namespace
- * contains allowed tags, tag attributes, and css properties.
+ /** Constants: XHTML_IM Namespace
+ * contains allowed tags, tag attributes, and css properties.
* Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset.
* See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended
* allowed tags and their attributes.
*/
XHTML: {
- tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'],
- attributes: {
- 'a': ['href'],
- 'blockquote': ['style'],
- 'br': [],
- 'cite': ['style'],
- 'em': [],
- 'img': ['src', 'alt', 'style', 'height', 'width'],
- 'li': ['style'],
- 'ol': ['style'],
- 'p': ['style'],
- 'span': ['style'],
- 'strong': [],
- 'ul': ['style'],
- 'body': []
- },
- css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'],
- validTag: function(tag)
- {
- for(var i = 0; i < Strophe.XHTML.tags.length; i++) {
- if(tag == Strophe.XHTML.tags[i]) {
- return true;
- }
- }
- return false;
- },
- validAttribute: function(tag, attribute)
- {
- if(typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) {
- for(var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
- if(attribute == Strophe.XHTML.attributes[tag][i]) {
- return true;
- }
- }
- }
- return false;
- },
- validCSS: function(style)
- {
- for(var i = 0; i < Strophe.XHTML.css.length; i++) {
- if(style == Strophe.XHTML.css[i]) {
- return true;
- }
- }
- return false;
- }
- },
-
- /** Function: addNamespace
- * This function is used to extend the current namespaces in
- * Strophe.NS. It takes a key and a value with the key being the
- * name of the new namespace, with its actual value.
- * For example:
- * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub");
- *
- * Parameters:
- * (String) name - The name under which the namespace will be
- * referenced under Strophe.NS
- * (String) value - The actual namespace.
- */
- addNamespace: function (name, value)
- {
- Strophe.NS[name] = value;
+ tags: ['a','blockquote','br','cite','em','img','li','ol','p','span','strong','ul','body'],
+ attributes: {
+ 'a': ['href'],
+ 'blockquote': ['style'],
+ 'br': [],
+ 'cite': ['style'],
+ 'em': [],
+ 'img': ['src', 'alt', 'style', 'height', 'width'],
+ 'li': ['style'],
+ 'ol': ['style'],
+ 'p': ['style'],
+ 'span': ['style'],
+ 'strong': [],
+ 'ul': ['style'],
+ 'body': []
+ },
+ css: ['background-color','color','font-family','font-size','font-style','font-weight','margin-left','margin-right','text-align','text-decoration'],
+ validTag: function(tag)
+ {
+ for(var i = 0; i < Strophe.XHTML.tags.length; i++) {
+ if(tag == Strophe.XHTML.tags[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+ validAttribute: function(tag, attribute)
+ {
+ if(typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) {
+ for(var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {
+ if(attribute == Strophe.XHTML.attributes[tag][i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ validCSS: function(style)
+ {
+ for(var i = 0; i < Strophe.XHTML.css.length; i++) {
+ if(style == Strophe.XHTML.css[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
},
/** Constants: Connection Status Constants
@@ -917,6 +804,23 @@ Strophe = {
TIMEOUT: 1.1,
SECONDARY_TIMEOUT: 0.1,
+ /** Function: addNamespace
+ * This function is used to extend the current namespaces in
+ * Strophe.NS. It takes a key and a value with the key being the
+ * name of the new namespace, with its actual value.
+ * For example:
+ * Strophe.addNamespace('PUBSUB', "http://jabber.org/protocol/pubsub");
+ *
+ * Parameters:
+ * (String) name - The name under which the namespace will be
+ * referenced under Strophe.NS
+ * (String) value - The actual namespace.
+ */
+ addNamespace: function (name, value)
+ {
+ Strophe.NS[name] = value;
+ },
+
/** Function: forEachChild
* Map a function over some or all child elements of a given element.
*
@@ -976,10 +880,10 @@ Strophe = {
var doc;
// IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload.
- // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be
- // less than 10 in the case of IE9 and below.
- if (document.implementation.createDocument === undefined ||
- document.implementation.createDocument && document.documentMode && document.documentMode < 10) {
+ // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be
+ // less than 10 in the case of IE9 and below.
+ if (document.implementation.createDocument === undefined ||
+ document.implementation.createDocument && document.documentMode && document.documentMode < 10) {
doc = this._getIEXmlDom();
doc.appendChild(doc.createElement('strophe'));
} else {
@@ -1097,7 +1001,7 @@ Strophe = {
* Parameters:
* (String) text - text to escape.
*
- * Returns:
+ * Returns:
* Escaped text.
*/
xmlescape: function(text)
@@ -1137,9 +1041,10 @@ Strophe = {
*/
xmlHtmlNode: function (html)
{
+ var node;
//ensure text is escaped
if (window.DOMParser) {
- parser = new DOMParser();
+ var parser = new DOMParser();
node = parser.parseFromString(html, "text/xml");
} else {
node = new ActiveXObject("Microsoft.XMLDOM");
@@ -1225,7 +1130,7 @@ Strophe = {
*/
createHtml: function (elem)
{
- var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue, children, child;
+ var i, el, j, tag, attribute, value, css, cssAttrs, attr, cssName, cssValue;
if (elem.nodeType == Strophe.ElementType.NORMAL) {
tag = elem.nodeName.toLowerCase();
if(Strophe.XHTML.validTag(tag)) {
@@ -1430,10 +1335,12 @@ Strophe = {
* be one of the values in Strophe.LogLevel.
* (String) msg - The log message.
*/
+ /* jshint ignore:start */
log: function (level, msg)
{
return;
},
+ /* jshint ignore:end */
/** Function: debug
* Log a message at the Strophe.LogLevel.DEBUG level.
@@ -1748,12 +1655,13 @@ Strophe.Builder.prototype = {
*/
cnode: function (elem)
{
+ var impNode;
var xmlGen = Strophe.xmlGenerator();
try {
- var impNode = (xmlGen.importNode !== undefined);
+ impNode = (xmlGen.importNode !== undefined);
}
catch (e) {
- var impNode = false;
+ impNode = false;
}
var newElem = impNode ?
xmlGen.importNode(elem, true) :
@@ -1847,7 +1755,7 @@ Strophe.Handler = function (handler, ns, name, type, id, from, options)
this.type = type;
this.id = id;
this.options = options || {matchBare: false};
-
+
// default matchBare to false if undefined
if (!this.options.matchBare) {
this.options.matchBare = false;
@@ -1877,7 +1785,7 @@ Strophe.Handler.prototype = {
{
var nsMatch;
var from = null;
-
+
if (this.options.matchBare) {
from = Strophe.getBareJidFromJid(elem.getAttribute('from'));
} else {
@@ -2028,121 +1936,13 @@ Strophe.TimedHandler.prototype = {
}
};
-/** PrivateClass: Strophe.Request
- * _Private_ helper class that provides a cross implementation abstraction
- * for a BOSH related XMLHttpRequest.
- *
- * The Strophe.Request class is used internally to encapsulate BOSH request
- * information. It is not meant to be used from user's code.
- */
-
-/** PrivateConstructor: Strophe.Request
- * Create and initialize a new Strophe.Request object.
- *
- * Parameters:
- * (XMLElement) elem - The XML data to be sent in the request.
- * (Function) func - The function that will be called when the
- * XMLHttpRequest readyState changes.
- * (Integer) rid - The BOSH rid attribute associated with this request.
- * (Integer) sends - The number of times this same request has been
- * sent.
- */
-Strophe.Request = function (elem, func, rid, sends)
-{
- this.id = ++Strophe._requestId;
- this.xmlData = elem;
- this.data = Strophe.serialize(elem);
- // save original function in case we need to make a new request
- // from this one.
- this.origFunc = func;
- this.func = func;
- this.rid = rid;
- this.date = NaN;
- this.sends = sends || 0;
- this.abort = false;
- this.dead = null;
- this.age = function () {
- if (!this.date) { return 0; }
- var now = new Date();
- return (now - this.date) / 1000;
- };
- this.timeDead = function () {
- if (!this.dead) { return 0; }
- var now = new Date();
- return (now - this.dead) / 1000;
- };
- this.xhr = this._newXHR();
-};
-
-Strophe.Request.prototype = {
- /** PrivateFunction: getResponse
- * Get a response from the underlying XMLHttpRequest.
- *
- * This function attempts to get a response from the request and checks
- * for errors.
- *
- * Throws:
- * "parsererror" - A parser error occured.
- *
- * 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) {
- Strophe.error("invalid response received");
- Strophe.error("responseText: " + this.xhr.responseText);
- Strophe.error("responseXML: " +
- Strophe.serialize(this.xhr.responseXML));
- }
-
- 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");
- }
- } 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.Connection
* XMPP Connection manager.
*
* This class is the main part of Strophe. It manages a BOSH connection
* to an XMPP server and dispatches events to the user callbacks as
- * data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, and legacy
- * authentication.
+ * data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1
+ * and legacy authentication.
*
* After creating a Strophe.Connection object, the user will typically
* call connect() with a user supplied callback to handle connection level
@@ -2161,31 +1961,75 @@ Strophe.Request.prototype = {
/** Constructor: Strophe.Connection
* Create and initialize a Strophe.Connection object.
*
+ * The transport-protocol for this connection will be chosen automatically
+ * based on the given service parameter. URLs starting with "ws://" or
+ * "wss://" will use WebSockets, URLs starting with "http://", "https://"
+ * or without a protocol will use BOSH.
+ *
+ * To make Strophe connect to the current host you can leave out the protocol
+ * and host part and just pass the path, e.g.
+ *
+ * > var conn = new Strophe.Connection("/http-bind/");
+ *
+ * WebSocket options:
+ *
+ * If you want to connect to the current host with a WebSocket connection you
+ * can tell Strophe to use WebSockets through a "protocol" attribute in the
+ * optional options parameter. Valid values are "ws" for WebSocket and "wss"
+ * for Secure WebSocket.
+ * So to connect to "wss://CURRENT_HOSTNAME/xmpp-websocket" you would call
+ *
+ * > var conn = new Strophe.Connection("/xmpp-websocket/", {protocol: "wss"});
+ *
+ * Note that relative URLs _NOT_ starting with a "/" will also include the path
+ * of the current site.
+ *
+ * Also because downgrading security is not permitted by browsers, when using
+ * relative URLs both BOSH and WebSocket connections will use their secure
+ * variants if the current connection to the site is also secure (https).
+ *
+ * BOSH options:
+ *
+ * by adding "sync" to the options, you can control if requests will
+ * be made synchronously or not. The default behaviour is asynchronous.
+ * If you want to make requests synchronous, make "sync" evaluate to true:
+ * > var conn = new Strophe.Connection("/http-bind/", {sync: true});
+ * You can also toggle this on an already established connection:
+ * > conn.options.sync = true;
+ *
+ *
* Parameters:
- * (String) service - The BOSH service URL.
+ * (String) service - The BOSH or WebSocket service URL.
+ * (Object) options - A hash of configuration options
*
* Returns:
* A new Strophe.Connection object.
*/
-Strophe.Connection = function (service)
+Strophe.Connection = function (service, options)
{
- /* The path to the httpbind service. */
+ // The service URL
this.service = service;
+
+ // Configuration options
+ this.options = options || {};
+ var proto = this.options.protocol || "";
+
+ // Select protocal based on service or options
+ if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 ||
+ proto.indexOf("ws") === 0) {
+ this._proto = new Strophe.Websocket(this);
+ } else {
+ this._proto = new Strophe.Bosh(this);
+ }
/* The connected JID. */
this.jid = "";
/* the JIDs domain */
this.domain = null;
- /* request id for body tags */
- this.rid = Math.floor(Math.random() * 4294967295);
-
- /* The current session ID. */
- this.sid = null;
- this.streamId = null;
/* stream:features */
this.features = null;
// SASL
- this._sasl_data = [];
+ this._sasl_data = {};
this.do_session = false;
this.do_bind = false;
@@ -2210,14 +2054,8 @@ Strophe.Connection = function (service)
this.paused = false;
- // default BOSH values
- this.hold = 1;
- this.wait = 60;
- this.window = 5;
-
this._data = [];
- this._requests = [];
- this._uniqueId = Math.round(Math.random() * 10000);
+ this._uniqueId = 0;
this._sasl_success_handler = null;
this._sasl_failure_handler = null;
@@ -2232,12 +2070,12 @@ Strophe.Connection = function (service)
// initialize plugins
for (var k in Strophe._connectionPlugins) {
if (Strophe._connectionPlugins.hasOwnProperty(k)) {
- var ptype = Strophe._connectionPlugins[k];
+ var ptype = Strophe._connectionPlugins[k];
// jslint complaints about the below line, but this is fine
- var F = function () {};
+ var F = function () {}; // jshint ignore:line
F.prototype = ptype;
this[k] = new F();
- this[k].init(this);
+ this[k].init(this);
}
}
};
@@ -2251,11 +2089,7 @@ Strophe.Connection.prototype = {
*/
reset: function ()
{
- this.rid = Math.floor(Math.random() * 4294967295);
- jQuery(document).trigger('ridChange', {rid: this.rid});
-
- this.sid = null;
- this.streamId = null;
+ this._proto._reset();
// SASL
this.do_session = false;
@@ -2277,16 +2111,17 @@ Strophe.Connection.prototype = {
this.errors = 0;
this._requests = [];
- this._uniqueId = Math.round(Math.random()*10000);
+ this._uniqueId = 0;
},
/** Function: pause
* Pause the request manager.
*
* This will prevent Strophe from sending any more requests to the
- * server. This is very useful for temporarily pausing while a lot
- * of send() calls are happening quickly. This causes Strophe to
- * send the data in a single request, saving many request trips.
+ * server. This is very useful for temporarily pausing
+ * BOSH-Connections while a lot of send() calls are happening quickly.
+ * This causes Strophe to send the data in a single request, saving
+ * many request trips.
*/
pause: function ()
{
@@ -2345,8 +2180,9 @@ Strophe.Connection.prototype = {
* constants. The error condition will be one of the conditions
* defined in RFC 3920 or the condition 'strophe-parsererror'.
*
- * Please see XEP 124 for a more detailed explanation of the optional
- * parameters below.
+ * The Parameters _wait_, _hold_ and _route_ are optional and only relevant
+ * for BOSH connections. Please see XEP 124 for a more detailed explanation
+ * of the optional parameters.
*
* Parameters:
* (String) jid - The user's JID. This may be a bare JID,
@@ -2360,53 +2196,39 @@ Strophe.Connection.prototype = {
* (Integer) hold - The optional HTTPBIND hold value. This is the
* number of connections the server will hold at one time. This
* should almost always be set to 1 (the default).
- * (String) route
+ * (String) route - The optional route value.
*/
connect: function (jid, pass, callback, wait, hold, route)
{
this.jid = jid;
+ /** Variable: authzid
+ * Authorization identity.
+ */
+ this.authzid = Strophe.getBareJidFromJid(this.jid);
+ /** Variable: authcid
+ * Authentication identity (User name).
+ */
+ this.authcid = Strophe.getNodeFromJid(this.jid);
+ /** Variable: pass
+ * Authentication identity (User password).
+ */
this.pass = pass;
+ /** Variable: servtype
+ * Digest MD5 compatibility.
+ */
+ this.servtype = "xmpp";
this.connect_callback = callback;
this.disconnecting = false;
this.connected = false;
this.authenticated = false;
this.errors = 0;
- this.wait = wait || this.wait;
- this.hold = hold || this.hold;
-
- // parse jid for domain and resource
- this.domain = this.domain || Strophe.getDomainFromJid(this.jid);
-
- // build the body tag
- var body = this._buildBody().attrs({
- to: this.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
- });
- }
+ // parse jid for domain
+ this.domain = Strophe.getDomainFromJid(this.jid);
this._changeConnectStatus(Strophe.Status.CONNECTING, null);
- var _connect_cb = this._connect_callback || this._connect_cb;
- this._connect_callback = null;
-
- this._requests.push(
- new Strophe.Request(body.tree(),
- this._onRequestStateChange.bind(
- this, _connect_cb.bind(this)),
- body.tree().getAttribute("rid")));
- this._throttledRequestHandler();
+ this._proto._connect(wait, hold, route);
},
/** Function: attach
@@ -2435,22 +2257,7 @@ Strophe.Connection.prototype = {
*/
attach: function (jid, sid, rid, callback, wait, hold, wind)
{
- this.jid = jid;
- this.sid = sid;
- this.rid = rid;
-
- this.connect_callback = callback;
-
- this.domain = Strophe.getDomainFromJid(this.jid);
-
- this.authenticated = true;
- this.connected = true;
-
- this.wait = wait || this.wait;
- this.hold = hold || this.hold;
- this.window = wind || this.window;
-
- this._changeConnectStatus(Strophe.Status.ATTACHED, null);
+ this._proto._attach(jid, sid, rid, callback, wait, hold, wind);
},
/** Function: xmlInput
@@ -2462,13 +2269,21 @@ Strophe.Connection.prototype = {
* > (user code)
* > };
*
+ * Due to limitations of current Browsers' XML-Parsers the opening and closing
+ * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here.
+ *
+ * BOSH-Connections will have all stanzas wrapped in a <body> tag. See
+ * <Strophe.Bosh.strip> if you want to strip this tag.
+ *
* Parameters:
* (XMLElement) elem - The XML data received by the connection.
*/
+ /* jshint unused:false */
xmlInput: function (elem)
{
return;
},
+ /* jshint unused:true */
/** Function: xmlOutput
* User overrideable function that receives XML data sent to the
@@ -2479,13 +2294,21 @@ Strophe.Connection.prototype = {
* > (user code)
* > };
*
+ * Due to limitations of current Browsers' XML-Parsers the opening and closing
+ * <stream> tag for WebSocket-Connoctions will be passed as selfclosing here.
+ *
+ * BOSH-Connections will have all stanzas wrapped in a <body> tag. See
+ * <Strophe.Bosh.strip> if you want to strip this tag.
+ *
* Parameters:
* (XMLElement) elem - The XMLdata sent by the connection.
*/
+ /* jshint unused:false */
xmlOutput: function (elem)
{
return;
},
+ /* jshint unused:true */
/** Function: rawInput
* User overrideable function that receives raw data coming into the
@@ -2499,10 +2322,12 @@ Strophe.Connection.prototype = {
* Parameters:
* (String) data - The data received by the connection.
*/
+ /* jshint unused:false */
rawInput: function (data)
{
return;
},
+ /* jshint unused:true */
/** Function: rawOutput
* User overrideable function that receives raw data sent to the
@@ -2516,10 +2341,12 @@ Strophe.Connection.prototype = {
* Parameters:
* (String) data - The data sent by the connection.
*/
+ /* jshint unused:false */
rawOutput: function (data)
{
return;
},
+ /* jshint unused:true */
/** Function: send
* Send a stanza.
@@ -2546,9 +2373,7 @@ Strophe.Connection.prototype = {
this._queueData(elem);
}
- this._throttledRequestHandler();
- clearTimeout(this._idleTimeout);
- this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
+ this._proto._send();
},
/** Function: flush
@@ -2588,54 +2413,54 @@ Strophe.Connection.prototype = {
if (typeof(elem.tree) === "function") {
elem = elem.tree();
}
- var id = elem.getAttribute('id');
+ var id = elem.getAttribute('id');
- // inject id if not found
- if (!id) {
- id = this.getUniqueId("sendIQ");
- elem.setAttribute("id", id);
- }
+ // inject id if not found
+ if (!id) {
+ id = this.getUniqueId("sendIQ");
+ elem.setAttribute("id", id);
+ }
- var handler = this.addHandler(function (stanza) {
- // remove timeout handler if there is one
+ var handler = this.addHandler(function (stanza) {
+ // remove timeout handler if there is one
if (timeoutHandler) {
that.deleteTimedHandler(timeoutHandler);
}
var iqtype = stanza.getAttribute('type');
- if (iqtype == 'result') {
- if (callback) {
+ if (iqtype == 'result') {
+ if (callback) {
callback(stanza);
}
- } else if (iqtype == 'error') {
- if (errback) {
+ } else if (iqtype == 'error') {
+ if (errback) {
errback(stanza);
}
- } else {
+ } else {
throw {
name: "StropheError",
- message: "Got bad IQ type of " + iqtype
+ message: "Got bad IQ type of " + iqtype
};
}
- }, null, 'iq', null, id);
+ }, null, 'iq', null, id);
- // if timeout specified, setup timeout handler.
- if (timeout) {
- timeoutHandler = this.addTimedHandler(timeout, function () {
+ // if timeout specified, setup timeout handler.
+ if (timeout) {
+ timeoutHandler = this.addTimedHandler(timeout, function () {
// get rid of normal handler
that.deleteHandler(handler);
- // call errback on timeout with null stanza
+ // call errback on timeout with null stanza
if (errback) {
- errback(null);
+ errback(null);
}
- return false;
- });
- }
+ return false;
+ });
+ }
- this.send(elem);
+ this.send(elem);
- return id;
+ return id;
},
/** PrivateFunction: _queueData
@@ -2651,7 +2476,7 @@ Strophe.Connection.prototype = {
message: "Cannot queue non-DOMElement."
};
}
-
+
this._data.push(element);
},
@@ -2662,8 +2487,8 @@ Strophe.Connection.prototype = {
{
this._data.push("restart");
- this._throttledRequestHandler();
- clearTimeout(this._idleTimeout);
+ this._proto._sendRestart();
+
this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
},
@@ -2795,10 +2620,18 @@ Strophe.Connection.prototype = {
Strophe.info("Disconnect was called because: " + reason);
if (this.connected) {
+ var pres = false;
+ this.disconnecting = true;
+ if (this.authenticated) {
+ pres = $pres({
+ xmlns: Strophe.NS.CLIENT,
+ type: 'unavailable'
+ });
+ }
// setup timeout handler
this._disconnectTimeout = this._addSysTimedHandler(
3000, this._onDisconnectTimeout.bind(this));
- this._sendTerminate();
+ this._proto._disconnect(pres);
}
},
@@ -2839,326 +2672,6 @@ Strophe.Connection.prototype = {
}
},
- /** 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});
- }
-
- return bodyWrap;
- },
-
- /** 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: _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 req = this._requests[i];
- var reqStatus = -1;
-
- try {
- if (req.xhr.readyState == 4) {
- reqStatus = req.xhr.status;
- }
- } catch (e) {
- Strophe.error("caught an error in _requests[" + i +
- "], reqStatus: " + reqStatus);
- }
-
- if (typeof(reqStatus) == "undefined") {
- reqStatus = -1;
- }
-
- // make sure we limit the number of retries
- if (req.sends > this.maxRetries) {
- this._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 {
- req.xhr.open("POST", this.service, Strophe.ASYNC);
- } catch (e2) {
- Strophe.error("XHR open failed.");
- if (!this.connected) {
- this._changeConnectStatus(Strophe.Status.CONNFAIL,
- "bad-service");
- }
- this.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();
- 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(sendFunc, backoff);
- } else {
- sendFunc();
- }
-
- req.sends++;
-
- if (this.xmlOutput !== Strophe.Connection.prototype.xmlOutput) {
- this.xmlOutput(req.xmlData);
- }
- if (this.rawOutput !== Strophe.Connection.prototype.rawOutput) {
- this.rawOutput(req.data);
- }
- } else {
- Strophe.debug("_processRequest: " +
- (i === 0 ? "first" : "second") +
- " request has readyState of " +
- req.xhr.readyState);
- }
- },
-
- /** 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);
- }
- },
-
- /** 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 == 2){
- jQuery(document).trigger('ridChange', {rid: Number(req.rid)+1});
- }
-
- // request complete
- var reqStatus;
- if (req.xhr.readyState == 4) {
- reqStatus = 0;
- try {
- reqStatus = req.xhr.status;
- } catch (e) {
- // ignore errors from undefined status attribute. works
- // around a browser bug
- }
-
- if (typeof(reqStatus) == "undefined") {
- reqStatus = 0;
- }
-
- if (this.disconnecting) {
- if (reqStatus >= 400) {
- this._hitError(reqStatus);
- return;
- }
- }
-
- var reqIs0 = (this._requests[0] == req);
- var reqIs1 = (this._requests[1] == req);
-
- if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
- // remove from internal queue
- this._removeRequest(req);
- Strophe.debug("request id " +
- req.id +
- " should now be removed");
- }
-
- // request succeeded
- if (reqStatus == 200) {
- // 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);
- }
-
- // call handler
- func(req);
- this.errors = 0;
- } else {
- Strophe.error("request id " +
- req.id + "." +
- req.sends + " error " + reqStatus +
- " happened");
- if (reqStatus === 0 ||
- (reqStatus >= 400 && reqStatus < 600) ||
- reqStatus >= 12000) {
- this._hitError(reqStatus);
- if (reqStatus >= 400 && reqStatus < 500) {
- this._changeConnectStatus(Strophe.Status.DISCONNECTING,
- null);
- this._doDisconnect();
- }
- }
- }
-
- if (!((reqStatus > 0 && reqStatus < 500) ||
- req.sends > 5)) {
- this._throttledRequestHandler();
- }
- }
- },
-
- /** 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._onDisconnectTimeout();
- }
- },
-
/** PrivateFunction: _doDisconnect
* _Private_ function to disconnect.
*
@@ -3167,19 +2680,17 @@ Strophe.Connection.prototype = {
*/
_doDisconnect: function ()
{
+ // Cancel Disconnect Timeout
+ if (this._disconnectTimeout !== null) {
+ this.deleteTimedHandler(this._disconnectTimeout);
+ this._disconnectTimeout = null;
+ }
+
Strophe.info("_doDisconnect was called");
+ this._proto._doDisconnect();
+
this.authenticated = false;
this.disconnecting = false;
- this.sid = null;
- this.streamId = null;
- this.rid = Math.floor(Math.random() * 4294967295);
- jQuery(document).trigger('ridChange', {rid: this.rid});
-
- // tell the parent we disconnected
- if (this.connected) {
- this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
- this.connected = false;
- }
// delete handlers
this.handlers = [];
@@ -3188,6 +2699,10 @@ Strophe.Connection.prototype = {
this.removeHandlers = [];
this.addTimeds = [];
this.addHandlers = [];
+
+ // tell the parent we disconnected
+ this._changeConnectStatus(Strophe.Status.DISCONNECTED, null);
+ this.connected = false;
},
/** PrivateFunction: _dataRecv
@@ -3200,22 +2715,27 @@ Strophe.Connection.prototype = {
*
* Parameters:
* (Strophe.Request) req - The request that has data ready.
+ * (string) req - The stanza a raw string (optiona).
*/
- _dataRecv: function (req)
+ _dataRecv: function (req, raw)
{
- try {
- var elem = req.getResponse();
- } catch (e) {
- if (e != "parsererror") { throw e; }
- this.disconnect("strophe-parsererror");
- }
+ Strophe.info("_dataRecv called");
+ var elem = this._proto._reqToData(req);
if (elem === null) { return; }
if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
- this.xmlInput(elem);
+ 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) {
- this.rawInput(Strophe.serialize(elem));
+ if (raw) {
+ this.rawInput(raw);
+ } else {
+ this.rawInput(Strophe.serialize(elem));
+ }
}
// remove handlers scheduled for deletion
@@ -3234,9 +2754,7 @@ Strophe.Connection.prototype = {
}
// handle graceful disconnect
- if (this.disconnecting && this._requests.length === 0) {
- this.deleteTimedHandler(this._disconnectTimeout);
- this._disconnectTimeout = null;
+ if (this.disconnecting && this._proto._emptyQueue()) {
this._doDisconnect();
return;
}
@@ -3260,7 +2778,7 @@ Strophe.Connection.prototype = {
} else {
this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
}
- this.disconnect();
+ this.disconnect('unknown stream-error');
return;
}
@@ -3285,41 +2803,18 @@ Strophe.Connection.prototype = {
that.handlers.push(hand);
}
} catch(e) {
- //if the handler throws an exception, we consider it as false
+ // if the handler throws an exception, we consider it as false
+ Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message);
}
}
});
},
- /** 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 ()
- {
- Strophe.info("_sendTerminate was called");
- var body = this._buildBody().attrs({type: "terminate"});
-
- if (this.authenticated) {
- body.c('presence', {
- xmlns: Strophe.NS.CLIENT,
- type: 'unavailable'
- });
- }
-
- this.disconnecting = true;
- var req = new Strophe.Request(body.tree(),
- this._onRequestStateChange.bind(
- this, this._dataRecv.bind(this)),
- body.tree().getAttribute("rid"));
-
- this._requests.push(req);
- this._throttledRequestHandler();
- },
+ /** Attribute: mechanisms
+ * SASL Mechanisms available for Conncection.
+ */
+ mechanisms: {},
/** PrivateFunction: _connect_cb
* _Private_ handler for initial connection request.
@@ -3337,59 +2832,41 @@ Strophe.Connection.prototype = {
* Useful for plugins with their own xmpp connect callback (when their)
* want to do something special).
*/
- _connect_cb: function (req, _callback)
+ _connect_cb: function (req, _callback, raw)
{
Strophe.info("_connect_cb was called");
this.connected = true;
- var bodyWrap = req.getResponse();
+
+ var bodyWrap = this._proto._reqToData(req);
if (!bodyWrap) { return; }
if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {
- this.xmlInput(bodyWrap);
+ 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) {
- this.rawInput(Strophe.serialize(bodyWrap));
- }
-
- var typ = bodyWrap.getAttribute("type");
- var cond, conflict;
- if (typ !== null && typ == "terminate") {
- // an error occurred
- cond = bodyWrap.getAttribute("condition");
- conflict = bodyWrap.getElementsByTagName("conflict");
- if (cond !== null) {
- if (cond == "remote-stream-error" && conflict.length > 0) {
- cond = "conflict";
- }
- this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);
+ if (raw) {
+ this.rawInput(raw);
} else {
- this._changeConnectStatus(Strophe.Status.CONNFAIL, "unknown");
+ this.rawInput(Strophe.serialize(bodyWrap));
}
- return;
}
- // 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");
- }
- if (!this.stream_id) {
- this.stream_id = bodyWrap.getAttribute("authid");
+ var conncheck = this._proto._connect_cb(bodyWrap);
+ if (conncheck === Strophe.Status.CONNFAIL) {
+ return;
}
- 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); }
this._authentication.sasl_scram_sha1 = false;
this._authentication.sasl_plain = false;
this._authentication.sasl_digest_md5 = false;
this._authentication.sasl_anonymous = false;
- this._authentication.legacy_auth = false;
+ this._authentication.legacy_auth = false;
// Check for the stream:features tag
var hasFeatures = bodyWrap.getElementsByTagName("stream:features").length > 0;
@@ -3397,45 +2874,28 @@ Strophe.Connection.prototype = {
hasFeatures = bodyWrap.getElementsByTagName("features").length > 0;
}
var mechanisms = bodyWrap.getElementsByTagName("mechanism");
- var i, mech, auth_str, hashed_auth_str,
- found_authentication = false;
- if (hasFeatures && mechanisms.length > 0) {
- var missmatchedmechs = 0;
+ var matched = [];
+ var i, mech, found_authentication = false;
+ if (!hasFeatures) {
+ this._proto._no_auth_received(_callback);
+ return;
+ }
+ if (mechanisms.length > 0) {
for (i = 0; i < mechanisms.length; i++) {
mech = Strophe.getText(mechanisms[i]);
- if (mech == 'SCRAM-SHA-1') {
- this._authentication.sasl_scram_sha1 = true;
- } else if (mech == 'DIGEST-MD5') {
- this._authentication.sasl_digest_md5 = true;
- } else if (mech == 'PLAIN') {
- this._authentication.sasl_plain = true;
- } else if (mech == 'ANONYMOUS') {
- this._authentication.sasl_anonymous = true;
- } else missmatchedmechs++;
+ if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]);
}
-
- this._authentication.legacy_auth =
- bodyWrap.getElementsByTagName("auth").length > 0;
-
- found_authentication =
- this._authentication.legacy_auth ||
- missmatchedmechs < mechanisms.length;
}
+ this._authentication.legacy_auth =
+ bodyWrap.getElementsByTagName("auth").length > 0;
+ found_authentication = this._authentication.legacy_auth ||
+ matched.length > 0;
if (!found_authentication) {
- _callback = _callback || this._connect_cb;
- // we didn't get stream:features yet, so we need wait for it
- // by sending a blank poll request
- var body = this._buildBody();
- this._requests.push(
- new Strophe.Request(body.tree(),
- this._onRequestStateChange.bind(
- this, _callback.bind(this)),
- body.tree().getAttribute("rid")));
- this._throttledRequestHandler();
+ this._proto._no_auth_received(_callback);
return;
}
if (this.do_authentication !== false)
- this.authenticate();
+ this.authenticate(matched);
},
/** Function: authenticate
@@ -3448,313 +2908,97 @@ Strophe.Connection.prototype = {
* the code will fall back to legacy authentication.
*
*/
- authenticate: function ()
- {
- if (Strophe.getNodeFromJid(this.jid) === null &&
- this._authentication.sasl_anonymous) {
- this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
- this._sasl_success_handler = this._addSysHandler(
- this._sasl_success_cb.bind(this), null,
- "success", null, null);
- this._sasl_failure_handler = this._addSysHandler(
- this._sasl_failure_cb.bind(this), null,
- "failure", null, null);
-
- this.send($build("auth", {
- xmlns: Strophe.NS.SASL,
- mechanism: "ANONYMOUS"
- }).tree());
- } else if (Strophe.getNodeFromJid(this.jid) === null) {
- // we don't have a node, which is required for non-anonymous
- // client connections
- this._changeConnectStatus(Strophe.Status.CONNFAIL,
- 'x-strophe-bad-non-anon-jid');
- this.disconnect();
- } else if (this._authentication.sasl_scram_sha1) {
- var cnonce = MD5.hexdigest(Math.random() * 1234567890);
-
- var auth_str = "n=" + Strophe.getNodeFromJid(this.jid);
- auth_str += ",r=";
- auth_str += cnonce;
-
- this._sasl_data["cnonce"] = cnonce;
- this._sasl_data["client-first-message-bare"] = auth_str;
-
- auth_str = "n,," + auth_str;
-
- this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
- this._sasl_challenge_handler = this._addSysHandler(
- this._sasl_scram_challenge_cb.bind(this), null,
- "challenge", null, null);
- this._sasl_failure_handler = this._addSysHandler(
- this._sasl_failure_cb.bind(this), null,
- "failure", null, null);
-
- this.send($build("auth", {
- xmlns: Strophe.NS.SASL,
- mechanism: "SCRAM-SHA-1"
- }).t(Base64.encode(auth_str)).tree());
- } else if (this._authentication.sasl_digest_md5) {
- this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
- this._sasl_challenge_handler = this._addSysHandler(
- this._sasl_digest_challenge1_cb.bind(this), null,
- "challenge", null, null);
- this._sasl_failure_handler = this._addSysHandler(
- this._sasl_failure_cb.bind(this), null,
- "failure", null, null);
-
- this.send($build("auth", {
- xmlns: Strophe.NS.SASL,
- mechanism: "DIGEST-MD5"
- }).tree());
- } else if (this._authentication.sasl_plain) {
- // Build the plain auth string (barejid null
- // username null password) and base 64 encoded.
- auth_str = Strophe.getBareJidFromJid(this.jid);
- auth_str = auth_str + "\u0000";
- auth_str = auth_str + Strophe.getNodeFromJid(this.jid);
- auth_str = auth_str + "\u0000";
- auth_str = auth_str + this.pass;
-
- this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
- this._sasl_success_handler = this._addSysHandler(
- this._sasl_success_cb.bind(this), null,
- "success", null, null);
- this._sasl_failure_handler = this._addSysHandler(
- this._sasl_failure_cb.bind(this), null,
- "failure", null, null);
-
- hashed_auth_str = Base64.encode(auth_str);
- this.send($build("auth", {
- xmlns: Strophe.NS.SASL,
- mechanism: "PLAIN"
- }).t(hashed_auth_str).tree());
- } else {
- this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
- this._addSysHandler(this._auth1_cb.bind(this), null, null,
- null, "_auth_1");
-
- this.send($iq({
- type: "get",
- to: this.domain,
- id: "_auth_1"
- }).c("query", {
- xmlns: Strophe.NS.AUTH
- }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
- }
- },
-
- /** PrivateFunction: _sasl_digest_challenge1_cb
- * _Private_ handler for DIGEST-MD5 SASL authentication.
- *
- * Parameters:
- * (XMLElement) elem - The challenge stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- _sasl_digest_challenge1_cb: function (elem)
+ authenticate: function (matched)
{
- var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
-
- var challenge = Base64.decode(Strophe.getText(elem));
- var cnonce = MD5.hexdigest("" + (Math.random() * 1234567890));
- var realm = "";
- var host = null;
- var nonce = "";
- var qop = "";
- var matches;
-
- // remove unneeded handlers
- this.deleteHandler(this._sasl_failure_handler);
-
- while (challenge.match(attribMatch)) {
- matches = challenge.match(attribMatch);
- challenge = challenge.replace(matches[0], "");
- matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
- switch (matches[1]) {
- case "realm":
- realm = matches[2];
- break;
- case "nonce":
- nonce = matches[2];
- break;
- case "qop":
- qop = matches[2];
- break;
- case "host":
- host = matches[2];
- break;
- }
+ var i;
+ // Sorting matched mechanisms according to priority.
+ for (i = 0; i < matched.length - 1; ++i) {
+ var higher = i;
+ for (var j = i + 1; j < matched.length; ++j) {
+ if (matched[j].prototype.priority > matched[higher].prototype.priority) {
+ higher = j;
+ }
}
-
- var digest_uri = "xmpp/" + this.domain;
- if (host !== null) {
- digest_uri = digest_uri + "/" + host;
+ if (higher != i) {
+ var swap = matched[i];
+ matched[i] = matched[higher];
+ matched[higher] = swap;
}
+ }
- var A1 = MD5.hash(Strophe.getNodeFromJid(this.jid) +
- ":" + realm + ":" + this.pass) +
- ":" + nonce + ":" + cnonce;
- var A2 = 'AUTHENTICATE:' + digest_uri;
-
- var responseText = "";
- responseText += 'username=' +
- this._quote(Strophe.getNodeFromJid(this.jid)) + ',';
- responseText += 'realm=' + this._quote(realm) + ',';
- responseText += 'nonce=' + this._quote(nonce) + ',';
- responseText += 'cnonce=' + this._quote(cnonce) + ',';
- responseText += 'nc="00000001",';
- responseText += 'qop="auth",';
- responseText += 'digest-uri=' + this._quote(digest_uri) + ',';
- responseText += 'response=' + this._quote(
- MD5.hexdigest(MD5.hexdigest(A1) + ":" +
- nonce + ":00000001:" +
- cnonce + ":auth:" +
- MD5.hexdigest(A2))) + ',';
- responseText += 'charset="utf-8"';
-
- this._sasl_challenge_handler = this._addSysHandler(
- this._sasl_digest_challenge2_cb.bind(this), null,
- "challenge", null, null);
- this._sasl_success_handler = this._addSysHandler(
- this._sasl_success_cb.bind(this), null,
- "success", null, null);
- this._sasl_failure_handler = this._addSysHandler(
- this._sasl_failure_cb.bind(this), null,
- "failure", null, null);
-
- this.send($build('response', {
- xmlns: Strophe.NS.SASL
- }).t(Base64.encode(responseText)).tree());
-
- return false;
- },
-
- /** PrivateFunction: _quote
- * _Private_ utility function to backslash escape and quote strings.
- *
- * Parameters:
- * (String) str - The string to be quoted.
- *
- * Returns:
- * quoted string
- */
- _quote: function (str)
- {
- return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
- //" end string workaround for emacs
- },
-
-
- /** PrivateFunction: _sasl_digest_challenge2_cb
- * _Private_ handler for second step of DIGEST-MD5 SASL authentication.
- *
- * Parameters:
- * (XMLElement) elem - The challenge stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- _sasl_digest_challenge2_cb: function (elem)
- {
- // remove unneeded handlers
- this.deleteHandler(this._sasl_success_handler);
- this.deleteHandler(this._sasl_failure_handler);
+ // run each mechanism
+ var mechanism_found = false;
+ for (i = 0; i < matched.length; ++i) {
+ if (!matched[i].test(this)) continue;
this._sasl_success_handler = this._addSysHandler(
- this._sasl_success_cb.bind(this), null,
- "success", null, null);
+ this._sasl_success_cb.bind(this), null,
+ "success", null, null);
this._sasl_failure_handler = this._addSysHandler(
- this._sasl_failure_cb.bind(this), null,
- "failure", null, null);
- this.send($build('response', {xmlns: Strophe.NS.SASL}).tree());
- return false;
- },
+ this._sasl_failure_cb.bind(this), null,
+ "failure", null, null);
+ this._sasl_challenge_handler = this._addSysHandler(
+ this._sasl_challenge_cb.bind(this), null,
+ "challenge", null, null);
- /** PrivateFunction: _sasl_scram_challenge_cb
- * _Private_ handler for SCRAM-SHA-1 SASL authentication.
- *
- * Parameters:
- * (XMLElement) elem - The challenge stanza.
- *
- * Returns:
- * false to remove the handler.
- */
- _sasl_scram_challenge_cb: function (elem)
- {
- var nonce, salt, iter, Hi, U, U_old;
- var clientKey, serverKey, clientSignature;
- var responseText = "c=biws,";
- var challenge = Base64.decode(Strophe.getText(elem));
- var authMessage = this._sasl_data["client-first-message-bare"] + "," +
- challenge + ",";
- var cnonce = this._sasl_data["cnonce"]
- var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
-
- // remove unneeded handlers
- this.deleteHandler(this._sasl_failure_handler);
+ this._sasl_mechanism = new matched[i]();
+ this._sasl_mechanism.onStart(this);
- while (challenge.match(attribMatch)) {
- matches = challenge.match(attribMatch);
- challenge = challenge.replace(matches[0], "");
- switch (matches[1]) {
- case "r":
- nonce = matches[2];
- break;
- case "s":
- salt = matches[2];
- break;
- case "i":
- iter = matches[2];
- break;
- }
- }
+ var request_auth_exchange = $build("auth", {
+ xmlns: Strophe.NS.SASL,
+ mechanism: this._sasl_mechanism.name
+ });
- if (!(nonce.substr(0, cnonce.length) === cnonce)) {
- this._sasl_data = [];
- return this._sasl_failure_cb(null);
+ if (this._sasl_mechanism.isClientFirst) {
+ var response = this._sasl_mechanism.onChallenge(this, null);
+ request_auth_exchange.t(Base64.encode(response));
}
- responseText += "r=" + nonce;
- authMessage += responseText;
-
- salt = Base64.decode(salt);
- salt += "\0\0\0\1";
+ this.send(request_auth_exchange.tree());
- Hi = U_old = core_hmac_sha1(this.pass, salt);
- for (i = 1; i < iter; i++) {
- U = core_hmac_sha1(this.pass, binb2str(U_old));
- for (k = 0; k < 5; k++) {
- Hi[k] ^= U[k];
- }
- U_old = U;
- }
- Hi = binb2str(Hi);
+ mechanism_found = true;
+ break;
+ }
- clientKey = core_hmac_sha1(Hi, "Client Key");
- serverKey = str_hmac_sha1(Hi, "Server Key");
- clientSignature = core_hmac_sha1(str_sha1(binb2str(clientKey)), authMessage);
- this._sasl_data["server-signature"] = b64_hmac_sha1(serverKey, authMessage);
+ if (!mechanism_found) {
+ // if none of the mechanism worked
+ if (Strophe.getNodeFromJid(this.jid) === null) {
+ // we don't have a node, which is required for non-anonymous
+ // client connections
+ this._changeConnectStatus(Strophe.Status.CONNFAIL,
+ 'x-strophe-bad-non-anon-jid');
+ this.disconnect('x-strophe-bad-non-anon-jid');
+ } else {
+ // fall back to legacy authentication
+ this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);
+ this._addSysHandler(this._auth1_cb.bind(this), null, null,
+ null, "_auth_1");
- for (k = 0; k < 5; k++) {
- clientKey[k] ^= clientSignature[k];
+ this.send($iq({
+ type: "get",
+ to: this.domain,
+ id: "_auth_1"
+ }).c("query", {
+ xmlns: Strophe.NS.AUTH
+ }).c("username", {}).t(Strophe.getNodeFromJid(this.jid)).tree());
}
+ }
- responseText += ",p=" + Base64.encode(binb2str(clientKey));
+ },
- this._sasl_success_handler = this._addSysHandler(
- this._sasl_success_cb.bind(this), null,
- "success", null, null);
- this._sasl_failure_handler = this._addSysHandler(
- this._sasl_failure_cb.bind(this), null,
- "failure", null, null);
+ _sasl_challenge_cb: function(elem) {
+ var challenge = Base64.decode(Strophe.getText(elem));
+ var response = this._sasl_mechanism.onChallenge(this, challenge);
- this.send($build('response', {
- xmlns: Strophe.NS.SASL
- }).t(Base64.encode(responseText)).tree());
+ var stanza = $build('response', {
+ xmlns: Strophe.NS.SASL
+ });
+ if (response !== "") {
+ stanza.t(Base64.encode(response));
+ }
+ this.send(stanza.tree());
- return false;
+ return true;
},
/** PrivateFunction: _auth1_cb
@@ -3771,6 +3015,7 @@ Strophe.Connection.prototype = {
* Returns:
* false to remove the handler.
*/
+ /* jshint unused:false */
_auth1_cb: function (elem)
{
// build plaintext auth iq
@@ -3795,6 +3040,7 @@ Strophe.Connection.prototype = {
return false;
},
+ /* jshint unused:true */
/** PrivateFunction: _sasl_success_cb
* _Private_ handler for succesful SASL authentication.
@@ -3811,25 +3057,29 @@ Strophe.Connection.prototype = {
var serverSignature;
var success = Base64.decode(Strophe.getText(elem));
var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
- matches = success.match(attribMatch);
+ var matches = success.match(attribMatch);
if (matches[1] == "v") {
serverSignature = matches[2];
}
- if (serverSignature != this._sasl_data["server-signature"]) {
- // remove old handlers
- this.deleteHandler(this._sasl_failure_handler);
- this._sasl_failure_handler = null;
- if (this._sasl_challenge_handler) {
- this.deleteHandler(this._sasl_challenge_handler);
- this._sasl_challenge_handler = null;
- }
-
- this._sasl_data = [];
- return this._sasl_failure_cb(null);
- }
- }
-
- Strophe.info("SASL authentication succeeded.");
+
+ if (serverSignature != this._sasl_data["server-signature"]) {
+ // remove old handlers
+ this.deleteHandler(this._sasl_failure_handler);
+ this._sasl_failure_handler = null;
+ if (this._sasl_challenge_handler) {
+ this.deleteHandler(this._sasl_challenge_handler);
+ this._sasl_challenge_handler = null;
+ }
+
+ this._sasl_data = {};
+ return this._sasl_failure_cb(null);
+ }
+ }
+
+ Strophe.info("SASL authentication succeeded.");
+
+ if(this._sasl_mechanism)
+ this._sasl_mechanism.onSuccess();
// remove old handlers
this.deleteHandler(this._sasl_failure_handler);
@@ -3910,10 +3160,10 @@ Strophe.Connection.prototype = {
{
if (elem.getAttribute("type") == "error") {
Strophe.info("SASL binding failed.");
- var conflict = elem.getElementsByTagName("conflict"), condition;
- if (conflict.length > 0) {
- condition = 'conflict';
- }
+ var conflict = elem.getElementsByTagName("conflict"), condition;
+ if (conflict.length > 0) {
+ condition = 'conflict';
+ }
this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition);
return false;
}
@@ -3981,6 +3231,7 @@ Strophe.Connection.prototype = {
* Returns:
* false to remove the handler.
*/
+ /* jshint unused:false */
_sasl_failure_cb: function (elem)
{
// delete unneeded handlers
@@ -3993,9 +3244,12 @@ Strophe.Connection.prototype = {
this._sasl_challenge_handler = null;
}
+ if(this._sasl_mechanism)
+ this._sasl_mechanism.onFailure();
this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
return false;
},
+ /* jshint unused:true */
/** PrivateFunction: _auth2_cb
* _Private_ handler to finish legacy authentication.
@@ -4016,7 +3270,7 @@ Strophe.Connection.prototype = {
this._changeConnectStatus(Strophe.Status.CONNECTED, null);
} else if (elem.getAttribute("type") == "error") {
this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);
- this.disconnect();
+ this.disconnect('authentication failed');
}
return false;
@@ -4076,16 +3330,7 @@ Strophe.Connection.prototype = {
{
Strophe.info("_onDisconnectTimeout was called");
- // cancel all remaining requests and clear the queue
- 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 () {};
- }
+ this._proto._onDisconnectTimeout();
// actually disconnect
this._doDisconnect();
@@ -4137,46 +3382,863 @@ Strophe.Connection.prototype = {
}
this.timedHandlers = newList;
- var body, time_elapsed;
-//console.log('Authenticated: '+this.authenticated+', req length: '+this._requests.length+', data length: '+
-// this._data.length+', dis: '+!this.disconnecting);
+ clearTimeout(this._idleTimeout);
+
+ this._proto._onIdle();
+
+ // reactivate the timer only if connected
+ if (this.connected) {
+ this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
+ }
+ }
+};
+
+if (callback) {
+ callback(Strophe, $build, $msg, $iq, $pres);
+}
+
+/** Class: Strophe.SASLMechanism
+ *
+ * encapsulates SASL authentication mechanisms.
+ *
+ * User code may override the priority for each mechanism or disable it completely.
+ * See <priority> for information about changing priority and <test> for informatian on
+ * how to disable a mechanism.
+ *
+ * By default, all mechanisms are enabled and the priorities are
+ *
+ * SCRAM-SHA1 - 40
+ * DIGEST-MD5 - 30
+ * Plain - 20
+ */
+
+/**
+ * PrivateConstructor: Strophe.SASLMechanism
+ * SASL auth mechanism abstraction.
+ *
+ * Parameters:
+ * (String) name - SASL Mechanism name.
+ * (Boolean) isClientFirst - If client should send response first without challenge.
+ * (Number) priority - Priority.
+ *
+ * Returns:
+ * A new Strophe.SASLMechanism object.
+ */
+Strophe.SASLMechanism = function(name, isClientFirst, priority) {
+ /** PrivateVariable: name
+ * Mechanism name.
+ */
+ this.name = name;
+ /** PrivateVariable: isClientFirst
+ * If client sends response without initial server challenge.
+ */
+ this.isClientFirst = isClientFirst;
+ /** Variable: priority
+ * Determines which <SASLMechanism> is chosen for authentication (Higher is better).
+ * Users may override this to prioritize mechanisms differently.
+ *
+ * In the default configuration the priorities are
+ *
+ * SCRAM-SHA1 - 40
+ * DIGEST-MD5 - 30
+ * Plain - 20
+ *
+ * Example: (This will cause Strophe to choose the mechanism that the server sent first)
+ *
+ * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority;
+ *
+ * See <SASL mechanisms> for a list of available mechanisms.
+ *
+ */
+ this.priority = priority;
+};
+
+Strophe.SASLMechanism.prototype = {
+ /**
+ * Function: test
+ * Checks if mechanism able to run.
+ * To disable a mechanism, make this return false;
+ *
+ * To disable plain authentication run
+ * > Strophe.SASLPlain.test = function() {
+ * > return false;
+ * > }
+ *
+ * See <SASL mechanisms> for a list of available mechanisms.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - Target Connection.
+ *
+ * Returns:
+ * (Boolean) If mechanism was able to run.
+ */
+ /* jshint unused:false */
+ test: function(connection) {
+ return true;
+ },
+ /* jshint unused:true */
+
+ /** PrivateFunction: onStart
+ * Called before starting mechanism on some connection.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - Target Connection.
+ */
+ onStart: function(connection)
+ {
+ this._connection = connection;
+ },
+
+ /** PrivateFunction: onChallenge
+ * Called by protocol implementation on incoming challenge. If client is
+ * first (isClientFirst == true) challenge will be null on the first call.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - Target Connection.
+ * (String) challenge - current challenge to handle.
+ *
+ * Returns:
+ * (String) Mechanism response.
+ */
+ /* jshint unused:false */
+ onChallenge: function(connection, challenge) {
+ throw new Error("You should implement challenge handling!");
+ },
+ /* jshint unused:true */
+
+ /** PrivateFunction: onFailure
+ * Protocol informs mechanism implementation about SASL failure.
+ */
+ onFailure: function() {
+ this._connection = null;
+ },
+
+ /** PrivateFunction: onSuccess
+ * Protocol informs mechanism implementation about SASL success.
+ */
+ onSuccess: function() {
+ this._connection = null;
+ }
+};
+
+ /** Constants: SASL mechanisms
+ * Available authentication mechanisms
+ *
+ * Strophe.SASLAnonymous - SASL Anonymous authentication.
+ * Strophe.SASLPlain - SASL Plain authentication.
+ * Strophe.SASLMD5 - SASL Digest-MD5 authentication
+ * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication
+ */
+
+// Building SASL callbacks
+
+/** PrivateConstructor: SASLAnonymous
+ * SASL Anonymous authentication.
+ */
+Strophe.SASLAnonymous = function() {};
+
+Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism("ANONYMOUS", false, 10);
+
+Strophe.SASLAnonymous.test = function(connection) {
+ return connection.authcid === null;
+};
+
+Strophe.Connection.prototype.mechanisms[Strophe.SASLAnonymous.prototype.name] = Strophe.SASLAnonymous;
+
+/** PrivateConstructor: SASLPlain
+ * SASL Plain authentication.
+ */
+Strophe.SASLPlain = function() {};
+
+Strophe.SASLPlain.prototype = new Strophe.SASLMechanism("PLAIN", true, 20);
+
+Strophe.SASLPlain.test = function(connection) {
+ return connection.authcid !== null;
+};
+
+Strophe.SASLPlain.prototype.onChallenge = function(connection) {
+ var auth_str = connection.authzid;
+ auth_str = auth_str + "\u0000";
+ auth_str = auth_str + connection.authcid;
+ auth_str = auth_str + "\u0000";
+ auth_str = auth_str + connection.pass;
+ return auth_str;
+};
+
+Strophe.Connection.prototype.mechanisms[Strophe.SASLPlain.prototype.name] = Strophe.SASLPlain;
+
+/** PrivateConstructor: SASLSHA1
+ * SASL SCRAM SHA 1 authentication.
+ */
+Strophe.SASLSHA1 = function() {};
+
+/* TEST:
+ * This is a simple example of a SCRAM-SHA-1 authentication exchange
+ * when the client doesn't support channel bindings (username 'user' and
+ * password 'pencil' are used):
+ *
+ * C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ * S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,
+ * i=4096
+ * C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,
+ * p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
+ * S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=
+ *
+ */
+
+Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism("SCRAM-SHA-1", true, 40);
+
+Strophe.SASLSHA1.test = function(connection) {
+ return connection.authcid !== null;
+};
+
+Strophe.SASLSHA1.prototype.onChallenge = function(connection, challenge, test_cnonce) {
+ var cnonce = test_cnonce || MD5.hexdigest(Math.random() * 1234567890);
+
+ var auth_str = "n=" + connection.authcid;
+ auth_str += ",r=";
+ auth_str += cnonce;
+
+ connection._sasl_data.cnonce = cnonce;
+ connection._sasl_data["client-first-message-bare"] = auth_str;
+
+ auth_str = "n,," + auth_str;
+
+ this.onChallenge = function (connection, challenge)
+ {
+ var nonce, salt, iter, Hi, U, U_old, i, k;
+ var clientKey, serverKey, clientSignature;
+ var responseText = "c=biws,";
+ var authMessage = connection._sasl_data["client-first-message-bare"] + "," +
+ challenge + ",";
+ var cnonce = connection._sasl_data.cnonce;
+ var attribMatch = /([a-z]+)=([^,]+)(,|$)/;
+
+ while (challenge.match(attribMatch)) {
+ var matches = challenge.match(attribMatch);
+ challenge = challenge.replace(matches[0], "");
+ switch (matches[1]) {
+ case "r":
+ nonce = matches[2];
+ break;
+ case "s":
+ salt = matches[2];
+ break;
+ case "i":
+ iter = matches[2];
+ break;
+ }
+ }
+
+ if (nonce.substr(0, cnonce.length) !== cnonce) {
+ connection._sasl_data = {};
+ return connection._sasl_failure_cb();
+ }
+
+ responseText += "r=" + nonce;
+ authMessage += responseText;
+
+ salt = Base64.decode(salt);
+ salt += "\x00\x00\x00\x01";
+
+ Hi = U_old = core_hmac_sha1(connection.pass, salt);
+ for (i = 1; i < iter; i++) {
+ U = core_hmac_sha1(connection.pass, binb2str(U_old));
+ for (k = 0; k < 5; k++) {
+ Hi[k] ^= U[k];
+ }
+ U_old = U;
+ }
+ Hi = binb2str(Hi);
+
+ clientKey = core_hmac_sha1(Hi, "Client Key");
+ serverKey = str_hmac_sha1(Hi, "Server Key");
+ clientSignature = core_hmac_sha1(str_sha1(binb2str(clientKey)), authMessage);
+ connection._sasl_data["server-signature"] = b64_hmac_sha1(serverKey, authMessage);
+
+ for (k = 0; k < 5; k++) {
+ clientKey[k] ^= clientSignature[k];
+ }
+
+ responseText += ",p=" + Base64.encode(binb2str(clientKey));
+
+ return responseText;
+ }.bind(this);
+
+ return auth_str;
+};
+
+Strophe.Connection.prototype.mechanisms[Strophe.SASLSHA1.prototype.name] = Strophe.SASLSHA1;
+
+/** PrivateConstructor: SASLMD5
+ * SASL DIGEST MD5 authentication.
+ */
+Strophe.SASLMD5 = function() {};
+
+Strophe.SASLMD5.prototype = new Strophe.SASLMechanism("DIGEST-MD5", false, 30);
+
+Strophe.SASLMD5.test = function(connection) {
+ return connection.authcid !== null;
+};
+
+/** PrivateFunction: _quote
+ * _Private_ utility function to backslash escape and quote strings.
+ *
+ * Parameters:
+ * (String) str - The string to be quoted.
+ *
+ * Returns:
+ * quoted string
+ */
+Strophe.SASLMD5.prototype._quote = function (str)
+ {
+ return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
+ //" end string workaround for emacs
+ };
+
+
+Strophe.SASLMD5.prototype.onChallenge = function(connection, challenge, test_cnonce) {
+ var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
+ var cnonce = test_cnonce || MD5.hexdigest("" + (Math.random() * 1234567890));
+ var realm = "";
+ var host = null;
+ var nonce = "";
+ var qop = "";
+ var matches;
+
+ while (challenge.match(attribMatch)) {
+ matches = challenge.match(attribMatch);
+ challenge = challenge.replace(matches[0], "");
+ matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
+ switch (matches[1]) {
+ case "realm":
+ realm = matches[2];
+ break;
+ case "nonce":
+ nonce = matches[2];
+ break;
+ case "qop":
+ qop = matches[2];
+ break;
+ case "host":
+ host = matches[2];
+ break;
+ }
+ }
+
+ var digest_uri = connection.servtype + "/" + connection.domain;
+ if (host !== null) {
+ digest_uri = digest_uri + "/" + host;
+ }
+
+ var A1 = MD5.hash(connection.authcid +
+ ":" + realm + ":" + this._connection.pass) +
+ ":" + nonce + ":" + cnonce;
+ var A2 = 'AUTHENTICATE:' + digest_uri;
+
+ var responseText = "";
+ responseText += 'charset=utf-8,';
+ responseText += 'username=' +
+ this._quote(connection.authcid) + ',';
+ responseText += 'realm=' + this._quote(realm) + ',';
+ responseText += 'nonce=' + this._quote(nonce) + ',';
+ responseText += 'nc=00000001,';
+ responseText += 'cnonce=' + this._quote(cnonce) + ',';
+ responseText += 'digest-uri=' + this._quote(digest_uri) + ',';
+ responseText += 'response=' + MD5.hexdigest(MD5.hexdigest(A1) + ":" +
+ nonce + ":00000001:" +
+ cnonce + ":auth:" +
+ MD5.hexdigest(A2)) + ",";
+ responseText += 'qop=auth';
+
+ this.onChallenge = function ()
+ {
+ return "";
+ }.bind(this);
+
+ return responseText;
+};
+
+Strophe.Connection.prototype.mechanisms[Strophe.SASLMD5.prototype.name] = Strophe.SASLMD5;
+
+})(function () {
+ window.Strophe = arguments[0];
+ window.$build = arguments[1];
+ window.$msg = arguments[2];
+ window.$iq = arguments[3];
+ window.$pres = arguments[4];
+});
+
+/*
+ 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 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.
+ */
+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.
+ *
+ * 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) {
+ Strophe.error("invalid response received");
+ Strophe.error("responseText: " + this.xhr.responseText);
+ Strophe.error("responseXML: " +
+ Strophe.serialize(this.xhr.responseXML));
+ }
+
+ 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");
+ }
+ } 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._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});
+ }
+
+ 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;
+
+ jQuery(document).trigger('ridChange', {rid: 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;
+
+ // 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: _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
+ Strophe.error("BOSH-Connection failed: " + cond);
+ cond = bodyWrap.getAttribute("condition");
+ 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();
+ 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); }
+ },
+
+ /** 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);
+
+ jQuery(document).trigger('ridChange', {rid: 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: _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._onDisconnectTimeout();
+ }
+ },
+
+ /** PrivateFunction: _no_auth_received
+ *
+ * Called on stream start/restart when no stream:features
+ * has been received and sends a blank poll request.
+ */
+ _no_auth_received: function (_callback)
+ {
+ if (_callback) {
+ _callback = _callback.bind(this._conn);
+ } else {
+ _callback = this._conn._connect_cb.bind(this._conn);
+ }
+ var body = this._buildBody();
+ this._requests.push(
+ new Strophe.Request(body.tree(),
+ this._onRequestStateChange.bind(
+ this, _callback.bind(this._conn)),
+ body.tree().getAttribute("rid")));
+ this._throttledRequestHandler();
+ },
+
+ /** PrivateFunction: _onDisconnectTimeout
+ * _Private_ timeout handler for handling non-graceful disconnection.
+ *
+ * Cancels all remaining Requests and clears the queue.
+ */
+ _onDisconnectTimeout: function ()
+ {
+ 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.authenticated && this._requests.length === 0 &&
- this._data.length === 0 && !this.disconnecting) {
+ if (this._conn.authenticated && this._requests.length === 0 &&
+ data.length === 0 && !this._conn.disconnecting) {
Strophe.info("no requests during idle cycle, sending " +
"blank request");
- this._data.push(null);
+ data.push(null);
}
- if (this._requests.length < 2 && this._data.length > 0 &&
- !this.paused) {
- body = this._buildBody();
- for (i = 0; i < this._data.length; i++) {
- if (this._data[i] !== null) {
- if (this._data[i] === "restart") {
+ if (this._requests.length < 2 && data.length > 0 &&
+ !this._conn.paused) {
+ var body = this._buildBody();
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] !== null) {
+ if (data[i] === "restart") {
body.attrs({
- to: this.domain,
+ to: this._conn.domain,
"xml:lang": "en",
"xmpp:restart": "true",
"xmlns:xmpp": Strophe.NS.BOSH
});
} else {
- body.cnode(this._data[i]).up();
+ body.cnode(data[i]).up();
}
}
}
- delete this._data;
- this._data = [];
+ delete this._conn._data;
+ this._conn._data = [];
this._requests.push(
new Strophe.Request(body.tree(),
this._onRequestStateChange.bind(
- this, this._dataRecv.bind(this)),
+ this, this._conn._dataRecv.bind(this._conn)),
body.tree().getAttribute("rid")));
this._processRequest(this._requests.length - 1);
}
if (this._requests.length > 0) {
- time_elapsed = this._requests[0].age();
+ 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)) {
@@ -4192,24 +4254,899 @@ Strophe.Connection.prototype = {
this._throttledRequestHandler();
}
}
+ },
- clearTimeout(this._idleTimeout);
+ /** 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);
- // reactivate the timer only if connected
- if (this.connected) {
- this._idleTimeout = setTimeout(this._onIdle.bind(this), 100);
+ if (req.abort) {
+ req.abort = false;
+ return;
+ }
+
+ if(req.xhr.readyState == 2){
+ jQuery(document).trigger('ridChange', {rid: Number(req.rid)+1});
+ }
+
+ // request complete
+ var reqStatus;
+ if (req.xhr.readyState == 4) {
+ reqStatus = 0;
+ try {
+ reqStatus = req.xhr.status;
+ } catch (e) {
+ // ignore errors from undefined status attribute. works
+ // around a browser bug
+ }
+
+ if (typeof(reqStatus) == "undefined") {
+ reqStatus = 0;
+ }
+
+ if (this.disconnecting) {
+ if (reqStatus >= 400) {
+ this._hitError(reqStatus);
+ return;
+ }
+ }
+
+ var reqIs0 = (this._requests[0] == req);
+ var reqIs1 = (this._requests[1] == req);
+
+ if ((reqStatus > 0 && reqStatus < 500) || req.sends > 5) {
+ // remove from internal queue
+ this._removeRequest(req);
+ Strophe.debug("request id " +
+ req.id +
+ " should now be removed");
+ }
+
+ // request succeeded
+ if (reqStatus == 200) {
+ // 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);
+ }
+ // call handler
+ Strophe.debug("request id " +
+ req.id + "." +
+ req.sends + " got 200");
+ func(req);
+ this.errors = 0;
+ } else {
+ Strophe.error("request id " +
+ req.id + "." +
+ req.sends + " error " + reqStatus +
+ " happened");
+ if (reqStatus === 0 ||
+ (reqStatus >= 400 && reqStatus < 600) ||
+ reqStatus >= 12000) {
+ this._hitError(reqStatus);
+ if (reqStatus >= 400 && reqStatus < 500) {
+ this._conn._changeConnectStatus(Strophe.Status.DISCONNECTING,
+ null);
+ this._conn._doDisconnect();
+ }
+ }
+ }
+
+ if (!((reqStatus > 0 && reqStatus < 500) ||
+ req.sends > 5)) {
+ this._throttledRequestHandler();
+ }
+ }
+ },
+
+ /** 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 = -1;
+
+ try {
+ if (req.xhr.readyState == 4) {
+ reqStatus = req.xhr.status;
+ }
+ } catch (e) {
+ Strophe.error("caught an error in _requests[" + i +
+ "], reqStatus: " + reqStatus);
+ }
+
+ if (typeof(reqStatus) == "undefined") {
+ reqStatus = -1;
+ }
+
+ // make sure we limit the number of retries
+ if (req.sends > this.maxRetries) {
+ this._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 {
+ req.xhr.open("POST", this._conn.service, this._conn.options.sync ? false : true);
+ } catch (e2) {
+ Strophe.error("XHR open failed.");
+ 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(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();
+ this._conn._idleTimeout = setTimeout(this._conn._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);
}
}
};
-if (callback) {
- callback(Strophe, $build, $msg, $iq, $pres);
-}
+/*
+ This program is distributed under the terms of the MIT license.
+ Please see the LICENSE file for details.
-})(function () {
- window.Strophe = arguments[0];
- window.$build = arguments[1];
- window.$msg = arguments[2];
- window.$iq = arguments[3];
- window.$pres = arguments[4];
-}); \ No newline at end of file
+ Copyright 2006-2008, OGG, LLC
+*/
+
+/* jshint undef: true, unused: true:, noarg: true, latedef: true */
+/*global document, window, clearTimeout, WebSocket,
+ DOMParser, Strophe, $build */
+
+/** Class: Strophe.WebSocket
+ * _Private_ helper class that handles WebSocket Connections
+ *
+ * The Strophe.WebSocket class is used internally by Strophe.Connection
+ * to encapsulate WebSocket sessions. It is not meant to be used from user's code.
+ */
+
+/** File: websocket.js
+ * A JavaScript library to enable XMPP over Websocket in Strophejs.
+ *
+ * This file implements XMPP over WebSockets for Strophejs.
+ * If a Connection is established with a Websocket url (ws://...)
+ * Strophe will use WebSockets.
+ * For more information on XMPP-over WebSocket see this RFC draft:
+ * http://tools.ietf.org/html/draft-ietf-xmpp-websocket-00
+ *
+ * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de)
+ */
+
+/** PrivateConstructor: Strophe.Websocket
+ * Create and initialize a Strophe.WebSocket object.
+ * Currently only sets the connection Object.
+ *
+ * Parameters:
+ * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets.
+ *
+ * Returns:
+ * A new Strophe.WebSocket object.
+ */
+Strophe.Websocket = function(connection) {
+ this._conn = connection;
+ this.strip = "stream:stream";
+
+ var service = connection.service;
+ if (service.indexOf("ws:") !== 0 && service.indexOf("wss:") !== 0) {
+ // If the service is not an absolute URL, assume it is a path and put the absolute
+ // URL together from options, current URL and the path.
+ var new_service = "";
+
+ if (connection.options.protocol === "ws" && window.location.protocol !== "https:") {
+ new_service += "ws";
+ } else {
+ new_service += "wss";
+ }
+
+ new_service += "://" + window.location.host;
+
+ if (service.indexOf("/") !== 0) {
+ new_service += window.location.pathname + service;
+ } else {
+ new_service += service;
+ }
+
+ connection.service = new_service;
+ }
+};
+
+Strophe.Websocket.prototype = {
+ /** PrivateFunction: _buildStream
+ * _Private_ helper function to generate the <stream> start tag for WebSockets
+ *
+ * Returns:
+ * A Strophe.Builder with a <stream> element.
+ */
+ _buildStream: function ()
+ {
+ return $build("stream:stream", {
+ "to": this._conn.domain,
+ "xmlns": Strophe.NS.CLIENT,
+ "xmlns:stream": Strophe.NS.STREAM,
+ "version": '1.0'
+ });
+ },
+
+ /** PrivateFunction: _check_streamerror
+ * _Private_ checks a message for stream:error
+ *
+ * Parameters:
+ * (Strophe.Request) bodyWrap - The received stanza.
+ * connectstatus - The ConnectStatus that will be set on error.
+ * Returns:
+ * true if there was a streamerror, false otherwise.
+ */
+ _check_streamerror: function (bodyWrap, connectstatus) {
+ var errors = bodyWrap.getElementsByTagName("stream:error");
+ if (errors.length === 0) {
+ return false;
+ }
+ var error = errors[0];
+
+ var condition = "";
+ var text = "";
+
+ var ns = "urn:ietf:params:xml:ns:xmpp-streams";
+ for (var i = 0; i < error.childNodes.length; i++) {
+ var e = error.childNodes[i];
+ if (e.getAttribute("xmlns") !== ns) {
+ break;
+ } if (e.nodeName === "text") {
+ text = e.textContent;
+ } else {
+ condition = e.nodeName;
+ }
+ }
+
+ var errorString = "WebSocket stream error: ";
+
+ if (condition) {
+ errorString += condition;
+ } else {
+ errorString += "unknown";
+ }
+
+ if (text) {
+ errorString += " - " + condition;
+ }
+
+ Strophe.error(errorString);
+
+ // close the connection on stream_error
+ this._conn._changeConnectStatus(connectstatus, condition);
+ this._conn._doDisconnect();
+ return true;
+ },
+
+ /** PrivateFunction: _reset
+ * Reset the connection.
+ *
+ * This function is called by the reset function of the Strophe Connection.
+ * Is not needed by WebSockets.
+ */
+ _reset: function ()
+ {
+ return;
+ },
+
+ /** PrivateFunction: _connect
+ * _Private_ function called by Strophe.Connection.connect
+ *
+ * Creates a WebSocket for a connection and assigns Callbacks to it.
+ * Does nothing if there already is a WebSocket.
+ */
+ _connect: function () {
+ // Ensure that there is no open WebSocket from a previous Connection.
+ this._closeSocket();
+
+ // Create the new WobSocket
+ this.socket = new WebSocket(this._conn.service, "xmpp");
+ this.socket.onopen = this._onOpen.bind(this);
+ this.socket.onerror = this._onError.bind(this);
+ this.socket.onclose = this._onClose.bind(this);
+ this.socket.onmessage = this._connect_cb_wrapper.bind(this);
+ },
+
+ /** PrivateFunction: _connect_cb
+ * _Private_ function called by Strophe.Connection._connect_cb
+ *
+ * checks for stream:error
+ *
+ * Parameters:
+ * (Strophe.Request) bodyWrap - The received stanza.
+ */
+ _connect_cb: function(bodyWrap) {
+ var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL);
+ if (error) {
+ return Strophe.Status.CONNFAIL;
+ }
+ },
+
+ /** PrivateFunction: _handleStreamStart
+ * _Private_ function that checks the opening stream:stream tag for errors.
+ *
+ * Disconnects if there is an error and returns false, true otherwise.
+ *
+ * Parameters:
+ * (Node) message - Stanza containing the stream:stream.
+ */
+ _handleStreamStart: function(message) {
+ var error = false;
+ // Check for errors in the stream:stream tag
+ var ns = message.getAttribute("xmlns");
+ if (typeof ns !== "string") {
+ error = "Missing xmlns in stream:stream";
+ } else if (ns !== Strophe.NS.CLIENT) {
+ error = "Wrong xmlns in stream:stream: " + ns;
+ }
+
+ var ns_stream = message.namespaceURI;
+ if (typeof ns_stream !== "string") {
+ error = "Missing xmlns:stream in stream:stream";
+ } else if (ns_stream !== Strophe.NS.STREAM) {
+ error = "Wrong xmlns:stream in stream:stream: " + ns_stream;
+ }
+
+ var ver = message.getAttribute("version");
+ if (typeof ver !== "string") {
+ error = "Missing version in stream:stream";
+ } else if (ver !== "1.0") {
+ error = "Wrong version in stream:stream: " + ver;
+ }
+
+ if (error) {
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error);
+ this._conn._doDisconnect();
+ return false;
+ }
+
+ return true;
+ },
+
+ /** PrivateFunction: _connect_cb_wrapper
+ * _Private_ function that handles the first connection messages.
+ *
+ * On receiving an opening stream tag this callback replaces itself with the real
+ * message handler. On receiving a stream error the connection is terminated.
+ */
+ _connect_cb_wrapper: function(message) {
+ if (message.data.indexOf("<stream:stream ") === 0 || message.data.indexOf("<?xml") === 0) {
+ // Strip the XML Declaration, if there is one
+ var data = message.data.replace(/^(<\?.*?\?>\s*)*/, "");
+ if (data === '') return;
+
+ //Make the initial stream:stream selfclosing to parse it without a SAX parser.
+ data = message.data.replace(/<stream:stream (.*[^\/])>/, "<stream:stream $1/>");
+
+ var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement;
+ this._conn.xmlInput(streamStart);
+ this._conn.rawInput(message.data);
+
+ //_handleStreamSteart will check for XML errors and disconnect on error
+ if (this._handleStreamStart(streamStart)) {
+
+ //_connect_cb will check for stream:error and disconnect on error
+ this._connect_cb(streamStart);
+
+ // ensure received stream:stream is NOT selfclosing and save it for following messages
+ this.streamStart = message.data.replace(/^<stream:(.*)\/>$/, "<stream:$1>");
+ }
+ } else if (message.data === "</stream:stream>") {
+ this._conn.rawInput(message.data);
+ this._conn.xmlInput(document.createElement("stream:stream"));
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream");
+ this._conn._doDisconnect();
+ return;
+ } else {
+ var string = this._streamWrap(message.data);
+ var elem = new DOMParser().parseFromString(string, "text/xml").documentElement;
+ this.socket.onmessage = this._onMessage.bind(this);
+ this._conn._connect_cb(elem, null, message.data);
+ }
+ },
+
+ /** PrivateFunction: _disconnect
+ * _Private_ function called by Strophe.Connection.disconnect
+ *
+ * Disconnects and sends a last stanza if one is given
+ *
+ * Parameters:
+ * (Request) pres - This stanza will be sent before disconnecting.
+ */
+ _disconnect: function (pres)
+ {
+ if (this.socket.readyState !== WebSocket.CLOSED) {
+ if (pres) {
+ this._conn.send(pres);
+ }
+ var close = '</stream:stream>';
+ this._conn.xmlOutput(document.createElement("stream:stream"));
+ this._conn.rawOutput(close);
+ try {
+ this.socket.send(close);
+ } catch (e) {
+ Strophe.info("Couldn't send closing stream tag.");
+ }
+ }
+
+ this._conn._doDisconnect();
+ },
+
+ /** PrivateFunction: _doDisconnect
+ * _Private_ function to disconnect.
+ *
+ * Just closes the Socket for WebSockets
+ */
+ _doDisconnect: function ()
+ {
+ Strophe.info("WebSockets _doDisconnect was called");
+ this._closeSocket();
+ },
+
+ /** PrivateFunction _streamWrap
+ * _Private_ helper function to wrap a stanza in a <stream> tag.
+ * This is used so Strophe can process stanzas from WebSockets like BOSH
+ */
+ _streamWrap: function (stanza)
+ {
+ return this.streamStart + stanza + '</stream:stream>';
+ },
+
+
+ /** PrivateFunction: _closeSocket
+ * _Private_ function to close the WebSocket.
+ *
+ * Closes the socket if it is still open and deletes it
+ */
+ _closeSocket: function ()
+ {
+ if (this.socket) { try {
+ this.socket.close();
+ } catch (e) {} }
+ this.socket = null;
+ },
+
+ /** PrivateFunction: _emptyQueue
+ * _Private_ function to check if the message queue is empty.
+ *
+ * Returns:
+ * True, because WebSocket messages are send immediately after queueing.
+ */
+ _emptyQueue: function ()
+ {
+ return true;
+ },
+
+ /** PrivateFunction: _onClose
+ * _Private_ function to handle websockets closing.
+ *
+ * Nothing to do here for WebSockets
+ */
+ _onClose: function() {
+ if(this._conn.connected && !this._conn.disconnecting) {
+ Strophe.error("Websocket closed unexcectedly");
+ this._conn._doDisconnect();
+ } else {
+ Strophe.info("Websocket closed");
+ }
+ },
+
+ /** PrivateFunction: _no_auth_received
+ *
+ * Called on stream start/restart when no stream:features
+ * has been received.
+ */
+ _no_auth_received: function (_callback)
+ {
+ Strophe.error("Server did not send any auth methods");
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods");
+ if (_callback) {
+ _callback = _callback.bind(this._conn);
+ _callback();
+ }
+ this._conn._doDisconnect();
+ },
+
+ /** PrivateFunction: _onDisconnectTimeout
+ * _Private_ timeout handler for handling non-graceful disconnection.
+ *
+ * This does nothing for WebSockets
+ */
+ _onDisconnectTimeout: function () {},
+
+ /** PrivateFunction: _onError
+ * _Private_ function to handle websockets errors.
+ *
+ * Parameters:
+ * (Object) error - The websocket error.
+ */
+ _onError: function(error) {
+ Strophe.error("Websocket error " + error);
+ this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The WebSocket connection could not be established was disconnected.");
+ this._disconnect();
+ },
+
+ /** PrivateFunction: _onIdle
+ * _Private_ function called by Strophe.Connection._onIdle
+ *
+ * sends all queued stanzas
+ */
+ _onIdle: function () {
+ var data = this._conn._data;
+ if (data.length > 0 && !this._conn.paused) {
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] !== null) {
+ var stanza, rawStanza;
+ if (data[i] === "restart") {
+ stanza = this._buildStream();
+ rawStanza = this._removeClosingTag(stanza);
+ stanza = stanza.tree();
+ } else {
+ stanza = data[i];
+ rawStanza = Strophe.serialize(stanza);
+ }
+ this._conn.xmlOutput(stanza);
+ this._conn.rawOutput(rawStanza);
+ this.socket.send(rawStanza);
+ }
+ }
+ this._conn._data = [];
+ }
+ },
+
+ /** PrivateFunction: _onMessage
+ * _Private_ function to handle websockets messages.
+ *
+ * This function parses each of the messages as if they are full documents. [TODO : We may actually want to use a SAX Push parser].
+ *
+ * Since all XMPP traffic starts with "<stream:stream version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='3697395463' from='SERVER'>"
+ * The first stanza will always fail to be parsed...
+ * Addtionnaly, the seconds stanza will always be a <stream:features> with the stream NS defined in the previous stanza... so we need to 'force' the inclusion of the NS in this stanza!
+ *
+ * Parameters:
+ * (string) message - The websocket message.
+ */
+ _onMessage: function(message) {
+ var elem, data;
+ // check for closing stream
+ if (message.data === "</stream:stream>") {
+ var close = "</stream:stream>";
+ this._conn.rawInput(close);
+ this._conn.xmlInput(document.createElement("stream:stream"));
+ if (!this._conn.disconnecting) {
+ this._conn._doDisconnect();
+ }
+ return;
+ } else if (message.data.search("<stream:stream ") === 0) {
+ //Make the initial stream:stream selfclosing to parse it without a SAX parser.
+ data = message.data.replace(/<stream:stream (.*[^\/])>/, "<stream:stream $1/>");
+ elem = new DOMParser().parseFromString(data, "text/xml").documentElement;
+
+ if (!this._handleStreamStart(elem)) {
+ return;
+ }
+ } else {
+ data = this._streamWrap(message.data);
+ elem = new DOMParser().parseFromString(data, "text/xml").documentElement;
+ }
+
+ if (this._check_streamerror(elem, Strophe.Status.ERROR)) {
+ return;
+ }
+
+ //handle unavailable presence stanza before disconnecting
+ if (this._conn.disconnecting &&
+ elem.firstChild.nodeName === "presence" &&
+ elem.firstChild.getAttribute("type") === "unavailable") {
+ this._conn.xmlInput(elem);
+ this._conn.rawInput(Strophe.serialize(elem));
+ // if we are already disconnecting we will ignore the unavailable stanza and
+ // wait for the </stream:stream> tag before we close the connection
+ return;
+ }
+ this._conn._dataRecv(elem, message.data);
+ },
+
+ /** PrivateFunction: _onOpen
+ * _Private_ function to handle websockets connection setup.
+ *
+ * The opening stream tag is sent here.
+ */
+ _onOpen: function() {
+ Strophe.info("Websocket open");
+ var start = this._buildStream();
+ this._conn.xmlOutput(start.tree());
+
+ var startString = this._removeClosingTag(start);
+ this._conn.rawOutput(startString);
+ this.socket.send(startString);
+ },
+
+ /** PrivateFunction: _removeClosingTag
+ * _Private_ function to Make the first <stream:stream> non-selfclosing
+ *
+ * Parameters:
+ * (Object) elem - The <stream:stream> tag.
+ *
+ * Returns:
+ * The stream:stream tag as String
+ */
+ _removeClosingTag: function(elem) {
+ var string = Strophe.serialize(elem);
+ string = string.replace(/<(stream:stream .*[^\/])\/>$/, "<$1>");
+ return string;
+ },
+
+ /** PrivateFunction: _reqToData
+ * _Private_ function to get a stanza out of a request.
+ *
+ * WebSockets don't use requests, so the passed argument is just returned.
+ *
+ * Parameters:
+ * (Object) stanza - The stanza.
+ *
+ * Returns:
+ * The stanza that was passed.
+ */
+ _reqToData: function (stanza)
+ {
+ return stanza;
+ },
+
+ /** PrivateFunction: _send
+ * _Private_ part of the Connection.send function for WebSocket
+ *
+ * Just flushes the messages that are in the queue
+ */
+ _send: function () {
+ this._conn.flush();
+ },
+
+ /** PrivateFunction: _sendRestart
+ *
+ * Send an xmpp:restart stanza.
+ */
+ _sendRestart: function ()
+ {
+ clearTimeout(this._conn._idleTimeout);
+ this._conn._onIdle.bind(this._conn)();
+ }
+};
diff --git a/build/js/ojsxc.js b/build/js/ojsxc.js
index 63d6b47..90f68ef 100644
--- a/build/js/ojsxc.js
+++ b/build/js/ojsxc.js
@@ -1,5 +1,5 @@
/**
- * ojsxc v0.5.1 - 2014-01-27
+ * ojsxc v0.5.2 - 2014-01-28
*
* Copyright (c) 2014 Klaus Herberth <klaus@jsxc.org> <br>
* Released under the MIT license
@@ -7,7 +7,7 @@
* Please see http://jsxc.org/
*
* @author Klaus Herberth <klaus@jsxc.org>
- * @version 0.5.1
+ * @version 0.5.2
*/
/* global jsxc, oc_appswebroots, OC, $ */
@@ -89,15 +89,6 @@ $(function() {
},
logoutElement: $('#logout'),
checkFlash: false,
- debug: function(msg, data) {
- if (data) {
- console.log(msg, data);
- jsxc.log = jsxc.log + msg + ' >> ' + $("<span>").prepend(data).html() + '\n';
- } else {
- console.log(msg);
- jsxc.log = jsxc.log + msg + '\n';
- }
- },
rosterAppend: 'body',
root: oc_appswebroots.ojsxc,
// @TODO: don't include get turn credentials routine into jsxc
diff --git a/css/jsxc.oc.css b/css/jsxc.oc.css
index 620bea3..4461ad1 100644
--- a/css/jsxc.oc.css
+++ b/css/jsxc.oc.css
@@ -583,4 +583,8 @@ div.jsxc_transfer.jsxc_enc.jsxc_trust {
background-image: url('%appswebroot%/ojsxc/img/fail-icon.png');
color: #800000;
border-color: #800000;
+}
+
+.jsxc_log{
+ width: 500px;
} \ No newline at end of file
diff --git a/js/jsxc b/js/jsxc
-Subproject bc3ec65a50d03bbe4f86c295712bd414c3901d0
+Subproject 32514ea25d919d5c1ea0022d635d91defc5bf40
diff --git a/js/ojsxc.js b/js/ojsxc.js
index 125078f..7fff95d 100644
--- a/js/ojsxc.js
+++ b/js/ojsxc.js
@@ -77,15 +77,6 @@ $(function() {
},
logoutElement: $('#logout'),
checkFlash: false,
- debug: function(msg, data) {
- if (data) {
- console.log(msg, data);
- jsxc.log = jsxc.log + msg + ' >> ' + $("<span>").prepend(data).html() + '\n';
- } else {
- console.log(msg);
- jsxc.log = jsxc.log + msg + '\n';
- }
- },
rosterAppend: 'body',
root: oc_appswebroots.ojsxc,
// @TODO: don't include get turn credentials routine into jsxc