Skip to content
14 changes: 14 additions & 0 deletions playwright/e2e/crypto/history-sharing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ test.describe("History sharing", function () {

// Create the room and send a message
await createRoom(alicePage, "TestRoom", true);

// The default history visibility for private rooms is "invited",
// so we need to change it to "shared" to ensure Bob can see the message when he joins.
const roomId = await aliceElementApp.getCurrentRoomIdFromUrl();
await aliceElementApp.client.sendStateEvent(roomId, "m.room.history_visibility", {
history_visibility: "shared",
});

await sendMessageInCurrentRoom(alicePage, "A message from Alice");

// Send the invite to Bob
Expand Down Expand Up @@ -101,6 +109,12 @@ test.describe("History sharing", function () {
await bobPage.getByRole("option", { name: "TestRoom" }).click();
await bobPage.getByRole("button", { name: "Accept" }).click();

// The room now defaults to "invited" history visibility, so we need to set it to "shared" first
await aliceElementApp.client.sendStateEvent(roomId, "m.room.history_visibility", {
history_visibility: "shared",
});
await expect(bobPage.getByText("Alice made future room history visible to all room members.")).toBeVisible();

// Bob sends a message with "shared" visibility
await sendMessageInCurrentRoom(bobPage, "Message1: 'shared' visibility");
await expect(alicePage.getByText("Message1")).toBeVisible();
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 16 additions & 3 deletions src/createRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
RoomCreateTypeField,
RoomType,
type ICreateRoomOpts,
type HistoryVisibility,
HistoryVisibility,
JoinRule,
Preset,
RestrictedAllowType,
Expand Down Expand Up @@ -294,11 +294,24 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
}
}

if (opts.historyVisibility) {
// Set history visibility to "invited" for DMs and non-public rooms unless explicitly overridden
// This ensures that room history is only visible to invited members by default
let historyVisibility = opts.historyVisibility;
if (!historyVisibility) {
// Determine if the room will be public based on the join rule or preset
const isPublicRoom = opts.joinRule === JoinRule.Public || createOpts.preset === Preset.PublicChat;

// For DMs and non-public rooms, set history visibility to "invited"
if (opts.dmUserId || !isPublicRoom) {
historyVisibility = HistoryVisibility.Invited;
}
}

if (historyVisibility) {
createOpts.initial_state.push({
type: EventType.RoomHistoryVisibility,
content: {
history_visibility: opts.historyVisibility,
history_visibility: historyVisibility,
},
});
}
Expand Down
36 changes: 35 additions & 1 deletion test/unit-tests/createRoom-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { mocked, type Mocked } from "jest-mock";
import {
type MatrixClient,
type Device,
HistoryVisibility,
Preset,
RoomType,
JoinRule,
Expand Down Expand Up @@ -58,7 +59,10 @@ describe("createRoom", () => {
expect(client.createRoom).toHaveBeenCalledWith({
preset: "private_chat",
visibility: "private",
initial_state: [{ state_key: "", type: "m.room.guest_access", content: { guest_access: "can_join" } }],
initial_state: [
{ state_key: "", type: "m.room.guest_access", content: { guest_access: "can_join" } },
{ type: "m.room.history_visibility", content: { history_visibility: "invited" } },
],
});
});

Expand All @@ -77,6 +81,7 @@ describe("createRoom", () => {
algorithm: "m.megolm.v1.aes-sha2",
},
},
{ type: "m.room.history_visibility", content: { history_visibility: "invited" } },
],
});
});
Expand Down Expand Up @@ -104,6 +109,7 @@ describe("createRoom", () => {
"io.element.msc4362.encrypt_state_events": true,
},
},
{ type: "m.room.history_visibility", content: { history_visibility: "invited" } },
// Room name is NOT included, since it needs to be encrypted.
],
});
Expand Down Expand Up @@ -146,6 +152,7 @@ describe("createRoom", () => {
"io.element.msc4362.encrypt_state_events": true,
},
},
{ type: "m.room.history_visibility", content: { history_visibility: "invited" } },
// Room name is NOT included, since it needs to be encrypted.
],
});
Expand Down Expand Up @@ -178,6 +185,7 @@ describe("createRoom", () => {
"io.element.msc4362.encrypt_state_events": true,
},
},
{ type: "m.room.history_visibility", content: { history_visibility: "invited" } },
// Room name is NOT included, since it needs to be encrypted.
],
});
Expand Down Expand Up @@ -218,6 +226,7 @@ describe("createRoom", () => {
initial_state: [
{ state_key: "", type: "m.room.guest_access", content: { guest_access: "can_join" } },
{ type: "m.space.parent", state_key: parentSpace.roomId, content: { canonical: true, via: [] } },
{ type: "m.room.history_visibility", content: { history_visibility: "invited" } },
],
});
});
Expand Down Expand Up @@ -354,6 +363,31 @@ describe("createRoom", () => {
);
});

it("should set history visibility to invited for DMs", async () => {
await createRoom(client, { dmUserId: "@bob:example.org" });
expect(client.createRoom).toHaveBeenCalledWith(
expect.objectContaining({
initial_state: expect.arrayContaining([
{ type: "m.room.history_visibility", content: { history_visibility: "invited" } },
]),
}),
);
});

it("should respect an explicit history visibility override", async () => {
await createRoom(client, {
createOpts: { preset: Preset.PrivateChat },
historyVisibility: HistoryVisibility.Shared,
});
expect(client.createRoom).toHaveBeenCalledWith(
expect.objectContaining({
initial_state: expect.arrayContaining([
{ type: "m.room.history_visibility", content: { history_visibility: "shared" } },
]),
}),
);
});

describe("room versions", () => {
afterEach(() => {
jest.clearAllMocks();
Expand Down
Loading