Commit bb95375f authored by JC Brand's avatar JC Brand

Support for roster versioning

fixes #1106
parent 4cfade28
......@@ -27,7 +27,7 @@
// 'prosody@conference.prosody.im',
// 'jdev@conference.jabber.org'
// ],
websocket_url: 'ws://chat.example.org:5280/xmpp-websocket',
// websocket_url: 'ws://chat.example.org:5280/xmpp-websocket',
view_mode: 'fullscreen',
archived_messages_page_size: '500',
allow_public_bookmarks: true,
......@@ -35,7 +35,7 @@
'discuss@conference.conversejs.org'
],
bosh_service_url: 'http://chat.example.org:5280/http-bind/',
bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
// bosh_service_url: 'https://conversejs.org/http-bind/', // Please use this connection manager only for testing purposes
message_archiving: 'always',
debug: true
});
......
......@@ -57,7 +57,7 @@
`<iq type="get" id="${stanza.getAttribute('id')}" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"/>`+
`</iq>`);
const result = $iq({
let result = $iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
......@@ -68,6 +68,31 @@
.c('item', {'jid': 'romeo@example.com'})
_converse.connection._dataRecv(test_utils.createRequest(result));
expect(_converse.roster.data.get('version')).toBe('ver7');
expect(_converse.roster.models.length).toBe(2);
_converse.roster.fetchFromServer();
stanza = _converse.connection.IQ_stanzas.pop().nodeTree;
expect(stanza.outerHTML).toBe(
`<iq type="get" id="${stanza.getAttribute('id')}" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster" ver="ver7"/>`+
`</iq>`);
result = $iq({
'to': _converse.connection.jid,
'type': 'result',
'id': stanza.getAttribute('id')
});
_converse.connection._dataRecv(test_utils.createRequest(result));
const roster_push = $iq({
'to': _converse.connection.jid,
'type': 'set',
}).c('query', {'xmlns': 'jabber:iq:roster', 'ver': 'ver34'})
.c('item', {'jid': 'romeo@example.com', 'subscription': 'remove'});
_converse.connection._dataRecv(test_utils.createRequest(roster_push));
expect(_converse.roster.data.get('version')).toBe('ver34');
expect(_converse.roster.models.length).toBe(1);
expect(_converse.roster.at(0).get('jid')).toBe('nurse@example.com');
done();
});
}));
......
......@@ -649,6 +649,7 @@
_converse.session.id = id; // Appears to be necessary for backbone.browserStorage
_converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
_converse.session.fetch();
_converse.emit('sessionInitialized');
};
this.clearSession = function () {
......
......@@ -219,12 +219,12 @@
function initStreamFeatures () {
_converse.stream_features = new Backbone.Collection();
_converse.stream_features.browserStorage = new Backbone.BrowserStorage[_converse.storage](
_converse.stream_features.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1(`converse.stream-features-${_converse.bare_jid}`)
);
_converse.stream_features.fetch({
success (collection) {
if (collection.length === 0) {
if (collection.length === 0 && _converse.connection.features) {
_.forEach(
_converse.connection.features.childNodes,
(feature) => {
......@@ -240,11 +240,10 @@
function initializeDisco () {
addClientFeatures();
initStreamFeatures();
_converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
_converse.disco_entities = new _converse.DiscoEntities();
_converse.disco_entities.browserStorage = new Backbone.BrowserStorage[_converse.storage](
_converse.disco_entities.browserStorage = new Backbone.BrowserStorage.session(
b64_sha1(`converse.disco-entities-${_converse.bare_jid}`)
);
......@@ -253,12 +252,12 @@
// If we don't have an entity for our own XMPP server,
// create one.
_converse.disco_entities.create({'jid': _converse.domain});
initStreamFeatures();
}
_converse.emit('discoInitialized');
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
}
_converse.api.listen.on('sessionInitialized', initStreamFeatures);
_converse.api.listen.on('reconnected', initializeDisco);
_converse.api.listen.on('connected', initializeDisco);
......
......@@ -382,12 +382,14 @@
* Returns a promise which resolves once the contacts have been
* fetched.
*/
const that = this;
return new Promise((resolve, reject) => {
this.fetch({
'add': true,
'silent': true,
success (collection) {
if (collection.length === 0) {
if (collection.length === 0 ||
(that.rosterVersioningSupported() && !_converse.session.get('roster_fetched'))) {
_converse.send_initial_presence = true;
_converse.roster.fetchFromServer().then(resolve).catch(reject);
} else {
......@@ -530,7 +532,11 @@
return;
}
_converse.connection.send($iq({type: 'result', id, from: _converse.connection.jid}));
const items = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"] item`, iq);
const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop();
this.data.save('version', query.getAttribute('ver'));
const items = sizzle(`item`, query);
if (items.length > 1) {
_converse.log(iq, Strophe.LogLevel.ERROR);
throw new Error('Roster push query may not contain more than one "item" element.');
......@@ -545,6 +551,10 @@
return;
},
rosterVersioningSupported () {
return _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') && this.data.get('version');
},
fetchFromServer () {
/* Fetch the roster from the XMPP server */
return new Promise((resolve, reject) => {
......@@ -552,7 +562,9 @@
'type': 'get',
'id': _converse.connection.getUniqueId('roster')
}).c('query', {xmlns: Strophe.NS.ROSTER});
if (this.rosterVersioningSupported()) {
iq.attrs({'ver': this.data.get('version')});
}
const callback = _.flow(this.onReceivedFromServer.bind(this), resolve);
const errback = function (iq) {
const errmsg = "Error while trying to fetch roster from the server";
......@@ -567,10 +579,13 @@
/* An IQ stanza containing the roster has been received from
* the XMPP server.
*/
const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop(),
items = sizzle(`item`, query);
_.each(items, (item) => this.updateContact(item));
this.data.save('version', query.getAttribute('ver'));
const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop();
if (query) {
const items = sizzle(`item`, query);
_.each(items, (item) => this.updateContact(item));
this.data.save('version', query.getAttribute('ver'));
_converse.session.save('roster_fetched', true);
}
_converse.emit('roster', iq);
},
......
......@@ -63,6 +63,18 @@
this.IQ_ids.push(id);
return id;
}
c.features = Strophe.xmlHtmlNode(
'<stream:features xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">'+
'<ver xmlns="urn:xmpp:features:rosterver"/>'+
'<csi xmlns="urn:xmpp:csi:0"/>'+
'<c xmlns="http://jabber.org/protocol/caps" ver="UwBpfJpEt3IoLYfWma/o/p3FFRo=" hash="sha-1" node="http://prosody.im"/>'+
'<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'+
'<required/>'+
'</bind>'+
'<session xmlns="urn:ietf:params:xml:ns:xmpp-session">'+
'<optional/>'+
'</session>'+
'</stream:features>').firstChild;
c._proto._connect = function () {
c.authenticated = true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment