diff --git a/examples/node/app.js b/examples/node/app.js index dcff22aac8e..fd486830bcb 100644 --- a/examples/node/app.js +++ b/examples/node/app.js @@ -202,9 +202,9 @@ function printRoomList() { dateStr = new Date(msg.getTs()).toISOString().replace( /T/, ' ').replace(/\..+/, ''); } - var me = roomList[i].getMember(myUserId); - if (me) { - fmt = fmts[me.membership]; + var myMembership = roomList[i].getMyMembership(); + if (myMembership) { + fmt = fmts[myMembership]; } var roomName = fixWidth(roomList[i].name, 25); print( diff --git a/spec/integ/matrix-client-methods.spec.js b/spec/integ/matrix-client-methods.spec.js index 57a63a8420f..5077a8ef4fa 100644 --- a/spec/integ/matrix-client-methods.spec.js +++ b/spec/integ/matrix-client-methods.spec.js @@ -159,7 +159,7 @@ describe("MatrixClient", function() { describe("joinRoom", function() { it("should no-op if you've already joined a room", function() { const roomId = "!foo:bar"; - const room = new Room(roomId); + const room = new Room(roomId, userId); room.addLiveEvents([ utils.mkMembership({ user: userId, room: roomId, mship: "join", event: true, diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index 3a436342d4a..2cc3bfe478a 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -387,7 +387,7 @@ describe("Room", function() { let events = null; beforeEach(function() { - room = new Room(roomId, {timelineSupport: timelineSupport}); + room = new Room(roomId, null, {timelineSupport: timelineSupport}); // set events each time to avoid resusing Event objects (which // doesn't work because they get frozen) events = [ @@ -469,7 +469,7 @@ describe("Room", function() { describe("compareEventOrdering", function() { beforeEach(function() { - room = new Room(roomId, {timelineSupport: true}); + room = new Room(roomId, null, {timelineSupport: true}); }); const events = [ @@ -658,7 +658,7 @@ describe("Room", function() { beforeEach(function() { // no mocking - room = new Room(roomId); + room = new Room(roomId, userA); }); describe("Room.recalculate => Stripped State Events", function() { @@ -677,7 +677,7 @@ describe("Room", function() { }, ]; - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(roomName); }); @@ -696,7 +696,7 @@ describe("Room", function() { }, ]; - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(roomName); }); }); @@ -711,7 +711,7 @@ describe("Room", function() { "m.heroes": [userB, userC, userD], }); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(`${userB} and 2 others`); }); @@ -721,7 +721,7 @@ describe("Room", function() { "m.joined_member_count": 2, }); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(userB); }); @@ -733,7 +733,7 @@ describe("Room", function() { "m.heroes": [userB], }); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(name); }); @@ -745,7 +745,7 @@ describe("Room", function() { "m.joined_member_count": 50, "m.invited_member_count": 50, }); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(`${name} and 98 others`); }); @@ -757,7 +757,7 @@ describe("Room", function() { room.setSummary({ "m.heroes": [userB, userC], }); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(`${nameB} and ${nameC}`); }); @@ -768,7 +768,7 @@ describe("Room", function() { room.setSummary({ "m.heroes": [userB], }); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(nameB); }); @@ -777,7 +777,7 @@ describe("Room", function() { "m.heroes": [], "m.invited_member_count": 1, }); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual("Empty room"); }); }); @@ -791,7 +791,7 @@ describe("Room", function() { addMember(userB); addMember(userC); addMember(userD); - room.recalculate(userA); + room.recalculate(); const name = room.name; // we expect at least 1 member to be mentioned const others = [userB, userC, userD]; @@ -812,7 +812,7 @@ describe("Room", function() { addMember(userA); addMember(userB); addMember(userC); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name.indexOf(userB)).toNotEqual(-1, name); expect(name.indexOf(userC)).toNotEqual(-1, name); @@ -825,7 +825,7 @@ describe("Room", function() { addMember(userA); addMember(userB); addMember(userC); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name.indexOf(userB)).toNotEqual(-1, name); expect(name.indexOf(userC)).toNotEqual(-1, name); @@ -837,7 +837,7 @@ describe("Room", function() { setJoinRule("public"); addMember(userA); addMember(userB); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name.indexOf(userB)).toNotEqual(-1, name); }); @@ -848,7 +848,7 @@ describe("Room", function() { setJoinRule("invite"); addMember(userA); addMember(userB); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name.indexOf(userB)).toNotEqual(-1, name); }); @@ -858,7 +858,7 @@ describe("Room", function() { setJoinRule("invite"); addMember(userA, "invite", {user: userB}); addMember(userB); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name.indexOf(userB)).toNotEqual(-1, name); }); @@ -868,7 +868,7 @@ describe("Room", function() { const alias = "#room_alias:here"; setJoinRule("invite"); setAliases([alias, "#another:one"]); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name).toEqual(alias); }); @@ -878,7 +878,7 @@ describe("Room", function() { const alias = "#room_alias:here"; setJoinRule("public"); setAliases([alias, "#another:one"]); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name).toEqual(alias); }); @@ -888,7 +888,7 @@ describe("Room", function() { const roomName = "A mighty name indeed"; setJoinRule("invite"); setRoomName(roomName); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name).toEqual(roomName); }); @@ -898,7 +898,7 @@ describe("Room", function() { const roomName = "A mighty name indeed"; setJoinRule("public"); setRoomName(roomName); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual(roomName); }); @@ -906,7 +906,7 @@ describe("Room", function() { " a room name and alias don't exist and it is a self-chat.", function() { setJoinRule("invite"); addMember(userA); - room.recalculate(userA); + room.recalculate(); expect(room.name).toEqual("Empty room"); }); @@ -914,7 +914,7 @@ describe("Room", function() { " room name and alias don't exist and it is a self-chat.", function() { setJoinRule("public"); addMember(userA); - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name).toEqual("Empty room"); }); @@ -922,7 +922,7 @@ describe("Room", function() { it("should return 'Empty room' if there is no name, " + "alias or members in the room.", function() { - room.recalculate(userA); + room.recalculate(); const name = room.name; expect(name).toEqual("Empty room"); }); @@ -931,9 +931,9 @@ describe("Room", function() { "available", function() { setJoinRule("invite"); - addMember(userA, 'join', {name: "Alice"}); - addMember(userB, "invite", {user: userA}); - room.recalculate(userB); + addMember(userB, 'join', {name: "Alice"}); + addMember(userA, "invite", {user: userA}); + room.recalculate(); const name = room.name; expect(name).toEqual("Alice"); }); @@ -941,11 +941,11 @@ describe("Room", function() { it("should return inviter mxid if display name not available", function() { setJoinRule("invite"); - addMember(userA); - addMember(userB, "invite", {user: userA}); - room.recalculate(userB); + addMember(userB); + addMember(userA, "invite", {user: userA}); + room.recalculate(); const name = room.name; - expect(name).toEqual(userA); + expect(name).toEqual(userB); }); }); }); @@ -1192,7 +1192,7 @@ describe("Room", function() { describe("addPendingEvent", function() { it("should add pending events to the pendingEventList if " + "pendingEventOrdering == 'detached'", function() { - const room = new Room(roomId, { + const room = new Room(roomId, userA, { pendingEventOrdering: "detached", }); const eventA = utils.mkMessage({ @@ -1218,7 +1218,7 @@ describe("Room", function() { it("should add pending events to the timeline if " + "pendingEventOrdering == 'chronological'", function() { - room = new Room(roomId, { + room = new Room(roomId, userA, { pendingEventOrdering: "chronological", }); const eventA = utils.mkMessage({ @@ -1242,7 +1242,7 @@ describe("Room", function() { describe("updatePendingEvent", function() { it("should remove cancelled events from the pending list", function() { - const room = new Room(roomId, { + const room = new Room(roomId, userA, { pendingEventOrdering: "detached", }); const eventA = utils.mkMessage({ @@ -1278,7 +1278,7 @@ describe("Room", function() { it("should remove cancelled events from the timeline", function() { - const room = new Room(roomId); + const room = new Room(roomId, userA); const eventA = utils.mkMessage({ room: roomId, user: userA, event: true, }); @@ -1318,7 +1318,7 @@ describe("Room", function() { }); it("should apply member events", async function() { - const room = new Room(roomId); + const room = new Room(roomId, null); await room.loadOutOfBandMembers(Promise.resolve([memberEvent])); const memberA = room.getMember("@user_a:bar"); expect(memberA.name).toEqual("User A"); @@ -1329,7 +1329,7 @@ describe("Room", function() { user: "@user_a:bar", mship: "join", room: roomId, event: true, name: "Ms A", }); - const room = new Room(roomId); + const room = new Room(roomId, null); const promise2 = Promise.resolve([memberEvent2]); const promise1 = promise2.then(() => [memberEvent]); @@ -1342,7 +1342,7 @@ describe("Room", function() { }); it("should revert needs loading on error", async function() { - const room = new Room(roomId); + const room = new Room(roomId, null); let hasThrown = false; try { await room.loadOutOfBandMembers(Promise.reject(new Error("bugger"))); @@ -1353,4 +1353,18 @@ describe("Room", function() { expect(room.needsOutOfBandMembers()).toEqual(true); }); }); + + describe("getMyMembership", function() { + it("should return synced membership if membership isn't available yet", + async function() { + const room = new Room(roomId, userA); + room.setSyncedMembership("invite"); + expect(room.getMyMembership()).toEqual("invite"); + room.addLiveEvents([utils.mkMembership({ + user: userA, mship: "join", + room: roomId, event: true, + })]); + expect(room.getMyMembership()).toEqual("join"); + }); + }); }); diff --git a/src/crypto/index.js b/src/crypto/index.js index 4a9cbf7eb26..273dcce70e5 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1008,14 +1008,8 @@ Crypto.prototype._getE2eRooms = function() { } // ignore any rooms which we have left - const me = room.getMember(this._userId); - if (!me || ( - me.membership !== "join" && me.membership !== "invite" - )) { - return false; - } - - return true; + const myMembership = room.getMyMembership(); + return myMembership === "join" || myMembership === "invite"; }); }; diff --git a/src/models/room.js b/src/models/room.js index 0d4d4deaa62..60e5e861c78 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -68,6 +68,7 @@ function synthesizeReceipt(userId, event, receiptType) { * @constructor * @alias module:models/room * @param {string} roomId Required. The ID of this room. + * @param {string} myUserId Required. The ID of the syncing user. * @param {Object=} opts Configuration options * @param {*} opts.storageToken Optional. The token which a data store can use * to remember the state of the room. What this means is dependent on the store @@ -102,7 +103,7 @@ function synthesizeReceipt(userId, event, receiptType) { * @prop {*} storageToken A token which a data store can use to remember * the state of the room. */ -function Room(roomId, opts) { +function Room(roomId, myUserId, opts) { opts = opts || {}; opts.pendingEventOrdering = opts.pendingEventOrdering || "chronological"; @@ -115,6 +116,7 @@ function Room(roomId, opts) { ); } + this.myUserId = myUserId; this.roomId = roomId; this.name = roomId; this.tags = { @@ -216,9 +218,9 @@ Room.prototype.getLastEventId = function() { * @param {string} myUserId the user id for the logged in member * @return {string} the membership type (join | leave | invite) for the logged in user */ -Room.prototype.getMyMembership = function(myUserId) { - if (myUserId) { - const me = this.getMember(myUserId); +Room.prototype.getMyMembership = function() { + if (this.myUserId) { + const me = this.getMember(this.myUserId); if (me) { return me.membership; } @@ -226,6 +228,28 @@ Room.prototype.getMyMembership = function(myUserId) { return this._syncedMembership; }; +/** + * If this room is a DM we're invited to, + * try to find out who invited us + * @return {string} user id of the inviter + */ +Room.prototype.getDMInviter = function() { + if (this.myUserId) { + const me = this.getMember(this.myUserId); + if (me) { + return me.getDMInviter(); + } + } + if (this._syncedMembership === "invite") { + // fall back to summary information + const memberCount = this.currentState.getJoinedMemberCount() + + this.currentState.getInvitedMemberCount(); + if (memberCount == 2 && this._summaryHeroes.length) { + return this._summaryHeroes[0]; + } + } +}; + /** * Sets the membership this room was received as during sync * @param {string} membership join | leave | invite @@ -1002,15 +1026,14 @@ Room.prototype.removeEvent = function(eventId) { * Recalculate various aspects of the room, including the room name and * room summary. Call this any time the room's current state is modified. * May fire "Room.name" if the room name is updated. - * @param {string} userId The client's user ID. * @fires module:client~MatrixClient#event:"Room.name" */ -Room.prototype.recalculate = function(userId) { +Room.prototype.recalculate = function() { // set fake stripped state events if this is an invite room so logic remains // consistent elsewhere. const self = this; const membershipEvent = this.currentState.getStateEvents( - "m.room.member", userId, + "m.room.member", this.myUserId, ); if (membershipEvent && membershipEvent.getContent().membership === "invite") { const strippedStateEvents = membershipEvent.event.invite_room_state || []; @@ -1026,14 +1049,14 @@ Room.prototype.recalculate = function(userId) { content: strippedEvent.content, event_id: "$fake" + Date.now(), room_id: self.roomId, - user_id: userId, // technically a lie + user_id: self.myUserId, // technically a lie })]); } }); } const oldName = this.name; - this.name = calculateRoomName(this, userId); + this.name = calculateRoomName(this, this.myUserId); this.summary = new RoomSummary(this.roomId, { title: this.name, }); @@ -1308,7 +1331,7 @@ function calculateRoomName(room, userId, ignoreRoomNameEvent) { return memberNamesToRoomName(otherNames, inviteJoinCount); } - const myMembership = room.getMyMembership(userId); + const myMembership = room.getMyMembership(); // if I have created a room and invited people throuh // 3rd party invites if (myMembership == 'join') { diff --git a/src/sync.js b/src/sync.js index 2e934fde3ab..c68f0a8b454 100644 --- a/src/sync.js +++ b/src/sync.js @@ -112,7 +112,7 @@ function SyncApi(client, opts) { */ SyncApi.prototype.createRoom = function(roomId) { const client = this.client; - const room = new Room(roomId, { + const room = new Room(roomId, client.getUserId(), { pendingEventOrdering: this.opts.pendingEventOrdering, timelineSupport: client.timelineSupport, }); @@ -231,7 +231,7 @@ SyncApi.prototype.syncLeftRooms = function() { self._processRoomEvents(room, stateEvents, timelineEvents); - room.recalculate(client.credentials.userId); + room.recalculate(); client.store.storeRoom(room); client.emit("Room", room); @@ -302,7 +302,7 @@ SyncApi.prototype.peek = function(roomId) { peekRoom.currentState.setStateEvents(stateEvents); self._resolveInvites(peekRoom); - peekRoom.recalculate(self.client.credentials.userId); + peekRoom.recalculate(); // roll backwards to diverge old state. addEventsToTimeline // will overwrite the pagination token, so make sure it overwrites @@ -955,7 +955,7 @@ SyncApi.prototype._processSyncResponse = async function( self._mapSyncEventsFormat(inviteObj.invite_state, room); self._processRoomEvents(room, stateEvents); if (inviteObj.isBrandNewRoom) { - room.recalculate(client.credentials.userId); + room.recalculate(); client.store.storeRoom(room); client.emit("Room", room); } @@ -1062,7 +1062,7 @@ SyncApi.prototype._processSyncResponse = async function( // we deliberately don't add accountData to the timeline room.addAccountData(accountDataEvents); - room.recalculate(client.credentials.userId); + room.recalculate(); if (joinObj.isBrandNewRoom) { client.store.storeRoom(room); client.emit("Room", room); @@ -1102,7 +1102,7 @@ SyncApi.prototype._processSyncResponse = async function( self._processRoomEvents(room, stateEvents, timelineEvents); room.addAccountData(accountDataEvents); - room.recalculate(client.credentials.userId); + room.recalculate(); if (leaveObj.isBrandNewRoom) { client.store.storeRoom(room); client.emit("Room", room); @@ -1392,7 +1392,7 @@ SyncApi.prototype._processRoomEvents = function(room, stateEventList, // a recalculation (like m.room.name) we won't recalculate until we've // finished adding all the events, which will cause the notification to have // the old room name rather than the new one. - room.recalculate(this.client.credentials.userId); + room.recalculate(); // If the timeline wasn't empty, we process the state events here: they're // defined as updates to the state before the start of the timeline, so this