You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Merge pull request #3798 from matrix-org/andybalaam/move-redacted-message-to-main
Move redacted messages out of any thread, into main timeline.
This commit is contained in:
@ -14,10 +14,19 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MockedObject } from "jest-mock";
|
||||||
|
|
||||||
import { MatrixEvent, MatrixEventEvent } from "../../../src/models/event";
|
import { MatrixEvent, MatrixEventEvent } from "../../../src/models/event";
|
||||||
import { emitPromise } from "../../test-utils/test-utils";
|
import { emitPromise } from "../../test-utils/test-utils";
|
||||||
import { Crypto, IEventDecryptionResult } from "../../../src/crypto";
|
import { Crypto, IEventDecryptionResult } from "../../../src/crypto";
|
||||||
import { IAnnotatedPushRule, PushRuleActionName, TweakName } from "../../../src";
|
import {
|
||||||
|
IAnnotatedPushRule,
|
||||||
|
MatrixClient,
|
||||||
|
PushRuleActionName,
|
||||||
|
Room,
|
||||||
|
THREAD_RELATION_TYPE,
|
||||||
|
TweakName,
|
||||||
|
} from "../../../src";
|
||||||
|
|
||||||
describe("MatrixEvent", () => {
|
describe("MatrixEvent", () => {
|
||||||
it("should create copies of itself", () => {
|
it("should create copies of itself", () => {
|
||||||
@ -77,17 +86,98 @@ describe("MatrixEvent", () => {
|
|||||||
expect(ev.getWireContent().body).toBeUndefined();
|
expect(ev.getWireContent().body).toBeUndefined();
|
||||||
expect(ev.getWireContent().ciphertext).toBe("xyz");
|
expect(ev.getWireContent().ciphertext).toBe("xyz");
|
||||||
|
|
||||||
|
const mockClient = {} as unknown as MockedObject<MatrixClient>;
|
||||||
|
const room = new Room("!roomid:e.xyz", mockClient, "myname");
|
||||||
const redaction = new MatrixEvent({
|
const redaction = new MatrixEvent({
|
||||||
type: "m.room.redaction",
|
type: "m.room.redaction",
|
||||||
redacts: ev.getId(),
|
redacts: ev.getId(),
|
||||||
});
|
});
|
||||||
|
|
||||||
ev.makeRedacted(redaction);
|
ev.makeRedacted(redaction, room);
|
||||||
expect(ev.getContent().body).toBeUndefined();
|
expect(ev.getContent().body).toBeUndefined();
|
||||||
expect(ev.getWireContent().body).toBeUndefined();
|
expect(ev.getWireContent().body).toBeUndefined();
|
||||||
expect(ev.getWireContent().ciphertext).toBeUndefined();
|
expect(ev.getWireContent().ciphertext).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should remain in the main timeline when redacted", async () => {
|
||||||
|
// Given an event in the main timeline
|
||||||
|
const mockClient = {
|
||||||
|
supportsThreads: jest.fn().mockReturnValue(true),
|
||||||
|
decryptEventIfNeeded: jest.fn().mockReturnThis(),
|
||||||
|
getUserId: jest.fn().mockReturnValue("@user:server"),
|
||||||
|
} as unknown as MockedObject<MatrixClient>;
|
||||||
|
const room = new Room("!roomid:e.xyz", mockClient, "myname");
|
||||||
|
const ev = new MatrixEvent({
|
||||||
|
type: "m.room.message",
|
||||||
|
content: {
|
||||||
|
body: "Test",
|
||||||
|
},
|
||||||
|
event_id: "$event1:server",
|
||||||
|
});
|
||||||
|
|
||||||
|
await room.addLiveEvents([ev]);
|
||||||
|
await room.createThreadsTimelineSets();
|
||||||
|
expect(ev.threadRootId).toBeUndefined();
|
||||||
|
expect(mainTimelineLiveEventIds(room)).toEqual(["$event1:server"]);
|
||||||
|
|
||||||
|
// When I redact it
|
||||||
|
const redaction = new MatrixEvent({
|
||||||
|
type: "m.room.redaction",
|
||||||
|
redacts: ev.getId(),
|
||||||
|
});
|
||||||
|
ev.makeRedacted(redaction, room);
|
||||||
|
|
||||||
|
// Then it remains in the main timeline
|
||||||
|
expect(ev.threadRootId).toBeUndefined();
|
||||||
|
expect(mainTimelineLiveEventIds(room)).toEqual(["$event1:server"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should move into the main timeline when redacted", async () => {
|
||||||
|
// Given an event in a thread
|
||||||
|
const mockClient = {
|
||||||
|
supportsThreads: jest.fn().mockReturnValue(true),
|
||||||
|
decryptEventIfNeeded: jest.fn().mockReturnThis(),
|
||||||
|
getUserId: jest.fn().mockReturnValue("@user:server"),
|
||||||
|
} as unknown as MockedObject<MatrixClient>;
|
||||||
|
const room = new Room("!roomid:e.xyz", mockClient, "myname");
|
||||||
|
const threadRoot = new MatrixEvent({
|
||||||
|
type: "m.room.message",
|
||||||
|
content: {
|
||||||
|
body: "threadRoot",
|
||||||
|
},
|
||||||
|
event_id: "$threadroot:server",
|
||||||
|
});
|
||||||
|
const ev = new MatrixEvent({
|
||||||
|
type: "m.room.message",
|
||||||
|
content: {
|
||||||
|
"body": "Test",
|
||||||
|
"m.relates_to": {
|
||||||
|
rel_type: THREAD_RELATION_TYPE.name,
|
||||||
|
event_id: "$threadroot:server",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event_id: "$event1:server",
|
||||||
|
});
|
||||||
|
|
||||||
|
await room.addLiveEvents([threadRoot, ev]);
|
||||||
|
await room.createThreadsTimelineSets();
|
||||||
|
expect(ev.threadRootId).toEqual("$threadroot:server");
|
||||||
|
expect(mainTimelineLiveEventIds(room)).toEqual(["$threadroot:server"]);
|
||||||
|
expect(threadLiveEventIds(room, 0)).toEqual(["$threadroot:server", "$event1:server"]);
|
||||||
|
|
||||||
|
// When I redact it
|
||||||
|
const redaction = new MatrixEvent({
|
||||||
|
type: "m.room.redaction",
|
||||||
|
redacts: ev.getId(),
|
||||||
|
});
|
||||||
|
ev.makeRedacted(redaction, room);
|
||||||
|
|
||||||
|
// Then it disappears from the thread and appears in the main timeline
|
||||||
|
expect(ev.threadRootId).toBeUndefined();
|
||||||
|
expect(mainTimelineLiveEventIds(room)).toEqual(["$threadroot:server", "$event1:server"]);
|
||||||
|
expect(threadLiveEventIds(room, 0)).not.toContain("$event1:server");
|
||||||
|
});
|
||||||
|
|
||||||
describe("applyVisibilityEvent", () => {
|
describe("applyVisibilityEvent", () => {
|
||||||
it("should emit VisibilityChange if a change was made", async () => {
|
it("should emit VisibilityChange if a change was made", async () => {
|
||||||
const ev = new MatrixEvent({
|
const ev = new MatrixEvent({
|
||||||
@ -330,3 +420,19 @@ describe("MatrixEvent", () => {
|
|||||||
expect(stateEvent.threadRootId).toBeUndefined();
|
expect(stateEvent.threadRootId).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mainTimelineLiveEventIds(room: Room): Array<string> {
|
||||||
|
return room
|
||||||
|
.getLiveTimeline()
|
||||||
|
.getEvents()
|
||||||
|
.map((e) => e.getId()!);
|
||||||
|
}
|
||||||
|
|
||||||
|
function threadLiveEventIds(room: Room, threadIndex: number): Array<string> {
|
||||||
|
return room
|
||||||
|
.getThreads()
|
||||||
|
[threadIndex].getUnfilteredTimelineSet()
|
||||||
|
.getLiveTimeline()
|
||||||
|
.getEvents()
|
||||||
|
.map((e) => e.getId()!);
|
||||||
|
}
|
||||||
|
@ -27,6 +27,7 @@ import { M_BEACON } from "../../src/@types/beacon";
|
|||||||
import { MatrixClient } from "../../src/client";
|
import { MatrixClient } from "../../src/client";
|
||||||
import { DecryptionError } from "../../src/crypto/algorithms";
|
import { DecryptionError } from "../../src/crypto/algorithms";
|
||||||
import { defer } from "../../src/utils";
|
import { defer } from "../../src/utils";
|
||||||
|
import { Room } from "../../src/models/room";
|
||||||
|
|
||||||
describe("RoomState", function () {
|
describe("RoomState", function () {
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
@ -362,9 +363,11 @@ describe("RoomState", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not add redacted beacon info events to state", () => {
|
it("does not add redacted beacon info events to state", () => {
|
||||||
|
const mockClient = {} as unknown as MockedObject<MatrixClient>;
|
||||||
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
|
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
|
||||||
const redactionEvent = new MatrixEvent({ type: "m.room.redaction" });
|
const redactionEvent = new MatrixEvent({ type: "m.room.redaction" });
|
||||||
redactedBeaconEvent.makeRedacted(redactionEvent);
|
const room = new Room(roomId, mockClient, userA);
|
||||||
|
redactedBeaconEvent.makeRedacted(redactionEvent, room);
|
||||||
const emitSpy = jest.spyOn(state, "emit");
|
const emitSpy = jest.spyOn(state, "emit");
|
||||||
|
|
||||||
state.setStateEvents([redactedBeaconEvent]);
|
state.setStateEvents([redactedBeaconEvent]);
|
||||||
@ -394,11 +397,13 @@ describe("RoomState", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("destroys and removes redacted beacon events", () => {
|
it("destroys and removes redacted beacon events", () => {
|
||||||
|
const mockClient = {} as unknown as MockedObject<MatrixClient>;
|
||||||
const beaconId = "$beacon1";
|
const beaconId = "$beacon1";
|
||||||
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const redactionEvent = new MatrixEvent({ type: "m.room.redaction", redacts: beaconEvent.getId() });
|
const redactionEvent = new MatrixEvent({ type: "m.room.redaction", redacts: beaconEvent.getId() });
|
||||||
redactedBeaconEvent.makeRedacted(redactionEvent);
|
const room = new Room(roomId, mockClient, userA);
|
||||||
|
redactedBeaconEvent.makeRedacted(redactionEvent, room);
|
||||||
|
|
||||||
state.setStateEvents([beaconEvent]);
|
state.setStateEvents([beaconEvent]);
|
||||||
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
||||||
|
@ -3564,7 +3564,7 @@ describe("Room", function () {
|
|||||||
expect(room.polls.get(pollStartEvent.getId()!)).toBeTruthy();
|
expect(room.polls.get(pollStartEvent.getId()!)).toBeTruthy();
|
||||||
|
|
||||||
const redactedEvent = new MatrixEvent({ type: "m.room.redaction" });
|
const redactedEvent = new MatrixEvent({ type: "m.room.redaction" });
|
||||||
pollStartEvent.makeRedacted(redactedEvent);
|
pollStartEvent.makeRedacted(redactedEvent, room);
|
||||||
|
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ import { DecryptionError } from "../crypto/algorithms";
|
|||||||
import { CryptoBackend } from "../common-crypto/CryptoBackend";
|
import { CryptoBackend } from "../common-crypto/CryptoBackend";
|
||||||
import { WITHHELD_MESSAGES } from "../crypto/OlmDevice";
|
import { WITHHELD_MESSAGES } from "../crypto/OlmDevice";
|
||||||
import { IAnnotatedPushRule } from "../@types/PushRules";
|
import { IAnnotatedPushRule } from "../@types/PushRules";
|
||||||
|
import { Room } from "./room";
|
||||||
|
import { EventTimeline } from "./event-timeline";
|
||||||
|
|
||||||
export { EventStatus } from "./event-status";
|
export { EventStatus } from "./event-status";
|
||||||
|
|
||||||
@ -1135,13 +1137,19 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
|||||||
return this.visibility;
|
return this.visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated In favor of the overload that includes a Room argument
|
||||||
|
*/
|
||||||
|
public makeRedacted(redactionEvent: MatrixEvent): void;
|
||||||
/**
|
/**
|
||||||
* Update the content of an event in the same way it would be by the server
|
* Update the content of an event in the same way it would be by the server
|
||||||
* if it were redacted before it was sent to us
|
* if it were redacted before it was sent to us
|
||||||
*
|
*
|
||||||
* @param redactionEvent - event causing the redaction
|
* @param redactionEvent - event causing the redaction
|
||||||
|
* @param room - the room in which the event exists
|
||||||
*/
|
*/
|
||||||
public makeRedacted(redactionEvent: MatrixEvent): void {
|
public makeRedacted(redactionEvent: MatrixEvent, room: Room): void;
|
||||||
|
public makeRedacted(redactionEvent: MatrixEvent, room?: Room): void {
|
||||||
// quick sanity-check
|
// quick sanity-check
|
||||||
if (!redactionEvent.event) {
|
if (!redactionEvent.event) {
|
||||||
throw new Error("invalid redactionEvent in makeRedacted");
|
throw new Error("invalid redactionEvent in makeRedacted");
|
||||||
@ -1185,6 +1193,21 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the redacted event was in a thread
|
||||||
|
if (room && this.threadRootId && this.threadRootId !== this.getId()) {
|
||||||
|
// Remove it from its thread
|
||||||
|
this.thread?.timelineSet.removeEvent(this.getId()!);
|
||||||
|
this.setThread(undefined);
|
||||||
|
|
||||||
|
// And insert it into the main timeline
|
||||||
|
const timeline = room.getLiveTimeline();
|
||||||
|
// We use insertEventIntoTimeline to insert it in timestamp order,
|
||||||
|
// because we don't know where it should go (until we have MSC4033).
|
||||||
|
timeline
|
||||||
|
.getTimelineSet()
|
||||||
|
.insertEventIntoTimeline(this, timeline, timeline.getState(EventTimeline.FORWARDS)!);
|
||||||
|
}
|
||||||
|
|
||||||
this.invalidateExtensibleEvent();
|
this.invalidateExtensibleEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2329,7 +2329,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
// if we know about this event, redact its contents now.
|
// if we know about this event, redact its contents now.
|
||||||
const redactedEvent = redactId ? this.findEventById(redactId) : undefined;
|
const redactedEvent = redactId ? this.findEventById(redactId) : undefined;
|
||||||
if (redactedEvent) {
|
if (redactedEvent) {
|
||||||
redactedEvent.makeRedacted(event);
|
redactedEvent.makeRedacted(event, this);
|
||||||
|
|
||||||
// If this is in the current state, replace it with the redacted version
|
// If this is in the current state, replace it with the redacted version
|
||||||
if (redactedEvent.isState()) {
|
if (redactedEvent.isState()) {
|
||||||
|
Reference in New Issue
Block a user