Skip to content

Commit 8e30510

Browse files
committed
Update the store on action
1 parent f9754ea commit 8e30510

File tree

2 files changed

+203
-7
lines changed

2 files changed

+203
-7
lines changed

src/stores/room-list-v3/RoomListStoreV3.ts

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
55
Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import type { EmptyObject, Room } from "matrix-js-sdk/src/matrix";
8+
import { logger } from "matrix-js-sdk/src/logger";
9+
import { EventType } from "matrix-js-sdk/src/matrix";
10+
11+
import type { EmptyObject, Room, RoomState } from "matrix-js-sdk/src/matrix";
912
import type { MatrixDispatcher } from "../../dispatcher/dispatcher";
1013
import type { ActionPayload } from "../../dispatcher/payloads";
1114
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
@@ -16,6 +19,8 @@ import { LISTS_UPDATE_EVENT } from "../room-list/RoomListStore";
1619
import { RoomSkipList } from "./skip-list/RoomSkipList";
1720
import { RecencySorter } from "./skip-list/sorters/RecencySorter";
1821
import { AlphabeticSorter } from "./skip-list/sorters/AlphabeticSorter";
22+
import { readReceiptChangeIsFor } from "../../utils/read-receipts";
23+
import { EffectiveMembership, getEffectiveMembership, getEffectiveMembershipTag } from "../../utils/membership";
1924

2025
/**
2126
* This store allows for fast retrieval of the room list in a sorted and filtered manner.
@@ -78,7 +83,93 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
7883
}
7984

8085
protected async onAction(payload: ActionPayload): Promise<void> {
81-
return;
86+
if (!this.matrixClient || !this.roomSkipList?.initialized) return;
87+
88+
switch (payload.action) {
89+
case "MatrixActions.Room.receipt": {
90+
if (readReceiptChangeIsFor(payload.event, this.matrixClient)) {
91+
const room = payload.room;
92+
if (!room) {
93+
logger.warn(`Own read receipt was in unknown room ${room.roomId}`);
94+
return;
95+
}
96+
this.addRoomAndEmit(room);
97+
}
98+
break;
99+
}
100+
case "MatrixActions.Room.tags": {
101+
const room = payload.room;
102+
this.addRoomAndEmit(room);
103+
break;
104+
}
105+
case "MatrixActions.Event.decrypted": {
106+
const roomId = payload.event.getRoomId();
107+
if (!roomId) return;
108+
const room = this.matrixClient.getRoom(roomId);
109+
if (!room) {
110+
logger.warn(`Event ${payload.event.getId()} was decrypted in an unknown room ${roomId}`);
111+
return;
112+
}
113+
break;
114+
}
115+
case "MatrixActions.accountData": {
116+
if (payload.event_type !== EventType.Direct) return;
117+
const dmMap = payload.event.getContent();
118+
console.log("dmMap", dmMap);
119+
for (const userId of Object.keys(dmMap)) {
120+
const roomIds = dmMap[userId];
121+
for (const roomId of roomIds) {
122+
const room = this.matrixClient.getRoom(roomId);
123+
console.log("room", room);
124+
if (!room) {
125+
logger.warn(`${roomId} was found in DMs but the room is not in the store`);
126+
continue;
127+
}
128+
this.addRoomAndEmit(room);
129+
}
130+
}
131+
break;
132+
}
133+
case "MatrixActions.Room.timeline": {
134+
// Ignore non-live events (backfill) and notification timeline set events (without a room)
135+
if (!payload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent || !payload.room) return;
136+
137+
const roomId = payload.event.getRoomId();
138+
const tryAdd = (): boolean => {
139+
const room = this.matrixClient?.getRoom(roomId);
140+
if (room) this.addRoomAndEmit(room);
141+
return !!room;
142+
};
143+
if (!tryAdd()) setTimeout(tryAdd, 100);
144+
break;
145+
}
146+
case "MatrixActions.Room.myMembership": {
147+
const oldMembership = getEffectiveMembership(payload.oldMembership);
148+
const newMembership = getEffectiveMembershipTag(payload.room, payload.membership);
149+
if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) {
150+
// If we're joining an upgraded room, we'll want to make sure we don't proliferate
151+
// the dead room in the list.
152+
const roomState: RoomState = payload.room.currentState;
153+
const predecessor = roomState.findPredecessor(this.msc3946ProcessDynamicPredecessor);
154+
if (predecessor) {
155+
const prevRoom = this.matrixClient?.getRoom(predecessor.roomId);
156+
if (prevRoom) {
157+
this.roomSkipList.removeRoom(prevRoom);
158+
} else {
159+
logger.warn(`Unable to find predecessor room with id ${predecessor.roomId}`);
160+
}
161+
}
162+
}
163+
this.addRoomAndEmit(payload.room);
164+
break;
165+
}
166+
}
167+
}
168+
169+
private addRoomAndEmit(room: Room): void {
170+
if (!this.roomSkipList) throw new Error("roomSkipList hasn't been created yet!");
171+
this.roomSkipList.addRoom(room);
172+
this.emit(LISTS_UPDATE_EVENT);
82173
}
83174
}
84175

test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,26 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
55
Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import type { MatrixDispatcher } from "../../../../src/dispatcher/dispatcher";
8+
import { EventType, KnownMembership, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
9+
910
import { RoomListStoreV3Class } from "../../../../src/stores/room-list-v3/RoomListStoreV3";
1011
import { AsyncStoreWithClient } from "../../../../src/stores/AsyncStoreWithClient";
1112
import { RecencySorter } from "../../../../src/stores/room-list-v3/skip-list/sorters/RecencySorter";
12-
import { stubClient } from "../../../test-utils";
13+
import { mkEvent, mkMessage, stubClient, upsertRoomStateEvents } from "../../../test-utils";
1314
import { getMockedRooms } from "./skip-list/getMockedRooms";
1415
import { AlphabeticSorter } from "../../../../src/stores/room-list-v3/skip-list/sorters/AlphabeticSorter";
16+
import dispatcher from "../../../../src/dispatcher/dispatcher";
17+
import { LISTS_UPDATE_EVENT } from "../../../../src/stores/room-list/RoomListStore";
1518

1619
describe("RoomListStoreV3", () => {
1720
async function getRoomListStore() {
1821
const client = stubClient();
1922
const rooms = getMockedRooms(client);
2023
client.getVisibleRooms = jest.fn().mockReturnValue(rooms);
2124
jest.spyOn(AsyncStoreWithClient.prototype, "matrixClient", "get").mockReturnValue(client);
22-
const fakeDispatcher = { register: jest.fn() } as unknown as MatrixDispatcher;
23-
const store = new RoomListStoreV3Class(fakeDispatcher);
25+
const store = new RoomListStoreV3Class(dispatcher);
2426
store.start();
25-
return { client, rooms, store };
27+
return { client, rooms, store, dispatcher };
2628
}
2729

2830
it("Provides an unsorted list of rooms", async () => {
@@ -50,4 +52,107 @@ describe("RoomListStoreV3", () => {
5052
sortedRooms = new RecencySorter(client.getSafeUserId()).sort(rooms);
5153
expect(store.getSortedRooms()).toEqual(sortedRooms);
5254
});
55+
56+
describe("Updates", () => {
57+
it("Room is re-inserted on timeline event", async () => {
58+
const { store, rooms, dispatcher } = await getRoomListStore();
59+
60+
// Let's pretend like a new timeline event came on the room in 37th index.
61+
const room = rooms[37];
62+
const event = mkMessage({ room: room.roomId, user: `@foo${3}:matrix.org`, ts: 1000, event: true });
63+
room.timeline.push(event);
64+
65+
const payload = {
66+
action: "MatrixActions.Room.timeline",
67+
event,
68+
isLiveEvent: true,
69+
isLiveUnfilteredRoomTimelineEvent: true,
70+
room,
71+
};
72+
73+
const fn = jest.fn();
74+
store.on(LISTS_UPDATE_EVENT, fn);
75+
dispatcher.dispatch(payload, true);
76+
77+
expect(fn).toHaveBeenCalled();
78+
expect(store.getSortedRooms()[0].roomId).toEqual(room.roomId);
79+
});
80+
81+
it("Predecessor room is removed on room upgrade", async () => {
82+
const { store, rooms, client, dispatcher } = await getRoomListStore();
83+
// Let's say that !foo32:matrix.org is being upgraded
84+
const oldRoom = rooms[32];
85+
// Create a new room with a predecessor event that points to oldRoom
86+
const newRoom = new Room("!foonew:matrix.org", client, client.getSafeUserId(), {});
87+
const createWithPredecessor = new MatrixEvent({
88+
type: EventType.RoomCreate,
89+
sender: "@foo:foo.org",
90+
room_id: newRoom.roomId,
91+
content: {
92+
predecessor: { room_id: oldRoom.roomId, event_id: "tombstone_event_id" },
93+
},
94+
event_id: "$create",
95+
state_key: "",
96+
});
97+
upsertRoomStateEvents(newRoom, [createWithPredecessor]);
98+
99+
const fn = jest.fn();
100+
store.on(LISTS_UPDATE_EVENT, fn);
101+
dispatcher.dispatch(
102+
{
103+
action: "MatrixActions.Room.myMembership",
104+
oldMembership: KnownMembership.Invite,
105+
membership: KnownMembership.Join,
106+
room: newRoom,
107+
},
108+
true,
109+
);
110+
111+
expect(fn).toHaveBeenCalled();
112+
const roomIds = store.getSortedRooms().map((r) => r.roomId);
113+
expect(roomIds).not.toContain(oldRoom.roomId);
114+
expect(roomIds).toContain(newRoom.roomId);
115+
});
116+
117+
it("Rooms are inserted on m.direct event", async () => {
118+
const { store, dispatcher } = await getRoomListStore();
119+
120+
// Let's create a m.direct event that we can dispatch
121+
const content = {
122+
"@bar1:matrix.org": ["!newroom1:matrix.org", "!newroom2:matrix.org"],
123+
"@bar2:matrix.org": ["!newroom3:matrix.org", "!newroom4:matrix.org"],
124+
"@bar3:matrix.org": ["!newroom5:matrix.org"],
125+
};
126+
const event = mkEvent({
127+
event: true,
128+
content,
129+
user: "@foo:matrix.org",
130+
type: EventType.Direct,
131+
});
132+
133+
const fn = jest.fn();
134+
store.on(LISTS_UPDATE_EVENT, fn);
135+
dispatcher.dispatch(
136+
{
137+
action: "MatrixActions.accountData",
138+
event_type: EventType.Direct,
139+
event,
140+
},
141+
true,
142+
);
143+
144+
// Each of these rooms should now appear in the store
145+
// We don't need to mock the rooms themselves since our mocked
146+
// client will create the rooms on getRoom() call.
147+
expect(fn).toHaveBeenCalledTimes(5);
148+
const roomIds = store.getSortedRooms().map((r) => r.roomId);
149+
[
150+
"!newroom1:matrix.org",
151+
"!newroom2:matrix.org",
152+
"!newroom3:matrix.org",
153+
"!newroom4:matrix.org",
154+
"!newroom5:matrix.org",
155+
].forEach((id) => expect(roomIds).toContain(id));
156+
});
157+
});
53158
});

0 commit comments

Comments
 (0)