summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-widget-api/src/ClientWidgetApi.ts
diff options
context:
space:
mode:
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-widget-api/src/ClientWidgetApi.ts')
-rw-r--r--includes/external/matrix/node_modules/matrix-widget-api/src/ClientWidgetApi.ts798
1 files changed, 0 insertions, 798 deletions
diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/ClientWidgetApi.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/ClientWidgetApi.ts
deleted file mode 100644
index fecbefc..0000000
--- a/includes/external/matrix/node_modules/matrix-widget-api/src/ClientWidgetApi.ts
+++ /dev/null
@@ -1,798 +0,0 @@
-/*
- * Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { EventEmitter } from "events";
-import { ITransport } from "./transport/ITransport";
-import { Widget } from "./models/Widget";
-import { PostmessageTransport } from "./transport/PostmessageTransport";
-import { WidgetApiDirection } from "./interfaces/WidgetApiDirection";
-import { IWidgetApiRequest, IWidgetApiRequestEmptyData } from "./interfaces/IWidgetApiRequest";
-import { IContentLoadedActionRequest } from "./interfaces/ContentLoadedAction";
-import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./interfaces/WidgetApiAction";
-import { IWidgetApiErrorResponseData } from "./interfaces/IWidgetApiErrorResponse";
-import { Capability, MatrixCapabilities } from "./interfaces/Capabilities";
-import { IOpenIDUpdate, ISendEventDetails, WidgetDriver } from "./driver/WidgetDriver";
-import {
- ICapabilitiesActionResponseData,
- INotifyCapabilitiesActionRequestData,
- IRenegotiateCapabilitiesActionRequest,
-} from "./interfaces/CapabilitiesAction";
-import {
- ISupportedVersionsActionRequest,
- ISupportedVersionsActionResponseData,
-} from "./interfaces/SupportedVersionsAction";
-import { CurrentApiVersions } from "./interfaces/ApiVersion";
-import { IScreenshotActionResponseData } from "./interfaces/ScreenshotAction";
-import { IVisibilityActionRequestData } from "./interfaces/VisibilityAction";
-import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponseData } from "./interfaces/IWidgetApiResponse";
-import {
- IModalWidgetButtonClickedRequestData,
- IModalWidgetOpenRequestData,
- IModalWidgetOpenRequestDataButton,
- IModalWidgetReturnData,
-} from "./interfaces/ModalWidgetActions";
-import {
- ISendEventFromWidgetActionRequest,
- ISendEventFromWidgetResponseData,
- ISendEventToWidgetRequestData,
-} from "./interfaces/SendEventAction";
-import {
- ISendToDeviceFromWidgetActionRequest,
- ISendToDeviceFromWidgetResponseData,
- ISendToDeviceToWidgetRequestData,
-} from "./interfaces/SendToDeviceAction";
-import { EventDirection, WidgetEventCapability } from "./models/WidgetEventCapability";
-import { IRoomEvent } from "./interfaces/IRoomEvent";
-import {
- IGetOpenIDActionRequest,
- IGetOpenIDActionResponseData,
- IOpenIDCredentials,
- OpenIDRequestState,
-} from "./interfaces/GetOpenIDAction";
-import { SimpleObservable } from "./util/SimpleObservable";
-import { IOpenIDCredentialsActionRequestData } from "./interfaces/OpenIDCredentialsAction";
-import { INavigateActionRequest } from "./interfaces/NavigateAction";
-import { IReadEventFromWidgetActionRequest, IReadEventFromWidgetResponseData } from "./interfaces/ReadEventAction";
-import {
- ITurnServer,
- IWatchTurnServersRequest,
- IUnwatchTurnServersRequest,
- IUpdateTurnServersRequestData,
-} from "./interfaces/TurnServerActions";
-import { Symbols } from "./Symbols";
-import {
- IReadRelationsFromWidgetActionRequest,
- IReadRelationsFromWidgetResponseData,
-} from "./interfaces/ReadRelationsAction";
-import {
- IUserDirectorySearchFromWidgetActionRequest,
- IUserDirectorySearchFromWidgetResponseData,
-} from "./interfaces/UserDirectorySearchAction";
-
-/**
- * API handler for the client side of widgets. This raises events
- * for each action received as `action:${action}` (eg: "action:screenshot").
- * Default handling can be prevented by using preventDefault() on the
- * raised event. The default handling varies for each action: ones
- * which the SDK can handle safely are acknowledged appropriately and
- * ones which are unhandled (custom or require the client to do something)
- * are rejected with an error.
- *
- * Events which are preventDefault()ed must reply using the transport.
- * The events raised will have a default of an IWidgetApiRequest
- * interface.
- *
- * When the ClientWidgetApi is ready to start sending requests, it will
- * raise a "ready" CustomEvent. After the ready event fires, actions can
- * be sent and the transport will be ready.
- *
- * When the widget has indicated it has loaded, this class raises a
- * "preparing" CustomEvent. The preparing event does not indicate that
- * the widget is ready to receive communications - that is signified by
- * the ready event exclusively.
- *
- * This class only handles one widget at a time.
- */
-export class ClientWidgetApi extends EventEmitter {
- public readonly transport: ITransport;
-
- // contentLoadedActionSent is used to check that only one ContentLoaded request is send.
- private contentLoadedActionSent = false;
- private allowedCapabilities = new Set<Capability>();
- private allowedEvents: WidgetEventCapability[] = [];
- private isStopped = false;
- private turnServers: AsyncGenerator<ITurnServer> | null = null;
-
- /**
- * Creates a new client widget API. This will instantiate the transport
- * and start everything. When the iframe is loaded under the widget's
- * conditions, a "ready" event will be raised.
- * @param {Widget} widget The widget to communicate with.
- * @param {HTMLIFrameElement} iframe The iframe the widget is in.
- * @param {WidgetDriver} driver The driver for this widget/client.
- */
- public constructor(
- public readonly widget: Widget,
- private iframe: HTMLIFrameElement,
- private driver: WidgetDriver,
- ) {
- super();
- if (!iframe?.contentWindow) {
- throw new Error("No iframe supplied");
- }
- if (!widget) {
- throw new Error("Invalid widget");
- }
- if (!driver) {
- throw new Error("Invalid driver");
- }
- this.transport = new PostmessageTransport(
- WidgetApiDirection.ToWidget,
- widget.id,
- iframe.contentWindow,
- window,
- );
- this.transport.targetOrigin = widget.origin;
- this.transport.on("message", this.handleMessage.bind(this));
-
- iframe.addEventListener("load", this.onIframeLoad.bind(this));
-
- this.transport.start();
- }
-
- public hasCapability(capability: Capability): boolean {
- return this.allowedCapabilities.has(capability);
- }
-
- public canUseRoomTimeline(roomId: string | Symbols.AnyRoom): boolean {
- return this.hasCapability(`org.matrix.msc2762.timeline:${Symbols.AnyRoom}`)
- || this.hasCapability(`org.matrix.msc2762.timeline:${roomId}`);
- }
-
- public canSendRoomEvent(eventType: string, msgtype: string | null = null): boolean {
- return this.allowedEvents.some(e => e.matchesAsRoomEvent(EventDirection.Send, eventType, msgtype));
- }
-
- public canSendStateEvent(eventType: string, stateKey: string): boolean {
- return this.allowedEvents.some(e => e.matchesAsStateEvent(EventDirection.Send, eventType, stateKey));
- }
-
- public canSendToDeviceEvent(eventType: string): boolean {
- return this.allowedEvents.some(e => e.matchesAsToDeviceEvent(EventDirection.Send, eventType));
- }
-
- public canReceiveRoomEvent(eventType: string, msgtype: string | null = null): boolean {
- return this.allowedEvents.some(e => e.matchesAsRoomEvent(EventDirection.Receive, eventType, msgtype));
- }
-
- public canReceiveStateEvent(eventType: string, stateKey: string | null): boolean {
- return this.allowedEvents.some(e => e.matchesAsStateEvent(EventDirection.Receive, eventType, stateKey));
- }
-
- public canReceiveToDeviceEvent(eventType: string): boolean {
- return this.allowedEvents.some(e => e.matchesAsToDeviceEvent(EventDirection.Receive, eventType));
- }
-
- public stop() {
- this.isStopped = true;
- this.transport.stop();
- }
-
- private beginCapabilities() {
- // widget has loaded - tell all the listeners that
- this.emit("preparing");
-
- let requestedCaps: Capability[];
- this.transport.send<IWidgetApiRequestEmptyData, ICapabilitiesActionResponseData>(
- WidgetApiToWidgetAction.Capabilities, {},
- ).then(caps => {
- requestedCaps = caps.capabilities;
- return this.driver.validateCapabilities(new Set(caps.capabilities));
- }).then(allowedCaps => {
- console.log(`Widget ${this.widget.id} is allowed capabilities:`, Array.from(allowedCaps));
- this.allowedCapabilities = allowedCaps;
- this.allowedEvents = WidgetEventCapability.findEventCapabilities(allowedCaps);
- this.notifyCapabilities(requestedCaps);
- this.emit("ready");
- });
- }
-
- private notifyCapabilities(requested: Capability[]) {
- this.transport.send(WidgetApiToWidgetAction.NotifyCapabilities, <INotifyCapabilitiesActionRequestData>{
- requested: requested,
- approved: Array.from(this.allowedCapabilities),
- }).catch(e => {
- console.warn("non-fatal error notifying widget of approved capabilities:", e);
- }).then(() => {
- this.emit("capabilitiesNotified")
- });
- }
-
- private onIframeLoad(ev: Event) {
- if (this.widget.waitForIframeLoad) {
- // If the widget is set to waitForIframeLoad the capabilities immediatly get setup after load.
- // The client does not wait for the ContentLoaded action.
- this.beginCapabilities();
- } else {
- // Reaching this means, that the Iframe got reloaded/loaded and
- // the clientApi is awaiting the FIRST ContentLoaded action.
- this.contentLoadedActionSent = false;
- }
- }
-
- private handleContentLoadedAction(action: IContentLoadedActionRequest) {
- if (this.contentLoadedActionSent) {
- throw new Error("Improper sequence: ContentLoaded Action can only be send once after the widget loaded "
- +"and should only be used if waitForIframeLoad is false (default=true)");
- }
- if (this.widget.waitForIframeLoad) {
- this.transport.reply(action, <IWidgetApiErrorResponseData>{
- error: {
- message: "Improper sequence: not expecting ContentLoaded event if "
- +"waitForIframLoad is true (default=true)",
- },
- });
- } else {
- this.transport.reply(action, <IWidgetApiRequestEmptyData>{});
- this.beginCapabilities();
- }
- this.contentLoadedActionSent = true;
- }
-
- private replyVersions(request: ISupportedVersionsActionRequest) {
- this.transport.reply<ISupportedVersionsActionResponseData>(request, {
- supported_versions: CurrentApiVersions,
- });
- }
-
- private handleCapabilitiesRenegotiate(request: IRenegotiateCapabilitiesActionRequest) {
- // acknowledge first
- this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
-
- const requested = request.data?.capabilities || [];
- const newlyRequested = new Set(requested.filter(r => !this.hasCapability(r)));
- if (newlyRequested.size === 0) {
- // Nothing to do - notify capabilities
- return this.notifyCapabilities([]);
- }
-
- this.driver.validateCapabilities(newlyRequested).then(allowed => {
- allowed.forEach(c => this.allowedCapabilities.add(c));
-
- const allowedEvents = WidgetEventCapability.findEventCapabilities(allowed);
- allowedEvents.forEach(c => this.allowedEvents.push(c));
-
- return this.notifyCapabilities(Array.from(newlyRequested));
- });
- }
-
- private handleNavigate(request: INavigateActionRequest) {
- if (!this.hasCapability(MatrixCapabilities.MSC2931Navigate)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Missing capability"},
- });
- }
-
- if (!request.data?.uri || !request.data?.uri.toString().startsWith("https://matrix.to/#")) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Invalid matrix.to URI"},
- });
- }
-
- const onErr = (e: any) => {
- console.error("[ClientWidgetApi] Failed to handle navigation: ", e);
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Error handling navigation"},
- });
- };
-
- try {
- this.driver.navigate(request.data.uri.toString()).catch(e => onErr(e)).then(() => {
- return this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
- });
- } catch (e) {
- return onErr(e);
- }
- }
-
- private handleOIDC(request: IGetOpenIDActionRequest) {
- let phase = 1; // 1 = initial request, 2 = after user manual confirmation
-
- const replyState = (state: OpenIDRequestState, credential?: IOpenIDCredentials) => {
- credential = credential || {};
- if (phase > 1) {
- return this.transport.send<IOpenIDCredentialsActionRequestData>(
- WidgetApiToWidgetAction.OpenIDCredentials,
- {
- state: state,
- original_request_id: request.requestId,
- ...credential,
- },
- );
- } else {
- return this.transport.reply<IGetOpenIDActionResponseData>(request, {
- state: state,
- ...credential,
- });
- }
- };
-
- const replyError = (msg: string) => {
- console.error("[ClientWidgetApi] Failed to handle OIDC: ", msg);
- if (phase > 1) {
- // We don't have a way to indicate that a random error happened in this flow, so
- // just block the attempt.
- return replyState(OpenIDRequestState.Blocked);
- } else {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: msg},
- });
- }
- };
-
- const observer = new SimpleObservable<IOpenIDUpdate>(update => {
- if (update.state === OpenIDRequestState.PendingUserConfirmation && phase > 1) {
- observer.close();
- return replyError("client provided out-of-phase response to OIDC flow");
- }
-
- if (update.state === OpenIDRequestState.PendingUserConfirmation) {
- replyState(update.state);
- phase++;
- return;
- }
-
- if (update.state === OpenIDRequestState.Allowed && !update.token) {
- return replyError("client provided invalid OIDC token for an allowed request");
- }
- if (update.state === OpenIDRequestState.Blocked) {
- update.token = undefined; // just in case the client did something weird
- }
-
- observer.close();
- return replyState(update.state, update.token);
- });
-
- this.driver.askOpenID(observer);
- }
-
- private handleReadEvents(request: IReadEventFromWidgetActionRequest) {
- if (!request.data.type) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Invalid request - missing event type"},
- });
- }
- if (request.data.limit !== undefined && (!request.data.limit || request.data.limit < 0)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Invalid request - limit out of range"},
- });
- }
-
- let askRoomIds: string[] | null = null; // null denotes current room only
- if (request.data.room_ids) {
- askRoomIds = request.data.room_ids as string[];
- if (!Array.isArray(askRoomIds)) {
- askRoomIds = [askRoomIds as any as string];
- }
- for (const roomId of askRoomIds) {
- if (!this.canUseRoomTimeline(roomId)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: `Unable to access room timeline: ${roomId}`},
- });
- }
- }
- }
-
- const limit = request.data.limit || 0;
-
- let events: Promise<IRoomEvent[]> = Promise.resolve([]);
- if (request.data.state_key !== undefined) {
- const stateKey = request.data.state_key === true ? undefined : request.data.state_key.toString();
- if (!this.canReceiveStateEvent(request.data.type, stateKey ?? null)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Cannot read state events of this type"},
- });
- }
- events = this.driver.readStateEvents(request.data.type, stateKey, limit, askRoomIds);
- } else {
- if (!this.canReceiveRoomEvent(request.data.type, request.data.msgtype)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Cannot read room events of this type"},
- });
- }
- events = this.driver.readRoomEvents(request.data.type, request.data.msgtype, limit, askRoomIds);
- }
-
- return events.then(evs => this.transport.reply<IReadEventFromWidgetResponseData>(request, {events: evs}));
- }
-
- private handleSendEvent(request: ISendEventFromWidgetActionRequest) {
- if (!request.data.type) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Invalid request - missing event type"},
- });
- }
-
- if (!!request.data.room_id && !this.canUseRoomTimeline(request.data.room_id)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: `Unable to access room timeline: ${request.data.room_id}`},
- });
- }
-
- const isState = request.data.state_key !== null && request.data.state_key !== undefined;
- let sendEventPromise: Promise<ISendEventDetails>;
- if (isState) {
- if (!this.canSendStateEvent(request.data.type, request.data.state_key!)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Cannot send state events of this type"},
- });
- }
-
- sendEventPromise = this.driver.sendEvent(
- request.data.type,
- request.data.content || {},
- request.data.state_key,
- request.data.room_id,
- );
- } else {
- const content = request.data.content as { msgtype?: string } || {};
- const msgtype = content['msgtype'];
- if (!this.canSendRoomEvent(request.data.type, msgtype)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Cannot send room events of this type"},
- });
- }
-
- sendEventPromise = this.driver.sendEvent(
- request.data.type,
- content,
- null, // not sending a state event
- request.data.room_id,
- );
- }
-
- sendEventPromise.then(sentEvent => {
- return this.transport.reply<ISendEventFromWidgetResponseData>(request, {
- room_id: sentEvent.roomId,
- event_id: sentEvent.eventId,
- });
- }).catch(e => {
- console.error("error sending event: ", e);
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Error sending event"},
- });
- });
- }
-
- private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise<void> {
- if (!request.data.type) {
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Invalid request - missing event type"},
- });
- } else if (!request.data.messages) {
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Invalid request - missing event contents"},
- });
- } else if (typeof request.data.encrypted !== "boolean") {
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Invalid request - missing encryption flag"},
- });
- } else if (!this.canSendToDeviceEvent(request.data.type)) {
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Cannot send to-device events of this type"},
- });
- } else {
- try {
- await this.driver.sendToDevice(request.data.type, request.data.encrypted, request.data.messages);
- await this.transport.reply<ISendToDeviceFromWidgetResponseData>(request, {});
- } catch (e) {
- console.error("error sending to-device event", e);
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Error sending event"},
- });
- }
- }
- }
-
- private async pollTurnServers(turnServers: AsyncGenerator<ITurnServer>, initialServer: ITurnServer) {
- try {
- await this.transport.send<IUpdateTurnServersRequestData>(
- WidgetApiToWidgetAction.UpdateTurnServers,
- initialServer as IUpdateTurnServersRequestData, // it's compatible, but missing the index signature
- );
-
- // Pick the generator up where we left off
- for await (const server of turnServers) {
- await this.transport.send<IUpdateTurnServersRequestData>(
- WidgetApiToWidgetAction.UpdateTurnServers,
- server as IUpdateTurnServersRequestData, // it's compatible, but missing the index signature
- );
- }
- } catch (e) {
- console.error("error polling for TURN servers", e);
- }
- }
-
- private async handleWatchTurnServers(request: IWatchTurnServersRequest): Promise<void> {
- if (!this.hasCapability(MatrixCapabilities.MSC3846TurnServers)) {
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Missing capability"},
- });
- } else if (this.turnServers) {
- // We're already polling, so this is a no-op
- await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
- } else {
- try {
- const turnServers = this.driver.getTurnServers();
-
- // Peek at the first result, so we can at least verify that the
- // client isn't banned from getting TURN servers entirely
- const { done, value } = await turnServers.next();
- if (done) throw new Error("Client refuses to provide any TURN servers");
- await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
-
- // Start the poll loop, sending the widget the initial result
- this.pollTurnServers(turnServers, value);
- this.turnServers = turnServers;
- } catch (e) {
- console.error("error getting first TURN server results", e);
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "TURN servers not available"},
- });
- }
- }
- }
-
- private async handleUnwatchTurnServers(request: IUnwatchTurnServersRequest): Promise<void> {
- if (!this.hasCapability(MatrixCapabilities.MSC3846TurnServers)) {
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: {message: "Missing capability"},
- });
- } else if (!this.turnServers) {
- // We weren't polling anyways, so this is a no-op
- await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
- } else {
- // Stop the generator, allowing it to clean up
- await this.turnServers.return(undefined);
- this.turnServers = null;
- await this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
- }
- }
-
- private async handleReadRelations(request: IReadRelationsFromWidgetActionRequest) {
- if (!request.data.event_id) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: "Invalid request - missing event ID" },
- });
- }
-
- if (request.data.limit !== undefined && request.data.limit < 0) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: "Invalid request - limit out of range" },
- });
- }
-
- if (request.data.room_id !== undefined && !this.canUseRoomTimeline(request.data.room_id)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: `Unable to access room timeline: ${request.data.room_id}` },
- });
- }
-
- try {
- const result = await this.driver.readEventRelations(
- request.data.event_id, request.data.room_id, request.data.rel_type,
- request.data.event_type, request.data.from, request.data.to,
- request.data.limit, request.data.direction,
- );
-
- // only return events that the user has the permission to receive
- const chunk = result.chunk.filter(e => {
- if (e.state_key !== undefined) {
- return this.canReceiveStateEvent(e.type, e.state_key);
- } else {
- return this.canReceiveRoomEvent(e.type, (e.content as { msgtype?: string })['msgtype']);
- }
- });
-
- return this.transport.reply<IReadRelationsFromWidgetResponseData>(
- request,
- {
- chunk,
- prev_batch: result.prevBatch,
- next_batch: result.nextBatch,
- },
- );
- } catch (e) {
- console.error("error getting the relations", e);
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: "Unexpected error while reading relations" },
- });
- }
- }
-
- private async handleUserDirectorySearch(request: IUserDirectorySearchFromWidgetActionRequest) {
- if (!this.hasCapability(MatrixCapabilities.MSC3973UserDirectorySearch)) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: "Missing capability" },
- });
- }
-
- if (typeof request.data.search_term !== 'string') {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: "Invalid request - missing search term" },
- });
- }
-
- if (request.data.limit !== undefined && request.data.limit < 0) {
- return this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: "Invalid request - limit out of range" },
- });
- }
-
- try {
- const result = await this.driver.searchUserDirectory(
- request.data.search_term, request.data.limit,
- );
-
- return this.transport.reply<IUserDirectorySearchFromWidgetResponseData>(
- request,
- {
- limited: result.limited,
- results: result.results.map(r => ({
- user_id: r.userId,
- display_name: r.displayName,
- avatar_url: r.avatarUrl,
- })),
- },
- );
- } catch (e) {
- console.error("error searching in the user directory", e);
- await this.transport.reply<IWidgetApiErrorResponseData>(request, {
- error: { message: "Unexpected error while searching in the user directory" },
- });
- }
- }
-
- private handleMessage(ev: CustomEvent<IWidgetApiRequest>) {
- if (this.isStopped) return;
- const actionEv = new CustomEvent(`action:${ev.detail.action}`, {
- detail: ev.detail,
- cancelable: true,
- });
- this.emit(`action:${ev.detail.action}`, actionEv);
- if (!actionEv.defaultPrevented) {
- switch (ev.detail.action) {
- case WidgetApiFromWidgetAction.ContentLoaded:
- return this.handleContentLoadedAction(<IContentLoadedActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.SupportedApiVersions:
- return this.replyVersions(<ISupportedVersionsActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.SendEvent:
- return this.handleSendEvent(<ISendEventFromWidgetActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.SendToDevice:
- return this.handleSendToDevice(<ISendToDeviceFromWidgetActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.GetOpenIDCredentials:
- return this.handleOIDC(<IGetOpenIDActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.MSC2931Navigate:
- return this.handleNavigate(<INavigateActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities:
- return this.handleCapabilitiesRenegotiate(<IRenegotiateCapabilitiesActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.MSC2876ReadEvents:
- return this.handleReadEvents(<IReadEventFromWidgetActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.WatchTurnServers:
- return this.handleWatchTurnServers(<IWatchTurnServersRequest>ev.detail);
- case WidgetApiFromWidgetAction.UnwatchTurnServers:
- return this.handleUnwatchTurnServers(<IUnwatchTurnServersRequest>ev.detail);
- case WidgetApiFromWidgetAction.MSC3869ReadRelations:
- return this.handleReadRelations(<IReadRelationsFromWidgetActionRequest>ev.detail);
- case WidgetApiFromWidgetAction.MSC3973UserDirectorySearch:
- return this.handleUserDirectorySearch(<IUserDirectorySearchFromWidgetActionRequest>ev.detail)
- default:
- return this.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
- error: {
- message: "Unknown or unsupported action: " + ev.detail.action,
- },
- });
- }
- }
- }
-
- /**
- * Takes a screenshot of the widget.
- * @returns Resolves to the widget's screenshot.
- * @throws Throws if there is a problem.
- */
- public takeScreenshot(): Promise<IScreenshotActionResponseData> {
- return this.transport.send(WidgetApiToWidgetAction.TakeScreenshot, <IWidgetApiRequestEmptyData>{});
- }
-
- /**
- * Alerts the widget to whether or not it is currently visible.
- * @param {boolean} isVisible Whether the widget is visible or not.
- * @returns {Promise<IWidgetApiResponseData>} Resolves when the widget acknowledges the update.
- */
- public updateVisibility(isVisible: boolean): Promise<IWidgetApiResponseData> {
- return this.transport.send(WidgetApiToWidgetAction.UpdateVisibility, <IVisibilityActionRequestData>{
- visible: isVisible,
- });
- }
-
- public sendWidgetConfig(data: IModalWidgetOpenRequestData): Promise<void> {
- return this.transport.send<IModalWidgetOpenRequestData>(WidgetApiToWidgetAction.WidgetConfig, data).then();
- }
-
- public notifyModalWidgetButtonClicked(id: IModalWidgetOpenRequestDataButton["id"]): Promise<void> {
- return this.transport.send<IModalWidgetButtonClickedRequestData>(
- WidgetApiToWidgetAction.ButtonClicked, {id},
- ).then();
- }
-
- public notifyModalWidgetClose(data: IModalWidgetReturnData): Promise<void> {
- return this.transport.send<IModalWidgetReturnData>(
- WidgetApiToWidgetAction.CloseModalWidget, data,
- ).then();
- }
-
- /**
- * Feeds an event to the widget. If the widget is not able to accept the event due to
- * permissions, this will no-op and return calmly. If the widget failed to handle the
- * event, this will raise an error.
- * @param {IRoomEvent} rawEvent The event to (try to) send to the widget.
- * @param {string} currentViewedRoomId The room ID the user is currently interacting with.
- * Not the room ID of the event.
- * @returns {Promise<void>} Resolves when complete, rejects if there was an error sending.
- */
- public async feedEvent(rawEvent: IRoomEvent, currentViewedRoomId: string): Promise<void> {
- if (rawEvent.room_id !== currentViewedRoomId && !this.canUseRoomTimeline(rawEvent.room_id)) {
- return; // no-op
- }
-
- if (rawEvent.state_key !== undefined && rawEvent.state_key !== null) {
- // state event
- if (!this.canReceiveStateEvent(rawEvent.type, rawEvent.state_key)) {
- return; // no-op
- }
- } else {
- // message event
- if (!this.canReceiveRoomEvent(rawEvent.type, (rawEvent.content as { msgtype?: string })?.["msgtype"])) {
- return; // no-op
- }
- }
-
- // Feed the event into the widget
- await this.transport.send<ISendEventToWidgetRequestData>(
- WidgetApiToWidgetAction.SendEvent,
- rawEvent as ISendEventToWidgetRequestData, // it's compatible, but missing the index signature
- );
- }
-
- /**
- * Feeds a to-device event to the widget. If the widget is not able to accept the
- * event due to permissions, this will no-op and return calmly. If the widget failed
- * to handle the event, this will raise an error.
- * @param {IRoomEvent} rawEvent The event to (try to) send to the widget.
- * @param {boolean} encrypted Whether the event contents were encrypted.
- * @returns {Promise<void>} Resolves when complete, rejects if there was an error sending.
- */
- public async feedToDevice(rawEvent: IRoomEvent, encrypted: boolean): Promise<void> {
- if (this.canReceiveToDeviceEvent(rawEvent.type)) {
- await this.transport.send<ISendToDeviceToWidgetRequestData>(
- WidgetApiToWidgetAction.SendToDevice,
- // it's compatible, but missing the index signature
- { ...rawEvent, encrypted } as ISendToDeviceToWidgetRequestData,
- );
- }
- }
-}