Files
element-web/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts
David Baker be3778bef0 Add key storage toggle to Encryption settings (#29310)
* Add key storage toggle to Encryption settings

* Keys in the acceptable order

* Fix some tests

* Fix import

* Fix toast showing condition

* Fix import order

* Fix playwright tests

* Fix bits lost in merge

* Add key storage delete confirm screen

* Fix hardcoded Element string

* Fix type imports

* Fix tests

* Tests for key storage delete panel

* Fix test

* Type import

* Test for the view model

* Fix type import

* Actually fix type imports

* Test updating

* Add playwright test & clarify slightly confusing comment

* Show the advnced section whatever the state of key storage

* Update screenshots

* Copy css to its own file

* Add missing doc & merge loading states

* Add tsdoc & loading alt text to spinner

* Turn comments into proper tsdoc

* Switch to TypedEventEmitter and remove unnecessary loading state

* Add screenshot

* Use higher level interface

* Merge the two hooks in EncryptionUserSettingsTab

* Remove unused import

* Don't check key backup enabled state separately

as we don't need it for all the screens

* Update snapshot

* Use fixed recovery key function

* Amalgamate duplicated CSS files

* Have "key storage disabled" as a separate state

* Update snapshot

* Fix... bad merge?

* Add backup enabled mock to more tests

* More snapshots

* Use defer util

* Update to use EncryptionCardButtons

* Update snapshots

* Use EncryptionCardEmphasisedContent

* Update snapshots

* Update snapshot

* Try screenshot from CI playwright

* Try playwright screenshots again

* More screenshots

* Rename to match files

* Test that 4S secrets are deleted

* Make description clearer

* Fix typo & move related states together

* Add comment

* More comments

* Fix hook docs

* restoreAllMocks

* Update snapshot

because pulling in upstream has caused IDs to shift

* Switch icon

as apparenty the error icon has changed

* Update snapshot

* Missing copyright

* Re-order states

and also sort out indenting

* Remove phantom space

* Clarify 'button'

* Clarify docs more

* Explain thinking behind updating

* Switch to getActiveBackupVersion

which checks that key backup is happining on this device, which is
consistent with EX.

* Add use of Key Storage Panel

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Change key storage panel to be consistent

ie. using getActiveBackupVersion(), and add comment

* Add tsdoc

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Use BACKUP_DISABLED_ACCOUNT_DATA_KEY in more places

* Expand doc

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Undo random yarn lock change

* Use aggregate method for disabling key storage

in https://github.com/matrix-org/matrix-js-sdk/pull/4742

* Fix tests

* Use key backup status event to update

* Comment formatting

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Fix comment & put check inside if statement

* Add comment

* Prettier

* Fix comment

* Update snapshot

Which has gained nowrap due to 917d53a56f

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-03-14 08:52:41 +00:00

117 lines
4.9 KiB
TypeScript

/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { useCallback, useEffect, useState } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../../../DeviceListener";
interface KeyStoragePanelState {
/**
* Whether the app's "key storage" option should show as enabled to the user,
* or 'undefined' if the state is still loading.
*/
isEnabled: boolean | undefined;
/**
* A function that can be called to enable or disable key storage.
* @param enable True to turn key storage on or false to turn it off
*/
setEnabled: (enable: boolean) => void;
/**
* True if the state is still loading for the first time
*/
loading: boolean;
/**
* True if the status is in the process of being changed
*/
busy: boolean;
}
/** Returns a ViewModel for use in {@link KeyStoragePanel} and {@link DeleteKeyStoragePanel}. */
export function useKeyStoragePanelViewModel(): KeyStoragePanelState {
const [isEnabled, setIsEnabled] = useState<boolean | undefined>(undefined);
const [loading, setLoading] = useState(true);
// Whilst the change is being made, the toggle will reflect the pending value rather than the actual state
const [pendingValue, setPendingValue] = useState<boolean | undefined>(undefined);
const matrixClient = useMatrixClientContext();
const checkStatus = useCallback(async () => {
const crypto = matrixClient.getCrypto();
if (!crypto) {
logger.error("Can't check key backup status: no crypto module available");
return;
}
// The toggle is enabled only if this device will upload megolm keys to the backup.
// This is consistent with EX.
const activeBackupVersion = await crypto.getActiveSessionBackupVersion();
setIsEnabled(activeBackupVersion !== null);
}, [matrixClient]);
useEffect(() => {
(async () => {
await checkStatus();
setLoading(false);
})();
}, [checkStatus]);
const setEnabled = useCallback(
async (enable: boolean) => {
setPendingValue(enable);
try {
// stop the device listener since enabling or (especially) disabling key storage must be
// done with a sequence of API calls that will put the account in a slightly different
// state each time, so suppress any warning toasts until the process is finished (when
// we'll turn it back on again.)
DeviceListener.sharedInstance().stop();
const crypto = matrixClient.getCrypto();
if (!crypto) {
logger.error("Can't change key backup status: no crypto module available");
return;
}
if (enable) {
// If there is no existing key backup on the server, create one.
// `resetKeyBackup` will delete any existing backup, so we only do this if there is no existing backup.
const currentKeyBackup = await crypto.checkKeyBackupAndEnable();
if (currentKeyBackup === null) {
await crypto.resetKeyBackup();
// resetKeyBackup fires this off in the background without waiting, so we need to do it
// explicitly and wait for it, otherwise it won't be enabled yet when we check again.
await crypto.checkKeyBackupAndEnable();
}
// Set the flag so that EX no longer thinks the user wants backup disabled
await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: false });
} else {
// This method will delete the key backup as well as server side recovery keys and other
// server-side crypto data.
await crypto.disableKeyStorage();
// Set a flag to say that the user doesn't want key backup.
// Element X uses this to determine whether to set up automatically,
// so this will stop EX turning it back on spontaneously.
await matrixClient.setAccountData(BACKUP_DISABLED_ACCOUNT_DATA_KEY, { disabled: true });
}
await checkStatus();
} finally {
setPendingValue(undefined);
DeviceListener.sharedInstance().start(matrixClient);
}
},
[setPendingValue, checkStatus, matrixClient],
);
return { isEnabled: pendingValue ?? isEnabled, setEnabled, loading, busy: pendingValue !== undefined };
}