Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1534,6 +1534,9 @@
"render_reaction_images_description": "Sometimes referred to as \"custom emojis\".",
"report_to_moderators": "Report to moderators",
"report_to_moderators_description": "In rooms that support moderation, the “Report” button will let you report abuse to room moderators.",
"share_history_on_invite": "Share encrypted history with new members",
"share_history_on_invite_description": "When inviting a user to an encrypted room that has history visibility set to \"shared\", share encrypted history with that user, and accept encrypted history when you re invited to such a room.",
"share_history_on_invite_warning": "This feature is EXPERIMENTAL and not all security precautions are implemented. Do not enable on production accounts.",
"sliding_sync": "Sliding Sync mode",
"sliding_sync_description": "Under active development, cannot be disabled.",
"sliding_sync_disabled_notice": "Log out and back in to disable",
Expand Down
24 changes: 24 additions & 0 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export interface Settings {
"feature_mjolnir": IFeature;
"feature_custom_themes": IFeature;
"feature_exclude_insecure_devices": IFeature;
"feature_share_history_on_invite": IFeature;
"feature_html_topic": IFeature;
"feature_bridge_state": IFeature;
"feature_jump_to_date": IFeature;
Expand Down Expand Up @@ -503,6 +504,29 @@ export const SETTINGS: Settings = {
supportedLevelsAreOrdered: true,
default: false,
},
"feature_share_history_on_invite": {
isFeature: true,
labsGroup: LabGroup.Encryption,
displayName: _td("labs|share_history_on_invite"),
description: () => (
<>
{_t("labs|share_history_on_invite_description")}
<div className="mx_SettingsFlag_microcopy">
{_t(
"settings|warning",
{},
{
w: (sub) => <span className="mx_SettingsTab_microcopy_warning">{sub}</span>,
description: _t("labs|share_history_on_invite_warning"),
},
)}
</div>
</>
),
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
supportedLevelsAreOrdered: true,
default: false,
},
"useOnlyCurrentProfiles": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("settings|disable_historical_profile"),
Expand Down
18 changes: 11 additions & 7 deletions src/stores/RoomViewStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.

import React, { type ReactNode } from "react";
import * as utils from "matrix-js-sdk/src/utils";
import { MatrixError, JoinRule, type Room, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixError, JoinRule, type Room, type MatrixEvent, type IJoinRoomOpts } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";
import { type ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
Expand Down Expand Up @@ -512,15 +512,19 @@ export class RoomViewStore extends EventEmitter {
// take a copy of roomAlias & roomId as they may change by the time the join is complete
const { roomAlias, roomId = payload.roomId } = this.state;
const address = roomAlias || roomId!;
const viaServers = this.state.viaServers || [];

const joinOpts: IJoinRoomOpts = {
viaServers: this.state.viaServers || [],
...(payload.opts ?? {}),
};
if (SettingsStore.getValue("feature_share_history_on_invite")) {
joinOpts.acceptSharedHistory = true;
}

try {
const cli = MatrixClientPeg.safeGet();
await retry<Room, MatrixError>(
() =>
cli.joinRoom(address, {
viaServers,
...(payload.opts || {}),
}),
() => cli.joinRoom(address, joinOpts),
NUM_JOIN_RETRY,
(err) => {
// if we received a Gateway timeout or Cloudflare timeout then retry
Expand Down
8 changes: 6 additions & 2 deletions src/utils/MultiInviter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import { MatrixError, type MatrixClient, EventType, type EmptyObject } from "matrix-js-sdk/src/matrix";
import { MatrixError, type MatrixClient, EventType, type EmptyObject, type InviteOpts } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger";

Expand Down Expand Up @@ -183,7 +183,11 @@ export default class MultiInviter {
}
}

return this.matrixClient.invite(roomId, addr, this.reason);
const opts: InviteOpts = {};
if (this.reason !== undefined) opts.reason = this.reason;
if (SettingsStore.getValue("feature_share_history_on_invite")) opts.shareEncryptedHistory = true;

return this.matrixClient.invite(roomId, addr, opts);
} else {
throw new Error("Unsupported address");
}
Expand Down
11 changes: 11 additions & 0 deletions test/unit-tests/stores/RoomViewStore-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,17 @@ describe("RoomViewStore", function () {
});
expect(mocked(dis.dispatch).mock.calls[2][0]).toEqual({ action: "prompt_ask_to_join" });
});

it("sets 'acceptSharedHistory' if that option is enabled", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => {
return settingName === "feature_share_history_on_invite"; // this is enabled, everything else is disabled.
});

dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
dis.dispatch({ action: Action.JoinRoom });
await untilDispatch(Action.JoinRoomReady, dis);
expect(mockClient.joinRoom).toHaveBeenCalledWith(roomId, { acceptSharedHistory: true, viaServers: [] });
});
});

describe("Action.JoinRoomError", () => {
Expand Down
24 changes: 17 additions & 7 deletions test/unit-tests/utils/MultiInviter-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ describe("MultiInviter", () => {
const result = await inviter.invite([MXID1, MXID2, MXID3]);

expect(client.invite).toHaveBeenCalledTimes(3);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, {});
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, {});
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, {});

expectAllInvitedResult(result);
});
Expand All @@ -114,9 +114,9 @@ describe("MultiInviter", () => {
const result = await inviter.invite([MXID1, MXID2, MXID3]);

expect(client.invite).toHaveBeenCalledTimes(3);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, {});
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, {});
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, {});

expectAllInvitedResult(result);
});
Expand All @@ -129,7 +129,7 @@ describe("MultiInviter", () => {
const result = await inviter.invite([MXID1, MXID2, MXID3]);

expect(client.invite).toHaveBeenCalledTimes(1);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, {});

// The resolved state is 'invited' for all users.
// With the above client expectations, the test ensures that only the first user is invited.
Expand Down Expand Up @@ -231,5 +231,15 @@ describe("MultiInviter", () => {
`"This space is unfederated. You cannot invite people from external servers."`,
);
});

it("should set shareEncryptedHistory if that setting is enabled", async () => {
mocked(SettingsStore.getValue).mockImplementation((settingName, roomId, value) => {
return settingName === "feature_share_history_on_invite"; // this is enabled, everything else is disabled.
});
await inviter.invite([MXID1]);

expect(client.invite).toHaveBeenCalledTimes(1);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, { shareEncryptedHistory: true });
});
});
});
Loading