Skip to content

Commit cb7f327

Browse files
committed
Implement MSC3946 for getRoomUpgradeHistory
1 parent 48d2715 commit cb7f327

File tree

2 files changed

+86
-9
lines changed

2 files changed

+86
-9
lines changed

spec/unit/matrix-client.spec.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,6 +2458,52 @@ describe("MatrixClient", function () {
24582458
return [room1, room2, room3, room4];
24592459
}
24602460

2461+
/**
2462+
* Creates 2 alternate chains of room history: one using create
2463+
* events, and one using MSC2946 predecessor+tombstone events.
2464+
*
2465+
* Using create, history looks like:
2466+
* room1->room2->room3->room4 (but note we do not create tombstones)
2467+
*
2468+
* Using predecessor+tombstone, history looks like:
2469+
* dynRoom1->dynRoom2->room3->dynRoom4->dynRoom4
2470+
*
2471+
* @returns [room1, room2, room3, room4, dynRoom1, dynRoom2,
2472+
* dynRoom4, dynRoom5].
2473+
*/
2474+
function createDynamicRoomHistory(): [Room, Room, Room, Room, Room, Room, Room, Room] {
2475+
// Don't create tombstones for the old versions - we generally
2476+
// expect only one tombstone in a room, and we are confused by
2477+
// anything else.
2478+
const creates = true;
2479+
const tombstones = false;
2480+
const [room1, room2, room3, room4] = createRoomHistory(creates, tombstones);
2481+
const dynRoom1 = new Room("dynRoom1", client, "@rick:grimes.example.com");
2482+
const dynRoom2 = new Room("dynRoom2", client, "@rick:grimes.example.com");
2483+
const dynRoom4 = new Room("dynRoom4", client, "@rick:grimes.example.com");
2484+
const dynRoom5 = new Room("dynRoom5", client, "@rick:grimes.example.com");
2485+
2486+
dynRoom1.addLiveEvents([tombstoneEvent(dynRoom2.roomId, dynRoom1.roomId)], {});
2487+
dynRoom2.addLiveEvents([predecessorEvent(dynRoom2.roomId, dynRoom1.roomId)]);
2488+
2489+
dynRoom2.addLiveEvents([tombstoneEvent(room3.roomId, dynRoom2.roomId)], {});
2490+
room3.addLiveEvents([predecessorEvent(room3.roomId, dynRoom2.roomId)]);
2491+
2492+
room3.addLiveEvents([tombstoneEvent(dynRoom4.roomId, room3.roomId)], {});
2493+
dynRoom4.addLiveEvents([predecessorEvent(dynRoom4.roomId, room3.roomId)]);
2494+
2495+
dynRoom4.addLiveEvents([tombstoneEvent(dynRoom5.roomId, dynRoom4.roomId)], {});
2496+
dynRoom5.addLiveEvents([predecessorEvent(dynRoom5.roomId, dynRoom4.roomId)]);
2497+
2498+
mocked(store.getRoom)
2499+
.mockClear()
2500+
.mockImplementation((roomId: string) => {
2501+
return { room1, room2, room3, room4, dynRoom1, dynRoom2, dynRoom4, dynRoom5 }[roomId] || null;
2502+
});
2503+
2504+
return [room1, room2, room3, room4, dynRoom1, dynRoom2, dynRoom4, dynRoom5];
2505+
}
2506+
24612507
it("Returns an empty list if room does not exist", () => {
24622508
const history = client.getRoomUpgradeHistory("roomthatdoesnotexist");
24632509
expect(history).toHaveLength(0);
@@ -2600,6 +2646,30 @@ describe("MatrixClient", function () {
26002646
room4.roomId,
26012647
]);
26022648
});
2649+
2650+
it("Returns the predecessors and subsequent rooms using MSC3945 dynamic room predecessors", () => {
2651+
const [, , room3, , dynRoom1, dynRoom2, dynRoom4, dynRoom5] = createDynamicRoomHistory();
2652+
const useMsc3946 = true;
2653+
const verifyLinks = false;
2654+
const history = client.getRoomUpgradeHistory(room3.roomId, verifyLinks, useMsc3946);
2655+
expect(history.map((room) => room.roomId)).toEqual([
2656+
dynRoom1.roomId,
2657+
dynRoom2.roomId,
2658+
room3.roomId,
2659+
dynRoom4.roomId,
2660+
dynRoom5.roomId,
2661+
]);
2662+
});
2663+
2664+
it("When not asking for MSC3946, verified history without tombstones is empty", () => {
2665+
// There no tombstones to match the create events
2666+
const [, , room3] = createDynamicRoomHistory();
2667+
const useMsc3946 = false;
2668+
const verifyLinks = true;
2669+
const history = client.getRoomUpgradeHistory(room3.roomId, verifyLinks, useMsc3946);
2670+
// So we get no history back
2671+
expect(history.map((room) => room.roomId)).toEqual([room3.roomId]);
2672+
});
26032673
});
26042674
});
26052675
});

src/client.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4990,24 +4990,31 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
49904990
* which can be proven to be linked. For example, rooms which have a create
49914991
* event pointing to an old room which the client is not aware of or doesn't
49924992
* have a matching tombstone would not be returned.
4993+
* @param msc3946ProcessDynamicPredecessor - if true, look for
4994+
* m.room.predecessor state events as well as create events, and prefer
4995+
* predecessor events where they exist (MSC3946).
49934996
* @returns An array of rooms representing the upgrade
49944997
* history.
49954998
*/
4996-
public getRoomUpgradeHistory(roomId: string, verifyLinks = false): Room[] {
4999+
public getRoomUpgradeHistory(
5000+
roomId: string,
5001+
verifyLinks = false,
5002+
msc3946ProcessDynamicPredecessor = false,
5003+
): Room[] {
49975004
const currentRoom = this.getRoom(roomId);
49985005
if (!currentRoom) return [];
49995006

5000-
const before = this.findPredecessorRooms(currentRoom, verifyLinks);
5001-
const after = this.findSuccessorRooms(currentRoom, verifyLinks);
5007+
const before = this.findPredecessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor);
5008+
const after = this.findSuccessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor);
50025009

50035010
return [...before, currentRoom, ...after];
50045011
}
50055012

5006-
private findPredecessorRooms(room: Room, verifyLinks: boolean): Room[] {
5013+
private findPredecessorRooms(room: Room, verifyLinks: boolean, msc3946ProcessDynamicPredecessor: boolean): Room[] {
50075014
const ret: Room[] = [];
50085015

50095016
// Work backwards from newer to older rooms
5010-
let predecessorRoomId = room.findPredecessorRoomId();
5017+
let predecessorRoomId = room.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
50115018
while (predecessorRoomId !== null) {
50125019
const predecessorRoom = this.getRoom(predecessorRoomId);
50135020
if (predecessorRoom === null) {
@@ -5024,23 +5031,23 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
50245031
ret.splice(0, 0, predecessorRoom);
50255032

50265033
room = predecessorRoom;
5027-
predecessorRoomId = room.findPredecessorRoomId();
5034+
predecessorRoomId = room.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
50285035
}
50295036
return ret;
50305037
}
50315038

5032-
private findSuccessorRooms(room: Room, verifyLinks: boolean): Room[] {
5039+
private findSuccessorRooms(room: Room, verifyLinks: boolean, msc3946ProcessDynamicPredecessor: boolean): Room[] {
50335040
const ret: Room[] = [];
50345041

50355042
// Work forwards, looking at tombstone events
50365043
let tombstoneEvent = room.currentState.getStateEvents(EventType.RoomTombstone, "");
50375044
while (tombstoneEvent) {
50385045
const successorRoom = this.getRoom(tombstoneEvent.getContent()["replacement_room"]);
50395046
if (!successorRoom) break; // end of the chain
5040-
if (successorRoom.roomId === room.roomId) break; // Tombstone is referencing it's own room
5047+
if (successorRoom.roomId === room.roomId) break; // Tombstone is referencing its own room
50415048

50425049
if (verifyLinks) {
5043-
const predecessorRoomId = successorRoom.findPredecessorRoomId();
5050+
const predecessorRoomId = successorRoom.findPredecessorRoomId(msc3946ProcessDynamicPredecessor);
50445051
if (!predecessorRoomId || predecessorRoomId !== room.roomId) {
50455052
break;
50465053
}

0 commit comments

Comments
 (0)