Skip to content

Commit 1bb482f

Browse files
authored
Replace Matrix.getKeyBackupEnabled by MatrixClient.CryptoApi.getActiveSessionBackupVersion (#28225)
* Migrating deprecated sync `MatrixClient.getKeyBackupEnabled` to async `MatrixClient.CryptoApi.getActiveSessionBackupVersion` in `NewRecoveryMethodDialog`. Rewrite `NewRecoveryMethodDialog` into a functional component to make it easier to handle the new async method. * Migrating deprecated sync `MatrixClient.getKeyBackupEnabled` to async `MatrixClient.CryptoApi.getActiveSessionBackupVersion` in `MatrixChat`.
1 parent 85d2bf3 commit 1bb482f

File tree

6 files changed

+312
-72
lines changed

6 files changed

+312
-72
lines changed

src/async-components/views/dialogs/security/NewRecoveryMethodDialog.tsx

Lines changed: 59 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,92 +7,82 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
77
Please see LICENSE files in the repository root for full details.
88
*/
99

10-
import React from "react";
11-
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
10+
import React, { JSX, useEffect, useState } from "react";
1211

13-
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
1412
import dis from "../../../../dispatcher/dispatcher";
1513
import { _t } from "../../../../languageHandler";
1614
import Modal from "../../../../Modal";
1715
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
1816
import { Action } from "../../../../dispatcher/actions";
1917
import DialogButtons from "../../../../components/views/elements/DialogButtons";
2018
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
19+
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
2120

22-
interface IProps {
23-
newVersionInfo: KeyBackupInfo;
21+
/**
22+
* Properties for {@link NewRecoveryMethodDialog}.
23+
*/
24+
interface NewRecoveryMethodDialogProps {
25+
/**
26+
* Callback when the dialog is dismissed.
27+
*/
2428
onFinished(): void;
2529
}
2630

27-
export default class NewRecoveryMethodDialog extends React.PureComponent<IProps> {
28-
private onOkClick = (): void => {
29-
this.props.onFinished();
30-
};
31+
// Export as default instead of a named export so that it can be dynamically imported with `Modal.createDialogAsync`
3132

32-
private onGoToSettingsClick = (): void => {
33-
this.props.onFinished();
34-
dis.fire(Action.ViewUserSettings);
35-
};
33+
/**
34+
* Dialog to inform the user that a new recovery method has been detected.
35+
*/
36+
export default function NewRecoveryMethodDialog({ onFinished }: NewRecoveryMethodDialogProps): JSX.Element {
37+
const matrixClient = useMatrixClientContext();
38+
const [isKeyBackupEnabled, setIsKeyBackupEnabled] = useState(false);
39+
useEffect(() => {
40+
const checkBackupEnabled = async (): Promise<void> => {
41+
const crypto = matrixClient.getCrypto();
42+
setIsKeyBackupEnabled(Boolean(crypto && (await crypto.getActiveSessionBackupVersion()) !== null));
43+
};
3644

37-
private onSetupClick = async (): Promise<void> => {
38-
Modal.createDialog(
39-
RestoreKeyBackupDialog,
40-
{
41-
onFinished: this.props.onFinished,
42-
},
43-
undefined,
44-
/* priority = */ false,
45-
/* static = */ true,
46-
);
47-
};
45+
checkBackupEnabled();
46+
}, [matrixClient]);
4847

49-
public render(): React.ReactNode {
50-
const title = (
51-
<span className="mx_KeyBackupFailedDialog_title">
52-
{_t("encryption|new_recovery_method_detected|title")}
53-
</span>
54-
);
55-
56-
const newMethodDetected = <p>{_t("encryption|new_recovery_method_detected|description_1")}</p>;
57-
58-
const hackWarning = (
59-
<strong className="warning">{_t("encryption|new_recovery_method_detected|warning")}</strong>
60-
);
61-
62-
let content: JSX.Element | undefined;
63-
if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
64-
content = (
65-
<div>
66-
{newMethodDetected}
67-
<p>{_t("encryption|new_recovery_method_detected|description_2")}</p>
68-
{hackWarning}
69-
<DialogButtons
70-
primaryButton={_t("action|ok")}
71-
onPrimaryButtonClick={this.onOkClick}
72-
cancelButton={_t("common|go_to_settings")}
73-
onCancel={this.onGoToSettingsClick}
74-
/>
75-
</div>
76-
);
48+
function onClick(): void {
49+
if (isKeyBackupEnabled) {
50+
onFinished();
7751
} else {
78-
content = (
79-
<div>
80-
{newMethodDetected}
81-
{hackWarning}
82-
<DialogButtons
83-
primaryButton={_t("common|setup_secure_messages")}
84-
onPrimaryButtonClick={this.onSetupClick}
85-
cancelButton={_t("common|go_to_settings")}
86-
onCancel={this.onGoToSettingsClick}
87-
/>
88-
</div>
52+
Modal.createDialog(
53+
RestoreKeyBackupDialog,
54+
{
55+
onFinished,
56+
},
57+
undefined,
58+
false,
59+
true,
8960
);
9061
}
91-
92-
return (
93-
<BaseDialog className="mx_KeyBackupFailedDialog" onFinished={this.props.onFinished} title={title}>
94-
{content}
95-
</BaseDialog>
96-
);
9762
}
63+
64+
return (
65+
<BaseDialog
66+
className="mx_KeyBackupFailedDialog"
67+
onFinished={onFinished}
68+
title={
69+
<span className="mx_KeyBackupFailedDialog_title">
70+
{_t("encryption|new_recovery_method_detected|title")}
71+
</span>
72+
}
73+
>
74+
<p>{_t("encryption|new_recovery_method_detected|description_1")}</p>
75+
{isKeyBackupEnabled && <p>{_t("encryption|new_recovery_method_detected|description_2")}</p>}
76+
<strong className="warning">{_t("encryption|new_recovery_method_detected|warning")}</strong>
77+
<DialogButtons
78+
primaryButton={_t("common|setup_secure_messages")}
79+
onPrimaryButtonClick={onClick}
80+
cancelButton={_t("common|go_to_settings")}
81+
onCancel={() => {
82+
onFinished();
83+
dis.fire(Action.ViewUserSettings);
84+
}}
85+
/>
86+
</BaseDialog>
87+
);
9888
}

src/components/structures/MatrixChat.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,8 +1631,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
16311631
cli.on(CryptoEvent.KeyBackupFailed, async (errcode): Promise<void> => {
16321632
let haveNewVersion: boolean | undefined;
16331633
let newVersionInfo: KeyBackupInfo | null = null;
1634+
const keyBackupEnabled = Boolean(
1635+
cli.getCrypto() && (await cli.getCrypto()?.getActiveSessionBackupVersion()) !== null,
1636+
);
1637+
16341638
// if key backup is still enabled, there must be a new backup in place
1635-
if (cli.getKeyBackupEnabled()) {
1639+
if (keyBackupEnabled) {
16361640
haveNewVersion = true;
16371641
} else {
16381642
// otherwise check the server to see if there's a new one
@@ -1650,7 +1654,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
16501654
import(
16511655
"../../async-components/views/dialogs/security/NewRecoveryMethodDialog"
16521656
) as unknown as Promise<typeof NewRecoveryMethodDialog>,
1653-
{ newVersionInfo: newVersionInfo! },
16541657
);
16551658
} else {
16561659
Modal.createDialogAsync(

test/test-utils/test-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export function createTestClient(): MatrixClient {
129129
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
130130
setDeviceIsolationMode: jest.fn(),
131131
prepareToEncrypt: jest.fn(),
132+
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
132133
}),
133134

134135
getPushActionsForEvent: jest.fn(),
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import React from "react";
9+
import { MatrixClient } from "matrix-js-sdk/src/matrix";
10+
import { render, screen } from "jest-matrix-react";
11+
import { waitFor } from "@testing-library/dom";
12+
import userEvent from "@testing-library/user-event";
13+
import { act } from "@testing-library/react-hooks/dom";
14+
15+
import NewRecoveryMethodDialog from "../../../../../src/async-components/views/dialogs/security/NewRecoveryMethodDialog";
16+
import { createTestClient } from "../../../../test-utils";
17+
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext.tsx";
18+
import dis from "../../../../../src/dispatcher/dispatcher.ts";
19+
import { Action } from "../../../../../src/dispatcher/actions.ts";
20+
import Modal from "../../../../../src/Modal.tsx";
21+
22+
describe("<NewRecoveryMethodDialog />", () => {
23+
let matrixClient: MatrixClient;
24+
beforeEach(() => {
25+
matrixClient = createTestClient();
26+
jest.spyOn(dis, "fire");
27+
jest.spyOn(Modal, "createDialog");
28+
});
29+
30+
afterEach(() => {
31+
jest.restoreAllMocks();
32+
});
33+
34+
function renderComponent(onFinished: () => void = jest.fn()) {
35+
return render(
36+
<MatrixClientContext.Provider value={matrixClient}>
37+
<NewRecoveryMethodDialog onFinished={onFinished} />
38+
</MatrixClientContext.Provider>,
39+
);
40+
}
41+
42+
test("when cancel is clicked", async () => {
43+
const onFinished = jest.fn();
44+
act(() => {
45+
renderComponent(onFinished);
46+
});
47+
48+
await userEvent.click(screen.getByRole("button", { name: "Go to Settings" }));
49+
expect(onFinished).toHaveBeenCalled();
50+
expect(dis.fire).toHaveBeenCalledWith(Action.ViewUserSettings);
51+
});
52+
53+
test("when key backup is enabled", async () => {
54+
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("version");
55+
56+
const onFinished = jest.fn();
57+
58+
await act(async () => {
59+
const { asFragment } = renderComponent(onFinished);
60+
await waitFor(() =>
61+
expect(
62+
screen.getByText("This session is encrypting history using the new recovery method."),
63+
).toBeInTheDocument(),
64+
);
65+
expect(asFragment()).toMatchSnapshot();
66+
});
67+
68+
await userEvent.click(screen.getByRole("button", { name: "Set up Secure Messages" }));
69+
expect(onFinished).toHaveBeenCalled();
70+
});
71+
72+
test("when key backup is disabled", async () => {
73+
const onFinished = jest.fn();
74+
75+
const { asFragment } = renderComponent(onFinished);
76+
expect(asFragment()).toMatchSnapshot();
77+
78+
await userEvent.click(screen.getByRole("button", { name: "Set up Secure Messages" }));
79+
await waitFor(() => expect(Modal.createDialog).toHaveBeenCalled());
80+
});
81+
});

0 commit comments

Comments
 (0)