Explorar o código

Remove local contacts not returned from a full roster response

JC Brand %!s(int64=3) %!d(string=hai) anos
pai
achega
2d5b0753e2
Modificáronse 3 ficheiros con 66 adicións e 3 borrados
  1. 1 0
      CHANGES.md
  2. 11 3
      src/headless/plugins/roster/contacts.js
  3. 54 0
      src/plugins/rosterview/tests/roster.js

+ 1 - 0
CHANGES.md

@@ -4,6 +4,7 @@
 
 - GIFs don't render inside unfurls and cause a TypeError
 - 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
 - #2868: Selected emoji is inserted into all open chat boxes
 

+ 11 - 3
src/headless/plugins/roster/contacts.js

@@ -67,7 +67,7 @@ const RosterContacts = Collection.extend({
                 'add': true,
                 'silent': true,
                 'success': resolve,
-                'error': (c, e) => reject(e)
+                'error': (_, e) => reject(e)
             });
         });
         if (u.isErrorObject(result)) {
@@ -262,11 +262,19 @@ const RosterContacts = Collection.extend({
         if (this.rosterVersioningSupported()) {
             stanza.attrs({'ver': this.data.get('version')});
         }
+
         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();
             if (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));
                 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");
             return;
         }
+
         _converse.session.save('roster_cached', true);
         /**
          * 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));
     },
 
-
     handleIncomingSubscription (presence) {
         const jid = presence.getAttribute('from'),
             bare_jid = Strophe.getBareJidFromJid(jid),

+ 54 - 0
src/plugins/rosterview/tests/roster.js

@@ -132,6 +132,59 @@ describe("The Contacts Roster", function () {
         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) {
         await mock.openControlBox(_converse);
         await mock.waitForRoster(_converse, 'current');
@@ -1175,6 +1228,7 @@ describe("The Contacts Roster", function () {
 
             const pres = $pres({from: 'data@enterprise/resource', type: 'subscribe'});
             _converse.connection._dataRecv(mock.createRequest(pres));
+
             expect(_converse.roster.pluck('jid').length).toBe(1);
             const rosterview = document.querySelector('converse-roster');
             await u.waitUntil(() => sizzle('a:contains("Contact requests")', rosterview).length, 700);