浏览代码

RAI: Handle MUCs that start out hidden

When a MUC starts out hidden, we first need to join in order to find out
whether we're affiliated before we can know whether we should enable RAI
or join the MUC as usual.
JC Brand 4 年之前
父节点
当前提交
6b9c718df7
共有 3 个文件被更改,包括 132 次插入47 次删除
  1. 2 2
      spec/mock.js
  2. 64 0
      spec/rai.js
  3. 66 45
      src/headless/plugins/muc/muc.js

+ 2 - 2
spec/mock.js

@@ -313,10 +313,10 @@ window.addEventListener('converse-loaded', () => {
     };
 
 
-    mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[], force_open=true) {
+    mock.openAndEnterChatRoom = async function (_converse, muc_jid, nick, features=[], members=[], force_open=true, settings={}) {
         const { api } = _converse;
         muc_jid = muc_jid.toLowerCase();
-        const room_creation_promise = api.rooms.open(muc_jid, {}, force_open);
+        const room_creation_promise = api.rooms.open(muc_jid, settings, force_open);
         await mock.getRoomFeatures(_converse, muc_jid, features);
         await mock.waitForReservedNick(_converse, muc_jid, nick);
         // The user has just entered the room (because join was called)

+ 64 - 0
spec/rai.js

@@ -114,6 +114,70 @@ describe("XEP-0437 Room Activity Indicators", function () {
         done();
     }));
 
+    it("will be activated for a MUC that starts out hidden",
+        mock.initConverse(
+            ['rosterGroupsFetched'], {
+                'allow_bookmarks': false, // Hack to get the rooms list to render
+                'muc_subscribe_to_rai': true,
+                'view_mode': 'fullscreen'},
+            async function (done, _converse) {
+
+        const { api } = _converse;
+        expect(_converse.session.get('rai_enabled_domains')).toBe(undefined);
+
+        const muc_jid = 'lounge@montague.lit';
+        const nick = 'romeo';
+        const sent_stanzas = _converse.connection.sent_stanzas;
+
+        const muc_creation_promise = await api.rooms.open(muc_jid, {nick, 'hidden': true}, false);
+        await mock.getRoomFeatures(_converse, muc_jid, []);
+        await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
+        await muc_creation_promise;
+
+        const view = api.chatviews.get(muc_jid);
+        await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
+        expect(view.model.get('hidden')).toBe(true);
+
+
+        const getSentPresences = () => sent_stanzas.filter(s => s.nodeName === 'presence');
+        await u.waitUntil(() => getSentPresences().length === 3, 500);
+        const sent_presences = getSentPresences();
+
+        expect(Strophe.serialize(sent_presences[1])).toBe(
+            `<presence to="${muc_jid}/romeo" type="unavailable" xmlns="jabber:client">`+
+                `<priority>0</priority>`+
+                `<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
+            `</presence>`
+        );
+        expect(Strophe.serialize(sent_presences[2])).toBe(
+            `<presence to="montague.lit" xmlns="jabber:client">`+
+                `<priority>0</priority>`+
+                `<c hash="sha-1" node="https://conversejs.org" ver="Hxbsr5fazs62i+O0GxIXf2OEDNs=" xmlns="http://jabber.org/protocol/caps"/>`+
+                `<rai xmlns="urn:xmpp:rai:0"/>`+
+            `</presence>`
+        );
+
+        await u.waitUntil(() => view.model.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED);
+        expect(view.model.get('has_activity')).toBe(false);
+
+        const lview = _converse.rooms_list_view
+        const room_el = await u.waitUntil(() => lview.el.querySelector(".available-chatroom"));
+        expect(Array.from(room_el.classList).includes('unread-msgs')).toBeFalsy();
+
+        const activity_stanza = u.toStanza(`
+            <message from="${Strophe.getDomainFromJid(muc_jid)}">
+                <rai xmlns="urn:xmpp:rai:0">
+                    <activity>${muc_jid}</activity>
+                </rai>
+            </message>
+        `);
+        _converse.connection._dataRecv(mock.createRequest(activity_stanza));
+
+        await u.waitUntil(() => view.model.get('has_activity'));
+        expect(Array.from(room_el.classList).includes('unread-msgs')).toBeTruthy();
+        done();
+    }));
+
 
     it("may not be activated due to server resource constraints",
         mock.initConverse(

+ 66 - 45
src/headless/plugins/muc/muc.js

@@ -125,6 +125,7 @@ const ChatRoomMixin = {
             // so we don't send out a presence stanza again.
             return this;
         }
+
         // Set this early, so we don't rejoin in onHiddenChange
         this.session.save('connection_status', converse.ROOMSTATUS.CONNECTING);
         await this.refreshDiscoInfo();
@@ -154,6 +155,17 @@ const ChatRoomMixin = {
         return this;
     },
 
+    /**
+     * Clear stale cache and re-join a MUC we've been in before.
+     * @private
+     * @method _converse.ChatRoom#rejoin
+     */
+    rejoin () {
+        this.registerHandlers();
+        this.clearCache();
+        return this.join();
+    },
+
     clearCache () {
         this.session.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
         if (this.occupants.length) {
@@ -185,12 +197,18 @@ const ChatRoomMixin = {
         }
     },
 
-    async enableRAI () {
-        if (api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') {
-            this.sendMarkerForLastMessage('received', true);
-            if (this.session.get('connection_status') !== converse.ROOMSTATUS.DISCONNECTED) {
-                await this.leave();
-            }
+    /**
+     * Ensures that the user is subscribed to XEP-0437 Room Activity Indicators
+     * if `muc_subscribe_to_rai` is set to `true`.
+     * Only affiliated users can subscribe to RAI, but this method doesn't
+     * check whether the current user is affiliated because it's intended to be
+     * called after the MUC has been left and we don't have that information
+     * anymore.
+     * @private
+     * @method _converse.ChatRoom#enableRAI
+     */
+    enableRAI () {
+        if (api.settings.get('muc_subscribe_to_rai')) {
             const rai_enabled = _converse.session.get('rai_enabled_domains') || '';
             const muc_domain = Strophe.getDomainFromJid(this.get('jid'));
             if (!rai_enabled.includes(muc_domain)) {
@@ -203,12 +221,19 @@ const ChatRoomMixin = {
     /**
      * Handler that gets called when the 'hidden' flag is toggled.
      * @private
-     * @method _converse.ChatRoomView#onHiddenChange
-     */
-    onHiddenChange () {
-        if (this.get('hidden')) {
-            this.enableRAI();
-        } else if (this.session.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
+     * @method _converse.ChatRoom#onHiddenChange
+     */
+    async onHiddenChange () {
+        const conn_status = this.session.get('connection_status');
+        if (this.get('hidden') && conn_status === converse.ROOMSTATUS.ENTERED) {
+            if (api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') {
+                if (conn_status !== converse.ROOMSTATUS.DISCONNECTED) {
+                    this.sendMarkerForLastMessage('received', true);
+                    await this.leave();
+                }
+                this.enableRAI();
+            }
+        } else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
             this.rejoin();
         }
     },
@@ -244,44 +269,40 @@ const ChatRoomMixin = {
         }
     },
 
-    /**
-     * Clear stale cache and re-join a MUC we've been in before.
-     * @private
-     * @method _converse.ChatRoom#rejoin
-     */
-    rejoin () {
-        this.registerHandlers();
-        this.clearCache();
-        return this.join();
+    async onRoomEntered () {
+        await this.occupants.fetchMembers();
+        if (api.settings.get('clear_messages_on_reconnection')) {
+            // Don't call this.clearMessages because we don't want to
+            // recreate promises, since that will cause some existing
+            // awaiters to never proceed.
+            await this.messages.clearStore();
+            // A bit hacky. No need to fetch messages after clearing
+            this.messages.fetched.resolve();
+        } else {
+            await this.fetchMessages();
+        }
+        /**
+         * Triggered when the user has entered a new MUC
+         * @event _converse#enteredNewRoom
+         * @type { _converse.ChatRoom}
+         * @example _converse.api.listen.on('enteredNewRoom', model => { ... });
+         */
+        api.trigger('enteredNewRoom', this);
+        if (
+            api.settings.get('auto_register_muc_nickname') &&
+            (await api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid')))
+        ) {
+            this.registerNickname();
+        }
     },
 
     async onConnectionStatusChanged () {
         if (this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
-            await this.occupants.fetchMembers();
-
-            if (api.settings.get('clear_messages_on_reconnection')) {
-                // Don't call this.clearMessages because we don't want to
-                // recreate promises, since that will cause some existing
-                // awaiters to never proceed.
-                await this.messages.clearStore();
-                // A bit hacky. No need to fetch messages after clearing
-                this.messages.fetched.resolve();
+            if (this.get('hidden') && api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') {
+                await this.leave();
+                this.enableRAI();
             } else {
-                await this.fetchMessages();
-            }
-            /**
-             * Triggered when the user has entered a new MUC
-             * @event _converse#enteredNewRoom
-             * @type { _converse.ChatRoom}
-             * @example _converse.api.listen.on('enteredNewRoom', model => { ... });
-             */
-            api.trigger('enteredNewRoom', this);
-
-            if (
-                api.settings.get('auto_register_muc_nickname') &&
-                (await api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid')))
-            ) {
-                this.registerNickname();
+                await this.onRoomEntered();
             }
         }
     },