From 5bb1d3d461e1fec44b7ebf9beeaf2019da79899e Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 18 Feb 2026 13:17:53 +0000 Subject: [PATCH 1/4] Add widget permissions module api --- .../element-web-module-api/src/api/index.ts | 7 ++ .../src/api/widget-lifecycle.ts | 82 +++++++++++++++++++ packages/element-web-module-api/src/index.ts | 1 + 3 files changed, 90 insertions(+) create mode 100644 packages/element-web-module-api/src/api/widget-lifecycle.ts diff --git a/packages/element-web-module-api/src/api/index.ts b/packages/element-web-module-api/src/api/index.ts index 1401e6fc6d..9c5e59ed57 100644 --- a/packages/element-web-module-api/src/api/index.ts +++ b/packages/element-web-module-api/src/api/index.ts @@ -19,6 +19,7 @@ import { type ExtrasApi } from "./extras.ts"; import { type BuiltinsApi } from "./builtins.ts"; import { type StoresApi } from "./stores.ts"; import { type ClientApi } from "./client.ts"; +import { type WidgetLifecycleApi } from "./widget-lifecycle.ts"; /** * Module interface for modules to implement. @@ -136,6 +137,12 @@ export interface Api */ readonly client: ClientApi; + /** + * API for modules to auto-approve widget preloading, identity token requests, and capability requests. + * @alpha Subject to change. + */ + readonly widgetLifecycle: WidgetLifecycleApi; + /** * Create a ReactDOM root for rendering React components. * Exposed to allow modules to avoid needing to bundle their own ReactDOM. diff --git a/packages/element-web-module-api/src/api/widget-lifecycle.ts b/packages/element-web-module-api/src/api/widget-lifecycle.ts new file mode 100644 index 0000000000..dd8125e397 --- /dev/null +++ b/packages/element-web-module-api/src/api/widget-lifecycle.ts @@ -0,0 +1,82 @@ +/* +Copyright 2026 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +/** + * The scope of a widget: room-scoped, user-scoped (account), or an ephemeral modal overlay. + * @alpha Subject to change. + */ +export type WidgetKind = "room" | "account" | "modal"; + +/** + * A description of a widget passed to approver callbacks. + * Contains the information needed to make approval decisions. + * @alpha Subject to change. + */ +export type WidgetDescriptor = { + /** The unique identifier of the widget. */ + id: string; + /** The template URL of the widget, which may contain `$matrix_*` placeholder variables. */ + templateUrl: string; + /** The Matrix user ID of the user who created the widget. */ + creatorUserId: string; + /** The widget type, e.g. `m.custom`, `m.jitsi`, `m.stickerpicker`. */ + type: string; + /** The origin of the widget URL. */ + origin: string; + /** The room ID the widget belongs to, if it is a room widget. */ + roomId?: string; + /** The scope of the widget. */ + kind: WidgetKind; +}; + +/** + * Callback that decides whether a widget should be auto-approved for preloading + * (i.e. loaded without the user clicking "Continue"). + * Return `true` to auto-approve, or any other value to defer to the default consent flow. + * @alpha Subject to change. + */ +export type PreloadApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; +/** + * Callback that decides whether a widget should be auto-approved to receive + * the user's OpenID identity token. + * Return `true` to auto-approve, or any other value to defer to the default consent flow. + * @alpha Subject to change. + */ +export type IdentityApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; +/** + * Callback that decides which of a widget's requested capabilities should be auto-approved. + * Return a `Set` of approved capability strings, or `undefined` to defer to the default consent flow. + * @alpha Subject to change. + */ +export type CapabilitiesApprover = ( + widget: WidgetDescriptor, + requestedCapabilities: Set, +) => Set | Promise | undefined> | undefined; + +/** + * API for modules to auto-approve widget preloading, identity token requests, and capability requests. + * @alpha Subject to change. + */ +export interface WidgetLifecycleApi { + /** + * Register a handler that can auto-approve widget preloading. + * Returning true auto-approves; any other value results in no auto-approval. + */ + registerPreloadApprover(approver: PreloadApprover): void; + + /** + * Register a handler that can auto-approve identity token requests. + * Returning true auto-approves; any other value results in no auto-approval. + */ + registerIdentityApprover(approver: IdentityApprover): void; + + /** + * Register a handler that can auto-approve widget capabilities. + * Return a set containing the capabilities to approve. + */ + registerCapabilitiesApprover(approver: CapabilitiesApprover): void; +} diff --git a/packages/element-web-module-api/src/index.ts b/packages/element-web-module-api/src/index.ts index 52a0593bc5..da9c3669d9 100644 --- a/packages/element-web-module-api/src/index.ts +++ b/packages/element-web-module-api/src/index.ts @@ -22,4 +22,5 @@ export type * from "./api/navigation"; export type * from "./api/builtins"; export type * from "./api/stores"; export type * from "./api/client"; +export type * from "./api/widget-lifecycle"; export * from "./api/watchable"; From a5ec21bd063c06e90c41ab5f694885983c6937aa Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 18 Feb 2026 13:18:03 +0000 Subject: [PATCH 2/4] Update element-web-module-api.api.md --- .../element-web-module-api.api.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/element-web-module-api/element-web-module-api.api.md b/packages/element-web-module-api/element-web-module-api.api.md index d7590281ea..b75e57e9c9 100644 --- a/packages/element-web-module-api/element-web-module-api.api.md +++ b/packages/element-web-module-api/element-web-module-api.api.md @@ -55,6 +55,8 @@ export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiEx readonly navigation: NavigationApi; readonly rootNode: HTMLElement; readonly stores: StoresApi; + // @alpha + readonly widgetLifecycle: WidgetLifecycleApi; } // @alpha @@ -64,6 +66,9 @@ export interface BuiltinsApi { renderRoomView(roomId: string, props?: RoomViewProps): React.ReactNode; } +// @alpha +export type CapabilitiesApprover = (widget: WidgetDescriptor, requestedCapabilities: Set) => Set | Promise | undefined> | undefined; + // @alpha @deprecated (undocumented) export interface ChatExportCustomisations { getForceChatExportParameters(): { @@ -180,6 +185,9 @@ export interface I18nApi { translate(key: keyof Translations, variables?: Variables): string; } +// @alpha +export type IdentityApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; + // @alpha @deprecated (undocumented) export type LegacyCustomisations = (customisations: T) => void; @@ -325,6 +333,9 @@ export type OriginalMessageComponentProps = { showUrlPreview?: boolean; }; +// @alpha +export type PreloadApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; + // @public export interface Profile { displayName?: string; @@ -422,6 +433,27 @@ export class Watchable { watch(listener: (value: T) => void): void; } +// @alpha +export type WidgetDescriptor = { + id: string; + templateUrl: string; + creatorUserId: string; + type: string; + origin: string; + roomId?: string; + kind: WidgetKind; +}; + +// @alpha +export type WidgetKind = "room" | "account" | "modal"; + +// @alpha +export interface WidgetLifecycleApi { + registerCapabilitiesApprover(approver: CapabilitiesApprover): void; + registerIdentityApprover(approver: IdentityApprover): void; + registerPreloadApprover(approver: PreloadApprover): void; +} + // @alpha @deprecated (undocumented) export interface WidgetPermissionsCustomisations { preapproveCapabilities?(widget: Widget, requestedCapabilities: Set): Promise>; From 4614d2f395a328ec501499683baafed5c4c81beb Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 18 Feb 2026 18:43:26 +0000 Subject: [PATCH 3/4] Remove WidgetKind and fix header --- .../element-web-module-api.api.md | 4 ---- .../element-web-module-api/src/api/widget-lifecycle.ts | 10 +--------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/element-web-module-api/element-web-module-api.api.md b/packages/element-web-module-api/element-web-module-api.api.md index b75e57e9c9..bf95140227 100644 --- a/packages/element-web-module-api/element-web-module-api.api.md +++ b/packages/element-web-module-api/element-web-module-api.api.md @@ -441,12 +441,8 @@ export type WidgetDescriptor = { type: string; origin: string; roomId?: string; - kind: WidgetKind; }; -// @alpha -export type WidgetKind = "room" | "account" | "modal"; - // @alpha export interface WidgetLifecycleApi { registerCapabilitiesApprover(approver: CapabilitiesApprover): void; diff --git a/packages/element-web-module-api/src/api/widget-lifecycle.ts b/packages/element-web-module-api/src/api/widget-lifecycle.ts index dd8125e397..9638a1096b 100644 --- a/packages/element-web-module-api/src/api/widget-lifecycle.ts +++ b/packages/element-web-module-api/src/api/widget-lifecycle.ts @@ -1,16 +1,10 @@ /* -Copyright 2026 New Vector Ltd. +Copyright 2026 Element Creations Ltd. SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -/** - * The scope of a widget: room-scoped, user-scoped (account), or an ephemeral modal overlay. - * @alpha Subject to change. - */ -export type WidgetKind = "room" | "account" | "modal"; - /** * A description of a widget passed to approver callbacks. * Contains the information needed to make approval decisions. @@ -29,8 +23,6 @@ export type WidgetDescriptor = { origin: string; /** The room ID the widget belongs to, if it is a room widget. */ roomId?: string; - /** The scope of the widget. */ - kind: WidgetKind; }; /** From 83d752cfd7ed9debd0c32932942eb916a32d138e Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 25 Feb 2026 09:33:48 +0000 Subject: [PATCH 4/4] Add MaybePromise --- .../element-web-module-api.api.md | 9 ++++++--- .../src/api/widget-lifecycle.ts | 8 +++++--- packages/element-web-module-api/src/index.ts | 1 + packages/element-web-module-api/src/utils.ts | 13 +++++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 packages/element-web-module-api/src/utils.ts diff --git a/packages/element-web-module-api/element-web-module-api.api.md b/packages/element-web-module-api/element-web-module-api.api.md index bf95140227..100ed0e0fb 100644 --- a/packages/element-web-module-api/element-web-module-api.api.md +++ b/packages/element-web-module-api/element-web-module-api.api.md @@ -67,7 +67,7 @@ export interface BuiltinsApi { } // @alpha -export type CapabilitiesApprover = (widget: WidgetDescriptor, requestedCapabilities: Set) => Set | Promise | undefined> | undefined; +export type CapabilitiesApprover = (widget: WidgetDescriptor, requestedCapabilities: Set) => MaybePromise | undefined>; // @alpha @deprecated (undocumented) export interface ChatExportCustomisations { @@ -186,7 +186,7 @@ export interface I18nApi { } // @alpha -export type IdentityApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; +export type IdentityApprover = (widget: WidgetDescriptor) => MaybePromise; // @alpha @deprecated (undocumented) export type LegacyCustomisations = (customisations: T) => void; @@ -242,6 +242,9 @@ export interface MatrixEvent { unsigned: Record; } +// @public +export type MaybePromise = T | PromiseLike; + // @alpha @deprecated (undocumented) export interface Media { // (undocumented) @@ -334,7 +337,7 @@ export type OriginalMessageComponentProps = { }; // @alpha -export type PreloadApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; +export type PreloadApprover = (widget: WidgetDescriptor) => MaybePromise; // @public export interface Profile { diff --git a/packages/element-web-module-api/src/api/widget-lifecycle.ts b/packages/element-web-module-api/src/api/widget-lifecycle.ts index 9638a1096b..6258629eab 100644 --- a/packages/element-web-module-api/src/api/widget-lifecycle.ts +++ b/packages/element-web-module-api/src/api/widget-lifecycle.ts @@ -5,6 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ +import type { MaybePromise } from "../utils"; + /** * A description of a widget passed to approver callbacks. * Contains the information needed to make approval decisions. @@ -31,14 +33,14 @@ export type WidgetDescriptor = { * Return `true` to auto-approve, or any other value to defer to the default consent flow. * @alpha Subject to change. */ -export type PreloadApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; +export type PreloadApprover = (widget: WidgetDescriptor) => MaybePromise; /** * Callback that decides whether a widget should be auto-approved to receive * the user's OpenID identity token. * Return `true` to auto-approve, or any other value to defer to the default consent flow. * @alpha Subject to change. */ -export type IdentityApprover = (widget: WidgetDescriptor) => boolean | Promise | undefined; +export type IdentityApprover = (widget: WidgetDescriptor) => MaybePromise; /** * Callback that decides which of a widget's requested capabilities should be auto-approved. * Return a `Set` of approved capability strings, or `undefined` to defer to the default consent flow. @@ -47,7 +49,7 @@ export type IdentityApprover = (widget: WidgetDescriptor) => boolean | Promise, -) => Set | Promise | undefined> | undefined; +) => MaybePromise | undefined>; /** * API for modules to auto-approve widget preloading, identity token requests, and capability requests. diff --git a/packages/element-web-module-api/src/index.ts b/packages/element-web-module-api/src/index.ts index da9c3669d9..89c8b24dbd 100644 --- a/packages/element-web-module-api/src/index.ts +++ b/packages/element-web-module-api/src/index.ts @@ -24,3 +24,4 @@ export type * from "./api/stores"; export type * from "./api/client"; export type * from "./api/widget-lifecycle"; export * from "./api/watchable"; +export type * from "./utils"; diff --git a/packages/element-web-module-api/src/utils.ts b/packages/element-web-module-api/src/utils.ts new file mode 100644 index 0000000000..8692b180c8 --- /dev/null +++ b/packages/element-web-module-api/src/utils.ts @@ -0,0 +1,13 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +/** + * A value that may be a direct value or a Promise resolving to that value. + * Useful for callback APIs that can operate synchronously or asynchronously. + * @public + */ +export type MaybePromise = T | PromiseLike;