Skip to content

Commit 71f9b25

Browse files
authored
Ensure we do not add relations to the wrong timeline (#3427)
* Do not assume that a relation lives in main timeline if we do not know its parent * For pagination, partition relations with unknown parents into a separate bucket And only add them to relation map, no timelines * Make addLiveEvents async and have it fetch parent events of unknown relations to not insert into the wrong timeline * Fix tests not awaiting addLIveEvents * Fix handling of thread roots in eventShouldLiveIn * Fix types * Fix tests * Fix import * Stash thread ID of relations in unsigned to be stashed in sync accumulator * Persist after processing * Revert "Persist after processing" This reverts commit 05ed640. * Update unsigned field name to match MSC4023 * Persist after processing to store thread id in unsigned sync accumulator * Add test * Fix replayEvents getting doubled up due to Thread::addEvents being called in createThread and separately * Fix test * Switch to using UnstableValue * Add comment * Iterate
1 parent 9c5c7dd commit 71f9b25

File tree

12 files changed

+517
-322
lines changed

12 files changed

+517
-322
lines changed

spec/integ/matrix-client-event-timeline.spec.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ describe("MatrixClient event timelines", function () {
11421142

11431143
const prom = emitPromise(room, ThreadEvent.Update);
11441144
// Assume we're seeing the reply while loading backlog
1145-
room.addLiveEvents([THREAD_REPLY2]);
1145+
await room.addLiveEvents([THREAD_REPLY2]);
11461146
httpBackend
11471147
.when(
11481148
"GET",
@@ -1156,7 +1156,7 @@ describe("MatrixClient event timelines", function () {
11561156
});
11571157
await flushHttp(prom);
11581158
// but while loading the metadata, a new reply has arrived
1159-
room.addLiveEvents([THREAD_REPLY3]);
1159+
await room.addLiveEvents([THREAD_REPLY3]);
11601160
const thread = room.getThread(THREAD_ROOT_UPDATED.event_id!)!;
11611161
// then the events should still be all in the right order
11621162
expect(thread.events.map((it) => it.getId())).toEqual([
@@ -1248,7 +1248,7 @@ describe("MatrixClient event timelines", function () {
12481248

12491249
const prom = emitPromise(room, ThreadEvent.Update);
12501250
// Assume we're seeing the reply while loading backlog
1251-
room.addLiveEvents([THREAD_REPLY2]);
1251+
await room.addLiveEvents([THREAD_REPLY2]);
12521252
httpBackend
12531253
.when(
12541254
"GET",
@@ -1267,7 +1267,7 @@ describe("MatrixClient event timelines", function () {
12671267
});
12681268
await flushHttp(prom);
12691269
// but while loading the metadata, a new reply has arrived
1270-
room.addLiveEvents([THREAD_REPLY3]);
1270+
await room.addLiveEvents([THREAD_REPLY3]);
12711271
const thread = room.getThread(THREAD_ROOT_UPDATED.event_id!)!;
12721272
// then the events should still be all in the right order
12731273
expect(thread.events.map((it) => it.getId())).toEqual([
@@ -1572,7 +1572,7 @@ describe("MatrixClient event timelines", function () {
15721572
respondToEvent(THREAD_ROOT_UPDATED);
15731573
respondToEvent(THREAD_ROOT_UPDATED);
15741574
respondToEvent(THREAD2_ROOT);
1575-
room.addLiveEvents([THREAD_REPLY2]);
1575+
await room.addLiveEvents([THREAD_REPLY2]);
15761576
await httpBackend.flushAllExpected();
15771577
await prom;
15781578
expect(thread.length).toBe(2);
@@ -1937,11 +1937,6 @@ describe("MatrixClient event timelines", function () {
19371937
.respond(200, function () {
19381938
return THREAD_ROOT;
19391939
});
1940-
httpBackend
1941-
.when("GET", "/rooms/!foo%3Abar/event/" + encodeURIComponent(THREAD_ROOT.event_id!))
1942-
.respond(200, function () {
1943-
return THREAD_ROOT;
1944-
});
19451940
httpBackend
19461941
.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_ROOT.event_id!))
19471942
.respond(200, function () {

spec/integ/matrix-client-syncing.spec.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ import {
3636
NotificationCountType,
3737
IEphemeral,
3838
Room,
39+
IndexedDBStore,
40+
RelationType,
3941
} from "../../src";
4042
import { ReceiptType } from "../../src/@types/read_receipts";
4143
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
4244
import * as utils from "../test-utils/test-utils";
4345
import { TestClient } from "../TestClient";
46+
import { emitPromise, mkEvent, mkMessage } from "../test-utils/test-utils";
47+
import { THREAD_RELATION_TYPE } from "../../src/models/thread";
4448

4549
describe("MatrixClient syncing", () => {
4650
const selfUserId = "@alice:localhost";
@@ -1867,4 +1871,124 @@ describe("MatrixClient syncing (IndexedDB version)", () => {
18671871
idbClient.stopClient();
18681872
idbHttpBackend.stop();
18691873
});
1874+
1875+
it("should query server for which thread a 2nd order relation belongs to and stash in sync accumulator", async () => {
1876+
const roomId = "!room:example.org";
1877+
1878+
async function startClient(client: MatrixClient): Promise<void> {
1879+
await Promise.all([
1880+
idbClient.startClient({
1881+
// Without this all events just go into the main timeline
1882+
threadSupport: true,
1883+
}),
1884+
idbHttpBackend.flushAllExpected(),
1885+
emitPromise(idbClient, ClientEvent.Room),
1886+
]);
1887+
}
1888+
1889+
function assertEventsExpected(client: MatrixClient): void {
1890+
const room = client.getRoom(roomId);
1891+
const mainTimelineEvents = room!.getLiveTimeline().getEvents();
1892+
expect(mainTimelineEvents).toHaveLength(1);
1893+
expect(mainTimelineEvents[0].getContent().body).toEqual("Test");
1894+
1895+
const thread = room!.getThread("$someThreadId")!;
1896+
expect(thread.replayEvents).toHaveLength(1);
1897+
expect(thread.replayEvents![0].getRelation()!.key).toEqual("🪿");
1898+
}
1899+
1900+
let idbTestClient = new TestClient(selfUserId, "DEVICE", selfAccessToken, undefined, {
1901+
store: new IndexedDBStore({
1902+
indexedDB: global.indexedDB,
1903+
dbName: "test",
1904+
}),
1905+
});
1906+
let idbHttpBackend = idbTestClient.httpBackend;
1907+
let idbClient = idbTestClient.client;
1908+
await idbClient.store.startup();
1909+
1910+
idbHttpBackend.when("GET", "/versions").respond(200, { versions: ["v1.4"] });
1911+
idbHttpBackend.when("GET", "/pushrules/").respond(200, {});
1912+
idbHttpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
1913+
1914+
const syncRoomSection = {
1915+
join: {
1916+
[roomId]: {
1917+
timeline: {
1918+
prev_batch: "foo",
1919+
events: [
1920+
mkMessage({
1921+
room: roomId,
1922+
user: selfUserId,
1923+
msg: "Test",
1924+
}),
1925+
mkEvent({
1926+
room: roomId,
1927+
user: selfUserId,
1928+
content: {
1929+
"m.relates_to": {
1930+
rel_type: RelationType.Annotation,
1931+
event_id: "$someUnknownEvent",
1932+
key: "🪿",
1933+
},
1934+
},
1935+
type: "m.reaction",
1936+
}),
1937+
],
1938+
},
1939+
},
1940+
},
1941+
};
1942+
idbHttpBackend.when("GET", "/sync").respond(200, {
1943+
...syncData,
1944+
rooms: syncRoomSection,
1945+
});
1946+
idbHttpBackend.when("GET", `/rooms/${encodeURIComponent(roomId)}/event/%24someUnknownEvent`).respond(
1947+
200,
1948+
mkEvent({
1949+
room: roomId,
1950+
user: selfUserId,
1951+
content: {
1952+
"body": "Thread response",
1953+
"m.relates_to": {
1954+
rel_type: THREAD_RELATION_TYPE.name,
1955+
event_id: "$someThreadId",
1956+
},
1957+
},
1958+
type: "m.room.message",
1959+
}),
1960+
);
1961+
1962+
await startClient(idbClient);
1963+
assertEventsExpected(idbClient);
1964+
1965+
idbHttpBackend.verifyNoOutstandingExpectation();
1966+
// Force sync accumulator to persist, reset client, assert it doesn't re-fetch event on next start-up
1967+
await idbClient.store.save(true);
1968+
await idbClient.stopClient();
1969+
await idbClient.store.destroy();
1970+
await idbHttpBackend.stop();
1971+
1972+
idbTestClient = new TestClient(selfUserId, "DEVICE", selfAccessToken, undefined, {
1973+
store: new IndexedDBStore({
1974+
indexedDB: global.indexedDB,
1975+
dbName: "test",
1976+
}),
1977+
});
1978+
idbHttpBackend = idbTestClient.httpBackend;
1979+
idbClient = idbTestClient.client;
1980+
await idbClient.store.startup();
1981+
1982+
idbHttpBackend.when("GET", "/versions").respond(200, { versions: ["v1.4"] });
1983+
idbHttpBackend.when("GET", "/pushrules/").respond(200, {});
1984+
idbHttpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
1985+
idbHttpBackend.when("GET", "/sync").respond(200, syncData);
1986+
1987+
await startClient(idbClient);
1988+
assertEventsExpected(idbClient);
1989+
1990+
idbHttpBackend.verifyNoOutstandingExpectation();
1991+
await idbClient.stopClient();
1992+
await idbHttpBackend.stop();
1993+
});
18701994
});

spec/integ/matrix-client-unread-notifications.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ describe("MatrixClient syncing", () => {
8989

9090
const thread = mkThread({ room, client: client!, authorId: selfUserId, participantUserIds: [selfUserId] });
9191
const threadReply = thread.events.at(-1)!;
92-
room.addLiveEvents([thread.rootEvent]);
92+
await room.addLiveEvents([thread.rootEvent]);
9393

9494
// Initialize read receipt datastructure before testing the reaction
9595
room.addReceiptToStructure(thread.rootEvent.getId()!, ReceiptType.Read, selfUserId, { ts: 1 }, false);

0 commit comments

Comments
 (0)