You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Emit relations created when target event added later
This changes the "relations created" event to ensure it is properly emitted even if the target event is added to the timeline after the relation event. There was perhaps always a risk of this happening in the past, but is seems more likely to bite now with delayed decryption. Part of https://github.com/vector-im/element-web/issues/17461
This commit is contained in:
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { EventTimelineSet } from "../../src/models/event-timeline-set";
|
||||||
import { MatrixEvent } from "../../src/models/event";
|
import { MatrixEvent } from "../../src/models/event";
|
||||||
import { Relations } from "../../src/models/relations";
|
import { Relations } from "../../src/models/relations";
|
||||||
|
|
||||||
@@ -70,4 +71,63 @@ describe("Relations", function() {
|
|||||||
expect(events.size).toEqual(1);
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -740,16 +740,11 @@ EventTimelineSet.prototype.setRelationsTarget = function(event) {
|
|||||||
if (!relationsForEvent) {
|
if (!relationsForEvent) {
|
||||||
return;
|
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) {
|
for (const relationsWithRelType of Object.values(relationsForEvent)) {
|
||||||
relationsWithEventType.setTargetEvent(event);
|
for (const relationsWithEventType of Object.values(relationsWithRelType)) {
|
||||||
|
relationsWithEventType.setTargetEvent(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -797,7 +792,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
|
|||||||
}
|
}
|
||||||
let relationsWithEventType = relationsWithRelType[eventType];
|
let relationsWithEventType = relationsWithRelType[eventType];
|
||||||
|
|
||||||
let isNewRelations = false;
|
|
||||||
let relatesToEvent;
|
let relatesToEvent;
|
||||||
if (!relationsWithEventType) {
|
if (!relationsWithEventType) {
|
||||||
relationsWithEventType = relationsWithRelType[eventType] = new Relations(
|
relationsWithEventType = relationsWithRelType[eventType] = new Relations(
|
||||||
@@ -805,7 +799,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
|
|||||||
eventType,
|
eventType,
|
||||||
this.room,
|
this.room,
|
||||||
);
|
);
|
||||||
isNewRelations = true;
|
|
||||||
relatesToEvent = this.findEventById(relatesToEventId) || this.room.getPendingEvent(relatesToEventId);
|
relatesToEvent = this.findEventById(relatesToEventId) || this.room.getPendingEvent(relatesToEventId);
|
||||||
if (relatesToEvent) {
|
if (relatesToEvent) {
|
||||||
relationsWithEventType.setTargetEvent(relatesToEvent);
|
relationsWithEventType.setTargetEvent(relatesToEvent);
|
||||||
@@ -813,11 +806,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
relationsWithEventType.addEvent(event);
|
relationsWithEventType.addEvent(event);
|
||||||
|
|
||||||
// only emit once event has been added to relations
|
|
||||||
if (isNewRelations && relatesToEvent) {
|
|
||||||
relatesToEvent.emit("Event.relationsCreated", relationType, eventType);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -48,6 +48,7 @@ export class Relations extends EventEmitter {
|
|||||||
this._sortedAnnotationsByKey = [];
|
this._sortedAnnotationsByKey = [];
|
||||||
this._targetEvent = null;
|
this._targetEvent = null;
|
||||||
this._room = room;
|
this._room = room;
|
||||||
|
this._creationEmitted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,6 +95,8 @@ export class Relations extends EventEmitter {
|
|||||||
event.on("Event.beforeRedaction", this._onBeforeRedaction);
|
event.on("Event.beforeRedaction", this._onBeforeRedaction);
|
||||||
|
|
||||||
this.emit("Relations.add", event);
|
this.emit("Relations.add", event);
|
||||||
|
|
||||||
|
this._maybeEmitCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -345,6 +348,7 @@ export class Relations extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._targetEvent = event;
|
this._targetEvent = event;
|
||||||
|
|
||||||
if (this.relationType === "m.replace") {
|
if (this.relationType === "m.replace") {
|
||||||
const replacement = await this.getLastReplacement();
|
const replacement = await this.getLastReplacement();
|
||||||
// this is the initial update, so only call it if we already have something
|
// 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._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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user