Commit bb95375f authored by JC Brand's avatar JC Brand

Support for roster versioning

fixes #1106
parent 4cfade28
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
// 'prosody@conference.prosody.im', // 'prosody@conference.prosody.im',
// 'jdev@conference.jabber.org' // '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', view_mode: 'fullscreen',
archived_messages_page_size: '500', archived_messages_page_size: '500',
allow_public_bookmarks: true, allow_public_bookmarks: true,
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
'discuss@conference.conversejs.org' 'discuss@conference.conversejs.org'
], ],
bosh_service_url: 'http://chat.example.org:5280/http-bind/', 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', message_archiving: 'always',
debug: true debug: true
}); });
......
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
`<iq type="get" id="${stanza.getAttribute('id')}" xmlns="jabber:client">`+ `<iq type="get" id="${stanza.getAttribute('id')}" xmlns="jabber:client">`+
`<query xmlns="jabber:iq:roster"/>`+ `<query xmlns="jabber:iq:roster"/>`+
`</iq>`); `</iq>`);
const result = $iq({ let result = $iq({
'to': _converse.connection.jid, 'to': _converse.connection.jid,
'type': 'result', 'type': 'result',
'id': stanza.getAttribute('id') 'id': stanza.getAttribute('id')
...@@ -68,6 +68,31 @@ ...@@ -68,6 +68,31 @@
.c('item', {'jid': 'romeo@example.com'}) .c('item', {'jid': 'romeo@example.com'})
_converse.connection._dataRecv(test_utils.createRequest(result)); _converse.connection._dataRecv(test_utils.createRequest(result));
expect(_converse.roster.data.get('version')).toBe('ver7'); 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(); done();
}); });
})); }));
......
...@@ -649,6 +649,7 @@ ...@@ -649,6 +649,7 @@
_converse.session.id = id; // Appears to be necessary for backbone.browserStorage _converse.session.id = id; // Appears to be necessary for backbone.browserStorage
_converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id); _converse.session.browserStorage = new Backbone.BrowserStorage[_converse.storage](id);
_converse.session.fetch(); _converse.session.fetch();
_converse.emit('sessionInitialized');
}; };
this.clearSession = function () { this.clearSession = function () {
......
...@@ -219,12 +219,12 @@ ...@@ -219,12 +219,12 @@
function initStreamFeatures () { function initStreamFeatures () {
_converse.stream_features = new Backbone.Collection(); _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}`) b64_sha1(`converse.stream-features-${_converse.bare_jid}`)
); );
_converse.stream_features.fetch({ _converse.stream_features.fetch({
success (collection) { success (collection) {
if (collection.length === 0) { if (collection.length === 0 && _converse.connection.features) {
_.forEach( _.forEach(
_converse.connection.features.childNodes, _converse.connection.features.childNodes,
(feature) => { (feature) => {
...@@ -240,11 +240,10 @@ ...@@ -240,11 +240,10 @@
function initializeDisco () { function initializeDisco () {
addClientFeatures(); addClientFeatures();
initStreamFeatures();
_converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null); _converse.connection.addHandler(onDiscoInfoRequest, Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);
_converse.disco_entities = new _converse.DiscoEntities(); _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}`) b64_sha1(`converse.disco-entities-${_converse.bare_jid}`)
); );
...@@ -253,12 +252,12 @@ ...@@ -253,12 +252,12 @@
// If we don't have an entity for our own XMPP server, // If we don't have an entity for our own XMPP server,
// create one. // create one.
_converse.disco_entities.create({'jid': _converse.domain}); _converse.disco_entities.create({'jid': _converse.domain});
initStreamFeatures();
} }
_converse.emit('discoInitialized'); _converse.emit('discoInitialized');
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
} }
_converse.api.listen.on('sessionInitialized', initStreamFeatures);
_converse.api.listen.on('reconnected', initializeDisco); _converse.api.listen.on('reconnected', initializeDisco);
_converse.api.listen.on('connected', initializeDisco); _converse.api.listen.on('connected', initializeDisco);
......
...@@ -382,12 +382,14 @@ ...@@ -382,12 +382,14 @@
* Returns a promise which resolves once the contacts have been * Returns a promise which resolves once the contacts have been
* fetched. * fetched.
*/ */
const that = this;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.fetch({ this.fetch({
'add': true, 'add': true,
'silent': true, 'silent': true,
success (collection) { success (collection) {
if (collection.length === 0) { if (collection.length === 0 ||
(that.rosterVersioningSupported() && !_converse.session.get('roster_fetched'))) {
_converse.send_initial_presence = true; _converse.send_initial_presence = true;
_converse.roster.fetchFromServer().then(resolve).catch(reject); _converse.roster.fetchFromServer().then(resolve).catch(reject);
} else { } else {
...@@ -530,7 +532,11 @@ ...@@ -530,7 +532,11 @@
return; return;
} }
_converse.connection.send($iq({type: 'result', id, from: _converse.connection.jid})); _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) { if (items.length > 1) {
_converse.log(iq, Strophe.LogLevel.ERROR); _converse.log(iq, Strophe.LogLevel.ERROR);
throw new Error('Roster push query may not contain more than one "item" element.'); throw new Error('Roster push query may not contain more than one "item" element.');
...@@ -545,6 +551,10 @@ ...@@ -545,6 +551,10 @@
return; return;
}, },
rosterVersioningSupported () {
return _converse.api.disco.stream.getFeature('ver', 'urn:xmpp:features:rosterver') && this.data.get('version');
},
fetchFromServer () { fetchFromServer () {
/* Fetch the roster from the XMPP server */ /* Fetch the roster from the XMPP server */
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
...@@ -552,7 +562,9 @@ ...@@ -552,7 +562,9 @@
'type': 'get', 'type': 'get',
'id': _converse.connection.getUniqueId('roster') 'id': _converse.connection.getUniqueId('roster')
}).c('query', {xmlns: Strophe.NS.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 callback = _.flow(this.onReceivedFromServer.bind(this), resolve);
const errback = function (iq) { const errback = function (iq) {
const errmsg = "Error while trying to fetch roster from the server"; const errmsg = "Error while trying to fetch roster from the server";
...@@ -567,10 +579,13 @@ ...@@ -567,10 +579,13 @@
/* An IQ stanza containing the roster has been received from /* An IQ stanza containing the roster has been received from
* the XMPP server. * the XMPP server.
*/ */
const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop(), const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop();
items = sizzle(`item`, query); if (query) {
_.each(items, (item) => this.updateContact(item)); const items = sizzle(`item`, query);
this.data.save('version', query.getAttribute('ver')); _.each(items, (item) => this.updateContact(item));
this.data.save('version', query.getAttribute('ver'));
_converse.session.save('roster_fetched', true);
}
_converse.emit('roster', iq); _converse.emit('roster', iq);
}, },
......
...@@ -63,6 +63,18 @@ ...@@ -63,6 +63,18 @@
this.IQ_ids.push(id); this.IQ_ids.push(id);
return 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._proto._connect = function () {
c.authenticated = true; 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