You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
Improve thread partitioning for 2nd degree relations (#2165)
This commit is contained in:
@@ -3,7 +3,6 @@ import { CRYPTO_ENABLED } from "../../src/client";
|
||||
import { MatrixEvent } from "../../src/models/event";
|
||||
import { Filter, MemoryStore, Room } from "../../src/matrix";
|
||||
import { TestClient } from "../TestClient";
|
||||
import { Thread } from "../../src/models/thread";
|
||||
|
||||
describe("MatrixClient", function() {
|
||||
let client = null;
|
||||
@@ -405,6 +404,11 @@ describe("MatrixClient", function() {
|
||||
|
||||
it("copies pre-thread in-timeline vote events onto both timelines", function() {
|
||||
client.clientOpts = { experimentalThreadSupport: true };
|
||||
|
||||
const eventMessageInThread = buildEventMessageInThread();
|
||||
const eventPollResponseReference = buildEventPollResponseReference();
|
||||
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
|
||||
|
||||
const events = [
|
||||
eventMessageInThread,
|
||||
eventPollResponseReference,
|
||||
@@ -435,6 +439,11 @@ describe("MatrixClient", function() {
|
||||
|
||||
it("copies pre-thread in-timeline reactions onto both timelines", function() {
|
||||
client.clientOpts = { experimentalThreadSupport: true };
|
||||
|
||||
const eventMessageInThread = buildEventMessageInThread();
|
||||
const eventReaction = buildEventReaction();
|
||||
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
|
||||
|
||||
const events = [
|
||||
eventMessageInThread,
|
||||
eventReaction,
|
||||
@@ -456,6 +465,11 @@ describe("MatrixClient", function() {
|
||||
|
||||
it("copies post-thread in-timeline vote events onto both timelines", function() {
|
||||
client.clientOpts = { experimentalThreadSupport: true };
|
||||
|
||||
const eventPollResponseReference = buildEventPollResponseReference();
|
||||
const eventMessageInThread = buildEventMessageInThread();
|
||||
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
|
||||
|
||||
const events = [
|
||||
eventPollResponseReference,
|
||||
eventMessageInThread,
|
||||
@@ -475,119 +489,13 @@ describe("MatrixClient", function() {
|
||||
]);
|
||||
});
|
||||
|
||||
it("copies post-thread in-thread vote events onto both timelines", function() {
|
||||
client.clientOpts = { experimentalThreadSupport: true };
|
||||
|
||||
// Events for this test only, because we hack around with them
|
||||
const eventMessageInThread2 = new MatrixEvent({
|
||||
"age": 80098509,
|
||||
"content": {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"ciphertext": "ENCRYPTEDSTUFF",
|
||||
"device_id": "XISFUZSKHH",
|
||||
"m.relates_to": {
|
||||
"event_id": "$AAA2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo",
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$AAA2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo",
|
||||
},
|
||||
"rel_type": "io.element.thread",
|
||||
},
|
||||
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
|
||||
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
|
||||
},
|
||||
"event_id": "$AAAhKIGYowtBblVLkRimeIg8TcdjETnxhDPGfi6NpDg",
|
||||
"origin_server_ts": 1643815466378,
|
||||
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
|
||||
"sender": "@andybalaam-test1:matrix.org",
|
||||
"type": "m.room.encrypted",
|
||||
"unsigned": { "age": 80098509 },
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventPollStartThreadRoot2 = new MatrixEvent({
|
||||
"age": 80108647,
|
||||
"content": {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"ciphertext": "ENCRYPTEDSTUFF",
|
||||
"device_id": "XISFUZSKHH",
|
||||
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
|
||||
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
|
||||
},
|
||||
"event_id": "$AAA2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo",
|
||||
"origin_server_ts": 1643815456240,
|
||||
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
|
||||
"sender": "@andybalaam-test1:matrix.org",
|
||||
"type": "m.room.encrypted",
|
||||
"unsigned": { "age": 80108647 },
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventPollResponseReference2 = new MatrixEvent({
|
||||
"age": 80098509,
|
||||
"content": {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
"ciphertext": "ENCRYPTEDSTUFF",
|
||||
"device_id": "XISFUZSKHH",
|
||||
"m.relates_to": {
|
||||
"event_id": "$AAA2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo",
|
||||
"rel_type": "m.reference",
|
||||
},
|
||||
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
|
||||
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
|
||||
},
|
||||
"event_id": "$AAAvpezvsF0cKgav3g8W-uEVS4WkDHgxbJZvL3uMR1g",
|
||||
"origin_server_ts": 1643815458650,
|
||||
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
|
||||
"sender": "@andybalaam-test1:matrix.org",
|
||||
"type": "m.room.encrypted",
|
||||
"unsigned": { "age": 80106237 },
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
// When we react within a thread, sometimes the thread root
|
||||
// has isThreadRelation === true, because thread is set on it,
|
||||
// but threadId is not.
|
||||
eventPollStartThreadRoot2.setThread(
|
||||
new Thread(
|
||||
eventPollStartThreadRoot2,
|
||||
{
|
||||
client,
|
||||
room: new Room(),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const events = [
|
||||
eventPollResponseReference2,
|
||||
eventMessageInThread2,
|
||||
eventPollStartThreadRoot2,
|
||||
];
|
||||
|
||||
const [timeline, threaded] = client.partitionThreadedEvents(events);
|
||||
|
||||
expect(timeline).toEqual([
|
||||
eventPollResponseReference2,
|
||||
// eventPollStartThreadRoot2,
|
||||
// This is weird: by hacking the thread root to have an inconsistency
|
||||
// between thread and threadId (which is what I have observed in the
|
||||
// wild), we have persuaded the code that the thread root is actually
|
||||
// within the thread, so it is not provided to the main timeline.
|
||||
//
|
||||
// This should go away when we fix this inconsistency. When that
|
||||
// happens, we should probably delete this test.
|
||||
]);
|
||||
|
||||
expect(threaded).toEqual([
|
||||
withThreadId(
|
||||
eventPollResponseReference2, eventPollStartThreadRoot2.getId(),
|
||||
),
|
||||
eventMessageInThread2,
|
||||
eventPollStartThreadRoot2, // See note above for why this appears here.
|
||||
]);
|
||||
});
|
||||
|
||||
it("copies post-thread in-timeline reactions onto both timelines", function() {
|
||||
client.clientOpts = { experimentalThreadSupport: true };
|
||||
|
||||
const eventReaction = buildEventReaction();
|
||||
const eventMessageInThread = buildEventMessageInThread();
|
||||
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
|
||||
|
||||
const events = [
|
||||
eventReaction,
|
||||
eventMessageInThread,
|
||||
@@ -610,6 +518,19 @@ describe("MatrixClient", function() {
|
||||
it("sends room state events to the main timeline only", function() {
|
||||
client.clientOpts = { experimentalThreadSupport: true };
|
||||
// This is based on recording the events in a real room:
|
||||
|
||||
const eventMessageInThread = buildEventMessageInThread();
|
||||
const eventPollResponseReference = buildEventPollResponseReference();
|
||||
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
|
||||
const eventRoomName = buildEventRoomName();
|
||||
const eventEncryption = buildEventEncryption();
|
||||
const eventGuestAccess = buildEventGuestAccess();
|
||||
const eventHistoryVisibility = buildEventHistoryVisibility();
|
||||
const eventJoinRules = buildEventJoinRules();
|
||||
const eventPowerLevels = buildEventPowerLevels();
|
||||
const eventMember = buildEventMember();
|
||||
const eventCreate = buildEventCreate();
|
||||
|
||||
const events = [
|
||||
eventMessageInThread,
|
||||
eventPollResponseReference,
|
||||
@@ -655,7 +576,7 @@ function withThreadId(event, newThreadId) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const eventMessageInThread = new MatrixEvent({
|
||||
const buildEventMessageInThread = () => new MatrixEvent({
|
||||
"age": 80098509,
|
||||
"content": {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
@@ -680,7 +601,7 @@ const eventMessageInThread = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventPollResponseReference = new MatrixEvent({
|
||||
const buildEventPollResponseReference = () => new MatrixEvent({
|
||||
"age": 80098509,
|
||||
"content": {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
@@ -702,7 +623,7 @@ const eventPollResponseReference = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventReaction = new MatrixEvent({
|
||||
const buildEventReaction = () => new MatrixEvent({
|
||||
"content": {
|
||||
"m.relates_to": {
|
||||
"event_id": "$VLS2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo",
|
||||
@@ -721,7 +642,7 @@ const eventReaction = new MatrixEvent({
|
||||
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
|
||||
});
|
||||
|
||||
const eventPollStartThreadRoot = new MatrixEvent({
|
||||
const buildEventPollStartThreadRoot = () => new MatrixEvent({
|
||||
"age": 80108647,
|
||||
"content": {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
@@ -739,7 +660,7 @@ const eventPollStartThreadRoot = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventRoomName = new MatrixEvent({
|
||||
const buildEventRoomName = () => new MatrixEvent({
|
||||
"age": 80123249,
|
||||
"content": {
|
||||
"name": "1 poll, 1 vote, 1 thread",
|
||||
@@ -754,7 +675,7 @@ const eventRoomName = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventEncryption = new MatrixEvent({
|
||||
const buildEventEncryption = () => new MatrixEvent({
|
||||
"age": 80123383,
|
||||
"content": {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
@@ -769,7 +690,7 @@ const eventEncryption = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventGuestAccess = new MatrixEvent({
|
||||
const buildEventGuestAccess = () => new MatrixEvent({
|
||||
"age": 80123473,
|
||||
"content": {
|
||||
"guest_access": "can_join",
|
||||
@@ -784,7 +705,7 @@ const eventGuestAccess = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventHistoryVisibility = new MatrixEvent({
|
||||
const buildEventHistoryVisibility = () => new MatrixEvent({
|
||||
"age": 80123556,
|
||||
"content": {
|
||||
"history_visibility": "shared",
|
||||
@@ -799,7 +720,7 @@ const eventHistoryVisibility = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventJoinRules = new MatrixEvent({
|
||||
const buildEventJoinRules = () => new MatrixEvent({
|
||||
"age": 80123696,
|
||||
"content": {
|
||||
"join_rule": "invite",
|
||||
@@ -814,7 +735,7 @@ const eventJoinRules = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventPowerLevels = new MatrixEvent({
|
||||
const buildEventPowerLevels = () => new MatrixEvent({
|
||||
"age": 80124105,
|
||||
"content": {
|
||||
"ban": 50,
|
||||
@@ -849,7 +770,7 @@ const eventPowerLevels = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventMember = new MatrixEvent({
|
||||
const buildEventMember = () => new MatrixEvent({
|
||||
"age": 80125279,
|
||||
"content": {
|
||||
"avatar_url": "mxc://matrix.org/aNtbVcFfwotudypZcHsIcPOc",
|
||||
@@ -866,7 +787,7 @@ const eventMember = new MatrixEvent({
|
||||
"user_id": "@andybalaam-test1:matrix.org",
|
||||
});
|
||||
|
||||
const eventCreate = new MatrixEvent({
|
||||
const buildEventCreate = () => new MatrixEvent({
|
||||
"age": 80126105,
|
||||
"content": {
|
||||
"creator": "@andybalaam-test1:matrix.org",
|
||||
|
||||
@@ -92,6 +92,7 @@ export enum EventType {
|
||||
export enum RelationType {
|
||||
Annotation = "m.annotation",
|
||||
Replace = "m.replace",
|
||||
Reference = "m.reference",
|
||||
/**
|
||||
* Note, "io.element.thread" is hardcoded
|
||||
* Should be replaced with "m.thread" once MSC3440 lands
|
||||
|
||||
@@ -3594,17 +3594,20 @@ export class MatrixClient extends EventEmitter {
|
||||
threadId = null;
|
||||
}
|
||||
|
||||
if (threadId && content["m.relates_to"]?.rel_type !== RelationType.Thread) {
|
||||
// If we expect that an event is part of a thread but is missing the relation
|
||||
// we need to add it manually, as well as the reply fallback
|
||||
if (threadId && !content["m.relates_to"]?.rel_type) {
|
||||
content["m.relates_to"] = {
|
||||
...content["m.relates_to"],
|
||||
"rel_type": RelationType.Thread,
|
||||
"event_id": threadId,
|
||||
};
|
||||
|
||||
const thread = this.getRoom(roomId)?.threads.get(threadId);
|
||||
if (thread) {
|
||||
content["m.relates_to"]["m.in_reply_to"] = {
|
||||
"event_id": thread.replyToEvent.getId(),
|
||||
"event_id": thread.lastReply((ev: MatrixEvent) => {
|
||||
return ev.isThreadRelation && !ev.status;
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3652,6 +3655,7 @@ export class MatrixClient extends EventEmitter {
|
||||
const thread = room?.threads.get(threadId);
|
||||
if (thread) {
|
||||
localEvent.setThread(thread);
|
||||
localEvent.setThreadId(thread.id);
|
||||
}
|
||||
|
||||
// if this is a relation or redaction of an event
|
||||
@@ -9089,6 +9093,52 @@ export class MatrixClient extends EventEmitter {
|
||||
return threadRoots;
|
||||
}
|
||||
|
||||
private eventShouldLiveIn(event: MatrixEvent, room: Room, events: MatrixEvent[], roots: Set<string>): {
|
||||
shouldLiveInRoom: boolean;
|
||||
shouldLiveInThread: boolean;
|
||||
threadId?: string;
|
||||
} {
|
||||
// A thread relation is always only shown in a thread
|
||||
if (event.isThreadRelation) {
|
||||
return {
|
||||
shouldLiveInRoom: false,
|
||||
shouldLiveInThread: true,
|
||||
threadId: event.relationEventId,
|
||||
};
|
||||
}
|
||||
|
||||
const parentEventId = event.getAssociatedId();
|
||||
const parentEvent = room?.findEventById(parentEventId) || events.find((mxEv: MatrixEvent) => (
|
||||
mxEv.getId() === parentEventId
|
||||
));
|
||||
|
||||
// A reaction targetting the thread root needs to be routed to both the
|
||||
// the main timeline and the associated thread
|
||||
const targetingThreadRoot = parentEvent?.isThreadRoot || roots.has(event.relationEventId);
|
||||
if (targetingThreadRoot) {
|
||||
return {
|
||||
shouldLiveInRoom: true,
|
||||
shouldLiveInThread: true,
|
||||
threadId: event.relationEventId,
|
||||
};
|
||||
}
|
||||
|
||||
// If the parent event also has an associated ID we want to re-run the
|
||||
// computation for that parent event.
|
||||
// In the case of the redaction of a reaction that targets a root event
|
||||
// we want that redaction to be pushed to both timeline
|
||||
if (parentEvent?.getAssociatedId()) {
|
||||
return this.eventShouldLiveIn(parentEvent, room, events, roots);
|
||||
} else {
|
||||
// We've exhausted all scenarios, can safely assume that this event
|
||||
// should live in the room timeline
|
||||
return {
|
||||
shouldLiveInRoom: true,
|
||||
shouldLiveInThread: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public partitionThreadedEvents(events: MatrixEvent[]): [MatrixEvent[], MatrixEvent[]] {
|
||||
// Indices to the events array, for readibility
|
||||
const ROOM = 0;
|
||||
@@ -9097,35 +9147,22 @@ export class MatrixClient extends EventEmitter {
|
||||
const threadRoots = this.findThreadRoots(events);
|
||||
return events.reduce((memo, event: MatrixEvent) => {
|
||||
const room = this.getRoom(event.getRoomId());
|
||||
// An event should live in the thread timeline if
|
||||
// - It's a reply in thread event
|
||||
// - It's related to a reply in thread event
|
||||
let shouldLiveInThreadTimeline = event.isThreadRelation;
|
||||
if (!shouldLiveInThreadTimeline) {
|
||||
const parentEventId = event.parentEventId;
|
||||
const parentEvent = room?.findEventById(parentEventId) || events.find((mxEv: MatrixEvent) => {
|
||||
return mxEv.getId() === parentEventId;
|
||||
});
|
||||
const targetingThreadRoot = parentEvent?.isThreadRoot || threadRoots.has(event.relationEventId);
|
||||
|
||||
if (targetingThreadRoot && !event.isThreadRelation && event.relationEventId) {
|
||||
// If we refer to the thread root, we should be copied
|
||||
// into the thread as well as the main timeline.
|
||||
// This happens for reactions, annotations, poll votes etc.
|
||||
const copiedEvent = event.toSnapshot();
|
||||
const {
|
||||
shouldLiveInRoom,
|
||||
shouldLiveInThread,
|
||||
threadId,
|
||||
} = this.eventShouldLiveIn(event, room, events, threadRoots);
|
||||
|
||||
// The copied event is in this thread:
|
||||
copiedEvent.setThreadId(parentEventId);
|
||||
memo[THREAD].push(copiedEvent);
|
||||
} else if (parentEvent?.isThreadRelation) {
|
||||
// If our parent is in a thread, we are in that
|
||||
// same thread too. (E.g. if I reply within a thread.)
|
||||
shouldLiveInThreadTimeline = true;
|
||||
event.setThreadId(parentEvent.threadRootId);
|
||||
}
|
||||
if (shouldLiveInRoom) {
|
||||
memo[ROOM].push(event);
|
||||
}
|
||||
const targetTimeline = shouldLiveInThreadTimeline ? THREAD : ROOM;
|
||||
memo[targetTimeline].push(event);
|
||||
|
||||
if (shouldLiveInThread) {
|
||||
event.setThreadId(threadId);
|
||||
memo[THREAD].push(event);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, [[], []]);
|
||||
} else {
|
||||
|
||||
@@ -28,7 +28,6 @@ import { Room } from "./room";
|
||||
import { Filter } from "../filter";
|
||||
import { EventType, RelationType } from "../@types/event";
|
||||
import { RoomState } from "./room-state";
|
||||
import { Thread } from "./thread";
|
||||
|
||||
// var DEBUG = false;
|
||||
const DEBUG = true;
|
||||
@@ -159,17 +158,12 @@ export class EventTimelineSet extends EventEmitter {
|
||||
*
|
||||
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
|
||||
*/
|
||||
public getPendingEvents(thread?: Thread): MatrixEvent[] {
|
||||
public getPendingEvents(): MatrixEvent[] {
|
||||
if (!this.room || !this.displayPendingEvents) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const pendingEvents = this.room.getPendingEvents(thread);
|
||||
if (this.filter) {
|
||||
return this.filter.filterRoomTimeline(pendingEvents);
|
||||
} else {
|
||||
return pendingEvents;
|
||||
}
|
||||
return this.room.getPendingEvents();
|
||||
}
|
||||
/**
|
||||
* Get the live timeline for this room.
|
||||
@@ -756,7 +750,7 @@ export class EventTimelineSet extends EventEmitter {
|
||||
*/
|
||||
public getRelationsForEvent(
|
||||
eventId: string,
|
||||
relationType: RelationType,
|
||||
relationType: RelationType | string,
|
||||
eventType: EventType | string,
|
||||
): Relations | undefined {
|
||||
if (!this.unstableClientRelationAggregation) {
|
||||
@@ -774,6 +768,17 @@ export class EventTimelineSet extends EventEmitter {
|
||||
return relationsWithRelType[eventType];
|
||||
}
|
||||
|
||||
public getAllRelationsEventForEvent(eventId: string): MatrixEvent[] {
|
||||
const relationsForEvent = this.relations[eventId] || {};
|
||||
const events = [];
|
||||
for (const relationsRecord of Object.values(relationsForEvent)) {
|
||||
for (const relations of Object.values(relationsRecord)) {
|
||||
events.push(...relations.getRelations());
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an event as the target event if any Relations exist for it already
|
||||
*
|
||||
|
||||
@@ -512,8 +512,7 @@ export class MatrixEvent extends EventEmitter {
|
||||
if (relatesTo?.rel_type === RelationType.Thread) {
|
||||
return relatesTo.event_id;
|
||||
} else {
|
||||
return this.threadId
|
||||
|| this.getThread()?.id;
|
||||
return this.getThread()?.id || this.threadId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,10 +536,6 @@ export class MatrixEvent extends EventEmitter {
|
||||
return !!threadDetails || (this.getThread()?.id === this.getId());
|
||||
}
|
||||
|
||||
public get parentEventId(): string {
|
||||
return this.replyEventId || this.relationEventId;
|
||||
}
|
||||
|
||||
public get replyEventId(): string {
|
||||
// We're prefer ev.getContent() over ev.getWireContent() to make sure
|
||||
// we grab the latest edit with potentially new relations. But we also
|
||||
@@ -1427,7 +1422,9 @@ export class MatrixEvent extends EventEmitter {
|
||||
*/
|
||||
public getAssociatedId(): string | undefined {
|
||||
const relation = this.getRelation();
|
||||
if (relation) {
|
||||
if (this.replyEventId) {
|
||||
return this.replyEventId;
|
||||
} else if (relation) {
|
||||
return relation.event_id;
|
||||
} else if (this.isRedaction()) {
|
||||
return this.event.redacts;
|
||||
@@ -1561,6 +1558,7 @@ export class MatrixEvent extends EventEmitter {
|
||||
*/
|
||||
public setThread(thread: Thread): void {
|
||||
this.thread = thread;
|
||||
this.setThreadId(thread.id);
|
||||
this.reEmitter.reEmit(thread, [ThreadEvent.Ready, ThreadEvent.Update]);
|
||||
}
|
||||
|
||||
|
||||
@@ -171,11 +171,11 @@ export class Relations extends EventEmitter {
|
||||
* @return {Array}
|
||||
* Relation events in insertion order.
|
||||
*/
|
||||
public getRelations() {
|
||||
public getRelations(): MatrixEvent[] {
|
||||
return [...this.relations];
|
||||
}
|
||||
|
||||
private addAnnotationToAggregation(event: MatrixEvent) {
|
||||
private addAnnotationToAggregation(event: MatrixEvent): void {
|
||||
const { key } = event.getRelation();
|
||||
if (!key) {
|
||||
return;
|
||||
@@ -204,7 +204,7 @@ export class Relations extends EventEmitter {
|
||||
eventsFromSender.add(event);
|
||||
}
|
||||
|
||||
private removeAnnotationFromAggregation(event: MatrixEvent) {
|
||||
private removeAnnotationFromAggregation(event: MatrixEvent): void {
|
||||
const { key } = event.getRelation();
|
||||
if (!key) {
|
||||
return;
|
||||
@@ -240,7 +240,7 @@ export class Relations extends EventEmitter {
|
||||
* @param {MatrixEvent} redactedEvent
|
||||
* The original relation event that is about to be redacted.
|
||||
*/
|
||||
private onBeforeRedaction = async (redactedEvent: MatrixEvent) => {
|
||||
private onBeforeRedaction = async (redactedEvent: MatrixEvent): Promise<void> => {
|
||||
if (!this.relations.has(redactedEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -512,16 +512,14 @@ export class Room extends EventEmitter {
|
||||
*
|
||||
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
|
||||
*/
|
||||
public getPendingEvents(thread?: Thread): MatrixEvent[] {
|
||||
public getPendingEvents(): MatrixEvent[] {
|
||||
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
|
||||
throw new Error(
|
||||
"Cannot call getPendingEvents with pendingEventOrdering == " +
|
||||
this.opts.pendingEventOrdering);
|
||||
}
|
||||
|
||||
return this.pendingEventList.filter(event => {
|
||||
return !thread || thread.id === event.threadRootId;
|
||||
});
|
||||
return this.pendingEventList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1358,7 +1356,7 @@ export class Room extends EventEmitter {
|
||||
} else if (event.isThreadRoot) {
|
||||
return this.threads.get(event.getId());
|
||||
} else {
|
||||
const parentEvent = this.findEventById(event.parentEventId);
|
||||
const parentEvent = this.findEventById(event.getAssociatedId());
|
||||
return this.findThreadForEvent(parentEvent);
|
||||
}
|
||||
}
|
||||
@@ -1396,21 +1394,15 @@ export class Room extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getUnsigned().transaction_id) {
|
||||
const existingEvent = this.txnToEvent[event.getUnsigned().transaction_id];
|
||||
if (existingEvent) {
|
||||
// remote echo of an event we sent earlier
|
||||
this.handleRemoteEcho(event, existingEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.emit(ThreadEvent.Update, thread);
|
||||
}
|
||||
|
||||
public createThread(rootEvent: MatrixEvent, events?: MatrixEvent[]): Thread | undefined {
|
||||
public createThread(rootEvent: MatrixEvent, events: MatrixEvent[] = []): Thread | undefined {
|
||||
const tl = this.getTimelineForEvent(rootEvent.getId());
|
||||
const relatedEvents = tl?.getTimelineSet().getAllRelationsEventForEvent(rootEvent.getId()) ?? [];
|
||||
|
||||
const thread = new Thread(rootEvent, {
|
||||
initialEvents: events,
|
||||
initialEvents: events.concat(relatedEvents),
|
||||
room: this,
|
||||
client: this.client,
|
||||
});
|
||||
@@ -1564,8 +1556,7 @@ export class Room extends EventEmitter {
|
||||
EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS), false);
|
||||
|
||||
this.txnToEvent[txnId] = event;
|
||||
const thread = this.findThreadForEvent(event);
|
||||
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached && !thread) {
|
||||
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
|
||||
if (this.pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
|
||||
logger.warn("Setting event as NOT_SENT due to messages in the same state");
|
||||
event.setStatus(EventStatus.NOT_SENT);
|
||||
@@ -1581,8 +1572,7 @@ export class Room extends EventEmitter {
|
||||
|
||||
if (event.isRedaction()) {
|
||||
const redactId = event.event.redacts;
|
||||
let redactedEvent = this.pendingEventList &&
|
||||
this.pendingEventList.find(e => e.getId() === redactId);
|
||||
let redactedEvent = this.pendingEventList?.find(e => e.getId() === redactId);
|
||||
if (!redactedEvent) {
|
||||
redactedEvent = this.findEventById(redactId);
|
||||
}
|
||||
@@ -1592,20 +1582,16 @@ export class Room extends EventEmitter {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (thread) {
|
||||
thread.addEvent(event, false);
|
||||
} else {
|
||||
for (let i = 0; i < this.timelineSets.length; i++) {
|
||||
const timelineSet = this.timelineSets[i];
|
||||
if (timelineSet.getFilter()) {
|
||||
if (timelineSet.getFilter().filterRoomTimeline([event]).length) {
|
||||
timelineSet.addEventToTimeline(event,
|
||||
timelineSet.getLiveTimeline(), false);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < this.timelineSets.length; i++) {
|
||||
const timelineSet = this.timelineSets[i];
|
||||
if (timelineSet.getFilter()) {
|
||||
if (timelineSet.getFilter().filterRoomTimeline([event]).length) {
|
||||
timelineSet.addEventToTimeline(event,
|
||||
timelineSet.getLiveTimeline(), false);
|
||||
}
|
||||
} else {
|
||||
timelineSet.addEventToTimeline(event,
|
||||
timelineSet.getLiveTimeline(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1666,7 +1652,9 @@ export class Room extends EventEmitter {
|
||||
const thread = this.findThreadForEvent(event);
|
||||
if (thread) {
|
||||
thread.timelineSet.aggregateRelations(event);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (thread?.id === event.getAssociatedId() || !thread) {
|
||||
// TODO: We should consider whether this means it would be a better
|
||||
// design to lift the relations handling up to the room instead.
|
||||
for (let i = 0; i < this.timelineSets.length; i++) {
|
||||
@@ -1682,6 +1670,10 @@ export class Room extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public getEventForTxnId(txnId: string): MatrixEvent {
|
||||
return this.txnToEvent[txnId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deal with the echo of a message we sent.
|
||||
*
|
||||
@@ -1696,7 +1688,7 @@ export class Room extends EventEmitter {
|
||||
* @fires module:client~MatrixClient#event:"Room.localEchoUpdated"
|
||||
* @private
|
||||
*/
|
||||
private handleRemoteEcho(remoteEvent: MatrixEvent, localEvent: MatrixEvent): void {
|
||||
public handleRemoteEcho(remoteEvent: MatrixEvent, localEvent: MatrixEvent): void {
|
||||
const oldEventId = localEvent.getId();
|
||||
const newEventId = remoteEvent.getId();
|
||||
const oldStatus = localEvent.status;
|
||||
@@ -1721,7 +1713,9 @@ export class Room extends EventEmitter {
|
||||
const thread = this.findThreadForEvent(remoteEvent);
|
||||
if (thread) {
|
||||
thread.timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (thread?.id === remoteEvent.getAssociatedId() || !thread) {
|
||||
for (let i = 0; i < this.timelineSets.length; i++) {
|
||||
const timelineSet = this.timelineSets[i];
|
||||
|
||||
@@ -1791,7 +1785,8 @@ export class Room extends EventEmitter {
|
||||
const thread = this.findThreadForEvent(event);
|
||||
if (thread) {
|
||||
thread.timelineSet.replaceEventId(oldEventId, newEventId);
|
||||
} else {
|
||||
}
|
||||
if (thread?.id === event.getAssociatedId() || !thread) {
|
||||
// if the event was already in the timeline (which will be the case if
|
||||
// opts.pendingEventOrdering==chronological), we need to update the
|
||||
// timeline map.
|
||||
|
||||
@@ -113,6 +113,27 @@ export class Thread extends TypedEventEmitter<ThreadEvent> {
|
||||
return this.room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
}
|
||||
|
||||
private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void {
|
||||
if (event.getUnsigned().transaction_id) {
|
||||
const existingEvent = this.room.getEventForTxnId(event.getUnsigned().transaction_id);
|
||||
if (existingEvent) {
|
||||
// remote echo of an event we sent earlier
|
||||
this.room.handleRemoteEcho(event, existingEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.findEventById(event.getId())) {
|
||||
this.timelineSet.addEventToTimeline(
|
||||
event,
|
||||
this.liveTimeline,
|
||||
toStartOfTimeline,
|
||||
false,
|
||||
this.roomState,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event to the thread and updates
|
||||
* the tail/root references if needed
|
||||
@@ -123,36 +144,20 @@ export class Thread extends TypedEventEmitter<ThreadEvent> {
|
||||
// Add all incoming events to the thread's timeline set when there's
|
||||
// no server support
|
||||
if (!this.hasServerSideSupport) {
|
||||
if (this.timelineSet.findEventById(event.getId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.timelineSet.addEventToTimeline(
|
||||
event,
|
||||
this.liveTimeline,
|
||||
toStartOfTimeline,
|
||||
false,
|
||||
this.roomState,
|
||||
);
|
||||
this.addEventToTimeline(event, toStartOfTimeline);
|
||||
|
||||
await this.client.decryptEventIfNeeded(event, {});
|
||||
}
|
||||
|
||||
if (this.hasServerSideSupport && this.initialEventsFetched) {
|
||||
if (event.localTimestamp > this.lastReply().localTimestamp && !this.findEventById(event.getId())) {
|
||||
this.timelineSet.addEventToTimeline(
|
||||
event,
|
||||
this.liveTimeline,
|
||||
false,
|
||||
false,
|
||||
this.roomState,
|
||||
);
|
||||
if (event.localTimestamp > this.lastReply().localTimestamp) {
|
||||
this.addEventToTimeline(event, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1727,7 +1727,7 @@ export class SyncApi {
|
||||
// extractRelatedEvents(event: MatrixEvent, events: MatrixEvent[], relatedEvents: MatrixEvent[] = []): MatrixEvent[] {
|
||||
// relatedEvents.push(event);
|
||||
|
||||
// const parentEventId = event.parentEventId;
|
||||
// const parentEventId = event.getAssociatedId();
|
||||
// const parentEventIndex = events.findIndex(event => event.getId() === parentEventId);
|
||||
|
||||
// if (parentEventIndex > -1) {
|
||||
|
||||
Reference in New Issue
Block a user