Convert WidgetUtils to TS and improve typing

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2020-09-23 11:01:19 +01:00
parent c8b99b54e0
commit 2e6bad8b07
2 changed files with 46 additions and 30 deletions

View file

@ -31,11 +31,16 @@ interface IState {}
export interface IApp { export interface IApp {
id: string; id: string;
url: string;
type: string; type: string;
name: string;
roomId: string; roomId: string;
eventId: string; eventId: string;
creatorUserId: string; creatorUserId: string;
waitForIframeLoad?: boolean; waitForIframeLoad?: boolean;
data?: {
title?: string;
};
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765 avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765
} }

View file

@ -2,6 +2,7 @@
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 Travis Ralston Copyright 2019 Travis Ralston
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import * as url from "url";
import {MatrixClientPeg} from '../MatrixClientPeg'; import {MatrixClientPeg} from '../MatrixClientPeg';
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher'; import dis from '../dispatcher/dispatcher';
import * as url from "url";
import WidgetEchoStore from '../stores/WidgetEchoStore'; 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 SettingsStore from "../settings/SettingsStore";
import ActiveWidgetStore from "../stores/ActiveWidgetStore"; import ActiveWidgetStore from "../stores/ActiveWidgetStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers"; import {IntegrationManagers} from "../integrations/IntegrationManagers";
@ -33,6 +31,19 @@ import {Room} from "matrix-js-sdk/src/models/room";
import {WidgetType} from "../widgets/WidgetType"; import {WidgetType} from "../widgets/WidgetType";
import {objectClone} from "./objects"; import {objectClone} from "./objects";
import {_t} from "../languageHandler"; 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 { export default class WidgetUtils {
/* Returns true if user is able to send state events to modify widgets in this room /* 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 * @return Boolean -- true if the user can modify widgets in this room
* @throws Error -- specifies the error reason * @throws Error -- specifies the error reason
*/ */
static canUserModifyWidgets(roomId) { static canUserModifyWidgets(roomId: string): boolean {
if (!roomId) { if (!roomId) {
console.warn('No room ID specified'); console.warn('No room ID specified');
return false; return false;
@ -80,7 +91,7 @@ export default class WidgetUtils {
* @param {[type]} testUrlString URL to check * @param {[type]} testUrlString URL to check
* @return {Boolean} True if specified URL is a scalar URL * @return {Boolean} True if specified URL is a scalar URL
*/ */
static isScalarUrl(testUrlString) { static isScalarUrl(testUrlString: string): boolean {
if (!testUrlString) { if (!testUrlString) {
console.error('Scalar URL check failed. No URL specified'); console.error('Scalar URL check failed. No URL specified');
return false; return false;
@ -123,7 +134,7 @@ export default class WidgetUtils {
* @returns {Promise} that resolves when the widget is in the * @returns {Promise} that resolves when the widget is in the
* requested state according to the `add` param * requested state according to the `add` param
*/ */
static waitForUserWidget(widgetId, add) { static waitForUserWidget(widgetId: string, add: boolean): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Tests an account data event, returning true if it's in the state // Tests an account data event, returning true if it's in the state
// we're waiting for it to be in // 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 * @returns {Promise} that resolves when the widget is in the
* requested state according to the `add` param * requested state according to the `add` param
*/ */
static waitForRoomWidget(widgetId, roomId, add) { static waitForRoomWidget(widgetId: string, roomId: string, add: boolean): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Tests a list of state events, returning true if it's in the state // Tests a list of state events, returning true if it's in the state
// we're waiting for it to be in // 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 = { const content = {
type: widgetType.preferred, type: widgetType.preferred,
url: widgetUrl, 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; let content;
const addingWidget = Boolean(widgetUrl); const addingWidget = Boolean(widgetUrl);
@ -307,7 +318,7 @@ export default class WidgetUtils {
* Get user specific widgets (not linked to a specific room) * Get user specific widgets (not linked to a specific room)
* @return {object} Event content object containing current / active user widgets * @return {object} Event content object containing current / active user widgets
*/ */
static getUserWidgets() { static getUserWidgets(): Record<string, IWidget> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
throw new Error('User not logged in'); 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 * Get user specific widgets (not linked to a specific room) as an array
* @return {[object]} Array containing current / active user widgets * @return {[object]} Array containing current / active user widgets
*/ */
static getUserWidgetsArray() { static getUserWidgetsArray(): IWidget[] {
return Object.values(WidgetUtils.getUserWidgets()); return Object.values(WidgetUtils.getUserWidgets());
} }
@ -331,7 +342,7 @@ export default class WidgetUtils {
* Get active stickerpicker widgets (stickerpickers are user widgets by nature) * Get active stickerpicker widgets (stickerpickers are user widgets by nature)
* @return {[object]} Array containing current / active stickerpicker widgets * @return {[object]} Array containing current / active stickerpicker widgets
*/ */
static getStickerpickerWidgets() { static getStickerpickerWidgets(): IWidget[] {
const widgets = WidgetUtils.getUserWidgetsArray(); const widgets = WidgetUtils.getUserWidgetsArray();
return widgets.filter((widget) => widget.content && widget.content.type === "m.stickerpicker"); 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. * Get all integration manager widgets for this user.
* @returns {Object[]} An array of integration manager user widgets. * @returns {Object[]} An array of integration manager user widgets.
*/ */
static getIntegrationManagerWidgets() { static getIntegrationManagerWidgets(): IWidget[] {
const widgets = WidgetUtils.getUserWidgetsArray(); const widgets = WidgetUtils.getUserWidgetsArray();
return widgets.filter(w => w.content && w.content.type === "m.integration_manager"); 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); const widgets = WidgetUtils.getRoomWidgets(room);
return (widgets || []).filter(w => { return (widgets || []).filter(w => {
const content = w.getContent(); const content = w.getContent();
@ -353,14 +364,14 @@ export default class WidgetUtils {
}); });
} }
static removeIntegrationManagerWidgets() { static removeIntegrationManagerWidgets(): Promise<void> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
throw new Error('User not logged in'); throw new Error('User not logged in');
} }
const widgets = client.getAccountData('m.widgets'); const widgets = client.getAccountData('m.widgets');
if (!widgets) return; if (!widgets) return;
const userWidgets = widgets.getContent() || {}; const userWidgets: IWidget[] = widgets.getContent() || {};
Object.entries(userWidgets).forEach(([key, widget]) => { Object.entries(userWidgets).forEach(([key, widget]) => {
if (widget.content && widget.content.type === "m.integration_manager") { if (widget.content && widget.content.type === "m.integration_manager") {
delete userWidgets[key]; delete userWidgets[key];
@ -369,7 +380,7 @@ export default class WidgetUtils {
return client.setAccountData('m.widgets', userWidgets); return client.setAccountData('m.widgets', userWidgets);
} }
static addIntegrationManagerWidget(name: string, uiUrl: string, apiUrl: string) { static addIntegrationManagerWidget(name: string, uiUrl: string, apiUrl: string): Promise<void> {
return WidgetUtils.setUserWidget( return WidgetUtils.setUserWidget(
"integration_manager_" + (new Date().getTime()), "integration_manager_" + (new Date().getTime()),
WidgetType.INTEGRATION_MANAGER, WidgetType.INTEGRATION_MANAGER,
@ -383,14 +394,14 @@ export default class WidgetUtils {
* Remove all stickerpicker widgets (stickerpickers are user widgets by nature) * Remove all stickerpicker widgets (stickerpickers are user widgets by nature)
* @return {Promise} Resolves on account data updated * @return {Promise} Resolves on account data updated
*/ */
static removeStickerpickerWidgets() { static removeStickerpickerWidgets(): Promise<void> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (!client) { if (!client) {
throw new Error('User not logged in'); throw new Error('User not logged in');
} }
const widgets = client.getAccountData('m.widgets'); const widgets = client.getAccountData('m.widgets');
if (!widgets) return; if (!widgets) return;
const userWidgets = widgets.getContent() || {}; const userWidgets: Record<string, IWidget> = widgets.getContent() || {};
Object.entries(userWidgets).forEach(([key, widget]) => { Object.entries(userWidgets).forEach(([key, widget]) => {
if (widget.content && widget.content.type === 'm.stickerpicker') { if (widget.content && widget.content.type === 'm.stickerpicker') {
delete userWidgets[key]; delete userWidgets[key];
@ -399,7 +410,7 @@ export default class WidgetUtils {
return client.setAccountData('m.widgets', userWidgets); 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) { if (!senderUserId) {
throw new Error("Widgets must be created by someone - provide a senderUserId"); throw new Error("Widgets must be created by someone - provide a senderUserId");
} }
@ -413,7 +424,7 @@ export default class WidgetUtils {
return app; return app;
} }
static getCapWhitelistForAppTypeInRoomId(appType, roomId) { static getCapWhitelistForAppTypeInRoomId(appType: string, roomId: string): Capability[] {
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", roomId); const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", roomId);
const capWhitelist = enableScreenshots ? [Capability.Screenshot] : []; const capWhitelist = enableScreenshots ? [Capability.Screenshot] : [];
@ -429,7 +440,7 @@ export default class WidgetUtils {
return capWhitelist; return capWhitelist;
} }
static getWidgetSecurityKey(widgetId, widgetUrl, isUserWidget) { static getWidgetSecurityKey(widgetId: string, widgetUrl: string, isUserWidget: boolean): string {
let widgetLocation = ActiveWidgetStore.getRoomId(widgetId); let widgetLocation = ActiveWidgetStore.getRoomId(widgetId);
if (isUserWidget) { if (isUserWidget) {
@ -450,7 +461,7 @@ export default class WidgetUtils {
return encodeURIComponent(`${widgetLocation}::${widgetUrl}`); 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 // NB. we can't just encodeURIComponent all of these because the $ signs need to be there
const queryStringParts = [ const queryStringParts = [
'conferenceDomain=$domain', 'conferenceDomain=$domain',
@ -466,7 +477,7 @@ export default class WidgetUtils {
} }
const queryString = queryStringParts.join('&'); const queryString = queryStringParts.join('&');
let baseUrl = window.location; let baseUrl = window.location.href;
if (window.location.protocol !== "https:" && !opts.forLocalRender) { if (window.location.protocol !== "https:" && !opts.forLocalRender) {
// Use an external wrapper if we're not locally rendering the widget. This is usually // 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 // 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; return url.href;
} }
static getWidgetName(app) { static getWidgetName(app?: IApp): string {
return app?.name?.trim() || _t("Unknown App"); return app?.name?.trim() || _t("Unknown App");
} }
static getWidgetDataTitle(app) { static getWidgetDataTitle(app?: IApp): string {
return app?.data?.title?.trim() || ""; return app?.data?.title?.trim() || "";
} }
static editWidget(room, app) { static editWidget(room: Room, app: IApp): void {
// TODO: Open the right manager for the widget // TODO: Open the right manager for the widget
if (SettingsStore.getValue("feature_many_integration_managers")) { if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); 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"); console.log("Requesting widget snapshot");
ActiveWidgetStore.getWidgetMessaging(app.id).getScreenshot().catch((err) => { ActiveWidgetStore.getWidgetMessaging(app.id).getScreenshot().catch((err) => {
console.error("Failed to get screenshot", err); console.error("Failed to get screenshot", err);