From 7bb57ca13074941c0bb4ce3a29cf41be89ef23d8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 12 May 2023 20:39:46 -0600 Subject: [PATCH] Raise `room.join` with an event ID; fix `room.leave` to check membership (#319) Fixes https://github.com/turt2live/matrix-bot-sdk/issues/304 --- src/MatrixClient.ts | 15 ++++--- test/MatrixClientTest.ts | 60 ++++++++++++++++++++++---- test/SynchronousMatrixClientTest.ts | 65 ++++++++++++++++++++++++----- 3 files changed, 117 insertions(+), 23 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 14c55956..47dc6b72 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -808,6 +808,9 @@ export class MatrixClient extends EventEmitter { if (event['type'] !== 'm.room.member') continue; if (event['state_key'] !== await this.getUserId()) continue; + const membership = event["content"]?.["membership"]; + if (membership !== "leave" && membership !== "ban") continue; + const oldAge = leaveEvent && leaveEvent['unsigned'] && leaveEvent['unsigned']['age'] ? leaveEvent['unsigned']['age'] : 0; const newAge = event['unsigned'] && event['unsigned']['age'] ? event['unsigned']['age'] : 0; if (leaveEvent && oldAge < newAge) continue; @@ -855,11 +858,6 @@ export class MatrixClient extends EventEmitter { // Process rooms we've joined and their events for (const roomId in joinedRooms) { - if (this.lastJoinedRoomIds.indexOf(roomId) === -1) { - await emitFn("room.join", roomId); - this.lastJoinedRoomIds.push(roomId); - } - const room = joinedRooms[roomId]; if (room['account_data'] && room['account_data']['events']) { @@ -871,6 +869,13 @@ export class MatrixClient extends EventEmitter { if (!room['timeline'] || !room['timeline']['events']) continue; for (let event of room['timeline']['events']) { + if (event['type'] === "m.room.member" && event['state_key'] === await this.getUserId()) { + if (event['content']?.['membership'] === "join" && this.lastJoinedRoomIds.indexOf(roomId) === -1) { + await emitFn("room.join", roomId, await this.processEvent(event)); + this.lastJoinedRoomIds.push(roomId); + } + } + event = await this.processEvent(event); if (event['type'] === 'm.room.encrypted' && await this.crypto?.isRoomEncrypted(roomId)) { await emitFn("room.encrypted_event", roomId, event); diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 69aa55a7..6abdf93e 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -1441,6 +1441,7 @@ describe('MatrixClient', () => { type: "m.room.member", state_key: userId, unsigned: { age: 0 }, + content: { membership: "leave" }, }, ]; @@ -1498,16 +1499,19 @@ describe('MatrixClient', () => { type: "m.room.member", state_key: userId, unsigned: { age: 2 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId, unsigned: { age: 1 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId, unsigned: { age: 3 }, + content: { membership: "leave" }, }, ]; @@ -1536,16 +1540,19 @@ describe('MatrixClient', () => { type: "m.room.not_member", state_key: userId, unsigned: { age: 1 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId, unsigned: { age: 1 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId + "_wrong_member", unsigned: { age: 1 }, + content: { membership: "leave" }, }, ]; @@ -1580,6 +1587,7 @@ describe('MatrixClient', () => { // type: "m.room.member", // state_key: userId, // unsigned: {age: 1}, + // content: { membership: "leave" }, // }, { type: "m.room.member", @@ -1612,6 +1620,7 @@ describe('MatrixClient', () => { { type: "m.room.member", state_key: userId, + content: { membership: "leave" }, }, ]; @@ -1793,7 +1802,6 @@ describe('MatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; const events = [ - // TODO: Surely the 'invite' membership should be in some sort of content field? { type: "m.room.member", state_key: userId, @@ -1822,16 +1830,25 @@ describe('MatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; + const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, + ]; client.userId = userId; - const spy = simple.stub().callFn((rid) => { + const spy = simple.stub().callFn((rid, ev) => { + expect(ev).toMatchObject(events[0]); expect(rid).toEqual(roomId); }); realClient.on("room.join", spy); const roomsObj = {}; - roomsObj[roomId] = {}; + roomsObj[roomId] = { timeline: { events: events } }; await client.processSync({ rooms: { join: roomsObj } }); expect(spy.callCount).toBe(1); }); @@ -1871,16 +1888,25 @@ describe('MatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; + const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, + ]; client.userId = userId; - const spy = simple.stub().callFn((rid) => { + const spy = simple.stub().callFn((rid, ev) => { + expect(ev).toMatchObject(events[0]); expect(rid).toEqual(roomId); }); realClient.on("room.join", spy); const roomsObj = {}; - roomsObj[roomId] = {}; + roomsObj[roomId] = { timeline: { events: events } }; await client.processSync({ rooms: { join: roomsObj } }); expect(spy.callCount).toBe(1); await client.processSync({ rooms: { join: roomsObj } }); @@ -1926,6 +1952,12 @@ describe('MatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, { type: "m.room.not_message", content: { body: "hello world 1" }, @@ -1971,7 +2003,7 @@ describe('MatrixClient', () => { expect(inviteSpy.callCount).toBe(0); expect(leaveSpy.callCount).toBe(0); expect(messageSpy.callCount).toBe(2); - expect(eventSpy.callCount).toBe(4); + expect(eventSpy.callCount).toBe(5); }); it('should process tombstone events', async () => { @@ -1981,6 +2013,12 @@ describe('MatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, { type: "m.room.tombstone", content: { body: "hello world 1" }, @@ -2020,7 +2058,7 @@ describe('MatrixClient', () => { expect(inviteSpy.callCount).toBe(0); expect(leaveSpy.callCount).toBe(0); expect(archiveSpy.callCount).toBe(1); - expect(eventSpy.callCount).toBe(2); + expect(eventSpy.callCount).toBe(3); }); it('should process create events with a predecessor', async () => { @@ -2030,6 +2068,12 @@ describe('MatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, { type: "m.room.tombstone", content: { body: "hello world 1" }, @@ -2069,7 +2113,7 @@ describe('MatrixClient', () => { expect(inviteSpy.callCount).toBe(0); expect(leaveSpy.callCount).toBe(0); expect(upgradedSpy.callCount).toBe(1); - expect(eventSpy.callCount).toBe(2); + expect(eventSpy.callCount).toBe(3); }); it('should send events through a processor', async () => { diff --git a/test/SynchronousMatrixClientTest.ts b/test/SynchronousMatrixClientTest.ts index 39256846..ba6078e3 100644 --- a/test/SynchronousMatrixClientTest.ts +++ b/test/SynchronousMatrixClientTest.ts @@ -71,6 +71,7 @@ describe('SynchronousMatrixClient', () => { type: "m.room.member", state_key: userId, unsigned: { age: 0 }, + content: { membership: "leave" }, }, ]; @@ -138,16 +139,19 @@ describe('SynchronousMatrixClient', () => { type: "m.room.member", state_key: userId, unsigned: { age: 2 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId, unsigned: { age: 1 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId, unsigned: { age: 3 }, + content: { membership: "leave" }, }, ]; @@ -181,16 +185,19 @@ describe('SynchronousMatrixClient', () => { type: "m.room.not_member", state_key: userId, unsigned: { age: 1 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId, unsigned: { age: 1 }, + content: { membership: "leave" }, }, { type: "m.room.member", state_key: userId + "_wrong_member", unsigned: { age: 1 }, + content: { membership: "leave" }, }, ]; @@ -230,6 +237,7 @@ describe('SynchronousMatrixClient', () => { // type: "m.room.member", // state_key: userId, // unsigned: {age: 1}, + // content: { membership: "leave" }, // }, { type: "m.room.member", @@ -267,6 +275,7 @@ describe('SynchronousMatrixClient', () => { { type: "m.room.member", state_key: userId, + content: { membership: "leave" }, }, ]; @@ -507,10 +516,19 @@ describe('SynchronousMatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; + const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, + ]; client.userId = userId; - const spy = simple.stub().callFn((rid) => { + const spy = simple.stub().callFn((rid, ev) => { + expect(ev).toMatchObject(events[0]); expect(rid).toEqual(roomId); }); const syncSpy = simple.mock(realClient, 'onRoomJoin').callFn((rid) => { @@ -519,7 +537,7 @@ describe('SynchronousMatrixClient', () => { realClient.on("room.join", spy); const roomsObj = {}; - roomsObj[roomId] = {}; + roomsObj[roomId] = { timeline: { events: events } }; await realClient.doProcessSync({ rooms: { join: roomsObj } }); expect(spy.callCount).toBe(1); expect(syncSpy.callCount).toBe(1); @@ -565,10 +583,19 @@ describe('SynchronousMatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; + const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, + ]; client.userId = userId; - const spy = simple.stub().callFn((rid) => { + const spy = simple.stub().callFn((rid, ev) => { + expect(ev).toMatchObject(events[0]); expect(rid).toEqual(roomId); }); const syncSpy = simple.mock(realClient, 'onRoomJoin').callFn((rid) => { @@ -577,7 +604,7 @@ describe('SynchronousMatrixClient', () => { realClient.on("room.join", spy); const roomsObj = {}; - roomsObj[roomId] = {}; + roomsObj[roomId] = { timeline: { events: events } }; await realClient.doProcessSync({ rooms: { join: roomsObj } }); expect(spy.callCount).toBe(1); expect(syncSpy.callCount).toBe(1); @@ -593,6 +620,12 @@ describe('SynchronousMatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, { type: "m.room.not_message", content: { body: "hello world 1" }, @@ -657,8 +690,8 @@ describe('SynchronousMatrixClient', () => { expect(syncLeaveSpy.callCount).toBe(0); expect(messageSpy.callCount).toBe(2); expect(syncMessageSpy.callCount).toBe(2); - expect(eventSpy.callCount).toBe(4); - expect(syncEventSpy.callCount).toBe(4); + expect(eventSpy.callCount).toBe(5); + expect(syncEventSpy.callCount).toBe(5); }); it('should process tombstone events', async () => { @@ -668,6 +701,12 @@ describe('SynchronousMatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, { type: "m.room.tombstone", content: { body: "hello world 1" }, @@ -726,8 +765,8 @@ describe('SynchronousMatrixClient', () => { expect(syncLeaveSpy.callCount).toBe(0); expect(archiveSpy.callCount).toBe(1); expect(syncArchiveSpy.callCount).toBe(1); - expect(eventSpy.callCount).toBe(2); - expect(syncEventSpy.callCount).toBe(2); + expect(eventSpy.callCount).toBe(3); + expect(syncEventSpy.callCount).toBe(3); }); it('should process create events with a predecessor', async () => { @@ -737,6 +776,12 @@ describe('SynchronousMatrixClient', () => { const userId = "@syncing:example.org"; const roomId = "!testing:example.org"; const events = [ + { + type: "m.room.member", + state_key: userId, + unsigned: { age: 0 }, + content: { membership: "join" }, + }, { type: "m.room.tombstone", content: { body: "hello world 1" }, @@ -795,8 +840,8 @@ describe('SynchronousMatrixClient', () => { expect(syncLeaveSpy.callCount).toBe(0); expect(upgradedSpy.callCount).toBe(1); expect(syncUpgradedSpy.callCount).toBe(1); - expect(eventSpy.callCount).toBe(2); - expect(syncEventSpy.callCount).toBe(2); + expect(eventSpy.callCount).toBe(3); + expect(syncEventSpy.callCount).toBe(3); }); }); });