Skip to content

Commit f665afc

Browse files
committed
MSC3946 Dynamic predecessors for getVisibleRooms
1 parent 999e355 commit f665afc

File tree

5 files changed

+189
-3
lines changed

5 files changed

+189
-3
lines changed

spec/unit/matrix-client.spec.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2234,6 +2234,20 @@ 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
it("Returns an empty list if there are no rooms", () => {
22382252
client.store = new StubStore();
22392253
client.store.getRooms = () => [];
@@ -2274,5 +2288,114 @@ describe("MatrixClient", function () {
22742288
expect(rooms).toContain(room1);
22752289
expect(rooms).toContain(room2);
22762290
});
2291+
2292+
function setUpReplacedRooms(): {
2293+
room1: Room;
2294+
room2: Room;
2295+
replacedByCreate1: Room;
2296+
replacedByCreate2: Room;
2297+
replacedByDynamicPredecessor1: Room;
2298+
replacedByDynamicPredecessor2: Room;
2299+
} {
2300+
const room1 = new Room("room1", client, "@carol:alexandria.example.com");
2301+
const replacedByCreate1 = new Room("replacedByCreate1", client, "@carol:alexandria.example.com");
2302+
const replacedByCreate2 = new Room("replacedByCreate2", client, "@carol:alexandria.example.com");
2303+
const replacedByDynamicPredecessor1 = new Room("dyn1", client, "@carol:alexandria.example.com");
2304+
const replacedByDynamicPredecessor2 = new Room("dyn2", client, "@carol:alexandria.example.com");
2305+
const room2 = new Room("room2", client, "@daryl:alexandria.example.com");
2306+
client.store = new StubStore();
2307+
client.store.getRooms = () => [
2308+
room1,
2309+
replacedByCreate1,
2310+
replacedByCreate2,
2311+
replacedByDynamicPredecessor1,
2312+
replacedByDynamicPredecessor2,
2313+
room2,
2314+
];
2315+
room1.addLiveEvents(
2316+
[
2317+
roomCreateEvent(room1.roomId, replacedByCreate1.roomId),
2318+
predecessorEvent(room1.roomId, replacedByDynamicPredecessor1.roomId),
2319+
],
2320+
{},
2321+
);
2322+
room2.addLiveEvents(
2323+
[
2324+
roomCreateEvent(room2.roomId, replacedByCreate2.roomId),
2325+
predecessorEvent(room2.roomId, replacedByDynamicPredecessor2.roomId),
2326+
],
2327+
{},
2328+
);
2329+
replacedByCreate1.addLiveEvents([tombstoneEvent(room1.roomId, replacedByCreate1.roomId)], {});
2330+
replacedByCreate2.addLiveEvents([tombstoneEvent(room2.roomId, replacedByCreate2.roomId)], {});
2331+
replacedByDynamicPredecessor1.addLiveEvents(
2332+
[tombstoneEvent(room1.roomId, replacedByDynamicPredecessor1.roomId)],
2333+
{},
2334+
);
2335+
replacedByDynamicPredecessor2.addLiveEvents(
2336+
[tombstoneEvent(room2.roomId, replacedByDynamicPredecessor2.roomId)],
2337+
{},
2338+
);
2339+
2340+
return {
2341+
room1,
2342+
room2,
2343+
replacedByCreate1,
2344+
replacedByCreate2,
2345+
replacedByDynamicPredecessor1,
2346+
replacedByDynamicPredecessor2,
2347+
};
2348+
}
2349+
2350+
it("Considers rooms replaced with m.predecessor events to be replaced", () => {
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 useMsc3946 = true;
2365+
const rooms = client.getVisibleRooms(useMsc3946);
2366+
2367+
// Then we only get the ones that have not been replaced
2368+
expect(rooms).not.toContain(replacedByDynamicPredecessor1);
2369+
expect(rooms).not.toContain(replacedByDynamicPredecessor2);
2370+
expect(rooms).toContain(replacedByCreate1);
2371+
expect(rooms).toContain(replacedByCreate2);
2372+
expect(rooms).toContain(room1);
2373+
expect(rooms).toContain(room2);
2374+
});
2375+
2376+
it("Ignores m.predecessor if we don't ask to use it", () => {
2377+
// Given 6 rooms, 2 of which have been replaced, and 2 of which WERE
2378+
// replaced by create events, but are now NOT replaced, because an
2379+
// m.predecessor event has changed the room's predecessor.
2380+
const {
2381+
room1,
2382+
room2,
2383+
replacedByCreate1,
2384+
replacedByCreate2,
2385+
replacedByDynamicPredecessor1,
2386+
replacedByDynamicPredecessor2,
2387+
} = setUpReplacedRooms();
2388+
2389+
// When we ask for the visible rooms
2390+
const rooms = client.getVisibleRooms(); // Don't supply msc3946ProcessDynamicPredecessor
2391+
2392+
// Then we only get the ones that have not been replaced
2393+
expect(rooms).not.toContain(replacedByCreate1);
2394+
expect(rooms).not.toContain(replacedByCreate2);
2395+
expect(rooms).toContain(replacedByDynamicPredecessor1);
2396+
expect(rooms).toContain(replacedByDynamicPredecessor2);
2397+
expect(rooms).toContain(room1);
2398+
expect(rooms).toContain(room2);
2399+
});
22772400
});
22782401
});

spec/unit/room.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3257,6 +3257,20 @@ describe("Room", function () {
32573257
});
32583258
}
32593259

3260+
function predecessorEvent(newRoomId: string, predecessorRoomId: string): MatrixEvent {
3261+
return new MatrixEvent({
3262+
content: {
3263+
predecessor_room_id: predecessorRoomId,
3264+
},
3265+
event_id: `predecessor_event_id_pred_${predecessorRoomId}`,
3266+
origin_server_ts: 1432735824653,
3267+
room_id: newRoomId,
3268+
sender: "@daryl:alexandria.example.com",
3269+
state_key: "",
3270+
type: "org.matrix.msc3946.room_predecessor",
3271+
});
3272+
}
3273+
32603274
it("Returns null if there is no create event", () => {
32613275
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
32623276
expect(room.findPredecessorRoomId()).toBeNull();
@@ -3273,5 +3287,37 @@ describe("Room", function () {
32733287
room.addLiveEvents([roomCreateEvent("roomid", "replacedroomid")]);
32743288
expect(room.findPredecessorRoomId()).toBe("replacedroomid");
32753289
});
3290+
3291+
it("Prefers the m.predecessor event if one exists", () => {
3292+
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
3293+
room.addLiveEvents([
3294+
roomCreateEvent("roomid", "replacedroomid"),
3295+
predecessorEvent("roomid", "otherreplacedroomid"),
3296+
]);
3297+
const useMsc3946 = true;
3298+
expect(room.findPredecessorRoomId(useMsc3946)).toBe("otherreplacedroomid");
3299+
});
3300+
3301+
it("Ignores the m.predecessor event if we don't ask to use it", () => {
3302+
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
3303+
room.addLiveEvents([
3304+
roomCreateEvent("roomid", "replacedroomid"),
3305+
predecessorEvent("roomid", "otherreplacedroomid"),
3306+
]);
3307+
// Don't provide an argument for msc3946ProcessDynamicPredecessor -
3308+
// we should ignore the predecessor event.
3309+
expect(room.findPredecessorRoomId()).toBe("replacedroomid");
3310+
});
3311+
3312+
it("Ignores the m.predecessor event and returns null if we don't ask to use it", () => {
3313+
const room = new Room("roomid", null as unknown as MatrixClient, "@u:example.com");
3314+
room.addLiveEvents([
3315+
roomCreateEvent("roomid", null), // Create event has no predecessor
3316+
predecessorEvent("roomid", "otherreplacedroomid"),
3317+
]);
3318+
// Don't provide an argument for msc3946ProcessDynamicPredecessor -
3319+
// we should ignore the predecessor event.
3320+
expect(room.findPredecessorRoomId()).toBeNull();
3321+
});
32763322
});
32773323
});

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
@@ -3763,14 +3763,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
37633763
* This is essentially getRooms() with some rooms filtered out, eg. old versions
37643764
* of rooms that have been replaced or (in future) other rooms that have been
37653765
* marked at the protocol level as not to be displayed to the user.
3766+
*
3767+
* @param msc3946ProcessDynamicPredecessor - if true, look for an
3768+
* m.room.predecessor state event and
3769+
* use it if found (MSC3946).
37663770
* @returns A list of rooms, or an empty list if there is no data store.
37673771
*/
3768-
public getVisibleRooms(): Room[] {
3772+
public getVisibleRooms(msc3946ProcessDynamicPredecessor = false): Room[] {
37693773
const allRooms = this.store.getRooms();
37703774

37713775
const replacedRooms = new Set();
37723776
for (const r of allRooms) {
3773-
const predecessor = r.findPredecessorRoomId();
3777+
const predecessor = r.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
37743778
if (predecessor) {
37753779
replacedRooms.add(predecessor);
37763780
}

src/models/room.ts

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

29722972
/**
2973+
* @param msc3946ProcessDynamicPredecessor - if true, look for an
2974+
* m.room.predecessor state event and
2975+
* use it if found (MSC3946).
29732976
* @returns the ID of the room that was this room's predecessor, or null if
29742977
* this room has no predecessor.
29752978
*/
2976-
public findPredecessorRoomId(): string | null {
2979+
public findPredecessorRoomId(msc3946ProcessDynamicPredecessor = false): string | null {
29772980
const currentState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);
29782981
if (!currentState) {
29792982
return null;
29802983
}
2984+
if (msc3946ProcessDynamicPredecessor) {
2985+
const predecessorEvent = currentState.getStateEvents(EventType.RoomPredecessor, "");
2986+
if (predecessorEvent) {
2987+
const roomId = predecessorEvent.getContent()["predecessor_room_id"];
2988+
if (roomId) {
2989+
return roomId;
2990+
}
2991+
}
2992+
}
29812993

29822994
const createEvent = currentState.getStateEvents(EventType.RoomCreate, "");
29832995
if (createEvent) {

0 commit comments

Comments
 (0)