瀏覽代碼

Avoid possible exception when leaving/reconnecting in a MUC

- Unregister nickname before sending unavailable presence
- Send unavailable presence before destroying state
- Call `leave` after unregistering, otherwise the disco entry for the MUC gets removed in `leave` while it's still necessary to unregister
- Wrap `this.leave` in  try/except in `onConnectionStatusChanged` handler
- Add new MUC connection status, `CLOSING` to avoid `this.leave()` being called when `hidden` gets set to `true` while the MUC is in the process of being closed.
JC Brand 3 年之前
父節點
當前提交
a60127e66f
共有 2 個文件被更改,包括 53 次插入35 次删除
  1. 1 0
      src/headless/plugins/muc/index.js
  2. 52 35
      src/headless/plugins/muc/muc.js

+ 1 - 0
src/headless/plugins/muc/index.js

@@ -117,6 +117,7 @@ converse.ROOMSTATUS = {
     ENTERED: 5,
     DESTROYED: 6,
     BANNED: 7,
+    CLOSING: 8
 };
 
 converse.plugins.add('converse-muc', {

+ 52 - 35
src/headless/plugins/muc/muc.js

@@ -259,19 +259,20 @@ const ChatRoomMixin = {
      * @method _converse.ChatRoom#onHiddenChange
      */
     async onHiddenChange () {
+        const roomstatus = converse.ROOMSTATUS;
         const conn_status = this.session.get('connection_status');
         if (this.get('hidden')) {
-            if (conn_status === converse.ROOMSTATUS.ENTERED &&
+            if (conn_status === roomstatus.ENTERED &&
                     api.settings.get('muc_subscribe_to_rai') &&
                     this.getOwnAffiliation() !== 'none') {
-                if (conn_status !== converse.ROOMSTATUS.DISCONNECTED) {
+                if (conn_status !== roomstatus.DISCONNECTED && conn_status !== roomstatus.CLOSING) {
                     this.sendMarkerForLastMessage('received', true);
                     await this.leave();
                 }
                 this.enableRAI();
             }
         } else {
-            if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
+            if (conn_status === roomstatus.DISCONNECTED) {
                 this.rejoin();
             }
             this.clearUnreadMsgCounter();
@@ -339,7 +340,11 @@ const ChatRoomMixin = {
     async onConnectionStatusChanged () {
         if (this.session.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
             if (this.get('hidden') && api.settings.get('muc_subscribe_to_rai') && this.getOwnAffiliation() !== 'none') {
-                await this.leave();
+                try {
+                    await this.leave();
+                } catch (e) {
+                    log.error(e);
+                }
                 this.enableRAI();
             } else {
                 await this.onRoomEntered();
@@ -852,28 +857,35 @@ const ChatRoomMixin = {
      * @param { string } [exit_msg] - Message to indicate your reason for leaving
      */
     async leave (exit_msg) {
-        this.features.destroy();
+        api.connection.connected() && api.user.presence.send('unavailable', this.getRoomJIDAndNick(), exit_msg);
 
+        // Delete the features model
+        if (this.features) {
+            await new Promise(resolve =>
+                this.features.destroy({
+                    'success': resolve,
+                    'error': (m, e) => { log.error(e); resolve(); }
+                })
+            );
+        }
+        // Delete disco entity
         const disco_entity = _converse.disco_entities?.get(this.get('jid'));
         if (disco_entity) {
-            await new Promise((success, error) => disco_entity.destroy({ success, error }));
-        }
-        if (api.connection.connected()) {
-            api.user.presence.send('unavailable', this.getRoomJIDAndNick(), exit_msg);
+            await new Promise(resolve => disco_entity.destroy({
+                'success': resolve,
+                'error': (m, e) => { log.error(e); resolve(); }
+            }));
         }
         u.safeSave(this.session, { 'connection_status': converse.ROOMSTATUS.DISCONNECTED });
     },
 
     async close (ev) {
+        u.safeSave(this.session, { 'connection_status': converse.ROOMSTATUS.CLOSING });
+        this.sendMarkerForLastMessage('received', true);
+        await this.unregisterNickname();
         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')) {
             this.clearMessages();
         }
@@ -882,20 +894,7 @@ const ChatRoomMixin = {
         await new Promise(resolve =>
             this.session.destroy({
                 'success': resolve,
-                'error': (m, e) => {
-                    log.error(e);
-                    resolve();
-                }
-            })
-        );
-        // Delete the features model
-        await new Promise(resolve =>
-            this.features.destroy({
-                'success': resolve,
-                'error': (m, e) => {
-                    log.error(e);
-                    resolve();
-                }
+                'error': (m, e) => { log.error(e); resolve(); }
             })
         );
         return _converse.ChatBox.prototype.close.call(this);
@@ -1647,15 +1646,31 @@ const ChatRoomMixin = {
         }
     },
 
+    /**
+     * Check whether we should unregister the user from this MUC, and if so,
+     * call { @link _converse.ChatRoom#sendUnregistrationIQ }
+     * @method _converse.ChatRoom#unregisterNickname
+     */
+    async unregisterNickname () {
+        if (api.settings.get('auto_register_muc_nickname') === 'unregister') {
+            try {
+                if (await api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'))) {
+                    await this.sendUnregistrationIQ();
+                }
+            } catch (e) {
+                log.error(e);
+            }
+        }
+    },
+
     /**
      * Send an IQ stanza to the MUC to unregister this user's nickname.
      * If the user had a 'member' affiliation, it'll be removed and their
      * nickname will no longer be reserved and can instead be used (and
      * registered) by other users.
-     * @private
-     * @method _converse.ChatRoom#unregisterNickname
+     * @method _converse.ChatRoom#sendUnregistrationIQ
      */
-    unregisterNickname () {
+    sendUnregistrationIQ () {
         const iq = $iq({ 'to': this.get('jid'), 'type': 'set' })
             .c('query', { 'xmlns': Strophe.NS.MUC_REGISTER })
             .c('remove');
@@ -2588,7 +2603,10 @@ const ChatRoomMixin = {
     async onOwnPresence (stanza) {
         await this.occupants.fetched;
         const old_status = this.session.get('connection_status');
-        if (stanza.getAttribute('type') !== 'unavailable' && old_status !== converse.ROOMSTATUS.ENTERED) {
+        if (stanza.getAttribute('type') !== 'unavailable' &&
+            old_status !== converse.ROOMSTATUS.ENTERED &&
+            old_status !== converse.ROOMSTATUS.CLOSING
+        ) {
             // Set connection_status before creating the occupant, but
             // only trigger afterwards, so that plugins can access the
             // occupant in their event handlers.
@@ -2627,7 +2645,6 @@ const ChatRoomMixin = {
                 }
             }
         }
-        this.session.save({ 'connection_status': converse.ROOMSTATUS.ENTERED });
     },
 
     /**