Browse Source

When inviting to a members-only room, first add to user to the member-list

JC Brand 8 years ago
parent
commit
376c50fbc8
4 changed files with 118 additions and 15 deletions
  1. 2 0
      docs/CHANGES.md
  2. 81 13
      spec/chatroom.js
  3. 1 0
      src/converse-core.js
  4. 34 2
      src/converse-muc.js

+ 2 - 0
docs/CHANGES.md

@@ -10,6 +10,8 @@
   disconnection or reconnection events. [jcbrand]
 - Optimize fetching of MAM messages (in some cases happened on each page load). [jcbrand]
 - Fix empty controlbox toggle after disconnect. [jcbrand]
+- When inviting someone to a members-only room, first add them to the member
+  list. [jcbrand]
 
 ## 2.0.3 (2016-11-30)
 - #735 Room configuration button not visible. [jcbrand]

+ 81 - 13
spec/chatroom.js

@@ -907,7 +907,7 @@
                 expect($occupants.children().first(0).text()).toBe("newnick");
             }));
 
-            if("queries for the room information before attempting to join the user",  mock.initConverse(function (converse) {
+            it("queries for the room information before attempting to join the user",  mock.initConverse(function (converse) {
                 var sent_IQ, IQ_id;
                 var sendIQ = converse.connection.sendIQ;
                 spyOn(converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
@@ -955,21 +955,21 @@
                             'type': 'text'
                         }).up()
                         .c('feature', {'var': 'http://jabber.org/protocol/muc'}).up()
-                        .c('feature', {'var': 'passwordprotected'}).up()
-                        .c('feature', {'var': 'hidden'}).up()
-                        .c('feature', {'var': 'temporary'}).up()
-                        .c('feature', {'var': 'open'}).up()
-                        .c('feature', {'var': 'unmoderated'}).up()
-                        .c('feature', {'var': 'nonanonymous'});
+                        .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));
 
                 var view = converse.chatboxviews.get('coven@chat.shakespeare.lit');
-                expect(view.model.get('passwordprotected')).toBe('true');
-                expect(view.model.get('hidden')).toBe('true');
-                expect(view.model.get('temporary')).toBe('true');
-                expect(view.model.get('open')).toBe('true');
-                expect(view.model.get('unmoderated')).toBe('true');
-                expect(view.model.get('nonanonymous')).toBe('true');
+                expect(view.model.get('passwordprotected')).toBe(true);
+                expect(view.model.get('hidden')).toBe(true);
+                expect(view.model.get('temporary')).toBe(true);
+                expect(view.model.get('open')).toBe(true);
+                expect(view.model.get('unmoderated')).toBe(true);
+                expect(view.model.get('nonanonymous')).toBe(true);
             }));
 
             it("indicates when a room is no longer anonymous", mock.initConverse(function (converse) {
@@ -1398,5 +1398,73 @@
                 expect(view.$el.find('.chatroom-body p:last').text()).toBe("This room has reached its maximum number of occupants");
             }));
         });
+
+        describe("Someone being invited to a chat room", function () {
+
+            it("will first be added to the member list if the chat room is members only", mock.initConverse(function (converse) {
+                var sent_IQ, IQ_id;
+                var sendIQ = converse.connection.sendIQ;
+                spyOn(converse.connection, 'sendIQ').andCallFake(function (iq, callback, errback) {
+                    sent_IQ = iq;
+                    IQ_id = sendIQ.bind(this)(iq, callback, errback);
+                });
+
+                test_utils.openChatRoom(converse, 'coven', 'chat.shakespeare.lit', 'dummy');
+
+                // State that the chat is members-only via the features IQ
+                var features_stanza = $iq({
+                        from: 'coven@chat.shakespeare.lit',
+                        'id': IQ_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': 'muc_hidden'}).up()
+                        .c('feature', {'var': 'muc_temporary'}).up()
+                        .c('feature', {'var': 'muc_membersonly'}).up();
+                converse.connection._dataRecv(test_utils.createRequest(features_stanza));
+
+                var view = converse.chatboxviews.get('coven@chat.shakespeare.lit');
+                expect(view.model.get('membersonly')).toBeTruthy();
+
+                spyOn(view, 'setMemberList').andCallThrough();
+
+                test_utils.createContacts(converse, 'current');
+
+                var sent_stanza,
+                    sent_id;
+                spyOn(converse.connection, 'send').andCallFake(function (stanza) {
+                    if (stanza.nodeTree && stanza.nodeTree.nodeName === 'message') {
+                        sent_id = stanza.nodeTree.getAttribute('id');
+                        sent_stanza = stanza;
+                    }
+                });
+
+                var name = mock.cur_names[0];
+                var invitee_jid = name.replace(/ /g,'.').toLowerCase() + '@localhost';
+                var reason = "Please join this chat room";
+                view.directInvite(invitee_jid, reason);
+
+                expect(sent_IQ.toLocaleString()).toBe(
+                    "<iq to='coven@chat.shakespeare.lit' type='set' xmlns='jabber:client' id='"+IQ_id+"'>"+
+                        "<query xmlns='http://jabber.org/protocol/muc#admin'>"+
+                            "<item affiliation='member' jid='"+invitee_jid+"'/>"+
+                        "</query>"+
+                    "</iq>");
+
+                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>"
+                );
+            }));
+        });
+
     });
 }));

+ 1 - 0
src/converse-core.js

@@ -1182,6 +1182,7 @@
                     chat_status = $presence.find('show').text() || 'online',
                     status_message = $presence.find('status'),
                     contact = this.get(bare_jid);
+
                 if (this.isSelf(bare_jid)) {
                     if ((converse.connection.jid !== jid) &&
                         (presence_type !== 'unavailable') &&

+ 34 - 2
src/converse-muc.js

@@ -494,6 +494,29 @@
                     this.insertIntoTextArea(ev.target.textContent);
                 },
 
+                setMemberList: function (members, onSuccess, onError) {
+                    /* Send an IQ stanza to the server to modify the
+                     * member-list of this room.
+                     *
+                     * See: http://xmpp.org/extensions/xep-0045.html#modifymember
+                     *
+                     * Parameters:
+                     *  (Array) members: An array of member objects, containing
+                     *      the JID and affiliation of each.
+                     *  (Function) onSuccess: callback for a succesful response
+                     *  (Function) onError: callback for an error response
+                     */
+                    var iq = $iq({to: this.model.get('jid'), type: "set"})
+                        .c("query", {xmlns: Strophe.NS.MUC_ADMIN});
+                    _.each(members, function (member) {
+                        iq.c("item", {
+                            'affiliation': member.affiliation,
+                            'jid': member.jid
+                        });
+                    });
+                    return converse.connection.sendIQ(iq, onSuccess, onError);
+                },
+
                 directInvite: function (recipient, reason) {
                     /* Send a direct invitation as per XEP-0249
                      *
@@ -501,6 +524,15 @@
                      *    (String) recipient - JID of the person being invited
                      *    (String) reason - Optional reason for the invitation
                      */
+                    if (this.model.get('membersonly')) {
+                        // When inviting to a members-only room, we first add
+                        // the person to the member list, otherwise they won't
+                        // be able to join.
+                        this.setMemberList([{
+                            'jid': recipient,
+                            'affiliation': 'member'
+                        }]);
+                    }
                     var attrs = {
                         'xmlns': 'jabber:x:conference',
                         'jid': this.model.get('jid')
@@ -1082,7 +1114,7 @@
                     var that = this;
                     converse.connection.disco.info(this.model.get('jid'), null,
                         function (iq) {
-                            /* 
+                            /*
                              * See http://xmpp.org/extensions/xep-0045.html#disco-roominfo
                              *
                              *  <identity
@@ -1514,7 +1546,7 @@
                         this.model.save('connection_status', Strophe.Status.DISCONNECTED);
                         this.showErrorMessage(pres);
                         return true;
-                    } 
+                    }
                     var show_status_messages = true;
                     var is_self = pres.querySelector("status[code='110']");
                     var new_room = pres.querySelector("status[code='201']");