1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Make local echo work for threads (#2026)

This commit is contained in:
Germain
2021-11-24 08:19:17 +00:00
committed by GitHub
parent 0ccde7807f
commit ddd6a05198
5 changed files with 383 additions and 90 deletions

View File

@ -23,7 +23,7 @@ describe("MatrixClient retrying", function() {
);
httpBackend = testClient.httpBackend;
client = testClient.client;
room = new Room(roomId);
room = new Room(roomId, client, userId);
client.store.storeRoom(room);
});
@ -50,7 +50,10 @@ describe("MatrixClient retrying", function() {
it("should mark events as EventStatus.CANCELLED when cancelled", function() {
// send a couple of events; the second will be queued
const p1 = client.sendMessage(roomId, "m1").then(function(ev) {
const p1 = client.sendMessage(roomId, {
"msgtype": "m.text",
"body": "m1",
}).then(function(ev) {
// we expect the first message to fail
throw new Error('Message 1 unexpectedly sent successfully');
}, (e) => {
@ -60,7 +63,10 @@ describe("MatrixClient retrying", function() {
// XXX: it turns out that the promise returned by this message
// never gets resolved.
// https://github.com/matrix-org/matrix-js-sdk/issues/496
client.sendMessage(roomId, "m2");
client.sendMessage(roomId, {
"msgtype": "m.text",
"body": "m2",
});
// both events should be in the timeline at this point
const tl = room.getLiveTimeline().getEvents();
@ -88,7 +94,7 @@ describe("MatrixClient retrying", function() {
}).respond(400); // fail the first message
// wait for the localecho of ev1 to be updated
const p3 = new Promise((resolve, reject) => {
const p3 = new Promise<void>((resolve, reject) => {
room.on("Room.localEchoUpdated", (ev0) => {
if (ev0 === ev1) {
resolve();

View File

@ -689,6 +689,12 @@ interface IRoomsKeysResponse {
}
/* eslint-enable camelcase */
// We're using this constant for methods overloading and inspect whether a variable
// contains an eventId or not. This was required to ensure backwards compatibility
// of methods for threads
// Probably not the most graceful solution but does a good enough job for now
const EVENT_ID_PREFIX = "$";
/**
* Represents a Matrix Client. Only directly construct this if you want to use
* custom modules. Normally, {@link createClient} should be used
@ -3392,10 +3398,11 @@ export class MatrixClient extends EventEmitter {
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} eventType
* @param {Object} content
* @param {string} txnId Optional.
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to an empty object {}
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3405,20 +3412,45 @@ export class MatrixClient extends EventEmitter {
content: IContent,
txnId?: string,
callback?: Callback,
);
public sendEvent(
roomId: string,
threadId: string | null,
eventType: string,
content: IContent,
txnId?: string,
callback?: Callback,
)
public sendEvent(
roomId: string,
threadId: string | null,
eventType: string | IContent,
content: IContent | string,
txnId?: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
return this.sendCompleteEvent(roomId, { type: eventType, content }, txnId, callback);
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = txnId as Callback;
txnId = content as string;
content = eventType as IContent;
eventType = threadId;
threadId = null;
}
return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, txnId as string, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {object} eventObject An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.
* @param {string} txnId Optional.
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to an empty object {}
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
private sendCompleteEvent(
roomId: string,
threadId: string | null,
eventObject: any,
txnId?: string,
callback?: Callback,
@ -3444,6 +3476,10 @@ export class MatrixClient extends EventEmitter {
}));
const room = this.getRoom(roomId);
const thread = room?.threads.get(threadId);
if (thread) {
localEvent.setThread(thread);
}
// if this is a relation or redaction of an event
// that hasn't been sent yet (e.g. with a local id starting with a ~)
@ -3460,12 +3496,12 @@ export class MatrixClient extends EventEmitter {
const type = localEvent.getType();
logger.log(`sendEvent of type ${type} in ${roomId} with txnId ${txnId}`);
localEvent.setTxnId(txnId);
localEvent.setTxnId(txnId as string);
localEvent.setStatus(EventStatus.SENDING);
// add this event immediately to the local store as 'sending'.
if (room) {
room.addPendingEvent(localEvent, txnId);
room.addPendingEvent(localEvent, txnId as string);
}
// addPendingEvent can change the state to NOT_SENT if it believes
@ -3655,7 +3691,7 @@ export class MatrixClient extends EventEmitter {
* supplied.
* @param {object|module:client.callback} cbOrOpts
* Options to pass on, may contain `reason`.
* Can be callback for backwards compatibility.
* Can be callback for backwards compatibility. Deprecated
* @return {Promise} Resolves: TODO
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3664,22 +3700,43 @@ export class MatrixClient extends EventEmitter {
eventId: string,
txnId?: string,
cbOrOpts?: Callback | IRedactOpts,
);
public redactEvent(
roomId: string,
threadId: string | null,
eventId: string,
txnId?: string,
cbOrOpts?: Callback | IRedactOpts,
);
public redactEvent(
roomId: string,
threadId: string | null,
eventId: string,
txnId?: string | Callback | IRedactOpts,
cbOrOpts?: Callback | IRedactOpts,
): Promise<ISendEventResponse> {
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
cbOrOpts = txnId as (Callback | IRedactOpts);
txnId = eventId;
eventId = threadId;
threadId = null;
}
const opts = typeof (cbOrOpts) === 'object' ? cbOrOpts : {};
const reason = opts.reason;
const callback = typeof (cbOrOpts) === 'function' ? cbOrOpts : undefined;
return this.sendCompleteEvent(roomId, {
return this.sendCompleteEvent(roomId, threadId, {
type: EventType.RoomRedaction,
content: { reason: reason },
redacts: eventId,
}, txnId, callback);
}, txnId as string, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {Object} content
* @param {string} txnId Optional.
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to an ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3688,19 +3745,47 @@ export class MatrixClient extends EventEmitter {
content: IContent,
txnId?: string,
callback?: Callback,
)
public sendMessage(
roomId: string,
threadId: string | null,
content: IContent,
txnId?: string,
callback?: Callback,
)
public sendMessage(
roomId: string,
threadId: string | null | IContent,
content: IContent | string,
txnId?: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
if (typeof threadId !== "string" && threadId !== null) {
callback = txnId as Callback;
txnId = content as string;
content = threadId as IContent;
threadId = null;
}
if (utils.isFunction(txnId)) {
callback = txnId as any as Callback; // for legacy
txnId = undefined;
}
return this.sendEvent(roomId, EventType.RoomMessage, content, txnId, callback);
return this.sendEvent(
roomId,
threadId as (string | null),
EventType.RoomMessage,
content as IContent,
txnId as string,
callback,
);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} body
* @param {string} txnId Optional.
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to an empty object {}
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3709,29 +3794,76 @@ export class MatrixClient extends EventEmitter {
body: string,
txnId?: string,
callback?: Callback,
)
public sendTextMessage(
roomId: string,
threadId: string | null,
body: string,
txnId?: string,
callback?: Callback,
)
public sendTextMessage(
roomId: string,
threadId: string | null,
body: string,
txnId?: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = txnId as Callback;
txnId = body;
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeTextMessage(body);
return this.sendMessage(roomId, content, txnId, callback);
return this.sendMessage(roomId, threadId, content, txnId as string, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} body
* @param {string} txnId Optional.
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to a ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public sendNotice(roomId: string, body: string, txnId?: string, callback?: Callback): Promise<ISendEventResponse> {
public sendNotice(
roomId: string,
body: string,
txnId?: string,
callback?: Callback,
)
public sendNotice(
roomId: string,
threadId: string | null,
body: string,
txnId?: string,
callback?: Callback,
);
public sendNotice(
roomId: string,
threadId: string | null,
body: string,
txnId?: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = txnId as Callback;
txnId = body;
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeNotice(body);
return this.sendMessage(roomId, content, txnId, callback);
return this.sendMessage(roomId, threadId, content, txnId as string, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} body
* @param {string} txnId Optional.
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to a ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3740,17 +3872,38 @@ export class MatrixClient extends EventEmitter {
body: string,
txnId?: string,
callback?: Callback,
)
public sendEmoteMessage(
roomId: string,
threadId: string | null,
body: string,
txnId?: string,
callback?: Callback,
);
public sendEmoteMessage(
roomId: string,
threadId: string | null,
body: string,
txnId?: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = txnId as Callback;
txnId = body;
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeEmoteMessage(body);
return this.sendMessage(roomId, content, txnId, callback);
return this.sendMessage(roomId, threadId, content, txnId as string, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} url
* @param {Object} info
* @param {string} text
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to a ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3758,9 +3911,32 @@ export class MatrixClient extends EventEmitter {
roomId: string,
url: string,
info?: IImageInfo,
text = "Image",
text?: string,
callback?: Callback,
);
public sendImageMessage(
roomId: string,
threadId: string | null,
url: string,
info?: IImageInfo,
text?: string,
callback?: Callback,
);
public sendImageMessage(
roomId: string,
threadId: string | null,
url: string | IImageInfo,
info?: IImageInfo | string,
text: Callback | string = "Image",
callback?: Callback,
): Promise<ISendEventResponse> {
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = text as Callback;
text = info as string || "Image";
info = url as IImageInfo;
url = threadId as string;
threadId = null;
}
if (utils.isFunction(text)) {
callback = text as any as Callback; // legacy
text = undefined;
@ -3771,15 +3947,16 @@ export class MatrixClient extends EventEmitter {
info: info,
body: text,
};
return this.sendMessage(roomId, content, undefined, callback);
return this.sendMessage(roomId, threadId, content, undefined, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} url
* @param {Object} info
* @param {string} text
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to a ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3787,9 +3964,32 @@ export class MatrixClient extends EventEmitter {
roomId: string,
url: string,
info?: IImageInfo,
text = "Sticker",
text?: string,
callback?: Callback,
);
public sendStickerMessage(
roomId: string,
threadId: string | null,
url: string,
info?: IImageInfo,
text?: string,
callback?: Callback,
);
public sendStickerMessage(
roomId: string,
threadId: string | null,
url: string | IImageInfo,
info?: IImageInfo | string,
text: Callback | string = "Sticker",
callback?: Callback,
): Promise<ISendEventResponse> {
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = text as Callback;
text = info as string || "Sticker";
info = url as IImageInfo;
url = threadId as string;
threadId = null;
}
if (utils.isFunction(text)) {
callback = text as any as Callback; // legacy
text = undefined;
@ -3799,14 +3999,15 @@ export class MatrixClient extends EventEmitter {
info: info,
body: text,
};
return this.sendEvent(roomId, EventType.Sticker, content, undefined, callback);
return this.sendEvent(roomId, threadId, EventType.Sticker, content, undefined, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} body
* @param {string} htmlBody
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to a ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3815,16 +4016,36 @@ export class MatrixClient extends EventEmitter {
body: string,
htmlBody: string,
callback?: Callback,
);
public sendHtmlMessage(
roomId: string,
threadId: string | null,
body: string,
htmlBody: string,
callback?: Callback,
)
public sendHtmlMessage(
roomId: string,
threadId: string | null,
body: string,
htmlBody: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
const content = ContentHelpers.makeHtmlMessage(body, htmlBody);
return this.sendMessage(roomId, content, undefined, callback);
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = htmlBody as Callback;
htmlBody = body as string;
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeHtmlMessage(body, htmlBody as string);
return this.sendMessage(roomId, threadId, content, undefined, callback);
}
/**
* @param {string} roomId
* @param {string} body
* @param {string} htmlBody
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to a ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3833,16 +4054,37 @@ export class MatrixClient extends EventEmitter {
body: string,
htmlBody: string,
callback?: Callback,
);
public sendHtmlNotice(
roomId: string,
threadId: string | null,
body: string,
htmlBody: string,
callback?: Callback,
)
public sendHtmlNotice(
roomId: string,
threadId: string | null,
body: string,
htmlBody: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
const content = ContentHelpers.makeHtmlNotice(body, htmlBody);
return this.sendMessage(roomId, content, undefined, callback);
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = htmlBody as Callback;
htmlBody = body as string;
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeHtmlNotice(body, htmlBody as string);
return this.sendMessage(roomId, threadId, content, undefined, callback);
}
/**
* @param {string} roomId
* @param {string} threadId
* @param {string} body
* @param {string} htmlBody
* @param {module:client.callback} callback Optional.
* @param {module:client.callback} callback Optional. Deprecated
* @return {Promise} Resolves: to a ISendEventResponse object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
@ -3851,9 +4093,29 @@ export class MatrixClient extends EventEmitter {
body: string,
htmlBody: string,
callback?: Callback,
);
public sendHtmlEmote(
roomId: string,
threadId: string | null,
body: string,
htmlBody: string,
callback?: Callback,
)
public sendHtmlEmote(
roomId: string,
threadId: string | null,
body: string,
htmlBody: string | Callback,
callback?: Callback,
): Promise<ISendEventResponse> {
const content = ContentHelpers.makeHtmlEmote(body, htmlBody);
return this.sendMessage(roomId, content, undefined, callback);
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
callback = htmlBody as Callback;
htmlBody = body as string;
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeHtmlEmote(body, htmlBody as string);
return this.sendMessage(roomId, threadId, content, undefined, callback);
}
/**

View File

@ -28,6 +28,7 @@ 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;
@ -153,18 +154,18 @@ export class EventTimelineSet extends EventEmitter {
*
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
*/
public getPendingEvents(): MatrixEvent[] {
public getPendingEvents(thread?: Thread): MatrixEvent[] {
if (!this.room || !this.displayPendingEvents) {
return [];
}
const pendingEvents = this.room.getPendingEvents(thread);
if (this.filter) {
return this.filter.filterRoomTimeline(this.room.getPendingEvents());
return this.filter.filterRoomTimeline(pendingEvents);
} else {
return this.room.getPendingEvents();
return pendingEvents;
}
}
/**
* Get the live timeline for this room.
*

View File

@ -225,12 +225,6 @@ export class Room extends EventEmitter {
this.reEmitter = new ReEmitter(this);
opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological;
if (["chronological", "detached"].indexOf(opts.pendingEventOrdering) === -1) {
throw new Error(
"opts.pendingEventOrdering MUST be either 'chronological' or " +
"'detached'. Got: '" + opts.pendingEventOrdering + "'",
);
}
this.name = roomId;
@ -241,7 +235,7 @@ export class Room extends EventEmitter {
this.fixUpLegacyTimelineFields();
if (this.opts.pendingEventOrdering == "detached") {
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
this.pendingEventList = [];
const serializedPendingEventList = client.sessionStore.store.getItem(pendingEventsKey(this.roomId));
if (serializedPendingEventList) {
@ -452,14 +446,16 @@ export class Room extends EventEmitter {
*
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
*/
public getPendingEvents(): MatrixEvent[] {
if (this.opts.pendingEventOrdering !== "detached") {
public getPendingEvents(thread?: Thread): MatrixEvent[] {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
throw new Error(
"Cannot call getPendingEvents with pendingEventOrdering == " +
this.opts.pendingEventOrdering);
}
return this.pendingEventList;
return this.pendingEventList.filter(event => {
return !thread || thread.id === event.threadRootId;
});
}
/**
@ -469,7 +465,7 @@ export class Room extends EventEmitter {
* @return {boolean} True if an element was removed.
*/
public removePendingEvent(eventId: string): boolean {
if (this.opts.pendingEventOrdering !== "detached") {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
throw new Error(
"Cannot call removePendingEvent with pendingEventOrdering == " +
this.opts.pendingEventOrdering);
@ -495,7 +491,7 @@ export class Room extends EventEmitter {
* @return {boolean}
*/
public hasPendingEvent(eventId: string): boolean {
if (this.opts.pendingEventOrdering !== "detached") {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
return false;
}
@ -509,7 +505,7 @@ export class Room extends EventEmitter {
* @return {MatrixEvent}
*/
public getPendingEvent(eventId: string): MatrixEvent | null {
if (this.opts.pendingEventOrdering !== "detached") {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
return null;
}
@ -856,8 +852,14 @@ export class Room extends EventEmitter {
* the given event, or null if unknown
*/
public getTimelineForEvent(eventId: string): EventTimeline {
const event = this.findEventById(eventId);
const thread = this.findThreadForEvent(event);
if (thread) {
return thread.timelineSet.getLiveTimeline();
} else {
return this.getUnfilteredTimelineSet().getTimelineForEvent(eventId);
}
}
/**
* Add a new timeline to this room's unfiltered timeline set
@ -1403,13 +1405,6 @@ export class Room extends EventEmitter {
* unique transaction id.
*/
public addPendingEvent(event: MatrixEvent, txnId: string): void {
// TODO: Enable "pending events" for threads
// There's a fair few things to update to make them work with Threads
// Will get back to it when the plan is to build a more polished UI ready for production
if (this.client?.supportsExperimentalThreads() && event.threadRootId) {
return;
}
if (event.status !== EventStatus.SENDING && event.status !== EventStatus.NOT_SENT) {
throw new Error("addPendingEvent called on an event with status " +
event.status);
@ -1426,8 +1421,8 @@ export class Room extends EventEmitter {
EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS), false);
this.txnToEvent[txnId] = event;
if (this.opts.pendingEventOrdering == "detached") {
const thread = this.threads.get(event.threadRootId);
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached && !thread) {
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);
@ -1446,13 +1441,17 @@ export class Room extends EventEmitter {
let redactedEvent = this.pendingEventList &&
this.pendingEventList.find(e => e.getId() === redactId);
if (!redactedEvent) {
redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
redactedEvent = this.findEventById(redactId);
}
if (redactedEvent) {
redactedEvent.markLocallyRedacted(event);
this.emit("Room.redaction", event, this);
}
}
} else {
if (thread) {
thread.timelineSet.addEventToTimeline(event,
thread.timelineSet.getLiveTimeline(), false);
} else {
for (let i = 0; i < this.timelineSets.length; i++) {
const timelineSet = this.timelineSets[i];
@ -1467,6 +1466,7 @@ export class Room extends EventEmitter {
}
}
}
}
this.emit("Room.localEchoUpdated", event, this, null, null);
}
@ -1521,6 +1521,10 @@ export class Room extends EventEmitter {
* @param {module:models/event.MatrixEvent} event the relation event that needs to be aggregated.
*/
private aggregateNonLiveRelation(event: MatrixEvent): void {
const thread = this.findThreadForEvent(event);
if (thread) {
thread.timelineSet.aggregateRelations(event);
} else {
// 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++) {
@ -1534,6 +1538,7 @@ export class Room extends EventEmitter {
}
}
}
}
/**
* Deal with the echo of a message we sent.
@ -1571,12 +1576,17 @@ export class Room extends EventEmitter {
// any, which is good, because we don't want to try decoding it again).
localEvent.handleRemoteEcho(remoteEvent.event);
const thread = this.threads.get(remoteEvent.threadRootId);
if (thread) {
thread.timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId);
} else {
for (let i = 0; i < this.timelineSets.length; i++) {
const timelineSet = this.timelineSets[i];
// if it's already in the timeline, update the timeline map. If it's not, add it.
timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId);
}
}
this.emit("Room.localEchoUpdated", localEvent, this,
oldEventId, oldStatus);
@ -1608,7 +1618,7 @@ export class Room extends EventEmitter {
// SENT races against /sync, so we have to special-case it.
if (newStatus == EventStatus.SENT) {
const timeline = this.getUnfilteredTimelineSet().eventIdToTimeline(newEventId);
const timeline = this.getTimelineForEvent(newEventId);
if (timeline) {
// we've already received the event via the event stream.
// nothing more to do here.
@ -1636,12 +1646,17 @@ export class Room extends EventEmitter {
// update the event id
event.replaceLocalEventId(newEventId);
const thread = this.findThreadForEvent(event);
if (thread) {
thread.timelineSet.replaceEventId(oldEventId, newEventId);
} else {
// if the event was already in the timeline (which will be the case if
// opts.pendingEventOrdering==chronological), we need to update the
// timeline map.
for (let i = 0; i < this.timelineSets.length; i++) {
this.timelineSets[i].replaceEventId(oldEventId, newEventId);
}
}
} else if (newStatus == EventStatus.CANCELLED) {
// remove it from the pending event list, or the timeline.
if (this.pendingEventList) {

View File

@ -55,11 +55,20 @@ export class Thread extends TypedEventEmitter<ThreadEvent> {
this.timelineSet = new EventTimelineSet(this.room, {
unstableClientRelationAggregation: true,
timelineSupport: true,
pendingEvents: false,
pendingEvents: true,
});
events.forEach(event => this.addEvent(event));
room.on("Room.localEchoUpdated", this.onEcho);
room.on("Room.timeline", this.onEcho);
}
onEcho = (event: MatrixEvent) => {
if (this.timelineSet.eventIdToTimeline(event.getId())) {
this.emit(ThreadEvent.Update, this);
}
};
/**
* Add an event to the thread and updates
* the tail/root references if needed