Remove local contacts not returned from a full roster response

This commit is contained in:
JC Brand 2022-04-14 10:01:47 +02:00
parent 8e1c3e47df
commit 2d5b0753e2
3 changed files with 66 additions and 3 deletions

View File

@ -4,6 +4,7 @@
- GIFs don't render inside unfurls and cause a TypeError - GIFs don't render inside unfurls and cause a TypeError
- Improve how the `muc_domain` setting is populated via service discovery - Improve how the `muc_domain` setting is populated via service discovery
- Remove local (non-requesting) contacts not returned from a full roster response
- #2746: Always reply to all iqs, even those not understood - #2746: Always reply to all iqs, even those not understood
- #2868: Selected emoji is inserted into all open chat boxes - #2868: Selected emoji is inserted into all open chat boxes

View File

@ -67,7 +67,7 @@ const RosterContacts = Collection.extend({
'add': true, 'add': true,
'silent': true, 'silent': true,
'success': resolve, 'success': resolve,
'error': (c, e) => reject(e) 'error': (_, e) => reject(e)
}); });
}); });
if (u.isErrorObject(result)) { if (u.isErrorObject(result)) {
@ -262,11 +262,19 @@ const RosterContacts = Collection.extend({
if (this.rosterVersioningSupported()) { if (this.rosterVersioningSupported()) {
stanza.attrs({'ver': this.data.get('version')}); stanza.attrs({'ver': this.data.get('version')});
} }
const iq = await api.sendIQ(stanza, null, false); const iq = await api.sendIQ(stanza, null, false);
if (iq.getAttribute('type') !== 'error') {
if (iq.getAttribute('type') === 'result') {
const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop(); const query = sizzle(`query[xmlns="${Strophe.NS.ROSTER}"]`, iq).pop();
if (query) { if (query) {
const items = sizzle(`item`, query); const items = sizzle(`item`, query);
if (!this.data.get('version')) {
// We're getting the full roster, so remove all cached
// contacts that aren't included in it.
const jids = items.map(item => item.getAttribute('jid'));
this.models.forEach(m => !m.get('requesting') && !jids.includes(m.get('jid')) && m.destroy());
}
items.forEach(item => this.updateContact(item)); items.forEach(item => this.updateContact(item));
this.data.save('version', query.getAttribute('ver')); this.data.save('version', query.getAttribute('ver'));
} }
@ -276,6 +284,7 @@ const RosterContacts = Collection.extend({
log.error("Error while trying to fetch roster from the server"); log.error("Error while trying to fetch roster from the server");
return; return;
} }
_converse.session.save('roster_cached', true); _converse.session.save('roster_cached', true);
/** /**
* When the roster has been received from the XMPP server. * When the roster has been received from the XMPP server.
@ -348,7 +357,6 @@ const RosterContacts = Collection.extend({
api.trigger('contactRequest', this.create(user_data)); api.trigger('contactRequest', this.create(user_data));
}, },
handleIncomingSubscription (presence) { handleIncomingSubscription (presence) {
const jid = presence.getAttribute('from'), const jid = presence.getAttribute('from'),
bare_jid = Strophe.getBareJidFromJid(jid), bare_jid = Strophe.getBareJidFromJid(jid),

View File

@ -132,6 +132,59 @@ describe("The Contacts Roster", function () {
expect(_converse.roster.at(0).get('jid')).toBe('nurse@example.com'); expect(_converse.roster.at(0).get('jid')).toBe('nurse@example.com');
})); }));
it("can be refreshed", mock.initConverse(
[], {}, async function (_converse) {
const sent_IQs = _converse.connection.IQ_stanzas;
let stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop());
_converse.connection._dataRecv(mock.createRequest($iq({
to: _converse.connection.jid,
type: 'result',
id: stanza.getAttribute('id')
}).c('query', {
xmlns: 'jabber:iq:roster',
}).c('item', {
jid: 'juliet@example.net',
name: 'Juliet',
subscription:'both'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'mercutio@example.net',
name: 'Mercutio',
subscription:'from'
}).c('group').t('Friends')));
while (sent_IQs.length) sent_IQs.pop();
await u.waitUntil(() => _converse.roster.length === 2);
expect(_converse.roster.pluck('jid')).toEqual(['juliet@example.net', 'mercutio@example.net']);
const rosterview = document.querySelector('converse-roster');
const sync_button = rosterview.querySelector('.sync-contacts');
sync_button.click();
stanza = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector('iq query[xmlns="jabber:iq:roster"]')).pop());
_converse.connection._dataRecv(mock.createRequest($iq({
to: _converse.connection.jid,
type: 'result',
id: stanza.getAttribute('id')
}).c('query', {
xmlns: 'jabber:iq:roster',
}).c('item', {
jid: 'juliet@example.net',
name: 'Juliet',
subscription:'both'
}).c('group').t('Friends').up().up()
.c('item', {
jid: 'lord.capulet@example.net',
name: 'Lord Capulet',
subscription:'from'
}).c('group').t('Acquaintences')));
await u.waitUntil(() => _converse.roster.pluck('jid').includes('lord.capulet@example.net'));
expect(_converse.roster.pluck('jid')).toEqual(['juliet@example.net', 'lord.capulet@example.net']);
}));
it("will also show contacts added afterwards", mock.initConverse([], {}, async function (_converse) { it("will also show contacts added afterwards", mock.initConverse([], {}, async function (_converse) {
await mock.openControlBox(_converse); await mock.openControlBox(_converse);
await mock.waitForRoster(_converse, 'current'); await mock.waitForRoster(_converse, 'current');
@ -1175,6 +1228,7 @@ describe("The Contacts Roster", function () {
const pres = $pres({from: 'data@enterprise/resource', type: 'subscribe'}); const pres = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
_converse.connection._dataRecv(mock.createRequest(pres)); _converse.connection._dataRecv(mock.createRequest(pres));
expect(_converse.roster.pluck('jid').length).toBe(1); expect(_converse.roster.pluck('jid').length).toBe(1);
const rosterview = document.querySelector('converse-roster'); const rosterview = document.querySelector('converse-roster');
await u.waitUntil(() => sizzle('a:contains("Contact requests")', rosterview).length, 700); await u.waitUntil(() => sizzle('a:contains("Contact requests")', rosterview).length, 700);