Skip to content

Commit 4a231c6

Browse files
authored
Initial support for runtime modules (#29104)
* Initial runtime Modules work Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Iterate Signed-off-by: Michael Telatynski <[email protected]> * Comments Signed-off-by: Michael Telatynski <[email protected]> --------- Signed-off-by: Michael Telatynski <[email protected]>
1 parent 3c690e6 commit 4a231c6

22 files changed

+209
-191
lines changed

docs/config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,3 +592,4 @@ The following are undocumented or intended for developer use only.
592592
2. `sync_timeline_limit`
593593
3. `dangerously_allow_unsafe_and_insecure_passwords`
594594
4. `latex_maths_delims`: An optional setting to override the default delimiters used for maths parsing. See https://github.com/matrix-org/matrix-react-sdk/pull/5939 for details. Only used when `feature_latex_maths` is enabled.
595+
5. `modules`: An optional list of modules to load. This is used for testing and development purposes only.

jest.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const config: Config = {
3838
"^!!raw-loader!.*": "jest-raw-loader",
3939
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
4040
"^fetch-mock$": "<rootDir>/node_modules/fetch-mock",
41+
// Requires ESM which is incompatible with our current Jest setup
42+
"^@element-hq/element-web-module-api$": "<rootDir>/__mocks__/empty.js",
4143
},
4244
transformIgnorePatterns: ["/node_modules/(?!(mime|matrix-js-sdk)).+$"],
4345
collectCoverageFrom: [

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
},
8181
"dependencies": {
8282
"@babel/runtime": "^7.12.5",
83+
"@element-hq/element-web-module-api": "^0.1.1",
8384
"@fontsource/inconsolata": "^5",
8485
"@fontsource/inter": "^5",
8586
"@formatjs/intl-segmenter": "^11.5.7",

playwright/e2e/modules/loader.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2025 New Vector Ltd.
3+
4+
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { test, expect } from "../../element-web-test";
9+
10+
test.describe("Module loading", () => {
11+
test.use({
12+
displayName: "Manny",
13+
});
14+
15+
test.describe("Example Module", () => {
16+
test.use({
17+
config: {
18+
modules: ["/modules/example-module.js"],
19+
},
20+
page: async ({ page }, use) => {
21+
await page.route("/modules/example-module.js", async (route) => {
22+
await route.fulfill({ path: "playwright/sample-files/example-module.js" });
23+
});
24+
await use(page);
25+
},
26+
});
27+
28+
test("should show alert", async ({ page }) => {
29+
const dialogPromise = page.waitForEvent("dialog");
30+
await page.goto("/");
31+
const dialog = await dialogPromise;
32+
expect(dialog.message()).toBe("Testing module loading successful!");
33+
});
34+
});
35+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
Copyright 2025 New Vector Ltd.
3+
4+
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
export default class ExampleModule {
9+
static moduleApiVersion = "^0.1.0";
10+
constructor(api) {
11+
this.api = api;
12+
}
13+
async load() {
14+
alert("Testing module loading successful!");
15+
}
16+
}

src/@types/global.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
1010
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
1111
import "@types/modernizr";
1212

13+
import type { ModuleLoader } from "@element-hq/element-web-module-api";
1314
import type { logger } from "matrix-js-sdk/src/logger";
1415
import type ContentMessages from "../ContentMessages";
1516
import { type IMatrixClientPeg } from "../MatrixClientPeg";
@@ -45,6 +46,7 @@ import { type MatrixDispatcher } from "../dispatcher/dispatcher";
4546
import { type DeepReadonly } from "./common";
4647
import type MatrixChat from "../components/structures/MatrixChat";
4748
import { type InitialCryptoSetupStore } from "../stores/InitialCryptoSetupStore";
49+
import { type ModuleApiType } from "../modules/Api.ts";
4850

4951
/* eslint-disable @typescript-eslint/naming-convention */
5052

@@ -122,6 +124,8 @@ declare global {
122124
mxRoomScrollStateStore?: RoomScrollStateStore;
123125
mxActiveWidgetStore?: ActiveWidgetStore;
124126
mxOnRecaptchaLoaded?: () => void;
127+
mxModuleLoader: ModuleLoader;
128+
mxModuleApi: ModuleApiType;
125129

126130
// electron-only
127131
electron?: Electron;

src/IConfigOptions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ export interface IConfigOptions {
206206
policy_uri?: string;
207207
contacts?: string[];
208208
};
209+
210+
modules?: string[];
209211
}
210212

211213
export interface ISsoRedirectOptions {

src/customisations/Alias.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
10-
function getDisplayAliasForAliasSet(canonicalAlias: string | null, altAliases: string[]): string | null {
11-
// E.g. prefer one of the aliases over another
12-
return null;
13-
}
14-
15-
// This interface summarises all available customisation points and also marks
16-
// them all as optional. This allows customisers to only define and export the
17-
// customisations they need while still maintaining type safety.
18-
export interface IAliasCustomisations {
19-
getDisplayAliasForAliasSet?: typeof getDisplayAliasForAliasSet;
20-
}
9+
import type { AliasCustomisations } from "@element-hq/element-web-module-api";
2110

2211
// A real customisation module will define and export one or more of the
23-
// customisation points that make up `IAliasCustomisations`.
24-
export default {} as IAliasCustomisations;
12+
// customisation points that make up `AliasCustomisations`.
13+
export default {} as AliasCustomisations;

src/customisations/ChatExport.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9+
import { type ChatExportCustomisations } from "@element-hq/element-web-module-api";
10+
911
import { type ExportFormat, type ExportType } from "../utils/exportUtils/exportUtils";
1012

11-
export type ForceChatExportParameters = {
12-
format?: ExportFormat;
13-
range?: ExportType;
14-
// must be < 10**8
15-
// only used when range is 'LastNMessages'
16-
// default is 100
17-
numberOfMessages?: number;
18-
includeAttachments?: boolean;
19-
// maximum size of exported archive
20-
// must be > 0 and < 8000
21-
sizeMb?: number;
22-
};
13+
export type ForceChatExportParameters = ReturnType<
14+
ChatExportCustomisations<ExportFormat, ExportType>["getForceChatExportParameters"]
15+
>;
2316

2417
/**
2518
* Force parameters in room chat export
@@ -30,15 +23,8 @@ const getForceChatExportParameters = (): ForceChatExportParameters => {
3023
return {};
3124
};
3225

33-
// This interface summarises all available customisation points and also marks
34-
// them all as optional. This allows customisers to only define and export the
35-
// customisations they need while still maintaining type safety.
36-
export interface IChatExportCustomisations {
37-
getForceChatExportParameters: typeof getForceChatExportParameters;
38-
}
39-
4026
// A real customisation module will define and export one or more of the
4127
// customisation points that make up `IChatExportCustomisations`.
4228
export default {
4329
getForceChatExportParameters,
44-
} as IChatExportCustomisations;
30+
} as ChatExportCustomisations<ExportFormat, ExportType>;

src/customisations/ComponentVisibility.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,7 @@ Please see LICENSE files in the repository root for full details.
1212

1313
// Populate this class with the details of your customisations when copying it.
1414

15-
import { type UIComponent } from "../settings/UIFeature";
16-
17-
/**
18-
* Determines whether or not the active MatrixClient user should be able to use
19-
* the given UI component. If shown, the user might still not be able to use the
20-
* component depending on their contextual permissions. For example, invite options
21-
* might be shown to the user but they won't have permission to invite users to
22-
* the current room: the button will appear disabled.
23-
* @param {UIComponent} component The component to check visibility for.
24-
* @returns {boolean} True (default) if the user is able to see the component, false
25-
* otherwise.
26-
*/
27-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
28-
function shouldShowComponent(component: UIComponent): boolean {
29-
return true; // default to visible
30-
}
31-
32-
// This interface summarises all available customisation points and also marks
33-
// them all as optional. This allows customisers to only define and export the
34-
// customisations they need while still maintaining type safety.
35-
export interface IComponentVisibilityCustomisations {
36-
shouldShowComponent?: typeof shouldShowComponent;
37-
}
15+
import { type ComponentVisibilityCustomisations as IComponentVisibilityCustomisations } from "@element-hq/element-web-module-api";
3816

3917
// A real customisation module will define and export one or more of the
4018
// customisation points that make up the interface above.

0 commit comments

Comments
 (0)