From d1db4dbac8c046b7cb702b18e19c07c47ca54e20 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 5 Jan 2023 09:25:29 +0000 Subject: [PATCH 1/2] Add failing test scenario when multiple receipts are in the same event --- spec/integ/matrix-client-syncing.spec.ts | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index 0f92dc60d95..75487eb81dd 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -1543,6 +1543,52 @@ describe("MatrixClient syncing", () => { }); }); }); + + it("only replays receipts relevant to the current context", 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 }, + }, + }, + "$otherevent:localhost": { + [ReceiptType.Read]: { + "@alice:localhost": { ts: 999, thread_id: "$otherthread:localhost" }, + }, + }, + }, + }; + 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", () => { From 03968c32dcc93b0375acabd54a3093570ab66a98 Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 4 Jan 2023 18:14:54 +0000 Subject: [PATCH 2/2] Fix cached read receipts --- src/@types/read_receipts.ts | 8 ++++++++ src/models/room.ts | 12 +++++++++--- src/models/thread.ts | 18 +++++------------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/@types/read_receipts.ts b/src/@types/read_receipts.ts index 34f1c67fad7..3032c5934df 100644 --- a/src/@types/read_receipts.ts +++ b/src/@types/read_receipts.ts @@ -54,3 +54,11 @@ export type Receipts = { [userId: string]: [WrappedReceipt | null, WrappedReceipt | null]; // Pair (both nullable) }; }; + +export type CachedReceiptStructure = { + eventId: string; + receiptType: string | ReceiptType; + userId: string; + receipt: Receipt; + synthetic: boolean; +}; diff --git a/src/models/room.ts b/src/models/room.ts index 0faea02c371..15d54b3e0cd 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -54,7 +54,13 @@ import { FILTER_RELATED_BY_SENDERS, ThreadFilterType, } from "./thread"; -import { MAIN_ROOM_TIMELINE, Receipt, ReceiptContent, ReceiptType } from "../@types/read_receipts"; +import { + CachedReceiptStructure, + MAIN_ROOM_TIMELINE, + Receipt, + ReceiptContent, + ReceiptType, +} from "../@types/read_receipts"; import { IStateEventWithRoomId } from "../@types/search"; import { RelationsContainer } from "./relations-container"; import { ReadReceipt, synthesizeReceipt } from "./read-receipt"; @@ -302,7 +308,7 @@ export class Room extends ReadReceipt { private txnToEvent: Record = {}; // Pending in-flight requests { string: MatrixEvent } private notificationCounts: NotificationCount = {}; private readonly threadNotifications = new Map(); - public readonly cachedThreadReadReceipts = new Map(); + public readonly cachedThreadReadReceipts = new Map(); private readonly timelineSets: EventTimelineSet[]; public readonly threadsTimelineSets: EventTimelineSet[] = []; // any filtered timeline sets we're maintaining for this room @@ -2721,7 +2727,7 @@ export class Room extends ReadReceipt { // when the thread is created this.cachedThreadReadReceipts.set(receipt.thread_id!, [ ...(this.cachedThreadReadReceipts.get(receipt.thread_id!) ?? []), - { event, synthetic }, + { eventId, receiptType, userId, receipt, synthetic }, ]); } }); diff --git a/src/models/thread.ts b/src/models/thread.ts index da8ddf0b1f8..25e454c32e2 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -27,7 +27,7 @@ import { RoomState } from "./room-state"; import { ServerControlledNamespacedValue } from "../NamespacedValue"; import { logger } from "../logger"; import { ReadReceipt } from "./read-receipt"; -import { Receipt, ReceiptContent, ReceiptType } from "../@types/read_receipts"; +import { CachedReceiptStructure, ReceiptType } from "../@types/read_receipts"; export enum ThreadEvent { New = "Thread.new", @@ -50,7 +50,7 @@ interface IThreadOpts { room: Room; client: MatrixClient; pendingEventOrdering?: PendingEventOrdering; - receipts?: { event: MatrixEvent; synthetic: boolean }[]; + receipts?: CachedReceiptStructure[]; } export enum FeatureSupport { @@ -317,17 +317,9 @@ export class Thread extends ReadReceipt { * and apply them to the current thread * @param receipts - A collection of the receipts cached from initial sync */ - private processReceipts(receipts: { event: MatrixEvent; synthetic: boolean }[] = []): void { - for (const { event, synthetic } of receipts) { - const content = event.getContent(); - Object.keys(content).forEach((eventId: string) => { - Object.keys(content[eventId]).forEach((receiptType: ReceiptType | string) => { - Object.keys(content[eventId][receiptType]).forEach((userId: string) => { - const receipt = content[eventId][receiptType][userId] as Receipt; - this.addReceiptToStructure(eventId, receiptType as ReceiptType, userId, receipt, synthetic); - }); - }); - }); + private processReceipts(receipts: CachedReceiptStructure[] = []): void { + for (const { eventId, receiptType, userId, receipt, synthetic } of receipts) { + this.addReceiptToStructure(eventId, receiptType as ReceiptType, userId, receipt, synthetic); } }