Merge branch 'travis/integs/base' into travis/integs/widgets

This commit is contained in:
Travis Ralston 2019-08-09 17:20:51 -06:00
commit 4c82d16e34
13 changed files with 275 additions and 151 deletions

View file

@ -63,7 +63,7 @@ import SdkConfig from './SdkConfig';
import { showUnknownDeviceDialogForCalls } from './cryptodevices'; import { showUnknownDeviceDialogForCalls } from './cryptodevices';
import WidgetUtils from './utils/WidgetUtils'; import WidgetUtils from './utils/WidgetUtils';
import WidgetEchoStore from './stores/WidgetEchoStore'; import WidgetEchoStore from './stores/WidgetEchoStore';
import ScalarAuthClient from './ScalarAuthClient'; import {IntegrationManagers} from "./integrations/IntegrationManagers";
global.mxCalls = { global.mxCalls = {
//room_id: MatrixCall //room_id: MatrixCall
@ -348,14 +348,20 @@ async function _startCallApp(roomId, type) {
// the state event in anyway, but the resulting widget would then not // the state event in anyway, but the resulting widget would then not
// work for us. Better that the user knows before everyone else in the // work for us. Better that the user knows before everyone else in the
// room sees it. // room sees it.
const scalarClient = new ScalarAuthClient(); const managers = IntegrationManagers.sharedInstance();
let haveScalar = false; let haveScalar = true;
try { if (managers.hasManager()) {
await scalarClient.connect(); try {
haveScalar = scalarClient.hasCredentials(); const scalarClient = managers.getPrimaryManager().getScalarClient();
} catch (e) { await scalarClient.connect();
// fall through haveScalar = scalarClient.hasCredentials();
} catch (e) {
// ignore
}
} else {
haveScalar = false;
} }
if (!haveScalar) { if (!haveScalar) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -421,7 +427,8 @@ async function _startCallApp(roomId, type) {
// URL, but this will at least allow the integration manager to not be hardcoded. // URL, but this will at least allow the integration manager to not be hardcoded.
widgetUrl = SdkConfig.get().integrations_jitsi_widget_url + '?' + queryString; widgetUrl = SdkConfig.get().integrations_jitsi_widget_url + '?' + queryString;
} else { } else {
widgetUrl = SdkConfig.get().integrations_rest_url + '/widgets/jitsi.html?' + queryString; const apiUrl = IntegrationManagers.sharedInstance().getPrimaryManager().apiUrl;
widgetUrl = apiUrl + '/widgets/jitsi.html?' + queryString;
} }
const widgetData = { widgetSessionId }; const widgetData = { widgetSessionId };

View file

@ -22,7 +22,7 @@ import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
import ActiveWidgetStore from './stores/ActiveWidgetStore'; import ActiveWidgetStore from './stores/ActiveWidgetStore';
import MatrixClientPeg from "./MatrixClientPeg"; import MatrixClientPeg from "./MatrixClientPeg";
import RoomViewStore from "./stores/RoomViewStore"; import RoomViewStore from "./stores/RoomViewStore";
import { showIntegrationsManager } from './integrations/integrations'; import {IntegrationManagers} from "./integrations/IntegrationManagers";
const WIDGET_API_VERSION = '0.0.2'; // Current API version const WIDGET_API_VERSION = '0.0.2'; // Current API version
const SUPPORTED_WIDGET_API_VERSIONS = [ const SUPPORTED_WIDGET_API_VERSIONS = [
@ -193,11 +193,12 @@ export default class FromWidgetPostMessageApi {
const integType = (data && data.integType) ? data.integType : null; const integType = (data && data.integType) ? data.integType : null;
const integId = (data && data.integId) ? data.integId : null; const integId = (data && data.integId) ? data.integId : null;
showIntegrationsManager({ // TODO: Open the right integration manager for the widget
room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), IntegrationManagers.sharedInstance().getPrimaryManager().open(
screen: 'type_' + integType, MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()),
integrationId: integId, `type_${integType}`,
}); integId,
);
} else if (action === 'set_always_on_screen') { } else if (action === 'set_always_on_screen') {
// This is a new message: there is no reason to support the deprecated widgetData here // This is a new message: there is no reason to support the deprecated widgetData here
const data = event.data.data; const data = event.data.data;

View file

@ -29,20 +29,43 @@ import * as Matrix from 'matrix-js-sdk';
// The version of the integration manager API we're intending to work with // The version of the integration manager API we're intending to work with
const imApiVersion = "1.1"; const imApiVersion = "1.1";
class ScalarAuthClient { export default class ScalarAuthClient {
constructor() { constructor(apiUrl, uiUrl) {
this.apiUrl = apiUrl;
this.uiUrl = uiUrl;
this.scalarToken = null; this.scalarToken = null;
// `undefined` to allow `startTermsFlow` to fallback to a default // `undefined` to allow `startTermsFlow` to fallback to a default
// callback if this is unset. // callback if this is unset.
this.termsInteractionCallback = undefined; this.termsInteractionCallback = undefined;
// We try and store the token on a per-manager basis, but need a fallback
// for the default manager.
const configApiUrl = SdkConfig.get()['integrations_rest_url'];
const configUiUrl = SdkConfig.get()['integrations_ui_url'];
this.isDefaultManager = apiUrl === configApiUrl && configUiUrl === uiUrl;
} }
/** _writeTokenToStore() {
* Determines if setting up a ScalarAuthClient is even possible window.localStorage.setItem("mx_scalar_token_at_" + this.apiUrl, this.scalarToken);
* @returns {boolean} true if possible, false otherwise. if (this.isDefaultManager) {
*/ // We remove the old token from storage to migrate upwards. This is safe
static isPossible() { // to do because even if the user switches to /app when this is on /develop
return SdkConfig.get()['integrations_rest_url'] && SdkConfig.get()['integrations_ui_url']; // they'll at worst register for a new token.
window.localStorage.removeItem("mx_scalar_token"); // no-op when not present
}
}
_readTokenFromStore() {
let token = window.localStorage.getItem("mx_scalar_token_at_" + this.apiUrl);
if (!token && this.isDefaultManager) {
token = window.localStorage.getItem("mx_scalar_token");
}
return token;
}
_readToken() {
if (this.scalarToken) return this.scalarToken;
return this._readTokenFromStore();
} }
setTermsInteractionCallback(callback) { setTermsInteractionCallback(callback) {
@ -61,8 +84,7 @@ class ScalarAuthClient {
// Returns a promise that resolves to a scalar_token string // Returns a promise that resolves to a scalar_token string
getScalarToken() { getScalarToken() {
let token = this.scalarToken; const token = this._readToken();
if (!token) token = window.localStorage.getItem("mx_scalar_token");
if (!token) { if (!token) {
return this.registerForToken(); return this.registerForToken();
@ -78,7 +100,7 @@ class ScalarAuthClient {
} }
_getAccountName(token) { _getAccountName(token) {
const url = SdkConfig.get().integrations_rest_url + "/account"; const url = this.apiUrl + "/account";
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
request({ request({
@ -111,7 +133,7 @@ class ScalarAuthClient {
return token; return token;
}).catch((e) => { }).catch((e) => {
if (e instanceof TermsNotSignedError) { if (e instanceof TermsNotSignedError) {
console.log("Integrations manager requires new terms to be agreed to"); console.log("Integration manager requires new terms to be agreed to");
// The terms endpoints are new and so live on standard _matrix prefixes, // The terms endpoints are new and so live on standard _matrix prefixes,
// but IM rest urls are currently configured with paths, so remove the // but IM rest urls are currently configured with paths, so remove the
// path from the base URL before passing it to the js-sdk // path from the base URL before passing it to the js-sdk
@ -126,7 +148,7 @@ class ScalarAuthClient {
// Once we've fully transitioned to _matrix URLs, we can give people // Once we've fully transitioned to _matrix URLs, we can give people
// a grace period to update their configs, then use the rest url as // a grace period to update their configs, then use the rest url as
// a regular base url. // a regular base url.
const parsedImRestUrl = url.parse(SdkConfig.get().integrations_rest_url); const parsedImRestUrl = url.parse(this.apiUrl);
parsedImRestUrl.path = ''; parsedImRestUrl.path = '';
parsedImRestUrl.pathname = ''; parsedImRestUrl.pathname = '';
return startTermsFlow([new Service( return startTermsFlow([new Service(
@ -147,17 +169,18 @@ class ScalarAuthClient {
return MatrixClientPeg.get().getOpenIdToken().then((tokenObject) => { return MatrixClientPeg.get().getOpenIdToken().then((tokenObject) => {
// Now we can send that to scalar and exchange it for a scalar token // Now we can send that to scalar and exchange it for a scalar token
return this.exchangeForScalarToken(tokenObject); return this.exchangeForScalarToken(tokenObject);
}).then((tokenObject) => { }).then((token) => {
// Validate it (this mostly checks to see if the IM needs us to agree to some terms) // Validate it (this mostly checks to see if the IM needs us to agree to some terms)
return this._checkToken(tokenObject); return this._checkToken(token);
}).then((tokenObject) => { }).then((token) => {
window.localStorage.setItem("mx_scalar_token", tokenObject); this.scalarToken = token;
return tokenObject; this._writeTokenToStore();
return token;
}); });
} }
exchangeForScalarToken(openidTokenObject) { exchangeForScalarToken(openidTokenObject) {
const scalarRestUrl = SdkConfig.get().integrations_rest_url; const scalarRestUrl = this.apiUrl;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
request({ request({
@ -181,7 +204,7 @@ class ScalarAuthClient {
} }
getScalarPageTitle(url) { getScalarPageTitle(url) {
let scalarPageLookupUrl = SdkConfig.get().integrations_rest_url + '/widgets/title_lookup'; let scalarPageLookupUrl = this.apiUrl + '/widgets/title_lookup';
scalarPageLookupUrl = this.getStarterLink(scalarPageLookupUrl); scalarPageLookupUrl = this.getStarterLink(scalarPageLookupUrl);
scalarPageLookupUrl += '&curl=' + encodeURIComponent(url); scalarPageLookupUrl += '&curl=' + encodeURIComponent(url);
@ -217,7 +240,7 @@ class ScalarAuthClient {
* @return {Promise} Resolves on completion * @return {Promise} Resolves on completion
*/ */
disableWidgetAssets(widgetType, widgetId) { disableWidgetAssets(widgetType, widgetId) {
let url = SdkConfig.get().integrations_rest_url + '/widgets/set_assets_state'; let url = this.apiUrl + '/widgets/set_assets_state';
url = this.getStarterLink(url); url = this.getStarterLink(url);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request({ request({
@ -246,7 +269,7 @@ class ScalarAuthClient {
getScalarInterfaceUrlForRoom(room, screen, id) { getScalarInterfaceUrlForRoom(room, screen, id) {
const roomId = room.roomId; const roomId = room.roomId;
const roomName = room.name; const roomName = room.name;
let url = SdkConfig.get().integrations_ui_url; let url = this.uiUrl;
url += "?scalar_token=" + encodeURIComponent(this.scalarToken); url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
url += "&room_id=" + encodeURIComponent(roomId); url += "&room_id=" + encodeURIComponent(roomId);
url += "&room_name=" + encodeURIComponent(roomName); url += "&room_name=" + encodeURIComponent(roomName);
@ -264,5 +287,3 @@ class ScalarAuthClient {
return starterLinkUrl + "?scalar_token=" + encodeURIComponent(this.scalarToken); return starterLinkUrl + "?scalar_token=" + encodeURIComponent(this.scalarToken);
} }
} }
module.exports = ScalarAuthClient;

View file

@ -232,13 +232,13 @@ Example:
} }
*/ */
import SdkConfig from './SdkConfig';
import MatrixClientPeg from './MatrixClientPeg'; import MatrixClientPeg from './MatrixClientPeg';
import { MatrixEvent } from 'matrix-js-sdk'; import { MatrixEvent } from 'matrix-js-sdk';
import dis from './dispatcher'; import dis from './dispatcher';
import WidgetUtils from './utils/WidgetUtils'; 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";
function sendResponse(event, res) { function sendResponse(event, res) {
const data = JSON.parse(JSON.stringify(event.data)); const data = JSON.parse(JSON.stringify(event.data));
@ -548,7 +548,8 @@ const onMessage = function(event) {
// (See https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) // (See https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
let configUrl; let configUrl;
try { try {
configUrl = new URL(SdkConfig.get().integrations_ui_url); // TODO: Support multiple integration managers
configUrl = new URL(IntegrationManagers.sharedInstance().getPrimaryManager().uiUrl);
} catch (e) { } catch (e) {
// No integrations UI URL, ignore silently. // No integrations UI URL, ignore silently.
return; return;

View file

@ -22,7 +22,6 @@ import qs from 'querystring';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import ScalarAuthClient from '../../../ScalarAuthClient';
import WidgetMessaging from '../../../WidgetMessaging'; import WidgetMessaging from '../../../WidgetMessaging';
import AccessibleButton from './AccessibleButton'; import AccessibleButton from './AccessibleButton';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
@ -35,7 +34,7 @@ import WidgetUtils from '../../../utils/WidgetUtils';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import classNames from 'classnames'; import classNames from 'classnames';
import { showIntegrationsManager } from '../../../integrations/integrations'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false; const ENABLE_REACT_PERF = false;
@ -178,9 +177,22 @@ export default class AppTile extends React.Component {
return; return;
} }
const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
console.warn("No integration manager - not setting scalar token", url);
this.setState({
error: null,
widgetUrl: this._addWurlParams(this.props.url),
initialising: false,
});
return;
}
// TODO: Pick the right manager for the widget
// Fetch the token before loading the iframe as we need it to mangle the URL // Fetch the token before loading the iframe as we need it to mangle the URL
if (!this._scalarClient) { if (!this._scalarClient) {
this._scalarClient = new ScalarAuthClient(); this._scalarClient = managers.getPrimaryManager().getScalarClient();
} }
this._scalarClient.getScalarToken().done((token) => { this._scalarClient.getScalarToken().done((token) => {
// Append scalar_token as a query param if not already present // Append scalar_token as a query param if not already present
@ -189,7 +201,7 @@ export default class AppTile extends React.Component {
const params = qs.parse(u.query); const params = qs.parse(u.query);
if (!params.scalar_token) { if (!params.scalar_token) {
params.scalar_token = encodeURIComponent(token); params.scalar_token = encodeURIComponent(token);
// u.search must be set to undefined, so that u.format() uses query paramerters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options // u.search must be set to undefined, so that u.format() uses query parameters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options
u.search = undefined; u.search = undefined;
u.query = params; u.query = params;
} }
@ -251,11 +263,12 @@ export default class AppTile extends React.Component {
if (this.props.onEditClick) { if (this.props.onEditClick) {
this.props.onEditClick(); this.props.onEditClick();
} else { } else {
showIntegrationsManager({ // TODO: Open the right manager for the widget
room: this.props.room, IntegrationManagers.sharedInstance().getPrimaryManager().open(
screen: 'type_' + this.props.type, this.props.room,
integrationId: this.props.id, this.props.type,
}); this.props.id,
);
} }
} }

View file

@ -18,9 +18,8 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import sdk from '../../../index'; import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { showIntegrationsManager } from '../../../integrations/integrations'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
export default class ManageIntegsButton extends React.Component { export default class ManageIntegsButton extends React.Component {
constructor(props) { constructor(props) {
@ -30,12 +29,17 @@ export default class ManageIntegsButton extends React.Component {
onManageIntegrations = (ev) => { onManageIntegrations = (ev) => {
ev.preventDefault(); ev.preventDefault();
showIntegrationsManager({ room: this.props.room }); const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
managers.openNoManagerDialog();
} else {
managers.getPrimaryManager().open(this.props.room);
}
}; };
render() { render() {
let integrationsButton = <div />; let integrationsButton = <div />;
if (ScalarAuthClient.isPossible()) { if (IntegrationManagers.sharedInstance().hasManager()) {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
integrationsButton = ( integrationsButton = (
<AccessibleButton <AccessibleButton

View file

@ -25,7 +25,6 @@ import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils'; import * as HtmlUtils from '../../../HtmlUtils';
import {formatDate} from '../../../DateUtils'; import {formatDate} from '../../../DateUtils';
import sdk from '../../../index'; import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
@ -35,6 +34,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import ReplyThread from "../elements/ReplyThread"; import ReplyThread from "../elements/ReplyThread";
import {host as matrixtoHost} from '../../../matrix-to'; import {host as matrixtoHost} from '../../../matrix-to';
import {pillifyLinks} from '../../../utils/pillify'; import {pillifyLinks} from '../../../utils/pillify';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'TextualBody', displayName: 'TextualBody',
@ -318,12 +318,19 @@ module.exports = React.createClass({
// which requires the user to click through and THEN we can open the link in a new tab because // which requires the user to click through and THEN we can open the link in a new tab because
// the window.open command occurs in the same stack frame as the onClick callback. // the window.open command occurs in the same stack frame as the onClick callback.
const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
managers.openNoManagerDialog();
return;
}
// Go fetch a scalar token // Go fetch a scalar token
const scalarClient = new ScalarAuthClient(); const integrationManager = managers.getPrimaryManager();
const scalarClient = integrationManager.getScalarClient();
scalarClient.connect().then(() => { scalarClient.connect().then(() => {
const completeUrl = scalarClient.getStarterLink(starterLink); const completeUrl = scalarClient.getStarterLink(starterLink);
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const integrationsUrl = SdkConfig.get().integrations_ui_url; const integrationsUrl = integrationManager.uiUrl;
Modal.createTrackedDialog('Add an integration', '', QuestionDialog, { Modal.createTrackedDialog('Add an integration', '', QuestionDialog, {
title: _t("Add an Integration"), title: _t("Add an Integration"),
description: description:

View file

@ -29,7 +29,7 @@ import { _t } from '../../../languageHandler';
import WidgetUtils from '../../../utils/WidgetUtils'; import WidgetUtils from '../../../utils/WidgetUtils';
import WidgetEchoStore from "../../../stores/WidgetEchoStore"; import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import { showIntegrationsManager } from '../../../integrations/integrations'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
// The maximum number of widgets that can be added in a room // The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2; const MAX_WIDGETS = 2;
@ -128,10 +128,7 @@ module.exports = React.createClass({
}, },
_launchManageIntegrations: function() { _launchManageIntegrations: function() {
showIntegrationsManager({ IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ');
room: this.props.room,
screen: 'add_integ',
});
}, },
onClickAddWidget: function(e) { onClickAddWidget: function(e) {

View file

@ -18,13 +18,12 @@ import {_t, _td} from '../../../languageHandler';
import AppTile from '../elements/AppTile'; import AppTile from '../elements/AppTile';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index'; import sdk from '../../../index';
import ScalarAuthClient from '../../../ScalarAuthClient';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import WidgetUtils from '../../../utils/WidgetUtils'; import WidgetUtils from '../../../utils/WidgetUtils';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import PersistedElement from "../elements/PersistedElement"; import PersistedElement from "../elements/PersistedElement";
import { showIntegrationsManager } from '../../../integrations/integrations'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
const widgetType = 'm.stickerpicker'; const widgetType = 'm.stickerpicker';
@ -67,8 +66,9 @@ export default class Stickerpicker extends React.Component {
_acquireScalarClient() { _acquireScalarClient() {
if (this.scalarClient) return Promise.resolve(this.scalarClient); if (this.scalarClient) return Promise.resolve(this.scalarClient);
if (ScalarAuthClient.isPossible()) { // TODO: Pick the right manager for the widget
this.scalarClient = new ScalarAuthClient(); if (IntegrationManagers.sharedInstance().hasManager()) {
this.scalarClient = IntegrationManagers.sharedInstance().getPrimaryManager().getScalarClient();
return this.scalarClient.connect().then(() => { return this.scalarClient.connect().then(() => {
this.forceUpdate(); this.forceUpdate();
return this.scalarClient; return this.scalarClient;
@ -348,11 +348,12 @@ export default class Stickerpicker extends React.Component {
* Launch the integrations manager on the stickers integration page * Launch the integrations manager on the stickers integration page
*/ */
_launchManageIntegrations() { _launchManageIntegrations() {
showIntegrationsManager({ // TODO: Open the right integration manager for the widget
room: this.props.room, IntegrationManagers.sharedInstance().getPrimaryManager().open(
screen: `type_${widgetType}`, this.props.room,
integrationId: this.state.widgetId, `type_${widgetType}`,
}); this.state.widgetId,
);
} }
render() { render() {

View file

@ -0,0 +1,81 @@
/*
Copyright 2019 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 ScalarAuthClient from "../ScalarAuthClient";
import sdk from "../index";
import {dialogTermsInteractionCallback, TermsNotSignedError} from "../Terms";
import type {Room} from "matrix-js-sdk";
import Modal from '../Modal';
export class IntegrationManagerInstance {
apiUrl: string;
uiUrl: string;
constructor(apiUrl: string, uiUrl: string) {
this.apiUrl = apiUrl;
this.uiUrl = uiUrl;
// Per the spec: UI URL is optional.
if (!this.uiUrl) this.uiUrl = this.apiUrl;
}
getScalarClient(): ScalarAuthClient {
return new ScalarAuthClient(this.apiUrl, this.uiUrl);
}
async open(room: Room = null, screen: string = null, integrationId: string = null): void {
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const dialog = Modal.createTrackedDialog(
'Integration Manager', '', IntegrationsManager,
{loading: true}, 'mx_IntegrationsManager',
);
const client = this.getScalarClient();
client.setTermsInteractionCallback((policyInfo, agreedUrls) => {
// To avoid visual glitching of two modals stacking briefly, we customise the
// terms dialog sizing when it will appear for the integrations manager so that
// it gets the same basic size as the IM's own modal.
return dialogTermsInteractionCallback(
policyInfo, agreedUrls, 'mx_TermsDialog_forIntegrationsManager',
);
});
const newProps = {};
try {
await client.connect();
if (!client.hasCredentials()) {
newProps["connected"] = false;
} else {
newProps["url"] = client.getScalarInterfaceUrlForRoom(room, screen, integrationId);
}
} catch (e) {
if (e instanceof TermsNotSignedError) {
dialog.close();
return;
}
console.error(e);
newProps["connected"] = false;
}
// Close the old dialog and open a new one
dialog.close();
Modal.createTrackedDialog(
'Integration Manager', '', IntegrationsManager,
newProps, 'mx_IntegrationsManager',
);
}
}

View file

@ -0,0 +1,68 @@
/*
Copyright 2019 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 SdkConfig from '../SdkConfig';
import sdk from "../index";
import Modal from '../Modal';
import {IntegrationManagerInstance} from "./IntegrationManagerInstance";
export class IntegrationManagers {
static _instance;
static sharedInstance(): IntegrationManagers {
if (!IntegrationManagers._instance) {
IntegrationManagers._instance = new IntegrationManagers();
}
return IntegrationManagers._instance;
}
_managers: IntegrationManagerInstance[] = [];
constructor() {
this._setupConfiguredManager();
}
_setupConfiguredManager() {
const apiUrl = SdkConfig.get()['integrations_rest_url'];
const uiUrl = SdkConfig.get()['integrations_ui_url'];
if (apiUrl && uiUrl) {
this._managers.push(new IntegrationManagerInstance(apiUrl, uiUrl));
}
}
hasManager(): boolean {
return this._managers.length > 0;
}
getPrimaryManager(): IntegrationManagerInstance {
if (this.hasManager()) {
// TODO: TravisR - Handle custom integration managers (widgets)
return this._managers[0];
} else {
return null;
}
}
openNoManagerDialog(): void {
// TODO: Is it Integrations (plural) or Integration (singular). Singular is easier spoken.
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
Modal.createTrackedDialog(
"Integration Manager", "None", IntegrationsManager,
{configured: false}, 'mx_IntegrationsManager',
);
}
}

View file

@ -1,79 +0,0 @@
/*
Copyright 2019 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 sdk from "../index";
import ScalarAuthClient from '../ScalarAuthClient';
import Modal from '../Modal';
import { TermsNotSignedError, dialogTermsInteractionCallback } from '../Terms';
export async function showIntegrationsManager(opts) {
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
let props = {};
if (ScalarAuthClient.isPossible()) {
props.loading = true;
} else {
props.configured = false;
}
const close = Modal.createTrackedDialog(
'Integrations Manager', '', IntegrationsManager, props, "mx_IntegrationsManager",
).close;
if (!ScalarAuthClient.isPossible()) {
return;
}
const scalarClient = new ScalarAuthClient();
scalarClient.setTermsInteractionCallback(integrationsTermsInteractionCallback);
try {
await scalarClient.connect();
if (!scalarClient.hasCredentials()) {
props = { connected: false };
} else {
props = {
url: scalarClient.getScalarInterfaceUrlForRoom(
opts.room,
opts.screen,
opts.integrationId,
),
};
}
} catch (err) {
if (err instanceof TermsNotSignedError) {
// user canceled terms dialog, so just cancel the action
close();
return;
}
console.error(err);
props = { connected: false };
}
close();
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, props, "mx_IntegrationsManager");
}
/*
* To avoid visual glitching of two modals stacking briefly, we customise the
* terms dialog sizing when it will appear for the integrations manager so that
* it gets the same basic size as the IM's own modal.
*/
function integrationsTermsInteractionCallback(policiesAndServicePairs, agreedUrls) {
return dialogTermsInteractionCallback(
policiesAndServicePairs,
agreedUrls,
"mx_TermsDialog_forIntegrationsManager",
);
}

View file

@ -27,6 +27,7 @@ import WidgetEchoStore from '../stores/WidgetEchoStore';
const WIDGET_WAIT_TIME = 20000; 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";
/** /**
* Encodes a URI according to a set of template variables. Variables will be * Encodes a URI according to a set of template variables. Variables will be
@ -102,7 +103,8 @@ export default class WidgetUtils {
let scalarUrls = SdkConfig.get().integrations_widgets_urls; let scalarUrls = SdkConfig.get().integrations_widgets_urls;
if (!scalarUrls || scalarUrls.length === 0) { if (!scalarUrls || scalarUrls.length === 0) {
scalarUrls = [SdkConfig.get().integrations_rest_url]; const defaultManager = IntegrationManagers.sharedInstance().getPrimaryManager();
if (defaultManager) scalarUrls = [defaultManager.apiUrl];
} }
for (let i = 0; i < scalarUrls.length; i++) { for (let i = 0; i < scalarUrls.length; i++) {