Skip to content

Commit

Permalink
Fix infinite loop when restoring cached read receipts (#2963)
Browse files Browse the repository at this point in the history
  • Loading branch information
Germain authored Dec 9, 2022
1 parent fc501de commit 2dd06e3
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 12 deletions.
16 changes: 5 additions & 11 deletions src/models/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2079,8 +2079,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
room: this,
client: this.client,
pendingEventOrdering: this.opts.pendingEventOrdering,
receipts: this.cachedThreadReadReceipts.get(threadId) ?? [],
});

// All read receipts should now come down from sync, we do not need to keep
// a reference to the cached receipts anymore.
this.cachedThreadReadReceipts.delete(threadId);

// This is necessary to be able to jump to events in threads:
// If we jump to an event in a thread where neither the event, nor the root,
// nor any thread event are loaded yet, we'll load the event as well as the thread root, create the thread,
Expand Down Expand Up @@ -2110,17 +2115,6 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
if (this.threadsReady) {
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
25 changes: 24 additions & 1 deletion src/models/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { RoomState } from "./room-state";
import { ServerControlledNamespacedValue } from "../NamespacedValue";
import { logger } from "../logger";
import { ReadReceipt } from "./read-receipt";
import { ReceiptType } from "../@types/read_receipts";
import { Receipt, ReceiptContent, ReceiptType } from "../@types/read_receipts";

export enum ThreadEvent {
New = "Thread.new",
Expand All @@ -50,6 +50,7 @@ interface IThreadOpts {
room: Room;
client: MatrixClient;
pendingEventOrdering?: PendingEventOrdering;
receipts?: { event: MatrixEvent; synthetic: boolean }[];
}

export enum FeatureSupport {
Expand Down Expand Up @@ -127,6 +128,8 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
this.timelineSet.on(RoomEvent.Timeline, this.onTimelineEvent);

this.processReceipts(opts.receipts);

// even if this thread is thought to be originating from this client, we initialise it as we may be in a
// gappy sync and a thread around this event may already exist.
this.updateThreadMetadata();
Expand Down Expand Up @@ -284,6 +287,26 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
this.timeline = this.events;
}

/**
* Processes the receipts that were caught during initial sync
* When clients become aware of a thread, they try to retrieve those read receipts
* 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<ReceiptContent>();
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 getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined {
return rootEvent?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
}
Expand Down

0 comments on commit 2dd06e3

Please sign in to comment.