diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index 10327ce4e9..4e6e5e626c 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -31,11 +31,16 @@ interface IState {} export interface IApp { id: string; + url: string; type: string; + name: string; roomId: string; eventId: string; creatorUserId: string; waitForIframeLoad?: boolean; + data?: { + title?: string; + }; // eslint-disable-next-line camelcase avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765 } diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.ts similarity index 90% rename from src/utils/WidgetUtils.js rename to src/utils/WidgetUtils.ts index d1daba7ca5..efdfbbb3cc 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.ts @@ -2,6 +2,7 @@ Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd Copyright 2019 Travis Ralston +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. @@ -16,15 +17,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +import * as url from "url"; + import {MatrixClientPeg} from '../MatrixClientPeg'; import SdkConfig from "../SdkConfig"; import dis from '../dispatcher/dispatcher'; -import * as url from "url"; import WidgetEchoStore from '../stores/WidgetEchoStore'; - -// How long we wait for the state event echo to come back from the server -// before waitFor[Room/User]Widget rejects its promise -const WIDGET_WAIT_TIME = 20000; import SettingsStore from "../settings/SettingsStore"; import ActiveWidgetStore from "../stores/ActiveWidgetStore"; import {IntegrationManagers} from "../integrations/IntegrationManagers"; @@ -33,6 +31,19 @@ import {Room} from "matrix-js-sdk/src/models/room"; import {WidgetType} from "../widgets/WidgetType"; import {objectClone} from "./objects"; import {_t} from "../languageHandler"; +import {IApp} from "../stores/WidgetStore"; + +// How long we wait for the state event echo to come back from the server +// before waitFor[Room/User]Widget rejects its promise +const WIDGET_WAIT_TIME = 20000; + +export interface IWidget { + id: string; + type: string; + sender: string; + state_key: string; + content: IApp; +} export default class WidgetUtils { /* Returns true if user is able to send state events to modify widgets in this room @@ -41,7 +52,7 @@ export default class WidgetUtils { * @return Boolean -- true if the user can modify widgets in this room * @throws Error -- specifies the error reason */ - static canUserModifyWidgets(roomId) { + static canUserModifyWidgets(roomId: string): boolean { if (!roomId) { console.warn('No room ID specified'); return false; @@ -80,7 +91,7 @@ export default class WidgetUtils { * @param {[type]} testUrlString URL to check * @return {Boolean} True if specified URL is a scalar URL */ - static isScalarUrl(testUrlString) { + static isScalarUrl(testUrlString: string): boolean { if (!testUrlString) { console.error('Scalar URL check failed. No URL specified'); return false; @@ -123,7 +134,7 @@ export default class WidgetUtils { * @returns {Promise} that resolves when the widget is in the * requested state according to the `add` param */ - static waitForUserWidget(widgetId, add) { + static waitForUserWidget(widgetId: string, add: boolean): Promise { return new Promise((resolve, reject) => { // Tests an account data event, returning true if it's in the state // we're waiting for it to be in @@ -170,7 +181,7 @@ export default class WidgetUtils { * @returns {Promise} that resolves when the widget is in the * requested state according to the `add` param */ - static waitForRoomWidget(widgetId, roomId, add) { + static waitForRoomWidget(widgetId: string, roomId: string, add: boolean): Promise { return new Promise((resolve, reject) => { // Tests a list of state events, returning true if it's in the state // we're waiting for it to be in @@ -213,7 +224,7 @@ export default class WidgetUtils { }); } - static setUserWidget(widgetId, widgetType: WidgetType, widgetUrl, widgetName, widgetData) { + static setUserWidget(widgetId: string, widgetType: WidgetType, widgetUrl: string, widgetName: string, widgetData: object) { const content = { type: widgetType.preferred, url: widgetUrl, @@ -257,7 +268,7 @@ export default class WidgetUtils { }); } - static setRoomWidget(roomId, widgetId, widgetType: WidgetType, widgetUrl, widgetName, widgetData) { + static setRoomWidget(roomId: string, widgetId: string, widgetType: WidgetType, widgetUrl: string, widgetName: string, widgetData: object) { let content; const addingWidget = Boolean(widgetUrl); @@ -307,7 +318,7 @@ export default class WidgetUtils { * Get user specific widgets (not linked to a specific room) * @return {object} Event content object containing current / active user widgets */ - static getUserWidgets() { + static getUserWidgets(): Record { const client = MatrixClientPeg.get(); if (!client) { throw new Error('User not logged in'); @@ -323,7 +334,7 @@ export default class WidgetUtils { * Get user specific widgets (not linked to a specific room) as an array * @return {[object]} Array containing current / active user widgets */ - static getUserWidgetsArray() { + static getUserWidgetsArray(): IWidget[] { return Object.values(WidgetUtils.getUserWidgets()); } @@ -331,7 +342,7 @@ export default class WidgetUtils { * Get active stickerpicker widgets (stickerpickers are user widgets by nature) * @return {[object]} Array containing current / active stickerpicker widgets */ - static getStickerpickerWidgets() { + static getStickerpickerWidgets(): IWidget[] { const widgets = WidgetUtils.getUserWidgetsArray(); return widgets.filter((widget) => widget.content && widget.content.type === "m.stickerpicker"); } @@ -340,12 +351,12 @@ export default class WidgetUtils { * Get all integration manager widgets for this user. * @returns {Object[]} An array of integration manager user widgets. */ - static getIntegrationManagerWidgets() { + static getIntegrationManagerWidgets(): IWidget[] { const widgets = WidgetUtils.getUserWidgetsArray(); return widgets.filter(w => w.content && w.content.type === "m.integration_manager"); } - static getRoomWidgetsOfType(room: Room, type: WidgetType) { + static getRoomWidgetsOfType(room: Room, type: WidgetType): IWidget[] { const widgets = WidgetUtils.getRoomWidgets(room); return (widgets || []).filter(w => { const content = w.getContent(); @@ -353,14 +364,14 @@ export default class WidgetUtils { }); } - static removeIntegrationManagerWidgets() { + static removeIntegrationManagerWidgets(): Promise { const client = MatrixClientPeg.get(); if (!client) { throw new Error('User not logged in'); } const widgets = client.getAccountData('m.widgets'); if (!widgets) return; - const userWidgets = widgets.getContent() || {}; + const userWidgets: IWidget[] = widgets.getContent() || {}; Object.entries(userWidgets).forEach(([key, widget]) => { if (widget.content && widget.content.type === "m.integration_manager") { delete userWidgets[key]; @@ -369,7 +380,7 @@ export default class WidgetUtils { return client.setAccountData('m.widgets', userWidgets); } - static addIntegrationManagerWidget(name: string, uiUrl: string, apiUrl: string) { + static addIntegrationManagerWidget(name: string, uiUrl: string, apiUrl: string): Promise { return WidgetUtils.setUserWidget( "integration_manager_" + (new Date().getTime()), WidgetType.INTEGRATION_MANAGER, @@ -383,14 +394,14 @@ export default class WidgetUtils { * Remove all stickerpicker widgets (stickerpickers are user widgets by nature) * @return {Promise} Resolves on account data updated */ - static removeStickerpickerWidgets() { + static removeStickerpickerWidgets(): Promise { const client = MatrixClientPeg.get(); if (!client) { throw new Error('User not logged in'); } const widgets = client.getAccountData('m.widgets'); if (!widgets) return; - const userWidgets = widgets.getContent() || {}; + const userWidgets: Record = widgets.getContent() || {}; Object.entries(userWidgets).forEach(([key, widget]) => { if (widget.content && widget.content.type === 'm.stickerpicker') { delete userWidgets[key]; @@ -399,7 +410,7 @@ export default class WidgetUtils { return client.setAccountData('m.widgets', userWidgets); } - static makeAppConfig(appId, app, senderUserId, roomId, eventId) { + static makeAppConfig(appId: string, app: IApp, senderUserId: string, roomId: string | null, eventId: string): IApp { if (!senderUserId) { throw new Error("Widgets must be created by someone - provide a senderUserId"); } @@ -413,7 +424,7 @@ export default class WidgetUtils { return app; } - static getCapWhitelistForAppTypeInRoomId(appType, roomId) { + static getCapWhitelistForAppTypeInRoomId(appType: string, roomId: string): Capability[] { const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", roomId); const capWhitelist = enableScreenshots ? [Capability.Screenshot] : []; @@ -429,7 +440,7 @@ export default class WidgetUtils { return capWhitelist; } - static getWidgetSecurityKey(widgetId, widgetUrl, isUserWidget) { + static getWidgetSecurityKey(widgetId: string, widgetUrl: string, isUserWidget: boolean): string { let widgetLocation = ActiveWidgetStore.getRoomId(widgetId); if (isUserWidget) { @@ -450,7 +461,7 @@ export default class WidgetUtils { return encodeURIComponent(`${widgetLocation}::${widgetUrl}`); } - static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string}={}) { + static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string} = {}) { // NB. we can't just encodeURIComponent all of these because the $ signs need to be there const queryStringParts = [ 'conferenceDomain=$domain', @@ -466,7 +477,7 @@ export default class WidgetUtils { } const queryString = queryStringParts.join('&'); - let baseUrl = window.location; + let baseUrl = window.location.href; if (window.location.protocol !== "https:" && !opts.forLocalRender) { // Use an external wrapper if we're not locally rendering the widget. This is usually // the URL that will end up in the widget event, so we want to make sure it's relatively @@ -479,15 +490,15 @@ export default class WidgetUtils { return url.href; } - static getWidgetName(app) { + static getWidgetName(app?: IApp): string { return app?.name?.trim() || _t("Unknown App"); } - static getWidgetDataTitle(app) { + static getWidgetDataTitle(app?: IApp): string { return app?.data?.title?.trim() || ""; } - static editWidget(room, app) { + static editWidget(room: Room, app: IApp): void { // TODO: Open the right manager for the widget if (SettingsStore.getValue("feature_many_integration_managers")) { IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); @@ -496,7 +507,7 @@ export default class WidgetUtils { } } - static snapshotWidget(app) { + static snapshotWidget(app: IApp): void { console.log("Requesting widget snapshot"); ActiveWidgetStore.getWidgetMessaging(app.id).getScreenshot().catch((err) => { console.error("Failed to get screenshot", err);