Replace SecurityCustomisations
with CryptoSetupExtension
(#12342)
* Changed call sites from customisations/security to ModuleRunner.extensions * Updated depenndecy and added tests * Fixed style and formatting with prettier * Fix according to Element PR comments * Fixing issues raised in PR review * Removed commented code. Improved encapsulation. Removed noisy logging * Improved language of comment about calling the factory * Refactor to get better encapsulation * Find a better name. Provide explicit reset function. Provide more TSDoc * Simplify mock for cryptoSetup, and add assertion for exception message. * Remove unused className property. Adjust TSDoc comments * Fix linting and code style issues * Added test to ensure we canregister anduse experimental extensions * Fix linting and code-style issues * Added test to ensure only on registration of experimental extensions * Added test toensure call to getDehydratedDeviceCallback() * Test what happens when there is no implementation * Iterating cryptoSetup tests * Lint/prettier fix * Assert both branches when checking for dehydrationkey callback * Update src/modules/ModuleRunner.ts Language and formatting Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Reset by setting a fresh ExtensionsManager Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Use regular comment instead of TSDoc style comment Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update test/MatrixClientPeg-test.ts No need to extend the base class Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Fix spelling Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Fix spelling Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/modules/ModuleRunner.ts Fix TSDoc formatting Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Simplify mock setup * Simplified mock and cleaned up a bit * Keeping track of extensions is an implementation detail internal to ExtensionsManager. Language and punctuation * Addressed issues and comments from PR review * Update src/modules/ModuleRunner.ts Keep the flags to track implementations as direct properties Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Fix flattening of implementation map * Update src/modules/ModuleRunner.ts Fix whitespace Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
parent
313b556044
commit
6392759bec
13 changed files with 361 additions and 28 deletions
|
@ -71,7 +71,7 @@
|
|||
"@matrix-org/emojibase-bindings": "^1.1.2",
|
||||
"@matrix-org/matrix-wysiwyg": "2.17.0",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"@matrix-org/react-sdk-module-api": "^2.3.0",
|
||||
"@matrix-org/react-sdk-module-api": "^2.4.0",
|
||||
"@matrix-org/spec": "^1.7.0",
|
||||
"@sentry/browser": "^7.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
|
|
|
@ -24,7 +24,7 @@ import { QueryDict } from "matrix-js-sdk/src/utils";
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { IMatrixClientCreds, MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import SecurityCustomisations from "./customisations/Security";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
import EventIndexPeg from "./indexing/EventIndexPeg";
|
||||
import createMatrixClient from "./utils/createMatrixClient";
|
||||
import Notifier from "./Notifier";
|
||||
|
@ -863,7 +863,7 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
|
|||
localStorage.setItem("mx_device_id", credentials.deviceId);
|
||||
}
|
||||
|
||||
SecurityCustomisations.persistCredentials?.(credentials);
|
||||
ModuleRunner.instance.extensions.cryptoSetup?.persistCredentials(credentials);
|
||||
|
||||
logger.log(`Session persisted for ${credentials.userId}`);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { IMatrixClientCreds } from "./MatrixClientPeg";
|
||||
import SecurityCustomisations from "./customisations/Security";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
import { getOidcClientId } from "./utils/oidc/registerClient";
|
||||
import { IConfigOptions } from "./IConfigOptions";
|
||||
import SdkConfig from "./SdkConfig";
|
||||
|
@ -291,7 +291,7 @@ export async function sendLoginRequest(
|
|||
accessToken: data.access_token,
|
||||
};
|
||||
|
||||
SecurityCustomisations.examineLoginResponse?.(data, creds);
|
||||
ModuleRunner.instance.extensions.cryptoSetup.examineLoginResponse(data, creds);
|
||||
|
||||
return creds;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientB
|
|||
import * as StorageManager from "./utils/StorageManager";
|
||||
import IdentityAuthClient from "./IdentityAuthClient";
|
||||
import { crossSigningCallbacks, tryToUnlockSecretStorageWithDehydrationKey } from "./SecurityManager";
|
||||
import SecurityCustomisations from "./customisations/Security";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
import { SlidingSyncManager } from "./SlidingSyncManager";
|
||||
import CryptoStoreTooNewDialog from "./components/views/dialogs/CryptoStoreTooNewDialog";
|
||||
import { _t, UserFriendlyError } from "./languageHandler";
|
||||
|
@ -463,8 +463,9 @@ class MatrixClientPegClass implements IMatrixClientPeg {
|
|||
},
|
||||
};
|
||||
|
||||
if (SecurityCustomisations.getDehydrationKey) {
|
||||
opts.cryptoCallbacks!.getDehydrationKey = SecurityCustomisations.getDehydrationKey;
|
||||
const dehydrationKeyCallback = ModuleRunner.instance.extensions.cryptoSetup.getDehydrationKeyCallback();
|
||||
if (dehydrationKeyCallback) {
|
||||
opts.cryptoCallbacks!.getDehydrationKey = dehydrationKeyCallback;
|
||||
}
|
||||
|
||||
this.matrixClient = createMatrixClient(opts);
|
||||
|
|
|
@ -33,7 +33,7 @@ import { isSecureBackupRequired } from "./utils/WellKnownUtils";
|
|||
import AccessSecretStorageDialog, { KeyParams } from "./components/views/dialogs/security/AccessSecretStorageDialog";
|
||||
import RestoreKeyBackupDialog from "./components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import SecurityCustomisations from "./customisations/Security";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
|
||||
|
||||
|
@ -137,9 +137,9 @@ async function getSecretStorageKey({
|
|||
}
|
||||
}
|
||||
|
||||
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
||||
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
if (keyFromCustomisations) {
|
||||
logger.log("Using key from security customisations (secret storage)");
|
||||
logger.log("CryptoSetupExtension: Using key from extension (secret storage)");
|
||||
cacheSecretStorageKey(keyId, keyInfo, keyFromCustomisations);
|
||||
return [keyId, keyFromCustomisations];
|
||||
}
|
||||
|
@ -187,9 +187,9 @@ export async function getDehydrationKey(
|
|||
keyInfo: SecretStorage.SecretStorageKeyDescription,
|
||||
checkFunc: (data: Uint8Array) => void,
|
||||
): Promise<Uint8Array> {
|
||||
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
||||
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
if (keyFromCustomisations) {
|
||||
logger.log("Using key from security customisations (dehydration)");
|
||||
logger.log("CryptoSetupExtension: Using key from extension (dehydration)");
|
||||
return keyFromCustomisations;
|
||||
}
|
||||
|
||||
|
@ -430,7 +430,7 @@ async function doAccessSecretStorage(func: () => Promise<void>, forceReset: bool
|
|||
// inner operation completes.
|
||||
return await func();
|
||||
} catch (e) {
|
||||
SecurityCustomisations.catchAccessSecretStorageError?.(e);
|
||||
ModuleRunner.instance.extensions.cryptoSetup.catchAccessSecretStorageError(e as Error);
|
||||
logger.error(e);
|
||||
// Re-throw so that higher level logic can abort as needed
|
||||
throw e;
|
||||
|
|
|
@ -40,7 +40,7 @@ import {
|
|||
isSecureBackupRequired,
|
||||
SecureBackupSetupMethod,
|
||||
} from "../../../../utils/WellKnownUtils";
|
||||
import SecurityCustomisations from "../../../../customisations/Security";
|
||||
import { ModuleRunner } from "../../../../modules/ModuleRunner";
|
||||
import Field from "../../../../components/views/elements/Field";
|
||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||
import Spinner from "../../../../components/views/elements/Spinner";
|
||||
|
@ -180,9 +180,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
}
|
||||
|
||||
private getInitialPhase(): void {
|
||||
const keyFromCustomisations = SecurityCustomisations.createSecretStorageKey?.();
|
||||
const keyFromCustomisations = ModuleRunner.instance.extensions.cryptoSetup.createSecretStorageKey();
|
||||
if (keyFromCustomisations) {
|
||||
logger.log("Created key via customisations, jumping to bootstrap step");
|
||||
logger.log("CryptoSetupExtension: Created key via extension, jumping to bootstrap step");
|
||||
this.recoveryKey = {
|
||||
privateKey: keyFromCustomisations,
|
||||
};
|
||||
|
|
|
@ -88,7 +88,7 @@ import { showToast as showMobileGuideToast } from "../../toasts/MobileGuideToast
|
|||
import { shouldUseLoginForWelcome } from "../../utils/pages";
|
||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
import { RoomUpdateCause } from "../../stores/room-list/models";
|
||||
import SecurityCustomisations from "../../customisations/Security";
|
||||
import { ModuleRunner } from "../../modules/ModuleRunner";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
||||
import UserSettingsDialog from "../views/dialogs/UserSettingsDialog";
|
||||
|
@ -442,7 +442,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
if (crossSigningIsSetUp) {
|
||||
// if the user has previously set up cross-signing, verify this device so we can fetch the
|
||||
// private keys.
|
||||
if (SecurityCustomisations.SHOW_ENCRYPTION_SETUP_UI === false) {
|
||||
|
||||
const cryptoExtension = ModuleRunner.instance.extensions.cryptoSetup;
|
||||
if (cryptoExtension.SHOW_ENCRYPTION_SETUP_UI == false) {
|
||||
this.onLoggedIn();
|
||||
} else {
|
||||
this.setStateForNewView({ view: Views.COMPLETE_SECURITY });
|
||||
|
|
|
@ -17,18 +17,108 @@ limitations under the License.
|
|||
import { safeSet } from "matrix-js-sdk/src/utils";
|
||||
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api/lib/types/translations";
|
||||
import { AnyLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/types";
|
||||
import {
|
||||
DefaultCryptoSetupExtensions,
|
||||
ProvideCryptoSetupExtensions,
|
||||
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
|
||||
import {
|
||||
DefaultExperimentalExtensions,
|
||||
ProvideExperimentalExtensions,
|
||||
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
|
||||
|
||||
import { AppModule } from "./AppModule";
|
||||
import { ModuleFactory } from "./ModuleFactory";
|
||||
|
||||
import "./ModuleComponents";
|
||||
|
||||
/**
|
||||
* Handles and manages extensions provided by modules.
|
||||
*/
|
||||
class ExtensionsManager {
|
||||
// Private backing fields for extensions
|
||||
private cryptoSetupExtension: ProvideCryptoSetupExtensions;
|
||||
private experimentalExtension: ProvideExperimentalExtensions;
|
||||
|
||||
/** `true` if `cryptoSetupExtension` is the default implementation; `false` if it is implemented by a module. */
|
||||
private hasDefaultCryptoSetupExtension = true;
|
||||
|
||||
/** `true` if `experimentalExtension` is the default implementation; `false` if it is implemented by a module. */
|
||||
private hasDefaultExperimentalExtension = true;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*/
|
||||
public constructor() {
|
||||
// Set up defaults
|
||||
this.cryptoSetupExtension = new DefaultCryptoSetupExtensions();
|
||||
this.experimentalExtension = new DefaultExperimentalExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a crypto setup extension.
|
||||
*
|
||||
* @returns The registered extension. If no module provides this extension, a default implementation is returned.
|
||||
*/
|
||||
public get cryptoSetup(): ProvideCryptoSetupExtensions {
|
||||
return this.cryptoSetupExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an experimental extension.
|
||||
*
|
||||
* @remarks
|
||||
* This method extension is provided to simplify experimentation and development, and is not intended for production code.
|
||||
*
|
||||
* @returns The registered extension. If no module provides this extension, a default implementation is returned.
|
||||
*/
|
||||
public get experimental(): ProvideExperimentalExtensions {
|
||||
return this.experimentalExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any extensions provided by the module.
|
||||
*
|
||||
* @param module - The appModule to check for extensions.
|
||||
*
|
||||
* @throws if an extension is provided by more than one module.
|
||||
*/
|
||||
public addExtensions(module: AppModule): void {
|
||||
const runtimeModule = module.module;
|
||||
|
||||
/* Add the cryptoSetup extension if any */
|
||||
if (runtimeModule.extensions?.cryptoSetup) {
|
||||
if (this.hasDefaultCryptoSetupExtension) {
|
||||
this.cryptoSetupExtension = runtimeModule.extensions?.cryptoSetup;
|
||||
this.hasDefaultCryptoSetupExtension = false;
|
||||
} else {
|
||||
throw new Error(
|
||||
`adding cryptoSetup extension implementation from module ${runtimeModule.moduleName} but an implementation was already provided.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add the experimental extension if any */
|
||||
if (runtimeModule.extensions?.experimental) {
|
||||
if (this.hasDefaultExperimentalExtension) {
|
||||
this.experimentalExtension = runtimeModule.extensions?.experimental;
|
||||
this.hasDefaultExperimentalExtension = false;
|
||||
} else {
|
||||
throw new Error(
|
||||
`adding experimental extension implementation from module ${runtimeModule.moduleName} but an implementation was already provided.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles and coordinates the operation of modules.
|
||||
*/
|
||||
export class ModuleRunner {
|
||||
public static readonly instance = new ModuleRunner();
|
||||
|
||||
private extensionsManager = new ExtensionsManager();
|
||||
|
||||
private modules: AppModule[] = [];
|
||||
|
||||
private constructor() {
|
||||
|
@ -36,12 +126,22 @@ export class ModuleRunner {
|
|||
}
|
||||
|
||||
/**
|
||||
* Resets the runner, clearing all known modules.
|
||||
* Exposes all extensions which may be overridden/provided by modules.
|
||||
*
|
||||
* @returns An `ExtensionsManager` which exposes the extensions.
|
||||
*/
|
||||
public get extensions(): ExtensionsManager {
|
||||
return this.extensionsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the runner, clearing all known modules, and all extensions
|
||||
*
|
||||
* Intended for test usage only.
|
||||
*/
|
||||
public reset(): void {
|
||||
this.modules = [];
|
||||
this.extensionsManager = new ExtensionsManager();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,7 +172,12 @@ export class ModuleRunner {
|
|||
* @param factory The module factory.
|
||||
*/
|
||||
public registerModule(factory: ModuleFactory): void {
|
||||
this.modules.push(new AppModule(factory));
|
||||
const appModule = new AppModule(factory);
|
||||
|
||||
this.modules.push(appModule);
|
||||
|
||||
// Check if the new module provides any extensions, and also ensure a given extension is only provided by a single runtime module.
|
||||
this.extensionsManager.addExtensions(appModule);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,8 @@ import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEnc
|
|||
import { accessSecretStorage } from "../SecurityManager";
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
import GenericToast from "../components/views/toasts/GenericToast";
|
||||
import SecurityCustomisations from "../customisations/Security";
|
||||
import { ModuleRunner } from "../modules/ModuleRunner";
|
||||
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
||||
import Spinner from "../components/views/elements/Spinner";
|
||||
|
||||
const TOAST_KEY = "setupencryption";
|
||||
|
@ -79,7 +80,12 @@ const onReject = (): void => {
|
|||
};
|
||||
|
||||
export const showToast = (kind: Kind): void => {
|
||||
if (SecurityCustomisations.setupEncryptionNeeded?.(kind)) {
|
||||
if (
|
||||
ModuleRunner.instance.extensions.cryptoSetup.setupEncryptionNeeded({
|
||||
kind: kind as any,
|
||||
storeProvider: { getInstance: () => SetupEncryptionStore.sharedInstance() },
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,10 @@ limitations under the License.
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import fetchMockJest from "fetch-mock-jest";
|
||||
import EventEmitter from "events";
|
||||
import {
|
||||
ProvideCryptoSetupExtensions,
|
||||
SecretStorageKeyDescription,
|
||||
} from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
|
||||
|
||||
import { advanceDateAndTime, stubClient } from "./test-utils";
|
||||
import { IMatrixClientPeg, MatrixClientPeg as peg } from "../src/MatrixClientPeg";
|
||||
|
@ -25,6 +29,7 @@ import Modal from "../src/Modal";
|
|||
import PlatformPeg from "../src/PlatformPeg";
|
||||
import { SettingLevel } from "../src/settings/SettingLevel";
|
||||
import { Features } from "../src/settings/Settings";
|
||||
import { ModuleRunner } from "../src/modules/ModuleRunner";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
|
@ -77,6 +82,78 @@ describe("MatrixClientPeg", () => {
|
|||
expect(peg.userRegisteredWithinLastHours(24)).toBe(false);
|
||||
});
|
||||
|
||||
describe(".start extensions", () => {
|
||||
let testPeg: IMatrixClientPeg;
|
||||
|
||||
beforeEach(() => {
|
||||
// instantiate a MatrixClientPegClass instance, with a new MatrixClient
|
||||
testPeg = new PegClass();
|
||||
fetchMockJest.get("http://example.com/_matrix/client/versions", {});
|
||||
});
|
||||
|
||||
describe("cryptoSetup extension", () => {
|
||||
it("should call default cryptoSetup.getDehydrationKeyCallback", async () => {
|
||||
const mockCryptoSetup = {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn(),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn().mockReturnValue(null),
|
||||
} as ProvideCryptoSetupExtensions;
|
||||
|
||||
// Ensure we have an instance before we set up spies
|
||||
const instance = ModuleRunner.instance;
|
||||
jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup);
|
||||
|
||||
testPeg.replaceUsingCreds({
|
||||
accessToken: "SEKRET",
|
||||
homeserverUrl: "http://example.com",
|
||||
userId: "@user:example.com",
|
||||
deviceId: "TEST_DEVICE_ID",
|
||||
});
|
||||
|
||||
expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call overridden cryptoSetup.getDehydrationKeyCallback", async () => {
|
||||
const mockDehydrationKeyCallback = () => Uint8Array.from([0x11, 0x22, 0x33]);
|
||||
|
||||
const mockCryptoSetup = {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn(),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn().mockReturnValue(mockDehydrationKeyCallback),
|
||||
} as ProvideCryptoSetupExtensions;
|
||||
|
||||
// Ensure we have an instance before we set up spies
|
||||
const instance = ModuleRunner.instance;
|
||||
jest.spyOn(instance.extensions, "cryptoSetup", "get").mockReturnValue(mockCryptoSetup);
|
||||
|
||||
testPeg.replaceUsingCreds({
|
||||
accessToken: "SEKRET",
|
||||
homeserverUrl: "http://example.com",
|
||||
userId: "@user:example.com",
|
||||
deviceId: "TEST_DEVICE_ID",
|
||||
});
|
||||
expect(mockCryptoSetup.getDehydrationKeyCallback).toHaveBeenCalledTimes(1);
|
||||
|
||||
const client = testPeg.get();
|
||||
const dehydrationKey = await client?.cryptoCallbacks.getDehydrationKey!(
|
||||
{} as SecretStorageKeyDescription,
|
||||
(key: Uint8Array) => true,
|
||||
);
|
||||
expect(dehydrationKey).toEqual(Uint8Array.from([0x11, 0x22, 0x33]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(".start", () => {
|
||||
let testPeg: IMatrixClientPeg;
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ limitations under the License.
|
|||
|
||||
import { RuntimeModule } from "@matrix-org/react-sdk-module-api/lib/RuntimeModule";
|
||||
import { ModuleApi } from "@matrix-org/react-sdk-module-api/lib/ModuleApi";
|
||||
import { AllExtensions } from "@matrix-org/react-sdk-module-api/lib/types/extensions";
|
||||
import { ProvideCryptoSetupExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/CryptoSetupExtensions";
|
||||
import { ProvideExperimentalExtensions } from "@matrix-org/react-sdk-module-api/lib/lifecycles/ExperimentalExtensions";
|
||||
|
||||
import { ModuleRunner } from "../../src/modules/ModuleRunner";
|
||||
|
||||
|
@ -29,6 +32,11 @@ export class MockModule extends RuntimeModule {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModule(): MockModule {
|
||||
let module: MockModule | undefined;
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
|
@ -43,3 +51,88 @@ export function registerMockModule(): MockModule {
|
|||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
class MockModuleWithCryptoSetupExtension extends RuntimeModule {
|
||||
public get apiInstance(): ModuleApi {
|
||||
return this.moduleApi;
|
||||
}
|
||||
|
||||
moduleName: string = MockModuleWithCryptoSetupExtension.name;
|
||||
|
||||
extensions: AllExtensions = {
|
||||
cryptoSetup: {
|
||||
SHOW_ENCRYPTION_SETUP_UI: true,
|
||||
examineLoginResponse: jest.fn(),
|
||||
persistCredentials: jest.fn(),
|
||||
getSecretStorageKey: jest.fn().mockReturnValue(Uint8Array.from([0x11, 0x22, 0x99])),
|
||||
createSecretStorageKey: jest.fn(),
|
||||
catchAccessSecretStorageError: jest.fn(),
|
||||
setupEncryptionNeeded: jest.fn(),
|
||||
getDehydrationKeyCallback: jest.fn(),
|
||||
} as ProvideCryptoSetupExtensions,
|
||||
};
|
||||
|
||||
public constructor(moduleApi: ModuleApi) {
|
||||
super(moduleApi);
|
||||
}
|
||||
}
|
||||
|
||||
class MockModuleWithExperimentalExtension extends RuntimeModule {
|
||||
public get apiInstance(): ModuleApi {
|
||||
return this.moduleApi;
|
||||
}
|
||||
|
||||
moduleName: string = MockModuleWithExperimentalExtension.name;
|
||||
|
||||
extensions: AllExtensions = {
|
||||
experimental: {
|
||||
experimentalMethod: jest.fn().mockReturnValue(Uint8Array.from([0x22, 0x44, 0x88])),
|
||||
} as ProvideExperimentalExtensions,
|
||||
};
|
||||
|
||||
public constructor(moduleApi: ModuleApi) {
|
||||
super(moduleApi);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module which implements the cryptoSetup extension.
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModuleWithCryptoSetupExtension(): MockModuleWithCryptoSetupExtension {
|
||||
let module: MockModuleWithCryptoSetupExtension | undefined;
|
||||
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
if (module) {
|
||||
throw new Error("State machine error: ModuleRunner created the module twice");
|
||||
}
|
||||
module = new MockModuleWithCryptoSetupExtension(api);
|
||||
return module;
|
||||
});
|
||||
if (!module) {
|
||||
throw new Error("State machine error: ModuleRunner did not create module");
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mock module which implements the experimental extension.
|
||||
*
|
||||
* @returns The registered module.
|
||||
*/
|
||||
export function registerMockModuleWithExperimentalExtension(): MockModuleWithExperimentalExtension {
|
||||
let module: MockModuleWithExperimentalExtension | undefined;
|
||||
|
||||
ModuleRunner.instance.registerModule((api) => {
|
||||
if (module) {
|
||||
throw new Error("State machine error: ModuleRunner created the module twice");
|
||||
}
|
||||
module = new MockModuleWithExperimentalExtension(api);
|
||||
return module;
|
||||
});
|
||||
if (!module) {
|
||||
throw new Error("State machine error: ModuleRunner did not create module");
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,12 @@ limitations under the License.
|
|||
|
||||
import { RoomPreviewOpts, RoomViewLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/RoomViewLifecycle";
|
||||
|
||||
import { MockModule, registerMockModule } from "./MockModule";
|
||||
import {
|
||||
MockModule,
|
||||
registerMockModule,
|
||||
registerMockModuleWithCryptoSetupExtension,
|
||||
registerMockModuleWithExperimentalExtension,
|
||||
} from "./MockModule";
|
||||
import { ModuleRunner } from "../../src/modules/ModuleRunner";
|
||||
|
||||
describe("ModuleRunner", () => {
|
||||
|
@ -49,4 +54,48 @@ describe("ModuleRunner", () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extensions", () => {
|
||||
it("should return default values when no crypto-setup extensions are provided by a registered module", async () => {
|
||||
registerMockModule();
|
||||
const result = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return default values when no experimental extensions are provided by a registered module", async () => {
|
||||
registerMockModule();
|
||||
const result = ModuleRunner.instance.extensions?.experimental.experimentalMethod();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return value from crypto-setup-extensions provided by a registered module", async () => {
|
||||
registerMockModuleWithCryptoSetupExtension();
|
||||
const result = ModuleRunner.instance.extensions.cryptoSetup.getSecretStorageKey();
|
||||
expect(result).toEqual(Uint8Array.from([0x11, 0x22, 0x99]));
|
||||
});
|
||||
|
||||
it("should return value from experimental-extensions provided by a registered module", async () => {
|
||||
registerMockModuleWithExperimentalExtension();
|
||||
const result = ModuleRunner.instance.extensions.experimental.experimentalMethod();
|
||||
expect(result).toEqual(Uint8Array.from([0x22, 0x44, 0x88]));
|
||||
});
|
||||
|
||||
it("must not allow multiple modules to provide cryptoSetup extension", async () => {
|
||||
registerMockModuleWithCryptoSetupExtension();
|
||||
const t = () => registerMockModuleWithCryptoSetupExtension();
|
||||
expect(t).toThrow(Error);
|
||||
expect(t).toThrow(
|
||||
"adding cryptoSetup extension implementation from module MockModuleWithCryptoSetupExtension but an implementation was already provided",
|
||||
);
|
||||
});
|
||||
|
||||
it("must not allow multiple modules to provide experimental extension", async () => {
|
||||
registerMockModuleWithExperimentalExtension();
|
||||
const t = () => registerMockModuleWithExperimentalExtension();
|
||||
expect(t).toThrow(Error);
|
||||
expect(t).toThrow(
|
||||
"adding experimental extension implementation from module MockModuleWithExperimentalExtension but an implementation was already provided",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1877,10 +1877,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@matrix-org/olm/-/olm-3.2.15.tgz#55f3c1b70a21bbee3f9195cecd6846b1083451ec"
|
||||
integrity sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==
|
||||
|
||||
"@matrix-org/react-sdk-module-api@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-2.3.0.tgz#85be5cfc73be0494c13d4dc9050cb70c58d7a08b"
|
||||
integrity sha512-x/ie44yaXNtE5AKcmQiW5yINVEIJ7IjjEc35vj6j52fM8tZ9XbJx9PANKSWsdd0NJp3OqyaeHftmN6ESfx4YoQ==
|
||||
"@matrix-org/react-sdk-module-api@^2.4.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/react-sdk-module-api/-/react-sdk-module-api-2.4.0.tgz#5e4552acbe728141f42c1d54d75dcb4efea9301c"
|
||||
integrity sha512-cPb+YaqllfJkRX0ofcG/0YdHxCvcMAvUbdNMO2olpGL8vwbBP6mHdhbZ87z9pgsRIVOqfFuLUE3WeW0hxWrklQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.17.9"
|
||||
|
||||
|
|
Loading…
Reference in a new issue