2025-03-14 08:52:41 +00:00
|
|
|
/*
|
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-10-03 09:04:06 -04:00
|
|
|
import { useCallback, useState } from "react";
|
|
|
|
|
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
|
2025-03-14 08:52:41 +00:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
|
|
|
|
|
|
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
|
|
|
|
|
import DeviceListener, { BACKUP_DISABLED_ACCOUNT_DATA_KEY } from "../../../../DeviceListener";
|
2025-10-03 09:04:06 -04:00
|
|
|
import { useEventEmitterAsyncState } from "../../../../hooks/useEventEmitter";
|
2025-03-14 08:52:41 +00:00
|
|
|
|
|
|
|
|
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 [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();
|
|
|
|
|
|
2025-10-03 09:04:06 -04:00
|
|
|
const isEnabled = useEventEmitterAsyncState(
|
|
|
|
|
matrixClient,
|
|
|
|
|
CryptoEvent.KeyBackupStatus,
|
|
|
|
|
async (enabled?: boolean) => {
|
|
|
|
|
// If we're called as a result of an event, rather than during
|
|
|
|
|
// initialisation, we can get the backup status from the event
|
|
|
|
|
// instead of having to query the backup version.
|
|
|
|
|
if (enabled !== undefined) {
|
|
|
|
|
return enabled;
|
|
|
|
|
}
|
2025-03-14 08:52:41 +00:00
|
|
|
|
2025-10-03 09:04:06 -04:00
|
|
|
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();
|
2025-03-14 08:52:41 +00:00
|
|
|
setLoading(false);
|
2025-10-03 09:04:06 -04:00
|
|
|
return activeBackupVersion !== null;
|
|
|
|
|
},
|
|
|
|
|
[matrixClient],
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
2025-03-14 08:52:41 +00:00
|
|
|
|
|
|
|
|
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) {
|
2025-09-15 13:18:34 +01:00
|
|
|
const childLogger = logger.getChild("[enable key storage]");
|
|
|
|
|
childLogger.info("User requested enabling key storage");
|
|
|
|
|
let currentKeyBackup = await crypto.checkKeyBackupAndEnable();
|
|
|
|
|
if (currentKeyBackup) {
|
|
|
|
|
logger.info(
|
|
|
|
|
`Existing key backup is present. version: ${currentKeyBackup.backupInfo.version}`,
|
|
|
|
|
currentKeyBackup.trustInfo,
|
|
|
|
|
);
|
|
|
|
|
// Check if the current key backup can be used. Either of these properties causes the key backup to be used.
|
|
|
|
|
if (currentKeyBackup.trustInfo.trusted || currentKeyBackup.trustInfo.matchesDecryptionKey) {
|
|
|
|
|
logger.info("Existing key backup can be used");
|
|
|
|
|
} else {
|
|
|
|
|
logger.warn("Existing key backup cannot be used, creating new backup");
|
|
|
|
|
// There aren't any *usable* backups, so we need to create a new one.
|
|
|
|
|
currentKeyBackup = null;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
logger.info("No existing key backup versions are present, creating new backup");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there is no usable key backup on the server, create one.
|
|
|
|
|
// `resetKeyBackup` will delete any existing backup, so we only do this if there is no usable backup.
|
2025-03-14 08:52:41 +00:00
|
|
|
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 {
|
2025-09-15 13:18:34 +01:00
|
|
|
logger.info("User requested disabling key backup");
|
2025-03-14 08:52:41 +00:00
|
|
|
// 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 });
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
setPendingValue(undefined);
|
|
|
|
|
DeviceListener.sharedInstance().start(matrixClient);
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-10-03 09:04:06 -04:00
|
|
|
[setPendingValue, matrixClient],
|
2025-03-14 08:52:41 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return { isEnabled: pendingValue ?? isEnabled, setEnabled, loading, busy: pendingValue !== undefined };
|
|
|
|
|
}
|