فهرست منبع

Refactoring of fetching of reserved nick

- Move `getDefaultNickName` to the model and rename to `getDefaultNick`
- Let `checkForReservedNick` return a promise and save `nick` once received
- Updated `openAndEnterChatRoom` to wait appropriately and remove presence-wrapper
- Update tests to wait appropriately
- Remove presence-wrapper in `getRoomFeatures`
JC Brand 6 سال پیش
والد
کامیت
acef8feaaa
7فایلهای تغییر یافته به همراه220 افزوده شده و 198 حذف شده
  1. 46 53
      dist/converse.js
  2. 5 4
      spec/chatroom.js
  3. 66 32
      spec/room_registration.js
  4. 1 1
      spec/roomslist.js
  5. 12 38
      src/converse-muc-views.js
  6. 37 16
      src/converse-muc.js
  7. 53 54
      tests/utils.js

+ 46 - 53
dist/converse.js

@@ -68983,31 +68983,19 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
            * If so, we'll use that, otherwise we render the nickname form.
            */
           this.showSpinner();
-          this.model.checkForReservedNick(this.onReservedNicknameFound.bind(this), this.onReservedNicknameNotFound.bind(this));
+          this.model.checkForReservedNick().then(this.onReservedNickFound.bind(this)).catch(this.onReservedNickNotFound.bind(this));
         },
 
-        onReservedNicknameFound(iq) {
-          /* We've received an IQ response from the server which
-           * might contain the user's reserved nickname.
-           * If no nickname is found we either render a form for
-           * them to specify one, or we try to join the groupchat with the
-           * node of the user's JID.
-           *
-           * Parameters:
-           *  (XMLElement) iq: The received IQ stanza
-           */
-          const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
-                nick = identity_el ? identity_el.getAttribute('name') : null;
-
-          if (!nick) {
-            this.onNickNameNotFound();
+        onReservedNickFound(iq) {
+          if (this.model.get('nick')) {
+            this.join();
           } else {
-            this.join(nick);
+            this.onReservedNickNotFound();
           }
         },
 
-        onReservedNicknameNotFound(message) {
-          const nick = this.getDefaultNickName();
+        onReservedNickNotFound(message) {
+          const nick = this.model.getDefaultNick();
 
           if (nick) {
             this.join(nick);
@@ -69016,21 +69004,6 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }
         },
 
-        getDefaultNickName() {
-          /* The default nickname (used when muc_nickname_from_jid is true)
-           * is the node part of the user's JID.
-           * We put this in a separate method so that it can be
-           * overridden by plugins.
-           */
-          const nick = _converse.xmppstatus.vcard.get('nickname');
-
-          if (nick) {
-            return nick;
-          } else if (_converse.muc_nickname_from_jid) {
-            return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
-          }
-        },
-
         onNicknameClash(presence) {
           /* When the nickname is already taken, we either render a
            * form for the user to choose a new nickname, or we
@@ -69043,7 +69016,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           if (_converse.muc_nickname_from_jid) {
             const nick = presence.getAttribute('from').split('/')[1];
 
-            if (nick === this.getDefaultNickName()) {
+            if (nick === this.model.getDefaultNick()) {
               this.join(nick + '-2');
             } else {
               const del = nick.lastIndexOf("-");
@@ -70110,8 +70083,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
         },
 
         async onConnectionStatusChanged() {
-          if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED && _converse.auto_register_muc_nickname) {
-            debugger;
+          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) {
@@ -70361,17 +70333,15 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
         getRoomFeatures() {
           /* Fetch the groupchat disco info, parse it and then save it.
            */
-          return new Promise((resolve, reject) => {
-            _converse.api.disco.info(this.get('jid'), null).then(stanza => {
-              this.parseRoomFeatures(stanza);
-              resolve();
-            }).catch(err => {
-              _converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
-
-              _converse.log(err, Strophe.LogLevel.WARN);
-
-              reject(err);
-            });
+          // XXX: Currently we store disco info on the room itself.
+          // A better design would probably be to create a
+          // DiscoEntity for this room, and then to let
+          // converse-disco manage all disco-related tasks.
+          // Then we can also use _converse.api.disco.supports.
+          return _converse.api.disco.info(this.get('jid'), null).then(stanza => this.parseRoomFeatures(stanza)).catch(err => {
+            _converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
+
+            _converse.log(err, Strophe.LogLevel.WARN);
           });
         },
 
@@ -70801,7 +70771,22 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
           this.getJidsWithAffiliations(affiliations).then(old_members => this.setAffiliations(deltaFunc(members, old_members))).then(() => this.occupants.fetchMembers()).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
         },
 
-        checkForReservedNick(callback, errback) {
+        getDefaultNick() {
+          /* The default nickname (used when muc_nickname_from_jid is true)
+           * is the node part of the user's JID.
+           * We put this in a separate method so that it can be
+           * overridden by plugins.
+           */
+          const nick = _converse.xmppstatus.vcard.get('nickname');
+
+          if (nick) {
+            return nick;
+          } else if (_converse.muc_nickname_from_jid) {
+            return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
+          }
+        },
+
+        checkForReservedNick() {
           /* Use service-discovery to ask the XMPP server whether
            * this user has a reserved nickname for this groupchat.
            * If so, we'll use that, otherwise we render the nickname form.
@@ -70810,16 +70795,24 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
            *  (Function) callback: Callback upon succesful IQ response
            *  (Function) errback: Callback upon error IQ response
            */
-          _converse.connection.sendIQ($iq({
+          return _converse.api.sendIQ($iq({
             'to': this.get('jid'),
             'from': _converse.connection.jid,
             'type': "get"
           }).c("query", {
             'xmlns': Strophe.NS.DISCO_INFO,
             'node': 'x-roomuser-item'
-          }), callback, errback);
-
-          return this;
+          })).then(iq => {
+            const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
+                  nick = identity_el ? identity_el.getAttribute('name') : null;
+            this.save({
+              'reserved_nick': nick,
+              'nick': nick
+            }, {
+              'silent': true
+            });
+            return iq;
+          });
         },
 
         async registerNickname() {

+ 5 - 4
spec/chatroom.js

@@ -354,8 +354,8 @@
                     }).c('error', {'type': 'cancel'})
                     .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
                     _converse.connection._dataRecv(test_utils.createRequest(stanza));
-
-                    var input = view.el.querySelector('input[name="nick"]');
+                    return test_utils.waitUntil(() => view.el.querySelector('input[name="nick"]'));
+                }).then(input => {
                     input.value = 'nicky';
                     view.el.querySelector('input[type=submit]').click();
                     expect(view.submitNickname).toHaveBeenCalled();
@@ -402,7 +402,7 @@
                             "<query xmlns='http://jabber.org/protocol/muc#owner'><x xmlns='jabber:x:data' type='submit'/>"+
                         "</query></iq>");
                     done();
-                });
+                }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
             }));
         });
 
@@ -1899,7 +1899,8 @@
                  *  </x>
                  *  </presence>
                  */
-                test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
+                test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy')
+                .then(() => {
                     var presence = $pres().attrs({
                             from:'lounge@localhost/dummy',
                             to:'dummy@localhost/pda',

+ 66 - 32
spec/room_registration.js

@@ -6,39 +6,73 @@
           Strophe = converse.env.Strophe,
           u = converse.env.utils;
 
-    describe("The _converse.api.rooms API", function () {
+    describe("Chatrooms", function () {
+        describe("The auto_register_muc_nickname option", function () {
 
-        it("allows you to register a user with a room", 
-            mock.initConverseWithPromises(
-                null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
-                function (done, _converse) {
+            it("allows you to automatically register your nickname when joining a room", 
+                mock.initConverseWithPromises(
+                    null, ['rosterGroupsFetched', 'chatBoxesFetched'], {'auto_register_muc_nickname': true},
+                    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));
-        }));
+                let view;
+                const room_jid = 'coven@chat.shakespeare.lit';
+                test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'romeo')
+                .then(() => {
+                    return test_utils.waitUntil(() => _.get(_.filter(
+                        _converse.connection.IQ_stanzas,
+                        iq => iq.nodeTree.querySelector(
+                            `iq[to="coven@chat.shakespeare.lit"] query[xmlns="http://jabber.org/protocol/disco#info"]`
+                        )
+                    ).pop(), 'nodeTree'));
+
+                }).then(stanza => {
+                    view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
+                    spyOn(view.model, 'parseRoomFeatures').and.callThrough();
+                    const features_stanza = $iq({
+                            'from': room_jid,
+                            'id': stanza.getAttribute('id'),
+                            'to': 'dummy@localhost/desktop',
+                            'type': 'result'
+                        })
+                        .c('query', { 'xmlns': 'http://jabber.org/protocol/disco#info'})
+                            .c('identity', {
+                                'category': 'conference',
+                                'name': 'A Dark Cave',
+                                'type': 'text'
+                            }).up()
+                            .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
+                            .c('feature', {'var': 'jabber:iq:register'}).up()
+                            .c('feature', {'var': 'muc_passwordprotected'}).up()
+                            .c('feature', {'var': 'muc_hidden'}).up()
+                            .c('feature', {'var': 'muc_temporary'}).up()
+                            .c('feature', {'var': 'muc_open'}).up()
+                            .c('feature', {'var': 'muc_unmoderated'}).up()
+                            .c('feature', {'var': 'muc_nonanonymous'});
+                    _converse.connection._dataRecv(test_utils.createRequest(features_stanza));
+                    return test_utils.waitUntil(() => view.model.parseRoomFeatures.calls.count(), 300)
+                }).then(() => {
+                    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
+                    view = _converse.chatboxviews.get(room_jid);
+                    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));
+            }));
+        });
     });
 }));

+ 1 - 1
spec/roomslist.js

@@ -262,7 +262,7 @@
                         type: 'groupchat'
                     }).c('body').t('romeo: Your attention is required').tree()
                 );
-                return test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator"));
+                return test_utils.waitUntil(() => _converse.rooms_list_view.el.querySelectorAll(".msgs-indicator").length);
             }).then(() => {
                 spyOn(view.model, 'incrementUnreadMsgCounter').and.callThrough();
                 const indicator_el = _converse.rooms_list_view.el.querySelector(".msgs-indicator");

+ 12 - 38
src/converse-muc-views.js

@@ -1203,33 +1203,21 @@
                      * If so, we'll use that, otherwise we render the nickname form.
                      */
                     this.showSpinner();
-                    this.model.checkForReservedNick(
-                        this.onReservedNicknameFound.bind(this),
-                        this.onReservedNicknameNotFound.bind(this)
-                    )
-                },
-
-                onReservedNicknameFound (iq) {
-                    /* We've received an IQ response from the server which
-                     * might contain the user's reserved nickname.
-                     * If no nickname is found we either render a form for
-                     * them to specify one, or we try to join the groupchat with the
-                     * node of the user's JID.
-                     *
-                     * Parameters:
-                     *  (XMLElement) iq: The received IQ stanza
-                     */
-                    const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
-                          nick = identity_el ? identity_el.getAttribute('name') : null;
-                    if (!nick) {
-                        this.onNickNameNotFound();
+                    this.model.checkForReservedNick()
+                        .then(this.onReservedNickFound.bind(this))
+                        .catch(this.onReservedNickNotFound.bind(this));
+                },
+
+                onReservedNickFound (iq) {
+                    if (this.model.get('nick')) {
+                        this.join();
                     } else {
-                        this.join(nick);
+                        this.onReservedNickNotFound();
                     }
                 },
 
-                onReservedNicknameNotFound (message) {
-                    const nick = this.getDefaultNickName();
+                onReservedNickNotFound (message) {
+                    const nick = this.model.getDefaultNick();
                     if (nick) {
                         this.join(nick);
                     } else {
@@ -1237,20 +1225,6 @@
                     }
                 },
 
-                getDefaultNickName () {
-                    /* The default nickname (used when muc_nickname_from_jid is true)
-                     * is the node part of the user's JID.
-                     * We put this in a separate method so that it can be
-                     * overridden by plugins.
-                     */
-                    const nick = _converse.xmppstatus.vcard.get('nickname');
-                    if (nick) {
-                        return nick;
-                    } else if (_converse.muc_nickname_from_jid) {
-                        return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
-                    }
-                },
-
                 onNicknameClash (presence) {
                     /* When the nickname is already taken, we either render a
                      * form for the user to choose a new nickname, or we
@@ -1262,7 +1236,7 @@
                      */
                     if (_converse.muc_nickname_from_jid) {
                         const nick = presence.getAttribute('from').split('/')[1];
-                        if (nick === this.getDefaultNickName()) {
+                        if (nick === this.model.getDefaultNick()) {
                             this.join(nick + '-2');
                         } else {
                             const del= nick.lastIndexOf("-");

+ 37 - 16
src/converse-muc.js

@@ -406,17 +406,17 @@
                 getRoomFeatures () {
                     /* Fetch the groupchat disco info, parse it and then save it.
                      */
-                    return new Promise((resolve, reject) => {
-                        _converse.api.disco.info(this.get('jid'), null)
-                            .then((stanza) => {
-                                this.parseRoomFeatures(stanza);
-                                resolve()
-                            }).catch((err) => {
-                                _converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
-                                _converse.log(err, Strophe.LogLevel.WARN);
-                                reject(err);
-                            });
-                    });
+                    // XXX: Currently we store disco info on the room itself.
+                    // A better design would probably be to create a
+                    // DiscoEntity for this room, and then to let
+                    // converse-disco manage all disco-related tasks.
+                    // Then we can also use _converse.api.disco.supports.
+                    return _converse.api.disco.info(this.get('jid'), null)
+                        .then(stanza => this.parseRoomFeatures(stanza))
+                        .catch(err => {
+                            _converse.log("Could not parse the groupchat features", Strophe.LogLevel.WARN);
+                            _converse.log(err, Strophe.LogLevel.WARN);
+                        });
                 },
 
                 getRoomJIDAndNick (nick) {
@@ -791,7 +791,21 @@
                         .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
                 },
 
-                checkForReservedNick (callback, errback) {
+                getDefaultNick () {
+                    /* The default nickname (used when muc_nickname_from_jid is true)
+                     * is the node part of the user's JID.
+                     * We put this in a separate method so that it can be
+                     * overridden by plugins.
+                     */
+                    const nick = _converse.xmppstatus.vcard.get('nickname');
+                    if (nick) {
+                        return nick;
+                    } else if (_converse.muc_nickname_from_jid) {
+                        return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
+                    }
+                },
+
+                checkForReservedNick () {
                     /* Use service-discovery to ask the XMPP server whether
                      * this user has a reserved nickname for this groupchat.
                      * If so, we'll use that, otherwise we render the nickname form.
@@ -800,7 +814,7 @@
                      *  (Function) callback: Callback upon succesful IQ response
                      *  (Function) errback: Callback upon error IQ response
                      */
-                    _converse.connection.sendIQ(
+                    return _converse.api.sendIQ(
                         $iq({
                             'to': this.get('jid'),
                             'from': _converse.connection.jid,
@@ -808,9 +822,16 @@
                         }).c("query", {
                             'xmlns': Strophe.NS.DISCO_INFO,
                             'node': 'x-roomuser-item'
-                        }),
-                        callback, errback);
-                    return this;
+                        })
+                    ).then(iq => {
+                        const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
+                              nick = identity_el ? identity_el.getAttribute('name') : null;
+                        this.save({
+                            'reserved_nick': nick,
+                            'nick': nick
+                        }, {'silent': true});
+                        return iq;
+                    });
                 },
 
                 async registerNickname () {

+ 53 - 54
tests/utils.js

@@ -126,61 +126,60 @@
     };
 
     utils.openAndEnterChatRoom = function (_converse, room, server, nick) {
-        let last_stanza;
+        let last_stanza, view;
 
-        return new Promise((resolve, reject) => {
-            return _converse.api.rooms.open(`${room}@${server}`).then(() => {
-                const view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
-                // We pretend this is a new room, so no disco info is returned.
-                let last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
-                const IQ_id = last_stanza.getAttribute('id');
-                const features_stanza = $iq({
-                        'from': room+'@'+server,
-                        'id': IQ_id,
-                        'to': nick+'@'+server,
-                        'type': 'error'
-                    }).c('error', {'type': 'cancel'})
-                        .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
-                _converse.connection._dataRecv(utils.createRequest(features_stanza));
-
-                utils.waitUntil(() => {
-                    return _.filter(
-                        _converse.connection.IQ_stanzas, (node) => {
-                            const query = node.nodeTree.querySelector('query');
-                            if (query && query.getAttribute('node') === 'x-roomuser-item') {
-                                last_stanza = node.nodeTree;
-                                return true;
-                            }
-                        }).length
-                }).then(function () {
-                    // The XMPP server returns the reserved nick for this user.
-                    const IQ_id = last_stanza.getAttribute('id');
-                    const stanza = $iq({
-                        'type': 'result',
-                        'id': IQ_id,
-                        'from': view.model.get('jid'),
-                        'to': _converse.connection.jid 
-                    }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
-                        .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
-                    _converse.connection._dataRecv(utils.createRequest(stanza));
-                    // The user has just entered the room (because join was called)
-                    // and receives their own presence from the server.
-                    // See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
-                    var presence = $pres({
-                            to: _converse.connection.jid,
-                            from: room+'@'+server+'/'+nick,
-                            id: 'DC352437-C019-40EC-B590-AF29E879AF97'
-                    }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
-                        .c('item').attrs({
-                            affiliation: 'owner',
-                            jid: _converse.bare_jid,
-                            role: 'moderator'
-                        }).up()
-                        .c('status').attrs({code:'110'});
-                    _converse.connection._dataRecv(utils.createRequest(presence));
-                    resolve();
-                }).catch(_.partial(console.error, _));
-            }).catch(_.partial(console.error, _));
+        return _converse.api.rooms.open(`${room}@${server}`).then(() => {
+            view = _converse.chatboxviews.get((room+'@'+server).toLowerCase());
+            // We pretend this is a new room, so no disco info is returned.
+            last_stanza = _.last(_converse.connection.IQ_stanzas).nodeTree;
+            const IQ_id = last_stanza.getAttribute('id');
+            const features_stanza = $iq({
+                    'from': room+'@'+server,
+                    'id': IQ_id,
+                    'to': nick+'@'+server,
+                    'type': 'error'
+                }).c('error', {'type': 'cancel'})
+                    .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
+            _converse.connection._dataRecv(utils.createRequest(features_stanza));
+            return utils.waitUntil(() => {
+                return _.filter(
+                    _converse.connection.IQ_stanzas, (node) => {
+                        const query = node.nodeTree.querySelector('query');
+                        if (query && query.getAttribute('node') === 'x-roomuser-item') {
+                            last_stanza = node.nodeTree;
+                            return true;
+                        }
+                    }).length
+            });
+        }).then(() => {
+            // The XMPP server returns the reserved nick for this user.
+            const IQ_id = last_stanza.getAttribute('id');
+            const stanza = $iq({
+                'type': 'result',
+                'id': IQ_id,
+                'from': view.model.get('jid'),
+                'to': _converse.connection.jid 
+            }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info', 'node': 'x-roomuser-item'})
+                .c('identity', {'category': 'conference', 'name': nick, 'type': 'text'});
+            _converse.connection._dataRecv(utils.createRequest(stanza));
+            return utils.waitUntil(() => view.model.get('nick'));
+        }).then(() => {
+            // The user has just entered the room (because join was called)
+            // and receives their own presence from the server.
+            // See example 24: http://xmpp.org/extensions/xep-0045.html#enter-pres
+            var presence = $pres({
+                    to: _converse.connection.jid,
+                    from: room+'@'+server+'/'+nick,
+                    id: 'DC352437-C019-40EC-B590-AF29E879AF97'
+            }).c('x').attrs({xmlns:'http://jabber.org/protocol/muc#user'})
+                .c('item').attrs({
+                    affiliation: 'owner',
+                    jid: _converse.bare_jid,
+                    role: 'moderator'
+                }).up()
+                .c('status').attrs({code:'110'});
+            _converse.connection._dataRecv(utils.createRequest(presence));
+            return utils.waitUntil(() => view.model.get('connection_status') === converse.ROOMSTATUS.ENTERED);
         }).catch(_.partial(console.error, _));
     };