You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Correctly handle limited sync responses by resetting the thread timeline (#3056)
* Reset thread livetimelines when desynced * Implement workaround for https://github.com/matrix-org/synapse/issues/14830
This commit is contained in:
committed by
GitHub
parent
7b10fa367d
commit
a34d06c7c2
@ -20,7 +20,7 @@ import { Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../../src/models/t
|
||||
import { mkThread } from "../../test-utils/thread";
|
||||
import { TestClient } from "../../TestClient";
|
||||
import { emitPromise, mkMessage, mock } from "../../test-utils/test-utils";
|
||||
import { EventStatus, MatrixEvent } from "../../../src";
|
||||
import { Direction, EventStatus, MatrixEvent } from "../../../src";
|
||||
import { ReceiptType } from "../../../src/@types/read_receipts";
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client";
|
||||
import { ReEmitter } from "../../../src/ReEmitter";
|
||||
@ -283,4 +283,143 @@ describe("Thread", () => {
|
||||
expect(thread2.getEventReadUpTo(myUserId)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resetLiveTimeline", () => {
|
||||
// ResetLiveTimeline is used when we have missing messages between the current live timeline's end and newly
|
||||
// received messages. In that case, we want to replace the existing live timeline. To ensure pagination
|
||||
// continues working correctly, new pagination tokens need to be set on both the old live timeline (which is
|
||||
// now a regular timeline) and the new live timeline.
|
||||
it("replaces the live timeline and correctly sets pagination tokens", async () => {
|
||||
const myUserId = "@bob:example.org";
|
||||
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, {
|
||||
timelineSupport: false,
|
||||
});
|
||||
const client = testClient.client;
|
||||
const room = new Room("123", client, myUserId, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
jest.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
|
||||
const { thread } = mkThread({
|
||||
room,
|
||||
client,
|
||||
authorId: myUserId,
|
||||
participantUserIds: ["@alice:example.org"],
|
||||
length: 3,
|
||||
});
|
||||
await emitPromise(thread, ThreadEvent.Update);
|
||||
expect(thread.length).toBe(2);
|
||||
|
||||
jest.spyOn(client, "createMessagesRequest").mockImplementation((_, token) =>
|
||||
Promise.resolve({
|
||||
chunk: [],
|
||||
start: `${token}-new`,
|
||||
end: `${token}-new`,
|
||||
}),
|
||||
);
|
||||
|
||||
function timelines(): [string | null, string | null][] {
|
||||
return thread.timelineSet
|
||||
.getTimelines()
|
||||
.map((it) => [it.getPaginationToken(Direction.Backward), it.getPaginationToken(Direction.Forward)]);
|
||||
}
|
||||
|
||||
expect(timelines()).toEqual([[null, null]]);
|
||||
const promise = thread.resetLiveTimeline("b1", "f1");
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f1"],
|
||||
["b1", null],
|
||||
]);
|
||||
await promise;
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f1-new"],
|
||||
["b1-new", null],
|
||||
]);
|
||||
});
|
||||
|
||||
// As the pagination tokens cannot be used right now, resetLiveTimeline needs to replace them before they can
|
||||
// be used. But if in the future the bug in synapse is fixed, and they can actually be used, we can get into a
|
||||
// state where the client has paginated (and changed the tokens) while resetLiveTimeline tries to set the
|
||||
// corrected tokens. To prevent such a race condition, we make sure that resetLiveTimeline respects any
|
||||
// changes done to the pagination tokens.
|
||||
it("replaces the live timeline but does not replace changed pagination tokens", async () => {
|
||||
const myUserId = "@bob:example.org";
|
||||
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, {
|
||||
timelineSupport: false,
|
||||
});
|
||||
const client = testClient.client;
|
||||
const room = new Room("123", client, myUserId, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
jest.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
|
||||
const { thread } = mkThread({
|
||||
room,
|
||||
client,
|
||||
authorId: myUserId,
|
||||
participantUserIds: ["@alice:example.org"],
|
||||
length: 3,
|
||||
});
|
||||
await emitPromise(thread, ThreadEvent.Update);
|
||||
expect(thread.length).toBe(2);
|
||||
|
||||
jest.spyOn(client, "createMessagesRequest").mockImplementation((_, token) =>
|
||||
Promise.resolve({
|
||||
chunk: [],
|
||||
start: `${token}-new`,
|
||||
end: `${token}-new`,
|
||||
}),
|
||||
);
|
||||
|
||||
function timelines(): [string | null, string | null][] {
|
||||
return thread.timelineSet
|
||||
.getTimelines()
|
||||
.map((it) => [it.getPaginationToken(Direction.Backward), it.getPaginationToken(Direction.Forward)]);
|
||||
}
|
||||
|
||||
expect(timelines()).toEqual([[null, null]]);
|
||||
const promise = thread.resetLiveTimeline("b1", "f1");
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f1"],
|
||||
["b1", null],
|
||||
]);
|
||||
thread.timelineSet.getTimelines()[0].setPaginationToken("f2", Direction.Forward);
|
||||
thread.timelineSet.getTimelines()[1].setPaginationToken("b2", Direction.Backward);
|
||||
await promise;
|
||||
expect(timelines()).toEqual([
|
||||
[null, "f2"],
|
||||
["b2", null],
|
||||
]);
|
||||
});
|
||||
|
||||
it("is correctly called by the room", async () => {
|
||||
const myUserId = "@bob:example.org";
|
||||
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, {
|
||||
timelineSupport: false,
|
||||
});
|
||||
const client = testClient.client;
|
||||
const room = new Room("123", client, myUserId, {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
jest.spyOn(client, "getRoom").mockReturnValue(room);
|
||||
|
||||
const { thread } = mkThread({
|
||||
room,
|
||||
client,
|
||||
authorId: myUserId,
|
||||
participantUserIds: ["@alice:example.org"],
|
||||
length: 3,
|
||||
});
|
||||
await emitPromise(thread, ThreadEvent.Update);
|
||||
expect(thread.length).toBe(2);
|
||||
const mock = jest.spyOn(thread, "resetLiveTimeline");
|
||||
mock.mockReturnValue(Promise.resolve());
|
||||
|
||||
room.resetLiveTimeline("b1", "f1");
|
||||
expect(mock).toHaveBeenCalledWith("b1", "f1");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user