2
0
Эх сурвалжийг харах

Test that device lists can get updated via PEP

Fix various bugs in the process.

updates #497
JC Brand 7 жил өмнө
parent
commit
ddd0ef8e20

+ 47 - 45
spec/bookmarks.js

@@ -320,52 +320,54 @@
                 [{'category': 'pubsub', 'type': 'pep'}],
                 ['http://jabber.org/protocol/pubsub#publish-options']
             ).then(function () {
-                test_utils.waitUntil(function () {
-                    return _converse.bookmarks;
-                }, 300).then(function () {
-                    /* The stored data is automatically pushed to all of the user's
-                    * connected resources.
-                    *
-                    * Publisher receives event notification
-                    * -------------------------------------
-                    * <message from='juliet@capulet.lit'
-                    *         to='juliet@capulet.lit/balcony'
-                    *         type='headline'
-                    *         id='rnfoo1'>
-                    * <event xmlns='http://jabber.org/protocol/pubsub#event'>
-                    *     <items node='storage:bookmarks'>
-                    *     <item id='current'>
-                    *         <storage xmlns='storage:bookmarks'>
-                    *         <conference name='The Play&apos;s the Thing'
-                    *                     autojoin='true'
-                    *                     jid='theplay@conference.shakespeare.lit'>
-                    *             <nick>JC</nick>
-                    *         </conference>
-                    *         </storage>
-                    *     </item>
-                    *     </items>
-                    * </event>
-                    * </message>
-                    */
-                    var stanza = $msg({
-                        'from': 'dummy@localhost',
-                        'to': 'dummy@localhost/resource',
-                        'type': 'headline',
-                        'id': 'rnfoo1'
-                    }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
-                        .c('items', {'node': 'storage:bookmarks'})
-                            .c('item', {'id': 'current'})
-                                .c('storage', {'xmlns': 'storage:bookmarks'})
-                                    .c('conference', {'name': 'The Play&apos;s the Thing',
-                                                    'autojoin': 'true',
-                                                    'jid':'theplay@conference.shakespeare.lit'})
-                                        .c('nick').t('JC');
+                return test_utils.waitUntil(() => _converse.bookmarks);
+            }).then(function () {
+                // Emit here instead of mocking fetching of bookmarks.
+                _converse.emit('bookmarksInitialized');
 
-                    _converse.connection._dataRecv(test_utils.createRequest(stanza));
-                    expect(_converse.bookmarks.length).toBe(1);
-                    expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
-                    done();
-                });
+               /* The stored data is automatically pushed to all of the user's
+                * connected resources.
+                *
+                * Publisher receives event notification
+                * -------------------------------------
+                * <message from='juliet@capulet.lit'
+                *         to='juliet@capulet.lit/balcony'
+                *         type='headline'
+                *         id='rnfoo1'>
+                * <event xmlns='http://jabber.org/protocol/pubsub#event'>
+                *     <items node='storage:bookmarks'>
+                *     <item id='current'>
+                *         <storage xmlns='storage:bookmarks'>
+                *         <conference name='The Play&apos;s the Thing'
+                *                     autojoin='true'
+                *                     jid='theplay@conference.shakespeare.lit'>
+                *             <nick>JC</nick>
+                *         </conference>
+                *         </storage>
+                *     </item>
+                *     </items>
+                * </event>
+                * </message>
+                */
+               var stanza = $msg({
+                   'from': 'dummy@localhost',
+                   'to': 'dummy@localhost/resource',
+                   'type': 'headline',
+                   'id': 'rnfoo1'
+               }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                   .c('items', {'node': 'storage:bookmarks'})
+                       .c('item', {'id': 'current'})
+                           .c('storage', {'xmlns': 'storage:bookmarks'})
+                               .c('conference', {'name': 'The Play&apos;s the Thing',
+                                               'autojoin': 'true',
+                                               'jid':'theplay@conference.shakespeare.lit'})
+                                   .c('nick').t('JC');
+               _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                return test_utils.waitUntil(() => _converse.bookmarks.length);
+            }).then(function () {
+               expect(_converse.bookmarks.length).toBe(1);
+               expect(_converse.chatboxviews.get('theplay@conference.shakespeare.lit')).not.toBeUndefined();
+               done();
             });
         }));
 

+ 162 - 2
spec/omemo.js

@@ -4,6 +4,7 @@
     var Strophe = converse.env.Strophe;
     var b64_sha1 = converse.env.b64_sha1;
     var $iq = converse.env.$iq;
+    var $msg = converse.env.$msg;
     var _ = converse.env._;
     var u = converse.env.utils;
 
@@ -17,6 +18,165 @@
             done();
         }));
 
+        it("updates the user's device list based on PEP messages",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
+            let iq_stanza;
+            test_utils.createContacts(_converse, 'current');
+            const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
+
+            test_utils.waitUntil(function () {
+                return _.filter(
+                    _converse.connection.IQ_stanzas,
+                    (iq) => {
+                        const node = iq.nodeTree.querySelector('iq[to="'+_converse.bare_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
+                        if (node) { iq_stanza = iq.nodeTree;}
+                        return node;
+                    }).length;
+            }).then(function () {
+                expect(iq_stanza.outerHTML).toBe(
+                    '<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
+                        '<query xmlns="http://jabber.org/protocol/disco#items" '+
+                               'node="eu.siacs.conversations.axolotl.devicelist"/>'+
+                    '</iq>');
+
+                const stanza = $iq({
+                    'from': contact_jid,
+                    'id': iq_stanza.getAttribute('id'),
+                    'to': _converse.bare_jid,
+                    'type': 'result',
+                }).c('query', {
+                    'xmlns': 'http://jabber.org/protocol/disco#items',
+                    'node': 'eu.siacs.conversations.axolotl.devicelist'
+                }).c('device', {'id': '555'}).up()
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(1);
+                const devicelist = _converse.devicelists.get(_converse.bare_jid);
+                expect(devicelist.devices.length).toBe(1);
+                expect(devicelist.devices.at(0).get('id')).toBe('555');
+                return test_utils.waitUntil(() => _converse.devicelists);
+            }).then(function () {
+                // We simply emit, to avoid doing all the setup work
+                _converse.emit('OMEMOInitialized');
+
+                let stanza = $msg({
+                    'from': contact_jid,
+                    'to': _converse.bare_jid,
+                    'type': 'headline',
+                    'id': 'update_01',
+                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                    .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
+                        .c('item')
+                            .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
+                                .c('device', {'id': '1234'})
+                                .c('device', {'id': '4223'})
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(2);
+                let devices = _converse.devicelists.get(contact_jid).devices;
+                expect(devices.length).toBe(2);
+                expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223');
+                expect(devices.get('1234').get('active')).toBe(true);
+                expect(devices.get('4223').get('active')).toBe(true);
+
+                stanza = $msg({
+                    'from': contact_jid,
+                    'to': _converse.bare_jid,
+                    'type': 'headline',
+                    'id': 'update_02',
+                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                    .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
+                        .c('item')
+                            .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
+                                .c('device', {'id': '4223'})
+                                .c('device', {'id': '4224'})
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(2);
+                expect(devices.length).toBe(3);
+                expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223,4224');
+                expect(devices.get('1234').get('active')).toBe(false);
+                expect(devices.get('4223').get('active')).toBe(true);
+                expect(devices.get('4224').get('active')).toBe(true);
+
+                // Check that own devicelist gets updated
+                stanza = $msg({
+                    'from': _converse.bare_jid,
+                    'to': _converse.bare_jid,
+                    'type': 'headline',
+                    'id': 'update_03',
+                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                    .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
+                        .c('item')
+                            .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
+                                .c('device', {'id': '555'})
+                                .c('device', {'id': '777'})
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(2);
+                devices = _converse.devicelists.get(_converse.bare_jid).devices;
+                expect(devices.length).toBe(3);
+                expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
+                expect(devices.get('123456789').get('active')).toBe(true);
+                expect(devices.get('555').get('active')).toBe(true);
+                expect(devices.get('777').get('active')).toBe(true);
+
+                _converse.connection.IQ_stanzas = [];
+
+                // Check that own device gets re-added
+                stanza = $msg({
+                    'from': _converse.bare_jid,
+                    'to': _converse.bare_jid,
+                    'type': 'headline',
+                    'id': 'update_03',
+                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                    .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
+                        .c('item')
+                            .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
+                                .c('device', {'id': '444'})
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                return test_utils.waitUntil(function () {
+                    return _.filter(
+                        _converse.connection.IQ_stanzas,
+                        (iq) => {
+                            const node = iq.nodeTree.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]');
+                            if (node) { iq_stanza = iq.nodeTree;}
+                            return node;
+                        }).length;
+                });
+            }).then(function () {
+                // Check that our own device is added again, but that removed
+                // devices are not added.
+                expect(iq_stanza.outerHTML).toBe(
+                    '<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
+                        '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
+                            '<publish node="eu.siacs.conversations.axolotl.devicelist">'+
+                                '<item>'+
+                                    '<list xmlns="eu.siacs.conversations.axolotl"/>'+
+                                    '<device id="123456789"/>'+
+                                    '<device id="444"/>'+
+                                '</item>'+
+                            '</publish>'+
+                        '</pubsub>'+
+                    '</iq>');
+                expect(_converse.devicelists.length).toBe(2);
+                const devices = _converse.devicelists.get(_converse.bare_jid).devices;
+                // The device id for this device (123456789) was also generated and added to the list,
+                // which is why we have 4 devices now.
+                expect(devices.length).toBe(4);
+                expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444,555,777');
+                expect(devices.get('123456789').get('active')).toBe(true);
+                expect(devices.get('444').get('active')).toBe(true);
+                expect(devices.get('555').get('active')).toBe(false);
+                expect(devices.get('777').get('active')).toBe(false);
+                done();
+            });
+        }));
+
         it("adds a toolbar button for starting an encrypted chat session",
             mock.initConverseWithPromises(
                 null, ['rosterGroupsFetched'], {},
@@ -60,7 +220,7 @@
                 test_utils.openChatBoxFor(_converse, contact_jid);
                 return test_utils.waitUntil(() => {
                     return _.filter(_converse.connection.IQ_stanzas, function (iq) {
-                        const node = iq.nodeTree.querySelector('publish[xmlns="eu.siacs.conversations.axolotl.devicelist"]');
+                        const node = iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.devicelist"]');
                         if (node) { iq_stanza = iq.nodeTree; }
                         return node;
                     }).length;
@@ -69,7 +229,7 @@
                 expect(iq_stanza.outerHTML).toBe(
                     '<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
                         '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
-                            '<publish xmlns="eu.siacs.conversations.axolotl.devicelist">'+
+                            '<publish node="eu.siacs.conversations.axolotl.devicelist">'+
                                 '<item>'+
                                     '<list xmlns="eu.siacs.conversations.axolotl"/>'+
                                     '<device id="482886413b977930064a5888b92134fe"/>'+

+ 4 - 2
src/converse-bookmarks.js

@@ -579,8 +579,10 @@
                 // Add a handler for bookmarks pushed from other connected clients
                 // (from the same user obviously)
                 _converse.connection.addHandler((message) => {
-                    if (message.querySelector('event[xmlns="'+Strophe.NS.PUBSUB+'#event"]')) {
-                        _converse.bookmarks.createBookmarksFromStanza(message);
+                    if (sizzle('event[xmlns="'+Strophe.NS.PUBSUB+'#event"] items[node="storage:bookmarks"]', message).length) {
+                        _converse.api.waitUntil('bookmarksInitialized')
+                            .then(() => _converse.bookmarks.createBookmarksFromStanza(message))
+                            .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                     }
                 }, null, 'message', 'headline', null, _converse.bare_jid);
             });

+ 41 - 29
src/converse-omemo.js

@@ -519,11 +519,11 @@
                             'from': _converse.bare_jid,
                             'type': 'set'
                         }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
-                            .c('publish', {'xmlns': Strophe.NS.OMEMO_DEVICELIST})
+                            .c('publish', {'node': Strophe.NS.OMEMO_DEVICELIST})
                                 .c('item')
                                     .c('list', {'xmlns': Strophe.NS.OMEMO}).up()
 
-                        this.devices.each((device) => {
+                        _.each(this.devices.where({'active': true}), (device) => {
                             stanza.c('device', {'id': device.get('id')}).up();
                         });
                         _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT);
@@ -584,48 +584,59 @@
                 /* If our own device is not on the list, add it.
                  * Also, deduplicate devices if necessary.
                  */
-                return new Promise((resolve, reject) => {
-                    restoreOMEMOSession().then(() => {
-                        const devicelist = _converse.devicelists.get(_converse.bare_jid);
-                        const device_id = _converse.omemo_store.get('device_id');
-                        if (!devicelist.devices.findWhere({'id': device_id})) {
-                            return devicelist.addDeviceToList(device_id).then(resolve).catch(reject);
-                        }
-                        resolve();
-                    }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
-                });
+                const devicelist = _converse.devicelists.get(_converse.bare_jid),
+                      device_id = _converse.omemo_store.get('device_id'),
+                      own_device = devicelist.devices.findWhere({'id': device_id});
+
+                if (!own_device) {
+                    return devicelist.addDeviceToList(device_id);
+                } else if (!own_device.get('active')) {
+                    own_device.set('active', true, {'silent': true});
+                    return devicelist.addDeviceToList(device_id);
+                } else {
+                    return Promise.resolve();
+                }
             }
 
 
             function updateBundleFromStanza (stanza) {
-                const items_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}"]`, stanza),
-                      device_id = items_el.getAttribute('node').split(':')[1],
+                const items_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}"]`, stanza).pop();
+                if (!items_el) {
+                    return;
+                }
+                const device_id = items_el.getAttribute('node').split(':')[1],
                       from = stanza.getAttribute('from'),
                       bundle_el = sizzle(`item list[xmlns="${Strophe.NS.OMEMO}"] bundle`, items_el).pop(),
-                      bundle = parseBundle(bundle_el);
-
-                const device = _converse.devicelists.get(from).devices.get(device_id);
-                device.save({'bundle': bundle});
+                      device = _converse.devicelists.get(from).devices.get(device_id);
+                device.save({'bundle': parseBundle(bundle_el)});
             }
 
             function updateDevicesFromStanza (stanza) {
-                // TODO: check whether our own device_id is still on the list,
-                // otherwise we need to update it.
+                const items_el = sizzle(`items[node="${Strophe.NS.OMEMO_DEVICELIST}"]`, stanza).pop();
+                if (!items_el) {
+                    return;
+                }
                 const device_ids = _.map(
-                    sizzle(`items[node="${Strophe.NS.OMEMO_DEVICELIST}"] item list[xmlns="${Strophe.NS.OMEMO}"] device`, stanza),
-                    (device) => device.getAttribute('id'));
-
-                const removed_ids = _.difference(_converse.devices.pluck('id'), device_ids);
-                _.forEach(removed_ids, (removed_id) => _converse.devices.get(removed_id).set('active', false));
+                    sizzle(`item list[xmlns="${Strophe.NS.OMEMO}"] device`, items_el),
+                    (device) => device.getAttribute('id')
+                );
+                const jid = stanza.getAttribute('from'),
+                      devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid}),
+                      devices = devicelist.devices,
+                      removed_ids = _.difference(devices.pluck('id'), device_ids);
 
+                _.forEach(removed_ids, (removed_id) => devices.get(removed_id).set('active', false));
                 _.forEach(device_ids, (device_id) => {
-                    const dev = _converse.devices.get(device_id);
+                    const dev = devices.get(device_id);
                     if (dev) {
                         dev.save({'active': true});
                     } else {
-                        _converse.devices.create({'id': device_id})
+                        devices.create({'id': device_id})
                     }
                 });
+                // Make sure our own device is on the list (i.e. if it was
+                // removed, add it again.
+                updateOwnDeviceList();
             }
 
             function registerPEPPushHandler () {
@@ -634,9 +645,9 @@
                     if (message.querySelector('event[xmlns="'+Strophe.NS.PUBSUB+'#event"]')) {
                         updateDevicesFromStanza(message);
                         updateBundleFromStanza(message);
-                        updateOwnDeviceList();
                     }
-                }, null, 'message', 'headline', null, _converse.bare_jid);
+                    return true;
+                }, null, 'message', 'headline');
             }
 
             function restoreOMEMOSession () {
@@ -655,6 +666,7 @@
                     b64_sha1(`converse.devicelists-${_converse.bare_jid}`)
                 );
                 fetchOwnDevices()
+                    .then(() => restoreOMEMOSession())
                     .then(() => updateOwnDeviceList())
                     .then(() => publishBundle())
                     .then(() => _converse.emit('OMEMOInitialized'))