diff --git a/spec/unit/relations.spec.ts b/spec/unit/relations.spec.ts index 0e561c856..3be34f332 100644 --- a/spec/unit/relations.spec.ts +++ b/spec/unit/relations.spec.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { EventTimelineSet } from "../../src/models/event-timeline-set"; import { MatrixEvent } from "../../src/models/event"; import { Relations } from "../../src/models/relations"; @@ -70,4 +71,63 @@ describe("Relations", function() { expect(events.size).toEqual(1); } }); + + it("should emit created regardless of ordering", async function () { + const targetEvent = new MatrixEvent({ + "sender": "@bob:example.com", + "type": "m.room.message", + "event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o", + "room_id": "!pzVjCQSoQPpXQeHpmK:example.com", + "content": {}, + }); + const relationEvent = new MatrixEvent({ + "sender": "@bob:example.com", + "type": "m.reaction", + "event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw", + "room_id": "!pzVjCQSoQPpXQeHpmK:example.com", + "content": { + "m.relates_to": { + "event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o", + "key": "👍️", + "rel_type": "m.annotation", + }, + }, + }); + + // Stub the room + const room = { + getPendingEvent() { return null; }, + getUnfilteredTimelineSet() { return null; }, + }; + + // Add the target event first, then the relation event + { + const relationsCreated = new Promise(resolve => { + targetEvent.once("Event.relationsCreated", resolve); + }) + + const timelineSet = new EventTimelineSet(room, { + unstableClientRelationAggregation: true, + }); + timelineSet.addLiveEvent(targetEvent); + timelineSet.addLiveEvent(relationEvent); + + await relationsCreated; + } + + // Add the relation event first, then the target event + { + const relationsCreated = new Promise(resolve => { + targetEvent.once("Event.relationsCreated", resolve); + }) + + const timelineSet = new EventTimelineSet(room, { + unstableClientRelationAggregation: true, + }); + timelineSet.addLiveEvent(relationEvent); + timelineSet.addLiveEvent(targetEvent); + + await relationsCreated; + } + }); }); diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 784dea359..c24e3a79d 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -740,16 +740,11 @@ EventTimelineSet.prototype.setRelationsTarget = function(event) { if (!relationsForEvent) { return; } - // don't need it for non m.replace relations for now - const relationsWithRelType = relationsForEvent["m.replace"]; - if (!relationsWithRelType) { - return; - } - // only doing replacements for messages for now (e.g. edits) - const relationsWithEventType = relationsWithRelType["m.room.message"]; - if (relationsWithEventType) { - relationsWithEventType.setTargetEvent(event); + for (const relationsWithRelType of Object.values(relationsForEvent)) { + for (const relationsWithEventType of Object.values(relationsWithRelType)) { + relationsWithEventType.setTargetEvent(event); + } } }; @@ -797,7 +792,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { } let relationsWithEventType = relationsWithRelType[eventType]; - let isNewRelations = false; let relatesToEvent; if (!relationsWithEventType) { relationsWithEventType = relationsWithRelType[eventType] = new Relations( @@ -805,7 +799,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { eventType, this.room, ); - isNewRelations = true; relatesToEvent = this.findEventById(relatesToEventId) || this.room.getPendingEvent(relatesToEventId); if (relatesToEvent) { relationsWithEventType.setTargetEvent(relatesToEvent); @@ -813,11 +806,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { } relationsWithEventType.addEvent(event); - - // only emit once event has been added to relations - if (isNewRelations && relatesToEvent) { - relatesToEvent.emit("Event.relationsCreated", relationType, eventType); - } }; /** diff --git a/src/models/relations.js b/src/models/relations.js index 6be559af8..3ea0f3bb2 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -48,6 +48,7 @@ export class Relations extends EventEmitter { this._sortedAnnotationsByKey = []; this._targetEvent = null; this._room = room; + this._creationEmitted = false; } /** @@ -94,6 +95,8 @@ export class Relations extends EventEmitter { event.on("Event.beforeRedaction", this._onBeforeRedaction); this.emit("Relations.add", event); + + this._maybeEmitCreated(); } /** @@ -345,6 +348,7 @@ export class Relations extends EventEmitter { return; } this._targetEvent = event; + if (this.relationType === "m.replace") { const replacement = await this.getLastReplacement(); // this is the initial update, so only call it if we already have something @@ -353,5 +357,24 @@ export class Relations extends EventEmitter { this._targetEvent.makeReplaced(replacement); } } + + this._maybeEmitCreated(); + } + + _maybeEmitCreated() { + if (this._creationEmitted) { + return; + } + // Only emit we're "created" once we have a target event instance _and_ + // at least one related event. + if (!this._targetEvent || !this._relations.size) { + return; + } + this._creationEmitted = true; + this._targetEvent.emit( + "Event.relationsCreated", + this.relationType, + this.eventType, + ); } }