Skip to content

Commit 48d2715

Browse files
committed
Implement MSC3946 for getVisibleRooms
1 parent 66ae985 commit 48d2715

File tree

5 files changed

+213
-3
lines changed

5 files changed

+213
-3
lines changed

spec/unit/matrix-client.spec.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2234,7 +2234,78 @@ describe("MatrixClient", function () {
22342234
});
22352235
}
22362236

2237+
function predecessorEvent(newRoomId: string, predecessorRoomId: string): MatrixEvent {
2238+
return new MatrixEvent({
2239+
content: {
2240+
predecessor_room_id: predecessorRoomId,
2241+
},
2242+
event_id: `predecessor_event_id_pred_${predecessorRoomId}`,
2243+
origin_server_ts: 1432735824653,
2244+
room_id: newRoomId,
2245+
sender: "@daryl:alexandria.example.com",
2246+
state_key: "",
2247+
type: "org.matrix.msc3946.room_predecessor",
2248+
});
2249+
}
2250+
22372251
describe("getVisibleRooms", () => {
2252+
function setUpReplacedRooms(): {
2253+
room1: Room;
2254+
room2: Room;
2255+
replacedByCreate1: Room;
2256+
replacedByCreate2: Room;
2257+
replacedByDynamicPredecessor1: Room;
2258+
replacedByDynamicPredecessor2: Room;
2259+
} {
2260+
const room1 = new Room("room1", client, "@carol:alexandria.example.com");
2261+
const replacedByCreate1 = new Room("replacedByCreate1", client, "@carol:alexandria.example.com");
2262+
const replacedByCreate2 = new Room("replacedByCreate2", client, "@carol:alexandria.example.com");
2263+
const replacedByDynamicPredecessor1 = new Room("dyn1", client, "@carol:alexandria.example.com");
2264+
const replacedByDynamicPredecessor2 = new Room("dyn2", client, "@carol:alexandria.example.com");
2265+
const room2 = new Room("room2", client, "@daryl:alexandria.example.com");
2266+
client.store = new StubStore();
2267+
client.store.getRooms = () => [
2268+
room1,
2269+
replacedByCreate1,
2270+
replacedByCreate2,
2271+
replacedByDynamicPredecessor1,
2272+
replacedByDynamicPredecessor2,
2273+
room2,
2274+
];
2275+
room1.addLiveEvents(
2276+
[
2277+
roomCreateEvent(room1.roomId, replacedByCreate1.roomId),
2278+
predecessorEvent(room1.roomId, replacedByDynamicPredecessor1.roomId),
2279+
],
2280+
{},
2281+
);
2282+
room2.addLiveEvents(
2283+
[
2284+
roomCreateEvent(room2.roomId, replacedByCreate2.roomId),
2285+
predecessorEvent(room2.roomId, replacedByDynamicPredecessor2.roomId),
2286+
],
2287+
{},
2288+
);
2289+
replacedByCreate1.addLiveEvents([tombstoneEvent(room1.roomId, replacedByCreate1.roomId)], {});
2290+
replacedByCreate2.addLiveEvents([tombstoneEvent(room2.roomId, replacedByCreate2.roomId)], {});
2291+
replacedByDynamicPredecessor1.addLiveEvents(
2292+
[tombstoneEvent(room1.roomId, replacedByDynamicPredecessor1.roomId)],
2293+
{},
2294+
);
2295+
replacedByDynamicPredecessor2.addLiveEvents(
2296+
[tombstoneEvent(room2.roomId, replacedByDynamicPredecessor2.roomId)],
2297+
{},
2298+
);
2299+
2300+
return {
2301+
room1,
2302+
room2,
2303+
replacedByCreate1,
2304+
replacedByCreate2,
2305+
replacedByDynamicPredecessor1,
2306+
replacedByDynamicPredecessor2,
2307+
};
2308+
}
22382309
it("Returns an empty list if there are no rooms", () => {
22392310
client.store = new StubStore();
22402311
client.store.getRooms = () => [];
@@ -2275,6 +2346,82 @@ describe("MatrixClient", function () {
22752346
expect(rooms).toContain(room1);
22762347
expect(rooms).toContain(room2);
22772348
});
2349+
2350+
it("Ignores m.predecessor if we don't ask to use it", () => {
2351+
// Given 6 rooms, 2 of which have been replaced, and 2 of which WERE
2352+
// replaced by create events, but are now NOT replaced, because an
2353+
// m.predecessor event has changed the room's predecessor.
2354+
const {
2355+
room1,
2356+
room2,
2357+
replacedByCreate1,
2358+
replacedByCreate2,
2359+
replacedByDynamicPredecessor1,
2360+
replacedByDynamicPredecessor2,
2361+
} = setUpReplacedRooms();
2362+
2363+
// When we ask for the visible rooms
2364+
const rooms = client.getVisibleRooms(); // Don't supply msc3946ProcessDynamicPredecessor
2365+
2366+
// Then we only get the ones that have not been replaced
2367+
expect(rooms).not.toContain(replacedByCreate1);
2368+
expect(rooms).not.toContain(replacedByCreate2);
2369+
expect(rooms).toContain(replacedByDynamicPredecessor1);
2370+
expect(rooms).toContain(replacedByDynamicPredecessor2);
2371+
expect(rooms).toContain(room1);
2372+
expect(rooms).toContain(room2);
2373+
});
2374+
2375+
it("Considers rooms replaced with m.predecessor events to be replaced", () => {
2376+
// Given 6 rooms, 2 of which have been replaced, and 2 of which WERE
2377+
// replaced by create events, but are now NOT replaced, because an
2378+
// m.predecessor event has changed the room's predecessor.
2379+
const {
2380+
room1,
2381+
room2,
2382+
replacedByCreate1,
2383+
replacedByCreate2,
2384+
replacedByDynamicPredecessor1,
2385+
replacedByDynamicPredecessor2,
2386+
} = setUpReplacedRooms();
2387+
2388+
// When we ask for the visible rooms
2389+
const useMsc3946 = true;
2390+
const rooms = client.getVisibleRooms(useMsc3946);
2391+
2392+
// Then we only get the ones that have not been replaced
2393+
expect(rooms).not.toContain(replacedByDynamicPredecessor1);
2394+
expect(rooms).not.toContain(replacedByDynamicPredecessor2);
2395+
expect(rooms).toContain(replacedByCreate1);
2396+
expect(rooms).toContain(replacedByCreate2);
2397+
expect(rooms).toContain(room1);
2398+
expect(rooms).toContain(room2);
2399+
});
2400+
2401+
it("Ignores m.predecessor if we don't ask to use it", () => {
2402+
// Given 6 rooms, 2 of which have been replaced, and 2 of which WERE
2403+
// replaced by create events, but are now NOT replaced, because an
2404+
// m.predecessor event has changed the room's predecessor.
2405+
const {
2406+
room1,
2407+
room2,
2408+
replacedByCreate1,
2409+
replacedByCreate2,
2410+
replacedByDynamicPredecessor1,
2411+
replacedByDynamicPredecessor2,
2412+
} = setUpReplacedRooms();
2413+
2414+
// When we ask for the visible rooms
2415+
const rooms = client.getVisibleRooms(); // Don't supply msc3946ProcessDynamicPredecessor
2416+
2417+
// Then we only get the ones that have not been replaced
2418+
expect(rooms).not.toContain(replacedByCreate1);
2419+
expect(rooms).not.toContain(replacedByCreate2);
2420+
expect(rooms).toContain(replacedByDynamicPredecessor1);
2421+
expect(rooms).toContain(replacedByDynamicPredecessor2);
2422+
expect(rooms).toContain(room1);
2423+
expect(rooms).toContain(room2);
2424+
});
22782425
});
22792426

22802427
describe("getRoomUpgradeHistory", () => {

spec/unit/room.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3266,6 +3266,20 @@ describe("Room", function () {
32663266
});
32673267
}
32683268

3269+
function predecessorEvent(newRoomId: string, predecessorRoomId: string): MatrixEvent {
3270+
return new MatrixEvent({
3271+
content: {
3272+
predecessor_room_id: predecessorRoomId,
3273+
},
3274+
event_id: `predecessor_event_id_pred_${predecessorRoomId}`,
3275+
origin_server_ts: 1432735824653,
3276+
room_id: newRoomId,
3277+
sender: "@daryl:alexandria.example.com",
3278+
state_key: "",
3279+
type: "org.matrix.msc3946.room_predecessor",
3280+
});
3281+
}
3282+
32693283
it("Returns null if there is no create event", () => {
32703284
const room = new Room("roomid", client!, "@u:example.com");
32713285
expect(room.findPredecessorRoomId()).toBeNull();
@@ -3282,5 +3296,37 @@ describe("Room", function () {
32823296
room.addLiveEvents([roomCreateEvent("roomid", "replacedroomid")]);
32833297
expect(room.findPredecessorRoomId()).toBe("replacedroomid");
32843298
});
3299+
3300+
it("Prefers the m.predecessor event if one exists", () => {
3301+
const room = new Room("roomid", client!, "@u:example.com");
3302+
room.addLiveEvents([
3303+
roomCreateEvent("roomid", "replacedroomid"),
3304+
predecessorEvent("roomid", "otherreplacedroomid"),
3305+
]);
3306+
const useMsc3946 = true;
3307+
expect(room.findPredecessorRoomId(useMsc3946)).toBe("otherreplacedroomid");
3308+
});
3309+
3310+
it("Ignores the m.predecessor event if we don't ask to use it", () => {
3311+
const room = new Room("roomid", client!, "@u:example.com");
3312+
room.addLiveEvents([
3313+
roomCreateEvent("roomid", "replacedroomid"),
3314+
predecessorEvent("roomid", "otherreplacedroomid"),
3315+
]);
3316+
// Don't provide an argument for msc3946ProcessDynamicPredecessor -
3317+
// we should ignore the predecessor event.
3318+
expect(room.findPredecessorRoomId()).toBe("replacedroomid");
3319+
});
3320+
3321+
it("Ignores the m.predecessor event and returns null if we don't ask to use it", () => {
3322+
const room = new Room("roomid", client!, "@u:example.com");
3323+
room.addLiveEvents([
3324+
roomCreateEvent("roomid", null), // Create event has no predecessor
3325+
predecessorEvent("roomid", "otherreplacedroomid"),
3326+
]);
3327+
// Don't provide an argument for msc3946ProcessDynamicPredecessor -
3328+
// we should ignore the predecessor event.
3329+
expect(room.findPredecessorRoomId()).toBeNull();
3330+
});
32853331
});
32863332
});

src/@types/event.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export enum EventType {
3333
RoomGuestAccess = "m.room.guest_access",
3434
RoomServerAcl = "m.room.server_acl",
3535
RoomTombstone = "m.room.tombstone",
36+
RoomPredecessor = "org.matrix.msc3946.room_predecessor",
3637

3738
SpaceChild = "m.space.child",
3839
SpaceParent = "m.space.parent",

src/client.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3780,14 +3780,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
37803780
* This is essentially getRooms() with some rooms filtered out, eg. old versions
37813781
* of rooms that have been replaced or (in future) other rooms that have been
37823782
* marked at the protocol level as not to be displayed to the user.
3783+
*
3784+
* @param msc3946ProcessDynamicPredecessor - if true, look for an
3785+
* m.room.predecessor state event and
3786+
* use it if found (MSC3946).
37833787
* @returns A list of rooms, or an empty list if there is no data store.
37843788
*/
3785-
public getVisibleRooms(): Room[] {
3789+
public getVisibleRooms(msc3946ProcessDynamicPredecessor = false): Room[] {
37863790
const allRooms = this.store.getRooms();
37873791

37883792
const replacedRooms = new Set();
37893793
for (const r of allRooms) {
3790-
const predecessor = r.findPredecessorRoomId();
3794+
const predecessor = r.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
37913795
if (predecessor) {
37923796
replacedRooms.add(predecessor);
37933797
}

src/models/room.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2991,14 +2991,26 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
29912991
}
29922992

29932993
/**
2994+
* @param msc3946ProcessDynamicPredecessor - if true, look for an
2995+
* m.room.predecessor state event and
2996+
* use it if found (MSC3946).
29942997
* @returns the ID of the room that was this room's predecessor, or null if
29952998
* this room has no predecessor.
29962999
*/
2997-
public findPredecessorRoomId(): string | null {
3000+
public findPredecessorRoomId(msc3946ProcessDynamicPredecessor = false): string | null {
29983001
const currentState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);
29993002
if (!currentState) {
30003003
return null;
30013004
}
3005+
if (msc3946ProcessDynamicPredecessor) {
3006+
const predecessorEvent = currentState.getStateEvents(EventType.RoomPredecessor, "");
3007+
if (predecessorEvent) {
3008+
const roomId = predecessorEvent.getContent()["predecessor_room_id"];
3009+
if (roomId) {
3010+
return roomId;
3011+
}
3012+
}
3013+
}
30023014

30033015
const createEvent = currentState.getStateEvents(EventType.RoomCreate, "");
30043016
if (createEvent) {

0 commit comments

Comments
 (0)