Merge pull request #4379 from matrix-org/travis/moar-jitsi

Support m.jitsi-typed widgets as Jitsi widgets
pull/21833/head
Travis Ralston 2020-04-20 09:31:23 -06:00 committed by GitHub
commit 37bd0f3508
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 18 deletions

View File

@ -66,6 +66,7 @@ import WidgetEchoStore from './stores/WidgetEchoStore';
import SettingsStore, { SettingLevel } from './settings/SettingsStore';
import {generateHumanReadableId} from "./utils/NamingUtils";
import {Jitsi} from "./widgets/Jitsi";
import {WidgetType} from "./widgets/WidgetType";
global.mxCalls = {
//room_id: MatrixCall
@ -401,9 +402,9 @@ async function _startCallApp(roomId, type) {
});
const room = MatrixClientPeg.get().getRoom(roomId);
const currentRoomWidgets = WidgetUtils.getRoomWidgets(room);
const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI);
if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, 'jitsi')) {
if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI)) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, {
@ -413,9 +414,6 @@ async function _startCallApp(roomId, type) {
return;
}
const currentJitsiWidgets = currentRoomWidgets.filter((ev) => {
return ev.getContent().type === 'jitsi';
});
if (currentJitsiWidgets.length > 0) {
console.warn(
"Refusing to start conference call widget in " + roomId +
@ -454,7 +452,7 @@ async function _startCallApp(roomId, type) {
Date.now()
);
WidgetUtils.setRoomWidget(roomId, widgetId, 'jitsi', widgetUrl, 'Jitsi', widgetData).then(() => {
WidgetUtils.setRoomWidget(roomId, widgetId, WidgetType.JITSI, widgetUrl, 'Jitsi', widgetData).then(() => {
console.log('Jitsi widget added');
}).catch((e) => {
if (e.errcode === 'M_FORBIDDEN') {

View File

@ -241,6 +241,7 @@ import WidgetUtils from './utils/WidgetUtils';
import RoomViewStore from './stores/RoomViewStore';
import { _t } from './languageHandler';
import {IntegrationManagers} from "./integrations/IntegrationManagers";
import {WidgetType} from "./widgets/WidgetType";
function sendResponse(event, res) {
const data = JSON.parse(JSON.stringify(event.data));
@ -292,7 +293,7 @@ function inviteUser(event, roomId, userId) {
function setWidget(event, roomId) {
const widgetId = event.data.widget_id;
const widgetType = event.data.type;
let widgetType = event.data.type;
const widgetUrl = event.data.url;
const widgetName = event.data.name; // optional
const widgetData = event.data.data; // optional
@ -324,6 +325,9 @@ function setWidget(event, roomId) {
}
}
// convert the widget type to a known widget type
widgetType = WidgetType.fromString(widgetType);
if (userWidget) {
WidgetUtils.setUserWidget(widgetId, widgetType, widgetUrl, widgetName, widgetData).then(() => {
sendResponse(event, {

View File

@ -35,6 +35,7 @@ import { abbreviateUrl } from './utils/UrlUtils';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks";
import {inviteUsersToRoom} from "./RoomInvite";
import { WidgetType } from "./widgets/WidgetType";
import sendBugReport from "./rageshake/submit-rageshake";
import SdkConfig from "./SdkConfig";
@ -779,7 +780,7 @@ export const Commands = [
const nowMs = (new Date()).getTime();
const widgetId = encodeURIComponent(`${roomId}_${userId}_${nowMs}`);
return success(WidgetUtils.setRoomWidget(
roomId, widgetId, "m.custom", args, "Custom Widget", {}));
roomId, widgetId, WidgetType.CUSTOM, args, "Custom Widget", {}));
} else {
return reject(_t("You cannot modify widgets in this room."));
}

View File

@ -38,6 +38,7 @@ import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import {aboveLeftOf, ContextMenu, ContextMenuButton} from "../../structures/ContextMenu";
import PersistedElement from "./PersistedElement";
import {WidgetType} from "../../../widgets/WidgetType";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false;
@ -454,7 +455,7 @@ export default class AppTile extends React.Component {
// We only tell Jitsi widgets that we're ready because they're realistically the only ones
// using this custom extension to the widget API.
if (this.props.app.type === 'jitsi') {
if (WidgetType.JITSI.matches(this.props.app.type)) {
widgetMessaging.flagReadyToContinue();
}
}).catch((err) => {
@ -597,7 +598,7 @@ export default class AppTile extends React.Component {
_getRenderedUrl() {
let url;
if (this.props.app.type === 'jitsi') {
if (WidgetType.JITSI.matches(this.props.app.type)) {
console.log("Replacing Jitsi widget URL with local wrapper");
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
url = this._addWurlParams(url);
@ -608,7 +609,7 @@ export default class AppTile extends React.Component {
}
_getPopoutUrl() {
if (this.props.app.type === 'jitsi') {
if (WidgetType.JITSI.matches(this.props.app.type)) {
return this._templatedUrl(
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
);

View File

@ -16,6 +16,7 @@ limitations under the License.
*/
import EventEmitter from 'events';
import {WidgetType} from "../widgets/WidgetType";
/**
* Acts as a place to get & set widget state, storing local echo state and
@ -64,7 +65,7 @@ class WidgetEchoStore extends EventEmitter {
return echoedWidgets;
}
roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type) {
roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type: WidgetType) {
const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]);
// any widget IDs that are already in the room are not pending, so
@ -79,7 +80,7 @@ class WidgetEchoStore extends EventEmitter {
return Object.keys(roomEchoState).length > 0;
} else {
return Object.values(roomEchoState).some((widget) => {
return widget.type === type;
return type.matches(widget.type);
});
}
}

View File

@ -29,6 +29,8 @@ import SettingsStore from "../settings/SettingsStore";
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers";
import {Capability} from "../widgets/WidgetApi";
import {Room} from "matrix-js-sdk/src/models/room";
import {WidgetType} from "../widgets/WidgetType";
export default class WidgetUtils {
/* Returns true if user is able to send state events to modify widgets in this room
@ -252,14 +254,16 @@ export default class WidgetUtils {
});
}
static setRoomWidget(roomId, widgetId, widgetType, widgetUrl, widgetName, widgetData) {
static setRoomWidget(roomId, widgetId, widgetType: WidgetType, widgetUrl, widgetName, widgetData) {
let content;
const addingWidget = Boolean(widgetUrl);
if (addingWidget) {
content = {
type: widgetType,
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
// For now we'll send the legacy event type for compatibility with older apps/riots
type: widgetType.legacy,
url: widgetUrl,
name: widgetName,
data: widgetData,
@ -281,10 +285,10 @@ export default class WidgetUtils {
/**
* Get room specific widgets
* @param {object} room The room to get widgets force
* @param {Room} room The room to get widgets force
* @return {[object]} Array containing current / active room widgets
*/
static getRoomWidgets(room) {
static getRoomWidgets(room: Room) {
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
if (!appsStateEvents) {
@ -338,6 +342,14 @@ export default class WidgetUtils {
return widgets.filter(w => w.content && w.content.type === "m.integration_manager");
}
static getRoomWidgetsOfType(room: Room, type: WidgetType) {
const widgets = WidgetUtils.getRoomWidgets(room);
return (widgets || []).filter(w => {
const content = w.getContent();
return content.url && type.matches(content.type);
});
}
static removeIntegrationManagerWidgets() {
const client = MatrixClientPeg.get();
if (!client) {
@ -405,7 +417,7 @@ export default class WidgetUtils {
// Obviously anyone that can add a widget can claim it's a jitsi widget,
// so this doesn't really offer much over the set of domains we load
// widgets from at all, but it probably makes sense for sanity.
if (appType === 'jitsi') {
if (WidgetType.JITSI.matches(appType)) {
capWhitelist.push(Capability.AlwaysOnScreen);
}

37
src/widgets/WidgetType.ts Normal file
View File

@ -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.
*/
export class WidgetType {
public static readonly JITSI = new WidgetType("m.jitsi", "jitsi");
public static readonly CUSTOM = new WidgetType("m.custom", "m.custom");
constructor(public readonly preferred: string, public readonly legacy: string) {
}
public matches(type: string): boolean {
return type === this.preferred || type === this.legacy;
}
static fromString(type: string): WidgetType {
// First try and match it against something we're already aware of
const known = Object.values(WidgetType).filter(v => v instanceof WidgetType);
const knownMatch = known.find(w => w.matches(type));
if (knownMatch) return knownMatch;
// If that fails, invent a new widget type
return new WidgetType(type, type);
}
}