From 51ec7f04b428dfa06ce277001fb7cfaa38afb1c8 Mon Sep 17 00:00:00 2001
From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Date: Wed, 20 Sep 2023 13:34:18 +0200
Subject: [PATCH] DeviceListener: Remove usage of deprecated keybackup API
 (#11614)

Primarily this means calling `CryptoApi.getActiveSessionBackupVersion` instead
of `MatrixClisnt.getKeyBackupEnabled`
---
 src/DeviceListener.ts       | 32 +++++++++++++++++++++-----------
 test/DeviceListener-test.ts | 32 ++++++++------------------------
 2 files changed, 29 insertions(+), 35 deletions(-)

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<string>();
     // 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<IKeyBackupInfo | null> {
+    /**
+     * 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<KeyBackupInfo | null> {
         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<void> => {
         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<CryptoApi>;
         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);
             });
         });