diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index f947c1b9ed..db3c0bf1f4 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -25,7 +25,7 @@ import { } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { CryptoEvent } from "matrix-js-sdk/src/crypto"; -import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup"; +import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api"; import dis from "./dispatcher/dispatcher"; import { @@ -62,10 +62,10 @@ export default class DeviceListener { private dismissed = new Set(); // has the user dismissed any of the various nag toasts to setup encryption on this device? private dismissedThisDeviceToast = false; - // cache of the key backup info - private keyBackupInfo: IKeyBackupInfo | null = null; + /** Cache of the info about the current key backup on the server. */ + private keyBackupInfo: KeyBackupInfo | null = null; + /** When `keyBackupInfo` was last updated */ private keyBackupFetchedAt: number | null = null; - private keyBackupStatusChecked = false; // We keep a list of our own device IDs so we can batch ones that were already // there the last time the app launched into a single toast, but display new // ones in their own toasts. @@ -243,9 +243,14 @@ export default class DeviceListener { this.updateClientInformation(); }; - // The server doesn't tell us when key backup is set up, so we poll - // & cache the result - private async getKeyBackupInfo(): Promise { + /** + * Fetch the key backup information from the server. + * + * The result is cached for `KEY_BACKUP_POLL_INTERVAL` ms to avoid repeated API calls. + * + * @returns The key backup info from the server, or `null` if there is no key backup. + */ + private async getKeyBackupInfo(): Promise { if (!this.client) return null; const now = new Date().getTime(); if ( @@ -402,18 +407,23 @@ export default class DeviceListener { this.displayingToastsForDeviceIds = newUnverifiedDeviceIds; } + /** + * Check if key backup is enabled, and if not, raise an `Action.ReportKeyBackupNotEnabled` event (which will + * trigger an auto-rageshake). + */ private checkKeyBackupStatus = async (): Promise => { if (this.keyBackupStatusChecked || !this.client) { return; } - // returns null when key backup status hasn't finished being checked - const isKeyBackupEnabled = this.client.getKeyBackupEnabled(); - this.keyBackupStatusChecked = isKeyBackupEnabled !== null; + const activeKeyBackupVersion = await this.client.getCrypto()?.getActiveSessionBackupVersion(); + // if key backup is enabled, no need to check this ever again (XXX: why only when it is enabled?) + this.keyBackupStatusChecked = !!activeKeyBackupVersion; - if (isKeyBackupEnabled === false) { + if (!activeKeyBackupVersion) { dis.dispatch({ action: Action.ReportKeyBackupNotEnabled }); } }; + private keyBackupStatusChecked = false; private onRecordClientInformationSettingChange: CallbackFn = ( _originalSettingName, diff --git a/test/DeviceListener-test.ts b/test/DeviceListener-test.ts index e1d7c1414c..aa6b14af7b 100644 --- a/test/DeviceListener-test.ts +++ b/test/DeviceListener-test.ts @@ -92,6 +92,7 @@ describe("DeviceListener", () => { isCrossSigningReady: jest.fn().mockResolvedValue(true), isSecretStorageReady: jest.fn().mockResolvedValue(true), userHasCrossSigningKeys: jest.fn(), + getActiveSessionBackupVersion: jest.fn(), } as unknown as Mocked; mockClient = getMockClientWithEventEmitter({ isGuest: jest.fn(), @@ -101,7 +102,6 @@ describe("DeviceListener", () => { getRooms: jest.fn().mockReturnValue([]), isVersionSupported: jest.fn().mockResolvedValue(true), isInitialSyncComplete: jest.fn().mockReturnValue(true), - getKeyBackupEnabled: jest.fn(), waitForClientWellKnown: jest.fn(), isRoomEncrypted: jest.fn(), getClientWellKnown: jest.fn(), @@ -337,7 +337,7 @@ describe("DeviceListener", () => { mockCrypto!.userHasCrossSigningKeys.mockResolvedValue(true); await createAndStart(); - expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled(); + expect(mockCrypto!.getActiveSessionBackupVersion).toHaveBeenCalled(); }); }); @@ -362,8 +362,7 @@ describe("DeviceListener", () => { it("checks keybackup status when cross signing and secret storage are ready", async () => { // default mocks set cross signing and secret storage to ready await createAndStart(); - expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled(); - expect(mockDispatcher.dispatch).not.toHaveBeenCalled(); + expect(mockCrypto.getActiveSessionBackupVersion).toHaveBeenCalled(); }); it("checks keybackup status when setup encryption toast has been dismissed", async () => { @@ -373,40 +372,25 @@ describe("DeviceListener", () => { instance.dismissEncryptionSetup(); await flushPromises(); - expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled(); - }); - - it("does not dispatch keybackup event when key backup check is not finished", async () => { - // returns null when key backup status hasn't finished being checked - mockClient!.getKeyBackupEnabled.mockReturnValue(null); - await createAndStart(); - expect(mockDispatcher.dispatch).not.toHaveBeenCalled(); + expect(mockCrypto.getActiveSessionBackupVersion).toHaveBeenCalled(); }); it("dispatches keybackup event when key backup is not enabled", async () => { - mockClient!.getKeyBackupEnabled.mockReturnValue(false); + mockCrypto.getActiveSessionBackupVersion.mockResolvedValue(null); await createAndStart(); expect(mockDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ReportKeyBackupNotEnabled }); }); it("does not check key backup status again after check is complete", async () => { - mockClient!.getKeyBackupEnabled.mockReturnValue(null); + mockCrypto.getActiveSessionBackupVersion.mockResolvedValue("1"); const instance = await createAndStart(); - expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalled(); - - // keyback check now complete - mockClient!.getKeyBackupEnabled.mockReturnValue(true); + expect(mockCrypto.getActiveSessionBackupVersion).toHaveBeenCalled(); // trigger a recheck instance.dismissEncryptionSetup(); await flushPromises(); - expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalledTimes(2); - - // trigger another recheck - instance.dismissEncryptionSetup(); - await flushPromises(); // not called again, check was complete last time - expect(mockClient!.getKeyBackupEnabled).toHaveBeenCalledTimes(2); + expect(mockCrypto.getActiveSessionBackupVersion).toHaveBeenCalledTimes(1); }); });