You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-12-04 05:02:41 +03:00
Fix race conditions around threads (#2331)
This commit is contained in:
committed by
GitHub
parent
274d6a9597
commit
ac5fee0a69
@@ -70,12 +70,11 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
public readonly room: Room;
|
||||
public readonly client: MatrixClient;
|
||||
|
||||
public initialEventsFetched = false;
|
||||
|
||||
public readonly id: string;
|
||||
public initialEventsFetched = !Thread.hasServerSideSupport;
|
||||
|
||||
constructor(
|
||||
public readonly rootEvent: MatrixEvent | undefined,
|
||||
public readonly id: string,
|
||||
public rootEvent: MatrixEvent | undefined,
|
||||
opts: IThreadOpts,
|
||||
) {
|
||||
super();
|
||||
@@ -99,12 +98,33 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
|
||||
this.timelineSet.on(RoomEvent.Timeline, this.onEcho);
|
||||
|
||||
// If we weren't able to find the root event, it's probably missing,
|
||||
// and we define the thread ID from one of the thread relation
|
||||
this.id = rootEvent?.getId() ?? opts?.initialEvents?.find(event => event.isThreadRelation)?.relationEventId;
|
||||
this.initialiseThread(this.rootEvent);
|
||||
if (opts.initialEvents) {
|
||||
this.addEvents(opts.initialEvents, false);
|
||||
}
|
||||
// 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.initialiseThread();
|
||||
|
||||
opts?.initialEvents?.forEach(event => this.addEvent(event, false));
|
||||
this.rootEvent?.setThread(this);
|
||||
}
|
||||
|
||||
private async fetchRootEvent(): Promise<void> {
|
||||
this.rootEvent = this.room.findEventById(this.id);
|
||||
// If the rootEvent does not exist in the local stores, then fetch it from the server.
|
||||
try {
|
||||
const eventData = await this.client.fetchRoomEvent(this.roomId, this.id);
|
||||
const mapper = this.client.getEventMapper();
|
||||
this.rootEvent = mapper(eventData); // will merge with existing event object if such is known
|
||||
} catch (e) {
|
||||
logger.error("Failed to fetch thread root to construct thread with", e);
|
||||
}
|
||||
|
||||
// The root event might be not be visible to the person requesting it.
|
||||
// If it wasn't fetched successfully the thread will work in "limited" mode and won't
|
||||
// benefit from all the APIs a homeserver can provide to enhance the thread experience
|
||||
this.rootEvent?.setThread(this);
|
||||
|
||||
this.emit(ThreadEvent.Update, this);
|
||||
}
|
||||
|
||||
public static setServerSideSupport(hasServerSideSupport: boolean, useStable: boolean): void {
|
||||
@@ -180,6 +200,11 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
}
|
||||
}
|
||||
|
||||
public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void {
|
||||
events.forEach(ev => this.addEvent(ev, toStartOfTimeline, false));
|
||||
this.emit(ThreadEvent.Update, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event to the thread and updates
|
||||
* the tail/root references if needed
|
||||
@@ -187,43 +212,59 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
* @param event The event to add
|
||||
* @param {boolean} toStartOfTimeline whether the event is being added
|
||||
* to the start (and not the end) of the timeline.
|
||||
* @param {boolean} emit whether to emit the Update event if the thread was updated or not.
|
||||
*/
|
||||
public async addEvent(event: MatrixEvent, toStartOfTimeline: boolean): Promise<void> {
|
||||
public addEvent(event: MatrixEvent, toStartOfTimeline: boolean, emit = true): void {
|
||||
event.setThread(this);
|
||||
|
||||
if (!this._currentUserParticipated && event.getSender() === this.client.getUserId()) {
|
||||
this._currentUserParticipated = true;
|
||||
}
|
||||
|
||||
// Add all annotations and replace relations to the timeline so that the relations are processed accordingly
|
||||
if ([RelationType.Annotation, RelationType.Replace].includes(event.getRelation()?.rel_type as RelationType)) {
|
||||
this.addEventToTimeline(event, toStartOfTimeline);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add all incoming events to the thread's timeline set when there's no server support
|
||||
if (!Thread.hasServerSideSupport) {
|
||||
// all the relevant membership info to hydrate events with a sender
|
||||
// is held in the main room timeline
|
||||
// We want to fetch the room state from there and pass it down to this thread
|
||||
// timeline set to let it reconcile an event with its relevant RoomMember
|
||||
|
||||
event.setThread(this);
|
||||
this.addEventToTimeline(event, toStartOfTimeline);
|
||||
|
||||
await this.client.decryptEventIfNeeded(event, {});
|
||||
this.client.decryptEventIfNeeded(event, {});
|
||||
} else if (!toStartOfTimeline &&
|
||||
this.initialEventsFetched &&
|
||||
event.localTimestamp > this.lastReply().localTimestamp
|
||||
event.localTimestamp > this.lastReply()?.localTimestamp
|
||||
) {
|
||||
await this.fetchEditsWhereNeeded(event);
|
||||
this.fetchEditsWhereNeeded(event);
|
||||
this.addEventToTimeline(event, false);
|
||||
}
|
||||
|
||||
if (!this._currentUserParticipated && event.getSender() === this.client.getUserId()) {
|
||||
this._currentUserParticipated = true;
|
||||
}
|
||||
|
||||
// If no thread support exists we want to count all thread relation
|
||||
// added as a reply. We can't rely on the bundled relationships count
|
||||
if (!Thread.hasServerSideSupport && event.isRelation(THREAD_RELATION_TYPE.name)) {
|
||||
if ((!Thread.hasServerSideSupport || !this.rootEvent) && event.isRelation(THREAD_RELATION_TYPE.name)) {
|
||||
this.replyCount++;
|
||||
}
|
||||
|
||||
this.emit(ThreadEvent.Update, this);
|
||||
if (emit) {
|
||||
this.emit(ThreadEvent.Update, this);
|
||||
}
|
||||
}
|
||||
|
||||
private initialiseThread(rootEvent: MatrixEvent | undefined): void {
|
||||
const bundledRelationship = rootEvent
|
||||
?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||
private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship {
|
||||
return rootEvent?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||
}
|
||||
|
||||
private async initialiseThread(): Promise<void> {
|
||||
let bundledRelationship = this.getRootEventBundledRelationship();
|
||||
if (Thread.hasServerSideSupport && !bundledRelationship) {
|
||||
await this.fetchRootEvent();
|
||||
bundledRelationship = this.getRootEventBundledRelationship();
|
||||
}
|
||||
|
||||
if (Thread.hasServerSideSupport && bundledRelationship) {
|
||||
this.replyCount = bundledRelationship.count;
|
||||
@@ -236,6 +277,8 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
|
||||
this.fetchEditsWhereNeeded(event);
|
||||
}
|
||||
|
||||
this.emit(ThreadEvent.Update, this);
|
||||
}
|
||||
|
||||
// XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084
|
||||
@@ -253,24 +296,10 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
}));
|
||||
}
|
||||
|
||||
public async fetchInitialEvents(): Promise<{
|
||||
originalEvent: MatrixEvent;
|
||||
events: MatrixEvent[];
|
||||
nextBatch?: string;
|
||||
prevBatch?: string;
|
||||
} | null> {
|
||||
if (!Thread.hasServerSideSupport) {
|
||||
this.initialEventsFetched = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.fetchEvents();
|
||||
this.initialEventsFetched = true;
|
||||
return response;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
public async fetchInitialEvents(): Promise<void> {
|
||||
if (this.initialEventsFetched) return;
|
||||
await this.fetchEvents();
|
||||
this.initialEventsFetched = true;
|
||||
}
|
||||
|
||||
private setEventMetadata(event: MatrixEvent): void {
|
||||
@@ -319,7 +348,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
* A getter for the last event added to the thread
|
||||
*/
|
||||
public get replyToEvent(): MatrixEvent {
|
||||
return this.lastEvent;
|
||||
return this.lastEvent ?? this.lastReply();
|
||||
}
|
||||
|
||||
public get events(): MatrixEvent[] {
|
||||
@@ -338,7 +367,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
return this.timelineSet.getLiveTimeline();
|
||||
}
|
||||
|
||||
public async fetchEvents(opts: IRelationsRequestOpts = { limit: 20 }): Promise<{
|
||||
public async fetchEvents(opts: IRelationsRequestOpts = { limit: 20, direction: Direction.Backward }): Promise<{
|
||||
originalEvent: MatrixEvent;
|
||||
events: MatrixEvent[];
|
||||
nextBatch?: string;
|
||||
@@ -370,7 +399,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
return this.client.decryptEventIfNeeded(event);
|
||||
}));
|
||||
|
||||
const prependEvents = !opts.direction || opts.direction === Direction.Backward;
|
||||
const prependEvents = (opts.direction ?? Direction.Backward) === Direction.Backward;
|
||||
|
||||
this.timelineSet.addEventsToTimeline(
|
||||
events,
|
||||
|
||||
Reference in New Issue
Block a user