Skip to content

Commit ab6ef2f

Browse files
richvdht3chguy
andauthored
Add labs option for history sharing on invite (#30313)
* Add labs option for "share history on invite" * Set `acceptSharedHistory` when joining a room * set `shareEncryptedHistory` when sending an invite * Update src/i18n/strings/en_EN.json Co-authored-by: Michael Telatynski <[email protected]> --------- Co-authored-by: Michael Telatynski <[email protected]>
1 parent c79c8c8 commit ab6ef2f

File tree

6 files changed

+72
-16
lines changed

6 files changed

+72
-16
lines changed

src/i18n/strings/en_EN.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,9 @@
15341534
"render_reaction_images_description": "Sometimes referred to as \"custom emojis\".",
15351535
"report_to_moderators": "Report to moderators",
15361536
"report_to_moderators_description": "In rooms that support moderation, the “Report” button will let you report abuse to room moderators.",
1537+
"share_history_on_invite": "Share encrypted history with new members",
1538+
"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 are invited to such a room.",
1539+
"share_history_on_invite_warning": "This feature is EXPERIMENTAL and not all security precautions are implemented. Do not enable on production accounts.",
15371540
"sliding_sync": "Sliding Sync mode",
15381541
"sliding_sync_description": "Under active development, cannot be disabled.",
15391542
"sliding_sync_disabled_notice": "Log out and back in to disable",

src/settings/Settings.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ export interface Settings {
205205
"feature_mjolnir": IFeature;
206206
"feature_custom_themes": IFeature;
207207
"feature_exclude_insecure_devices": IFeature;
208+
"feature_share_history_on_invite": IFeature;
208209
"feature_html_topic": IFeature;
209210
"feature_bridge_state": IFeature;
210211
"feature_jump_to_date": IFeature;
@@ -503,6 +504,29 @@ export const SETTINGS: Settings = {
503504
supportedLevelsAreOrdered: true,
504505
default: false,
505506
},
507+
"feature_share_history_on_invite": {
508+
isFeature: true,
509+
labsGroup: LabGroup.Encryption,
510+
displayName: _td("labs|share_history_on_invite"),
511+
description: () => (
512+
<>
513+
{_t("labs|share_history_on_invite_description")}
514+
<div className="mx_SettingsFlag_microcopy">
515+
{_t(
516+
"settings|warning",
517+
{},
518+
{
519+
w: (sub) => <span className="mx_SettingsTab_microcopy_warning">{sub}</span>,
520+
description: _t("labs|share_history_on_invite_warning"),
521+
},
522+
)}
523+
</div>
524+
</>
525+
),
526+
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
527+
supportedLevelsAreOrdered: true,
528+
default: false,
529+
},
506530
"useOnlyCurrentProfiles": {
507531
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
508532
displayName: _td("settings|disable_historical_profile"),

src/stores/RoomViewStore.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Please see LICENSE files in the repository root for full details.
1010

1111
import React, { type ReactNode } from "react";
1212
import * as utils from "matrix-js-sdk/src/utils";
13-
import { MatrixError, JoinRule, type Room, type MatrixEvent } from "matrix-js-sdk/src/matrix";
13+
import { MatrixError, JoinRule, type Room, type MatrixEvent, type IJoinRoomOpts } from "matrix-js-sdk/src/matrix";
1414
import { KnownMembership } from "matrix-js-sdk/src/types";
1515
import { logger } from "matrix-js-sdk/src/logger";
1616
import { type ViewRoom as ViewRoomEvent } from "@matrix-org/analytics-events/types/typescript/ViewRoom";
@@ -512,15 +512,19 @@ export class RoomViewStore extends EventEmitter {
512512
// take a copy of roomAlias & roomId as they may change by the time the join is complete
513513
const { roomAlias, roomId = payload.roomId } = this.state;
514514
const address = roomAlias || roomId!;
515-
const viaServers = this.state.viaServers || [];
515+
516+
const joinOpts: IJoinRoomOpts = {
517+
viaServers: this.state.viaServers || [],
518+
...(payload.opts ?? {}),
519+
};
520+
if (SettingsStore.getValue("feature_share_history_on_invite")) {
521+
joinOpts.acceptSharedHistory = true;
522+
}
523+
516524
try {
517525
const cli = MatrixClientPeg.safeGet();
518526
await retry<Room, MatrixError>(
519-
() =>
520-
cli.joinRoom(address, {
521-
viaServers,
522-
...(payload.opts || {}),
523-
}),
527+
() => cli.joinRoom(address, joinOpts),
524528
NUM_JOIN_RETRY,
525529
(err) => {
526530
// if we received a Gateway timeout or Cloudflare timeout then retry

src/utils/MultiInviter.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

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

@@ -183,7 +183,11 @@ export default class MultiInviter {
183183
}
184184
}
185185

186-
return this.matrixClient.invite(roomId, addr, this.reason);
186+
const opts: InviteOpts = {};
187+
if (this.reason !== undefined) opts.reason = this.reason;
188+
if (SettingsStore.getValue("feature_share_history_on_invite")) opts.shareEncryptedHistory = true;
189+
190+
return this.matrixClient.invite(roomId, addr, opts);
187191
} else {
188192
throw new Error("Unsupported address");
189193
}

test/unit-tests/stores/RoomViewStore-test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,17 @@ describe("RoomViewStore", function () {
440440
});
441441
expect(mocked(dis.dispatch).mock.calls[2][0]).toEqual({ action: "prompt_ask_to_join" });
442442
});
443+
444+
it("sets 'acceptSharedHistory' if that option is enabled", async () => {
445+
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => {
446+
return settingName === "feature_share_history_on_invite"; // this is enabled, everything else is disabled.
447+
});
448+
449+
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
450+
dis.dispatch({ action: Action.JoinRoom });
451+
await untilDispatch(Action.JoinRoomReady, dis);
452+
expect(mockClient.joinRoom).toHaveBeenCalledWith(roomId, { acceptSharedHistory: true, viaServers: [] });
453+
});
443454
});
444455

445456
describe("Action.JoinRoomError", () => {

test/unit-tests/utils/MultiInviter-test.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ describe("MultiInviter", () => {
9696
const result = await inviter.invite([MXID1, MXID2, MXID3]);
9797

9898
expect(client.invite).toHaveBeenCalledTimes(3);
99-
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
100-
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
101-
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
99+
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, {});
100+
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, {});
101+
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, {});
102102

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

116116
expect(client.invite).toHaveBeenCalledTimes(3);
117-
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
118-
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
119-
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
117+
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, {});
118+
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, {});
119+
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, {});
120120

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

131131
expect(client.invite).toHaveBeenCalledTimes(1);
132-
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
132+
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, {});
133133

134134
// The resolved state is 'invited' for all users.
135135
// With the above client expectations, the test ensures that only the first user is invited.
@@ -231,5 +231,15 @@ describe("MultiInviter", () => {
231231
`"This space is unfederated. You cannot invite people from external servers."`,
232232
);
233233
});
234+
235+
it("should set shareEncryptedHistory if that setting is enabled", async () => {
236+
mocked(SettingsStore.getValue).mockImplementation((settingName, roomId, value) => {
237+
return settingName === "feature_share_history_on_invite"; // this is enabled, everything else is disabled.
238+
});
239+
await inviter.invite([MXID1]);
240+
241+
expect(client.invite).toHaveBeenCalledTimes(1);
242+
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, { shareEncryptedHistory: true });
243+
});
234244
});
235245
});

0 commit comments

Comments
 (0)