瀏覽代碼

Add option to auto-register your nickname to a room

See https://xmpp.org/extensions/xep-0045.html#register
JC Brand 6 年之前
父節點
當前提交
2df9b24211

+ 1 - 1
.eslintrc.json

@@ -111,7 +111,7 @@
         "no-bitwise": "off",
         "no-bitwise": "off",
         "no-caller": "error",
         "no-caller": "error",
         "no-console": "off",
         "no-console": "off",
-        "no-catch-shadow": "error",
+        "no-catch-shadow": "off",
         "no-cond-assign": [
         "no-cond-assign": [
             "error",
             "error",
             "except-parens"
             "except-parens"

+ 18 - 17
CHANGES.md

@@ -2,6 +2,7 @@
 
 
 ## 4.0.1 (Unreleased)
 ## 4.0.1 (Unreleased)
 
 
+- New configuration setting [auto_register_muc_nickname](https://conversejs.org/docs/html/configuration.html#auto-register-muc-nickname)
 - #1182 MUC occupants without nick or JID created
 - #1182 MUC occupants without nick or JID created
 - #1184 Notification error when message has no body
 - #1184 Notification error when message has no body
 
 
@@ -30,7 +31,7 @@
   If the device is not trusted, sessionStorage is used and all user data is deleted from the browser cache upon logout.
   If the device is not trusted, sessionStorage is used and all user data is deleted from the browser cache upon logout.
   If the device is trusted, localStorage is used and user data is cached indefinitely.
   If the device is trusted, localStorage is used and user data is cached indefinitely.
 - Initial support for [XEP-0357 Push Notifications](https://xmpp.org/extensions/xep-0357.html), specifically registering an "App Server".
 - Initial support for [XEP-0357 Push Notifications](https://xmpp.org/extensions/xep-0357.html), specifically registering an "App Server".
-- Add support for logging in via OAuth (see the [oauth_providers](https://conversejs.org/docs/html/configurations.html#oauth-providers) setting)
+- Add support for logging in via OAuth (see the [oauth_providers](https://conversejs.org/docs/html/configuration.html#oauth-providers) setting)
 
 
 ### Bugfixes
 ### Bugfixes
 
 
@@ -61,7 +62,7 @@
 
 
 ## Configuration changes 
 ## Configuration changes 
 
 
-- Removed the `storage` configuration setting, use [trusted](https://conversejs.org/docs/html/configurations.html#trusted) instead.
+- Removed the `storage` configuration setting, use [trusted](https://conversejs.org/docs/html/configuration.html#trusted) instead.
 - Removed the `use_vcards` configuration setting, instead VCards are always used.
 - Removed the `use_vcards` configuration setting, instead VCards are always used.
 - Removed the `xhr_custom_status` and `xhr_custom_status_url` configuration
 - Removed the `xhr_custom_status` and `xhr_custom_status_url` configuration
   settings. If you relied on these settings, you can instead listen for the
   settings. If you relied on these settings, you can instead listen for the
@@ -71,8 +72,8 @@
 - `xhr_user_search_url` has to include the `?` character now in favor of more flexibility. See example in the documentation.
 - `xhr_user_search_url` has to include the `?` character now in favor of more flexibility. See example in the documentation.
 - The data returned from the `xhr_user_search_url` must now include the user's
 - The data returned from the `xhr_user_search_url` must now include the user's
   `jid` instead of just an `id`.
   `jid` instead of just an `id`.
-- New configuration settings [nickname](https://conversejs.org/docs/html/configurations.html#nickname)
-  and [auto_join_private_chats](https://conversejs.org/docs/html/configurations.html#auto-join-private-chats).
+- New configuration settings [nickname](https://conversejs.org/docs/html/configuration.html#nickname)
+  and [auto_join_private_chats](https://conversejs.org/docs/html/configuration.html#auto-join-private-chats).
 
 
 ## Architectural changes
 ## Architectural changes
 
 
@@ -133,7 +134,7 @@
 - Listen for new room bookmarks pushed from the user's PEP service.
 - Listen for new room bookmarks pushed from the user's PEP service.
 - Simplified the [embedded](https://conversejs.org/demo/embedded.html) usecase.
 - Simplified the [embedded](https://conversejs.org/demo/embedded.html) usecase.
     - No need to manually blacklist or whitelist any plugins.
     - No need to manually blacklist or whitelist any plugins.
-    - Relies on the [view_mode](https://conversejs.org/docs/html/configurations.html#view-mode) being set to `'embedded'`.
+    - Relies on the [view_mode](https://conversejs.org/docs/html/configuration.html#view-mode) being set to `'embedded'`.
     - The main `converse.js` build can be used for the embedded usecase.
     - The main `converse.js` build can be used for the embedded usecase.
     - Maintain MUC session upon page reload
     - Maintain MUC session upon page reload
 
 
@@ -142,9 +143,9 @@
 
 
 ### Configuration settings
 ### Configuration settings
 - `auto_reconnect` is now set to `true` by default.
 - `auto_reconnect` is now set to `true` by default.
-- New configuration setting [allow_public_bookmarks](https://conversejs.org/docs/html/configurations.html#allow-public-bookmarks)
-- New configuration setting [root](https://conversejs.org/docs/html/configurations.html#root)
-- The [view_mode](https://conversejs.org/docs/html/configurations.html#view-mode) setting now has a new possible value: `embedded`
+- New configuration setting [allow_public_bookmarks](https://conversejs.org/docs/html/configuration.html#allow-public-bookmarks)
+- New configuration setting [root](https://conversejs.org/docs/html/configuration.html#root)
+- The [view_mode](https://conversejs.org/docs/html/configuration.html#view-mode) setting now has a new possible value: `embedded`
 
 
 ### Translation updates
 ### Translation updates
 - Chinese (Traditional), French, German, Portuguese (Brazil), Russian, Ukrainian
 - Chinese (Traditional), French, German, Portuguese (Brazil), Russian, Ukrainian
@@ -173,7 +174,7 @@
 
 
 ### UI/UX changes
 ### UI/UX changes
 - Add new configuration option
 - Add new configuration option
-  [show_message_load_animation](https://conversejs.org/docs/html/configurations.html#show-message-load-animation)
+  [show_message_load_animation](https://conversejs.org/docs/html/configuration.html#show-message-load-animation)
   with a default value of `false`. The message load animations (added in 3.3.0)
   with a default value of `false`. The message load animations (added in 3.3.0)
   cause slowness and performance issues in Firefox, so they're now disabled by default.
   cause slowness and performance issues in Firefox, so they're now disabled by default.
 
 
@@ -210,7 +211,7 @@
   and private chats with a URL fragment such as `#converse/chat?jid=user@domain`
   and private chats with a URL fragment such as `#converse/chat?jid=user@domain`
 - #828 Add routing for the `#converse/login` and `#converse/register` URL
 - #828 Add routing for the `#converse/login` and `#converse/register` URL
   fragments, which will render the registration and login forms respectively.
   fragments, which will render the registration and login forms respectively.
-- New configuration setting [view_mode](https://conversejs.org/docs/html/configurations.html#view-mode)
+- New configuration setting [view_mode](https://conversejs.org/docs/html/configuration.html#view-mode)
   This removes the need for separate `inverse.js` and `converse-mobile.js`
   This removes the need for separate `inverse.js` and `converse-mobile.js`
   builds. Instead the `converse.js` build is now used with `view_mode` set to
   builds. Instead the `converse.js` build is now used with `view_mode` set to
   `fullscreen` and `mobile` respectively.
   `fullscreen` and `mobile` respectively.
@@ -248,7 +249,7 @@
 - Converse.js no longer includes all the translations in its build. Instead,
 - Converse.js no longer includes all the translations in its build. Instead,
   only the currently relevant translation is requested. This results in a much
   only the currently relevant translation is requested. This results in a much
   smaller filesize but means that the translations you want to provide need to
   smaller filesize but means that the translations you want to provide need to
-  be available. See the [locales_url](https://conversejs.org/docs/html/configurations.html#locales-url)
+  be available. See the [locales_url](https://conversejs.org/docs/html/configuration.html#locales-url)
   configuration setting for more info.
   configuration setting for more info.
 - The translation machinery has now been moved to a separate module in `src/i18n.js`.
 - The translation machinery has now been moved to a separate module in `src/i18n.js`.
 - jQuery has been completely removed as a dependency (still used in tests though).
 - jQuery has been completely removed as a dependency (still used in tests though).
@@ -277,10 +278,10 @@
 
 
 ### New configuration settings
 ### New configuration settings
 * The `visible_toolbar_buttons.emoticons` configuration option is now changed to `visible_toolbar_buttons.emoji`.
 * The `visible_toolbar_buttons.emoticons` configuration option is now changed to `visible_toolbar_buttons.emoji`.
-* [use_emojione](https://conversejs.org/docs/html/configurations.html#use-emojione)
+* [use_emojione](https://conversejs.org/docs/html/configuration.html#use-emojione)
   is used to determine whether Emojione should be used to render emojis,
   is used to determine whether Emojione should be used to render emojis,
   otherwise rendering falls back to native browser or OS support.
   otherwise rendering falls back to native browser or OS support.
-* [emojione_image_path](https://conversejs.org/docs/html/configurations.html#emojione-image-path)
+* [emojione_image_path](https://conversejs.org/docs/html/configuration.html#emojione-image-path)
   is used to specify from where Emojione will load images for rendering emojis.
   is used to specify from where Emojione will load images for rendering emojis.
 
 
 ### New events
 ### New events
@@ -334,7 +335,7 @@ More info here: https://github.com/LeaVerou/awesomplete/pull/17082
 
 
 ### New configuration settings
 ### New configuration settings
 - New setting for `converse-bookmarks`:
 - New setting for `converse-bookmarks`:
-  [hide_open_bookmarks](https://conversejs.org/docs/html/configurations.html#hide-open-bookmarks)
+  [hide_open_bookmarks](https://conversejs.org/docs/html/configuration.html#hide-open-bookmarks)
   It is meant to be set to `true` when using `converse-roomslist` so that open
   It is meant to be set to `true` when using `converse-roomslist` so that open
   rooms aren't listed twice (in the rooms list and the bookmarks list).
   rooms aren't listed twice (in the rooms list and the bookmarks list).
   [jcbrand]
   [jcbrand]
@@ -388,13 +389,13 @@ More info here: https://github.com/LeaVerou/awesomplete/pull/17082
 - #628 Fixes the bug in displaying chat status during private chat. [saganshul]
 - #628 Fixes the bug in displaying chat status during private chat. [saganshul]
 - #628 Changes the message displayed while typing from a different resource of the same user. [smitbose]
 - #628 Changes the message displayed while typing from a different resource of the same user. [smitbose]
 - #675 Time format made configurable.
 - #675 Time format made configurable.
-   See [time_format](https://conversejs.org/docs/html/configurations.html#time-format)
+   See [time_format](https://conversejs.org/docs/html/configuration.html#time-format)
    [smitbose]
    [smitbose]
 - #682 Add "Send" button to input box in chat dialog window.
 - #682 Add "Send" button to input box in chat dialog window.
-   See [show_send_button](https://conversejs.org/docs/html/configurations.html#show-send-button)
+   See [show_send_button](https://conversejs.org/docs/html/configuration.html#show-send-button)
    [saganshul]
    [saganshul]
 - #704 Automatic fetching of registration form when
 - #704 Automatic fetching of registration form when
-   [registration_domain](https://conversejs.org/docs/html/configurations.html#registration-domain)
+   [registration_domain](https://conversejs.org/docs/html/configuration.html#registration-domain)
    is set. [smitbose]
    is set. [smitbose]
 - #806 The `_converse.listen` API event listeners aren't triggered. [jcbrand]
 - #806 The `_converse.listen` API event listeners aren't triggered. [jcbrand]
 - #807 Error: Plugin "converse-dragresize" tried to override HeadlinesBoxView but it's not found. [jcbrand]
 - #807 Error: Plugin "converse-dragresize" tried to override HeadlinesBoxView but it's not found. [jcbrand]

+ 82 - 3
dist/converse.js

@@ -62864,6 +62864,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
   Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
   Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
   Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
   Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
   Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
   Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
+  Strophe.addNamespace('REGISTER', 'jabber:iq:register');
   Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
   Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
   Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
   Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
   Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
   Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
@@ -68982,10 +68983,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
            * If so, we'll use that, otherwise we render the nickname form.
            * If so, we'll use that, otherwise we render the nickname form.
            */
            */
           this.showSpinner();
           this.showSpinner();
-          this.model.checkForReservedNick(this.onNickNameFound.bind(this), this.onNickNameNotFound.bind(this));
+          this.model.checkForReservedNick(this.onReservedNicknameFound.bind(this), this.onReservedNicknameNotFound.bind(this));
         },
         },
 
 
-        onNickNameFound(iq) {
+        onReservedNicknameFound(iq) {
           /* We've received an IQ response from the server which
           /* We've received an IQ response from the server which
            * might contain the user's reserved nickname.
            * might contain the user's reserved nickname.
            * If no nickname is found we either render a form for
            * If no nickname is found we either render a form for
@@ -69005,7 +69006,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }
           }
         },
         },
 
 
-        onNickNameNotFound(message) {
+        onReservedNicknameNotFound(message) {
           const nick = this.getDefaultNickName();
           const nick = this.getDefaultNickName();
 
 
           if (nick) {
           if (nick) {
@@ -70032,6 +70033,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
         allow_muc_invitations: true,
         allow_muc_invitations: true,
         auto_join_on_invite: false,
         auto_join_on_invite: false,
         auto_join_rooms: [],
         auto_join_rooms: [],
+        auto_register_muc_nickname: false,
         muc_domain: undefined,
         muc_domain: undefined,
         muc_history_max_stanzas: undefined,
         muc_history_max_stanzas: undefined,
         muc_instant_rooms: true,
         muc_instant_rooms: true,
@@ -70100,12 +70102,24 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
         initialize() {
         initialize() {
           this.constructor.__super__.initialize.apply(this, arguments);
           this.constructor.__super__.initialize.apply(this, arguments);
 
 
+          this.on('change:connection_status', this.onConnectionStatusChanged, this);
           this.occupants = new _converse.ChatRoomOccupants();
           this.occupants = new _converse.ChatRoomOccupants();
           this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`));
           this.occupants.browserStorage = new Backbone.BrowserStorage.session(b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`));
           this.occupants.chatroom = this;
           this.occupants.chatroom = this;
           this.registerHandlers();
           this.registerHandlers();
         },
         },
 
 
+        async onConnectionStatusChanged() {
+          if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED && _converse.auto_register_muc_nickname) {
+            debugger;
+            const result = await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'));
+
+            if (result.length) {
+              this.registerNickname();
+            }
+          }
+        },
+
         registerHandlers() {
         registerHandlers() {
           /* Register presence and message handlers for this chat
           /* Register presence and message handlers for this chat
            * groupchat
            * groupchat
@@ -70808,6 +70822,53 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
           return this;
           return this;
         },
         },
 
 
+        async registerNickname() {
+          try {
+            await _converse.api.sendIQ($iq({
+              'from': _converse.bare_jid,
+              'to': this.get('jid'),
+              'type': 'get'
+            }).c('query', {
+              'xmlns': Strophe.NS.MUC_REGISTER
+            }));
+          } catch (e) {
+            if (sizzle('item-not-found[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+              _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')} which does not exist.`);
+            } else if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+              _converse.log(`You're not allowed to register in the groupchat ${this.get('jid')}`);
+            }
+
+            return _converse.log(e, Strophe.LogLevel.ERROR);
+          }
+
+          try {
+            await _converse.api.sendIQ($iq({
+              'from': _converse.bare_jid,
+              'to': this.get('jid'),
+              'type': 'set'
+            }).c('query', {
+              'xmlns': Strophe.NS.MUC_REGISTER
+            }).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(this.get('nick')));
+          } catch (e) {
+            if (sizzle('conflict[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+              _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')}, it's already taken.`);
+            } else if (sizzle('service-unavailable[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+              _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')}, it doesn't support registration.`);
+            } else if (sizzle('bad-request[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+              _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')}, invalid data form supplied.`);
+            }
+
+            return _converse.log(e, Strophe.LogLevel.ERROR);
+          }
+        },
+
         updateOccupantsOnPresence(pres) {
         updateOccupantsOnPresence(pres) {
           /* Given a presence stanza, update the occupant model
           /* Given a presence stanza, update the occupant model
            * based on its contents.
            * based on its contents.
@@ -71330,6 +71391,22 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
           }
           }
         });
         });
       }
       }
+
+      function fetchRegistrationForm(room_jid, user_jid) {
+        _converse.api.sendIQ($iq({
+          'from': user_jid,
+          'to': room_jid,
+          'type': 'get'
+        }).c('query', {
+          'xmlns': Strophe.NS.REGISTER
+        })).then(iq => {}).catch(iq => {
+          if (sizzle('item-not-found[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', iq).length) {
+            this.feedback.set('error', __(`Error: the groupchat ${this.model.getDisplayName()} does not exist.`));
+          } else if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
+            this.feedback.set('error', __(`Sorry, you're not allowed to registerd in this groupchat`));
+          }
+        });
+      }
       /************************ BEGIN Event Handlers ************************/
       /************************ BEGIN Event Handlers ************************/
 
 
 
 
@@ -78494,6 +78571,8 @@ __e(o.info_configure) +
  } ;
  } ;
 __p += '\n    <a class="chatbox-btn show-room-details-modal fa fa-info-circle" title="' +
 __p += '\n    <a class="chatbox-btn show-room-details-modal fa fa-info-circle" title="' +
 __e(o.info_details) +
 __e(o.info_details) +
+'"></a>\n    <a class="chatbox-btn show-room-registration-modal fa fa-file-signature" title="' +
+__e(o.info_register) +
 '"></a>\n</div>\n';
 '"></a>\n</div>\n';
 return __p
 return __p
 };
 };

+ 10 - 0
docs/source/configuration.rst

@@ -328,6 +328,16 @@ wiped from memory. This configuration can however still be useful when using
 Converse in desktop apps, for example those based on `CEF <https://bitbucket.org/chromiumembedded/cef>`_
 Converse in desktop apps, for example those based on `CEF <https://bitbucket.org/chromiumembedded/cef>`_
 or `electron <http://electron.atom.io/>`_.
 or `electron <http://electron.atom.io/>`_.
 
 
+auto_register_muc_nickname
+--------------------------
+
+* Default: ``false``
+
+Determines whether Converse should automatically register a user's nickname
+when they enter a groupchat.
+
+See here fore more details: https://xmpp.org/extensions/xep-0045.html#register
+
 auto_subscribe
 auto_subscribe
 --------------
 --------------
 
 

+ 1 - 1
sass/font-awesome.scss

@@ -10,7 +10,6 @@
   url('webfonts/fa-brands-400.svg#fontawesome') format('svg');
   url('webfonts/fa-brands-400.svg#fontawesome') format('svg');
 }
 }
 
 
-
 @font-face {
 @font-face {
   font-family: 'ConverseFontAwesomeRegular';
   font-family: 'ConverseFontAwesomeRegular';
   font-style: normal;
   font-style: normal;
@@ -46,6 +45,7 @@
   font-family: 'ConverseFontAwesomeSolid' !important;
   font-family: 'ConverseFontAwesomeSolid' !important;
   font-weight: 900;
   font-weight: 900;
 }
 }
+
 .fab {
 .fab {
   font-family: 'ConverseFontAwesomeBrands';
   font-family: 'ConverseFontAwesomeBrands';
 }
 }

+ 3 - 4
spec/chatroom.js

@@ -12,7 +12,7 @@
           Backbone = converse.env.Backbone,
           Backbone = converse.env.Backbone,
           u = converse.env.utils;
           u = converse.env.utils;
 
 
-    return describe("ChatRooms", function () {
+    return describe("Chatrooms", function () {
         describe("The \"rooms\" API", function () {
         describe("The \"rooms\" API", function () {
 
 
             it("has a method 'close' which closes rooms by JID or all rooms when called with no arguments",
             it("has a method 'close' which closes rooms by JID or all rooms when called with no arguments",
@@ -816,9 +816,8 @@
                         null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                         null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
                         function (done, _converse) {
                         function (done, _converse) {
 
 
-                var view;
-                var sent_IQ, IQ_id;
-                var sendIQ = _converse.connection.sendIQ;
+                let view, sent_IQ, IQ_id;
+                const sendIQ = _converse.connection.sendIQ;
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                 spyOn(_converse.connection, 'sendIQ').and.callFake(function (iq, callback, errback) {
                     sent_IQ = iq;
                     sent_IQ = iq;
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);
                     IQ_id = sendIQ.bind(this)(iq, callback, errback);

+ 44 - 0
spec/room_registration.js

@@ -0,0 +1,44 @@
+(function (root, factory) {
+    define(["jasmine", "mock", "test-utils" ], factory);
+} (this, function (jasmine, mock, test_utils) {
+    const _ = converse.env._,
+          $iq = converse.env.$iq,
+          Strophe = converse.env.Strophe,
+          u = converse.env.utils;
+
+    describe("The _converse.api.rooms API", function () {
+
+        it("allows you to register a user with a room", 
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
+                function (done, _converse) {
+
+            let view;
+            const room_jid = 'coven@chat.shakespeare.lit';
+            test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'romeo')
+            .then(() => {
+                view = _converse.chatboxviews.get(room_jid);
+                _converse.api.rooms.register(room_jid, _converse.bare_jid, 'romeo');
+                return test_utils.waitUntil(() => _.get(_.filter(
+                    _converse.connection.IQ_stanzas,
+                    iq => iq.nodeTree.querySelector(`iq[to="coven@chat.shakespeare.lit"] query[xmlns="jabber:iq:register"]`)
+                ).pop(), 'nodeTree'));
+            }).then(stanza => {
+                expect(stanza.outerHTML)
+                .toBe(`<iq from="dummy@localhost" to="coven@chat.shakespeare.lit" `+
+                            `type="get" xmlns="jabber:client" id="${stanza.getAttribute('id')}">`+
+                        `<query xmlns="jabber:iq:register"/></iq>`);
+                // Room does not exist
+                const result = $iq({
+                    'from': view.model.get('jid'),
+                    'id': stanza.getAttribute('id'),
+                    'to': _converse.bare_jid,
+                    'type': 'error',
+                }).c('error', {'type': "cancel"})
+                    .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"})
+                _converse.connection._dataRecv(test_utils.createRequest(result));
+                done();
+            }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
+        }));
+    });
+}));

+ 2 - 1
src/converse-core.js

@@ -39,6 +39,7 @@
     Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
     Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
     Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
     Strophe.addNamespace('OUTOFBAND', 'jabber:x:oob');
     Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
     Strophe.addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
+    Strophe.addNamespace('REGISTER', 'jabber:iq:register');
     Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
     Strophe.addNamespace('ROSTERX', 'http://jabber.org/protocol/rosterx');
     Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
     Strophe.addNamespace('RSM', 'http://jabber.org/protocol/rsm');
     Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
     Strophe.addNamespace('SID', 'urn:xmpp:sid:0');
@@ -484,7 +485,7 @@
                 // Waiting time of less then one second means features aren't used.
                 // Waiting time of less then one second means features aren't used.
                 return;
                 return;
             }
             }
-            _converse.idle_seconds = 0;
+            _converse.idle_seconds = 0
             _converse.auto_changed_status = false; // Was the user's status changed by _converse.js?
             _converse.auto_changed_status = false; // Was the user's status changed by _converse.js?
             window.addEventListener('click', _converse.onUserActivity);
             window.addEventListener('click', _converse.onUserActivity);
             window.addEventListener('focus', _converse.onUserActivity);
             window.addEventListener('focus', _converse.onUserActivity);

+ 4 - 4
src/converse-muc-views.js

@@ -1204,12 +1204,12 @@
                      */
                      */
                     this.showSpinner();
                     this.showSpinner();
                     this.model.checkForReservedNick(
                     this.model.checkForReservedNick(
-                        this.onNickNameFound.bind(this),
-                        this.onNickNameNotFound.bind(this)
+                        this.onReservedNicknameFound.bind(this),
+                        this.onReservedNicknameNotFound.bind(this)
                     )
                     )
                 },
                 },
 
 
-                onNickNameFound (iq) {
+                onReservedNicknameFound (iq) {
                     /* We've received an IQ response from the server which
                     /* We've received an IQ response from the server which
                      * might contain the user's reserved nickname.
                      * might contain the user's reserved nickname.
                      * If no nickname is found we either render a form for
                      * If no nickname is found we either render a form for
@@ -1228,7 +1228,7 @@
                     }
                     }
                 },
                 },
 
 
-                onNickNameNotFound (message) {
+                onReservedNicknameNotFound (message) {
                     const nick = this.getDefaultNickName();
                     const nick = this.getDefaultNickName();
                     if (nick) {
                     if (nick) {
                         this.join(nick);
                         this.join(nick);

+ 73 - 0
src/converse-muc.js

@@ -113,6 +113,7 @@
                 allow_muc_invitations: true,
                 allow_muc_invitations: true,
                 auto_join_on_invite: false,
                 auto_join_on_invite: false,
                 auto_join_rooms: [],
                 auto_join_rooms: [],
+                auto_register_muc_nickname: false,
                 muc_domain: undefined,
                 muc_domain: undefined,
                 muc_history_max_stanzas: undefined,
                 muc_history_max_stanzas: undefined,
                 muc_instant_rooms: true,
                 muc_instant_rooms: true,
@@ -184,6 +185,8 @@
 
 
                 initialize() {
                 initialize() {
                     this.constructor.__super__.initialize.apply(this, arguments);
                     this.constructor.__super__.initialize.apply(this, arguments);
+                    this.on('change:connection_status', this.onConnectionStatusChanged, this);
+
                     this.occupants = new _converse.ChatRoomOccupants();
                     this.occupants = new _converse.ChatRoomOccupants();
                     this.occupants.browserStorage = new Backbone.BrowserStorage.session(
                     this.occupants.browserStorage = new Backbone.BrowserStorage.session(
                         b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`)
                         b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`)
@@ -192,6 +195,18 @@
                     this.registerHandlers();
                     this.registerHandlers();
                 },
                 },
 
 
+                async onConnectionStatusChanged () {
+                    if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED &&
+                            _converse.auto_register_muc_nickname &&
+                            this.get('reserved_nick')) {
+
+                        const result = await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'));
+                        if (result.length) {
+                            this.registerNickname()
+                        }
+                    }
+                },
+
                 registerHandlers () {
                 registerHandlers () {
                     /* Register presence and message handlers for this chat
                     /* Register presence and message handlers for this chat
                      * groupchat
                      * groupchat
@@ -798,6 +813,45 @@
                     return this;
                     return this;
                 },
                 },
 
 
+                async registerNickname () {
+                    try {
+                        await _converse.api.sendIQ(
+                            $iq({
+                                'from': _converse.bare_jid,
+                                'to': this.get('jid'),
+                                'type': 'get'
+                            }).c('query', {'xmlns': Strophe.NS.MUC_REGISTER})
+                        );
+                    } catch (e) {
+                        if (sizzle('item-not-found[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+                            _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')} which does not exist.`);
+                        } else if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+                            _converse.log(`You're not allowed to register in the groupchat ${this.get('jid')}`);
+                        }
+                        return _converse.log(e, Strophe.LogLevel.ERROR);
+                    }
+                    try {
+                        await _converse.api.sendIQ($iq({
+                                'from': _converse.bare_jid,
+                                'to': this.get('jid'),
+                                'type': 'set'
+                            }).c('query', {'xmlns': Strophe.NS.MUC_REGISTER})
+                                .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(this.get('nick'))
+                        );
+                    } catch (e) {
+                        if (sizzle('conflict[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+                            _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')}, it's already taken.`);
+                        } else if (sizzle('service-unavailable[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+                            _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')}, it doesn't support registration.`);
+                        } else if (sizzle('bad-request[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
+                            _converse.log(`Can't register nickname ${this.get('nick')} in the groupchat ${this.get('jid')}, invalid data form supplied.`);
+                        }
+                        return _converse.log(e, Strophe.LogLevel.ERROR);
+                    }
+                },
+
                 updateOccupantsOnPresence (pres) {
                 updateOccupantsOnPresence (pres) {
                     /* Given a presence stanza, update the occupant model
                     /* Given a presence stanza, update the occupant model
                      * based on its contents.
                      * based on its contents.
@@ -1259,6 +1313,25 @@
                 });
                 });
             }
             }
 
 
+            function fetchRegistrationForm (room_jid, user_jid) {
+                _converse.api.sendIQ(
+                    $iq({
+                        'from': user_jid,
+                        'to': room_jid,
+                        'type': 'get'
+                    }).c('query', {'xmlns': Strophe.NS.REGISTER})
+                ).then(iq => {
+
+                }).catch(iq => {
+                    if (sizzle('item-not-found[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', iq).length) {
+                        this.feedback.set('error', __(`Error: the groupchat ${this.model.getDisplayName()} does not exist.`));
+                    } else if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
+                        this.feedback.set('error', __(`Sorry, you're not allowed to registerd in this groupchat`));
+                    }
+                });
+            }
+
+
             /************************ BEGIN Event Handlers ************************/
             /************************ BEGIN Event Handlers ************************/
             _converse.on('addClientFeatures', () => {
             _converse.on('addClientFeatures', () => {
                 if (_converse.allow_muc) {
                 if (_converse.allow_muc) {

+ 19 - 0
src/templates/chatroom_registration_modal.html

@@ -0,0 +1,19 @@
+<div class="modal fade" id="room-registration-modal" tabindex="-1" role="dialog" aria-labelledby="room-registration-modal-label" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="room-registration-modal-label">{{{o.display_name}}}</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="{{{o.label_close}}}"><span aria-hidden="true">&times;</span></button>
+            </div>
+            <div class="modal-body">
+                <form class="converse-form">
+                    {[ if (o.feedback.get('error')) { ]} <div class="alert alert-danger" role="alert">{{{o.feedback.get('error')}}}</div> {[ } ]}
+                    {[ if (!o.feedback.get('error')) { ]} <span class="spinner fa fa-spinner"></span> {[ } ]}
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">{{{o.__('Close')}}}</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 0
tests/runner.js

@@ -202,6 +202,7 @@ var specs = [
     "spec/user-details-modal",
     "spec/user-details-modal",
     "spec/messages",
     "spec/messages",
     "spec/chatroom",
     "spec/chatroom",
+    "spec/room_registration",
     "spec/autocomplete",
     "spec/autocomplete",
     "spec/minchats",
     "spec/minchats",
     "spec/notification",
     "spec/notification",