Merge pull request #4379 from matrix-org/travis/moar-jitsi
Support m.jitsi-typed widgets as Jitsi widgets
This commit is contained in:
commit
37bd0f3508
7 changed files with 72 additions and 18 deletions
|
@ -66,6 +66,7 @@ import WidgetEchoStore from './stores/WidgetEchoStore';
|
||||||
import SettingsStore, { SettingLevel } from './settings/SettingsStore';
|
import SettingsStore, { SettingLevel } from './settings/SettingsStore';
|
||||||
import {generateHumanReadableId} from "./utils/NamingUtils";
|
import {generateHumanReadableId} from "./utils/NamingUtils";
|
||||||
import {Jitsi} from "./widgets/Jitsi";
|
import {Jitsi} from "./widgets/Jitsi";
|
||||||
|
import {WidgetType} from "./widgets/WidgetType";
|
||||||
|
|
||||||
global.mxCalls = {
|
global.mxCalls = {
|
||||||
//room_id: MatrixCall
|
//room_id: MatrixCall
|
||||||
|
@ -401,9 +402,9 @@ async function _startCallApp(roomId, type) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
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");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
||||||
Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, {
|
Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, {
|
||||||
|
@ -413,9 +414,6 @@ async function _startCallApp(roomId, type) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentJitsiWidgets = currentRoomWidgets.filter((ev) => {
|
|
||||||
return ev.getContent().type === 'jitsi';
|
|
||||||
});
|
|
||||||
if (currentJitsiWidgets.length > 0) {
|
if (currentJitsiWidgets.length > 0) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Refusing to start conference call widget in " + roomId +
|
"Refusing to start conference call widget in " + roomId +
|
||||||
|
@ -454,7 +452,7 @@ async function _startCallApp(roomId, type) {
|
||||||
Date.now()
|
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');
|
console.log('Jitsi widget added');
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
if (e.errcode === 'M_FORBIDDEN') {
|
if (e.errcode === 'M_FORBIDDEN') {
|
||||||
|
|
|
@ -241,6 +241,7 @@ import WidgetUtils from './utils/WidgetUtils';
|
||||||
import RoomViewStore from './stores/RoomViewStore';
|
import RoomViewStore from './stores/RoomViewStore';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||||
|
import {WidgetType} from "./widgets/WidgetType";
|
||||||
|
|
||||||
function sendResponse(event, res) {
|
function sendResponse(event, res) {
|
||||||
const data = JSON.parse(JSON.stringify(event.data));
|
const data = JSON.parse(JSON.stringify(event.data));
|
||||||
|
@ -292,7 +293,7 @@ function inviteUser(event, roomId, userId) {
|
||||||
|
|
||||||
function setWidget(event, roomId) {
|
function setWidget(event, roomId) {
|
||||||
const widgetId = event.data.widget_id;
|
const widgetId = event.data.widget_id;
|
||||||
const widgetType = event.data.type;
|
let widgetType = event.data.type;
|
||||||
const widgetUrl = event.data.url;
|
const widgetUrl = event.data.url;
|
||||||
const widgetName = event.data.name; // optional
|
const widgetName = event.data.name; // optional
|
||||||
const widgetData = event.data.data; // 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) {
|
if (userWidget) {
|
||||||
WidgetUtils.setUserWidget(widgetId, widgetType, widgetUrl, widgetName, widgetData).then(() => {
|
WidgetUtils.setUserWidget(widgetId, widgetType, widgetUrl, widgetName, widgetData).then(() => {
|
||||||
sendResponse(event, {
|
sendResponse(event, {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { abbreviateUrl } from './utils/UrlUtils';
|
||||||
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
|
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
|
||||||
import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks";
|
import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks";
|
||||||
import {inviteUsersToRoom} from "./RoomInvite";
|
import {inviteUsersToRoom} from "./RoomInvite";
|
||||||
|
import { WidgetType } from "./widgets/WidgetType";
|
||||||
import sendBugReport from "./rageshake/submit-rageshake";
|
import sendBugReport from "./rageshake/submit-rageshake";
|
||||||
import SdkConfig from "./SdkConfig";
|
import SdkConfig from "./SdkConfig";
|
||||||
|
|
||||||
|
@ -779,7 +780,7 @@ export const Commands = [
|
||||||
const nowMs = (new Date()).getTime();
|
const nowMs = (new Date()).getTime();
|
||||||
const widgetId = encodeURIComponent(`${roomId}_${userId}_${nowMs}`);
|
const widgetId = encodeURIComponent(`${roomId}_${userId}_${nowMs}`);
|
||||||
return success(WidgetUtils.setRoomWidget(
|
return success(WidgetUtils.setRoomWidget(
|
||||||
roomId, widgetId, "m.custom", args, "Custom Widget", {}));
|
roomId, widgetId, WidgetType.CUSTOM, args, "Custom Widget", {}));
|
||||||
} else {
|
} else {
|
||||||
return reject(_t("You cannot modify widgets in this room."));
|
return reject(_t("You cannot modify widgets in this room."));
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
import {aboveLeftOf, ContextMenu, ContextMenuButton} from "../../structures/ContextMenu";
|
import {aboveLeftOf, ContextMenu, ContextMenuButton} from "../../structures/ContextMenu";
|
||||||
import PersistedElement from "./PersistedElement";
|
import PersistedElement from "./PersistedElement";
|
||||||
|
import {WidgetType} from "../../../widgets/WidgetType";
|
||||||
|
|
||||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||||
const ENABLE_REACT_PERF = false;
|
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
|
// We only tell Jitsi widgets that we're ready because they're realistically the only ones
|
||||||
// using this custom extension to the widget API.
|
// using this custom extension to the widget API.
|
||||||
if (this.props.app.type === 'jitsi') {
|
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||||
widgetMessaging.flagReadyToContinue();
|
widgetMessaging.flagReadyToContinue();
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -597,7 +598,7 @@ export default class AppTile extends React.Component {
|
||||||
_getRenderedUrl() {
|
_getRenderedUrl() {
|
||||||
let url;
|
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");
|
console.log("Replacing Jitsi widget URL with local wrapper");
|
||||||
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
|
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
|
||||||
url = this._addWurlParams(url);
|
url = this._addWurlParams(url);
|
||||||
|
@ -608,7 +609,7 @@ export default class AppTile extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPopoutUrl() {
|
_getPopoutUrl() {
|
||||||
if (this.props.app.type === 'jitsi') {
|
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||||
return this._templatedUrl(
|
return this._templatedUrl(
|
||||||
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
|
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
|
import {WidgetType} from "../widgets/WidgetType";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acts as a place to get & set widget state, storing local echo state and
|
* Acts as a place to get & set widget state, storing local echo state and
|
||||||
|
@ -64,7 +65,7 @@ class WidgetEchoStore extends EventEmitter {
|
||||||
return echoedWidgets;
|
return echoedWidgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type) {
|
roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type: WidgetType) {
|
||||||
const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]);
|
const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]);
|
||||||
|
|
||||||
// any widget IDs that are already in the room are not pending, so
|
// 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;
|
return Object.keys(roomEchoState).length > 0;
|
||||||
} else {
|
} else {
|
||||||
return Object.values(roomEchoState).some((widget) => {
|
return Object.values(roomEchoState).some((widget) => {
|
||||||
return widget.type === type;
|
return type.matches(widget.type);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ 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";
|
||||||
import {Capability} from "../widgets/WidgetApi";
|
import {Capability} from "../widgets/WidgetApi";
|
||||||
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
import {WidgetType} from "../widgets/WidgetType";
|
||||||
|
|
||||||
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
|
||||||
|
@ -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;
|
let content;
|
||||||
|
|
||||||
const addingWidget = Boolean(widgetUrl);
|
const addingWidget = Boolean(widgetUrl);
|
||||||
|
|
||||||
if (addingWidget) {
|
if (addingWidget) {
|
||||||
content = {
|
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,
|
url: widgetUrl,
|
||||||
name: widgetName,
|
name: widgetName,
|
||||||
data: widgetData,
|
data: widgetData,
|
||||||
|
@ -281,10 +285,10 @@ export default class WidgetUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get room specific widgets
|
* 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
|
* @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)
|
// 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');
|
const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||||
if (!appsStateEvents) {
|
if (!appsStateEvents) {
|
||||||
|
@ -338,6 +342,14 @@ export default class WidgetUtils {
|
||||||
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) {
|
||||||
|
const widgets = WidgetUtils.getRoomWidgets(room);
|
||||||
|
return (widgets || []).filter(w => {
|
||||||
|
const content = w.getContent();
|
||||||
|
return content.url && type.matches(content.type);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static removeIntegrationManagerWidgets() {
|
static removeIntegrationManagerWidgets() {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
@ -405,7 +417,7 @@ export default class WidgetUtils {
|
||||||
// Obviously anyone that can add a widget can claim it's a jitsi widget,
|
// 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
|
// 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.
|
// widgets from at all, but it probably makes sense for sanity.
|
||||||
if (appType === 'jitsi') {
|
if (WidgetType.JITSI.matches(appType)) {
|
||||||
capWhitelist.push(Capability.AlwaysOnScreen);
|
capWhitelist.push(Capability.AlwaysOnScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
37
src/widgets/WidgetType.ts
Normal file
37
src/widgets/WidgetType.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue