Browse Source

Add test that the OMEMO toolbar button renders

Fix and improve accordingly. updates #497
JC Brand 7 years ago
parent
commit
6042c233bc
12 changed files with 231 additions and 74 deletions
  1. 4 3
      .eslintrc.json
  2. 2 2
      css/converse.css
  3. 2 2
      css/inverse.css
  4. 1 1
      sass/_chatbox.scss
  5. 1 34
      spec/chatbox.js
  6. 20 18
      spec/chatroom.js
  7. 1 1
      spec/converse.js
  8. 156 0
      spec/omemo.js
  9. 2 0
      src/converse-core.js
  10. 40 12
      src/converse-omemo.js
  11. 1 0
      src/converse.js
  12. 1 1
      src/templates/toolbar_omemo.html

+ 4 - 3
.eslintrc.json

@@ -9,10 +9,11 @@
     "plugins": ["lodash"],
     "plugins": ["lodash"],
     "extends": ["eslint:recommended", "plugin:lodash/canonical"],
     "extends": ["eslint:recommended", "plugin:lodash/canonical"],
     "globals": {
     "globals": {
-        "window": true,
-        "sinon": true,
+        "Promise": true,
         "define": true,
         "define": true,
-        "require": true
+        "require": true,
+        "sinon": true,
+        "window": true
     },
     },
     "rules": {
     "rules": {
         "lodash/prefer-lodash-method": [2, {
         "lodash/prefer-lodash-method": [2, {

+ 2 - 2
css/converse.css

@@ -7331,8 +7331,8 @@ body.reset {
     min-height: 1px;
     min-height: 1px;
     padding-right: 15px;
     padding-right: 15px;
     padding-left: 15px;
     padding-left: 15px;
-    flex: 0 0 33.3333333333%;
-    max-width: 33.3333333333%;
+    flex: 0 0 25%;
+    max-width: 25%;
     padding: 0; }
     padding: 0; }
   #conversejs .chat-head .user-custom-message {
   #conversejs .chat-head .user-custom-message {
     color: white;
     color: white;

+ 2 - 2
css/inverse.css

@@ -7373,8 +7373,8 @@ body {
     min-height: 1px;
     min-height: 1px;
     padding-right: 15px;
     padding-right: 15px;
     padding-left: 15px;
     padding-left: 15px;
-    flex: 0 0 33.3333333333%;
-    max-width: 33.3333333333%;
+    flex: 0 0 25%;
+    max-width: 25%;
     padding: 0; }
     padding: 0; }
   #conversejs .chat-head .user-custom-message {
   #conversejs .chat-head .user-custom-message {
     color: white;
     color: white;

+ 1 - 1
sass/_chatbox.scss

@@ -56,7 +56,7 @@
         .chatbox-buttons {
         .chatbox-buttons {
             flex-direction: row-reverse;
             flex-direction: row-reverse;
             @include make-col-ready();
             @include make-col-ready();
-            @include make-col(4);
+            @include make-col(3);
             padding: 0;
             padding: 0;
         }
         }
 
 

+ 1 - 34
spec/chatbox.js

@@ -431,7 +431,7 @@
                     expect(view).toBeDefined();
                     expect(view).toBeDefined();
                     var $toolbar = $(view.el).find('ul.chat-toolbar');
                     var $toolbar = $(view.el).find('ul.chat-toolbar');
                     expect($toolbar.length).toBe(1);
                     expect($toolbar.length).toBe(1);
-                    expect($toolbar.children('li').length).toBe(2);
+                    expect($toolbar.children('li').length).toBe(1);
                     done();
                     done();
                 }));
                 }));
 
 
@@ -496,39 +496,6 @@
                     }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                     }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
                 }));
                 }));
 
 
-                it("contains a button for starting an encrypted chat session",
-                    mock.initConverseWithPromises(
-                        null, ['rosterGroupsFetched'], {},
-                        function (done, _converse) {
-
-                    var timeout = true, $toolbar, view;
-                    test_utils.createContacts(_converse, 'current');
-                    test_utils.openControlBox();
-
-                    test_utils.waitUntil(function () {
-                        return $(_converse.rosterview.el).find('.roster-group').length;
-                    }, 300).then(function () {
-                        // TODO: More tests can be added here...
-                        var contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
-                        test_utils.openChatBoxFor(_converse, contact_jid);
-                        view = _converse.chatboxviews.get(contact_jid);
-                        $toolbar = $(view.el).find('ul.chat-toolbar');
-                        expect($toolbar.find('.toggle-otr').length).toBe(1);
-                        // Register spies
-                        spyOn(view, 'toggleOTRMenu').and.callThrough();
-                        view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
-
-                        timeout = false;
-                        $toolbar[0].querySelector('.toggle-otr').click();
-                        return test_utils.waitUntil(function () {
-                            return view.el.querySelector('.otr-menu').offsetHeight;
-                        }, 300)
-                    }).then(function () {
-                        expect(view.toggleOTRMenu).toHaveBeenCalled();
-                        done();
-                    });
-                }));
-
                 it("can contain a button for starting a call",
                 it("can contain a button for starting a call",
                     mock.initConverseWithPromises(
                     mock.initConverseWithPromises(
                         null, ['rosterGroupsFetched'], {},
                         null, ['rosterGroupsFetched'], {},

+ 20 - 18
spec/chatroom.js

@@ -2886,6 +2886,7 @@
                     null, ['rosterGroupsFetched'], {},
                     null, ['rosterGroupsFetched'], {},
                     function (done, _converse) {
                     function (done, _converse) {
 
 
+                var muc_iq;
                 var sent_IQs = [], IQ_ids = [];
                 var sent_IQs = [], IQ_ids = [];
                 var sendIQ = _converse.connection.sendIQ;
                 var sendIQ = _converse.connection.sendIQ;
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
@@ -3008,30 +3009,31 @@
                             'jid': 'crone1@shakespeare.lit',
                             'jid': 'crone1@shakespeare.lit',
                         });
                         });
                 _converse.connection._dataRecv(test_utils.createRequest(owner_list_stanza));
                 _converse.connection._dataRecv(test_utils.createRequest(owner_list_stanza));
-
-                test_utils.waitUntil(function () {
-                    return IQ_ids.length;
-                }, 300).then(function () {
+                test_utils.waitUntil(() => {
+                    return _.filter(
+                        _converse.connection.IQ_stanzas,
+                        (iq) => {
+                            if (iq.nodeTree.getAttribute('to') === 'coven@chat.shakespeare.lit' && iq.nodeTree.getAttribute('type') === 'set') {
+                                muc_iq = iq;
+                                return true;
+                            }
+                            return false;
+                        }).length;
+                }).then(function () {
                     // Check that the member list now gets updated
                     // Check that the member list now gets updated
-                    var iq = "<iq to='coven@chat.shakespeare.lit' type='set' xmlns='jabber:client' id='"+IQ_ids.pop()+"'>"+
+                    expect(muc_iq.toLocaleString()).toBe("<iq to='coven@chat.shakespeare.lit' type='set' xmlns='jabber:client' id='"+muc_iq.nodeTree.getAttribute('id')+"'>"+
                             "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
                             "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
                                 "<item affiliation='member' jid='"+invitee_jid+"'>"+
                                 "<item affiliation='member' jid='"+invitee_jid+"'>"+
                                     "<reason>Please join this chat room</reason>"+
                                     "<reason>Please join this chat room</reason>"+
                                 "</item>"+
                                 "</item>"+
                             "</query>"+
                             "</query>"+
-                        "</iq>";
-
-                    test_utils.waitUntil(function () {
-                        return _.includes(_.invokeMap(sent_IQs, Object.prototype.toLocaleString), iq);
-                    }, 300).then(function () {
-                        // Finally check that the user gets invited.
-                        expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
-                            "<message from='dummy@localhost/resource' to='"+invitee_jid+"' id='"+sent_id+"' xmlns='jabber:client'>"+
-                                "<x xmlns='jabber:x:conference' jid='coven@chat.shakespeare.lit' reason='Please join this chat room'/>"+
-                            "</message>"
-                        );
-                        done();
-                    });
+                        "</iq>");
+                    // Finally check that the user gets invited.
+                    expect(sent_stanza.toLocaleString()).toBe( // Strophe adds the xmlns attr (although not in spec)
+                        "<message from='dummy@localhost/resource' to='"+invitee_jid+"' id='"+sent_id+"' xmlns='jabber:client'>"+
+                            "<x xmlns='jabber:x:conference' jid='coven@chat.shakespeare.lit' reason='Please join this chat room'/>"+
+                        "</message>");
+                    done();
                 });
                 });
             }));
             }));
         });
         });

+ 1 - 1
spec/converse.js

@@ -317,7 +317,7 @@
                 expect(box.get('box_id')).toBe(b64_sha1(jid));
                 expect(box.get('box_id')).toBe(b64_sha1(jid));
                 expect(
                 expect(
                     _.keys(box),
                     _.keys(box),
-                    ['close', 'endOTR', 'focus', 'get', 'initiateOTR', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
+                    ['close', 'focus', 'get', 'is_chatroom', 'maximize', 'minimize', 'open', 'set']
                 );
                 );
                 chatboxview = _converse.chatboxviews.get(jid);
                 chatboxview = _converse.chatboxviews.get(jid);
                 expect($(chatboxview.el).is(':visible')).toBeTruthy();
                 expect($(chatboxview.el).is(':visible')).toBeTruthy();

+ 156 - 0
spec/omemo.js

@@ -0,0 +1,156 @@
+(function (root, factory) {
+    define(["jasmine", "mock", "converse-core", "test-utils"], factory);
+} (this, function (jasmine, mock, converse, test_utils) {
+    var Strophe = converse.env.Strophe;
+    var b64_sha1 = converse.env.b64_sha1;
+    var $iq = converse.env.$iq;
+    var _ = converse.env._;
+
+    window.libsignal = {
+        'KeyHelper': {
+            'generateIdentityKeyPair': function () {
+                return Promise.resolve({
+                    'pubKey': 1234,
+                    'privKey': 4321
+                });
+            },
+            'generateRegistrationId': function () {
+                return 1234;
+            }
+        }
+    };
+
+    describe("The OMEMO module", function() {
+
+        it("will add processing hints to sent out encrypted <message> stanzas",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+            // TODO
+            done();
+        }));
+
+        it("adds a toolbar button for starting an encrypted chat session",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
+            let devicelist_iq,
+                disco_info_iq;
+            test_utils.createContacts(_converse, 'current');
+            const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
+
+            test_utils.waitUntil(function () {
+                return _.filter(_converse.connection.IQ_stanzas, function (iq) {
+                    const node = iq.nodeTree.querySelector('iq[to="dummy@localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
+                    if (node) { disco_info_iq = iq; }
+                    return node;
+                }).length > 0;
+            }, 1000).then(function () {
+                /* PEP support is prerequisite for OMEMO */
+                const stanza = $iq({
+                    'type': 'result',
+                    'from': 'dummy@localhost',
+                    'to': 'dummy@localhost/resource',
+                    'id': disco_info_iq.nodeTree.getAttribute('id'),
+                }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
+                    .c('identity', {
+                        'category': 'pubsub',
+                        'type': 'pep'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+                return test_utils.waitUntil(() => {
+                    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) {
+                                devicelist_iq = iq;
+                            }
+                            return node;
+                        }).length;
+                });
+            }).then(function () {
+                expect(devicelist_iq.toLocaleString()).toBe(
+                    "<iq type='get' from='dummy@localhost' to='dummy@localhost' xmlns='jabber:client' id='"+devicelist_iq.nodeTree.getAttribute('id')+"'>"+
+                        "<query xmlns='http://jabber.org/protocol/disco#items' "+
+                               "node='eu.siacs.conversations.axolotl.devicelist'/>"+
+                    "</iq>");
+                const stanza = $iq({
+                    'from': contact_jid,
+                    'id': devicelist_iq.nodeTree.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': '482886413b977930064a5888b92134fe'}).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('482886413b977930064a5888b92134fe');
+
+                test_utils.openChatBoxFor(_converse, contact_jid);
+                return test_utils.waitUntil(() => {
+                    return _.filter(
+                        _converse.connection.IQ_stanzas,
+                        (iq) => {
+                            const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
+                            if (node) {
+                                devicelist_iq = iq;
+                            }
+                            return node;
+                        }).length;});
+            }).then(function () {
+                expect(devicelist_iq.toLocaleString()).toBe(
+                    "<iq type='get' from='dummy@localhost' to='"+contact_jid+"' xmlns='jabber:client' id='"+devicelist_iq.nodeTree.getAttribute('id')+"'>"+
+                        "<query xmlns='http://jabber.org/protocol/disco#items' "+
+                               "node='eu.siacs.conversations.axolotl.devicelist'/>"+
+                    "</iq>");
+                const stanza = $iq({
+                    'from': contact_jid,
+                    'id': devicelist_iq.nodeTree.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': '368866411b877c30064a5f62b917cffe'}).up()
+                  .c('device', {'id': '3300659945416e274474e469a1f0154c'}).up()
+                  .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
+                  .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(2);
+                const devicelist = _converse.devicelists.get(contact_jid);
+                expect(devicelist.devices.length).toBe(4);
+                expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe');
+                expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
+                expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
+                expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
+                return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
+            }).then(function () {
+                const view = _converse.chatboxviews.get(contact_jid);
+                const toolbar = view.el.querySelector('.chat-toolbar');
+                expect(_.isNull(toolbar.querySelector('.toggle-omemo'))).toBe(false);
+                spyOn(view, 'toggleOMEMO').and.callThrough();
+                view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
+                toolbar.querySelector('.toggle-omemo').click();
+                expect(view.toggleOMEMO).toHaveBeenCalled();
+                done();
+            });
+        }));
+    });
+
+    describe("A chatbox with an active OMEMO session", function() {
+
+        it("will not show the spoiler toolbar button",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+            // TODO
+            done()
+        }));
+    });
+}));

+ 2 - 0
src/converse-core.js

@@ -131,6 +131,8 @@
     _converse.OPENED = 'opened';
     _converse.OPENED = 'opened';
     _converse.PREBIND = "prebind";
     _converse.PREBIND = "prebind";
 
 
+    _converse.IQ_TIMEOUT = 20000;
+
     _converse.CONNECTION_STATUS = {
     _converse.CONNECTION_STATUS = {
         0: 'ERROR',
         0: 'ERROR',
         1: 'CONNECTING',
         1: 'CONNECTING',

+ 40 - 12
src/converse-omemo.js

@@ -7,12 +7,11 @@
 (function (root, factory) {
 (function (root, factory) {
     define([
     define([
         "converse-core",
         "converse-core",
-        "tpl!toolbar_omemo",
-        "libsignal"
+        "tpl!toolbar_omemo"
     ], factory);
     ], factory);
-}(this, function (converse, tpl_toolbar_omemo, libsignal) {
+}(this, function (converse, tpl_toolbar_omemo) {
 
 
-    const { Backbone, Promise, Strophe, sizzle, $build, _, b64_sha1 } = converse.env;
+    const { Backbone, Promise, Strophe, sizzle, $iq, _, b64_sha1 } = converse.env;
 
 
     Strophe.addNamespace('OMEMO', "eu.siacs.conversations.axolotl");
     Strophe.addNamespace('OMEMO', "eu.siacs.conversations.axolotl");
     Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO+".devicelist");
     Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO+".devicelist");
@@ -23,11 +22,16 @@
     const TRUSTED = 1;
     const TRUSTED = 1;
     const UNTRUSTED = -1;
     const UNTRUSTED = -1;
 
 
+
     function getDevicesForContact (_converse, jid) {
     function getDevicesForContact (_converse, jid) {
         return new Promise((resolve, reject) => {
         return new Promise((resolve, reject) => {
             _converse.api.waitUntil('OMEMOInitialized').then(() => {
             _converse.api.waitUntil('OMEMOInitialized').then(() => {
-                const devicelist = _converse.devicelists.get(jid);
-                resolve(devicelist ? devicelist.devices : []);
+                let devicelist = _converse.devicelists.get(jid);
+                if (_.isNil(devicelist)) {
+                    devicelist = _converse.devicelists.create({'jid': jid});
+                }
+                devicelist.fetchDevices().then(() => resolve(devicelist.devices));
+
             }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
             }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
         });
         });
     }
     }
@@ -55,6 +59,14 @@
 
 
         overrides: {
         overrides: {
             ChatBoxView:  {
             ChatBoxView:  {
+                events: {
+                    'click .toggle-omemo': 'toggleOMEMO',
+                },
+
+                toggleOMEMO (ev) {
+                    // TODO:
+                    ev.preventDefault();
+                },
 
 
                 addOMEMOToolbarButton (options) {
                 addOMEMOToolbarButton (options) {
                     const { _converse } = this.__super__,
                     const { _converse } = this.__super__,
@@ -65,13 +77,12 @@
                     ]).then((support) => {
                     ]).then((support) => {
                         const client_supports = support[0],
                         const client_supports = support[0],
                               server_supports = support[1];
                               server_supports = support[1];
-
                         if (client_supports && server_supports) {
                         if (client_supports && server_supports) {
                             this.el.querySelector('.chat-toolbar').insertAdjacentHTML(
                             this.el.querySelector('.chat-toolbar').insertAdjacentHTML(
                                 'beforeend',
                                 'beforeend',
                                 tpl_toolbar_omemo({'__': __}));
                                 tpl_toolbar_omemo({'__': __}));
                         }
                         }
-                    }, _.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+                    }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
                 },
                 },
 
 
                 renderToolbar (toolbar, options) {
                 renderToolbar (toolbar, options) {
@@ -92,7 +103,6 @@
 
 
 
 
             _converse.OMEMOSession = Backbone.Model.extend({
             _converse.OMEMOSession = Backbone.Model.extend({
-
                 initialize () {
                 initialize () {
                     this.keyhelper = window.libsignal.KeyHelper;
                     this.keyhelper = window.libsignal.KeyHelper;
                 },
                 },
@@ -159,10 +169,28 @@
                 },
                 },
 
 
                 fetchDevicesFromServer () {
                 fetchDevicesFromServer () {
-                    // TODO: send IQ stanza to get device list.
-                    return Promise.resolve([]);
+                    return new Promise((resolve, reject) => {
+                        const stanza = $iq({
+                            'type': 'get',
+                            'from': _converse.bare_jid,
+                            'to': this.get('jid')
+                        }).c('query', {
+                            'xmlns': Strophe.NS.DISCO_ITEMS,
+                            'node': Strophe.NS.OMEMO_DEVICELIST
+                        });
+                        _converse.connection.sendIQ(
+                            stanza,
+                            (iq) => {
+                                _.forEach(
+                                    iq.querySelectorAll('device'),
+                                    (dev) => this.devices.create({'id': dev.getAttribute('id')})
+                                );
+                                resolve();
+                            },
+                            reject,
+                            _converse.IQ_TIMEOUT);
+                    });
                 }
                 }
-
             });
             });
 
 
             _converse.DeviceLists = Backbone.Collection.extend({
             _converse.DeviceLists = Backbone.Collection.extend({

+ 1 - 0
src/converse.js

@@ -21,6 +21,7 @@ if (typeof define !== 'undefined') {
         "converse-muc-views",
         "converse-muc-views",
         "converse-muc-views",       // Views related to MUC
         "converse-muc-views",       // Views related to MUC
         "converse-notification",    // HTML5 Notifications
         "converse-notification",    // HTML5 Notifications
+        "converse-omemo",
         "converse-ping",            // XEP-0199 XMPP Ping
         "converse-ping",            // XEP-0199 XMPP Ping
         "converse-roster",
         "converse-roster",
         "converse-register",        // XEP-0077 In-band registration
         "converse-register",        // XEP-0077 In-band registration

+ 1 - 1
src/templates/toolbar_omemo.html

@@ -1 +1 @@
-<li class="toggle-omemo fa fa-unlock" title="{{{__('Messages are being sent in plaintext')}}}"></li>
+<li class="toggle-omemo fa fa-unlock" title="{{{o.__('Messages are being sent in plaintext')}}}"></li>