diff --git a/src/matrix/room/timeline/persistence/GapWriter.js b/src/matrix/room/timeline/persistence/GapWriter.js index 7b6e7600..2fc56a21 100644 --- a/src/matrix/room/timeline/persistence/GapWriter.js +++ b/src/matrix/room/timeline/persistence/GapWriter.js @@ -119,13 +119,14 @@ export class GapWriter { eventStorageEntry.displayName = member.displayName; eventStorageEntry.avatarUrl = member.avatarUrl; } - txn.timelineEvents.insert(eventStorageEntry); - const eventEntry = new EventEntry(eventStorageEntry, this._fragmentIdComparer); - directionalAppend(entries, eventEntry, direction); - const updatedRelationTargetEntries = await this._relationWriter.writeRelation(eventEntry, txn, log); + // this will modify eventStorageEntry if it is a relation target + const updatedRelationTargetEntries = await this._relationWriter.writeGapRelation(eventStorageEntry, direction, txn, log); if (updatedRelationTargetEntries) { updatedEntries.push(...updatedRelationTargetEntries); } + txn.timelineEvents.insert(eventStorageEntry); + const eventEntry = new EventEntry(eventStorageEntry, this._fragmentIdComparer); + directionalAppend(entries, eventEntry, direction); } return {entries, updatedEntries}; } diff --git a/src/matrix/room/timeline/persistence/RelationWriter.js b/src/matrix/room/timeline/persistence/RelationWriter.js index 3838c494..13b0014d 100644 --- a/src/matrix/room/timeline/persistence/RelationWriter.js +++ b/src/matrix/room/timeline/persistence/RelationWriter.js @@ -44,10 +44,36 @@ export class RelationWriter { } } } - // TODO: check if sourceEntry is in timelineRelations as a target, and if so reaggregate it return null; } + /** + * @param {Object} storageEntry the event object, as it will be stored in storage. + * Will be modified (but not written to storage) in case this event is + * a relation target for which we've previously received relations. + * @param {Direction} direction of the gap fill + * */ + async writeGapRelation(storageEntry, direction, txn, log) { + const sourceEntry = new EventEntry(storageEntry, this._fragmentIdComparer); + const result = await this.writeRelation(sourceEntry, txn, log); + // when back-paginating, it can also happen that we've received relations + // for this event before, which now upon receiving the target need to be aggregated. + if (direction.isBackward) { + const relations = await txn.timelineRelations.getAllForTarget(this._roomId, sourceEntry.id); + if (relations.length) { + for (const r of relations) { + const relationStorageEntry = await txn.timelineEvents.getByEventId(this._roomId, r.sourceEventId); + if (relationStorageEntry) { + const relationEntry = new EventEntry(relationStorageEntry, this._fragmentIdComparer); + await this._applyRelation(relationEntry, storageEntry, txn, log); + } + } + } + } + + return result; + } + /** * @param {EventEntry} sourceEntry * @param {Object} targetStorageEntry event entry as stored in the timelineEvents store @@ -224,4 +250,4 @@ const _REDACT_KEEP_CONTENT_MAP = { }, 'm.room.aliases': {'aliases': 1}, }; -// end of matrix-js-sdk code \ No newline at end of file +// end of matrix-js-sdk code diff --git a/src/matrix/storage/idb/stores/TimelineRelationStore.js b/src/matrix/storage/idb/stores/TimelineRelationStore.js index 504693f9..013fb2d6 100644 --- a/src/matrix/storage/idb/stores/TimelineRelationStore.js +++ b/src/matrix/storage/idb/stores/TimelineRelationStore.js @@ -59,4 +59,17 @@ export class TimelineRelationStore { const keys = await this._store.selectAll(range); return keys.map(decodeKey); } + + async getAllForTarget(roomId, targetId) { + // exclude both keys as they are theoretical min and max, + // but we should't have a match for just the room id, or room id with max + const range = this._store.IDBKeyRange.bound( + encodeKey(roomId, targetId, MIN_UNICODE, MIN_UNICODE), + encodeKey(roomId, targetId, MAX_UNICODE, MAX_UNICODE), + true, + true + ); + const keys = await this._store.selectAll(range); + return keys.map(decodeKey); + } }