diff --git a/extensions/matrix/src/matrix/direct-management.test.ts b/extensions/matrix/src/matrix/direct-management.test.ts index 2d3753789d9..88b092f3f8f 100644 --- a/extensions/matrix/src/matrix/direct-management.test.ts +++ b/extensions/matrix/src/matrix/direct-management.test.ts @@ -210,6 +210,37 @@ describe("repairMatrixDirectRooms", () => { ); }); + it("persists discovered strict rooms alongside an older strict mapped room", async () => { + const setAccountData = vi.fn(async () => undefined); + const client = createClient({ + getAccountData: vi.fn(async () => ({ + "@alice:example.org": ["!older:example.org"], + })), + getJoinedRooms: vi.fn(async () => ["!older:example.org", "!fresh:example.org"]), + getJoinedRoomMembers: vi.fn(async () => ["@bot:example.org", "@alice:example.org"]), + setAccountData, + }); + + const result = await repairMatrixDirectRooms({ + client, + remoteUserId: "@alice:example.org", + }); + + expect(result.activeRoomId).toBe("!older:example.org"); + expect(result.discoveredStrictRoomIds).toEqual(["!fresh:example.org"]); + expect(result.changed).toBe(true); + expect(result.directContentAfter["@alice:example.org"]).toEqual([ + "!older:example.org", + "!fresh:example.org", + ]); + expect(setAccountData).toHaveBeenCalledWith( + EventType.Direct, + expect.objectContaining({ + "@alice:example.org": ["!older:example.org", "!fresh:example.org"], + }), + ); + }); + it("rejects unqualified Matrix user ids", async () => { const client = createClient(); diff --git a/extensions/matrix/src/matrix/direct-management.ts b/extensions/matrix/src/matrix/direct-management.ts index 7eb926d5c73..d6458d2ad67 100644 --- a/extensions/matrix/src/matrix/direct-management.ts +++ b/extensions/matrix/src/matrix/direct-management.ts @@ -100,12 +100,14 @@ function normalizeRoomIdList(values: readonly string[]): string[] { return normalized; } -function hasPrimaryMatrixDirectRoomMapping(params: { +function hasMatrixDirectRoomMappings(params: { directContent: MatrixDirectAccountData; remoteUserId: string; - roomId: string; + roomIds: readonly string[]; }): boolean { - return normalizeMappedRoomIds(params.directContent, params.remoteUserId)[0] === params.roomId; + const current = normalizeMappedRoomIds(params.directContent, params.remoteUserId); + const next = normalizeRoomIdList([...params.roomIds, ...current]); + return current.length === next.length && current.every((roomId, index) => roomId === next[index]); } function resolveDirectAccountDataWriteQueue(client: MatrixClient): KeyedAsyncQueue { @@ -118,10 +120,10 @@ function resolveDirectAccountDataWriteQueue(client: MatrixClient): KeyedAsyncQue return created; } -async function writeMatrixDirectRoomMapping(params: { +async function writeMatrixDirectRoomMappings(params: { client: MatrixClient; remoteUserId: string; - roomId: string; + roomIds: readonly string[]; }): Promise { return await resolveDirectAccountDataWriteQueue(params.client).enqueue( DIRECT_ACCOUNT_DATA_QUEUE_KEY, @@ -130,12 +132,12 @@ async function writeMatrixDirectRoomMapping(params: { const directContentAfter = buildNextDirectContent({ directContent: directContentBefore, remoteUserId: params.remoteUserId, - roomId: params.roomId, + roomIds: params.roomIds, }); - const changed = !hasPrimaryMatrixDirectRoomMapping({ + const changed = !hasMatrixDirectRoomMappings({ directContent: directContentBefore, remoteUserId: params.remoteUserId, - roomId: params.roomId, + roomIds: params.roomIds, }); if (changed) { await params.client.setAccountData(EventType.Direct, directContentAfter); @@ -178,10 +180,10 @@ async function classifyDirectRoomCandidate(params: { function buildNextDirectContent(params: { directContent: MatrixDirectAccountData; remoteUserId: string; - roomId: string; + roomIds: readonly string[]; }): MatrixDirectAccountData { const current = normalizeMappedRoomIds(params.directContent, params.remoteUserId); - const nextRooms = normalizeRoomIdList([params.roomId, ...current]); + const nextRooms = normalizeRoomIdList([...params.roomIds, ...current]); return { ...params.directContent, [params.remoteUserId]: nextRooms, @@ -195,10 +197,10 @@ export async function persistMatrixDirectRoomMapping(params: { }): Promise { const remoteUserId = normalizeRemoteUserId(params.remoteUserId); return ( - await writeMatrixDirectRoomMapping({ + await writeMatrixDirectRoomMappings({ client: params.client, remoteUserId, - roomId: params.roomId, + roomIds: [params.roomId], }) ).changed; } @@ -331,10 +333,10 @@ export async function repairMatrixDirectRooms(params: { encrypted: params.encrypted === true, })); const createdRoomId = inspected.activeRoomId ? null : activeRoomId; - const mappingWrite = await writeMatrixDirectRoomMapping({ + const mappingWrite = await writeMatrixDirectRoomMappings({ client: params.client, remoteUserId, - roomId: activeRoomId, + roomIds: [activeRoomId, ...inspected.discoveredStrictRoomIds], }); return { ...inspected,