diff --git a/src/domain/session/room/timeline/ReactionsViewModel.js b/src/domain/session/room/timeline/ReactionsViewModel.js index ad29c01a..bb5511e6 100644 --- a/src/domain/session/room/timeline/ReactionsViewModel.js +++ b/src/domain/session/room/timeline/ReactionsViewModel.js @@ -254,8 +254,16 @@ export function tests() { // 2. setup queue & timeline const queue = new SendQueue({roomId, storage, hsApi: new MockHomeServer().api}); const powerLevelsObservable = new ObservableValue(new PowerLevels({ ownUserId: alice, membership: "join" })); - const timeline = new Timeline({roomId, storage, fragmentIdComparer, - clock: new MockClock(), pendingEvents: queue.pendingEvents, powerLevelsObservable}); + const timeline = new Timeline({ + roomId, + storage, + fragmentIdComparer, + clock: new MockClock(), + pendingEvents: queue.pendingEvents, + powerLevelsObservable, + fetchEventFromHomeserver: () => {}, + fetchEventFromStorage: () => {} + }); // 3. load the timeline, which will load the message with the reaction await timeline.load(new User(alice), "join", new NullLogItem()); const tiles = mapMessageEntriesToBaseMessageTile(timeline, queue); diff --git a/src/matrix/net/HomeServerApi.ts b/src/matrix/net/HomeServerApi.ts index bacf26b0..db5e281b 100644 --- a/src/matrix/net/HomeServerApi.ts +++ b/src/matrix/net/HomeServerApi.ts @@ -128,6 +128,10 @@ export class HomeServerApi { return this._get("/sync", {since, timeout, filter}, undefined, options); } + event(roomId, eventId) { + return this._get(`/rooms/${encodeURIComponent(roomId)}/event/${encodeURIComponent(eventId)}`); + } + // params is from, dir and optionally to, limit, filter. messages(roomId: string, params: Record, options?: IRequestOptions): IHomeServerRequest { return this._get(`/rooms/${encodeURIComponent(roomId)}/messages`, params, undefined, options); diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index 93973b71..b483d65b 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -501,7 +501,9 @@ export class BaseRoom extends EventEmitter { }, clock: this._platform.clock, logger: this._platform.logger, - powerLevelsObservable: await this.observePowerLevels() + powerLevelsObservable: await this.observePowerLevels(), + fetchEventFromStorage: eventId => this._readEventById(eventId), + fetchEventFromHomeserver: eventId => this._getEventFromHomeserver(eventId) }); try { if (this._roomEncryption) { @@ -559,6 +561,12 @@ export class BaseRoom extends EventEmitter { } } + async _getEventFromHomeserver(eventId) { + const response = await this._hsApi.event(this._roomId, eventId).response(); + const entry = new EventEntry({ event: response }, this._fragmentIdComparer); + return entry; + } + dispose() { this._roomEncryption?.dispose(); diff --git a/src/matrix/room/timeline/Timeline.js b/src/matrix/room/timeline/Timeline.js index 04adde0d..bd3f9e8f 100644 --- a/src/matrix/room/timeline/Timeline.js +++ b/src/matrix/room/timeline/Timeline.js @@ -25,8 +25,10 @@ import {getRelation, ANNOTATION_RELATION_TYPE} from "./relations.js"; import {REDACTION_TYPE} from "../common.js"; export class Timeline { - constructor({roomId, storage, closeCallback, fragmentIdComparer, pendingEvents, clock, powerLevelsObservable}) { + constructor({roomId, storage, closeCallback, fragmentIdComparer, pendingEvents, clock, powerLevelsObservable, fetchEventFromStorage, fetchEventFromHomeserver}) { this._roomId = roomId; + this._fetchEventFromStorage = fetchEventFromStorage; + this._fetchEventFromHomeserver = fetchEventFromHomeserver; this._storage = storage; this._closeCallback = closeCallback; this._fragmentIdComparer = fragmentIdComparer; @@ -78,6 +80,7 @@ export class Timeline { const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(20, txn, log)); try { const entries = await readerRequest.complete(); + await this._loadRelatedEvents(entries); this._setupEntries(entries); } finally { this._disposables.disposeTracked(readerRequest); @@ -211,8 +214,9 @@ export class Timeline { } /** @package */ - replaceEntries(entries) { + replaceEntries(entries) { this._addLocalRelationsToNewRemoteEntries(entries); + this._loadRelatedEvents(entries); for (const entry of entries) { try { this._remoteEntries.getAndUpdate(entry, Timeline._entryUpdater); @@ -236,8 +240,30 @@ export class Timeline { /** @package */ addEntries(newEntries) { this._addLocalRelationsToNewRemoteEntries(newEntries); + this._loadRelatedEvents(newEntries); this._remoteEntries.setManySorted(newEntries); } + + async _loadRelatedEvents(entries) { + const filteredEntries = entries.filter(e => !!e.relation); + for (const entry of filteredEntries) { + const id = entry.relatedEventId; + let relatedEvent; + // find in remote events + relatedEvent = this.getByEventId(id); + // find in storage + if (!relatedEvent) { + relatedEvent = await this._fetchEventFromStorage(id); + } + // fetch from hs + if (!relatedEvent) { + relatedEvent = await this._fetchEventFromHomeserver(id); + } + if (relatedEvent) { + entry.setRelatedEntry(relatedEvent); + } + } + } // tries to prepend `amount` entries to the `entries` list. /** diff --git a/src/matrix/room/timeline/entries/EventEntry.js b/src/matrix/room/timeline/entries/EventEntry.js index 89d3f379..e626b02f 100644 --- a/src/matrix/room/timeline/entries/EventEntry.js +++ b/src/matrix/room/timeline/entries/EventEntry.js @@ -24,6 +24,7 @@ export class EventEntry extends BaseEventEntry { this._eventEntry = eventEntry; this._decryptionError = null; this._decryptionResult = null; + this._relatedEntry = null; } clone() { @@ -41,6 +42,10 @@ export class EventEntry extends BaseEventEntry { } } + setRelatedEntry(entry) { + this._relatedEntry = entry; + } + get event() { return this._eventEntry.event; } @@ -122,6 +127,10 @@ export class EventEntry extends BaseEventEntry { return getRelatedEventId(this.event); } + get relatedEntry() { + return this._relatedEntry; + } + get isRedacted() { return super.isRedacted || isRedacted(this._eventEntry.event); }