diff options
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-widget-api/src')
48 files changed, 4193 insertions, 0 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 new file mode 100644 index 0000000..fecbefc --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/ClientWidgetApi.ts @@ -0,0 +1,798 @@ +/* + * 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, + ); + } + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/Symbols.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/Symbols.ts new file mode 100644 index 0000000..85ca12e --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/Symbols.ts @@ -0,0 +1,19 @@ +/* + * Copyright 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. + */ + +export enum Symbols { + AnyRoom = "*", +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/WidgetApi.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/WidgetApi.ts new file mode 100644 index 0000000..a74187a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/WidgetApi.ts @@ -0,0 +1,717 @@ +/* + * 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 { Capability } from "./interfaces/Capabilities"; +import { IWidgetApiRequest, IWidgetApiRequestEmptyData } from "./interfaces/IWidgetApiRequest"; +import { IWidgetApiAcknowledgeResponseData } from "./interfaces/IWidgetApiResponse"; +import { WidgetApiDirection } from "./interfaces/WidgetApiDirection"; +import { + ISupportedVersionsActionRequest, + ISupportedVersionsActionResponseData, +} from "./interfaces/SupportedVersionsAction"; +import { ApiVersion, CurrentApiVersions, UnstableApiVersion } from "./interfaces/ApiVersion"; +import { + ICapabilitiesActionRequest, + ICapabilitiesActionResponseData, + INotifyCapabilitiesActionRequest, + IRenegotiateCapabilitiesRequestData, +} from "./interfaces/CapabilitiesAction"; +import { ITransport } from "./transport/ITransport"; +import { PostmessageTransport } from "./transport/PostmessageTransport"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./interfaces/WidgetApiAction"; +import { IWidgetApiErrorResponseData } from "./interfaces/IWidgetApiErrorResponse"; +import { IStickerActionRequestData } from "./interfaces/StickerAction"; +import { IStickyActionRequestData, IStickyActionResponseData } from "./interfaces/StickyAction"; +import { + IGetOpenIDActionRequestData, + IGetOpenIDActionResponse, + IOpenIDCredentials, + OpenIDRequestState, +} from "./interfaces/GetOpenIDAction"; +import { IOpenIDCredentialsActionRequest } from "./interfaces/OpenIDCredentialsAction"; +import { MatrixWidgetType, WidgetType } from "./interfaces/WidgetType"; +import { + BuiltInModalButtonID, + IModalWidgetCreateData, + IModalWidgetOpenRequestData, + IModalWidgetOpenRequestDataButton, + IModalWidgetReturnData, + ModalButtonID, +} from "./interfaces/ModalWidgetActions"; +import { ISetModalButtonEnabledActionRequestData } from "./interfaces/SetModalButtonEnabledAction"; +import { ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData } from "./interfaces/SendEventAction"; +import { + ISendToDeviceFromWidgetRequestData, + ISendToDeviceFromWidgetResponseData, +} from "./interfaces/SendToDeviceAction"; +import { EventDirection, WidgetEventCapability } from "./models/WidgetEventCapability"; +import { INavigateActionRequestData } from "./interfaces/NavigateAction"; +import { IReadEventFromWidgetRequestData, IReadEventFromWidgetResponseData } from "./interfaces/ReadEventAction"; +import { IRoomEvent } from "./interfaces/IRoomEvent"; +import { ITurnServer, IUpdateTurnServersRequest } from "./interfaces/TurnServerActions"; +import { Symbols } from "./Symbols"; +import { + IReadRelationsFromWidgetRequestData, + IReadRelationsFromWidgetResponseData, +} from "./interfaces/ReadRelationsAction"; +import { + IUserDirectorySearchFromWidgetRequestData, + IUserDirectorySearchFromWidgetResponseData, +} from "./interfaces/UserDirectorySearchAction"; + +/** + * API handler for 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 widget to do something) are rejected with an error. + * + * Events which are preventDefault()ed must reply using the + * transport. The events raised will have a detail of an + * IWidgetApiRequest interface. + * + * When the WidgetApi 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. + */ +export class WidgetApi extends EventEmitter { + public readonly transport: ITransport; + + private capabilitiesFinished = false; + private supportsMSC2974Renegotiate = false; + private requestedCapabilities: Capability[] = []; + private approvedCapabilities?: Capability[]; + private cachedClientVersions?: ApiVersion[]; + private turnServerWatchers = 0; + + /** + * Creates a new API handler for the given widget. + * @param {string} widgetId The widget ID to listen for. If not supplied then + * the API will use the widget ID from the first valid request it receives. + * @param {string} clientOrigin The origin of the client, or null if not known. + */ + public constructor(widgetId: string | null = null, private clientOrigin: string | null = null) { + super(); + if (!window.parent) { + throw new Error("No parent window. This widget doesn't appear to be embedded properly."); + } + this.transport = new PostmessageTransport( + WidgetApiDirection.FromWidget, + widgetId, + window.parent, + window, + ); + this.transport.targetOrigin = clientOrigin; + this.transport.on("message", this.handleMessage.bind(this)); + } + + /** + * Determines if the widget was granted a particular capability. Note that on + * clients where the capabilities are not fed back to the widget this function + * will rely on requested capabilities instead. + * @param {Capability} capability The capability to check for approval of. + * @returns {boolean} True if the widget has approval for the given capability. + */ + public hasCapability(capability: Capability): boolean { + if (Array.isArray(this.approvedCapabilities)) { + return this.approvedCapabilities.includes(capability); + } + return this.requestedCapabilities.includes(capability); + } + + /** + * Request a capability from the client. It is not guaranteed to be allowed, + * but will be asked for. + * @param {Capability} capability The capability to request. + * @throws Throws if the capabilities negotiation has already started and the + * widget is unable to request additional capabilities. + */ + public requestCapability(capability: Capability) { + if (this.capabilitiesFinished && !this.supportsMSC2974Renegotiate) { + throw new Error("Capabilities have already been negotiated"); + } + + this.requestedCapabilities.push(capability); + } + + /** + * Request capabilities from the client. They are not guaranteed to be allowed, + * but will be asked for if the negotiation has not already happened. + * @param {Capability[]} capabilities The capabilities to request. + * @throws Throws if the capabilities negotiation has already started. + */ + public requestCapabilities(capabilities: Capability[]) { + capabilities.forEach(cap => this.requestCapability(cap)); + } + + /** + * Requests the capability to interact with rooms other than the user's currently + * viewed room. Applies to event receiving and sending. + * @param {string | Symbols.AnyRoom} roomId The room ID, or `Symbols.AnyRoom` to + * denote all known rooms. + */ + public requestCapabilityForRoomTimeline(roomId: string | Symbols.AnyRoom) { + this.requestCapability(`org.matrix.msc2762.timeline:${roomId}`); + } + + /** + * Requests the capability to send a given state event with optional explicit + * state key. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} eventType The state event type to ask for. + * @param {string} stateKey If specified, the specific state key to request. + * Otherwise all state keys will be requested. + */ + public requestCapabilityToSendState(eventType: string, stateKey?: string) { + this.requestCapability(WidgetEventCapability.forStateEvent(EventDirection.Send, eventType, stateKey).raw); + } + + /** + * Requests the capability to receive a given state event with optional explicit + * state key. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} eventType The state event type to ask for. + * @param {string} stateKey If specified, the specific state key to request. + * Otherwise all state keys will be requested. + */ + public requestCapabilityToReceiveState(eventType: string, stateKey?: string) { + this.requestCapability(WidgetEventCapability.forStateEvent(EventDirection.Receive, eventType, stateKey).raw); + } + + /** + * Requests the capability to send a given to-device event. It is not + * guaranteed to be allowed, but will be asked for if the negotiation has + * not already happened. + * @param {string} eventType The room event type to ask for. + */ + public requestCapabilityToSendToDevice(eventType: string) { + this.requestCapability(WidgetEventCapability.forToDeviceEvent(EventDirection.Send, eventType).raw); + } + + /** + * Requests the capability to receive a given to-device event. It is not + * guaranteed to be allowed, but will be asked for if the negotiation has + * not already happened. + * @param {string} eventType The room event type to ask for. + */ + public requestCapabilityToReceiveToDevice(eventType: string) { + this.requestCapability(WidgetEventCapability.forToDeviceEvent(EventDirection.Receive, eventType).raw); + } + + /** + * Requests the capability to send a given room event. It is not guaranteed to be + * allowed, but will be asked for if the negotiation has not already happened. + * @param {string} eventType The room event type to ask for. + */ + public requestCapabilityToSendEvent(eventType: string) { + this.requestCapability(WidgetEventCapability.forRoomEvent(EventDirection.Send, eventType).raw); + } + + /** + * Requests the capability to receive a given room event. It is not guaranteed to be + * allowed, but will be asked for if the negotiation has not already happened. + * @param {string} eventType The room event type to ask for. + */ + public requestCapabilityToReceiveEvent(eventType: string) { + this.requestCapability(WidgetEventCapability.forRoomEvent(EventDirection.Receive, eventType).raw); + } + + /** + * Requests the capability to send a given message event with optional explicit + * `msgtype`. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} msgtype If specified, the specific msgtype to request. + * Otherwise all message types will be requested. + */ + public requestCapabilityToSendMessage(msgtype?: string) { + this.requestCapability(WidgetEventCapability.forRoomMessageEvent(EventDirection.Send, msgtype).raw); + } + + /** + * Requests the capability to receive a given message event with optional explicit + * `msgtype`. It is not guaranteed to be allowed, but will be asked for if the + * negotiation has not already happened. + * @param {string} msgtype If specified, the specific msgtype to request. + * Otherwise all message types will be requested. + */ + public requestCapabilityToReceiveMessage(msgtype?: string) { + this.requestCapability(WidgetEventCapability.forRoomMessageEvent(EventDirection.Receive, msgtype).raw); + } + + /** + * Requests an OpenID Connect token from the client for the currently logged in + * user. This token can be validated server-side with the federation API. Note + * that the widget is responsible for validating the token and caching any results + * it needs. + * @returns {Promise<IOpenIDCredentials>} Resolves to a token for verification. + * @throws Throws if the user rejected the request or the request failed. + */ + public requestOpenIDConnectToken(): Promise<IOpenIDCredentials> { + return new Promise<IOpenIDCredentials>((resolve, reject) => { + this.transport.sendComplete<IGetOpenIDActionRequestData, IGetOpenIDActionResponse>( + WidgetApiFromWidgetAction.GetOpenIDCredentials, {}, + ).then(response => { + const rdata = response.response; + if (rdata.state === OpenIDRequestState.Allowed) { + resolve(rdata); + } else if (rdata.state === OpenIDRequestState.Blocked) { + reject(new Error("User declined to verify their identity")); + } else if (rdata.state === OpenIDRequestState.PendingUserConfirmation) { + const handlerFn = (ev: CustomEvent<IOpenIDCredentialsActionRequest>) => { + ev.preventDefault(); + const request = ev.detail; + if (request.data.original_request_id !== response.requestId) return; + if (request.data.state === OpenIDRequestState.Allowed) { + resolve(request.data); + this.transport.reply(request, <IWidgetApiRequestEmptyData>{}); // ack + } else if (request.data.state === OpenIDRequestState.Blocked) { + reject(new Error("User declined to verify their identity")); + this.transport.reply(request, <IWidgetApiRequestEmptyData>{}); // ack + } else { + reject(new Error("Invalid state on reply: " + rdata.state)); + this.transport.reply(request, <IWidgetApiErrorResponseData>{ + error: { + message: "Invalid state", + }, + }); + } + this.off(`action:${WidgetApiToWidgetAction.OpenIDCredentials}`, handlerFn); + }; + this.on(`action:${WidgetApiToWidgetAction.OpenIDCredentials}`, handlerFn); + } else { + reject(new Error("Invalid state: " + rdata.state)); + } + }).catch(reject); + }); + } + + /** + * Asks the client for additional capabilities. Capabilities can be queued for this + * request with the requestCapability() functions. + * @returns {Promise<void>} Resolves when complete. Note that the promise resolves when + * the capabilities request has gone through, not when the capabilities are approved/denied. + * Use the WidgetApiToWidgetAction.NotifyCapabilities action to detect changes. + */ + public updateRequestedCapabilities(): Promise<void> { + return this.transport.send(WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities, + <IRenegotiateCapabilitiesRequestData>{ + capabilities: this.requestedCapabilities, + }).then(); + } + + /** + * Tell the client that the content has been loaded. + * @returns {Promise} Resolves when the client acknowledges the request. + */ + public sendContentLoaded(): Promise<void> { + return this.transport.send(WidgetApiFromWidgetAction.ContentLoaded, <IWidgetApiRequestEmptyData>{}).then(); + } + + /** + * Sends a sticker to the client. + * @param {IStickerActionRequestData} sticker The sticker to send. + * @returns {Promise} Resolves when the client acknowledges the request. + */ + public sendSticker(sticker: IStickerActionRequestData): Promise<void> { + return this.transport.send(WidgetApiFromWidgetAction.SendSticker, sticker).then(); + } + + /** + * Asks the client to set the always-on-screen status for this widget. + * @param {boolean} value The new state to request. + * @returns {Promise<boolean>} Resolve with true if the client was able to fulfill + * the request, resolves to false otherwise. Rejects if an error occurred. + */ + public setAlwaysOnScreen(value: boolean): Promise<boolean> { + return this.transport.send<IStickyActionRequestData, IStickyActionResponseData>( + WidgetApiFromWidgetAction.UpdateAlwaysOnScreen, {value}, + ).then(res => res.success); + } + + /** + * Opens a modal widget. + * @param {string} url The URL to the modal widget. + * @param {string} name The name of the widget. + * @param {IModalWidgetOpenRequestDataButton[]} buttons The buttons to have on the widget. + * @param {IModalWidgetCreateData} data Data to supply to the modal widget. + * @param {WidgetType} type The type of modal widget. + * @returns {Promise<void>} Resolves when the modal widget has been opened. + */ + public openModalWidget( + url: string, + name: string, + buttons: IModalWidgetOpenRequestDataButton[] = [], + data: IModalWidgetCreateData = {}, + type: WidgetType = MatrixWidgetType.Custom, + ): Promise<void> { + return this.transport.send<IModalWidgetOpenRequestData>( + WidgetApiFromWidgetAction.OpenModalWidget, { type, url, name, buttons, data }, + ).then(); + } + + /** + * Closes the modal widget. The widget's session will be terminated shortly after. + * @param {IModalWidgetReturnData} data Optional data to close the modal widget with. + * @returns {Promise<void>} Resolves when complete. + */ + public closeModalWidget(data: IModalWidgetReturnData = {}): Promise<void> { + return this.transport.send<IModalWidgetReturnData>(WidgetApiFromWidgetAction.CloseModalWidget, data).then(); + } + + public sendRoomEvent( + eventType: string, + content: unknown, + roomId?: string, + ): Promise<ISendEventFromWidgetResponseData> { + return this.transport.send<ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData>( + WidgetApiFromWidgetAction.SendEvent, + {type: eventType, content, room_id: roomId}, + ); + } + + public sendStateEvent( + eventType: string, + stateKey: string, + content: unknown, + roomId?: string, + ): Promise<ISendEventFromWidgetResponseData> { + return this.transport.send<ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData>( + WidgetApiFromWidgetAction.SendEvent, + {type: eventType, content, state_key: stateKey, room_id: roomId}, + ); + } + + /** + * Sends a to-device event. + * @param {string} eventType The type of events being sent. + * @param {boolean} encrypted Whether to encrypt the message contents. + * @param {Object} contentMap A map from user IDs to device IDs to message contents. + * @returns {Promise<ISendToDeviceFromWidgetResponseData>} Resolves when complete. + */ + public sendToDevice( + eventType: string, + encrypted: boolean, + contentMap: { [userId: string]: { [deviceId: string]: object } }, + ): Promise<ISendToDeviceFromWidgetResponseData> { + return this.transport.send<ISendToDeviceFromWidgetRequestData, ISendToDeviceFromWidgetResponseData>( + WidgetApiFromWidgetAction.SendToDevice, + {type: eventType, encrypted, messages: contentMap}, + ); + } + + public readRoomEvents( + eventType: string, + limit?: number, + msgtype?: string, + roomIds?: (string | Symbols.AnyRoom)[], + ): Promise<IRoomEvent[]> { + const data: IReadEventFromWidgetRequestData = {type: eventType, msgtype: msgtype}; + if (limit !== undefined) { + data.limit = limit; + } + if (roomIds) { + if (roomIds.includes(Symbols.AnyRoom)) { + data.room_ids = Symbols.AnyRoom; + } else { + data.room_ids = roomIds; + } + } + return this.transport.send<IReadEventFromWidgetRequestData, IReadEventFromWidgetResponseData>( + WidgetApiFromWidgetAction.MSC2876ReadEvents, + data, + ).then(r => r.events); + } + + /** + * Reads all related events given a known eventId. + * @param eventId The id of the parent event to be read. + * @param roomId The room to look within. When undefined, the user's currently + * viewed room. + * @param relationType The relationship type of child events to search for. + * When undefined, all relations are returned. + * @param eventType The event type of child events to search for. When undefined, + * all related events are returned. + * @param limit The maximum number of events to retrieve per room. If not + * supplied, the server will apply a default limit. + * @param from The pagination token to start returning results from, as + * received from a previous call. If not supplied, results start at the most + * recent topological event known to the server. + * @param to The pagination token to stop returning results at. If not + * supplied, results continue up to limit or until there are no more events. + * @param direction The direction to search for according to MSC3715. + * @returns Resolves to the room relations. + */ + public async readEventRelations( + eventId: string, + roomId?: string, + relationType?: string, + eventType?: string, + limit?: number, + from?: string, + to?: string, + direction?: 'f' | 'b', + ): Promise<IReadRelationsFromWidgetResponseData> { + const versions = await this.getClientVersions(); + if (!versions.includes(UnstableApiVersion.MSC3869)) { + throw new Error("The read_relations action is not supported by the client.") + } + + const data: IReadRelationsFromWidgetRequestData = { + event_id: eventId, + rel_type: relationType, + event_type: eventType, + room_id: roomId, + to, + from, + limit, + direction, + }; + + return this.transport.send<IReadRelationsFromWidgetRequestData, IReadRelationsFromWidgetResponseData>( + WidgetApiFromWidgetAction.MSC3869ReadRelations, + data, + ) + } + + public readStateEvents( + eventType: string, + limit?: number, + stateKey?: string, + roomIds?: (string | Symbols.AnyRoom)[], + ): Promise<IRoomEvent[]> { + const data: IReadEventFromWidgetRequestData = { + type: eventType, + state_key: stateKey === undefined ? true : stateKey, + }; + if (limit !== undefined) { + data.limit = limit; + } + if (roomIds) { + if (roomIds.includes(Symbols.AnyRoom)) { + data.room_ids = Symbols.AnyRoom; + } else { + data.room_ids = roomIds; + } + } + return this.transport.send<IReadEventFromWidgetRequestData, IReadEventFromWidgetResponseData>( + WidgetApiFromWidgetAction.MSC2876ReadEvents, + data, + ).then(r => r.events); + } + + /** + * Sets a button as disabled or enabled on the modal widget. Buttons are enabled by default. + * @param {ModalButtonID} buttonId The button ID to enable/disable. + * @param {boolean} isEnabled Whether or not the button is enabled. + * @returns {Promise<void>} Resolves when complete. + * @throws Throws if the button cannot be disabled, or the client refuses to disable the button. + */ + public setModalButtonEnabled(buttonId: ModalButtonID, isEnabled: boolean): Promise<void> { + if (buttonId === BuiltInModalButtonID.Close) { + throw new Error("The close button cannot be disabled"); + } + return this.transport.send<ISetModalButtonEnabledActionRequestData>( + WidgetApiFromWidgetAction.SetModalButtonEnabled, {button: buttonId, enabled: isEnabled}, + ).then(); + } + + /** + * Attempts to navigate the client to the given URI. This can only be called with Matrix URIs + * (currently only matrix.to, but in future a Matrix URI scheme will be defined). + * @param {string} uri The URI to navigate to. + * @returns {Promise<void>} Resolves when complete. + * @throws Throws if the URI is invalid or cannot be processed. + * @deprecated This currently relies on an unstable MSC (MSC2931). + */ + public navigateTo(uri: string): Promise<void> { + if (!uri || !uri.startsWith("https://matrix.to/#")) { + throw new Error("Invalid matrix.to URI"); + } + + return this.transport.send<INavigateActionRequestData>( + WidgetApiFromWidgetAction.MSC2931Navigate, {uri}, + ).then(); + } + + /** + * Starts watching for TURN servers, yielding an initial set of credentials as soon as possible, + * and thereafter yielding new credentials whenever the previous ones expire. + * @yields {ITurnServer} The TURN server URIs and credentials currently available to the widget. + */ + public async* getTurnServers(): AsyncGenerator<ITurnServer> { + let setTurnServer: (server: ITurnServer) => void; + + const onUpdateTurnServers = async (ev: CustomEvent<IUpdateTurnServersRequest>) => { + ev.preventDefault(); + setTurnServer(ev.detail.data); + await this.transport.reply<IWidgetApiAcknowledgeResponseData>(ev.detail, {}); + }; + + // Start listening for updates before we even start watching, to catch + // TURN data that is sent immediately + this.on(`action:${WidgetApiToWidgetAction.UpdateTurnServers}`, onUpdateTurnServers); + + // Only send the 'watch' action if we aren't already watching + if (this.turnServerWatchers === 0) { + try { + await this.transport.send<IWidgetApiRequestEmptyData>(WidgetApiFromWidgetAction.WatchTurnServers, {}); + } catch (e) { + this.off(`action:${WidgetApiToWidgetAction.UpdateTurnServers}`, onUpdateTurnServers); + throw e; + } + } + this.turnServerWatchers++; + + try { + // Watch for new data indefinitely (until this generator's return method is called) + while (true) { + yield await new Promise<ITurnServer>(resolve => setTurnServer = resolve); + } + } finally { + // The loop was broken by the caller - clean up + this.off(`action:${WidgetApiToWidgetAction.UpdateTurnServers}`, onUpdateTurnServers); + + // Since sending the 'unwatch' action will end updates for all other + // consumers, only send it if we're the only consumer remaining + this.turnServerWatchers--; + if (this.turnServerWatchers === 0) { + await this.transport.send<IWidgetApiRequestEmptyData>(WidgetApiFromWidgetAction.UnwatchTurnServers, {}); + } + } + } + + /** + * Search for users in the user directory. + * @param searchTerm The term to search for. + * @param limit The maximum number of results to return. If not supplied, the + * @returns Resolves to the search results. + */ + public async searchUserDirectory( + searchTerm: string, + limit?: number, + ): Promise<IUserDirectorySearchFromWidgetResponseData> { + const versions = await this.getClientVersions(); + if (!versions.includes(UnstableApiVersion.MSC3973)) { + throw new Error("The user_directory_search action is not supported by the client.") + } + + const data: IUserDirectorySearchFromWidgetRequestData = { + search_term: searchTerm, + limit, + }; + + return this.transport.send< + IUserDirectorySearchFromWidgetRequestData, + IUserDirectorySearchFromWidgetResponseData + >(WidgetApiFromWidgetAction.MSC3973UserDirectorySearch, data); + } + + /** + * Starts the communication channel. This should be done early to ensure + * that messages are not missed. Communication can only be stopped by the client. + */ + public start() { + this.transport.start(); + this.getClientVersions().then(v => { + if (v.includes(UnstableApiVersion.MSC2974)) { + this.supportsMSC2974Renegotiate = true; + } + }); + } + + private handleMessage(ev: CustomEvent<IWidgetApiRequest>) { + 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 WidgetApiToWidgetAction.SupportedApiVersions: + return this.replyVersions(<ISupportedVersionsActionRequest>ev.detail); + case WidgetApiToWidgetAction.Capabilities: + return this.handleCapabilities(<ICapabilitiesActionRequest>ev.detail); + case WidgetApiToWidgetAction.UpdateVisibility: + return this.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack to avoid error spam + case WidgetApiToWidgetAction.NotifyCapabilities: + return this.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack to avoid error spam + default: + return this.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{ + error: { + message: "Unknown or unsupported action: " + ev.detail.action, + }, + }); + } + } + } + + private replyVersions(request: ISupportedVersionsActionRequest) { + this.transport.reply<ISupportedVersionsActionResponseData>(request, { + supported_versions: CurrentApiVersions, + }); + } + + public getClientVersions(): Promise<ApiVersion[]> { + if (Array.isArray(this.cachedClientVersions)) { + return Promise.resolve(this.cachedClientVersions); + } + + return this.transport.send<IWidgetApiRequestEmptyData, ISupportedVersionsActionResponseData>( + WidgetApiFromWidgetAction.SupportedApiVersions, {}, + ).then(r => { + this.cachedClientVersions = r.supported_versions; + return r.supported_versions; + }).catch(e => { + console.warn("non-fatal error getting supported client versions: ", e); + return []; + }); + } + + private handleCapabilities(request: ICapabilitiesActionRequest) { + if (this.capabilitiesFinished) { + return this.transport.reply<IWidgetApiErrorResponseData>(request, { + error: { + message: "Capability negotiation already completed", + }, + }); + } + + // See if we can expect a capabilities notification or not + return this.getClientVersions().then(v => { + if (v.includes(UnstableApiVersion.MSC2871)) { + this.once( + `action:${WidgetApiToWidgetAction.NotifyCapabilities}`, + (ev: CustomEvent<INotifyCapabilitiesActionRequest>) => { + this.approvedCapabilities = ev.detail.data.approved; + this.emit("ready"); + }, + ); + } else { + // if we can't expect notification, we're as done as we can be + this.emit("ready"); + } + + // in either case, reply to that capabilities request + this.capabilitiesFinished = true; + return this.transport.reply<ICapabilitiesActionResponseData>(request, { + capabilities: this.requestedCapabilities, + }); + }); + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/driver/WidgetDriver.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/driver/WidgetDriver.ts new file mode 100644 index 0000000..9fdeee2 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/driver/WidgetDriver.ts @@ -0,0 +1,247 @@ +/* + * 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 { Capability, IOpenIDCredentials, OpenIDRequestState, SimpleObservable, IRoomEvent, ITurnServer } from ".."; + +export interface ISendEventDetails { + roomId: string; + eventId: string; +} + +export interface IOpenIDUpdate { + state: OpenIDRequestState; + token?: IOpenIDCredentials; +} + +export interface IReadEventRelationsResult { + chunk: IRoomEvent[]; + nextBatch?: string; + prevBatch?: string; +} + +export interface ISearchUserDirectoryResult { + limited: boolean; + results: Array<{ + userId: string; + displayName?: string; + avatarUrl?: string; + }>; +} + +/** + * Represents the functions and behaviour the widget-api is unable to + * do, such as prompting the user for information or interacting with + * the UI. Clients are expected to implement this class and override + * any functions they need/want to support. + * + * This class assumes the client will have a context of a Widget + * instance already. + */ +export abstract class WidgetDriver { + /** + * Verifies the widget's requested capabilities, returning the ones + * it is approved to use. Mutating the requested capabilities will + * have no effect. + * + * This SHOULD result in the user being prompted to approve/deny + * capabilities. + * + * By default this rejects all capabilities (returns an empty set). + * @param {Set<Capability>} requested The set of requested capabilities. + * @returns {Promise<Set<Capability>>} Resolves to the allowed capabilities. + */ + public validateCapabilities(requested: Set<Capability>): Promise<Set<Capability>> { + return Promise.resolve(new Set()); + } + + /** + * Sends an event into a room. If `roomId` is falsy, the client should send the event + * into the room the user is currently looking at. The widget API will have already + * verified that the widget is capable of sending the event to that room. + * @param {string} eventType The event type to be sent. + * @param {*} content The content for the event. + * @param {string|null} stateKey The state key if this is a state event, otherwise null. + * May be an empty string. + * @param {string|null} roomId The room ID to send the event to. If falsy, the room the + * user is currently looking at. + * @returns {Promise<ISendEventDetails>} Resolves when the event has been sent with + * details of that event. + * @throws Rejected when the event could not be sent. + */ + public sendEvent( + eventType: string, + content: unknown, + stateKey: string | null = null, + roomId: string | null = null, + ): Promise<ISendEventDetails> { + return Promise.reject(new Error("Failed to override function")); + } + + /** + * Sends a to-device event. The widget API will have already verified that the widget + * is capable of sending the event. + * @param {string} eventType The event type to be sent. + * @param {boolean} encrypted Whether to encrypt the message contents. + * @param {Object} contentMap A map from user ID and device ID to event content. + * @returns {Promise<void>} Resolves when the event has been sent. + * @throws Rejected when the event could not be sent. + */ + public sendToDevice( + eventType: string, + encrypted: boolean, + contentMap: { [userId: string]: { [deviceId: string]: object } }, + ): Promise<void> { + return Promise.reject(new Error("Failed to override function")); + } + + /** + * Reads all events of the given type, and optionally `msgtype` (if applicable/defined), + * the user has access to. The widget API will have already verified that the widget is + * capable of receiving the events. Less events than the limit are allowed to be returned, + * but not more. If `roomIds` is supplied, it may contain `Symbols.AnyRoom` to denote that + * `limit` in each of the client's known rooms should be returned. When `null`, only the + * room the user is currently looking at should be considered. + * @param eventType The event type to be read. + * @param msgtype The msgtype of the events to be read, if applicable/defined. + * @param limit The maximum number of events to retrieve per room. Will be zero to denote "as many + * as possible". + * @param roomIds When null, the user's currently viewed room. Otherwise, the list of room IDs + * to look within, possibly containing Symbols.AnyRoom to denote all known rooms. + * @returns {Promise<IRoomEvent[]>} Resolves to the room events, or an empty array. + */ + public readRoomEvents( + eventType: string, + msgtype: string | undefined, + limit: number, + roomIds: string[] | null = null, + ): Promise<IRoomEvent[]> { + return Promise.resolve([]); + } + + /** + * Reads all events of the given type, and optionally state key (if applicable/defined), + * the user has access to. The widget API will have already verified that the widget is + * capable of receiving the events. Less events than the limit are allowed to be returned, + * but not more. If `roomIds` is supplied, it may contain `Symbols.AnyRoom` to denote that + * `limit` in each of the client's known rooms should be returned. When `null`, only the + * room the user is currently looking at should be considered. + * @param eventType The event type to be read. + * @param stateKey The state key of the events to be read, if applicable/defined. + * @param limit The maximum number of events to retrieve. Will be zero to denote "as many + * as possible". + * @param roomIds When null, the user's currently viewed room. Otherwise, the list of room IDs + * to look within, possibly containing Symbols.AnyRoom to denote all known rooms. + * @returns {Promise<IRoomEvent[]>} Resolves to the state events, or an empty array. + */ + public readStateEvents( + eventType: string, + stateKey: string | undefined, + limit: number, + roomIds: string[] | null = null, + ): Promise<IRoomEvent[]> { + return Promise.resolve([]); + } + + /** + * Reads all events that are related to a given event. The widget API will + * have already verified that the widget is capable of receiving the event, + * or will make sure to reject access to events which are returned from this + * function, but are not capable of receiving. If `relationType` or `eventType` + * are set, the returned events should already be filtered. Less events than + * the limit are allowed to be returned, but not more. + * @param eventId The id of the parent event to be read. + * @param roomId The room to look within. When undefined, the user's + * currently viewed room. + * @param relationType The relationship type of child events to search for. + * When undefined, all relations are returned. + * @param eventType The event type of child events to search for. When undefined, + * all related events are returned. + * @param from The pagination token to start returning results from, as + * received from a previous call. If not supplied, results start at the most + * recent topological event known to the server. + * @param to The pagination token to stop returning results at. If not + * supplied, results continue up to limit or until there are no more events. + * @param limit The maximum number of events to retrieve per room. If not + * supplied, the server will apply a default limit. + * @param direction The direction to search for according to MSC3715 + * @returns Resolves to the room relations. + */ + public readEventRelations( + eventId: string, + roomId?: string, + relationType?: string, + eventType?: string, + from?: string, + to?: string, + limit?: number, + direction?: 'f' | 'b', + ): Promise<IReadEventRelationsResult> { + return Promise.resolve({ chunk: [] }); + } + + /** + * Asks the user for permission to validate their identity through OpenID Connect. The + * interface for this function is an observable which accepts the state machine of the + * OIDC exchange flow. For example, if the client/user blocks the request then it would + * feed back a `{state: Blocked}` into the observable. Similarly, if the user already + * approved the widget then a `{state: Allowed}` would be fed into the observable alongside + * the token itself. If the client is asking for permission, it should feed in a + * `{state: PendingUserConfirmation}` followed by the relevant Allowed or Blocked state. + * + * The widget API will reject the widget's request with an error if this contract is not + * met properly. By default, the widget driver will block all OIDC requests. + * @param {SimpleObservable<IOpenIDUpdate>} observer The observable to feed updates into. + */ + public askOpenID(observer: SimpleObservable<IOpenIDUpdate>) { + observer.update({state: OpenIDRequestState.Blocked}); + } + + /** + * Navigates the client with a matrix.to URI. In future this function will also be provided + * with the Matrix URIs once matrix.to is replaced. The given URI will have already been + * lightly checked to ensure it looks like a valid URI, though the implementation is recommended + * to do further checks on the URI. + * @param {string} uri The URI to navigate to. + * @returns {Promise<void>} Resolves when complete. + * @throws Throws if there's a problem with the navigation, such as invalid format. + */ + public navigate(uri: string): Promise<void> { + throw new Error("Navigation is not implemented"); + } + + /** + * Polls for TURN server data, yielding an initial set of credentials as soon as possible, and + * thereafter yielding new credentials whenever the previous ones expire. The widget API will + * have already verified that the widget has permission to access TURN servers. + * @yields {ITurnServer} The TURN server URIs and credentials currently available to the client. + */ + public getTurnServers(): AsyncGenerator<ITurnServer> { + throw new Error("TURN server support is not implemented"); + } + + /** + * Search for users in the user directory. + * @param searchTerm The term to search for. + * @param limit The maximum number of results to return. If not supplied, the + * @returns Resolves to the search results. + */ + public searchUserDirectory( + searchTerm: string, + limit?: number, + ): Promise<ISearchUserDirectoryResult> { + return Promise.resolve({ limited: false, results: [] }); + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/index.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/index.ts new file mode 100644 index 0000000..b7247c9 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/index.ts @@ -0,0 +1,73 @@ +/* +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. +*/ + +// Primary structures +export * from "./WidgetApi"; +export * from "./ClientWidgetApi"; +export * from "./Symbols"; + +// Transports (not sure why you'd use these directly, but might as well export all the things) +export * from "./transport/ITransport"; +export * from "./transport/PostmessageTransport"; + +// Interfaces and simple models +export * from "./interfaces/ICustomWidgetData"; +export * from "./interfaces/IJitsiWidgetData"; +export * from "./interfaces/IStickerpickerWidgetData"; +export * from "./interfaces/IWidget"; +export * from "./interfaces/WidgetType"; +export * from "./interfaces/IWidgetApiErrorResponse"; +export * from "./interfaces/IWidgetApiRequest"; +export * from "./interfaces/IWidgetApiResponse"; +export * from "./interfaces/WidgetApiAction"; +export * from "./interfaces/WidgetApiDirection"; +export * from "./interfaces/ApiVersion"; +export * from "./interfaces/Capabilities"; +export * from "./interfaces/CapabilitiesAction"; +export * from "./interfaces/ContentLoadedAction"; +export * from "./interfaces/ScreenshotAction"; +export * from "./interfaces/StickerAction"; +export * from "./interfaces/StickyAction"; +export * from "./interfaces/SupportedVersionsAction"; +export * from "./interfaces/VisibilityAction"; +export * from "./interfaces/GetOpenIDAction"; +export * from "./interfaces/OpenIDCredentialsAction"; +export * from "./interfaces/WidgetKind"; +export * from "./interfaces/ModalButtonKind"; +export * from "./interfaces/ModalWidgetActions"; +export * from "./interfaces/SetModalButtonEnabledAction"; +export * from "./interfaces/WidgetConfigAction"; +export * from "./interfaces/SendEventAction"; +export * from "./interfaces/SendToDeviceAction"; +export * from "./interfaces/ReadEventAction"; +export * from "./interfaces/IRoomEvent"; +export * from "./interfaces/NavigateAction"; +export * from "./interfaces/TurnServerActions"; +export * from "./interfaces/ReadRelationsAction"; + +// Complex models +export * from "./models/WidgetEventCapability"; +export * from "./models/validation/url"; +export * from "./models/validation/utils"; +export * from "./models/Widget"; +export * from "./models/WidgetParser"; + +// Utilities +export * from "./templating/url-template"; +export * from "./util/SimpleObservable"; + +// Drivers +export * from "./driver/WidgetDriver"; diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ApiVersion.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ApiVersion.ts new file mode 100644 index 0000000..6586c14 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ApiVersion.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2020 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. + */ + +export enum MatrixApiVersion { + Prerelease1 = "0.0.1", + Prerelease2 = "0.0.2", + //V010 = "0.1.0", // first release +} + +export enum UnstableApiVersion { + MSC2762 = "org.matrix.msc2762", + MSC2871 = "org.matrix.msc2871", + MSC2931 = "org.matrix.msc2931", + MSC2974 = "org.matrix.msc2974", + MSC2876 = "org.matrix.msc2876", + MSC3819 = "org.matrix.msc3819", + MSC3846 = "town.robin.msc3846", + MSC3869 = "org.matrix.msc3869", + MSC3973 = "org.matrix.msc3973", +} + +export type ApiVersion = MatrixApiVersion | UnstableApiVersion | string; + +export const CurrentApiVersions: ApiVersion[] = [ + MatrixApiVersion.Prerelease1, + MatrixApiVersion.Prerelease2, + //MatrixApiVersion.V010, + UnstableApiVersion.MSC2762, + UnstableApiVersion.MSC2871, + UnstableApiVersion.MSC2931, + UnstableApiVersion.MSC2974, + UnstableApiVersion.MSC2876, + UnstableApiVersion.MSC3819, + UnstableApiVersion.MSC3846, + UnstableApiVersion.MSC3869, + UnstableApiVersion.MSC3973, +]; diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/Capabilities.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/Capabilities.ts new file mode 100644 index 0000000..1572105 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/Capabilities.ts @@ -0,0 +1,71 @@ +/* + * 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 { Symbols } from "../Symbols"; + +export enum MatrixCapabilities { + Screenshots = "m.capability.screenshot", + StickerSending = "m.sticker", + AlwaysOnScreen = "m.always_on_screen", + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + * Ask Element to not give the option to move the widget into a separate tab. + */ + RequiresClient = "io.element.requires_client", + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC2931Navigate = "org.matrix.msc2931.navigate", + MSC3846TurnServers = "town.robin.msc3846.turn_servers", + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC3973UserDirectorySearch = "org.matrix.msc3973.user_directory_search", +} + +export type Capability = MatrixCapabilities | string; + +export const StickerpickerCapabilities: Capability[] = [MatrixCapabilities.StickerSending]; +export const VideoConferenceCapabilities: Capability[] = [MatrixCapabilities.AlwaysOnScreen]; + +/** + * Determines if a capability is a capability for a timeline. + * @param {Capability} capability The capability to test. + * @returns {boolean} True if a timeline capability, false otherwise. + */ +export function isTimelineCapability(capability: Capability): boolean { + // TODO: Change when MSC2762 becomes stable. + return capability?.startsWith("org.matrix.msc2762.timeline:"); +} + +/** + * Determines if a capability is a timeline capability for the given room. + * @param {Capability} capability The capability to test. + * @param {string | Symbols.AnyRoom} roomId The room ID, or `Symbols.AnyRoom` for that designation. + * @returns {boolean} True if a matching capability, false otherwise. + */ +export function isTimelineCapabilityFor(capability: Capability, roomId: string | Symbols.AnyRoom): boolean { + return capability === `org.matrix.msc2762.timeline:${roomId}`; +} + +/** + * Gets the room ID described by a timeline capability. + * @param {string} capability The capability to parse. + * @returns {string} The room ID. + */ +export function getTimelineRoomIDFromCapability(capability: Capability): string { + return capability.substring(capability.indexOf(":") + 1); +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/CapabilitiesAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/CapabilitiesAction.ts new file mode 100644 index 0000000..365bb79 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/CapabilitiesAction.ts @@ -0,0 +1,60 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData, IWidgetApiRequestEmptyData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { Capability } from "./Capabilities"; +import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponseData } from "./IWidgetApiResponse"; + +export interface ICapabilitiesActionRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.Capabilities; + data: IWidgetApiRequestEmptyData; +} + +export interface ICapabilitiesActionResponseData extends IWidgetApiResponseData { + capabilities: Capability[]; +} + +export interface ICapabilitiesActionResponse extends ICapabilitiesActionRequest { + response: ICapabilitiesActionResponseData; +} + +export interface INotifyCapabilitiesActionRequestData extends IWidgetApiRequestData { + requested: Capability[]; + approved: Capability[]; +} + +export interface INotifyCapabilitiesActionRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.NotifyCapabilities; + data: INotifyCapabilitiesActionRequestData; +} + +export interface INotifyCapabilitiesActionResponse extends INotifyCapabilitiesActionRequest { + response: IWidgetApiAcknowledgeResponseData; +} + +export interface IRenegotiateCapabilitiesActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities; + data: IRenegotiateCapabilitiesRequestData; +} + +export interface IRenegotiateCapabilitiesRequestData extends IWidgetApiResponseData { + capabilities: Capability[]; +} + +export interface IRenegotiateCapabilitiesActionResponse extends IRenegotiateCapabilitiesActionRequest { + // nothing +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ContentLoadedAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ContentLoadedAction.ts new file mode 100644 index 0000000..ceca93f --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ContentLoadedAction.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestEmptyData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData } from "./IWidgetApiResponse"; + +export interface IContentLoadedActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.ContentLoaded; + data: IWidgetApiRequestEmptyData; +} + +export interface IContentLoadedActionResponse extends IContentLoadedActionRequest { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/GetOpenIDAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/GetOpenIDAction.ts new file mode 100644 index 0000000..000313c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/GetOpenIDAction.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; + +export enum OpenIDRequestState { + Allowed = "allowed", + Blocked = "blocked", + PendingUserConfirmation = "request", +} + +export interface IOpenIDCredentials { + access_token?: string; // eslint-disable-line camelcase + expires_in?: number; // eslint-disable-line camelcase + matrix_server_name?: string; // eslint-disable-line camelcase + token_type?: "Bearer" | string; // eslint-disable-line camelcase +} + +export interface IGetOpenIDActionRequestData extends IWidgetApiRequestData { + // nothing +} + +export interface IGetOpenIDActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.GetOpenIDCredentials; + data: IGetOpenIDActionRequestData; +} + +export interface IGetOpenIDActionResponseData extends IWidgetApiResponseData, IOpenIDCredentials { + state: OpenIDRequestState; +} + +export interface IGetOpenIDActionResponse extends IGetOpenIDActionRequest { + response: IGetOpenIDActionResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ICustomWidgetData.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ICustomWidgetData.ts new file mode 100644 index 0000000..56657fb --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ICustomWidgetData.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2020 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 { IWidgetData } from "./IWidget"; + +/** + * Widget data for m.custom specifically. + */ +export interface ICustomWidgetData extends IWidgetData { + /** + * The URL for the widget if the templated URL is not exactly what will be loaded. + */ + url?: string; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IJitsiWidgetData.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IJitsiWidgetData.ts new file mode 100644 index 0000000..65b22a0 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IJitsiWidgetData.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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 { IWidgetData } from "./IWidget"; + +/** + * Widget data for m.jitsi widgets. + */ +export interface IJitsiWidgetData extends IWidgetData { + /** + * The domain where the Jitsi Meet conference is being held. + */ + domain: string; + + /** + * The conference ID (also known as the room name) where the conference is being held. + */ + conferenceId: string; + + /** + * Optional. True to indicate that the conference should be without video, false + * otherwise (default). + */ + isAudioOnly?: boolean; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IRoomEvent.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IRoomEvent.ts new file mode 100644 index 0000000..5e90005 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IRoomEvent.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +export interface IRoomEvent { + type: string; + sender: string; + event_id: string; // eslint-disable-line camelcase + room_id: string; // eslint-disable-line camelcase + state_key?: string; // eslint-disable-line camelcase + origin_server_ts: number; // eslint-disable-line camelcase + content: unknown; + unsigned: unknown; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IStickerpickerWidgetData.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IStickerpickerWidgetData.ts new file mode 100644 index 0000000..1459fa5 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IStickerpickerWidgetData.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 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 { IWidgetData } from "./IWidget"; + +export interface IStickerpickerWidgetData extends IWidgetData { + // no additional properties (for now) +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidget.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidget.ts new file mode 100644 index 0000000..a6ee670 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidget.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2020 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 { WidgetType } from "./WidgetType"; + +/** + * Widget data. + */ +export interface IWidgetData { + /** + * Optional title for the widget. + */ + title?: string; + + /** + * Custom keys for inclusion in the template URL. + */ + [key: string]: unknown; +} + +/** + * Common properties of a widget. + * https://matrix.org/docs/spec/widgets/latest#widgetcommonproperties-schema + */ +export interface IWidget { + /** + * The ID of the widget. + */ + id: string; + + /** + * The user ID who originally created the widget. + */ + creatorUserId: string; + + /** + * Optional name for the widget. + */ + name?: string; + + /** + * The type of widget. + */ + type: WidgetType; + + /** + * The URL for the widget, with template variables. + */ + url: string; + + /** + * Optional flag to indicate whether or not the client should initiate communication + * right after the iframe loads (default, true) or when the widget indicates it is + * ready (false). + */ + waitForIframeLoad?: boolean; + + /** + * Data for the widget. + */ + data?: IWidgetData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiErrorResponse.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiErrorResponse.ts new file mode 100644 index 0000000..f9e123f --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiErrorResponse.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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 { IWidgetApiResponse, IWidgetApiResponseData } from "./IWidgetApiResponse"; + +export interface IWidgetApiErrorResponseData extends IWidgetApiResponseData { + error: { + message: string; + }; +} + +export interface IWidgetApiErrorResponse extends IWidgetApiResponse { + response: IWidgetApiErrorResponseData; +} + +export function isErrorResponse(responseData: IWidgetApiResponseData): boolean { + if ("error" in responseData) { + const err = <IWidgetApiErrorResponseData>responseData; + return !!err.error.message; + } + return false; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiRequest.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiRequest.ts new file mode 100644 index 0000000..ec9e211 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiRequest.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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 { WidgetApiDirection } from "./WidgetApiDirection"; +import { WidgetApiAction } from "./WidgetApiAction"; + +export interface IWidgetApiRequestData { + [key: string]: unknown; +} + +export interface IWidgetApiRequestEmptyData extends IWidgetApiRequestData { + // nothing +} + +export interface IWidgetApiRequest { + api: WidgetApiDirection; + requestId: string; + action: WidgetApiAction; + widgetId: string; + data: IWidgetApiRequestData; + // XXX: This is for Scalar support + // TODO: Fix scalar + visible?: any; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiResponse.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiResponse.ts new file mode 100644 index 0000000..2347b6f --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/IWidgetApiResponse.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest } from "./IWidgetApiRequest"; + +export interface IWidgetApiResponseData { + [key: string]: unknown; +} + +export interface IWidgetApiAcknowledgeResponseData extends IWidgetApiResponseData { + // nothing +} + +export interface IWidgetApiResponse extends IWidgetApiRequest { + response: IWidgetApiResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ModalButtonKind.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ModalButtonKind.ts new file mode 100644 index 0000000..e82c939 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ModalButtonKind.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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. + */ + +export enum ModalButtonKind { + Primary = "m.primary", + Secondary = "m.secondary", + Warning = "m.warning", + Danger = "m.danger", + Link = "m.link", +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ModalWidgetActions.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ModalWidgetActions.ts new file mode 100644 index 0000000..b8f07d4 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ModalWidgetActions.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponse } from "./IWidgetApiResponse"; +import { IWidget } from "./IWidget"; +import { ModalButtonKind } from "./ModalButtonKind"; + +export enum BuiltInModalButtonID { + Close = "m.close", +} +export type ModalButtonID = BuiltInModalButtonID | string; + +export interface IModalWidgetCreateData extends IWidgetApiRequestData { + [key: string]: unknown; +} + +export interface IModalWidgetReturnData { + [key: string]: unknown; +} + +// Types for a normal modal requesting the opening a modal widget +export interface IModalWidgetOpenRequestDataButton { + id: ModalButtonID; + label: string; + kind: ModalButtonKind | string; + disabled?: boolean; +} + +export interface IModalWidgetOpenRequestData extends IModalWidgetCreateData, Omit<IWidget, "id" | "creatorUserId"> { + buttons?: IModalWidgetOpenRequestDataButton[]; +} + +export interface IModalWidgetOpenRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.OpenModalWidget; + data: IModalWidgetOpenRequestData; +} + +export interface IModalWidgetOpenResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} + +// Types for a modal widget receiving notifications that its buttons have been pressed +export interface IModalWidgetButtonClickedRequestData extends IWidgetApiRequestData { + id: IModalWidgetOpenRequestDataButton["id"]; +} + +export interface IModalWidgetButtonClickedRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.ButtonClicked; + data: IModalWidgetButtonClickedRequestData; +} + +export interface IModalWidgetButtonClickedResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} + +// Types for a modal widget requesting close +export interface IModalWidgetCloseRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.CloseModalWidget; + data: IModalWidgetReturnData; +} + +export interface IModalWidgetCloseResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} + +// Types for a normal widget being notified that the modal widget it opened has been closed +export interface IModalWidgetCloseNotificationRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.CloseModalWidget; + data: IModalWidgetReturnData; +} + +export interface IModalWidgetCloseNotificationResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/NavigateAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/NavigateAction.ts new file mode 100644 index 0000000..04960eb --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/NavigateAction.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData } from "./IWidgetApiResponse"; + +export interface INavigateActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC2931Navigate; + data: INavigateActionRequestData; +} + +export interface INavigateActionRequestData extends IWidgetApiRequestData { + uri: string; +} + +export interface INavigateActionResponse extends INavigateActionRequest { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/OpenIDCredentialsAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/OpenIDCredentialsAction.ts new file mode 100644 index 0000000..c4766f1 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/OpenIDCredentialsAction.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; +import { IOpenIDCredentials, OpenIDRequestState } from "./GetOpenIDAction"; + +export interface IOpenIDCredentialsActionRequestData extends IWidgetApiRequestData, IOpenIDCredentials { + state: OpenIDRequestState; + original_request_id: string; // eslint-disable-line camelcase +} + +export interface IOpenIDCredentialsActionRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.OpenIDCredentials; + data: IOpenIDCredentialsActionRequestData; +} + +export interface IOpenIDCredentialsActionResponseData extends IWidgetApiResponseData { + // nothing +} + +export interface IOpenIDCredentialsIDActionResponse extends IOpenIDCredentialsActionRequest { + response: IOpenIDCredentialsActionResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ReadEventAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ReadEventAction.ts new file mode 100644 index 0000000..8cea7cf --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ReadEventAction.ts @@ -0,0 +1,42 @@ +/* + * Copyright 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; +import { IRoomEvent } from "./IRoomEvent"; +import { Symbols } from "../Symbols"; + +export interface IReadEventFromWidgetRequestData extends IWidgetApiRequestData { + state_key?: string | boolean; // eslint-disable-line camelcase + msgtype?: string; + type: string; + limit?: number; + room_ids?: Symbols.AnyRoom | string[]; // eslint-disable-line camelcase +} + +export interface IReadEventFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC2876ReadEvents; + data: IReadEventFromWidgetRequestData; +} + +export interface IReadEventFromWidgetResponseData extends IWidgetApiResponseData { + events: IRoomEvent[]; +} + +export interface IReadEventFromWidgetActionResponse extends IReadEventFromWidgetActionRequest { + response: IReadEventFromWidgetResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ReadRelationsAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ReadRelationsAction.ts new file mode 100644 index 0000000..76a041a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ReadRelationsAction.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Nordeck IT + Consulting GmbH. + * + * 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 { IRoomEvent } from "./IRoomEvent"; +import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; + +export interface IReadRelationsFromWidgetRequestData extends IWidgetApiRequestData { + event_id: string; // eslint-disable-line camelcase + rel_type?: string; // eslint-disable-line camelcase + event_type?: string; // eslint-disable-line camelcase + room_id?: string; // eslint-disable-line camelcase + + limit?: number; + from?: string; + to?: string; + direction?: 'f' | 'b'; +} + +export interface IReadRelationsFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC3869ReadRelations; + data: IReadRelationsFromWidgetRequestData; +} + +export interface IReadRelationsFromWidgetResponseData extends IWidgetApiResponseData { + chunk: IRoomEvent[]; + + next_batch?: string; // eslint-disable-line camelcase + prev_batch?: string; // eslint-disable-line camelcase +} + +export interface IReadRelationsFromWidgetActionResponse extends IReadRelationsFromWidgetActionRequest { + response: IReadRelationsFromWidgetResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ScreenshotAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ScreenshotAction.ts new file mode 100644 index 0000000..f9ec315 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/ScreenshotAction.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestEmptyData } from "./IWidgetApiRequest"; +import { WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; + +export interface IScreenshotActionRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.TakeScreenshot; + data: IWidgetApiRequestEmptyData; +} + +export interface IScreenshotActionResponseData extends IWidgetApiResponseData { + screenshot: Blob; +} + +export interface IScreenshotActionResponse extends IScreenshotActionRequest { + response: IScreenshotActionResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SendEventAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SendEventAction.ts new file mode 100644 index 0000000..8fe6da0 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SendEventAction.ts @@ -0,0 +1,57 @@ +/* + * 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; +import { IRoomEvent } from "./IRoomEvent"; + +export interface ISendEventFromWidgetRequestData extends IWidgetApiRequestData { + state_key?: string; // eslint-disable-line camelcase + type: string; + content: unknown; + room_id?: string; // eslint-disable-line camelcase +} + +export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.SendEvent; + data: ISendEventFromWidgetRequestData; +} + +export interface ISendEventFromWidgetResponseData extends IWidgetApiResponseData { + room_id: string; // eslint-disable-line camelcase + event_id: string; // eslint-disable-line camelcase +} + +export interface ISendEventFromWidgetActionResponse extends ISendEventFromWidgetActionRequest { + response: ISendEventFromWidgetResponseData; +} + +export interface ISendEventToWidgetRequestData extends IWidgetApiRequestData, IRoomEvent { +} + +export interface ISendEventToWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.SendEvent; + data: ISendEventToWidgetRequestData; +} + +export interface ISendEventToWidgetResponseData extends IWidgetApiResponseData { + // nothing +} + +export interface ISendEventToWidgetActionResponse extends ISendEventToWidgetActionRequest { + response: ISendEventToWidgetResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SendToDeviceAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SendToDeviceAction.ts new file mode 100644 index 0000000..e7507b3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SendToDeviceAction.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2022 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; +import { IRoomEvent } from "./IRoomEvent"; + +export interface ISendToDeviceFromWidgetRequestData extends IWidgetApiRequestData { + type: string; + encrypted: boolean; + messages: { [userId: string]: { [deviceId: string]: object } }; +} + +export interface ISendToDeviceFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.SendToDevice; + data: ISendToDeviceFromWidgetRequestData; +} + +export interface ISendToDeviceFromWidgetResponseData extends IWidgetApiResponseData { + // nothing +} + +export interface ISendToDeviceFromWidgetActionResponse extends ISendToDeviceFromWidgetActionRequest { + response: ISendToDeviceFromWidgetResponseData; +} + +export interface ISendToDeviceToWidgetRequestData extends IWidgetApiRequestData, IRoomEvent { + encrypted: boolean; +} + +export interface ISendToDeviceToWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.SendToDevice; + data: ISendToDeviceToWidgetRequestData; +} + +export interface ISendToDeviceToWidgetResponseData extends IWidgetApiResponseData { + // nothing +} + +export interface ISendToDeviceToWidgetActionResponse extends ISendToDeviceToWidgetActionRequest { + response: ISendToDeviceToWidgetResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SetModalButtonEnabledAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SetModalButtonEnabledAction.ts new file mode 100644 index 0000000..5702e8c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SetModalButtonEnabledAction.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData } from "./IWidgetApiResponse"; +import { ModalButtonID } from "./ModalWidgetActions"; + +export interface ISetModalButtonEnabledActionRequestData extends IWidgetApiRequestData { + enabled: boolean; + button: ModalButtonID; +} + +export interface ISetModalButtonEnabledActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.SetModalButtonEnabled; + data: ISetModalButtonEnabledActionRequestData; +} + +export interface ISetModalButtonEnabledActionResponse extends ISetModalButtonEnabledActionRequest { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/StickerAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/StickerAction.ts new file mode 100644 index 0000000..13cb94a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/StickerAction.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData } from "./IWidgetApiResponse"; + +export interface IStickerActionRequestData extends IWidgetApiRequestData { + name: string; + description?: string; + content: { + url: string; + info?: { + h?: number; + w?: number; + mimetype?: string; + size?: number; + thumbnail_info?: { // eslint-disable-line camelcase + h?: number; + w?: number; + mimetype?: string; + size?: number; + }; + }; + }; +} + +export interface IStickerActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.SendSticker; + data: IStickerActionRequestData; +} + +export interface IStickerActionResponse extends IStickerActionRequest { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/StickyAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/StickyAction.ts new file mode 100644 index 0000000..7d49f02 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/StickyAction.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; + +export interface IStickyActionRequestData extends IWidgetApiRequestData { + value: boolean; +} + +export interface IStickyActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.UpdateAlwaysOnScreen; + data: IStickyActionRequestData; +} + +export interface IStickyActionResponseData extends IWidgetApiResponseData { + success: boolean; +} + +export interface IStickyActionResponse extends IStickyActionRequest { + response: IStickyActionResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SupportedVersionsAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SupportedVersionsAction.ts new file mode 100644 index 0000000..8486ebc --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/SupportedVersionsAction.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestEmptyData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { ApiVersion } from "./ApiVersion"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; + +export interface ISupportedVersionsActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.SupportedApiVersions | WidgetApiToWidgetAction.SupportedApiVersions; + data: IWidgetApiRequestEmptyData; +} + +export interface ISupportedVersionsActionResponseData extends IWidgetApiResponseData { + supported_versions: ApiVersion[]; // eslint-disable-line camelcase +} + +export interface ISupportedVersionsActionResponse extends ISupportedVersionsActionRequest { + response: ISupportedVersionsActionResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/TurnServerActions.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/TurnServerActions.ts new file mode 100644 index 0000000..3a9bd29 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/TurnServerActions.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2022 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 { IWidgetApiRequest, IWidgetApiRequestData, IWidgetApiRequestEmptyData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponse } from "./IWidgetApiResponse"; + +export interface ITurnServer { + uris: string[]; + username: string; + password: string; +} + +export interface IWatchTurnServersRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.WatchTurnServers; + data: IWidgetApiRequestEmptyData; +} + +export interface IWatchTurnServersResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} + +export interface IUnwatchTurnServersRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.UnwatchTurnServers; + data: IWidgetApiRequestEmptyData; +} + +export interface IUnwatchTurnServersResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} + +export interface IUpdateTurnServersRequestData extends IWidgetApiRequestData, ITurnServer { +} + +export interface IUpdateTurnServersRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.UpdateTurnServers; + data: IUpdateTurnServersRequestData; +} + +export interface IUpdateTurnServersResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/UserDirectorySearchAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/UserDirectorySearchAction.ts new file mode 100644 index 0000000..fb900cc --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/UserDirectorySearchAction.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Nordeck IT + Consulting GmbH. + * + * 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; + +export interface IUserDirectorySearchFromWidgetRequestData extends IWidgetApiRequestData { + search_term: string; // eslint-disable-line camelcase + limit?: number; +} + +export interface IUserDirectorySearchFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC3973UserDirectorySearch; + data: IUserDirectorySearchFromWidgetRequestData; +} + +export interface IUserDirectorySearchFromWidgetResponseData extends IWidgetApiResponseData { + limited: boolean; + results: Array<{ + user_id: string; // eslint-disable-line camelcase + display_name?: string; // eslint-disable-line camelcase + avatar_url?: string; // eslint-disable-line camelcase + }>; +} + +export interface IUserDirectorySearchFromWidgetActionResponse extends IUserDirectorySearchFromWidgetActionRequest { + response: IUserDirectorySearchFromWidgetResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/VisibilityAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/VisibilityAction.ts new file mode 100644 index 0000000..55aa53f --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/VisibilityAction.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData } from "./IWidgetApiResponse"; + +export interface IVisibilityActionRequestData extends IWidgetApiRequestData { + visible: boolean; +} + +export interface IVisibilityActionRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.UpdateVisibility; + data: IVisibilityActionRequestData; +} + +export interface IVisibilityActionResponse extends IVisibilityActionRequest { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetApiAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetApiAction.ts new file mode 100644 index 0000000..34c8aa3 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetApiAction.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2020 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. + */ + +export enum WidgetApiToWidgetAction { + SupportedApiVersions = "supported_api_versions", + Capabilities = "capabilities", + NotifyCapabilities = "notify_capabilities", + TakeScreenshot = "screenshot", + UpdateVisibility = "visibility", + OpenIDCredentials = "openid_credentials", + WidgetConfig = "widget_config", + CloseModalWidget = "close_modal", + ButtonClicked = "button_clicked", + SendEvent = "send_event", + SendToDevice = "send_to_device", + UpdateTurnServers = "update_turn_servers", +} + +export enum WidgetApiFromWidgetAction { + SupportedApiVersions = "supported_api_versions", + ContentLoaded = "content_loaded", + SendSticker = "m.sticker", + UpdateAlwaysOnScreen = "set_always_on_screen", + GetOpenIDCredentials = "get_openid", + CloseModalWidget = "close_modal", + OpenModalWidget = "open_modal", + SetModalButtonEnabled = "set_button_enabled", + SendEvent = "send_event", + SendToDevice = "send_to_device", + WatchTurnServers = "watch_turn_servers", + UnwatchTurnServers = "unwatch_turn_servers", + + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC2876ReadEvents = "org.matrix.msc2876.read_events", + + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC2931Navigate = "org.matrix.msc2931.navigate", + + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC2974RenegotiateCapabilities = "org.matrix.msc2974.request_capabilities", + + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC3869ReadRelations = "org.matrix.msc3869.read_relations", + + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC3973UserDirectorySearch = "org.matrix.msc3973.user_directory_search", +} + +export type WidgetApiAction = WidgetApiToWidgetAction | WidgetApiFromWidgetAction | string; diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetApiDirection.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetApiDirection.ts new file mode 100644 index 0000000..e11e144 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetApiDirection.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2020 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. + */ + +export enum WidgetApiDirection { + ToWidget = "toWidget", + FromWidget = "fromWidget", +} + +export function invertedDirection(dir: WidgetApiDirection): WidgetApiDirection { + if (dir === WidgetApiDirection.ToWidget) { + return WidgetApiDirection.FromWidget; + } else if (dir === WidgetApiDirection.FromWidget) { + return WidgetApiDirection.ToWidget; + } else { + throw new Error("Invalid direction"); + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetConfigAction.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetConfigAction.ts new file mode 100644 index 0000000..b10314c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetConfigAction.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2020 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 { IWidgetApiRequest } from "./IWidgetApiRequest"; +import { WidgetApiToWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponse } from "./IWidgetApiResponse"; +import { IModalWidgetOpenRequestData } from "./ModalWidgetActions"; + +export interface IWidgetConfigRequest extends IWidgetApiRequest { + action: WidgetApiToWidgetAction.WidgetConfig; + data: IModalWidgetOpenRequestData; +} + +export interface IWidgetConfigResponse extends IWidgetApiResponse { + response: IWidgetApiAcknowledgeResponseData; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetKind.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetKind.ts new file mode 100644 index 0000000..374e198 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetKind.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 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. + */ + +export enum WidgetKind { + Room = "room", + Account = "account", + Modal = "modal", +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetType.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetType.ts new file mode 100644 index 0000000..d6b3e33 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/interfaces/WidgetType.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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. + */ + +export enum MatrixWidgetType { + Custom = "m.custom", + JitsiMeet = "m.jitsi", + Stickerpicker = "m.stickerpicker", +} + +export type WidgetType = MatrixWidgetType | string; diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/models/Widget.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/models/Widget.ts new file mode 100644 index 0000000..0b66452 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/models/Widget.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2020 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 { IWidget, IWidgetData, WidgetType } from ".."; +import { assertPresent } from "./validation/utils"; +import { ITemplateParams, runTemplate } from ".."; + +/** + * Represents the barest form of widget. + */ +export class Widget { + public constructor(private definition: IWidget) { + if (!this.definition) throw new Error("Definition is required"); + + assertPresent(definition, "id"); + assertPresent(definition, "creatorUserId"); + assertPresent(definition, "type"); + assertPresent(definition, "url"); + } + + /** + * The user ID who created the widget. + */ + public get creatorUserId(): string { + return this.definition.creatorUserId; + } + + /** + * The type of widget. + */ + public get type(): WidgetType { + return this.definition.type; + } + + /** + * The ID of the widget. + */ + public get id(): string { + return this.definition.id; + } + + /** + * The name of the widget, or null if not set. + */ + public get name(): string | null { + return this.definition.name || null; + } + + /** + * The title for the widget, or null if not set. + */ + public get title(): string | null { + return this.rawData.title || null; + } + + /** + * The templated URL for the widget. + */ + public get templateUrl(): string { + return this.definition.url; + } + + /** + * The origin for this widget. + */ + public get origin(): string { + return new URL(this.templateUrl).origin; + } + + /** + * Whether or not the client should wait for the iframe to load. Defaults + * to true. + */ + public get waitForIframeLoad(): boolean { + if (this.definition.waitForIframeLoad === false) return false; + if (this.definition.waitForIframeLoad === true) return true; + return true; // default true + } + + /** + * The raw data for the widget. This will always be defined, though + * may be empty. + */ + public get rawData(): IWidgetData { + return this.definition.data || {}; + } + + /** + * Gets a complete widget URL for the client to render. + * @param {ITemplateParams} params The template parameters. + * @returns {string} A templated URL. + */ + public getCompleteUrl(params: ITemplateParams): string { + return runTemplate(this.templateUrl, this.definition, params); + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetEventCapability.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetEventCapability.ts new file mode 100644 index 0000000..16d933e --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetEventCapability.ts @@ -0,0 +1,204 @@ +/* + * Copyright 2020 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 { Capability } from ".."; + +export enum EventKind { + Event = "event", + State = "state_event", + ToDevice = "to_device", +} + +export enum EventDirection { + Send = "send", + Receive = "receive", +} + +export class WidgetEventCapability { + private constructor( + public readonly direction: EventDirection, + public readonly eventType: string, + public readonly kind: EventKind, + public readonly keyStr: string | null, + public readonly raw: string, + ) { + } + + public matchesAsStateEvent(direction: EventDirection, eventType: string, stateKey: string | null): boolean { + if (this.kind !== EventKind.State) return false; // not a state event + if (this.direction !== direction) return false; // direction mismatch + if (this.eventType !== eventType) return false; // event type mismatch + if (this.keyStr === null) return true; // all state keys are allowed + if (this.keyStr === stateKey) return true; // this state key is allowed + + // Default not allowed + return false; + } + + public matchesAsToDeviceEvent(direction: EventDirection, eventType: string): boolean { + if (this.kind !== EventKind.ToDevice) return false; // not a to-device event + if (this.direction !== direction) return false; // direction mismatch + if (this.eventType !== eventType) return false; // event type mismatch + + // Checks passed, the event is allowed + return true; + } + + public matchesAsRoomEvent(direction: EventDirection, eventType: string, msgtype: string | null = null): boolean { + if (this.kind !== EventKind.Event) return false; // not a room event + if (this.direction !== direction) return false; // direction mismatch + if (this.eventType !== eventType) return false; // event type mismatch + + if (this.eventType === "m.room.message") { + if (this.keyStr === null) return true; // all message types are allowed + if (this.keyStr === msgtype) return true; // this message type is allowed + } else { + return true; // already passed the check for if the event is allowed + } + + // Default not allowed + return false; + } + + public static forStateEvent( + direction: EventDirection, + eventType: string, + stateKey?: string, + ): WidgetEventCapability { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + eventType = eventType.replace(/#/g, '\\#'); + stateKey = stateKey !== null && stateKey !== undefined ? `#${stateKey}` : ''; + const str = `org.matrix.msc2762.${direction}.state_event:${eventType}${stateKey}`; + + // cheat by sending it through the processor + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + + public static forToDeviceEvent(direction: EventDirection, eventType: string): WidgetEventCapability { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/56 + const str = `org.matrix.msc3819.${direction}.to_device:${eventType}`; + + // cheat by sending it through the processor + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + + public static forRoomEvent(direction: EventDirection, eventType: string): WidgetEventCapability { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + const str = `org.matrix.msc2762.${direction}.event:${eventType}`; + + // cheat by sending it through the processor + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + + public static forRoomMessageEvent(direction: EventDirection, msgtype?: string): WidgetEventCapability { + // TODO: Enable support for m.* namespace once the MSC lands. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + msgtype = msgtype === null || msgtype === undefined ? '' : msgtype; + const str = `org.matrix.msc2762.${direction}.event:m.room.message#${msgtype}`; + + // cheat by sending it through the processor + return WidgetEventCapability.findEventCapabilities([str])[0]; + } + + /** + * Parses a capabilities request to find all the event capability requests. + * @param {Iterable<Capability>} capabilities The capabilities requested/to parse. + * @returns {WidgetEventCapability[]} An array of event capability requests. May be empty, but never null. + */ + public static findEventCapabilities(capabilities: Iterable<Capability>): WidgetEventCapability[] { + const parsed: WidgetEventCapability[] = []; + for (const cap of capabilities) { + let direction: EventDirection | null = null; + let eventSegment: string | undefined; + let kind: EventKind | null = null; + + // TODO: Enable support for m.* namespace once the MSCs land. + // https://github.com/matrix-org/matrix-widget-api/issues/22 + // https://github.com/matrix-org/matrix-widget-api/issues/56 + + if (cap.startsWith("org.matrix.msc2762.send.event:")) { + direction = EventDirection.Send; + kind = EventKind.Event; + eventSegment = cap.substring("org.matrix.msc2762.send.event:".length); + } else if (cap.startsWith("org.matrix.msc2762.send.state_event:")) { + direction = EventDirection.Send; + kind = EventKind.State; + eventSegment = cap.substring("org.matrix.msc2762.send.state_event:".length); + } else if (cap.startsWith("org.matrix.msc3819.send.to_device:")) { + direction = EventDirection.Send; + kind = EventKind.ToDevice; + eventSegment = cap.substring("org.matrix.msc3819.send.to_device:".length); + } else if (cap.startsWith("org.matrix.msc2762.receive.event:")) { + direction = EventDirection.Receive; + kind = EventKind.Event; + eventSegment = cap.substring("org.matrix.msc2762.receive.event:".length); + } else if (cap.startsWith("org.matrix.msc2762.receive.state_event:")) { + direction = EventDirection.Receive; + kind = EventKind.State; + eventSegment = cap.substring("org.matrix.msc2762.receive.state_event:".length); + } else if (cap.startsWith("org.matrix.msc3819.receive.to_device:")) { + direction = EventDirection.Receive; + kind = EventKind.ToDevice; + eventSegment = cap.substring("org.matrix.msc3819.receive.to_device:".length); + } + + if (direction === null || kind === null || eventSegment === undefined) continue; + + // The capability uses `#` as a separator between event type and state key/msgtype, + // so we split on that. However, a # is also valid in either one of those so we + // join accordingly. + // Eg: `m.room.message##m.text` is "m.room.message" event with msgtype "#m.text". + const expectingKeyStr = eventSegment.startsWith("m.room.message#") || kind === EventKind.State; + let keyStr: string | null = null; + if (eventSegment.includes('#') && expectingKeyStr) { + // Dev note: regex is difficult to write, so instead the rules are manually written + // out. This is probably just as understandable as a boring regex though, so win-win? + + // Test cases: + // str eventSegment keyStr + // ------------------------------------------------------------- + // m.room.message# m.room.message <empty string> + // m.room.message#test m.room.message test + // m.room.message\# m.room.message# test + // m.room.message##test m.room.message #test + // m.room.message\##test m.room.message# test + // m.room.message\\##test m.room.message\# test + // m.room.message\\###test m.room.message\# #test + + // First step: explode the string + const parts = eventSegment.split('#'); + + // To form the eventSegment, we'll keep finding parts of the exploded string until + // there's one that doesn't end with the escape character (\). We'll then join those + // segments together with the exploding character. We have to remember to consume the + // escape character as well. + const idx = parts.findIndex(p => !p.endsWith("\\")); + eventSegment = parts.slice(0, idx + 1) + .map(p => p.endsWith('\\') ? p.substring(0, p.length - 1) : p) + .join('#'); + + // The keyStr is whatever is left over. + keyStr = parts.slice(idx + 1).join('#'); + } + + parsed.push(new WidgetEventCapability(direction, eventSegment, kind, keyStr, cap)); + } + return parsed; + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetParser.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetParser.ts new file mode 100644 index 0000000..f93c077 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/models/WidgetParser.ts @@ -0,0 +1,147 @@ +/* + * Copyright 2020 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 { Widget } from "./Widget"; +import { IWidget } from ".."; +import { isValidUrl } from "./validation/url"; + +export interface IStateEvent { + event_id: string; // eslint-disable-line camelcase + room_id: string; // eslint-disable-line camelcase + type: string; + sender: string; + origin_server_ts: number; // eslint-disable-line camelcase + unsigned?: unknown; + content: unknown; + state_key: string; // eslint-disable-line camelcase +} + +export interface IAccountDataWidgets { + [widgetId: string]: { + type: "m.widget"; + // the state_key is also the widget's ID + state_key: string; // eslint-disable-line camelcase + sender: string; // current user's ID + content: IWidget; + id?: string; // off-spec, but possible + }; +} + +export class WidgetParser { + private constructor() { + // private constructor because this is a util class + } + + /** + * Parses widgets from the "m.widgets" account data event. This will always + * return an array, though may be empty if no valid widgets were found. + * @param {IAccountDataWidgets} content The content of the "m.widgets" account data. + * @returns {Widget[]} The widgets in account data, or an empty array. + */ + public static parseAccountData(content: IAccountDataWidgets): Widget[] { + if (!content) return []; + + const result: Widget[] = []; + for (const widgetId of Object.keys(content)) { + const roughWidget = content[widgetId]; + if (!roughWidget) continue; + if (roughWidget.type !== "m.widget" && roughWidget.type !== "im.vector.modular.widgets") continue; + if (!roughWidget.sender) continue; + + const probableWidgetId = roughWidget.state_key || roughWidget.id; + if (probableWidgetId !== widgetId) continue; + + const asStateEvent: IStateEvent = { + content: roughWidget.content, + sender: roughWidget.sender, + type: "m.widget", + state_key: widgetId, + event_id: "$example", + room_id: "!example", + origin_server_ts: 1, + }; + + const widget = WidgetParser.parseRoomWidget(asStateEvent); + if (widget) result.push(widget); + } + + return result; + } + + /** + * Parses all the widgets possible in the given array. This will always return + * an array, though may be empty if no widgets could be parsed. + * @param {IStateEvent[]} currentState The room state to parse. + * @returns {Widget[]} The widgets in the state, or an empty array. + */ + public static parseWidgetsFromRoomState(currentState: IStateEvent[]): Widget[] { + if (!currentState) return []; + const result: Widget[] = []; + for (const state of currentState) { + const widget = WidgetParser.parseRoomWidget(state); + if (widget) result.push(widget); + } + return result; + } + + /** + * Parses a state event into a widget. If the state event does not represent + * a widget (wrong event type, invalid widget, etc) then null is returned. + * @param {IStateEvent} stateEvent The state event. + * @returns {Widget|null} The widget, or null if invalid + */ + public static parseRoomWidget(stateEvent: IStateEvent): Widget | null { + if (!stateEvent) return null; + + // TODO: [Legacy] Remove legacy support + if (stateEvent.type !== "m.widget" && stateEvent.type !== "im.vector.modular.widgets") { + return null; + } + + // Dev note: Throughout this function we have null safety to ensure that + // if the caller did not supply something useful that we don't error. This + // is done against the requirements of the interface because not everyone + // will have an interface to validate against. + + const content = stateEvent.content as IWidget || {}; + + // Form our best approximation of a widget with the information we have + const estimatedWidget: IWidget = { + id: stateEvent.state_key, + creatorUserId: content['creatorUserId'] || stateEvent.sender, + name: content['name'], + type: content['type'], + url: content['url'], + waitForIframeLoad: content['waitForIframeLoad'], + data: content['data'], + }; + + // Finally, process that widget + return WidgetParser.processEstimatedWidget(estimatedWidget); + } + + private static processEstimatedWidget(widget: IWidget): Widget | null { + // Validate that the widget has the best chance of passing as a widget + if (!widget.id || !widget.creatorUserId || !widget.type) { + return null; + } + if (!isValidUrl(widget.url)) { + return null; + } + // TODO: Validate data for known widget types + return new Widget(widget); + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/models/validation/url.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/models/validation/url.ts new file mode 100644 index 0000000..c56a9c6 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/models/validation/url.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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. + */ + +export function isValidUrl(val: string): boolean { + if (!val) return false; // easy: not valid if not present + + try { + const parsed = new URL(val); + if (parsed.protocol !== "http" && parsed.protocol !== "https") { + return false; + } + return true; + } catch (e) { + if (e instanceof TypeError) { + return false; + } + throw e; + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/models/validation/utils.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/models/validation/utils.ts new file mode 100644 index 0000000..b9c8761 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/models/validation/utils.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 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. + */ + +export function assertPresent<O>(obj: O, key: keyof O) { + if (!obj[key]) { + throw new Error(`${key} is required`); + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/templating/url-template.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/templating/url-template.ts new file mode 100644 index 0000000..901f131 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/templating/url-template.ts @@ -0,0 +1,62 @@ +/* + * 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 { IWidget } from ".."; + +export interface ITemplateParams { + widgetRoomId?: string; + currentUserId: string; + userDisplayName?: string; + userHttpAvatarUrl?: string; + clientId?: string; + clientTheme?: string; + clientLanguage?: string; +} + +export function runTemplate(url: string, widget: IWidget, params: ITemplateParams): string { + // Always apply the supplied params over top of data to ensure the data can't lie about them. + const variables = Object.assign({}, widget.data, { + 'matrix_room_id': params.widgetRoomId || "", + 'matrix_user_id': params.currentUserId, + 'matrix_display_name': params.userDisplayName || params.currentUserId, + 'matrix_avatar_url': params.userHttpAvatarUrl || "", + 'matrix_widget_id': widget.id, + + // TODO: Convert to stable (https://github.com/matrix-org/matrix-doc/pull/2873) + 'org.matrix.msc2873.client_id': params.clientId || "", + 'org.matrix.msc2873.client_theme': params.clientTheme || "", + 'org.matrix.msc2873.client_language': params.clientLanguage || "", + }); + let result = url; + for (const key of Object.keys(variables)) { + // Regex escape from https://stackoverflow.com/a/6969486/7037379 + const pattern = `$${key}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + const rexp = new RegExp(pattern, 'g'); + + // This is technically not what we're supposed to do for a couple of reasons: + // 1. We are assuming that there won't later be a $key match after we replace a variable. + // 2. We are assuming that the variable is in a place where it can be escaped (eg: path or query string). + result = result.replace(rexp, encodeURIComponent(toString(variables[key]))); + } + return result; +} + +export function toString(a: unknown): string { + if (a === null || a === undefined) { + return `${a}`; + } + return String(a); +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/transport/ITransport.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/transport/ITransport.ts new file mode 100644 index 0000000..b3b2e9a --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/transport/ITransport.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2020 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 { + IWidgetApiAcknowledgeResponseData, + IWidgetApiRequest, + IWidgetApiRequestData, + IWidgetApiResponse, + IWidgetApiResponseData, + WidgetApiAction, +} from ".."; + +/** + * A transport for widget requests/responses. All actions + * get raised through a "message" CustomEvent with detail + * of the IWidgetApiRequest. + */ +export interface ITransport extends EventEmitter { + /** + * True if the transport is ready to start sending, false otherwise. + */ + readonly ready: boolean; + + /** + * The widget ID, if known. If not known, null. + */ + readonly widgetId: string | null; + + /** + * If true, the transport will refuse requests from origins other than the + * widget's current origin. This is intended to be used only by widgets which + * need excess security. + */ + strictOriginCheck: boolean; + + /** + * The origin the transport should be replying/sending to. If not known, leave + * null. + */ + targetOrigin: string | null; + + /** + * The number of seconds an outbound request is allowed to take before it + * times out. + */ + timeoutSeconds: number; + + /** + * Starts the transport for listening + */ + start(): void; + + /** + * Stops the transport. It cannot be re-started. + */ + stop(): void; + + /** + * Sends a request to the remote end. + * @param {WidgetApiAction} action The action to send. + * @param {IWidgetApiRequestData} data The request data. + * @returns {Promise<IWidgetApiResponseData>} A promise which resolves + * to the remote end's response, or throws with an Error if the request + * failed. + */ + send<T extends IWidgetApiRequestData, R extends IWidgetApiResponseData = IWidgetApiAcknowledgeResponseData>( + action: WidgetApiAction, + data: T + ): Promise<R>; + + /** + * Sends a request to the remote end. This is similar to the send() function + * however this version returns the full response rather than just the response + * data. + * @param {WidgetApiAction} action The action to send. + * @param {IWidgetApiRequestData} data The request data. + * @returns {Promise<IWidgetApiResponseData>} A promise which resolves + * to the remote end's response, or throws with an Error if the request + * failed. + */ + sendComplete<T extends IWidgetApiRequestData, R extends IWidgetApiResponse>(action: WidgetApiAction, data: T) + : Promise<R>; + + /** + * Replies to a request. + * @param {IWidgetApiRequest} request The request to reply to. + * @param {IWidgetApiResponseData} responseData The response data to reply with. + */ + reply<T extends IWidgetApiResponseData>(request: IWidgetApiRequest, responseData: T): void; +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/transport/PostmessageTransport.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/transport/PostmessageTransport.ts new file mode 100644 index 0000000..9f86aa5 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/transport/PostmessageTransport.ts @@ -0,0 +1,203 @@ +/* + * Copyright 2020 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 "./ITransport"; +import { + invertedDirection, + isErrorResponse, + IWidgetApiErrorResponseData, + IWidgetApiRequest, + IWidgetApiRequestData, + IWidgetApiResponse, + IWidgetApiResponseData, + WidgetApiAction, + WidgetApiDirection, + WidgetApiToWidgetAction, +} from ".."; + +interface IOutboundRequest { + request: IWidgetApiRequest; + resolve: (response: IWidgetApiResponse) => void; + reject: (err: Error) => void; +} + +/** + * Transport for the Widget API over postMessage. + */ +export class PostmessageTransport extends EventEmitter implements ITransport { + public strictOriginCheck = false; + public targetOrigin = "*"; + public timeoutSeconds = 10; + + private _ready = false; + private _widgetId: string | null = null; + private outboundRequests = new Map<string, IOutboundRequest | null>(); + private stopController = new AbortController(); + + public get ready(): boolean { + return this._ready; + } + + public get widgetId(): string | null { + return this._widgetId || null; + } + + public constructor( + private sendDirection: WidgetApiDirection, + private initialWidgetId: string | null, + private transportWindow: Window, + private inboundWindow: Window, + ) { + super(); + this._widgetId = initialWidgetId; + } + + private get nextRequestId(): string { + const idBase = `widgetapi-${Date.now()}`; + let index = 0; + let id = idBase; + while (this.outboundRequests.has(id)) { + id = `${idBase}-${index++}`; + } + + // reserve the ID + this.outboundRequests.set(id, null); + + return id; + } + + private sendInternal(message: IWidgetApiRequest | IWidgetApiResponse) { + console.log(`[PostmessageTransport] Sending object to ${this.targetOrigin}: `, message); + this.transportWindow.postMessage(message, this.targetOrigin); + } + + public reply<T extends IWidgetApiResponseData>(request: IWidgetApiRequest, responseData: T) { + return this.sendInternal(<IWidgetApiResponse>{ + ...request, + response: responseData, + }); + } + + public send<T extends IWidgetApiRequestData, R extends IWidgetApiResponseData>( + action: WidgetApiAction, data: T, + ): Promise<R> { + return this.sendComplete(action, data).then(r => <R>r.response); + } + + public sendComplete<T extends IWidgetApiRequestData, R extends IWidgetApiResponse>( + action: WidgetApiAction, data: T, + ): Promise<R> { + if (!this.ready || !this.widgetId) { + return Promise.reject(new Error("Not ready or unknown widget ID")); + } + const request: IWidgetApiRequest = { + api: this.sendDirection, + widgetId: this.widgetId, + requestId: this.nextRequestId, + action: action, + data: data, + }; + if (action === WidgetApiToWidgetAction.UpdateVisibility) { + request['visible'] = data['visible']; + } + return new Promise<R>((prResolve, prReject) => { + const resolve = (response: IWidgetApiResponse) => { + cleanUp(); + prResolve(<R>response); + }; + const reject = (err: Error) => { + cleanUp(); + prReject(err); + }; + + const timerId = setTimeout( + () => reject(new Error("Request timed out")), + (this.timeoutSeconds || 1) * 1000, + ); + + const onStop = () => reject(new Error("Transport stopped")); + this.stopController.signal.addEventListener("abort", onStop); + + const cleanUp = () => { + this.outboundRequests.delete(request.requestId); + clearTimeout(timerId); + this.stopController.signal.removeEventListener("abort", onStop); + }; + + this.outboundRequests.set(request.requestId, { request, resolve, reject }); + this.sendInternal(request); + }); + } + + public start() { + this.inboundWindow.addEventListener("message", (ev: MessageEvent) => { + this.handleMessage(ev); + }); + this._ready = true; + } + + public stop() { + this._ready = false; + this.stopController.abort(); + } + + private handleMessage(ev: MessageEvent) { + if (this.stopController.signal.aborted) return; + if (!ev.data) return; // invalid event + + if (this.strictOriginCheck && ev.origin !== window.origin) return; // bad origin + + // treat the message as a response first, then downgrade to a request + const response = <IWidgetApiResponse>ev.data; + if (!response.action || !response.requestId || !response.widgetId) return; // invalid request/response + + if (!response.response) { + // it's a request + const request = <IWidgetApiRequest>response; + if (request.api !== invertedDirection(this.sendDirection)) return; // wrong direction + this.handleRequest(request); + } else { + // it's a response + if (response.api !== this.sendDirection) return; // wrong direction + this.handleResponse(response); + } + } + + private handleRequest(request: IWidgetApiRequest) { + if (this.widgetId) { + if (this.widgetId !== request.widgetId) return; // wrong widget + } else { + this._widgetId = request.widgetId; + } + + this.emit("message", new CustomEvent("message", {detail: request})); + } + + private handleResponse(response: IWidgetApiResponse) { + if (response.widgetId !== this.widgetId) return; // wrong widget + + const req = this.outboundRequests.get(response.requestId); + if (!req) return; // response to an unknown request + + if (isErrorResponse(response.response)) { + const err = <IWidgetApiErrorResponseData>response.response; + req.reject(new Error(err.error.message)); + } else { + req.resolve(response); + } + } +} diff --git a/includes/external/matrix/node_modules/matrix-widget-api/src/util/SimpleObservable.ts b/includes/external/matrix/node_modules/matrix-widget-api/src/util/SimpleObservable.ts new file mode 100644 index 0000000..77b2080 --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-widget-api/src/util/SimpleObservable.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2020 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. + */ + +export type ObservableFunction<T> = (val: T) => void; + +export class SimpleObservable<T> { + private listeners: ObservableFunction<T>[] = []; + + public constructor(initialFn?: ObservableFunction<T>) { + if (initialFn) this.listeners.push(initialFn); + } + + public onUpdate(fn: ObservableFunction<T>) { + this.listeners.push(fn); + } + + public update(val: T) { + for (const listener of this.listeners) { + listener(val); + } + } + + public close() { + this.listeners = []; // reset + } +} |