From 54a4ff8fd73b2f17bf129b1bd1e43944958a1888 Mon Sep 17 00:00:00 2001
From: ubermanu <e.vodor@gmail.com>
Date: Thu, 11 Mar 2021 13:29:58 +0100
Subject: [PATCH] bugfix: update the rai implementation using conversejs one

---
 src/plugins/converse-rai.js         | 297 +++++++++++++---------------
 src/plugins/sib-subscribe-to-rai.js |   4 +-
 2 files changed, 139 insertions(+), 162 deletions(-)

diff --git a/src/plugins/converse-rai.js b/src/plugins/converse-rai.js
index c110906..42773a4 100644
--- a/src/plugins/converse-rai.js
+++ b/src/plugins/converse-rai.js
@@ -1,195 +1,172 @@
-(function() {
-  let Strophe, $iq, $msg, $pres, $build, b64_sha1, _, Backbone, dayjs, _converse;
-  let interestingServers = new Set();
-  let subscribedServers = new Set();
-
-  converse.plugins.add('converse-rai', {
-    'dependencies': [],
-
-    'initialize': function() {
-      _converse = this._converse;
-
-      Strophe = converse.env.Strophe;
-      $iq = converse.env.$iq;
-      $msg = converse.env.$msg;
-      $pres = converse.env.$pres;
-      $build = converse.env.$build;
-      b64_sha1 = converse.env.b64_sha1;
-      _ = converse.env._;
-      Backbone = converse.env.Backbone;
-      dayjs = converse.env.dayjs;
-
-      _converse.api.settings.extend({
-        rai_notification: true,
-        rai_notification_label: 'Room Activity Indicator',
-      });
-
-      _converse.api.listen.on('connected', function() {
-        setupRoomActivityIndicators();
-      });
-
-      _converse.api.listen.on('chatRoomViewInitialized', function(view) {
-        const jid = view.model.get('jid');
-
-        if (view.model.get('num_unread') > 0 || view.model.get('num_unread_general') > 0) {
-          emitNotification(jid);
-        }
-      });
-
-      _converse.api.listen.on('raiRoomsUpdated', function(rooms) {
-        interestingServers = new Set(rooms.filter(room => room).map(Strophe.getDomainFromJid));
-        if (_converse.api.connection.connected()) {
-          updateSubscriptions();
-        }
-      });
-
-      _converse.api.listen.on('chatBoxScrolledDown', function(view) {
-        const jid = view.chatbox.get('jid');
-        const id_attr = 'stanza_id ' + jid;
-        const messages = view.chatbox.messages;
-
-        if (!getUnreadStatus(jid)) {
-          return;
+/**
+ * Add RAI support to MUCs.
+ * @see https://xmpp.org/extensions/inbox/room-activity-indicators.html
+ */
+converse.plugins.add('converse-rai', {
+  dependencies: [
+    'converse-chatboxes',
+  ],
+  overrides: {
+    ChatRoom: {
+      async initialize() {
+        await this.__super__.initialize.apply(this, arguments);
+        this.listenTo(this, 'change:hidden', this.onHiddenChange);
+      },
+
+      /**
+       * Subscribe to RAI if connected to the room, but it's hidden.
+       * @private
+       * @method _converse.ChatRoom#onConnectionStatusChanged
+       */
+      async onConnectionStatusChanged() {
+        console.log('ChatRoom#onConnectionStatusChanged')
+        const { api } = this.__super__._converse;
+        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();
+            this.enableRAI();
+          } else {
+            await this.__super__.onConnectionStatusChanged.apply(this, arguments);
+          }
         }
-
-        for (let i = messages.length - 1; i >= 0; i--) {
-          const message = messages.at(i);
-
-          if (message.has(id_attr)) {
-            let id = message.get(id_attr);
-            if (id != sessionStorage.getItem('rai_displayed.' + jid)) {
-              sessionStorage.setItem('rai_displayed.' + jid, id);
-              setUnreadStatus(jid, false);
-              setTimeout(() => sendMarker(jid, id, 'displayed'), 0);
+      },
+
+      /**
+       * Handler that gets called when the 'hidden' flag is toggled.
+       * @private
+       * @method _converse.ChatRoom#onHiddenChange
+       */
+      async onHiddenChange() {
+        console.log('ChatRoomView#onHiddenChange')
+        const conn_status = this.session.get('connection_status');
+        const { api } = this.__super__._converse;
+
+        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();
             }
-            break;
+            this.enableRAI();
           }
+        } else if (conn_status === converse.ROOMSTATUS.DISCONNECTED) {
+          this.rejoin();
         }
-      });
-
-      _converse.api.listen.on('chatBoxInsertedIntoDOM', function(view) {
-        const jid = view.model.get('jid');
-
-        if (view.model.get('num_unread') > 0) {
-          emitNotification(jid);
+      },
+
+      /**
+       * 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() {
+        console.log('ChatRoom#enableRAI')
+        const { api } = this.__super__._converse;
+        if (api.settings.get('muc_subscribe_to_rai')) {
+          api.rooms.subscribe(this.get('jid'));
         }
+      },
+    },
+    ChatBox: {
+      /**
+       * Finds the last eligible message and then sends a XEP-0333 chat marker for it.
+       * @param { ('received'|'displayed'|'acknowledged') } [type='displayed']
+       * @param { Boolean } force - Whether a marker should be sent for the
+       *  message, even if it didn't include a `markable` element.
+       */
+      sendMarkerForLastMessage(type = 'displayed', force = false) {
+        const msgs = Array.from(this.messages.models);
+        msgs.reverse();
+        const msg = msgs.find(m => m.get('sender') === 'them' && (force || m.get('is_markable')));
+        msg && this.sendMarkerForMessage(msg, type, force);
+      },
+    }
+  },
+  initialize() {
+    const { Strophe, _, $pres, u } = converse.env;
+    const _converse = this._converse;
 
-      });
+    // Register namespace
+    Strophe.addNamespace('RAI', 'urn:xmpp:rai:0');
 
-      _converse.api.listen.on('message', function(data) {
-        var chatbox = data.chatbox;
-        var history = data.attrs.is_archived;
-        var sender = data.attrs.sender;
-        var body = data.attrs.body;
+    _converse.api.listen.on('addClientFeatures', () => {
+      _converse.api.disco.own.features.add(Strophe.NS.RAI);
+    });
 
-        if (!history && body && chatbox && sender !== 'me') {
-          const alert = chatbox.get('num_unread') > 0;
-          const notify = chatbox.get('num_unread_general') > 0;
+    // Register settings
+    _converse.api.settings.extend({
+      muc_subscribe_to_rai: true,
+    });
 
-          if (alert || notify) {
-            emitNotification(chatbox.get('jid'), alert);
-          }
+    Object.assign(_converse.api.rooms, {
+      /**
+       * Send an RAI stanza for the given jids.
+       * The presence stanza is sent for a whole muc domain.
+       *
+       * @param {string|Array} jids
+       * @returns {void}
+       */
+      subscribe(jids) {
+        if (typeof jids === 'string') {
+          jids = [jids];
         }
-      });
-    },
-  });
-
 
-  function subscribeServer(server_name) {
-    const id = Math.random().toString(36).substr(2, 9);
-    _converse.connection.send(converse.env.$pres({
-      to: server_name,
-      id: id,
-    }).c('rai', {
-      'xmlns': 'xmpp:prosody.im/protocol/rai',
-    }));
-  }
-
-  function unsubscribeServer(server_name) {
-    _converse.connection.send(converse.env.$pres({
-      to: server_name, type: 'unavailable',
-    }).c('rai', {
-      'xmlns': 'xmpp:prosody.im/protocol/rai',
-    }));
-  }
+        const muc_domains = jids.map(jid => Strophe.getDomainFromJid(jid));
 
-  function updateSubscriptions() {
-    var new_servers = new Set([...interestingServers].filter(server => !subscribedServers.has(server)));
-    var obsolete_servers = new Set([...subscribedServers].filter(server => !interestingServers.has(server)));
-    for (let server of obsolete_servers) {
-      unsubscribeServer(server);
-    }
-    for (let server of new_servers) {
-      subscribeServer(server);
-    }
-  }
-
-  function setupRoomActivityIndicators() {
-    updateSubscriptions();
-    // If we already have unread notifications stored for this session, emit them now
-    for (var i = 0; i < sessionStorage.length; i++) {
-      if (sessionStorage.key(i).indexOf('rai_notify.') == 0) {
-        const jid = sessionStorage.key(i).substring(11);
-        emitNotification(jid);
+        _.uniq(muc_domains).forEach(muc_domain => {
+          const rai = $pres({ to: muc_domain, id: u.getUniqueId() }).c('rai', {
+            'xmlns': Strophe.NS.RAI
+          });
+          _converse.api.send(rai);
+          console.log('Sent RAI stanza for muc_domain', muc_domain, rai.toString());
+        });
       }
-    }
+    });
 
-    // Listen for incoming RAI from the server
-    _converse.connection.addHandler(function(message) {
-      const from_jid = message.attributes.from?.nodevalue;
-      const room_jid = from_jid?.split('/')[0];
-      const room = '';
+    function mucActivityHandler(message) {
+      console.log('mucActivityHandler', message)
+      const from_jid = message.attributes.from?.nodevalue
+      const room_jid = from_jid?.split("/")[0]
+      let room = '';
       let ignore = false;
+
       for (let i = 0; i < _converse.chatboxes.models.length; i++) {
         if (_converse.chatboxes.models[i].id === room_jid) {
           room = _converse.chatboxes.models[i].id;
           break;
         }
       }
+
       if (room && from_jid && room_jid) {
         if (from_jid === room_jid + '/' + room.get('nick')) {
           ignore = true;
         }
       }
-      if (message && !ignore)
-        message.querySelectorAll('activity').forEach(function(activity) {
-          if (activity && activity.namespaceURI == 'xmpp:prosody.im/protocol/rai') {
+
+      if (message && !ignore) {
+        message.querySelectorAll('activity').forEach(activity => {
+          if (activity && activity.namespaceURI === Strophe.NS.RAI) {
             const jid = activity.textContent;
-            setUnreadStatus(jid, true);
-            emitNotification(jid);
+            // TODO: Give the chatroom object instead of the jid
+            _converse.api.trigger('chatRoomHasActivity', jid);
           }
         });
-      return true;
-    }, null, 'message');
-  }
+      }
 
-  function setUnreadStatus(jid, flag) {
-    if (flag) {
-      sessionStorage.setItem('rai_notify.' + jid, 'true');
-    } else {
-      sessionStorage.removeItem('rai_notify.' + jid);
+      return true;
     }
-  }
-
-  function getUnreadStatus(jid) {
-    return sessionStorage.getItem('rai_notify.' + jid) == 'true';
-  }
-
-  function emitNotification(jid, alert) {
-    _converse.api.trigger('chatRoomActivityIndicators', jid);
-  }
 
-  function sendMarker(to_jid, id, type) {
-    const stanza = converse.env.$msg({
-      'from': _converse.connection.jid,
-      'id': Math.random().toString(36).substr(2, 9),
-      'to': to_jid,
-      'type': 'groupchat',
-    }).c(type, {
-      'xmlns': converse.env.Strophe.NS.MARKERS,
-      'id': id,
+    // TODO: On loading subscribe to all the ChatViews in navigation (into another plugin)
+    _converse.api.listen.on('connected', () => {
+      _converse.connection.addHandler(mucActivityHandler, null, 'message', 'groupchat');
     });
 
-    _converse.api.send(stanza);
+    // TODO: Remove
+    // _converse.api.listen.on('send', stanza => console.log('send', stanza));
   }
-})();
+});
diff --git a/src/plugins/sib-subscribe-to-rai.js b/src/plugins/sib-subscribe-to-rai.js
index 51dbb91..58bc2d3 100644
--- a/src/plugins/sib-subscribe-to-rai.js
+++ b/src/plugins/sib-subscribe-to-rai.js
@@ -57,9 +57,9 @@ converse.plugins.add('sib-subscribe-to-rai', {
     ])).flat();
 
     // @MattJ Here userRooms is an array of each jabberID the user is on.
-    api.trigger('raiRoomsUpdated', userRooms);
+    api.rooms.subscribe(userRooms);
 
-    api.listen.on('chatRoomActivityIndicators', jid => {
+    api.listen.on('chatRoomHasActivity', jid => {
       window.dispatchEvent(new CustomEvent('newMessage', {
         detail: {
           jid,
-- 
GitLab