Skip to content

Commit

Permalink
Cache read receipts for unknown threads (#2953)
Browse files Browse the repository at this point in the history
  • Loading branch information
Germain authored Dec 8, 2022
1 parent c4006d7 commit 16d791b
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 9 deletions.
53 changes: 51 additions & 2 deletions spec/integ/matrix-client-syncing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ import {
IJoinedRoom,
IStateEvent,
IMinimalEvent,
NotificationCountType, IEphemeral,
NotificationCountType, IEphemeral, Room,
} from "../../src";
import { ReceiptType } from '../../src/@types/read_receipts';
import { UNREAD_THREAD_NOTIFICATIONS } from '../../src/@types/sync';
import * as utils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
Expand Down Expand Up @@ -1312,7 +1313,8 @@ describe("MatrixClient syncing", () => {
join: {
[roomOne]: {
ephemeral: {
events: [],
events: [
],
} as IEphemeral,
timeline: {
events: [
Expand Down Expand Up @@ -1397,6 +1399,9 @@ describe("MatrixClient syncing", () => {
rooms: {
join: {
[roomOne]: {
ephemeral: {
events: [],
},
timeline: {
events: [
utils.mkMessage({
Expand Down Expand Up @@ -1455,6 +1460,50 @@ describe("MatrixClient syncing", () => {
expect(room!.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(2);
});
});

it("caches unknown threads receipts and replay them when the thread is created", async () => {
const THREAD_ID = "$unknownthread:localhost";

const receipt = {
type: "m.receipt",
room_id: "!foo:bar",
content: {
"$event1:localhost": {
[ReceiptType.Read]: {
"@alice:localhost": { ts: 666, thread_id: THREAD_ID },
},
},
},
};
syncData.rooms.join[roomOne].ephemeral.events = [receipt];

httpBackend!.when("GET", "/sync").respond(200, syncData);
client!.startClient();

return Promise.all([
httpBackend!.flushAllExpected(),
awaitSyncEvent(),
]).then(() => {
const room = client?.getRoom(roomOne);
expect(room).toBeInstanceOf(Room);

expect(room?.cachedThreadReadReceipts.has(THREAD_ID)).toBe(true);

const thread = room!.createThread(THREAD_ID, undefined, [], true);

expect(room?.cachedThreadReadReceipts.has(THREAD_ID)).toBe(false);

const receipt = thread.getReadReceiptForUserId("@alice:localhost");

expect(receipt).toStrictEqual({
"data": {
"thread_id": "$unknownthread:localhost",
"ts": 666,
},
"eventId": "$event1:localhost",
});
});
});
});

describe("of a room", () => {
Expand Down
36 changes: 29 additions & 7 deletions src/models/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
private txnToEvent: Record<string, MatrixEvent> = {}; // Pending in-flight requests { string: MatrixEvent }
private notificationCounts: NotificationCount = {};
private readonly threadNotifications = new Map<string, NotificationCount>();
public readonly cachedThreadReadReceipts = new Map<string, { event: MatrixEvent, synthetic: boolean }[]>();
private readonly timelineSets: EventTimelineSet[];
public readonly threadsTimelineSets: EventTimelineSet[] = [];
// any filtered timeline sets we're maintaining for this room
Expand Down Expand Up @@ -2146,6 +2147,16 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
this.updateThreadRootEvents(thread, toStartOfTimeline, false);
}

// Pulling all the cached thread read receipts we've discovered when we
// did an initial sync, and applying them to the thread now that it exists
// on the client side
if (this.cachedThreadReadReceipts.has(threadId)) {
for (const { event, synthetic } of this.cachedThreadReadReceipts.get(threadId)!) {
this.addReceipt(event, synthetic);
}
this.cachedThreadReadReceipts.delete(threadId);
}

this.emit(ThreadEvent.New, thread, toStartOfTimeline);

return thread;
Expand Down Expand Up @@ -2727,13 +2738,24 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
const receiptDestination: Thread | this | undefined = receiptForMainTimeline
? this
: this.threads.get(receipt.thread_id ?? "");
receiptDestination?.addReceiptToStructure(
eventId,
receiptType as ReceiptType,
userId,
receipt,
synthetic,
);

if (receiptDestination) {
receiptDestination.addReceiptToStructure(
eventId,
receiptType as ReceiptType,
userId,
receipt,
synthetic,
);
} else {
// The thread does not exist locally, keep the read receipt
// in a cache locally, and re-apply the `addReceipt` logic
// when the thread is created
this.cachedThreadReadReceipts.set(receipt.thread_id!, [
...(this.cachedThreadReadReceipts.get(receipt.thread_id!) ?? []),
{ event, synthetic },
]);
}
});
});
});
Expand Down

0 comments on commit 16d791b

Please sign in to comment.