You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Fix issues around echo & redaction handling in threads (#2286)
This commit is contained in:
committed by
GitHub
parent
5937e6a6a8
commit
286500e335
@@ -8,6 +8,7 @@ import { logger } from '../../src/logger';
|
|||||||
import { IContent, IEvent, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event";
|
import { IContent, IEvent, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event";
|
||||||
import { ClientEvent, EventType, MatrixClient } from "../../src";
|
import { ClientEvent, EventType, MatrixClient } from "../../src";
|
||||||
import { SyncState } from "../../src/sync";
|
import { SyncState } from "../../src/sync";
|
||||||
|
import { eventMapperFor } from "../../src/event-mapper";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a promise that is resolved when the client next emits a
|
* Return a promise that is resolved when the client next emits a
|
||||||
@@ -79,6 +80,7 @@ interface IEventOpts {
|
|||||||
redacts?: string;
|
redacts?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let testEventIndex = 1; // counter for events, easier for comparison of randomly generated events
|
||||||
/**
|
/**
|
||||||
* Create an Event.
|
* Create an Event.
|
||||||
* @param {Object} opts Values for the event.
|
* @param {Object} opts Values for the event.
|
||||||
@@ -88,9 +90,10 @@ interface IEventOpts {
|
|||||||
* @param {string} opts.skey Optional. The state key (auto inserts empty string)
|
* @param {string} opts.skey Optional. The state key (auto inserts empty string)
|
||||||
* @param {Object} opts.content The event.content
|
* @param {Object} opts.content The event.content
|
||||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||||
|
* @param {MatrixClient} client If passed along with opts.event=true will be used to set up re-emitters.
|
||||||
* @return {Object} a JSON object representing this event.
|
* @return {Object} a JSON object representing this event.
|
||||||
*/
|
*/
|
||||||
export function mkEvent(opts: IEventOpts): object | MatrixEvent {
|
export function mkEvent(opts: IEventOpts, client?: MatrixClient): object | MatrixEvent {
|
||||||
if (!opts.type || !opts.content) {
|
if (!opts.type || !opts.content) {
|
||||||
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
|
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
|
||||||
}
|
}
|
||||||
@@ -100,7 +103,7 @@ export function mkEvent(opts: IEventOpts): object | MatrixEvent {
|
|||||||
sender: opts.sender || opts.user, // opts.user for backwards-compat
|
sender: opts.sender || opts.user, // opts.user for backwards-compat
|
||||||
content: opts.content,
|
content: opts.content,
|
||||||
unsigned: opts.unsigned || {},
|
unsigned: opts.unsigned || {},
|
||||||
event_id: "$" + Math.random() + "-" + Math.random(),
|
event_id: "$" + testEventIndex++ + "-" + Math.random() + "-" + Math.random(),
|
||||||
txn_id: "~" + Math.random(),
|
txn_id: "~" + Math.random(),
|
||||||
redacts: opts.redacts,
|
redacts: opts.redacts,
|
||||||
};
|
};
|
||||||
@@ -117,6 +120,11 @@ export function mkEvent(opts: IEventOpts): object | MatrixEvent {
|
|||||||
].includes(opts.type)) {
|
].includes(opts.type)) {
|
||||||
event.state_key = "";
|
event.state_key = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.event && client) {
|
||||||
|
return eventMapperFor(client, {})(event);
|
||||||
|
}
|
||||||
|
|
||||||
return opts.event ? new MatrixEvent(event) : event;
|
return opts.event ? new MatrixEvent(event) : event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,9 +217,10 @@ interface IMessageOpts {
|
|||||||
* @param {string} opts.user The user ID for the event.
|
* @param {string} opts.user The user ID for the event.
|
||||||
* @param {string} opts.msg Optional. The content.body for the event.
|
* @param {string} opts.msg Optional. The content.body for the event.
|
||||||
* @param {boolean} opts.event True to make a MatrixEvent.
|
* @param {boolean} opts.event True to make a MatrixEvent.
|
||||||
|
* @param {MatrixClient} client If passed along with opts.event=true will be used to set up re-emitters.
|
||||||
* @return {Object|MatrixEvent} The event
|
* @return {Object|MatrixEvent} The event
|
||||||
*/
|
*/
|
||||||
export function mkMessage(opts: IMessageOpts): object | MatrixEvent {
|
export function mkMessage(opts: IMessageOpts, client?: MatrixClient): object | MatrixEvent {
|
||||||
const eventOpts: IEventOpts = {
|
const eventOpts: IEventOpts = {
|
||||||
...opts,
|
...opts,
|
||||||
type: EventType.RoomMessage,
|
type: EventType.RoomMessage,
|
||||||
@@ -224,7 +233,7 @@ export function mkMessage(opts: IMessageOpts): object | MatrixEvent {
|
|||||||
if (!eventOpts.content.body) {
|
if (!eventOpts.content.body) {
|
||||||
eventOpts.content.body = "Random->" + Math.random();
|
eventOpts.content.body = "Random->" + Math.random();
|
||||||
}
|
}
|
||||||
return mkEvent(eventOpts);
|
return mkEvent(eventOpts, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -50,7 +50,7 @@ describe("Room", function() {
|
|||||||
event: true,
|
event: true,
|
||||||
user: userA,
|
user: userA,
|
||||||
room: roomId,
|
room: roomId,
|
||||||
}) as MatrixEvent;
|
}, room.client) as MatrixEvent;
|
||||||
|
|
||||||
const mkReply = (target: MatrixEvent) => utils.mkEvent({
|
const mkReply = (target: MatrixEvent) => utils.mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
@@ -65,7 +65,7 @@ describe("Room", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) as MatrixEvent;
|
}, room.client) as MatrixEvent;
|
||||||
|
|
||||||
const mkEdit = (target: MatrixEvent, salt = Math.random()) => utils.mkEvent({
|
const mkEdit = (target: MatrixEvent, salt = Math.random()) => utils.mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
@@ -82,7 +82,7 @@ describe("Room", function() {
|
|||||||
event_id: target.getId(),
|
event_id: target.getId(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) as MatrixEvent;
|
}, room.client) as MatrixEvent;
|
||||||
|
|
||||||
const mkThreadResponse = (root: MatrixEvent) => utils.mkEvent({
|
const mkThreadResponse = (root: MatrixEvent) => utils.mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
@@ -99,7 +99,7 @@ describe("Room", function() {
|
|||||||
"rel_type": "m.thread",
|
"rel_type": "m.thread",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) as MatrixEvent;
|
}, room.client) as MatrixEvent;
|
||||||
|
|
||||||
const mkReaction = (target: MatrixEvent) => utils.mkEvent({
|
const mkReaction = (target: MatrixEvent) => utils.mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
@@ -113,7 +113,7 @@ describe("Room", function() {
|
|||||||
"key": Math.random().toString(),
|
"key": Math.random().toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}) as MatrixEvent;
|
}, room.client) as MatrixEvent;
|
||||||
|
|
||||||
const mkRedaction = (target: MatrixEvent) => utils.mkEvent({
|
const mkRedaction = (target: MatrixEvent) => utils.mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
@@ -122,7 +122,7 @@ describe("Room", function() {
|
|||||||
room: roomId,
|
room: roomId,
|
||||||
redacts: target.getId(),
|
redacts: target.getId(),
|
||||||
content: {},
|
content: {},
|
||||||
}) as MatrixEvent;
|
}, room.client) as MatrixEvent;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
room = new Room(roomId, new TestClient(userA, "device").client, userA);
|
room = new Room(roomId, new TestClient(userA, "device").client, userA);
|
||||||
@@ -1899,6 +1899,7 @@ describe("Room", function() {
|
|||||||
"@alice:example.com", "alicedevice",
|
"@alice:example.com", "alicedevice",
|
||||||
)).client;
|
)).client;
|
||||||
room = new Room(roomId, client, userA);
|
room = new Room(roomId, client, userA);
|
||||||
|
client.getRoom = () => room;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allow create threads without a root event", function() {
|
it("allow create threads without a root event", function() {
|
||||||
@@ -1938,11 +1939,7 @@ describe("Room", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Edits update the lastReply event", async () => {
|
it("Edits update the lastReply event", async () => {
|
||||||
const client = (new TestClient(
|
room.client.supportsExperimentalThreads = () => true;
|
||||||
"@alice:example.com", "alicedevice",
|
|
||||||
)).client;
|
|
||||||
client.supportsExperimentalThreads = () => true;
|
|
||||||
room = new Room(roomId, client, userA);
|
|
||||||
|
|
||||||
const randomMessage = mkMessage();
|
const randomMessage = mkMessage();
|
||||||
const threadRoot = mkMessage();
|
const threadRoot = mkMessage();
|
||||||
@@ -1951,7 +1948,7 @@ describe("Room", function() {
|
|||||||
const threadResponseEdit = mkEdit(threadResponse);
|
const threadResponseEdit = mkEdit(threadResponse);
|
||||||
threadResponseEdit.localTimestamp += 2000;
|
threadResponseEdit.localTimestamp += 2000;
|
||||||
|
|
||||||
client.fetchRoomEvent = (eventId: string) => Promise.resolve({
|
room.client.fetchRoomEvent = (eventId: string) => Promise.resolve({
|
||||||
...threadRoot.event,
|
...threadRoot.event,
|
||||||
unsigned: {
|
unsigned: {
|
||||||
"age": 123,
|
"age": 123,
|
||||||
@@ -1975,6 +1972,121 @@ describe("Room", function() {
|
|||||||
await emitPromise(thread, ThreadEvent.Update);
|
await emitPromise(thread, ThreadEvent.Update);
|
||||||
expect(thread.replyToEvent.getContent().body).toBe(threadResponseEdit.getContent()["m.new_content"].body);
|
expect(thread.replyToEvent.getContent().body).toBe(threadResponseEdit.getContent()["m.new_content"].body);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Redactions to thread responses decrement the length", async () => {
|
||||||
|
room.client.supportsExperimentalThreads = () => true;
|
||||||
|
|
||||||
|
const threadRoot = mkMessage();
|
||||||
|
const threadResponse1 = mkThreadResponse(threadRoot);
|
||||||
|
threadResponse1.localTimestamp += 1000;
|
||||||
|
const threadResponse2 = mkThreadResponse(threadRoot);
|
||||||
|
threadResponse2.localTimestamp += 2000;
|
||||||
|
|
||||||
|
room.client.fetchRoomEvent = (eventId: string) => Promise.resolve({
|
||||||
|
...threadRoot.event,
|
||||||
|
unsigned: {
|
||||||
|
"age": 123,
|
||||||
|
"m.relations": {
|
||||||
|
"m.thread": {
|
||||||
|
latest_event: threadResponse2.event,
|
||||||
|
count: 2,
|
||||||
|
current_user_participated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
room.addLiveEvents([threadRoot, threadResponse1, threadResponse2]);
|
||||||
|
const thread = await emitPromise(room, ThreadEvent.New);
|
||||||
|
|
||||||
|
expect(thread).toHaveLength(2);
|
||||||
|
expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId());
|
||||||
|
|
||||||
|
const threadResponse1Redaction = mkRedaction(threadResponse1);
|
||||||
|
room.addLiveEvents([threadResponse1Redaction]);
|
||||||
|
await emitPromise(thread, ThreadEvent.Update);
|
||||||
|
expect(thread).toHaveLength(1);
|
||||||
|
expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Redactions to reactions in threads do not decrement the length", async () => {
|
||||||
|
room.client.supportsExperimentalThreads = () => true;
|
||||||
|
|
||||||
|
const threadRoot = mkMessage();
|
||||||
|
const threadResponse1 = mkThreadResponse(threadRoot);
|
||||||
|
threadResponse1.localTimestamp += 1000;
|
||||||
|
const threadResponse2 = mkThreadResponse(threadRoot);
|
||||||
|
threadResponse2.localTimestamp += 2000;
|
||||||
|
const threadResponse2Reaction = mkReaction(threadResponse2);
|
||||||
|
|
||||||
|
room.client.fetchRoomEvent = (eventId: string) => Promise.resolve({
|
||||||
|
...threadRoot.event,
|
||||||
|
unsigned: {
|
||||||
|
"age": 123,
|
||||||
|
"m.relations": {
|
||||||
|
"m.thread": {
|
||||||
|
latest_event: threadResponse2.event,
|
||||||
|
count: 2,
|
||||||
|
current_user_participated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
room.addLiveEvents([threadRoot, threadResponse1, threadResponse2, threadResponse2Reaction]);
|
||||||
|
const thread = await emitPromise(room, ThreadEvent.New);
|
||||||
|
|
||||||
|
expect(thread).toHaveLength(2);
|
||||||
|
expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId());
|
||||||
|
|
||||||
|
const threadResponse2ReactionRedaction = mkRedaction(threadResponse2Reaction);
|
||||||
|
room.addLiveEvents([threadResponse2ReactionRedaction]);
|
||||||
|
await emitPromise(thread, ThreadEvent.Update);
|
||||||
|
expect(thread).toHaveLength(2);
|
||||||
|
expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Redacting the lastEvent finds a new lastEvent", async () => {
|
||||||
|
room.client.supportsExperimentalThreads = () => true;
|
||||||
|
|
||||||
|
const threadRoot = mkMessage();
|
||||||
|
const threadResponse1 = mkThreadResponse(threadRoot);
|
||||||
|
threadResponse1.localTimestamp += 1000;
|
||||||
|
const threadResponse2 = mkThreadResponse(threadRoot);
|
||||||
|
threadResponse2.localTimestamp += 2000;
|
||||||
|
|
||||||
|
room.client.fetchRoomEvent = (eventId: string) => Promise.resolve({
|
||||||
|
...threadRoot.event,
|
||||||
|
unsigned: {
|
||||||
|
"age": 123,
|
||||||
|
"m.relations": {
|
||||||
|
"m.thread": {
|
||||||
|
latest_event: threadResponse2.event,
|
||||||
|
count: 2,
|
||||||
|
current_user_participated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
room.addLiveEvents([threadRoot, threadResponse1, threadResponse2]);
|
||||||
|
const thread = await emitPromise(room, ThreadEvent.New);
|
||||||
|
|
||||||
|
expect(thread).toHaveLength(2);
|
||||||
|
expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId());
|
||||||
|
|
||||||
|
const threadResponse2Redaction = mkRedaction(threadResponse2);
|
||||||
|
room.addLiveEvents([threadResponse2Redaction]);
|
||||||
|
await emitPromise(thread, ThreadEvent.Update);
|
||||||
|
expect(thread).toHaveLength(1);
|
||||||
|
expect(thread.replyToEvent.getId()).toBe(threadResponse1.getId());
|
||||||
|
|
||||||
|
const threadResponse1Redaction = mkRedaction(threadResponse1);
|
||||||
|
room.addLiveEvents([threadResponse1Redaction]);
|
||||||
|
await emitPromise(thread, ThreadEvent.Update);
|
||||||
|
expect(thread).toHaveLength(0);
|
||||||
|
expect(thread.replyToEvent.getId()).toBe(threadRoot.getId());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("eventShouldLiveIn", () => {
|
describe("eventShouldLiveIn", () => {
|
||||||
|
@@ -66,6 +66,9 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
|
|||||||
MatrixEventEvent.Replaced,
|
MatrixEventEvent.Replaced,
|
||||||
MatrixEventEvent.VisibilityChange,
|
MatrixEventEvent.VisibilityChange,
|
||||||
]);
|
]);
|
||||||
|
room?.reEmitter.reEmit(event, [
|
||||||
|
MatrixEventEvent.BeforeRedaction,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ import { Direction, EventTimeline } from "./event-timeline";
|
|||||||
import { getHttpUriForMxc } from "../content-repo";
|
import { getHttpUriForMxc } from "../content-repo";
|
||||||
import * as utils from "../utils";
|
import * as utils from "../utils";
|
||||||
import { defer, normalize } from "../utils";
|
import { defer, normalize } from "../utils";
|
||||||
import { IEvent, IThreadBundledRelationship, MatrixEvent } from "./event";
|
import { IEvent, IThreadBundledRelationship, MatrixEvent, MatrixEventEvent, MatrixEventHandlerMap } from "./event";
|
||||||
import { EventStatus } from "./event-status";
|
import { EventStatus } from "./event-status";
|
||||||
import { RoomMember } from "./room-member";
|
import { RoomMember } from "./room-member";
|
||||||
import { IRoomSummary, RoomSummary } from "./room-summary";
|
import { IRoomSummary, RoomSummary } from "./room-summary";
|
||||||
@@ -171,7 +171,8 @@ type EmittedEvents = RoomEvent
|
|||||||
| ThreadEvent.Update
|
| ThreadEvent.Update
|
||||||
| ThreadEvent.NewReply
|
| ThreadEvent.NewReply
|
||||||
| RoomEvent.Timeline
|
| RoomEvent.Timeline
|
||||||
| RoomEvent.TimelineReset;
|
| RoomEvent.TimelineReset
|
||||||
|
| MatrixEventEvent.BeforeRedaction;
|
||||||
|
|
||||||
export type RoomEventHandlerMap = {
|
export type RoomEventHandlerMap = {
|
||||||
[RoomEvent.MyMembership]: (room: Room, membership: string, prevMembership?: string) => void;
|
[RoomEvent.MyMembership]: (room: Room, membership: string, prevMembership?: string) => void;
|
||||||
@@ -188,10 +189,10 @@ export type RoomEventHandlerMap = {
|
|||||||
oldStatus?: EventStatus,
|
oldStatus?: EventStatus,
|
||||||
) => void;
|
) => void;
|
||||||
[ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;
|
[ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;
|
||||||
} & ThreadHandlerMap;
|
} & ThreadHandlerMap & MatrixEventHandlerMap;
|
||||||
|
|
||||||
export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap> {
|
export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap> {
|
||||||
private readonly reEmitter: TypedReEmitter<EmittedEvents, RoomEventHandlerMap>;
|
public readonly reEmitter: TypedReEmitter<EmittedEvents, RoomEventHandlerMap>;
|
||||||
private txnToEvent: Record<string, MatrixEvent> = {}; // Pending in-flight requests { string: MatrixEvent }
|
private txnToEvent: Record<string, MatrixEvent> = {}; // Pending in-flight requests { string: MatrixEvent }
|
||||||
// receipts should clobber based on receipt_type and user_id pairs hence
|
// receipts should clobber based on receipt_type and user_id pairs hence
|
||||||
// the form of this structure. This is sub-optimal for the exposed APIs
|
// the form of this structure. This is sub-optimal for the exposed APIs
|
||||||
@@ -383,7 +384,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
|||||||
return this.threadTimelineSetsPromise;
|
return this.threadTimelineSetsPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.client?.supportsExperimentalThreads) {
|
if (this.client?.supportsExperimentalThreads()) {
|
||||||
try {
|
try {
|
||||||
this.threadTimelineSetsPromise = Promise.all([
|
this.threadTimelineSetsPromise = Promise.all([
|
||||||
this.createThreadTimelineSet(),
|
this.createThreadTimelineSet(),
|
||||||
@@ -1676,6 +1677,8 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
|||||||
thread = await this.threadPromises.get(threadId);
|
thread = await this.threadPromises.get(threadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
events = events.filter(e => e.getId() !== threadId); // filter out any root events
|
||||||
|
|
||||||
if (thread) {
|
if (thread) {
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
await thread.addEvent(event, toStartOfTimeline);
|
await thread.addEvent(event, toStartOfTimeline);
|
||||||
@@ -1810,7 +1813,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private processLiveEvent(event: MatrixEvent): Promise<void> {
|
private processLiveEvent(event: MatrixEvent): void {
|
||||||
this.applyRedaction(event);
|
this.applyRedaction(event);
|
||||||
|
|
||||||
// Implement MSC3531: hiding messages.
|
// Implement MSC3531: hiding messages.
|
||||||
@@ -1827,7 +1830,6 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
|||||||
if (existingEvent) {
|
if (existingEvent) {
|
||||||
// remote echo of an event we sent earlier
|
// remote echo of an event we sent earlier
|
||||||
this.handleRemoteEcho(event, existingEvent);
|
this.handleRemoteEcho(event, existingEvent);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient, RelationType, RoomEvent } from "../matrix";
|
import { MatrixClient, MatrixEventEvent, RelationType, RoomEvent } from "../matrix";
|
||||||
import { TypedReEmitter } from "../ReEmitter";
|
import { TypedReEmitter } from "../ReEmitter";
|
||||||
import { IRelationsRequestOpts } from "../@types/requests";
|
import { IRelationsRequestOpts } from "../@types/requests";
|
||||||
import { IThreadBundledRelationship, MatrixEvent } from "./event";
|
import { IThreadBundledRelationship, MatrixEvent } from "./event";
|
||||||
@@ -94,6 +94,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
RoomEvent.TimelineReset,
|
RoomEvent.TimelineReset,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.room.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
|
||||||
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
|
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
|
||||||
this.timelineSet.on(RoomEvent.Timeline, this.onEcho);
|
this.timelineSet.on(RoomEvent.Timeline, this.onEcho);
|
||||||
|
|
||||||
@@ -114,7 +115,29 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onBeforeRedaction = (event: MatrixEvent) => {
|
||||||
|
if (event?.isRelation(THREAD_RELATION_TYPE.name) &&
|
||||||
|
this.room.eventShouldLiveIn(event).threadId === this.id
|
||||||
|
) {
|
||||||
|
this.replyCount--;
|
||||||
|
this.emit(ThreadEvent.Update, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.lastEvent?.getId() === event.getId()) {
|
||||||
|
const events = [...this.timelineSet.getLiveTimeline().getEvents()].reverse();
|
||||||
|
this.lastEvent = events.find(e => (
|
||||||
|
!e.isRedacted() &&
|
||||||
|
e.getId() !== event.getId() &&
|
||||||
|
e.isRelation(THREAD_RELATION_TYPE.name)
|
||||||
|
)) ?? this.rootEvent;
|
||||||
|
this.emit(ThreadEvent.NewReply, this, this.lastEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private onEcho = (event: MatrixEvent) => {
|
private onEcho = (event: MatrixEvent) => {
|
||||||
|
if (event.threadRootId !== this.id) return; // ignore echoes for other timelines
|
||||||
|
if (this.lastEvent === event) return;
|
||||||
|
|
||||||
// There is a risk that the `localTimestamp` approximation will not be accurate
|
// There is a risk that the `localTimestamp` approximation will not be accurate
|
||||||
// when threads are used over federation. That could result in the reply
|
// when threads are used over federation. That could result in the reply
|
||||||
// count value drifting away from the value returned by the server
|
// count value drifting away from the value returned by the server
|
||||||
@@ -135,9 +158,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.timelineSet.eventIdToTimeline(event.getId())) {
|
this.emit(ThreadEvent.Update, this);
|
||||||
this.emit(ThreadEvent.Update, this);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public get roomState(): RoomState {
|
public get roomState(): RoomState {
|
||||||
@@ -188,10 +209,9 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
|||||||
this._currentUserParticipated = true;
|
this._currentUserParticipated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isThreadReply = event.isRelation(THREAD_RELATION_TYPE.name);
|
|
||||||
// If no thread support exists we want to count all thread relation
|
// If no thread support exists we want to count all thread relation
|
||||||
// added as a reply. We can't rely on the bundled relationships count
|
// added as a reply. We can't rely on the bundled relationships count
|
||||||
if (!Thread.hasServerSideSupport && isThreadReply) {
|
if (!Thread.hasServerSideSupport && event.isRelation(THREAD_RELATION_TYPE.name)) {
|
||||||
this.replyCount++;
|
this.replyCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user