Browse Source

Add option to deregister nickname when closing a MUC

By setting `auto_register_muc_nickname` to `'unregister'`
JC Brand 4 years ago
parent
commit
59d55b3526

+ 1 - 0
CHANGES.md

@@ -24,6 +24,7 @@
 - Use the MUC stanza id when sending XEP-0333 markers
 - Use the MUC stanza id when sending XEP-0333 markers
 - Add support for rendering unfurls via [mod_ogp](https://modules.prosody.im/mod_ogp.html)
 - Add support for rendering unfurls via [mod_ogp](https://modules.prosody.im/mod_ogp.html)
 - Add a Description Of A Project (DOAP) file
 - Add a Description Of A Project (DOAP) file
+- Add ability to deregister nickname when closing a MUC by setting `auto_register_muc_nickname` to `'unregister'`.
 
 
 ### Breaking Changes
 ### Breaking Changes
 
 

+ 7 - 2
docs/source/configuration.rst

@@ -435,12 +435,17 @@ auto_register_muc_nickname
 --------------------------
 --------------------------
 
 
 * Default: ``false``
 * Default: ``false``
+* Allowed values: ``false``, ``true``, ``'unregister'``
 
 
-Determines whether Converse should automatically register a user's nickname
-when they enter a groupchat.
+If truthy, Converse will automatically register a user's nickname upon entering
+a groupchat.
 
 
 See here fore more details: https://xmpp.org/extensions/xep-0045.html#register
 See here fore more details: https://xmpp.org/extensions/xep-0045.html#register
 
 
+If set to ``'unregister'``, then the user's nickname will be registered
+(because it's a truthy value) and also be unregistered when the user
+permanently leaves the MUC by closing it.
+
 auto_subscribe
 auto_subscribe
 --------------
 --------------
 
 

+ 1 - 0
karma.conf.js

@@ -33,6 +33,7 @@ module.exports = function(config) {
       { pattern: "src/headless/plugins/chat/tests/api.js", type: 'module' },
       { pattern: "src/headless/plugins/chat/tests/api.js", type: 'module' },
       { pattern: "src/headless/plugins/disco/tests/disco.js", type: 'module' },
       { pattern: "src/headless/plugins/disco/tests/disco.js", type: 'module' },
       { pattern: "src/headless/plugins/muc/tests/affiliations.js", type: 'module' },
       { pattern: "src/headless/plugins/muc/tests/affiliations.js", type: 'module' },
+      { pattern: "src/headless/plugins/muc/tests/registration.js", type: 'module' },
       { pattern: "src/headless/plugins/ping/tests/ping.js", type: 'module' },
       { pattern: "src/headless/plugins/ping/tests/ping.js", type: 'module' },
       { pattern: "src/headless/plugins/roster/tests/presence.js", type: 'module' },
       { pattern: "src/headless/plugins/roster/tests/presence.js", type: 'module' },
       { pattern: "src/headless/plugins/smacks/tests/smacks.js", type: 'module' },
       { pattern: "src/headless/plugins/smacks/tests/smacks.js", type: 'module' },

+ 2 - 1
spec/mock.js

@@ -322,7 +322,8 @@ mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[
     const affs = _converse.muc_fetch_members;
     const affs = _converse.muc_fetch_members;
     const all_affiliations = Array.isArray(affs) ? affs :  (affs ? ['member', 'admin', 'owner'] : []);
     const all_affiliations = Array.isArray(affs) ? affs :  (affs ? ['member', 'admin', 'owner'] : []);
     await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
     await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
-    return model.messages.fetched;
+    await model.messages.fetched;
+    return model;
 };
 };
 
 
 mock.createContact = async function (_converse, name, ask, requesting, subscription) {
 mock.createContact = async function (_converse, name, ask, requesting, subscription) {

+ 34 - 13
src/headless/plugins/muc/muc.js

@@ -845,6 +845,12 @@ const ChatRoomMixin = {
 
 
     async close (ev) {
     async close (ev) {
         await this.leave();
         await this.leave();
+        if (
+            api.settings.get('auto_register_muc_nickname') === 'unregister' &&
+            (await api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid')))
+        ) {
+            this.unregisterNickname();
+        }
         this.occupants.clearStore();
         this.occupants.clearStore();
 
 
         if (ev?.name !== 'closeAllChatBoxes' && api.settings.get('muc_clear_messages_on_leave')) {
         if (ev?.name !== 'closeAllChatBoxes' && api.settings.get('muc_clear_messages_on_leave')) {
@@ -1533,7 +1539,7 @@ const ChatRoomMixin = {
 
 
     async registerNickname () {
     async registerNickname () {
         // See https://xmpp.org/extensions/xep-0045.html#register
         // See https://xmpp.org/extensions/xep-0045.html#register
-        const __ = _converse.__;
+        const { __ } = _converse;
         const nick = this.get('nick');
         const nick = this.get('nick');
         const jid = this.get('jid');
         const jid = this.get('jid');
         let iq, err_msg;
         let iq, err_msg;
@@ -1541,7 +1547,6 @@ const ChatRoomMixin = {
             iq = await api.sendIQ(
             iq = await api.sendIQ(
                 $iq({
                 $iq({
                     'to': jid,
                     'to': jid,
-                    'from': _converse.connection.jid,
                     'type': 'get'
                     'type': 'get'
                 }).c('query', { 'xmlns': Strophe.NS.MUC_REGISTER })
                 }).c('query', { 'xmlns': Strophe.NS.MUC_REGISTER })
             );
             );
@@ -1562,19 +1567,13 @@ const ChatRoomMixin = {
             await api.sendIQ(
             await api.sendIQ(
                 $iq({
                 $iq({
                     'to': jid,
                     'to': jid,
-                    'from': _converse.connection.jid,
                     'type': 'set'
                     'type': 'set'
-                })
-                    .c('query', { 'xmlns': Strophe.NS.MUC_REGISTER })
+                }).c('query', { 'xmlns': Strophe.NS.MUC_REGISTER })
                     .c('x', { 'xmlns': Strophe.NS.XFORM, 'type': 'submit' })
                     .c('x', { 'xmlns': Strophe.NS.XFORM, 'type': 'submit' })
-                    .c('field', { 'var': 'FORM_TYPE' })
-                    .c('value')
-                    .t('http://jabber.org/protocol/muc#register')
-                    .up()
-                    .up()
-                    .c('field', { 'var': 'muc#register_roomnick' })
-                    .c('value')
-                    .t(nick)
+                        .c('field', { 'var': 'FORM_TYPE' })
+                            .c('value').t('http://jabber.org/protocol/muc#register').up().up()
+                        .c('field', { 'var': 'muc#register_roomnick' })
+                            .c('value').t(nick)
             );
             );
         } catch (e) {
         } catch (e) {
             if (sizzle(`service-unavailable[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
             if (sizzle(`service-unavailable[xmlns="${Strophe.NS.STANZAS}"]`, e).length) {
@@ -1588,6 +1587,28 @@ const ChatRoomMixin = {
         }
         }
     },
     },
 
 
+    async unregisterNickname () {
+        const jid = this.get('jid');
+        let iq;
+        try {
+            iq = await api.sendIQ(
+                $iq({
+                    'to': jid,
+                    'type': 'set'
+                }).c('query', { 'xmlns': Strophe.NS.MUC_REGISTER })
+            );
+        } catch (e) {
+            log.error(e);
+            return e;
+        }
+        if (sizzle(`query[xmlns="${Strophe.NS.MUC_REGISTER}"] registered`, iq).pop()) {
+            const iq = $iq({ 'to': jid, 'type': 'set' })
+                .c('query', { 'xmlns': Strophe.NS.MUC_REGISTER })
+                .c('remove');
+            return api.sendIQ(iq).catch(e => log.error(e));
+        }
+    },
+
     /**
     /**
      * Given a presence stanza, update the occupant model based on its contents.
      * Given a presence stanza, update the occupant model based on its contents.
      * @private
      * @private

+ 116 - 0
src/headless/plugins/muc/tests/registration.js

@@ -0,0 +1,116 @@
+/*global mock, converse */
+
+const { $iq, Strophe, sizzle, u } = converse.env;
+
+describe("Chatrooms", function () {
+
+    describe("The auto_register_muc_nickname option", function () {
+
+        it("allows you to automatically register your nickname when joining a room",
+                mock.initConverse(['chatBoxesFetched'], {'auto_register_muc_nickname': true},
+                async function (done, _converse) {
+
+            const muc_jid = 'coven@chat.shakespeare.lit';
+            const room = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
+
+            let stanza = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
+                iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
+            ).pop());
+
+            expect(Strophe.serialize(stanza))
+            .toBe(`<iq id="${stanza.getAttribute('id')}" to="${muc_jid}" `+
+                        `type="get" xmlns="jabber:client">`+
+                    `<query xmlns="jabber:iq:register"/></iq>`);
+            const result = $iq({
+                'from': room.get('jid'),
+                'id': stanza.getAttribute('id'),
+                'to': _converse.bare_jid,
+                'type': 'result',
+            }).c('query', {'xmlns': 'jabber:iq:register'})
+                .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
+                    .c('field', {
+                        'label': 'Desired Nickname',
+                        'type': 'text-single',
+                        'var': 'muc#register_roomnick'
+                    }).c('required');
+            _converse.connection._dataRecv(mock.createRequest(result));
+            stanza = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
+                iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
+            ).pop());
+
+            expect(Strophe.serialize(stanza)).toBe(
+                `<iq id="${stanza.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
+                    `<query xmlns="jabber:iq:register">`+
+                        `<x type="submit" xmlns="jabber:x:data">`+
+                            `<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
+                            `<field var="muc#register_roomnick"><value>romeo</value></field>`+
+                        `</x>`+
+                    `</query>`+
+                `</iq>`);
+            done();
+        }));
+
+        it("allows you to automatically deregister your nickname when closing a room",
+                mock.initConverse(['chatBoxesFetched'], {'auto_register_muc_nickname': 'unregister'},
+                async function (done, _converse) {
+
+            const muc_jid = 'coven@chat.shakespeare.lit';
+            const room = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
+
+            let stanza = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
+                iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
+            ).pop());
+            let result = $iq({
+                'from': room.get('jid'),
+                'id': stanza.getAttribute('id'),
+                'to': _converse.bare_jid,
+                'type': 'result',
+            }).c('query', {'xmlns': 'jabber:iq:register'})
+                .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
+                    .c('field', {
+                        'label': 'Desired Nickname',
+                        'type': 'text-single',
+                        'var': 'muc#register_roomnick'
+                    }).c('required');
+            _converse.connection._dataRecv(mock.createRequest(result));
+            await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
+                iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
+            ).pop());
+
+            _converse.connection.IQ_stanzas = [];
+            room.close();
+            stanza = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
+                iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
+            ).pop());
+            _converse.connection.IQ_stanzas = [];
+
+            result = $iq({
+                'from': room.get('jid'),
+                'id': stanza.getAttribute('id'),
+                'to': _converse.bare_jid,
+                'type': 'result',
+            }).c('query', {'xmlns': 'jabber:iq:register'})
+                .c('registered').up()
+                .c('username').t('romeo');
+            _converse.connection._dataRecv(mock.createRequest(result));
+
+            stanza = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
+                iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
+            ).pop());
+            expect(Strophe.serialize(stanza)).toBe(
+                `<iq id="${stanza.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
+                    `<query xmlns="jabber:iq:register"><remove/></query>`+
+                `</iq>`);
+
+            result = $iq({
+                'from': room.get('jid'),
+                'id': stanza.getAttribute('id'),
+                'to': _converse.bare_jid,
+                'type': 'result',
+            }).c('query', {'xmlns': 'jabber:iq:register'});
+            _converse.connection._dataRecv(mock.createRequest(result));
+
+            done();
+        }));
+    });
+});

+ 6 - 62
src/plugins/muc-views/tests/muc-registration.js

@@ -1,9 +1,6 @@
-/*global mock, converse, _ */
+/*global mock, converse */
 
 
-const $iq = converse.env.$iq,
-      Strophe = converse.env.Strophe,
-      sizzle = converse.env.sizzle,
-      u = converse.env.utils;
+const { $iq, Strophe, sizzle, u } = converse.env;
 
 
 describe("Chatrooms", function () {
 describe("Chatrooms", function () {
 
 
@@ -24,12 +21,11 @@ describe("Chatrooms", function () {
                 preventDefault: function preventDefault () {},
                 preventDefault: function preventDefault () {},
                 keyCode: 13
                 keyCode: 13
             });
             });
-            let stanza = await u.waitUntil(() => _.filter(
-                _converse.connection.IQ_stanzas,
+            let stanza = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
                 iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
                 iq => sizzle(`iq[to="${muc_jid}"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
             ).pop());
             ).pop());
             expect(Strophe.serialize(stanza))
             expect(Strophe.serialize(stanza))
-                .toBe(`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
+                .toBe(`<iq id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
                             `type="get" xmlns="jabber:client">`+
                             `type="get" xmlns="jabber:client">`+
                         `<query xmlns="jabber:iq:register"/></iq>`);
                         `<query xmlns="jabber:iq:register"/></iq>`);
             const result = $iq({
             const result = $iq({
@@ -45,64 +41,12 @@ describe("Chatrooms", function () {
                         'var': 'muc#register_roomnick'
                         'var': 'muc#register_roomnick'
                     }).c('required');
                     }).c('required');
             _converse.connection._dataRecv(mock.createRequest(result));
             _converse.connection._dataRecv(mock.createRequest(result));
-            stanza = await u.waitUntil(() => _.filter(
-                _converse.connection.IQ_stanzas,
+            stanza = await u.waitUntil(() => _converse.connection.IQ_stanzas.filter(
                 iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
                 iq => sizzle(`iq[to="${muc_jid}"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
             ).pop());
             ).pop());
 
 
             expect(Strophe.serialize(stanza)).toBe(
             expect(Strophe.serialize(stanza)).toBe(
-                `<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
-                    `<query xmlns="jabber:iq:register">`+
-                        `<x type="submit" xmlns="jabber:x:data">`+
-                            `<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
-                            `<field var="muc#register_roomnick"><value>romeo</value></field>`+
-                        `</x>`+
-                    `</query>`+
-                `</iq>`);
-            done();
-        }));
-
-    });
-
-    describe("The auto_register_muc_nickname option", function () {
-
-        it("allows you to automatically register your nickname when joining a room",
-                mock.initConverse(['chatBoxesFetched'], {'auto_register_muc_nickname': true},
-                async function (done, _converse) {
-
-            const muc_jid = 'coven@chat.shakespeare.lit';
-            await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
-            const view = _converse.chatboxviews.get(muc_jid);
-
-            let stanza = await u.waitUntil(() => _.filter(
-                _converse.connection.IQ_stanzas,
-                iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="get"] query[xmlns="jabber:iq:register"]`, iq).length
-            ).pop());
-
-            expect(Strophe.serialize(stanza))
-            .toBe(`<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" `+
-                        `type="get" xmlns="jabber:client">`+
-                    `<query xmlns="jabber:iq:register"/></iq>`);
-            const result = $iq({
-                'from': view.model.get('jid'),
-                'id': stanza.getAttribute('id'),
-                'to': _converse.bare_jid,
-                'type': 'result',
-            }).c('query', {'type': 'jabber:iq:register'})
-                .c('x', {'xmlns': 'jabber:x:data', 'type': 'form'})
-                    .c('field', {
-                        'label': 'Desired Nickname',
-                        'type': 'text-single',
-                        'var': 'muc#register_roomnick'
-                    }).c('required');
-            _converse.connection._dataRecv(mock.createRequest(result));
-            stanza = await u.waitUntil(() => _.filter(
-                _converse.connection.IQ_stanzas,
-                iq => sizzle(`iq[to="coven@chat.shakespeare.lit"][type="set"] query[xmlns="jabber:iq:register"]`, iq).length
-            ).pop());
-
-            expect(Strophe.serialize(stanza)).toBe(
-                `<iq from="romeo@montague.lit/orchard" id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
+                `<iq id="${stanza.getAttribute('id')}" to="coven@chat.shakespeare.lit" type="set" xmlns="jabber:client">`+
                     `<query xmlns="jabber:iq:register">`+
                     `<query xmlns="jabber:iq:register">`+
                         `<x type="submit" xmlns="jabber:x:data">`+
                         `<x type="submit" xmlns="jabber:x:data">`+
                             `<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+
                             `<field var="FORM_TYPE"><value>http://jabber.org/protocol/muc#register</value></field>`+