Skip to content

Commit b94d137

Browse files
committed
Move redacted messages out of any thread, into main timeline.
For consistency with the spec at room version 11. See matrix-org/matrix-spec-proposals#3389 for a proposal to make this unnecessary.
1 parent 5595e84 commit b94d137

File tree

5 files changed

+141
-7
lines changed

5 files changed

+141
-7
lines changed

spec/unit/models/event.spec.ts

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,19 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { MockedObject } from "jest-mock";
18+
1719
import { MatrixEvent, MatrixEventEvent } from "../../../src/models/event";
1820
import { emitPromise } from "../../test-utils/test-utils";
1921
import { Crypto, IEventDecryptionResult } from "../../../src/crypto";
20-
import { IAnnotatedPushRule, PushRuleActionName, TweakName } from "../../../src";
22+
import {
23+
IAnnotatedPushRule,
24+
MatrixClient,
25+
PushRuleActionName,
26+
Room,
27+
THREAD_RELATION_TYPE,
28+
TweakName,
29+
} from "../../../src";
2130

2231
describe("MatrixEvent", () => {
2332
it("should create copies of itself", () => {
@@ -77,17 +86,98 @@ describe("MatrixEvent", () => {
7786
expect(ev.getWireContent().body).toBeUndefined();
7887
expect(ev.getWireContent().ciphertext).toBe("xyz");
7988

89+
const mockClient = {} as unknown as MockedObject<MatrixClient>;
90+
const room = new Room("!roomid:e.xyz", mockClient, "myname");
8091
const redaction = new MatrixEvent({
8192
type: "m.room.redaction",
8293
redacts: ev.getId(),
8394
});
8495

85-
ev.makeRedacted(redaction);
96+
ev.makeRedacted(redaction, room);
8697
expect(ev.getContent().body).toBeUndefined();
8798
expect(ev.getWireContent().body).toBeUndefined();
8899
expect(ev.getWireContent().ciphertext).toBeUndefined();
89100
});
90101

102+
it("should remain in the main timeline when redacted", async () => {
103+
// Given an event in the main timeline
104+
const mockClient = {
105+
supportsThreads: jest.fn().mockReturnValue(true),
106+
decryptEventIfNeeded: jest.fn().mockReturnThis(),
107+
getUserId: jest.fn().mockReturnValue("@user:server"),
108+
} as unknown as MockedObject<MatrixClient>;
109+
const room = new Room("!roomid:e.xyz", mockClient, "myname");
110+
const ev = new MatrixEvent({
111+
type: "m.room.message",
112+
content: {
113+
body: "Test",
114+
},
115+
event_id: "$event1:server",
116+
});
117+
118+
await room.addLiveEvents([ev]);
119+
await room.createThreadsTimelineSets();
120+
expect(ev.threadRootId).toBeUndefined();
121+
expect(mainTimelineLiveEventIds(room)).toEqual(["$event1:server"]);
122+
123+
// When I redact it
124+
const redaction = new MatrixEvent({
125+
type: "m.room.redaction",
126+
redacts: ev.getId(),
127+
});
128+
ev.makeRedacted(redaction, room);
129+
130+
// Then it remains in the main timeline
131+
expect(ev.threadRootId).toBeUndefined();
132+
expect(mainTimelineLiveEventIds(room)).toEqual(["$event1:server"]);
133+
});
134+
135+
it("should move into the main timeline when redacted", async () => {
136+
// Given an event in a thread
137+
const mockClient = {
138+
supportsThreads: jest.fn().mockReturnValue(true),
139+
decryptEventIfNeeded: jest.fn().mockReturnThis(),
140+
getUserId: jest.fn().mockReturnValue("@user:server"),
141+
} as unknown as MockedObject<MatrixClient>;
142+
const room = new Room("!roomid:e.xyz", mockClient, "myname");
143+
const threadRoot = new MatrixEvent({
144+
type: "m.room.message",
145+
content: {
146+
body: "threadRoot",
147+
},
148+
event_id: "$threadroot:server",
149+
});
150+
const ev = new MatrixEvent({
151+
type: "m.room.message",
152+
content: {
153+
"body": "Test",
154+
"m.relates_to": {
155+
rel_type: THREAD_RELATION_TYPE.name,
156+
event_id: "$threadroot:server",
157+
},
158+
},
159+
event_id: "$event1:server",
160+
});
161+
162+
await room.addLiveEvents([threadRoot, ev]);
163+
await room.createThreadsTimelineSets();
164+
expect(ev.threadRootId).toEqual("$threadroot:server");
165+
expect(mainTimelineLiveEventIds(room)).toEqual(["$threadroot:server"]);
166+
expect(threadLiveEventIds(room, 0)).toEqual(["$threadroot:server", "$event1:server"]);
167+
168+
// When I redact it
169+
const redaction = new MatrixEvent({
170+
type: "m.room.redaction",
171+
redacts: ev.getId(),
172+
});
173+
ev.makeRedacted(redaction, room);
174+
175+
// Then it disappears from the thread and appears in the main timeline
176+
expect(ev.threadRootId).toBeUndefined();
177+
expect(mainTimelineLiveEventIds(room)).toEqual(["$threadroot:server", "$event1:server"]);
178+
expect(threadLiveEventIds(room, 0)).not.toContain("$event1:server");
179+
});
180+
91181
describe("applyVisibilityEvent", () => {
92182
it("should emit VisibilityChange if a change was made", async () => {
93183
const ev = new MatrixEvent({
@@ -330,3 +420,19 @@ describe("MatrixEvent", () => {
330420
expect(stateEvent.threadRootId).toBeUndefined();
331421
});
332422
});
423+
424+
function mainTimelineLiveEventIds(room: Room): Array<string> {
425+
return room
426+
.getLiveTimeline()
427+
.getEvents()
428+
.map((e) => e.getId()!);
429+
}
430+
431+
function threadLiveEventIds(room: Room, threadIndex: number): Array<string> {
432+
return room
433+
.getThreads()
434+
[threadIndex].getUnfilteredTimelineSet()
435+
.getLiveTimeline()
436+
.getEvents()
437+
.map((e) => e.getId()!);
438+
}

spec/unit/room-state.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { M_BEACON } from "../../src/@types/beacon";
2727
import { MatrixClient } from "../../src/client";
2828
import { DecryptionError } from "../../src/crypto/algorithms";
2929
import { defer } from "../../src/utils";
30+
import { Room } from "../../src/models/room";
3031

3132
describe("RoomState", function () {
3233
const roomId = "!foo:bar";
@@ -362,9 +363,11 @@ describe("RoomState", function () {
362363
});
363364

364365
it("does not add redacted beacon info events to state", () => {
366+
const mockClient = {} as unknown as MockedObject<MatrixClient>;
365367
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
366368
const redactionEvent = new MatrixEvent({ type: "m.room.redaction" });
367-
redactedBeaconEvent.makeRedacted(redactionEvent);
369+
const room = new Room(roomId, mockClient, userA);
370+
redactedBeaconEvent.makeRedacted(redactionEvent, room);
368371
const emitSpy = jest.spyOn(state, "emit");
369372

370373
state.setStateEvents([redactedBeaconEvent]);
@@ -394,11 +397,13 @@ describe("RoomState", function () {
394397
});
395398

396399
it("destroys and removes redacted beacon events", () => {
400+
const mockClient = {} as unknown as MockedObject<MatrixClient>;
397401
const beaconId = "$beacon1";
398402
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
399403
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
400404
const redactionEvent = new MatrixEvent({ type: "m.room.redaction", redacts: beaconEvent.getId() });
401-
redactedBeaconEvent.makeRedacted(redactionEvent);
405+
const room = new Room(roomId, mockClient, userA);
406+
redactedBeaconEvent.makeRedacted(redactionEvent, room);
402407

403408
state.setStateEvents([beaconEvent]);
404409
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));

spec/unit/room.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3564,7 +3564,7 @@ describe("Room", function () {
35643564
expect(room.polls.get(pollStartEvent.getId()!)).toBeTruthy();
35653565

35663566
const redactedEvent = new MatrixEvent({ type: "m.room.redaction" });
3567-
pollStartEvent.makeRedacted(redactedEvent);
3567+
pollStartEvent.makeRedacted(redactedEvent, room);
35683568

35693569
await flushPromises();
35703570

src/models/event.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import { DecryptionError } from "../crypto/algorithms";
4545
import { CryptoBackend } from "../common-crypto/CryptoBackend";
4646
import { WITHHELD_MESSAGES } from "../crypto/OlmDevice";
4747
import { IAnnotatedPushRule } from "../@types/PushRules";
48+
import { Room } from "./room";
49+
import { EventTimeline } from "./event-timeline";
4850

4951
export { EventStatus } from "./event-status";
5052

@@ -1132,13 +1134,19 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
11321134
return this.visibility;
11331135
}
11341136

1137+
/**
1138+
* @deprecated In favor of the overload that includes a Room argument
1139+
*/
1140+
public makeRedacted(redactionEvent: MatrixEvent): void;
11351141
/**
11361142
* Update the content of an event in the same way it would be by the server
11371143
* if it were redacted before it was sent to us
11381144
*
11391145
* @param redactionEvent - event causing the redaction
1146+
* @param room - the room in which the event exists
11401147
*/
1141-
public makeRedacted(redactionEvent: MatrixEvent): void {
1148+
public makeRedacted(redactionEvent: MatrixEvent, room: Room): void;
1149+
public makeRedacted(redactionEvent: MatrixEvent, room?: Room): void {
11421150
// quick sanity-check
11431151
if (!redactionEvent.event) {
11441152
throw new Error("invalid redactionEvent in makeRedacted");
@@ -1182,6 +1190,21 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
11821190
}
11831191
}
11841192

1193+
// If the redacted event was in a thread
1194+
if (room && this.threadRootId && this.threadRootId !== this.getId()) {
1195+
// Remove it from its thread
1196+
this.thread?.timelineSet.removeEvent(this.getId()!);
1197+
this.setThread(undefined);
1198+
1199+
// And insert it into the main timeline
1200+
const timeline = room.getLiveTimeline();
1201+
// We use insertEventIntoTimeline to insert it in timestamp order,
1202+
// because we don't know where it should go (until we have MSC4033).
1203+
timeline
1204+
.getTimelineSet()
1205+
.insertEventIntoTimeline(this, timeline, timeline.getState(EventTimeline.FORWARDS)!);
1206+
}
1207+
11851208
this.invalidateExtensibleEvent();
11861209
}
11871210

src/models/room.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2329,7 +2329,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
23292329
// if we know about this event, redact its contents now.
23302330
const redactedEvent = redactId ? this.findEventById(redactId) : undefined;
23312331
if (redactedEvent) {
2332-
redactedEvent.makeRedacted(event);
2332+
redactedEvent.makeRedacted(event, this);
23332333

23342334
// If this is in the current state, replace it with the redacted version
23352335
if (redactedEvent.isState()) {

0 commit comments

Comments
 (0)