Bug 1699093 - Show prompt for invites to Matrix DM rooms. r=clokep
authorMartin Giger <martin@humanoids.be>
Wed, 16 Jun 2021 04:02:16 +0000
changeset 32830 be4b329957e6565c9ef472ac800ff59329f0eeaf
parent 32829 5c9c0312e5da9264366459a3369843a0febccc39
child 32831 8cda313415a3c5f233f6dc5ff8d4ee2cfbd89d12
push id18880
push usergeoff@darktrojan.net
push dateWed, 16 Jun 2021 04:04:25 +0000
treeherdercomm-central@be4b329957e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersclokep
bugs1699093
Bug 1699093 - Show prompt for invites to Matrix DM rooms. r=clokep Differential Revision: https://phabricator.services.mozilla.com/D116888
chat/protocols/matrix/matrix.jsm
chat/protocols/matrix/test/head.js
chat/protocols/matrix/test/test_matrixAccount.js
--- a/chat/protocols/matrix/matrix.jsm
+++ b/chat/protocols/matrix/matrix.jsm
@@ -2,22 +2,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var EXPORTED_SYMBOLS = ["MatrixProtocol"];
 
 const { clearTimeout, setTimeout } = ChromeUtils.import(
   "resource://gre/modules/Timer.jsm"
 );
-var {
-  XPCOMUtils,
-  EmptyEnumerator,
-  nsSimpleEnumerator,
-  l10nHelper,
-} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
+var { XPCOMUtils, nsSimpleEnumerator, l10nHelper } = ChromeUtils.import(
+  "resource:///modules/imXPCOMUtils.jsm"
+);
 var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
 var {
   GenericAccountPrototype,
   GenericConvChatPrototype,
   GenericConvChatBuddyPrototype,
   GenericProtocolPrototype,
   GenericConversationPrototype,
   GenericConvIMPrototype,
@@ -210,26 +207,29 @@ MatrixBuddy.prototype = {
   get buddyIconFilename() {
     return (
       (this._user &&
         getHttpUriForMxc(this._account._baseURL, this._user.avatarUrl)) ||
       ""
     );
   },
 
+  get canSendMessage() {
+    return true;
+  },
+
   /**
    * Initialize the buddy with a user.
    *
    * @param {User} user - Matrix user.
    */
   setUser(user) {
     this._user = user;
     this._serverAlias = user.displayName;
-    this._statusType = getStatusFromPresence(user);
-    this._statusText = user.presenceStatusMsg ?? "";
+    this.setStatus(getStatusFromPresence(user), user.presenceStatusMsg ?? "");
   },
 
   /**
    * Updates the buddy's status based on its JS SDK user's presence.
    */
   setStatusFromPresence() {
     this.setStatus(
       getStatusFromPresence(this._user),
@@ -247,16 +247,24 @@ MatrixBuddy.prototype = {
           delete conversation.buddy;
           conversation.close();
         }
       }
     }
     this._account.buddies.delete(this.userName);
     GenericAccountBuddyPrototype.remove.call(this);
   },
+
+  getTooltipInfo() {
+    return this._account.getBuddyInfo(this.userName);
+  },
+
+  createConversation() {
+    return this._account.getDirectConversation(this.userName);
+  },
 };
 
 /**
  * Matrix rooms are androgynous. Sometimes they are DM conversations, other
  * times they are MUCs.
  * This class implements both conversations state and transition between the
  * two. Methods are grouped by shared/MUC/DM.
  * The type is only changed on explicit request.
@@ -834,39 +842,49 @@ MatrixRoom.prototype = {
   /**
    * Initialize the room after the response from the Matrix client.
    *
    * @param {Room} room - associated room with the conversation.
    */
   initRoomDm(room) {
     const dmUserId = room.guessDMUserId();
     if (dmUserId === this._account.userId) {
-      // We are the only member of the room.
-      this._setInitialized();
+      // We are the only member of the room that we know of.
+      // This can sometimes happen when we get a room before all membership
+      // events got synced in.
       return;
     }
     if (!this.buddy) {
-      if (this._account.buddies.has(dmUserId)) {
-        this.buddy = this._account.buddies.get(dmUserId);
-        if (!this.buddy._user) {
-          const user = this._account._client.getUser(dmUserId);
-          this.buddy.setUser(user);
-        }
-        return;
+      this.initBuddy(dmUserId);
+    }
+  },
+
+  /**
+   * Initialize the buddy for this conversation.
+   *
+   * @param {string} dmUserId - MXID of the user on the other side of this DM.
+   */
+  initBuddy(dmUserId) {
+    if (this._account.buddies.has(dmUserId)) {
+      this.buddy = this._account.buddies.get(dmUserId);
+      if (!this.buddy._user) {
+        const user = this._account._client.getUser(dmUserId);
+        this.buddy.setUser(user);
       }
-      const user = this._account._client.getUser(dmUserId);
-      this.buddy = new MatrixBuddy(
-        this._account,
-        null,
-        Services.tags.defaultTag,
-        user
-      );
-      Services.contacts.accountBuddyAdded(this.buddy);
-      this._account.buddies.set(dmUserId, this.buddy);
+      return;
     }
+    const user = this._account._client.getUser(dmUserId);
+    this.buddy = new MatrixBuddy(
+      this._account,
+      null,
+      Services.tags.defaultTag,
+      user
+    );
+    Services.contacts.accountBuddyAdded(this.buddy);
+    this._account.buddies.set(dmUserId, this.buddy);
   },
 
   /**
    * Clean up the buddy associated with this DM conversation if it is the last
    * conversation associated with it.
    */
   closeDm() {
     if (this.buddy) {
@@ -1253,33 +1271,17 @@ MatrixAccount.prototype = {
     this._client.on("Room", room => {
       if (this._catchingUp || room.isSpaceRoom()) {
         return;
       }
       let me = room.getMember(this.userId);
       // For now just auto accept the invites by joining the room.
       if (me && me.membership == "invite") {
         if (me.events.member.getContent().is_direct) {
-          let roomMembers = room.getJoinedMembers();
-          // If there is just single user in the room, then set the
-          // room as a DM Room by adding it to dmMap in our user's accountData.
-          if (roomMembers.length == 1) {
-            let interlocutorId = roomMembers[0].userId;
-            this.setDirectRoom(interlocutorId, room.roomId);
-            // For the invited rooms, we will not get the summary info from
-            // the room object created after the joining. So we need to use
-            // the name from the room object here.
-            this.getDirectConversation(
-              interlocutorId,
-              room.roomId,
-              room.summary.info.title
-            );
-          } else {
-            this.getGroupConversation(room.roomId, room.summary.info.title);
-          }
+          this.invitedToDM(room);
         } else {
           this.getGroupConversation(room.roomId, room.summary.info.title);
         }
       } else if (me && me.membership == "join") {
         // To avoid the race condition. Whenever we will create the room,
         // this will also be fired. So we want to avoid creating duplicate
         // conversations for the same room.
         if (
@@ -1395,16 +1397,47 @@ MatrixAccount.prototype = {
       // state.
       if (this.getDMRoomIdsForUserId(userId).length === 0) {
         buddy.remove();
       }
     }
   },
 
   /**
+   * A user invited this user to a DM room.
+   *
+   * @param {Room} room - Room we're invited to.
+   */
+  invitedToDM(room) {
+    let userId = room.getDMInviter();
+    this.addBuddyRequest(
+      userId,
+      () => {
+        this.setDirectRoom(userId, room.roomId);
+        // For the invited rooms, we will not get the summary info from
+        // the room object created after the joining. So we need to use
+        // the name from the room object here.
+        const conversation = this.getDirectConversation(
+          userId,
+          room.roomId,
+          room.summary.info.title
+        );
+        if (room.getInvitedAndJoinedMemberCount() !== 2) {
+          conversation.waitForRoom().then((conv) => {
+            conv.checkForUpdate();
+          });
+        }
+      },
+      () => {
+        this._client.leave(room.roomId);
+      }
+    );
+  },
+
+  /**
    * Set the matrix user presence based on the given status info.
    *
    * @param {imIStatus} statusInfo
    */
   setPresence(statusInfo) {
     const presenceDetails = {
       presence: "offline",
       status_msg: statusInfo.statusText,
@@ -1805,16 +1838,21 @@ MatrixAccount.prototype = {
         .joinRoom(DMRoomId || roomID)
         .catch(error => {
           conv.joining = false;
           conv.close();
           throw error;
         })
         .then(room => {
           conv.initRoom(room);
+          // The membership events will sometimes be missing to initialize the
+          // buddy correctly in the normal room init.
+          if (!conv.buddy) {
+            conv.initBuddy(userId);
+          }
         })
         .catch(error => {
           this.ERROR(
             "Error creating conversation " + (DMRoomId || roomID) + ": " + error
           );
           if (conv.joining) {
             conv.joining = false;
             conv.forget();
@@ -1900,25 +1938,29 @@ MatrixAccount.prototype = {
     throw new Error("Adding buddies is not yet supported");
   },
   loadBuddy(aBuddy, aTag) {
     const buddy = new MatrixBuddy(this, aBuddy, aTag);
     this.buddies.set(buddy.userName, buddy);
     return buddy;
   },
 
-  requestBuddyInfo(aUserId) {
+  /**
+   * Get tooltip info for a user.
+   *
+   * @param {string} aUserId - MXID to get tooltip data for.
+   * @returns {Array<prplITooltipInfo>}
+   */
+  getBuddyInfo(aUserId) {
+    if (!this.connected) {
+      return [];
+    }
     let user = this._client.getUser(aUserId);
     if (!user) {
-      Services.obs.notifyObservers(
-        EmptyEnumerator,
-        "user-info-received",
-        aUserId
-      );
-      return;
+      return [];
     }
 
     // Convert timespan in milli-seconds into a human-readable form.
     let getNormalizedTime = function(aTime) {
       let valuesAndUnits = DownloadUtils.convertTimeUnits(aTime / 1000);
       // If the time is exact to the first set of units, trim off
       // the subsequent zeroes.
       if (!valuesAndUnits[2]) {
@@ -1964,18 +2006,22 @@ MatrixAccount.prototype = {
         false
       );
       // TODO Cache the photo URI for this participant.
       tooltipInfo.push(
         new TooltipInfo(null, realUrl, Ci.prplITooltipInfo.icon)
       );
     }
 
+    return tooltipInfo;
+  },
+
+  requestBuddyInfo(aUserId) {
     Services.obs.notifyObservers(
-      new nsSimpleEnumerator(tooltipInfo),
+      new nsSimpleEnumerator(this.getBuddyInfo(aUserId)),
       "user-info-received",
       aUserId
     );
   },
 
   get userId() {
     return this._client.credentials.userId;
   },
--- a/chat/protocols/matrix/test/head.js
+++ b/chat/protocols/matrix/test/head.js
@@ -61,16 +61,19 @@ function getClientRoom(roomId, clientHan
               },
             };
           },
         };
       },
       isSpaceRoom() {
         return false;
       },
+      getLastActiveTimestamp() {
+        return Date.now();
+      },
     },
     makeProxyHandler(clientHandler)
   );
   client._rooms.set(roomId, room);
   return room;
 }
 
 /**
@@ -100,16 +103,17 @@ function getAccount(clientHandler) {
         return this._rooms.get(roomId);
       },
       async joinRoom(roomId) {
         if (!this._rooms.has(roomId)) {
           getClientRoom(roomId, clientHandler, this);
         }
         return this._rooms.get(roomId);
       },
+      setAccountData(field, data) {},
     },
     makeProxyHandler(clientHandler)
   );
   return account;
 }
 
 /**
  * @param {(any, string) => any?|object} [clientHandler]
--- a/chat/protocols/matrix/test/test_matrixAccount.js
+++ b/chat/protocols/matrix/test/test_matrixAccount.js
@@ -290,16 +290,45 @@ add_task(async function test_getDMRoomId
   equal(invalid.length, 0);
 
   const rooms = account.getDMRoomIdsForUserId("@test:example.com");
   ok(Array.isArray(rooms));
   equal(rooms.length, 1);
   equal(rooms[0], "!asdf:example.com");
 });
 
+add_task(async function test_invitedToDMIn_deny() {
+  const dmRoomId = "!test:example.com";
+  let leftRoom = false;
+  const account = getAccount({
+    leave(roomId) {
+      equal(roomId, dmRoomId);
+      leftRoom = true;
+    },
+  });
+  const room = getClientRoom(
+    dmRoomId,
+    {
+      getDMInviter() {
+        return "@other:example.com";
+      },
+    },
+    account._client
+  );
+  const requestObserver = TestUtils.topicObserved(
+    "buddy-authorization-request"
+  );
+  account.invitedToDM(room);
+  const [request] = await requestObserver;
+  request.QueryInterface(Ci.prplIBuddyRequest);
+  equal(request.userName, "@other:example.com");
+  request.deny();
+  ok(leftRoom);
+});
+
 function mockMatrixRoom(roomId) {
   return {
     getMyMembership() {
       return "join";
     },
     getJoinedMembers() {
       return [];
     },