Skip to content

Commit 56da3fc

Browse files
uhoregomitech
authored andcommitted
Update key storage toggle when key storage status changes (element-hq#30934)
* update key storage toggle when key storage status changes Listen for the CryptoEvent.KeyBackupStatus event and update the state when it changes. * fixup! update key storage toggle when key storage status changes * add comment about handling event
1 parent a2b1d3e commit 56da3fc

File tree

3 files changed

+70
-23
lines changed

3 files changed

+70
-23
lines changed

playwright/e2e/crypto/crypto.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ test.describe("Cryptography", function () {
146146
}).toPass();
147147
});
148148

149+
// When the user resets their identity, key storage also gets enabled.
150+
// Check that the toggle updates to show the correct state.
151+
test("Key backup status updates after resetting identity", async ({ page, app, user: aliceCredentials }) => {
152+
await app.client.bootstrapCrossSigning(aliceCredentials);
153+
154+
const encryptionTab = await app.settings.openUserSettings("Encryption");
155+
const keyStorageToggle = encryptionTab.getByRole("switch", { name: "Allow key storage" });
156+
// Check that key storage starts off as disabled
157+
expect(await keyStorageToggle.isChecked()).toBe(false);
158+
// Find "the Reset cryptographic identity" button
159+
await encryptionTab.getByRole("button", { name: "Reset cryptographic identity" }).click();
160+
161+
// Confirm
162+
await encryptionTab.getByRole("button", { name: "Continue" }).click();
163+
164+
// Enter the password
165+
await page.getByPlaceholder("Password").fill(aliceCredentials.password);
166+
await page.getByRole("button", { name: "Continue" }).click();
167+
168+
// Key storage should now be enabled
169+
expect(await keyStorageToggle.isChecked()).toBe(true);
170+
});
171+
149172
test(
150173
"creating a DM should work, being e2e-encrypted / user verification",
151174
{ tag: "@screenshot" },

src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts

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

8-
import { useCallback, useEffect, useState } from "react";
8+
import { useCallback, useState } from "react";
9+
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
910
import { logger } from "matrix-js-sdk/src/logger";
1011

1112
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
1213
import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../../../DeviceListener";
14+
import { useEventEmitterAsyncState } from "../../../../hooks/useEventEmitter";
1315

1416
interface KeyStoragePanelState {
1517
/**
@@ -37,31 +39,37 @@ interface KeyStoragePanelState {
3739

3840
/** Returns a ViewModel for use in {@link KeyStoragePanel} and {@link DeleteKeyStoragePanel}. */
3941
export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
40-
const [isEnabled, setIsEnabled] = useState<boolean | undefined>(undefined);
4142
const [loading, setLoading] = useState(true);
4243
// Whilst the change is being made, the toggle will reflect the pending value rather than the actual state
4344
const [pendingValue, setPendingValue] = useState<boolean | undefined>(undefined);
4445

4546
const matrixClient = useMatrixClientContext();
4647

47-
const checkStatus = useCallback(async () => {
48-
const crypto = matrixClient.getCrypto();
49-
if (!crypto) {
50-
logger.error("Can't check key backup status: no crypto module available");
51-
return;
52-
}
53-
// The toggle is enabled only if this device will upload megolm keys to the backup.
54-
// This is consistent with EX.
55-
const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
56-
setIsEnabled(activeBackupVersion !== null);
57-
}, [matrixClient]);
58-
59-
useEffect(() => {
60-
(async () => {
61-
await checkStatus();
48+
const isEnabled = useEventEmitterAsyncState(
49+
matrixClient,
50+
CryptoEvent.KeyBackupStatus,
51+
async (enabled?: boolean) => {
52+
// If we're called as a result of an event, rather than during
53+
// initialisation, we can get the backup status from the event
54+
// instead of having to query the backup version.
55+
if (enabled !== undefined) {
56+
return enabled;
57+
}
58+
59+
const crypto = matrixClient.getCrypto();
60+
if (!crypto) {
61+
logger.error("Can't check key backup status: no crypto module available");
62+
return;
63+
}
64+
// The toggle is enabled only if this device will upload megolm keys to the backup.
65+
// This is consistent with EX.
66+
const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
6267
setLoading(false);
63-
})();
64-
}, [checkStatus]);
68+
return activeBackupVersion !== null;
69+
},
70+
[matrixClient],
71+
undefined,
72+
);
6573

6674
const setEnabled = useCallback(
6775
async (enable: boolean) => {
@@ -121,14 +129,12 @@ export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
121129
// so this will stop EX turning it back on spontaneously.
122130
await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: true });
123131
}
124-
125-
await checkStatus();
126132
} finally {
127133
setPendingValue(undefined);
128134
DeviceListener.sharedInstance().start(matrixClient);
129135
}
130136
},
131-
[setPendingValue, checkStatus, matrixClient],
137+
[setPendingValue, matrixClient],
132138
);
133139

134140
return { isEnabled: pendingValue ?? isEnabled, setEnabled, loading, busy: pendingValue !== undefined };

test/unit-tests/components/viewmodels/settings/encryption/KeyStoragePanelViewModel-test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
55
Please see LICENSE files in the repository root for full details.
66
*/
77

8-
import { renderHook } from "jest-matrix-react";
8+
import { renderHook, waitFor } from "jest-matrix-react";
99
import { act } from "react";
1010
import { mocked } from "jest-mock";
11+
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
1112

1213
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
1314
import type { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
@@ -37,6 +38,23 @@ describe("KeyStoragePanelViewModel", () => {
3738
expect(result.current.busy).toBe(true);
3839
});
3940

41+
it("should update if a KeyBackupStatus event is received", async () => {
42+
const { result } = renderHook(
43+
() => useKeyStoragePanelViewModel(),
44+
withClientContextRenderOptions(matrixClient),
45+
);
46+
await waitFor(() => expect(result.current.isEnabled).toBe(false));
47+
48+
const mock = mocked(matrixClient.getCrypto()!.getActiveSessionBackupVersion);
49+
mock.mockResolvedValue("1");
50+
matrixClient.emit(CryptoEvent.KeyBackupStatus, true);
51+
await waitFor(() => expect(result.current.isEnabled).toBe(true));
52+
53+
mock.mockResolvedValue(null);
54+
matrixClient.emit(CryptoEvent.KeyBackupStatus, false);
55+
await waitFor(() => expect(result.current.isEnabled).toBe(false));
56+
});
57+
4058
it("should call resetKeyBackup if there is no backup currently", async () => {
4159
mocked(matrixClient.getCrypto()!.checkKeyBackupAndEnable).mockResolvedValue(null);
4260

0 commit comments

Comments
 (0)